From 7475f1ba18e456be3445d80d2ecc361ff63e7413 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Thu, 13 Jun 2024 23:19:57 +0200 Subject: [PATCH] refactor: move indicators from config into code --- docs/api/config-manager.md | 28 --- packages/cc/src/cc/IndicatorCC.ts | 44 ++-- packages/config/config/indicators.json | 166 -------------- .../config/maintenance/lintConfigFiles.ts | 15 -- packages/config/src/ConfigManager.ts | 168 -------------- packages/config/src/Indicators.test.ts | 141 ------------ packages/config/src/Indicators.ts | 86 -------- packages/config/src/index.ts | 1 - packages/config/src/index_safe.ts | 1 - packages/core/src/index.ts | 1 + packages/core/src/index_safe.ts | 1 + packages/core/src/registries/Indicators.ts | 206 ++++++++++++++++++ .../src/lib/test/cc/IndicatorCC.test.ts | 6 - test/debug.js | 1 - 14 files changed, 224 insertions(+), 641 deletions(-) delete mode 100644 packages/config/config/indicators.json delete mode 100644 packages/config/src/Indicators.test.ts delete mode 100644 packages/config/src/Indicators.ts create mode 100644 packages/core/src/registries/Indicators.ts diff --git a/docs/api/config-manager.md b/docs/api/config-manager.md index b84299101633..ae311d02fbc0 100644 --- a/docs/api/config-manager.md +++ b/docs/api/config-manager.md @@ -27,34 +27,6 @@ Looks up the name of the manufacturer with the given ID in the configuration DB > [!NOTE] `loadManufacturers` must be used first. -### `loadIndicators` - -```ts -loadIndicators(): Promise; -``` - -Loads the indicators config which is used to lookup indicators and indicator properties. - -### `lookupIndicator` - -```ts -lookupIndicator(indicatorId: number): string | undefined; -``` - -Looks up the label of the indicator with the given ID in the configuration DB and returns it if it was found. - -> [!NOTE] `loadIndicators` must be used first. - -### `lookupProperty` - -```ts -lookupProperty(propertyId: number): IndicatorProperty | undefined; -``` - -Looks up the property definition for a given indicator property id - -> [!NOTE] `loadIndicators` must be used first. - ### `loadDeviceIndex` ```ts diff --git a/packages/cc/src/cc/IndicatorCC.ts b/packages/cc/src/cc/IndicatorCC.ts index 9e951b6fe0f0..c0021263040d 100644 --- a/packages/cc/src/cc/IndicatorCC.ts +++ b/packages/cc/src/cc/IndicatorCC.ts @@ -2,6 +2,7 @@ import type { ConfigManager } from "@zwave-js/config"; import { CommandClasses, type IZWaveEndpoint, + Indicator, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -11,6 +12,7 @@ import { ZWaveError, ZWaveErrorCodes, encodeBitMask, + getIndicatorProperty, parseBitMask, validatePayload, } from "@zwave-js/core/safe"; @@ -197,13 +199,13 @@ export const IndicatorCCValues = Object.freeze({ */ function getIndicatorMetadata( configManager: ConfigManager, - indicatorId: number, + indicatorId: Indicator, propertyId: number, overrideIndicatorLabel?: string, ): ValueMetadata { const label = overrideIndicatorLabel - || configManager.lookupIndicator(indicatorId); - const prop = configManager.lookupProperty(propertyId); + || getIndicatorName(indicatorId); + const prop = getIndicatorProperty(propertyId); const baseMetadata = IndicatorCCValues.valueV2( indicatorId, propertyId, @@ -244,16 +246,15 @@ function getIndicatorMetadata( } function getIndicatorName( - configManager: ConfigManager, indicatorId: number | undefined, ): string { - let indicatorName = "0 (default)"; if (indicatorId) { - indicatorName = `${num2hex(indicatorId)} (${ - configManager.lookupIndicator(indicatorId) ?? `Unknown` + return `${num2hex(indicatorId)} (${ + indicatorId in Indicator ? Indicator[indicatorId] : "Unknown" })`; + } else { + return "0 (default)"; } - return indicatorName; } const MAX_INDICATOR_OBJECTS = 31; @@ -808,7 +809,7 @@ export class IndicatorCC extends CommandClass { && typeof propertyKey === "number" ) { // The indicator property is our property key - const prop = applHost.configManager.lookupProperty(propertyKey); + const prop = getIndicatorProperty(propertyKey); if (prop) return prop.label; } return super.translatePropertyKey(applHost, property, propertyKey); @@ -821,8 +822,7 @@ export class IndicatorCC extends CommandClass { ): string { if (typeof property === "number" && typeof propertyKey === "number") { // The indicator corresponds to our property - const label = applHost.configManager.lookupIndicator(property); - if (label) return label; + if (property in Indicator) return Indicator[property]; } return super.translateProperty(applHost, property, propertyKey); } @@ -1232,10 +1232,7 @@ export class IndicatorCCGet extends IndicatorCC { return { ...super.toLogEntry(applHost), message: { - indicator: getIndicatorName( - applHost.configManager, - this.indicatorId, - ), + indicator: getIndicatorName(this.indicatorId), }, }; } @@ -1318,23 +1315,17 @@ export class IndicatorCCSupportedReport extends IndicatorCC { return { ...super.toLogEntry(applHost), message: { - indicator: getIndicatorName( - applHost.configManager, - this.indicatorId, - ), + indicator: getIndicatorName(this.indicatorId), "supported properties": `${ this.supportedProperties .map( (id) => - applHost.configManager.lookupProperty(id)?.label + getIndicatorProperty(id)?.label ?? `Unknown (${num2hex(id)})`, ) .join(", ") }`, - "next indicator": getIndicatorName( - applHost.configManager, - this.nextIndicatorId, - ), + "next indicator": getIndicatorName(this.nextIndicatorId), }, }; } @@ -1384,10 +1375,7 @@ export class IndicatorCCSupportedGet extends IndicatorCC { return { ...super.toLogEntry(applHost), message: { - indicator: getIndicatorName( - applHost.configManager, - this.indicatorId, - ), + indicator: getIndicatorName(this.indicatorId), }, }; } diff --git a/packages/config/config/indicators.json b/packages/config/config/indicators.json deleted file mode 100644 index 85c3877326d3..000000000000 --- a/packages/config/config/indicators.json +++ /dev/null @@ -1,166 +0,0 @@ -// Z-Wave indicator definitions -// based on SDS14220, version 2020-01-01 -{ - "indicators": { - "0x01": "Armed", - "0x02": "Not armed / disarmed", - "0x03": "Ready", - "0x04": "Fault", - "0x05": "Busy", - "0x06": "Enter ID", - "0x07": "Enter PIN", - "0x08": "Code Accepted", - "0x09": "Code not accepted", - "0x0a": "Armed Stay", - "0x0b": "Armed Away", - "0x0c": "Alarming", - "0x0d": "Alarming: Burglar", - "0x0e": "Alarming: Smoke / Fire", - "0x0f": "Alarming: Carbon Monoxide", - "0x10": "Bypass challenge", - "0x11": "Entry Delay", - "0x12": "Exit Delay", - "0x13": "Alarming: Medical", - "0x14": "Alarming: freeze warning", - "0x15": "Alarming: Water leak", - "0x16": "Alarming: Panic", - "0x20": "Zone 1 armed", - "0x21": "Zone 2 armed", - "0x22": "Zone 3 armed", - "0x23": "Zone 4 armed", - "0x24": "Zone 5 armed", - "0x25": "Zone 6 armed", - "0x26": "Zone 7 armed", - "0x27": "Zone 8 armed", - "0x30": "LCD backlight", - "0x40": "Button backlight letters", - "0x41": "Button backlight digits", - "0x42": "Button backlight command", - "0x43": "Button 1 indication", - "0x44": "Button 2 indication", - "0x45": "Button 3 indication", - "0x46": "Button 4 indication", - "0x47": "Button 5 indication", - "0x48": "Button 6 indication", - "0x49": "Button 7 indication", - "0x4a": "Button 8 indication", - "0x4b": "Button 9 indication", - "0x4c": "Button 10 indication", - "0x4d": "Button 11 indication", - "0x4e": "Button 12 indication", - "0x50": "Node Identify", - "0x60": "Generic event sound notification 1", - "0x61": "Generic event sound notification 2", - "0x62": "Generic event sound notification 3", - "0x63": "Generic event sound notification 4", - "0x64": "Generic event sound notification 5", - "0x65": "Generic event sound notification 6", - "0x66": "Generic event sound notification 7", - "0x67": "Generic event sound notification 8", - "0x68": "Generic event sound notification 9", - "0x69": "Generic event sound notification 10", - "0x6a": "Generic event sound notification 11", - "0x6b": "Generic event sound notification 12", - "0x6c": "Generic event sound notification 13", - "0x6d": "Generic event sound notification 14", - "0x6e": "Generic event sound notification 15", - "0x6f": "Generic event sound notification 16", - "0x70": "Generic event sound notification 17", - "0x71": "Generic event sound notification 18", - "0x72": "Generic event sound notification 19", - "0x73": "Generic event sound notification 20", - "0x74": "Generic event sound notification 21", - "0x75": "Generic event sound notification 22", - "0x76": "Generic event sound notification 23", - "0x77": "Generic event sound notification 24", - "0x78": "Generic event sound notification 25", - "0x79": "Generic event sound notification 26", - "0x7a": "Generic event sound notification 27", - "0x7b": "Generic event sound notification 28", - "0x7c": "Generic event sound notification 29", - "0x7d": "Generic event sound notification 30", - "0x7e": "Generic event sound notification 31", - "0x7f": "Generic event sound notification 32", - "0x80": "Manufacturer defined indicator 1", - "0x81": "Manufacturer defined indicator 2", - "0x82": "Manufacturer defined indicator 3", - "0x83": "Manufacturer defined indicator 4", - "0x84": "Manufacturer defined indicator 5", - "0x85": "Manufacturer defined indicator 6", - "0x86": "Manufacturer defined indicator 7", - "0x87": "Manufacturer defined indicator 8", - "0x88": "Manufacturer defined indicator 9", - "0x89": "Manufacturer defined indicator 10", - "0x8a": "Manufacturer defined indicator 11", - "0x8b": "Manufacturer defined indicator 12", - "0x8c": "Manufacturer defined indicator 13", - "0x8d": "Manufacturer defined indicator 14", - "0x8e": "Manufacturer defined indicator 15", - "0x8f": "Manufacturer defined indicator 16", - "0x90": "Manufacturer defined indicator 17", - "0x91": "Manufacturer defined indicator 18", - "0x92": "Manufacturer defined indicator 19", - "0x93": "Manufacturer defined indicator 20", - "0x94": "Manufacturer defined indicator 21", - "0x95": "Manufacturer defined indicator 22", - "0x96": "Manufacturer defined indicator 23", - "0x97": "Manufacturer defined indicator 24", - "0x98": "Manufacturer defined indicator 25", - "0x99": "Manufacturer defined indicator 26", - "0x9a": "Manufacturer defined indicator 27", - "0x9b": "Manufacturer defined indicator 28", - "0x9c": "Manufacturer defined indicator 29", - "0x9d": "Manufacturer defined indicator 30", - "0x9e": "Manufacturer defined indicator 31", - "0x9f": "Manufacturer defined indicator 32", - "0xf0": "Buzzer" - }, - "properties": { - "0x01": { - "label": "Multilevel" - }, - "0x02": { - "label": "Binary", - "type": "boolean" - }, - "0x03": { - "label": "On/Off Period: Duration", - "description": "Sets the duration of an on/off period in 1/10th seconds. Must be set together with \"On/Off Cycle Count\"" - }, - "0x04": { - "label": "On/Off Cycle Count", - "description": "Sets the number of on/off periods. 0xff means infinite. Must be set together with \"On/Off Period duration\"" - }, - "0x05": { - "label": "On/Off Period: On time", - "description": "This property is used to set the length of the On time during an On/Off period. It allows asymmetric On/Off periods. The value 0x00 MUST represent symmetric On/Off period (On time equal to Off time)" - }, - "0x0a": { - "label": "Timeout: Hours", - "description": "Turns the indicator of after this amount of hours. Can be used together with other timeout properties" - }, - "0x06": { - "label": "Timeout: Minutes", - "description": "Turns the indicator of after this amount of minutes. Can be used together with other timeout properties" - }, - "0x07": { - "label": "Timeout: Seconds", - "description": "Turns the indicator of after this amount of seconds. Can be used together with other timeout properties" - }, - "0x08": { - "label": "Timeout: 1/100th seconds", - "description": "Turns the indicator of after this amount of 1/100th seconds. Can be used together with other timeout properties" - }, - "0x09": { - "label": "Sound level", - "description": "This property is used to set the volume of a indicator. 0 means off/mute.", - "max": 100 - }, - "0x10": { - "label": "Low power", - "description": "This property MAY be used to by a supporting node advertise that the indicator can continue working in sleep mode.", - "readonly": true, - "type": "boolean" - } - } -} diff --git a/packages/config/maintenance/lintConfigFiles.ts b/packages/config/maintenance/lintConfigFiles.ts index 002795ca1b54..2c7a70c1dc15 100644 --- a/packages/config/maintenance/lintConfigFiles.ts +++ b/packages/config/maintenance/lintConfigFiles.ts @@ -39,20 +39,6 @@ async function lintManufacturers(): Promise { // TODO: Validate that the file is semantically correct } -async function lintIndicators(): Promise { - await configManager.loadIndicators(); - const properties = configManager.indicatorProperties; - - if (!(properties.get(1)?.label === "Multilevel")) { - throw new Error( - `The indicator property Multilevel (0x01) is required!`, - ); - } - if (!(properties.get(2)?.label === "Binary")) { - throw new Error(`The indicator property Binary (0x02) is required!`); - } -} - function getAllConditions( config: ConditionalDeviceConfig, ): Map> { @@ -1273,7 +1259,6 @@ export async function lintConfigFiles(): Promise { await lintManufacturers(); await lintDevices(); await lintNotifications(); - await lintIndicators(); console.log(); console.log(green("The config files are valid!")); diff --git a/packages/config/src/ConfigManager.ts b/packages/config/src/ConfigManager.ts index f076319c1814..d3541f615b08 100644 --- a/packages/config/src/ConfigManager.ts +++ b/packages/config/src/ConfigManager.ts @@ -9,11 +9,6 @@ import { isObject } from "alcalzone-shared/typeguards"; import { pathExists, readFile } from "fs-extra"; import JSON5 from "json5"; import path from "node:path"; -import { - type IndicatorMap, - type IndicatorPropertiesMap, - IndicatorProperty, -} from "./Indicators"; import { ConfigLogger } from "./Logger"; import { type ManufacturersMap, @@ -63,28 +58,6 @@ export class ConfigManager { private logger: ConfigLogger; - private _indicators: IndicatorMap | undefined; - public get indicators(): IndicatorMap { - if (!this._indicators) { - throw new ZWaveError( - "The config has not been loaded yet!", - ZWaveErrorCodes.Driver_NotReady, - ); - } - return this._indicators; - } - - private _indicatorProperties: IndicatorPropertiesMap | undefined; - public get indicatorProperties(): IndicatorPropertiesMap { - if (!this._indicatorProperties) { - throw new ZWaveError( - "The config has not been loaded yet!", - ZWaveErrorCodes.Driver_NotReady, - ); - } - return this._indicatorProperties; - } - private _manufacturers: ManufacturersMap | undefined; public get manufacturers(): ManufacturersMap { if (!this._manufacturers) { @@ -135,7 +108,6 @@ export class ConfigManager { await this.loadManufacturers(); await this.loadDeviceIndex(); await this.loadNotifications(); - await this.loadIndicators(); } public async loadManufacturers(): Promise { @@ -205,61 +177,6 @@ export class ConfigManager { this._manufacturers.set(manufacturerId, manufacturerName); } - public async loadIndicators(): Promise { - try { - const config = await loadIndicatorsInternal( - this._useExternalConfig, - ); - this._indicators = config.indicators; - this._indicatorProperties = config.properties; - } catch (e) { - // If the config file is missing or invalid, don't try to find it again - if (isZWaveError(e) && e.code === ZWaveErrorCodes.Config_Invalid) { - if (process.env.NODE_ENV !== "test") { - this.logger.print( - `Could not load indicators config: ${e.message}`, - "error", - ); - } - if (!this._indicators) this._indicators = new Map(); - if (!this._indicatorProperties) { - this._indicatorProperties = new Map(); - } - } else { - // This is an unexpected error - throw e; - } - } - } - - /** - * Looks up the label for a given indicator id - */ - public lookupIndicator(indicatorId: number): string | undefined { - if (!this._indicators) { - throw new ZWaveError( - "The config has not been loaded yet!", - ZWaveErrorCodes.Driver_NotReady, - ); - } - - return this._indicators.get(indicatorId); - } - - /** - * Looks up the property definition for a given indicator property id - */ - public lookupProperty(propertyId: number): IndicatorProperty | undefined { - if (!this._indicatorProperties) { - throw new ZWaveError( - "The config has not been loaded yet!", - ZWaveErrorCodes.Driver_NotReady, - ); - } - - return this._indicatorProperties.get(propertyId); - } - public async loadDeviceIndex(): Promise { try { // The index of config files included in this package @@ -479,91 +396,6 @@ export class ConfigManager { } } -/** @internal */ -export async function loadIndicatorsInternal( - externalConfig?: boolean, -): Promise<{ - indicators: IndicatorMap; - properties: IndicatorPropertiesMap; -}> { - const indicatorsConfigPath = path.join( - (externalConfig && externalConfigDir()) || configDir, - "indicators.json", - ); - - if (!(await pathExists(indicatorsConfigPath))) { - throw new ZWaveError( - "The config file does not exist!", - ZWaveErrorCodes.Config_Invalid, - ); - } - - try { - const fileContents = await readFile(indicatorsConfigPath, "utf8"); - const definition = JSON5.parse(fileContents); - if (!isObject(definition)) { - throwInvalidConfig("indicators", "the database is not an object"); - } - if (!("indicators" in definition)) { - throwInvalidConfig( - "indicators", - `the required key "indicators" is missing`, - ); - } - if (!("properties" in definition)) { - throwInvalidConfig( - "indicators", - `the required key "properties" is missing`, - ); - } - - const indicators = new Map(); - for (const [id, label] of Object.entries(definition.indicators)) { - if (!hexKeyRegexNDigits.test(id)) { - throwInvalidConfig( - "indicators", - `found invalid key "${id}" in "indicators". Indicators must have lowercase hexadecimal IDs.`, - ); - } - if (typeof label !== "string") { - throwInvalidConfig( - "indicators", - `indicator "${id}" must be a string`, - ); - } - const idNum = parseInt(id.slice(2), 16); - indicators.set(idNum, label); - } - - const properties = new Map(); - for ( - const [id, propDefinition] of Object.entries( - definition.properties, - ) - ) { - if (!hexKeyRegexNDigits.test(id)) { - throwInvalidConfig( - "indicators", - `found invalid key "${id}" in "properties". Indicator properties must have lowercase hexadecimal IDs.`, - ); - } - const idNum = parseInt(id.slice(2), 16); - properties.set( - idNum, - new IndicatorProperty(idNum, propDefinition as any), - ); - } - - return { indicators, properties }; - } catch (e) { - if (isZWaveError(e)) { - throw e; - } else { - throwInvalidConfig("indicators"); - } - } -} - /** @internal */ export async function loadNotificationsInternal( externalConfig?: boolean, diff --git a/packages/config/src/Indicators.test.ts b/packages/config/src/Indicators.test.ts deleted file mode 100644 index a713777ae340..000000000000 --- a/packages/config/src/Indicators.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import test, { type ExecutionContext } from "ava"; -import fsExtra from "fs-extra"; -import sinon from "sinon"; -import { ConfigManager } from "./ConfigManager"; - -const readFileStub = sinon.stub(fsExtra, "readFile"); -const pathExistsStub = sinon.stub(fsExtra, "pathExists"); - -const dummyIndicators = { - indicators: { - "0x01": "Indicator 1", - "0x02": "Indicator 2", - }, - properties: { - "0x01": { - label: "Property 1", - }, - }, -}; - -{ - async function prepareTest(t: ExecutionContext): Promise { - // Loading configuration may take a while on CI - t.timeout(30000); - - pathExistsStub.reset(); - readFileStub.reset(); - pathExistsStub.resolves(false); - readFileStub.rejects(new Error("File does not exist")); - - const configManager = new ConfigManager(); - await configManager.loadIndicators(); - return configManager; - } - - test.serial( - "lookupIndicator (with missing file) does not throw", - async (t) => { - const configManager = await prepareTest(t); - t.notThrows(() => configManager.lookupIndicator(1)); - }, - ); - - test.serial( - "lookupIndicator (with missing file) returns undefined", - async (t) => { - const configManager = await prepareTest(t); - t.is(configManager.lookupIndicator(0x0e), undefined); - t.is(configManager.lookupIndicator(0xff), undefined); - }, - ); -} - -{ - async function prepareTest(t: ExecutionContext): Promise { - // Loading configuration may take a while on CI - t.timeout(30000); - - pathExistsStub.reset(); - readFileStub.reset(); - pathExistsStub.resolves(true); - readFileStub.resolves(`{"0x01": ` as any); - - const configManager = new ConfigManager(); - await configManager.loadIndicators(); - return configManager; - } - - test.serial( - "lookupIndicator (with invalid file) does not throw", - async (t) => { - const configManager = await prepareTest(t); - t.notThrows(() => configManager.lookupIndicator(0x1)); - }, - ); - - test.serial( - "lookupIndicator (with invalid file) returns undefined", - async (t) => { - const configManager = await prepareTest(t); - t.is(configManager.lookupIndicator(0x01), undefined); - }, - ); -} - -{ - async function prepareTest(t: ExecutionContext): Promise { - // Loading configuration may take a while on CI - t.timeout(30000); - - pathExistsStub.reset(); - readFileStub.reset(); - - pathExistsStub.resolves(true); - readFileStub.resolves(JSON.stringify(dummyIndicators) as any); - - const configManager = new ConfigManager(); - await configManager.loadIndicators(); - return configManager; - } - - test.serial( - "lookupIndicator() returns the indicator definition if it is defined", - async (t) => { - const configManager = await prepareTest(t); - const test1 = configManager.lookupIndicator(0x01); - t.is(test1, "Indicator 1"); - - t.is(configManager.lookupIndicator(0xff), undefined); - }, - ); -} - -{ - async function prepareTest(t: ExecutionContext): Promise { - // Loading configuration may take a while on CI - t.timeout(30000); - - pathExistsStub.reset(); - readFileStub.reset(); - pathExistsStub.resolves(true); - readFileStub.resolves(JSON.stringify(dummyIndicators) as any); - - const configManager = new ConfigManager(); - await configManager.loadIndicators(); - return configManager; - } - - test.serial( - "lookupIndicatorProperty() returns the property definition if it is defined", - async (t) => { - const configManager = await prepareTest(t); - - const test1 = configManager.lookupProperty(0x01); - t.not(test1, undefined); - t.is(test1!.label, "Property 1"); - - t.is(configManager.lookupProperty(0xff), undefined); - }, - ); -} diff --git a/packages/config/src/Indicators.ts b/packages/config/src/Indicators.ts deleted file mode 100644 index 852624cd66c9..000000000000 --- a/packages/config/src/Indicators.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { ValueType } from "@zwave-js/core/safe"; -import { type JSONObject, num2hex } from "@zwave-js/shared/safe"; -import { throwInvalidConfig } from "./utils_safe"; - -export type IndicatorMap = ReadonlyMap; -export type IndicatorPropertiesMap = ReadonlyMap; - -export class IndicatorProperty { - public constructor(id: number, definition: JSONObject) { - this.id = id; - - if (typeof definition.label !== "string") { - throwInvalidConfig( - "indicators", - `The label for property ${num2hex(id)} is not a string!`, - ); - } - this.label = definition.label; - - if ( - definition.description != undefined - && typeof definition.description !== "string" - ) { - throwInvalidConfig( - "indicators", - `The description for property ${num2hex(id)} is not a string!`, - ); - } - this.description = definition.description; - - if (definition.min != undefined && typeof definition.min !== "number") { - throwInvalidConfig( - "indicators", - `The minimum value for property ${ - num2hex( - id, - ) - } is not a number!`, - ); - } - this.min = definition.min; - - if (definition.max != undefined && typeof definition.max !== "number") { - throwInvalidConfig( - "indicators", - `The maximum value for property ${ - num2hex( - id, - ) - } is not a number!`, - ); - } - this.max = definition.max; - - if ( - definition.readonly != undefined - && typeof definition.readonly !== "boolean" - ) { - throwInvalidConfig( - "indicators", - `readonly for property ${num2hex(id)} is not a boolean!`, - ); - } - this.readonly = definition.readonly; - - if ( - definition.type != undefined - && typeof definition.type !== "string" - ) { - throwInvalidConfig( - "indicators", - `type for property ${num2hex(id)} is not a string!`, - ); - } - // TODO: Validate that the value is ok - this.type = definition.type; - } - - public readonly id: number; - public readonly label: string; - public readonly description: string | undefined; - public readonly min: number | undefined; - public readonly max: number | undefined; - public readonly readonly: boolean | undefined; - public readonly type: ValueType | undefined; -} diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index d0f5f86dc975..abacbb40de80 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -1,5 +1,4 @@ export * from "./ConfigManager"; -export * from "./Indicators"; export * from "./Logger_safe"; export * from "./Manufacturers"; export * from "./Notifications"; diff --git a/packages/config/src/index_safe.ts b/packages/config/src/index_safe.ts index 20947ea408df..7172b4e5957b 100644 --- a/packages/config/src/index_safe.ts +++ b/packages/config/src/index_safe.ts @@ -1,5 +1,4 @@ /* @forbiddenImports external */ -export * from "./Indicators"; export * from "./Logger_safe"; export * from "./Notifications"; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b2b40b5c8a71..ee712e1d8233 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -15,6 +15,7 @@ export * from "./log/Controller"; export * from "./log/shared"; export * from "./log/shared_safe"; export * from "./registries/DeviceClasses"; +export * from "./registries/Indicators"; export * from "./registries/Meters"; export * from "./registries/Scales"; export * from "./registries/Sensors"; diff --git a/packages/core/src/index_safe.ts b/packages/core/src/index_safe.ts index 626ee5dabbda..08760845d209 100644 --- a/packages/core/src/index_safe.ts +++ b/packages/core/src/index_safe.ts @@ -15,6 +15,7 @@ export * from "./consts"; export * from "./error/ZWaveError"; export * from "./log/shared_safe"; export * from "./registries/DeviceClasses"; +export * from "./registries/Indicators"; export * from "./registries/Meters"; export * from "./registries/Scales"; export * from "./registries/Sensors"; diff --git a/packages/core/src/registries/Indicators.ts b/packages/core/src/registries/Indicators.ts new file mode 100644 index 000000000000..305f7e00d4c0 --- /dev/null +++ b/packages/core/src/registries/Indicators.ts @@ -0,0 +1,206 @@ +import { type ValueType } from "../values/Metadata"; + +export enum Indicator { + "Armed" = 0x01, + "Not armed / disarmed" = 0x02, + "Ready" = 0x03, + "Fault" = 0x04, + "Busy" = 0x05, + "Enter ID" = 0x06, + "Enter PIN" = 0x07, + "Code accepted" = 0x08, + "Code not accepted" = 0x09, + "Armed Stay" = 0x0a, + "Armed Away" = 0x0b, + "Alarming" = 0x0c, + "Alarming: Burglar" = 0x0d, + "Alarming: Smoke / Fire" = 0x0e, + "Alarming: Carbon Monoxide" = 0x0f, + "Bypass challenge" = 0x10, + "Entry Delay" = 0x11, + "Exit Delay" = 0x12, + "Alarming: Medical" = 0x13, + "Alarming: Freeze warning" = 0x14, + "Alarming: Water leak" = 0x15, + "Alarming: Panic" = 0x16, + "Zone 1 armed" = 0x20, + "Zone 2 armed" = 0x21, + "Zone 3 armed" = 0x22, + "Zone 4 armed" = 0x23, + "Zone 5 armed" = 0x24, + "Zone 6 armed" = 0x25, + "Zone 7 armed" = 0x26, + "Zone 8 armed" = 0x27, + "LCD backlight" = 0x30, + "Button backlight letters" = 0x40, + "Button backlight digits" = 0x41, + "Button backlight command" = 0x42, + "Button 1 indication" = 0x43, + "Button 2 indication" = 0x44, + "Button 3 indication" = 0x45, + "Button 4 indication" = 0x46, + "Button 5 indication" = 0x47, + "Button 6 indication" = 0x48, + "Button 7 indication" = 0x49, + "Button 8 indication" = 0x4a, + "Button 9 indication" = 0x4b, + "Button 10 indication" = 0x4c, + "Button 11 indication" = 0x4d, + "Button 12 indication" = 0x4e, + "Node Identify" = 0x50, + "Generic event sound notification 1" = 0x60, + "Generic event sound notification 2" = 0x61, + "Generic event sound notification 3" = 0x62, + "Generic event sound notification 4" = 0x63, + "Generic event sound notification 5" = 0x64, + "Generic event sound notification 6" = 0x65, + "Generic event sound notification 7" = 0x66, + "Generic event sound notification 8" = 0x67, + "Generic event sound notification 9" = 0x68, + "Generic event sound notification 10" = 0x69, + "Generic event sound notification 11" = 0x6a, + "Generic event sound notification 12" = 0x6b, + "Generic event sound notification 13" = 0x6c, + "Generic event sound notification 14" = 0x6d, + "Generic event sound notification 15" = 0x6e, + "Generic event sound notification 16" = 0x6f, + "Generic event sound notification 17" = 0x70, + "Generic event sound notification 18" = 0x71, + "Generic event sound notification 19" = 0x72, + "Generic event sound notification 20" = 0x73, + "Generic event sound notification 21" = 0x74, + "Generic event sound notification 22" = 0x75, + "Generic event sound notification 23" = 0x76, + "Generic event sound notification 24" = 0x77, + "Generic event sound notification 25" = 0x78, + "Generic event sound notification 26" = 0x79, + "Generic event sound notification 27" = 0x7a, + "Generic event sound notification 28" = 0x7b, + "Generic event sound notification 29" = 0x7c, + "Generic event sound notification 30" = 0x7d, + "Generic event sound notification 31" = 0x7e, + "Generic event sound notification 32" = 0x7f, + "Manufacturer defined 1" = 0x80, + "Manufacturer defined 2" = 0x81, + "Manufacturer defined 3" = 0x82, + "Manufacturer defined 4" = 0x83, + "Manufacturer defined 5" = 0x84, + "Manufacturer defined 6" = 0x85, + "Manufacturer defined 7" = 0x86, + "Manufacturer defined 8" = 0x87, + "Manufacturer defined 9" = 0x88, + "Manufacturer defined 10" = 0x89, + "Manufacturer defined 11" = 0x8a, + "Manufacturer defined 12" = 0x8b, + "Manufacturer defined 13" = 0x8c, + "Manufacturer defined 14" = 0x8d, + "Manufacturer defined 15" = 0x8e, + "Manufacturer defined 16" = 0x8f, + "Manufacturer defined 17" = 0x90, + "Manufacturer defined 18" = 0x91, + "Manufacturer defined 19" = 0x92, + "Manufacturer defined 20" = 0x93, + "Manufacturer defined 21" = 0x94, + "Manufacturer defined 22" = 0x95, + "Manufacturer defined 23" = 0x96, + "Manufacturer defined 24" = 0x97, + "Manufacturer defined 25" = 0x98, + "Manufacturer defined 26" = 0x99, + "Manufacturer defined 27" = 0x9a, + "Manufacturer defined 28" = 0x9b, + "Manufacturer defined 29" = 0x9c, + "Manufacturer defined 30" = 0x9d, + "Manufacturer defined 31" = 0x9e, + "Manufacturer defined 32" = 0x9f, + "Buzzer" = 0xf0, +} + +export interface IndicatorPropertyDefinition { + readonly label: string; + readonly description?: string; + readonly min?: number; + readonly max?: number; + readonly readonly?: boolean; + readonly type?: ValueType; +} + +export interface IndicatorProperty extends IndicatorPropertyDefinition { + readonly id: number; +} + +const indicatorProperties = Object.freeze( + { + [0x01]: { + label: "Multilevel", + }, + [0x02]: { + label: "Binary", + type: "boolean", + }, + [0x03]: { + label: "On/Off Period: Duration", + description: + "Sets the duration of an on/off period in 1/10th seconds. Must be set together with \"On/Off Cycle Count\"", + }, + [0x04]: { + label: "On/Off Cycle Count", + description: + "Sets the number of on/off periods. 0xff means infinite. Must be set together with \"On/Off Period duration\"", + }, + [0x05]: { + label: "On/Off Period: On time", + description: + "This property is used to set the length of the On time during an On/Off period. It allows asymmetric On/Off periods. The value 0x00 MUST represent symmetric On/Off period (On time equal to Off time)", + }, + [0x0a]: { + label: "Timeout: Hours", + description: + "Turns the indicator of after this amount of hours. Can be used together with other timeout properties", + }, + [0x06]: { + label: "Timeout: Minutes", + description: + "Turns the indicator of after this amount of minutes. Can be used together with other timeout properties", + }, + [0x07]: { + label: "Timeout: Seconds", + description: + "Turns the indicator of after this amount of seconds. Can be used together with other timeout properties", + }, + [0x08]: { + label: "Timeout: 1/100th seconds", + description: + "Turns the indicator of after this amount of 1/100th seconds. Can be used together with other timeout properties", + }, + [0x09]: { + label: "Sound level", + description: + "This property is used to set the volume of a indicator. 0 means off/mute.", + max: 100, + }, + [0x10]: { + label: "Low power", + description: + "This property MAY be used to by a supporting node advertise that the indicator can continue working in sleep mode.", + readonly: true, + type: "boolean", + }, + } as const satisfies Record, +); + +/** Returns the indicator property definition for the given id */ +export function getIndicatorProperty( + id: ID, +): ID extends keyof typeof indicatorProperties ? typeof indicatorProperties[ID] + : (IndicatorProperty | undefined) +{ + const property: IndicatorPropertyDefinition | undefined = + (indicatorProperties as any)[id]; + // @ts-expect-error Undefined is valid if the property is not found + if (!property) return; + + return { + id, + ...property, + } satisfies IndicatorProperty as any; +} diff --git a/packages/zwave-js/src/lib/test/cc/IndicatorCC.test.ts b/packages/zwave-js/src/lib/test/cc/IndicatorCC.test.ts index 601004ef951f..626c640c93f2 100644 --- a/packages/zwave-js/src/lib/test/cc/IndicatorCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/IndicatorCC.test.ts @@ -23,12 +23,6 @@ function buildCCBuffer(payload: Buffer): Buffer { const host = createTestingHost(); -test.before(async (t) => { - // Loading configuration may take a while on CI - t.timeout(30000); - await host.configManager.loadIndicators(); -}); - test("the Get command (V1) should serialize correctly", (t) => { const cc = new IndicatorCCGet(host, { nodeId: 1 }); const expected = buildCCBuffer( diff --git a/test/debug.js b/test/debug.js index 24a7cd27f09b..2cb55aa7b647 100644 --- a/test/debug.js +++ b/test/debug.js @@ -18,7 +18,6 @@ const { ConfigManager } = require("@zwave-js/config"); await configManager.loadManufacturers(); await configManager.loadDeviceIndex(); await configManager.loadNotifications(); - await configManager.loadIndicators(); // The data to decode const data = Buffer.from(