Skip to content

Commit

Permalink
Update to version 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mj23000 committed Mar 28, 2023
1 parent 1cb31cd commit 53a2667
Show file tree
Hide file tree
Showing 19 changed files with 1,110 additions and 1,251 deletions.
30 changes: 10 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,15 @@ Devices that have been tested and _should_ work without any trouble are:
- [Beosound Balance](https://www.bang-olufsen.com/en/dk/speakers/beosound-balance)
- [Beosound Emerge](https://www.bang-olufsen.com/en/dk/speakers/beosound-emerge)
- [Beosound Level](https://www.bang-olufsen.com/en/dk/speakers/beosound-level)
- [Beosound Theatre](https://www.bang-olufsen.com/en/dk/soundbars/beosound-theatre) (Audio only for now)
- [Beosound Theatre](https://www.bang-olufsen.com/en/dk/soundbars/beosound-theatre)

## Configuration

This device can be added to your Home Assistant installation manually by using the UI or by auto-discovery.

## Entities

This integration adds an array of different useful entities that are generated and added automatically upon setup, customized for the supported features of the device.

```python
SUPPORTS_PROXIMITY_SENSOR = (
"BeoLab 28",
"Beosound Balance",
"Beosound Level",
"Beosound Theatre",
)
```
This integration adds an array of different useful entities that are generated and added automatically upon setup, customized for the supported features of the device. Some of these features, such as `proximity sensor` and `home-control` are manually defined based on model name in the code, as they currently can't be determined in any other way.

### Media Player entity

Expand All @@ -46,9 +37,6 @@ SUPPORTS_PROXIMITY_SENSOR = (
- Displaying currently playing artist and track
- Displaying playback progress
- Media seeking (Currently only when using Deezer)
- Device triggers for automations by using device buttons such as Preset1, Bluetooth etc.
- Device triggers for automations by using the Beoremote One Control and Light events
- Events fired upon WebSocket events received
- Media browsing:
- Playback of local media
- Radio Browsing
Expand Down Expand Up @@ -98,8 +86,6 @@ SUPPORTS_PROXIMITY_SENSOR = (
- media_previous_track
- toggle

Some entities are added according to lists of supported devices. These are currently:

### Binary Sensor entity

- Battery Charging (If available)
Expand All @@ -119,32 +105,36 @@ Some entities are added according to lists of supported devices. These are curre
- Battery Level (If available)
- Battery Charging Time (If available)
- Battery Playing Time (If available)
- Media ID (Disabled by default)
- Input Signal (Disabled by default)

### Select entity

- Sound mode (If available)
- Listening Position (If available)

### Switch entity

- Loudness

### Text entity

- Media ID (If available)
- Friendly Name
- Home Control URI (If available)

## Getting Deezer URIs

In order to find Deezer playlist, album URIs and user IDs for Deezer flows, the Deezer website has to be accessed. When navigating to an album, the URL will look something like: "https://www.deezer.com/en/album/ALBUM_ID", and this simply needs to be converted to: `album:ALBUM_ID` and the same applies to playlist, which have the format: `playlist:PLAYLIST_ID`.
In order to find Deezer playlist, album URIs and user IDs for Deezer flows, the Deezer website has to be accessed. When navigating to an album, the URL will look something like: <https://www.deezer.com/en/album/ALBUM_ID>, and this simply needs to be converted to: `album:ALBUM_ID` and the same applies to playlist, which have the format: `playlist:PLAYLIST_ID`.

Additionally a Deezer user ID can be found at https://www.deezer.com/en/profile/USER_ID by selecting the active user in a web browser.
Additionally a Deezer user ID can be found at <https://www.deezer.com/en/profile/USER_ID> by selecting the active user in a web browser.

Deezer track IDs can currently only easily be found by playing the track on the device and looking at the extra state attributes, where it is shown with the key "deezer_track_id". Tracks do not have a prefix so the ID needs to be used directly.

## Automations

All device triggers can be received by listinging to `bangolufsen_event` event types.

Additionally the "raw" WebSocket notifications received from the device are fired as events in Home Assistant. These can be received by listening to `bangolufsen_websocket_event` event types.
Additionally the "raw" WebSocket notifications received from the device are fired as events in Home Assistant. These can be received by listening to `bangolufsen_websocket_event` event types where `device_id` is used to differentiate devices.

### Physical buttons and sensors

Expand Down
120 changes: 73 additions & 47 deletions custom_components/bangolufsen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,42 @@
import logging

from mozart_api.mozart_client import MozartClient
from urllib3.exceptions import MaxRetryError

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_MODEL, CONF_NAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_send

from .binary_sensor import (
BangOlufsenBinarySensor,
BangOlufsenBinarySensorBatteryCharging,
BangOlufsenBinarySensorProximity,
)
from .button import BangOlufsenButtonFavourite
from .const import (
DOMAIN,
HASS_BINARY_SENSORS,
HASS_CONTROLLER,
HASS_COORDINATOR,
HASS_FAVOURITES,
HASS_MEDIA_PLAYER,
HASS_NUMBERS,
HASS_SELECTS,
HASS_SENSORS,
HASS_SWITCHES,
HASS_TEXT,
SUPPORTS_PROXIMITY_SENSOR,
)
from .controller import BangOlufsenController
from .const import DOMAIN, STOP_WEBSOCKET, EntityEnum, ModelEnum, SupportEnum
from .coordinator import BangOlufsenCoordinator
from .media_player import BangOlufsenMediaPlayer
from .number import BangOlufsenNumberBass, BangOlufsenNumberTreble
from .select import BangOlufsenSelectSoundMode
from .number import BangOlufsenNumber, BangOlufsenNumberBass, BangOlufsenNumberTreble
from .select import (
BangOlufsenSelect,
BangOlufsenSelectListeningPosition,
BangOlufsenSelectSoundMode,
)
from .sensor import (
BangOlufsenSensor,
BangOlufsenSensorBatteryChargingTime,
BangOlufsenSensorBatteryLevel,
BangOlufsenSensorBatteryPlayingTime,
BangOlufsenSensorInputSignal,
BangOlufsenSensorMediaId,
)
from .switch import BangOlufsenSwitch, BangOlufsenSwitchLoudness
from .text import (
BangOlufsenText,
BangOlufsenTextFriendlyName,
BangOlufsenTextHomeControlUri,
)
from .switch import BangOlufsenSwitchLoudness
from .text import BangOlufsenTextMediaId

PLATFORMS = [
Platform.BINARY_SENSOR,
Expand Down Expand Up @@ -75,6 +74,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
# Close WebSocket listener(s) and coordinator
async_dispatcher_send(hass, f"{entry.unique_id}_{STOP_WEBSOCKET}")

unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

if unload_ok:
Expand All @@ -90,17 +92,16 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:

async def init_entities(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Initialise the supported entities of the device."""

client = MozartClient(host=entry.data[CONF_HOST])
supports_battery = False
model = entry.data[CONF_MODEL]

# Check connection and try to initialize it.
if not (
battery_state := client.get_battery_state(
try:
battery_state = client.get_battery_state(
async_req=True, _request_timeout=3
).get()
):
except MaxRetryError:
_LOGGER.error("Unable to connect to %s", entry.data[CONF_NAME])
return False

Expand All @@ -119,25 +120,31 @@ async def init_entities(hass: HomeAssistant, entry: ConfigEntry) -> bool:
binary_sensors.append(BangOlufsenBinarySensorBatteryCharging(entry))

# Check if device supports proxmity detection.
if model in SUPPORTS_PROXIMITY_SENSOR:
if model in SupportEnum.PROXIMITY_SENSOR.value:
binary_sensors.append(BangOlufsenBinarySensorProximity(entry))

# Create the Number entities.
numbers = [BangOlufsenNumberBass(entry), BangOlufsenNumberTreble(entry)]
numbers: list[BangOlufsenNumber] = [
BangOlufsenNumberBass(entry),
BangOlufsenNumberTreble(entry),
]

# Get available favourites.
favourites = client.get_presets(async_req=True).get()

# Create the favourites Button entities.
favourite_buttons = []
favourite_buttons: list[BangOlufsenButtonFavourite] = []

for favourite_id in favourites:
favourite_buttons.append(
BangOlufsenButtonFavourite(entry, coordinator, favourites[favourite_id])
)

# Create the Sensor entities.
sensors = []
sensors: list[BangOlufsenSensor] = [
BangOlufsenSensorInputSignal(entry),
BangOlufsenSensorMediaId(entry),
]

if supports_battery:
sensors.extend(
Expand All @@ -149,37 +156,56 @@ async def init_entities(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)

# Create the Switch entities.
switches = [BangOlufsenSwitchLoudness(entry)]
switches: list[BangOlufsenSwitch] = [BangOlufsenSwitchLoudness(entry)]

# Create the Text entities.
texts = [BangOlufsenTextMediaId(entry)]
beolink_self = client.get_beolink_self(async_req=True).get()

texts: list[BangOlufsenText] = [
BangOlufsenTextFriendlyName(entry, beolink_self.friendly_name),
]

# Add the Home Control URI entity if the device supports it
if model in SupportEnum.HOME_CONTROL.value:
home_control = client.get_remote_home_control_uri(async_req=True).get()

texts.append(BangOlufsenTextHomeControlUri(entry, home_control.uri))

# Create the Select entities.
selects = []
selects: list[BangOlufsenSelect] = []

# Create the listening position Select entity if supported
scenes = client.get_all_scenes(async_req=True).get()

# Listening positions
for scene_key in scenes:
scene = scenes[scene_key]

if scene.tags is not None and "listeningposition" in scene.tags:
selects.append(BangOlufsenSelectListeningPosition(entry))
break

# Create the sound mode select entity if supported
listening_modes = client.get_listening_mode_set(async_req=True).get()
if len(listening_modes) > 0:
selects.append(BangOlufsenSelectSoundMode(entry))
# Currently the Balance does not expose any useful Sound Modes and should be excluded
if model != ModelEnum.balance:
listening_modes = client.get_listening_mode_set(async_req=True).get()
if len(listening_modes) > 0:
selects.append(BangOlufsenSelectSoundMode(entry))

# Create the Media Player entity.
media_player = BangOlufsenMediaPlayer(entry, coordinator)

# Handle WebSocket notifications
controller = BangOlufsenController(hass, entry)
media_player = BangOlufsenMediaPlayer(entry)

# Add the created entities
hass.data[DOMAIN][entry.unique_id] = {
HASS_BINARY_SENSORS: binary_sensors,
HASS_CONTROLLER: controller,
HASS_COORDINATOR: coordinator,
HASS_MEDIA_PLAYER: media_player,
HASS_NUMBERS: numbers,
HASS_FAVOURITES: favourite_buttons,
HASS_SENSORS: sensors,
HASS_SWITCHES: switches,
HASS_SELECTS: selects,
HASS_TEXT: texts,
EntityEnum.BINARY_SENSORS: binary_sensors,
EntityEnum.COORDINATOR: coordinator,
EntityEnum.MEDIA_PLAYER: media_player,
EntityEnum.NUMBERS: numbers,
EntityEnum.FAVOURITES: favourite_buttons,
EntityEnum.SENSORS: sensors,
EntityEnum.SWITCHES: switches,
EntityEnum.SELECTS: selects,
EntityEnum.TEXT: texts,
}

return True
Loading

0 comments on commit 53a2667

Please sign in to comment.