homebridge-rinnai-touch-platform
Version:
Homebridge Plugin to control heating/cooling via a Rinnai Touch WiFi Module
242 lines • 9.08 kB
JavaScript
;
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