UNPKG

homebridge-pioneer-avr-2025

Version:

A Pioneer AVR plugin for homebridge. This plugin is designed for Pioneer models that use Telnet Commands (e.g., VSX-922). It may not be compatible with newer models (e.g., VSX-LX304).

543 lines 23.7 kB
"use strict"; // src/telnet-avr/connection.ts var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Connection = void 0; const net_1 = __importDefault(require("net")); const messageQueue_1 = require("./messageQueue"); const dataHandler_1 = __importDefault(require("./dataHandler")); const exitHandler_1 = require("../exitHandler"); const discovery_1 = require("../discovery"); let onExitCalled = false; class Connection { constructor(telnetThis) { var _a, _b, _c; this.socket = null; this.lastConnect = null; this.connectionReady = false; this.lastWrite = null; this.clearQueueTimeout = null; this.disconnectTimeout = null; this.reconnectCounter = 0; this.forcedDisconnect = false; this.isConnecting = null; this.discoveredDevices = []; this.maxReconnectAttemptsBeforeDiscover = 10; this.maxReconnectAttempts = 1000; this.isReconnect = false; this.reconnectFunctionFlag = false; this.avr = telnetThis.avr; this.port = telnetThis.port; this.host = telnetThis.host; this.log = telnetThis.avr.log; this.device = telnetThis.device; this.discoveredDevices = (_a = telnetThis.platform.config.discoveredDevices) !== null && _a !== void 0 ? _a : []; this.messageQueue = new messageQueue_1.MessageQueue(this); this.dataHandler = new dataHandler_1.default(telnetThis, this.messageQueue); // Set default values from config or use defaults this.maxReconnectAttemptsBeforeDiscover = parseInt(((_b = telnetThis.platform.config.maxReconnectAttemptsBeforeDiscover) !== null && _b !== void 0 ? _b : "10").toString(), 10); this.maxReconnectAttempts = parseInt(((_c = telnetThis.platform.config.maxReconnectAttempts) !== null && _c !== void 0 ? _c : "1000").toString(), 10); // Ensure maxReconnectAttemptsBeforeDiscover is at least 10 and at most 100 if (this.maxReconnectAttemptsBeforeDiscover < 10) { this.maxReconnectAttemptsBeforeDiscover = 10; } else if (this.maxReconnectAttemptsBeforeDiscover > 100) { this.maxReconnectAttemptsBeforeDiscover = 100; } // Ensure maxReconnectAttempts is at least 100 and at most 1000 if (this.maxReconnectAttempts < 100) { this.maxReconnectAttempts = 100; } else if (this.maxReconnectAttempts > 100000) { this.maxReconnectAttempts = 100000; } // Ensure maxReconnectAttempts is greater than maxReconnectAttemptsBeforeDiscover if (this.maxReconnectAttempts <= this.maxReconnectAttemptsBeforeDiscover) { // Increase maxReconnectAttempts to be at least one more than maxReconnectAttemptsBeforeDiscover this.maxReconnectAttempts = this.maxReconnectAttemptsBeforeDiscover + 10; // Also, ensure it meets the minimum threshold for maxReconnectAttempts if (this.maxReconnectAttempts < 100) { this.maxReconnectAttempts = 100; } } (0, exitHandler_1.addExitHandler)(this.disconnectOnExit.bind(this), this); setTimeout(() => { clearInterval(this.checkConnInterval); this.checkConnInterval = setInterval(() => { var _a; if (this.connectionReady && this.reconnectCounter === 0 && this.lastWrite !== null && this.lastMessageReceived !== null && this.lastWrite - this.lastMessageReceived > 10000 && Date.now() - this.lastMessageReceived > 60 * 1000) { this.log.warn(` > Device ${this.avr.device.name} not responding.`); this.connectionReady = false; this.messageQueue.clearQueue(); if (this.connectionReady || !this.socket || ((_a = this.socket) === null || _a === void 0 ? void 0 : _a.readyState) === 'open') { this.connectionReady = false; this.disconnect(); } this.connect(); // this.isConnecting = Date.now(); } }, 3107); }, 5000); } disconnectOnExit() { var _a; onExitCalled = true; if (this.disconnectTimeout) { clearTimeout(this.disconnectTimeout); } if (this.checkConnInterval) { clearInterval(this.checkConnInterval); } if (this.reconnectTimeout) { clearTimeout(this.reconnectTimeout); } if (this.reconnectCallbackTimeout) { clearTimeout(this.reconnectCallbackTimeout); } if (this.connectionReady || !this.socket || ((_a = this.socket) === null || _a === void 0 ? void 0 : _a.readyState) === 'open') { this.connectionReady = false; this.disconnect(); } } connect(callback = () => { }) { var _a; // this.log.debug('connect called', this.connectionReady, this.isConnecting, this.socket) if (!this.connectionReady && this.isConnecting !== null && Date.now() - this.isConnecting < 30000) { setTimeout(callback, 1500); return; } if (this.socket) { if ((!this.connectionReady || this.socket.readyState !== 'open') && Date.now() - ((_a = this.lastConnect) !== null && _a !== void 0 ? _a : 0) > 15 * 1000) { this.reconnect(callback); } else if (this.connectionReady || this.socket.readyState === 'open') { this.log.debug('Already connected, delaying callback.'); setTimeout(callback, 1500); } else { try { callback(); } catch (e) { this.log.error('Connect callback error:', e); } } } else { // if (!this.socket) { this.initializeSocket(callback); } } initializeSocket(callback) { // this.log.debug('initializeSocket() called'); this.isConnecting = Date.now(); this.socket = new net_1.default.Socket(); this.socket.setTimeout(30 * 1000); this.socket.removeAllListeners('connect'); this.socket.on('connect', () => { if (onExitCalled || !this.socket) { return; } if (this.socket.destroyed) { return; } if (this.socket.connecting || this.socket.readyState !== 'open') { return; } this.reconnectCounter = 0; this.lastMessageReceived = null; this.lastConnect = Date.now(); this.log.debug('Socket connected.'); setTimeout(() => { this.sendMessage('?P', 'PWR', () => __awaiter(this, void 0, void 0, function* () { this.setConnectionReady(true); this.lastConnect = Date.now(); if (!this.isReconnect) { try { callback(); } catch (e) { this.log.error('Connect initializeSocket callback error:', e); } this.isReconnect = true; } else { this.log.info('>> successfuly reconnected ' + this.avr.device.name); } try { this.onConnect(); } catch (e) { this.log.error('Connect initializeSocket onConnect error:', e); } })); }, 500); }); this.socket.on('close', () => { this.handleClose(); }); this.socket.on('data', (data) => { this.handleData(data); }); this.socket.on('error', (err) => { this.handleError(err); }); this.socket.connect(this.port, this.host); } handleClose() { var _a, _b; this.isConnecting = null; this.setConnectionReady(false); if (onExitCalled) { return; } if (!this.connectionReady || !this.socket || !((_a = this.socket) === null || _a === void 0 ? void 0 : _a.connecting) || ((_b = this.socket) === null || _b === void 0 ? void 0 : _b.readyState) !== 'open') { if (this.avr.lastUserInteraction && Date.now() - this.avr.lastUserInteraction < 60 * 1000) { this.forcedDisconnect = false; this.log.debug('Socket closed, attempting reconnect.'); setTimeout(() => { this.tryReconnect(); }, 100); } } } handleData(data) { var _a; if (this.disconnectTimeout) { clearTimeout(this.disconnectTimeout); } this.disconnectTimeout = setTimeout(() => { var _a; if (this.connectionReady || !this.socket || ((_a = this.socket) === null || _a === void 0 ? void 0 : _a.readyState) === 'open') { this.connectionReady = false; this.disconnect(); } }, 35 * 1000); (_a = this.dataHandler) === null || _a === void 0 ? void 0 : _a.handleData(data); } handleError(err) { this.isConnecting = null; if (this.reconnectCounter > 1) { this.log.debug('Connection error:', err); } else { this.log.error('Connection error:', err); } if (this.disconnectTimeout) { clearTimeout(this.disconnectTimeout); } if (err.message.includes('CONN') || err.message.includes('EHOSTUNREACH') || err.message.includes('ENOTFOUND') || err.message.includes('ETIMEDOUT')) { this.setConnectionReady(false); this.disconnect(); if (!this.forcedDisconnect) { this.log.debug('Socket closed, attempting reconnect. (1)'); setTimeout(() => { this.tryReconnect(); }, 100); } } } tryReconnect() { if (onExitCalled) { return; } if (this.reconnectCounter >= (this.maxReconnectAttempts * 2)) { // process.exit(1); return; } if (this.disconnectTimeout) { clearTimeout(this.disconnectTimeout); } this.reconnectCounter++; let delay = this.reconnectCounter > 30 ? 60 : 15; if (this.reconnectCounter <= 1 || !this.avr.lastUserInteraction || (Date.now() - this.avr.lastUserInteraction < 60 * 1000)) { delay = 0; } if (delay === 0 || this.reconnectTimeout) { if (this.reconnectTimeout) { clearTimeout(this.reconnectTimeout); } this.reconnectTimeout = setTimeout(() => { if (onExitCalled || this.connectionReady) { return; } if (this.reconnectCounter > 1) { this.log.debug(`telnet> Attempting ${this.reconnectCounter}. reconnection to ${this.avr.device.name} ...`); } else { this.log.info(`telnet> Attempting reconnection to ${this.avr.device.name} ...`); } this.reconnect(() => { }); }, delay * 1000); } } reconnect(callback) { return __awaiter(this, void 0, void 0, function* () { // this.log.debug('reconnect() called'); var _a, _b, _c, _d, _e; if (onExitCalled) { return; } if (this.forcedDisconnect) { return; } if (this.reconnectFunctionFlag) { // this.log.debug('reconnect blocked by this.reconnectFunctionFlag'); return; } // this.log.debug('set true this.reconnectFunctionFlag', !!this.socket); this.reconnectFunctionFlag = true; if (this.disconnectTimeout) { clearTimeout(this.disconnectTimeout); } if (this.connectionReady || (this.socket && (((_a = this === null || this === void 0 ? void 0 : this.socket) === null || _a === void 0 ? void 0 : _a.connecting) || ((_b = this === null || this === void 0 ? void 0 : this.socket) === null || _b === void 0 ? void 0 : _b.readyState) === 'open'))) { try { callback(); } catch (error) { this.log.debug('reconnect callback error', error); } this.reconnectFunctionFlag = false; return; } if (this.reconnectCounter >= this.maxReconnectAttempts) { if (this.reconnectCallbackTimeout) { clearTimeout(this.reconnectCallbackTimeout); } this.reconnectCallbackTimeout = setTimeout(() => { try { callback(); } catch (error) { this.log.debug('reconnect callback error', error); } }, 1 * 24 * 60 * 60 * 1000); this.reconnectFunctionFlag = false; return; } if (this.reconnectCounter >= (this.maxReconnectAttempts * 2)) { // no callback. this.reconnectFunctionFlag = false; return; } if (!this.forcedDisconnect && this.reconnectCounter >= this.maxReconnectAttemptsBeforeDiscover && this.avr.device.source == 'bonjour') { const devices = yield (0, discovery_1.findDevices)(this.avr.device.origName, this.avr.platform.TELNET_PORTS || [23, 24, 8102], this.log, 1); if (devices.length > 0) { // First, filter devices matching by fqdn or host + origName of the current device const filteredDevices = devices.filter(device => (this.device.fqdn && device.fqdn === this.device.fqdn) || // Use fqdn as primary identifier (device.host === this.device.host && device.origName === this.device.origName) // Fallback: host + origName match ); let updatedDevice; // If no matching device is found, filter for new devices that are not already in discoveredDevices if (filteredDevices.length > 0) { updatedDevice = filteredDevices[0]; } else { const newDevices = devices.filter(device => !this.discoveredDevices.some(discovered => // Check if the device exists in discoveredDevices by matching fqdn (if available) or host + origName (discovered.fqdn && device.fqdn === discovered.fqdn) || (device.host === discovered.host && device.origName === discovered.origName))); updatedDevice = newDevices.length > 0 ? newDevices[0] : null; this.device = updatedDevice; } if (updatedDevice) { this.host = updatedDevice.host; this.port = updatedDevice.port; this.log.info(`Updated device ${this.avr.device.name} connection info: ${this.host}:${this.port}`); this.reconnectCounter = 0; // Reset counter after successful discovery yield new Promise((resolve) => setTimeout(resolve, 1201)); } } else { yield new Promise((resolve) => setTimeout(resolve, 5000)); } } if (this.socket && (this.socket.connecting || this.socket.readyState !== 'closed')) { let wc = 0; while (wc++ < 100 && ((_c = this === null || this === void 0 ? void 0 : this.socket) === null || _c === void 0 ? void 0 : _c.readyState) !== 'open') { yield new Promise((resolve) => setTimeout(resolve, 180)); } if (((_d = this === null || this === void 0 ? void 0 : this.socket) === null || _d === void 0 ? void 0 : _d.readyState) === 'open') { try { callback(); } catch (error) { this.log.debug('reconnect callback error', error); } } } else if (!this.socket) { this.initializeSocket(callback); } else if (!this.lastConnect || Date.now() - ((_e = this.lastConnect) !== null && _e !== void 0 ? _e : 0) > 40 * 1000) { if (this.connectionReady) { this.connectionReady = false; this.disconnect(); } this.log.debug('Reconnecting socket.'); this.connect(); try { callback(); } catch (error) { this.log.debug('reconnect callback error', error); } } else { if (this.reconnectCallbackTimeout) { clearTimeout(this.reconnectCallbackTimeout); } this.reconnectCallbackTimeout = setTimeout(() => { try { callback(); } catch (error) { this.log.debug('reconnect callback error', error); } }, 30000); } this.reconnectFunctionFlag = false; }); } disconnect() { var _a; if (((_a = this === null || this === void 0 ? void 0 : this.socket) === null || _a === void 0 ? void 0 : _a.readyState) === 'open') { this.log.info(`telnet> ${this.avr.device.name} disconnected`); } // this.log.debug('disconnect() called'); // this.reconnectFunctionFlag = false; this.lastMessageReceived = null; this.isConnecting = null; this.setConnectionReady(false); this.onDisconnect(); if (this.disconnectTimeout) { clearTimeout(this.disconnectTimeout); } if (this.socket) { this.socket.removeAllListeners('connect'); this.socket.end(); this.socket.destroy(); this.socket = null; } } isConnected() { return this.connectionReady; } sendMessage(message, callbackChars, callback) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; // this.log.debug('in sendMessage', message); if (!this.socket || !((_a = this.socket) === null || _a === void 0 ? void 0 : _a.connecting) && ((_b = this.socket) === null || _b === void 0 ? void 0 : _b.readyState) !== 'open') { if (this.reconnectCounter > 10 && this.avr.lastUserInteraction && Date.now() - this.avr.lastUserInteraction < 60 * 1000) { this.reconnectCounter = 0; this.forcedDisconnect = false; this.connect(); } else { this.tryReconnect(); } } if (this.disconnectTimeout) { clearTimeout(this.disconnectTimeout); } this.disconnectTimeout = setTimeout(() => { var _a; if (this.connectionReady || !this.socket || ((_a = this.socket) === null || _a === void 0 ? void 0 : _a.readyState) === 'open') { this.connectionReady = false; this.disconnect(); } }, 35 * 1000); if (!message.startsWith('?') && !message.startsWith('!') && this.socket && ((_c = this.socket) === null || _c === void 0 ? void 0 : _c.readyState) === 'open' && callbackChars === undefined && this.messageQueue.queue.length === 0) { this.directSend(message, callback); } else { this.queueMessage(message, callbackChars, callback); } }); } directSend(message, callback) { var _a; // this.log.debug('in directSend', message); if (!this.socket || this.socket.connecting || this.socket.readyState !== 'open') { this.log.warn('Connection not ready, skipping direct send.'); return; } if (message.startsWith('!')) { message = message.substring(1); } this.log.debug('telnet write>', message); (_a = this.socket) === null || _a === void 0 ? void 0 : _a.write(message + '\r\n'); this.setLastWrite(Date.now()); callback === null || callback === void 0 ? void 0 : callback(null, `${message}:SENT`); this.messageQueue.processQueue(); } queueMessage(message, callbackChars, callback) { // this.log.debug('in queueMessage', message); this.messageQueue.enqueue(message, callbackChars, callback); if (this.clearQueueTimeout) { clearTimeout(this.clearQueueTimeout); } this.clearQueueTimeout = setTimeout(() => { this.messageQueue.clearQueue(); }, 25 * 1000); } onDisconnect() { this.log.debug('Disconnected!'); } onConnect() { this.log.debug('Connected!'); } setConnectionReady(ready) { this.connectionReady = ready; } setLastWrite(timestamp) { this.lastWrite = timestamp; } // Accessor for lastMessageReceived from DataHandler get lastMessageReceived() { var _a; return ((_a = this.dataHandler) === null || _a === void 0 ? void 0 : _a.lastMessageReceived) || null; } // Setter for the last message received timestamp set lastMessageReceived(value) { if (this.dataHandler) { this.dataHandler.lastMessageReceived = value; } } } exports.Connection = Connection; //# sourceMappingURL=connection.js.map