homebridge-musiccast-bnetqc
Version:
Homebridge Yamaha MusicCast Multiroom Plugin with precise volume control
405 lines • 21 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.YamahaDevice = void 0;
class YamahaDevice {
constructor(config, api, cache, log, yamahaAPI) {
this.api = api;
this.cache = cache;
this.config = config;
this.log = log;
this.yamahaAPI = yamahaAPI;
if (this.config.volumeMin === undefined) {
this.config.volumeMin = 0;
}
if (this.config.volumeMax === undefined) {
this.config.volumeMax = 80;
}
if (!config.serverDevice) {
if (this.config.inputs !== undefined) {
for (let i = 0; i < this.config.inputs.length; i++) {
this.config.inputs[i].identifier = i + 100;
}
}
else {
this.config.inputs = [];
}
if (this.config.clients === undefined) {
this.config.clients = [];
}
}
}
async publishAccessory(pluginName) {
await this.setInitialStatus();
let accessories = [];
let services = {};
// SUPPRIMÉ : L'accessoire Power n'est plus créé ici
let { volumeAccessory, volumeService } = this.getVolumeAccessory(pluginName);
this.log.info("publishing accessory " + volumeAccessory.displayName);
accessories.push(volumeAccessory);
services.volumeService = volumeService;
if (!this.config.serverDevice) {
let { presetAccessory, presetService } = this.getInputPresetAccessory(pluginName, this.config.inputs);
this.log.info("publishing accessory " + presetAccessory.displayName);
accessories.push(presetAccessory);
services.presetService = presetService;
if (this.shouldPublishLipSyncSwitch()) {
let { lipSyncAccessory, lipSyncService } = this.getLipSyncAccessory(pluginName);
this.log.info("publishing accessory " + lipSyncAccessory.displayName);
accessories.push(lipSyncAccessory);
services.lipSyncService = lipSyncService;
}
if (this.shouldPublishSurroundDecoderSwitch()) {
let { surroundDecoderAccessory, surroundDecoderService } = this.getSurroundDecoderAccessory(pluginName);
this.log.info("publishing accessory " + surroundDecoderAccessory.displayName);
accessories.push(surroundDecoderAccessory);
services.surroundDecoderService = surroundDecoderService;
}
}
this.api.publishExternalAccessories(pluginName, accessories);
this.cache.setCallback(this.getHost(), this.updateStatus.bind(this), [services]);
}
getHost() {
return this.config.host;
}
async setInitialStatus() {
this.cache.set(this.getHost(), 'deviceInfo', await this.yamahaAPI.getDeviceInfo(this.getHost()));
this.cache.set(this.getHost(), 'presetInfo', await this.yamahaAPI.getPresetInfo(this.getHost()));
this.cache.set(this.getHost(), 'status', await this.yamahaAPI.getStatus(this.getHost()));
this.cache.set(this.getHost(), 'playInfo', await this.yamahaAPI.getPlayInfo(this.getHost()));
this.cache.set(this.getHost(), 'features', await this.yamahaAPI.getFeatures(this.getHost()));
}
shouldPublishLipSyncSwitch() {
var _a;
const features = this.cache.get(this.getHost(), 'features');
const mainZone = features.zone.find(zone => zone.id === 'main');
return !!(mainZone && ((_a = mainZone.link_audio_delay_list) === null || _a === void 0 ? void 0 : _a.includes("lip_sync")) && mainZone.link_audio_delay_list.includes("audio_sync"));
}
shouldPublishSurroundDecoderSwitch() {
var _a;
const features = this.cache.get(this.getHost(), 'features');
const mainZone = features.zone.find(zone => zone.id === 'main');
return !!(mainZone && ((_a = mainZone.sound_program_list) === null || _a === void 0 ? void 0 : _a.includes("surr_decoder")) && mainZone.sound_program_list.includes("straight"));
}
async updateStatus(services) {
let status;
try {
if (this.getCurrentPowerSwitchStatus() && services.presetService) {
const [newStatus, playInfo] = await Promise.all([
this.yamahaAPI.getStatus(this.getHost()),
this.yamahaAPI.getPlayInfo(this.getHost())
]);
status = newStatus;
this.cache.set(this.getHost(), 'playInfo', playInfo);
}
else {
status = await this.yamahaAPI.getStatus(this.getHost());
}
const lastStatus = this.cache.get(this.getHost(), 'status');
const poweredOn = status.power === 'on';
const userActivity = JSON.stringify(lastStatus) !== JSON.stringify(status);
this.cache.set(this.getHost(), 'status', status);
this.cache.ping(this.getHost(), poweredOn, userActivity);
this.updateStatusFromCache(services);
}
catch (error) {
this.log.error(`Failed to update status for ${this.getHost()}: ${error}`);
}
}
updateStatusFromCache(services) {
// SUPPRIMÉ : Le bloc pour le powerService a été retiré
if (services.volumeService) {
const isPoweredOn = this.getCurrentPowerSwitchStatus();
if (isPoweredOn) {
const isNotMuted = !this.getCurrentMuteStatus();
services.volumeService.getCharacteristic(this.api.hap.Characteristic.On).updateValue(isNotMuted);
const status = this.cache.get(this.getHost(), 'status');
const currentVolume = Math.max(this.config.volumeMin, Math.min(this.config.volumeMax, status.volume));
services.volumeService.getCharacteristic(this.api.hap.Characteristic.RotationSpeed).updateValue(currentVolume);
}
else {
services.volumeService.getCharacteristic(this.api.hap.Characteristic.On).updateValue(false);
services.volumeService.getCharacteristic(this.api.hap.Characteristic.RotationSpeed).updateValue(this.config.volumeMin);
}
}
if (services.presetService) {
const active = this.getCurrentPowerSwitchStatus() ? this.api.hap.Characteristic.Active.ACTIVE : this.api.hap.Characteristic.Active.INACTIVE;
services.presetService.getCharacteristic(this.api.hap.Characteristic.Active).updateValue(active);
let presetId = this.getCurrentInputPresetIdentifier();
if (presetId !== undefined) {
services.presetService.getCharacteristic(this.api.hap.Characteristic.ActiveIdentifier).updateValue(presetId);
}
}
if (services.lipSyncService) {
services.lipSyncService.getCharacteristic(this.api.hap.Characteristic.On).updateValue(this.getCurrentLipSyncSwitchStatus());
}
if (services.surroundDecoderService) {
services.surroundDecoderService.getCharacteristic(this.api.hap.Characteristic.On).updateValue(this.getCurrentSurroundDecoderSwitchStatus());
}
}
getCurrentInputPresetIdentifier() {
const statusInfo = this.cache.get(this.getHost(), 'status');
for (let inputConfig of this.config.inputs) {
if (statusInfo.input === inputConfig.input) {
return inputConfig.identifier;
}
}
const playInfo = this.cache.get(this.getHost(), 'playInfo');
const presetInfos = this.cache.get(this.getHost(), 'presetInfo');
for (let presetInfo of presetInfos.preset_info) {
if ((playInfo.input === 'server' || playInfo.input === 'net_radio') && (presetInfo.text === playInfo.track || presetInfo.text === playInfo.artist)) {
return presetInfo.identifier;
}
}
return undefined;
}
getCurrentPowerSwitchStatus() {
const status = this.cache.get(this.getHost(), 'status');
return status.power === "on";
}
getCurrentMuteStatus() {
const status = this.cache.get(this.getHost(), 'status');
return status.mute;
}
getCurrentLipSyncSwitchStatus() {
const status = this.cache.get(this.getHost(), 'status');
return status.link_audio_delay === "lip_sync";
}
getCurrentSurroundDecoderSwitchStatus() {
const status = this.cache.get(this.getHost(), 'status');
return status.sound_program === "surr_decoder";
}
async recallInputPreset(identifier) {
let input;
let presetId;
for (let inputConfig of this.config.inputs) {
if (inputConfig.identifier === identifier) {
input = inputConfig.input;
break;
}
}
const presetInfos = this.cache.get(this.getHost(), 'presetInfo');
for (let presetInfo of presetInfos.preset_info) {
if (presetInfo.identifier === identifier) {
presetId = Number(presetInfo.presetId);
break;
}
}
if (input) {
await this.yamahaAPI.setInput(this.getHost(), input);
}
else if (presetId) {
await this.yamahaAPI.recallPreset(this.getHost(), presetId);
}
return this.waitForInputPreset(identifier);
}
async waitForInputPreset(identifier, maxWait = 10000) {
const delay = 1000;
const currentPresetIdentifier = this.getCurrentInputPresetIdentifier();
if (currentPresetIdentifier !== identifier && maxWait > 0) {
return new Promise(resolve => setTimeout(async () => {
await this.waitForInputPreset(identifier, maxWait - delay);
resolve(undefined);
}, delay));
}
}
async setPower(status) {
await this.yamahaAPI.setPower(this.getHost(), status);
return this.waitForPower(status);
}
async setMute(status) {
await this.yamahaAPI.setMute(this.getHost(), status);
}
async waitForPower(status, maxWait = 10000) {
const delay = 1000;
const currentStatus = this.getCurrentPowerSwitchStatus();
if (currentStatus !== status && maxWait > 0) {
return new Promise(resolve => setTimeout(async () => {
await this.waitForPower(status, maxWait - delay);
resolve(undefined);
}, delay));
}
}
async linkWithHost() {
if (this.config.serverDevice) {
await this.config.serverDevice.setPower(true);
await this.setPower(true);
await this.yamahaAPI.setServerInfo(this.getHost(), this.config.serverDevice.getHost(), 'remove');
await this.yamahaAPI.setClientInfo(this.getHost(), this.config.serverDevice.getHost());
await this.yamahaAPI.setServerInfo(this.getHost(), this.config.serverDevice.getHost(), 'add');
await this.yamahaAPI.startDistribution(this.config.serverDevice.getHost());
}
}
async powerOffClients() {
if (this.config.clients) {
for (let client of this.config.clients) {
await this.yamahaAPI.setPower(client, false);
this.cache.ping(client, false, true);
}
}
}
addServiceAccessoryInformation(accessory) {
const deviceInfo = this.cache.get(this.getHost(), 'deviceInfo');
accessory.getService(this.api.hap.Service.AccessoryInformation)
.setCharacteristic(this.api.hap.Characteristic.Manufacturer, "Yamaha")
.setCharacteristic(this.api.hap.Characteristic.Model, deviceInfo.model_name)
.setCharacteristic(this.api.hap.Characteristic.SerialNumber, `${deviceInfo.serial_number} ${this.getHost()}`)
.setCharacteristic(this.api.hap.Characteristic.SoftwareRevision, deviceInfo.api_version.toString())
.setCharacteristic(this.api.hap.Characteristic.FirmwareRevision, deviceInfo.system_version.toString());
}
// SUPPRIMÉ : La méthode getPowerAccessory a été retirée
getVolumeAccessory(pluginName) {
const deviceInfo = this.cache.get(this.getHost(), 'deviceInfo');
const accessoryName = `Volume ${deviceInfo.model_name}`;
const uuid = this.api.hap.uuid.generate(`${pluginName}-${this.getHost()}-volume-fan`);
const accessory = new this.api.platformAccessory(accessoryName, uuid, 34 /* this.api.hap.Categories.AUDIO_RECEIVER */);
const service = accessory.addService(this.api.hap.Service.Fan, "Volume");
this.addServiceAccessoryInformation(accessory);
service.getCharacteristic(this.api.hap.Characteristic.On)
.onGet(async () => {
if (!this.getCurrentPowerSwitchStatus()) {
return false;
}
return !this.getCurrentMuteStatus();
})
.on("set" /* this.api.hap.CharacteristicEventTypes.SET */, (value, callback) => {
callback(null);
(async () => {
const turnOn = value;
if (turnOn && !this.getCurrentPowerSwitchStatus()) {
await this.setPower(true);
}
await this.setMute(!turnOn);
this.cache.ping(this.getHost(), undefined, true);
})();
});
service.getCharacteristic(this.api.hap.Characteristic.RotationSpeed)
.setProps({
minValue: this.config.volumeMin,
maxValue: this.config.volumeMax,
minStep: 1,
})
.onGet(async () => {
const status = this.cache.get(this.getHost(), 'status');
if (!this.getCurrentPowerSwitchStatus()) {
return this.config.volumeMin;
}
return Math.max(this.config.volumeMin, Math.min(this.config.volumeMax, status.volume));
})
.on("set" /* this.api.hap.CharacteristicEventTypes.SET */, (value, callback) => {
callback(null);
(async () => {
const targetVolume = value;
if (targetVolume > this.config.volumeMin && !this.getCurrentPowerSwitchStatus()) {
await this.setPower(true);
}
await this.yamahaAPI.setVolume(this.getHost(), targetVolume);
this.cache.ping(this.getHost(), undefined, true);
})();
});
return { volumeAccessory: accessory, volumeService: service };
}
getInputPresetAccessory(pluginName, inputConfigs) {
const service = new this.api.hap.Service.Television();
const deviceInfo = this.cache.get(this.getHost(), 'deviceInfo');
const name = "Preset " + deviceInfo.model_name;
const uuid = this.api.hap.uuid.generate(`${pluginName}-${this.getHost()}-preset`);
const accessory = new this.api.platformAccessory(name, uuid, 34 /* this.api.hap.Categories.AUDIO_RECEIVER */);
accessory.addService(service);
this.addServiceAccessoryInformation(accessory);
service.getCharacteristic(this.api.hap.Characteristic.Active)
.onGet(async () => this.getCurrentPowerSwitchStatus() ? this.api.hap.Characteristic.Active.ACTIVE : this.api.hap.Characteristic.Active.INACTIVE)
.on("set" /* this.api.hap.CharacteristicEventTypes.SET */, (active, callback) => {
callback(null);
(async () => {
const isOn = active === this.api.hap.Characteristic.Active.ACTIVE;
await this.setPower(isOn);
this.cache.ping(this.getHost(), isOn, true);
if (isOn && this.config.serverDevice) {
this.cache.ping(this.config.serverDevice.getHost(), true, true);
this.linkWithHost();
}
if (!isOn) {
this.powerOffClients();
}
})();
});
service
.getCharacteristic(this.api.hap.Characteristic.ActiveIdentifier)
.onGet(async () => this.getCurrentInputPresetIdentifier() || 0)
.on("set" /* this.api.hap.CharacteristicEventTypes.SET */, (presetId, callback) => {
callback(null);
(async () => {
if (!this.getCurrentPowerSwitchStatus()) {
await this.setPower(true);
}
await this.recallInputPreset(presetId);
this.cache.ping(this.getHost(), undefined, true);
})();
});
for (let inputConfig of inputConfigs) {
let inputSource = accessory.addService(this.api.hap.Service.InputSource, inputConfig.name, inputConfig.identifier.toString());
inputSource
.setCharacteristic(this.api.hap.Characteristic.Identifier, inputConfig.identifier)
.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, inputConfig.name)
.setCharacteristic(this.api.hap.Characteristic.IsConfigured, this.api.hap.Characteristic.IsConfigured.CONFIGURED)
.setCharacteristic(this.api.hap.Characteristic.InputSourceType, this.api.hap.Characteristic.InputSourceType.APPLICATION);
service.addLinkedService(inputSource);
}
const presetInfos = this.cache.get(this.getHost(), 'presetInfo');
for (let presetInfo of presetInfos.preset_info) {
let inputSource = accessory.addService(this.api.hap.Service.InputSource, presetInfo.displayText, presetInfo.identifier.toString());
inputSource
.setCharacteristic(this.api.hap.Characteristic.Identifier, presetInfo.identifier)
.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, presetInfo.displayText)
.setCharacteristic(this.api.hap.Characteristic.IsConfigured, this.api.hap.Characteristic.IsConfigured.CONFIGURED)
.setCharacteristic(this.api.hap.Characteristic.InputSourceType, this.api.hap.Characteristic.InputSourceType.APPLICATION);
service.addLinkedService(inputSource);
}
const displayOrder = inputConfigs.map(inputConfig => inputConfig.identifier).concat(presetInfos.preset_info.map(presetInfo => presetInfo.identifier));
service.setCharacteristic(this.api.hap.Characteristic.DisplayOrder, this.api.hap.encode(1, displayOrder).toString('base64'));
return { presetAccessory: accessory, presetService: service };
}
getLipSyncAccessory(pluginName) {
const service = new this.api.hap.Service.Switch();
const deviceInfo = this.cache.get(this.getHost(), 'deviceInfo');
const name = "LipSync " + deviceInfo.model_name;
const uuid = this.api.hap.uuid.generate(`${pluginName}-${this.getHost()}-lipsync`);
const accessory = new this.api.platformAccessory(name, uuid, 34 /* this.api.hap.Categories.AUDIO_RECEIVER */);
accessory.addService(service);
this.addServiceAccessoryInformation(accessory);
service
.getCharacteristic(this.api.hap.Characteristic.On)
.onGet(async () => this.getCurrentLipSyncSwitchStatus())
.on("set" /* this.api.hap.CharacteristicEventTypes.SET */, (value, callback) => {
callback(null);
(async () => {
let audioDelay = value ? "lip_sync" : "audio_sync";
await this.yamahaAPI.setLinkAudioDelay(this.getHost(), audioDelay);
this.cache.ping(this.getHost(), undefined, true);
})();
});
return { lipSyncAccessory: accessory, lipSyncService: service };
}
getSurroundDecoderAccessory(pluginName) {
const service = new this.api.hap.Service.Switch();
const name = "Surround Decoder " + this.cache.get(this.getHost(), 'deviceInfo').model_name;
const uuid = this.api.hap.uuid.generate(`${pluginName}-${this.getHost()}-surround`);
const accessory = new this.api.platformAccessory(name, uuid, 34 /* this.api.hap.Categories.AUDIO_RECEIVER */);
accessory.addService(service);
this.addServiceAccessoryInformation(accessory);
service
.getCharacteristic(this.api.hap.Characteristic.On)
.onGet(async () => this.getCurrentSurroundDecoderSwitchStatus())
.on("set" /* this.api.hap.CharacteristicEventTypes.SET */, (value, callback) => {
callback(null);
(async () => {
let program = value ? "surr_decoder" : "straight";
await this.yamahaAPI.setSoundProgram(this.getHost(), program);
this.cache.ping(this.getHost(), undefined, true);
})();
});
return { surroundDecoderAccessory: accessory, surroundDecoderService: service };
}
}
exports.YamahaDevice = YamahaDevice;
//# sourceMappingURL=YamahaDevice.js.map