From cc460e91bedb89807c9f87ad8eafc25fe671d00b Mon Sep 17 00:00:00 2001 From: AlCalzone Date: Wed, 11 Dec 2024 15:41:36 +0100 Subject: [PATCH] refactor: add portable bindings for the filesystem (#7428) --- .github/workflows/test-and-release.yml | 2 - packages/cc/src/cc/AlarmSensorCC.ts | 7 +- packages/cc/src/cc/AssociationCC.ts | 9 +- packages/cc/src/cc/AssociationGroupInfoCC.ts | 7 +- packages/cc/src/cc/BarrierOperatorCC.ts | 7 +- packages/cc/src/cc/BasicCC.ts | 13 +- packages/cc/src/cc/BatteryCC.ts | 18 +- packages/cc/src/cc/BinarySensorCC.ts | 7 +- packages/cc/src/cc/BinarySwitchCC.ts | 7 +- packages/cc/src/cc/CRC16CC.ts | 7 +- packages/cc/src/cc/CentralSceneCC.ts | 7 +- .../cc/src/cc/ClimateControlScheduleCC.ts | 7 +- packages/cc/src/cc/ClockCC.ts | 7 +- packages/cc/src/cc/ColorSwitchCC.ts | 7 +- packages/cc/src/cc/ConfigurationCC.ts | 14 +- packages/cc/src/cc/DeviceResetLocallyCC.ts | 2 +- packages/cc/src/cc/DoorLockCC.ts | 7 +- packages/cc/src/cc/DoorLockLoggingCC.ts | 7 +- packages/cc/src/cc/EnergyProductionCC.ts | 7 +- packages/cc/src/cc/EntryControlCC.ts | 7 +- .../cc/src/cc/FirmwareUpdateMetaDataCC.ts | 7 +- packages/cc/src/cc/HumidityControlModeCC.ts | 7 +- .../src/cc/HumidityControlOperatingStateCC.ts | 3 +- .../cc/src/cc/HumidityControlSetpointCC.ts | 7 +- packages/cc/src/cc/InclusionControllerCC.ts | 7 +- packages/cc/src/cc/IndicatorCC.ts | 7 +- packages/cc/src/cc/IrrigationCC.ts | 7 +- packages/cc/src/cc/LanguageCC.ts | 7 +- packages/cc/src/cc/LockCC.ts | 7 +- .../cc/src/cc/ManufacturerProprietaryCC.ts | 2 +- packages/cc/src/cc/ManufacturerSpecificCC.ts | 12 +- packages/cc/src/cc/MeterCC.ts | 13 +- .../cc/src/cc/MultiChannelAssociationCC.ts | 7 +- packages/cc/src/cc/MultiChannelCC.ts | 7 +- packages/cc/src/cc/MultiCommandCC.ts | 7 +- packages/cc/src/cc/MultilevelSensorCC.ts | 17 +- packages/cc/src/cc/MultilevelSwitchCC.ts | 7 +- packages/cc/src/cc/NodeNamingCC.ts | 7 +- packages/cc/src/cc/NotificationCC.ts | 15 +- packages/cc/src/cc/PowerlevelCC.ts | 7 +- packages/cc/src/cc/ProtectionCC.ts | 7 +- packages/cc/src/cc/SceneActivationCC.ts | 7 +- .../cc/src/cc/SceneActuatorConfigurationCC.ts | 7 +- .../src/cc/SceneControllerConfigurationCC.ts | 9 +- packages/cc/src/cc/ScheduleEntryLockCC.ts | 7 +- packages/cc/src/cc/Security2CC.ts | 7 +- packages/cc/src/cc/SecurityCC.ts | 7 +- packages/cc/src/cc/SoundSwitchCC.ts | 7 +- packages/cc/src/cc/SupervisionCC.ts | 9 +- packages/cc/src/cc/ThermostatFanModeCC.ts | 7 +- packages/cc/src/cc/ThermostatFanStateCC.ts | 3 +- packages/cc/src/cc/ThermostatModeCC.ts | 7 +- .../cc/src/cc/ThermostatOperatingStateCC.ts | 8 +- packages/cc/src/cc/ThermostatSetbackCC.ts | 7 +- packages/cc/src/cc/ThermostatSetpointCC.ts | 7 +- packages/cc/src/cc/TimeCC.ts | 7 +- packages/cc/src/cc/TimeParametersCC.ts | 9 +- packages/cc/src/cc/TransportServiceCC.ts | 7 +- packages/cc/src/cc/UserCodeCC.ts | 9 +- packages/cc/src/cc/VersionCC.ts | 7 +- packages/cc/src/cc/WakeUpCC.ts | 7 +- packages/cc/src/cc/WindowCoveringCC.ts | 7 +- packages/cc/src/cc/ZWavePlusCC.ts | 7 +- packages/cc/src/cc/ZWaveProtocolCC.ts | 2 +- .../cc/manufacturerProprietary/FibaroCC.ts | 9 +- packages/cc/src/index.ts | 1 + packages/cc/src/index_browser.ts | 1 + packages/cc/src/lib/API.ts | 38 +-- packages/cc/src/lib/CommandClass.ts | 23 +- packages/cc/src/lib/Values.ts | 3 +- packages/cc/src/lib/traits.ts | 152 ++++++++++ packages/cc/src/lib/utils.ts | 11 +- packages/config/maintenance/importConfig.ts | 11 +- .../config/maintenance/lintConfigFiles.ts | 3 + packages/config/package.json | 1 + packages/config/src/ConfigManager.test.ts | 47 ++-- packages/config/src/ConfigManager.ts | 33 ++- packages/config/src/JsonTemplate.test.ts | 49 +++- packages/config/src/JsonTemplate.ts | 23 +- packages/config/src/Manufacturers.test.ts | 14 +- packages/config/src/Manufacturers.ts | 24 +- packages/config/src/devices/DeviceConfig.ts | 68 +++-- packages/config/src/index.ts | 1 + packages/config/src/index_safe.ts | 1 + packages/config/src/traits.ts | 12 + packages/config/src/utils.ts | 39 ++- packages/core/package.json | 6 + packages/core/src/bindings/fs/node.ts | 151 ++++++++++ .../crypto/primitives/primitives.browser.ts | 2 +- .../src/crypto/primitives/primitives.node.ts | 2 +- .../src/crypto/primitives/primitives.test.ts | 2 +- .../core/src/crypto/primitives/primitives.ts | 52 ---- packages/core/src/log/shared.ts | 2 +- packages/core/src/log/shared_safe.ts | 3 + packages/core/src/traits/CommandClasses.ts | 25 ++ packages/core/src/traits/Endpoints.ts | 12 + packages/core/src/traits/FileSystem.ts | 2 + packages/core/src/traits/GetValueDB.ts | 10 + packages/core/src/traits/HostIDs.ts | 7 + packages/core/src/traits/Nodes.ts | 15 +- packages/core/src/traits/index.ts | 2 + packages/flash/package.json | 1 + packages/flash/src/cli.ts | 4 +- packages/host/package.json | 5 - packages/host/src/ZWaveHost.ts | 171 ------------ packages/host/src/ZWaveHostOptions.ts | 77 ----- packages/host/src/index.ts | 3 - packages/host/src/index_safe.ts | 5 - packages/host/src/mocks.ts | 70 +---- packages/maintenance/src/convert-json.ts | 8 +- packages/maintenance/src/generateTypedDocs.ts | 22 +- packages/maintenance/src/refactorImports.ts | 100 +++++++ .../maintenance/src/remove-unnecessary.ts | 8 +- packages/nvmedit/src/cli.ts | 24 +- packages/nvmedit/src/convert.test.ts | 19 +- packages/serial/package.json | 5 + packages/serial/src/bindings/node.ts | 60 ++++ packages/serial/src/index.ts | 1 + packages/serial/src/index_safe.ts | 1 + packages/serial/src/message/Message.ts | 10 +- .../application/ApplicationCommandRequest.ts | 3 +- .../BridgeApplicationCommandRequest.ts | 3 +- .../network-mgmt/AddNodeToNetworkRequest.ts | 2 +- .../transport/SendDataBridgeMessages.ts | 3 +- .../serialapi/transport/SendDataMessages.ts | 3 +- packages/serial/src/serialport/Bindings.ts | 24 ++ packages/shared/package.json | 8 +- packages/shared/src/bindings.ts | 137 +++++++++ packages/shared/src/docker.ts | 14 +- packages/shared/src/fs.ts | 100 ++++++- packages/testing/src/MockNode.ts | 3 +- packages/zwave-js/package.json | 2 + packages/zwave-js/src/lib/driver/Driver.ts | 262 +++++++++++------- .../zwave-js/src/lib/driver/DriverMock.ts | 8 +- packages/zwave-js/src/lib/driver/Host.ts | 11 + .../src/lib/driver/MessageGenerators.ts | 2 +- .../zwave-js/src/lib/driver/NetworkCache.ts | 18 +- .../zwave-js/src/lib/driver/UpdateConfig.ts | 97 +++---- .../zwave-js/src/lib/driver/ZWaveOptions.ts | 31 ++- packages/zwave-js/src/lib/node/Node.ts | 2 +- .../src/lib/node/mixins/60_ScheduledPoll.ts | 11 +- packages/zwave-js/src/lib/node/utils.ts | 12 +- .../src/lib/test/cc/BinarySwitchCC.test.ts | 7 +- .../src/lib/test/cc/ColorSwitchCC.test.ts | 2 +- .../zwave-js/src/lib/test/cc/MeterCC.test.ts | 3 +- .../lib/test/cc/MultilevelSwitchCC.test.ts | 7 +- .../src/lib/test/integrationTestSuite.ts | 11 +- .../src/lib/test/integrationTestSuiteMulti.ts | 11 +- packages/zwave-js/src/lib/test/mocks.ts | 9 +- packages/zwave-js/src/lib/zniffer/Zniffer.ts | 47 +++- test/run.ts | 8 +- yarn.lock | 5 + 152 files changed, 1665 insertions(+), 1136 deletions(-) create mode 100644 packages/cc/src/lib/traits.ts create mode 100644 packages/config/src/traits.ts create mode 100644 packages/core/src/bindings/fs/node.ts delete mode 100644 packages/core/src/crypto/primitives/primitives.ts create mode 100644 packages/core/src/traits/GetValueDB.ts create mode 100644 packages/core/src/traits/HostIDs.ts delete mode 100644 packages/host/src/ZWaveHost.ts delete mode 100644 packages/host/src/ZWaveHostOptions.ts delete mode 100644 packages/host/src/index_safe.ts create mode 100644 packages/maintenance/src/refactorImports.ts create mode 100644 packages/serial/src/bindings/node.ts create mode 100644 packages/serial/src/serialport/Bindings.ts create mode 100644 packages/shared/src/bindings.ts create mode 100644 packages/zwave-js/src/lib/driver/Host.ts diff --git a/.github/workflows/test-and-release.yml b/.github/workflows/test-and-release.yml index 295836592ae8..90cb8ad21184 100644 --- a/.github/workflows/test-and-release.yml +++ b/.github/workflows/test-and-release.yml @@ -110,7 +110,6 @@ jobs: node -e '(async () => { await import("@zwave-js/cc/safe") })()' node -e '(async () => { await import("@zwave-js/config/safe") })()' node -e '(async () => { await import("@zwave-js/core/safe") })()' - node -e '(async () => { await import("@zwave-js/host/safe") })()' node -e '(async () => { await import("@zwave-js/nvmedit/safe") })()' node -e '(async () => { await import("@zwave-js/serial/safe") })()' node -e '(async () => { await import("@zwave-js/shared/safe") })()' @@ -130,7 +129,6 @@ jobs: node -e 'require("@zwave-js/cc/safe")' node -e 'require("@zwave-js/config/safe")' node -e 'require("@zwave-js/core/safe")' - node -e 'require("@zwave-js/host/safe")' node -e 'require("@zwave-js/nvmedit/safe")' node -e 'require("@zwave-js/serial/safe")' node -e 'require("@zwave-js/shared/safe")' diff --git a/packages/cc/src/cc/AlarmSensorCC.ts b/packages/cc/src/cc/AlarmSensorCC.ts index 35347488e221..4138113152f3 100644 --- a/packages/cc/src/cc/AlarmSensorCC.ts +++ b/packages/cc/src/cc/AlarmSensorCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, type EndpointId, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -12,11 +14,6 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, isEnumMember, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/AssociationCC.ts b/packages/cc/src/cc/AssociationCC.ts index c6fb5eddd1d4..ce861bdc9fe8 100644 --- a/packages/cc/src/cc/AssociationCC.ts +++ b/packages/cc/src/cc/AssociationCC.ts @@ -1,5 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; +import { type GetDeviceConfig } from "@zwave-js/config"; import type { EndpointId, + GetValueDB, MaybeNotKnown, MessageRecord, SupervisionResult, @@ -14,12 +17,6 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetDeviceConfig, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { distinct } from "alcalzone-shared/arrays"; diff --git a/packages/cc/src/cc/AssociationGroupInfoCC.ts b/packages/cc/src/cc/AssociationGroupInfoCC.ts index 01ddacee82e8..5a40e52b5f0f 100644 --- a/packages/cc/src/cc/AssociationGroupInfoCC.ts +++ b/packages/cc/src/cc/AssociationGroupInfoCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, type EndpointId, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -12,11 +14,6 @@ import { parseCCId, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { cpp2js, getEnumMemberName, num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/BarrierOperatorCC.ts b/packages/cc/src/cc/BarrierOperatorCC.ts index a06cd16f5b38..3a6908bc4964 100644 --- a/packages/cc/src/cc/BarrierOperatorCC.ts +++ b/packages/cc/src/cc/BarrierOperatorCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MaybeUnknown, type MessageOrCCLogEntry, @@ -15,11 +17,6 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, diff --git a/packages/cc/src/cc/BasicCC.ts b/packages/cc/src/cc/BasicCC.ts index 68f3bb81863e..1f1263c952d8 100644 --- a/packages/cc/src/cc/BasicCC.ts +++ b/packages/cc/src/cc/BasicCC.ts @@ -1,9 +1,14 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; +import { type GetDeviceConfig } from "@zwave-js/config"; import { CommandClasses, type ControlsCC, Duration, type EndpointId, type GetEndpoint, + type GetNode, + type GetSupportedCCVersion, + type GetValueDB, type MaybeNotKnown, type MaybeUnknown, type MessageOrCCLogEntry, @@ -19,14 +24,6 @@ import { parseMaybeNumber, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetDeviceConfig, - GetNode, - GetSupportedCCVersion, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/BatteryCC.ts b/packages/cc/src/cc/BatteryCC.ts index db57e5c7575f..3859390b4759 100644 --- a/packages/cc/src/cc/BatteryCC.ts +++ b/packages/cc/src/cc/BatteryCC.ts @@ -1,4 +1,12 @@ -import { type WithAddress, timespan } from "@zwave-js/core"; +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; +import { type GetDeviceConfig } from "@zwave-js/config"; +import { + type GetNode, + type GetSupportedCCVersion, + type GetValueDB, + type WithAddress, + timespan, +} from "@zwave-js/core"; import type { ControlsCC, EndpointId, @@ -18,14 +26,6 @@ import { parseFloatWithScale, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetDeviceConfig, - GetNode, - GetSupportedCCVersion, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { type AllOrNone, getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { diff --git a/packages/cc/src/cc/BinarySensorCC.ts b/packages/cc/src/cc/BinarySensorCC.ts index cfa0d6fcee6e..4c56c5137e8d 100644 --- a/packages/cc/src/cc/BinarySensorCC.ts +++ b/packages/cc/src/cc/BinarySensorCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, type EndpointId, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -11,11 +13,6 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, isEnumMember } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/BinarySwitchCC.ts b/packages/cc/src/cc/BinarySwitchCC.ts index aea3984bb92b..b7555c5c8af1 100644 --- a/packages/cc/src/cc/BinarySwitchCC.ts +++ b/packages/cc/src/cc/BinarySwitchCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, Duration, + type GetValueDB, type MaybeNotKnown, type MaybeUnknown, type MessageOrCCLogEntry, @@ -15,11 +17,6 @@ import { parseMaybeBoolean, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { diff --git a/packages/cc/src/cc/CRC16CC.ts b/packages/cc/src/cc/CRC16CC.ts index 11970dc3c6c3..7f9b952bba25 100644 --- a/packages/cc/src/cc/CRC16CC.ts +++ b/packages/cc/src/cc/CRC16CC.ts @@ -2,16 +2,12 @@ import { CRC16_CCITT, CommandClasses, EncapsulationFlags, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, type WithAddress, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { CCAPI } from "../lib/API.js"; import { type CCRaw, CommandClass } from "../lib/CommandClass.js"; import { @@ -22,6 +18,7 @@ import { implementedVersion, } from "../lib/CommandClassDecorators.js"; +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { Bytes } from "@zwave-js/shared/safe"; import { CRC16Command } from "../lib/_Types.js"; diff --git a/packages/cc/src/cc/CentralSceneCC.ts b/packages/cc/src/cc/CentralSceneCC.ts index 7f461e93af9c..de56d290d208 100644 --- a/packages/cc/src/cc/CentralSceneCC.ts +++ b/packages/cc/src/cc/CentralSceneCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -15,11 +17,6 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/ClimateControlScheduleCC.ts b/packages/cc/src/cc/ClimateControlScheduleCC.ts index 47ec8f37f52c..9296b697efe4 100644 --- a/packages/cc/src/cc/ClimateControlScheduleCC.ts +++ b/packages/cc/src/cc/ClimateControlScheduleCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, type SupervisionResult, @@ -10,11 +12,6 @@ import { enumValuesToMetadataStates, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/ClockCC.ts b/packages/cc/src/cc/ClockCC.ts index 1d9f9d909cb9..23c9b2bdc451 100644 --- a/packages/cc/src/cc/ClockCC.ts +++ b/packages/cc/src/cc/ClockCC.ts @@ -1,4 +1,6 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import type { + GetValueDB, MessageOrCCLogEntry, SupervisionResult, WithAddress, @@ -11,11 +13,6 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/ColorSwitchCC.ts b/packages/cc/src/cc/ColorSwitchCC.ts index 979e634d9606..b9a41204b24f 100644 --- a/packages/cc/src/cc/ColorSwitchCC.ts +++ b/packages/cc/src/cc/ColorSwitchCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, Duration, + type GetValueDB, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, @@ -17,11 +19,6 @@ import { validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown, encodeBitMask } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, diff --git a/packages/cc/src/cc/ConfigurationCC.ts b/packages/cc/src/cc/ConfigurationCC.ts index bf5e8fdc7cac..52e88fc5f4b9 100644 --- a/packages/cc/src/cc/ConfigurationCC.ts +++ b/packages/cc/src/cc/ConfigurationCC.ts @@ -1,4 +1,5 @@ -import type { ParamInfoMap } from "@zwave-js/config"; +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; +import type { GetDeviceConfig, ParamInfoMap } from "@zwave-js/config"; import { CommandClasses, ConfigValueFormat, @@ -6,6 +7,9 @@ import { type ControlsCC, type EndpointId, type GetEndpoint, + type GetNode, + type GetSupportedCCVersion, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -30,14 +34,6 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetDeviceConfig, - GetNode, - GetSupportedCCVersion, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/DeviceResetLocallyCC.ts b/packages/cc/src/cc/DeviceResetLocallyCC.ts index 536f25e7fc1b..aa808149a76d 100644 --- a/packages/cc/src/cc/DeviceResetLocallyCC.ts +++ b/packages/cc/src/cc/DeviceResetLocallyCC.ts @@ -1,10 +1,10 @@ +import { type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, type MaybeNotKnown, TransmitOptions, validatePayload, } from "@zwave-js/core/safe"; -import { type CCParsingContext } from "@zwave-js/host"; import { CCAPI } from "../lib/API.js"; import { type CCRaw, CommandClass } from "../lib/CommandClass.js"; import { diff --git a/packages/cc/src/cc/DoorLockCC.ts b/packages/cc/src/cc/DoorLockCC.ts index 2b0fb8e1a24c..4724c999a5f5 100644 --- a/packages/cc/src/cc/DoorLockCC.ts +++ b/packages/cc/src/cc/DoorLockCC.ts @@ -1,7 +1,9 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, Duration, type EndpointId, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -16,11 +18,6 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/DoorLockLoggingCC.ts b/packages/cc/src/cc/DoorLockLoggingCC.ts index a1ac6e42f170..52529c4b690d 100644 --- a/packages/cc/src/cc/DoorLockLoggingCC.ts +++ b/packages/cc/src/cc/DoorLockLoggingCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -9,11 +11,6 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { isPrintableASCII, num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/EnergyProductionCC.ts b/packages/cc/src/cc/EnergyProductionCC.ts index 6ab33848db0f..eb8e0e44aa13 100644 --- a/packages/cc/src/cc/EnergyProductionCC.ts +++ b/packages/cc/src/cc/EnergyProductionCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MessageOrCCLogEntry, MessagePriority, ValueMetadata, @@ -9,11 +11,6 @@ import { validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host"; import { Bytes, getEnumMemberName, pick } from "@zwave-js/shared"; import { validateArgs } from "@zwave-js/transformers"; import { diff --git a/packages/cc/src/cc/EntryControlCC.ts b/packages/cc/src/cc/EntryControlCC.ts index e136c3cde1f8..4fd9cd7c3ddc 100644 --- a/packages/cc/src/cc/EntryControlCC.ts +++ b/packages/cc/src/cc/EntryControlCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -14,11 +16,6 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { buffer2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts b/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts index 2998ecd95576..aac68ad61ee3 100644 --- a/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts +++ b/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CRC16_CCITT, CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -10,11 +12,6 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { type AllOrNone, diff --git a/packages/cc/src/cc/HumidityControlModeCC.ts b/packages/cc/src/cc/HumidityControlModeCC.ts index 531abd0bfee4..8c2bcbcf8302 100644 --- a/packages/cc/src/cc/HumidityControlModeCC.ts +++ b/packages/cc/src/cc/HumidityControlModeCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -13,11 +15,6 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/HumidityControlOperatingStateCC.ts b/packages/cc/src/cc/HumidityControlOperatingStateCC.ts index e2ad6324452d..0e8bddac0107 100644 --- a/packages/cc/src/cc/HumidityControlOperatingStateCC.ts +++ b/packages/cc/src/cc/HumidityControlOperatingStateCC.ts @@ -1,5 +1,7 @@ +import { type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -8,7 +10,6 @@ import { enumValuesToMetadataStates, validatePayload, } from "@zwave-js/core/safe"; -import type { CCParsingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { CCAPI, diff --git a/packages/cc/src/cc/HumidityControlSetpointCC.ts b/packages/cc/src/cc/HumidityControlSetpointCC.ts index 63dfa8ef7e56..bd03888bfc3a 100644 --- a/packages/cc/src/cc/HumidityControlSetpointCC.ts +++ b/packages/cc/src/cc/HumidityControlSetpointCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -18,11 +20,6 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/InclusionControllerCC.ts b/packages/cc/src/cc/InclusionControllerCC.ts index 924cbd3f2fdc..2cb4ecd4db54 100644 --- a/packages/cc/src/cc/InclusionControllerCC.ts +++ b/packages/cc/src/cc/InclusionControllerCC.ts @@ -1,15 +1,12 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MessageOrCCLogEntry, type WithAddress, validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host"; import { Bytes, getEnumMemberName } from "@zwave-js/shared"; import { CCAPI } from "../lib/API.js"; import { type CCRaw, CommandClass } from "../lib/CommandClass.js"; diff --git a/packages/cc/src/cc/IndicatorCC.ts b/packages/cc/src/cc/IndicatorCC.ts index 0e8f97ed7ba6..e5f767e744eb 100644 --- a/packages/cc/src/cc/IndicatorCC.ts +++ b/packages/cc/src/cc/IndicatorCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, type EndpointId, + type GetValueDB, Indicator, type MaybeNotKnown, type MessageOrCCLogEntry, @@ -16,11 +18,6 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/IrrigationCC.ts b/packages/cc/src/cc/IrrigationCC.ts index e41449605c8e..120ef29a9b1e 100644 --- a/packages/cc/src/cc/IrrigationCC.ts +++ b/packages/cc/src/cc/IrrigationCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, type EndpointId, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -16,11 +18,6 @@ import { parseFloatWithScale, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/LanguageCC.ts b/packages/cc/src/cc/LanguageCC.ts index 53c13971273f..e647d9d95386 100644 --- a/packages/cc/src/cc/LanguageCC.ts +++ b/packages/cc/src/cc/LanguageCC.ts @@ -1,4 +1,6 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import type { + GetValueDB, MessageOrCCLogEntry, MessageRecord, SupervisionResult, @@ -13,11 +15,6 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/LockCC.ts b/packages/cc/src/cc/LockCC.ts index 606d2f2d210c..df400653c133 100644 --- a/packages/cc/src/cc/LockCC.ts +++ b/packages/cc/src/cc/LockCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -11,11 +13,6 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { diff --git a/packages/cc/src/cc/ManufacturerProprietaryCC.ts b/packages/cc/src/cc/ManufacturerProprietaryCC.ts index fc2194b4b5ac..75ab7c2f5ae4 100644 --- a/packages/cc/src/cc/ManufacturerProprietaryCC.ts +++ b/packages/cc/src/cc/ManufacturerProprietaryCC.ts @@ -1,3 +1,4 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, type WithAddress, @@ -5,7 +6,6 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { CCEncodingContext, CCParsingContext } from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, type CCAPIEndpoint, type CCAPIHost } from "../lib/API.js"; diff --git a/packages/cc/src/cc/ManufacturerSpecificCC.ts b/packages/cc/src/cc/ManufacturerSpecificCC.ts index 6a579629d42a..e7438612e66b 100644 --- a/packages/cc/src/cc/ManufacturerSpecificCC.ts +++ b/packages/cc/src/cc/ManufacturerSpecificCC.ts @@ -1,4 +1,9 @@ -import type { MessageOrCCLogEntry, WithAddress } from "@zwave-js/core/safe"; +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; +import type { + GetValueDB, + MessageOrCCLogEntry, + WithAddress, +} from "@zwave-js/core/safe"; import { CommandClasses, type MaybeNotKnown, @@ -8,11 +13,6 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, num2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/MeterCC.ts b/packages/cc/src/cc/MeterCC.ts index 6ea2de1e2e2a..4efd70b009d1 100644 --- a/packages/cc/src/cc/MeterCC.ts +++ b/packages/cc/src/cc/MeterCC.ts @@ -1,5 +1,10 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; +import { type GetDeviceConfig } from "@zwave-js/config"; import { type FloatParameters, + type GetNode, + type GetSupportedCCVersion, + type GetValueDB, type MaybeUnknown, type WithAddress, encodeBitMask, @@ -30,14 +35,6 @@ import { parseFloatWithScale, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetDeviceConfig, - GetNode, - GetSupportedCCVersion, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { type AllOrNone, diff --git a/packages/cc/src/cc/MultiChannelAssociationCC.ts b/packages/cc/src/cc/MultiChannelAssociationCC.ts index 0891b159059a..47c255270694 100644 --- a/packages/cc/src/cc/MultiChannelAssociationCC.ts +++ b/packages/cc/src/cc/MultiChannelAssociationCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import type { EndpointId, + GetValueDB, MessageRecord, SupervisionResult, WithAddress, @@ -16,11 +18,6 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/MultiChannelCC.ts b/packages/cc/src/cc/MultiChannelCC.ts index c18bd7874d88..09f2789a9695 100644 --- a/packages/cc/src/cc/MultiChannelCC.ts +++ b/packages/cc/src/cc/MultiChannelCC.ts @@ -1,7 +1,9 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { type ApplicationNodeInformation, CommandClasses, type GenericDeviceClass, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -19,11 +21,6 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { distinct } from "alcalzone-shared/arrays"; diff --git a/packages/cc/src/cc/MultiCommandCC.ts b/packages/cc/src/cc/MultiCommandCC.ts index b52a88a00e77..aa57d85beeb2 100644 --- a/packages/cc/src/cc/MultiCommandCC.ts +++ b/packages/cc/src/cc/MultiCommandCC.ts @@ -1,16 +1,13 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, EncapsulationFlags, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, type WithAddress, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI } from "../lib/API.js"; diff --git a/packages/cc/src/cc/MultilevelSensorCC.ts b/packages/cc/src/cc/MultilevelSensorCC.ts index 0728b40e882a..3155aebc9bc6 100644 --- a/packages/cc/src/cc/MultilevelSensorCC.ts +++ b/packages/cc/src/cc/MultilevelSensorCC.ts @@ -1,4 +1,10 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; +import { type GetDeviceConfig } from "@zwave-js/config"; import { + type GetNode, + type GetSupportedCCVersion, + type GetValueDB, + type LogNode, type WithAddress, encodeBitMask, getSensor, @@ -30,16 +36,6 @@ import { parseFloatWithScale, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetDeviceConfig, - GetNode, - GetSupportedCCVersion, - GetUserPreferences, - GetValueDB, - LogNode, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { type AllOrNone, num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -74,6 +70,7 @@ import { MultilevelSensorCommand, type MultilevelSensorValue, } from "../lib/_Types.js"; +import { type GetUserPreferences } from "../lib/traits.js"; export const MultilevelSensorCCValues = Object.freeze({ ...V.defineStaticCCValues(CommandClasses["Multilevel Sensor"], { diff --git a/packages/cc/src/cc/MultilevelSwitchCC.ts b/packages/cc/src/cc/MultilevelSwitchCC.ts index 543361ad6789..df5d9bd07d4c 100644 --- a/packages/cc/src/cc/MultilevelSwitchCC.ts +++ b/packages/cc/src/cc/MultilevelSwitchCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, Duration, + type GetValueDB, type MaybeNotKnown, type MaybeUnknown, type MessageOrCCLogEntry, @@ -14,11 +16,6 @@ import { parseMaybeNumber, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/NodeNamingCC.ts b/packages/cc/src/cc/NodeNamingCC.ts index 72a98322e40a..48de256e6891 100644 --- a/packages/cc/src/cc/NodeNamingCC.ts +++ b/packages/cc/src/cc/NodeNamingCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -10,11 +12,6 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes, stringToUint8ArrayUTF16BE, diff --git a/packages/cc/src/cc/NotificationCC.ts b/packages/cc/src/cc/NotificationCC.ts index 982c54e6f678..eb6124df8aa4 100644 --- a/packages/cc/src/cc/NotificationCC.ts +++ b/packages/cc/src/cc/NotificationCC.ts @@ -1,4 +1,10 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; +import { type GetDeviceConfig } from "@zwave-js/config"; import { + type GetNode, + type GetSupportedCCVersion, + type GetValueDB, + type LogNode, type Notification, type NotificationState, type NotificationValue, @@ -36,15 +42,6 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetDeviceConfig, - GetNode, - GetSupportedCCVersion, - GetValueDB, - LogNode, -} from "@zwave-js/host/safe"; import { Bytes, isUint8Array } from "@zwave-js/shared/safe"; import { buffer2hex, num2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/PowerlevelCC.ts b/packages/cc/src/cc/PowerlevelCC.ts index 940c54c65a3e..01bcbecff619 100644 --- a/packages/cc/src/cc/PowerlevelCC.ts +++ b/packages/cc/src/cc/PowerlevelCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, type MessageRecord, @@ -10,11 +12,6 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/ProtectionCC.ts b/packages/cc/src/cc/ProtectionCC.ts index 16842826384c..917a3627eb05 100644 --- a/packages/cc/src/cc/ProtectionCC.ts +++ b/packages/cc/src/cc/ProtectionCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, MAX_NODES, type MaybeNotKnown, type MessageOrCCLogEntry, @@ -15,11 +17,6 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/SceneActivationCC.ts b/packages/cc/src/cc/SceneActivationCC.ts index e7417445cb5e..1ac86e00f0d7 100644 --- a/packages/cc/src/cc/SceneActivationCC.ts +++ b/packages/cc/src/cc/SceneActivationCC.ts @@ -1,4 +1,6 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import type { + GetValueDB, MessageOrCCLogEntry, MessageRecord, SupervisionResult, @@ -11,11 +13,6 @@ import { ValueMetadata, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { diff --git a/packages/cc/src/cc/SceneActuatorConfigurationCC.ts b/packages/cc/src/cc/SceneActuatorConfigurationCC.ts index 364c94233c4e..fcf4e6c1c076 100644 --- a/packages/cc/src/cc/SceneActuatorConfigurationCC.ts +++ b/packages/cc/src/cc/SceneActuatorConfigurationCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, Duration, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, type MessageRecord, @@ -12,11 +14,6 @@ import { getCCName, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/SceneControllerConfigurationCC.ts b/packages/cc/src/cc/SceneControllerConfigurationCC.ts index 5bbd04af941f..4f25674723a3 100644 --- a/packages/cc/src/cc/SceneControllerConfigurationCC.ts +++ b/packages/cc/src/cc/SceneControllerConfigurationCC.ts @@ -1,7 +1,10 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; +import { type GetDeviceConfig } from "@zwave-js/config"; import { CommandClasses, Duration, type EndpointId, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -13,12 +16,6 @@ import { getCCName, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetDeviceConfig, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/ScheduleEntryLockCC.ts b/packages/cc/src/cc/ScheduleEntryLockCC.ts index 747224fc1357..c46f4fddca75 100644 --- a/packages/cc/src/cc/ScheduleEntryLockCC.ts +++ b/packages/cc/src/cc/ScheduleEntryLockCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, @@ -14,11 +16,6 @@ import { validatePayload, } from "@zwave-js/core"; import { type EndpointId, type MaybeNotKnown } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host"; import { type AllOrNone, Bytes, diff --git a/packages/cc/src/cc/Security2CC.ts b/packages/cc/src/cc/Security2CC.ts index 9c2cbe6e6581..af874add4ae6 100644 --- a/packages/cc/src/cc/Security2CC.ts +++ b/packages/cc/src/cc/Security2CC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, MPANState, type MessageOrCCLogEntry, MessagePriority, @@ -38,11 +40,6 @@ import { type SecurityManagers, encodeCCList, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { buffer2hex, getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { wait } from "alcalzone-shared/async"; diff --git a/packages/cc/src/cc/SecurityCC.ts b/packages/cc/src/cc/SecurityCC.ts index 299d1cfcbafd..42837c658105 100644 --- a/packages/cc/src/cc/SecurityCC.ts +++ b/packages/cc/src/cc/SecurityCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, EncapsulationFlags, + type GetValueDB, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, @@ -28,11 +30,6 @@ import { validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { buffer2hex, num2hex, pick } from "@zwave-js/shared/safe"; import { wait } from "alcalzone-shared/async"; diff --git a/packages/cc/src/cc/SoundSwitchCC.ts b/packages/cc/src/cc/SoundSwitchCC.ts index b0daf4987eb4..265794baa2cc 100644 --- a/packages/cc/src/cc/SoundSwitchCC.ts +++ b/packages/cc/src/cc/SoundSwitchCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -12,11 +14,6 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/SupervisionCC.ts b/packages/cc/src/cc/SupervisionCC.ts index 49162062bbda..c4aa0a9a0dab 100644 --- a/packages/cc/src/cc/SupervisionCC.ts +++ b/packages/cc/src/cc/SupervisionCC.ts @@ -1,9 +1,12 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, Duration, EncapsulationFlags, type EndpointId, type GetEndpoint, + type GetNode, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -20,12 +23,6 @@ import { isTransmissionError, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetNode, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { PhysicalCCAPI } from "../lib/API.js"; diff --git a/packages/cc/src/cc/ThermostatFanModeCC.ts b/packages/cc/src/cc/ThermostatFanModeCC.ts index 374f4c112a9e..58c5007a58c4 100644 --- a/packages/cc/src/cc/ThermostatFanModeCC.ts +++ b/packages/cc/src/cc/ThermostatFanModeCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -14,11 +16,6 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/ThermostatFanStateCC.ts b/packages/cc/src/cc/ThermostatFanStateCC.ts index f13678050580..13366e42acb2 100644 --- a/packages/cc/src/cc/ThermostatFanStateCC.ts +++ b/packages/cc/src/cc/ThermostatFanStateCC.ts @@ -1,5 +1,7 @@ +import { type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -9,7 +11,6 @@ import { enumValuesToMetadataStates, validatePayload, } from "@zwave-js/core/safe"; -import type { CCParsingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { CCAPI, diff --git a/packages/cc/src/cc/ThermostatModeCC.ts b/packages/cc/src/cc/ThermostatModeCC.ts index df8a729b8bae..6fb4f8d3e0c8 100644 --- a/packages/cc/src/cc/ThermostatModeCC.ts +++ b/packages/cc/src/cc/ThermostatModeCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -15,11 +17,6 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { buffer2hex, getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/ThermostatOperatingStateCC.ts b/packages/cc/src/cc/ThermostatOperatingStateCC.ts index 4af58dbef9fa..57f2ecdae4ad 100644 --- a/packages/cc/src/cc/ThermostatOperatingStateCC.ts +++ b/packages/cc/src/cc/ThermostatOperatingStateCC.ts @@ -1,4 +1,9 @@ -import type { MessageOrCCLogEntry, WithAddress } from "@zwave-js/core/safe"; +import { type CCParsingContext } from "@zwave-js/cc"; +import type { + GetValueDB, + MessageOrCCLogEntry, + WithAddress, +} from "@zwave-js/core/safe"; import { CommandClasses, type MaybeNotKnown, @@ -7,7 +12,6 @@ import { enumValuesToMetadataStates, validatePayload, } from "@zwave-js/core/safe"; -import type { CCParsingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { CCAPI, diff --git a/packages/cc/src/cc/ThermostatSetbackCC.ts b/packages/cc/src/cc/ThermostatSetbackCC.ts index a50d10a3a32e..eaa05535c86e 100644 --- a/packages/cc/src/cc/ThermostatSetbackCC.ts +++ b/packages/cc/src/cc/ThermostatSetbackCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -7,11 +9,6 @@ import { type WithAddress, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/ThermostatSetpointCC.ts b/packages/cc/src/cc/ThermostatSetpointCC.ts index 9e29eae8a2a0..84ec29042951 100644 --- a/packages/cc/src/cc/ThermostatSetpointCC.ts +++ b/packages/cc/src/cc/ThermostatSetpointCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -19,11 +21,6 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/TimeCC.ts b/packages/cc/src/cc/TimeCC.ts index ed26faba1b24..a606dbca108f 100644 --- a/packages/cc/src/cc/TimeCC.ts +++ b/packages/cc/src/cc/TimeCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, type DSTInfo, + type GetValueDB, type MessageOrCCLogEntry, MessagePriority, type SupervisionResult, @@ -12,11 +14,6 @@ import { validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/TimeParametersCC.ts b/packages/cc/src/cc/TimeParametersCC.ts index 4bcbadd14a31..ada4553c3d6c 100644 --- a/packages/cc/src/cc/TimeParametersCC.ts +++ b/packages/cc/src/cc/TimeParametersCC.ts @@ -1,5 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; +import { type GetDeviceConfig } from "@zwave-js/config"; import { CommandClasses, + type GetValueDB, type MessageOrCCLogEntry, MessagePriority, type SupervisionResult, @@ -14,12 +17,6 @@ import { type MaybeNotKnown, type SupportsCC, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetDeviceConfig, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { diff --git a/packages/cc/src/cc/TransportServiceCC.ts b/packages/cc/src/cc/TransportServiceCC.ts index 6c27a8e8bc6a..5841781ec9e0 100644 --- a/packages/cc/src/cc/TransportServiceCC.ts +++ b/packages/cc/src/cc/TransportServiceCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CRC16_CCITT, CommandClasses, + type GetValueDB, type MessageOrCCLogEntry, type SinglecastCC, type WithAddress, @@ -8,11 +10,6 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { buffer2hex } from "@zwave-js/shared/safe"; import { type CCRaw, CommandClass } from "../lib/CommandClass.js"; diff --git a/packages/cc/src/cc/UserCodeCC.ts b/packages/cc/src/cc/UserCodeCC.ts index bb49744e09ce..ba5ed01ec952 100644 --- a/packages/cc/src/cc/UserCodeCC.ts +++ b/packages/cc/src/cc/UserCodeCC.ts @@ -1,6 +1,9 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, type EndpointId, + type GetSupportedCCVersion, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -16,12 +19,6 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetSupportedCCVersion, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes, isUint8Array, uint8ArrayToString } from "@zwave-js/shared/safe"; import { getEnumMemberName, diff --git a/packages/cc/src/cc/VersionCC.ts b/packages/cc/src/cc/VersionCC.ts index 3b84cc6e1972..03f20a9ed7df 100644 --- a/packages/cc/src/cc/VersionCC.ts +++ b/packages/cc/src/cc/VersionCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -16,11 +18,6 @@ import { securityClassOrder, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, num2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/WakeUpCC.ts b/packages/cc/src/cc/WakeUpCC.ts index 9f0e8f587a82..162a639aaab6 100644 --- a/packages/cc/src/cc/WakeUpCC.ts +++ b/packages/cc/src/cc/WakeUpCC.ts @@ -1,5 +1,7 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -10,11 +12,6 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/WindowCoveringCC.ts b/packages/cc/src/cc/WindowCoveringCC.ts index f5dbd1845baa..5fe05c76859f 100644 --- a/packages/cc/src/cc/WindowCoveringCC.ts +++ b/packages/cc/src/cc/WindowCoveringCC.ts @@ -1,6 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, Duration, + type GetValueDB, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, @@ -12,11 +14,6 @@ import { validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/ZWavePlusCC.ts b/packages/cc/src/cc/ZWavePlusCC.ts index 615badbaa9f1..41497d520b01 100644 --- a/packages/cc/src/cc/ZWavePlusCC.ts +++ b/packages/cc/src/cc/ZWavePlusCC.ts @@ -1,16 +1,13 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { CommandClasses, + type GetValueDB, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type WithAddress, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, num2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; diff --git a/packages/cc/src/cc/ZWaveProtocolCC.ts b/packages/cc/src/cc/ZWaveProtocolCC.ts index 2ad8a6e7d90c..669fd164609d 100644 --- a/packages/cc/src/cc/ZWaveProtocolCC.ts +++ b/packages/cc/src/cc/ZWaveProtocolCC.ts @@ -1,3 +1,4 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; import { type BasicDeviceClass, CommandClasses, @@ -21,7 +22,6 @@ import { parseNodeProtocolInfoAndDeviceClass, validatePayload, } from "@zwave-js/core"; -import type { CCEncodingContext, CCParsingContext } from "@zwave-js/host"; import { Bytes } from "@zwave-js/shared/safe"; import { type CCRaw, CommandClass } from "../lib/CommandClass.js"; import { diff --git a/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts b/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts index 40d0ebb933f7..dc5269e96e2f 100644 --- a/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts +++ b/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts @@ -1,5 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; +import { type GetDeviceConfig } from "@zwave-js/config"; import { CommandClasses, + type GetValueDB, type MaybeUnknown, type MessageOrCCLogEntry, type MessageRecord, @@ -11,12 +14,6 @@ import { parseMaybeNumber, validatePayload, } from "@zwave-js/core/safe"; -import type { - CCEncodingContext, - CCParsingContext, - GetDeviceConfig, - GetValueDB, -} from "@zwave-js/host/safe"; import { Bytes, pick } from "@zwave-js/shared"; import { validateArgs } from "@zwave-js/transformers"; import { isArray } from "alcalzone-shared/typeguards"; diff --git a/packages/cc/src/index.ts b/packages/cc/src/index.ts index 643da4bf4f46..745c27084047 100644 --- a/packages/cc/src/index.ts +++ b/packages/cc/src/index.ts @@ -26,3 +26,4 @@ export type { } from "./lib/Values.js"; export * from "./lib/_Types.js"; export { utils }; +export type * from "./lib/traits.js"; diff --git a/packages/cc/src/index_browser.ts b/packages/cc/src/index_browser.ts index cdd1a0e6c2f3..fbf808a5d552 100644 --- a/packages/cc/src/index_browser.ts +++ b/packages/cc/src/index_browser.ts @@ -10,3 +10,4 @@ export type { PartialCCValuePredicate, } from "./lib/Values.js"; export * from "./lib/_Types.js"; +export type * from "./lib/traits.js"; diff --git a/packages/cc/src/lib/API.ts b/packages/cc/src/lib/API.ts index 6debd7c0b307..71915e35cd00 100644 --- a/packages/cc/src/lib/API.ts +++ b/packages/cc/src/lib/API.ts @@ -1,11 +1,21 @@ -import { type CompatOverrideQueries } from "@zwave-js/config"; +import { type SendCommand } from "@zwave-js/cc"; +import { + type CompatOverrideQueries, + type GetDeviceConfig, +} from "@zwave-js/config"; import { CommandClasses, type ControlsCC, type Duration, type EndpointId, type GetEndpoint, + type GetNode, + type GetSafeCCVersion, + type GetSupportedCCVersion, + type GetValueDB, + type HostIDs, type ListenBehavior, + type LogNode, type MaybeNotKnown, NODE_ID_BROADCAST, NODE_ID_BROADCAST_LR, @@ -27,19 +37,6 @@ import { getCCName, stripUndefined, } from "@zwave-js/core"; -import type { - GetCommunicationTimeouts, - GetDeviceConfig, - GetNode, - GetSafeCCVersion, - GetSupportedCCVersion, - GetUserPreferences, - GetValueDB, - HostIDs, - LogNode, - SchedulePoll, - SendCommand, -} from "@zwave-js/host"; import { type AllOrNone, type OnlyMethods, @@ -55,6 +52,11 @@ import { getImplementedVersion, } from "./CommandClassDecorators.js"; import { type CCValue, type StaticCCValue } from "./Values.js"; +import { + type GetRefreshValueTimeouts, + type GetUserPreferences, + type SchedulePoll, +} from "./traits.js"; export type ValueIDProperties = Pick; @@ -161,7 +163,7 @@ export function throwWrongValueType( ); } -export interface SchedulePollOptions { +export interface CCAPISchedulePollOptions { duration?: Duration; transition?: "fast" | "slow"; } @@ -176,7 +178,7 @@ export type CCAPIHost = & SecurityManagers & GetDeviceConfig & SendCommand - & GetCommunicationTimeouts + & GetRefreshValueTimeouts & GetUserPreferences & SchedulePoll & LogNode; @@ -363,12 +365,12 @@ export class CCAPI { protected schedulePoll( { property, propertyKey }: ValueIDProperties, expectedValue: unknown, - { duration, transition = "slow" }: SchedulePollOptions = {}, + { duration, transition = "slow" }: CCAPISchedulePollOptions = {}, ): boolean { // 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 timeouts = this.host.getRefreshValueTimeouts(); const additionalDelay = !!durationMs || transition === "fast" ? timeouts.refreshValueAfterTransition : timeouts.refreshValue; diff --git a/packages/cc/src/lib/CommandClass.ts b/packages/cc/src/lib/CommandClass.ts index 18f02418e051..42ab8c0c66c2 100644 --- a/packages/cc/src/lib/CommandClass.ts +++ b/packages/cc/src/lib/CommandClass.ts @@ -1,3 +1,8 @@ +import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; +import { + type GetDeviceConfig, + type LookupManufacturer, +} from "@zwave-js/config"; import { type BroadcastCC, type CCAddress, @@ -10,7 +15,12 @@ import { type GetAllEndpoints, type GetCCs, type GetEndpoint, + type GetNode, + type GetSupportedCCVersion, + type GetValueDB, + type HostIDs, type ListenBehavior, + type LogNode, type MessageOrCCLogEntry, type MessageRecord, type ModifyCCs, @@ -34,18 +44,6 @@ import { parseCCId, valueIdToString, } from "@zwave-js/core"; -import type { - CCEncodingContext, - CCParsingContext, - GetDeviceConfig, - GetInterviewOptions, - GetNode, - GetSupportedCCVersion, - GetValueDB, - HostIDs, - LogNode, - LookupManufacturer, -} from "@zwave-js/host"; import { Bytes, type JSONObject, @@ -78,6 +76,7 @@ import { type StaticCCValue, defaultCCValueOptions, } from "./Values.js"; +import { type GetInterviewOptions } from "./traits.js"; export interface CommandClassOptions extends CCAddress { ccId?: number; // Used to overwrite the declared CC ID diff --git a/packages/cc/src/lib/Values.ts b/packages/cc/src/lib/Values.ts index cd1f491e8b0f..53897a713b05 100644 --- a/packages/cc/src/lib/Values.ts +++ b/packages/cc/src/lib/Values.ts @@ -1,10 +1,11 @@ +import { type GetDeviceConfig } from "@zwave-js/config"; import { type CommandClasses, type EndpointId, + type GetValueDB, type ValueID, ValueMetadata, } from "@zwave-js/core/safe"; -import type { GetDeviceConfig, GetValueDB } from "@zwave-js/host"; import { type FnOrStatic, type ReturnTypeOrStatic, diff --git a/packages/cc/src/lib/traits.ts b/packages/cc/src/lib/traits.ts new file mode 100644 index 000000000000..a19e311d77fd --- /dev/null +++ b/packages/cc/src/lib/traits.ts @@ -0,0 +1,152 @@ +import { type GetDeviceConfig } from "@zwave-js/config"; +import { + type CCId, + type FrameType, + type GetSupportedCCVersion, + type HostIDs, + type MaybeNotKnown, + type SecurityClass, + type SecurityManagers, + type SendCommandOptions, + type SendCommandReturnType, + type ValueID, +} from "@zwave-js/core"; + +/** Allows scheduling a value refresh (poll) for a later time */ +export interface SchedulePoll { + schedulePoll( + nodeId: number, + valueId: ValueID, + options: SchedulePollOptions, + ): boolean; +} + +export interface SchedulePollOptions { + /** The timeout after which the poll is to be scheduled */ + timeoutMs?: number; + /** + * The expected value that's should be verified with this poll. + * When this value is received in the meantime, the poll will be cancelled. + */ + expectedValue?: unknown; +} + +export interface RefreshValueTimeouts { + /** + * How long to wait for a poll after setting a value without transition duration + */ + refreshValue: number; + + /** + * How long to wait for a poll after setting a value with transition duration. This doubles as the "fast" delay. + */ + refreshValueAfterTransition: number; +} + +/** Allows reading timeouts for refreshing values from a node */ +export interface GetRefreshValueTimeouts { + getRefreshValueTimeouts(): RefreshValueTimeouts; +} + +export interface UserPreferences { + /** + * The preferred scales to use when querying sensors. The key is either: + * - the name of a named scale group, e.g. "temperature", which applies to every sensor type that uses this scale group. + * - or the numeric sensor type to specify the scale for a single sensor type + * + * Single-type preferences have a higher priority than named ones. For example, the following preference + * ```js + * { + * temperature: "°F", + * 0x01: "°C", + * } + * ``` + * will result in using the Fahrenheit scale for all temperature sensors, except the air temperature (0x01). + * + * The value must match what is defined in the sensor type config file and contain either: + * - the label (e.g. "Celsius", "Fahrenheit") + * - the unit (e.g. "°C", "°F") + * - or the numeric key of the scale (e.g. 0 or 1). + * + * Default: + * ```js + * { + * temperature: "Celsius" + * } + * ``` + */ + scales: Partial>; +} + +/** Allows reading user preferences */ +export interface GetUserPreferences { + getUserPreferences(): UserPreferences; +} + +export interface InterviewOptions { + /** + * Whether all user code should be queried during the interview of the UserCode CC. + * Note that enabling this can cause a lot of traffic during the interview. + */ + queryAllUserCodes?: boolean; +} + +/** Allows reading options to use for interviewing devices */ +export interface GetInterviewOptions { + getInterviewOptions(): InterviewOptions; +} + +/** 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; + + hasSecurityClass( + nodeId: number, + securityClass: SecurityClass, + ): MaybeNotKnown; + + setSecurityClass( + nodeId: number, + securityClass: SecurityClass, + granted: boolean, + ): void; +} + +/** 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; + + hasSecurityClass( + nodeId: number, + securityClass: SecurityClass, + ): MaybeNotKnown; + + setSecurityClass( + nodeId: number, + securityClass: SecurityClass, + granted: boolean, + ): void; +} + +/** Allows sending commands to one or more nodes */ +export interface SendCommand { + sendCommand( + command: CCId, + options?: SendCommandOptions, + ): Promise>; +} diff --git a/packages/cc/src/lib/utils.ts b/packages/cc/src/lib/utils.ts index 55ac2b450a7f..bba9755280fd 100644 --- a/packages/cc/src/lib/utils.ts +++ b/packages/cc/src/lib/utils.ts @@ -1,10 +1,13 @@ -import type { AssociationConfig } from "@zwave-js/config"; +import type { AssociationConfig, GetDeviceConfig } from "@zwave-js/config"; import { CommandClasses, type ControlsCC, type EndpointId, type GetAllEndpoints, type GetEndpoint, + type GetNode, + type GetValueDB, + type HostIDs, type MaybeNotKnown, NOT_KNOWN, type NodeId, @@ -19,12 +22,6 @@ import { isLongRangeNodeId, isSensorCC, } from "@zwave-js/core/safe"; -import { - type GetDeviceConfig, - type GetNode, - type GetValueDB, - type HostIDs, -} from "@zwave-js/host"; import { ObjectKeyMap, type ReadonlyObjectKeyMap, diff --git a/packages/config/maintenance/importConfig.ts b/packages/config/maintenance/importConfig.ts index ee3e5d2c5052..c77b0861d1a4 100644 --- a/packages/config/maintenance/importConfig.ts +++ b/packages/config/maintenance/importConfig.ts @@ -9,6 +9,7 @@ process.on("unhandledRejection", (r) => { }); import { CommandClasses, getIntegerLimits } from "@zwave-js/core"; +import { fs as nodeFS } from "@zwave-js/core/bindings/fs/node"; import { enumFilesRecursive, formatId, @@ -852,6 +853,7 @@ async function parseZWAFiles(): Promise { let jsonData = []; const configFiles = await enumFilesRecursive( + nodeFS, zwaTempDir, (file) => file.endsWith(".json"), ); @@ -1602,8 +1604,9 @@ async function maintenanceParse(): Promise { const zwaData = []; // Load the zwa files - await fs.mkdir(zwaTempDir, { recursive: true }); + await nodeFS.ensureDir(zwaTempDir); const zwaFiles = await enumFilesRecursive( + nodeFS, zwaTempDir, (file) => file.endsWith(".json"), ); @@ -1611,7 +1614,7 @@ async function maintenanceParse(): Promise { // zWave Alliance numbering isn't always continuous and an html page is // returned when a device number doesn't. Test for and delete such files. try { - zwaData.push(await readJSON(file)); + zwaData.push(await readJSON(nodeFS, file)); } catch { await fs.unlink(file); } @@ -1619,6 +1622,7 @@ async function maintenanceParse(): Promise { // Build the list of device files const configFiles = await enumFilesRecursive( + nodeFS, processedDir, (file) => file.endsWith(".json"), ); @@ -2033,7 +2037,7 @@ async function importConfigFilesOH(): Promise { } } outFilename += ".json"; - await fs.ensureDir(path.dirname(outFilename)); + await nodeFS.ensureDir(path.dirname(outFilename)); const output = stringify(parsed, "\t") + "\n"; await fs.writeFile(outFilename, output, "utf8"); @@ -2305,6 +2309,7 @@ function getLatestConfigVersion( /** Changes the manufacturer names in all device config files to match manufacturers.json */ async function updateManufacturerNames(): Promise { const configFiles = await enumFilesRecursive( + nodeFS, processedDir, (file) => file.endsWith(".json") && !file.endsWith("index.json"), ); diff --git a/packages/config/maintenance/lintConfigFiles.ts b/packages/config/maintenance/lintConfigFiles.ts index ff4090e43851..268dc837c353 100644 --- a/packages/config/maintenance/lintConfigFiles.ts +++ b/packages/config/maintenance/lintConfigFiles.ts @@ -4,6 +4,7 @@ import { getLegalRangeForBitMask, getMinimumShiftForBitMask, } from "@zwave-js/core"; +import { fs } from "@zwave-js/core/bindings/fs/node"; import { reportProblem } from "@zwave-js/maintenance"; import { enumFilesRecursive, @@ -262,6 +263,7 @@ async function lintDevices(): Promise { const rootDir = path.join(configDir, "devices"); const forbiddenFiles = await enumFilesRecursive( + fs, rootDir, (filename) => !filename.endsWith(".json"), ); @@ -286,6 +288,7 @@ async function lintDevices(): Promise { let conditionalConfig: ConditionalDeviceConfig; try { conditionalConfig = await ConditionalDeviceConfig.from( + fs, filePath, true, { diff --git a/packages/config/package.json b/packages/config/package.json index ca89954af55b..40ac20e87a04 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -64,6 +64,7 @@ "ansi-colors": "^4.1.3", "json-logic-js": "^2.0.5", "json5": "^2.2.3", + "pathe": "^1.1.2", "semver": "^7.6.3", "winston": "^3.15.0" }, diff --git a/packages/config/src/ConfigManager.test.ts b/packages/config/src/ConfigManager.test.ts index cadb240d39a4..01305cca3bff 100644 --- a/packages/config/src/ConfigManager.test.ts +++ b/packages/config/src/ConfigManager.test.ts @@ -1,6 +1,7 @@ import { ZWaveLogContainer } from "@zwave-js/core"; +import { fs } from "@zwave-js/core/bindings/fs/node"; import { pathExists } from "@zwave-js/shared"; -import fs from "node:fs/promises"; +import fsp from "node:fs/promises"; import { tmpdir } from "node:os"; import * as path from "node:path"; import semverInc from "semver/functions/inc.js"; @@ -25,7 +26,7 @@ const test = baseTest.extend({ async ({}, use) => { // Setup const tempDir = path.join(tmpdir(), "zwavejs_test"); - await fs.mkdir(tempDir, { recursive: true }); + await fsp.mkdir(tempDir, { recursive: true }); const logContainer = new ZWaveLogContainer({ enabled: false }); const logger = new ConfigLogger(logContainer); @@ -34,15 +35,15 @@ const test = baseTest.extend({ await use({ tempDir, logContainer, logger }); // Teardown - await fs.rm(tempDir, { recursive: true, force: true }); + await fsp.rm(tempDir, { recursive: true, force: true }); }, { auto: true }, ], }); beforeEach(async ({ context, expect }) => { - await fs.rm(context.tempDir, { recursive: true, force: true }); - await fs.mkdir(context.tempDir, { recursive: true }); + await fsp.rm(context.tempDir, { recursive: true, force: true }); + await fsp.mkdir(context.tempDir, { recursive: true }); }); test.sequential( @@ -51,11 +52,11 @@ test.sequential( const { tempDir, logger } = context; const configDir = path.join(tempDir, "extconfig"); - await syncExternalConfigDir(configDir, logger); + await syncExternalConfigDir(fs, configDir, logger); - expect(await pathExists(configDir)).toBe(true); + expect(await pathExists(fs, configDir)).toBe(true); expect( - await fs.readFile(path.join(configDir, "version"), "utf8"), + await fsp.readFile(path.join(configDir, "version"), "utf8"), ).toBe(ownVersion); }, 60000, @@ -69,19 +70,19 @@ test.sequential( const configDir = path.join(tempDir, "extconfig"); const otherVersion = semverInc(ownVersion, "major"); - await fs.mkdir(configDir, { recursive: true }); - await fs.writeFile( + await fsp.mkdir(configDir, { recursive: true }); + await fsp.writeFile( path.join(configDir, "version"), otherVersion!, "utf8", ); - await syncExternalConfigDir(configDir, logger); + await syncExternalConfigDir(fs, configDir, logger); - expect(await pathExists(configDir)).toBe(true); + expect(await pathExists(fs, configDir)).toBe(true); expect( - await fs.readFile(path.join(configDir, "version"), "utf8"), + await fsp.readFile(path.join(configDir, "version"), "utf8"), ).toBe(ownVersion); }, 60000, @@ -95,19 +96,19 @@ test.sequential( const configDir = path.join(tempDir, "extconfig"); const otherVersion = semverInc(ownVersion, "prerelease")!; - await fs.mkdir(configDir, { recursive: true }); - await fs.writeFile( + await fsp.mkdir(configDir, { recursive: true }); + await fsp.writeFile( path.join(configDir, "version"), otherVersion, "utf8", ); - await syncExternalConfigDir(configDir, logger); + await syncExternalConfigDir(fs, configDir, logger); - expect(await pathExists(configDir)).toBe(true); + expect(await pathExists(fs, configDir)).toBe(true); expect( - await fs.readFile(path.join(configDir, "version"), "utf8"), + await fsp.readFile(path.join(configDir, "version"), "utf8"), ).toBe(otherVersion); }, 60000, @@ -140,7 +141,7 @@ test.sequential( const cm = new ConfigManager({ logContainer }); await cm.loadAll(); - expect(await pathExists(configDir)).toBe(true); + expect(await pathExists(fs, configDir)).toBe(true); // Load the Aeotec ZW100 Multisensor 6 - we know that it uses multiple imports that could fail validation const device = await cm.lookupDevice(0x0086, 0x0002, 0x0064); @@ -167,7 +168,7 @@ async function testDeviceConfigPriorityDir( // Set up a dummy structure in the priority dir const priorityDir = path.join(tempDir, "priority"); - await fs.mkdir(path.join(priorityDir, "templates"), { recursive: true }); + await fsp.mkdir(path.join(priorityDir, "templates"), { recursive: true }); let json: any = { manufacturer: "AEON Labs", manufacturerId: "0x0086", @@ -190,7 +191,7 @@ async function testDeviceConfigPriorityDir( }, ], }; - await fs.writeFile( + await fsp.writeFile( path.join(priorityDir, "aeotec.json"), JSON.stringify(json, null, 4), ); @@ -204,7 +205,7 @@ async function testDeviceConfigPriorityDir( unsigned: true, }, }; - await fs.writeFile( + await fsp.writeFile( path.join(priorityDir, "templates/template.json"), JSON.stringify(json, null, 4), ); @@ -217,7 +218,7 @@ async function testDeviceConfigPriorityDir( await cm.loadAll(); if (useExternalConfig) { - expect(await pathExists(externalConfigDir!)).toBe(true); + expect(await pathExists(fs, externalConfigDir!)).toBe(true); } // Load the dummy device diff --git a/packages/config/src/ConfigManager.ts b/packages/config/src/ConfigManager.ts index 04af72e7b56d..e563db2aeedc 100644 --- a/packages/config/src/ConfigManager.ts +++ b/packages/config/src/ConfigManager.ts @@ -5,7 +5,8 @@ import { isZWaveError, } from "@zwave-js/core"; import { getErrorMessage, pathExists } from "@zwave-js/shared"; -import path from "node:path"; +import { type FileSystem } from "@zwave-js/shared/bindings"; +import path from "pathe"; import { ConfigLogger } from "./Logger.js"; import { type ManufacturersMap, @@ -32,6 +33,7 @@ import { } from "./utils.js"; export interface ConfigManagerOptions { + bindings?: FileSystem; logContainer?: ZWaveLogContainer; deviceConfigPriorityDir?: string; deviceConfigExternalDir?: string; @@ -39,6 +41,7 @@ export interface ConfigManagerOptions { export class ConfigManager { public constructor(options: ConfigManagerOptions = {}) { + this._fs = options.bindings; this.logger = new ConfigLogger( options.logContainer ?? new ZWaveLogContainer({ enabled: false }), ); @@ -48,6 +51,12 @@ export class ConfigManager { this._configVersion = PACKAGE_VERSION; } + private _fs: FileSystem | undefined; + private async getFS(): Promise { + this._fs ??= (await import("@zwave-js/core/bindings/fs/node")).fs; + return this._fs; + } + private _configVersion: string; public get configVersion(): string { return this._configVersion; @@ -88,6 +97,7 @@ export class ConfigManager { const externalConfigDir = this.externalConfigDir; if (externalConfigDir) { syncResult = await syncExternalConfigDir( + await this.getFS(), externalConfigDir, this.logger, ); @@ -112,6 +122,7 @@ export class ConfigManager { public async loadManufacturers(): Promise { try { this._manufacturers = await loadManufacturersInternal( + await this.getFS(), this._useExternalConfig && this.externalConfigDir || undefined, ); } catch (e) { @@ -139,7 +150,10 @@ export class ConfigManager { ); } - await saveManufacturersInternal(this._manufacturers); + await saveManufacturersInternal( + await this.getFS(), + this._manufacturers, + ); } /** @@ -177,18 +191,21 @@ export class ConfigManager { } public async loadDeviceIndex(): Promise { + const fs = await this.getFS(); try { // The index of config files included in this package const embeddedIndex = await loadDeviceIndexInternal( + fs, this.logger, this._useExternalConfig && this.externalConfigDir || undefined, ); // A dynamic index of the user-defined priority device config files const priorityIndex: DeviceConfigIndex = []; if (this.deviceConfigPriorityDir) { - if (await pathExists(this.deviceConfigPriorityDir)) { + if (await pathExists(fs, this.deviceConfigPriorityDir)) { priorityIndex.push( ...(await generatePriorityDeviceIndex( + fs, this.deviceConfigPriorityDir, this.logger, )), @@ -230,7 +247,10 @@ export class ConfigManager { } public async loadFulltextDeviceIndex(): Promise { - this.fulltextIndex = await loadFulltextDeviceIndexInternal(this.logger); + this.fulltextIndex = await loadFulltextDeviceIndexInternal( + await this.getFS(), + this.logger, + ); } public getFulltextIndex(): FulltextDeviceConfigIndex | undefined { @@ -254,6 +274,8 @@ export class ConfigManager { // Load/regenerate the index if necessary if (!this.index) await this.loadDeviceIndex(); + const fs = await this.getFS(); + // Look up the device in the index const indexEntries = this.index!.filter( getDeviceEntryPredicate( @@ -274,7 +296,7 @@ export class ConfigManager { const filePath = path.isAbsolute(indexEntry.filename) ? indexEntry.filename : path.join(devicesDir, indexEntry.filename); - if (!(await pathExists(filePath))) return; + if (!(await pathExists(fs, filePath))) return; // A config file is treated as am embedded one when it is located under the devices root dir // or the external config dir @@ -291,6 +313,7 @@ export class ConfigManager { try { return await ConditionalDeviceConfig.from( + fs, filePath, isEmbedded, { rootDir, fallbackDirs }, diff --git a/packages/config/src/JsonTemplate.test.ts b/packages/config/src/JsonTemplate.test.ts index 0534aebe806e..05fa57765fda 100644 --- a/packages/config/src/JsonTemplate.test.ts +++ b/packages/config/src/JsonTemplate.test.ts @@ -1,5 +1,6 @@ import { ZWaveErrorCodes, assertZWaveError } from "@zwave-js/core"; -import fs from "node:fs/promises"; +import { fs } from "@zwave-js/core/bindings/fs/node"; +import fsp from "node:fs/promises"; import { tmpdir } from "node:os"; import * as path from "node:path"; import { afterEach, beforeAll, test } from "vitest"; @@ -8,17 +9,17 @@ import { readJsonWithTemplate } from "./JsonTemplate.js"; const mockDir = path.join(tmpdir(), `zwave-js-template-test`); async function mockFs(files: Record): Promise { - await fs.mkdir(mockDir, { recursive: true }); + await fsp.mkdir(mockDir, { recursive: true }); for (const [name, content] of Object.entries(files)) { const relative = name.replace(/^\//, "./"); const filename = path.join(mockDir, relative); const dirname = path.join(mockDir, path.dirname(relative)); - await fs.mkdir(dirname, { recursive: true }); - await fs.writeFile(filename, content); + await fsp.mkdir(dirname, { recursive: true }); + await fsp.writeFile(filename, content); } } mockFs.restore = async (): Promise => { - await fs.rm(mockDir, { recursive: true, force: true }); + await fsp.rm(mockDir, { recursive: true, force: true }); }; beforeAll(() => mockFs.restore()); @@ -36,6 +37,7 @@ test.sequential( }); const content = await readJsonWithTemplate( + fs, path.join(mockDir, "test.json"), ); t.expect(content).toStrictEqual(file); @@ -58,6 +60,7 @@ test.sequential( }); const content = await readJsonWithTemplate( + fs, path.join(mockDir, "test.json"), ); t.expect(content).toStrictEqual(template); @@ -81,6 +84,7 @@ test.sequential( }); const content = await readJsonWithTemplate( + fs, path.join(mockDir, "test.json"), ); t.expect(content).toStrictEqual(template); @@ -106,6 +110,7 @@ test.sequential( }); const content = await readJsonWithTemplate( + fs, path.join(mockDir, "test.json"), ); t.expect(content).toStrictEqual(expected); @@ -127,7 +132,7 @@ test.sequential( await assertZWaveError( t.expect, - () => readJsonWithTemplate(path.join(mockDir, "test.json")), + () => readJsonWithTemplate(fs, path.join(mockDir, "test.json")), { errorCode: ZWaveErrorCodes.Config_Invalid, }, @@ -157,7 +162,7 @@ test.sequential( await assertZWaveError( t.expect, - () => readJsonWithTemplate(path.join(mockDir, "test.json")), + () => readJsonWithTemplate(fs, path.join(mockDir, "test.json")), { errorCode: ZWaveErrorCodes.Config_Invalid, messageMatches: "Import specifier", @@ -184,7 +189,7 @@ test.sequential( await assertZWaveError( t.expect, - () => readJsonWithTemplate(path.join(mockDir, "test.json")), + () => readJsonWithTemplate(fs, path.join(mockDir, "test.json")), { errorCode: ZWaveErrorCodes.Config_Invalid, }, @@ -219,6 +224,7 @@ test.sequential( }); const content = await readJsonWithTemplate( + fs, path.join(mockDir, "test.json"), ); t.expect(content).toStrictEqual(expected); @@ -266,6 +272,7 @@ test.sequential( }); const content = await readJsonWithTemplate( + fs, path.join(mockDir, "test.json"), ); t.expect(content).toStrictEqual(expected); @@ -289,7 +296,7 @@ test.sequential( await assertZWaveError( t.expect, - () => readJsonWithTemplate(path.join(mockDir, "test.json")), + () => readJsonWithTemplate(fs, path.join(mockDir, "test.json")), { errorCode: ZWaveErrorCodes.Config_CircularImport, }, @@ -318,7 +325,7 @@ test.sequential( await assertZWaveError( t.expect, - () => readJsonWithTemplate(path.join(mockDir, "test.json")), + () => readJsonWithTemplate(fs, path.join(mockDir, "test.json")), { errorCode: ZWaveErrorCodes.Config_CircularImport, }, @@ -360,7 +367,7 @@ test.sequential( await assertZWaveError( t.expect, - () => readJsonWithTemplate(path.join(mockDir, "test.json")), + () => readJsonWithTemplate(fs, path.join(mockDir, "test.json")), { errorCode: ZWaveErrorCodes.Config_CircularImport, }, @@ -384,6 +391,7 @@ test.sequential( }); const content = await readJsonWithTemplate( + fs, path.join(mockDir, "foo/bar/test.json"), ); t.expect(content).toStrictEqual(template); @@ -406,6 +414,7 @@ test.sequential( }); const content = await readJsonWithTemplate( + fs, path.join(mockDir, "foo/bar/test.json"), path.join(mockDir, "foo"), ); @@ -425,7 +434,11 @@ test.sequential( }); await assertZWaveError( t.expect, - () => readJsonWithTemplate(path.join(mockDir, "foo/bar/test.json")), + () => + readJsonWithTemplate( + fs, + path.join(mockDir, "foo/bar/test.json"), + ), { messageMatches: "import specifier cannot start with ~/", errorCode: ZWaveErrorCodes.Config_Invalid, @@ -454,6 +467,7 @@ test.sequential( }); const content = await readJsonWithTemplate( + fs, path.join(mockDir, "test.json"), ); t.expect(content).toStrictEqual(expected); @@ -487,6 +501,7 @@ test.sequential( }); const content = await readJsonWithTemplate( + fs, path.join(mockDir, "test.json"), ); t.expect(content).toStrictEqual(expected); @@ -524,7 +539,7 @@ test.sequential( await assertZWaveError( t.expect, - () => readJsonWithTemplate(path.join(mockDir, "test.json")), + () => readJsonWithTemplate(fs, path.join(mockDir, "test.json")), { errorCode: ZWaveErrorCodes.Config_CircularImport, }, @@ -545,7 +560,7 @@ test.sequential( await assertZWaveError( t.expect, - () => readJsonWithTemplate(path.join(mockDir, "test.json")), + () => readJsonWithTemplate(fs, path.join(mockDir, "test.json")), {}, ); }, @@ -569,7 +584,7 @@ test.sequential( await assertZWaveError( t.expect, - () => readJsonWithTemplate(path.join(mockDir, "test.json")), + () => readJsonWithTemplate(fs, path.join(mockDir, "test.json")), { errorCode: ZWaveErrorCodes.Config_CircularImport, }, @@ -616,6 +631,7 @@ test.sequential( }; const content = await readJsonWithTemplate( + fs, path.join(mockDir, "test.json"), ); t.expect(content).toStrictEqual(expected); @@ -661,6 +677,7 @@ test.sequential( }; const content = await readJsonWithTemplate( + fs, path.join(mockDir, "test.json"), ); t.expect(content).toStrictEqual(expected); @@ -699,6 +716,7 @@ test.sequential( }; const content = await readJsonWithTemplate( + fs, path.join(mockDir, "test.json"), ); t.expect(content).toStrictEqual(expected); @@ -721,6 +739,7 @@ test.sequential( t.expect, () => readJsonWithTemplate( + fs, path.join(mockDir, rootDir, "test.json"), path.join(mockDir, rootDir), ), diff --git a/packages/config/src/JsonTemplate.ts b/packages/config/src/JsonTemplate.ts index 1e42244415d5..c5554668e95b 100644 --- a/packages/config/src/JsonTemplate.ts +++ b/packages/config/src/JsonTemplate.ts @@ -1,10 +1,13 @@ import { ZWaveError, ZWaveErrorCodes } from "@zwave-js/core/safe"; -import { pathExists } from "@zwave-js/shared"; +import { pathExists, readTextFile } from "@zwave-js/shared"; +import { + type ReadFile, + type ReadFileSystemInfo, +} from "@zwave-js/shared/bindings"; import { getErrorMessage } from "@zwave-js/shared/safe"; import { isArray, isObject } from "alcalzone-shared/typeguards"; import JSON5 from "json5"; -import fs from "node:fs/promises"; -import * as path from "node:path"; +import path from "pathe"; const IMPORT_KEY = "$import"; const importSpecifierRegex = @@ -22,10 +25,11 @@ export function clearTemplateCache(): void { /** Parses a JSON file with $import keys and replaces them with the selected objects */ export async function readJsonWithTemplate( + fs: ReadFileSystemInfo & ReadFile, filename: string, rootDirs?: string | string[], ): Promise> { - if (!(await pathExists(filename))) { + if (!(await pathExists(fs, filename))) { throw new ZWaveError( `Could not open config file ${filename}: not found!`, ZWaveErrorCodes.Config_NotFound, @@ -37,6 +41,7 @@ export async function readJsonWithTemplate( // Try to use the cached versions of the template files to speed up the loading const fileCache = new Map(templateCache); const ret = await readJsonWithTemplateInternal( + fs, filename, undefined, [], @@ -127,6 +132,7 @@ function getImportStack( } async function readJsonWithTemplateInternal( + fs: ReadFileSystemInfo & ReadFile, filename: string, selector: string | undefined, visited: string[], @@ -172,7 +178,7 @@ ${getImportStack(visited, selector)}`, json = fileCache.get(filename)!; } else { try { - const fileContent = await fs.readFile(filename, "utf8"); + const fileContent = await readTextFile(fs, filename, "utf8"); json = JSON5.parse(fileContent); fileCache.set(filename, json); } catch (e) { @@ -188,6 +194,7 @@ ${getImportStack(visited, selector)}`, } // Resolve the JSON imports for (a subset) of the file and return the compound file return resolveJsonImports( + fs, selector ? select(json, selector) : json, filename, [...visited, specifier], @@ -198,6 +205,7 @@ ${getImportStack(visited, selector)}`, /** Replaces all `$import` properties in a JSON object with object spreads of the referenced file/property */ async function resolveJsonImports( + fs: ReadFileSystemInfo & ReadFile, json: Record, filename: string, visited: string[], @@ -225,7 +233,7 @@ async function resolveJsonImports( rootDir, importFilename.slice(2), ); - if (await pathExists(newFilename)) { + if (await pathExists(fs, newFilename)) { break; } else { // Try the next @@ -275,6 +283,7 @@ async function resolveJsonImports( // const importFilename = path.join(path.dirname(filename), val); const imported = await readJsonWithTemplateInternal( + fs, newFilename, selector, visited, @@ -285,6 +294,7 @@ async function resolveJsonImports( } else if (isObject(val)) { // We're looking at an object, recurse into it ret[prop] = await resolveJsonImports( + fs, val, filename, visited, @@ -298,6 +308,7 @@ async function resolveJsonImports( if (isObject(v)) { vals.push( await resolveJsonImports( + fs, v, filename, visited, diff --git a/packages/config/src/Manufacturers.test.ts b/packages/config/src/Manufacturers.test.ts index 8aa75123cd31..1ea08bb036cd 100644 --- a/packages/config/src/Manufacturers.test.ts +++ b/packages/config/src/Manufacturers.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-restricted-globals */ import { pathExists } from "@zwave-js/shared"; import { readFile } from "node:fs/promises"; import { test, vi } from "vitest"; @@ -54,7 +55,9 @@ const pathExistsStub = vi.mocked(pathExists); pathExistsStub.mockClear(); readFileStub.mockClear(); pathExistsStub.mockResolvedValue(true); - readFileStub.mockResolvedValue(`{"0x000e": ` as any); + readFileStub.mockResolvedValue( + Buffer.from(`{"0x000e": `, "utf8"), + ); const configManager = new ConfigManager(); await configManager.loadManufacturers(); @@ -89,9 +92,12 @@ const pathExistsStub = vi.mocked(pathExists); pathExistsStub.mockClear(); pathExistsStub.mockResolvedValue(true); readFileStub.mockResolvedValue( - JSON.stringify({ - "0x000e": "Test", - }) as any, + Buffer.from( + JSON.stringify({ + "0x000e": "Test", + }), + "utf8", + ), ); const configManager = new ConfigManager(); diff --git a/packages/config/src/Manufacturers.ts b/packages/config/src/Manufacturers.ts index de7a516fb0da..3ee7c0cb95f4 100644 --- a/packages/config/src/Manufacturers.ts +++ b/packages/config/src/Manufacturers.ts @@ -1,9 +1,19 @@ import { ZWaveError, ZWaveErrorCodes, isZWaveError } from "@zwave-js/core"; -import { formatId, pathExists, stringify } from "@zwave-js/shared"; +import { + formatId, + pathExists, + readTextFile, + stringify, + writeTextFile, +} from "@zwave-js/shared"; +import { + type ReadFile, + type ReadFileSystemInfo, + type WriteFile, +} from "@zwave-js/shared/bindings"; import { isObject } from "alcalzone-shared/typeguards"; import JSON5 from "json5"; -import fs from "node:fs/promises"; -import path from "node:path"; +import path from "pathe"; import { configDir } from "./utils.js"; import { hexKeyRegex4Digits, throwInvalidConfig } from "./utils_safe.js"; @@ -11,6 +21,7 @@ export type ManufacturersMap = Map; /** @internal */ export async function loadManufacturersInternal( + fs: ReadFileSystemInfo & ReadFile, externalConfigDir?: string, ): Promise { const configPath = path.join( @@ -18,14 +29,14 @@ export async function loadManufacturersInternal( "manufacturers.json", ); - if (!(await pathExists(configPath))) { + if (!(await pathExists(fs, configPath))) { throw new ZWaveError( "The manufacturer config file does not exist!", ZWaveErrorCodes.Config_Invalid, ); } try { - const fileContents = await fs.readFile(configPath, "utf8"); + const fileContents = await readTextFile(fs, configPath, "utf8"); const definition = JSON5.parse(fileContents); if (!isObject(definition)) { throwInvalidConfig( @@ -66,6 +77,7 @@ export async function loadManufacturersInternal( * Write current manufacturers map to json */ export async function saveManufacturersInternal( + fs: WriteFile, manufacturers: ManufacturersMap, ): Promise { const data: Record = {}; @@ -79,5 +91,5 @@ export async function saveManufacturersInternal( } const configPath = path.join(configDir, "manufacturers.json"); - await fs.writeFile(configPath, stringify(data, "\t") + "\n"); + await writeTextFile(fs, configPath, stringify(data, "\t") + "\n"); } diff --git a/packages/config/src/devices/DeviceConfig.ts b/packages/config/src/devices/DeviceConfig.ts index e74c541eacd5..77d339f81abc 100644 --- a/packages/config/src/devices/DeviceConfig.ts +++ b/packages/config/src/devices/DeviceConfig.ts @@ -9,12 +9,18 @@ import { padVersion, pathExists, pick, + readTextFile, stringify, + writeTextFile, } from "@zwave-js/shared"; +import { + type ReadFile, + type ReadFileSystemInfo, + type WriteFile, +} from "@zwave-js/shared/bindings"; import { isArray, isObject } from "alcalzone-shared/typeguards"; import JSON5 from "json5"; -import fs from "node:fs/promises"; -import path from "node:path"; +import path from "pathe"; import semverGt from "semver/functions/gt.js"; import { clearTemplateCache, readJsonWithTemplate } from "../JsonTemplate.js"; import type { ConfigLogger } from "../Logger.js"; @@ -85,13 +91,14 @@ export type DeviceConfigIndex = DeviceConfigIndexEntry[]; export type FulltextDeviceConfigIndex = FulltextDeviceConfigIndexEntry[]; async function hasChangedDeviceFiles( + fs: ReadFileSystemInfo, devicesRoot: string, dir: string, lastChange: Date, ): Promise { // Check if there are any files BUT index.json that were changed // or directories that were modified - const filesAndDirs = await fs.readdir(dir); + const filesAndDirs = await fs.readDir(dir); for (const f of filesAndDirs) { const fullPath = path.join(dir, f); @@ -105,7 +112,12 @@ async function hasChangedDeviceFiles( } else if (stat.isDirectory()) { // we need to go deeper! if ( - await hasChangedDeviceFiles(devicesRoot, fullPath, lastChange) + await hasChangedDeviceFiles( + fs, + devicesRoot, + fullPath, + lastChange, + ) ) { return true; } @@ -119,6 +131,7 @@ async function hasChangedDeviceFiles( * Does not update the index itself. */ async function generateIndex>( + fs: ReadFileSystemInfo & ReadFile, devicesDir: string, isEmbedded: boolean, extractIndexEntries: (config: DeviceConfig) => T[], @@ -128,6 +141,7 @@ async function generateIndex>( clearTemplateCache(); const configFiles = await enumFilesRecursive( + fs, devicesDir, (file) => file.endsWith(".json") @@ -147,11 +161,16 @@ async function generateIndex>( .replaceAll("\\", "/"); // Try parsing the file try { - const config = await DeviceConfig.from(file, isEmbedded, { - rootDir: devicesDir, - fallbackDirs, - relative: true, - }); + const config = await DeviceConfig.from( + fs, + file, + isEmbedded, + { + rootDir: devicesDir, + fallbackDirs, + relative: true, + }, + ); // Add the file to the index index.push( ...extractIndexEntries(config).map((entry) => { @@ -184,19 +203,20 @@ async function generateIndex>( } async function loadDeviceIndexShared>( + fs: ReadFileSystemInfo & ReadFile & WriteFile, devicesDir: string, indexPath: string, extractIndexEntries: (config: DeviceConfig) => T[], logger?: ConfigLogger, ): Promise<(T & { filename: string })[]> { // The index file needs to be regenerated if it does not exist - let needsUpdate = !(await pathExists(indexPath)); + let needsUpdate = !(await pathExists(fs, indexPath)); let index: (T & { filename: string })[] | undefined; let mtimeIndex: Date | undefined; // ...or if cannot be parsed if (!needsUpdate) { try { - const fileContents = await fs.readFile(indexPath, "utf8"); + const fileContents = await readTextFile(fs, indexPath, "utf8"); index = JSON5.parse(fileContents); mtimeIndex = (await fs.stat(indexPath)).mtime; } catch { @@ -219,6 +239,7 @@ async function loadDeviceIndexShared>( // ...or if there were any changes in the file system if (!needsUpdate) { needsUpdate = await hasChangedDeviceFiles( + fs, devicesDir, devicesDir, mtimeIndex!, @@ -234,6 +255,7 @@ async function loadDeviceIndexShared>( if (needsUpdate) { // Read all files from disk and generate an index index = await generateIndex( + fs, devicesDir, true, extractIndexEntries, @@ -241,7 +263,8 @@ async function loadDeviceIndexShared>( ); // Save the index to disk try { - await fs.writeFile( + await writeTextFile( + fs, path.join(indexPath), `// This file is auto-generated. DO NOT edit it by hand if you don't know what you're doing!" ${stringify(index, "\t")} @@ -268,11 +291,13 @@ ${stringify(index, "\t")} * Transparently handles updating the index if necessary */ export async function generatePriorityDeviceIndex( + fs: ReadFileSystemInfo & ReadFile, deviceConfigPriorityDir: string, logger?: ConfigLogger, ): Promise { return ( await generateIndex( + fs, deviceConfigPriorityDir, false, (config) => @@ -304,6 +329,7 @@ export async function generatePriorityDeviceIndex( * Transparently handles updating the index if necessary */ export async function loadDeviceIndexInternal( + fs: ReadFileSystemInfo & ReadFile & WriteFile, logger?: ConfigLogger, externalConfigDir?: string, ): Promise { @@ -312,6 +338,7 @@ export async function loadDeviceIndexInternal( ); return loadDeviceIndexShared( + fs, devicesDir, indexPath, (config) => @@ -334,10 +361,12 @@ export async function loadDeviceIndexInternal( * Transparently handles updating the index if necessary */ export async function loadFulltextDeviceIndexInternal( + fs: ReadFileSystemInfo & ReadFile & WriteFile, logger?: ConfigLogger, ): Promise { // This method is not meant to operate with the external device index! return loadDeviceIndexShared( + fs, embeddedDevicesDir, fulltextIndexPath, (config) => @@ -375,6 +404,7 @@ function isFirmwareVersion(val: any): val is string { /** This class represents a device config entry whose conditional settings have not been evaluated yet */ export class ConditionalDeviceConfig { public static async from( + fs: ReadFileSystemInfo & ReadFile, filename: string, isEmbedded: boolean, options: { @@ -388,10 +418,14 @@ export class ConditionalDeviceConfig { const relativePath = relative ? path.relative(rootDir, filename).replaceAll("\\", "/") : filename; - const json = await readJsonWithTemplate(filename, [ - options.rootDir, - ...(options.fallbackDirs ?? []), - ]); + const json = await readJsonWithTemplate( + fs, + filename, + [ + options.rootDir, + ...(options.fallbackDirs ?? []), + ], + ); return new ConditionalDeviceConfig(relativePath, isEmbedded, json); } @@ -663,6 +697,7 @@ metadata is not an object`, export class DeviceConfig { public static async from( + fs: ReadFileSystemInfo & ReadFile, filename: string, isEmbedded: boolean, options: { @@ -673,6 +708,7 @@ export class DeviceConfig { }, ): Promise { const ret = await ConditionalDeviceConfig.from( + fs, filename, isEmbedded, options, diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index fda4b49e65ca..6f789da50b71 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -10,3 +10,4 @@ export * from "./devices/DeviceMetadata.js"; export * from "./devices/EndpointConfig.js"; export * from "./devices/ParamInformation.js"; export * from "./devices/shared.js"; +export type * from "./traits.js"; diff --git a/packages/config/src/index_safe.ts b/packages/config/src/index_safe.ts index 709b2e58b9b3..a282a820f578 100644 --- a/packages/config/src/index_safe.ts +++ b/packages/config/src/index_safe.ts @@ -2,3 +2,4 @@ export * from "./Logger_safe.js"; export { PACKAGE_VERSION } from "./_version.js"; +export type * from "./traits.js"; diff --git a/packages/config/src/traits.ts b/packages/config/src/traits.ts new file mode 100644 index 000000000000..39544eda6345 --- /dev/null +++ b/packages/config/src/traits.ts @@ -0,0 +1,12 @@ +import { type DeviceConfig } from "./devices/DeviceConfig.js"; + +/** Allows querying device configuration for a node */ +export interface GetDeviceConfig { + getDeviceConfig(nodeId: number): DeviceConfig | undefined; +} + +/** 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; +} diff --git a/packages/config/src/utils.ts b/packages/config/src/utils.ts index bbb56121f9cf..faa65fc5e504 100644 --- a/packages/config/src/utils.ts +++ b/packages/config/src/utils.ts @@ -1,7 +1,19 @@ -import { copyFilesRecursive, formatId, padVersion } from "@zwave-js/shared"; -import fs from "node:fs/promises"; +import { + copyFilesRecursive, + formatId, + padVersion, + readTextFile, + writeTextFile, +} from "@zwave-js/shared"; +import { + type CopyFile, + type ManageDirectory, + type ReadFile, + type ReadFileSystemInfo, + type WriteFile, +} from "@zwave-js/shared/bindings"; import { createRequire } from "node:module"; -import path from "node:path"; +import path from "pathe"; import semverGte from "semver/functions/gte.js"; import semverInc from "semver/functions/inc.js"; import semverLte from "semver/functions/lte.js"; @@ -64,6 +76,7 @@ export type SyncExternalConfigDirResult = * Synchronizes or updates the external config directory and returns whether the directory is in a state that can be used */ export async function syncExternalConfigDir( + fs: ManageDirectory & ReadFileSystemInfo & ReadFile & CopyFile & WriteFile, extConfigDir: string, logger: ConfigLogger, ): Promise { @@ -71,7 +84,7 @@ export async function syncExternalConfigDir( // Make sure the config dir exists try { - await fs.mkdir(extConfigDir, { recursive: true }); + await fs.ensureDir(extConfigDir); } catch { logger.print( `Synchronizing external config dir failed - directory could not be created`, @@ -98,7 +111,11 @@ export async function syncExternalConfigDir( let wipe = false; let externalVersion: string | undefined; try { - externalVersion = await fs.readFile(externalVersionFilename, "utf8"); + externalVersion = await readTextFile( + fs, + externalVersionFilename, + "utf8", + ); if (!semverValid(externalVersion)) { wipe = true; } else if ( @@ -118,14 +135,20 @@ export async function syncExternalConfigDir( // Wipe and override the external dir try { logger.print(`Synchronizing external config dir ${extConfigDir}...`); - await fs.rm(extConfigDir, { recursive: true, force: true }); - await fs.mkdir(extConfigDir, { recursive: true }); + await fs.deleteDir(extConfigDir); + await fs.ensureDir(extConfigDir); await copyFilesRecursive( + fs, configDir, extConfigDir, (src) => src.endsWith(".json"), ); - await fs.writeFile(externalVersionFilename, currentVersion, "utf8"); + await writeTextFile( + fs, + externalVersionFilename, + currentVersion, + "utf8", + ); externalVersion = currentVersion; } catch { // Something went wrong diff --git a/packages/core/package.json b/packages/core/package.json index 2b9f7eff27f7..8a122a5a3e11 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -14,6 +14,11 @@ "import": "./build/esm/index.js", "require": "./build/cjs/index.js" }, + "./bindings/*": { + "@@dev": "./src/bindings/*.ts", + "import": "./build/esm/bindings/*.js", + "require": "./build/cjs/bindings/*.js" + }, "./safe": { "@@dev": "./src/index_safe.ts", "import": "./build/esm/index_safe.js", @@ -120,6 +125,7 @@ "fflate": "^0.8.2", "logform": "^2.6.1", "nrf-intel-hex": "^1.4.0", + "pathe": "^1.1.2", "reflect-metadata": "^0.2.2", "semver": "^7.6.3", "triple-beam": "*", diff --git a/packages/core/src/bindings/fs/node.ts b/packages/core/src/bindings/fs/node.ts new file mode 100644 index 000000000000..8585b121f60d --- /dev/null +++ b/packages/core/src/bindings/fs/node.ts @@ -0,0 +1,151 @@ +import { + fileHandleToReadableStream, + fileHandleToWritableStream, +} from "@zwave-js/shared"; +import type { + FSStats, + FileHandle, + FileSystem, +} from "@zwave-js/shared/bindings"; +import fsp from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; + +/** An implementation of the FileSystem bindings for Node.js */ +export const fs: FileSystem = { + readDir(path: string): Promise { + return fsp.readdir(path); + }, + readFile(path: string): Promise { + return fsp.readFile(path); + }, + writeFile(path: string, data: Uint8Array): Promise { + return fsp.writeFile(path, data); + }, + copyFile(source: string, dest: string): Promise { + return fsp.copyFile(source, dest); + }, + async ensureDir(path: string): Promise { + await fsp.mkdir(path, { recursive: true }); + }, + deleteDir(path: string): Promise { + return fsp.rm(path, { recursive: true, force: true }); + }, + stat(path: string): Promise { + return fsp.stat(path); + }, + async open( + path: string, + flags: { + read: boolean; + write: boolean; + create: boolean; + truncate: boolean; + }, + ): Promise { + let mode = ""; + if (!flags.truncate && !flags.read) { + throw new Error( + "Cannot open a file writeonly without truncating it", + ); + } + if (!flags.write && flags.create) { + throw new Error("Cannot open a file readonly with create flag"); + } + + // FIXME: Figure out what the correct behavior is for each combination of flags + if (flags.read && !flags.write) { + mode = "r"; + } else if (flags.read && flags.write && !flags.create) { + mode = "r+"; + } else if (flags.write && flags.create && flags.truncate) { + mode = flags.read ? "w+" : "w"; + } + + return new NodeFileHandle( + await fsp.open(path, mode), + { + read: flags.read, + write: flags.write, + }, + ); + }, + async makeTempDir(prefix: string): Promise { + return fsp.mkdtemp(path.join(os.tmpdir(), prefix)); + }, +}; + +export class NodeFileHandle implements FileHandle { + public constructor( + handle: fsp.FileHandle, + flags: { read: boolean; write: boolean }, + ) { + this.open = true; + this.handle = handle; + this.flags = flags; + } + + private open: boolean; + private handle: fsp.FileHandle; + private flags: { read: boolean; write: boolean }; + + private _readable?: ReadableStream; + private _writable?: WritableStream; + + public get readable(): ReadableStream { + if (!this.flags.read) { + throw new Error("File is not readable"); + } + if (!this._readable) { + this._readable = fileHandleToReadableStream(this); + } + return this._readable; + } + + public get writable(): WritableStream { + if (!this.flags.write) { + throw new Error("File is not writable"); + } + if (!this._writable) { + this._writable = fileHandleToWritableStream(this); + } + return this._writable; + } + + async close(): Promise { + if (!this.open) return; + this.open = false; + await this.handle.close(); + } + + async read( + position?: number | null, + length?: number, + ): Promise<{ data: Uint8Array; bytesRead: number }> { + if (!this.open) throw new Error("File is not open"); + const ret = await this.handle.read({ + position, + length, + }); + return { + data: ret.buffer.subarray(0, ret.bytesRead), + bytesRead: ret.bytesRead, + }; + } + + async write( + data: Uint8Array, + position?: number | null, + ): Promise<{ bytesWritten: number }> { + if (!this.open) throw new Error("File is not open"); + const ret = await this.handle.write(data, null, null, position); + return { + bytesWritten: ret.bytesWritten, + }; + } + + stat(): Promise { + if (!this.open) throw new Error("File is not open"); + return this.handle.stat(); + } +} diff --git a/packages/core/src/crypto/primitives/primitives.browser.ts b/packages/core/src/crypto/primitives/primitives.browser.ts index f266da35b13a..b63a4281c9a0 100644 --- a/packages/core/src/crypto/primitives/primitives.browser.ts +++ b/packages/core/src/crypto/primitives/primitives.browser.ts @@ -1,6 +1,6 @@ +import { type CryptoPrimitives } from "@zwave-js/shared/bindings"; import { Bytes } from "@zwave-js/shared/safe"; import { BLOCK_SIZE, xor, zeroPad } from "../shared.js"; -import { type CryptoPrimitives } from "./primitives.js"; const webcrypto = typeof process !== "undefined" && (globalThis as any).crypto === undefined diff --git a/packages/core/src/crypto/primitives/primitives.node.ts b/packages/core/src/crypto/primitives/primitives.node.ts index 24d921890ab5..53f1cac35bb0 100644 --- a/packages/core/src/crypto/primitives/primitives.node.ts +++ b/packages/core/src/crypto/primitives/primitives.node.ts @@ -1,7 +1,7 @@ +import { type CryptoPrimitives } from "@zwave-js/shared/bindings"; import { Bytes } from "@zwave-js/shared/safe"; import crypto from "node:crypto"; import { BLOCK_SIZE, zeroPad } from "../shared.js"; -import { type CryptoPrimitives } from "./primitives.js"; // For Node.js, we use the built-in crypto module since it has better support // for some algorithms Z-Wave needs than the Web Crypto API, so we can implement diff --git a/packages/core/src/crypto/primitives/primitives.test.ts b/packages/core/src/crypto/primitives/primitives.test.ts index ca7e2f929af5..48e6271733fc 100644 --- a/packages/core/src/crypto/primitives/primitives.test.ts +++ b/packages/core/src/crypto/primitives/primitives.test.ts @@ -1,6 +1,6 @@ +import { type CryptoPrimitives } from "@zwave-js/shared/bindings"; import { Bytes } from "@zwave-js/shared/safe"; import { type ExpectStatic, test } from "vitest"; -import { type CryptoPrimitives } from "./primitives.js"; function assertBufferEquals( expect: ExpectStatic, diff --git a/packages/core/src/crypto/primitives/primitives.ts b/packages/core/src/crypto/primitives/primitives.ts deleted file mode 100644 index e7a399e2bb90..000000000000 --- a/packages/core/src/crypto/primitives/primitives.ts +++ /dev/null @@ -1,52 +0,0 @@ -export interface CryptoPrimitives { - randomBytes(length: number): Uint8Array; - /** Encrypts a payload using AES-128-ECB */ - encryptAES128ECB( - plaintext: Uint8Array, - key: Uint8Array, - ): Promise; - /** Encrypts a payload using AES-128-CBC */ - encryptAES128CBC( - plaintext: Uint8Array, - key: Uint8Array, - iv: Uint8Array, - ): Promise; - /** Encrypts a payload using AES-128-OFB */ - encryptAES128OFB( - plaintext: Uint8Array, - key: Uint8Array, - iv: Uint8Array, - ): Promise; - /** Decrypts a payload using AES-128-OFB */ - decryptAES128OFB( - ciphertext: Uint8Array, - key: Uint8Array, - iv: Uint8Array, - ): Promise; - /** Decrypts a payload using AES-256-CBC */ - decryptAES256CBC( - ciphertext: Uint8Array, - key: Uint8Array, - iv: Uint8Array, - ): Promise; - /** Encrypts and authenticates a payload using AES-128-CCM */ - encryptAES128CCM( - plaintext: Uint8Array, - key: Uint8Array, - iv: Uint8Array, - additionalData: Uint8Array, - authTagLength: number, - ): Promise<{ ciphertext: Uint8Array; authTag: Uint8Array }>; - /** Decrypts and verifies a payload using AES-128-CCM */ - decryptAES128CCM( - ciphertext: Uint8Array, - key: Uint8Array, - iv: Uint8Array, - additionalData: Uint8Array, - authTag: Uint8Array, - ): Promise<{ plaintext: Uint8Array; authOK: boolean }>; - digest( - algorithm: "md5" | "sha-1" | "sha-256", - data: Uint8Array, - ): Promise; -} diff --git a/packages/core/src/log/shared.ts b/packages/core/src/log/shared.ts index 19d1b97e84aa..0ece6aeb2af3 100644 --- a/packages/core/src/log/shared.ts +++ b/packages/core/src/log/shared.ts @@ -1,6 +1,6 @@ import { flatMap } from "@zwave-js/shared"; import type { Format, TransformFunction } from "logform"; -import * as path from "node:path"; +import path from "pathe"; import { MESSAGE, configs } from "triple-beam"; import winston from "winston"; import DailyRotateFile from "winston-daily-rotate-file"; diff --git a/packages/core/src/log/shared_safe.ts b/packages/core/src/log/shared_safe.ts index d1a77c98bc77..f16b2240ac62 100644 --- a/packages/core/src/log/shared_safe.ts +++ b/packages/core/src/log/shared_safe.ts @@ -2,6 +2,7 @@ import type { TransformableInfo } from "logform"; import type { Logger } from "winston"; import type Transport from "winston-transport"; import type { ValueID } from "../values/_Types.js"; +import { type ControllerLogger } from "./Controller.js"; export const timestampFormatShort = "HH:mm:ss.SSS"; export const timestampPaddingShort = " ".repeat( @@ -114,3 +115,5 @@ export function stringToNodeList(nodes?: string): number[] | undefined { .map((n) => parseInt(n)) .filter((n) => !Number.isNaN(n)); } + +export type LogNode = Pick; diff --git a/packages/core/src/traits/CommandClasses.ts b/packages/core/src/traits/CommandClasses.ts index 06a81c0436de..2a030a0726c1 100644 --- a/packages/core/src/traits/CommandClasses.ts +++ b/packages/core/src/traits/CommandClasses.ts @@ -60,3 +60,28 @@ export interface ModifyCCs { addCC(cc: CommandClasses, info: Partial): void; removeCC(cc: CommandClasses): void; } + +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. + */ + getSupportedCCVersion( + cc: CommandClasses, + nodeId: number, + endpointIndex?: number, + ): number; +} + +export interface GetSafeCCVersion { + /** + * 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. + */ + getSafeCCVersion( + cc: CommandClasses, + nodeId: number, + endpointIndex?: number, + ): number | undefined; +} diff --git a/packages/core/src/traits/Endpoints.ts b/packages/core/src/traits/Endpoints.ts index 36e22a8adc26..5259a7a69a7b 100644 --- a/packages/core/src/traits/Endpoints.ts +++ b/packages/core/src/traits/Endpoints.ts @@ -13,3 +13,15 @@ export interface VirtualEndpointId { readonly nodeId: number | MulticastDestination; readonly index: number; } + +/** 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[]; +} diff --git a/packages/core/src/traits/FileSystem.ts b/packages/core/src/traits/FileSystem.ts index f229bcc76b44..8190c989e853 100644 --- a/packages/core/src/traits/FileSystem.ts +++ b/packages/core/src/traits/FileSystem.ts @@ -1,3 +1,5 @@ +// FIXME: Get rid of this once the legacy FS bindings are removed + /** Defines which methods must be supported by a replacement filesystem */ export interface FileSystem { ensureDir(path: string): Promise; diff --git a/packages/core/src/traits/GetValueDB.ts b/packages/core/src/traits/GetValueDB.ts new file mode 100644 index 000000000000..ba6e15dcdd14 --- /dev/null +++ b/packages/core/src/traits/GetValueDB.ts @@ -0,0 +1,10 @@ +import { type ValueDB } from "../values/ValueDB.js"; + +/** Host application abstractions that provide support for reading and writing values to a database */ +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; + + /** Returns the value DB which belongs to the node with the given ID, or `undefined` if the Value DB cannot be accessed */ + tryGetValueDB(nodeId: number): ValueDB | undefined; +} diff --git a/packages/core/src/traits/HostIDs.ts b/packages/core/src/traits/HostIDs.ts new file mode 100644 index 000000000000..76eb225efd4c --- /dev/null +++ b/packages/core/src/traits/HostIDs.ts @@ -0,0 +1,7 @@ +/** 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; +} diff --git a/packages/core/src/traits/Nodes.ts b/packages/core/src/traits/Nodes.ts index 0bc42429bbeb..d10478472d3d 100644 --- a/packages/core/src/traits/Nodes.ts +++ b/packages/core/src/traits/Nodes.ts @@ -14,16 +14,15 @@ 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 a specific node */ +export interface GetNode { + getNode(nodeId: number): T | undefined; + getNodeOrThrow(nodeId: number): T; } -/** Allows accessing all endpoints */ -export interface GetAllEndpoints { - getAllEndpoints(): T[]; +/** Allows accessing all nodes */ +export interface GetAllNodes { + getAllNodes(): T[]; } /** Allows querying whether a node is a listening, FLiRS or sleeping device */ diff --git a/packages/core/src/traits/index.ts b/packages/core/src/traits/index.ts index 67cbbb817259..ee5a3d5fbd00 100644 --- a/packages/core/src/traits/index.ts +++ b/packages/core/src/traits/index.ts @@ -1,6 +1,8 @@ export type * from "./CommandClasses.js"; export type * from "./Endpoints.js"; export type * from "./FileSystem.js"; +export type * from "./GetValueDB.js"; +export type * from "./HostIDs.js"; export type * from "./Nodes.js"; export type * from "./SecurityClasses.js"; export type * from "./SecurityManagers.js"; diff --git a/packages/flash/package.json b/packages/flash/package.json index 2a0c8961d5da..089446cbd2f0 100644 --- a/packages/flash/package.json +++ b/packages/flash/package.json @@ -35,6 +35,7 @@ }, "dependencies": { "@zwave-js/core": "workspace:*", + "pathe": "^1.1.2", "yargs": "^17.7.2", "zwave-js": "workspace:*" }, diff --git a/packages/flash/src/cli.ts b/packages/flash/src/cli.ts index 63c03647a24f..831f9bcd5ed0 100644 --- a/packages/flash/src/cli.ts +++ b/packages/flash/src/cli.ts @@ -1,7 +1,7 @@ +import { fs } from "@zwave-js/core/bindings/fs/node"; import { ZWaveErrorCodes, isZWaveError } from "@zwave-js/core/safe"; import { wait } from "alcalzone-shared/async"; -import fs from "node:fs/promises"; -import path from "node:path"; +import path from "pathe"; import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import { diff --git a/packages/host/package.json b/packages/host/package.json index 1a58ebf28b25..cc999d113cb3 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -13,11 +13,6 @@ "import": "./build/esm/index.js", "require": "./build/cjs/index.js" }, - "./safe": { - "@@dev": "./src/index_safe.ts", - "import": "./build/esm/index_safe.js", - "require": "./build/cjs/index_safe.js" - }, "./package.json": "./package.json" }, "files": [ diff --git a/packages/host/src/ZWaveHost.ts b/packages/host/src/ZWaveHost.ts deleted file mode 100644 index f684c7da267c..000000000000 --- a/packages/host/src/ZWaveHost.ts +++ /dev/null @@ -1,171 +0,0 @@ -import type { DeviceConfig } from "@zwave-js/config"; -import type { - CCId, - CommandClasses, - ControllerLogger, - FrameType, - MaybeNotKnown, - NodeId, - SecurityClass, - SecurityManagers, - SendCommandOptions, - SendCommandReturnType, - ValueDB, - ValueID, -} from "@zwave-js/core"; -import type { ZWaveHostOptions } from "./ZWaveHostOptions.js"; - -/** 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; -} - -/** 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. - */ - getSupportedCCVersion( - cc: CommandClasses, - nodeId: number, - endpointIndex?: number, - ): number; -} - -export interface GetSafeCCVersion { - /** - * 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. - */ - getSafeCCVersion( - cc: CommandClasses, - nodeId: number, - endpointIndex?: number, - ): 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; - - hasSecurityClass( - nodeId: number, - securityClass: SecurityClass, - ): MaybeNotKnown; - - setSecurityClass( - nodeId: number, - securityClass: SecurityClass, - granted: boolean, - ): void; -} - -/** 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; - - hasSecurityClass( - nodeId: number, - securityClass: SecurityClass, - ): MaybeNotKnown; - - setSecurityClass( - nodeId: number, - securityClass: SecurityClass, - granted: boolean, - ): void; -} - -/** Host application abstractions that provide support for reading and writing values to a database */ -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; - - /** Returns the value DB which belongs to the node with the given ID, or `undefined` if the Value DB cannot be accessed */ - tryGetValueDB(nodeId: number): ValueDB | undefined; -} - -/** Allows accessing a specific node */ -export interface GetNode { - getNode(nodeId: number): T | undefined; - getNodeOrThrow(nodeId: number): T; -} - -/** Allows accessing all nodes */ -export interface GetAllNodes { - getAllNodes(): T[]; -} - -/** 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; -} - -/** 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"]; -} - -export type LogNode = Pick; - -/** Allows scheduling a value refresh (poll) for a later time */ -export interface SchedulePoll { - schedulePoll( - nodeId: number, - valueId: ValueID, - options: NodeSchedulePollOptions, - ): boolean; -} - -export interface NodeSchedulePollOptions { - /** The timeout after which the poll is to be scheduled */ - timeoutMs?: number; - /** - * The expected value that's should be verified with this poll. - * When this value is received in the meantime, the poll will be cancelled. - */ - expectedValue?: unknown; -} diff --git a/packages/host/src/ZWaveHostOptions.ts b/packages/host/src/ZWaveHostOptions.ts deleted file mode 100644 index c09b9dcdac21..000000000000 --- a/packages/host/src/ZWaveHostOptions.ts +++ /dev/null @@ -1,77 +0,0 @@ -export interface ZWaveHostOptions { - /** Specify timeouts in milliseconds */ - timeouts: { - /** - * How long to wait for a poll after setting a value without transition duration - */ - refreshValue: number; - - /** - * How long to wait for a poll after setting a value with transition duration. This doubles as the "fast" delay. - */ - refreshValueAfterTransition: number; - }; - - attempts: { - /** How often the driver should try communication with the controller before giving up */ - controller: number; // [1...3], default: 3 - - /** How often the driver should try sending SendData commands before giving up */ - sendData: number; // [1...5], default: 3 - - /** - * How many attempts should be made for each node interview before giving up - */ - nodeInterview: number; // [1...10], default: 5 - }; - - interview?: { - /** - * Whether all user code should be queried during the interview of the UserCode CC. - * Note that enabling this can cause a lot of traffic during the interview. - */ - queryAllUserCodes?: boolean; - }; - - /** - * Some SET-type commands optimistically update the current value to match the target value - * when the device acknowledges the command. - * - * While this generally makes UIs feel more responsive, it is not necessary for devices which report their status - * on their own and can lead to confusing behavior when dealing with slow devices like blinds. - * - * To disable the optimistic update, set this option to `true`. - * Default: `false` - */ - disableOptimisticValueUpdate?: boolean; - - preferences?: { - /** - * The preferred scales to use when querying sensors. The key is either: - * - the name of a named scale group, e.g. "temperature", which applies to every sensor type that uses this scale group. - * - or the numeric sensor type to specify the scale for a single sensor type - * - * Single-type preferences have a higher priority than named ones. For example, the following preference - * ```js - * { - * temperature: "°F", - * 0x01: "°C", - * } - * ``` - * will result in using the Fahrenheit scale for all temperature sensors, except the air temperature (0x01). - * - * The value must match what is defined in the sensor type config file and contain either: - * - the label (e.g. "Celsius", "Fahrenheit") - * - the unit (e.g. "°C", "°F") - * - or the numeric key of the scale (e.g. 0 or 1). - * - * Default: - * ```js - * { - * temperature: "Celsius" - * } - * ``` - */ - scales: Partial>; - }; -} diff --git a/packages/host/src/index.ts b/packages/host/src/index.ts index 5acc76f975b7..b98d66ede710 100644 --- a/packages/host/src/index.ts +++ b/packages/host/src/index.ts @@ -1,4 +1 @@ -/* eslint-disable @typescript-eslint/consistent-type-exports */ -export * from "./ZWaveHost.js"; -export * from "./ZWaveHostOptions.js"; export * from "./mocks.js"; diff --git a/packages/host/src/index_safe.ts b/packages/host/src/index_safe.ts deleted file mode 100644 index 8fbb0d65dad6..000000000000 --- a/packages/host/src/index_safe.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable @typescript-eslint/consistent-type-exports */ -/* @forbiddenImports external */ - -export * from "./ZWaveHost.js"; -export * from "./ZWaveHostOptions.js"; diff --git a/packages/host/src/mocks.ts b/packages/host/src/mocks.ts index 0996b80934f6..25c09d37cb0d 100644 --- a/packages/host/src/mocks.ts +++ b/packages/host/src/mocks.ts @@ -1,9 +1,16 @@ +import { type GetDeviceConfig } from "@zwave-js/config"; import { type ControlsCC, type EndpointId, + type GetAllNodes, type GetEndpoint, + type GetNode, + type GetSupportedCCVersion, + type GetValueDB, + type HostIDs, type IsCCSecure, type ListenBehavior, + type LogNode, type NodeId, type QuerySecurityClasses, type SetSecurityClass, @@ -13,15 +20,9 @@ import { ZWaveErrorCodes, } from "@zwave-js/core"; import { createThrowingMap } from "@zwave-js/shared"; -import type { - GetAllNodes, - GetDeviceConfig, - GetNode, - GetSupportedCCVersion, - GetValueDB, - HostIDs, - LogNode, -} from "./ZWaveHost.js"; + +// FIXME: At some points this module should be moved into @zwave-js/testing, +// but this doesn't work right now due to circular dependencies export interface CreateTestingHostOptions extends HostIDs, GetDeviceConfig {} @@ -73,36 +74,7 @@ export function createTestingHost( homeId: options.homeId ?? 0x7e570001, ownNodeId: options.ownNodeId ?? 1, 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); }, @@ -115,14 +87,10 @@ export function createTestingHost( 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)) { @@ -140,24 +108,6 @@ export function createTestingHost( tryGetValueDB: (nodeId) => { return ret.getValueDB(nodeId); }, - // 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/convert-json.ts b/packages/maintenance/src/convert-json.ts index e1f53efa5a51..bc47f82dfef3 100644 --- a/packages/maintenance/src/convert-json.ts +++ b/packages/maintenance/src/convert-json.ts @@ -3,9 +3,10 @@ * Execute with `yarn ts packages/maintenance/src/convert-json.ts` */ +import { fs } from "@zwave-js/core/bindings/fs/node"; import { enumFilesRecursive } from "@zwave-js/shared"; import esMain from "es-main"; -import fs from "node:fs/promises"; +import fsp from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { Project, ts } from "ts-morph"; @@ -19,6 +20,7 @@ async function main() { const devicesDir = path.join(__dirname, "../../config/config/devices"); const configFiles = await enumFilesRecursive( + fs, devicesDir, (file) => file.endsWith(".json") @@ -28,7 +30,7 @@ async function main() { ); for (const filename of configFiles) { - const content = await fs.readFile(filename, "utf8"); + const content = await fsp.readFile(filename, "utf8"); const sourceFile = project.createSourceFile(filename, content, { overwrite: true, scriptKind: ts.ScriptKind.JSON, @@ -102,7 +104,7 @@ async function main() { if (didChange) { let output = sourceFile.getFullText(); output = formatWithDprint(filename, output); - await fs.writeFile(filename, output, "utf8"); + await fsp.writeFile(filename, output, "utf8"); } } } diff --git a/packages/maintenance/src/generateTypedDocs.ts b/packages/maintenance/src/generateTypedDocs.ts index 00456ea7c111..6f54c8fabcd9 100644 --- a/packages/maintenance/src/generateTypedDocs.ts +++ b/packages/maintenance/src/generateTypedDocs.ts @@ -3,10 +3,11 @@ */ import { CommandClasses, getCCName } from "@zwave-js/core"; +import { fs } from "@zwave-js/core/bindings/fs/node"; import { enumFilesRecursive, num2hex } from "@zwave-js/shared"; import c from "ansi-colors"; import esMain from "es-main"; -import fs from "node:fs/promises"; +import fsp from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { isMainThread } from "node:worker_threads"; @@ -282,7 +283,7 @@ export async function processDocFile( docFile: string, ): Promise { console.log(`processing ${docFile}...`); - let fileContent = await fs.readFile(docFile, "utf8"); + let fileContent = await fsp.readFile(docFile, "utf8"); const ranges = findImportRanges(fileContent); let hasErrors = false; // Replace from back to start so we can reuse the indizes @@ -314,7 +315,7 @@ ${source} fileContent = fileContent.replaceAll("\r\n", "\n"); fileContent = formatWithDprint(docFile, fileContent); if (!hasErrors) { - await fs.writeFile(docFile, fileContent, "utf8"); + await fsp.writeFile(docFile, fileContent, "utf8"); } return hasErrors; } @@ -322,6 +323,7 @@ ${source} /** Processes all imports, returns true if there was an error */ async function processImports(piscina: Piscina): Promise { const files = await enumFilesRecursive( + fs, path.join(projectRoot, "docs"), (f) => !f.includes("/CCs/") && !f.includes("\\CCs\\") && f.endsWith(".md"), @@ -657,7 +659,7 @@ ${formatValueType(idType)} text = text.replaceAll("\r\n", "\n"); text = formatWithDprint(filename, text); - await fs.writeFile(path.join(ccDocsDir, filename), text, "utf8"); + await fsp.writeFile(path.join(ccDocsDir, filename), text, "utf8"); return { generatedIndex, generatedSidebar }; } @@ -671,7 +673,7 @@ async function generateCCDocs( // Load the index file before it gets deleted const indexFilename = path.join(ccDocsDir, "index.md"); - let indexFileContent = await fs.readFile(indexFilename, "utf8"); + let indexFileContent = await fsp.readFile(indexFilename, "utf8"); const indexAutoGenToken = ""; const indexAutoGenStart = indexFileContent.indexOf(indexAutoGenToken); if (indexAutoGenStart === -1) { @@ -681,8 +683,8 @@ async function generateCCDocs( return false; } - await fs.rm(ccDocsDir, { recursive: true, force: true }); - await fs.mkdir(ccDocsDir, { recursive: true }); + await fsp.rm(ccDocsDir, { recursive: true, force: true }); + await fsp.mkdir(ccDocsDir, { recursive: true }); // Find CC APIs const ccFiles = program.getSourceFiles("packages/cc/src/cc/**/*CC.ts"); @@ -712,10 +714,10 @@ async function generateCCDocs( indexAutoGenStart + indexAutoGenToken.length, ) + generatedIndex; indexFileContent = formatWithDprint("index.md", indexFileContent); - await fs.writeFile(indexFilename, indexFileContent, "utf8"); + await fsp.writeFile(indexFilename, indexFileContent, "utf8"); const sidebarInputFilename = path.join(docsDir, "_sidebar.md"); - let sidebarFileContent = await fs.readFile(sidebarInputFilename, "utf8"); + let sidebarFileContent = await fsp.readFile(sidebarInputFilename, "utf8"); const sidebarAutoGenToken = ""; const sidebarAutoGenStart = sidebarFileContent.indexOf(sidebarAutoGenToken); if (sidebarAutoGenStart === -1) { @@ -730,7 +732,7 @@ async function generateCCDocs( sidebarAutoGenStart + sidebarAutoGenToken.length, ); sidebarFileContent = formatWithDprint("_sidebar.md", sidebarFileContent); - await fs.writeFile( + await fsp.writeFile( path.join(ccDocsDir, "_sidebar.md"), sidebarFileContent, "utf8", diff --git a/packages/maintenance/src/refactorImports.ts b/packages/maintenance/src/refactorImports.ts new file mode 100644 index 000000000000..86dae19f8ba3 --- /dev/null +++ b/packages/maintenance/src/refactorImports.ts @@ -0,0 +1,100 @@ +import fs from "node:fs/promises"; +import { Project, SyntaxKind } from "ts-morph"; + +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; + +const yargsInstance = yargs(hideBin(process.argv)); + +const args = yargsInstance + .strict() + .usage("Import refactor script\n\nUsage: $0 [options]") + .alias("h", "help") + .alias("v", "version") + .wrap(Math.min(100, yargsInstance.terminalWidth())) + .options({ + import: { + alias: "i", + describe: "The import to move", + type: "string", + demandOption: true, + }, + module: { + alias: "m", + describe: "The module specifier to move the import to", + type: "string", + demandOption: true, + }, + typeOnly: { + alias: "t", + describe: "Whether the import should be type-only", + type: "boolean", + default: true, + }, + }) + .parseSync(); + +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.getBaseNameWithoutExtension().endsWith("CC") + )*/; + for (const file of sourceFiles) { + // const filePath = path.relative(process.cwd(), file.getFilePath()); + + // Move import to the correct statements + const importToMove = file.getImportDeclarations().map( + (decl) => + decl.getNamedImports().find((imp) => + imp.getName() === args.import + ), + ).find((imp) => !!imp); + + if (!importToMove) { + continue; + } + + let targetImport = file.getImportDeclaration((decl) => + decl.getModuleSpecifierValue().startsWith(args.module) + ); + if (!targetImport) { + targetImport = file.addImportDeclaration({ + moduleSpecifier: args.module, + namedImports: [], + }); + } + + if (importToMove) { + targetImport.addNamedImport({ + name: importToMove.getName(), + isTypeOnly: args.typeOnly, + }); + + const parent = importToMove.getFirstAncestorByKind( + SyntaxKind.ImportDeclaration, + ); + importToMove.remove(); + + if (parent?.getNamedImports().length === 0) { + parent.remove(); + } + } + + await file.save(); + } +} + +void main().catch(async (e) => { + debugger; + 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/remove-unnecessary.ts b/packages/maintenance/src/remove-unnecessary.ts index ef8c99a42d90..e679ed7fe1c9 100644 --- a/packages/maintenance/src/remove-unnecessary.ts +++ b/packages/maintenance/src/remove-unnecessary.ts @@ -1,9 +1,10 @@ // Script to remove unnecessary min/maxValue from config files +import { fs } from "@zwave-js/core/bindings/fs/node"; import { enumFilesRecursive } from "@zwave-js/shared"; import * as JSONC from "comment-json"; import esMain from "es-main"; -import fs from "node:fs/promises"; +import fsp from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { formatWithDprint } from "./dprint.js"; @@ -14,6 +15,7 @@ async function main() { const devicesDir = path.join(__dirname, "../../config/config/devices"); const configFiles = await enumFilesRecursive( + fs, devicesDir, (file) => file.endsWith(".json") @@ -24,7 +26,7 @@ async function main() { for (const filename of configFiles) { const config = JSONC.parse( - await fs.readFile(filename, "utf8"), + await fsp.readFile(filename, "utf8"), ) as JSONC.CommentObject; if (!config.paramInformation) continue; @@ -47,7 +49,7 @@ async function main() { let output = JSONC.stringify(config, null, "\t"); output = formatWithDprint(filename, output); - await fs.writeFile(filename, output, "utf8"); + await fsp.writeFile(filename, output, "utf8"); } } diff --git a/packages/nvmedit/src/cli.ts b/packages/nvmedit/src/cli.ts index 1d14aead6dc6..a8e99152c2b8 100644 --- a/packages/nvmedit/src/cli.ts +++ b/packages/nvmedit/src/cli.ts @@ -1,7 +1,7 @@ -import { readJSON } from "@zwave-js/shared"; +import { readJSON, writeTextFile } from "@zwave-js/shared"; import { isObject } from "alcalzone-shared/typeguards"; -import fs from "node:fs/promises"; import "reflect-metadata"; +import { fs } from "@zwave-js/core/bindings/fs/node"; import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import { @@ -59,7 +59,7 @@ void yargsInstance process.exit(1); } } - await fs.writeFile(argv.out, JSON.stringify(json, null, "\t")); + await writeTextFile(fs, argv.out, JSON.stringify(json, null, "\t")); console.error(`NVM (JSON) written to ${argv.out}`); process.exit(0); @@ -96,7 +96,7 @@ void yargsInstance const { protocolVersion } = argv; const versionIs500 = /^\d\.\d+$/.test(protocolVersion); - const json = await readJSON(argv.in); + const json = await readJSON(fs, argv.in); const jsonIs500 = json.format === 500; if (versionIs500 && !jsonIs500) { console.error( @@ -156,9 +156,13 @@ Create a backup of the target stick, use the nvm2json command to convert it to J }, }), async (argv) => { - const json500 = await readJSON(argv.in); + const json500 = await readJSON(fs, argv.in); const json700 = json500To700(json500, argv.truncate); - await fs.writeFile(argv.out, JSON.stringify(json700, null, "\t")); + await writeTextFile( + fs, + argv.out, + JSON.stringify(json700, null, "\t"), + ); console.error(`700-series NVM (JSON) written to ${argv.out}`); process.exit(0); @@ -181,9 +185,13 @@ Create a backup of the target stick, use the nvm2json command to convert it to J }, }), async (argv) => { - const json700 = await readJSON(argv.in); + const json700 = await readJSON(fs, argv.in); const json500 = json700To500(json700); - await fs.writeFile(argv.out, JSON.stringify(json500, null, "\t")); + await writeTextFile( + fs, + argv.out, + JSON.stringify(json500, null, "\t"), + ); console.error(`500-series NVM (JSON) written to ${argv.out}`); process.exit(0); diff --git a/packages/nvmedit/src/convert.test.ts b/packages/nvmedit/src/convert.test.ts index b13fd1daae87..49a80973455d 100644 --- a/packages/nvmedit/src/convert.test.ts +++ b/packages/nvmedit/src/convert.test.ts @@ -1,6 +1,6 @@ +import { fs } from "@zwave-js/core/bindings/fs/node"; import { readJSON } from "@zwave-js/shared"; import { cloneDeep } from "@zwave-js/shared/safe"; -import fs from "node:fs"; import fsp from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -31,7 +31,7 @@ function bufferEquals( const suite = "700-series, binary to JSON"; const fixturesDir = path.join(__dirname, "../test/fixtures/nvm_700_binary"); - const files = fs.readdirSync(fixturesDir); + const files = await fsp.readdir(fixturesDir); for (const file of files) { test(`${suite} -> ${file}`, async (t) => { @@ -46,11 +46,12 @@ function bufferEquals( const suite = "700 series, JSON to NVM to JSON round-trip"; const fixturesDir = path.join(__dirname, "../test/fixtures/nvm_700_json"); - const files = fs.readdirSync(fixturesDir); + const files = await fsp.readdir(fixturesDir); for (const file of files) { test(`${suite} -> ${file}`, async (t) => { const jsonInput: NVMJSON = await readJSON( + fs, path.join(fixturesDir, file), ); const nvm = await jsonToNVM( @@ -73,7 +74,7 @@ function bufferEquals( __dirname, "../test/fixtures/nvm_700_invariants", ); - const files = fs.readdirSync(fixturesDir); + const files = await fsp.readdir(fixturesDir); for (const file of files) { test(`${suite} -> ${file}`, async (t) => { @@ -92,7 +93,7 @@ function bufferEquals( const suite = "500-series, binary to JSON"; const fixturesDir = path.join(__dirname, "../test/fixtures/nvm_500_binary"); - const files = fs.readdirSync(fixturesDir); + const files = await fsp.readdir(fixturesDir); for (const file of files) { test(`${suite} -> ${file}`, async (t) => { @@ -110,7 +111,7 @@ function bufferEquals( __dirname, "../test/fixtures/nvm_500_invariants", ); - const files = fs.readdirSync(fixturesDir); + const files = await fsp.readdir(fixturesDir); // For debugging purposes // function toHex(buffer: Buffer): string { @@ -148,11 +149,12 @@ function bufferEquals( const suite = "500 to 700 series JSON conversion"; const fixturesDir = path.join(__dirname, "../test/fixtures/nvm_500_json"); - const files = fs.readdirSync(fixturesDir); + const files = await fsp.readdir(fixturesDir); for (const file of files) { test(`${suite} -> ${file}`, async (t) => { const json500: NVM500JSON = await readJSON( + fs, path.join(fixturesDir, file), ); const json700 = json500To700(json500, true); @@ -165,11 +167,12 @@ function bufferEquals( const suite = "500 to 700 to 500 series JSON round-trip"; const fixturesDir = path.join(__dirname, "../test/fixtures/nvm_500_json"); - const files = fs.readdirSync(fixturesDir); + const files = await fsp.readdir(fixturesDir); for (const file of files) { test(`${suite} -> ${file}`, async (t) => { const json500: NVM500JSON = await readJSON( + fs, path.join(fixturesDir, file), ); const json700 = json500To700(json500, true); diff --git a/packages/serial/package.json b/packages/serial/package.json index 0310a2b76928..6a6a68eec025 100644 --- a/packages/serial/package.json +++ b/packages/serial/package.json @@ -27,6 +27,11 @@ "import": "./build/esm/index_mock.js", "require": "./build/cjs/index_mock.js" }, + "./bindings/*": { + "@@dev": "./src/bindings/*.ts", + "import": "./build/esm/bindings/*.js", + "require": "./build/cjs/bindings/*.js" + }, "./package.json": "./package.json" }, "keywords": [], diff --git a/packages/serial/src/bindings/node.ts b/packages/serial/src/bindings/node.ts new file mode 100644 index 000000000000..fd5affff8423 --- /dev/null +++ b/packages/serial/src/bindings/node.ts @@ -0,0 +1,60 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { SerialPort } from "serialport"; +import { type EnumeratedPort, type Serial } from "../serialport/Bindings.js"; +import { createNodeSerialPortFactory } from "../serialport/NodeSerialPort.js"; +import { createNodeSocketFactory } from "../serialport/NodeSocket.js"; + +/** An implementation of the Serial bindings for Node.js */ +export const serial: Serial = { + createFactoryByPath(path) { + if (path.startsWith("tcp://")) { + const url = new URL(path); + return Promise.resolve(createNodeSocketFactory({ + host: url.hostname, + port: parseInt(url.port), + })); + } else { + return Promise.resolve(createNodeSerialPortFactory( + path, + )); + } + }, + + async list() { + // Put symlinks to the serial ports first if possible + const ret: EnumeratedPort[] = []; + if (os.platform() === "linux") { + const dir = "/dev/serial/by-id"; + const symlinks = await fs.readdir(dir).catch(() => []); + + for (const l of symlinks) { + try { + const fullPath = path.join(dir, l); + const target = path.join( + dir, + await fs.readlink(fullPath), + ); + if (!target.startsWith("/dev/tty")) continue; + + ret.push({ + type: "link", + path: fullPath, + }); + } catch { + // Ignore. The target might not exist or we might not have access. + } + } + } + + // Then the actual serial ports + const ports = await SerialPort.list(); + ret.push(...ports.map((port) => ({ + type: "tty" as const, + path: port.path, + }))); + + return ret; + }, +}; diff --git a/packages/serial/src/index.ts b/packages/serial/src/index.ts index 295b05ed49d8..75f85dbbd231 100644 --- a/packages/serial/src/index.ts +++ b/packages/serial/src/index.ts @@ -11,6 +11,7 @@ export * from "./parsers/SerialAPIParser.js"; export * from "./parsers/ZWaveSerialFrame.js"; export * from "./parsers/ZnifferSerialFrame.js"; export * from "./plumbing/Faucet.js"; +export type * from "./serialport/Bindings.js"; export * from "./serialport/LegacyBindingWrapper.js"; export * from "./serialport/NodeSerialPort.js"; export * from "./serialport/NodeSocket.js"; diff --git a/packages/serial/src/index_safe.ts b/packages/serial/src/index_safe.ts index 53faed97feb5..19512f14a5d9 100644 --- a/packages/serial/src/index_safe.ts +++ b/packages/serial/src/index_safe.ts @@ -4,3 +4,4 @@ export type { SerialLogContext } from "./log/Logger_safe.js"; export * from "./message/Constants.js"; export * from "./message/MessageHeaders.js"; export * from "./message/SuccessIndicator.js"; +export type * from "./serialport/Bindings.js"; diff --git a/packages/serial/src/message/Message.ts b/packages/serial/src/message/Message.ts index c10966234ddc..a4c3a3c39049 100644 --- a/packages/serial/src/message/Message.ts +++ b/packages/serial/src/message/Message.ts @@ -1,4 +1,8 @@ +import { type GetDeviceConfig } from "@zwave-js/config"; import { + type GetNode, + type GetSupportedCCVersion, + type HostIDs, type MaybeNotKnown, type MessageOrCCLogEntry, type MessagePriority, @@ -12,12 +16,6 @@ import { highResTimestamp, } from "@zwave-js/core"; import { createReflectionDecorator } from "@zwave-js/core/reflection"; -import type { - GetDeviceConfig, - GetNode, - GetSupportedCCVersion, - HostIDs, -} from "@zwave-js/host"; import { Bytes, type JSONObject, diff --git a/packages/serial/src/serialapi/application/ApplicationCommandRequest.ts b/packages/serial/src/serialapi/application/ApplicationCommandRequest.ts index 804600e7163d..ae3058947a58 100644 --- a/packages/serial/src/serialapi/application/ApplicationCommandRequest.ts +++ b/packages/serial/src/serialapi/application/ApplicationCommandRequest.ts @@ -1,4 +1,4 @@ -import { type CommandClass } from "@zwave-js/cc"; +import { type CCEncodingContext, type CommandClass } from "@zwave-js/cc"; import { type FrameType, type MessageOrCCLogEntry, @@ -9,7 +9,6 @@ import { encodeNodeID, parseNodeID, } from "@zwave-js/core"; -import { type CCEncodingContext } from "@zwave-js/host"; import { FunctionType, Message, diff --git a/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.ts b/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.ts index b7d78bee13ae..f26cc5ebd6d0 100644 --- a/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.ts +++ b/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.ts @@ -1,4 +1,4 @@ -import { type CommandClass } from "@zwave-js/cc"; +import { type CCEncodingContext, type CommandClass } from "@zwave-js/cc"; import { type FrameType, type MessageOrCCLogEntry, @@ -15,7 +15,6 @@ import { parseNodeBitMask, parseNodeID, } from "@zwave-js/core"; -import { type CCEncodingContext } from "@zwave-js/host"; import { FunctionType, Message, diff --git a/packages/serial/src/serialapi/network-mgmt/AddNodeToNetworkRequest.ts b/packages/serial/src/serialapi/network-mgmt/AddNodeToNetworkRequest.ts index 5062690bdd57..29f57535d7ef 100644 --- a/packages/serial/src/serialapi/network-mgmt/AddNodeToNetworkRequest.ts +++ b/packages/serial/src/serialapi/network-mgmt/AddNodeToNetworkRequest.ts @@ -1,6 +1,7 @@ import { type BasicDeviceClass, type CommandClasses, + type GetAllNodes, type ListenBehavior, type MessageOrCCLogEntry, MessagePriority, @@ -14,7 +15,6 @@ import { parseNodeID, parseNodeUpdatePayload, } from "@zwave-js/core"; -import type { GetAllNodes } from "@zwave-js/host"; import type { MessageEncodingContext, MessageParsingContext, diff --git a/packages/serial/src/serialapi/transport/SendDataBridgeMessages.ts b/packages/serial/src/serialapi/transport/SendDataBridgeMessages.ts index 2262475f3e2c..6a01a1cf62c5 100644 --- a/packages/serial/src/serialapi/transport/SendDataBridgeMessages.ts +++ b/packages/serial/src/serialapi/transport/SendDataBridgeMessages.ts @@ -1,4 +1,4 @@ -import type { CommandClass } from "@zwave-js/cc"; +import type { CCEncodingContext, CommandClass } from "@zwave-js/cc"; import { MAX_NODES, type MessageOrCCLogEntry, @@ -14,7 +14,6 @@ import { encodeNodeID, parseNodeID, } from "@zwave-js/core"; -import type { CCEncodingContext } from "@zwave-js/host"; import type { MessageEncodingContext, MessageParsingContext, diff --git a/packages/serial/src/serialapi/transport/SendDataMessages.ts b/packages/serial/src/serialapi/transport/SendDataMessages.ts index 4e376d7f10f7..2e137b68d194 100644 --- a/packages/serial/src/serialapi/transport/SendDataMessages.ts +++ b/packages/serial/src/serialapi/transport/SendDataMessages.ts @@ -1,4 +1,4 @@ -import { type CommandClass } from "@zwave-js/cc"; +import { type CCEncodingContext, type CommandClass } from "@zwave-js/cc"; import { MAX_NODES, type MessageOrCCLogEntry, @@ -15,7 +15,6 @@ import { encodeNodeID, parseNodeID, } from "@zwave-js/core"; -import type { CCEncodingContext } from "@zwave-js/host"; import { FunctionType, Message, diff --git a/packages/serial/src/serialport/Bindings.ts b/packages/serial/src/serialport/Bindings.ts new file mode 100644 index 000000000000..4ed6761d71ab --- /dev/null +++ b/packages/serial/src/serialport/Bindings.ts @@ -0,0 +1,24 @@ +import { type ZWaveSerialBindingFactory } from "./ZWaveSerialStream.js"; + +export type EnumeratedPort = { + type: "link"; + path: string; +} | { + type: "tty"; + path: string; +} | { + type: "socket"; + path: string; +} | { + type: "custom"; + factory: ZWaveSerialBindingFactory; +}; + +/** Abstractions to interact with serial ports on different platforms */ +export interface Serial { + /** Create a binding factory from the given path, if supported by the platform */ + createFactoryByPath?: (path: string) => Promise; + + /** List the available serial ports, if supported by the platform */ + list?: () => Promise; +} diff --git a/packages/shared/package.json b/packages/shared/package.json index e5d333436f02..cffab980e8e5 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -19,6 +19,11 @@ "import": "./build/esm/index_safe.js", "require": "./build/cjs/index_safe.js" }, + "./bindings": { + "@@dev": "./src/bindings.ts", + "import": "./build/esm/bindings.js", + "require": "./build/cjs/bindings.js" + }, "./package.json": "./package.json" }, "sideEffects": false, @@ -46,7 +51,8 @@ "node": ">= 18" }, "dependencies": { - "alcalzone-shared": "^5.0.0" + "alcalzone-shared": "^5.0.0", + "pathe": "^1.1.2" }, "scripts": { "build": "tsc -b tsconfig.build.json --pretty && yarn postbuild", diff --git a/packages/shared/src/bindings.ts b/packages/shared/src/bindings.ts new file mode 100644 index 000000000000..02d0b712782f --- /dev/null +++ b/packages/shared/src/bindings.ts @@ -0,0 +1,137 @@ +// Definitions for runtime-agnostic low level bindings like cryptography, +// file system access, etc. + +import { type ReadableWritablePair } from "node:stream/web"; + +export interface CryptoPrimitives { + randomBytes(length: number): Uint8Array; + /** Encrypts a payload using AES-128-ECB */ + encryptAES128ECB( + plaintext: Uint8Array, + key: Uint8Array, + ): Promise; + /** Encrypts a payload using AES-128-CBC */ + encryptAES128CBC( + plaintext: Uint8Array, + key: Uint8Array, + iv: Uint8Array, + ): Promise; + /** Encrypts a payload using AES-128-OFB */ + encryptAES128OFB( + plaintext: Uint8Array, + key: Uint8Array, + iv: Uint8Array, + ): Promise; + /** Decrypts a payload using AES-128-OFB */ + decryptAES128OFB( + ciphertext: Uint8Array, + key: Uint8Array, + iv: Uint8Array, + ): Promise; + /** Decrypts a payload using AES-256-CBC */ + decryptAES256CBC( + ciphertext: Uint8Array, + key: Uint8Array, + iv: Uint8Array, + ): Promise; + /** Encrypts and authenticates a payload using AES-128-CCM */ + encryptAES128CCM( + plaintext: Uint8Array, + key: Uint8Array, + iv: Uint8Array, + additionalData: Uint8Array, + authTagLength: number, + ): Promise<{ ciphertext: Uint8Array; authTag: Uint8Array }>; + /** Decrypts and verifies a payload using AES-128-CCM */ + decryptAES128CCM( + ciphertext: Uint8Array, + key: Uint8Array, + iv: Uint8Array, + additionalData: Uint8Array, + authTag: Uint8Array, + ): Promise<{ plaintext: Uint8Array; authOK: boolean }>; + digest( + algorithm: "md5" | "sha-1" | "sha-256", + data: Uint8Array, + ): Promise; +} + +export interface FSStats { + isDirectory(): boolean; + isFile(): boolean; + mtime: Date; + size: number; +} + +export interface FileHandle + extends ReadableWritablePair +{ + close(): Promise; + read( + position?: number | null, + length?: number, + ): Promise<{ data: Uint8Array; bytesRead: number }>; + write( + data: Uint8Array, + position?: number | null, + ): Promise<{ bytesWritten: number }>; + stat(): Promise; +} + +export interface ReadFile { + /** Reads the given file */ + readFile(path: string): Promise; +} + +export interface WriteFile { + /** Writes the given data to a file */ + writeFile(path: string, data: Uint8Array): Promise; +} + +export interface CopyFile { + /** Copies a file */ + copyFile(source: string, dest: string): Promise; +} + +export interface ReadFileSystemInfo { + /** Lists files and subdirectories in the given directory */ + readDir(path: string): Promise; + /** Returns information about a file or directory, or throws if it does not exist */ + stat(path: string): Promise; +} + +export interface ManageDirectory { + /** Recursively creates a directory and all its parent directories that do not exist */ + ensureDir(path: string): Promise; + /** Deletes a directory and all its contents */ + deleteDir(path: string): Promise; +} + +export interface MakeTempDirectory { + /** Create a temporary directory with the given prefix in a suitable location and return its path */ + makeTempDir(prefix: string): Promise; +} + +export interface OpenFile { + /** Opens a file handle */ + open(path: string, flags: { + // FIXME: Define expected behavior for each flag + read: boolean; + write: boolean; + create: boolean; + truncate: boolean; + }): Promise; +} + +export interface FileSystem + extends + ReadFile, + WriteFile, + CopyFile, + OpenFile, + ReadFileSystemInfo, + ManageDirectory, + MakeTempDirectory +{} + +export type Platform = "linux" | "darwin" | "win32" | "browser" | "other"; diff --git a/packages/shared/src/docker.ts b/packages/shared/src/docker.ts index a76b1c4e0cee..c5b41b0d3127 100644 --- a/packages/shared/src/docker.ts +++ b/packages/shared/src/docker.ts @@ -1,10 +1,11 @@ // Shamelessly copied from https://github.com/sindresorhus/is-docker -import fs from "node:fs"; +import { createRequire } from "node:module"; +const require = createRequire(import.meta.url); function hasDockerEnv(): boolean { try { - fs.statSync("/.dockerenv"); + require("node:fs").statSync("/.dockerenv"); return true; } catch { return false; @@ -13,7 +14,9 @@ function hasDockerEnv(): boolean { function hasDockerCGroup(): boolean { try { - return fs.readFileSync("/proc/self/cgroup", "utf8").includes("docker"); + return require("node:fs") + .readFileSync("/proc/self/cgroup", "utf8") + .includes("docker"); } catch { return false; } @@ -21,7 +24,10 @@ function hasDockerCGroup(): boolean { let _isDocker: boolean | undefined; -/** Check if the process is running inside a Docker container */ +/** + * Check if the process is running inside a Docker container + * @deprecated Use `is-docker` package instead, or copy this code into your project + */ export function isDocker(): boolean { if (_isDocker === undefined) { _isDocker = hasDockerEnv() || hasDockerCGroup(); diff --git a/packages/shared/src/fs.ts b/packages/shared/src/fs.ts index d45a3f48b16b..36a6916d4f68 100644 --- a/packages/shared/src/fs.ts +++ b/packages/shared/src/fs.ts @@ -1,19 +1,31 @@ -import fs from "node:fs/promises"; -import path from "node:path"; +import { type ReadableWritablePair } from "node:stream/web"; +import path from "pathe"; +import { Bytes } from "./Bytes.js"; +import { + type CopyFile, + type FileHandle, + type ManageDirectory, + type ReadFile, + type ReadFileSystemInfo, + type WriteFile, +} from "./bindings.js"; import { getErrorMessage } from "./errors.js"; export async function enumFilesRecursive( + fs: ReadFileSystemInfo, rootDir: string, predicate?: (filename: string) => boolean, ): Promise { const ret: string[] = []; try { - const filesAndDirs = await fs.readdir(rootDir); + const filesAndDirs = await fs.readDir(rootDir); for (const f of filesAndDirs) { const fullPath = path.join(rootDir, f); if ((await fs.stat(fullPath)).isDirectory()) { - ret.push(...(await enumFilesRecursive(fullPath, predicate))); + ret.push( + ...(await enumFilesRecursive(fs, fullPath, predicate)), + ); } else if (predicate == undefined || predicate(fullPath)) { ret.push(fullPath); } @@ -28,26 +40,88 @@ export async function enumFilesRecursive( } export async function copyFilesRecursive( + fs: ManageDirectory & CopyFile & ReadFileSystemInfo, sourceDir: string, targetDir: string, predicate?: (filename: string) => boolean, ): Promise { - const files = await enumFilesRecursive(sourceDir, predicate); + const files = await enumFilesRecursive(fs, sourceDir, predicate); for (const file of files) { const relative = path.relative(sourceDir, file); const target = path.join(targetDir, relative); - await fs.mkdir(path.dirname(target), { recursive: true }); + await fs.ensureDir(path.dirname(target)); await fs.copyFile(file, target); } } -export async function readJSON(filename: string): Promise { - const data = await fs.readFile(filename, "utf8"); - return JSON.parse(data); +export async function readTextFile( + fs: ReadFile, + filename: string, + encoding: BufferEncoding = "utf8", +): Promise { + const buffer = await fs.readFile(filename); + return Bytes.view(buffer).toString(encoding); } -export async function pathExists(filename: string): Promise { - return fs.access(filename) - .then(() => true) - .catch(() => false); +export async function writeTextFile( + fs: WriteFile, + filename: string, + content: string, + encoding: BufferEncoding = "utf8", +): Promise { + const buffer = Bytes.from(content, encoding); + await fs.writeFile(filename, buffer); +} + +export async function readJSON( + fs: ReadFile, + filename: string, +): Promise { + const content = await readTextFile(fs, filename); + return JSON.parse(content); +} + +export async function pathExists( + fs: ReadFileSystemInfo, + filename: string, +): Promise { + try { + await fs.stat(filename); + return true; + } catch { + return false; + } +} + +export function fileHandleToWritableStream( + handle: Omit, +): WritableStream { + return new WritableStream({ + async write(chunk) { + while (chunk.length > 0) { + const { bytesWritten } = await handle.write(chunk); + chunk = chunk.subarray(bytesWritten); + } + }, + }); +} + +export function fileHandleToReadableStream( + handle: Omit, +): ReadableStream { + return new ReadableStream({ + async pull(controller) { + const { data } = await handle.read(null, 16 * 1024); + controller.enqueue(data); + }, + }); +} + +export function fileHandleToStreams( + handle: Omit, +): ReadableWritablePair { + return { + readable: fileHandleToReadableStream(handle), + writable: fileHandleToWritableStream(handle), + }; } diff --git a/packages/testing/src/MockNode.ts b/packages/testing/src/MockNode.ts index 0cd42563046c..68dce8130bb0 100644 --- a/packages/testing/src/MockNode.ts +++ b/packages/testing/src/MockNode.ts @@ -1,4 +1,4 @@ -import type { CommandClass } from "@zwave-js/cc"; +import type { CCEncodingContext, CommandClass } from "@zwave-js/cc"; import { type CommandClassInfo, type CommandClasses, @@ -8,7 +8,6 @@ import { type SecurityManagers, securityClassOrder, } from "@zwave-js/core"; -import type { CCEncodingContext } from "@zwave-js/host"; import { TimedExpectation } from "@zwave-js/shared"; import { isDeepStrictEqual } from "node:util"; import type { CCIdToCapabilities } from "./CCSpecificCapabilities.js"; diff --git a/packages/zwave-js/package.json b/packages/zwave-js/package.json index 210162076a90..8673a0216cd2 100644 --- a/packages/zwave-js/package.json +++ b/packages/zwave-js/package.json @@ -88,6 +88,7 @@ "ts": "tsx --conditions=@@dev", "codegen": "yarn ts maintenance/codegen.ts", "build": "tsc -b tsconfig.build.json --pretty && yarn postbuild", + "watch": "tsc -b tsconfig.build.json --pretty --watch", "postbuild": "yarn esm2cjs --in build/esm --out build/cjs -l error -t node18", "clean": "del-cli build/ \"*.tsbuildinfo\"", "extract-api": "yarn api-extractor run", @@ -113,6 +114,7 @@ "ky": "^1.7.2", "mdns-server": "^1.0.11", "p-queue": "^8.0.1", + "pathe": "^1.1.2", "proper-lockfile": "^4.1.2", "reflect-metadata": "^0.2.2", "semver": "^7.6.3", diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index afaaeb18c6dc..20e21cec3f04 100644 --- a/packages/zwave-js/src/lib/driver/Driver.ts +++ b/packages/zwave-js/src/lib/driver/Driver.ts @@ -1,18 +1,23 @@ import { JsonlDB, type JsonlDBOptions } from "@alcalzone/jsonl-db"; import { type CCAPIHost, + type CCEncodingContext, + type CCParsingContext, CRC16CC, CRC16CCCommandEncapsulation, CommandClass, type FirmwareUpdateResult, type InterviewContext, + type InterviewOptions, InvalidCC, KEXFailType, MultiChannelCC, NoOperationCC, type PersistValuesContext, type Powerlevel, + type RefreshValueTimeouts, type RefreshValuesContext, + type SchedulePollOptions, Security2CC, Security2CCCommandsSupportedGet, Security2CCCommandsSupportedReport, @@ -35,6 +40,7 @@ import { TransportServiceCCSegmentWait, type TransportServiceCCSubsequentSegment, TransportServiceTimeouts, + type UserPreferences, VersionCommand, WakeUpCCNoMoreInformation, WakeUpCCValues, @@ -53,6 +59,7 @@ import { ControllerStatus, Duration, EncapsulationFlags, + type HostIDs, type KeyPair, type LogConfig, type LogNodeOptions, @@ -106,16 +113,10 @@ import { timespan, wasControllerReset, } from "@zwave-js/core"; -import type { - CCEncodingContext, - CCParsingContext, - HostIDs, - NodeSchedulePollOptions, - ZWaveHostOptions, -} from "@zwave-js/host"; import { type BootloaderChunk, BootloaderChunkType, + type EnumeratedPort, FunctionType, type HasNodeId, Message, @@ -131,8 +132,6 @@ import { type ZWaveSerialPortImplementation, type ZWaveSerialStream, ZWaveSerialStreamFactory, - createNodeSerialPortFactory, - createNodeSocketFactory, getDefaultPriority, hasNodeId, isSuccessIndicator, @@ -165,6 +164,17 @@ import { isSendDataTransmitReport, isTransmitReport, } from "@zwave-js/serial/serialapi"; +import { + SendTestFrameRequest, + SendTestFrameTransmitReport, +} from "@zwave-js/serial/serialapi"; +import { + type CommandRequest, + type ContainsCC, + containsCC, + containsSerializedCC, + isCommandRequest, +} from "@zwave-js/serial/serialapi"; import { AsyncQueue, Bytes, @@ -180,9 +190,12 @@ import { mergeDeep, noop, num2hex, - pathExists, pick, } from "@zwave-js/shared"; +import { + type ReadFile, + type ReadFileSystemInfo, +} from "@zwave-js/shared/bindings"; import { distinct } from "alcalzone-shared/arrays"; import { wait } from "alcalzone-shared/async"; import { @@ -190,11 +203,9 @@ import { createDeferredPromise, } from "alcalzone-shared/deferred-promise"; import { isArray, isObject } from "alcalzone-shared/typeguards"; -import fs from "node:fs/promises"; -import os from "node:os"; -import path from "node:path"; import * as util from "node:util"; -import { SerialPort } from "serialport"; +import path from "pathe"; +import { PACKAGE_NAME, PACKAGE_VERSION } from "../_version.js"; import { ZWaveController } from "../controller/Controller.js"; import { InclusionState, RemoveNodeReason } from "../controller/Inclusion.js"; import { DriverLogger } from "../log/Driver.js"; @@ -207,23 +218,10 @@ import { type ZWaveNotificationCallback, zWaveNodeEvents, } from "../node/_Types.js"; - -import { - SendTestFrameRequest, - SendTestFrameTransmitReport, -} from "@zwave-js/serial/serialapi"; -import { - type CommandRequest, - type ContainsCC, - containsCC, - containsSerializedCC, - isCommandRequest, -} from "@zwave-js/serial/serialapi"; -import { PACKAGE_NAME, PACKAGE_VERSION } from "../_version.js"; import { type ZWaveNodeBase } from "../node/mixins/00_Base.js"; import { type NodeWakeup } from "../node/mixins/30_Wakeup.js"; import { type NodeValues } from "../node/mixins/40_Values.js"; -import { type SchedulePoll } from "../node/mixins/60_ScheduledPoll.js"; +import { type NodeSchedulePoll } from "../node/mixins/60_ScheduledPoll.js"; import { reportMissingDeviceConfig } from "../telemetry/deviceConfig.js"; import { type AppInfo, @@ -317,20 +315,6 @@ const defaultOptions: ZWaveOptions = { queryAllUserCodes: false, }, storage: { - driver: { - async ensureDir(path) { - await fs.mkdir(path, { recursive: true }); - }, - pathExists(path) { - return pathExists(path); - }, - readFile(file, encoding) { - return fs.readFile(file, { encoding }); - }, - writeFile(file, data, options) { - return fs.writeFile(file, data, options); - }, - }, cacheDir: path.join(process.cwd(), "cache"), lockDir: process.env.ZWAVEJS_LOCK_DIRECTORY, throttle: "normal", @@ -619,6 +603,40 @@ function assertValidCCs(container: ContainsCC): void { } } +function wrapLegacyFSDriverForCacheMigrationOnly( + legacy: import("@zwave-js/core/traits").FileSystem, +): ReadFileSystemInfo & ReadFile { + // This usage only needs readFile and checking if a file exists + // Every other usage will throw! + return { + async readFile(path) { + const text = await legacy.readFile(path, "utf8"); + return Bytes.from(text, "utf8"); + }, + async stat(path) { + if (await legacy.pathExists(path)) { + return { + isDirectory() { + return false; + }, + isFile() { + return true; + }, + mtime: new Date(), + size: 0, + }; + } else { + throw new Error("File not found"); + } + }, + readDir(_path) { + return Promise.reject( + new Error("Not implemented for the legacy FS driver"), + ); + }, + }; +} + // Strongly type the event emitter events export interface DriverEventCallbacks extends PrefixedNodeEvents { "driver ready": () => void; @@ -1165,7 +1183,7 @@ export class Driver extends TypedEventTarget * * Not intended to be used by applications */ - public getUserPreferences(): ZWaveHostOptions["preferences"] { + public getUserPreferences(): UserPreferences { return this._options.preferences; } @@ -1174,7 +1192,7 @@ export class Driver extends TypedEventTarget * * Not intended to be used by applications */ - public getInterviewOptions(): ZWaveHostOptions["interview"] { + public getInterviewOptions(): InterviewOptions { return this._options.interview; } @@ -1183,8 +1201,12 @@ export class Driver extends TypedEventTarget * * Not intended to be used by applications */ - public getCommunicationTimeouts(): ZWaveHostOptions["timeouts"] { - return this._options.timeouts; + public getRefreshValueTimeouts(): RefreshValueTimeouts { + return { + refreshValue: this._options.timeouts.refreshValue, + refreshValueAfterTransition: + this._options.timeouts.refreshValueAfterTransition, + }; } /** @@ -1199,43 +1221,38 @@ export class Driver extends TypedEventTarget local?: boolean; remote?: boolean; } = {}): Promise { - const symlinkedPorts: string[] = []; - const localPorts: string[] = []; - const remotePorts: string[] = []; - if (local) { - // Put symlinks to the serial ports first if possible - if (os.platform() === "linux") { - const dir = "/dev/serial/by-id"; - const symlinks = await fs.readdir(dir).catch(() => []); - - for (const l of symlinks) { - try { - const fullPath = path.join(dir, l); - const target = path.join( - dir, - await fs.readlink(fullPath), - ); - if (!target.startsWith("/dev/tty")) continue; + const ret: (EnumeratedPort & { path: string })[] = []; - symlinkedPorts.push(fullPath); - } catch { - // Ignore. The target might not exist or we might not have access. - } - } - } + // Ideally we'd use the host bindings used by the driver, but we can't access them in a static method - // Then the actual serial ports - const ports = await SerialPort.list(); - localPorts.push(...ports.map((port) => port.path)); + const bindings = + (await import("@zwave-js/serial/bindings/node")).serial; + if (local && typeof bindings.list === "function") { + for (const port of await bindings.list()) { + if (port.type === "custom") continue; + ret.push(port); + } } if (remote) { const ports = await discoverRemoteSerialPorts(); if (ports) { - remotePorts.push(...ports.map((p) => p.port)); + ret.push(...ports.map((p) => ({ + type: "socket" as const, + path: p.port, + }))); } } - return distinct([...symlinkedPorts, ...remotePorts, ...localPorts]); + const portOrder: EnumeratedPort["type"][] = ["link", "socket", "tty"]; + + ret.sort((a, b) => { + const typeA = portOrder.indexOf(a.type); + const typeB = portOrder.indexOf(b.type); + if (typeA !== typeB) return typeA - typeB; + return a.path.localeCompare(b.path); + }); + + return distinct(ret.map((p) => p.path)); } /** Updates a subset of the driver options on the fly */ @@ -1287,6 +1304,12 @@ export class Driver extends TypedEventTarget return this._options; } + /** + * The host bindings used to access file system etc. + */ + // This is set during `start()` and should not be accessed before + private bindings!: Required>; + private _wasStarted: boolean = false; private _isOpen: boolean = false; @@ -1302,6 +1325,15 @@ export class Driver extends TypedEventTarget if (this._wasStarted) return Promise.resolve(); this._wasStarted = true; + // Populate default bindings. This has to happen asynchronously, so the driver does not have a hard dependency + // on Node.js internals + this.bindings = { + fs: this._options.host?.fs + ?? (await import("@zwave-js/core/bindings/fs/node")).fs, + serial: this._options.host?.serial + ?? (await import("@zwave-js/serial/bindings/node")).serial, + }; + const spOpenPromise = createDeferredPromise(); // Log which version is running @@ -1314,19 +1346,22 @@ export class Driver extends TypedEventTarget // Open the serial port let binding: ZWaveSerialBindingFactory; if (typeof this.port === "string") { - if (this.port.startsWith("tcp://")) { - const url = new URL(this.port); - this.driverLog.print(`opening serial port ${this.port}`); - binding = createNodeSocketFactory({ - host: url.hostname, - port: parseInt(url.port), - }); - } else { + if ( + typeof this.bindings.serial.createFactoryByPath === "function" + ) { this.driverLog.print(`opening serial port ${this.port}`); - binding = createNodeSerialPortFactory( + binding = await this.bindings.serial.createFactoryByPath( this.port, - // this._options.testingHooks?.serialPortBinding, ); + } else { + spOpenPromise.reject( + new ZWaveError( + "This platform does not support creating a serial connection by path", + ZWaveErrorCodes.Driver_Failed, + ), + ); + void this.destroy(); + return; } } else if (isZWaveSerialPortImplementation(this.port)) { this.driverLog.print( @@ -1425,7 +1460,13 @@ export class Driver extends TypedEventTarget // Try to create the cache directory. This can fail, in which case we should expose a good error message try { - await this._options.storage.driver.ensureDir(this.cacheDir); + // eslint-disable-next-line @typescript-eslint/no-deprecated + if (this._options.storage.driver) { + // eslint-disable-next-line @typescript-eslint/no-deprecated + await this._options.storage.driver.ensureDir(this.cacheDir); + } else { + await this.bindings.fs.ensureDir(this.cacheDir); + } } catch (e) { let message: string; if ( @@ -1576,8 +1617,8 @@ export class Driver extends TypedEventTarget this._valueDB = new JsonlDB(valueDBFile, { ...options, enableTimestamps: true, - reviver: (key, value) => deserializeCacheValue(value), - serializer: (key, value) => serializeCacheValue(value), + reviver: (_key, value) => deserializeCacheValue(value), + serializer: (_key, value) => serializeCacheValue(value), }); await this._valueDB.open(); @@ -1617,7 +1658,13 @@ export class Driver extends TypedEventTarget this.controller.homeId, this._networkCache, this._valueDB, - this._options.storage.driver, + // eslint-disable-next-line @typescript-eslint/no-deprecated + this._options.storage.driver + ? wrapLegacyFSDriverForCacheMigrationOnly( + // eslint-disable-next-line @typescript-eslint/no-deprecated + this._options.storage.driver, + ) + : this.bindings.fs, this.cacheDir, ); @@ -2821,7 +2868,9 @@ export class Driver extends TypedEventTarget }; /** Checks if there are any pending messages for the given node */ - private hasPendingMessages(node: ZWaveNodeBase & SchedulePoll): boolean { + private hasPendingMessages( + node: ZWaveNodeBase & NodeSchedulePoll, + ): boolean { // First check if there are messages in the queue if ( this.hasPendingTransactions( @@ -2977,7 +3026,7 @@ export class Driver extends TypedEventTarget public schedulePoll( nodeId: number, valueId: ValueID, - options: NodeSchedulePollOptions, + options: SchedulePollOptions, ): boolean { const node = this.controller.nodes.getOrThrow(nodeId); return node.schedulePoll(valueId, options); @@ -7176,7 +7225,7 @@ ${handlers.length} left`, * Marks a node for a later sleep command. Every call refreshes the period until the node actually goes to sleep */ public debounceSendNodeToSleep( - node: ZWaveNodeBase & SchedulePoll & NodeValues & NodeWakeup, + node: ZWaveNodeBase & NodeSchedulePoll & NodeValues & NodeWakeup, ): void { // TODO: This should be a single command to the send thread // Delete old timers if any exist @@ -7186,7 +7235,7 @@ ${handlers.length} left`, // Sends a node to sleep if it has no more messages. const sendNodeToSleep = ( - node: ZWaveNodeBase & SchedulePoll & NodeWakeup, + node: ZWaveNodeBase & NodeSchedulePoll & NodeWakeup, ): void => { this.sendNodeToSleepTimers.delete(node.id); if (!this.hasPendingMessages(node)) { @@ -7373,6 +7422,8 @@ ${handlers.length} left`, } } + private _installConfigUpdatePromise: Promise | undefined; + /** * Installs an update for the embedded configuration DB if there is a compatible one. * Returns `true` when an update was installed, `false` otherwise. @@ -7382,6 +7433,19 @@ ${handlers.length} left`, public async installConfigUpdate(): Promise { this.ensureReady(); + if (this._installConfigUpdatePromise) { + return this._installConfigUpdatePromise; + } + this._installConfigUpdatePromise = this.installConfigUpdateInternal(); + + try { + return await this._installConfigUpdatePromise; + } finally { + this._installConfigUpdatePromise = undefined; + } + } + + private async installConfigUpdateInternal(): Promise { const newVersion = await this.checkForConfigUpdates(true); if (!newVersion) return false; @@ -7398,10 +7462,11 @@ ${handlers.length} left`, `Installing version ${newVersion} of configuration DB...`, ); try { - await installConfigUpdate(newVersion, { - cacheDir: this.cacheDir, - configDir: extConfigDir, - }); + await installConfigUpdate( + this.bindings.fs, + newVersion, + { configDir: extConfigDir }, + ); } catch (e) { this.driverLog.print(getErrorMessage(e), "error"); return false; @@ -7416,10 +7481,7 @@ ${handlers.length} left`, // Now try to apply them to all known devices if (this._controller) { for (const node of this._controller.nodes.values()) { - if (node.ready) { - await node["loadDeviceConfig"](); - // TODO: If the device config did change, expose this information - } + if (node.ready) await node["loadDeviceConfig"](); } } diff --git a/packages/zwave-js/src/lib/driver/DriverMock.ts b/packages/zwave-js/src/lib/driver/DriverMock.ts index 30905a430527..d7dc8795d639 100644 --- a/packages/zwave-js/src/lib/driver/DriverMock.ts +++ b/packages/zwave-js/src/lib/driver/DriverMock.ts @@ -1,9 +1,9 @@ import { type ZWaveSerialStream } from "@zwave-js/serial"; import { MockPort } from "@zwave-js/serial/mock"; +import { type FileSystem } from "@zwave-js/shared/bindings"; import { createDeferredPromise } from "alcalzone-shared/deferred-promise"; -import fs from "node:fs/promises"; import { tmpdir } from "node:os"; -import path from "node:path"; +import path from "pathe"; import { Driver } from "./Driver.js"; import type { PartialZWaveOptions, ZWaveOptions } from "./ZWaveOptions.js"; @@ -112,6 +112,7 @@ export interface CreateAndStartTestingDriverOptions { loadConfiguration?: boolean; portAddress: string; + fs?: FileSystem; } export async function createAndStartTestingDriver( @@ -125,6 +126,7 @@ export async function createAndStartTestingDriver( skipBootloaderCheck = true, skipNodeInterview = false, loadConfiguration = true, + fs = (await import("@zwave-js/core/bindings/fs/node")).fs, ...internalOptions } = options; @@ -168,7 +170,7 @@ export async function createAndStartTestingDriver( const originalDestroy = driver.destroy.bind(driver); driver.destroy = async () => { await originalDestroy(); - await fs.rm(cacheDir, { recursive: true, force: true }); + await fs.deleteDir(cacheDir); }; return new Promise((resolve) => { diff --git a/packages/zwave-js/src/lib/driver/Host.ts b/packages/zwave-js/src/lib/driver/Host.ts new file mode 100644 index 000000000000..e0c2857346e9 --- /dev/null +++ b/packages/zwave-js/src/lib/driver/Host.ts @@ -0,0 +1,11 @@ +// FIXME: This should eventually live in @zwave-js/host + +import { type Serial } from "@zwave-js/serial"; +import { type FileSystem, type Platform } from "@zwave-js/shared/bindings"; + +/** Abstractions for a host system Z-Wave JS is running on */ +export interface Host { + fs: FileSystem; + platform: Platform; + serial: Serial; +} diff --git a/packages/zwave-js/src/lib/driver/MessageGenerators.ts b/packages/zwave-js/src/lib/driver/MessageGenerators.ts index 53c2067be9c1..28aab147e831 100644 --- a/packages/zwave-js/src/lib/driver/MessageGenerators.ts +++ b/packages/zwave-js/src/lib/driver/MessageGenerators.ts @@ -1,4 +1,5 @@ import { + type CCEncodingContext, type CommandClass, MGRPExtension, MPANExtension, @@ -41,7 +42,6 @@ import { ZWaveErrorCodes, mergeSupervisionResults, } from "@zwave-js/core"; -import { type CCEncodingContext } from "@zwave-js/host"; import type { Message } from "@zwave-js/serial"; import { type SendDataMessage, diff --git a/packages/zwave-js/src/lib/driver/NetworkCache.ts b/packages/zwave-js/src/lib/driver/NetworkCache.ts index e358070de94a..4d3aa13ce386 100644 --- a/packages/zwave-js/src/lib/driver/NetworkCache.ts +++ b/packages/zwave-js/src/lib/driver/NetworkCache.ts @@ -2,7 +2,6 @@ import type { JsonlDB } from "@alcalzone/jsonl-db"; import { type AssociationAddress } from "@zwave-js/cc"; import { type CommandClasses, - type FileSystem, NodeType, Protocols, SecurityClass, @@ -13,8 +12,9 @@ import { securityClassOrder, } from "@zwave-js/core"; import { Bytes, getEnumMemberName, num2hex, pickDeep } from "@zwave-js/shared"; +import type { ReadFile, ReadFileSystemInfo } from "@zwave-js/shared/bindings"; import { isArray, isObject } from "alcalzone-shared/typeguards"; -import path from "node:path"; +import path from "pathe"; import { ProvisioningEntryStatus, type SmartStartProvisioningEntry, @@ -623,12 +623,20 @@ export async function migrateLegacyNetworkCache( homeId: number, networkCache: JsonlDB, valueDB: JsonlDB, - storageDriver: FileSystem, + fs: ReadFileSystemInfo & ReadFile, cacheDir: string, ): Promise { const cacheFile = path.join(cacheDir, `${homeId.toString(16)}.json`); - if (!(await storageDriver.pathExists(cacheFile))) return; - const legacy = JSON.parse(await storageDriver.readFile(cacheFile, "utf8")); + try { + const stat = await fs.stat(cacheFile); + if (!stat.isFile()) return; + } catch { + // The file does not exist + return; + } + + const legacyContents = await fs.readFile(cacheFile); + const legacy = JSON.parse(Bytes.view(legacyContents).toString("utf8")); const jsonl = networkCache; function tryMigrate( diff --git a/packages/zwave-js/src/lib/driver/UpdateConfig.ts b/packages/zwave-js/src/lib/driver/UpdateConfig.ts index 3f29be0102a2..3c1ce1fb8dd5 100644 --- a/packages/zwave-js/src/lib/driver/UpdateConfig.ts +++ b/packages/zwave-js/src/lib/driver/UpdateConfig.ts @@ -1,11 +1,22 @@ import { ZWaveError, ZWaveErrorCodes } from "@zwave-js/core"; -import { copyFilesRecursive, getErrorMessage } from "@zwave-js/shared"; +import { + copyFilesRecursive, + getErrorMessage, + noop, + writeTextFile, +} from "@zwave-js/shared"; +import { + type CopyFile, + type FileHandle, + type MakeTempDirectory, + type ManageDirectory, + type OpenFile, + type ReadFileSystemInfo, + type WriteFile, +} from "@zwave-js/shared/bindings"; import { isObject } from "alcalzone-shared/typeguards"; import execa from "execa"; -import fs from "node:fs/promises"; -import os from "node:os"; -import * as path from "node:path"; -import * as lockfile from "proper-lockfile"; +import path from "pathe"; import semverInc from "semver/functions/inc.js"; import semverValid from "semver/functions/valid.js"; import semverMaxSatisfying from "semver/ranges/max-satisfying.js"; @@ -60,10 +71,16 @@ export async function checkForConfigUpdates( * This only works if an external configuation directory is used. */ export async function installConfigUpdate( + fs: + & ManageDirectory + & ReadFileSystemInfo + & WriteFile + & CopyFile + & OpenFile + & MakeTempDirectory, newVersion: string, external: { configDir: string; - cacheDir: string; }, ): Promise { const { default: ky } = await import("ky"); @@ -88,43 +105,11 @@ export async function installConfigUpdate( ); } - const lockfilePath = external.cacheDir; - const lockfileOptions: lockfile.LockOptions = { - lockfilePath: path.join(external.cacheDir, "config-update.lock"), - }; - - try { - await lockfile.lock(lockfilePath, { - ...lockfileOptions, - onCompromised: () => { - // do nothing - }, - }); - } catch { - throw new ZWaveError( - `Config update failed: Another installation is already in progress!`, - ZWaveErrorCodes.Config_Update_InstallFailed, - ); - } - - const freeLock = async () => { - try { - if (await lockfile.check(lockfilePath, lockfileOptions)) { - await lockfile.unlock(lockfilePath, lockfileOptions); - } - } catch { - // whatever - just don't crash - } - }; - // Download tarball to a temporary directory let tmpDir: string; try { - tmpDir = await fs.mkdtemp( - path.join(os.tmpdir(), "zjs-config-update-"), - ); + tmpDir = await fs.makeTempDir("zjs-config-update-"); } catch (e) { - await freeLock(); throw new ZWaveError( `Config update failed: Could not create temporary directory. Reason: ${ getErrorMessage( @@ -137,19 +122,18 @@ export async function installConfigUpdate( // Download the package tarball into the temporary directory const tarFilename = path.join(tmpDir, "zjs-config-update.tgz"); - let fileHandle: fs.FileHandle | undefined; + let fileHandle: FileHandle | undefined; try { - fileHandle = await fs.open(tarFilename, "w"); - const writable = new WritableStream({ - async write(chunk) { - await fileHandle!.write(chunk); - }, + fileHandle = await fs.open(tarFilename, { + read: false, + write: true, + create: true, + truncate: true, }); const response = await ky.get(url); - await response.body?.pipeTo(writable); + await response.body?.pipeTo(fileHandle.writable); } catch (e) { - await freeLock(); throw new ZWaveError( `Config update failed: Could not download tarball. Reason: ${ getErrorMessage( @@ -174,8 +158,8 @@ export async function installConfigUpdate( const extractedDir = path.join(tmpDir, "extracted"); try { // Extract the tarball in the temporary folder - await fs.rm(extractedDir, { recursive: true, force: true }); - await fs.mkdir(extractedDir, { recursive: true }); + await fs.deleteDir(extractedDir); + await fs.ensureDir(extractedDir); await execa("tar", [ "--strip-components=1", "-xzf", @@ -185,9 +169,10 @@ export async function installConfigUpdate( ]); // then overwrite the files in the external config directory - await fs.rm(external.configDir, { recursive: true, force: true }); - await fs.mkdir(external.configDir, { recursive: true }); + await fs.deleteDir(external.configDir); + await fs.ensureDir(external.configDir); await copyFilesRecursive( + fs, path.join(extractedDir, "config"), external.configDir, (src) => src.endsWith(".json"), @@ -196,9 +181,8 @@ export async function installConfigUpdate( external.configDir, "version", ); - await fs.writeFile(externalVersionFilename, newVersion, "utf8"); + await writeTextFile(fs, externalVersionFilename, newVersion); } catch { - await freeLock(); throw new ZWaveError( `Config update failed: Could not extract tarball`, ZWaveErrorCodes.Config_Update_InstallFailed, @@ -206,10 +190,5 @@ export async function installConfigUpdate( } // Clean up the temp dir and ignore errors - void fs.rm(tmpDir, { recursive: true, force: true }).catch(() => { - // ignore - }); - - // Free the lock - await freeLock(); + await fs.deleteDir(tmpDir).catch(noop); } diff --git a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts index b3d79c78a999..959a1dad3cca 100644 --- a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts +++ b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts @@ -1,18 +1,18 @@ import type { - FileSystem, + FileSystem as LegacyFileSystemBindings, LogConfig, LongRangeChannel, RFRegion, } from "@zwave-js/core"; -import type { ZWaveHostOptions } from "@zwave-js/host"; -import { type ZWaveSerialStream } from "@zwave-js/serial"; +import { type Serial, type ZWaveSerialStream } from "@zwave-js/serial"; import { type DeepPartial, type Expand } from "@zwave-js/shared"; +import type { FileSystem } from "@zwave-js/shared/bindings"; import type { InclusionUserCallbacks, JoinNetworkUserCallbacks, } from "../controller/Inclusion.js"; -export interface ZWaveOptions extends ZWaveHostOptions { +export interface ZWaveOptions { /** Specify timeouts in milliseconds */ timeouts: { /** how long to wait for an ACK */ @@ -125,9 +125,26 @@ export interface ZWaveOptions extends ZWaveHostOptions { disableOnNodeAdded?: boolean; }; + /** Host abstractions allowing Z-Wave JS to run on different platforms */ + host?: { + /** + * Specifies which bindings are used to access the file system when + * reading or writing the cache, or loading device configuration files. + */ + fs?: FileSystem; + + /** + * Specifies which bindings are used interact with serial ports. + */ + serial?: Serial; + }; + storage: { - /** Allows you to replace the default file system driver used to store and read the cache */ - driver: FileSystem; + /** + * Allows you to replace the default file system driver used to store and read the cache + * @deprecated Use `bindings.fs` instead. + */ + driver?: LegacyFileSystemBindings; /** Allows you to specify a different cache directory */ cacheDir: string; /** @@ -395,6 +412,7 @@ export type PartialZWaveOptions = Expand< | "joinNetworkUserCallbacks" | "logConfig" | "testingHooks" + | "host" > > & Partial< @@ -402,6 +420,7 @@ export type PartialZWaveOptions = Expand< ZWaveOptions, | "testingHooks" | "vendor" + | "host" > > & { diff --git a/packages/zwave-js/src/lib/node/Node.ts b/packages/zwave-js/src/lib/node/Node.ts index feb5fcee5434..00dfef93957b 100644 --- a/packages/zwave-js/src/lib/node/Node.ts +++ b/packages/zwave-js/src/lib/node/Node.ts @@ -233,7 +233,7 @@ import { } from "alcalzone-shared/deferred-promise"; import { roundTo } from "alcalzone-shared/math"; import { isArray, isObject } from "alcalzone-shared/typeguards"; -import path from "node:path"; +import path from "pathe"; import semverParse from "semver/functions/parse.js"; import { RemoveNodeReason } from "../controller/Inclusion.js"; import { determineNIF } from "../controller/NodeInformationFrame.js"; diff --git a/packages/zwave-js/src/lib/node/mixins/60_ScheduledPoll.ts b/packages/zwave-js/src/lib/node/mixins/60_ScheduledPoll.ts index 8e11f7151db5..9651dc6f34cf 100644 --- a/packages/zwave-js/src/lib/node/mixins/60_ScheduledPoll.ts +++ b/packages/zwave-js/src/lib/node/mixins/60_ScheduledPoll.ts @@ -1,4 +1,4 @@ -import { type CCAPI } from "@zwave-js/cc"; +import { type CCAPI, type SchedulePollOptions } from "@zwave-js/cc"; import { type ValueDB, normalizeValueID } from "@zwave-js/core"; import { type CommandClasses, @@ -7,7 +7,6 @@ import { 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.js"; @@ -20,7 +19,7 @@ export interface ScheduledPoll { } /** Defines functionality of Z-Wave nodes for scheduling polls for a later time and canceling scheduled polls */ -export interface SchedulePoll { +export interface NodeSchedulePoll { /** * @internal * Returns whether a poll is currently scheduled for this node @@ -34,7 +33,7 @@ export interface SchedulePoll { */ schedulePoll( valueId: ValueID, - options: NodeSchedulePollOptions, + options: SchedulePollOptions, ): boolean; /** @@ -57,7 +56,7 @@ export interface SchedulePoll { } export abstract class SchedulePollMixin extends EndpointsMixin - implements SchedulePoll + implements NodeSchedulePoll { public constructor( nodeId: number, @@ -112,7 +111,7 @@ export abstract class SchedulePollMixin extends EndpointsMixin public schedulePoll( valueId: ValueID, - options: NodeSchedulePollOptions = {}, + options: SchedulePollOptions = {}, ): boolean { const { timeoutMs = this.driver.options.timeouts.refreshValue, diff --git a/packages/zwave-js/src/lib/node/utils.ts b/packages/zwave-js/src/lib/node/utils.ts index 65e84820bd7a..f7e7c7fb62e5 100644 --- a/packages/zwave-js/src/lib/node/utils.ts +++ b/packages/zwave-js/src/lib/node/utils.ts @@ -1,10 +1,15 @@ import { CommandClass } from "@zwave-js/cc"; import { MultiChannelCCValues } from "@zwave-js/cc/MultiChannelCC"; +import { type GetDeviceConfig } from "@zwave-js/config"; import { CommandClasses, type ControlsCC, type EndpointId, type GetEndpoint, + type GetNode, + type GetSupportedCCVersion, + type GetValueDB, + type HostIDs, type MaybeNotKnown, type NodeId, type SetValueOptions, @@ -17,13 +22,6 @@ import { applicationCCs, getCCName, } from "@zwave-js/core"; -import type { - GetDeviceConfig, - GetNode, - GetSupportedCCVersion, - GetValueDB, - HostIDs, -} from "@zwave-js/host"; function getValue( ctx: GetValueDB, 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 47c1ed46aeb1..c2e776ee4abf 100644 --- a/packages/zwave-js/src/lib/test/cc/BinarySwitchCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/BinarySwitchCC.test.ts @@ -6,8 +6,11 @@ import { BinarySwitchCommand, CommandClass, } from "@zwave-js/cc"; -import { CommandClasses, Duration } from "@zwave-js/core"; -import { type GetSupportedCCVersion } from "@zwave-js/host"; +import { + CommandClasses, + Duration, + type GetSupportedCCVersion, +} from "@zwave-js/core"; import { Bytes } from "@zwave-js/shared/safe"; import { test } from "vitest"; 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 3b4b0712621d..4f93cff0029d 100644 --- a/packages/zwave-js/src/lib/test/cc/ColorSwitchCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ColorSwitchCC.test.ts @@ -14,10 +14,10 @@ import { import { CommandClasses, Duration, + type GetSupportedCCVersion, ZWaveErrorCodes, assertZWaveError, } from "@zwave-js/core"; -import { type GetSupportedCCVersion } from "@zwave-js/host"; import { Bytes } from "@zwave-js/shared/safe"; import { test } from "vitest"; 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 59690805a145..71c6465f3f5d 100644 --- a/packages/zwave-js/src/lib/test/cc/MeterCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MeterCC.test.ts @@ -11,10 +11,11 @@ import { } from "@zwave-js/cc"; import { CommandClasses, + type GetSupportedCCVersion, ZWaveErrorCodes, assertZWaveError, } from "@zwave-js/core"; -import { type GetSupportedCCVersion, createTestingHost } from "@zwave-js/host"; +import { createTestingHost } from "@zwave-js/host"; import { Bytes } from "@zwave-js/shared/safe"; import { test } from "vitest"; import * as nodeUtils from "../../node/utils.js"; 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 9dca50ddca54..a7d999729e8e 100644 --- a/packages/zwave-js/src/lib/test/cc/MultilevelSwitchCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MultilevelSwitchCC.test.ts @@ -9,8 +9,11 @@ import { MultilevelSwitchCCSupportedGet, MultilevelSwitchCommand, } from "@zwave-js/cc"; -import { CommandClasses, Duration } from "@zwave-js/core"; -import { type GetSupportedCCVersion } from "@zwave-js/host"; +import { + CommandClasses, + Duration, + type GetSupportedCCVersion, +} from "@zwave-js/core"; import { Bytes } from "@zwave-js/shared/safe"; import { test } from "vitest"; diff --git a/packages/zwave-js/src/lib/test/integrationTestSuite.ts b/packages/zwave-js/src/lib/test/integrationTestSuite.ts index 91e818bd40ba..55bb3c7d648f 100644 --- a/packages/zwave-js/src/lib/test/integrationTestSuite.ts +++ b/packages/zwave-js/src/lib/test/integrationTestSuite.ts @@ -1,3 +1,4 @@ +import { fs } from "@zwave-js/core/bindings/fs/node"; import { type ZWaveSerialStream } from "@zwave-js/serial"; import type { MockPort } from "@zwave-js/serial/mock"; import { copyFilesRecursive, noop } from "@zwave-js/shared"; @@ -9,7 +10,7 @@ import { } from "@zwave-js/testing"; import { wait } from "alcalzone-shared/async"; import crypto from "node:crypto"; -import fs from "node:fs/promises"; +import fsp from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { type TestContext, test } from "vitest"; @@ -87,12 +88,12 @@ function suite( } // Make sure every test is starting fresh - await fs.rm(cacheDir, { recursive: true, force: true }).catch(noop); - await fs.mkdir(cacheDir, { recursive: true }); + await fsp.rm(cacheDir, { recursive: true, force: true }).catch(noop); + await fsp.mkdir(cacheDir, { recursive: true }); // And potentially provision the cache if (provisioningDirectory) { - await copyFilesRecursive(provisioningDirectory, cacheDir); + await copyFilesRecursive(fs, provisioningDirectory, cacheDir); } ({ driver, continueStartup, mockPort, serial } = await prepareDriver( @@ -171,7 +172,7 @@ function suite( await driver.destroy(); if (!debug) { - await fs.rm(cacheDir, { recursive: true, force: true }) + await fsp.rm(cacheDir, { recursive: true, force: true }) .catch(noop); } }); diff --git a/packages/zwave-js/src/lib/test/integrationTestSuiteMulti.ts b/packages/zwave-js/src/lib/test/integrationTestSuiteMulti.ts index 52f387a41382..6c0136250e38 100644 --- a/packages/zwave-js/src/lib/test/integrationTestSuiteMulti.ts +++ b/packages/zwave-js/src/lib/test/integrationTestSuiteMulti.ts @@ -1,3 +1,4 @@ +import { fs } from "@zwave-js/core/bindings/fs/node"; import { type ZWaveSerialStream } from "@zwave-js/serial"; import type { MockPort } from "@zwave-js/serial/mock"; import { copyFilesRecursive, noop } from "@zwave-js/shared"; @@ -9,7 +10,7 @@ import { } from "@zwave-js/testing"; import { wait } from "alcalzone-shared/async"; import crypto from "node:crypto"; -import fs from "node:fs/promises"; +import fsp from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { type TestContext, test } from "vitest"; @@ -87,12 +88,12 @@ function suite( } // Make sure every test is starting fresh - await fs.rm(cacheDir, { recursive: true, force: true }).catch(noop); - await fs.mkdir(cacheDir, { recursive: true }); + await fsp.rm(cacheDir, { recursive: true, force: true }).catch(noop); + await fsp.mkdir(cacheDir, { recursive: true }); // And potentially provision the cache if (provisioningDirectory) { - await copyFilesRecursive(provisioningDirectory, cacheDir); + await copyFilesRecursive(fs, provisioningDirectory, cacheDir); } ({ driver, continueStartup, mockPort, serial } = await prepareDriver( @@ -173,7 +174,7 @@ function suite( await driver.destroy(); if (!debug) { - await fs.rm(cacheDir, { recursive: true, force: true }) + await fsp.rm(cacheDir, { recursive: true, force: true }) .catch(noop); } }); diff --git a/packages/zwave-js/src/lib/test/mocks.ts b/packages/zwave-js/src/lib/test/mocks.ts index 9598e48540d8..b84949ca4705 100644 --- a/packages/zwave-js/src/lib/test/mocks.ts +++ b/packages/zwave-js/src/lib/test/mocks.ts @@ -4,6 +4,8 @@ import { type CommandClassInfo, type CommandClasses, type FLiRS, + type GetNode, + type GetValueDB, type InterviewStage, type MaybeNotKnown, MessagePriority, @@ -14,12 +16,7 @@ import { ZWaveErrorCodes, securityClassOrder, } from "@zwave-js/core"; -import type { - BaseTestEndpoint, - BaseTestNode, - GetNode, - GetValueDB, -} from "@zwave-js/host"; +import type { BaseTestEndpoint, BaseTestNode } from "@zwave-js/host"; import { type FunctionType, Message, diff --git a/packages/zwave-js/src/lib/zniffer/Zniffer.ts b/packages/zwave-js/src/lib/zniffer/Zniffer.ts index 45607f992b1c..5f3c80b16752 100644 --- a/packages/zwave-js/src/lib/zniffer/Zniffer.ts +++ b/packages/zwave-js/src/lib/zniffer/Zniffer.ts @@ -1,4 +1,5 @@ import { + type CCParsingContext, CommandClass, Security2CCMessageEncapsulation, Security2CCNonceGet, @@ -9,6 +10,7 @@ import { DeviceConfig } from "@zwave-js/config"; import { CommandClasses, type FrameType, + type HostIDs, type LogConfig, MPDUHeaderType, type MaybeNotKnown, @@ -32,7 +34,6 @@ import { securityClassIsS2, } from "@zwave-js/core"; import { sdkVersionGte } from "@zwave-js/core"; -import { type CCParsingContext, type HostIDs } from "@zwave-js/host"; import { type ZWaveSerialBindingFactory, type ZWaveSerialPortImplementation, @@ -57,8 +58,6 @@ import { ZnifferStartResponse, ZnifferStopRequest, ZnifferStopResponse, - createNodeSerialPortFactory, - createNodeSocketFactory, isZWaveSerialPortImplementation, wrapLegacySerialBinding, } from "@zwave-js/serial"; @@ -76,7 +75,6 @@ import { type DeferredPromise, createDeferredPromise, } from "alcalzone-shared/deferred-promise"; -import fs from "node:fs/promises"; import { type ZWaveOptions } from "../driver/ZWaveOptions.js"; import { ZnifferLogger } from "../log/Zniffer.js"; import { @@ -126,6 +124,8 @@ export interface ZnifferOptions { /** Security keys for decrypting Z-Wave Long Range traffic */ securityKeysLongRange?: ZWaveOptions["securityKeysLongRange"]; + host?: ZWaveOptions["host"]; + /** * The RSSI values reported by the Zniffer are not actual RSSI values. * They can be converted to dBm, but the conversion is chip dependent and not documented for 700/800 series Zniffers. @@ -275,6 +275,12 @@ export class Zniffer extends TypedEventTarget { private _options: ZnifferOptions; + /** + * The host bindings used to access file system etc. + */ + // This is set during `init()` and should not be accessed before + private bindings!: Required>; + private serialFactory: ZnifferSerialStreamFactory | undefined; /** The serial port instance */ private serial: ZnifferSerialStream | undefined; @@ -342,21 +348,29 @@ export class Zniffer extends TypedEventTarget { ); } + // Populate default bindings. This has to happen asynchronously, so the driver does not have a hard dependency + // on Node.js internals + this.bindings = { + fs: this._options.host?.fs + ?? (await import("@zwave-js/core/bindings/fs/node")).fs, + serial: this._options.host?.serial + ?? (await import("@zwave-js/serial/bindings/node")).serial, + }; + // Open the serial port let binding: ZWaveSerialBindingFactory; if (typeof this.port === "string") { - if (this.port.startsWith("tcp://")) { - const url = new URL(this.port); - this.znifferLog.print(`opening serial port ${this.port}`); - binding = createNodeSocketFactory({ - host: url.hostname, - port: parseInt(url.port), - }); - } else { + if ( + typeof this.bindings.serial.createFactoryByPath === "function" + ) { this.znifferLog.print(`opening serial port ${this.port}`); - binding = createNodeSerialPortFactory( + binding = await this.bindings.serial.createFactoryByPath( this.port, - // this._options.testingHooks?.serialPortBinding, + ); + } else { + throw new ZWaveError( + "This platform does not support creating a serial connection by path", + ZWaveErrorCodes.Driver_Failed, ); } } else if (isZWaveSerialPortImplementation(this.port)) { @@ -1024,7 +1038,10 @@ supported frequencies: ${ filePath: string, frameFilter?: (frame: CapturedFrame) => boolean, ): Promise { - await fs.writeFile(filePath, this.getCaptureAsZLFBuffer(frameFilter)); + await this.bindings.fs.writeFile( + filePath, + this.getCaptureAsZLFBuffer(frameFilter), + ); } /** diff --git a/test/run.ts b/test/run.ts index d7607bd872c1..f49d3c640cb3 100644 --- a/test/run.ts +++ b/test/run.ts @@ -22,10 +22,10 @@ process.on("unhandledRejection", (_r) => { // : "/dev/ttyACM0"; // const port = require("os").platform() === "win32" ? "COM5" : "/dev/ttyUSB0"; // 800 series -// const port = os.platform() === "win32" -// ? "COM5" -// : "/dev/serial/by-id/usb-Zooz_800_Z-Wave_Stick_533D004242-if00"; -const port = "tcp://127.0.0.1:5555"; +const port = os.platform() === "win32" + ? "COM5" + : "/dev/serial/by-id/usb-Zooz_800_Z-Wave_Stick_533D004242-if00"; +// const port = "tcp://127.0.0.1:5555"; const driver = new Driver(port, { // logConfig: { diff --git a/yarn.lock b/yarn.lock index 8ab4da54398d..d07bef48074a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2559,6 +2559,7 @@ __metadata: json-logic-js: "npm:^2.0.5" json5: "npm:^2.2.3" ky: "npm:^1.7.2" + pathe: "npm:^1.1.2" peggy: "npm:^3.0.2" proxyquire: "npm:^2.1.3" semver: "npm:^7.6.3" @@ -2592,6 +2593,7 @@ __metadata: fflate: "npm:^0.8.2" logform: "npm:^2.6.1" nrf-intel-hex: "npm:^1.4.0" + pathe: "npm:^1.1.2" reflect-metadata: "npm:^0.2.2" semver: "npm:^7.6.3" sinon: "npm:^19.0.2" @@ -2628,6 +2630,7 @@ __metadata: "@types/yargs": "npm:^17.0.33" "@zwave-js/core": "workspace:*" del-cli: "npm:^6.0.0" + pathe: "npm:^1.1.2" typescript: "npm:5.6.2" yargs: "npm:^17.7.2" zwave-js: "workspace:*" @@ -2827,6 +2830,7 @@ __metadata: "@types/sinon": "npm:^17.0.3" alcalzone-shared: "npm:^5.0.0" del-cli: "npm:^6.0.0" + pathe: "npm:^1.1.2" sinon: "npm:^19.0.2" tsx: "npm:^4.19.2" typescript: "npm:5.6.2" @@ -9923,6 +9927,7 @@ __metadata: mdns-server: "npm:^1.0.11" mockdate: "npm:^3.0.5" p-queue: "npm:^8.0.1" + pathe: "npm:^1.1.2" proper-lockfile: "npm:^4.1.2" proxyquire: "npm:^2.1.3" reflect-metadata: "npm:^0.2.2"