Skip to content

Commit

Permalink
Add radio stations to play_media
Browse files Browse the repository at this point in the history
Temp fix for invalid active sound mode
Prepare media_player for video sources
Tweaks
  • Loading branch information
mj23000 committed Jan 6, 2023
1 parent 5e3000f commit 047b26e
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 62 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ This integration adds an array of different useful entities that are generated a

```python
SUPPORTS_PROXIMITY_SENSOR = (
"BeoLab 28",
"Beosound Balance",
"Beosound Level",
"BeoLab 28",
"Beosound Theatre",
)
```
Expand Down
4 changes: 3 additions & 1 deletion custom_components/bangolufsen/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class BangOlufsenMediaType(StrEnum):

FAVOURITE = "favourite"
DEEZER = "deezer"
RADIO = "radio"


# Proximity detection for binary_sensor
Expand Down Expand Up @@ -176,6 +177,7 @@ class ProximityEnum(Enum):
VALID_MEDIA_TYPES: Final[tuple] = (
BangOlufsenMediaType.FAVOURITE,
BangOlufsenMediaType.DEEZER,
BangOlufsenMediaType.RADIO,
MediaType.MUSIC,
MediaType.URL,
)
Expand Down Expand Up @@ -249,9 +251,9 @@ class ProximityEnum(Enum):

# Product capabilities for creating entities
SUPPORTS_PROXIMITY_SENSOR: Final[tuple] = (
"BeoLab 28",
"Beosound Balance",
"Beosound Level",
"BeoLab 28",
"Beosound Theatre",
)

