UNPKG

homebridge-rinnai-touch-platform

Version:

Homebridge Plugin to control heating/cooling via a Rinnai Touch WiFi Module

242 lines 9.08 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RinnaiSession = void 0; const events = require("events"); const schedule = require("node-schedule"); const cq = require("concurrent-queue"); const TcpService_1 = require("./TcpService"); const Status_1 = require("../models/Status"); const Command_1 = require("../models/Command"); class RinnaiSession extends events.EventEmitter { constructor(options = {}) { var _a, _b, _c, _d, _e, _f; super(); this._status = new Status_1.Status; this.connectionError = false; this.setMaxListeners(40); this.log = (_a = options.log) !== null && _a !== void 0 ? _a : console; if (options.address) { this.address = { address: options.address, port: (_b = options.port) !== null && _b !== void 0 ? _b : 27847, }; } this.showModuleEvents = (_c = options.showModuleEvents) !== null && _c !== void 0 ? _c : true; this.showModuleStatus = (_d = options.showModuleStatus) !== null && _d !== void 0 ? _d : false; if (((_e = options.bootTime) !== null && _e !== void 0 ? _e : '').length === 5 && ((_f = options.bootPassword) !== null && _f !== void 0 ? _f : '').length > 0) { this.bootTime = { hour: Number(options.bootTime.substring(0, 2)), minute: Number(options.bootTime.substring(3, 5)), }; this.bootPassword = options.bootPassword; } this.tcp = new TcpService_1.TcpService({ log: this.log, address: this.address }); this.queue = cq() .limit({ concurrency: 1 }) .process(this.process.bind(this)); } async start() { this.log.debug(this.constructor.name, 'start'); let connected = false; while (!connected) { for (let i = 1; i <= 3; i++) { try { await this.tcp.connect(); connected = true; this.connectionError = false; this.emit('connection'); break; } catch (error) { this.connectionError = true; this.emit('connection'); if (error instanceof Error) { this.log.warn(`TCP Connection failed. Attempt ${i} of 3 [Error: ${error.message}]`); } await this.delay(500); } } if (!connected) { this.log.warn('Will try again in 1 minute'); await this.delay(60000); } } // message handler this.tcp.on('message', this.receiveMessage.bind(this)); // error handler this.tcp.on('connection_error', this.handleConnectionError.bind(this)); // Ping Job this.jobPing = schedule.scheduleJob('*/1 * * * *', async () => { await this.sendCommand(new Command_1.Command({ command: Command_1.Commands.Ping })); }); // Boot Job if (this.bootTime !== undefined) { this.jobBoot = schedule.scheduleJob(`${this.bootTime.minute} ${this.bootTime.hour} * * *`, async () => { await this.sendCommand(new Command_1.Command({ command: Command_1.Commands.Boot })); }); } // Wait for first status await new Promise((resolve) => { this.once('status', resolve); }); } stop() { var _a, _b; this.log.debug(this.constructor.name, 'stop'); try { (_a = this.jobPing) === null || _a === void 0 ? void 0 : _a.cancel(); (_b = this.jobBoot) === null || _b === void 0 ? void 0 : _b.cancel(); this.tcp.removeAllListeners(); this.tcp.destroy(); } catch (error) { if (error instanceof Error) { this.log.error(error.message); } } } get message() { return this._message; } get status() { return this._status; } async sendCommand(command) { this.log.debug(this.constructor.name, 'sendCommand', command.toString()); try { await this.queue(command); } catch (error) { if (error instanceof Error) { this.log.error(error.message); } throw error; } } async process(command) { this.log.debug(this.constructor.name, 'process', command.toString()); try { if (command.isBoot) { const payload = `CS<DVPW>${this.bootPassword}<BOOT>\r`; this.log.info(`Sending: ${payload}`); await this.tcp.write(payload); return; } let payload = 'N' + this.getNextSequence(); const states = command.toJson(this.status); if (!command.isPing) { if (states === undefined) { this.log.warn(`${command.toString()} is invalid due to module's current Status`); return; } if (this.status.hasStates(states)) { this.log.debug(`${command.toString()} not required as Status already in the requested state`); return; } payload += JSON.stringify(states); } if (this.showModuleEvents && !command.isPing) { this.log.info(`Sending: ${payload}`); } for (let i = 1; i <= 3; i++) { await this.tcp.write(payload); if (command.isPing) { return; } const success = await this.commandSucceeded(states); if (success) { return; } this.log.warn(`Command failed. Attempt ${i} of 3`); } } catch (error) { if (error instanceof Error) { this.log.error(error.message); } throw error; } } async commandSucceeded(states) { this.log.debug(this.constructor.name, 'commandSucceeded', states); let checkStatus; return new Promise((resolve, reject) => { try { const startTime = Date.now(); const timerId = setTimeout(() => { this.off('status', checkStatus); resolve(false); }, 10000); checkStatus = (status) => { if (status.hasStates(states)) { clearTimeout(timerId); if (this.showModuleEvents) { this.log.info(`Command succeeded. Took ${Date.now() - startTime} ms`); } this.off('status', checkStatus); resolve(true); } }; this.on('status', checkStatus); } catch (error) { this.off('status', checkStatus); reject(error); } }); } receiveMessage(message) { var _a; this.log.debug(this.constructor.name, 'receiveMessage', message.toString()); try { if (message.status === ((_a = this.message) === null || _a === void 0 ? void 0 : _a.status)) { return; } if (this.showModuleStatus) { this.log.info(message.status); } this._message = message; this.status.update(message.status); this.emit('status', this.status); } catch (error) { if (error instanceof Error) { this.log.error(error.message); } } } async handleConnectionError(error) { this.log.debug(this.constructor.name, 'handleError', error); try { this.log.warn(`TCP Connection failed. Attempting to reconnect [Error: ${error}]`); this.connectionError = true; this.emit('connection'); this.stop(); await this.delay(2000); await this.start(); } catch (error) { if (error instanceof Error) { this.log.error(error.message); } } } get hasConnectionError() { return this.connectionError; } getNextSequence() { var _a, _b; let nextSequence = (((_b = (_a = this.message) === null || _a === void 0 ? void 0 : _a.sequence) !== null && _b !== void 0 ? _b : 0) + 1) % 255; if (nextSequence === 0) { nextSequence = 1; } return nextSequence.toString().padStart(6, '0'); } async delay(ms) { await new Promise((resolve) => { setTimeout(resolve, ms); }); } } exports.RinnaiSession = RinnaiSession; //# sourceMappingURL=RinnaiSession.js.map