1 """Arcam media player."""
3 from __future__
import annotations
5 from collections.abc
import Callable, Coroutine
10 from arcam.fmj
import ConnectionFailed, SourceCodes
11 from arcam.fmj.state
import State
18 MediaPlayerEntityFeature,
29 from .
import ArcamFmjConfigEntry
34 SIGNAL_CLIENT_STARTED,
35 SIGNAL_CLIENT_STOPPED,
38 _LOGGER = logging.getLogger(__name__)
43 config_entry: ArcamFmjConfigEntry,
44 async_add_entities: AddEntitiesCallback,
46 """Set up the configuration entry."""
48 client = config_entry.runtime_data
55 config_entry.unique_id
or config_entry.entry_id,
63 def convert_exception[**_P, _R](
64 func: Callable[_P, Coroutine[Any, Any, _R]],
65 ) -> Callable[_P, Coroutine[Any, Any, _R]]:
66 """Return decorator to convert a connection error into a home assistant error."""
68 @functools.wraps(func)
69 async
def _convert_exception(*args: _P.args, **kwargs: _P.kwargs) -> _R:
71 return await func(*args, **kwargs)
72 except ConnectionFailed
as exception:
74 f
"Connection failed to device during {func}"
77 return _convert_exception
81 """Representation of a media device."""
83 _attr_should_poll =
False
84 _attr_has_entity_name =
True
92 """Initialize device."""
96 MediaPlayerEntityFeature.SELECT_SOURCE
97 | MediaPlayerEntityFeature.PLAY_MEDIA
98 | MediaPlayerEntityFeature.BROWSE_MEDIA
99 | MediaPlayerEntityFeature.VOLUME_SET
100 | MediaPlayerEntityFeature.VOLUME_MUTE
101 | MediaPlayerEntityFeature.VOLUME_STEP
102 | MediaPlayerEntityFeature.TURN_OFF
103 | MediaPlayerEntityFeature.TURN_ON
113 manufacturer=
"Arcam",
114 model=
"Arcam FMJ AVR",
119 def state(self) -> MediaPlayerState:
120 """Return the state of the device."""
121 if self.
_state_state.get_power():
122 return MediaPlayerState.ON
123 return MediaPlayerState.OFF
126 """Once registered, add listener for events."""
127 await self.
_state_state.start()
130 except ConnectionFailed
as connection:
131 _LOGGER.debug(
"Connection lost during addition: %s", connection)
134 def _data(host: str) ->
None:
135 if host == self.
_state_state.client.host:
139 def _started(host: str) ->
None:
140 if host == self.
_state_state.client.host:
144 def _stopped(host: str) ->
None:
145 if host == self.
_state_state.client.host:
161 """Force update of state."""
162 _LOGGER.debug(
"Update state %s", self.
namename)
165 except ConnectionFailed
as connection:
166 _LOGGER.debug(
"Connection lost during update: %s", connection)
170 """Send mute command."""
171 await self.
_state_state.set_mute(mute)
176 """Select a specific source."""
178 value = SourceCodes[source]
180 _LOGGER.error(
"Unsupported source %s", source)
183 await self.
_state_state.set_source(value)
188 """Select a specific source."""
190 await self.
_state_state.set_decode_mode(sound_mode)
191 except (KeyError, ValueError)
as exception:
193 f
"Unsupported sound_mode {sound_mode}"
200 """Set volume level, range 0..1."""
201 await self.
_state_state.set_volume(round(volume * 99.0))
206 """Turn volume up for media player."""
207 await self.
_state_state.inc_volume()
212 """Turn volume up for media player."""
213 await self.
_state_state.dec_volume()
218 """Turn the media player on."""
219 if self.
_state_state.get_power()
is not None:
220 _LOGGER.debug(
"Turning on device using connection")
221 await self.
_state_state.set_power(
True)
223 _LOGGER.debug(
"Firing event to turn on device")
224 self.
hasshass.bus.async_fire(EVENT_TURN_ON, {ATTR_ENTITY_ID: self.
entity_identity_id})
228 """Turn the media player off."""
229 await self.
_state_state.set_power(
False)
233 media_content_type: MediaType | str |
None =
None,
234 media_content_id: str |
None =
None,
236 """Implement the websocket media browsing helper."""
237 if media_content_id
not in (
None,
"root"):
239 f
"Media not found: {media_content_type} / {media_content_id}"
242 presets = self.
_state_state.get_preset_details()
247 media_class=MediaClass.MUSIC,
248 media_content_id=f
"preset:{preset.index}",
249 media_content_type=MediaType.MUSIC,
253 for preset
in presets.values()
257 title=
"Arcam FMJ Receiver",
258 media_class=MediaClass.DIRECTORY,
259 media_content_id=
"root",
260 media_content_type=
"library",
268 self, media_type: MediaType | str, media_id: str, **kwargs: Any
272 if media_id.startswith(
"preset:"):
273 preset =
int(media_id[7:])
274 await self.
_state_state.set_tuner_preset(preset)
276 _LOGGER.error(
"Media %s is not supported", media_id)
281 """Return the current input source."""
282 if (value := self.
_state_state.get_source())
is None:
288 """List of available input sources."""
289 return [x.name
for x
in self.
_state_state.get_source_list()]
293 """Name of the current sound mode."""
294 if (value := self.
_state_state.get_decode_mode())
is None:
300 """List of available sound modes."""
301 if (values := self.
_state_state.get_decode_modes())
is None:
303 return [x.name
for x
in values]
307 """Boolean if volume is currently muted."""
308 if (value := self.
_state_state.get_mute())
is None:
314 """Volume level of device."""
315 if (value := self.
_state_state.get_volume())
is None:
321 """Content type of current playing media."""
322 source = self.
_state_state.get_source()
323 if source
in (SourceCodes.DAB, SourceCodes.FM):
324 value = MediaType.MUSIC
331 """Content type of current playing media."""
332 source = self.
_state_state.get_source()
333 if source
in (SourceCodes.DAB, SourceCodes.FM):
334 if preset := self.
_state_state.get_tuner_preset():
335 value = f
"preset:{preset}"
345 """Channel currently playing."""
346 source = self.
_state_state.get_source()
347 if source == SourceCodes.DAB:
348 value = self.
_state_state.get_dab_station()
349 elif source == SourceCodes.FM:
350 value = self.
_state_state.get_rds_information()
357 """Artist of current playing media, music track only."""
358 if self.
_state_state.get_source() == SourceCodes.DAB:
359 value = self.
_state_state.get_dls_pdt()
366 """Title of current playing media."""
367 if (source := self.
_state_state.get_source())
is None:
371 value = f
"{source.name} - {channel}"
None async_schedule_update_ha_state(self, bool force_refresh=False)
None async_write_ha_state(self)
None async_on_remove(self, CALLBACK_TYPE func)
str|UndefinedType|None name(self)
IssData update(pyiss.ISS iss)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)