From 2efdbe3291f296f8e51bd2f4012fd443f2076c84 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Wed, 6 Nov 2024 21:19:47 +0100 Subject: [PATCH 1/3] refactor: reorganize definitions in @zwave/core --- packages/core/src/consts/Transmission.ts | 433 ------------------ .../ControllerStatus.ts | 0 .../src/definitions/EncapsulationFlags.ts | 7 + packages/core/src/definitions/Frame.ts | 2 + .../{consts => definitions}/InterviewStage.ts | 0 .../core/src/definitions/MessagePriority.ts | 33 ++ packages/core/src/definitions/NodeID.ts | 18 + packages/core/src/definitions/NodeInfo.ts | 2 +- .../src/{consts => definitions}/NodeStatus.ts | 0 packages/core/src/definitions/RSSI.ts | 55 +++ packages/core/src/definitions/Route.ts | 3 + .../core/src/definitions/RoutingScheme.ts | 42 ++ packages/core/src/definitions/Supervision.ts | 108 +++++ packages/core/src/definitions/TXReport.ts | 49 ++ packages/core/src/definitions/Transactions.ts | 29 ++ .../Transmission.test.ts | 2 +- packages/core/src/definitions/Transmission.ts | 119 +++++ .../index.ts => definitions/consts.ts} | 24 - packages/core/src/definitions/index.ts | 13 + packages/core/src/index.ts | 2 +- packages/core/src/index_browser.ts | 2 +- packages/core/src/index_safe.ts | 8 +- packages/core/src/log/Controller.test.ts | 2 +- packages/core/src/log/Controller.ts | 2 +- packages/core/src/security/Manager2.ts | 2 +- packages/core/src/traits/CommandClasses.ts | 10 +- packages/core/src/traits/Endpoints.ts | 2 +- .../src => core/src/traits}/FileSystem.ts | 0 packages/core/src/traits/Nodes.ts | 2 +- packages/core/src/values/Primitive.ts | 2 +- packages/host/src/index.ts | 1 - packages/host/src/index_safe.ts | 1 - packages/host/tsconfig.json | 2 +- packages/zwave-js/src/Driver.ts | 2 +- .../zwave-js/src/lib/driver/NetworkCache.ts | 2 +- .../zwave-js/src/lib/driver/ZWaveOptions.ts | 9 +- 36 files changed, 507 insertions(+), 483 deletions(-) delete mode 100644 packages/core/src/consts/Transmission.ts rename packages/core/src/{consts => definitions}/ControllerStatus.ts (100%) create mode 100644 packages/core/src/definitions/EncapsulationFlags.ts rename packages/core/src/{consts => definitions}/InterviewStage.ts (100%) create mode 100644 packages/core/src/definitions/MessagePriority.ts create mode 100644 packages/core/src/definitions/NodeID.ts rename packages/core/src/{consts => definitions}/NodeStatus.ts (100%) create mode 100644 packages/core/src/definitions/RSSI.ts create mode 100644 packages/core/src/definitions/RoutingScheme.ts create mode 100644 packages/core/src/definitions/Supervision.ts create mode 100644 packages/core/src/definitions/TXReport.ts create mode 100644 packages/core/src/definitions/Transactions.ts rename packages/core/src/{consts => definitions}/Transmission.test.ts (90%) create mode 100644 packages/core/src/definitions/Transmission.ts rename packages/core/src/{consts/index.ts => definitions/consts.ts} (52%) rename packages/{host/src => core/src/traits}/FileSystem.ts (100%) diff --git a/packages/core/src/consts/Transmission.ts b/packages/core/src/consts/Transmission.ts deleted file mode 100644 index 821cd96163b1..000000000000 --- a/packages/core/src/consts/Transmission.ts +++ /dev/null @@ -1,433 +0,0 @@ -import { num2hex } from "@zwave-js/shared/safe"; -import { isObject } from "alcalzone-shared/typeguards"; -import type { ProtocolDataRate } from "../definitions/Protocol.js"; -import { type SecurityClass } from "../definitions/SecurityClass.js"; -import type { CCId } from "../traits/CommandClasses.js"; -import { Duration } from "../values/Duration.js"; - -/** The priority of messages, sorted from high (0) to low (>0) */ -export enum MessagePriority { - // High-priority controller commands that must be handled before all other commands. - // We use this priority to decide which messages go onto the immediate queue. - ControllerImmediate = 0, - // Controller commands finish quickly and should be preferred over node queries - Controller, - // Some node commands like nonces, responses to Supervision and Transport Service - // need to be handled before all node commands. - // We use this priority to decide which messages go onto the immediate queue. - Immediate, - // To avoid S2 collisions, some commands that normally have Immediate priority - // have to go onto the normal queue, but still before all other messages - ImmediateLow, - // Pings (NoOP) are used for device probing at startup and for network diagnostics - Ping, - // Whenever sleeping devices wake up, their queued messages must be handled quickly - // because they want to go to sleep soon. So prioritize them over non-sleeping devices - WakeUp, - // Normal operation and node data exchange - Normal, - // Node querying is expensive and happens whenever a new node is discovered. - // In order to keep the system responsive, give them a lower priority - NodeQuery, - // Some devices need their state to be polled at regular intervals. Only do that when - // nothing else needs to be done - Poll, -} - -export function isMessagePriority(val: unknown): val is MessagePriority { - return typeof val === "number" && val in MessagePriority; -} - -export type MulticastDestination = [number, number, ...number[]]; - -export enum TransmitOptions { - NotSet = 0, - - ACK = 1 << 0, - LowPower = 1 << 1, - AutoRoute = 1 << 2, - - NoRoute = 1 << 4, - Explore = 1 << 5, - - DEFAULT = ACK | AutoRoute | Explore, - DEFAULT_NOACK = DEFAULT & ~ACK, -} - -export enum TransmitStatus { - OK = 0x00, - NoAck = 0x01, - Fail = 0x02, - NotIdle = 0x03, - NoRoute = 0x04, -} - -export type FrameType = "singlecast" | "broadcast" | "multicast"; - -/** A number between -128 and +124 dBm or one of the special values in {@link RssiError} indicating an error */ -export type RSSI = number | RssiError; - -export enum RssiError { - NotAvailable = 127, - ReceiverSaturated = 126, - NoSignalDetected = 125, -} - -export function isRssiError(rssi: RSSI): rssi is RssiError { - return rssi >= RssiError.NoSignalDetected; -} - -/** Averages RSSI measurements using an exponential moving average with the given weight for the accumulator */ -export function averageRSSI( - acc: number | undefined, - rssi: RSSI, - weight: number, -): number { - if (isRssiError(rssi)) { - switch (rssi) { - case RssiError.NotAvailable: - // If we don't have a value yet, return 0 - return acc ?? 0; - case RssiError.ReceiverSaturated: - // Assume rssi is 0 dBm - rssi = 0; - break; - case RssiError.NoSignalDetected: - // Assume rssi is -128 dBm - rssi = -128; - break; - } - } - - if (acc == undefined) return rssi; - return Math.round(acc * weight + rssi * (1 - weight)); -} - -/** - * Converts an RSSI value to a human readable format, i.e. the measurement including the unit or the corresponding error message. - */ -export function rssiToString(rssi: RSSI): string { - switch (rssi) { - case RssiError.NotAvailable: - return "N/A"; - case RssiError.ReceiverSaturated: - return "Receiver saturated"; - case RssiError.NoSignalDetected: - return "No signal detected"; - default: - return `${rssi} dBm`; - } -} - -/** - * How the controller transmitted a frame to a node. - */ -export enum RoutingScheme { - Idle, - Direct, - Priority, - LWR, - NLWR, - Auto, - ResortDirect, - Explore, -} - -/** - * Converts a routing scheme value to a human readable format. - */ -export function routingSchemeToString(scheme: RoutingScheme): string { - switch (scheme) { - case RoutingScheme.Idle: - return "Idle"; - case RoutingScheme.Direct: - return "Direct"; - case RoutingScheme.Priority: - return "Priority Route"; - case RoutingScheme.LWR: - return "LWR"; - case RoutingScheme.NLWR: - return "NLWR"; - case RoutingScheme.Auto: - return "Auto Route"; - case RoutingScheme.ResortDirect: - return "Resort to Direct"; - case RoutingScheme.Explore: - return "Explorer Frame"; - default: - return `Unknown (${num2hex(scheme)})`; - } -} - -/** Information about the transmission as received by the controller */ -export interface TXReport { - /** Transmission time in ticks (multiples of 10ms) */ - txTicks: number; - /** RSSI value of the acknowledgement frame */ - ackRSSI?: RSSI; - /** RSSI values of the incoming acknowledgement frame, measured by repeater 0...3 */ - ackRepeaterRSSI?: [RSSI?, RSSI?, RSSI?, RSSI?]; - /** Channel number the acknowledgement frame is received on */ - ackChannelNo?: number; - /** Channel number used to transmit the data */ - txChannelNo: number; - /** State of the route resolution for the transmission attempt. Encoding is manufacturer specific. Z-Wave JS uses the Silicon Labs interpretation. */ - routeSchemeState: RoutingScheme; - /** Node IDs of the repeater 0..3 used in the route. */ - repeaterNodeIds: [number?, number?, number?, number?]; - /** Whether the destination requires a 1000ms beam to be reached */ - beam1000ms: boolean; - /** Whether the destination requires a 250ms beam to be reached */ - beam250ms: boolean; - /** Transmission speed used in the route */ - routeSpeed: ProtocolDataRate; - /** How many routing attempts have been made to transmit the payload */ - routingAttempts: number; - /** When a route failed, this indicates the last functional Node ID in the last used route */ - failedRouteLastFunctionalNodeId?: number; - /** When a route failed, this indicates the first non-functional Node ID in the last used route */ - failedRouteFirstNonFunctionalNodeId?: number; - /** Transmit power used for the transmission in dBm */ - txPower?: number; - /** Measured noise floor during the outgoing transmission */ - measuredNoiseFloor?: RSSI; - /** TX power in dBm used by the destination to transmit the ACK */ - destinationAckTxPower?: number; - /** Measured RSSI of the acknowledgement frame received from the destination */ - destinationAckMeasuredRSSI?: RSSI; - /** Noise floor measured by the destination during the ACK transmission */ - destinationAckMeasuredNoiseFloor?: RSSI; -} - -/** Information about the transmission, but for serialization in mocks */ -export type SerializableTXReport = - & Partial> - & Pick; - -export interface SendMessageOptions { - /** The priority of the message to send. If none is given, the defined default priority of the message class will be used. */ - priority?: MessagePriority; - /** If an exception should be thrown when the message to send is not supported. Setting this to false is is useful if the capabilities haven't been determined yet. Default: true */ - supportCheck?: boolean; - /** - * Whether the driver should update the node status to asleep or dead when a transaction is not acknowledged (repeatedly). - * Setting this to false will cause the simply transaction to be rejected on failure. - * Default: true - */ - changeNodeStatusOnMissingACK?: boolean; - /** Sets the number of milliseconds after which a queued message expires. When the expiration timer elapses, the promise is rejected with the error code `Controller_MessageExpired`. */ - expire?: number; - /** - * @internal - * Information used to identify or mark this transaction - */ - tag?: any; - /** - * @internal - * Whether the send thread MUST be paused after this message was handled - */ - pauseSendThread?: boolean; - /** If a Wake Up On Demand should be requested for the target node. */ - requestWakeUpOnDemand?: boolean; - /** - * When a message sent to a node results in a TX report to be received, this callback will be called. - * For multi-stage messages, the callback may be called multiple times. - */ - onTXReport?: (report: TXReport) => void; - - /** Will be called when the transaction for this message progresses. */ - onProgress?: TransactionProgressListener; -} - -export enum EncapsulationFlags { - None = 0, - Supervision = 1 << 0, - // Multi Channel is tracked through the endpoint index - Security = 1 << 1, - CRC16 = 1 << 2, -} - -export type SupervisionOptions = - | ( - & { - /** Whether supervision may be used. `false` disables supervision. Default: `"auto"`. */ - useSupervision?: "auto"; - } - & ( - | { - requestStatusUpdates?: false; - } - | { - requestStatusUpdates: true; - onUpdate: SupervisionUpdateHandler; - } - ) - ) - | { - useSupervision: false; - }; - -export type SendCommandSecurityS2Options = { - /** Send the command using a different (lower) security class */ - s2OverrideSecurityClass?: SecurityClass; - /** Whether delivery of non-supervised SET-type commands is verified by waiting for potential Nonce Reports. Default: true */ - s2VerifyDelivery?: boolean; - /** Whether the MOS extension should be included in S2 message encapsulation. */ - s2MulticastOutOfSync?: boolean; - /** The optional multicast group ID to use for S2 message encapsulation. */ - s2MulticastGroupId?: number; -}; - -export type SendCommandOptions = - & SendMessageOptions - & SupervisionOptions - & SendCommandSecurityS2Options - & { - /** How many times the driver should try to send the message. Defaults to the configured Driver option */ - maxSendAttempts?: number; - /** Whether the driver should automatically handle the encapsulation. Default: true */ - autoEncapsulate?: boolean; - /** Used to send a response with the same encapsulation flags as the corresponding request. */ - encapsulationFlags?: EncapsulationFlags; - /** Overwrite the default transmit options */ - transmitOptions?: TransmitOptions; - /** Overwrite the default report timeout */ - reportTimeoutMs?: number; - }; - -export type SendCommandReturnType = - undefined extends TResponse ? SupervisionResult | undefined - : TResponse | undefined; - -export enum SupervisionStatus { - NoSupport = 0x00, - Working = 0x01, - Fail = 0x02, - Success = 0xff, -} - -export type SupervisionResult = - | { - status: - | SupervisionStatus.NoSupport - | SupervisionStatus.Fail - | SupervisionStatus.Success; - remainingDuration?: undefined; - } - | { - status: SupervisionStatus.Working; - remainingDuration: Duration; - }; - -export type SupervisionUpdateHandler = (update: SupervisionResult) => void; - -export function isSupervisionResult(obj: unknown): obj is SupervisionResult { - return ( - isObject(obj) - && "status" in obj - && typeof SupervisionStatus[obj.status as any] === "string" - ); -} - -export function supervisedCommandSucceeded( - result: unknown, -): result is SupervisionResult & { - status: SupervisionStatus.Success | SupervisionStatus.Working; -} { - return ( - isSupervisionResult(result) - && (result.status === SupervisionStatus.Success - || result.status === SupervisionStatus.Working) - ); -} - -export function supervisedCommandFailed( - result: unknown, -): result is SupervisionResult & { - status: SupervisionStatus.Fail | SupervisionStatus.NoSupport; -} { - return ( - isSupervisionResult(result) - && (result.status === SupervisionStatus.Fail - || result.status === SupervisionStatus.NoSupport) - ); -} - -export function isUnsupervisedOrSucceeded( - result: SupervisionResult | undefined, -): result is - | undefined - | (SupervisionResult & { - status: SupervisionStatus.Success | SupervisionStatus.Working; - }) -{ - return !result || supervisedCommandSucceeded(result); -} - -/** Figures out the final supervision result from an array of things that may be supervision results */ -export function mergeSupervisionResults( - results: unknown[], -): SupervisionResult | undefined { - const supervisionResults = results.filter(isSupervisionResult); - if (!supervisionResults.length) return undefined; - - if (supervisionResults.some((r) => r.status === SupervisionStatus.Fail)) { - return { - status: SupervisionStatus.Fail, - }; - } else if ( - supervisionResults.some((r) => r.status === SupervisionStatus.NoSupport) - ) { - return { - status: SupervisionStatus.NoSupport, - }; - } - const working = supervisionResults.filter( - (r): r is SupervisionResult & { status: SupervisionStatus.Working } => - r.status === SupervisionStatus.Working, - ); - if (working.length > 0) { - const durations = working.map((r) => - r.remainingDuration.serializeSet() - ); - const maxDuration = (durations.length > 0 - && Duration.parseReport(Math.max(...durations))) - || Duration.unknown(); - return { - status: SupervisionStatus.Working, - remainingDuration: maxDuration, - }; - } - return { - status: SupervisionStatus.Success, - }; -} - -/** - * The state a transaction is in. - */ -export enum TransactionState { - /** The transaction is currently queued */ - Queued, - /** The transaction is currently being handled */ - Active, - /** The transaction was completed */ - Completed, - /** The transaction failed */ - Failed, -} - -export type TransactionProgress = { - state: - | TransactionState.Queued - | TransactionState.Active - | TransactionState.Completed; -} | { - state: TransactionState.Failed; - /** Why the transaction failed */ - reason?: string; -}; - -export type TransactionProgressListener = ( - progress: TransactionProgress, -) => void; diff --git a/packages/core/src/consts/ControllerStatus.ts b/packages/core/src/definitions/ControllerStatus.ts similarity index 100% rename from packages/core/src/consts/ControllerStatus.ts rename to packages/core/src/definitions/ControllerStatus.ts diff --git a/packages/core/src/definitions/EncapsulationFlags.ts b/packages/core/src/definitions/EncapsulationFlags.ts new file mode 100644 index 000000000000..38bbd660eaec --- /dev/null +++ b/packages/core/src/definitions/EncapsulationFlags.ts @@ -0,0 +1,7 @@ +export enum EncapsulationFlags { + None = 0, + Supervision = 1 << 0, + // Multi Channel is tracked through the endpoint index + Security = 1 << 1, + CRC16 = 1 << 2, +} diff --git a/packages/core/src/definitions/Frame.ts b/packages/core/src/definitions/Frame.ts index 8d4dc1750540..61073c1d138b 100644 --- a/packages/core/src/definitions/Frame.ts +++ b/packages/core/src/definitions/Frame.ts @@ -12,3 +12,5 @@ export enum BeamingInfo { LongContinuous = 0b10, Fragmented = 0b100, } + +export type FrameType = "singlecast" | "broadcast" | "multicast"; diff --git a/packages/core/src/consts/InterviewStage.ts b/packages/core/src/definitions/InterviewStage.ts similarity index 100% rename from packages/core/src/consts/InterviewStage.ts rename to packages/core/src/definitions/InterviewStage.ts diff --git a/packages/core/src/definitions/MessagePriority.ts b/packages/core/src/definitions/MessagePriority.ts new file mode 100644 index 000000000000..bd56928f906b --- /dev/null +++ b/packages/core/src/definitions/MessagePriority.ts @@ -0,0 +1,33 @@ +/** The priority of messages, sorted from high (0) to low (>0) */ + +export enum MessagePriority { + // High-priority controller commands that must be handled before all other commands. + // We use this priority to decide which messages go onto the immediate queue. + ControllerImmediate = 0, + // Controller commands finish quickly and should be preferred over node queries + Controller, + // Some node commands like nonces, responses to Supervision and Transport Service + // need to be handled before all node commands. + // We use this priority to decide which messages go onto the immediate queue. + Immediate, + // To avoid S2 collisions, some commands that normally have Immediate priority + // have to go onto the normal queue, but still before all other messages + ImmediateLow, + // Pings (NoOP) are used for device probing at startup and for network diagnostics + Ping, + // Whenever sleeping devices wake up, their queued messages must be handled quickly + // because they want to go to sleep soon. So prioritize them over non-sleeping devices + WakeUp, + // Normal operation and node data exchange + Normal, + // Node querying is expensive and happens whenever a new node is discovered. + // In order to keep the system responsive, give them a lower priority + NodeQuery, + // Some devices need their state to be polled at regular intervals. Only do that when + // nothing else needs to be done + Poll, +} + +export function isMessagePriority(val: unknown): val is MessagePriority { + return typeof val === "number" && val in MessagePriority; +} diff --git a/packages/core/src/definitions/NodeID.ts b/packages/core/src/definitions/NodeID.ts new file mode 100644 index 000000000000..aa6eb48dd470 --- /dev/null +++ b/packages/core/src/definitions/NodeID.ts @@ -0,0 +1,18 @@ +import { MAX_NODES } from "./consts.js"; + +export enum NodeIDType { + Short = 0x01, + Long = 0x02, +} + +/** The broadcast target node id */ +export const NODE_ID_BROADCAST = 0xff; + +/** The broadcast target node id for Z-Wave LR */ +export const NODE_ID_BROADCAST_LR = 0xfff; + +/** The highest allowed node id */ +// FIXME: Rename probably +export const NODE_ID_MAX = MAX_NODES; + +export type MulticastDestination = [number, number, ...number[]]; diff --git a/packages/core/src/definitions/NodeInfo.ts b/packages/core/src/definitions/NodeInfo.ts index 81eb6d454eda..60083f214f64 100644 --- a/packages/core/src/definitions/NodeInfo.ts +++ b/packages/core/src/definitions/NodeInfo.ts @@ -1,9 +1,9 @@ import { Bytes } from "@zwave-js/shared/safe"; import { sum } from "@zwave-js/shared/safe"; -import { NodeIDType } from "../consts/index.js"; import { type BasicDeviceClass } from "../registries/DeviceClasses.js"; import { validatePayload } from "../util/misc.js"; import { CommandClasses } from "./CommandClasses.js"; +import { NodeIDType } from "./NodeID.js"; import { type ProtocolVersion } from "./Protocol.js"; export interface ApplicationNodeInformation { diff --git a/packages/core/src/consts/NodeStatus.ts b/packages/core/src/definitions/NodeStatus.ts similarity index 100% rename from packages/core/src/consts/NodeStatus.ts rename to packages/core/src/definitions/NodeStatus.ts diff --git a/packages/core/src/definitions/RSSI.ts b/packages/core/src/definitions/RSSI.ts new file mode 100644 index 000000000000..1168e6035af7 --- /dev/null +++ b/packages/core/src/definitions/RSSI.ts @@ -0,0 +1,55 @@ +/** A number between -128 and +124 dBm or one of the special values in {@link RssiError} indicating an error */ + +export type RSSI = number | RssiError; + +export enum RssiError { + NotAvailable = 127, + ReceiverSaturated = 126, + NoSignalDetected = 125, +} + +export function isRssiError(rssi: RSSI): rssi is RssiError { + return rssi >= RssiError.NoSignalDetected; +} +/** Averages RSSI measurements using an exponential moving average with the given weight for the accumulator */ + +export function averageRSSI( + acc: number | undefined, + rssi: RSSI, + weight: number, +): number { + if (isRssiError(rssi)) { + switch (rssi) { + case RssiError.NotAvailable: + // If we don't have a value yet, return 0 + return acc ?? 0; + case RssiError.ReceiverSaturated: + // Assume rssi is 0 dBm + rssi = 0; + break; + case RssiError.NoSignalDetected: + // Assume rssi is -128 dBm + rssi = -128; + break; + } + } + + if (acc == undefined) return rssi; + return Math.round(acc * weight + rssi * (1 - weight)); +} +/** + * Converts an RSSI value to a human readable format, i.e. the measurement including the unit or the corresponding error message. + */ + +export function rssiToString(rssi: RSSI): string { + switch (rssi) { + case RssiError.NotAvailable: + return "N/A"; + case RssiError.ReceiverSaturated: + return "Receiver saturated"; + case RssiError.NoSignalDetected: + return "No signal detected"; + default: + return `${rssi} dBm`; + } +} diff --git a/packages/core/src/definitions/Route.ts b/packages/core/src/definitions/Route.ts index 178ac486b5b6..528d471d3932 100644 --- a/packages/core/src/definitions/Route.ts +++ b/packages/core/src/definitions/Route.ts @@ -26,3 +26,6 @@ export function isEmptyRoute(route: Route): boolean { && route.routeSpeed === ZWaveDataRate["9k6"] ); } + +/** How many repeaters can appear in a route */ +export const MAX_REPEATERS = 4; diff --git a/packages/core/src/definitions/RoutingScheme.ts b/packages/core/src/definitions/RoutingScheme.ts new file mode 100644 index 000000000000..af9b6868e3d8 --- /dev/null +++ b/packages/core/src/definitions/RoutingScheme.ts @@ -0,0 +1,42 @@ +import { num2hex } from "@zwave-js/shared/safe"; + +/** + * How the controller transmitted a frame to a node. + */ + +export enum RoutingScheme { + Idle, + Direct, + Priority, + LWR, + NLWR, + Auto, + ResortDirect, + Explore, +} +/** + * Converts a routing scheme value to a human readable format. + */ + +export function routingSchemeToString(scheme: RoutingScheme): string { + switch (scheme) { + case RoutingScheme.Idle: + return "Idle"; + case RoutingScheme.Direct: + return "Direct"; + case RoutingScheme.Priority: + return "Priority Route"; + case RoutingScheme.LWR: + return "LWR"; + case RoutingScheme.NLWR: + return "NLWR"; + case RoutingScheme.Auto: + return "Auto Route"; + case RoutingScheme.ResortDirect: + return "Resort to Direct"; + case RoutingScheme.Explore: + return "Explorer Frame"; + default: + return `Unknown (${num2hex(scheme)})`; + } +} diff --git a/packages/core/src/definitions/Supervision.ts b/packages/core/src/definitions/Supervision.ts new file mode 100644 index 000000000000..09f78f53934a --- /dev/null +++ b/packages/core/src/definitions/Supervision.ts @@ -0,0 +1,108 @@ +import { isObject } from "alcalzone-shared/typeguards"; +import { Duration } from "../values/Duration.js"; + +export enum SupervisionStatus { + NoSupport = 0x00, + Working = 0x01, + Fail = 0x02, + Success = 0xff, +} + +export type SupervisionResult = + | { + status: + | SupervisionStatus.NoSupport + | SupervisionStatus.Fail + | SupervisionStatus.Success; + remainingDuration?: undefined; + } + | { + status: SupervisionStatus.Working; + remainingDuration: Duration; + }; + +export type SupervisionUpdateHandler = (update: SupervisionResult) => void; + +export function isSupervisionResult(obj: unknown): obj is SupervisionResult { + return ( + isObject(obj) + && "status" in obj + && typeof SupervisionStatus[obj.status as any] === "string" + ); +} + +export function supervisedCommandSucceeded( + result: unknown, +): result is SupervisionResult & { + status: SupervisionStatus.Success | SupervisionStatus.Working; +} { + return ( + isSupervisionResult(result) + && (result.status === SupervisionStatus.Success + || result.status === SupervisionStatus.Working) + ); +} + +export function supervisedCommandFailed( + result: unknown, +): result is SupervisionResult & { + status: SupervisionStatus.Fail | SupervisionStatus.NoSupport; +} { + return ( + isSupervisionResult(result) + && (result.status === SupervisionStatus.Fail + || result.status === SupervisionStatus.NoSupport) + ); +} + +export function isUnsupervisedOrSucceeded( + result: SupervisionResult | undefined, +): result is + | undefined + | (SupervisionResult & { + status: SupervisionStatus.Success | SupervisionStatus.Working; + }) +{ + return !result || supervisedCommandSucceeded(result); +} + +/** Figures out the final supervision result from an array of things that may be supervision results */ +export function mergeSupervisionResults( + results: unknown[], +): SupervisionResult | undefined { + const supervisionResults = results.filter(isSupervisionResult); + if (!supervisionResults.length) return undefined; + + if (supervisionResults.some((r) => r.status === SupervisionStatus.Fail)) { + return { + status: SupervisionStatus.Fail, + }; + } else if ( + supervisionResults.some((r) => r.status === SupervisionStatus.NoSupport) + ) { + return { + status: SupervisionStatus.NoSupport, + }; + } + const working = supervisionResults.filter( + (r): r is SupervisionResult & { status: SupervisionStatus.Working } => + r.status === SupervisionStatus.Working, + ); + if (working.length > 0) { + const durations = working.map((r) => + r.remainingDuration.serializeSet() + ); + const maxDuration = (durations.length > 0 + && Duration.parseReport(Math.max(...durations))) + || Duration.unknown(); + return { + status: SupervisionStatus.Working, + remainingDuration: maxDuration, + }; + } + return { + status: SupervisionStatus.Success, + }; +} + +export const MAX_SUPERVISION_SESSION_ID = 0b111111; diff --git a/packages/core/src/definitions/TXReport.ts b/packages/core/src/definitions/TXReport.ts new file mode 100644 index 000000000000..4b003689cd01 --- /dev/null +++ b/packages/core/src/definitions/TXReport.ts @@ -0,0 +1,49 @@ +import type { ProtocolDataRate } from "./Protocol.js"; +import type { RSSI } from "./RSSI.js"; +import type { RoutingScheme } from "./RoutingScheme.js"; + +/** Information about the transmission as received by the controller */ + +export interface TXReport { + /** Transmission time in ticks (multiples of 10ms) */ + txTicks: number; + /** RSSI value of the acknowledgement frame */ + ackRSSI?: RSSI; + /** RSSI values of the incoming acknowledgement frame, measured by repeater 0...3 */ + ackRepeaterRSSI?: [RSSI?, RSSI?, RSSI?, RSSI?]; + /** Channel number the acknowledgement frame is received on */ + ackChannelNo?: number; + /** Channel number used to transmit the data */ + txChannelNo: number; + /** State of the route resolution for the transmission attempt. Encoding is manufacturer specific. Z-Wave JS uses the Silicon Labs interpretation. */ + routeSchemeState: RoutingScheme; + /** Node IDs of the repeater 0..3 used in the route. */ + repeaterNodeIds: [number?, number?, number?, number?]; + /** Whether the destination requires a 1000ms beam to be reached */ + beam1000ms: boolean; + /** Whether the destination requires a 250ms beam to be reached */ + beam250ms: boolean; + /** Transmission speed used in the route */ + routeSpeed: ProtocolDataRate; + /** How many routing attempts have been made to transmit the payload */ + routingAttempts: number; + /** When a route failed, this indicates the last functional Node ID in the last used route */ + failedRouteLastFunctionalNodeId?: number; + /** When a route failed, this indicates the first non-functional Node ID in the last used route */ + failedRouteFirstNonFunctionalNodeId?: number; + /** Transmit power used for the transmission in dBm */ + txPower?: number; + /** Measured noise floor during the outgoing transmission */ + measuredNoiseFloor?: RSSI; + /** TX power in dBm used by the destination to transmit the ACK */ + destinationAckTxPower?: number; + /** Measured RSSI of the acknowledgement frame received from the destination */ + destinationAckMeasuredRSSI?: RSSI; + /** Noise floor measured by the destination during the ACK transmission */ + destinationAckMeasuredNoiseFloor?: RSSI; +} +/** Information about the transmission, but for serialization in mocks */ + +export type SerializableTXReport = + & Partial> + & Pick; diff --git a/packages/core/src/definitions/Transactions.ts b/packages/core/src/definitions/Transactions.ts new file mode 100644 index 000000000000..81a6fef077c8 --- /dev/null +++ b/packages/core/src/definitions/Transactions.ts @@ -0,0 +1,29 @@ +/** + * The state a transaction is in. + */ + +export enum TransactionState { + /** The transaction is currently queued */ + Queued, + /** The transaction is currently being handled */ + Active, + /** The transaction was completed */ + Completed, + /** The transaction failed */ + Failed, +} + +export type TransactionProgress = { + state: + | TransactionState.Queued + | TransactionState.Active + | TransactionState.Completed; +} | { + state: TransactionState.Failed; + /** Why the transaction failed */ + reason?: string; +}; + +export type TransactionProgressListener = ( + progress: TransactionProgress, +) => void; diff --git a/packages/core/src/consts/Transmission.test.ts b/packages/core/src/definitions/Transmission.test.ts similarity index 90% rename from packages/core/src/consts/Transmission.test.ts rename to packages/core/src/definitions/Transmission.test.ts index 82a4f51e1ade..c5cb0ea4805a 100644 --- a/packages/core/src/consts/Transmission.test.ts +++ b/packages/core/src/definitions/Transmission.test.ts @@ -1,5 +1,5 @@ import { test } from "vitest"; -import { MessagePriority, isMessagePriority } from "./Transmission.js"; +import { MessagePriority, isMessagePriority } from "./MessagePriority.js"; test("isMessagePriority() should detect numbers in the enum range as a message priority", (t) => { const numericKeys = Object.keys(MessagePriority) diff --git a/packages/core/src/definitions/Transmission.ts b/packages/core/src/definitions/Transmission.ts new file mode 100644 index 000000000000..3b95d406cd03 --- /dev/null +++ b/packages/core/src/definitions/Transmission.ts @@ -0,0 +1,119 @@ +import type { CCId } from "../traits/CommandClasses.js"; +import { type EncapsulationFlags } from "./EncapsulationFlags.js"; +import { type MessagePriority } from "./MessagePriority.js"; +import { type SecurityClass } from "./SecurityClass.js"; +import { + type SupervisionResult, + type SupervisionUpdateHandler, +} from "./Supervision.js"; +import { type TXReport } from "./TXReport.js"; +import { type TransactionProgressListener } from "./Transactions.js"; + +export enum TransmitOptions { + NotSet = 0, + + ACK = 1 << 0, + LowPower = 1 << 1, + AutoRoute = 1 << 2, + + NoRoute = 1 << 4, + Explore = 1 << 5, + + DEFAULT = ACK | AutoRoute | Explore, + DEFAULT_NOACK = DEFAULT & ~ACK, +} + +export enum TransmitStatus { + OK = 0x00, + NoAck = 0x01, + Fail = 0x02, + NotIdle = 0x03, + NoRoute = 0x04, +} + +export interface SendMessageOptions { + /** The priority of the message to send. If none is given, the defined default priority of the message class will be used. */ + priority?: MessagePriority; + /** If an exception should be thrown when the message to send is not supported. Setting this to false is is useful if the capabilities haven't been determined yet. Default: true */ + supportCheck?: boolean; + /** + * Whether the driver should update the node status to asleep or dead when a transaction is not acknowledged (repeatedly). + * Setting this to false will cause the simply transaction to be rejected on failure. + * Default: true + */ + changeNodeStatusOnMissingACK?: boolean; + /** Sets the number of milliseconds after which a queued message expires. When the expiration timer elapses, the promise is rejected with the error code `Controller_MessageExpired`. */ + expire?: number; + /** + * @internal + * Information used to identify or mark this transaction + */ + tag?: any; + /** + * @internal + * Whether the send thread MUST be paused after this message was handled + */ + pauseSendThread?: boolean; + /** If a Wake Up On Demand should be requested for the target node. */ + requestWakeUpOnDemand?: boolean; + /** + * When a message sent to a node results in a TX report to be received, this callback will be called. + * For multi-stage messages, the callback may be called multiple times. + */ + onTXReport?: (report: TXReport) => void; + + /** Will be called when the transaction for this message progresses. */ + onProgress?: TransactionProgressListener; +} + +export type SupervisionOptions = + | ( + & { + /** Whether supervision may be used. `false` disables supervision. Default: `"auto"`. */ + useSupervision?: "auto"; + } + & ( + | { + requestStatusUpdates?: false; + } + | { + requestStatusUpdates: true; + onUpdate: SupervisionUpdateHandler; + } + ) + ) + | { + useSupervision: false; + }; + +export type SendCommandSecurityS2Options = { + /** Send the command using a different (lower) security class */ + s2OverrideSecurityClass?: SecurityClass; + /** Whether delivery of non-supervised SET-type commands is verified by waiting for potential Nonce Reports. Default: true */ + s2VerifyDelivery?: boolean; + /** Whether the MOS extension should be included in S2 message encapsulation. */ + s2MulticastOutOfSync?: boolean; + /** The optional multicast group ID to use for S2 message encapsulation. */ + s2MulticastGroupId?: number; +}; + +export type SendCommandOptions = + & SendMessageOptions + & SupervisionOptions + & SendCommandSecurityS2Options + & { + /** How many times the driver should try to send the message. Defaults to the configured Driver option */ + maxSendAttempts?: number; + /** Whether the driver should automatically handle the encapsulation. Default: true */ + autoEncapsulate?: boolean; + /** Used to send a response with the same encapsulation flags as the corresponding request. */ + encapsulationFlags?: EncapsulationFlags; + /** Overwrite the default transmit options */ + transmitOptions?: TransmitOptions; + /** Overwrite the default report timeout */ + reportTimeoutMs?: number; + }; + +export type SendCommandReturnType = + undefined extends TResponse ? SupervisionResult | undefined + : TResponse | undefined; diff --git a/packages/core/src/consts/index.ts b/packages/core/src/definitions/consts.ts similarity index 52% rename from packages/core/src/consts/index.ts rename to packages/core/src/definitions/consts.ts index 0465e2fe27ab..6ce9cba18db0 100644 --- a/packages/core/src/consts/index.ts +++ b/packages/core/src/definitions/consts.ts @@ -4,15 +4,6 @@ export const MAX_NODES = 232; /** Max number of nodes in a Z-Wave LR network */ export const MAX_NODES_LR = 4000; // FIXME: This seems too even, figure out the exact number -/** The broadcast target node id */ -export const NODE_ID_BROADCAST = 0xff; - -/** The broadcast target node id for Z-Wave LR */ -export const NODE_ID_BROADCAST_LR = 0xfff; - -/** The highest allowed node id */ -export const NODE_ID_MAX = MAX_NODES; - /** The number of bytes in a node bit mask */ export const NUM_NODEMASK_BYTES = MAX_NODES / 8; @@ -22,22 +13,7 @@ export const NUM_LR_NODES_PER_SEGMENT = 128; /** The number of bytes in a long range node bit mask segment */ export const NUM_LR_NODEMASK_SEGMENT_BYTES = NUM_LR_NODES_PER_SEGMENT / 8; -export enum NodeIDType { - Short = 0x01, - Long = 0x02, -} - /** The size of a Home ID */ export const HOMEID_BYTES = 4; -/** How many repeaters can appear in a route */ -export const MAX_REPEATERS = 4; - -export { ControllerStatus } from "./ControllerStatus.js"; -export { InterviewStage } from "./InterviewStage.js"; -export { NodeStatus } from "./NodeStatus.js"; -export * from "./Transmission.js"; - -export const MAX_SUPERVISION_SESSION_ID = 0b111111; - export const MAX_TRANSPORT_SERVICE_SESSION_ID = 0b1111; diff --git a/packages/core/src/definitions/index.ts b/packages/core/src/definitions/index.ts index 247534b7a9f1..0e7a9db0b53a 100644 --- a/packages/core/src/definitions/index.ts +++ b/packages/core/src/definitions/index.ts @@ -1,11 +1,24 @@ export * from "./CommandClasses.js"; export * from "./ControllerCapabilities.js"; +export * from "./ControllerStatus.js"; +export * from "./EncapsulationFlags.js"; export * from "./Frame.js"; +export * from "./InterviewStage.js"; export * from "./LibraryTypes.js"; +export * from "./MessagePriority.js"; +export * from "./NodeID.js"; export * from "./NodeInfo.js"; +export * from "./NodeStatus.js"; export * from "./Protocol.js"; export * from "./RFRegion.js"; +export * from "./RSSI.js"; export * from "./Route.js"; +export * from "./RoutingScheme.js"; export * from "./SecurityClass.js"; +export * from "./Supervision.js"; +export type * from "./TXReport.js"; +export * from "./Transactions.js"; +export * from "./Transmission.js"; export type * from "./ZWaveApiVersion.js"; export * from "./ZWaveChipTypes.js"; +export * from "./consts.js"; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 44163bf64678..86f38ed7f5a5 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/consistent-type-exports */ -export * from "./consts/index.js"; export * from "./definitions/index.js"; export * from "./dsk/index.js"; export * from "./error/ZWaveError.js"; @@ -15,6 +14,7 @@ export * from "./security/ctr_drbg.js"; export * from "./test/assertZWaveError.js"; export * from "./traits/CommandClasses.js"; export * from "./traits/Endpoints.js"; +export type * from "./traits/FileSystem.js"; export * from "./traits/Nodes.js"; export * from "./traits/SecurityClasses.js"; export * from "./traits/SecurityManagers.js"; diff --git a/packages/core/src/index_browser.ts b/packages/core/src/index_browser.ts index 8131aab703e8..3976051f2c17 100644 --- a/packages/core/src/index_browser.ts +++ b/packages/core/src/index_browser.ts @@ -1,7 +1,6 @@ /* @forbiddenImports external */ // FIXME: Find a way to make sure that the forbiddenImports lint uses the "browser" condition -export * from "./consts/index.js"; export * from "./definitions/index.js"; export * from "./dsk/index.js"; export * from "./error/ZWaveError.js"; @@ -15,6 +14,7 @@ export * from "./registries/Scales.js"; export * from "./registries/Sensors.js"; export type * from "./traits/CommandClasses.js"; export type * from "./traits/Endpoints.js"; +export type * from "./traits/FileSystem.js"; export type * from "./traits/Nodes.js"; export type * from "./traits/SecurityClasses.js"; export type * from "./traits/SecurityManagers.js"; diff --git a/packages/core/src/index_safe.ts b/packages/core/src/index_safe.ts index 705a24c6acb7..fca7019c4927 100644 --- a/packages/core/src/index_safe.ts +++ b/packages/core/src/index_safe.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/consistent-type-exports */ /* @forbiddenImports external */ -export * from "./consts/index.js"; export * from "./definitions/index.js"; export * from "./dsk/index.js"; export * from "./error/ZWaveError.js"; @@ -15,9 +14,10 @@ export * from "./registries/Scales.js"; export * from "./registries/Sensors.js"; export * from "./traits/CommandClasses.js"; export * from "./traits/Endpoints.js"; -export * from "./traits/Nodes.js"; -export * from "./traits/SecurityClasses.js"; -export * from "./traits/SecurityManagers.js"; +export type * from "./traits/FileSystem.js"; +export type * from "./traits/Nodes.js"; +export type * from "./traits/SecurityClasses.js"; +export type * from "./traits/SecurityManagers.js"; export * from "./util/_Types.js"; export * from "./util/config.js"; export * from "./util/crc.js"; diff --git a/packages/core/src/log/Controller.test.ts b/packages/core/src/log/Controller.test.ts index 438ade627596..538b2e863939 100644 --- a/packages/core/src/log/Controller.test.ts +++ b/packages/core/src/log/Controller.test.ts @@ -1,6 +1,6 @@ import { beforeEach, test as baseTest } from "vitest"; -import { InterviewStage } from "../consts/InterviewStage.js"; import { CommandClasses } from "../definitions/CommandClasses.js"; +import { InterviewStage } from "../definitions/InterviewStage.js"; import { SpyTransport, assertLogInfo, diff --git a/packages/core/src/log/Controller.ts b/packages/core/src/log/Controller.ts index 7329bc11c172..cf26ac5311a3 100644 --- a/packages/core/src/log/Controller.ts +++ b/packages/core/src/log/Controller.ts @@ -1,6 +1,6 @@ import { isObject } from "alcalzone-shared/typeguards"; -import { InterviewStage } from "../consts/InterviewStage.js"; import { CommandClasses } from "../definitions/CommandClasses.js"; +import { InterviewStage } from "../definitions/InterviewStage.js"; import type { ValueAddedArgs, ValueID, diff --git a/packages/core/src/security/Manager2.ts b/packages/core/src/security/Manager2.ts index ccf954bbc44c..5225bcaa631b 100644 --- a/packages/core/src/security/Manager2.ts +++ b/packages/core/src/security/Manager2.ts @@ -3,11 +3,11 @@ import { createWrappingCounter, getEnumMemberName } from "@zwave-js/shared"; import * as crypto from "node:crypto"; import { deflateSync } from "node:zlib"; -import { MAX_NODES_LR } from "../consts/index.js"; import { type S2SecurityClass, SecurityClass, } from "../definitions/SecurityClass.js"; +import { MAX_NODES_LR } from "../definitions/consts.js"; import { ZWaveError, ZWaveErrorCodes } from "../error/ZWaveError.js"; import { highResTimestamp } from "../util/date.js"; import { encodeBitMask } from "../values/Primitive.js"; diff --git a/packages/core/src/traits/CommandClasses.ts b/packages/core/src/traits/CommandClasses.ts index 9e8d9593900c..06a81c0436de 100644 --- a/packages/core/src/traits/CommandClasses.ts +++ b/packages/core/src/traits/CommandClasses.ts @@ -1,12 +1,12 @@ -import type { - MulticastDestination, - NODE_ID_BROADCAST, - NODE_ID_BROADCAST_LR, -} from "../consts/index.js"; import type { CommandClassInfo, CommandClasses, } from "../definitions/CommandClasses.js"; +import type { + MulticastDestination, + NODE_ID_BROADCAST, + NODE_ID_BROADCAST_LR, +} from "../definitions/NodeID.js"; /** Identifies which node and/or endpoint a CC is addressed to */ export interface CCAddress { diff --git a/packages/core/src/traits/Endpoints.ts b/packages/core/src/traits/Endpoints.ts index 99ec0840f444..36e22a8adc26 100644 --- a/packages/core/src/traits/Endpoints.ts +++ b/packages/core/src/traits/Endpoints.ts @@ -1,4 +1,4 @@ -import { type MulticastDestination } from "../consts/Transmission.js"; +import { type MulticastDestination } from "../definitions/NodeID.js"; /** Identifies an endpoint */ export interface EndpointId { diff --git a/packages/host/src/FileSystem.ts b/packages/core/src/traits/FileSystem.ts similarity index 100% rename from packages/host/src/FileSystem.ts rename to packages/core/src/traits/FileSystem.ts diff --git a/packages/core/src/traits/Nodes.ts b/packages/core/src/traits/Nodes.ts index 9b3cf0bc009c..0bc42429bbeb 100644 --- a/packages/core/src/traits/Nodes.ts +++ b/packages/core/src/traits/Nodes.ts @@ -1,5 +1,5 @@ -import { type NodeStatus } from "../consts/NodeStatus.js"; import { type FLiRS } from "../definitions/NodeInfo.js"; +import { type NodeStatus } from "../definitions/NodeStatus.js"; import { type MaybeNotKnown } from "../values/Primitive.js"; import { type EndpointId, type VirtualEndpointId } from "./Endpoints.js"; diff --git a/packages/core/src/values/Primitive.ts b/packages/core/src/values/Primitive.ts index 398d349f870c..97150ee526c8 100644 --- a/packages/core/src/values/Primitive.ts +++ b/packages/core/src/values/Primitive.ts @@ -3,7 +3,7 @@ import { MAX_NODES_LR, NUM_LR_NODES_PER_SEGMENT, NUM_NODEMASK_BYTES, -} from "../consts/index.js"; +} from "../definitions/consts.js"; import { ZWaveError, ZWaveErrorCodes } from "../error/ZWaveError.js"; import { getBitMaskWidth, diff --git a/packages/host/src/index.ts b/packages/host/src/index.ts index 4fed45e686d2..5acc76f975b7 100644 --- a/packages/host/src/index.ts +++ b/packages/host/src/index.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/consistent-type-exports */ -export * from "./FileSystem.js"; 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 index ec8ea79bf3fa..8fbb0d65dad6 100644 --- a/packages/host/src/index_safe.ts +++ b/packages/host/src/index_safe.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/consistent-type-exports */ /* @forbiddenImports external */ -export * from "./FileSystem.js"; export * from "./ZWaveHost.js"; export * from "./ZWaveHostOptions.js"; diff --git a/packages/host/tsconfig.json b/packages/host/tsconfig.json index 3d1e2856a002..4d6b6de9e79f 100644 --- a/packages/host/tsconfig.json +++ b/packages/host/tsconfig.json @@ -2,6 +2,6 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": {}, - "include": ["src/**/*.ts"], + "include": ["src/**/*.ts", "../core/src/traits/FileSystem.ts"], "exclude": ["build/**", "node_modules/**"] } diff --git a/packages/zwave-js/src/Driver.ts b/packages/zwave-js/src/Driver.ts index f09234bbd226..f3e784e1d636 100644 --- a/packages/zwave-js/src/Driver.ts +++ b/packages/zwave-js/src/Driver.ts @@ -1,6 +1,6 @@ export { MessagePriority } from "@zwave-js/core"; export type { SendMessageOptions } from "@zwave-js/core"; -export type { FileSystem } from "@zwave-js/host/safe"; +export type { FileSystem } from "@zwave-js/core"; export { FunctionType, Message, MessageType } from "@zwave-js/serial"; export type { MessageOptions, diff --git a/packages/zwave-js/src/lib/driver/NetworkCache.ts b/packages/zwave-js/src/lib/driver/NetworkCache.ts index 0a6914c16519..e358070de94a 100644 --- a/packages/zwave-js/src/lib/driver/NetworkCache.ts +++ b/packages/zwave-js/src/lib/driver/NetworkCache.ts @@ -2,6 +2,7 @@ import type { JsonlDB } from "@alcalzone/jsonl-db"; import { type AssociationAddress } from "@zwave-js/cc"; import { type CommandClasses, + type FileSystem, NodeType, Protocols, SecurityClass, @@ -11,7 +12,6 @@ import { dskToString, securityClassOrder, } from "@zwave-js/core"; -import type { FileSystem } from "@zwave-js/host"; import { Bytes, getEnumMemberName, num2hex, pickDeep } from "@zwave-js/shared"; import { isArray, isObject } from "alcalzone-shared/typeguards"; import path from "node:path"; diff --git a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts index e52f2473a264..39c6646c37e9 100644 --- a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts +++ b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts @@ -1,5 +1,10 @@ -import type { LogConfig, LongRangeChannel, RFRegion } from "@zwave-js/core"; -import type { FileSystem, ZWaveHostOptions } from "@zwave-js/host"; +import type { + FileSystem, + LogConfig, + LongRangeChannel, + RFRegion, +} from "@zwave-js/core"; +import type { ZWaveHostOptions } from "@zwave-js/host"; import type { ZWaveSerialPortBase } from "@zwave-js/serial"; import { type DeepPartial, type Expand } from "@zwave-js/shared"; import type { SerialPort } from "serialport"; From 2a8caf27da819fcf7543bb9cb9835640c33b40ac Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Wed, 6 Nov 2024 21:23:58 +0100 Subject: [PATCH 2/3] fix: flaky tests --- packages/zwave-js/src/lib/driver/Task.test.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/zwave-js/src/lib/driver/Task.test.ts b/packages/zwave-js/src/lib/driver/Task.test.ts index ba7b65e6b953..5d1a38031c07 100644 --- a/packages/zwave-js/src/lib/driver/Task.test.ts +++ b/packages/zwave-js/src/lib/driver/Task.test.ts @@ -1034,9 +1034,12 @@ test("Tasks can be removed while paused", async (t) => { const order: string[] = []; scheduler.start(); + const t1WasStarted = createDeferredPromise(); + const task1 = scheduler.queueTask({ priority: TaskPriority.Normal, task: async function*() { + t1WasStarted.resolve(); order.push("1a"); yield () => wait(10); order.push("1b"); @@ -1048,7 +1051,7 @@ test("Tasks can be removed while paused", async (t) => { }, }); - await wait(1); + await t1WasStarted; // The task should have run to the first yield t.expect(order).toStrictEqual(["1a"]); @@ -1066,9 +1069,13 @@ test("Tasks can be removed while paused, part 2", async (t) => { const order: string[] = []; scheduler.start(); + const t1WasStarted = createDeferredPromise(); + const t2WasStarted = createDeferredPromise(); + const task1 = scheduler.queueTask({ priority: TaskPriority.Normal, task: async function*() { + t1WasStarted.resolve(); order.push("1a"); yield () => wait(10); order.push("1b"); @@ -1084,6 +1091,7 @@ test("Tasks can be removed while paused, part 2", async (t) => { name: "task2", priority: TaskPriority.Normal, task: async function*() { + t2WasStarted.resolve(); order.push("2a"); yield () => wait(10); order.push("2b"); @@ -1095,8 +1103,8 @@ test("Tasks can be removed while paused, part 2", async (t) => { }, }); - await wait(1); - // The tasks should have run to the first yield + await Promise.all([t1WasStarted, t2WasStarted]); + // The tasks have run to the first yield t.expect(order).toStrictEqual(["1a", "2a"]); await scheduler.removeTasks((t) => true); From 9eaef5dfdf457fff0f530a9e71abd4aabf43e3a2 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Wed, 6 Nov 2024 21:32:56 +0100 Subject: [PATCH 3/3] fix: flaky tests --- packages/zwave-js/src/lib/driver/Task.test.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/zwave-js/src/lib/driver/Task.test.ts b/packages/zwave-js/src/lib/driver/Task.test.ts index 5d1a38031c07..41a58922cc70 100644 --- a/packages/zwave-js/src/lib/driver/Task.test.ts +++ b/packages/zwave-js/src/lib/driver/Task.test.ts @@ -1069,13 +1069,9 @@ test("Tasks can be removed while paused, part 2", async (t) => { const order: string[] = []; scheduler.start(); - const t1WasStarted = createDeferredPromise(); - const t2WasStarted = createDeferredPromise(); - const task1 = scheduler.queueTask({ priority: TaskPriority.Normal, task: async function*() { - t1WasStarted.resolve(); order.push("1a"); yield () => wait(10); order.push("1b"); @@ -1091,7 +1087,6 @@ test("Tasks can be removed while paused, part 2", async (t) => { name: "task2", priority: TaskPriority.Normal, task: async function*() { - t2WasStarted.resolve(); order.push("2a"); yield () => wait(10); order.push("2b"); @@ -1103,7 +1098,7 @@ test("Tasks can be removed while paused, part 2", async (t) => { }, }); - await Promise.all([t1WasStarted, t2WasStarted]); + await wait(5); // The tasks have run to the first yield t.expect(order).toStrictEqual(["1a", "2a"]);