zigbee-herdsman-zigate
Version:
An open source ZigBee gateway solution with node.js.
487 lines • 25.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const entity_1 = __importDefault(require("./entity"));
const Zcl = __importStar(require("../../zcl"));
const zclTransactionSequenceNumber_1 = __importDefault(require("../helpers/zclTransactionSequenceNumber"));
const ZclFrameConverter = __importStar(require("../helpers/zclFrameConverter"));
const group_1 = __importDefault(require("./group"));
const device_1 = __importDefault(require("./device"));
const debug_1 = __importDefault(require("debug"));
const assert_1 = __importDefault(require("assert"));
const debug = {
info: debug_1.default('zigbee-herdsman:controller:endpoint'),
error: debug_1.default('zigbee-herdsman:controller:endpoint'),
};
class Endpoint extends entity_1.default {
constructor(ID, profileID, deviceID, inputClusters, outputClusters, deviceNetworkAddress, deviceIeeeAddress, clusters, binds, meta) {
super();
this.ID = ID;
this.profileID = profileID;
this.deviceID = deviceID;
this.inputClusters = inputClusters;
this.outputClusters = outputClusters;
this.deviceNetworkAddress = deviceNetworkAddress;
this.deviceIeeeAddress = deviceIeeeAddress;
this.clusters = clusters;
this._binds = binds;
this.meta = meta;
}
// Getters/setters
get binds() {
return this._binds.map((entry) => {
let target = null;
if (entry.type === 'endpoint') {
const device = device_1.default.byIeeeAddr(entry.deviceIeeeAddress);
if (device) {
target = device.getEndpoint(entry.endpointID);
}
}
else {
target = group_1.default.byGroupID(entry.groupID);
}
if (target) {
return { target, cluster: Zcl.Utils.getCluster(entry.cluster) };
}
else {
return undefined;
}
}).filter(b => b !== undefined);
}
/**
* Get device of this endpoint
*/
getDevice() {
return device_1.default.byIeeeAddr(this.deviceIeeeAddress);
}
/**
* @param {number|string} clusterKey
* @returns {boolean}
*/
supportsInputCluster(clusterKey) {
const cluster = Zcl.Utils.getCluster(clusterKey);
return this.inputClusters.includes(cluster.ID);
}
/**
* @param {number|string} clusterKey
* @returns {boolean}
*/
supportsOutputCluster(clusterKey) {
const cluster = Zcl.Utils.getCluster(clusterKey);
return this.outputClusters.includes(cluster.ID);
}
/**
* @returns {Zcl.TsType.Cluster[]}
*/
getInputClusters() {
return this.clusterNumbersToClusters(this.inputClusters);
}
/**
* @returns {Zcl.TsType.Cluster[]}
*/
getOutputClusters() {
return this.clusterNumbersToClusters(this.outputClusters);
}
clusterNumbersToClusters(clusterNumbers) {
return clusterNumbers.map((c) => {
try {
return Zcl.Utils.getCluster(c, this.getDevice().manufacturerID);
}
catch (_a) {
return null;
}
}).filter((c) => c !== null);
}
/*
* CRUD
*/
static fromDatabaseRecord(record, deviceNetworkAddress, deviceIeeeAddress) {
// Migrate attrs to attributes
for (const entry of Object.values(record.clusters).filter((e) => e.hasOwnProperty('attrs'))) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
entry.attributes = entry.attrs;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete entry.attrs;
}
const meta = record.meta ? record.meta : {};
return new Endpoint(record.epId, record.profId, record.devId, record.inClusterList, record.outClusterList, deviceNetworkAddress, deviceIeeeAddress, record.clusters, record.binds || [], meta);
}
toDatabaseRecord() {
return {
profId: this.profileID, epId: this.ID, devId: this.deviceID,
inClusterList: this.inputClusters, outClusterList: this.outputClusters, clusters: this.clusters,
binds: this._binds, meta: this.meta,
};
}
static create(ID, profileID, deviceID, inputClusters, outputClusters, deviceNetworkAddress, deviceIeeeAddress) {
return new Endpoint(ID, profileID, deviceID, inputClusters, outputClusters, deviceNetworkAddress, deviceIeeeAddress, {}, [], {});
}
saveClusterAttributeKeyValue(clusterKey, list) {
const cluster = Zcl.Utils.getCluster(clusterKey);
if (!this.clusters[cluster.name])
this.clusters[cluster.name] = { attributes: {} };
for (const [attribute, value] of Object.entries(list)) {
this.clusters[cluster.name].attributes[attribute] = value;
}
}
getClusterAttributeValue(clusterKey, attributeKey) {
const cluster = Zcl.Utils.getCluster(clusterKey);
const attribute = cluster.getAttribute(attributeKey);
if (this.clusters[cluster.name] && this.clusters[cluster.name].attributes) {
return this.clusters[cluster.name].attributes[attribute.name];
}
return null;
}
/*
* Zigbee functions
*/
checkStatus(payload) {
for (const item of payload) {
if (item.status !== Zcl.Status.SUCCESS) {
throw new Zcl.ZclStatusError(item.status);
}
}
}
write(clusterKey, attributes, options) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
options = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER);
const cluster = Zcl.Utils.getCluster(clusterKey);
const payload = [];
for (const [nameOrID, value] of Object.entries(attributes)) {
if (cluster.hasAttribute(nameOrID)) {
const attribute = cluster.getAttribute(nameOrID);
payload.push({ attrId: attribute.ID, attrData: value, dataType: attribute.type });
}
else if (!isNaN(Number(nameOrID))) {
payload.push({ attrId: Number(nameOrID), attrData: value.value, dataType: value.type });
}
else {
throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
}
}
const log = `Write ${this.deviceIeeeAddress}/${this.ID} ` +
`${cluster.name}(${JSON.stringify(attributes)}, ${JSON.stringify(options)})`;
debug.info(log);
try {
const frame = Zcl.ZclFrame.create(Zcl.FrameType.GLOBAL, options.direction, options.disableDefaultResponse, options.manufacturerCode, (_a = options.transactionSequenceNumber) !== null && _a !== void 0 ? _a : zclTransactionSequenceNumber_1.default.next(), 'write', cluster.ID, payload, options.reservedBits);
const result = yield entity_1.default.adapter.sendZclFrameToEndpoint(this.deviceIeeeAddress, this.deviceNetworkAddress, this.ID, frame, options.timeout, options.disableResponse, options.disableRecovery, options.srcEndpoint);
if (!options.disableResponse) {
this.checkStatus(result.frame.Payload);
}
}
catch (error) {
error.message = `${log} failed (${error.message})`;
debug.error(error.message);
throw error;
}
});
}
read(clusterKey, attributes, options) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
options = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER);
const cluster = Zcl.Utils.getCluster(clusterKey);
const payload = [];
for (const attribute of attributes) {
payload.push({ attrId: typeof attribute === 'number' ? attribute : cluster.getAttribute(attribute).ID });
}
const frame = Zcl.ZclFrame.create(Zcl.FrameType.GLOBAL, options.direction, options.disableDefaultResponse, options.manufacturerCode, (_a = options.transactionSequenceNumber) !== null && _a !== void 0 ? _a : zclTransactionSequenceNumber_1.default.next(), 'read', cluster.ID, payload, options.reservedBits);
const log = `Read ${this.deviceIeeeAddress}/${this.ID} ` +
`${cluster.name}(${JSON.stringify(attributes)}, ${JSON.stringify(options)})`;
debug.info(log);
try {
const result = yield entity_1.default.adapter.sendZclFrameToEndpoint(this.deviceIeeeAddress, this.deviceNetworkAddress, this.ID, frame, options.timeout, options.disableResponse, options.disableRecovery, options.srcEndpoint);
if (!options.disableResponse) {
this.checkStatus(result.frame.Payload);
return ZclFrameConverter.attributeKeyValue(result.frame);
}
else {
return null;
}
}
catch (error) {
error.message = `${log} failed (${error.message})`;
debug.error(error.message);
throw error;
}
});
}
readResponse(clusterKey, transactionSequenceNumber, attributes, options) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.default(!options || !options.hasOwnProperty('transactionSequenceNumber'), 'Use parameter');
options = this.getOptionsWithDefaults(options, true, Zcl.Direction.SERVER_TO_CLIENT);
const cluster = Zcl.Utils.getCluster(clusterKey);
const payload = [];
for (const [nameOrID, value] of Object.entries(attributes)) {
if (cluster.hasAttribute(nameOrID)) {
const attribute = cluster.getAttribute(nameOrID);
payload.push({ attrId: attribute.ID, attrData: value, dataType: attribute.type, status: 0 });
}
else if (!isNaN(Number(nameOrID))) {
payload.push({ attrId: Number(nameOrID), attrData: value.value, dataType: value.type, status: 0 });
}
else {
throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
}
}
const frame = Zcl.ZclFrame.create(Zcl.FrameType.GLOBAL, options.direction, options.disableDefaultResponse, options.manufacturerCode, transactionSequenceNumber, 'readRsp', cluster.ID, payload, options.reservedBits);
const log = `ReadResponse ${this.deviceIeeeAddress}/${this.ID} ` +
`${cluster.name}(${JSON.stringify(attributes)}, ${JSON.stringify(options)})`;
debug.info(log);
try {
yield entity_1.default.adapter.sendZclFrameToEndpoint(this.deviceIeeeAddress, this.deviceNetworkAddress, this.ID, frame, options.timeout, options.disableResponse, options.disableRecovery, options.srcEndpoint);
}
catch (error) {
error.message = `${log} failed (${error.message})`;
debug.error(error.message);
throw error;
}
});
}
bind(clusterKey, target) {
return __awaiter(this, void 0, void 0, function* () {
const cluster = Zcl.Utils.getCluster(clusterKey);
const type = target instanceof Endpoint ? 'endpoint' : 'group';
const destinationAddress = target instanceof Endpoint ? target.deviceIeeeAddress : (target instanceof group_1.default ? target.groupID : target);
const log = `Bind ${this.deviceIeeeAddress}/${this.ID} ${cluster.name} from ` +
`'${target instanceof Endpoint ? `${destinationAddress}/${target.ID}` : destinationAddress}'`;
debug.info(log);
try {
yield entity_1.default.adapter.bind(this.deviceNetworkAddress, this.deviceIeeeAddress, this.ID, cluster.ID, destinationAddress, type, target instanceof Endpoint ? target.ID : null);
// NOTE: In case the bind is done by group number, it won't be saved to the database
if (!this.binds.find((b) => b.cluster.ID === cluster.ID && b.target === target)) {
if (target instanceof group_1.default) {
this._binds.push({ cluster: cluster.ID, groupID: target.groupID, type: 'group' });
}
else if (target instanceof Endpoint) {
this._binds.push({
cluster: cluster.ID, type: 'endpoint', deviceIeeeAddress: target.deviceIeeeAddress,
endpointID: target.ID
});
}
this.save();
}
}
catch (error) {
error.message = `${log} failed (${error.message})`;
debug.error(error.message);
throw error;
}
});
}
save() {
this.getDevice().save();
}
unbind(clusterKey, target) {
return __awaiter(this, void 0, void 0, function* () {
const cluster = Zcl.Utils.getCluster(clusterKey);
const type = target instanceof Endpoint ? 'endpoint' : 'group';
const destinationAddress = target instanceof Endpoint ? target.deviceIeeeAddress : (target instanceof group_1.default ? target.groupID : target);
const log = `Unbind ${this.deviceIeeeAddress}/${this.ID} ${cluster.name} from ` +
`'${target instanceof Endpoint ? `${destinationAddress}/${target.ID}` : destinationAddress}'`;
debug.info(log);
try {
yield entity_1.default.adapter.unbind(this.deviceNetworkAddress, this.deviceIeeeAddress, this.ID, cluster.ID, destinationAddress, type, target instanceof Endpoint ? target.ID : null);
const index = this.binds.findIndex((b) => b.cluster.ID === cluster.ID && b.target === target);
if (index !== -1) {
this._binds.splice(index, 1);
this.save();
}
}
catch (error) {
error.message = `${log} failed (${error.message})`;
debug.error(error.message);
throw error;
}
});
}
defaultResponse(commandID, status, clusterID, transactionSequenceNumber, options) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.default(!options || !options.hasOwnProperty('transactionSequenceNumber'), 'Use parameter');
options = this.getOptionsWithDefaults(options, true, Zcl.Direction.SERVER_TO_CLIENT);
const payload = { cmdId: commandID, statusCode: status };
const frame = Zcl.ZclFrame.create(Zcl.FrameType.GLOBAL, options.direction, options.disableDefaultResponse, options.manufacturerCode, transactionSequenceNumber, 'defaultRsp', clusterID, payload, options.reservedBits);
const log = `DefaultResponse ${this.deviceIeeeAddress}/${this.ID} ` +
`${clusterID}(${commandID}, ${JSON.stringify(options)})`;
debug.info(log);
try {
yield entity_1.default.adapter.sendZclFrameToEndpoint(this.deviceIeeeAddress, this.deviceNetworkAddress, this.ID, frame, options.timeout, options.disableResponse, options.disableRecovery, options.srcEndpoint);
}
catch (error) {
error.message = `${log} failed (${error.message})`;
debug.error(error.message);
throw error;
}
});
}
configureReporting(clusterKey, items, options) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
options = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER);
const cluster = Zcl.Utils.getCluster(clusterKey);
const payload = items.map((item) => {
let dataType, attrId;
if (typeof item.attribute === 'object') {
dataType = item.attribute.type;
attrId = item.attribute.ID;
}
else {
/* istanbul ignore else */
if (cluster.hasAttribute(item.attribute)) {
const attribute = cluster.getAttribute(item.attribute);
dataType = attribute.type;
attrId = attribute.ID;
}
}
return {
direction: Zcl.Direction.CLIENT_TO_SERVER,
attrId, dataType,
minRepIntval: item.minimumReportInterval,
maxRepIntval: item.maximumReportInterval,
repChange: item.reportableChange,
};
});
const frame = Zcl.ZclFrame.create(Zcl.FrameType.GLOBAL, options.direction, options.disableDefaultResponse, options.manufacturerCode, (_a = options.transactionSequenceNumber) !== null && _a !== void 0 ? _a : zclTransactionSequenceNumber_1.default.next(), 'configReport', cluster.ID, payload, options.reservedBits);
const log = `ConfigureReporting ${this.deviceIeeeAddress}/${this.ID} ` +
`${cluster.name}(${JSON.stringify(items)}, ${JSON.stringify(options)})`;
debug.info(log);
try {
const result = yield entity_1.default.adapter.sendZclFrameToEndpoint(this.deviceIeeeAddress, this.deviceNetworkAddress, this.ID, frame, options.timeout, options.disableResponse, options.disableRecovery, options.srcEndpoint);
if (!options.disableResponse) {
this.checkStatus(result.frame.Payload);
}
}
catch (error) {
error.message = `${log} failed (${error.message})`;
debug.error(error.message);
throw error;
}
});
}
command(clusterKey, commandKey, payload, options) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const cluster = Zcl.Utils.getCluster(clusterKey);
const command = cluster.getCommand(commandKey);
const hasResponse = command.hasOwnProperty('response');
options = this.getOptionsWithDefaults(options, hasResponse, Zcl.Direction.CLIENT_TO_SERVER);
const frame = Zcl.ZclFrame.create(Zcl.FrameType.SPECIFIC, options.direction, options.disableDefaultResponse, options.manufacturerCode, (_a = options.transactionSequenceNumber) !== null && _a !== void 0 ? _a : zclTransactionSequenceNumber_1.default.next(), command.ID, cluster.ID, payload, options.reservedBits);
const log = `Command ${this.deviceIeeeAddress}/${this.ID} ` +
`${cluster.name}.${command.name}(${JSON.stringify(payload)}, ${JSON.stringify(options)})`;
debug.info(log);
try {
const result = yield entity_1.default.adapter.sendZclFrameToEndpoint(this.deviceIeeeAddress, this.deviceNetworkAddress, this.ID, frame, options.timeout, options.disableResponse, options.disableRecovery, options.srcEndpoint);
if (result) {
return result.frame.Payload;
}
}
catch (error) {
error.message = `${log} failed (${error.message})`;
debug.error(error.message);
throw error;
}
});
}
commandResponse(clusterKey, commandKey, payload, options, transactionSequenceNumber) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.default(!options || !options.hasOwnProperty('transactionSequenceNumber'), 'Use parameter');
const cluster = Zcl.Utils.getCluster(clusterKey);
const command = cluster.getCommandResponse(commandKey);
transactionSequenceNumber = transactionSequenceNumber || zclTransactionSequenceNumber_1.default.next();
options = this.getOptionsWithDefaults(options, true, Zcl.Direction.SERVER_TO_CLIENT);
const frame = Zcl.ZclFrame.create(Zcl.FrameType.SPECIFIC, options.direction, options.disableDefaultResponse, options.manufacturerCode, transactionSequenceNumber, command.ID, cluster.ID, payload, options.reservedBits);
const log = `CommandResponse ${this.deviceIeeeAddress}/${this.ID} ` +
`${cluster.name}.${command.name}(${JSON.stringify(payload)}, ${JSON.stringify(options)})`;
debug.info(log);
try {
yield entity_1.default.adapter.sendZclFrameToEndpoint(this.deviceIeeeAddress, this.deviceNetworkAddress, this.ID, frame, options.timeout, options.disableResponse, options.disableRecovery, options.srcEndpoint);
}
catch (error) {
error.message = `${log} failed (${error.message})`;
debug.error(error.message);
throw error;
}
});
}
waitForCommand(clusterKey, commandKey, transactionSequenceNumber, timeout) {
const cluster = Zcl.Utils.getCluster(clusterKey);
const command = cluster.getCommand(commandKey);
const waiter = entity_1.default.adapter.waitFor(this.deviceNetworkAddress, this.ID, Zcl.FrameType.SPECIFIC, Zcl.Direction.CLIENT_TO_SERVER, transactionSequenceNumber, cluster.ID, command.ID, timeout);
const promise = new Promise((resolve, reject) => {
waiter.promise.then((payload) => resolve({ header: payload.frame.Header, payload: payload.frame.Payload }), (error) => reject(error));
});
return { promise, cancel: waiter.cancel };
}
getOptionsWithDefaults(options, disableDefaultResponse, direction) {
const providedOptions = options || {};
return Object.assign({ timeout: 10000, disableResponse: false, disableRecovery: false, disableDefaultResponse,
direction, srcEndpoint: null, reservedBits: 0, manufacturerCode: null, transactionSequenceNumber: null }, providedOptions);
}
addToGroup(group) {
return __awaiter(this, void 0, void 0, function* () {
yield this.command('genGroups', 'add', { groupid: group.groupID, groupname: '' });
group.addMember(this);
});
}
/**
* Remove endpoint from a group, accepts both a Group and number as parameter.
* The number parameter type should only be used when removing from a group which is not known
* to zigbee-herdsman.
*/
removeFromGroup(group) {
return __awaiter(this, void 0, void 0, function* () {
yield this.command('genGroups', 'remove', { groupid: group instanceof group_1.default ? group.groupID : group });
if (group instanceof group_1.default) {
group.removeMember(this);
}
});
}
removeFromAllGroups() {
return __awaiter(this, void 0, void 0, function* () {
yield this.command('genGroups', 'removeAll', {}, { disableDefaultResponse: true });
this.removeFromAllGroupsDatabase();
});
}
removeFromAllGroupsDatabase() {
for (const group of group_1.default.all()) {
if (group.hasMember(this)) {
group.removeMember(this);
}
}
}
}
exports.default = Endpoint;
//# sourceMappingURL=endpoint.js.map