Expand Down
20 changes: 10 additions & 10 deletions custom_components/bangolufsen/manifest.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"domain": "bangolufsen",
"name": "Bang & Olufsen",
"documentation": "https://github.com/bang-olufsen/bangolufsen-hacs",
"issue_tracker": "https://github.com/bang-olufsen/bangolufsen-hacs/issues",
"requirements": ["mozart-api==2.3.4.15123.6"],
"zeroconf": ["_bangolufsen._tcp.local."],
"version": "0.5.3",
"codeowners": ["@mj23000"],
"iot_class": "local_push",
"config_flow": true
"domain": "bangolufsen",
"name": "Bang & Olufsen",
"documentation": "https://github.com/bang-olufsen/bangolufsen-hacs",
"issue_tracker": "https://github.com/bang-olufsen/bangolufsen-hacs/issues",
"requirements": ["mozart-api==2.3.4.15123.6"],
"zeroconf": ["_bangolufsen._tcp.local."],
"version": "0.6.0",
"codeowners": ["@mj23000"],
"iot_class": "local_push",
"config_flow": true
}
90 changes: 58 additions & 32 deletions custom_components/bangolufsen/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from mozart_api.exceptions import ApiException
from mozart_api.models import (
Action,
Art,
BeolinkLeader,
BeolinkListener,
Expand All @@ -21,6 +22,7 @@
PlayQueueItemType,
PlayQueueSettings,
RenderingState,
SceneProperties,
SoftwareUpdateState,
SoftwareUpdateStatus,
Source,
Expand Down Expand Up @@ -255,8 +257,8 @@ def __init__(self, entry: ConfigEntry, coordinator: BangOlufsenCoordinator) -> N
self._state: MediaPlayerState | str = MediaPlayerState.IDLE
self._media_image: Art = Art()
self._last_update: datetime = datetime(1970, 1, 1, 0, 0, 0, 0)
self._source_list: list[str] = []
self._source_list_friendly: list[str] = []
self._sources: dict[str, str] = {}
self._audio_sources: dict[str, str] = {}
self._beolink_listeners: list[BeolinkListener] = []
self._remote_leader: BeolinkLeader | None = None
self._queue_settings: PlayQueueSettings = PlayQueueSettings()
Expand Down Expand Up @@ -377,25 +379,7 @@ async def bangolufsen_init(self) -> bool:
beolink_self = self._client.get_beolink_self(async_req=True).get()
self._friendly_name = beolink_self.friendly_name

# If the device has been updated with new sources, then the API will fail here.
try:
# Get all available sources.
sources = self._client.get_available_sources(
target_remote=False, async_req=True
).get()

# Use a fallback list of sources
except ValueError:
_LOGGER.warning(
"The API is outdated compared to the device software version. Using fallback sources"
)
sources = FALLBACK_SOURCES

# Save all of the relevant enabled sources, both the ID and the friendly name for displaying.
for source in sources.items:
if source.is_enabled is True and source.id not in HIDDEN_SOURCE_IDS:
self._source_list.append(source.id)
self._source_list_friendly.append(source.name)
await self._get_sources()

# Set the default and maximum volume of the product.
self._client.set_volume_settings(
Expand Down Expand Up @@ -436,7 +420,7 @@ async def bangolufsen_init(self) -> bool:
await self._update_bluetooth()

# Set the static entity attributes that needed more information.
self._attr_source_list = self._source_list_friendly
self._attr_source_list = list(self._sources.values())

# Wait for other entities to be initialized before starting the WebSocket listener
await asyncio.sleep(1)
Expand All @@ -448,6 +432,34 @@ async def bangolufsen_init(self) -> bool:

return True

async def _get_sources(self) -> None:
"""Get sources for the specific product."""

# Audio sources
# If the device has been updated with new sources, then the API will fail here.
try:
# Get all available sources.
sources = self._client.get_available_sources(
target_remote=False, async_req=True
).get()

# Use a fallback list of sources
except ValueError:
_LOGGER.warning(
"The API is outdated compared to the device software version. Using fallback sources"
)
sources = FALLBACK_SOURCES

# Save all of the relevant enabled sources, both the ID and the friendly name for displaying in a dict.
self._audio_sources = {
x.id: x.name
for x in sources.items
if x.is_enabled is True and x.id not in HIDDEN_SOURCE_IDS
}

# Combine the source dicts
self._sources.update(self._audio_sources)

def _get_beolink_jid(self, entity_id: str) -> str | None:
"""Get beolink JID from entity_id."""
entity_registry = er.async_get(self.hass)
Expand Down Expand Up @@ -962,20 +974,21 @@ async def async_set_repeat(self, repeat: RepeatMode) -> None:

async def async_select_source(self, source: str) -> None:
"""Select an input source."""
if source in self._source_list_friendly:
index = self._source_list_friendly.index(source)

self._client.set_active_source(
source_id=self._source_list[index],
async_req=True,
)

else:
if source not in self._sources.values():
_LOGGER.error(
"Invalid source: %s. Valid sources are: %s",
source,
self._source_list_friendly,
list(self._sources.values()),
)
return

# pylint: disable=consider-using-dict-items
key = [x for x in self._sources if self._sources[x] == source][0]

# Check for source type
if source in self._audio_sources.values():
# Audio
self._client.set_active_source(source_id=key, async_req=True)

async def async_join_players(self, group_members: list[str]) -> None:
"""Create a Beolink session with defined group members."""
Expand Down Expand Up @@ -1037,6 +1050,19 @@ async def async_play_media(

self._client.post_uri_source(uri=Uri(location=media_id), async_req=True)

elif media_type == BangOlufsenMediaType.RADIO:
self._client.run_provided_scene(
scene_properties=SceneProperties(
action_list=[
Action(
type="radio",
radio_station_id=media_id,
)
]
),
async_req=True,
)

elif media_type == BangOlufsenMediaType.FAVOURITE:
self._client.activate_preset(id=media_id, async_req=True)

Expand Down
33 changes: 15 additions & 18 deletions custom_components/bangolufsen/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,21 +84,25 @@ def __init__(self, entry: ConfigEntry) -> None:
self._attr_unique_id = f"{self._unique_id}-sound-mode"
self._attr_icon = "mdi:sine-wave"

self._sound_modes: dict[str, int] = {}
self._sound_modes: dict[int, str] = {}
self._initial_setup()

def _initial_setup(self) -> None:
"""Get the available sound modes and setup Select functionality."""
sound_modes = self._client.get_listening_mode_set(async_req=True).get()

for sound_mode in sound_modes:
self._sound_modes[sound_mode["name"]] = sound_mode["id"]

active_sound_mode = self._client.get_active_listening_mode(async_req=True).get()

# Add the key to make the labels unique as well
self._sound_modes = {x["id"]: f"{x['name']} - {x['id']}" for x in sound_modes}

# Set available options and selected option.
self._attr_options = list(self._sound_modes)
self._attr_current_option = self._get_sound_mode_name(active_sound_mode.id)
self._attr_options = list(self._sound_modes.values())

# Temp fix for any invalid active sound mode
try:
self._attr_current_option = self._sound_modes[active_sound_mode.id]
except KeyError:
self._attr_current_option = None

async def async_added_to_hass(self) -> None:
"""Turn on the dispatchers."""
Expand All @@ -117,20 +121,13 @@ async def async_added_to_hass(self) -> None:

async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
self._client.activate_listening_mode(
id=self._sound_modes[option], async_req=True
)

def _get_sound_mode_name(self, sound_mode_id: int) -> str:
"""Get the sound mode name from ID."""
# pylint: disable=consider-using-dict-items
return [x for x in self._sound_modes if self._sound_modes[x] == sound_mode_id][
0
]
key = [x for x in self._sound_modes if self._sound_modes[x] == option][0]

self._client.activate_listening_mode(id=key, async_req=True)

async def _update_sound_mode(self, data: ListeningModeProps) -> None:
"""Update sound mode."""
active_sound_mode = data
self._attr_current_option = self._get_sound_mode_name(active_sound_mode.id)
self._attr_current_option = self._sound_modes[active_sound_mode.id]

self.async_write_ha_state()

0 comments on commit 047b26e

Please sign in to comment.