UNPKG

zigbee

Version:

ZigBee for Node.JS (using the TI CC2530/CC2531)

254 lines (214 loc) 7.36 kB
'use strict'; var debug = require('debug')('Cluster'); var Concentrate = require('concentrate'); var Dissolve = require('dissolve'); var Enum = require('enum'); var when = require('when'); var profileStore = new (require('./ProfileStore'))(['ha', 'zll']); var packets = require('../zcl/packets'); var ZNP = require('../znp/constants'); var ZCL = require('../zcl/constants.js'); /** * Represents a ZCL cluster on a specific endpoint on a device. * * @param {Endpoint} endpoint * @param {Number} clusterId */ function ZCLCluster(endpoint, clusterId) { this.endpoint = endpoint; this.device = this.endpoint.device; this.client = this.device.client; this.comms = this.client.comms; this.clusterId = clusterId; this.description = profileStore.getCluster(clusterId) || { name: 'UNKNOWN DEVICE' }; this.name = this.description.name; this.attributes = {}; if (this.description.attribute) { this.description.attribute.forEach(function(attr) { var attrId = parseInt(attr.id, 16); this.attributes[attr.name] = this.attributes[attrId] = { name: attr.name, id: attr.id, read: function() { return this.readAttributes(attrId).then(function(responses) { var response = responses[attrId]; console.log('Responses', responses); if (response.status.key !== 'SUCCESS') { throw new Error('Failed to get attribute. Status', status); } return response.value; }); }.bind(this) /*, write: function(value) { return this.writeAttributes(attrId).then(function(responses) { var response = responses[0]; if (response.status.key !== 'SUCCESS') { throw new Error('Failed to get attribute. Status', status); } return response.value; }); }.bind(this)*/ }; }.bind(this)); } this.commands = {}; if (this.description.command) { this.description.command.forEach(function(command) { var commandId = parseInt(command.id, 16); this.commands[command.name] = this.commands[commandId] = function(payload) { debug(this, 'Sending command', command, command.id, commandId); return this.sendClusterSpecificCommand(commandId, payload); }.bind(this); }.bind(this)); } } /** * Helper method to run a command on this cluster. * * @param {Number} commandId The command identifier on this cluster * @param {Buffer} payload The payload of the command * @return {Promise} A promise that resolves to a status */ ZCLCluster.prototype.sendClusterSpecificCommand = function(commandId, payload) { payload = payload || new Buffer(0); debug(this, 'Sending cluster specific command', commandId, payload); return this.client.sendZCLFrame({ FrameControl: { ClusterSpecific: true }, DeviceShortAddress: this.endpoint.device.shortAddress, CommandIdentifier: commandId, payload: payload }, { DstAddr: { address: this.endpoint.device.shortAddress }, DstEndpoint: this.endpoint.endpointId, ClusterID: this.clusterId, Options: { ackRequest: true, discoverRoute: true } }); }; /* TODO: Use new sendZCL function ZCLCluster.prototype.discoverAttributes = function(startAttribute, maxAttributes) { var payload = Concentrate() .uint16le(startAttribute) .uint8(maxAttributes) .result(); debug(true, 'Discovering attributes'); return this.client.sendZCLFrame(null, this.endpoint.device.shortAddress, this.endpoint.endpointId, this.clusterId, false, null, ZCL.GeneralCommands.DiscoverAttributes, payload) .then(function(response) { var deferred = when.defer(); var numAttributes = (response.payload.length - 1) / 3; var attributes = []; Dissolve() .uint8('discoveryComplete') .loop('attributes', function(end) { if (numAttributes) { this .uint16le('identifier') .uint8('dataType') .tap(function() { attributes.push(this.vars); //this.push(this.vars); // XXX: This isn't working, I only get the first attribute? //this.vars = {}; }); numAttributes--; } if (!numAttributes) { end(true); } }) .tap(function() { deferred.resolve(attributes); }) .write(response.payload); return deferred.promise; }); };*/ /** * Reads multiple attributes in the same request. * * @param {Attribute|String|Number...} attributes Can be an attribute object, an ID, or it's name. * @return {Promise} Resolves to an object with the requested attribute names as keys */ ZCLCluster.prototype.readAttributes = function() { var attributeIds = Array.prototype.slice.call(arguments).map(function(id) { if (id.id) { // It's an attribute object return id.id; } else if (typeof id === 'string') { // It's an attribute name return this.attributes[id].id; } else { return id; } }.bind(this)); debug(this, 'Reading attributes', attributeIds, packets.ZCL_READ_ATTRIBUTES.attributes); var payload = packets.ZCL_READ_ATTRIBUTES.write({ Attributes: attributeIds }); return this.client.sendZCLFrame({ FrameControl: {}, DeviceShortAddress: this.endpoint.device.shortAddress, CommandIdentifier: ZCL.GeneralCommands.ReadAttributes, payload: payload }, { DstAddr: { address: this.endpoint.device.shortAddress }, DstEndpoint: this.endpoint.endpointId, ClusterID: this.clusterId, Options: { ackRequest: true, discoverRoute: true }, }).then(this._parseReadAttributesResponse.bind(this)); }; ZCLCluster.prototype._parseReadAttributesResponse = function(data) { var attributes = packets.ZCL_READ_ATTRIBUTES_RESPONSE.read(data.payload).Attributes; var response = {}; attributes.forEach(function(a) { response[a.attributeIdentifier] = a; if (a.status.value === 0) { // We only care if we have it. if (this.attributes[a.attributeIdentifier]) { a.name = this.attributes[a.attributeIdentifier].name; } else { a.name = 'Unknown attribute. Please add to the XCL XML.'; } response[a.name] = a.value; } }.bind(this)); return response; }; ZCLCluster.prototype.reportAttributes = function() { var attributes = Array.prototype.slice.call(arguments); debug(this, 'Reporting attributes', attributes); var payload = packets.ZCL_CONFIGURE_REPORTING.write({ Attributes: attributes }); return this.client.sendZCLFrame({ FrameControl: {}, DeviceShortAddress: this.endpoint.device.shortAddress, CommandIdentifier: ZCL.GeneralCommands.ConfigureReporting, payload: payload }, { DstAddr: { address: this.endpoint.device.shortAddress }, DstEndpoint: this.endpoint.endpointId, ClusterID: this.clusterId, Options: { ackRequest: true, discoverRoute: true }, });//.then(this._parseReadAttributesResponse.bind(this)); }; ZCLCluster.prototype.toString = function() { return this.endpoint + ' [Cluster: ' + this.clusterId.toString(16) + ' (' + this.description.name + ')]'; }; module.exports = ZCLCluster;