UNPKG

homebridge-lg-ac

Version:

A Homebridge plugin for controlling/monitoring LG AirConditioning device via LG ThinQ platform.

209 lines 9.25 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 }); exports.ThinQ = void 0; const API_1 = require("./API"); const Device_1 = require("./Device"); const Path = __importStar(require("path")); const forge = __importStar(require("node-forge")); const DeviceModel_1 = require("./DeviceModel"); const settings_1 = require("../settings"); const aws_iot_device_sdk_1 = require("aws-iot-device-sdk"); const url_1 = require("url"); const Persist_1 = __importDefault(require("./Persist")); class ThinQ { constructor(platform, config, log) { this.platform = platform; this.config = config; this.log = log; this.deviceModel = {}; this.api = new API_1.API(this.config.country, this.config.language); this.api.logger = log; this.api.httpClient.interceptors.response.use(response => { this.log.debug('[request]', response.config.method, response.config.url); return response; }, err => { return Promise.reject(err); }); if (config.refresh_token) { this.api.setRefreshToken(config.refresh_token); } else if (config.username && config.password) { this.api.setUsernamePassword(config.username, config.password); } this.persist = new Persist_1.default(Path.join(this.platform.api.user.storagePath(), settings_1.PLUGIN_NAME, 'persist', 'devices')); } async devices() { const listDevices = await this.api.getListDevices().catch(() => { return []; }); return listDevices.map(dev => new Device_1.Device(dev)); } async setup(device) { // load device model device.deviceModel = await this.loadDeviceModel(device); if (device.deviceModel.data.Monitoring === undefined && device.deviceModel.data.MonitoringValue === undefined && device.deviceModel.data.Value === undefined) { this.log.warn('[' + device.name + '] This device may not "smart" device. Ignore it!'); } return true; } async loadDeviceModel(device) { let deviceModel = await this.persist.getItem(device.id); if (!deviceModel) { this.log.debug('[' + device.id + '] Device model cache missed.'); deviceModel = await this.api.httpClient.get(device.data.modelJsonUri).then(res => res.data); await this.persist.setItem(device.id, deviceModel); } return this.deviceModel[device.id] = device.deviceModel = new DeviceModel_1.DeviceModel(deviceModel); } deviceControl(device, values, command = 'Set', ctrlKey = 'basicCtrl') { const id = device instanceof Device_1.Device ? device.id : device; return this.api.sendCommandToDevice(id, values, command, ctrlKey).catch(err => { var _a, _b; // submitted same value if (((_b = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.resultCode) === '0103') { return false; } this.log.error('Unknown Error: ', err.response); }); } async registerMQTTListener(callback) { const delayMs = ms => new Promise(res => setTimeout(res, ms)); let tried = 5; while (tried > 0) { try { await this._registerMQTTListener(callback); return; } catch (err) { tried--; this.log.debug('Cannot start MQTT, retrying in 5s.'); this.log.debug('mqtt err:', err); await delayMs(5000); } } this.log.error('Cannot start MQTT!'); } async _registerMQTTListener(callback) { const route = await this.api.getRequest('https://common.lgthinq.com/route').then(data => data.result); // key-pair const keys = await this.persist.cacheForever('keys', async () => { this.log.debug('Generating 2048-bit key-pair...'); const keys = forge.pki.rsa.generateKeyPair(2048); return { privateKey: forge.pki.privateKeyToPem(keys.privateKey), publicKey: forge.pki.publicKeyToPem(keys.publicKey), }; }); // CSR const csr = await this.persist.cacheForever('csr', async () => { this.log.debug('Creating certification request (CSR)...'); const csr = forge.pki.createCertificationRequest(); csr.publicKey = forge.pki.publicKeyFromPem(keys.publicKey); csr.setSubject([ { shortName: 'CN', value: 'AWS IoT Certificate', }, { shortName: 'O', value: 'Amazon', }, ]); csr.sign(forge.pki.privateKeyFromPem(keys.privateKey), forge.md.sha256.create()); return forge.pki.certificationRequestToPem(csr); }); const submitCSR = async () => { await this.api.postRequest('service/users/client', {}); return await this.api.postRequest('service/users/client/certificate', { csr: csr.replace(/-----(BEGIN|END) CERTIFICATE REQUEST-----/g, '').replace(/(\r\n|\r|\n)/g, ''), }).then(data => data.result); }; const urls = new url_1.URL(route.mqttServer); // get trusted cer root based on hostname let rootCAUrl; if (urls.hostname.match(/^([^.]+)-ats.iot.([^.]+).amazonaws.com$/g)) { // ats endpoint rootCAUrl = 'https://www.amazontrust.com/repository/AmazonRootCA1.pem'; } else if (urls.hostname.match(/^([^.]+).iot.ruic.lgthinq.com$/g)) { // LG owned certificate - Comodo CA rootCAUrl = 'http://www.tbs-x509.com/Comodo_AAA_Certificate_Services.crt'; } else { // use legacy VeriSign cert for other endpoint // eslint-disable-next-line max-len rootCAUrl = 'https://www.websecurity.digicert.com/content/dam/websitesecurity/digitalassets/desktop/pdfs/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem'; } const rootCA = await this.api.getRequest(rootCAUrl); const connectToMqtt = async () => { // submit csr const certificate = await submitCSR(); const connectData = { caCert: Buffer.from(rootCA, 'utf-8'), privateKey: Buffer.from(keys.privateKey, 'utf-8'), clientCert: Buffer.from(certificate.certificatePem, 'utf-8'), clientId: this.api.client_id, host: urls.hostname, }; this.log.debug('open mqtt connection to', route.mqttServer); const device = (0, aws_iot_device_sdk_1.device)(connectData); device.on('error', (err) => { this.log.error('mqtt err:', err); }); device.on('connect', () => { this.log.info('Successfully connected to the MQTT server.'); this.log.debug('mqtt connected:', route.mqttServer); for (const subscription of certificate.subscriptions) { device.subscribe(subscription); } }); device.on('message', (topic, payload) => { callback(JSON.parse(payload.toString())); this.log.debug('mqtt message received:', payload.toString()); }); device.on('offline', () => { device.end(); this.log.info('MQTT disconnected, retrying in 60 seconds!'); setTimeout(async () => { await connectToMqtt(); }, 60000); }); }; // first call await connectToMqtt(); } async isReady() { await this.persist.init(); await this.api.ready(); } } exports.ThinQ = ThinQ; //# sourceMappingURL=ThinQ.js.map