diff --git a/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts b/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts index b5dbdf38ead9..1286c83b33a8 100644 --- a/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts +++ b/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts @@ -43,7 +43,6 @@ import { V } from "../lib/Values"; import { FirmwareDownloadStatus, FirmwareUpdateActivationStatus, - type FirmwareUpdateInitResult, type FirmwareUpdateMetaData, FirmwareUpdateMetaDataCommand, FirmwareUpdateRequestStatus, @@ -161,12 +160,12 @@ export class FirmwareUpdateMetaDataCCAPI extends PhysicalCCAPI { /** * Requests the device to start the firmware update process. - * WARNING: This method may wait up to 60 seconds for a reply. + * This does not wait for the reply - that is up to the caller of this method. */ @validateArgs() public async requestUpdate( options: FirmwareUpdateMetaDataCCRequestGetOptions, - ): Promise { + ): Promise { this.assertSupportsCommand( FirmwareUpdateMetaDataCommand, FirmwareUpdateMetaDataCommand.RequestGet, @@ -177,29 +176,12 @@ export class FirmwareUpdateMetaDataCCAPI extends PhysicalCCAPI { endpoint: this.endpoint.index, ...options, }); - // Since the response may take longer than with other commands, - // we do not use the built-in waiting functionality, which would block - // all other communication. await this.applHost.sendCommand(cc, { ...this.commandOptions, // Do not wait for Nonce Reports s2VerifyDelivery: false, }); - const result = await this.applHost - .waitForCommand< - FirmwareUpdateMetaDataCCRequestReport - >( - (cc) => - cc instanceof FirmwareUpdateMetaDataCCRequestReport - && cc.nodeId === this.endpoint.nodeId, - 60000, - ); - return pick(result, [ - "status", - "resume", - "nonSecureTransfer", - ]); } /** diff --git a/packages/cc/src/cc/ManufacturerSpecificCC.ts b/packages/cc/src/cc/ManufacturerSpecificCC.ts index 5bf1db1f1c35..56bc0febeafd 100644 --- a/packages/cc/src/cc/ManufacturerSpecificCC.ts +++ b/packages/cc/src/cc/ManufacturerSpecificCC.ts @@ -190,7 +190,7 @@ export class ManufacturerSpecificCC extends CommandClass { endpoint, ).withOptions({ priority: MessagePriority.NodeQuery }); - if (!applHost.isControllerNode(node.id)) { + if (node.id !== applHost.ownNodeId) { applHost.controllerLog.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, @@ -207,7 +207,7 @@ export class ManufacturerSpecificCC extends CommandClass { const logMessage = `received response for manufacturer information: manufacturer: ${ - applHost.configManager.lookupManufacturer( + applHost.lookupManufacturer( mfResp.manufacturerId, ) || "unknown" diff --git a/packages/cc/src/cc/WakeUpCC.ts b/packages/cc/src/cc/WakeUpCC.ts index b17942fc252a..55c84591d7a4 100644 --- a/packages/cc/src/cc/WakeUpCC.ts +++ b/packages/cc/src/cc/WakeUpCC.ts @@ -242,7 +242,7 @@ export class WakeUpCC extends CommandClass { direction: "none", }); - if (applHost.isControllerNode(node.id)) { + if (node.id === applHost.ownNodeId) { applHost.controllerLog.logNode( node.id, `skipping wakeup configuration for the controller`, diff --git a/packages/cc/src/lib/CommandClass.ts b/packages/cc/src/lib/CommandClass.ts index b027147469f3..0050b16184b9 100644 --- a/packages/cc/src/lib/CommandClass.ts +++ b/packages/cc/src/lib/CommandClass.ts @@ -143,12 +143,16 @@ export type CCNode = export function getEffectiveCCVersion( ctx: GetSupportedCCVersion, - cc: CommandClass, + cc: CCId, defaultVersion?: number, ): number { // For multicast and broadcast CCs, just use the highest implemented version to serialize // Older nodes will ignore the additional fields - if (!cc.isSinglecast()) { + if ( + typeof cc.nodeId !== "number" + || cc.nodeId === NODE_ID_BROADCAST + || cc.nodeId === NODE_ID_BROADCAST_LR + ) { return getImplementedVersion(cc.ccId); } // For singlecast CCs, set the CC version as high as possible diff --git a/packages/core/src/abstractions/ICommandClass.ts b/packages/core/src/abstractions/ICommandClass.ts index e66bdce35195..98b7a08ce799 100644 --- a/packages/core/src/abstractions/ICommandClass.ts +++ b/packages/core/src/abstractions/ICommandClass.ts @@ -20,6 +20,7 @@ export interface SecurityManagers { /** A basic abstraction of a Z-Wave Command Class providing access to the relevant functionality */ export interface CCId { nodeId: number | MulticastDestination; + endpointIndex?: number; ccId: CommandClasses; ccCommand?: number; } diff --git a/packages/host/src/ZWaveHost.ts b/packages/host/src/ZWaveHost.ts index 17b4d78fc076..e0ca54461fa1 100644 --- a/packages/host/src/ZWaveHost.ts +++ b/packages/host/src/ZWaveHost.ts @@ -1,11 +1,10 @@ -import type { ConfigManager, DeviceConfig } from "@zwave-js/config"; +import type { DeviceConfig } from "@zwave-js/config"; import type { CCId, CommandClasses, ControllerLogger, FrameType, MaybeNotKnown, - NodeIDType, NodeId, SecurityClass, SecurityManagers, @@ -51,6 +50,19 @@ export interface GetSupportedCCVersion { ): 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 @@ -118,6 +130,20 @@ 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>; +} + export interface ZWaveApplicationHost extends GetValueDB, @@ -125,66 +151,26 @@ export interface ZWaveApplicationHost GetNode, GetAllNodes, SecurityManagers, - GetDeviceConfig + GetDeviceConfig, + LookupManufacturer, + SchedulePoll, + GetSupportedCCVersion, + GetSafeCCVersion, + SendCommand { - /** Gives access to the configuration files */ - configManager: ConfigManager; - options: ZWaveHostOptions; - readonly nodeIdType?: NodeIDType; - // TODO: There's probably a better fitting name for this now controllerLog: ControllerLogger; +} - /** Whether the node with the given ID is the controller */ - isControllerNode(nodeId: number): boolean; - - sendCommand( - command: CCId, - options?: SendCommandOptions, - ): Promise>; - - waitForCommand( - predicate: (cc: CCId) => boolean, - timeout: number, - ): Promise; - +/** Allows scheduling a value refresh (poll) for a later time */ +export interface SchedulePoll { schedulePoll( nodeId: number, valueId: ValueID, options: NodeSchedulePollOptions, ): boolean; - - /** - * 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; - - /** - * 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; - - /** - * Determines whether a CC must be secure for a given node and endpoint. - */ - isCCSecure( - cc: CommandClasses, - nodeId: number, - endpointIndex?: number, - ): boolean; } export interface NodeSchedulePollOptions { diff --git a/packages/host/src/mocks.ts b/packages/host/src/mocks.ts index babd0e065c2e..fc2ef8e157a6 100644 --- a/packages/host/src/mocks.ts +++ b/packages/host/src/mocks.ts @@ -1,10 +1,8 @@ /* eslint-disable @typescript-eslint/require-await */ -import { ConfigManager } from "@zwave-js/config"; import { type EndpointId, type GetEndpoint, type IsCCSecure, - NodeIDType, type NodeId, type QuerySecurityClasses, type SetSecurityClass, @@ -54,8 +52,6 @@ export function createTestingHost< const ret: TestingHost = { homeId: options.homeId ?? 0x7e570001, ownNodeId: options.ownNodeId ?? 1, - nodeIdType: NodeIDType.Short, - isControllerNode: (nodeId) => nodeId === ret.ownNodeId, securityManager: undefined, securityManager2: undefined, securityManagerLR: undefined, @@ -67,7 +63,7 @@ export function createTestingHost< }; }, }), - configManager: new ConfigManager(), + lookupManufacturer: () => undefined, options: { attempts: { nodeInterview: 1, @@ -114,15 +110,6 @@ export function createTestingHost< tryGetValueDB: (nodeId) => { return ret.getValueDB(nodeId); }, - isCCSecure: (ccId, nodeId, endpointIndex = 0) => { - const node = nodes.get(nodeId); - const endpoint = node?.getEndpoint(endpointIndex); - return ( - node?.isSecure !== false - && !!(endpoint ?? node)?.isCCSecure(ccId) - && !!(ret.securityManager || ret.securityManager2) - ); - }, // getHighestSecurityClass: (nodeId) => { // const node = nodes.getOrThrow(nodeId); // return node.getHighestSecurityClass(); @@ -138,9 +125,6 @@ export function createTestingHost< sendCommand: async (_command, _options) => { return undefined as any; }, - waitForCommand: async (_predicate, _timeout) => { - return undefined as any; - }, schedulePoll: (_nodeId, _valueId, _options) => { return false; }, diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index 1477004adc88..97f60ce53aea 100644 --- a/packages/zwave-js/src/lib/driver/Driver.ts +++ b/packages/zwave-js/src/lib/driver/Driver.ts @@ -726,7 +726,7 @@ export class Driver extends TypedEventEmitter ...this.messageEncodingContext, ownNodeId: this.controller.ownNodeId!, homeId: this.controller.homeId!, - nodeIdType: this.nodeIdType, + nodeIdType: this._controller?.nodeIdType ?? NodeIDType.Short, securityManager: this.securityManager, securityManager2: this.securityManager2, securityManagerLR: this.securityManagerLR, @@ -740,7 +740,7 @@ export class Driver extends TypedEventEmitter ...this.messageParsingContext, ownNodeId: this.controller.ownNodeId!, homeId: this.controller.homeId!, - nodeIdType: this.nodeIdType, + nodeIdType: this._controller?.nodeIdType ?? NodeIDType.Short, securityManager: this.securityManager, securityManager2: this.securityManager2, securityManagerLR: this.securityManagerLR, @@ -983,10 +983,6 @@ export class Driver extends TypedEventEmitter return this.controller.ownNodeId!; } - public get nodeIdType(): NodeIDType { - return this._controller?.nodeIdType ?? NodeIDType.Short; - } - /** @internal Used for compatibility with the ZWaveApplicationHost interface */ public getNode(nodeId: number): ZWaveNode | undefined { return this.controller.nodes.get(nodeId); @@ -1042,6 +1038,10 @@ export class Driver extends TypedEventEmitter return this.controller.nodes.get(nodeId)?.deviceConfig; } + public lookupManufacturer(manufacturerId: number): string | undefined { + return this.configManager.lookupManufacturer(manufacturerId); + } + public getHighestSecurityClass( nodeId: number, ): MaybeNotKnown { @@ -1074,16 +1074,6 @@ export class Driver extends TypedEventEmitter node.setSecurityClass(securityClass, granted); } - /** - * **!!! INTERNAL !!!** - * - * Not intended to be used by applications. Use `node.isControllerNode` instead! - */ - public isControllerNode(nodeId: number): boolean { - // This is needed for the ZWaveHost interface - return nodeId === this.ownNodeId; - } - /** Updates the logging configuration without having to restart the driver. */ public updateLogConfig(config: Partial): void { this._logContainer.updateConfiguration(config); diff --git a/packages/zwave-js/src/lib/node/mixins/70_FirmwareUpdate.ts b/packages/zwave-js/src/lib/node/mixins/70_FirmwareUpdate.ts index 7a049b6ffd8f..8214c9756b92 100644 --- a/packages/zwave-js/src/lib/node/mixins/70_FirmwareUpdate.ts +++ b/packages/zwave-js/src/lib/node/mixins/70_FirmwareUpdate.ts @@ -1,10 +1,10 @@ import { - type FirmwareUpdateInitResult, type FirmwareUpdateMetaData, FirmwareUpdateMetaDataCC, FirmwareUpdateMetaDataCCGet, type FirmwareUpdateMetaDataCCMetaDataGet, FirmwareUpdateMetaDataCCReport, + FirmwareUpdateMetaDataCCRequestReport, FirmwareUpdateMetaDataCCStatusReport, type FirmwareUpdateOptions, type FirmwareUpdateProgress, @@ -636,7 +636,7 @@ export abstract class FirmwareUpdateMixin extends SchedulePollMixin } /** Kicks off a firmware update of a single target. Returns whether the node accepted resuming and non-secure transfer */ - private *beginFirmwareUpdateInternal( + private async *beginFirmwareUpdateInternal( data: Buffer, target: number, meta: FirmwareUpdateMetaData, @@ -655,21 +655,30 @@ export abstract class FirmwareUpdateMixin extends SchedulePollMixin direction: "outbound", }); - // Request the node to start the upgrade. Pause the task until this is done, - // since the call can block for a long time - const result: FirmwareUpdateInitResult = yield () => - api.requestUpdate({ - // TODO: Should manufacturer id and firmware id be provided externally? - manufacturerId: meta.manufacturerId, - firmwareId: target == 0 - ? meta.firmwareId - : meta.additionalFirmwareIDs[target - 1], - firmwareTarget: target, - fragmentSize, - checksum, - resume, - nonSecureTransfer, - }); + // Request the node to start the upgrade + await api.requestUpdate({ + // TODO: Should manufacturer id and firmware id be provided externally? + manufacturerId: meta.manufacturerId, + firmwareId: target == 0 + ? meta.firmwareId + : meta.additionalFirmwareIDs[target - 1], + firmwareTarget: target, + fragmentSize, + checksum, + resume, + nonSecureTransfer, + }); + // Pause the task until the response is received, because that can take + // up to a minute + const result: FirmwareUpdateMetaDataCCRequestReport = yield () => + this.driver + .waitForCommand( + (cc) => + cc instanceof FirmwareUpdateMetaDataCCRequestReport + && cc.nodeId === this.id, + 60000, + ); + switch (result.status) { case FirmwareUpdateRequestStatus.Error_AuthenticationExpected: throw new ZWaveError( diff --git a/packages/zwave-js/src/lib/node/utils.ts b/packages/zwave-js/src/lib/node/utils.ts index dd98b3252e41..15ba32c92139 100644 --- a/packages/zwave-js/src/lib/node/utils.ts +++ b/packages/zwave-js/src/lib/node/utils.ts @@ -327,7 +327,7 @@ export function getDefinedValueIDsInternal( includeInternal: boolean = false, ): TranslatedValueID[] { // The controller has no values. Even if some ended up in the cache somehow, do not return any. - if (applHost.isControllerNode(node.id)) return []; + if (node.id === applHost.ownNodeId) return []; let ret: ValueID[] = []; const allowControlled: CommandClasses[] = [