zigbee2mqtt/index.js

176 lines
6.0 KiB
JavaScript
Raw Normal View History

const debug = require('debug')('xiaomi-zb2mqtt')
const util = require("util");
const perfy = require('perfy');
const ZShepherd = require('zigbee-shepherd');
const mqtt = require('mqtt')
const ArgumentParser = require('argparse').ArgumentParser;
2017-09-27 09:39:10 -07:00
// Parse arguments
const parser = new ArgumentParser({
version: '1.0.0',
addHelp:true,
description: 'Xiaomi Zigbee to MQTT bridge using zigbee-shepherd'
});
2017-09-27 09:39:10 -07:00
parser.addArgument(
['-d', '--device'],
{
help: 'CC2531 USB stick location, E.G. /dev/ttyACM0',
required: true,
2017-09-27 09:39:10 -07:00
}
);
parser.addArgument(
['-m', '--mqtt'],
{
help: 'MQTT server URL, E.G. mqtt://192.168.1.10',
required: true,
}
);
2018-04-08 06:51:33 -07:00
parser.addArgument(
['--join'],
{
help: 'Allow new devices to join the network',
action: 'storeTrue',
}
);
const args = parser.parseArgs();
// Setup client
2018-04-08 06:28:59 -07:00
console.log(`Connecting to MQTT server at ${args.mqtt}`)
const client = mqtt.connect(args.mqtt)
const shepherd = new ZShepherd(args.device, {net: {panId: 0x1a62}});
2017-09-27 09:39:10 -07:00
2018-04-08 06:00:36 -07:00
// Register callbacks
shepherd.on('ready', handleReady);
client.on('connect', handleConnect);
shepherd.on('ind', handleInd);
2018-04-08 06:33:47 -07:00
process.on('SIGINT', handleQuit);
2018-04-08 06:00:36 -07:00
// Start server
2018-04-08 06:28:59 -07:00
console.log(`Starting zigbee-shepherd with device ${args.device}`)
2018-04-08 06:00:36 -07:00
shepherd.start((err) => {
if (err) {
2018-04-08 06:28:59 -07:00
console.error('Error while starting zigbee-shepherd');
2018-04-08 06:00:36 -07:00
console.error(err);
2018-04-08 06:28:59 -07:00
} else {
console.error('zigbee-shepherd started');
2018-04-08 06:00:36 -07:00
}
});
// Callbacks
function handleReady() {
2018-04-08 06:51:33 -07:00
console.log('zigbee-shepherd ready');
2018-04-08 06:28:59 -07:00
shepherd.list().forEach(function(dev){
if (dev.type === 'EndDevice')
console.log(dev.ieeeAddr + ' ' + dev.nwkAddr + ' ' + dev.modelId);
if (dev.manufId === 4151) // set all xiaomi devices to be online, so shepherd won't try to query info from devices (which would fail because they go tosleep)
shepherd.find(dev.ieeeAddr,1).getDevice().update({ status: 'online', joinTime: Math.floor(Date.now()/1000) });
});
2018-04-08 06:51:33 -07:00
// Allow or disallow new devices to join the network.
if (args.join) {
console.log('WARNING: --join parameter detected, allowing new devices to join. Remove this parameter once you added all devices.')
}
shepherd.permitJoin(args.join ? 255 : 0, (err) => {
if (err) {
2017-09-27 09:39:10 -07:00
console.log(err);
2018-04-08 06:51:33 -07:00
}
});
2018-04-08 06:00:36 -07:00
}
function handleConnect() {
client.publish('xiaomiZb', 'Bridge online');
}
function handleInd(msg) {
// debug('msg: ' + util.inspect(msg, false, null));
var pl = null;
2017-09-27 15:08:29 -07:00
var topic = 'xiaomiZb/';
2017-09-27 09:39:10 -07:00
switch (msg.type) {
case 'devIncoming':
console.log('Device: ' + msg.data + ' joining the network!');
break;
case 'attReport':
console.log('attreport: ' + msg.endpoints[0].device.ieeeAddr + ' ' + msg.endpoints[0].devId + ' ' + msg.endpoints[0].epId + ' ' + util.inspect(msg.data, false, null));
// defaults, will be extended or overridden based on device and message
topic += msg.endpoints[0].device.ieeeAddr.substr(2);
pl=1;
switch (msg.data.cid) {
case 'genOnOff': // various switches
topic += '/' + msg.endpoints[0].epId;
pl = msg.data.data['onOff'];
break;
case 'msTemperatureMeasurement': // Aqara Temperature/Humidity
topic += "/temperature";
pl = parseFloat(msg.data.data['measuredValue']) / 100.0;
break;
case 'msRelativeHumidity':
topic += "/humidity";
pl = parseFloat(msg.data.data['measuredValue']) / 100.0;
break;
case 'msPressureMeasurement':
topic += "/pressure";
pl = parseFloat(msg.data.data['16']) / 10.0;
break;
case 'msOccupancySensing': // motion sensor
topic += "/occupancy";
pl = msg.data.data['occupancy'];
break;
case 'msIlluminanceMeasurement':
topic += "/illuminance";
pl = msg.data.data['measuredValue'];
break;
}
switch (msg.endpoints[0].devId) {
case 260: // WXKG01LM switch
if (msg.data.data['onOff'] == 0) { // click down
perfy.start(msg.endpoints[0].device.ieeeAddr); // start timer
pl = null; // do not send mqtt message
} else if (msg.data.data['onOff'] == 1) { // click release
if (perfy.exists(msg.endpoints[0].device.ieeeAddr)) { // do we have timer running
var clicktime = perfy.end(msg.endpoints[0].device.ieeeAddr); // end timer
if (clicktime.seconds > 0 || clicktime.milliseconds > 240) { // seems like a long press so ..
topic = topic.slice(0,-1) + '2'; //change topic to 2
pl = clicktime.seconds + Math.floor(clicktime.milliseconds) + ''; // and payload to elapsed seconds
}
}
} else if (msg.data.data['32768']) { // multiple clicks
pl = msg.data.data['32768'];
}
}
break;
2017-09-27 09:39:10 -07:00
default:
// console.log(util.inspect(msg, false, null));
2017-09-27 09:39:10 -07:00
// Not deal with other msg.type in this example
break;
}
if (pl != null) { // only publish message if we have not set payload to null
console.log("MQTT Reporting to ", topic, " value ", pl)
client.publish(topic, pl.toString());
}
2018-04-08 06:33:47 -07:00
}
function handleQuit() {
console.log("Stopping zigbee-shepherd...");
shepherd.stop((err) => {
if (err) {
console.error('Error while stopping zigbee-shepherd');
} else {
console.error('zigbee-shepherd stopped')
}
process.exit();
});
}