1 """Platform for the KEF Wireless Speakers."""
3 from __future__
import annotations
5 from datetime
import timedelta
6 from functools
import partial
10 from aiokef
import AsyncKefSpeaker
11 from aiokef.aiokef
import DSP_OPTION_MAPPING
12 from getmac
import get_mac_address
13 import voluptuous
as vol
16 PLATFORM_SCHEMA
as MEDIA_PLAYER_PLATFORM_SCHEMA,
18 MediaPlayerEntityFeature,
29 _LOGGER = logging.getLogger(__name__)
33 DEFAULT_MAX_VOLUME = 0.5
34 DEFAULT_VOLUME_STEP = 0.05
35 DEFAULT_INVERSE_SPEAKER_MODE =
False
36 DEFAULT_SUPPORTS_ON =
True
42 SOURCES = {
"LSX": [
"Wifi",
"Bluetooth",
"Aux",
"Opt"]}
43 SOURCES[
"LS50"] = SOURCES[
"LSX"] + [
"Usb"]
45 CONF_MAX_VOLUME =
"maximum_volume"
46 CONF_VOLUME_STEP =
"volume_step"
47 CONF_INVERSE_SPEAKER_MODE =
"inverse_speaker_mode"
48 CONF_SUPPORTS_ON =
"supports_on"
49 CONF_STANDBY_TIME =
"standby_time"
51 SERVICE_MODE =
"set_mode"
52 SERVICE_DESK_DB =
"set_desk_db"
53 SERVICE_WALL_DB =
"set_wall_db"
54 SERVICE_TREBLE_DB =
"set_treble_db"
55 SERVICE_HIGH_HZ =
"set_high_hz"
56 SERVICE_LOW_HZ =
"set_low_hz"
57 SERVICE_SUB_DB =
"set_sub_db"
58 SERVICE_UPDATE_DSP =
"update_dsp"
62 PLATFORM_SCHEMA = MEDIA_PLAYER_PLATFORM_SCHEMA.extend(
64 vol.Required(CONF_HOST): cv.string,
65 vol.Required(CONF_TYPE): vol.In([
"LS50",
"LSX"]),
66 vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
67 vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
68 vol.Optional(CONF_MAX_VOLUME, default=DEFAULT_MAX_VOLUME): cv.small_float,
69 vol.Optional(CONF_VOLUME_STEP, default=DEFAULT_VOLUME_STEP): cv.small_float,
71 CONF_INVERSE_SPEAKER_MODE, default=DEFAULT_INVERSE_SPEAKER_MODE
73 vol.Optional(CONF_SUPPORTS_ON, default=DEFAULT_SUPPORTS_ON): cv.boolean,
74 vol.Optional(CONF_STANDBY_TIME): vol.In([20, 60]),
80 """Get the 'mode' used to retrieve the MAC address."""
82 ip_address = ipaddress.ip_address(host)
86 if ip_address.version == 6:
94 async_add_entities: AddEntitiesCallback,
95 discovery_info: DiscoveryInfoType |
None =
None,
97 """Set up the KEF platform."""
98 if DOMAIN
not in hass.data:
99 hass.data[DOMAIN] = {}
101 host = config[CONF_HOST]
102 speaker_type = config[CONF_TYPE]
103 port = config[CONF_PORT]
104 name = config[CONF_NAME]
105 maximum_volume = config[CONF_MAX_VOLUME]
106 volume_step = config[CONF_VOLUME_STEP]
107 inverse_speaker_mode = config[CONF_INVERSE_SPEAKER_MODE]
108 supports_on = config[CONF_SUPPORTS_ON]
109 standby_time = config.get(CONF_STANDBY_TIME)
111 sources = SOURCES[speaker_type]
114 "Setting up %s with host: %s, port: %s, name: %s, sources: %s",
123 mac = await hass.async_add_executor_job(partial(get_mac_address, **{mode: host}))
124 if mac
is None or mac ==
"00:00:00:00:00:00":
127 unique_id = f
"kef-{mac}"
136 inverse_speaker_mode,
144 if host
in hass.data[DOMAIN]:
145 _LOGGER.debug(
"%s is already configured", host)
147 hass.data[DOMAIN][host] = media_player
150 platform = entity_platform.async_get_current_platform()
152 platform.async_register_entity_service(
155 vol.Optional(
"desk_mode"): cv.boolean,
156 vol.Optional(
"wall_mode"): cv.boolean,
157 vol.Optional(
"phase_correction"): cv.boolean,
158 vol.Optional(
"high_pass"): cv.boolean,
159 vol.Optional(
"sub_polarity"): vol.In([
"-",
"+"]),
160 vol.Optional(
"bass_extension"): vol.In([
"Less",
"Standard",
"Extra"]),
164 platform.async_register_entity_service(SERVICE_UPDATE_DSP,
None,
"update_dsp")
166 def add_service(name, which, option):
167 options = DSP_OPTION_MAPPING[which]
168 dtype = type(options[0])
169 platform.async_register_entity_service(
172 vol.Required(option): vol.All(
173 vol.Coerce(float), vol.Coerce(dtype), vol.In(options)
179 add_service(SERVICE_DESK_DB,
"desk_db",
"db_value")
180 add_service(SERVICE_WALL_DB,
"wall_db",
"db_value")
181 add_service(SERVICE_TREBLE_DB,
"treble_db",
"db_value")
182 add_service(SERVICE_HIGH_HZ,
"high_hz",
"hz_value")
183 add_service(SERVICE_LOW_HZ,
"low_hz",
"hz_value")
184 add_service(SERVICE_SUB_DB,
"sub_db",
"db_value")
188 """Kef Player Object."""
190 _attr_icon =
"mdi:speaker-wireless"
200 inverse_speaker_mode,
207 """Initialize the media player."""
216 inverse_speaker_mode,
228 MediaPlayerEntityFeature.VOLUME_SET
229 | MediaPlayerEntityFeature.VOLUME_STEP
230 | MediaPlayerEntityFeature.VOLUME_MUTE
231 | MediaPlayerEntityFeature.SELECT_SOURCE
232 | MediaPlayerEntityFeature.TURN_OFF
233 | MediaPlayerEntityFeature.NEXT_TRACK
234 | MediaPlayerEntityFeature.PAUSE
235 | MediaPlayerEntityFeature.PLAY
236 | MediaPlayerEntityFeature.PREVIOUS_TRACK
242 """Update latest state."""
243 _LOGGER.debug(
"Running async_update")
250 ) = await self.
_speaker_speaker.get_volume_and_is_muted()
254 MediaPlayerState.ON
if state.is_on
else MediaPlayerState.OFF
256 if self.
_dsp_dsp
is None:
264 except (ConnectionError, TimeoutError)
as err:
265 _LOGGER.debug(
"Error in `update`: %s", err)
269 """Turn the media player off."""
273 """Turn the media player on."""
275 raise NotImplementedError
279 """Volume up the media player."""
280 await self.
_speaker_speaker.increase_volume()
283 """Volume down the media player."""
284 await self.
_speaker_speaker.decrease_volume()
287 """Set volume level, range 0..1."""
288 await self.
_speaker_speaker.set_volume(volume)
291 """Mute (True) or unmute (False) media player."""
295 await self.
_speaker_speaker.unmute()
298 """Select input source."""
300 await self.
_speaker_speaker.set_source(source)
302 raise ValueError(f
"Unknown input source: {source}.")
305 """Send play command."""
306 await self.
_speaker_speaker.set_play_pause()
309 """Send pause command."""
310 await self.
_speaker_speaker.set_play_pause()
313 """Send previous track command."""
314 await self.
_speaker_speaker.prev_track()
317 """Send next track command."""
318 await self.
_speaker_speaker.next_track()
321 """Update the DSP settings."""
326 mode = await self.
_speaker_speaker.get_mode()
328 "desk_db": await self.
_speaker_speaker.get_desk_db(),
329 "wall_db": await self.
_speaker_speaker.get_wall_db(),
330 "treble_db": await self.
_speaker_speaker.get_treble_db(),
331 "high_hz": await self.
_speaker_speaker.get_high_hz(),
332 "low_hz": await self.
_speaker_speaker.get_low_hz(),
333 "sub_db": await self.
_speaker_speaker.get_sub_db(),
338 """Subscribe to DSP updates."""
344 """Unsubscribe to DSP updates."""
350 """Return the DSP settings of the KEF device."""
351 return self.
_dsp_dsp
or {}
357 phase_correction=None,
362 """Set the speaker mode."""
366 phase_correction=phase_correction,
368 sub_polarity=sub_polarity,
369 bass_extension=bass_extension,
374 """Set desk_db of the KEF speakers."""
379 """Set wall_db of the KEF speakers."""
384 """Set treble_db of the KEF speakers."""
389 """Set high_hz of the KEF speakers."""
394 """Set low_hz of the KEF speakers."""
399 """Set sub_db of the KEF speakers."""
str|float get_state(dict[str, float] data, str key)
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)