diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 45d3e0c980a1..2e32508f9cb6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,8 +18,6 @@ updates: # so only apply patch and minor updates automatically - dependency-name: '@types/node' update-types: ['version-update:semver-major'] - # xstate usually requires manual intervention - - dependency-name: 'xstate' - package-ecosystem: github-actions directory: '/' 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/package.json b/package.json index e53ffe027e97..0cee8f664159 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "@alcalzone/monopack": "^1.3.0", "@alcalzone/release-script": "~3.8.0", "@commitlint/cli": "^19.5.0", - "@commitlint/config-conventional": "^19.5.0", + "@commitlint/config-conventional": "^19.6.0", "@dprint/formatter": "^0.4.1", "@dprint/json": "^0.19.4", "@dprint/markdown": "^0.17.8", @@ -70,7 +70,7 @@ "del-cli": "^6.0.0", "dprint": "^0.47.5", "eslint": "^9.12.0", - "eslint-plugin-unicorn": "^56.0.0", + "eslint-plugin-unicorn": "^56.0.1", "eslint-plugin-unused-imports": "patch:eslint-plugin-unused-imports@npm%3A4.1.4#~/.yarn/patches/eslint-plugin-unused-imports-npm-4.1.4-a7d7c7cdf3.patch", "execa": "^5.1.1", "husky": "^9.1.6", 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 c142e676ff89..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,22 +10,12 @@ 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, - type CCResponseRole, - CommandClass, -} from "../lib/CommandClass.js"; +import { type CCRaw, CommandClass } from "../lib/CommandClass.js"; import { CCCommand, commandClass, - expectedCCResponse, implementedVersion, } from "../lib/CommandClassDecorators.js"; import { TransportServiceCommand } from "../lib/_Types.js"; @@ -81,7 +73,7 @@ export function isTransportServiceEncapsulation( } @CCCommand(TransportServiceCommand.FirstSegment) -// @expectedCCResponse(TransportServiceCCReport) +// Handling expected responses is done by the RX state machine export class TransportServiceCCFirstSegment extends TransportServiceCC { public constructor( options: WithAddress, @@ -227,7 +219,7 @@ export interface TransportServiceCCSubsequentSegmentOptions } @CCCommand(TransportServiceCommand.SubsequentSegment) -// @expectedCCResponse(TransportServiceCCReport) +// Handling expected responses is done by the RX state machine export class TransportServiceCCSubsequentSegment extends TransportServiceCC { public constructor( options: WithAddress, @@ -465,23 +457,8 @@ export interface TransportServiceCCSegmentRequestOptions { datagramOffset: number; } -function testResponseForSegmentRequest( - sent: TransportServiceCCSegmentRequest, - received: TransportServiceCC, -): CCResponseRole { - return ( - (sent.datagramOffset === 0 - && received instanceof TransportServiceCCFirstSegment - && received.sessionId === sent.sessionId) - || (sent.datagramOffset > 0 - && received instanceof TransportServiceCCSubsequentSegment - && sent.datagramOffset === received.datagramOffset - && received.sessionId === sent.sessionId) - ); -} - @CCCommand(TransportServiceCommand.SegmentRequest) -@expectedCCResponse(TransportServiceCC, testResponseForSegmentRequest) +// Handling expected responses is done by the RX state machine export class TransportServiceCCSegmentRequest extends TransportServiceCC { public constructor( options: WithAddress, 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 0254b228f7ef..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, @@ -182,56 +183,56 @@ function updateNumberOrDefault( /** Retrieves the list of database IDs from the OpenSmartHouse DB */ async function fetchIDsOH(): Promise { - const { got } = await import("got"); - const data = (await got.get(ohUrlIDs).json()) as any; + const { default: ky } = await import("ky"); + const data = (await ky.get(ohUrlIDs).json()) as any; return data.devices.map((d: any) => d.id); } /** Retrieves the definition for a specific device from the OpenSmartHouse DB */ async function fetchDeviceOH(id: number): Promise { - const { got } = await import("got"); - const source = (await got.get(ohUrlDevice(id)).json()) as any; + const { default: ky } = await import("ky"); + const source = (await ky.get(ohUrlDevice(id)).json()) as any; return stringify(source, "\t"); } /** Retrieves the definition for a specific device from the Z-Wave Alliance DB */ async function fetchDeviceZWA(id: number): Promise { - const { got } = await import("got"); - const source = (await got.get(zwaUrlDevice(id)).json()) as any; + const { default: ky } = await import("ky"); + const source = (await ky.get(zwaUrlDevice(id)).json()) as any; return stringify(source, "\t"); } /** Downloads ozw master archive and store it on `tmpDir` */ async function downloadOZWConfig(): Promise { console.log("downloading ozw archive..."); - const { got } = await import("got"); + const { default: ky } = await import("ky"); // create tmp directory if missing await fs.mkdir(ozwTempDir, { recursive: true }); // this will return a stream in `data` that we pipe into write stream // to store the file in `tmpDir` - const data = got.stream.get(ozwTarUrl); - return new Promise(async (resolve, reject) => { + let fileHandle: fs.FileHandle | undefined; + try { + // Create a stream to write the file const fileDest = path.join(ozwTempDir, ozwTarName); - const handle = await fs.open(fileDest, "w"); - const stream = handle.createWriteStream(); - data.pipe(stream); - let hasError = false; - stream.on("error", (err) => { - hasError = true; - stream.close(); - reject(err); + fileHandle = await fs.open(fileDest, "w"); + const writable = new WritableStream({ + async write(chunk) { + await fileHandle!.write(chunk); + }, }); - stream.on("close", () => { - if (!hasError) { - resolve(fileDest); - console.log("ozw archive stored in temporary directory"); - } - }); - }); + // And pipe the response into the stream + const response = await ky.get(ozwTarUrl); + await response.body?.pipeTo(writable); + + console.log("ozw archive stored in temporary directory"); + return fileDest; + } finally { + await fileHandle?.close(); + } } /** Extract `config` folder from ozw archive in `tmpDir` */ @@ -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"), ); @@ -1701,7 +1705,7 @@ async function retrieveZWADeviceIds( highestDeviceOnly: boolean = true, manufacturer: number[] = [-1], ): Promise { - const { got } = await import("got"); + const { default: ky } = await import("ky"); const deviceIdsSet = new Set(); for (const manu of manufacturer) { @@ -1709,7 +1713,7 @@ async function retrieveZWADeviceIds( // Page 1 let currentUrl = `https://products.z-wavealliance.org/search/DoAdvancedSearch?productName=&productIdentifier=&productDescription=&category=-1&brand=${manu}®ionId=-1&order=&page=${page}`; - const firstPage = await got.get(currentUrl).text(); + const firstPage = await ky.get(currentUrl).text(); for (const i of firstPage.match(/(?<=productId=).*?(?=[\&\"])/g)!) { deviceIdsSet.add(i); } @@ -1730,7 +1734,7 @@ async function retrieveZWADeviceIds( ); currentUrl = `https://products.z-wavealliance.org/search/DoAdvancedSearch?productName=&productIdentifier=&productDescription=&category=-1&brand=${manu}®ionId=-1&order=&page=${page}`; - const nextPage = await got.get(currentUrl).text(); + const nextPage = await ky.get(currentUrl).text(); const nextPageIds = nextPage.match( /(?<=productId=).*?(?=[\&\"])/g, )!; @@ -1814,8 +1818,8 @@ async function downloadDevicesOH(IDs?: number[]): Promise { async function downloadManufacturersOH(): Promise { process.stdout.write("Fetching manufacturers..."); - const { got } = await import("got"); - const data = await got.get(ohUrlManufacturers).json(); + const { default: ky } = await import("ky"); + const data = await ky.get(ohUrlManufacturers).json(); // Delete the last line process.stdout.write("\r\x1b[K"); @@ -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 2fd7ed3f596f..e2e6a5c0df94 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" }, @@ -73,7 +74,6 @@ "@types/js-levenshtein": "^1.1.3", "@types/json-logic-js": "^2.0.7", "@types/node": "^18.19.63", - "@types/proxyquire": "^1.3.31", "@types/semver": "^7.5.8", "@types/sinon": "^17.0.3", "@types/xml2js": "^0.4.14", @@ -82,10 +82,9 @@ "comment-json": "^4.2.5", "del-cli": "^6.0.0", "es-main": "^1.3.0", - "got": "^13.0.0", "js-levenshtein": "^1.1.6", + "ky": "^1.7.2", "peggy": "^3.0.2", - "proxyquire": "^2.1.3", "sinon": "^19.0.2", "ts-pegjs": "patch:ts-pegjs@npm%3A4.2.1#~/.yarn/patches/ts-pegjs-npm-4.2.1-0f567a1059.patch", "tsx": "^4.19.2", 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/db/jsonl.ts b/packages/core/src/bindings/db/jsonl.ts new file mode 100644 index 000000000000..4aea5c53effa --- /dev/null +++ b/packages/core/src/bindings/db/jsonl.ts @@ -0,0 +1,16 @@ +import { JsonlDB } from "@alcalzone/jsonl-db"; +import { + type Database, + type DatabaseFactory, + type DatabaseOptions, +} from "@zwave-js/shared/bindings"; + +/** An implementation of the Database bindings for Node.js based on JsonlDB */ +export const db: DatabaseFactory = { + createInstance( + filename: string, + options?: DatabaseOptions, + ): Database { + return new JsonlDB(filename, options); + }, +}; 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/fsm/FSM.ts b/packages/core/src/fsm/FSM.ts new file mode 100644 index 000000000000..82b40b5bee7e --- /dev/null +++ b/packages/core/src/fsm/FSM.ts @@ -0,0 +1,88 @@ +export interface StateMachineTransition< + State extends StateMachineState, + Effect = undefined, +> { + effect?: Effect; + newState: State; +} + +export interface StateMachineState { + value: number | string; + done?: boolean; +} + +export interface StateMachineInput { + value: number | string; +} + +export type StateMachineTransitionMap< + State extends StateMachineState, + Input extends StateMachineInput, + Effect = undefined, +> = ( + state: State, +) => ( + input: Input, +) => StateMachineTransition | undefined; + +export type InferStateMachineTransitions< + T extends StateMachine, +> = T extends StateMachine + ? StateMachineTransitionMap + : never; + +export class StateMachine< + State extends StateMachineState, + Input extends StateMachineInput, + Effect = undefined, +> { + public constructor( + initialState: State, + transitions: StateMachineTransitionMap< + State, + Input, + Effect | undefined + >, + ) { + this._initial = this._state = initialState; + this.transitions = transitions; + } + + protected transitions: StateMachineTransitionMap< + State, + Input, + Effect | undefined + >; + + /** Restarts the machine from the initial state */ + public restart(): void { + this._state = this._initial; + } + + /** Determines the next transition to take */ + public next( + input: Input, + ): StateMachineTransition | undefined { + if (this._state.done) return; + return this.transitions(this._state)(input); + } + + /** Transitions the machine to the next state. This does not execute effects */ + public transition(next?: State): void { + // Allow some convenience by passing the transition's next state directly + if (next == undefined) return; + this._state = next; + } + + private _initial: State; + private _state: State; + /** Returns the current state of the state machine */ + public get state(): State { + return this._state; + } + + /** Returns whether this state machine is done */ + public get done(): boolean { + return !!this._state.done; + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 668462c617b7..17d751ea1325 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -3,6 +3,7 @@ export * from "./crypto/index.node.js"; export * from "./definitions/index.js"; export * from "./dsk/index.js"; export * from "./error/ZWaveError.js"; +export * from "./fsm/FSM.js"; export * from "./log/Controller.js"; export * from "./log/shared.js"; export * from "./log/shared_safe.js"; @@ -16,7 +17,7 @@ export * from "./test/assertZWaveError.js"; export type * from "./traits/index.js"; export * from "./util/_Types.js"; export * from "./util/compareVersions.js"; -export { deflateSync } from "./util/compression.js"; +export { deflateSync, gunzipSync } from "./util/compression.js"; export * from "./util/config.js"; export * from "./util/crc.js"; export * from "./util/date.js"; diff --git a/packages/core/src/index_browser.ts b/packages/core/src/index_browser.ts index d29e8324cf29..e1c43b053449 100644 --- a/packages/core/src/index_browser.ts +++ b/packages/core/src/index_browser.ts @@ -16,7 +16,7 @@ export * from "./registries/Scales.js"; export * from "./registries/Sensors.js"; export type * from "./traits/index.js"; export type * from "./util/_Types.js"; -export { deflateSync } from "./util/compression.js"; +export { deflateSync, gunzipSync } from "./util/compression.js"; export * from "./util/config.js"; export * from "./util/crc.js"; export * from "./util/graph.js"; diff --git a/packages/core/src/index_safe.ts b/packages/core/src/index_safe.ts index 683b9a763d88..917dc42e1d4a 100644 --- a/packages/core/src/index_safe.ts +++ b/packages/core/src/index_safe.ts @@ -15,7 +15,7 @@ export * from "./registries/Scales.js"; export * from "./registries/Sensors.js"; export type * from "./traits/index.js"; export * from "./util/_Types.js"; -export { deflateSync } from "./util/compression.js"; +export { deflateSync, gunzipSync } from "./util/compression.js"; export * from "./util/config.js"; export * from "./util/crc.js"; export * from "./util/graph.js"; 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/core/src/util/compression.ts b/packages/core/src/util/compression.ts index ed1c365ff447..9e4699181bf3 100644 --- a/packages/core/src/util/compression.ts +++ b/packages/core/src/util/compression.ts @@ -1,5 +1,9 @@ -import { deflateSync as defflateSync } from "fflate"; +import { deflateSync as defflateSync, gunzipSync as fgunzipSync } from "fflate"; export function deflateSync(data: Uint8Array): Uint8Array { return defflateSync(data); } + +export function gunzipSync(data: Uint8Array): Uint8Array { + return fgunzipSync(data); +} diff --git a/packages/core/src/values/CacheBackedMap.ts b/packages/core/src/values/CacheBackedMap.ts index 71119ef67eee..8525d1339842 100644 --- a/packages/core/src/values/CacheBackedMap.ts +++ b/packages/core/src/values/CacheBackedMap.ts @@ -1,4 +1,4 @@ -import type { JsonlDB } from "@alcalzone/jsonl-db"; +import { type Database } from "@zwave-js/shared/bindings"; export interface CacheBackedMapKeys { /** The common prefix all keys start with */ @@ -12,7 +12,7 @@ export interface CacheBackedMapKeys { /** Wrapper class which allows storing a Map as a subset of a JsonlDB */ export class CacheBackedMap implements Map { constructor( - private readonly cache: JsonlDB, + private readonly cache: Database, private readonly cacheKeys: CacheBackedMapKeys, ) { this.map = new Map(); diff --git a/packages/core/src/values/ValueDB.ts b/packages/core/src/values/ValueDB.ts index ddfe67842dd8..a46cb83dd3e8 100644 --- a/packages/core/src/values/ValueDB.ts +++ b/packages/core/src/values/ValueDB.ts @@ -1,5 +1,5 @@ -import type { JsonlDB } from "@alcalzone/jsonl-db"; import { TypedEventTarget } from "@zwave-js/shared"; +import { type Database } from "@zwave-js/shared/bindings"; import type { CommandClasses } from "../definitions/CommandClasses.js"; import { ZWaveError, @@ -106,8 +106,8 @@ export class ValueDB extends TypedEventTarget { */ public constructor( nodeId: number, - valueDB: JsonlDB, - metadataDB: JsonlDB, + valueDB: Database, + metadataDB: Database, ownKeys?: Set, ) { super(); @@ -119,8 +119,8 @@ export class ValueDB extends TypedEventTarget { } private nodeId: number; - private _db: JsonlDB; - private _metadata: JsonlDB; + private _db: Database; + private _metadata: Database; private _index: Set; private buildIndex(): Set { @@ -603,7 +603,9 @@ function compareDBKeyFast( } /** Extracts an index for each node from one or more JSONL DBs */ -export function indexDBsByNode(databases: JsonlDB[]): Map> { +export function indexDBsByNode( + databases: Database[], +): Map> { const indexes = new Map>(); for (const db of databases) { for (const key of db.keys()) { diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 084c122f418c..5639e83ab3f4 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -43,7 +43,7 @@ "@typescript-eslint/utils": "^8.8.1", "@zwave-js/core": "workspace:*", "eslint": "^9.12.0", - "eslint-compat-utils": "^0.5.1", + "eslint-compat-utils": "^0.6.4", "eslint-plugin-jsonc": "^2.16.0", "typescript": "5.6.2" } 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 9721ee83f0e4..6899eb054b83 100644 --- a/packages/serial/src/index.ts +++ b/packages/serial/src/index.ts @@ -8,13 +8,15 @@ export * from "./message/SuccessIndicator.js"; export * from "./message/ZnifferMessages.js"; export * from "./parsers/BootloaderParsers.js"; export * from "./parsers/SerialAPIParser.js"; -export * from "./serialport/ZWaveSerialPort.js"; -export * from "./serialport/ZWaveSerialPortBase.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/ZWaveSerialPortImplementation.js"; -export * from "./serialport/ZWaveSocket.js"; +export * from "./serialport/ZWaveSerialStream.js"; export * from "./serialport/ZWaveSocketOptions.js"; -export * from "./zniffer/ZnifferSerialPort.js"; -export * from "./zniffer/ZnifferSerialPortBase.js"; -export * from "./zniffer/ZnifferSocket.js"; +export * from "./serialport/definitions.js"; +export * from "./zniffer/ZnifferSerialStream.js"; export * from "./index_serialapi.js"; diff --git a/packages/serial/src/index_mock.ts b/packages/serial/src/index_mock.ts index e077354e3e17..60f70ead21eb 100644 --- a/packages/serial/src/index_mock.ts +++ b/packages/serial/src/index_mock.ts @@ -1,3 +1 @@ -export * from "./mock/MockSerialPort.js"; -export * from "./mock/SerialPortBindingMock.js"; -export * from "./mock/SerialPortMock.js"; +export * from "./mock/MockPort.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/mock/MockPort.ts b/packages/serial/src/mock/MockPort.ts new file mode 100644 index 000000000000..17f2756acc7a --- /dev/null +++ b/packages/serial/src/mock/MockPort.ts @@ -0,0 +1,81 @@ +import { ZWaveLogContainer } from "@zwave-js/core"; +import type { UnderlyingSink, UnderlyingSource } from "node:stream/web"; +import { + type ZWaveSerialBindingFactory, + type ZWaveSerialStream, + ZWaveSerialStreamFactory, +} from "../serialport/ZWaveSerialStream.js"; + +export class MockPort { + public constructor() { + const { readable, writable: sink } = new TransformStream(); + this.#sink = sink; + this.readable = readable; + } + + // Remembers the last written data + public lastWrite: Uint8Array | undefined; + + // Internal stream to allow emitting data from the port + #sourceController: ReadableStreamDefaultController | undefined; + + // Public readable stream to allow handling the written data + #sink: WritableStream; + /** Exposes the data written by the host as a readable stream */ + public readonly readable: ReadableStream; + + public factory(): ZWaveSerialBindingFactory { + return () => { + const sink: UnderlyingSink = { + write: async (chunk, _controller) => { + // Remember the last written data + this.lastWrite = chunk; + // Only write to the sink if its readable side has a reader attached. + // Otherwise, we get backpressure on the writable side of the mock port + if (this.readable.locked) { + const writer = this.#sink.getWriter(); + try { + await writer.write(chunk); + } finally { + writer.releaseLock(); + } + } + }, + }; + + const source: UnderlyingSource = { + start: (controller) => { + this.#sourceController = controller; + }, + }; + + return Promise.resolve({ sink, source }); + }; + } + + public emitData(data: Uint8Array): void { + this.#sourceController?.enqueue(data); + } + + public destroy(): void { + try { + this.#sourceController?.close(); + this.#sourceController = undefined; + } catch { + // Ignore - the controller might already be closed + } + } +} + +export async function createAndOpenMockedZWaveSerialPort(): Promise<{ + port: MockPort; + serial: ZWaveSerialStream; +}> { + const port = new MockPort(); + const factory = new ZWaveSerialStreamFactory( + port.factory(), + new ZWaveLogContainer({ enabled: false }), + ); + const serial = await factory.createStream(); + return { port, serial }; +} diff --git a/packages/serial/src/mock/MockSerialPort.ts b/packages/serial/src/mock/MockSerialPort.ts deleted file mode 100644 index 370272b8dd92..000000000000 --- a/packages/serial/src/mock/MockSerialPort.ts +++ /dev/null @@ -1,129 +0,0 @@ -// TODO: Get rid of this entire thing and use the serialport-based mocking instead. - -import { ZWaveLogContainer } from "@zwave-js/core"; -import { Mixin } from "@zwave-js/shared"; -import { EventEmitter } from "node:events"; -import { PassThrough } from "node:stream"; -import sinon from "sinon"; -import { ZWaveSerialPort } from "../serialport/ZWaveSerialPort.js"; -import type { ZWaveSerialPortEventCallbacks } from "../serialport/ZWaveSerialPortBase.js"; -import { - MockBinding as SerialPortMockBinding, - type MockPortBinding as SerialPortMockPortBinding, -} from "./SerialPortBindingMock.js"; -import { SerialPortMock } from "./SerialPortMock.js"; - -const instances = new Map(); - -@Mixin([EventEmitter]) -class MockBinding extends PassThrough {} - -interface MockSerialPortEventCallbacks extends ZWaveSerialPortEventCallbacks { - write: (data: Uint8Array) => void; -} - -type MockSerialPortEvents = Extract; - -export interface MockSerialPort { - on( - event: TEvent, - callback: MockSerialPortEventCallbacks[TEvent], - ): this; - addListener( - event: TEvent, - callback: MockSerialPortEventCallbacks[TEvent], - ): this; - once( - event: TEvent, - callback: MockSerialPortEventCallbacks[TEvent], - ): this; - off( - event: TEvent, - callback: MockSerialPortEventCallbacks[TEvent], - ): this; - removeListener( - event: TEvent, - callback: MockSerialPortEventCallbacks[TEvent], - ): this; - removeAllListeners(event?: MockSerialPortEvents): this; - - emit( - event: TEvent, - ...args: Parameters - ): boolean; -} - -export class MockSerialPort extends ZWaveSerialPort { - constructor(port: string, loggers: ZWaveLogContainer) { - super(port, loggers, MockBinding as any); - instances.set(port, this); - } - - public static getInstance(port: string): MockSerialPort | undefined { - return instances.get(port); - } - - private __isOpen: boolean = false; - public get isOpen(): boolean { - return this.__isOpen; - } - - public open(): Promise { - return this.openStub().then(() => { - this.__isOpen = true; - }); - } - public readonly openStub = sinon.stub().resolves(); - - public close(): Promise { - return this.closeStub().then(() => { - this.__isOpen = false; - }); - } - public readonly closeStub = sinon.stub().resolves(); - - public receiveData(data: Uint8Array): void { - this.serial.emit("data", data); - } - - public raiseError(err: Error): void { - this.emit("error", err); - } - - public writeAsync(data: Uint8Array): Promise { - this._lastWrite = data; - this.emit("write", data); - return this.writeStub(data); - } - public readonly writeStub = sinon.stub(); - - private _lastWrite: string | number[] | Uint8Array | undefined; - public get lastWrite(): string | number[] | Uint8Array | undefined { - return this._lastWrite; - } -} - -export async function createAndOpenMockedZWaveSerialPort( - path: string, -): Promise<{ - port: ZWaveSerialPort; - binding: SerialPortMockPortBinding; -}> { - SerialPortMockBinding.reset(); - SerialPortMockBinding.createPort(path, { - record: true, - readyData: new Uint8Array(), - }); - - const port = new ZWaveSerialPort( - path, - new ZWaveLogContainer({ - enabled: false, - }), - // @ts-expect-error We're using an internal signature here - SerialPortMock, - ); - await port.open(); - const binding = (port["serial"] as SerialPortMock).port!; - return { port, binding }; -} diff --git a/packages/serial/src/mock/SerialPortBindingMock.ts b/packages/serial/src/mock/SerialPortBindingMock.ts deleted file mode 100644 index 12a2a742b216..000000000000 --- a/packages/serial/src/mock/SerialPortBindingMock.ts +++ /dev/null @@ -1,406 +0,0 @@ -/* eslint-disable no-restricted-globals -- The serialport typings require a Node.js Buffer */ -/* eslint-disable @typescript-eslint/require-await */ -// Clone of https://github.com/serialport/binding-mock with support for emitting events on the written side - -import type { - BindingInterface, - BindingPortInterface, - OpenOptions, - PortInfo, - PortStatus, - SetOptions, - UpdateOptions, -} from "@serialport/bindings-interface"; -import { Bytes, TypedEventTarget, isUint8Array } from "@zwave-js/shared"; - -export interface MockPortInternal { - data: Uint8Array; - // echo: boolean; - // record: boolean; - info: PortInfo; - maxReadSize: number; - readyData?: Uint8Array; - openOpt?: OpenOptions; - instance?: MockPortBinding; -} - -export interface CreatePortOptions { - echo?: boolean; - record?: boolean; - readyData?: Uint8Array; - maxReadSize?: number; - manufacturer?: string; - vendorId?: string; - productId?: string; -} - -let ports: { - [key: string]: MockPortInternal; -} = {}; -let serialNumber = 0; - -function resolveNextTick() { - return new Promise((resolve) => process.nextTick(() => resolve())); -} - -export class CanceledError extends Error { - canceled: true; - constructor(message: string) { - super(message); - this.canceled = true; - } -} - -export interface MockBindingInterface - extends BindingInterface -{ - reset(): void; - createPort(path: string, opt?: CreatePortOptions): void; - getInstance(path: string): MockPortBinding | undefined; -} - -export const MockBinding: MockBindingInterface = { - reset() { - ports = {}; - serialNumber = 0; - }, - - // Create a mock port - createPort(path: string, options: CreatePortOptions = {}) { - serialNumber++; - const optWithDefaults = { - echo: false, - record: false, - manufacturer: "The J5 Robotics Company", - vendorId: undefined, - productId: undefined, - maxReadSize: 1024, - ...options, - }; - - ports[path] = { - data: new Uint8Array(), - // echo: optWithDefaults.echo, - // record: optWithDefaults.record, - readyData: optWithDefaults.readyData, - maxReadSize: optWithDefaults.maxReadSize, - info: { - path, - manufacturer: optWithDefaults.manufacturer, - serialNumber: `${serialNumber}`, - pnpId: undefined, - locationId: undefined, - vendorId: optWithDefaults.vendorId, - productId: optWithDefaults.productId, - }, - }; - }, - - async list() { - return Object.values(ports).map((port) => port.info); - }, - - async open(options) { - if (!options || typeof options !== "object" || Array.isArray(options)) { - throw new TypeError("\"options\" is not an object"); - } - - if (!options.path) { - throw new TypeError("\"path\" is not a valid port"); - } - - if (!options.baudRate) { - throw new TypeError("\"baudRate\" is not a valid baudRate"); - } - - const openOptions: Required = { - dataBits: 8, - lock: true, - stopBits: 1, - parity: "none", - rtscts: false, - xon: false, - xoff: false, - xany: false, - hupcl: true, - ...options, - }; - const { path } = openOptions; - - const port = ports[path]; - await resolveNextTick(); - if (!port) { - throw new Error( - `Port does not exist - please call MockBinding.createPort('${path}') first`, - ); - } - - if (port.openOpt?.lock) { - throw new Error("Port is locked cannot open"); - } - - port.openOpt = { ...openOptions }; - - port.instance = new MockPortBinding(port, openOptions); - return port.instance; - }, - - getInstance(path: string): MockPortBinding | undefined { - return ports[path]?.instance; - }, -}; - -interface MockPortBindingEvents { - write: (data: Uint8Array) => void; - close: () => void; -} - -/** - * Mock bindings for pretend serialport access - */ -export class MockPortBinding extends TypedEventTarget - implements BindingPortInterface -{ - readonly openOptions: Required; - readonly port: MockPortInternal; - private pendingRead: null | ((err: null | Error) => void); - lastWrite: null | Uint8Array; - recording: Uint8Array; - writeOperation: null | Promise; - isOpen: boolean; - serialNumber?: string; - - constructor(port: MockPortInternal, openOptions: Required) { - super(); - - this.port = port; - this.openOptions = openOptions; - this.pendingRead = null; - this.isOpen = true; - this.lastWrite = null; - this.recording = new Uint8Array(); - this.writeOperation = null; // in flight promise or null - this.serialNumber = port.info.serialNumber; - - if (port.readyData) { - const data = port.readyData; - process.nextTick(() => { - if (this.isOpen) { - this.emitData(data); - } - }); - } - } - - // Emit data on a mock port - emitData(data: Uint8Array | string): void { - if (!this.isOpen || !this.port) { - throw new Error("Port must be open to pretend to receive data"); - } - const bufferData = isUint8Array(data) ? data : Bytes.from(data); - this.port.data = Bytes.concat([this.port.data, bufferData]); - if (this.pendingRead) { - process.nextTick(this.pendingRead); - this.pendingRead = null; - } - } - - async close(): Promise { - if (!this.isOpen) { - throw new Error("Port is not open"); - } - - const port = this.port; - if (!port) { - throw new Error("already closed"); - } - - port.openOpt = undefined; - // reset data on close - port.data = new Uint8Array(); - this.serialNumber = undefined; - this.isOpen = false; - if (this.pendingRead) { - this.pendingRead(new CanceledError("port is closed")); - } - - this.emit("close"); - } - - async read( - buffer: Buffer, - offset: number, - length: number, - ): Promise<{ - buffer: Buffer; - bytesRead: number; - }> { - if (!Buffer.isBuffer(buffer)) { - throw new TypeError("\"buffer\" is not a Buffer"); - } - - if (typeof offset !== "number" || Number.isNaN(offset)) { - throw new TypeError( - `"offset" is not an integer got "${ - Number.isNaN(offset) ? "NaN" : typeof offset - }"`, - ); - } - - if (typeof length !== "number" || Number.isNaN(length)) { - throw new TypeError( - `"length" is not an integer got "${ - Number.isNaN(length) ? "NaN" : typeof length - }"`, - ); - } - - if (buffer.length < offset + length) { - throw new Error("buffer is too small"); - } - - if (!this.isOpen) { - throw new Error("Port is not open"); - } - - await resolveNextTick(); - if (!this.isOpen || !this.port) { - throw new CanceledError("Read canceled"); - } - if (this.port.data.length <= 0) { - return new Promise((resolve, reject) => { - this.pendingRead = async (err) => { - if (err) { - return reject(err); - } - try { - const readResult = await this.read( - buffer, - offset, - length, - ); - resolve(readResult); - } catch (e) { - reject(e as Error); - } - }; - }); - } - - const lengthToRead = this.port.maxReadSize > length - ? length - : this.port.maxReadSize; - - const data = this.port.data.subarray(0, lengthToRead); - buffer.set(data, offset); - this.port.data = this.port.data.subarray(lengthToRead); - return { bytesRead: data.length, buffer }; - } - - async write(buffer: Uint8Array): Promise { - if (!isUint8Array(buffer)) { - throw new TypeError("\"buffer\" is not a Uint8Array"); - } - - if (!this.isOpen || !this.port) { - throw new Error("Port is not open"); - } - - if (this.writeOperation) { - throw new Error( - "Overlapping writes are not supported and should be queued by the serialport object", - ); - } - this.writeOperation = (async () => { - await resolveNextTick(); - if (!this.isOpen || !this.port) { - return; - // throw new Error("Write canceled"); - } - const data = (this.lastWrite = Bytes.from(buffer)); // copy - this.emit("write", data); - - // if (this.port.record) { - // this.recording = Buffer.concat([this.recording, data]); - // } - // if (this.port.echo) { - // process.nextTick(() => { - // if (this.isOpen) { - // this.emitData(data); - // } - // }); - // } - this.writeOperation = null; - })(); - return this.writeOperation; - } - - async update(options: UpdateOptions): Promise { - if (typeof options !== "object") { - throw TypeError("\"options\" is not an object"); - } - - if (typeof options.baudRate !== "number") { - throw new TypeError("\"options.baudRate\" is not a number"); - } - - if (!this.isOpen || !this.port) { - throw new Error("Port is not open"); - } - await resolveNextTick(); - if (this.port.openOpt) { - this.port.openOpt.baudRate = options.baudRate; - } - } - - async set(options: SetOptions): Promise { - if (typeof options !== "object") { - throw new TypeError("\"options\" is not an object"); - } - if (!this.isOpen) { - throw new Error("Port is not open"); - } - await resolveNextTick(); - } - - async get(): Promise { - if (!this.isOpen) { - throw new Error("Port is not open"); - } - await resolveNextTick(); - return { - cts: true, - dsr: false, - dcd: false, - }; - } - - async getBaudRate(): Promise<{ baudRate: number }> { - if (!this.isOpen || !this.port) { - throw new Error("Port is not open"); - } - await resolveNextTick(); - if (!this.port.openOpt?.baudRate) { - throw new Error("Internal Error"); - } - return { - baudRate: this.port.openOpt.baudRate, - }; - } - - async flush(): Promise { - if (!this.isOpen || !this.port) { - throw new Error("Port is not open"); - } - await resolveNextTick(); - this.port.data = new Uint8Array(); - } - - async drain(): Promise { - if (!this.isOpen) { - throw new Error("Port is not open"); - } - await this.writeOperation; - await resolveNextTick(); - } -} diff --git a/packages/serial/src/mock/SerialPortMock.ts b/packages/serial/src/mock/SerialPortMock.ts deleted file mode 100644 index b2144b78f3ad..000000000000 --- a/packages/serial/src/mock/SerialPortMock.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Clone of https://github.com/serialport/node-serialport/blob/4e8a3c4a9f46a09d39374eb67a59ff10eb09a5cd/packages/serialport/lib/serialport-mock.ts -// with support for emitting events on the written side - -import { - type ErrorCallback, - type OpenOptions, - SerialPortStream, -} from "@serialport/stream"; -import { - MockBinding, - type MockBindingInterface, -} from "./SerialPortBindingMock.js"; - -export type SerialPortMockOpenOptions = Omit< - OpenOptions, - "binding" ->; - -export class SerialPortMock extends SerialPortStream { - // es tic list = MockBinding.list; - static readonly binding = MockBinding; - - constructor( - options: SerialPortMockOpenOptions, - openCallback?: ErrorCallback, - ) { - const opts: OpenOptions = { - binding: MockBinding, - ...options, - }; - super(opts, openCallback); - } -} diff --git a/packages/serial/src/parsers/BootloaderParsers.ts b/packages/serial/src/parsers/BootloaderParsers.ts index e35a7d209584..6f36b74f7d1b 100644 --- a/packages/serial/src/parsers/BootloaderParsers.ts +++ b/packages/serial/src/parsers/BootloaderParsers.ts @@ -1,39 +1,13 @@ -import { Transform, type TransformCallback } from "node:stream"; +import { Bytes } from "@zwave-js/shared"; +import { type Transformer } from "node:stream/web"; import type { SerialLogger } from "../log/Logger.js"; import { XModemMessageHeaders } from "../message/MessageHeaders.js"; - -export enum BootloaderChunkType { - Error, - Menu, - Message, - FlowControl, -} - -export type BootloaderChunk = - | { - type: BootloaderChunkType.Error; - error: string; - _raw: string; - } - | { - type: BootloaderChunkType.Menu; - version: string; - options: { num: number; option: string }[]; - _raw: string; - } - | { - type: BootloaderChunkType.Message; - message: string; - _raw: string; - } - | { - type: BootloaderChunkType.FlowControl; - command: - | XModemMessageHeaders.ACK - | XModemMessageHeaders.NAK - | XModemMessageHeaders.CAN - | XModemMessageHeaders.C; - }; +import { + type BootloaderChunk, + BootloaderChunkType, + type ZWaveSerialFrame, + ZWaveSerialFrameType, +} from "./ZWaveSerialFrame.js"; function isFlowControl(byte: number): boolean { return ( @@ -44,27 +18,24 @@ function isFlowControl(byte: number): boolean { ); } -/** Parses the screen output from the bootloader, either waiting for a NUL char or a timeout */ -export class BootloaderScreenParser extends Transform { - constructor(private logger?: SerialLogger) { - // We read byte streams but emit messages - super({ readableObjectMode: true }); - } +class BootloaderScreenParserTransformer + implements Transformer +{ + constructor(private logger?: SerialLogger) {} private receiveBuffer = ""; private flushTimeout: NodeJS.Timeout | undefined; - _transform( - chunk: any, - encoding: string, - callback: TransformCallback, - ): void { + transform( + chunk: Uint8Array, + controller: TransformStreamDefaultController, + ) { if (this.flushTimeout) { clearTimeout(this.flushTimeout); this.flushTimeout = undefined; } - this.receiveBuffer += chunk.toString("utf8"); + this.receiveBuffer += Bytes.view(chunk).toString("utf8"); // Correct buggy ordering of NUL char in error codes. // The bootloader may send errors as "some error 0x\012" instead of "some error 0x12\0" @@ -82,7 +53,7 @@ export class BootloaderScreenParser extends Transform { if (screen === "") continue; this.logger?.bootloaderScreen(screen); - this.push(screen); + controller.enqueue(screen); } // Emit single flow-control bytes @@ -91,7 +62,7 @@ export class BootloaderScreenParser extends Transform { if (!isFlowControl(charCode)) break; this.logger?.data("inbound", Uint8Array.from([charCode])); - this.push(charCode); + controller.enqueue(charCode); this.receiveBuffer = this.receiveBuffer.slice(1); } @@ -99,12 +70,21 @@ export class BootloaderScreenParser extends Transform { if (this.receiveBuffer) { this.flushTimeout = setTimeout(() => { this.flushTimeout = undefined; - this.push(this.receiveBuffer); + controller.enqueue(this.receiveBuffer); this.receiveBuffer = ""; }, 500); } + } +} - callback(); +/** Parses the screen output from the bootloader, either waiting for a NUL char or a timeout */ +export class BootloaderScreenParser + extends TransformStream +{ + constructor( + logger?: SerialLogger, + ) { + super(new BootloaderScreenParserTransformer(logger)); } } @@ -116,77 +96,84 @@ const menuSuffix = "BL >"; const optionsRegex = /^(?\d+)\. (?