UNPKG

@palekseii/homebridge-tuya-platform

Version:

Fork version of official Tuya Homebridge plugin. Brings a bunch of bug fix and new device support.

309 lines 14.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = __importDefault(require("events")); const TuyaOpenMQ_1 = __importDefault(require("../core/TuyaOpenMQ")); const Logger_1 = require("../util/Logger"); const TuyaDevice_1 = __importStar(require("./TuyaDevice")); var Events; (function (Events) { Events["DEVICE_ADD"] = "DEVICE_ADD"; Events["DEVICE_INFO_UPDATE"] = "DEVICE_INFO_UPDATE"; Events["DEVICE_STATUS_UPDATE"] = "DEVICE_STATUS_UPDATE"; Events["DEVICE_DELETE"] = "DEVICE_DELETE"; })(Events || (Events = {})); var TuyaMQTTProtocol; (function (TuyaMQTTProtocol) { TuyaMQTTProtocol[TuyaMQTTProtocol["DEVICE_STATUS_UPDATE"] = 4] = "DEVICE_STATUS_UPDATE"; TuyaMQTTProtocol[TuyaMQTTProtocol["DEVICE_INFO_UPDATE"] = 20] = "DEVICE_INFO_UPDATE"; })(TuyaMQTTProtocol || (TuyaMQTTProtocol = {})); class TuyaDeviceManager extends events_1.default { constructor(api, debug = false) { super(); this.api = api; this.debug = debug; this.ownerIDs = []; this.devices = []; const log = this.api.log.log; this.log = new Logger_1.PrefixLogger(log, TuyaDeviceManager.name, debug); this.mq = new TuyaOpenMQ_1.default(api, log); this.mq.addMessageListener(this.onMQTTMessage.bind(this)); } getDevice(deviceID) { return Array.from(this.devices).find(device => device.id === deviceID); } // eslint-disable-next-line @typescript-eslint/no-unused-vars async updateDevices(ownerIDs) { return []; } async updateDevice(deviceID) { const res = await this.getDeviceInfo(deviceID); if (!res.success) { return null; } const device = new TuyaDevice_1.default(res.result); device.schema = await this.getDeviceSchema(deviceID); const oldDevice = this.getDevice(deviceID); if (oldDevice) { this.devices.splice(this.devices.indexOf(oldDevice), 1); } this.devices.push(device); return device; } async getDeviceInfo(deviceID) { const res = await this.api.get(`/v1.0/devices/${deviceID}`); return res; } async getDeviceListInfo(deviceIDs = []) { const res = await this.api.get('/v1.0/devices', { 'device_ids': deviceIDs.join(',') }); return res; } async getDeviceSchema(deviceID) { // const res = await this.api.get(`/v1.2/iot-03/devices/${deviceID}/specification`); const res = await this.api.get(`/v1.0/devices/${deviceID}/specifications`); if (res.success === false) { this.log.warn('Get device specification failed. devId = %s, code = %s, msg = %s', deviceID, res.code, res.msg); return []; } // Combine functions and status together, as it used to be. const schemas = new Map(); for (const { code, type, values } of [...res.result.status, ...res.result.functions]) { if (schemas[code]) { continue; } const read = (res.result.status).find(schema => schema.code === code) !== undefined; const write = (res.result.functions).find(schema => schema.code === code) !== undefined; let mode = TuyaDevice_1.TuyaDeviceSchemaMode.UNKNOWN; if (read && write) { mode = TuyaDevice_1.TuyaDeviceSchemaMode.READ_WRITE; } else if (read && !write) { mode = TuyaDevice_1.TuyaDeviceSchemaMode.READ_ONLY; } else if (!read && write) { mode = TuyaDevice_1.TuyaDeviceSchemaMode.WRITE_ONLY; } let property; try { property = JSON.parse(values); schemas[code] = { code, mode, type, property }; } catch (error) { // ignore infrared remote's invalid schema because it's not used. } } return Object.values(schemas).sort((a, b) => a.code > b.code ? 1 : -1); } async getInfraredRemotes(infraredID) { const res = await this.api.get(`/v2.0/infrareds/${infraredID}/remotes`); return res; } async getInfraredKeys(infraredID, remoteID) { const res = await this.api.get(`/v2.0/infrareds/${infraredID}/remotes/${remoteID}/keys`); return res; } async getInfraredACStatus(infraredID, remoteID) { const res = await this.api.get(`/v2.0/infrareds/${infraredID}/remotes/${remoteID}/ac/status`); return res; } async getInfraredDIYKeys(infraredID, remoteID) { const res = await this.api.get(`/v2.0/infrareds/${infraredID}/remotes/${remoteID}/learning-codes`); return res; } async updateInfraredRemotes(allDevices) { var _a; const irDevices = allDevices.filter(device => device.isIRControlHub()); for (const irDevice of irDevices) { const res = await this.getInfraredRemotes(irDevice.id); if (!res.success) { this.log.warn('Get infrared remotes failed. deviceId = %d, code = %s, msg = %s', irDevice.id, res.code, res.msg); continue; } for (const { category_id, remote_id } of res.result) { const subDevice = allDevices.find(device => device.id === remote_id); if (!subDevice) { continue; } subDevice.parent_id = irDevice.id; subDevice.schema = []; const res = await this.getInfraredKeys(irDevice.id, subDevice.id); if (!res.success) { this.log.warn('Get infrared remote keys failed. deviceId = %d, code = %s, msg = %s', subDevice.id, res.code, res.msg); continue; } subDevice.remote_keys = res.result; if (subDevice.category === 'infrared_ac') { // AC Device const res = await this.getInfraredACStatus(irDevice.id, subDevice.id); if (!res.success) { this.log.warn('Get infrared ac status failed. deviceId = %d, code = %s, msg = %s', subDevice.id, res.code, res.msg); continue; } subDevice.status = Object.entries(res.result).map(([key, value]) => ({ code: key, value })); } else if (category_id === 999) { // DIY Device const res = await this.getInfraredDIYKeys(irDevice.id, subDevice.id); if (!res.success) { this.log.warn('Get infrared diy keys failed. deviceId = %d, code = %s, msg = %s', subDevice.id, res.code, res.msg); continue; } const key_list = ((_a = subDevice.remote_keys) === null || _a === void 0 ? void 0 : _a.key_list) || []; for (const key of key_list) { const item = res.result.find(item => item['id'] === key.key_id && item['key'] === key.key); if (!item) { continue; } this.log.debug('learning_code:', item['code']); key.learning_code = item['code']; } } } } } async sendInfraredCommands(infraredID, remoteID, category_id, remote_index, key, key_id) { const res = await this.api.post(`/v2.0/infrareds/${infraredID}/remotes/${remoteID}/raw/command`, { category_id, remote_index, key, key_id, }); return res; } async sendInfraredACCommands(infraredID, remoteID, power, mode, temp, wind) { const commands = (power === 1) ? { power, mode, temp, wind } : { power }; const res = await this.api.post(`/v2.0/infrareds/${infraredID}/air-conditioners/${remoteID}/scenes/command`, commands); if (!res.success) { this.log.info('Send AC command failed. code = %d, msg = %s', res.code, res.msg); } return res; } async sendInfraredDIYCommands(infraredID, remoteID, code) { const res = await this.api.post(`/v2.0/infrareds/${infraredID}/remotes/${remoteID}/learning-codes`, { code }); return res; } async getLockTemporaryKey(deviceID) { // const res = await this.api.post(`/v1.0/smart-lock/devices/${deviceID}/door-lock/password-ticket`); const res = await this.api.post(`/v1.0/smart-lock/devices/${deviceID}/password-ticket`); if (res.success === false) { this.log.warn('Get Temporary Pass failed. devID = %s, code = %s, msg = %s', deviceID, res.code, res.msg); } return res; } async sendLockCommands(deviceID, ticketID, open) { const res = await this.api.post(`/v1.0/smart-lock/devices/${deviceID}/password-free/door-operate`, { device_id: deviceID, ticket_id: ticketID, open, }); return res; } async sendCommands(deviceID, commands) { const res = await this.api.post(`/v1.0/devices/${deviceID}/commands`, { commands }); return res.result; } async onMQTTMessage(topic, protocol, message) { switch (protocol) { case TuyaMQTTProtocol.DEVICE_STATUS_UPDATE: { const { devId, status } = message; const device = this.getDevice(devId); if (!device) { return; } for (const item of device.status) { const _item = status.find(_item => _item.code === item.code); if (!_item) { continue; } item.value = _item.value; } this.emit(Events.DEVICE_STATUS_UPDATE, device, status); break; } case TuyaMQTTProtocol.DEVICE_INFO_UPDATE: { const { bizCode, bizData, devId } = message; if (bizCode === 'bindUser') { const { ownerId } = bizData; if (!this.ownerIDs.includes(ownerId)) { this.log.warn('Update devId = %s not included in your ownerIDs. Skip.', devId); return; } // TODO failed if request to quickly await new Promise(resolve => setTimeout(resolve, 10000)); const device = await this.updateDevice(devId); if (!device) { return; } this.mq.start(); // Force reconnect, unless new device status update won't get received this.emit(Events.DEVICE_ADD, device); } else if (bizCode === 'nameUpdate') { const { name } = bizData; const device = this.getDevice(devId); if (!device) { return; } device.name = name; this.emit(Events.DEVICE_INFO_UPDATE, device, bizData); } else if (bizCode === 'online' || bizCode === 'offline') { const device = this.getDevice(devId); if (!device) { return; } device.online = (bizCode === 'online') ? true : false; this.emit(Events.DEVICE_INFO_UPDATE, device, bizData); } else if (bizCode === 'delete') { const { ownerId } = bizData; if (!this.ownerIDs.includes(ownerId)) { this.log.warn('Remove devId = %s not included in your ownerIDs. Skip.', devId); return; } const device = this.getDevice(devId); if (!device) { return; } this.devices.splice(this.devices.indexOf(device), 1); this.emit(Events.DEVICE_DELETE, devId); } else if (bizCode === 'event_notify') { // doorbell event } else if (bizCode === 'p2pSignal') { // p2p signal } else { this.log.warn('Unhandled mqtt message: bizCode = %s, bizData = %o', bizCode, bizData); } break; } default: this.log.warn('Unhandled mqtt message: protocol = %s, message = %o', protocol, message); break; } } } exports.default = TuyaDeviceManager; TuyaDeviceManager.Events = Events; //# sourceMappingURL=TuyaDeviceManager.js.map