zwave-js
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
155 lines • 6.87 kB
JavaScript
import { CommandClass, defaultCCValueOptions, getCCValues, } from "@zwave-js/cc";
import { ValueDB, ValueMetadata, applicationCCs, getCCName, } from "@zwave-js/core";
import { pick } from "@zwave-js/shared";
import * as nodeUtils from "../utils.js";
import { NodeWakeupMixin } from "./30_Wakeup.js";
export class NodeValuesMixin extends NodeWakeupMixin {
constructor(nodeId, driver, endpointIndex, deviceClass, supportedCCs, valueDB) {
// Define this node's intrinsic endpoint as the root device (0)
super(nodeId, driver, endpointIndex, deviceClass, supportedCCs);
this._valueDB = valueDB
?? new ValueDB(nodeId, driver.valueDB, driver.metadataDB);
// Pass value events to our listeners
for (const event of [
"value added",
"value updated",
"value removed",
"value notification",
"metadata updated",
]) {
this._valueDB.on(event, this.translateValueEvent.bind(this, event));
}
}
_valueDB;
get valueDB() {
return this._valueDB;
}
getValue(valueId) {
return this._valueDB.getValue(valueId);
}
getValueTimestamp(valueId) {
return this._valueDB.getTimestamp(valueId);
}
getValueMetadata(valueId) {
// Check if a corresponding CC value is defined for this value ID
// so we can extend the returned metadata
const definedCCValues = getCCValues(valueId.commandClass);
let valueOptions;
let meta;
if (definedCCValues) {
const value = Object.values(definedCCValues)
.find((v) => v?.is(valueId));
if (value) {
if (typeof value !== "function") {
meta = value.meta;
}
valueOptions = value.options;
}
}
const existingMetadata = this._valueDB.getMetadata(valueId);
return {
// The priority for returned metadata is valueDB > defined value > Any (default)
...(existingMetadata ?? meta ?? ValueMetadata.Any),
// ...except for these flags, which are taken from defined values:
stateful: valueOptions?.stateful ?? defaultCCValueOptions.stateful,
secret: valueOptions?.secret ?? defaultCCValueOptions.secret,
};
}
/**
* Enhances the raw event args of the ValueDB so it can be consumed better by applications
*/
translateValueEvent(eventName, arg) {
// Try to retrieve the speaking CC name
const outArg = nodeUtils.translateValueID(this.driver, this, arg);
// This can happen for value updated events
if ("source" in outArg)
delete outArg.source;
const loglevel = this.driver.getLogConfig().level;
// If this is a metadata event, make sure we return the merged metadata
if ("metadata" in outArg) {
outArg.metadata = this
.getValueMetadata(arg);
}
const ccInstance = CommandClass.createInstanceUnchecked(this, arg.commandClass);
const isInternalValue = !!ccInstance?.isInternalValue(arg);
// Check whether this value change may be logged
const isSecretValue = !!ccInstance?.isSecretValue(arg);
if (loglevel === "silly") {
this.driver.controllerLog.logNode(this.id, {
message: `[translateValueEvent: ${eventName}]
commandClass: ${getCCName(arg.commandClass)}
endpoint: ${arg.endpoint}
property: ${arg.property}
propertyKey: ${arg.propertyKey}
internal: ${isInternalValue}
secret: ${isSecretValue}
event source: ${arg.source}`,
level: "silly",
});
}
if (!isSecretValue
&& arg.source !== "driver") {
// Log the value change, except for updates caused by the driver itself
// I don't like the splitting and any but its the easiest solution here
const [changeTarget, changeType] = eventName.split(" ");
const logArgument = {
...outArg,
nodeId: this.id,
internal: isInternalValue,
};
if (changeTarget === "value") {
this.driver.controllerLog.value(changeType, logArgument);
}
else if (changeTarget === "metadata") {
this.driver.controllerLog.metadataUpdated(logArgument);
}
}
// Don't expose value events for internal value IDs...
if (isInternalValue)
return;
if (loglevel === "silly") {
this.driver.controllerLog.logNode(this.id, {
message: `[translateValueEvent: ${eventName}]
is root endpoint: ${!arg.endpoint}
is application CC: ${applicationCCs.includes(arg.commandClass)}
should hide root values: ${nodeUtils.shouldHideRootApplicationCCValues(this.driver, this.id)}`,
level: "silly",
});
}
// ... and root values ID that mirrors endpoint functionality
if (
// Only root endpoint values need to be filtered
!arg.endpoint
// Only application CCs need to be filtered
&& applicationCCs.includes(arg.commandClass)
// and only if the endpoints are not unnecessary and the root values mirror them
&& nodeUtils.shouldHideRootApplicationCCValues(this.driver, this.id)) {
// Iterate through all possible non-root endpoints of this node and
// check if there is a value ID that mirrors root endpoint functionality
for (const endpoint of nodeUtils.getEndpointIndizes(this.driver, this.id)) {
const possiblyMirroredValueID = {
// same CC, property and key
...pick(arg, ["commandClass", "property", "propertyKey"]),
// but different endpoint
endpoint,
};
if (this.valueDB.hasValue(possiblyMirroredValueID)) {
if (loglevel === "silly") {
this.driver.controllerLog.logNode(this.id, {
message: `[translateValueEvent: ${eventName}] found mirrored value ID on different endpoint, ignoring event:
commandClass: ${getCCName(possiblyMirroredValueID.commandClass)}
endpoint: ${possiblyMirroredValueID.endpoint}
property: ${possiblyMirroredValueID.property}
propertyKey: ${possiblyMirroredValueID.propertyKey}`,
level: "silly",
});
}
return;
}
}
}
// And pass the translated event to our listeners
this._emit(eventName, this, outArg);
}
}
//# sourceMappingURL=40_Values.js.map