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).
468 lines • 21 kB
JavaScript
"use strict";
// src/pioneer-avr/volume.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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.VolumeManagementMixin = VolumeManagementMixin;
const exitHandler_1 = require("../exitHandler");
/**
* This mixin adds volume management methods to a base class, including volume control,
* mute functionality, and listening mode toggling.
* @param Base - The base class to extend with volume management methods.
* @returns A new class that extends the base class with added volume management functionality.
*/
function VolumeManagementMixin(Base) {
return class extends Base {
// public allListeingModes: { [key: string]: string } = {
// '0001': 'STEREO (cyclic)',
// '0009': 'STEREO (direct set) (set to SCI-FI mode.)',
// '0151': 'Auto Level Control (A.L.C.)',
// '0003': 'Front Stage Surround Advance Focus',
// '0004': 'Front Stage Surround Advance Wide (set to PURE DIRECT)',
// '0153': 'RETRIEVER AIR',
// '0010': 'STANDARD mode.',
// '0011': '(2ch source)',
// '0013': 'PRO LOGIC2 MOVIE',
// '0018': 'PRO LOGIC2x MOVIE',
// '0014': 'PRO LOGIC2 MUSIC',
// '0019': 'PRO LOGIC2x MUSIC',
// '0015': 'PRO LOGIC2 GAME',
// '0020': 'PRO LOGIC2x GAME',
// '0032': 'WIDE SURROUND MOVIE',
// '0033': 'WIDE SURROUND MUSIC',
// '0012': 'PRO LOGIC',
// '0016': 'Neo:6 CINEMA',
// '0017': 'Neo:6 MUSIC',
// '0028': 'XM HD SURROUND',
// '0029': 'NEURAL SURROUND',
// '0024': '(Multi ch source)+PRO LOGIC2x MUSIC',
// '0034': '(Multi-ch Source)+PRO LOGIC2z HEIGHT',
// '0035': '(Multi-ch Source)+WIDE SURROUND MOVIE',
// '0036': '(Multi-ch Source)+WIDE SURROUND MUSIC',
// '0025': 'DTS-ES Neo:6',
// '0026': 'DTS-ES matrix',
// '0027': 'DTS-ES discrete',
// '0101': 'ACTION',
// '0103': 'DRAMA',
// '0102': 'SCI-FI',
// '0105': 'MONO FILM',
// '0104': 'ENTERTAINMENT SHOW',
// '0106': 'EXPANDED THEATER',
// '0116': 'TV SURROUND',
// '0118': 'ADVANCED GAME',
// '0117': 'SPORTS',
// '0107': 'CLASSICAL',
// '0110': 'ROCK/POP',
// '0109': 'UNPLUGGED',
// '0112': 'EXTENDED STEREO',
// '0113': 'PHONES SURROUND',
// '0051': 'PROLOGIC + THX CINEMA',
// '0052': 'PL2 MOVIE + THX CINEMA',
// '0053': 'Neo:6 CINEMA + THX CINEMA',
// '0054': 'PL2x MOVIE + THX CINEMA',
// '0092': 'PL2z HEIGHT + THX CINEMA',
// '0055': 'THX SELECT2 GAMES',
// '0093': 'PL2z HEIGHT + THX MUSIC',
// '0073': 'Neo:6 MUSIC + THX MUSIC',
// '0074': 'PL2 GAME + THX GAMES',
// '0075': 'PL2x GAME + THX GAMES',
// '0094': 'PL2z HEIGHT + THX GAMES',
// '0076': 'THX ULTRA2 GAMES',
// '0077': 'PROLOGIC + THX MUSIC',
// '0057': 'THX SURROUND EX (for multi ch)',
// '0058': 'PL2x MOVIE + THX CINEMA (for multi ch)',
// '0095': 'PL2z HEIGHT + THX CINEMA (for multi ch)',
// '0067': 'ES 8ch DISCRETE + THX CINEMA (for multi ch)',
// '0031': 'PRO LOGIC2z Height',
// '0100': 'ADVANCED SURROUND (cyclic)',
// '0050': 'THX (cyclic)',
// '0068': 'THX CINEMA (for 2ch)',
// '0069': 'THX MUSIC (for 2ch)',
// '0070': 'THX GAMES (for 2ch)',
// '0071': 'PL2 MUSIC + THX MUSIC',
// '0072': 'PL2x MUSIC + THX MUSIC',
// '0078': 'PROLOGIC + THX GAMES',
// '0056': 'THX CINEMA (for multi ch)',
// '0059': 'ES Neo:6 + THX CINEMA (for multi ch)',
// '0060': 'ES MATRIX + THX CINEMA (for multi ch)',
// '0061': 'ES DISCRETE + THX CINEMA (for multi ch)',
// '0062': 'THX SELECT2 CINEMA (for multi ch)',
// '0063': 'THX SELECT2 MUSIC (for multi ch)',
// '0064': 'THX SELECT2 GAMES (for multi ch)',
// '0065': 'THX ULTRA2 CINEMA (for multi ch)',
// '0066': 'THX ULTRA2 MUSIC (for multi ch)',
// '0079': 'THX ULTRA2 GAMES (for multi ch)',
// '0080': 'THX MUSIC (for multi ch)',
// '0081': 'THX GAMES (for multi ch)',
// '0082': 'PL2x MUSIC + THX MUSIC (for multi ch)',
// '0096': 'PL2z HEIGHT + THX MUSIC (for multi ch)',
// '0083': 'EX + THX GAMES (for multi ch)',
// '0097': 'PL2z HEIGHT + THX GAMES (for multi ch)',
// '0084': 'Neo:6 + THX MUSIC (for multi ch)',
// '0085': 'Neo:6 + THX GAMES (for multi ch)',
// '0086': 'ES MATRIX + THX MUSIC (for multi ch)',
// '0087': 'ES MATRIX + THX GAMES (for multi ch)',
// '0088': 'ES DISCRETE + THX MUSIC (for multi ch)',
// '0089': 'ES DISCRETE + THX GAMES (for multi ch)',
// '0090': 'ES 8CH DISCRETE + THX MUSIC (for multi ch)',
// '0091': 'ES 8CH DISCRETE + THX GAMES (for multi ch)',
// '0005': 'AUTO SURR/STREAM DIRECT (cyclic)',
// '0006': 'AUTO SURROUND',
// '0152': 'OPTIMUM SURROUND',
// '0007': 'DIRECT',
// '0008': 'PURE DIRECT'
// };
constructor(...args) {
super(...args);
this.updateVolumeTimeout = null;
this.cancelVolumeDownSteps = false;
this.activeVolumeDownStepTimeouts = []; // Array to track active timeouts
this.lastSetVolume = '';
// Handle disconnection by resetting the volume display
this.telnetAvr.addOnDisconnectCallback(() => {
var _a, _b;
try {
(_a = this.functionSetLightbulbVolume) === null || _a === void 0 ? void 0 : _a.call(this, this.state.volume);
}
catch (e) {
this.log.debug('functionSetLightbulbVolume error', e);
}
try {
(_b = this.functionSetSwitchListeningMode) === null || _b === void 0 ? void 0 : _b.call(this);
}
catch (e) {
this.log.debug('functionSetSwitchListeningMode error', e);
}
});
// Handle reconnection by updating the volume, mute, and listening mode
this.telnetAvr.addOnConnectCallback(() => __awaiter(this, void 0, void 0, function* () {
setTimeout(() => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
try {
let error = null;
let attempts = 0;
if (this.state.on) {
do {
error = yield new Promise((resolve) => {
this.__updateListeningMode((err) => resolve(err));
});
if (error) {
yield new Promise((resolve) => setTimeout(resolve, 1500));
attempts++;
}
} while (error && attempts < 10);
}
error = null;
attempts = 0;
do {
error = yield new Promise((resolve) => {
this.__updateVolume((err) => resolve(err));
});
if (error) {
yield new Promise((resolve) => setTimeout(resolve, 1500));
attempts++;
}
} while (error && attempts < 10);
error = null;
attempts = 0;
do {
error = yield new Promise((resolve) => {
this.__updateMute((err) => resolve(err));
});
if (error) {
yield new Promise((resolve) => setTimeout(resolve, 1500));
attempts++;
}
} while (error && attempts < 10);
(_a = this.functionSetLightbulbVolume) === null || _a === void 0 ? void 0 : _a.call(this, this.state.volume);
(_b = this.functionSetSwitchListeningMode) === null || _b === void 0 ? void 0 : _b.call(this);
}
catch (e) {
this.log.debug('functionSetLightbulbVolume error', e);
}
}), 5321);
}));
// Handle exit by resetting the volume display before shutdown
(0, exitHandler_1.addExitHandler)(() => {
var _a, _b;
this.state.on = false;
try {
(_a = this.functionSetLightbulbVolume) === null || _a === void 0 ? void 0 : _a.call(this, this.state.volume);
}
catch (e) {
this.log.debug('functionSetLightbulbVolume error', e);
}
try {
(_b = this.functionSetSwitchListeningMode) === null || _b === void 0 ? void 0 : _b.call(this);
}
catch (e) {
this.log.debug('functionSetSwitchListeningMode error', e);
}
}, this);
}
/**
* Placeholder for setting the volume of the AVR.
* Intended to be overridden externally.
*/
functionSetLightbulbVolume(_argument) {
// Implement volume setting logic here
}
functionSetLightbulbMuted(_argument) { }
functionSetSwitchListeningMode(_argument) { }
/**
* Sends a volume status query to the AVR and updates the volume state.
* @param callback - Function to run after updating the volume.
*/
__updateVolume(callback) {
return __awaiter(this, void 0, void 0, function* () {
this.telnetAvr.sendMessage('?V', 'VOL', callback);
});
}
/**
* Sends a mute status query to the AVR and updates the mute state.
* @param callback - Function to run after updating the mute state.
*/
__updateMute(callback) {
return __awaiter(this, void 0, void 0, function* () {
this.telnetAvr.sendMessage('?M', 'MUT', callback);
});
}
/**
* Retrieves the current volume status.
* @param callback - The function to handle the volume result.
*/
volumeStatus(callback) {
if (this.state.volume !== null) {
callback(null, this.state.volume);
return;
}
this.__updateVolume(() => {
callback(null, this.state.volume);
});
}
/**
* Sets a specific volume on the AVR.
* @param targetVolume - The desired volume level.
* @param callback - The function to call after setting the volume.
*/
setVolume(targetVolume, callback) {
if (!this.telnetAvr ||
!this.telnetAvr.connectionReady ||
!this.state.on) {
return;
}
targetVolume = parseInt(targetVolume.toString(), 10);
if (isNaN(targetVolume) ||
Math.floor(targetVolume) === this.state.volume) {
if (callback) {
try {
callback(null, '');
}
catch (e) {
this.log.debug('', e);
}
}
return;
}
let vsxVol = 0;
if (this.maxVolume > 0) {
const minVolumeIn185 = this.minVolume / 100 * 185;
const maxVolumeIn185 = this.maxVolume / 100 * 185;
vsxVol =
targetVolume / 100 * (maxVolumeIn185 - minVolumeIn185) +
minVolumeIn185;
}
else {
vsxVol = targetVolume * 185 / 100;
}
vsxVol = Math.floor(vsxVol);
const vsxVolStr = vsxVol.toString().padStart(3, '0');
if (this.lastSetVolume !== vsxVolStr) {
this.lastSetVolume = vsxVolStr;
this.telnetAvr.sendMessage(`${vsxVolStr}VL`, undefined, callback);
this.lastUserInteraction = Date.now();
}
}
/**
* Increases the volume by one step.
*/
volumeUp() {
this.lastUserInteraction = Date.now();
if (!this.telnetAvr ||
!this.telnetAvr.connectionReady ||
!this.state.on) {
return;
}
this.log.debug('Volume up');
if (this.updateVolumeTimeout) {
clearTimeout(this.updateVolumeTimeout);
}
this.telnetAvr.sendMessage('VU', undefined, () => {
this.updateVolumeTimeout = setTimeout(() => {
this.__updateVolume(() => { });
this.__updateMute(() => { });
}, 1000);
});
}
/**
* Decreases the volume by three steps, with a delay of 100ms between each step.
*/
volumeDown() {
this.lastUserInteraction = Date.now();
if (!this.telnetAvr ||
!this.telnetAvr.connectionReady ||
!this.state.on) {
return;
}
this.log.debug('Volume down');
// Cancel any ongoing steps and clear active timeouts
this.cancelVolumeDownSteps = true;
if (this.updateVolumeTimeout) {
clearTimeout(this.updateVolumeTimeout);
}
this.activeVolumeDownStepTimeouts.forEach((timeout) => {
clearTimeout(timeout);
});
this.activeVolumeDownStepTimeouts = [];
const steps = 3;
const delay = 25;
// Function to execute each step with a delay
const executeStep = (step) => {
if (step > 0 && !this.cancelVolumeDownSteps) {
this.telnetAvr.sendMessage('VD', 'VOL', () => {
const timeout = setTimeout(() => {
executeStep(step - 1);
}, delay);
this.activeVolumeDownStepTimeouts.push(timeout); // Track the timeout
});
}
else if (step === 0 && !this.cancelVolumeDownSteps) {
if (this.updateVolumeTimeout) {
clearTimeout(this.updateVolumeTimeout);
}
// After the last step, update volume and mute status
this.updateVolumeTimeout = setTimeout(() => {
this.__updateVolume(() => { });
this.__updateMute(() => { });
}, 1000);
}
};
// Reset the cancel flag and start the first step
this.cancelVolumeDownSteps = false;
executeStep(steps);
}
/**
* Retrieves the mute status of the AVR.
* @param callback - The function to handle the mute result.
*/
muteStatus(callback) {
if (this.state.muted !== null) {
callback(null, this.state.muted);
return;
}
this.__updateMute(() => {
callback(null, this.state.muted);
});
}
/**
* Sends a mute-on command to the AVR.
*/
muteOn() {
this.lastUserInteraction = Date.now();
if (!this.telnetAvr ||
!this.telnetAvr.connectionReady ||
!this.state.on ||
this.state.muted === true) {
return;
}
this.log.debug('Mute on');
this.telnetAvr.sendMessage('MO');
}
/**
* Sends a mute-off command to the AVR.
*/
muteOff() {
this.lastUserInteraction = Date.now();
if (!this.telnetAvr ||
!this.telnetAvr.connectionReady ||
!this.state.on ||
this.state.muted === false) {
return;
}
this.log.debug('Mute off');
this.telnetAvr.sendMessage('MF');
}
/**
* Sends a query to update the current listening mode.
* @param callback - The function to handle the response.
*/
__updateListeningMode(callback) {
this.telnetAvr.sendMessage('?S', 'SR', callback);
}
// /**
// * Retrieves the current listening mode.
// * @param callback - The function to handle the listening mode result.
// */
// public getListeningMode(callback: (err: any, mode?: string) => void) {
// if (this.state.listeningMode) {
// callback(null, this.state.listeningMode);
//
// } else {
// this.__updateListeningMode(() => {
// callback(null, this.state.listeningMode ?? undefined);
// });
// }
// }
/**
* Toggles the listening mode between presets.
* @param callback - Optional function to handle the toggle result.
*/
toggleListeningMode(callback) {
var _a;
this.lastUserInteraction = Date.now();
// Check if the AVR is ready and if a listening mode is set
if (!this.isReady || !this.state.listeningMode) {
if (callback) {
callback(null, (_a = this.state.listeningMode) !== null && _a !== void 0 ? _a : '');
}
return;
}
this.log.debug('Toggle Listening Mode', this.state.listeningMode);
const isValidListeningMode = (value, defaultValue) => {
return /^[0-9]{4}$/.test(value || '') ? value : defaultValue;
};
const listeningModeOne = isValidListeningMode(this.device.listeningMode || this.platform.config.listeningMode, '0013'); // PRO LOGIC2 MOVIE
const listeningModeFallback = isValidListeningMode(this.device.listeningModeFallback || this.platform.config.listeningModeFallback, '0101'); // ACTION
const listeningModeOther = isValidListeningMode(this.device.listeningModeOther || this.platform.config.listeningModeOther, '0112'); // EXTENDED STEREO
if ([listeningModeOne, listeningModeFallback].includes(this.state.listeningMode)) {
this.telnetAvr.sendMessage(listeningModeOther + 'SR');
this.state.listeningMode = listeningModeOther;
if (callback) {
callback(null, this.state.listeningMode);
}
}
else {
this.state.listeningMode = listeningModeOne;
this.telnetAvr.sendMessage('!' + listeningModeOne + 'SR', 'SR', (error, _data) => {
if (error) {
this.state.listeningMode = listeningModeFallback;
setTimeout(() => {
this.telnetAvr.sendMessage(listeningModeFallback + 'SR');
}, 100);
}
if (callback) {
callback(null, this.state.listeningMode);
}
});
}
}
};
}
//# sourceMappingURL=volume.js.map