1 """Support for Songpal-enabled (Sony) media devices."""
3 from __future__
import annotations
6 from collections
import OrderedDict
18 from songpal.containers
import Setting
19 import voluptuous
as vol
22 MediaPlayerDeviceClass,
24 MediaPlayerEntityFeature,
32 config_validation
as cv,
33 device_registry
as dr,
40 from .const
import CONF_ENDPOINT, DOMAIN, ERROR_REQUEST_RETRY, SET_SOUND_SETTING
42 _LOGGER = logging.getLogger(__name__)
47 INITIAL_RETRY_DELAY = 10
53 async_add_entities: AddEntitiesCallback,
54 discovery_info: DiscoveryInfoType |
None =
None,
56 """Set up from legacy configuration file. Obsolete."""
58 "Configuring Songpal through media_player platform is no longer supported."
59 " Convert to songpal platform or UI configuration"
65 config_entry: ConfigEntry,
66 async_add_entities: AddEntitiesCallback,
68 """Set up songpal media player."""
69 name = config_entry.data[CONF_NAME]
70 endpoint = config_entry.data[CONF_ENDPOINT]
74 async
with asyncio.timeout(
77 await device.get_supported_methods()
78 except (SongpalException, TimeoutError)
as ex:
79 _LOGGER.warning(
"[%s(%s)] Unable to connect", name, endpoint)
80 _LOGGER.debug(
"Unable to get methods from songpal: %s", ex)
81 raise PlatformNotReady
from ex
86 platform = entity_platform.async_get_current_platform()
87 platform.async_register_entity_service(
89 {vol.Required(PARAM_NAME): cv.string, vol.Required(PARAM_VALUE): cv.string},
90 "async_set_sound_setting",
95 """Class representing a Songpal device."""
97 _attr_should_poll =
False
98 _attr_device_class = MediaPlayerDeviceClass.RECEIVER
99 _attr_supported_features = (
100 MediaPlayerEntityFeature.VOLUME_SET
101 | MediaPlayerEntityFeature.VOLUME_STEP
102 | MediaPlayerEntityFeature.VOLUME_MUTE
103 | MediaPlayerEntityFeature.SELECT_SOURCE
104 | MediaPlayerEntityFeature.SELECT_SOUND_MODE
105 | MediaPlayerEntityFeature.TURN_ON
106 | MediaPlayerEntityFeature.TURN_OFF
108 _attr_has_entity_name =
True
134 """Run when entity is added to hass."""
138 """Run when entity will be removed from hass."""
139 await self.
_dev_dev.stop_listen_notifications()
142 """Get available sound modes and the active one."""
143 for settings
in await self.
_dev_dev.get_sound_settings():
144 if settings.target ==
"soundField":
149 if isinstance(settings, Setting):
150 settings = [settings]
153 active_sound_mode =
None
154 for setting
in settings:
155 cur = setting.currentValue
156 for opt
in setting.candidate:
157 if not opt.isAvailable:
160 active_sound_mode = opt.value
161 sound_modes[opt.value] = opt
163 _LOGGER.debug(
"Got sound modes: %s", sound_modes)
164 _LOGGER.debug(
"Active sound mode: %s", active_sound_mode)
166 return active_sound_mode, sound_modes
169 """Activate websocket for listening if wanted."""
170 _LOGGER.debug(
"Activating websocket connection")
172 async
def _volume_changed(volume: VolumeChange):
173 _LOGGER.debug(
"Volume changed: %s", volume)
174 self.
_volume_volume = volume.volume
178 async
def _source_changed(content: ContentChange):
179 _LOGGER.debug(
"Source changed: %s", content)
182 _LOGGER.debug(
"New active source: %s", self.
_active_source_active_source)
185 _LOGGER.debug(
"Got non-handled content change: %s", content)
187 async
def _setting_changed(setting: SettingChange):
188 _LOGGER.debug(
"Setting changed: %s", setting)
190 if setting.target ==
"soundField":
195 _LOGGER.debug(
"Got non-handled setting change: %s", setting)
197 async
def _power_changed(power: PowerChange):
198 _LOGGER.debug(
"Power changed: %s", power)
199 self.
_state_state = power.status
202 async
def _try_reconnect(connect: ConnectChange):
204 "[%s(%s)] Got disconnected, trying to reconnect",
206 self.
_dev_dev.endpoint,
208 _LOGGER.debug(
"Disconnected: %s", connect.exception)
214 delay = INITIAL_RETRY_DELAY
216 _LOGGER.debug(
"Trying to reconnect in %s seconds", delay)
217 await asyncio.sleep(delay)
220 await self.
_dev_dev.get_supported_methods()
221 except SongpalException
as ex:
222 _LOGGER.debug(
"Failed to reconnect: %s", ex)
223 delay =
min(2 * delay, 300)
229 self.
hasshass.loop.create_task(self.
_dev_dev.listen_notifications())
231 "[%s(%s)] Connection reestablished", self.
namename, self.
_dev_dev.endpoint
234 self.
_dev_dev.on_notification(VolumeChange, _volume_changed)
235 self.
_dev_dev.on_notification(ContentChange, _source_changed)
236 self.
_dev_dev.on_notification(PowerChange, _power_changed)
237 self.
_dev_dev.on_notification(SettingChange, _setting_changed)
238 self.
_dev_dev.on_notification(ConnectChange, _try_reconnect)
240 async
def handle_stop(event):
241 await self.
_dev_dev.stop_listen_notifications()
243 self.
hasshass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop)
245 self.
hasshass.loop.create_task(self.
_dev_dev.listen_notifications())
249 """Return a unique ID."""
250 return self.
_sysinfo_sysinfo.macAddr
or self.
_sysinfo_sysinfo.wirelessMacAddr
254 """Return the device info."""
257 connections.add((dr.CONNECTION_NETWORK_MAC, self.
_sysinfo_sysinfo.macAddr))
258 if self.
_sysinfo_sysinfo.wirelessMacAddr:
259 connections.add((dr.CONNECTION_NETWORK_MAC, self.
_sysinfo_sysinfo.wirelessMacAddr))
261 connections=connections,
263 manufacturer=
"Sony Corporation",
265 name=self.
_name_name,
266 sw_version=self.
_sysinfo_sysinfo.version,
270 """Change a setting on the device."""
271 _LOGGER.debug(
"Calling set_sound_setting with %s: %s", name, value)
272 await self.
_dev_dev.set_sound_settings(name, value)
275 """Fetch updates from the device."""
278 self.
_sysinfo_sysinfo = await self.
_dev_dev.get_system_info()
280 if self.
_model_model
is None:
281 interface_info = await self.
_dev_dev.get_interface_information()
282 self.
_model_model = interface_info.modelName
284 volumes = await self.
_dev_dev.get_volume_information()
286 _LOGGER.error(
"Got no volume controls, bailing out")
291 _LOGGER.debug(
"Got %s volume controls, using the first one", volumes)
294 _LOGGER.debug(
"Current volume: %s", volume)
298 self.
_volume_volume = volume.volume
302 status = await self.
_dev_dev.get_power()
303 self.
_state_state = status.status
304 _LOGGER.debug(
"Got state: %s", status)
306 inputs = await self.
_dev_dev.get_inputs()
307 _LOGGER.debug(
"Got ins: %s", inputs)
309 self.
_sources_sources = OrderedDict()
310 for input_
in inputs:
311 self.
_sources_sources[input_.uri] = input_
315 _LOGGER.debug(
"Active source: %s", self.
_active_source_active_source)
324 except SongpalException
as ex:
325 _LOGGER.error(
"Unable to update: %s", ex)
330 for out
in self.
_sources_sources.values():
331 if out.title == source:
335 _LOGGER.error(
"Unable to find output: %s", source)
339 """Return list of available sources."""
340 return [src.title
for src
in self.
_sources_sources.values()]
343 """Select sound mode."""
345 if mode.title == sound_mode:
346 await self.
_dev_dev.set_sound_settings(
"soundField", mode.value)
349 _LOGGER.error(
"Unable to find sound mode: %s", sound_mode)
353 """Return list of available sound modes.
355 When active mode is None it means that sound mode is unavailable on the sound bar.
356 Can be due to incompatible sound bar or the sound bar is in a mode that does not
357 support sound mode changes.
361 return [sound_mode.title
for sound_mode
in self.
_sound_modes_sound_modes.values()]
364 def state(self) -> MediaPlayerState:
365 """Return current state."""
367 return MediaPlayerState.ON
368 return MediaPlayerState.OFF
372 """Return currently active source."""
378 """Return currently active sound_mode."""
380 return active_sound_mode.title
if active_sound_mode
else None
384 """Return volume level."""
388 """Set volume level."""
390 _LOGGER.debug(
"Setting volume to %s", volume)
398 """Set volume down."""
402 """Turn the device on."""
404 await self.
_dev_dev.set_power(
True)
405 except SongpalException
as ex:
406 if ex.code == ERROR_REQUEST_RETRY:
408 "Swallowing %s, the device might be already in the wanted state", ex
414 """Turn the device off."""
416 await self.
_dev_dev.set_power(
False)
417 except SongpalException
as ex:
418 if ex.code == ERROR_REQUEST_RETRY:
420 "Swallowing %s, the device might be already in the wanted state", ex
426 """Mute or unmute the device."""
427 _LOGGER.debug(
"Set mute: %s", mute)
None async_update_ha_state(self, bool force_refresh=False)
None async_write_ha_state(self)
str|UndefinedType|None name(self)
web.Response get(self, web.Request request, str config_key)