Add KEF services for DSP (#31967)

* add services for DSP

* add homeassistant/components/kef/const.py

* add services.yaml

* fix set_mode

* fix services

* media_player.py fixes

* bump aiokef to 0.2.9

* update requirements_all.txt

* add basic sensor.py

* add DSP settings as attributes

* add message about kef.update_dsp

* remove sensor.py

* fix pylint issues

* update_dsp inside async_added_to_hass

* use {...} instead of dict(...)

* get DSP settings when connecting to HA or once on update

* simplify condition

* do not get mode twice

* remove async_added_to_hass

* use async_register_entity_service

* remove entity_id from schema and prepend _value

* invalidate self._dsp after setting a DSP setting

* schedule update_dsp every hour

* subscribe and unsubscribe on adding and removing to HA

* don't pass hass and set _update_dsp_task_remover to None after removing
This commit is contained in:
Bas Nijholt 2020-03-30 19:45:24 +02:00 committed by GitHub
parent bcd1eb952c
commit 3e0ccd2e86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 238 additions and 5 deletions

View File

@ -4,5 +4,5 @@
"documentation": "https://www.home-assistant.io/integrations/kef", "documentation": "https://www.home-assistant.io/integrations/kef",
"dependencies": [], "dependencies": [],
"codeowners": ["@basnijholt"], "codeowners": ["@basnijholt"],
"requirements": ["aiokef==0.2.7", "getmac==0.8.1"] "requirements": ["aiokef==0.2.9", "getmac==0.8.1"]
} }

View File

@ -1,11 +1,13 @@
"""Platform for the KEF Wireless Speakers.""" """Platform for the KEF Wireless Speakers."""
import asyncio
from datetime import timedelta from datetime import timedelta
from functools import partial from functools import partial
import ipaddress import ipaddress
import logging import logging
from aiokef import AsyncKefSpeaker from aiokef import AsyncKefSpeaker
from aiokef.aiokef import DSP_OPTION_MAPPING
from getmac import get_mac_address from getmac import get_mac_address
import voluptuous as vol import voluptuous as vol
@ -31,7 +33,8 @@ from homeassistant.const import (
STATE_OFF, STATE_OFF,
STATE_ON, 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__) _LOGGER = logging.getLogger(__name__)
@ -55,6 +58,17 @@ CONF_INVERSE_SPEAKER_MODE = "inverse_speaker_mode"
CONF_SUPPORTS_ON = "supports_on" CONF_SUPPORTS_ON = "supports_on"
CONF_STANDBY_TIME = "standby_time" 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( PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{ {
vol.Required(CONF_HOST): cv.string, 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, inverse_speaker_mode,
supports_on, supports_on,
sources, sources,
speaker_type,
ioloop=hass.loop, ioloop=hass.loop,
unique_id=unique_id, 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 hass.data[DOMAIN][host] = media_player
async_add_entities([media_player], update_before_add=True) 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): class KefMediaPlayer(MediaPlayerDevice):
"""Kef Player Object.""" """Kef Player Object."""
@ -143,6 +188,7 @@ class KefMediaPlayer(MediaPlayerDevice):
inverse_speaker_mode, inverse_speaker_mode,
supports_on, supports_on,
sources, sources,
speaker_type,
ioloop, ioloop,
unique_id, unique_id,
): ):
@ -160,12 +206,15 @@ class KefMediaPlayer(MediaPlayerDevice):
) )
self._unique_id = unique_id self._unique_id = unique_id
self._supports_on = supports_on self._supports_on = supports_on
self._speaker_type = speaker_type
self._state = None self._state = None
self._muted = None self._muted = None
self._source = None self._source = None
self._volume = None self._volume = None
self._is_online = None self._is_online = None
self._dsp = None
self._update_dsp_task_remover = None
@property @property
def name(self): def name(self):
@ -190,6 +239,9 @@ class KefMediaPlayer(MediaPlayerDevice):
state = await self._speaker.get_state() state = await self._speaker.get_state()
self._source = state.source self._source = state.source
self._state = STATE_ON if state.is_on else STATE_OFF 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: else:
self._muted = None self._muted = None
self._source = None self._source = None
@ -291,11 +343,11 @@ class KefMediaPlayer(MediaPlayerDevice):
async def async_media_play(self): async def async_media_play(self):
"""Send play command.""" """Send play command."""
await self._speaker.play_pause() await self._speaker.set_play_pause()
async def async_media_pause(self): async def async_media_pause(self):
"""Send pause command.""" """Send pause command."""
await self._speaker.play_pause() await self._speaker.set_play_pause()
async def async_media_previous_track(self): async def async_media_previous_track(self):
"""Send previous track command.""" """Send previous track command."""
@ -304,3 +356,87 @@ class KefMediaPlayer(MediaPlayerDevice):
async def async_media_next_track(self): async def async_media_next_track(self):
"""Send next track command.""" """Send next track command."""
await self._speaker.next_track() 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

View File

@ -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

View File

@ -184,7 +184,7 @@ aioimaplib==0.7.15
aiokafka==0.5.1 aiokafka==0.5.1
# homeassistant.components.kef # homeassistant.components.kef
aiokef==0.2.7 aiokef==0.2.9
# homeassistant.components.lifx # homeassistant.components.lifx
aiolifx==0.6.7 aiolifx==0.6.7