zwave-js
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
172 lines • 7.81 kB
JavaScript
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