From a0a7584257257089742aef387386b7f30abfb429 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Wed, 9 Oct 2024 21:22:41 +0200 Subject: [PATCH] refactor: further reduce dependency on IZWaveNode and IZWaveEndpoint --- packages/cc/src/cc/AssociationGroupInfoCC.ts | 8 ++- packages/cc/src/cc/ConfigurationCC.ts | 12 ++--- .../cc/src/cc/ManufacturerProprietaryCC.ts | 6 +-- packages/cc/src/cc/TimeParametersCC.ts | 11 ++-- packages/cc/src/lib/API.ts | 49 ++++++++++++++---- packages/cc/src/lib/CommandClass.ts | 1 + packages/cc/src/lib/utils.ts | 14 +++-- .../core/src/abstractions/IZWaveEndpoint.ts | 51 ++++++++++++++----- packages/core/src/abstractions/IZWaveNode.ts | 51 ++++++++++++++----- .../zwave-js/src/lib/node/VirtualEndpoint.ts | 3 +- 10 files changed, 143 insertions(+), 63 deletions(-) diff --git a/packages/cc/src/cc/AssociationGroupInfoCC.ts b/packages/cc/src/cc/AssociationGroupInfoCC.ts index 0550413ab64b..2c68764e7583 100644 --- a/packages/cc/src/cc/AssociationGroupInfoCC.ts +++ b/packages/cc/src/cc/AssociationGroupInfoCC.ts @@ -1,11 +1,11 @@ import { CommandClasses, type EndpointId, - type IZWaveEndpoint, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, + type SupportsCC, encodeCCId, getCCName, parseCCId, @@ -302,8 +302,7 @@ export class AssociationGroupInfoCC extends CommandClass { public static findGroupsForIssuedCommand( applHost: ZWaveApplicationHost, - // FIXME: GH#7261 ID and endpoint capabilities would be enough - endpoint: IZWaveEndpoint, + endpoint: EndpointId & SupportsCC, ccId: CommandClasses, command: number, ): number[] { @@ -333,8 +332,7 @@ export class AssociationGroupInfoCC extends CommandClass { private static getAssociationGroupCountCached( applHost: ZWaveApplicationHost, - // FIXME: GH#7261 ID and endpoint capabilities would be enough - endpoint: IZWaveEndpoint, + endpoint: EndpointId & SupportsCC, ): number { // The association group count is either determined by the // Association CC or the Multi Channel Association CC diff --git a/packages/cc/src/cc/ConfigurationCC.ts b/packages/cc/src/cc/ConfigurationCC.ts index c6e74742edb1..6cd1c063ecc1 100644 --- a/packages/cc/src/cc/ConfigurationCC.ts +++ b/packages/cc/src/cc/ConfigurationCC.ts @@ -3,8 +3,6 @@ import { CommandClasses, ConfigValueFormat, type ConfigurationMetadata, - type IVirtualEndpoint, - type IZWaveEndpoint, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -38,6 +36,7 @@ import { composeObject } from "alcalzone-shared/objects"; import { padStart } from "alcalzone-shared/strings"; import { CCAPI, + type CCAPIEndpoint, POLL_VALUE, type PollValueImplementation, SET_VALUE, @@ -145,8 +144,7 @@ type NormalizedConfigurationCCAPISetOptions = function createConfigurationCCInstance( applHost: ZWaveApplicationHost, - // FIXME: GH#7261 ID and index should be enough - endpoint: IZWaveEndpoint | IVirtualEndpoint, + endpoint: CCAPIEndpoint, ): ConfigurationCC { return CommandClass.createInstanceUnchecked( applHost, @@ -157,8 +155,7 @@ function createConfigurationCCInstance( function normalizeConfigurationCCAPISetOptions( applHost: ZWaveApplicationHost, - // FIXME: GH#7261 ID and index should be enough - endpoint: IZWaveEndpoint | IVirtualEndpoint, + endpoint: CCAPIEndpoint, options: ConfigurationCCAPISetOptions, ): NormalizedConfigurationCCAPISetOptions { if ("bitMask" in options && options.bitMask) { @@ -215,8 +212,7 @@ function normalizeConfigurationCCAPISetOptions( function bulkMergePartialParamValues( applHost: ZWaveApplicationHost, - // FIXME: GH#7261 ID and index should be enough - endpoint: IZWaveEndpoint | IVirtualEndpoint, + endpoint: CCAPIEndpoint, options: NormalizedConfigurationCCAPISetOptions[], ): (NormalizedConfigurationCCAPISetOptions & { bitMask?: undefined })[] { // Merge partial parameters before doing anything else. Therefore, take the non-partials, ... diff --git a/packages/cc/src/cc/ManufacturerProprietaryCC.ts b/packages/cc/src/cc/ManufacturerProprietaryCC.ts index 8b71365c3f11..c53b6372c7dc 100644 --- a/packages/cc/src/cc/ManufacturerProprietaryCC.ts +++ b/packages/cc/src/cc/ManufacturerProprietaryCC.ts @@ -1,7 +1,5 @@ import { CommandClasses, - type IVirtualEndpoint, - type IZWaveEndpoint, ZWaveError, ZWaveErrorCodes, validatePayload, @@ -9,7 +7,7 @@ import { import type { ZWaveApplicationHost, ZWaveHost } from "@zwave-js/host/safe"; import { staticExtends } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; -import { CCAPI } from "../lib/API"; +import { CCAPI, type CCAPIEndpoint } from "../lib/API"; import { type CCCommandOptions, CommandClass, @@ -41,7 +39,7 @@ export type ManufacturerProprietaryCCConstructor< export class ManufacturerProprietaryCCAPI extends CCAPI { public constructor( applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint | IVirtualEndpoint, + endpoint: CCAPIEndpoint, ) { super(applHost, endpoint); diff --git a/packages/cc/src/cc/TimeParametersCC.ts b/packages/cc/src/cc/TimeParametersCC.ts index 07a4189d86d2..53f949260652 100644 --- a/packages/cc/src/cc/TimeParametersCC.ts +++ b/packages/cc/src/cc/TimeParametersCC.ts @@ -1,6 +1,5 @@ import { CommandClasses, - type IZWaveEndpoint, type MessageOrCCLogEntry, MessagePriority, type SupervisionResult, @@ -8,7 +7,12 @@ import { formatDate, validatePayload, } from "@zwave-js/core"; -import { type MaybeNotKnown } from "@zwave-js/core/safe"; +import { + type ControlsCC, + type EndpointId, + type MaybeNotKnown, + type SupportsCC, +} from "@zwave-js/core/safe"; import type { ZWaveApplicationHost, ZWaveHost, @@ -60,8 +64,7 @@ export const TimeParametersCCValues = Object.freeze({ */ function shouldUseLocalTime( applHost: ZWaveApplicationHost, - // FIXME: GH#7261 ID and endpoint capabilities would be enough - endpoint: IZWaveEndpoint, + endpoint: EndpointId & SupportsCC & ControlsCC, ): boolean { // GH#311 Some nodes have no way to determine the time zone offset, // so they need to interpret the set time as local time instead of UTC. diff --git a/packages/cc/src/lib/API.ts b/packages/cc/src/lib/API.ts index 9c9d7aad6e54..4ad5ce219b30 100644 --- a/packages/cc/src/lib/API.ts +++ b/packages/cc/src/lib/API.ts @@ -1,7 +1,10 @@ import { type CompatOverrideQueries } from "@zwave-js/config"; import { CommandClasses, + type ControlsCC, type Duration, + type EndpointId, + type GetEndpoint, type IVirtualEndpoint, type IZWaveEndpoint, type IZWaveNode, @@ -9,12 +12,15 @@ import { NODE_ID_BROADCAST, NODE_ID_BROADCAST_LR, NOT_KNOWN, + type PhysicalNodes, type SendCommandOptions, type SupervisionResult, + type SupportsCC, type TXReport, type ValueChangeOptions, type ValueDB, type ValueID, + type VirtualEndpointId, ZWaveError, ZWaveErrorCodes, getCCName, @@ -148,6 +154,31 @@ export interface SchedulePollOptions { transition?: "fast" | "slow"; } +// Defines the necessary traits an endpoint passed to a CC API must have +export type CCAPIEndpoint = + & ( + | ( + // Physical endpoints must let us query their controlled CCs + EndpointId & ControlsCC + ) + | ( + // Virtual endpoints must let us query their physical nodes, + // the CCs those nodes implement, and access the endpoints of those + // physical nodes + VirtualEndpointId & { + node: PhysicalNodes< + & SupportsCC + & ControlsCC + & GetEndpoint + >; + } + ) + ) + & SupportsCC; + +export type PhysicalCCAPIEndpoint = CCAPIEndpoint & EndpointId; +export type VirtualCCAPIEndpoint = CCAPIEndpoint & VirtualEndpointId; + /** * The base class for all CC APIs exposed via `Node.commandClasses.` * @publicAPI @@ -155,7 +186,7 @@ export interface SchedulePollOptions { export class CCAPI { public constructor( protected readonly applHost: ZWaveApplicationHost, - protected readonly endpoint: IZWaveEndpoint | IVirtualEndpoint, + protected readonly endpoint: CCAPIEndpoint, ) { this.ccId = getCommandClass(this); } @@ -163,7 +194,7 @@ export class CCAPI { public static create( ccId: T, applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint | IVirtualEndpoint, + endpoint: CCAPIEndpoint, requireSupport?: boolean, ): CommandClasses extends T ? CCAPI : CCToAPI { const APIConstructor = getAPI(ccId); @@ -312,7 +343,7 @@ export class CCAPI { const timeoutMs = durationMs + additionalDelay; if (this.isSinglecast()) { - const node = this.endpoint.getNodeUnsafe(); + const node = this.getNodeUnsafe(); if (!node) return false; return this.applHost.schedulePoll( @@ -416,8 +447,8 @@ export class CCAPI { } protected assertPhysicalEndpoint( - endpoint: IZWaveEndpoint | IVirtualEndpoint, - ): asserts endpoint is IZWaveEndpoint { + endpoint: EndpointId | VirtualEndpointId, + ): asserts endpoint is EndpointId { if (endpoint.virtual) { throw new ZWaveError( `This method is not supported for virtual nodes!`, @@ -602,7 +633,7 @@ export class CCAPI { function overrideQueriesWrapper( applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + endpoint: PhysicalCCAPIEndpoint, ccId: CommandClasses, method: string, overrides: CompatOverrideQueries, @@ -742,18 +773,18 @@ function overrideQueriesWrapper( export class PhysicalCCAPI extends CCAPI { public constructor( applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint | IVirtualEndpoint, + endpoint: CCAPIEndpoint, ) { super(applHost, endpoint); this.assertPhysicalEndpoint(endpoint); } - declare protected readonly endpoint: IZWaveEndpoint; + declare protected readonly endpoint: PhysicalCCAPIEndpoint; } export type APIConstructor = new ( applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint | IVirtualEndpoint, + endpoint: CCAPIEndpoint, ) => T; // This type is auto-generated by maintenance/generateCCAPIInterface.ts diff --git a/packages/cc/src/lib/CommandClass.ts b/packages/cc/src/lib/CommandClass.ts index 2d14d935cc9e..2b68a2bad357 100644 --- a/packages/cc/src/lib/CommandClass.ts +++ b/packages/cc/src/lib/CommandClass.ts @@ -808,6 +808,7 @@ export class CommandClass implements ICommandClass { && value.options.autoCreate( applHost, { + virtual: false, nodeId: this.nodeId as number, index: this.endpointIndex, }, diff --git a/packages/cc/src/lib/utils.ts b/packages/cc/src/lib/utils.ts index d2f704624dc2..20b19dad0344 100644 --- a/packages/cc/src/lib/utils.ts +++ b/packages/cc/src/lib/utils.ts @@ -6,7 +6,9 @@ import { type IZWaveNode, type MaybeNotKnown, NOT_KNOWN, + type NodeId, SecurityClass, + type SupportsCC, ZWaveError, ZWaveErrorCodes, actuatorCCs, @@ -36,8 +38,7 @@ import { export function getAssociations( applHost: ZWaveApplicationHost, - // FIXME: GH#7261 ID and endpoint capabilities would be enough - endpoint: IZWaveEndpoint, + endpoint: EndpointId & SupportsCC, ): ReadonlyMap { const ret = new Map(); @@ -242,8 +243,7 @@ export function checkAssociation( export function getAssociationGroups( applHost: ZWaveApplicationHost, - // FIXME: GH#7261 ID and endpoint capabilities would be enough - endpoint: IZWaveEndpoint, + endpoint: EndpointId & SupportsCC, ): ReadonlyMap { // Check whether we have multi channel support or not let assocInstance: typeof AssociationCC; @@ -640,8 +640,7 @@ export async function removeAssociations( export function getLifelineGroupIds( applHost: ZWaveApplicationHost, - // FIXME: GH#7261 ID and endpoint capabilities would be enough - endpoint: IZWaveEndpoint, + endpoint: EndpointId & SupportsCC, ): number[] { // For now only support this for the root endpoint - i.e. node if (endpoint.index > 0) return []; @@ -1243,8 +1242,7 @@ export async function assignLifelineIssueingCommand( export function doesAnyLifelineSendActuatorOrSensorReports( applHost: ZWaveApplicationHost, - // FIXME: GH#7261 ID and endpoint capabilities would be enough - node: IZWaveNode, + node: NodeId & SupportsCC, ): MaybeNotKnown { // No association support means no unsolicited reports if ( diff --git a/packages/core/src/abstractions/IZWaveEndpoint.ts b/packages/core/src/abstractions/IZWaveEndpoint.ts index 1cd832902b47..cb9ca6e3bf92 100644 --- a/packages/core/src/abstractions/IZWaveEndpoint.ts +++ b/packages/core/src/abstractions/IZWaveEndpoint.ts @@ -7,29 +7,56 @@ import type { IVirtualNode, IZWaveNode } from "./IZWaveNode"; /** Identifies an endpoint */ export interface EndpointId { + readonly virtual: false; readonly nodeId: number; readonly index: number; } -/** A basic abstraction of a Z-Wave endpoint providing access to the relevant functionality */ -export interface IZWaveEndpoint extends EndpointId { - readonly virtual: false; +/** Allows querying if a CC is supported and in which version */ +export interface SupportsCC { + supportsCC(cc: CommandClasses): boolean; getCCVersion(cc: CommandClasses): number; +} + +/** Allows querying if a CC is controlled */ +export interface ControlsCC { + controlsCC(cc: CommandClasses): boolean; +} + +/** Allows querying if a CC is supported or controlled only securely */ +export interface IsCCSecure { + isCCSecure(cc: CommandClasses): boolean; +} + +/** Allows modifying the list of supported/controlled CCs */ +export interface ModifyCCs { addCC(cc: CommandClasses, info: Partial): void; removeCC(cc: CommandClasses): void; +} + +/** A basic abstraction of a Z-Wave endpoint providing access to the relevant functionality */ +export interface IZWaveEndpoint + extends EndpointId, SupportsCC, ControlsCC, IsCCSecure, ModifyCCs +{ getCCs(): Iterable<[ccId: CommandClasses, info: CommandClassInfo]>; - supportsCC(cc: CommandClasses): boolean; - controlsCC(cc: CommandClasses): boolean; - isCCSecure(cc: CommandClasses): boolean; getNodeUnsafe(): IZWaveNode | undefined; } -/** A basic abstraction of an endpoint of a virtual node (multicast or broadcast) providing access to the relevant functionality */ -export interface IVirtualEndpoint { +/** Identifies a virtual endpoint */ +export interface VirtualEndpointId { + readonly virtual: true; readonly nodeId: number | MulticastDestination; - readonly node: IVirtualNode; readonly index: number; - readonly virtual: true; - getCCVersion(cc: CommandClasses): number; - supportsCC(cc: CommandClasses): boolean; } + +/** A basic abstraction of an endpoint of a virtual node (multicast or broadcast) providing access to the relevant functionality */ +export interface IVirtualEndpoint extends VirtualEndpointId, SupportsCC { + readonly node: IVirtualNode; +} + +// TODO: Use cases in CCAPI implementations: +// - EndpointId or VirtualEndpointId +// - SupportsCC +// - VirtualEndpoint: +// - physical nodes -> NodeId +// - physical nodes -> Endpoint -> SupportsCC,ControlsCC diff --git a/packages/core/src/abstractions/IZWaveNode.ts b/packages/core/src/abstractions/IZWaveNode.ts index b24420db58b5..1684a448936b 100644 --- a/packages/core/src/abstractions/IZWaveNode.ts +++ b/packages/core/src/abstractions/IZWaveNode.ts @@ -2,30 +2,57 @@ import type { FLiRS } from "../capabilities/NodeInfo"; import type { InterviewStage, NodeStatus } from "../consts"; import type { SecurityClassOwner } from "../security/SecurityClass"; import { type MaybeNotKnown } from "../values/Primitive"; -import type { IVirtualEndpoint, IZWaveEndpoint } from "./IZWaveEndpoint"; +import type { + EndpointId, + IVirtualEndpoint, + IZWaveEndpoint, + VirtualEndpointId, +} from "./IZWaveEndpoint"; -/** A basic abstraction of a Z-Wave node providing access to the relevant functionality */ -export interface IZWaveNode extends IZWaveEndpoint, SecurityClassOwner { +export interface NodeId extends EndpointId { readonly id: number; + // FIXME: GH#7261 this should have type 0 + readonly index: number; +} + +export interface GetEndpoint { + getEndpoint(index: 0): T; + getEndpoint(index: number): T | undefined; + getEndpointOrThrow(index: number): T; +} + +/** A basic abstraction of a Z-Wave node providing access to the relevant functionality */ +export interface IZWaveNode + extends + IZWaveEndpoint, + NodeId, + GetEndpoint, + SecurityClassOwner +{ isListening: MaybeNotKnown; isFrequentListening: MaybeNotKnown; readonly canSleep: MaybeNotKnown; readonly status: NodeStatus; interviewStage: InterviewStage; - getEndpoint(index: 0): IZWaveEndpoint; - getEndpoint(index: number): IZWaveEndpoint | undefined; - getEndpointOrThrow(index: number): IZWaveEndpoint; getAllEndpoints(): IZWaveEndpoint[]; readonly isSecure: MaybeNotKnown; } -/** A basic abstraction of a virtual node (multicast or broadcast) providing access to the relevant functionality */ -export interface IVirtualNode extends IVirtualEndpoint { +export interface VirtualNodeId extends VirtualEndpointId { readonly id: number | undefined; - readonly physicalNodes: readonly IZWaveNode[]; +} - getEndpoint(index: 0): IVirtualEndpoint; - getEndpoint(index: number): IVirtualEndpoint | undefined; - getEndpointOrThrow(index: number): IVirtualEndpoint; +export interface PhysicalNodes { + readonly physicalNodes: readonly (NodeId & T)[]; +} + +/** A basic abstraction of a virtual node (multicast or broadcast) providing access to the relevant functionality */ +export interface IVirtualNode + extends + IVirtualEndpoint, + VirtualNodeId, + GetEndpoint, + PhysicalNodes +{ } diff --git a/packages/zwave-js/src/lib/node/VirtualEndpoint.ts b/packages/zwave-js/src/lib/node/VirtualEndpoint.ts index 71223ea3d7b1..079544b76f75 100644 --- a/packages/zwave-js/src/lib/node/VirtualEndpoint.ts +++ b/packages/zwave-js/src/lib/node/VirtualEndpoint.ts @@ -4,6 +4,7 @@ import { type CCAPIs, type CCNameOrId, PhysicalCCAPI, + type VirtualCCAPIEndpoint, getAPI, normalizeCCNameOrId, } from "@zwave-js/cc"; @@ -91,7 +92,7 @@ export class VirtualEndpoint implements IVirtualEndpoint { */ public createAPI(ccId: CommandClasses): CCAPI { const createCCAPI = ( - endpoint: IVirtualEndpoint, + endpoint: VirtualCCAPIEndpoint, secClass: SecurityClass, ) => { if (