zigbee
Version:
ZigBee for Node.JS (using the TI CC2530/CC2531)
416 lines (325 loc) • 12.6 kB
JavaScript
var zigbee = require('./index');
var Table = require('cli-table');
var when = require('when');
var ZCLCluster = require('./lib/zcl').ZCLCluster;
var ResetHue = require('./lib/util/ResetHue');
var Concentrate = require('concentrate');
var glob = require('glob');
glob('/dev/cu.usbmodem*', function (err, devices) {
// TODO: Support the CC2530 on the Sphere.
if (err || devices.length != 1) {
//throw new Error('Found ' + devices.length + ' devices that could be the CC2531 usb dongle.');
}
connect(devices[0]);
});
//connect('/dev/cu.usbmodem1411');
function connect(path) {
var client = zigbee.createClient();
client.connectToPort(path)
.then(client.firmwareVersion.bind(client))
.then(function(version) {
var versionString = [
version.specifics.majorRelease,
version.specifics.minorRelease,
version.specifics.maintenanceRelease
].join('.');
console.log('CC2530/1 firmware version: %s %s', version.type, versionString);
/*/ reset our device so we get back to a clean state
.then(function() {
client.resetDevice(true);
})//*/
})
.then(client.startCoordinator.bind(client))
.then(function() {
//setTimeout(function() {
//var reset = new ResetHue(zigbeeClient);
//reset.findHues();
//}, 15000);*/
// zigbeeClient.permitJoin(128);
//return;
displayZigBeeDevices(client);
var seen = {};
client.devices().then(function(devices) {
devices.forEach(function(device) {
setInterval(function() {
if (!seen[device.IEEEAddress]) {
seen[device.IEEEAddress] = true;
handleDevice(device);
}
}, 5000);
});
});
/*zigbeeClient.on('device_announce', function(device) {
console.log('Got new ZigBee device:', device.IEEEAddress);
displayZigBeeDevices(zigbeeClient);
handleDevice(device);
});*/
})
.catch(function(err) {
console.log('ZigBee client failed:', err.stack);
})
.done(function(err) {
console.log('ZigBee client running.', err);
});
}
function assert(expected, message) {
return function(actual) {
if (expected !== actual) {
throw new Error(message + ' (Expected: ' + expected + ', Actual:', actual,')');
}
};
}
function handleDevice(device) {
console.log('listening for endpoints');
device.on('endpoint', function(endpoint) {
console.log('Got endpoint', endpoint.toString());
endpoint.inClusters().then(function(inClusters) {
console.log('Found clusters:', inClusters, 'for endpoint ' + endpoint);
inClusters.forEach(function(zcl) {
//*
if (zcl.description.name == 'Basic') {
zcl.attributes.readAttributes('ManufacturerName', 'ModelIdentifier').then(function(values) {
console.log('Device : ' + values.ManufacturerName + ' - Manufacturer : ' + values.ModelIdentifier);
}).catch(function(err) {
console.error('Failed to read manufacturer name and device id', err.stack);
});
}//*/
/*if (zcl.name == 'Level Control') {
zcl.attributes.CurrentLevel.read().then(function(level) {
console.log('Current level', level);
});
var payload = Concentrate();
payload.uint8(200); // Level
payload.uint16le(0); // Transition duration (1/10th seconds)
zcl.commands['Move to Level (with On/Off)'](payload.result()).then(function(response) {
console.log('Move to level response', response);
});
}*/
var colorspaces = require('colorspaces');
var Color = require('color');
function setColor(hex) {
var color = colorspaces.make_color('hex', hex).as('CIExyY');
var payload = Concentrate();
payload.uint16le(Math.floor(color[0] * 0xFFFF)); // Color X
payload.uint16le(Math.floor(color[1] * 0xFFFF)); // Color Y
payload.uint16le(3); // Transition duration (1/10th seconds)
return zcl.commands['Move to Color'](payload.result());
/*var color = new Color(hex);
console.log('HUe', color.hue());
var payload = Concentrate();
payload.uint16le(Math.floor(color.hue() * 0xFFFF)); // Color X
payload.uint16le(Math.floor(color[1] * 0xFFFF)); // Color Y
payload.uint16le(3); // Transition duration (1/10th seconds)
return zcl.commands['Move to Color'](payload.result());*/
}
function go() {
return setColor('#ff0000')
.delay(300)
.then(function() {
return setColor('#00ff00');
})
.delay(300)
.then(function() {
return setColor('#0000ff');
})
.delay(300);
}
if (zcl.name == 'Color Control') {
go();
/*
zcl.readAttributes('CurrentHue', 'CurrentSaturation', 'RemainingTime').then(function(results) {
console.log(zcl.toString(), 'attributes 0-9', JSON.stringify(results,2,2));
});//*/
//go();
/*
zcl.reportAttributes({
attributeIdentifier: 0x00, // CurrentHue
attributeDataType: 0x21, // uint16le
minimumReportingInterval: 0,
maximumReportingInterval: 0,
reportableChange: 1
}).then(function(results) {
console.log(zcl.toString(), 'reporting color attributes', results);
});//*/
/*
setColor('#ff0000')
.delay(300)
.then(function() {
return setColor('#00ff00');
})
.delay(300)
.then(function() {
return setColor('#0000ff');
})
.delay(300);*/
}
/*if (zcl.description.name == 'ZLL Commissioning') {
var payload = Concentrate();
payload.uint32le(12345); // Inter-PAN transaction identifier
payload.uint16le(15); // Identify duration (seconds)
zcl.commands['Identify Request'](payload.result()).then(function(response) {
console.log('Identify response', response);
});
}*/
if (zcl.name == 'IAS Zone') {
zcl.reportAttributes({
attributeIdentifier: 0x0002, // ZoneStatus
attributeDataType: 0x19, // bmp16
minimumReportingInterval: 0, // in seconds (no minimum)
maximumReportingInterval: 10, // in seconds
reportableChange: true
}).then(function(results) {
console.log(zcl.toString(), 'reporting ZoneStatus attribute', results);
});
zcl.attributes.ZoneState.read()
.then(function(value) {
console.log('ZoneState value', value);
});
zcl.attributes.ZoneType.read()
.then(function(value) {
console.log('Zonetype value', value);
});
zcl.attributes.ZoneStatus.read()
.then(function(value) {
console.log('ZoneStatus value', value);
});
}
if (zcl.name == 'Occupancy sensing') {
zcl.reportAttributes({
attributeIdentifier: 0x0000, // Occupancy
attributeDataType: 0x18, // bmp8
minimumReportingInterval: 0,
maximumReportingInterval: 0,
reportableChange: true
}).then(function(results) {
console.log(zcl.toString(), 'reporting ooccupancy attribute', results);
});
zcl.attributes.Occupancy.read()
.then(function(value) {
console.log('Occupancy value', value);
});
}
//*
if (zcl.description.name == 'On/Off') {
console.log('Found on/off!');
zcl.reportAttributes({
attributeIdentifier: 0x0000, // OnOff
attributeDataType: 0x10, // boolean
minimumReportingInterval: 0,
maximumReportingInterval: 0,
reportableChange: true
}).then(function(results) {
console.log(zcl.toString(), 'reporting onoff attribute', results);
});
zcl.attributes.OnOff.read()
.then(function(value) {
console.log('Device started on?', value);
if (!value) { // Ensure we always start on. The relay doesn't appear to respond if its already on!
return zcl.commands.On()
.then(zcl.attributes.OnOff.read)
.then(assert(true, 'Device didn\'t turn off'))
.delay(1000);
}
})
.then(zcl.commands.Off)
.then(zcl.attributes.OnOff.read)
.then(assert(false, 'Device didn\'t turn off!'))
.delay(1000)
.then(zcl.commands.On)
.then(zcl.attributes.OnOff.read)
.then(assert(true, 'Device didn\'t turn on!'))
.delay(1000)
.then(zcl.commands.Toggle)
.then(zcl.attributes.OnOff.read)
.then(assert(false, 'Device didn\'t toggle!'))
.then(function() {
console.log('Successfully turned the on/off device on and off and whatever.');
})
.catch(function(err) {
console.error('On/Off Cluster fail!', err.stack);
});
}//*/
console.log('cluster! ', zcl.toString(),
JSON.stringify(Object.keys(zcl.attributes)),
Object.keys(zcl.commands));
//*/
/*
zcl.discoverAttributes(0x0000, 100).then(function(attributes) {
console.log('Discovered', attributes.length, 'attributes on ', endpoint.toString(), 'cluster', id);
}).timeout(20000).catch(function(e) {
console.log('Attribute discovery errored for cluster', id, 'on endpoint', endpoint.toString(), e.stack);
console.log(JSON.stringify(e));
console.dir(e);
});
*/
/*if (zcl.attributes.MeasuredValue) {
setInterval(function() {
zcl.attributes.MeasuredValue.read().then(function(response) {
console.log('MeasuredValue!', response);
}).catch(function(err) {
console.log('Failed to read MeasuredValue', err);
});
}, 1000);
}
switch (id) {
case 0x0006: // On/Off
setInterval(function() {
zcl.sendClusterSpecificCommand(0x02); // Toggle
}, 2000);
break;
/*case 0x0006: // Identify
zcl.sendClusterSpecificCommand(0x00); // Identify
break;
case 0x0400: // Luminance
zcl.readAttribute(0x0000).then(function(message) {
console.log('Response from luminance read', message);
}).catch(function(error) {
console.error('Failed on luminance read', error.stack);
}); // Measured Value
break;
case 0x0702: // Smart Energy: Metering
setInterval(function() {
zcl.readAttribute(0x0400).then(function(message) { // Instantaneous demand
console.log('Response from instantanteous demand read', message);
}).catch(function(error) {
console.error('Failed on instantanteous demand read', error.stack);
});
}, 2000);
break;
}*/
});
/*if (inClusters.indexOf(0x0702) != -1) { // Smart Energy: Metering
var zcl = new ZCLCluster(endpoint, 0x0702);
var i = 0;
setInterval(function() {
zcl.readAttribute(i++); // Measured Value
}, 1000);
}*/
}).catch(function(err) {
console.error('Failed getting clusters for device', err.stack);
});
});
}
function displayZigBeeDevices(zigbeeClient) {
zigbeeClient.devices().then(function(devices) {
var table = new Table({
head: ['ShortAddr', 'IEEE Address'],
colWidths: [12, 25],
});
devices.forEach(function(device) {
table.push([
device.shortAddress ? device.shortAddress.toString(16) : 'undefined',
device.IEEEAddress ? device.IEEEAddress.toString(16) : 'undefined'
]);
});
console.log(table.toString());
})
.catch(function(err) {
console.error(err.stack);
});
}
process.on('uncaughtException', function(err) {
console.error('UNCAUGHT ' + err);
console.error(err.stack);
});
;