UNPKG

zigbee-herdsman-zigate

Version:

An open source ZigBee gateway solution with node.js.

487 lines 25.5 kB
"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