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",
"dependencies": [],
"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."""
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

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
# homeassistant.components.kef
aiokef==0.2.7
aiokef==0.2.9
# homeassistant.components.lifx
aiolifx==0.6.7