UNPKG

node-miio

Version:

Control Mi Home devices, such as Mi Robot Vacuums, Mi Air Purifiers, Mi Smart Home Gateway (Aqara) and more

193 lines (148 loc) 3.49 kB
'use strict'; const { TimedDiscovery, BasicDiscovery, search, addService, removeService, } = require('tinkerhub-discovery'); const { Children } = require('abstract-things'); const util = require('util'); const dns = require('dns'); const network = require('./network'); const infoFromHostname = require('./infoFromHostname'); const connectToDevice = require('./connectToDevice'); const tryAdd = Symbol('tryAdd'); const Browser = (module.exports.Browser = class Browser extends TimedDiscovery { static get type() { return 'miio'; } constructor(options) { super({ maxStaleTime: (options.cacheTime || 1800) * 1000, }); this.manualTokens = options.tokens || {}; this[tryAdd] = this[tryAdd].bind(this); this.start(); } _manualToken(id) { return this.manualTokens[id] || null; } start() { this.handle = network.ref(); network.on('device', this[tryAdd]); super.start(); } stop() { super.stop(); network.removeListener('device', this[tryAdd]); this.handle.release(); } [search]() { network.search(); } [tryAdd](device) { const service = { id: device.id, address: device.address, port: device.port, token: device.token || this._manualToken(device.id), autoToken: device.autoToken, connect: function (options = {}) { return connectToDevice( Object.assign( { address: this.address, port: this.port, model: this.model, }, options ) ); }, }; const add = () => this[addService](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.model = info.model; } add(); }); } [util.inspect.custom]() { return 'MiioBrowser{}'; } }); class Devices extends BasicDiscovery { static get type() { return 'miio:devices'; } constructor(options) { super(); this._filter = options && options.filter; this._skipSubDevices = options && options.skipSubDevices; this._browser = new Browser(options).map((reg) => { return connectToDevice({ address: reg.address, port: reg.port, model: reg.model, withPlaceholder: true, token: options.token, }); }); this._browser.on('available', (s) => { this[addService](s); if (s instanceof Children) { this._bindSubDevices(s); } }); this._browser.on('unavailable', (s) => { this[removeService](s); }); } start() { super.start(); this._browser.start(); } stop() { super.stop(); this._browser.stop(); } [util.inspect.custom]() { return 'MiioDevices{}'; } _bindSubDevices(device) { if (this._skipSubDevices) return; const handleAvailable = (sub) => { if (!sub.miioModel) return; const reg = { id: sub.internalId, model: sub.model, type: sub.type, parent: device, device: sub, }; if (this._filter && !this._filter(reg)) { // Filter does not match sub device return; } // Register and emit event this[addService](sub); }; device.on('thing:available', handleAvailable); device.on('thing:unavailable', (sub) => this[removeService](sub.id)); // Register initial devices for (const child of device.children()) { handleAvailable(child); } } } module.exports.Devices = Devices;