2018-04-08 05:46:20 -07:00
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
2018-04-08 05:46:20 -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
2018-04-08 05:46:20 -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
}
2018-04-08 05:46:20 -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' ,
}
) ;
2018-04-08 05:46:20 -07:00
const args = parser . parseArgs ( ) ;
// Setup client
2018-04-08 06:28:59 -07:00
console . log ( ` Connecting to MQTT server at ${ args . mqtt } ` )
2018-04-08 05:46:20 -07:00
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
2017-12-24 13:41:01 -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
}
2017-09-27 14:56:21 -07:00
} ) ;
2018-04-08 06:00:36 -07:00
}
function handleConnect ( ) {
client . publish ( 'xiaomiZb' , 'Bridge online' ) ;
}
function handleInd ( msg ) {
2017-11-28 03:38:12 -07:00
// debug('msg: ' + util.inspect(msg, false, null));
2017-09-27 14:56:21 -07:00
var pl = null ;
2017-09-27 15:08:29 -07:00
var topic = 'xiaomiZb/' ;
2017-11-28 03:38:12 -07:00
2017-09-27 09:39:10 -07:00
switch ( msg . type ) {
case 'devIncoming' :
console . log ( 'Device: ' + msg . data + ' joining the network!' ) ;
break ;
case 'attReport' :
2017-09-27 14:56:21 -07:00
console . log ( 'attreport: ' + msg . endpoints [ 0 ] . device . ieeeAddr + ' ' + msg . endpoints [ 0 ] . devId + ' ' + msg . endpoints [ 0 ] . epId + ' ' + util . inspect ( msg . data , false , null ) ) ;
2017-11-28 03:38:12 -07:00
// defaults, will be extended or overridden based on device and message
topic += msg . endpoints [ 0 ] . device . ieeeAddr . substr ( 2 ) ;
2017-11-26 05:33:32 -07:00
pl = 1 ;
2017-09-27 14:56:21 -07:00
2017-11-28 03:38:12 -07:00
switch ( msg . data . cid ) {
case 'genOnOff' : // various switches
topic += '/' + msg . endpoints [ 0 ] . epId ;
2017-12-08 02:29:34 -07:00
pl = msg . data . data [ 'onOff' ] ;
2017-11-28 03:38:12 -07:00
break ;
case 'msTemperatureMeasurement' : // Aqara Temperature/Humidity
2017-11-26 05:33:32 -07:00
topic += "/temperature" ;
pl = parseFloat ( msg . data . data [ 'measuredValue' ] ) / 100.0 ;
2017-09-27 14:56:21 -07:00
break ;
2017-11-26 05:33:32 -07:00
case 'msRelativeHumidity' :
2017-12-08 02:29:34 -07:00
topic += "/humidity" ;
2017-11-26 05:33:32 -07:00
pl = parseFloat ( msg . data . data [ 'measuredValue' ] ) / 100.0 ;
break ;
case 'msPressureMeasurement' :
topic += "/pressure" ;
pl = parseFloat ( msg . data . data [ '16' ] ) / 10.0 ;
break ;
2017-12-08 02:29:34 -07:00
case 'msOccupancySensing' : // motion sensor
topic += "/occupancy" ;
pl = msg . data . data [ 'occupancy' ] ;
break ;
case 'msIlluminanceMeasurement' :
topic += "/illuminance" ;
pl = msg . data . data [ 'measuredValue' ] ;
break ;
2017-11-26 05:33:32 -07:00
}
switch ( msg . endpoints [ 0 ] . devId ) {
2017-11-28 03:38:12 -07:00
case 260 : // WXKG01LM switch
2017-09-27 14:56:21 -07:00
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
2017-11-28 03:38:12 -07:00
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
}
2017-09-27 14:56:21 -07:00
}
} else if ( msg . data . data [ '32768' ] ) { // multiple clicks
pl = msg . data . data [ '32768' ] ;
}
}
break ;
2017-09-27 09:39:10 -07:00
default :
2017-09-27 14:56:21 -07:00
// 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 ;
}
2017-09-27 14:56:21 -07:00
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 ( ) ;
} ) ;
}