homebridge-denon-heos-audio
Version:
Control your Denon speakers via AVR Control and/or HeosCLI
220 lines • 8.06 kB
JavaScript
import { CommandMode, DenonClient, findMapByValue, findValueByMap, InvalidResponseException, Playing } from "./denonClient.js";
import * as DenonProtocol from "./denonProtocol.js";
export class DenonClientAvrControl extends DenonClient {
controlMode = DenonProtocol.ControlMode.AVRCONTROL;
static PROTOCOL = {
POWER: {
GET: {
COMMAND: "PW?",
PARAMS: "",
EXP_RES: /^PW(\w+)$/,
},
SET: {
COMMAND: "PW",
PARAMS: "[VALUE]",
EXP_RES: /^PW(\w+)$/,
},
VALUES: {
ON: {
VALUE: "ON",
MAP: true,
},
OFF: {
VALUE: "STANDBY",
MAP: false,
},
},
},
MUTE: {
GET: {
COMMAND: "MU?",
PARAMS: "",
EXP_RES: /^MU(\w+)$/,
},
SET: {
COMMAND: "MU",
PARAMS: "[VALUE]",
EXP_RES: /^MU(\w+)$/,
},
VALUES: {
ON: {
VALUE: "ON",
MAP: true,
},
OFF: {
VALUE: "OFF",
MAP: false,
},
},
},
VOLUME: {
GET: {
COMMAND: "MV?",
PARAMS: "",
EXP_RES: /^MV(\d{2})5?$/,
},
SET: {
COMMAND: "MV",
PARAMS: "[VALUE]",
EXP_RES: /^MV(\d{2})5?$/,
},
},
VOLUME_UP: {
SET: {
COMMAND: "MVUP",
},
},
VOLUME_DOWN: {
SET: {
COMMAND: "MVDOWN",
},
},
};
constructor(serialNumber, host, connect_timeout, response_timeout, debugLogCallback, powerUpdateCallback, muteUpdateCallback, volumeUpdateCallback) {
super(serialNumber, {
host: host,
port: DenonProtocol.ControlMode.AVRCONTROL,
connect_timeout: connect_timeout,
response_timeout: response_timeout,
command_prefix: undefined,
command_separator: "\r\n",
response_separator: "\r",
all_responses_to_generic: false,
}, debugLogCallback, powerUpdateCallback, muteUpdateCallback, volumeUpdateCallback);
this.connect();
}
async subscribeToChangeEvents() {
// not necessary - change events are automatically sent in AVR control
}
responseRouter(response) {
if (this.responseCallback) {
let out = undefined;
if (this.responseCallback.expectedResponse) {
const match = response.match(this.responseCallback.expectedResponse);
if (match) {
out = match[1] ?? match[0];
}
}
else {
out = response;
}
if (out) {
this.debugLog("Received response:", response);
this.responseCallback.callback(out);
this.responseCallback = undefined;
if (this.params.all_responses_to_generic) {
this.genericResponseHandler(response);
}
}
}
else {
this.genericResponseHandler(response);
}
}
genericResponseHandler(response) {
this.debugLog("Received data event:", response);
// Power
let match = response.match(DenonClientAvrControl.PROTOCOL.POWER.GET.EXP_RES);
if (match) {
const mappedValue = findMapByValue(DenonClientAvrControl.PROTOCOL.POWER.VALUES, match[1]);
if (mappedValue === undefined) {
throw new InvalidResponseException("Unexpected power state", Object.values(DenonClientAvrControl.PROTOCOL.POWER.VALUES).map((value) => value.VALUE), match[1]);
}
if (this.powerUpdateCallback) {
this.powerUpdateCallback(mappedValue);
}
return;
}
// Mute
match = response.match(DenonClientAvrControl.PROTOCOL.MUTE.GET.EXP_RES);
if (match) {
const mappedValue = findMapByValue(DenonClientAvrControl.PROTOCOL.MUTE.VALUES, match[1]);
if (mappedValue === undefined) {
throw new InvalidResponseException("Unexpected mute state", Object.values(DenonClientAvrControl.PROTOCOL.MUTE.VALUES).map((value) => value.VALUE), match[1]);
}
if (this.muteUpdateCallback) {
this.muteUpdateCallback(mappedValue);
}
return;
}
// Volume
match = response.match(DenonClientAvrControl.PROTOCOL.VOLUME.GET.EXP_RES);
if (match) {
if (this.volumeUpdateCallback) {
this.volumeUpdateCallback(Number(match[1]));
}
return;
}
}
async getPower() {
const response = await this.sendCommand(DenonClientAvrControl.PROTOCOL.POWER, CommandMode.GET, {});
const mappedValue = findMapByValue(DenonClientAvrControl.PROTOCOL.POWER.VALUES, response);
return mappedValue;
}
async setPower(power) {
const response = await this.sendCommand(DenonClientAvrControl.PROTOCOL.POWER, CommandMode.SET, {
value: findValueByMap(DenonClientAvrControl.PROTOCOL.POWER.VALUES, power),
});
const mappedValue = findMapByValue(DenonClientAvrControl.PROTOCOL.POWER.VALUES, response);
return mappedValue;
}
async getPlaying() {
// not supported
return Playing.UNSUPPORTED;
}
async setPlaying() {
// not supported
return Playing.UNSUPPORTED;
}
async setPlayNext() {
// not supported
}
async setPlayPrevious() {
// not supported
}
async getMute() {
const response = await this.sendCommand(DenonClientAvrControl.PROTOCOL.MUTE, CommandMode.GET, {});
const mappedValue = findMapByValue(DenonClientAvrControl.PROTOCOL.MUTE.VALUES, response);
return mappedValue;
}
async setMute(mute) {
const response = await this.sendCommand(DenonClientAvrControl.PROTOCOL.MUTE, CommandMode.SET, {
value: findValueByMap(DenonClientAvrControl.PROTOCOL.MUTE.VALUES, mute),
});
const mappedValue = findMapByValue(DenonClientAvrControl.PROTOCOL.MUTE.VALUES, response);
return mappedValue;
}
async getVolume() {
const response = await this.sendCommand(DenonClientAvrControl.PROTOCOL.VOLUME, CommandMode.GET, {});
return Number(response);
}
async setVolume(volume) {
if (volume < 0 || volume > 100) {
throw new Error("Volume must be between 0 and 100");
}
if (volume === 100) {
volume = 99;
}
const response = await this.sendCommand(DenonClientAvrControl.PROTOCOL.VOLUME, CommandMode.SET, {
value: Math.round(volume).toString().padStart(2, "0"),
});
return Number(response);
}
async setVolumeUp(volumeIncrement) {
if (volumeIncrement < 1 || volumeIncrement > 10) {
throw new Error("Volume increment must be between 1 and 10");
}
for (let i = 0; i < Math.round(volumeIncrement); i++) {
await this.sendCommand(DenonClientAvrControl.PROTOCOL.VOLUME_UP, CommandMode.SET, {});
}
}
async setVolumeDown(volumeDecrement) {
if (volumeDecrement < 1 || volumeDecrement > 10) {
throw new Error("Volume decrement must be between 1 and 10");
}
for (let i = 0; i < Math.round(volumeDecrement); i++) {
await this.sendCommand(DenonClientAvrControl.PROTOCOL.VOLUME_DOWN, CommandMode.SET, {});
}
}
}
//# sourceMappingURL=denonClientAvrControl.js.map