@zwave-js/config 15.19.0 → 15.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/ConfigManager.js +1 -1
- package/build/cjs/ConfigManager.js.map +1 -1
- package/build/cjs/_version.d.ts +1 -1
- package/build/cjs/_version.js +1 -1
- package/build/cjs/_version.js.map +1 -1
- package/build/cjs/devices/DeviceConfig.d.ts +3 -2
- package/build/cjs/devices/DeviceConfig.js +16 -2
- package/build/cjs/devices/DeviceConfig.js.map +2 -2
- package/build/cjs/devices/ParamInformation.d.ts +4 -1
- package/build/cjs/devices/ParamInformation.js +94 -3
- package/build/cjs/devices/ParamInformation.js.map +2 -2
- package/build/esm/ConfigManager.js +1 -1
- package/build/esm/ConfigManager.js.map +1 -1
- package/build/esm/_version.d.ts +1 -1
- package/build/esm/_version.js +1 -1
- package/build/esm/devices/DeviceConfig.d.ts +3 -2
- package/build/esm/devices/DeviceConfig.d.ts.map +1 -1
- package/build/esm/devices/DeviceConfig.js +23 -2
- package/build/esm/devices/DeviceConfig.js.map +1 -1
- package/build/esm/devices/ParamInformation.d.ts +4 -1
- package/build/esm/devices/ParamInformation.d.ts.map +1 -1
- package/build/esm/devices/ParamInformation.js +109 -5
- package/build/esm/devices/ParamInformation.js.map +1 -1
- package/config/devices/0x0039/39357_39464_zw3011.json +1 -1
- package/config/devices/0x008b/trane_xr524.json +1 -1
- package/config/devices/0x008b/xl624.json +1 -1
- package/config/devices/0x015f/mh-dt311_411.json +16 -4
- package/config/devices/0x016a/ft116.json +6 -4
- package/config/devices/0x0190/adc-s2000-t-ra.json +3 -2
- package/config/devices/0x019a/11_02_022.json +1 -1
- package/config/devices/0x0214/dp-32_ix-30_ix-32_6.0_255.255.json +6 -3
- package/config/devices/0x0214/es-61_0.0_5.255.json +6 -3
- package/config/devices/0x027a/zen16.json +27 -3
- package/config/devices/0x0344/he-hls01.json +19 -5
- package/config/devices/0x0346/contact_sensor_gen2.json +11 -3
- package/config/devices/0x0438/4512757.json +4 -3
- package/config/devices/0x0438/dimmer2-400w.json +16 -4
- package/config/eslint.config.mjs +3 -0
- package/package.json +4 -4
|
@@ -65,7 +65,7 @@ class ConfigManager {
|
|
|
65
65
|
_logger;
|
|
66
66
|
async getLogger() {
|
|
67
67
|
if (!this._logContainer) {
|
|
68
|
-
this._logContainer = //
|
|
68
|
+
this._logContainer = // oxlint-disable-next-line typescript/ban-ts-comment
|
|
69
69
|
// @ts-ignore - For some reason, VSCode does not like this import, although tsc is fine with it
|
|
70
70
|
(await import("#default_bindings/log")).log({
|
|
71
71
|
enabled: false
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/ConfigManager.ts"],
|
|
4
|
-
"sourcesContent": ["import { configDir } from \"#config_dir\";\nimport {\n\ttype LogContainer,\n\tZWaveError,\n\tZWaveErrorCodes,\n\tisZWaveError,\n} from \"@zwave-js/core\";\nimport { getErrorMessage, pathExists } from \"@zwave-js/shared\";\nimport type { FileSystem } from \"@zwave-js/shared/bindings\";\nimport path from \"pathe\";\nimport { ConfigLogger } from \"./Logger.js\";\nimport {\n\ttype ManufacturersMap,\n\tloadManufacturersInternal,\n\tsaveManufacturersInternal,\n} from \"./Manufacturers.js\";\nimport { PACKAGE_VERSION } from \"./_version.js\";\nimport {\n\tConditionalDeviceConfig,\n\ttype DeviceConfig,\n\ttype DeviceConfigIndex,\n\ttype FulltextDeviceConfigIndex,\n\tgeneratePriorityDeviceIndex,\n\tgetDevicesPaths,\n\tloadDeviceIndexInternal,\n\tloadFulltextDeviceIndexInternal,\n} from \"./devices/DeviceConfig.js\";\nimport {\n\ttype SyncExternalConfigDirResult,\n\tgetDeviceEntryPredicate,\n\tgetExternalConfigDirEnvVariable,\n\tsyncExternalConfigDir,\n} from \"./utils.js\";\n\nexport interface ConfigManagerOptions {\n\tbindings?: FileSystem;\n\tlogContainer?: LogContainer;\n\tdeviceConfigPriorityDir?: string;\n\tdeviceConfigExternalDir?: string;\n}\n\nexport class ConfigManager {\n\tpublic constructor(options: ConfigManagerOptions = {}) {\n\t\tthis._fs = options.bindings;\n\t\tthis._logContainer = options.logContainer;\n\n\t\tthis.deviceConfigPriorityDir = options.deviceConfigPriorityDir;\n\t\tthis.deviceConfigExternalDir = options.deviceConfigExternalDir;\n\n\t\tthis._configVersion = PACKAGE_VERSION;\n\t}\n\n\tprivate _fs: FileSystem | undefined;\n\tprivate async getFS(): Promise<FileSystem> {\n\t\tthis._fs ??= (await import(\"#default_bindings/fs\")).fs;\n\t\treturn this._fs;\n\t}\n\n\tprivate _configVersion: string;\n\tpublic get configVersion(): string {\n\t\treturn this._configVersion;\n\t}\n\n\tprivate _logContainer: LogContainer | undefined;\n\tprivate _logger: ConfigLogger | undefined;\n\tprivate async getLogger(): Promise<ConfigLogger> {\n\t\tif (!this._logContainer) {\n\t\t\tthis._logContainer =\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t\t\t// @ts-ignore - For some reason, VSCode does not like this import, although tsc is fine with it\n\t\t\t\t(await import(\"#default_bindings/log\")).log({\n\t\t\t\t\tenabled: false,\n\t\t\t\t});\n\t\t}\n\t\tif (!this._logger) {\n\t\t\tthis._logger = new ConfigLogger(this._logContainer);\n\t\t}\n\t\treturn this._logger;\n\t}\n\n\tprivate _manufacturers: ManufacturersMap | undefined;\n\tpublic get manufacturers(): ManufacturersMap {\n\t\tif (!this._manufacturers) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t\"The config has not been loaded yet!\",\n\t\t\t\tZWaveErrorCodes.Driver_NotReady,\n\t\t\t);\n\t\t}\n\t\treturn this._manufacturers;\n\t}\n\n\tprivate deviceConfigPriorityDir: string | undefined;\n\tprivate deviceConfigExternalDir: string | undefined;\n\tpublic get externalConfigDir(): string | undefined {\n\t\treturn this.deviceConfigExternalDir\n\t\t\t?? getExternalConfigDirEnvVariable();\n\t}\n\n\tprivate index: DeviceConfigIndex | undefined;\n\tprivate fulltextIndex: FulltextDeviceConfigIndex | undefined;\n\n\tprivate _useExternalConfig: boolean = false;\n\tpublic get useExternalConfig(): boolean {\n\t\treturn this._useExternalConfig;\n\t}\n\n\tpublic async loadAll(): Promise<void> {\n\t\tconst logger = await this.getLogger();\n\t\t// If the environment option for an external config dir is set\n\t\t// try to sync it and then use it\n\t\tlet syncResult: SyncExternalConfigDirResult | undefined;\n\t\tconst externalConfigDir = this.externalConfigDir;\n\t\tif (externalConfigDir) {\n\t\t\tsyncResult = await syncExternalConfigDir(\n\t\t\t\tawait this.getFS(),\n\t\t\t\texternalConfigDir,\n\t\t\t\tlogger,\n\t\t\t);\n\t\t}\n\n\t\tif (syncResult?.success) {\n\t\t\tthis._useExternalConfig = true;\n\t\t\tlogger.print(\n\t\t\t\t`Using external configuration dir ${externalConfigDir}`,\n\t\t\t);\n\t\t\tthis._configVersion = syncResult.version;\n\t\t} else {\n\t\t\tthis._useExternalConfig = false;\n\t\t\tthis._configVersion = PACKAGE_VERSION;\n\t\t}\n\t\tlogger.print(`version ${this._configVersion}`, \"info\");\n\n\t\tawait this.loadManufacturers();\n\t\tawait this.loadDeviceIndex();\n\t}\n\n\tpublic async loadManufacturers(): Promise<void> {\n\t\ttry {\n\t\t\tthis._manufacturers = await loadManufacturersInternal(\n\t\t\t\tawait this.getFS(),\n\t\t\t\tthis._useExternalConfig && this.externalConfigDir || undefined,\n\t\t\t);\n\t\t} catch (e) {\n\t\t\t// If the config file is missing or invalid, don't try to find it again\n\t\t\tif (isZWaveError(e) && e.code === ZWaveErrorCodes.Config_Invalid) {\n\t\t\t\tif (process.env.NODE_ENV !== \"test\") {\n\t\t\t\t\t(await this.getLogger()).print(\n\t\t\t\t\t\t`Could not load manufacturers config: ${e.message}`,\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!this._manufacturers) this._manufacturers = new Map();\n\t\t\t} else {\n\t\t\t\t// This is an unexpected error\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic async saveManufacturers(): Promise<void> {\n\t\tif (!this._manufacturers) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t\"The config has not been loaded yet!\",\n\t\t\t\tZWaveErrorCodes.Driver_NotReady,\n\t\t\t);\n\t\t}\n\n\t\tawait saveManufacturersInternal(\n\t\t\tawait this.getFS(),\n\t\t\tthis._manufacturers,\n\t\t);\n\t}\n\n\t/**\n\t * Looks up the name of the manufacturer with the given ID in the configuration DB\n\t * @param manufacturerId The manufacturer id to look up\n\t */\n\tpublic lookupManufacturer(manufacturerId: number): string | undefined {\n\t\tif (!this._manufacturers) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t\"The config has not been loaded yet!\",\n\t\t\t\tZWaveErrorCodes.Driver_NotReady,\n\t\t\t);\n\t\t}\n\n\t\treturn this._manufacturers.get(manufacturerId);\n\t}\n\n\t/**\n\t * Add new manufacturers to configuration DB\n\t * @param manufacturerId The manufacturer id to look up\n\t * @param manufacturerName The manufacturer name\n\t */\n\tpublic setManufacturer(\n\t\tmanufacturerId: number,\n\t\tmanufacturerName: string,\n\t): void {\n\t\tif (!this._manufacturers) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t\"The config has not been loaded yet!\",\n\t\t\t\tZWaveErrorCodes.Driver_NotReady,\n\t\t\t);\n\t\t}\n\n\t\tthis._manufacturers.set(manufacturerId, manufacturerName);\n\t}\n\n\tpublic async loadDeviceIndex(): Promise<void> {\n\t\tconst fs = await this.getFS();\n\t\tconst logger = await this.getLogger();\n\t\ttry {\n\t\t\t// The index of config files included in this package\n\t\t\tconst embeddedIndex = await loadDeviceIndexInternal(\n\t\t\t\tfs,\n\t\t\t\tlogger,\n\t\t\t\tthis._useExternalConfig && this.externalConfigDir || undefined,\n\t\t\t);\n\t\t\t// A dynamic index of the user-defined priority device config files\n\t\t\tconst priorityIndex: DeviceConfigIndex = [];\n\t\t\tif (this.deviceConfigPriorityDir) {\n\t\t\t\tif (await pathExists(fs, this.deviceConfigPriorityDir)) {\n\t\t\t\t\tpriorityIndex.push(\n\t\t\t\t\t\t...(await generatePriorityDeviceIndex(\n\t\t\t\t\t\t\tfs,\n\t\t\t\t\t\t\tthis.deviceConfigPriorityDir,\n\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t)),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tlogger.print(\n\t\t\t\t\t\t`Priority device configuration directory ${this.deviceConfigPriorityDir} not found`,\n\t\t\t\t\t\t\"warn\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Put the priority index in front, so the files get resolved first\n\t\t\tthis.index = [...priorityIndex, ...embeddedIndex];\n\t\t} catch (e) {\n\t\t\t// If the index file is missing or invalid, don't try to find it again\n\t\t\tif (\n\t\t\t\t(!isZWaveError(e) && e instanceof Error)\n\t\t\t\t|| (isZWaveError(e)\n\t\t\t\t\t&& e.code === ZWaveErrorCodes.Config_Invalid)\n\t\t\t) {\n\t\t\t\t// Fall back to no index on production systems\n\t\t\t\tif (!this.index) this.index = [];\n\t\t\t\tif (process.env.NODE_ENV !== \"test\") {\n\t\t\t\t\tlogger.print(\n\t\t\t\t\t\t`Could not load or regenerate device config index: ${e.message}`,\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t);\n\t\t\t\t\t// and don't throw\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// But fail hard in tests\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic getIndex(): DeviceConfigIndex | undefined {\n\t\treturn this.index;\n\t}\n\n\tpublic async loadFulltextDeviceIndex(): Promise<void> {\n\t\tthis.fulltextIndex = await loadFulltextDeviceIndexInternal(\n\t\t\tawait this.getFS(),\n\t\t\tawait this.getLogger(),\n\t\t);\n\t}\n\n\tpublic getFulltextIndex(): FulltextDeviceConfigIndex | undefined {\n\t\treturn this.fulltextIndex;\n\t}\n\n\t/**\n\t * Looks up the definition of a given device in the configuration DB, but does not evaluate conditional settings.\n\t * @param manufacturerId The manufacturer id of the device\n\t * @param productType The product type of the device\n\t * @param productId The product id of the device\n\t * @param firmwareVersion If known, configuration for a specific firmware version can be loaded.\n\t * If this is `undefined` or not given, the first matching file with a defined firmware range will be returned.\n\t */\n\tpublic async lookupDevicePreserveConditions(\n\t\tmanufacturerId: number,\n\t\tproductType: number,\n\t\tproductId: number,\n\t\tfirmwareVersion?: string,\n\t): Promise<ConditionalDeviceConfig | undefined> {\n\t\t// Load/regenerate the index if necessary\n\t\tif (!this.index) await this.loadDeviceIndex();\n\n\t\tconst fs = await this.getFS();\n\n\t\t// Look up the device in the index\n\t\tconst indexEntries = this.index!.filter(\n\t\t\tgetDeviceEntryPredicate(\n\t\t\t\tmanufacturerId,\n\t\t\t\tproductType,\n\t\t\t\tproductId,\n\t\t\t\tfirmwareVersion,\n\t\t\t),\n\t\t);\n\t\t// If there are multiple with overlapping firmware ranges, return the preferred one first\n\t\tconst indexEntry = indexEntries.find((e) => !!e.preferred)\n\t\t\t?? indexEntries[0];\n\n\t\tif (indexEntry) {\n\t\t\tconst devicesDir = getDevicesPaths(\n\t\t\t\tthis._useExternalConfig && this.externalConfigDir || configDir,\n\t\t\t).devicesDir;\n\t\t\tconst filePath = path.isAbsolute(indexEntry.filename)\n\t\t\t\t? indexEntry.filename\n\t\t\t\t: path.join(devicesDir, indexEntry.filename);\n\t\t\tif (!(await pathExists(fs, filePath))) return;\n\n\t\t\t// A config file is treated as am embedded one when it is located under the devices root dir\n\t\t\t// or the external config dir\n\t\t\tconst isEmbedded = !path\n\t\t\t\t.relative(devicesDir, filePath)\n\t\t\t\t.startsWith(\"..\");\n\n\t\t\t// When a device file is located in a different root directory than the embedded config files,\n\t\t\t// we use the embedded dir a fallback\n\t\t\tconst rootDir = indexEntry.rootDir ?? devicesDir;\n\t\t\tconst fallbackDirs = rootDir === devicesDir\n\t\t\t\t? undefined\n\t\t\t\t: [devicesDir];\n\n\t\t\ttry {\n\t\t\t\treturn await ConditionalDeviceConfig.from(\n\t\t\t\t\tfs,\n\t\t\t\t\tfilePath,\n\t\t\t\t\tisEmbedded,\n\t\t\t\t\t{ rootDir, fallbackDirs },\n\t\t\t\t);\n\t\t\t} catch (e) {\n\t\t\t\tif (process.env.NODE_ENV !== \"test\") {\n\t\t\t\t\t(await this.getLogger()).print(\n\t\t\t\t\t\t`Error loading device config ${filePath}: ${\n\t\t\t\t\t\t\tgetErrorMessage(\n\t\t\t\t\t\t\t\te,\n\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}`,\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Looks up the definition of a given device in the configuration DB\n\t * @param manufacturerId The manufacturer id of the device\n\t * @param productType The product type of the device\n\t * @param productId The product id of the device\n\t * @param firmwareVersion If known, configuration for a specific firmware version can be loaded.\n\t * If this is `undefined` or not given, the first matching file with a defined firmware range will be returned.\n\t * @param sdkVersion If known, the SDK version can be used in conditional settings.\n\t */\n\tpublic async lookupDevice(\n\t\tmanufacturerId: number,\n\t\tproductType: number,\n\t\tproductId: number,\n\t\tfirmwareVersion?: string,\n\t\tsdkVersion?: string,\n\t): Promise<DeviceConfig | undefined> {\n\t\tconst ret = await this.lookupDevicePreserveConditions(\n\t\t\tmanufacturerId,\n\t\t\tproductType,\n\t\t\tproductId,\n\t\t\tfirmwareVersion,\n\t\t);\n\t\treturn ret?.evaluate({\n\t\t\tmanufacturerId,\n\t\t\tproductType,\n\t\t\tproductId,\n\t\t\tfirmwareVersion,\n\t\t\tsdkVersion,\n\t\t});\n\t}\n}\n"],
|
|
4
|
+
"sourcesContent": ["import { configDir } from \"#config_dir\";\nimport {\n\ttype LogContainer,\n\tZWaveError,\n\tZWaveErrorCodes,\n\tisZWaveError,\n} from \"@zwave-js/core\";\nimport { getErrorMessage, pathExists } from \"@zwave-js/shared\";\nimport type { FileSystem } from \"@zwave-js/shared/bindings\";\nimport path from \"pathe\";\nimport { ConfigLogger } from \"./Logger.js\";\nimport {\n\ttype ManufacturersMap,\n\tloadManufacturersInternal,\n\tsaveManufacturersInternal,\n} from \"./Manufacturers.js\";\nimport { PACKAGE_VERSION } from \"./_version.js\";\nimport {\n\tConditionalDeviceConfig,\n\ttype DeviceConfig,\n\ttype DeviceConfigIndex,\n\ttype FulltextDeviceConfigIndex,\n\tgeneratePriorityDeviceIndex,\n\tgetDevicesPaths,\n\tloadDeviceIndexInternal,\n\tloadFulltextDeviceIndexInternal,\n} from \"./devices/DeviceConfig.js\";\nimport {\n\ttype SyncExternalConfigDirResult,\n\tgetDeviceEntryPredicate,\n\tgetExternalConfigDirEnvVariable,\n\tsyncExternalConfigDir,\n} from \"./utils.js\";\n\nexport interface ConfigManagerOptions {\n\tbindings?: FileSystem;\n\tlogContainer?: LogContainer;\n\tdeviceConfigPriorityDir?: string;\n\tdeviceConfigExternalDir?: string;\n}\n\nexport class ConfigManager {\n\tpublic constructor(options: ConfigManagerOptions = {}) {\n\t\tthis._fs = options.bindings;\n\t\tthis._logContainer = options.logContainer;\n\n\t\tthis.deviceConfigPriorityDir = options.deviceConfigPriorityDir;\n\t\tthis.deviceConfigExternalDir = options.deviceConfigExternalDir;\n\n\t\tthis._configVersion = PACKAGE_VERSION;\n\t}\n\n\tprivate _fs: FileSystem | undefined;\n\tprivate async getFS(): Promise<FileSystem> {\n\t\tthis._fs ??= (await import(\"#default_bindings/fs\")).fs;\n\t\treturn this._fs;\n\t}\n\n\tprivate _configVersion: string;\n\tpublic get configVersion(): string {\n\t\treturn this._configVersion;\n\t}\n\n\tprivate _logContainer: LogContainer | undefined;\n\tprivate _logger: ConfigLogger | undefined;\n\tprivate async getLogger(): Promise<ConfigLogger> {\n\t\tif (!this._logContainer) {\n\t\t\tthis._logContainer =\n\t\t\t\t// oxlint-disable-next-line typescript/ban-ts-comment\n\t\t\t\t// @ts-ignore - For some reason, VSCode does not like this import, although tsc is fine with it\n\t\t\t\t(await import(\"#default_bindings/log\")).log({\n\t\t\t\t\tenabled: false,\n\t\t\t\t});\n\t\t}\n\t\tif (!this._logger) {\n\t\t\tthis._logger = new ConfigLogger(this._logContainer);\n\t\t}\n\t\treturn this._logger;\n\t}\n\n\tprivate _manufacturers: ManufacturersMap | undefined;\n\tpublic get manufacturers(): ManufacturersMap {\n\t\tif (!this._manufacturers) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t\"The config has not been loaded yet!\",\n\t\t\t\tZWaveErrorCodes.Driver_NotReady,\n\t\t\t);\n\t\t}\n\t\treturn this._manufacturers;\n\t}\n\n\tprivate deviceConfigPriorityDir: string | undefined;\n\tprivate deviceConfigExternalDir: string | undefined;\n\tpublic get externalConfigDir(): string | undefined {\n\t\treturn this.deviceConfigExternalDir\n\t\t\t?? getExternalConfigDirEnvVariable();\n\t}\n\n\tprivate index: DeviceConfigIndex | undefined;\n\tprivate fulltextIndex: FulltextDeviceConfigIndex | undefined;\n\n\tprivate _useExternalConfig: boolean = false;\n\tpublic get useExternalConfig(): boolean {\n\t\treturn this._useExternalConfig;\n\t}\n\n\tpublic async loadAll(): Promise<void> {\n\t\tconst logger = await this.getLogger();\n\t\t// If the environment option for an external config dir is set\n\t\t// try to sync it and then use it\n\t\tlet syncResult: SyncExternalConfigDirResult | undefined;\n\t\tconst externalConfigDir = this.externalConfigDir;\n\t\tif (externalConfigDir) {\n\t\t\tsyncResult = await syncExternalConfigDir(\n\t\t\t\tawait this.getFS(),\n\t\t\t\texternalConfigDir,\n\t\t\t\tlogger,\n\t\t\t);\n\t\t}\n\n\t\tif (syncResult?.success) {\n\t\t\tthis._useExternalConfig = true;\n\t\t\tlogger.print(\n\t\t\t\t`Using external configuration dir ${externalConfigDir}`,\n\t\t\t);\n\t\t\tthis._configVersion = syncResult.version;\n\t\t} else {\n\t\t\tthis._useExternalConfig = false;\n\t\t\tthis._configVersion = PACKAGE_VERSION;\n\t\t}\n\t\tlogger.print(`version ${this._configVersion}`, \"info\");\n\n\t\tawait this.loadManufacturers();\n\t\tawait this.loadDeviceIndex();\n\t}\n\n\tpublic async loadManufacturers(): Promise<void> {\n\t\ttry {\n\t\t\tthis._manufacturers = await loadManufacturersInternal(\n\t\t\t\tawait this.getFS(),\n\t\t\t\tthis._useExternalConfig && this.externalConfigDir || undefined,\n\t\t\t);\n\t\t} catch (e) {\n\t\t\t// If the config file is missing or invalid, don't try to find it again\n\t\t\tif (isZWaveError(e) && e.code === ZWaveErrorCodes.Config_Invalid) {\n\t\t\t\tif (process.env.NODE_ENV !== \"test\") {\n\t\t\t\t\t(await this.getLogger()).print(\n\t\t\t\t\t\t`Could not load manufacturers config: ${e.message}`,\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!this._manufacturers) this._manufacturers = new Map();\n\t\t\t} else {\n\t\t\t\t// This is an unexpected error\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic async saveManufacturers(): Promise<void> {\n\t\tif (!this._manufacturers) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t\"The config has not been loaded yet!\",\n\t\t\t\tZWaveErrorCodes.Driver_NotReady,\n\t\t\t);\n\t\t}\n\n\t\tawait saveManufacturersInternal(\n\t\t\tawait this.getFS(),\n\t\t\tthis._manufacturers,\n\t\t);\n\t}\n\n\t/**\n\t * Looks up the name of the manufacturer with the given ID in the configuration DB\n\t * @param manufacturerId The manufacturer id to look up\n\t */\n\tpublic lookupManufacturer(manufacturerId: number): string | undefined {\n\t\tif (!this._manufacturers) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t\"The config has not been loaded yet!\",\n\t\t\t\tZWaveErrorCodes.Driver_NotReady,\n\t\t\t);\n\t\t}\n\n\t\treturn this._manufacturers.get(manufacturerId);\n\t}\n\n\t/**\n\t * Add new manufacturers to configuration DB\n\t * @param manufacturerId The manufacturer id to look up\n\t * @param manufacturerName The manufacturer name\n\t */\n\tpublic setManufacturer(\n\t\tmanufacturerId: number,\n\t\tmanufacturerName: string,\n\t): void {\n\t\tif (!this._manufacturers) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t\"The config has not been loaded yet!\",\n\t\t\t\tZWaveErrorCodes.Driver_NotReady,\n\t\t\t);\n\t\t}\n\n\t\tthis._manufacturers.set(manufacturerId, manufacturerName);\n\t}\n\n\tpublic async loadDeviceIndex(): Promise<void> {\n\t\tconst fs = await this.getFS();\n\t\tconst logger = await this.getLogger();\n\t\ttry {\n\t\t\t// The index of config files included in this package\n\t\t\tconst embeddedIndex = await loadDeviceIndexInternal(\n\t\t\t\tfs,\n\t\t\t\tlogger,\n\t\t\t\tthis._useExternalConfig && this.externalConfigDir || undefined,\n\t\t\t);\n\t\t\t// A dynamic index of the user-defined priority device config files\n\t\t\tconst priorityIndex: DeviceConfigIndex = [];\n\t\t\tif (this.deviceConfigPriorityDir) {\n\t\t\t\tif (await pathExists(fs, this.deviceConfigPriorityDir)) {\n\t\t\t\t\tpriorityIndex.push(\n\t\t\t\t\t\t...(await generatePriorityDeviceIndex(\n\t\t\t\t\t\t\tfs,\n\t\t\t\t\t\t\tthis.deviceConfigPriorityDir,\n\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t)),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tlogger.print(\n\t\t\t\t\t\t`Priority device configuration directory ${this.deviceConfigPriorityDir} not found`,\n\t\t\t\t\t\t\"warn\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Put the priority index in front, so the files get resolved first\n\t\t\tthis.index = [...priorityIndex, ...embeddedIndex];\n\t\t} catch (e) {\n\t\t\t// If the index file is missing or invalid, don't try to find it again\n\t\t\tif (\n\t\t\t\t(!isZWaveError(e) && e instanceof Error)\n\t\t\t\t|| (isZWaveError(e)\n\t\t\t\t\t&& e.code === ZWaveErrorCodes.Config_Invalid)\n\t\t\t) {\n\t\t\t\t// Fall back to no index on production systems\n\t\t\t\tif (!this.index) this.index = [];\n\t\t\t\tif (process.env.NODE_ENV !== \"test\") {\n\t\t\t\t\tlogger.print(\n\t\t\t\t\t\t`Could not load or regenerate device config index: ${e.message}`,\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t);\n\t\t\t\t\t// and don't throw\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// But fail hard in tests\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic getIndex(): DeviceConfigIndex | undefined {\n\t\treturn this.index;\n\t}\n\n\tpublic async loadFulltextDeviceIndex(): Promise<void> {\n\t\tthis.fulltextIndex = await loadFulltextDeviceIndexInternal(\n\t\t\tawait this.getFS(),\n\t\t\tawait this.getLogger(),\n\t\t);\n\t}\n\n\tpublic getFulltextIndex(): FulltextDeviceConfigIndex | undefined {\n\t\treturn this.fulltextIndex;\n\t}\n\n\t/**\n\t * Looks up the definition of a given device in the configuration DB, but does not evaluate conditional settings.\n\t * @param manufacturerId The manufacturer id of the device\n\t * @param productType The product type of the device\n\t * @param productId The product id of the device\n\t * @param firmwareVersion If known, configuration for a specific firmware version can be loaded.\n\t * If this is `undefined` or not given, the first matching file with a defined firmware range will be returned.\n\t */\n\tpublic async lookupDevicePreserveConditions(\n\t\tmanufacturerId: number,\n\t\tproductType: number,\n\t\tproductId: number,\n\t\tfirmwareVersion?: string,\n\t): Promise<ConditionalDeviceConfig | undefined> {\n\t\t// Load/regenerate the index if necessary\n\t\tif (!this.index) await this.loadDeviceIndex();\n\n\t\tconst fs = await this.getFS();\n\n\t\t// Look up the device in the index\n\t\tconst indexEntries = this.index!.filter(\n\t\t\tgetDeviceEntryPredicate(\n\t\t\t\tmanufacturerId,\n\t\t\t\tproductType,\n\t\t\t\tproductId,\n\t\t\t\tfirmwareVersion,\n\t\t\t),\n\t\t);\n\t\t// If there are multiple with overlapping firmware ranges, return the preferred one first\n\t\tconst indexEntry = indexEntries.find((e) => !!e.preferred)\n\t\t\t?? indexEntries[0];\n\n\t\tif (indexEntry) {\n\t\t\tconst devicesDir = getDevicesPaths(\n\t\t\t\tthis._useExternalConfig && this.externalConfigDir || configDir,\n\t\t\t).devicesDir;\n\t\t\tconst filePath = path.isAbsolute(indexEntry.filename)\n\t\t\t\t? indexEntry.filename\n\t\t\t\t: path.join(devicesDir, indexEntry.filename);\n\t\t\tif (!(await pathExists(fs, filePath))) return;\n\n\t\t\t// A config file is treated as am embedded one when it is located under the devices root dir\n\t\t\t// or the external config dir\n\t\t\tconst isEmbedded = !path\n\t\t\t\t.relative(devicesDir, filePath)\n\t\t\t\t.startsWith(\"..\");\n\n\t\t\t// When a device file is located in a different root directory than the embedded config files,\n\t\t\t// we use the embedded dir a fallback\n\t\t\tconst rootDir = indexEntry.rootDir ?? devicesDir;\n\t\t\tconst fallbackDirs = rootDir === devicesDir\n\t\t\t\t? undefined\n\t\t\t\t: [devicesDir];\n\n\t\t\ttry {\n\t\t\t\treturn await ConditionalDeviceConfig.from(\n\t\t\t\t\tfs,\n\t\t\t\t\tfilePath,\n\t\t\t\t\tisEmbedded,\n\t\t\t\t\t{ rootDir, fallbackDirs },\n\t\t\t\t);\n\t\t\t} catch (e) {\n\t\t\t\tif (process.env.NODE_ENV !== \"test\") {\n\t\t\t\t\t(await this.getLogger()).print(\n\t\t\t\t\t\t`Error loading device config ${filePath}: ${\n\t\t\t\t\t\t\tgetErrorMessage(\n\t\t\t\t\t\t\t\te,\n\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}`,\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Looks up the definition of a given device in the configuration DB\n\t * @param manufacturerId The manufacturer id of the device\n\t * @param productType The product type of the device\n\t * @param productId The product id of the device\n\t * @param firmwareVersion If known, configuration for a specific firmware version can be loaded.\n\t * If this is `undefined` or not given, the first matching file with a defined firmware range will be returned.\n\t * @param sdkVersion If known, the SDK version can be used in conditional settings.\n\t */\n\tpublic async lookupDevice(\n\t\tmanufacturerId: number,\n\t\tproductType: number,\n\t\tproductId: number,\n\t\tfirmwareVersion?: string,\n\t\tsdkVersion?: string,\n\t): Promise<DeviceConfig | undefined> {\n\t\tconst ret = await this.lookupDevicePreserveConditions(\n\t\t\tmanufacturerId,\n\t\t\tproductType,\n\t\t\tproductId,\n\t\t\tfirmwareVersion,\n\t\t);\n\t\treturn ret?.evaluate({\n\t\t\tmanufacturerId,\n\t\t\tproductType,\n\t\t\tproductId,\n\t\t\tfirmwareVersion,\n\t\t\tsdkVersion,\n\t\t});\n\t}\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["path"]
|
|
7
7
|
}
|
package/build/cjs/_version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "15.
|
|
1
|
+
export declare const PACKAGE_VERSION = "15.20.0";
|
|
2
2
|
//# sourceMappingURL=_version.d.ts.map
|
package/build/cjs/_version.js
CHANGED
|
@@ -21,7 +21,7 @@ __export(version_exports, {
|
|
|
21
21
|
PACKAGE_VERSION: () => PACKAGE_VERSION
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(version_exports);
|
|
24
|
-
const PACKAGE_VERSION = "15.
|
|
24
|
+
const PACKAGE_VERSION = "15.20.0";
|
|
25
25
|
// Annotate the CommonJS export names for ESM import in node:
|
|
26
26
|
0 && (module.exports = {
|
|
27
27
|
PACKAGE_VERSION
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/_version.ts"],
|
|
4
|
-
"sourcesContent": ["// This file is auto-generated by the codegen maintenance script\nexport const PACKAGE_VERSION = \"15.
|
|
4
|
+
"sourcesContent": ["// This file is auto-generated by the codegen maintenance script\nexport const PACKAGE_VERSION = \"15.20.0\";\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;;;;;AACO,MAAM,kBAAkB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -73,6 +73,7 @@ export declare class ConditionalDeviceConfig {
|
|
|
73
73
|
readonly isEmbedded: boolean;
|
|
74
74
|
evaluate(deviceId?: DeviceID): DeviceConfig;
|
|
75
75
|
}
|
|
76
|
+
export type DeviceConfigHashVersion = 0 | 1 | 2 | 3;
|
|
76
77
|
export declare class DeviceConfig {
|
|
77
78
|
static from(fs: ReadFileSystemInfo & ReadFile, filename: string, isEmbedded: boolean, options: {
|
|
78
79
|
rootDir: string;
|
|
@@ -117,8 +118,8 @@ export declare class DeviceConfig {
|
|
|
117
118
|
/**
|
|
118
119
|
* Returns a hash code that can be used to check whether a device config has changed enough to require a re-interview.
|
|
119
120
|
*/
|
|
120
|
-
getHash(version?:
|
|
121
|
-
static get maxHashVersion():
|
|
121
|
+
getHash(version?: DeviceConfigHashVersion): Promise<BytesView>;
|
|
122
|
+
static get maxHashVersion(): 3;
|
|
122
123
|
static areHashesEqual(hash: BytesView, other: BytesView): boolean;
|
|
123
124
|
}
|
|
124
125
|
//# sourceMappingURL=DeviceConfig.d.ts.map
|
|
@@ -552,7 +552,7 @@ class DeviceConfig {
|
|
|
552
552
|
hashable.compat = c;
|
|
553
553
|
}
|
|
554
554
|
}
|
|
555
|
-
if (version
|
|
555
|
+
if (version >= 2) {
|
|
556
556
|
for (const ep of Object.values(hashable.endpoints ?? {})) {
|
|
557
557
|
for (const param of ep.paramInformation ?? []) {
|
|
558
558
|
delete param.label;
|
|
@@ -563,6 +563,20 @@ class DeviceConfig {
|
|
|
563
563
|
}
|
|
564
564
|
}
|
|
565
565
|
}
|
|
566
|
+
if (version < 3) {
|
|
567
|
+
for (const ep of Object.values(hashable.endpoints ?? {})) {
|
|
568
|
+
for (const param of ep.paramInformation ?? []) {
|
|
569
|
+
if ((0, import_typeguards.isArray)(param.allowed) && param.allowed.length === 1 && (0, import_typeguards.isObject)(param.allowed[0])) {
|
|
570
|
+
const allowed = param.allowed[0];
|
|
571
|
+
if (typeof allowed.from === "number" && typeof allowed.to === "number" && (allowed.step == void 0 || allowed.step === 1)) {
|
|
572
|
+
param.minValue = allowed.from;
|
|
573
|
+
param.maxValue = allowed.to;
|
|
574
|
+
delete param.allowed;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
566
580
|
hashable = sortObject(hashable);
|
|
567
581
|
return hashable;
|
|
568
582
|
}
|
|
@@ -589,7 +603,7 @@ class DeviceConfig {
|
|
|
589
603
|
return import_shared.Bytes.concat([prefixBytes, hash]);
|
|
590
604
|
}
|
|
591
605
|
static get maxHashVersion() {
|
|
592
|
-
return
|
|
606
|
+
return 3;
|
|
593
607
|
}
|
|
594
608
|
static areHashesEqual(hash, other) {
|
|
595
609
|
const parsedHash = parseHash(hash);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/devices/DeviceConfig.ts"],
|
|
4
|
-
"sourcesContent": ["import { configDir } from \"#config_dir\";\nimport {\n\tZWaveError,\n\tZWaveErrorCodes,\n\tdeflateSync,\n\tdigest,\n} from \"@zwave-js/core\";\nimport {\n\tBytes,\n\ttype BytesView,\n\ttype JSONObject,\n\tcloneDeep,\n\tenumFilesRecursive,\n\tformatId,\n\tgetenv,\n\tnum2hex,\n\tpadVersion,\n\tpathExists,\n\tpick,\n\treadTextFile,\n\tstringify,\n\twriteTextFile,\n} from \"@zwave-js/shared\";\nimport type {\n\tReadFile,\n\tReadFileSystemInfo,\n\tWriteFile,\n} from \"@zwave-js/shared/bindings\";\nimport { isArray, isObject } from \"alcalzone-shared/typeguards\";\nimport JSON5 from \"json5\";\nimport path from \"pathe\";\nimport semverGt from \"semver/functions/gt.js\";\nimport { clearTemplateCache, readJsonWithTemplate } from \"../JsonTemplate.js\";\nimport type { ConfigLogger } from \"../Logger.js\";\nimport { hexKeyRegex4Digits, throwInvalidConfig } from \"../utils_safe.js\";\nimport {\n\ttype AssociationConfig,\n\tConditionalAssociationConfig,\n} from \"./AssociationConfig.js\";\nimport { type CompatConfig, ConditionalCompatConfig } from \"./CompatConfig.js\";\nimport { evaluateDeep, validateCondition } from \"./ConditionalItem.js\";\nimport {\n\ttype ConditionalPrimitive,\n\tparseConditionalPrimitive,\n} from \"./ConditionalPrimitive.js\";\nimport {\n\tConditionalDeviceMetadata,\n\ttype DeviceMetadata,\n} from \"./DeviceMetadata.js\";\nimport {\n\tConditionalEndpointConfig,\n\ttype EndpointConfig,\n} from \"./EndpointConfig.js\";\nimport {\n\ttype ConditionalParamInfoMap,\n\ttype ParamInfoMap,\n\ttype ParamInformation,\n\tparseConditionalParamInformationMap,\n} from \"./ParamInformation.js\";\nimport { ConditionalSceneConfig, type SceneConfig } from \"./SceneConfig.js\";\nimport type { DeviceID, FirmwareVersionRange } from \"./shared.js\";\n\nexport interface DeviceConfigIndexEntry {\n\tmanufacturerId: string;\n\tproductType: string;\n\tproductId: string;\n\tfirmwareVersion: FirmwareVersionRange;\n\tpreferred?: true;\n\trootDir?: string;\n\tfilename: string;\n}\n\nexport interface FulltextDeviceConfigIndexEntry {\n\tmanufacturerId: string;\n\tmanufacturer: string;\n\tlabel: string;\n\tdescription: string;\n\tproductType: string;\n\tproductId: string;\n\tfirmwareVersion: FirmwareVersionRange;\n\tpreferred?: true;\n\trootDir?: string;\n\tfilename: string;\n}\n\nexport const embeddedDevicesDir = path.join(configDir, \"devices\");\nconst fulltextIndexPath = path.join(embeddedDevicesDir, \"fulltext_index.json\");\n\nexport function getDevicesPaths(configDir: string): {\n\tdevicesDir: string;\n\tindexPath: string;\n} {\n\tconst devicesDir = path.join(configDir, \"devices\");\n\tconst indexPath = path.join(devicesDir, \"index.json\");\n\treturn { devicesDir, indexPath };\n}\n\nexport type DeviceConfigIndex = DeviceConfigIndexEntry[];\nexport type FulltextDeviceConfigIndex = FulltextDeviceConfigIndexEntry[];\n\nasync function hasChangedDeviceFiles(\n\tfs: ReadFileSystemInfo,\n\tdevicesRoot: string,\n\tdir: string,\n\tlastChange: Date,\n): Promise<boolean> {\n\t// Check if there are any files BUT index.json that were changed\n\t// or directories that were modified\n\tconst filesAndDirs = await fs.readDir(dir);\n\tfor (const f of filesAndDirs) {\n\t\tconst fullPath = path.join(dir, f);\n\n\t\tconst stat = await fs.stat(fullPath);\n\t\tif (\n\t\t\t(dir !== devicesRoot || f !== \"index.json\")\n\t\t\t&& (stat.isFile() || stat.isDirectory())\n\t\t\t&& stat.mtime > lastChange\n\t\t) {\n\t\t\treturn true;\n\t\t} else if (stat.isDirectory()) {\n\t\t\t// we need to go deeper!\n\t\t\tif (\n\t\t\t\tawait hasChangedDeviceFiles(\n\t\t\t\t\tfs,\n\t\t\t\t\tdevicesRoot,\n\t\t\t\t\tfullPath,\n\t\t\t\t\tlastChange,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Read all device config files from a given directory and return them as index entries.\n * Does not update the index itself.\n */\nasync function generateIndex<T extends Record<string, unknown>>(\n\tfs: ReadFileSystemInfo & ReadFile,\n\tdevicesDir: string,\n\tisEmbedded: boolean,\n\textractIndexEntries: (config: DeviceConfig) => T[],\n\tlogger?: ConfigLogger,\n): Promise<(T & { filename: string; rootDir?: string })[]> {\n\tconst index: (T & { filename: string; rootDir?: string })[] = [];\n\n\tclearTemplateCache();\n\tconst configFiles = await enumFilesRecursive(\n\t\tfs,\n\t\tdevicesDir,\n\t\t(file) =>\n\t\t\tfile.endsWith(\".json\")\n\t\t\t&& !file.endsWith(\"index.json\")\n\t\t\t&& !file.includes(\"/templates/\")\n\t\t\t&& !file.includes(\"\\\\templates\\\\\"),\n\t);\n\n\t// Add the embedded devices dir as a fallback if necessary\n\tconst fallbackDirs = devicesDir !== embeddedDevicesDir\n\t\t? [embeddedDevicesDir]\n\t\t: undefined;\n\n\tfor (const file of configFiles) {\n\t\tconst relativePath = path\n\t\t\t.relative(devicesDir, file)\n\t\t\t.replaceAll(\"\\\\\", \"/\");\n\t\t// Try parsing the file\n\t\ttry {\n\t\t\tconst config = await DeviceConfig.from(\n\t\t\t\tfs,\n\t\t\t\tfile,\n\t\t\t\tisEmbedded,\n\t\t\t\t{\n\t\t\t\t\trootDir: devicesDir,\n\t\t\t\t\tfallbackDirs,\n\t\t\t\t\trelative: true,\n\t\t\t\t},\n\t\t\t);\n\t\t\t// Add the file to the index\n\t\t\tindex.push(\n\t\t\t\t...extractIndexEntries(config).map((entry) => {\n\t\t\t\t\tconst ret: T & { filename: string; rootDir?: string } = {\n\t\t\t\t\t\t...entry,\n\t\t\t\t\t\tfilename: relativePath,\n\t\t\t\t\t};\n\t\t\t\t\t// Only add the root dir to the index if necessary\n\t\t\t\t\tif (devicesDir !== embeddedDevicesDir) {\n\t\t\t\t\t\tret.rootDir = devicesDir;\n\t\t\t\t\t}\n\t\t\t\t\treturn ret;\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (e) {\n\t\t\tconst message = `Error parsing config file ${relativePath}: ${\n\t\t\t\t(e as Error).message\n\t\t\t}`;\n\t\t\t// Crash hard during tests, just print an error when in production systems.\n\t\t\t// A user could have changed a config file\n\t\t\tif (process.env.NODE_ENV === \"test\" || !!getenv(\"CI\")) {\n\t\t\t\tthrow new ZWaveError(message, ZWaveErrorCodes.Config_Invalid);\n\t\t\t} else {\n\t\t\t\tlogger?.print(message, \"error\");\n\t\t\t}\n\t\t}\n\t}\n\n\treturn index;\n}\n\nasync function loadDeviceIndexShared<T extends Record<string, unknown>>(\n\tfs: ReadFileSystemInfo & ReadFile & WriteFile,\n\tdevicesDir: string,\n\tindexPath: string,\n\textractIndexEntries: (config: DeviceConfig) => T[],\n\tlogger?: ConfigLogger,\n): Promise<(T & { filename: string })[]> {\n\t// The index file needs to be regenerated if it does not exist\n\tlet needsUpdate = !(await pathExists(fs, indexPath));\n\tlet index: (T & { filename: string })[] | undefined;\n\tlet mtimeIndex: Date | undefined;\n\t// ...or if cannot be parsed\n\tif (!needsUpdate) {\n\t\ttry {\n\t\t\tconst fileContents = await readTextFile(fs, indexPath, \"utf8\");\n\t\t\tindex = JSON5.parse(fileContents);\n\t\t\tmtimeIndex = (await fs.stat(indexPath)).mtime;\n\t\t} catch {\n\t\t\tlogger?.print(\n\t\t\t\t\"Error while parsing index file - regenerating...\",\n\t\t\t\t\"warn\",\n\t\t\t);\n\t\t\tneedsUpdate = true;\n\t\t} finally {\n\t\t\tif (!index) {\n\t\t\t\tlogger?.print(\n\t\t\t\t\t\"Index file was malformed - regenerating...\",\n\t\t\t\t\t\"warn\",\n\t\t\t\t);\n\t\t\t\tneedsUpdate = true;\n\t\t\t}\n\t\t}\n\t}\n\n\t// ...or if there were any changes in the file system\n\tif (!needsUpdate) {\n\t\tneedsUpdate = await hasChangedDeviceFiles(\n\t\t\tfs,\n\t\t\tdevicesDir,\n\t\t\tdevicesDir,\n\t\t\tmtimeIndex!,\n\t\t);\n\t\tif (needsUpdate) {\n\t\t\tlogger?.print(\n\t\t\t\t\"Device configuration files on disk changed - regenerating index...\",\n\t\t\t\t\"verbose\",\n\t\t\t);\n\t\t}\n\t}\n\n\tif (needsUpdate) {\n\t\t// Read all files from disk and generate an index\n\t\tindex = await generateIndex(\n\t\t\tfs,\n\t\t\tdevicesDir,\n\t\t\ttrue,\n\t\t\textractIndexEntries,\n\t\t\tlogger,\n\t\t);\n\t\t// Save the index to disk\n\t\ttry {\n\t\t\tawait writeTextFile(\n\t\t\t\tfs,\n\t\t\t\tpath.join(indexPath),\n\t\t\t\t`// This file is auto-generated. DO NOT edit it by hand if you don't know what you're doing!\"\n${stringify(index, \"\\t\")}\n`,\n\t\t\t\t\"utf8\",\n\t\t\t);\n\t\t\tlogger?.print(\"Device index regenerated\", \"verbose\");\n\t\t} catch (e) {\n\t\t\tlogger?.print(\n\t\t\t\t`Writing the device index to disk failed: ${\n\t\t\t\t\t(e as Error).message\n\t\t\t\t}`,\n\t\t\t\t\"error\",\n\t\t\t);\n\t\t}\n\t}\n\n\treturn index!;\n}\n\n/**\n * @internal\n * Loads the index file to quickly access the device configs.\n * Transparently handles updating the index if necessary\n */\nexport async function generatePriorityDeviceIndex(\n\tfs: ReadFileSystemInfo & ReadFile,\n\tdeviceConfigPriorityDir: string,\n\tlogger?: ConfigLogger,\n): Promise<DeviceConfigIndex> {\n\treturn (\n\t\tawait generateIndex(\n\t\t\tfs,\n\t\t\tdeviceConfigPriorityDir,\n\t\t\tfalse,\n\t\t\t(config) =>\n\t\t\t\tconfig.devices.map((dev) => ({\n\t\t\t\t\tmanufacturerId: formatId(\n\t\t\t\t\t\tconfig.manufacturerId.toString(16),\n\t\t\t\t\t),\n\t\t\t\t\tmanufacturer: config.manufacturer,\n\t\t\t\t\tlabel: config.label,\n\t\t\t\t\tproductType: formatId(dev.productType),\n\t\t\t\t\tproductId: formatId(dev.productId),\n\t\t\t\t\tfirmwareVersion: config.firmwareVersion,\n\t\t\t\t\t...(config.preferred ? { preferred: true as const } : {}),\n\t\t\t\t\trootDir: deviceConfigPriorityDir,\n\t\t\t\t})),\n\t\t\tlogger,\n\t\t)\n\t).map(({ filename, ...entry }) => ({\n\t\t...entry,\n\t\t// The generated index makes the filenames relative to the given directory\n\t\t// but we need them to be absolute\n\t\tfilename: path.join(deviceConfigPriorityDir, filename),\n\t}));\n}\n\n/**\n * @internal\n * Loads the index file to quickly access the device configs.\n * Transparently handles updating the index if necessary\n */\nexport async function loadDeviceIndexInternal(\n\tfs: ReadFileSystemInfo & ReadFile & WriteFile,\n\tlogger?: ConfigLogger,\n\texternalConfigDir?: string,\n): Promise<DeviceConfigIndex> {\n\tconst { devicesDir, indexPath } = getDevicesPaths(\n\t\texternalConfigDir || configDir,\n\t);\n\n\treturn loadDeviceIndexShared(\n\t\tfs,\n\t\tdevicesDir,\n\t\tindexPath,\n\t\t(config) =>\n\t\t\tconfig.devices.map((dev) => ({\n\t\t\t\tmanufacturerId: formatId(config.manufacturerId.toString(16)),\n\t\t\t\tmanufacturer: config.manufacturer,\n\t\t\t\tlabel: config.label,\n\t\t\t\tproductType: formatId(dev.productType),\n\t\t\t\tproductId: formatId(dev.productId),\n\t\t\t\tfirmwareVersion: config.firmwareVersion,\n\t\t\t\t...(config.preferred ? { preferred: true as const } : {}),\n\t\t\t})),\n\t\tlogger,\n\t);\n}\n\n/**\n * @internal\n * Loads the full text index file to quickly search the device configs.\n * Transparently handles updating the index if necessary\n */\nexport async function loadFulltextDeviceIndexInternal(\n\tfs: ReadFileSystemInfo & ReadFile & WriteFile,\n\tlogger?: ConfigLogger,\n): Promise<FulltextDeviceConfigIndex> {\n\t// This method is not meant to operate with the external device index!\n\treturn loadDeviceIndexShared(\n\t\tfs,\n\t\tembeddedDevicesDir,\n\t\tfulltextIndexPath,\n\t\t(config) =>\n\t\t\tconfig.devices.map((dev) => ({\n\t\t\t\tmanufacturerId: formatId(config.manufacturerId.toString(16)),\n\t\t\t\tmanufacturer: config.manufacturer,\n\t\t\t\tlabel: config.label,\n\t\t\t\tdescription: config.description,\n\t\t\t\tproductType: formatId(dev.productType),\n\t\t\t\tproductId: formatId(dev.productId),\n\t\t\t\tfirmwareVersion: config.firmwareVersion,\n\t\t\t\t...(config.preferred ? { preferred: true as const } : {}),\n\t\t\t\trootDir: embeddedDevicesDir,\n\t\t\t})),\n\t\tlogger,\n\t);\n}\n\nfunction isHexKeyWith4Digits(val: any): val is string {\n\treturn typeof val === \"string\" && hexKeyRegex4Digits.test(val);\n}\n\nconst firmwareVersionRegex = /^\\d{1,3}\\.\\d{1,3}(\\.\\d{1,3})?$/;\nfunction isFirmwareVersion(val: any): val is string {\n\treturn (\n\t\ttypeof val === \"string\"\n\t\t&& firmwareVersionRegex.test(val)\n\t\t&& val\n\t\t\t.split(\".\")\n\t\t\t.map((str) => parseInt(str, 10))\n\t\t\t.every((num) => num >= 0 && num <= 255)\n\t);\n}\n\nconst deflateDict = Bytes.from(\n\t// Substrings appearing in the device config files in descending order of frequency\n\t// except for very short ones like 0, 1, ...\n\t// WARNING: THIS MUST NOT BE CHANGED! Doing so breaks decompressing stored hashes.\n\t[\n\t\t`\"parameterNumber\":`,\n\t\t`255`,\n\t\t`\"value\":`,\n\t\t`\"defaultValue\":`,\n\t\t`\"valueSize\":`,\n\t\t`\"maxValue\":`,\n\t\t`\"minValue\":`,\n\t\t`\"options\":`,\n\t\t`true`,\n\t\t`false`,\n\t\t`\"allowManualEntry\":`,\n\t\t`\"maxNodes\":`,\n\t\t`100`,\n\t\t`\"unsigned\":`,\n\t\t`\"paramInformation\":`,\n\t\t`\"isLifeline\":`,\n\t\t`\"seconds\"`,\n\t\t`99`,\n\t\t`127`,\n\t\t`\"%\"`,\n\t\t`65535`,\n\t\t`32767`,\n\t\t`\"minutes\"`,\n\t\t`\"endpoints\":`,\n\t\t`\"hours\"`,\n\t\t`\"multiChannel\":`,\n\t]\n\t\t.join(\"\"),\n\t\"utf8\",\n);\n\n/** This class represents a device config entry whose conditional settings have not been evaluated yet */\nexport class ConditionalDeviceConfig {\n\tpublic static async from(\n\t\tfs: ReadFileSystemInfo & ReadFile,\n\t\tfilename: string,\n\t\tisEmbedded: boolean,\n\t\toptions: {\n\t\t\trootDir: string;\n\t\t\tfallbackDirs?: string[];\n\t\t\trelative?: boolean;\n\t\t},\n\t): Promise<ConditionalDeviceConfig> {\n\t\tconst { relative, rootDir } = options;\n\n\t\tconst relativePath = relative\n\t\t\t? path.relative(rootDir, filename).replaceAll(\"\\\\\", \"/\")\n\t\t\t: filename;\n\t\tconst json = await readJsonWithTemplate(\n\t\t\tfs,\n\t\t\tfilename,\n\t\t\t[\n\t\t\t\toptions.rootDir,\n\t\t\t\t...(options.fallbackDirs ?? []),\n\t\t\t],\n\t\t);\n\t\treturn new ConditionalDeviceConfig(relativePath, isEmbedded, json);\n\t}\n\n\tpublic constructor(\n\t\tfilename: string,\n\t\tisEmbedded: boolean,\n\t\tdefinition: JSONObject,\n\t) {\n\t\tthis.filename = filename;\n\t\tthis.isEmbedded = isEmbedded;\n\n\t\tif (!isHexKeyWith4Digits(definition.manufacturerId)) {\n\t\t\tthrowInvalidConfig(\n\t\t\t\t`device`,\n\t\t\t\t`packages/config/config/devices/${filename}:\nmanufacturer id must be a lowercase hexadecimal number with 4 digits`,\n\t\t\t);\n\t\t}\n\t\tthis.manufacturerId = parseInt(definition.manufacturerId, 16);\n\n\t\tfor (const prop of [\"manufacturer\", \"label\", \"description\"] as const) {\n\t\t\tthis[prop] = parseConditionalPrimitive(\n\t\t\t\tfilename,\n\t\t\t\t\"string\",\n\t\t\t\tprop,\n\t\t\t\tdefinition[prop],\n\t\t\t);\n\t\t}\n\n\t\tif (\n\t\t\t!isArray(definition.devices)\n\t\t\t|| !(definition.devices as any[]).every(\n\t\t\t\t(dev: unknown) =>\n\t\t\t\t\tisObject(dev)\n\t\t\t\t\t&& isHexKeyWith4Digits(dev.productType)\n\t\t\t\t\t&& isHexKeyWith4Digits(dev.productId),\n\t\t\t)\n\t\t) {\n\t\t\tthrowInvalidConfig(\n\t\t\t\t`device`,\n\t\t\t\t`packages/config/config/devices/${filename}:\ndevices is malformed (not an object or type/id that is not a lowercase 4-digit hex key)`,\n\t\t\t);\n\t\t}\n\t\tthis.devices = (definition.devices as any[]).map(\n\t\t\t({ productType, productId }) => ({\n\t\t\t\tproductType: parseInt(productType, 16),\n\t\t\t\tproductId: parseInt(productId, 16),\n\t\t\t}),\n\t\t);\n\n\t\tif (\n\t\t\t!isObject(definition.firmwareVersion)\n\t\t\t|| !isFirmwareVersion(definition.firmwareVersion.min)\n\t\t\t|| !isFirmwareVersion(definition.firmwareVersion.max)\n\t\t) {\n\t\t\tthrowInvalidConfig(\n\t\t\t\t`device`,\n\t\t\t\t`packages/config/config/devices/${filename}:\nfirmwareVersion is malformed or invalid. Must be x.y or x.y.z where x, y, and z are integers between 0 and 255`,\n\t\t\t);\n\t\t} else {\n\t\t\tconst { min, max } = definition.firmwareVersion;\n\t\t\tif (semverGt(padVersion(min), padVersion(max))) {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\nfirmwareVersion.min ${min} must not be greater than firmwareVersion.max ${max}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.firmwareVersion = { min, max };\n\t\t}\n\n\t\tif (\n\t\t\tdefinition.preferred != undefined\n\t\t\t&& definition.preferred !== true\n\t\t) {\n\t\t\tthrowInvalidConfig(\n\t\t\t\t`device`,\n\t\t\t\t`packages/config/config/devices/${filename}:\npreferred must be true or omitted`,\n\t\t\t);\n\t\t}\n\t\tthis.preferred = !!definition.preferred;\n\n\t\tif (definition.endpoints != undefined) {\n\t\t\tconst endpoints = new Map<number, ConditionalEndpointConfig>();\n\t\t\tif (!isObject(definition.endpoints)) {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\nendpoints is not an object`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tfor (const [key, ep] of Object.entries(definition.endpoints)) {\n\t\t\t\tif (!/^\\d+$/.test(key)) {\n\t\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t\t`device`,\n\t\t\t\t\t\t`packages/config/config/devices/${filename}:\nfound non-numeric endpoint index \"${key}\" in endpoints`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst epIndex = parseInt(key, 10);\n\t\t\t\tendpoints.set(\n\t\t\t\t\tepIndex,\n\t\t\t\t\tnew ConditionalEndpointConfig(this, epIndex, ep as any),\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.endpoints = endpoints;\n\t\t}\n\n\t\tif (definition.associations != undefined) {\n\t\t\tconst associations = new Map<\n\t\t\t\tnumber,\n\t\t\t\tConditionalAssociationConfig\n\t\t\t>();\n\t\t\tif (!isObject(definition.associations)) {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\nassociations is not an object`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tfor (\n\t\t\t\tconst [key, assocDefinition] of Object.entries(\n\t\t\t\t\tdefinition.associations,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tif (!/^[1-9][0-9]*$/.test(key)) {\n\t\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t\t`device`,\n\t\t\t\t\t\t`packages/config/config/devices/${filename}:\nfound non-numeric group id \"${key}\" in associations`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst keyNum = parseInt(key, 10);\n\t\t\t\tassociations.set(\n\t\t\t\t\tkeyNum,\n\t\t\t\t\tnew ConditionalAssociationConfig(\n\t\t\t\t\t\tfilename,\n\t\t\t\t\t\tkeyNum,\n\t\t\t\t\t\tassocDefinition as any,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.associations = associations;\n\t\t}\n\n\t\tif (definition.paramInformation != undefined) {\n\t\t\tthis.paramInformation = parseConditionalParamInformationMap(\n\t\t\t\tdefinition,\n\t\t\t\tthis,\n\t\t\t);\n\t\t}\n\n\t\tif (definition.proprietary != undefined) {\n\t\t\tif (!isObject(definition.proprietary)) {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\nproprietary is not an object`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.proprietary = definition.proprietary;\n\t\t}\n\n\t\tif (definition.compat != undefined) {\n\t\t\tif (\n\t\t\t\tisArray(definition.compat)\n\t\t\t\t&& definition.compat.every((item: any) => isObject(item))\n\t\t\t) {\n\t\t\t\t// Make sure all conditions are valid\n\t\t\t\tfor (const entry of definition.compat) {\n\t\t\t\t\tvalidateCondition(\n\t\t\t\t\t\tfilename,\n\t\t\t\t\t\tentry,\n\t\t\t\t\t\t`At least one entry of compat contains an`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tthis.compat = definition.compat.map(\n\t\t\t\t\t(item: any) => new ConditionalCompatConfig(filename, item),\n\t\t\t\t);\n\t\t\t} else if (isObject(definition.compat)) {\n\t\t\t\tthis.compat = new ConditionalCompatConfig(\n\t\t\t\t\tfilename,\n\t\t\t\t\tdefinition.compat,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\ncompat must be an object or any array of conditional objects`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (definition.metadata != undefined) {\n\t\t\tif (!isObject(definition.metadata)) {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\nmetadata is not an object`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.metadata = new ConditionalDeviceMetadata(\n\t\t\t\tfilename,\n\t\t\t\tdefinition.metadata,\n\t\t\t);\n\t\t}\n\n\t\tif (definition.scenes != undefined) {\n\t\t\tconst scenes = new Map<\n\t\t\t\tnumber,\n\t\t\t\tConditionalSceneConfig\n\t\t\t>();\n\t\t\tif (!isObject(definition.scenes)) {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\nscenes is not an object`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tfor (\n\t\t\t\tconst [key, sceneDefinition] of Object.entries(\n\t\t\t\t\tdefinition.scenes,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tif (!/^[1-9][0-9]*$/.test(key)) {\n\t\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t\t`device`,\n\t\t\t\t\t\t`packages/config/config/devices/${filename}:\ninvalid scene id \"${key}\" in scenes - must be a positive integer (1-255)`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst keyNum = parseInt(key, 10);\n\t\t\t\tif (keyNum < 1 || keyNum > 255) {\n\t\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t\t`device`,\n\t\t\t\t\t\t`packages/config/config/devices/${filename}:\nscene number ${keyNum} must be between 1 and 255`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tscenes.set(\n\t\t\t\t\tkeyNum,\n\t\t\t\t\tnew ConditionalSceneConfig(\n\t\t\t\t\t\tfilename,\n\t\t\t\t\t\tkeyNum,\n\t\t\t\t\t\tsceneDefinition as any,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.scenes = scenes;\n\t\t}\n\t}\n\n\tpublic readonly filename: string;\n\n\tpublic readonly manufacturer!: ConditionalPrimitive<string>;\n\tpublic readonly manufacturerId: number;\n\tpublic readonly label!: ConditionalPrimitive<string>;\n\tpublic readonly description!: ConditionalPrimitive<string>;\n\tpublic readonly devices: readonly {\n\t\tproductType: number;\n\t\tproductId: number;\n\t}[];\n\tpublic readonly firmwareVersion: FirmwareVersionRange;\n\t/** Mark this configuration as preferred over other config files with an overlapping firmware range */\n\tpublic readonly preferred: boolean;\n\tpublic readonly endpoints?: ReadonlyMap<number, ConditionalEndpointConfig>;\n\tpublic readonly associations?: ReadonlyMap<\n\t\tnumber,\n\t\tConditionalAssociationConfig\n\t>;\n\tpublic readonly scenes?: ReadonlyMap<number, ConditionalSceneConfig>;\n\tpublic readonly paramInformation?: ConditionalParamInfoMap;\n\t/**\n\t * Contains manufacturer-specific support information for the\n\t * ManufacturerProprietary CC\n\t */\n\tpublic readonly proprietary?: Record<string, unknown>;\n\t/** Contains compatibility options */\n\tpublic readonly compat?:\n\t\t| ConditionalCompatConfig\n\t\t| ConditionalCompatConfig[];\n\t/** Contains instructions and other metadata for the device */\n\tpublic readonly metadata?: ConditionalDeviceMetadata;\n\n\t/** Whether this is an embedded configuration or not */\n\tpublic readonly isEmbedded: boolean;\n\n\tpublic evaluate(deviceId?: DeviceID): DeviceConfig {\n\t\treturn new DeviceConfig(\n\t\t\tthis.filename,\n\t\t\tthis.isEmbedded,\n\t\t\tevaluateDeep(this.manufacturer, deviceId),\n\t\t\tthis.manufacturerId,\n\t\t\tevaluateDeep(this.label, deviceId),\n\t\t\tevaluateDeep(this.description, deviceId),\n\t\t\tthis.devices,\n\t\t\tthis.firmwareVersion,\n\t\t\tthis.preferred,\n\t\t\tevaluateDeep(this.endpoints, deviceId),\n\t\t\tevaluateDeep(this.associations, deviceId),\n\t\t\tevaluateDeep(this.scenes, deviceId),\n\t\t\tevaluateDeep(this.paramInformation, deviceId),\n\t\t\tthis.proprietary,\n\t\t\tevaluateDeep(this.compat, deviceId),\n\t\t\tevaluateDeep(this.metadata, deviceId),\n\t\t);\n\t}\n}\n\nexport class DeviceConfig {\n\tpublic static async from(\n\t\tfs: ReadFileSystemInfo & ReadFile,\n\t\tfilename: string,\n\t\tisEmbedded: boolean,\n\t\toptions: {\n\t\t\trootDir: string;\n\t\t\tfallbackDirs?: string[];\n\t\t\trelative?: boolean;\n\t\t\tdeviceId?: DeviceID;\n\t\t},\n\t): Promise<DeviceConfig> {\n\t\tconst ret = await ConditionalDeviceConfig.from(\n\t\t\tfs,\n\t\t\tfilename,\n\t\t\tisEmbedded,\n\t\t\toptions,\n\t\t);\n\t\treturn ret.evaluate(options.deviceId);\n\t}\n\n\tpublic constructor(\n\t\tfilename: string,\n\t\tisEmbedded: boolean,\n\t\tmanufacturer: string,\n\t\tmanufacturerId: number,\n\t\tlabel: string,\n\t\tdescription: string,\n\t\tdevices: readonly {\n\t\t\tproductType: number;\n\t\t\tproductId: number;\n\t\t}[],\n\t\tfirmwareVersion: FirmwareVersionRange,\n\t\tpreferred: boolean,\n\t\tendpoints?: ReadonlyMap<number, EndpointConfig>,\n\t\tassociations?: ReadonlyMap<number, AssociationConfig>,\n\t\tscenes?: ReadonlyMap<number, SceneConfig>,\n\t\tparamInformation?: ParamInfoMap,\n\t\tproprietary?: Record<string, unknown>,\n\t\tcompat?: CompatConfig,\n\t\tmetadata?: DeviceMetadata,\n\t) {\n\t\tthis.filename = filename;\n\t\tthis.isEmbedded = isEmbedded;\n\t\tthis.manufacturer = manufacturer;\n\t\tthis.manufacturerId = manufacturerId;\n\t\tthis.label = label;\n\t\tthis.description = description;\n\t\tthis.devices = devices;\n\t\tthis.firmwareVersion = firmwareVersion;\n\t\tthis.preferred = preferred;\n\t\tthis.endpoints = endpoints;\n\t\tthis.associations = associations;\n\t\tthis.scenes = scenes;\n\t\tthis.paramInformation = paramInformation;\n\t\tthis.proprietary = proprietary;\n\t\tthis.compat = compat;\n\t\tthis.metadata = metadata;\n\t}\n\n\tpublic readonly filename: string;\n\t/** Whether this is an embedded configuration or not */\n\tpublic readonly isEmbedded: boolean;\n\tpublic readonly manufacturer: string;\n\tpublic readonly manufacturerId: number;\n\tpublic readonly label: string;\n\tpublic readonly description: string;\n\tpublic readonly devices: readonly {\n\t\tproductType: number;\n\t\tproductId: number;\n\t}[];\n\tpublic readonly firmwareVersion: FirmwareVersionRange;\n\t/** Mark this configuration as preferred over other config files with an overlapping firmware range */\n\tpublic readonly preferred: boolean;\n\tpublic readonly endpoints?: ReadonlyMap<number, EndpointConfig>;\n\tpublic readonly associations?: ReadonlyMap<number, AssociationConfig>;\n\tpublic readonly scenes?: ReadonlyMap<number, SceneConfig>;\n\tpublic readonly paramInformation?: ParamInfoMap;\n\t/**\n\t * Contains manufacturer-specific support information for the\n\t * ManufacturerProprietary CC\n\t */\n\tpublic readonly proprietary?: Record<string, unknown>;\n\t/** Contains compatibility options */\n\tpublic readonly compat?: CompatConfig;\n\t/** Contains instructions and other metadata for the device */\n\tpublic readonly metadata?: DeviceMetadata;\n\n\t/** Returns the association config for a given endpoint */\n\tpublic getAssociationConfigForEndpoint(\n\t\tendpointIndex: number,\n\t\tgroup: number,\n\t): AssociationConfig | undefined {\n\t\tif (endpointIndex === 0) {\n\t\t\t// The root endpoint's associations may be configured separately or as part of \"endpoints\"\n\t\t\treturn (\n\t\t\t\tthis.associations?.get(group)\n\t\t\t\t\t?? this.endpoints?.get(0)?.associations?.get(group)\n\t\t\t);\n\t\t} else {\n\t\t\t// The other endpoints can only have a configuration as part of \"endpoints\"\n\t\t\treturn this.endpoints?.get(endpointIndex)?.associations?.get(group);\n\t\t}\n\t}\n\n\tprivate getHashable(version: 0 | 1 | 2): Record<string, any> {\n\t\t// We only need to compare the information that is persisted elsewhere:\n\t\t// - config parameters\n\t\t// - functional association settings\n\t\t// - CC-related compat flags\n\n\t\tlet hashable: Record<string, any> = {\n\t\t\t// endpoints: {\n\t\t\t// \tassociations: {},\n\t\t\t// \tparamInformation: []\n\t\t\t// },\n\t\t\t// proprietary: {},\n\t\t\t// compat: {},\n\t\t};\n\n\t\tconst sortObject = (obj: Record<string, any>) => {\n\t\t\tconst ret: Record<string, any> = {};\n\t\t\tfor (const key of Object.keys(obj).toSorted()) {\n\t\t\t\tret[key] = obj[key];\n\t\t\t}\n\t\t\treturn ret;\n\t\t};\n\n\t\tconst cloneAssociationConfig = (a: AssociationConfig) => {\n\t\t\treturn sortObject(\n\t\t\t\tpick(a, [\"maxNodes\", \"multiChannel\", \"isLifeline\"]),\n\t\t\t);\n\t\t};\n\t\tconst cloneAssociationMap = (\n\t\t\ttarget: Record<string, any>,\n\t\t\tmap: ReadonlyMap<number, AssociationConfig> | undefined,\n\t\t) => {\n\t\t\tif (!map || !map.size) return;\n\t\t\ttarget.associations = {};\n\t\t\tfor (const [key, value] of map) {\n\t\t\t\ttarget.associations[key] = cloneAssociationConfig(value);\n\t\t\t}\n\t\t\ttarget.associations = sortObject(target.associations);\n\t\t};\n\n\t\tconst cloneParamInformationMap = (\n\t\t\ttarget: Record<string, any>,\n\t\t\tmap: ParamInfoMap | undefined,\n\t\t) => {\n\t\t\tif (!map || !map.size) return;\n\t\t\tconst getParamKey = (param: ParamInformation) =>\n\t\t\t\t`${param.parameterNumber}${\n\t\t\t\t\tparam.valueBitMask ? `[${num2hex(param.valueBitMask)}]` : \"\"\n\t\t\t\t}`;\n\t\t\ttarget.paramInformation = [...map.values()]\n\t\t\t\t.toSorted((a, b) =>\n\t\t\t\t\tgetParamKey(a).localeCompare(getParamKey(b))\n\t\t\t\t)\n\t\t\t\t.map((p) => cloneDeep(p));\n\t\t};\n\n\t\t// Clone associations and param information on the root (ep 0) and endpoints\n\t\t{\n\t\t\tlet ep0: Record<string, any> = {};\n\t\t\tcloneAssociationMap(ep0, this.associations);\n\t\t\tcloneParamInformationMap(ep0, this.paramInformation);\n\t\t\tep0 = sortObject(ep0);\n\n\t\t\tif (Object.keys(ep0).length > 0) {\n\t\t\t\thashable.endpoints ??= {};\n\t\t\t\thashable.endpoints[0] = ep0;\n\t\t\t}\n\t\t}\n\n\t\tif (this.endpoints) {\n\t\t\tfor (const [index, endpoint] of this.endpoints) {\n\t\t\t\tlet ep: Record<string, any> = {};\n\n\t\t\t\tcloneAssociationMap(ep, endpoint.associations);\n\t\t\t\tcloneParamInformationMap(ep, endpoint.paramInformation);\n\n\t\t\t\tep = sortObject(ep);\n\n\t\t\t\tif (Object.keys(ep).length > 0) {\n\t\t\t\t\thashable.endpoints ??= {};\n\t\t\t\t\thashable.endpoints[index] = ep;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Clone proprietary config\n\t\tif (this.proprietary && Object.keys(this.proprietary).length > 0) {\n\t\t\thashable.proprietary = sortObject({ ...this.proprietary });\n\t\t}\n\n\t\t// Clone relevant compat flags\n\t\tif (this.compat) {\n\t\t\tlet c: Record<string, any> = {};\n\n\t\t\t// Copy some simple flags over\n\t\t\tfor (\n\t\t\t\tconst prop of [\n\t\t\t\t\t\"forceSceneControllerGroupCount\",\n\t\t\t\t\t\"mapRootReportsToEndpoint\",\n\t\t\t\t\t\"mapBasicSet\",\n\t\t\t\t\t\"preserveRootApplicationCCValueIDs\",\n\t\t\t\t\t\"preserveEndpoints\",\n\t\t\t\t\t\"removeEndpoints\",\n\t\t\t\t\t\"treatMultilevelSwitchSetAsEvent\",\n\t\t\t\t] as const\n\t\t\t) {\n\t\t\t\tif (this.compat[prop] != undefined) {\n\t\t\t\t\tc[prop] = this.compat[prop];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Copy other, more complex flags\n\t\t\tif (this.compat.overrideQueries) {\n\t\t\t\tc.overrideQueries = Object.fromEntries(\n\t\t\t\t\tthis.compat.overrideQueries[\"overrides\"],\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (this.compat.addCCs) {\n\t\t\t\tc.addCCs = Object.fromEntries(\n\t\t\t\t\t[...this.compat.addCCs].map(([ccId, def]) => [\n\t\t\t\t\t\tccId,\n\t\t\t\t\t\tObject.fromEntries(def.endpoints),\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (this.compat.removeCCs) {\n\t\t\t\tc.removeCCs = Object.fromEntries(this.compat.removeCCs);\n\t\t\t}\n\t\t\tif (this.compat.treatSetAsReport) {\n\t\t\t\tc.treatSetAsReport = [...this.compat.treatSetAsReport]\n\t\t\t\t\t.toSorted();\n\t\t\t}\n\n\t\t\tc = sortObject(c);\n\t\t\tif (Object.keys(c).length > 0) {\n\t\t\t\thashable.compat = c;\n\t\t\t}\n\t\t}\n\n\t\tif (version > 1) {\n\t\t\t// From version 2 and on, we ignore labels and descriptions, and load them dynamically\n\t\t\tfor (\n\t\t\t\tconst ep of Object.values<Record<string, any>>(\n\t\t\t\t\thashable.endpoints ?? {},\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tfor (const param of ep.paramInformation ?? []) {\n\t\t\t\t\tdelete param.label;\n\t\t\t\t\tdelete param.description;\n\t\t\t\t\tfor (const opt of param.options ?? []) {\n\t\t\t\t\t\tdelete opt.label;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\thashable = sortObject(hashable);\n\t\treturn hashable;\n\t}\n\n\t/**\n\t * Returns a hash code that can be used to check whether a device config has changed enough to require a re-interview.\n\t */\n\tpublic async getHash(\n\t\tversion: 0 | 1 | 2 = DeviceConfig.maxHashVersion,\n\t): Promise<BytesView> {\n\t\t// Figure out what to hash\n\t\tconst hashable = this.getHashable(version);\n\n\t\t// And create a \"hash\" from it. Older versions used a non-cryptographic hash,\n\t\t// newer versions compress a subset of the config file.\n\t\tlet hash: BytesView;\n\t\tif (version === 0) {\n\t\t\tconst buffer = Bytes.from(JSON.stringify(hashable), \"utf8\");\n\t\t\treturn await digest(\"md5\", buffer);\n\t\t} else if (version === 1) {\n\t\t\tconst buffer = Bytes.from(JSON.stringify(hashable), \"utf8\");\n\t\t\treturn await digest(\"sha-256\", buffer);\n\t\t} else {\n\t\t\thash = deflateSync(\n\t\t\t\tBytes.from(JSON.stringify(hashable), \"utf8\"),\n\t\t\t\t// Try to make the hash as small as possible\n\t\t\t\t{ level: 9, dictionary: deflateDict },\n\t\t\t);\n\t\t}\n\n\t\t// Version the hash from v2 onwards, so we can change the format in the future\n\t\tconst prefixBytes = Bytes.from(`$v${version}$`, \"utf8\");\n\t\treturn Bytes.concat([prefixBytes, hash]);\n\t}\n\n\tpublic static get maxHashVersion(): 2 {\n\t\treturn 2;\n\t}\n\n\tpublic static areHashesEqual(hash: BytesView, other: BytesView): boolean {\n\t\tconst parsedHash = parseHash(hash);\n\t\tconst parsedOther = parseHash(other);\n\t\t// If one of the hashes could not be parsed, they are not equal\n\t\tif (!parsedHash || !parsedOther) return false;\n\n\t\t// For legacy hashes, we only compare the hash data. We already make sure during\n\t\t// parsing of the cache files that we only need to compare hashes of the same version,\n\t\t// so simply comparing the contents is sufficient.\n\t\tif (parsedHash.version < 2 && parsedOther.version < 2) {\n\t\t\treturn Bytes.view(parsedHash.hashData).equals(parsedOther.hashData);\n\t\t}\n\t\t// We take care during loading to downlevel the current config hash to legacy versions if needed.\n\t\t// If we end up with just one legacy hash here, something went wrong. Just bail in that case.\n\t\tif (parsedHash.version < 2 || parsedOther.version < 2) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// This is a versioned hash. If both versions are equal, it's simple - just compare the hash data\n\t\tif (parsedHash.version === parsedOther.version) {\n\t\t\treturn Bytes.view(parsedHash.hashData).equals(parsedOther.hashData);\n\t\t}\n\n\t\t// For different versions, we have to do some case by case checks. For example, a newer hash version\n\t\t// might remove or add data into the hashable, so we cannot simply convert between versions easily.\n\t\t// Implement when that is actually needed.\n\t\treturn false;\n\t}\n}\n\nfunction parseHash(hash: BytesView): {\n\tversion: number;\n\thashData: BytesView;\n} | undefined {\n\tconst hashString = Bytes.view(hash).toString(\"utf8\");\n\tconst versionMatch = hashString.match(/^\\$v(\\d+)\\$/);\n\tif (versionMatch) {\n\t\t// This is a versioned hash\n\t\tconst version = parseInt(versionMatch[1], 10);\n\t\tconst hashData = hash.subarray(\n\t\t\t// The prefix is ASCII, so this is safe to do even in the context of UTF-8\n\t\t\tversionMatch[0].length,\n\t\t);\n\t\treturn {\n\t\t\tversion,\n\t\t\thashData,\n\t\t};\n\t}\n\n\t// This is probably an unversioned legacy hash\n\tswitch (hash.length) {\n\t\tcase 16: // MD5\n\t\t\treturn {\n\t\t\t\tversion: 0,\n\t\t\t\thashData: hash,\n\t\t\t};\n\t\tcase 32: // SHA-256\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\thashData: hash,\n\t\t\t};\n\t\tdefault:\n\t\t\t// This is not a valid hash\n\t\t\treturn undefined;\n\t}\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { configDir } from \"#config_dir\";\nimport {\n\tZWaveError,\n\tZWaveErrorCodes,\n\tdeflateSync,\n\tdigest,\n} from \"@zwave-js/core\";\nimport {\n\tBytes,\n\ttype BytesView,\n\ttype JSONObject,\n\tcloneDeep,\n\tenumFilesRecursive,\n\tformatId,\n\tgetenv,\n\tnum2hex,\n\tpadVersion,\n\tpathExists,\n\tpick,\n\treadTextFile,\n\tstringify,\n\twriteTextFile,\n} from \"@zwave-js/shared\";\nimport type {\n\tReadFile,\n\tReadFileSystemInfo,\n\tWriteFile,\n} from \"@zwave-js/shared/bindings\";\nimport { isArray, isObject } from \"alcalzone-shared/typeguards\";\nimport JSON5 from \"json5\";\nimport path from \"pathe\";\nimport semverGt from \"semver/functions/gt.js\";\nimport { clearTemplateCache, readJsonWithTemplate } from \"../JsonTemplate.js\";\nimport type { ConfigLogger } from \"../Logger.js\";\nimport { hexKeyRegex4Digits, throwInvalidConfig } from \"../utils_safe.js\";\nimport {\n\ttype AssociationConfig,\n\tConditionalAssociationConfig,\n} from \"./AssociationConfig.js\";\nimport { type CompatConfig, ConditionalCompatConfig } from \"./CompatConfig.js\";\nimport { evaluateDeep, validateCondition } from \"./ConditionalItem.js\";\nimport {\n\ttype ConditionalPrimitive,\n\tparseConditionalPrimitive,\n} from \"./ConditionalPrimitive.js\";\nimport {\n\tConditionalDeviceMetadata,\n\ttype DeviceMetadata,\n} from \"./DeviceMetadata.js\";\nimport {\n\tConditionalEndpointConfig,\n\ttype EndpointConfig,\n} from \"./EndpointConfig.js\";\nimport {\n\ttype ConditionalParamInfoMap,\n\ttype ParamInfoMap,\n\ttype ParamInformation,\n\tparseConditionalParamInformationMap,\n} from \"./ParamInformation.js\";\nimport { ConditionalSceneConfig, type SceneConfig } from \"./SceneConfig.js\";\nimport type { DeviceID, FirmwareVersionRange } from \"./shared.js\";\n\nexport interface DeviceConfigIndexEntry {\n\tmanufacturerId: string;\n\tproductType: string;\n\tproductId: string;\n\tfirmwareVersion: FirmwareVersionRange;\n\tpreferred?: true;\n\trootDir?: string;\n\tfilename: string;\n}\n\nexport interface FulltextDeviceConfigIndexEntry {\n\tmanufacturerId: string;\n\tmanufacturer: string;\n\tlabel: string;\n\tdescription: string;\n\tproductType: string;\n\tproductId: string;\n\tfirmwareVersion: FirmwareVersionRange;\n\tpreferred?: true;\n\trootDir?: string;\n\tfilename: string;\n}\n\nexport const embeddedDevicesDir = path.join(configDir, \"devices\");\nconst fulltextIndexPath = path.join(embeddedDevicesDir, \"fulltext_index.json\");\n\nexport function getDevicesPaths(configDir: string): {\n\tdevicesDir: string;\n\tindexPath: string;\n} {\n\tconst devicesDir = path.join(configDir, \"devices\");\n\tconst indexPath = path.join(devicesDir, \"index.json\");\n\treturn { devicesDir, indexPath };\n}\n\nexport type DeviceConfigIndex = DeviceConfigIndexEntry[];\nexport type FulltextDeviceConfigIndex = FulltextDeviceConfigIndexEntry[];\n\nasync function hasChangedDeviceFiles(\n\tfs: ReadFileSystemInfo,\n\tdevicesRoot: string,\n\tdir: string,\n\tlastChange: Date,\n): Promise<boolean> {\n\t// Check if there are any files BUT index.json that were changed\n\t// or directories that were modified\n\tconst filesAndDirs = await fs.readDir(dir);\n\tfor (const f of filesAndDirs) {\n\t\tconst fullPath = path.join(dir, f);\n\n\t\tconst stat = await fs.stat(fullPath);\n\t\tif (\n\t\t\t(dir !== devicesRoot || f !== \"index.json\")\n\t\t\t&& (stat.isFile() || stat.isDirectory())\n\t\t\t&& stat.mtime > lastChange\n\t\t) {\n\t\t\treturn true;\n\t\t} else if (stat.isDirectory()) {\n\t\t\t// we need to go deeper!\n\t\t\tif (\n\t\t\t\tawait hasChangedDeviceFiles(\n\t\t\t\t\tfs,\n\t\t\t\t\tdevicesRoot,\n\t\t\t\t\tfullPath,\n\t\t\t\t\tlastChange,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Read all device config files from a given directory and return them as index entries.\n * Does not update the index itself.\n */\nasync function generateIndex<T extends Record<string, unknown>>(\n\tfs: ReadFileSystemInfo & ReadFile,\n\tdevicesDir: string,\n\tisEmbedded: boolean,\n\textractIndexEntries: (config: DeviceConfig) => T[],\n\tlogger?: ConfigLogger,\n): Promise<(T & { filename: string; rootDir?: string })[]> {\n\tconst index: (T & { filename: string; rootDir?: string })[] = [];\n\n\tclearTemplateCache();\n\tconst configFiles = await enumFilesRecursive(\n\t\tfs,\n\t\tdevicesDir,\n\t\t(file) =>\n\t\t\tfile.endsWith(\".json\")\n\t\t\t&& !file.endsWith(\"index.json\")\n\t\t\t&& !file.includes(\"/templates/\")\n\t\t\t&& !file.includes(\"\\\\templates\\\\\"),\n\t);\n\n\t// Add the embedded devices dir as a fallback if necessary\n\tconst fallbackDirs = devicesDir !== embeddedDevicesDir\n\t\t? [embeddedDevicesDir]\n\t\t: undefined;\n\n\tfor (const file of configFiles) {\n\t\tconst relativePath = path\n\t\t\t.relative(devicesDir, file)\n\t\t\t.replaceAll(\"\\\\\", \"/\");\n\t\t// Try parsing the file\n\t\ttry {\n\t\t\tconst config = await DeviceConfig.from(\n\t\t\t\tfs,\n\t\t\t\tfile,\n\t\t\t\tisEmbedded,\n\t\t\t\t{\n\t\t\t\t\trootDir: devicesDir,\n\t\t\t\t\tfallbackDirs,\n\t\t\t\t\trelative: true,\n\t\t\t\t},\n\t\t\t);\n\t\t\t// Add the file to the index\n\t\t\tindex.push(\n\t\t\t\t...extractIndexEntries(config).map((entry) => {\n\t\t\t\t\tconst ret: T & { filename: string; rootDir?: string } = {\n\t\t\t\t\t\t...entry,\n\t\t\t\t\t\tfilename: relativePath,\n\t\t\t\t\t};\n\t\t\t\t\t// Only add the root dir to the index if necessary\n\t\t\t\t\tif (devicesDir !== embeddedDevicesDir) {\n\t\t\t\t\t\tret.rootDir = devicesDir;\n\t\t\t\t\t}\n\t\t\t\t\treturn ret;\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (e) {\n\t\t\tconst message = `Error parsing config file ${relativePath}: ${\n\t\t\t\t(e as Error).message\n\t\t\t}`;\n\t\t\t// Crash hard during tests, just print an error when in production systems.\n\t\t\t// A user could have changed a config file\n\t\t\tif (process.env.NODE_ENV === \"test\" || !!getenv(\"CI\")) {\n\t\t\t\tthrow new ZWaveError(message, ZWaveErrorCodes.Config_Invalid);\n\t\t\t} else {\n\t\t\t\tlogger?.print(message, \"error\");\n\t\t\t}\n\t\t}\n\t}\n\n\treturn index;\n}\n\nasync function loadDeviceIndexShared<T extends Record<string, unknown>>(\n\tfs: ReadFileSystemInfo & ReadFile & WriteFile,\n\tdevicesDir: string,\n\tindexPath: string,\n\textractIndexEntries: (config: DeviceConfig) => T[],\n\tlogger?: ConfigLogger,\n): Promise<(T & { filename: string })[]> {\n\t// The index file needs to be regenerated if it does not exist\n\tlet needsUpdate = !(await pathExists(fs, indexPath));\n\tlet index: (T & { filename: string })[] | undefined;\n\tlet mtimeIndex: Date | undefined;\n\t// ...or if cannot be parsed\n\tif (!needsUpdate) {\n\t\ttry {\n\t\t\tconst fileContents = await readTextFile(fs, indexPath, \"utf8\");\n\t\t\tindex = JSON5.parse(fileContents);\n\t\t\tmtimeIndex = (await fs.stat(indexPath)).mtime;\n\t\t} catch {\n\t\t\tlogger?.print(\n\t\t\t\t\"Error while parsing index file - regenerating...\",\n\t\t\t\t\"warn\",\n\t\t\t);\n\t\t\tneedsUpdate = true;\n\t\t} finally {\n\t\t\tif (!index) {\n\t\t\t\tlogger?.print(\n\t\t\t\t\t\"Index file was malformed - regenerating...\",\n\t\t\t\t\t\"warn\",\n\t\t\t\t);\n\t\t\t\tneedsUpdate = true;\n\t\t\t}\n\t\t}\n\t}\n\n\t// ...or if there were any changes in the file system\n\tif (!needsUpdate) {\n\t\tneedsUpdate = await hasChangedDeviceFiles(\n\t\t\tfs,\n\t\t\tdevicesDir,\n\t\t\tdevicesDir,\n\t\t\tmtimeIndex!,\n\t\t);\n\t\tif (needsUpdate) {\n\t\t\tlogger?.print(\n\t\t\t\t\"Device configuration files on disk changed - regenerating index...\",\n\t\t\t\t\"verbose\",\n\t\t\t);\n\t\t}\n\t}\n\n\tif (needsUpdate) {\n\t\t// Read all files from disk and generate an index\n\t\tindex = await generateIndex(\n\t\t\tfs,\n\t\t\tdevicesDir,\n\t\t\ttrue,\n\t\t\textractIndexEntries,\n\t\t\tlogger,\n\t\t);\n\t\t// Save the index to disk\n\t\ttry {\n\t\t\tawait writeTextFile(\n\t\t\t\tfs,\n\t\t\t\tpath.join(indexPath),\n\t\t\t\t`// This file is auto-generated. DO NOT edit it by hand if you don't know what you're doing!\"\n${stringify(index, \"\\t\")}\n`,\n\t\t\t\t\"utf8\",\n\t\t\t);\n\t\t\tlogger?.print(\"Device index regenerated\", \"verbose\");\n\t\t} catch (e) {\n\t\t\tlogger?.print(\n\t\t\t\t`Writing the device index to disk failed: ${\n\t\t\t\t\t(e as Error).message\n\t\t\t\t}`,\n\t\t\t\t\"error\",\n\t\t\t);\n\t\t}\n\t}\n\n\treturn index!;\n}\n\n/**\n * @internal\n * Loads the index file to quickly access the device configs.\n * Transparently handles updating the index if necessary\n */\nexport async function generatePriorityDeviceIndex(\n\tfs: ReadFileSystemInfo & ReadFile,\n\tdeviceConfigPriorityDir: string,\n\tlogger?: ConfigLogger,\n): Promise<DeviceConfigIndex> {\n\treturn (\n\t\tawait generateIndex(\n\t\t\tfs,\n\t\t\tdeviceConfigPriorityDir,\n\t\t\tfalse,\n\t\t\t(config) =>\n\t\t\t\tconfig.devices.map((dev) => ({\n\t\t\t\t\tmanufacturerId: formatId(\n\t\t\t\t\t\tconfig.manufacturerId.toString(16),\n\t\t\t\t\t),\n\t\t\t\t\tmanufacturer: config.manufacturer,\n\t\t\t\t\tlabel: config.label,\n\t\t\t\t\tproductType: formatId(dev.productType),\n\t\t\t\t\tproductId: formatId(dev.productId),\n\t\t\t\t\tfirmwareVersion: config.firmwareVersion,\n\t\t\t\t\t...(config.preferred ? { preferred: true as const } : {}),\n\t\t\t\t\trootDir: deviceConfigPriorityDir,\n\t\t\t\t})),\n\t\t\tlogger,\n\t\t)\n\t).map(({ filename, ...entry }) => ({\n\t\t...entry,\n\t\t// The generated index makes the filenames relative to the given directory\n\t\t// but we need them to be absolute\n\t\tfilename: path.join(deviceConfigPriorityDir, filename),\n\t}));\n}\n\n/**\n * @internal\n * Loads the index file to quickly access the device configs.\n * Transparently handles updating the index if necessary\n */\nexport async function loadDeviceIndexInternal(\n\tfs: ReadFileSystemInfo & ReadFile & WriteFile,\n\tlogger?: ConfigLogger,\n\texternalConfigDir?: string,\n): Promise<DeviceConfigIndex> {\n\tconst { devicesDir, indexPath } = getDevicesPaths(\n\t\texternalConfigDir || configDir,\n\t);\n\n\treturn loadDeviceIndexShared(\n\t\tfs,\n\t\tdevicesDir,\n\t\tindexPath,\n\t\t(config) =>\n\t\t\tconfig.devices.map((dev) => ({\n\t\t\t\tmanufacturerId: formatId(config.manufacturerId.toString(16)),\n\t\t\t\tmanufacturer: config.manufacturer,\n\t\t\t\tlabel: config.label,\n\t\t\t\tproductType: formatId(dev.productType),\n\t\t\t\tproductId: formatId(dev.productId),\n\t\t\t\tfirmwareVersion: config.firmwareVersion,\n\t\t\t\t...(config.preferred ? { preferred: true as const } : {}),\n\t\t\t})),\n\t\tlogger,\n\t);\n}\n\n/**\n * @internal\n * Loads the full text index file to quickly search the device configs.\n * Transparently handles updating the index if necessary\n */\nexport async function loadFulltextDeviceIndexInternal(\n\tfs: ReadFileSystemInfo & ReadFile & WriteFile,\n\tlogger?: ConfigLogger,\n): Promise<FulltextDeviceConfigIndex> {\n\t// This method is not meant to operate with the external device index!\n\treturn loadDeviceIndexShared(\n\t\tfs,\n\t\tembeddedDevicesDir,\n\t\tfulltextIndexPath,\n\t\t(config) =>\n\t\t\tconfig.devices.map((dev) => ({\n\t\t\t\tmanufacturerId: formatId(config.manufacturerId.toString(16)),\n\t\t\t\tmanufacturer: config.manufacturer,\n\t\t\t\tlabel: config.label,\n\t\t\t\tdescription: config.description,\n\t\t\t\tproductType: formatId(dev.productType),\n\t\t\t\tproductId: formatId(dev.productId),\n\t\t\t\tfirmwareVersion: config.firmwareVersion,\n\t\t\t\t...(config.preferred ? { preferred: true as const } : {}),\n\t\t\t\trootDir: embeddedDevicesDir,\n\t\t\t})),\n\t\tlogger,\n\t);\n}\n\nfunction isHexKeyWith4Digits(val: any): val is string {\n\treturn typeof val === \"string\" && hexKeyRegex4Digits.test(val);\n}\n\nconst firmwareVersionRegex = /^\\d{1,3}\\.\\d{1,3}(\\.\\d{1,3})?$/;\nfunction isFirmwareVersion(val: any): val is string {\n\treturn (\n\t\ttypeof val === \"string\"\n\t\t&& firmwareVersionRegex.test(val)\n\t\t&& val\n\t\t\t.split(\".\")\n\t\t\t.map((str) => parseInt(str, 10))\n\t\t\t.every((num) => num >= 0 && num <= 255)\n\t);\n}\n\nconst deflateDict = Bytes.from(\n\t// Substrings appearing in the device config files in descending order of frequency\n\t// except for very short ones like 0, 1, ...\n\t// WARNING: THIS MUST NOT BE CHANGED! Doing so breaks decompressing stored hashes.\n\t[\n\t\t`\"parameterNumber\":`,\n\t\t`255`,\n\t\t`\"value\":`,\n\t\t`\"defaultValue\":`,\n\t\t`\"valueSize\":`,\n\t\t`\"maxValue\":`,\n\t\t`\"minValue\":`,\n\t\t`\"options\":`,\n\t\t`true`,\n\t\t`false`,\n\t\t`\"allowManualEntry\":`,\n\t\t`\"maxNodes\":`,\n\t\t`100`,\n\t\t`\"unsigned\":`,\n\t\t`\"paramInformation\":`,\n\t\t`\"isLifeline\":`,\n\t\t`\"seconds\"`,\n\t\t`99`,\n\t\t`127`,\n\t\t`\"%\"`,\n\t\t`65535`,\n\t\t`32767`,\n\t\t`\"minutes\"`,\n\t\t`\"endpoints\":`,\n\t\t`\"hours\"`,\n\t\t`\"multiChannel\":`,\n\t]\n\t\t.join(\"\"),\n\t\"utf8\",\n);\n\n/** This class represents a device config entry whose conditional settings have not been evaluated yet */\nexport class ConditionalDeviceConfig {\n\tpublic static async from(\n\t\tfs: ReadFileSystemInfo & ReadFile,\n\t\tfilename: string,\n\t\tisEmbedded: boolean,\n\t\toptions: {\n\t\t\trootDir: string;\n\t\t\tfallbackDirs?: string[];\n\t\t\trelative?: boolean;\n\t\t},\n\t): Promise<ConditionalDeviceConfig> {\n\t\tconst { relative, rootDir } = options;\n\n\t\tconst relativePath = relative\n\t\t\t? path.relative(rootDir, filename).replaceAll(\"\\\\\", \"/\")\n\t\t\t: filename;\n\t\tconst json = await readJsonWithTemplate(\n\t\t\tfs,\n\t\t\tfilename,\n\t\t\t[\n\t\t\t\toptions.rootDir,\n\t\t\t\t...(options.fallbackDirs ?? []),\n\t\t\t],\n\t\t);\n\t\treturn new ConditionalDeviceConfig(relativePath, isEmbedded, json);\n\t}\n\n\tpublic constructor(\n\t\tfilename: string,\n\t\tisEmbedded: boolean,\n\t\tdefinition: JSONObject,\n\t) {\n\t\tthis.filename = filename;\n\t\tthis.isEmbedded = isEmbedded;\n\n\t\tif (!isHexKeyWith4Digits(definition.manufacturerId)) {\n\t\t\tthrowInvalidConfig(\n\t\t\t\t`device`,\n\t\t\t\t`packages/config/config/devices/${filename}:\nmanufacturer id must be a lowercase hexadecimal number with 4 digits`,\n\t\t\t);\n\t\t}\n\t\tthis.manufacturerId = parseInt(definition.manufacturerId, 16);\n\n\t\tfor (const prop of [\"manufacturer\", \"label\", \"description\"] as const) {\n\t\t\tthis[prop] = parseConditionalPrimitive(\n\t\t\t\tfilename,\n\t\t\t\t\"string\",\n\t\t\t\tprop,\n\t\t\t\tdefinition[prop],\n\t\t\t);\n\t\t}\n\n\t\tif (\n\t\t\t!isArray(definition.devices)\n\t\t\t|| !(definition.devices as any[]).every(\n\t\t\t\t(dev: unknown) =>\n\t\t\t\t\tisObject(dev)\n\t\t\t\t\t&& isHexKeyWith4Digits(dev.productType)\n\t\t\t\t\t&& isHexKeyWith4Digits(dev.productId),\n\t\t\t)\n\t\t) {\n\t\t\tthrowInvalidConfig(\n\t\t\t\t`device`,\n\t\t\t\t`packages/config/config/devices/${filename}:\ndevices is malformed (not an object or type/id that is not a lowercase 4-digit hex key)`,\n\t\t\t);\n\t\t}\n\t\tthis.devices = (definition.devices as any[]).map(\n\t\t\t({ productType, productId }) => ({\n\t\t\t\tproductType: parseInt(productType, 16),\n\t\t\t\tproductId: parseInt(productId, 16),\n\t\t\t}),\n\t\t);\n\n\t\tif (\n\t\t\t!isObject(definition.firmwareVersion)\n\t\t\t|| !isFirmwareVersion(definition.firmwareVersion.min)\n\t\t\t|| !isFirmwareVersion(definition.firmwareVersion.max)\n\t\t) {\n\t\t\tthrowInvalidConfig(\n\t\t\t\t`device`,\n\t\t\t\t`packages/config/config/devices/${filename}:\nfirmwareVersion is malformed or invalid. Must be x.y or x.y.z where x, y, and z are integers between 0 and 255`,\n\t\t\t);\n\t\t} else {\n\t\t\tconst { min, max } = definition.firmwareVersion;\n\t\t\tif (semverGt(padVersion(min), padVersion(max))) {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\nfirmwareVersion.min ${min} must not be greater than firmwareVersion.max ${max}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.firmwareVersion = { min, max };\n\t\t}\n\n\t\tif (\n\t\t\tdefinition.preferred != undefined\n\t\t\t&& definition.preferred !== true\n\t\t) {\n\t\t\tthrowInvalidConfig(\n\t\t\t\t`device`,\n\t\t\t\t`packages/config/config/devices/${filename}:\npreferred must be true or omitted`,\n\t\t\t);\n\t\t}\n\t\tthis.preferred = !!definition.preferred;\n\n\t\tif (definition.endpoints != undefined) {\n\t\t\tconst endpoints = new Map<number, ConditionalEndpointConfig>();\n\t\t\tif (!isObject(definition.endpoints)) {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\nendpoints is not an object`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tfor (const [key, ep] of Object.entries(definition.endpoints)) {\n\t\t\t\tif (!/^\\d+$/.test(key)) {\n\t\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t\t`device`,\n\t\t\t\t\t\t`packages/config/config/devices/${filename}:\nfound non-numeric endpoint index \"${key}\" in endpoints`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst epIndex = parseInt(key, 10);\n\t\t\t\tendpoints.set(\n\t\t\t\t\tepIndex,\n\t\t\t\t\tnew ConditionalEndpointConfig(this, epIndex, ep as any),\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.endpoints = endpoints;\n\t\t}\n\n\t\tif (definition.associations != undefined) {\n\t\t\tconst associations = new Map<\n\t\t\t\tnumber,\n\t\t\t\tConditionalAssociationConfig\n\t\t\t>();\n\t\t\tif (!isObject(definition.associations)) {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\nassociations is not an object`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tfor (\n\t\t\t\tconst [key, assocDefinition] of Object.entries(\n\t\t\t\t\tdefinition.associations,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tif (!/^[1-9][0-9]*$/.test(key)) {\n\t\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t\t`device`,\n\t\t\t\t\t\t`packages/config/config/devices/${filename}:\nfound non-numeric group id \"${key}\" in associations`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst keyNum = parseInt(key, 10);\n\t\t\t\tassociations.set(\n\t\t\t\t\tkeyNum,\n\t\t\t\t\tnew ConditionalAssociationConfig(\n\t\t\t\t\t\tfilename,\n\t\t\t\t\t\tkeyNum,\n\t\t\t\t\t\tassocDefinition as any,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.associations = associations;\n\t\t}\n\n\t\tif (definition.paramInformation != undefined) {\n\t\t\tthis.paramInformation = parseConditionalParamInformationMap(\n\t\t\t\tdefinition,\n\t\t\t\tthis,\n\t\t\t);\n\t\t}\n\n\t\tif (definition.proprietary != undefined) {\n\t\t\tif (!isObject(definition.proprietary)) {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\nproprietary is not an object`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.proprietary = definition.proprietary;\n\t\t}\n\n\t\tif (definition.compat != undefined) {\n\t\t\tif (\n\t\t\t\tisArray(definition.compat)\n\t\t\t\t&& definition.compat.every((item: any) => isObject(item))\n\t\t\t) {\n\t\t\t\t// Make sure all conditions are valid\n\t\t\t\tfor (const entry of definition.compat) {\n\t\t\t\t\tvalidateCondition(\n\t\t\t\t\t\tfilename,\n\t\t\t\t\t\tentry,\n\t\t\t\t\t\t`At least one entry of compat contains an`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tthis.compat = definition.compat.map(\n\t\t\t\t\t(item: any) => new ConditionalCompatConfig(filename, item),\n\t\t\t\t);\n\t\t\t} else if (isObject(definition.compat)) {\n\t\t\t\tthis.compat = new ConditionalCompatConfig(\n\t\t\t\t\tfilename,\n\t\t\t\t\tdefinition.compat,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\ncompat must be an object or any array of conditional objects`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (definition.metadata != undefined) {\n\t\t\tif (!isObject(definition.metadata)) {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\nmetadata is not an object`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.metadata = new ConditionalDeviceMetadata(\n\t\t\t\tfilename,\n\t\t\t\tdefinition.metadata,\n\t\t\t);\n\t\t}\n\n\t\tif (definition.scenes != undefined) {\n\t\t\tconst scenes = new Map<\n\t\t\t\tnumber,\n\t\t\t\tConditionalSceneConfig\n\t\t\t>();\n\t\t\tif (!isObject(definition.scenes)) {\n\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t`device`,\n\t\t\t\t\t`packages/config/config/devices/${filename}:\nscenes is not an object`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tfor (\n\t\t\t\tconst [key, sceneDefinition] of Object.entries(\n\t\t\t\t\tdefinition.scenes,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tif (!/^[1-9][0-9]*$/.test(key)) {\n\t\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t\t`device`,\n\t\t\t\t\t\t`packages/config/config/devices/${filename}:\ninvalid scene id \"${key}\" in scenes - must be a positive integer (1-255)`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst keyNum = parseInt(key, 10);\n\t\t\t\tif (keyNum < 1 || keyNum > 255) {\n\t\t\t\t\tthrowInvalidConfig(\n\t\t\t\t\t\t`device`,\n\t\t\t\t\t\t`packages/config/config/devices/${filename}:\nscene number ${keyNum} must be between 1 and 255`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tscenes.set(\n\t\t\t\t\tkeyNum,\n\t\t\t\t\tnew ConditionalSceneConfig(\n\t\t\t\t\t\tfilename,\n\t\t\t\t\t\tkeyNum,\n\t\t\t\t\t\tsceneDefinition as any,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.scenes = scenes;\n\t\t}\n\t}\n\n\tpublic readonly filename: string;\n\n\tpublic readonly manufacturer!: ConditionalPrimitive<string>;\n\tpublic readonly manufacturerId: number;\n\tpublic readonly label!: ConditionalPrimitive<string>;\n\tpublic readonly description!: ConditionalPrimitive<string>;\n\tpublic readonly devices: readonly {\n\t\tproductType: number;\n\t\tproductId: number;\n\t}[];\n\tpublic readonly firmwareVersion: FirmwareVersionRange;\n\t/** Mark this configuration as preferred over other config files with an overlapping firmware range */\n\tpublic readonly preferred: boolean;\n\tpublic readonly endpoints?: ReadonlyMap<number, ConditionalEndpointConfig>;\n\tpublic readonly associations?: ReadonlyMap<\n\t\tnumber,\n\t\tConditionalAssociationConfig\n\t>;\n\tpublic readonly scenes?: ReadonlyMap<number, ConditionalSceneConfig>;\n\tpublic readonly paramInformation?: ConditionalParamInfoMap;\n\t/**\n\t * Contains manufacturer-specific support information for the\n\t * ManufacturerProprietary CC\n\t */\n\tpublic readonly proprietary?: Record<string, unknown>;\n\t/** Contains compatibility options */\n\tpublic readonly compat?:\n\t\t| ConditionalCompatConfig\n\t\t| ConditionalCompatConfig[];\n\t/** Contains instructions and other metadata for the device */\n\tpublic readonly metadata?: ConditionalDeviceMetadata;\n\n\t/** Whether this is an embedded configuration or not */\n\tpublic readonly isEmbedded: boolean;\n\n\tpublic evaluate(deviceId?: DeviceID): DeviceConfig {\n\t\treturn new DeviceConfig(\n\t\t\tthis.filename,\n\t\t\tthis.isEmbedded,\n\t\t\tevaluateDeep(this.manufacturer, deviceId),\n\t\t\tthis.manufacturerId,\n\t\t\tevaluateDeep(this.label, deviceId),\n\t\t\tevaluateDeep(this.description, deviceId),\n\t\t\tthis.devices,\n\t\t\tthis.firmwareVersion,\n\t\t\tthis.preferred,\n\t\t\tevaluateDeep(this.endpoints, deviceId),\n\t\t\tevaluateDeep(this.associations, deviceId),\n\t\t\tevaluateDeep(this.scenes, deviceId),\n\t\t\tevaluateDeep(this.paramInformation, deviceId),\n\t\t\tthis.proprietary,\n\t\t\tevaluateDeep(this.compat, deviceId),\n\t\t\tevaluateDeep(this.metadata, deviceId),\n\t\t);\n\t}\n}\n\nexport type DeviceConfigHashVersion = 0 | 1 | 2 | 3;\n\nexport class DeviceConfig {\n\tpublic static async from(\n\t\tfs: ReadFileSystemInfo & ReadFile,\n\t\tfilename: string,\n\t\tisEmbedded: boolean,\n\t\toptions: {\n\t\t\trootDir: string;\n\t\t\tfallbackDirs?: string[];\n\t\t\trelative?: boolean;\n\t\t\tdeviceId?: DeviceID;\n\t\t},\n\t): Promise<DeviceConfig> {\n\t\tconst ret = await ConditionalDeviceConfig.from(\n\t\t\tfs,\n\t\t\tfilename,\n\t\t\tisEmbedded,\n\t\t\toptions,\n\t\t);\n\t\treturn ret.evaluate(options.deviceId);\n\t}\n\n\tpublic constructor(\n\t\tfilename: string,\n\t\tisEmbedded: boolean,\n\t\tmanufacturer: string,\n\t\tmanufacturerId: number,\n\t\tlabel: string,\n\t\tdescription: string,\n\t\tdevices: readonly {\n\t\t\tproductType: number;\n\t\t\tproductId: number;\n\t\t}[],\n\t\tfirmwareVersion: FirmwareVersionRange,\n\t\tpreferred: boolean,\n\t\tendpoints?: ReadonlyMap<number, EndpointConfig>,\n\t\tassociations?: ReadonlyMap<number, AssociationConfig>,\n\t\tscenes?: ReadonlyMap<number, SceneConfig>,\n\t\tparamInformation?: ParamInfoMap,\n\t\tproprietary?: Record<string, unknown>,\n\t\tcompat?: CompatConfig,\n\t\tmetadata?: DeviceMetadata,\n\t) {\n\t\tthis.filename = filename;\n\t\tthis.isEmbedded = isEmbedded;\n\t\tthis.manufacturer = manufacturer;\n\t\tthis.manufacturerId = manufacturerId;\n\t\tthis.label = label;\n\t\tthis.description = description;\n\t\tthis.devices = devices;\n\t\tthis.firmwareVersion = firmwareVersion;\n\t\tthis.preferred = preferred;\n\t\tthis.endpoints = endpoints;\n\t\tthis.associations = associations;\n\t\tthis.scenes = scenes;\n\t\tthis.paramInformation = paramInformation;\n\t\tthis.proprietary = proprietary;\n\t\tthis.compat = compat;\n\t\tthis.metadata = metadata;\n\t}\n\n\tpublic readonly filename: string;\n\t/** Whether this is an embedded configuration or not */\n\tpublic readonly isEmbedded: boolean;\n\tpublic readonly manufacturer: string;\n\tpublic readonly manufacturerId: number;\n\tpublic readonly label: string;\n\tpublic readonly description: string;\n\tpublic readonly devices: readonly {\n\t\tproductType: number;\n\t\tproductId: number;\n\t}[];\n\tpublic readonly firmwareVersion: FirmwareVersionRange;\n\t/** Mark this configuration as preferred over other config files with an overlapping firmware range */\n\tpublic readonly preferred: boolean;\n\tpublic readonly endpoints?: ReadonlyMap<number, EndpointConfig>;\n\tpublic readonly associations?: ReadonlyMap<number, AssociationConfig>;\n\tpublic readonly scenes?: ReadonlyMap<number, SceneConfig>;\n\tpublic readonly paramInformation?: ParamInfoMap;\n\t/**\n\t * Contains manufacturer-specific support information for the\n\t * ManufacturerProprietary CC\n\t */\n\tpublic readonly proprietary?: Record<string, unknown>;\n\t/** Contains compatibility options */\n\tpublic readonly compat?: CompatConfig;\n\t/** Contains instructions and other metadata for the device */\n\tpublic readonly metadata?: DeviceMetadata;\n\n\t/** Returns the association config for a given endpoint */\n\tpublic getAssociationConfigForEndpoint(\n\t\tendpointIndex: number,\n\t\tgroup: number,\n\t): AssociationConfig | undefined {\n\t\tif (endpointIndex === 0) {\n\t\t\t// The root endpoint's associations may be configured separately or as part of \"endpoints\"\n\t\t\treturn (\n\t\t\t\tthis.associations?.get(group)\n\t\t\t\t\t?? this.endpoints?.get(0)?.associations?.get(group)\n\t\t\t);\n\t\t} else {\n\t\t\t// The other endpoints can only have a configuration as part of \"endpoints\"\n\t\t\treturn this.endpoints?.get(endpointIndex)?.associations?.get(group);\n\t\t}\n\t}\n\n\tprivate getHashable(version: DeviceConfigHashVersion): Record<string, any> {\n\t\t// We only need to compare the information that is persisted elsewhere:\n\t\t// - config parameters\n\t\t// - functional association settings\n\t\t// - CC-related compat flags\n\n\t\tlet hashable: Record<string, any> = {\n\t\t\t// endpoints: {\n\t\t\t// \tassociations: {},\n\t\t\t// \tparamInformation: []\n\t\t\t// },\n\t\t\t// proprietary: {},\n\t\t\t// compat: {},\n\t\t};\n\n\t\tconst sortObject = (obj: Record<string, any>) => {\n\t\t\tconst ret: Record<string, any> = {};\n\t\t\tfor (const key of Object.keys(obj).toSorted()) {\n\t\t\t\tret[key] = obj[key];\n\t\t\t}\n\t\t\treturn ret;\n\t\t};\n\n\t\tconst cloneAssociationConfig = (a: AssociationConfig) => {\n\t\t\treturn sortObject(\n\t\t\t\tpick(a, [\"maxNodes\", \"multiChannel\", \"isLifeline\"]),\n\t\t\t);\n\t\t};\n\t\tconst cloneAssociationMap = (\n\t\t\ttarget: Record<string, any>,\n\t\t\tmap: ReadonlyMap<number, AssociationConfig> | undefined,\n\t\t) => {\n\t\t\tif (!map || !map.size) return;\n\t\t\ttarget.associations = {};\n\t\t\tfor (const [key, value] of map) {\n\t\t\t\ttarget.associations[key] = cloneAssociationConfig(value);\n\t\t\t}\n\t\t\ttarget.associations = sortObject(target.associations);\n\t\t};\n\n\t\tconst cloneParamInformationMap = (\n\t\t\ttarget: Record<string, any>,\n\t\t\tmap: ParamInfoMap | undefined,\n\t\t) => {\n\t\t\tif (!map || !map.size) return;\n\t\t\tconst getParamKey = (param: ParamInformation) =>\n\t\t\t\t`${param.parameterNumber}${\n\t\t\t\t\tparam.valueBitMask ? `[${num2hex(param.valueBitMask)}]` : \"\"\n\t\t\t\t}`;\n\t\t\ttarget.paramInformation = [...map.values()]\n\t\t\t\t.toSorted((a, b) =>\n\t\t\t\t\tgetParamKey(a).localeCompare(getParamKey(b))\n\t\t\t\t)\n\t\t\t\t.map((p) => cloneDeep(p));\n\t\t};\n\n\t\t// Clone associations and param information on the root (ep 0) and endpoints\n\t\t{\n\t\t\tlet ep0: Record<string, any> = {};\n\t\t\tcloneAssociationMap(ep0, this.associations);\n\t\t\tcloneParamInformationMap(ep0, this.paramInformation);\n\t\t\tep0 = sortObject(ep0);\n\n\t\t\tif (Object.keys(ep0).length > 0) {\n\t\t\t\thashable.endpoints ??= {};\n\t\t\t\thashable.endpoints[0] = ep0;\n\t\t\t}\n\t\t}\n\n\t\tif (this.endpoints) {\n\t\t\tfor (const [index, endpoint] of this.endpoints) {\n\t\t\t\tlet ep: Record<string, any> = {};\n\n\t\t\t\tcloneAssociationMap(ep, endpoint.associations);\n\t\t\t\tcloneParamInformationMap(ep, endpoint.paramInformation);\n\n\t\t\t\tep = sortObject(ep);\n\n\t\t\t\tif (Object.keys(ep).length > 0) {\n\t\t\t\t\thashable.endpoints ??= {};\n\t\t\t\t\thashable.endpoints[index] = ep;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Clone proprietary config\n\t\tif (this.proprietary && Object.keys(this.proprietary).length > 0) {\n\t\t\thashable.proprietary = sortObject({ ...this.proprietary });\n\t\t}\n\n\t\t// Clone relevant compat flags\n\t\tif (this.compat) {\n\t\t\tlet c: Record<string, any> = {};\n\n\t\t\t// Copy some simple flags over\n\t\t\tfor (\n\t\t\t\tconst prop of [\n\t\t\t\t\t\"forceSceneControllerGroupCount\",\n\t\t\t\t\t\"mapRootReportsToEndpoint\",\n\t\t\t\t\t\"mapBasicSet\",\n\t\t\t\t\t\"preserveRootApplicationCCValueIDs\",\n\t\t\t\t\t\"preserveEndpoints\",\n\t\t\t\t\t\"removeEndpoints\",\n\t\t\t\t\t\"treatMultilevelSwitchSetAsEvent\",\n\t\t\t\t] as const\n\t\t\t) {\n\t\t\t\tif (this.compat[prop] != undefined) {\n\t\t\t\t\tc[prop] = this.compat[prop];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Copy other, more complex flags\n\t\t\tif (this.compat.overrideQueries) {\n\t\t\t\tc.overrideQueries = Object.fromEntries(\n\t\t\t\t\tthis.compat.overrideQueries[\"overrides\"],\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (this.compat.addCCs) {\n\t\t\t\tc.addCCs = Object.fromEntries(\n\t\t\t\t\t[...this.compat.addCCs].map(([ccId, def]) => [\n\t\t\t\t\t\tccId,\n\t\t\t\t\t\tObject.fromEntries(def.endpoints),\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (this.compat.removeCCs) {\n\t\t\t\tc.removeCCs = Object.fromEntries(this.compat.removeCCs);\n\t\t\t}\n\t\t\tif (this.compat.treatSetAsReport) {\n\t\t\t\tc.treatSetAsReport = [...this.compat.treatSetAsReport]\n\t\t\t\t\t.toSorted();\n\t\t\t}\n\n\t\t\tc = sortObject(c);\n\t\t\tif (Object.keys(c).length > 0) {\n\t\t\t\thashable.compat = c;\n\t\t\t}\n\t\t}\n\n\t\tif (version >= 2) {\n\t\t\t// From version 2 and on, we ignore labels and descriptions, and load them dynamically\n\t\t\tfor (\n\t\t\t\tconst ep of Object.values<Record<string, any>>(\n\t\t\t\t\thashable.endpoints ?? {},\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tfor (const param of ep.paramInformation ?? []) {\n\t\t\t\t\tdelete param.label;\n\t\t\t\t\tdelete param.description;\n\t\t\t\t\tfor (const opt of param.options ?? []) {\n\t\t\t\t\t\tdelete opt.label;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (version < 3) {\n\t\t\t// Version 3 added the `allowed` field. When targeting older versions\n\t\t\t// and the allowed field only has a single range, replace it with\n\t\t\t// minValue/maxValue for compatibility\n\t\t\tfor (\n\t\t\t\tconst ep of Object.values<Record<string, any>>(\n\t\t\t\t\thashable.endpoints ?? {},\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tfor (const param of ep.paramInformation ?? []) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tisArray(param.allowed)\n\t\t\t\t\t\t&& param.allowed.length === 1\n\t\t\t\t\t\t&& isObject(param.allowed[0])\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst allowed = param.allowed[0] as Record<string, any>;\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\ttypeof allowed.from === \"number\"\n\t\t\t\t\t\t\t&& typeof allowed.to === \"number\"\n\t\t\t\t\t\t\t&& (allowed.step == undefined || allowed.step === 1)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tparam.minValue = allowed.from;\n\t\t\t\t\t\t\tparam.maxValue = allowed.to;\n\t\t\t\t\t\t\tdelete param.allowed;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\thashable = sortObject(hashable);\n\t\treturn hashable;\n\t}\n\n\t/**\n\t * Returns a hash code that can be used to check whether a device config has changed enough to require a re-interview.\n\t */\n\tpublic async getHash(\n\t\tversion: DeviceConfigHashVersion = DeviceConfig.maxHashVersion,\n\t): Promise<BytesView> {\n\t\t// Figure out what to hash\n\t\tconst hashable = this.getHashable(version);\n\n\t\t// And create a \"hash\" from it. Older versions used a non-cryptographic hash,\n\t\t// newer versions compress a subset of the config file.\n\t\tlet hash: BytesView;\n\t\tif (version === 0) {\n\t\t\tconst buffer = Bytes.from(JSON.stringify(hashable), \"utf8\");\n\t\t\treturn await digest(\"md5\", buffer);\n\t\t} else if (version === 1) {\n\t\t\tconst buffer = Bytes.from(JSON.stringify(hashable), \"utf8\");\n\t\t\treturn await digest(\"sha-256\", buffer);\n\t\t} else {\n\t\t\thash = deflateSync(\n\t\t\t\tBytes.from(JSON.stringify(hashable), \"utf8\"),\n\t\t\t\t// Try to make the hash as small as possible\n\t\t\t\t{ level: 9, dictionary: deflateDict },\n\t\t\t);\n\t\t}\n\n\t\t// Version the hash from v2 onwards, so we can change the format in the future\n\t\tconst prefixBytes = Bytes.from(`$v${version}$`, \"utf8\");\n\t\treturn Bytes.concat([prefixBytes, hash]);\n\t}\n\n\tpublic static get maxHashVersion(): 3 {\n\t\treturn 3;\n\t}\n\n\tpublic static areHashesEqual(hash: BytesView, other: BytesView): boolean {\n\t\tconst parsedHash = parseHash(hash);\n\t\tconst parsedOther = parseHash(other);\n\t\t// If one of the hashes could not be parsed, they are not equal\n\t\tif (!parsedHash || !parsedOther) return false;\n\n\t\t// For legacy hashes, we only compare the hash data. We already make sure during\n\t\t// parsing of the cache files that we only need to compare hashes of the same version,\n\t\t// so simply comparing the contents is sufficient.\n\t\tif (parsedHash.version < 2 && parsedOther.version < 2) {\n\t\t\treturn Bytes.view(parsedHash.hashData).equals(parsedOther.hashData);\n\t\t}\n\t\t// We take care during loading to downlevel the current config hash to legacy versions if needed.\n\t\t// If we end up with just one legacy hash here, something went wrong. Just bail in that case.\n\t\tif (parsedHash.version < 2 || parsedOther.version < 2) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// This is a versioned hash. If both versions are equal, it's simple - just compare the hash data\n\t\tif (parsedHash.version === parsedOther.version) {\n\t\t\treturn Bytes.view(parsedHash.hashData).equals(parsedOther.hashData);\n\t\t}\n\n\t\t// For different versions, we have to do some case by case checks. For example, a newer hash version\n\t\t// might remove or add data into the hashable, so we cannot simply convert between versions easily.\n\t\t// Implement when that is actually needed.\n\t\treturn false;\n\t}\n}\n\nfunction parseHash(hash: BytesView): {\n\tversion: number;\n\thashData: BytesView;\n} | undefined {\n\tconst hashString = Bytes.view(hash).toString(\"utf8\");\n\tconst versionMatch = hashString.match(/^\\$v(\\d+)\\$/);\n\tif (versionMatch) {\n\t\t// This is a versioned hash\n\t\tconst version = parseInt(versionMatch[1], 10);\n\t\tconst hashData = hash.subarray(\n\t\t\t// The prefix is ASCII, so this is safe to do even in the context of UTF-8\n\t\t\tversionMatch[0].length,\n\t\t);\n\t\treturn {\n\t\t\tversion,\n\t\t\thashData,\n\t\t};\n\t}\n\n\t// This is probably an unversioned legacy hash\n\tswitch (hash.length) {\n\t\tcase 16: // MD5\n\t\t\treturn {\n\t\t\t\tversion: 0,\n\t\t\t\thashData: hash,\n\t\t\t};\n\t\tcase 32: // SHA-256\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\thashData: hash,\n\t\t\t};\n\t\tdefault:\n\t\t\t// This is not a valid hash\n\t\t\treturn undefined;\n\t}\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;AAAA,wBAA0B;AAC1B,kBAKO;AACP,oBAeO;AAMP,wBAAkC;AAClC,mBAAkB;AAClB,mBAAiB;AACjB,gBAAqB;AACrB,0BAAyD;AAEzD,wBAAuD;AACvD,+BAGO;AACP,0BAA2D;AAC3D,6BAAgD;AAChD,kCAGO;AACP,4BAGO;AACP,4BAGO;AACP,8BAKO;AACP,yBAAyD;AA0BlD,MAAM,qBAAqB,aAAAA,QAAK,KAAK,6BAAW,SAAS;AAChE,MAAM,oBAAoB,aAAAA,QAAK,KAAK,oBAAoB,qBAAqB;AAEvE,SAAU,gBAAgBC,YAAiB;AAIhD,QAAM,aAAa,aAAAD,QAAK,KAAKC,YAAW,SAAS;AACjD,QAAM,YAAY,aAAAD,QAAK,KAAK,YAAY,YAAY;AACpD,SAAO,EAAE,YAAY,UAAS;AAC/B;AAPgB;AAYhB,eAAe,sBACd,IACA,aACA,KACA,YAAgB;AAIhB,QAAM,eAAe,MAAM,GAAG,QAAQ,GAAG;AACzC,aAAW,KAAK,cAAc;AAC7B,UAAM,WAAW,aAAAA,QAAK,KAAK,KAAK,CAAC;AAEjC,UAAM,OAAO,MAAM,GAAG,KAAK,QAAQ;AACnC,SACE,QAAQ,eAAe,MAAM,kBAC1B,KAAK,OAAM,KAAM,KAAK,YAAW,MAClC,KAAK,QAAQ,YACf;AACD,aAAO;IACR,WAAW,KAAK,YAAW,GAAI;AAE9B,UACC,MAAM,sBACL,IACA,aACA,UACA,UAAU,GAEV;AACD,eAAO;MACR;IACD;EACD;AACA,SAAO;AACR;AAlCe;AAwCf,eAAe,cACd,IACA,YACA,YACA,qBACA,QAAqB;AAErB,QAAM,QAAwD,CAAA;AAE9D,8CAAkB;AAClB,QAAM,cAAc,UAAM,kCACzB,IACA,YACA,CAAC,SACA,KAAK,SAAS,OAAO,KAClB,CAAC,KAAK,SAAS,YAAY,KAC3B,CAAC,KAAK,SAAS,aAAa,KAC5B,CAAC,KAAK,SAAS,eAAe,CAAC;AAIpC,QAAM,eAAe,eAAe,qBACjC,CAAC,kBAAkB,IACnB;AAEH,aAAW,QAAQ,aAAa;AAC/B,UAAM,eAAe,aAAAA,QACnB,SAAS,YAAY,IAAI,EACzB,WAAW,MAAM,GAAG;AAEtB,QAAI;AACH,YAAM,SAAS,MAAM,aAAa,KACjC,IACA,MACA,YACA;QACC,SAAS;QACT;QACA,UAAU;OACV;AAGF,YAAM,KACL,GAAG,oBAAoB,MAAM,EAAE,IAAI,CAAC,UAAS;AAC5C,cAAM,MAAkD;UACvD,GAAG;UACH,UAAU;;AAGX,YAAI,eAAe,oBAAoB;AACtC,cAAI,UAAU;QACf;AACA,eAAO;MACR,CAAC,CAAC;IAEJ,SAAS,GAAG;AACX,YAAM,UAAU,6BAA6B,YAAY,KACvD,EAAY,OACd;AAGA,UAAI,QAAQ,IAAI,aAAa,UAAU,CAAC,KAAC,sBAAO,IAAI,GAAG;AACtD,cAAM,IAAI,uBAAW,SAAS,4BAAgB,cAAc;MAC7D,OAAO;AACN,gBAAQ,MAAM,SAAS,OAAO;MAC/B;IACD;EACD;AAEA,SAAO;AACR;AAtEe;AAwEf,eAAe,sBACd,IACA,YACA,WACA,qBACA,QAAqB;AAGrB,MAAI,cAAc,CAAE,UAAM,0BAAW,IAAI,SAAS;AAClD,MAAI;AACJ,MAAI;AAEJ,MAAI,CAAC,aAAa;AACjB,QAAI;AACH,YAAM,eAAe,UAAM,4BAAa,IAAI,WAAW,MAAM;AAC7D,cAAQ,aAAAE,QAAM,MAAM,YAAY;AAChC,oBAAc,MAAM,GAAG,KAAK,SAAS,GAAG;IACzC,QAAQ;AACP,cAAQ,MACP,oDACA,MAAM;AAEP,oBAAc;IACf;AACC,UAAI,CAAC,OAAO;AACX,gBAAQ,MACP,8CACA,MAAM;AAEP,sBAAc;MACf;IACD;EACD;AAGA,MAAI,CAAC,aAAa;AACjB,kBAAc,MAAM,sBACnB,IACA,YACA,YACA,UAAW;AAEZ,QAAI,aAAa;AAChB,cAAQ,MACP,sEACA,SAAS;IAEX;EACD;AAEA,MAAI,aAAa;AAEhB,YAAQ,MAAM,cACb,IACA,YACA,MACA,qBACA,MAAM;AAGP,QAAI;AACH,gBAAM,6BACL,IACA,aAAAF,QAAK,KAAK,SAAS,GACnB;MACF,yBAAU,OAAO,GAAI,CAAC;GAEpB,MAAM;AAEP,cAAQ,MAAM,4BAA4B,SAAS;IACpD,SAAS,GAAG;AACX,cAAQ,MACP,4CACE,EAAY,OACd,IACA,OAAO;IAET;EACD;AAEA,SAAO;AACR;AAjFe;AAwFf,eAAsB,4BACrB,IACA,yBACA,QAAqB;AAErB,UACC,MAAM,cACL,IACA,yBACA,OACA,CAAC,WACA,OAAO,QAAQ,IAAI,CAAC,SAAS;IAC5B,oBAAgB,wBACf,OAAO,eAAe,SAAS,EAAE,CAAC;IAEnC,cAAc,OAAO;IACrB,OAAO,OAAO;IACd,iBAAa,wBAAS,IAAI,WAAW;IACrC,eAAW,wBAAS,IAAI,SAAS;IACjC,iBAAiB,OAAO;IACxB,GAAI,OAAO,YAAY,EAAE,WAAW,KAAa,IAAK,CAAA;IACtD,SAAS;IACR,GACH,MAAM,GAEN,IAAI,CAAC,EAAE,UAAU,GAAG,MAAK,OAAQ;IAClC,GAAG;;;IAGH,UAAU,aAAAA,QAAK,KAAK,yBAAyB,QAAQ;IACpD;AACH;AA/BsB;AAsCtB,eAAsB,wBACrB,IACA,QACA,mBAA0B;AAE1B,QAAM,EAAE,YAAY,UAAS,IAAK,gBACjC,qBAAqB,2BAAS;AAG/B,SAAO,sBACN,IACA,YACA,WACA,CAAC,WACA,OAAO,QAAQ,IAAI,CAAC,SAAS;IAC5B,oBAAgB,wBAAS,OAAO,eAAe,SAAS,EAAE,CAAC;IAC3D,cAAc,OAAO;IACrB,OAAO,OAAO;IACd,iBAAa,wBAAS,IAAI,WAAW;IACrC,eAAW,wBAAS,IAAI,SAAS;IACjC,iBAAiB,OAAO;IACxB,GAAI,OAAO,YAAY,EAAE,WAAW,KAAa,IAAK,CAAA;IACrD,GACH,MAAM;AAER;AAzBsB;AAgCtB,eAAsB,gCACrB,IACA,QAAqB;AAGrB,SAAO,sBACN,IACA,oBACA,mBACA,CAAC,WACA,OAAO,QAAQ,IAAI,CAAC,SAAS;IAC5B,oBAAgB,wBAAS,OAAO,eAAe,SAAS,EAAE,CAAC;IAC3D,cAAc,OAAO;IACrB,OAAO,OAAO;IACd,aAAa,OAAO;IACpB,iBAAa,wBAAS,IAAI,WAAW;IACrC,eAAW,wBAAS,IAAI,SAAS;IACjC,iBAAiB,OAAO;IACxB,GAAI,OAAO,YAAY,EAAE,WAAW,KAAa,IAAK,CAAA;IACtD,SAAS;IACR,GACH,MAAM;AAER;AAvBsB;AAyBtB,SAAS,oBAAoB,KAAQ;AACpC,SAAO,OAAO,QAAQ,YAAY,qCAAmB,KAAK,GAAG;AAC9D;AAFS;AAIT,MAAM,uBAAuB;AAC7B,SAAS,kBAAkB,KAAQ;AAClC,SACC,OAAO,QAAQ,YACZ,qBAAqB,KAAK,GAAG,KAC7B,IACD,MAAM,GAAG,EACT,IAAI,CAAC,QAAQ,SAAS,KAAK,EAAE,CAAC,EAC9B,MAAM,CAAC,QAAQ,OAAO,KAAK,OAAO,GAAG;AAEzC;AATS;AAWT,MAAM,cAAc,oBAAM;;;;EAIzB;IACC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAEC,KAAK,EAAE;EACT;AAAM;AAID,MAAO,wBAAuB;EAhcpC,OAgcoC;;;EAC5B,aAAa,KACnB,IACA,UACA,YACA,SAIC;AAED,UAAM,EAAE,UAAU,QAAO,IAAK;AAE9B,UAAM,eAAe,WAClB,aAAAA,QAAK,SAAS,SAAS,QAAQ,EAAE,WAAW,MAAM,GAAG,IACrD;AACH,UAAM,OAAO,UAAM,0CAClB,IACA,UACA;MACC,QAAQ;MACR,GAAI,QAAQ,gBAAgB,CAAA;KAC5B;AAEF,WAAO,IAAI,wBAAwB,cAAc,YAAY,IAAI;EAClE;EAEA,YACC,UACA,YACA,YAAsB;AAEtB,SAAK,WAAW;AAChB,SAAK,aAAa;AAElB,QAAI,CAAC,oBAAoB,WAAW,cAAc,GAAG;AACpD,gDACC,UACA,kCAAkC,QAAQ;qEACuB;IAEnE;AACA,SAAK,iBAAiB,SAAS,WAAW,gBAAgB,EAAE;AAE5D,eAAW,QAAQ,CAAC,gBAAgB,SAAS,aAAa,GAAY;AACrE,WAAK,IAAI,QAAI,uDACZ,UACA,UACA,MACA,WAAW,IAAI,CAAC;IAElB;AAEA,QACC,KAAC,2BAAQ,WAAW,OAAO,KACxB,CAAE,WAAW,QAAkB,MACjC,CAAC,YACA,4BAAS,GAAG,KACT,oBAAoB,IAAI,WAAW,KACnC,oBAAoB,IAAI,SAAS,CAAC,GAEtC;AACD,gDACC,UACA,kCAAkC,QAAQ;wFAC0C;IAEtF;AACA,SAAK,UAAW,WAAW,QAAkB,IAC5C,CAAC,EAAE,aAAa,UAAS,OAAQ;MAChC,aAAa,SAAS,aAAa,EAAE;MACrC,WAAW,SAAS,WAAW,EAAE;MAChC;AAGH,QACC,KAAC,4BAAS,WAAW,eAAe,KACjC,CAAC,kBAAkB,WAAW,gBAAgB,GAAG,KACjD,CAAC,kBAAkB,WAAW,gBAAgB,GAAG,GACnD;AACD,gDACC,UACA,kCAAkC,QAAQ;+GACiE;IAE7G,OAAO;AACN,YAAM,EAAE,KAAK,IAAG,IAAK,WAAW;AAChC,cAAI,UAAAG,aAAS,0BAAW,GAAG,OAAG,0BAAW,GAAG,CAAC,GAAG;AAC/C,kDACC,UACA,kCAAkC,QAAQ;sBACzB,GAAG,iDAAiD,GAAG,EAAE;MAE5E;AACA,WAAK,kBAAkB,EAAE,KAAK,IAAG;IAClC;AAEA,QACC,WAAW,aAAa,UACrB,WAAW,cAAc,MAC3B;AACD,gDACC,UACA,kCAAkC,QAAQ;kCACZ;IAEhC;AACA,SAAK,YAAY,CAAC,CAAC,WAAW;AAE9B,QAAI,WAAW,aAAa,QAAW;AACtC,YAAM,YAAY,oBAAI,IAAG;AACzB,UAAI,KAAC,4BAAS,WAAW,SAAS,GAAG;AACpC,kDACC,UACA,kCAAkC,QAAQ;2BACpB;MAExB;AACA,iBAAW,CAAC,KAAK,EAAE,KAAK,OAAO,QAAQ,WAAW,SAAS,GAAG;AAC7D,YAAI,CAAC,QAAQ,KAAK,GAAG,GAAG;AACvB,oDACC,UACA,kCAAkC,QAAQ;oCACZ,GAAG,gBAAgB;QAEnD;AAEA,cAAM,UAAU,SAAS,KAAK,EAAE;AAChC,kBAAU,IACT,SACA,IAAI,gDAA0B,MAAM,SAAS,EAAS,CAAC;MAEzD;AACA,WAAK,YAAY;IAClB;AAEA,QAAI,WAAW,gBAAgB,QAAW;AACzC,YAAM,eAAe,oBAAI,IAAG;AAI5B,UAAI,KAAC,4BAAS,WAAW,YAAY,GAAG;AACvC,kDACC,UACA,kCAAkC,QAAQ;8BACjB;MAE3B;AACA,iBACO,CAAC,KAAK,eAAe,KAAK,OAAO,QACtC,WAAW,YAAY,GAEvB;AACD,YAAI,CAAC,gBAAgB,KAAK,GAAG,GAAG;AAC/B,oDACC,UACA,kCAAkC,QAAQ;8BAClB,GAAG,mBAAmB;QAEhD;AAEA,cAAM,SAAS,SAAS,KAAK,EAAE;AAC/B,qBAAa,IACZ,QACA,IAAI,sDACH,UACA,QACA,eAAsB,CACtB;MAEH;AACA,WAAK,eAAe;IACrB;AAEA,QAAI,WAAW,oBAAoB,QAAW;AAC7C,WAAK,uBAAmB,6DACvB,YACA,IAAI;IAEN;AAEA,QAAI,WAAW,eAAe,QAAW;AACxC,UAAI,KAAC,4BAAS,WAAW,WAAW,GAAG;AACtC,kDACC,UACA,kCAAkC,QAAQ;6BAClB;MAE1B;AACA,WAAK,cAAc,WAAW;IAC/B;AAEA,QAAI,WAAW,UAAU,QAAW;AACnC,cACC,2BAAQ,WAAW,MAAM,KACtB,WAAW,OAAO,MAAM,CAAC,aAAc,4BAAS,IAAI,CAAC,GACvD;AAED,mBAAW,SAAS,WAAW,QAAQ;AACtC,wDACC,UACA,OACA,0CAA0C;QAE5C;AAEA,aAAK,SAAS,WAAW,OAAO,IAC/B,CAAC,SAAc,IAAI,4CAAwB,UAAU,IAAI,CAAC;MAE5D,eAAW,4BAAS,WAAW,MAAM,GAAG;AACvC,aAAK,SAAS,IAAI,4CACjB,UACA,WAAW,MAAM;MAEnB,OAAO;AACN,kDACC,UACA,kCAAkC,QAAQ;6DACc;MAE1D;IACD;AAEA,QAAI,WAAW,YAAY,QAAW;AACrC,UAAI,KAAC,4BAAS,WAAW,QAAQ,GAAG;AACnC,kDACC,UACA,kCAAkC,QAAQ;0BACrB;MAEvB;AACA,WAAK,WAAW,IAAI,gDACnB,UACA,WAAW,QAAQ;IAErB;AAEA,QAAI,WAAW,UAAU,QAAW;AACnC,YAAM,SAAS,oBAAI,IAAG;AAItB,UAAI,KAAC,4BAAS,WAAW,MAAM,GAAG;AACjC,kDACC,UACA,kCAAkC,QAAQ;wBACvB;MAErB;AACA,iBACO,CAAC,KAAK,eAAe,KAAK,OAAO,QACtC,WAAW,MAAM,GAEjB;AACD,YAAI,CAAC,gBAAgB,KAAK,GAAG,GAAG;AAC/B,oDACC,UACA,kCAAkC,QAAQ;oBAC5B,GAAG,kDAAkD;QAErE;AAEA,cAAM,SAAS,SAAS,KAAK,EAAE;AAC/B,YAAI,SAAS,KAAK,SAAS,KAAK;AAC/B,oDACC,UACA,kCAAkC,QAAQ;eACjC,MAAM,4BAA4B;QAE7C;AAEA,eAAO,IACN,QACA,IAAI,0CACH,UACA,QACA,eAAsB,CACtB;MAEH;AACA,WAAK,SAAS;IACf;EACD;EAEgB;EAEA;EACA;EACA;EACA;EACA;EAIA;;EAEA;EACA;EACA;EAIA;EACA;;;;;EAKA;;EAEA;;EAIA;;EAGA;EAET,SAAS,UAAmB;AAClC,WAAO,IAAI,aACV,KAAK,UACL,KAAK,gBACL,qCAAa,KAAK,cAAc,QAAQ,GACxC,KAAK,oBACL,qCAAa,KAAK,OAAO,QAAQ,OACjC,qCAAa,KAAK,aAAa,QAAQ,GACvC,KAAK,SACL,KAAK,iBACL,KAAK,eACL,qCAAa,KAAK,WAAW,QAAQ,OACrC,qCAAa,KAAK,cAAc,QAAQ,OACxC,qCAAa,KAAK,QAAQ,QAAQ,OAClC,qCAAa,KAAK,kBAAkB,QAAQ,GAC5C,KAAK,iBACL,qCAAa,KAAK,QAAQ,QAAQ,OAClC,qCAAa,KAAK,UAAU,QAAQ,CAAC;EAEvC;;AAKK,MAAO,aAAY;EAvxBzB,OAuxByB;;;EACjB,aAAa,KACnB,IACA,UACA,YACA,SAKC;AAED,UAAM,MAAM,MAAM,wBAAwB,KACzC,IACA,UACA,YACA,OAAO;AAER,WAAO,IAAI,SAAS,QAAQ,QAAQ;EACrC;EAEA,YACC,UACA,YACA,cACA,gBACA,OACA,aACA,SAIA,iBACA,WACA,WACA,cACA,QACA,kBACA,aACA,QACA,UAAyB;AAEzB,SAAK,WAAW;AAChB,SAAK,aAAa;AAClB,SAAK,eAAe;AACpB,SAAK,iBAAiB;AACtB,SAAK,QAAQ;AACb,SAAK,cAAc;AACnB,SAAK,UAAU;AACf,SAAK,kBAAkB;AACvB,SAAK,YAAY;AACjB,SAAK,YAAY;AACjB,SAAK,eAAe;AACpB,SAAK,SAAS;AACd,SAAK,mBAAmB;AACxB,SAAK,cAAc;AACnB,SAAK,SAAS;AACd,SAAK,WAAW;EACjB;EAEgB;;EAEA;EACA;EACA;EACA;EACA;EACA;EAIA;;EAEA;EACA;EACA;EACA;EACA;;;;;EAKA;;EAEA;;EAEA;;EAGT,gCACN,eACA,OAAa;AAEb,QAAI,kBAAkB,GAAG;AAExB,aACC,KAAK,cAAc,IAAI,KAAK,KACxB,KAAK,WAAW,IAAI,CAAC,GAAG,cAAc,IAAI,KAAK;IAErD,OAAO;AAEN,aAAO,KAAK,WAAW,IAAI,aAAa,GAAG,cAAc,IAAI,KAAK;IACnE;EACD;EAEQ,YAAY,SAAgC;AAMnD,QAAI,WAAgC;;;;;;;;AASpC,UAAM,aAAa,wBAAC,QAA4B;AAC/C,YAAM,MAA2B,CAAA;AACjC,iBAAW,OAAO,OAAO,KAAK,GAAG,EAAE,SAAQ,GAAI;AAC9C,YAAI,GAAG,IAAI,IAAI,GAAG;MACnB;AACA,aAAO;IACR,GANmB;AAQnB,UAAM,yBAAyB,wBAAC,MAAwB;AACvD,aAAO,eACN,oBAAK,GAAG,CAAC,YAAY,gBAAgB,YAAY,CAAC,CAAC;IAErD,GAJ+B;AAK/B,UAAM,sBAAsB,wBAC3B,QACA,QACG;AACH,UAAI,CAAC,OAAO,CAAC,IAAI;AAAM;AACvB,aAAO,eAAe,CAAA;AACtB,iBAAW,CAAC,KAAK,KAAK,KAAK,KAAK;AAC/B,eAAO,aAAa,GAAG,IAAI,uBAAuB,KAAK;MACxD;AACA,aAAO,eAAe,WAAW,OAAO,YAAY;IACrD,GAV4B;AAY5B,UAAM,2BAA2B,wBAChC,QACA,QACG;AACH,UAAI,CAAC,OAAO,CAAC,IAAI;AAAM;AACvB,YAAM,cAAc,wBAAC,UACpB,GAAG,MAAM,eAAe,GACvB,MAAM,eAAe,QAAI,uBAAQ,MAAM,YAAY,CAAC,MAAM,EAC3D,IAHmB;AAIpB,aAAO,mBAAmB,CAAC,GAAG,IAAI,OAAM,CAAE,EACxC,SAAS,CAAC,GAAG,MACb,YAAY,CAAC,EAAE,cAAc,YAAY,CAAC,CAAC,CAAC,EAE5C,IAAI,CAAC,UAAM,yBAAU,CAAC,CAAC;IAC1B,GAdiC;AAiBjC;AACC,UAAI,MAA2B,CAAA;AAC/B,0BAAoB,KAAK,KAAK,YAAY;AAC1C,+BAAyB,KAAK,KAAK,gBAAgB;AACnD,YAAM,WAAW,GAAG;AAEpB,UAAI,OAAO,KAAK,GAAG,EAAE,SAAS,GAAG;AAChC,iBAAS,cAAc,CAAA;AACvB,iBAAS,UAAU,CAAC,IAAI;MACzB;IACD;AAEA,QAAI,KAAK,WAAW;AACnB,iBAAW,CAAC,OAAO,QAAQ,KAAK,KAAK,WAAW;AAC/C,YAAI,KAA0B,CAAA;AAE9B,4BAAoB,IAAI,SAAS,YAAY;AAC7C,iCAAyB,IAAI,SAAS,gBAAgB;AAEtD,aAAK,WAAW,EAAE;AAElB,YAAI,OAAO,KAAK,EAAE,EAAE,SAAS,GAAG;AAC/B,mBAAS,cAAc,CAAA;AACvB,mBAAS,UAAU,KAAK,IAAI;QAC7B;MACD;IACD;AAGA,QAAI,KAAK,eAAe,OAAO,KAAK,KAAK,WAAW,EAAE,SAAS,GAAG;AACjE,eAAS,cAAc,WAAW,EAAE,GAAG,KAAK,YAAW,CAAE;IAC1D;AAGA,QAAI,KAAK,QAAQ;AAChB,UAAI,IAAyB,CAAA;AAG7B,iBACO,QAAQ;QACb;QACA;QACA;QACA;QACA;QACA;QACA;SAEA;AACD,YAAI,KAAK,OAAO,IAAI,KAAK,QAAW;AACnC,YAAE,IAAI,IAAI,KAAK,OAAO,IAAI;QAC3B;MACD;AAGA,UAAI,KAAK,OAAO,iBAAiB;AAChC,UAAE,kBAAkB,OAAO,YAC1B,KAAK,OAAO,gBAAgB,WAAW,CAAC;MAE1C;AACA,UAAI,KAAK,OAAO,QAAQ;AACvB,UAAE,SAAS,OAAO,YACjB,CAAC,GAAG,KAAK,OAAO,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM;UAC5C;UACA,OAAO,YAAY,IAAI,SAAS;SAChC,CAAC;MAEJ;AACA,UAAI,KAAK,OAAO,WAAW;AAC1B,UAAE,YAAY,OAAO,YAAY,KAAK,OAAO,SAAS;MACvD;AACA,UAAI,KAAK,OAAO,kBAAkB;AACjC,UAAE,mBAAmB,CAAC,GAAG,KAAK,OAAO,gBAAgB,EACnD,SAAQ;MACX;AAEA,UAAI,WAAW,CAAC;AAChB,UAAI,OAAO,KAAK,CAAC,EAAE,SAAS,GAAG;AAC9B,iBAAS,SAAS;MACnB;IACD;AAEA,QAAI,WAAW,GAAG;AAEjB,iBACO,MAAM,OAAO,OAClB,SAAS,aAAa,CAAA,CAAE,GAExB;AACD,mBAAW,SAAS,GAAG,oBAAoB,CAAA,GAAI;AAC9C,iBAAO,MAAM;AACb,iBAAO,MAAM;AACb,qBAAW,OAAO,MAAM,WAAW,CAAA,GAAI;AACtC,mBAAO,IAAI;UACZ;QACD;MACD;IACD;AAEA,QAAI,UAAU,GAAG;AAIhB,iBACO,MAAM,OAAO,OAClB,SAAS,aAAa,CAAA,CAAE,GAExB;AACD,mBAAW,SAAS,GAAG,oBAAoB,CAAA,GAAI;AAC9C,kBACC,2BAAQ,MAAM,OAAO,KAClB,MAAM,QAAQ,WAAW,SACzB,4BAAS,MAAM,QAAQ,CAAC,CAAC,GAC3B;AACD,kBAAM,UAAU,MAAM,QAAQ,CAAC;AAC/B,gBACC,OAAO,QAAQ,SAAS,YACrB,OAAO,QAAQ,OAAO,aACrB,QAAQ,QAAQ,UAAa,QAAQ,SAAS,IACjD;AACD,oBAAM,WAAW,QAAQ;AACzB,oBAAM,WAAW,QAAQ;AACzB,qBAAO,MAAM;YACd;UACD;QACD;MACD;IACD;AAEA,eAAW,WAAW,QAAQ;AAC9B,WAAO;EACR;;;;EAKO,MAAM,QACZ,UAAmC,aAAa,gBAAc;AAG9D,UAAM,WAAW,KAAK,YAAY,OAAO;AAIzC,QAAI;AACJ,QAAI,YAAY,GAAG;AAClB,YAAM,SAAS,oBAAM,KAAK,KAAK,UAAU,QAAQ,GAAG,MAAM;AAC1D,aAAO,UAAM,oBAAO,OAAO,MAAM;IAClC,WAAW,YAAY,GAAG;AACzB,YAAM,SAAS,oBAAM,KAAK,KAAK,UAAU,QAAQ,GAAG,MAAM;AAC1D,aAAO,UAAM,oBAAO,WAAW,MAAM;IACtC,OAAO;AACN,iBAAO;QACN,oBAAM,KAAK,KAAK,UAAU,QAAQ,GAAG,MAAM;;QAE3C,EAAE,OAAO,GAAG,YAAY,YAAW;MAAE;IAEvC;AAGA,UAAM,cAAc,oBAAM,KAAK,KAAK,OAAO,KAAK,MAAM;AACtD,WAAO,oBAAM,OAAO,CAAC,aAAa,IAAI,CAAC;EACxC;EAEO,WAAW,iBAAc;AAC/B,WAAO;EACR;EAEO,OAAO,eAAe,MAAiB,OAAgB;AAC7D,UAAM,aAAa,UAAU,IAAI;AACjC,UAAM,cAAc,UAAU,KAAK;AAEnC,QAAI,CAAC,cAAc,CAAC;AAAa,aAAO;AAKxC,QAAI,WAAW,UAAU,KAAK,YAAY,UAAU,GAAG;AACtD,aAAO,oBAAM,KAAK,WAAW,QAAQ,EAAE,OAAO,YAAY,QAAQ;IACnE;AAGA,QAAI,WAAW,UAAU,KAAK,YAAY,UAAU,GAAG;AACtD,aAAO;IACR;AAGA,QAAI,WAAW,YAAY,YAAY,SAAS;AAC/C,aAAO,oBAAM,KAAK,WAAW,QAAQ,EAAE,OAAO,YAAY,QAAQ;IACnE;AAKA,WAAO;EACR;;AAGD,SAAS,UAAU,MAAe;AAIjC,QAAM,aAAa,oBAAM,KAAK,IAAI,EAAE,SAAS,MAAM;AACnD,QAAM,eAAe,WAAW,MAAM,aAAa;AACnD,MAAI,cAAc;AAEjB,UAAM,UAAU,SAAS,aAAa,CAAC,GAAG,EAAE;AAC5C,UAAM,WAAW,KAAK;;MAErB,aAAa,CAAC,EAAE;IAAM;AAEvB,WAAO;MACN;MACA;;EAEF;AAGA,UAAQ,KAAK,QAAQ;IACpB,KAAK;AACJ,aAAO;QACN,SAAS;QACT,UAAU;;IAEZ,KAAK;AACJ,aAAO;QACN,SAAS;QACT,UAAU;;IAEZ;AAEC,aAAO;EACT;AACD;AAnCS;",
|
|
6
6
|
"names": ["path", "configDir", "JSON5", "semverGt"]
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AllowedConfigValue } from "@zwave-js/core";
|
|
1
2
|
import { type JSONObject, type ReadonlyObjectKeyMap } from "@zwave-js/shared";
|
|
2
3
|
import { type ConditionalItem } from "./ConditionalItem.js";
|
|
3
4
|
import type { ConditionalDeviceConfig } from "./DeviceConfig.js";
|
|
@@ -10,6 +11,7 @@ export declare class ConditionalParamInformation implements ConditionalItem<Para
|
|
|
10
11
|
readonly label: string;
|
|
11
12
|
readonly description?: string;
|
|
12
13
|
readonly valueSize: number;
|
|
14
|
+
readonly allowed?: readonly AllowedConfigValue[];
|
|
13
15
|
readonly minValue?: number;
|
|
14
16
|
readonly maxValue?: number;
|
|
15
17
|
readonly unsigned?: boolean;
|
|
@@ -25,10 +27,11 @@ export declare class ConditionalParamInformation implements ConditionalItem<Para
|
|
|
25
27
|
readonly condition?: string;
|
|
26
28
|
evaluateCondition(deviceId?: DeviceID): ParamInformation | undefined;
|
|
27
29
|
}
|
|
28
|
-
export type ParamInformation = Omit<ConditionalParamInformation, "condition" | "evaluateCondition" | "options" | "minValue" | "maxValue"> & {
|
|
30
|
+
export type ParamInformation = Omit<ConditionalParamInformation, "condition" | "evaluateCondition" | "options" | "minValue" | "maxValue" | "allowed"> & {
|
|
29
31
|
options: readonly ConfigOption[];
|
|
30
32
|
minValue: NonNullable<ConditionalParamInformation["minValue"]>;
|
|
31
33
|
maxValue: NonNullable<ConditionalParamInformation["maxValue"]>;
|
|
34
|
+
allowed: NonNullable<ConditionalParamInformation["allowed"]>;
|
|
32
35
|
};
|
|
33
36
|
export declare class ConditionalConfigOption implements ConditionalItem<ConfigOption> {
|
|
34
37
|
readonly value: number;
|
|
@@ -117,14 +117,87 @@ Parameter #${parameterNumber}: options is malformed!`);
|
|
|
117
117
|
(0, import_utils_safe.throwInvalidConfig)("devices", `packages/config/config/devices/${parent.filename}:
|
|
118
118
|
Parameter #${parameterNumber} has a non-boolean property hidden`);
|
|
119
119
|
}
|
|
120
|
-
this.hidden = definition.hidden
|
|
120
|
+
this.hidden = definition.hidden;
|
|
121
|
+
if (definition.allowed != void 0) {
|
|
122
|
+
if (this.minValue != void 0 || this.maxValue != void 0) {
|
|
123
|
+
(0, import_utils_safe.throwInvalidConfig)("devices", `packages/config/config/devices/${parent.filename}:
|
|
124
|
+
Parameter #${parameterNumber}: "allowed" cannot be used together with "minValue" or "maxValue"!`);
|
|
125
|
+
}
|
|
126
|
+
if (this.allowManualEntry === false) {
|
|
127
|
+
(0, import_utils_safe.throwInvalidConfig)("devices", `packages/config/config/devices/${parent.filename}:
|
|
128
|
+
Parameter #${parameterNumber}: "allowed" cannot be used with "allowManualEntry: false"!`);
|
|
129
|
+
}
|
|
130
|
+
if (!(0, import_typeguards.isArray)(definition.allowed)) {
|
|
131
|
+
(0, import_utils_safe.throwInvalidConfig)("devices", `packages/config/config/devices/${parent.filename}:
|
|
132
|
+
Parameter #${parameterNumber}: "allowed" must be an array!`);
|
|
133
|
+
}
|
|
134
|
+
if (definition.allowed.length === 0) {
|
|
135
|
+
(0, import_utils_safe.throwInvalidConfig)("devices", `packages/config/config/devices/${parent.filename}:
|
|
136
|
+
Parameter #${parameterNumber}: "allowed" array must contain at least one entry!`);
|
|
137
|
+
}
|
|
138
|
+
const parsedValues = [];
|
|
139
|
+
for (let i = 0; i < definition.allowed.length; i++) {
|
|
140
|
+
const def = definition.allowed[i];
|
|
141
|
+
if (!(0, import_typeguards.isObject)(def)) {
|
|
142
|
+
(0, import_utils_safe.throwInvalidConfig)("devices", `packages/config/config/devices/${parent.filename}:
|
|
143
|
+
Parameter #${parameterNumber}: allowed[${i}] must be an object!`);
|
|
144
|
+
}
|
|
145
|
+
if ("value" in def) {
|
|
146
|
+
if (typeof def.value !== "number") {
|
|
147
|
+
(0, import_utils_safe.throwInvalidConfig)("devices", `packages/config/config/devices/${parent.filename}:
|
|
148
|
+
Parameter #${parameterNumber}: allowed[${i}].value must be a number!`);
|
|
149
|
+
}
|
|
150
|
+
parsedValues.push({ value: def.value });
|
|
151
|
+
} else if ("range" in def) {
|
|
152
|
+
if (!(0, import_typeguards.isArray)(def.range)) {
|
|
153
|
+
(0, import_utils_safe.throwInvalidConfig)("devices", `packages/config/config/devices/${parent.filename}:
|
|
154
|
+
Parameter #${parameterNumber}: allowed[${i}].range must be an array!`);
|
|
155
|
+
}
|
|
156
|
+
if (def.range.length !== 2) {
|
|
157
|
+
(0, import_utils_safe.throwInvalidConfig)("devices", `packages/config/config/devices/${parent.filename}:
|
|
158
|
+
Parameter #${parameterNumber}: allowed[${i}].range must have exactly 2 elements [from, to]!`);
|
|
159
|
+
}
|
|
160
|
+
const [from, to] = def.range;
|
|
161
|
+
const step = def.step;
|
|
162
|
+
if (typeof from !== "number" || typeof to !== "number") {
|
|
163
|
+
(0, import_utils_safe.throwInvalidConfig)("devices", `packages/config/config/devices/${parent.filename}:
|
|
164
|
+
Parameter #${parameterNumber}: allowed[${i}].range elements must be numbers!`);
|
|
165
|
+
}
|
|
166
|
+
if (step !== void 0 && typeof step !== "number") {
|
|
167
|
+
(0, import_utils_safe.throwInvalidConfig)("devices", `packages/config/config/devices/${parent.filename}:
|
|
168
|
+
Parameter #${parameterNumber}: allowed[${i}].step must be a number if provided!`);
|
|
169
|
+
}
|
|
170
|
+
parsedValues.push({
|
|
171
|
+
from,
|
|
172
|
+
to,
|
|
173
|
+
...step !== void 0 && { step }
|
|
174
|
+
});
|
|
175
|
+
} else {
|
|
176
|
+
(0, import_utils_safe.throwInvalidConfig)("devices", `packages/config/config/devices/${parent.filename}:
|
|
177
|
+
Parameter #${parameterNumber}: allowed[${i}] must have either "value" or "range" property!`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
this.allowed = parsedValues;
|
|
181
|
+
}
|
|
121
182
|
}
|
|
183
|
+
// IMPORTANT: Changes to these properties can affect the device config hash,
|
|
184
|
+
// which may prompt users to re-interview their devices.
|
|
185
|
+
//
|
|
186
|
+
// Whenever adding, removing or changing properties of this class,
|
|
187
|
+
// make sure that these changes do not cause the hash to change unintentionally.
|
|
188
|
+
// For example, adding a new optional property with a default value of `undefined`
|
|
189
|
+
// is fine, unless the constructor always sets it to a concrete value.
|
|
190
|
+
//
|
|
191
|
+
// In all other case, make sure to increase the `maxHashVersion` in DeviceConfig.ts
|
|
192
|
+
// and add appropriate, backwards-compatible handling for the new version
|
|
193
|
+
// in `areHashesEqual`
|
|
122
194
|
parent;
|
|
123
195
|
parameterNumber;
|
|
124
196
|
valueBitMask;
|
|
125
197
|
label;
|
|
126
198
|
description;
|
|
127
199
|
valueSize;
|
|
200
|
+
allowed;
|
|
128
201
|
minValue;
|
|
129
202
|
maxValue;
|
|
130
203
|
unsigned;
|
|
@@ -148,6 +221,7 @@ Parameter #${parameterNumber} has a non-boolean property hidden`);
|
|
|
148
221
|
"label",
|
|
149
222
|
"description",
|
|
150
223
|
"valueSize",
|
|
224
|
+
"allowed",
|
|
151
225
|
"minValue",
|
|
152
226
|
"maxValue",
|
|
153
227
|
"unsigned",
|
|
@@ -162,7 +236,21 @@ Parameter #${parameterNumber} has a non-boolean property hidden`);
|
|
|
162
236
|
]),
|
|
163
237
|
options: (0, import_ConditionalItem.evaluateDeep)(this.options, deviceId, true)
|
|
164
238
|
};
|
|
165
|
-
if (ret.
|
|
239
|
+
if (ret.allowed && ret.allowed.length > 0) {
|
|
240
|
+
let globalMin = Infinity;
|
|
241
|
+
let globalMax = -Infinity;
|
|
242
|
+
for (const def of ret.allowed) {
|
|
243
|
+
if ("value" in def) {
|
|
244
|
+
globalMin = Math.min(globalMin, def.value);
|
|
245
|
+
globalMax = Math.max(globalMax, def.value);
|
|
246
|
+
} else {
|
|
247
|
+
globalMin = Math.min(globalMin, def.from);
|
|
248
|
+
globalMax = Math.max(globalMax, def.to);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
ret.minValue = globalMin;
|
|
252
|
+
ret.maxValue = globalMax;
|
|
253
|
+
} else if (ret.minValue == void 0) {
|
|
166
254
|
if (ret.allowManualEntry === false && ret.options.length > 0) {
|
|
167
255
|
ret.minValue = Math.min(...ret.options.map((o) => o.value));
|
|
168
256
|
} else {
|
|
@@ -170,7 +258,7 @@ Parameter #${parameterNumber} has a non-boolean property hidden`);
|
|
|
170
258
|
Parameter #${this.parameterNumber} is missing required property "minValue"!`);
|
|
171
259
|
}
|
|
172
260
|
}
|
|
173
|
-
if (ret.maxValue == void 0) {
|
|
261
|
+
if (ret.allowed == void 0 && ret.maxValue == void 0) {
|
|
174
262
|
if (ret.allowManualEntry === false && ret.options.length > 0) {
|
|
175
263
|
ret.maxValue = Math.max(...ret.options.map((o) => o.value));
|
|
176
264
|
} else {
|
|
@@ -178,6 +266,9 @@ Parameter #${this.parameterNumber} is missing required property "minValue"!`);
|
|
|
178
266
|
Parameter #${this.parameterNumber} is missing required property "maxValue"!`);
|
|
179
267
|
}
|
|
180
268
|
}
|
|
269
|
+
if (ret.allowed == void 0) {
|
|
270
|
+
ret.allowed = [{ from: ret.minValue, to: ret.maxValue }];
|
|
271
|
+
}
|
|
181
272
|
return ret;
|
|
182
273
|
}
|
|
183
274
|
}
|