diff --git a/homeassistant/components/kef/manifest.json b/homeassistant/components/kef/manifest.json index 135f8e1cf54..4af0626ace9 100644 --- a/homeassistant/components/kef/manifest.json +++ b/homeassistant/components/kef/manifest.json @@ -4,5 +4,5 @@ "documentation": "https://www.home-assistant.io/integrations/kef", "dependencies": [], "codeowners": ["@basnijholt"], - "requirements": ["aiokef==0.2.7", "getmac==0.8.1"] + "requirements": ["aiokef==0.2.9", "getmac==0.8.1"] } diff --git a/homeassistant/components/kef/media_player.py b/homeassistant/components/kef/media_player.py index d4a1d7a4df3..2a227212006 100644 --- a/homeassistant/components/kef/media_player.py +++ b/homeassistant/components/kef/media_player.py @@ -1,11 +1,13 @@ """Platform for the KEF Wireless Speakers.""" +import asyncio from datetime import timedelta from functools import partial import ipaddress import logging from aiokef import AsyncKefSpeaker +from aiokef.aiokef import DSP_OPTION_MAPPING from getmac import get_mac_address import voluptuous as vol @@ -31,7 +33,8 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, entity_platform +from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) @@ -55,6 +58,17 @@ CONF_INVERSE_SPEAKER_MODE = "inverse_speaker_mode" CONF_SUPPORTS_ON = "supports_on" CONF_STANDBY_TIME = "standby_time" +SERVICE_MODE = "set_mode" +SERVICE_DESK_DB = "set_desk_db" +SERVICE_WALL_DB = "set_wall_db" +SERVICE_TREBLE_DB = "set_treble_db" +SERVICE_HIGH_HZ = "set_high_hz" +SERVICE_LOW_HZ = "set_low_hz" +SERVICE_SUB_DB = "set_sub_db" +SERVICE_UPDATE_DSP = "update_dsp" + +DSP_SCAN_INTERVAL = 3600 + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, @@ -118,6 +132,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= inverse_speaker_mode, supports_on, sources, + speaker_type, ioloop=hass.loop, unique_id=unique_id, ) @@ -128,6 +143,36 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= hass.data[DOMAIN][host] = media_player async_add_entities([media_player], update_before_add=True) + platform = entity_platform.current_platform.get() + + platform.async_register_entity_service( + SERVICE_MODE, + { + vol.Optional("desk_mode"): cv.boolean, + vol.Optional("wall_mode"): cv.boolean, + vol.Optional("phase_correction"): cv.boolean, + vol.Optional("high_pass"): cv.boolean, + vol.Optional("sub_polarity"): vol.In(["-", "+"]), + vol.Optional("bass_extension"): vol.In(["Less", "Standard", "Extra"]), + }, + "set_mode", + ) + platform.async_register_entity_service(SERVICE_UPDATE_DSP, {}, "update_dsp") + + def add_service(name, which, option): + platform.async_register_entity_service( + name, + {vol.Required(option): vol.In(DSP_OPTION_MAPPING[which])}, + f"set_{which}", + ) + + add_service(SERVICE_DESK_DB, "desk_db", "db_value") + add_service(SERVICE_WALL_DB, "wall_db", "db_value") + add_service(SERVICE_TREBLE_DB, "treble_db", "db_value") + add_service(SERVICE_HIGH_HZ, "high_hz", "hz_value") + add_service(SERVICE_LOW_HZ, "low_hz", "hz_value") + add_service(SERVICE_SUB_DB, "sub_db", "db_value") + class KefMediaPlayer(MediaPlayerDevice): """Kef Player Object.""" @@ -143,6 +188,7 @@ class KefMediaPlayer(MediaPlayerDevice): inverse_speaker_mode, supports_on, sources, + speaker_type, ioloop, unique_id, ): @@ -160,12 +206,15 @@ class KefMediaPlayer(MediaPlayerDevice): ) self._unique_id = unique_id self._supports_on = supports_on + self._speaker_type = speaker_type self._state = None self._muted = None self._source = None self._volume = None self._is_online = None + self._dsp = None + self._update_dsp_task_remover = None @property def name(self): @@ -190,6 +239,9 @@ class KefMediaPlayer(MediaPlayerDevice): state = await self._speaker.get_state() self._source = state.source self._state = STATE_ON if state.is_on else STATE_OFF + if self._dsp is None: + # Only do this when necessary because it is a slow operation + await self.update_dsp() else: self._muted = None self._source = None @@ -291,11 +343,11 @@ class KefMediaPlayer(MediaPlayerDevice): async def async_media_play(self): """Send play command.""" - await self._speaker.play_pause() + await self._speaker.set_play_pause() async def async_media_pause(self): """Send pause command.""" - await self._speaker.play_pause() + await self._speaker.set_play_pause() async def async_media_previous_track(self): """Send previous track command.""" @@ -304,3 +356,87 @@ class KefMediaPlayer(MediaPlayerDevice): async def async_media_next_track(self): """Send next track command.""" await self._speaker.next_track() + + async def update_dsp(self) -> None: + """Update the DSP settings.""" + if self._speaker_type == "LS50" and self._state == STATE_OFF: + # The LSX is able to respond when off the LS50 has to be on. + return + + (mode, *rest) = await asyncio.gather( + self._speaker.get_mode(), + self._speaker.get_desk_db(), + self._speaker.get_wall_db(), + self._speaker.get_treble_db(), + self._speaker.get_high_hz(), + self._speaker.get_low_hz(), + self._speaker.get_sub_db(), + ) + keys = ["desk_db", "wall_db", "treble_db", "high_hz", "low_hz", "sub_db"] + self._dsp = dict(zip(keys, rest), **mode._asdict()) + + async def async_added_to_hass(self): + """Subscribe to DSP updates.""" + self._update_dsp_task_remover = async_track_time_interval( + self.hass, self.update_dsp, DSP_SCAN_INTERVAL + ) + + async def async_will_remove_from_hass(self): + """Unsubscribe to DSP updates.""" + self._update_dsp_task_remover() + self._update_dsp_task_remover = None + + @property + def device_state_attributes(self): + """Return the DSP settings of the KEF device.""" + return self._dsp or {} + + async def set_mode( + self, + desk_mode=None, + wall_mode=None, + phase_correction=None, + high_pass=None, + sub_polarity=None, + bass_extension=None, + ): + """Set the speaker mode.""" + await self._speaker.set_mode( + desk_mode=desk_mode, + wall_mode=wall_mode, + phase_correction=phase_correction, + high_pass=high_pass, + sub_polarity=sub_polarity, + bass_extension=bass_extension, + ) + self._dsp = None + + async def set_desk_db(self, db_value): + """Set desk_db of the KEF speakers.""" + await self._speaker.set_desk_db(db_value) + self._dsp = None + + async def set_wall_db(self, db_value): + """Set wall_db of the KEF speakers.""" + await self._speaker.set_wall_db(db_value) + self._dsp = None + + async def set_treble_db(self, db_value): + """Set treble_db of the KEF speakers.""" + await self._speaker.set_treble_db(db_value) + self._dsp = None + + async def set_high_hz(self, hz_value): + """Set high_hz of the KEF speakers.""" + await self._speaker.set_high_hz(hz_value) + self._dsp = None + + async def set_low_hz(self, hz_value): + """Set low_hz of the KEF speakers.""" + await self._speaker.set_low_hz(hz_value) + self._dsp = None + + async def set_sub_db(self, db_value): + """Set sub_db of the KEF speakers.""" + await self._speaker.set_sub_db(db_value) + self._dsp = None diff --git a/homeassistant/components/kef/services.yaml b/homeassistant/components/kef/services.yaml new file mode 100644 index 00000000000..2226d3b6c2d --- /dev/null +++ b/homeassistant/components/kef/services.yaml @@ -0,0 +1,97 @@ +update_dsp: + description: Update all DSP settings. + fields: + entity_id: + description: The entity_id of the KEF speaker. + example: media_player.kef_lsx + +set_mode: + description: Set the mode of the speaker. + fields: + entity_id: + description: The entity_id of the KEF speaker. + example: media_player.kef_lsx + desk_mode: + description: > + "Desk mode" (true or false) + example: true + wall_mode: + description: > + "Wall mode" (true or false) + example: true + phase_correction: + description: > + "Phase correction" (true or false) + example: true + high_pass: + description: > + "High-pass mode" (true or false) + example: true + sub_polarity: + description: > + "Sub polarity" ("-" or "+") + example: "+" + bass_extension: + description: > + "Bass extension" selector ("Less", "Standard", or "Extra") + example: "Extra" + +set_desk_db: + description: Set the "Desk mode" slider of the speaker in dB. + fields: + entity_id: + description: The entity_id of the KEF speaker. + example: media_player.kef_lsx + db_value: + description: Value of the slider (-6 to 0 with steps of 0.5) + example: 0.0 + +set_wall_db: + description: Set the "Wall mode" slider of the speaker in dB. + fields: + entity_id: + description: The entity_id of the KEF speaker. + example: media_player.kef_lsx + db_value: + description: Value of the slider (-6 to 0 with steps of 0.5) + example: 0.0 + +set_treble_db: + description: Set desk the "Treble trim" slider of the speaker in dB. + fields: + entity_id: + description: The entity_id of the KEF speaker. + example: media_player.kef_lsx + db_value: + description: Value of the slider (-2 to 2 with steps of 0.5) + example: 0.0 + +set_high_hz: + description: Set the "High-pass mode" slider of the speaker in Hz. + fields: + entity_id: + description: The entity_id of the KEF speaker. + example: media_player.kef_lsx + hz_value: + description: Value of the slider (50 to 120 with steps of 5) + example: 95 + +set_low_hz: + description: Set the "Sub out low-pass frequency" slider of the speaker in Hz. + fields: + entity_id: + description: The entity_id of the KEF speaker. + example: media_player.kef_lsx + hz_value: + description: Value of the slider (40 to 250 with steps of 5) + example: 80 + +set_sub_db: + description: Set the "Sub gain" slider of the speaker in dB. + fields: + entity_id: + description: The entity_id of the KEF speaker. + example: media_player.kef_lsx + db_value: + description: Value of the slider (-10 to 10 with steps of 1) + example: 0 diff --git a/requirements_all.txt b/requirements_all.txt index 94ce70f6e4e..3e7e5fa5653 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -184,7 +184,7 @@ aioimaplib==0.7.15 aiokafka==0.5.1 # homeassistant.components.kef -aiokef==0.2.7 +aiokef==0.2.9 # homeassistant.components.lifx aiolifx==0.6.7