UNPKG

node-mihome

Version:

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

206 lines (178 loc) 5.38 kB
const EventEmitter = require('events'); const fetch = require('node-fetch'); const miioProtocol = require('./protocol-miio'); const miCloudProtocol = require('./protocol-micloud'); const sleep = time => { return new Promise(resolve => { setTimeout(() => { resolve(); }, time); }); }; module.exports = class MiioDevice extends EventEmitter { constructor({ id, address, token, protocol, refresh, }) { super(); this.id = String(id); this.address = address; this.token = token; this.protocol = protocol || 'local'; this.refresh = refresh >= 0 ? refresh : 15000; this._properties = {}; this._propertiesToMonitor = []; this._miotSpec = null; this._miotSpecType = null; miioProtocol.updateDevice(address, { id, token, }); } get properties() { return { ...this._properties }; } async init() { if (this._miotSpecType) { await this.miotFetchSpec(this._miotSpecType); } await this.loadProperties(); await this.poll(); } destroy() { if (this._refreshInterval) { clearInterval(this._refreshInterval); } } async send(method, params, options = {}) { const result = this.protocol === 'cloud' ? await this.cloudSend(method, params, options) : this.localSend(method, params, options); return result; } async localSend(method, params, options = {}) { const result = await miioProtocol.send(this.address, method, params, options); return result; } async cloudSend(method, params, options) { const result = await miCloudProtocol.miioCall(this.id, method, params, options); return result; } async poll() { if (this.refresh > 0) { this._refreshInterval = setInterval(async () => { await this.loadProperties(); }, this.refresh); } } async loadProperties(props) { try { if (typeof props === 'undefined') { props = this._propertiesToMonitor; } const data = {}; const propsChunks = []; const chunkSize = 16; for (let i = 0; i < props.length; i += chunkSize) { propsChunks.push(props.slice(i, i + chunkSize)); } let result = []; for (const propChunk of propsChunks) { const resultChunk = await this.getProperties(propChunk); if (!resultChunk) { throw new Error('Properties is empty'); } if (resultChunk.length !== propChunk.length) { throw new Error(`Result ${JSON.stringify(resultChunk)} and props ${JSON.stringify(propChunk)} does not match length`); } result = result.concat(resultChunk); } props.forEach((prop, i) => { const value = result[i]; data[prop] = value; }); this._properties = Object.assign(this._properties, data); this.emit('available', true); this.emit('properties', data); } catch (e) { this.emit('unavailable', e.message); } } async getProperties(props) { const result = this._miotSpec ? await this.miotGetProperties(props) : await this.miioGetProperties(props); return result; } async miioGetProperties(props) { const result = await this.send('get_prop', props, { retries: 1, }); return result; } async miotGetProperties(props) { const did = this.id; const params = props.map(prop => { const { siid, piid } = this._miotSpec[prop]; return { did, siid, piid }; }); const result = await this.send('get_properties', params, { retries: 1, }); return result.map(({ code, value }) => { if (code === 0) { return value; } return undefined; }); } async miioCall(method, params, options = {}) { const result = await this.send(method, params, options); if (options.refresh !== false) { await sleep(50); await this.loadProperties(options.refresh); } // TODO check OK return result; } async miotSetProperty(prop, value, options = {}) { if (!this._miotSpec) { throw new Error('This device don\'t config miot spec'); } const def = this._miotSpec[prop]; if (!def) { throw new Error(`Property ${prop} is not define`); } const { siid, piid } = def; const did = this.id; const result = await this.send('set_properties', [{ did, siid, piid, value, }]); if (!result || !result[0] || result[0].code !== 0) { throw new Error('Could not perform operation'); } if (options.refresh !== false) { await sleep(50); await this.loadProperties(options.refresh); } return result[0]; } async miotFetchSpec(spec) { const url = `https://miot-spec.org/miot-spec-v2/instance?type=${spec}`; const res = await fetch(url); if (!res.ok) { throw new Error(`Get spec error with status ${res.statusText}`); } const { services } = await res.json(); const result = {}; services.forEach(service => { const { properties } = service; properties.forEach(property => { const key = [service.type.split(':')[3], property.type.split(':')[3]].join(':'); result[key] = { siid: service.iid, piid: property.iid, desc: `${service.description} - ${property.description}`, }; }); }); this._miotSpec = result; return result; } };