UNPKG

zigbee-herdsman

Version:

An open source Zigbee gateway solution with node.js.

312 lines 12.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isAnalogDataType = isAnalogDataType; exports.getCluster = getCluster; exports.getClusterAttribute = getClusterAttribute; exports.getClusterCommand = getClusterCommand; exports.getClusterCommandResponse = getClusterCommandResponse; exports.getGlobalCommand = getGlobalCommand; exports.isClusterName = isClusterName; exports.getFoundationCommand = getFoundationCommand; exports.getFoundationCommandByName = getFoundationCommandByName; exports.processAttributeWrite = processAttributeWrite; exports.processAttributePreRead = processAttributePreRead; exports.processAttributePostRead = processAttributePostRead; exports.processParameterWrite = processParameterWrite; exports.processParameterRead = processParameterRead; const cluster_1 = require("./definition/cluster"); const datatypes_1 = require("./definition/datatypes"); const enums_1 = require("./definition/enums"); const foundation_1 = require("./definition/foundation"); const status_1 = require("./definition/status"); const zclStatusError_1 = require("./zclStatusError"); /** Runtime fast lookup */ const ZCL_CLUSTERS_ID_TO_NAMES = (() => { const map = new Map(); for (const clusterName in cluster_1.Clusters) { const cluster = cluster_1.Clusters[clusterName]; map.set(cluster.ID, clusterName); } return map; })(); function isAnalogDataType(dataType) { return (dataType === enums_1.DataType.UINT8 || dataType === enums_1.DataType.UINT16 || dataType === enums_1.DataType.UINT24 || dataType === enums_1.DataType.UINT32 || dataType === enums_1.DataType.UINT40 || dataType === enums_1.DataType.UINT48 || dataType === enums_1.DataType.UINT56 || dataType === enums_1.DataType.INT8 || dataType === enums_1.DataType.INT16 || dataType === enums_1.DataType.INT24 || dataType === enums_1.DataType.INT32 || dataType === enums_1.DataType.INT40 || dataType === enums_1.DataType.INT48 || dataType === enums_1.DataType.INT56 || dataType === enums_1.DataType.SEMI_PREC || dataType === enums_1.DataType.SINGLE_PREC || dataType === enums_1.DataType.DOUBLE_PREC || dataType === enums_1.DataType.TOD || dataType === enums_1.DataType.DATE || dataType === enums_1.DataType.UTC); } function getCluster(key, manufacturerCode = undefined, customClusters = {}) { let cluster; if (typeof key === "number") { // custom clusters have priority over Zcl clusters, except in case of better match (see below) for (const clusterName in customClusters) { const foundCluster = customClusters[clusterName]; if (foundCluster.ID === key) { // priority on first match when matching only ID if (cluster === undefined) { cluster = foundCluster; } if (manufacturerCode && foundCluster.manufacturerCode === manufacturerCode) { cluster = foundCluster; break; } if (!foundCluster.manufacturerCode) { cluster = foundCluster; break; } } } if (!cluster) { const zclName = ZCL_CLUSTERS_ID_TO_NAMES.get(key); if (zclName) { const foundCluster = cluster_1.Clusters[zclName]; // TODO: can remove all below once all manuf-specific moved to ZHC // priority on first match when matching only ID if (cluster === undefined) { cluster = foundCluster; } if (manufacturerCode && foundCluster.manufacturerCode === manufacturerCode) { cluster = foundCluster; } else if (foundCluster.manufacturerCode === undefined) { cluster = foundCluster; } } } // TODO: cluster.ID can't be undefined? if (!cluster || cluster.ID === undefined) { cluster = { name: `${key}`, ID: key, attributes: {}, commands: {}, commandsResponse: {} }; // XXX: align behavior with string key? // throw new ZclStatusError(Status.UNSUPPORTED_CLUSTER, `${key}`); } } else { cluster = key in customClusters ? customClusters[key] : cluster_1.Clusters[key]; // TODO: cluster.ID can't be undefined? if (!cluster || cluster.ID === undefined) { throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUPPORTED_CLUSTER, key); } } return cluster; } function getClusterAttribute(cluster, key, manufacturerCode) { const attributes = cluster.attributes; if (typeof key === "number") { let partialMatchAttr; for (const attrKey in attributes) { const attr = attributes[attrKey]; if (attr.ID === key) { if (manufacturerCode !== undefined && attr.manufacturerCode === manufacturerCode) { return attr; } if (attr.manufacturerCode === undefined) { partialMatchAttr = attr; } } } return partialMatchAttr; } return attributes[key]; // XXX: align behavior with cmds? // throw new ZclStatusError(Status.UNSUPPORTED_ATTRIBUTE, `${cluster.name}:${key}`); } function getClusterCommand(cluster, key) { const commands = cluster.commands; if (typeof key === "number") { for (const cmdKey in commands) { const cmd = commands[cmdKey]; if (cmd.ID === key) { return cmd; } } } else { const cmd = commands[key]; if (cmd) { return cmd; } } throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUP_COMMAND, `${cluster.name}:${key}`); } function getClusterCommandResponse(cluster, key) { const commandResponses = cluster.commandsResponse; if (typeof key === "number") { for (const cmdKey in commandResponses) { const cmd = commandResponses[cmdKey]; if (cmd.ID === key) { return cmd; } } } else { const cmd = commandResponses[key]; if (cmd) { return cmd; } } throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUP_COMMAND, `response ${cluster.name}:${key}`); } function getGlobalCommandNameById(id) { for (const commandName in foundation_1.Foundation) { if (foundation_1.Foundation[commandName].ID === id) { return commandName; } } throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUP_COMMAND, `foundation:${id}`); } function getGlobalCommand(key) { const name = typeof key === "number" ? getGlobalCommandNameById(key) : key; const command = foundation_1.Foundation[name]; if (!command) { throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUP_COMMAND, `foundation:${key}`); } return command; } function isClusterName(name) { return name in cluster_1.Clusters; } function getFoundationCommand(id) { for (const commandName in foundation_1.Foundation) { const command = foundation_1.Foundation[commandName]; if (command.ID === id) { return command; } } throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUP_COMMAND, `foundation:${id}`); } function getFoundationCommandByName(name) { const command = foundation_1.Foundation[name]; if (command === undefined) { throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUP_COMMAND, `foundation:${name}`); } return command; } /** Check if value is equal to either min, max, minRef or maxRef */ function isMinOrMax(entry, value) { if (value === entry.max || value === entry.min) { return true; } return false; } function processRestrictions(entry, value) { if (entry.min !== undefined && value < entry.min) { throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires min of ${entry.min}`); } if (entry.minExcl !== undefined && value <= entry.minExcl) { throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires min exclusive of ${entry.minExcl}`); } if (entry.max !== undefined && value > entry.max) { throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires max of ${entry.max}`); } if (entry.maxExcl !== undefined && value >= entry.maxExcl) { throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires max exclusive of ${entry.maxExcl}`); } if (entry.length !== undefined && value.length !== entry.length) { throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires length of ${entry.length}`); } if (entry.minLen !== undefined && value.length < entry.minLen) { throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires min length of ${entry.minLen}`); } if (entry.maxLen !== undefined && value.length > entry.maxLen) { throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires max length of ${entry.maxLen}`); } } function processAttributeWrite(attribute, value) { if (attribute.write !== true) { throw new zclStatusError_1.ZclStatusError(status_1.Status.NOT_AUTHORIZED, `${attribute.name} (${attribute.ID}) is not writable`); } if (value == null) { return attribute.default !== undefined ? attribute.default : value /* XXX: dangerous fallback */; } // if default, always valid if (attribute.default === value) { return value; } if (Number.isNaN(value)) { if (attribute.default === undefined) { const nonValue = datatypes_1.ZCL_TYPE_INVALID_BY_TYPE[attribute.type]; if (nonValue === undefined) { throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_FIELD, `${attribute.name} (${attribute.ID}) does not have a default nor a non-value`); } return nonValue; } return attribute.default; } processRestrictions(attribute, value); return value; } function processAttributePreRead(attribute) { if (attribute.read === false) { throw new zclStatusError_1.ZclStatusError(status_1.Status.NOT_AUTHORIZED, `${attribute.name} (${attribute.ID}) is not readable`); } } function processAttributePostRead(attribute, value) { // should never happen? if (value == null) { return value; } // if default, always valid if (attribute.default === value) { return value; } // if type does not have an `invalid` (undefined) it won't match since value is checked above if (value === datatypes_1.ZCL_TYPE_INVALID_BY_TYPE[attribute.type]) { // if value is same as max or min, ignore invalid sentinel if (isMinOrMax(attribute, value)) { return value; } // return NaN for both number & bigint to keep logic consistent return Number.NaN; } processRestrictions(attribute, value); return value; } function processParameterWrite(parameter, value) { // should never happen? if (value == null) { return value; } if (Number.isNaN(value)) { const nonValue = datatypes_1.ZCL_TYPE_INVALID_BY_TYPE[parameter.type]; if (nonValue === undefined) { throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_FIELD, `${parameter.name} does not have a non-value`); } return nonValue; } processRestrictions(parameter, value); return value; } function processParameterRead(parameter, value) { // should never happen? if (value == null) { return value; } // if type does not have an `invalid` (undefined) it won't match since value is checked above if (value === datatypes_1.ZCL_TYPE_INVALID_BY_TYPE[parameter.type]) { // if value is same as max or min, ignore invalid sentinel if (isMinOrMax(parameter, value)) { return value; } // return NaN for both number & bigint to keep logic consistent return Number.NaN; } processRestrictions(parameter, value); return value; } //# sourceMappingURL=utils.js.map