zigbee-herdsman
Version:
An open source Zigbee gateway solution with node.js.
312 lines • 12.7 kB
JavaScript
;
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