From b679ed8b4bb85d573a582ca91054d6b8218a2767 Mon Sep 17 00:00:00 2001 From: Andreas Holstenson Date: Mon, 17 Apr 2017 10:57:22 +0200 Subject: [PATCH] Improving discovery with update event and model info --- cli/index.js | 3 ++- lib/discovery.js | 40 +++++++++++++++++++++++++++++++++++++--- lib/index.js | 39 +++------------------------------------ lib/infoFromHostname.js | 17 +++++++++++++++++ lib/models.js | 23 +++++++++++++++++++++++ 5 files changed, 82 insertions(+), 40 deletions(-) create mode 100644 lib/infoFromHostname.js create mode 100644 lib/models.js diff --git a/cli/index.js b/cli/index.js index 02bf334..0a90488 100755 --- a/cli/index.js +++ b/cli/index.js @@ -34,7 +34,8 @@ if(args.discover) { const browser = new Browser(60); browser.on('available', reg => { console.log('Device ID:', reg.id); - console.log('Address:', reg.address); + console.log('Model:', reg.model || 'Unknown'); + console.log('Address:', reg.address + (reg.hostname ? ' (' + reg.hostname + ')' : '')); console.log('Token:', reg.token); console.log(); }); diff --git a/lib/discovery.js b/lib/discovery.js index ce3999b..21bdca6 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -1,7 +1,9 @@ const dgram = require('dgram'); +const dns = require('dns'); const EventEmitter = require('events').EventEmitter; +const infoFromHostname = require('./infoFromHostname'); const Packet = require('./packet'); const PORT = 54321; @@ -63,7 +65,7 @@ class Browser { _search() { this._packet.handshake(); - const data = this._packet.raw; + const data = Buffer.from(this._packet.raw); this._socket.send(data, 0, data.length, PORT, '255.255.255.255'); if(this.cacheTime / 3 > 500) { @@ -75,14 +77,46 @@ class Browser { } _addService(service) { - const added = ! this._services[service.id]; + const existing = this._services[service.id]; this._services[service.id] = service; service.lastSeen = Date.now(); - if(added) { + if(existing) { + // This is an existing device, skip extra discovery + if(existing.address !== service.address) { + this._events.emit('update', service); + } + + return; + } + + let added = false; + const add = () => { + if(added) return; + added = true; + this._events.emit('available', service); } + + // Give us five seconds to try resolve some extras for new devices + setTimeout(add, 5000); + + dns.lookupService(service.address, service.port, (err, hostname) => { + if(err || ! hostname) { + add(); + return; + } + + service.hostname = hostname; + const info = infoFromHostname(hostname); + if(info) { + service.type = info.type; + service.model = info.model; + } + + add(); + }); } _removeService(name) { diff --git a/lib/index.js b/lib/index.js index da4bbd1..5c44ffd 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,29 +3,8 @@ const Discovery = require('./discovery'); const Device = require('./device'); -const AirPurifier = require('./devices/air-purifier'); -const Switch = require('./devices/switch'); -const Vacuum = require('./devices/vacuum'); -const Gateway = require('./devices/gateway'); - -const devices = { - 'zhimi.airpurifier.m1': AirPurifier, - 'zhimi.airpurifier.v1': AirPurifier, - 'zhimi.airpurifier.v2': AirPurifier, - 'zhimi.airpurifier.v6': AirPurifier, - - 'chuangmi.plug.m1': Switch, - 'chuangmi.plug.v1': Switch, - 'chuangmi.plug.v2': Switch, - - 'rockrobo.vacuum.v1': Vacuum, - - 'lumi.gateway.v2': Gateway, - 'lumi.gateway.v3': Gateway, -}; - module.exports.Device = Device; -module.exports.devices = devices; +const models = module.exports.models = require('./models'); /** * Resolve a device from the given options. @@ -67,7 +46,7 @@ module.exports.device = function(options) { const createDevice = module.exports.createDevice = function(options) { if(! options.address) throw new Error('Address to device is required'); - const d = devices[options.model]; + const d = models[options.model]; let device; if(! d) { device = new Device(options); @@ -81,19 +60,7 @@ const createDevice = module.exports.createDevice = function(options) { /** * Extract information about a device from its hostname on the local network. */ -module.exports.infoFromHostname = function(hostname) { - const m = /(.+)_miio(\d+)/g.exec(hostname); - if(! m) return null; - - const model = m[1].replace(/-/g, '.'); - - const device = devices[model]; - return { - model: model, - type: (device && device.TYPE) || 'generic', - id: m[2] - }; -}; +module.exports.infoFromHostname = require('./infoFromHostname.js'); module.exports.browser = function(options) { const cacheTime = options && options.cacheTime ? options.cacheTime : 1800; diff --git a/lib/infoFromHostname.js b/lib/infoFromHostname.js new file mode 100644 index 0000000..88c8e86 --- /dev/null +++ b/lib/infoFromHostname.js @@ -0,0 +1,17 @@ + +const models = require('./models'); + +module.exports = function(hostname) { + // Extract info via hostname structure + const m = /(.+)_miio(\d+)/g.exec(hostname); + if(! m) return null; + + const model = m[1].replace(/-/g, '.'); + + const device = models[model]; + return { + model: model, + type: (device && device.TYPE) || 'generic', + id: m[2] + }; +} diff --git a/lib/models.js b/lib/models.js new file mode 100644 index 0000000..856c574 --- /dev/null +++ b/lib/models.js @@ -0,0 +1,23 @@ +/** + * Mapping from models into high-level devices. + */ +const AirPurifier = require('./devices/air-purifier'); +const Switch = require('./devices/switch'); +const Vacuum = require('./devices/vacuum'); +const Gateway = require('./devices/gateway'); + +module.exports = { + 'zhimi.airpurifier.m1': AirPurifier, + 'zhimi.airpurifier.v1': AirPurifier, + 'zhimi.airpurifier.v2': AirPurifier, + 'zhimi.airpurifier.v6': AirPurifier, + + 'chuangmi.plug.m1': Switch, + 'chuangmi.plug.v1': require('./devices/chuangmi.plug.v1'), + 'chuangmi.plug.v2': Switch, + + 'rockrobo.vacuum.v1': Vacuum, + + 'lumi.gateway.v2': Gateway, + 'lumi.gateway.v3': Gateway, +};