UNPKG

homebridge-denon-v3

Version:

Denon and Marantz AVR support for Homebridge: https://github.com/nfarina/homebridge

253 lines 8.54 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DenonMarantzController = void 0; const telnet_client_1 = __importDefault(require("telnet-client")); const types_1 = require("./types"); function msleep(n) { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n); } const serverControllerPort = 23; class DenonMarantzController { constructor(log, ipaddress) { this.COMMANDS = { "PW": ["Power", null], "ZM": ["Main Zone", null], "Z2": ["Zone 2", (data) => this.parseZ2(data)], "Z3": ["Zone 3", (data) => this.parseZ3(data)], "MU": ["Muted", null], "Z2MU": ["Z2 Muted", null], "Z3MU": ["Z3 Muted", null], "MV": ["Volume", (data) => this.parseMV(data)], "Z2MV": ["Z2 Volume", null], "Z3MV": ["Z3 Volume", null], "SI": ["Source", null], "Z2SI": ["Z2 Source", null], "Z3SI": ["Z3 Source", null], // "MS": [ "Surround Mode", null], // "CV": [ "Channel Bias", null], // "PV": [ "Picture Mode", null], // "ECO": [ "Eco Mode", null], // "SSSOD": [ "Available Source", null], // "PSDEL": [ "Sound Delay", null], // "PSDRC": [ "Dynamic Range Compression", null], // "PSDYNVOL": [ "Dynamic Volume", null], }; this.log = log; this.ipaddress = ipaddress; this.maxVol = 98.0; // good default this.serverController = new telnet_client_1.default(); this.log.info("Connecting to Denon/Marantz Controller at ", ipaddress); this.serverControllerConnect(); this.state = {}; this.reconnectOnErrRetires = 5; for (const command of Object.keys(this.COMMANDS)) { this.state[command] = '-'; } } async serverControllerConnect() { let params = { host: this.ipaddress, port: serverControllerPort, echoLines: 0, irs: '\r', negotiationMandatory: false, ors: '\r', separator: false, shellPrompt: '', timeout: 1500, }; await this.serverController.connect(params); this.serverController.on('data', this.serverControllerDataCallback.bind(this)); this.reconnectOnErrRetires = 5; this.refresh(); } async refresh() { try { Object.keys(this.COMMANDS).forEach((cmd) => { // for status add question mark this.serverControllerSend(`${cmd}?`); }); this.log.info(`current state ${this.ipaddress} of controller ${this.state}`); } catch (error) { this.log.error(`received error ${error}`); if (this.reconnectOnErrRetires > 0) { // try to connect again. await this.serverControllerConnect(); this.reconnectOnErrRetires--; } else { throw error; } } } serverControllerDataCallback(data) { this.parseCommandResult(data.toString()); } async serverControllerSend(data) { // msleep(50); let result = await this.serverController.exec(data); this.parseCommandResult(result); } parseMany(cmd, data) { this.log.info(`ParseMany ${cmd} with ${data}`); this.state[cmd] = data; } // only works for main zone, others zones handled differently parseMV(data) { this.log.info(`ParseMv with ${data}`); let isMax = false; let value = ""; if (data.startsWith('MVMAX')) { isMax = true; value = data.split('MVMAX')[1].trim(); } else { value = data.split('MV')[1].trim(); } let volume = Number(value); if (value.length > 2) { // multipled by 10 to not be float (example: 505 == 50.5) volume = volume / 10; } this.log.info("new value", volume, value, value.length); if (isMax) { this.maxVol = volume; } else { this.state['MV'] = volume; } } parseZone(zone, data) { this.log.info(`parseZone ${zone} with ${data}`); if (['ON', 'OFF'].includes(data)) { this.state[zone] = data; return; } if (!isNaN(Number(data))) { let value = Number(data); if (data.length > 2) { // multipled by 10 to not be float (example: 505 == 50.5) value = value / 10; } this.state[`${zone}MV`] = value; return; } // assume source inputs if (types_1.INPUTS.includes(data)) { this.state[`${zone}SI`] = data; return; } } parseZ2(data) { this.parseZone('Z2', data.split('Z2')[1].trim()); } parseZ3(data) { this.parseZone('Z3', data.split('Z3')[1].trim()); } parseCommandResult(data) { this.log.info(`parsing result ${data}`); let results = data.split('\r'); results.forEach((result) => { Object.keys(this.COMMANDS).forEach((cmd) => { if (result.startsWith(cmd)) { if (this.COMMANDS[cmd][1] != null) { this.COMMANDS[cmd][1](result); } else { this.parseMany(cmd, result.split(cmd)[1].trim()); } } }); }); } getPrefixByZone(zone) { switch (zone) { case 'main': return ''; //'ZM' case 'zone2': return 'Z2'; case 'zone3': return 'Z3'; case 'zone4': return 'Z4'; default: // cant happen return '??'; } } parseBoolString(b) { if (b == 'ON') { return true; } else { return false; } } boolToString(b) { if (b) { return 'ON'; } else { return 'OFF'; } } GetPowerState(zone) { let prefix = this.getPrefixByZone(zone); if (zone === 'main') { prefix = 'PW'; } return this.parseBoolString(this.state[prefix]); } async SetPowerState(zone, value) { let prefix = this.getPrefixByZone(zone); let state = value ? 'ON' : 'OFF'; if (zone === 'main') { prefix = 'PW'; state = value ? 'ON' : 'STANDBY'; } await this.serverControllerSend(`${prefix}${state}`); } GetMuteState(zone) { let commandPrefix = `${this.getPrefixByZone(zone)}MU`; return this.parseBoolString(this.state[commandPrefix]); } async SetMuteSate(zone, value) { let commandPrefix = `${this.getPrefixByZone(zone)}MU`; await this.serverControllerSend(`${commandPrefix}${this.boolToString(value)}`); } GetVolume(zone) { let commandPrefix = `${this.getPrefixByZone(zone)}MV`; return Number(this.state[commandPrefix]); } async SetVolume(zone, value) { // for some reason this does not work on anything but main zone, if (value > this.maxVol) { value = this.maxVol; } if ((Number(value) * 10) % 10) { value = 5 * Math.round(Number(value) * 10 / 5); } this.log.info(`setting new volume: ${value}`); // TODO: currently doesn't work with values below 10 because it needs to be padded by zero let commandPrefix = `${this.getPrefixByZone(zone)}MV${value}`; await this.serverControllerSend(commandPrefix); } GetInputSource(zone) { let commandPrefix = `${this.getPrefixByZone(zone)}SI`; return this.state[commandPrefix]; } async SetInputSource(zone, source) { if (zone == 'main') { await this.serverControllerSend(`SI${source}`); } else { await this.serverControllerSend(`${this.getPrefixByZone(zone)}${source}`); } } } exports.DenonMarantzController = DenonMarantzController; //# sourceMappingURL=controller.js.map