UNPKG

homebridge-musiccast-bnetqc

Version:

Homebridge Yamaha MusicCast Multiroom Plugin with precise volume control

405 lines 21 kB
"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