UNPKG

zwave-js

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

172 lines 7.81 kB
import { refreshConfigParamMetadataFromConfigFile } from "@zwave-js/cc/ConfigurationCC"; import { DeviceConfig, parseDeviceConfigHash } from "@zwave-js/config"; import { InterviewStage, NOT_KNOWN } from "@zwave-js/core"; import { Bytes, formatId } from "@zwave-js/shared"; import { cacheKeys } from "../../driver/NetworkCache.js"; import { FirmwareUpdateMixin } from "./70_FirmwareUpdate.js"; export class DeviceConfigMixin extends FirmwareUpdateMixin { _deviceConfig; /** * Contains additional information about this node, loaded from a config file */ get deviceConfig() { return this._deviceConfig; } set deviceConfig(value) { this._deviceConfig = value; } /** * Returns the manufacturer/brand name defined in the device configuration, * or looks it up from the manufacturer database if no config is available */ get manufacturer() { if (this._deviceConfig) return this._deviceConfig.manufacturer; if (this.manufacturerId != undefined) { return this.driver.lookupManufacturer(this.manufacturerId); } } /** * Returns the device label defined in the device configuration. */ get label() { return this._deviceConfig?.label; } get deviceDatabaseUrl() { if (this.manufacturerId != undefined && this.productType != undefined && this.productId != undefined) { const manufacturerId = formatId(this.manufacturerId); const productType = formatId(this.productType); const productId = formatId(this.productId); const firmwareVersion = this.firmwareVersion || "0.0"; return `https://devices.zwave-js.io/?jumpTo=${manufacturerId}:${productType}:${productId}:${firmwareVersion}`; } } /** * Returns whether the device config for this node has changed since the last interview * in a way that affects interview behavior (compat flags, association settings, proprietary config). * Changes to configuration parameter metadata are applied dynamically and do not require re-interview. */ hasDeviceConfigChanged() { // We can't know if the node is not fully interviewed if (this.interviewStage !== InterviewStage.Complete) return NOT_KNOWN; // The controller cannot be re-interviewed if (this.isControllerNode) return false; // If the hash was never stored, we can only (very likely) know if the config has not changed if (this.cachedDeviceConfigHash == undefined) { return this.deviceConfig == undefined ? false : NOT_KNOWN; } // If it was, a change in hash means the config has changed. // We handle the different hash versions when loading the config already. if (this._currentDeviceConfigHash) { return !DeviceConfig.areHashesEqual(this._currentDeviceConfigHash, this.cachedDeviceConfigHash); } return true; } /** * @internal * The hash of the device config that was applied during the last interview. */ get cachedDeviceConfigHash() { return this.driver.cacheGet(cacheKeys.node(this.id).deviceConfigHash); } set cachedDeviceConfigHash(value) { this.driver.cacheSet(cacheKeys.node(this.id).deviceConfigHash, value); } _currentDeviceConfigHash; /** * @internal * The hash of the currently used device config */ get currentDeviceConfigHash() { return this._currentDeviceConfigHash; } set currentDeviceConfigHash(value) { this._currentDeviceConfigHash = value; } /** * Loads the device configuration for this node from a config file */ async loadDeviceConfig() { // But the configuration definitions might change if (this.manufacturerId == undefined || this.productType == undefined || this.productId == undefined) { return; } // Try to load the config file this.deviceConfig = await this.driver.configManager.lookupDevice(this.manufacturerId, this.productType, this.productId, this.firmwareVersion, this.sdkVersion); if (!this.deviceConfig) { this.driver.controllerLog.logNode(this.id, "No device config found", "warn"); return; } // We need to remember the hash of the device config here, because we're in an async context // and later comparisons are sync. // There are two legacy versions of the device config hash: // - 16 byte MD5 hash // - 32 byte SHA-256 hash // Both only support checking for exact equality of the config hashable. // To be able to compare those stored hashes with the current config, // we need to figure out the stored version now and hash the config using // the same version. // New "hashes" are variable length, contain a condensed version of the device config and are prefixed with a version number. const versionPrefix = Bytes.from("$v", "utf8"); const hasVersionPrefix = !!this.cachedDeviceConfigHash && Bytes .view(this.cachedDeviceConfigHash.subarray(0, 2)) .equals(versionPrefix); let cachedHashVersion; if (this.cachedDeviceConfigHash?.length === 16 && !hasVersionPrefix) { // MD5 = version 0 cachedHashVersion = 0; this._currentDeviceConfigHash = await this.deviceConfig .getHash(0); } else if (this.cachedDeviceConfigHash?.length === 32 && !hasVersionPrefix) { // SHA-256 = version 1 cachedHashVersion = 1; this._currentDeviceConfigHash = await this.deviceConfig .getHash(1); } else { // Variable length prefixed hash - determine the hash version from the cache if (this.cachedDeviceConfigHash) { const parsed = parseDeviceConfigHash(this.cachedDeviceConfigHash); if (parsed) { cachedHashVersion = parsed.version; } } // Use that version for comparison purposes if possible this._currentDeviceConfigHash = await this.deviceConfig.getHash(); // and default to requiring an upgrade if the version could not be parsed cachedHashVersion ??= 0; } // Update the cached device config hash to the most recent version upon restoring, // if the node was previously interviewed and the device config has not changed // since then. if (this.interviewStage === InterviewStage.Complete) { if (cachedHashVersion < DeviceConfig.maxHashVersion && this.hasDeviceConfigChanged() === false) { this.cachedDeviceConfigHash = await this.deviceConfig.getHash(); // Also update the current hash to the new version, in case // it was previously generated with an older version this._currentDeviceConfigHash = this.cachedDeviceConfigHash; } // Apply all param metadata from the device config dynamically. // This handles updates to existing params, addition of new params, // and removal/hiding of old params. for (const ep of this.getAllEndpoints()) { refreshConfigParamMetadataFromConfigFile(this.driver, this.id, ep.index); } } this.driver.controllerLog.logNode(this.id, `${this.deviceConfig.isEmbedded ? "Embedded" : "User-provided"} device config loaded`); } } //# sourceMappingURL=80_DeviceConfig.js.map