mirror of
https://github.com/home-assistant/core.git
synced 2026-05-08 09:38:58 +01:00
Add battery charging binary sensor to Bang & Olufsen (#160527)
Co-authored-by: Josef Zweck <josef@zweck.dev>
This commit is contained in:
@@ -34,7 +34,12 @@ class BeoData:
|
||||
|
||||
type BeoConfigEntry = ConfigEntry[BeoData]
|
||||
|
||||
PLATFORMS = [Platform.EVENT, Platform.MEDIA_PLAYER, Platform.SENSOR]
|
||||
PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.EVENT,
|
||||
Platform.MEDIA_PLAYER,
|
||||
Platform.SENSOR,
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: BeoConfigEntry) -> bool:
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Binary Sensor entities for the Bang & Olufsen integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from mozart_api.models import BatteryState
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
BinarySensorEntity,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import BeoConfigEntry
|
||||
from .const import CONNECTION_STATUS, DOMAIN, WebsocketNotification
|
||||
from .entity import BeoEntity
|
||||
from .util import supports_battery
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: BeoConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Binary Sensor entities from config entry."""
|
||||
if await supports_battery(config_entry.runtime_data.client):
|
||||
async_add_entities(new_entities=[BeoBinarySensorBatteryCharging(config_entry)])
|
||||
|
||||
|
||||
class BeoBinarySensorBatteryCharging(BinarySensorEntity, BeoEntity):
|
||||
"""Battery charging Binary Sensor."""
|
||||
|
||||
_attr_device_class = BinarySensorDeviceClass.BATTERY_CHARGING
|
||||
_attr_is_on = False
|
||||
|
||||
def __init__(self, config_entry: BeoConfigEntry) -> None:
|
||||
"""Init the battery charging Binary Sensor."""
|
||||
super().__init__(config_entry, config_entry.runtime_data.client)
|
||||
|
||||
self._attr_unique_id = f"{self._unique_id}_charging"
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Turn on the dispatchers."""
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{DOMAIN}_{self._unique_id}_{CONNECTION_STATUS}",
|
||||
self._async_update_connection_state,
|
||||
)
|
||||
)
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{DOMAIN}_{self._unique_id}_{WebsocketNotification.BATTERY}",
|
||||
self._update_battery_charging,
|
||||
)
|
||||
)
|
||||
|
||||
async def _update_battery_charging(self, data: BatteryState) -> None:
|
||||
"""Update battery charging."""
|
||||
self._attr_is_on = bool(data.is_charging)
|
||||
self.async_write_ha_state()
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.components.event import DOMAIN as EVENT_DOMAIN
|
||||
from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
@@ -97,4 +98,15 @@ async def async_get_config_entry_diagnostics(
|
||||
state_dict.pop("context")
|
||||
data["battery_level"] = state_dict
|
||||
|
||||
# Get Mozart battery charging entity
|
||||
if entity_id := entity_registry.async_get_entity_id(
|
||||
BINARY_SENSOR_DOMAIN, DOMAIN, f"{config_entry.unique_id}_charging"
|
||||
):
|
||||
if state := hass.states.get(entity_id):
|
||||
state_dict = dict(state.as_dict())
|
||||
|
||||
# Remove context as it is not relevant
|
||||
state_dict.pop("context")
|
||||
data["charging"] = state_dict
|
||||
|
||||
return data
|
||||
|
||||
@@ -72,7 +72,10 @@ TEST_NAME_4 = f"{TEST_MODEL_A5}-{TEST_SERIAL_NUMBER_4}"
|
||||
TEST_JID_4 = f"{TEST_TYPE_NUMBER}.{TEST_ITEM_NUMBER}.{TEST_SERIAL_NUMBER_4}@products.bang-olufsen.com"
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID_4 = f"media_player.beosound_a5_{TEST_SERIAL_NUMBER_4}"
|
||||
TEST_HOST_4 = "192.168.0.4"
|
||||
TEST_BATTERY_A5_SENSOR_ENTITY_ID = f"sensor.beosound_a5_{TEST_SERIAL_NUMBER_4}_battery"
|
||||
TEST_BATTERY_SENSOR_ENTITY_ID = f"sensor.beosound_a5_{TEST_SERIAL_NUMBER_4}_battery"
|
||||
TEST_BATTERY_CHARGING_BINARY_SENSOR_ENTITY_ID = (
|
||||
f"binary_sensor.beosound_a5_{TEST_SERIAL_NUMBER_4}_charging"
|
||||
)
|
||||
|
||||
# Beoremote One
|
||||
TEST_REMOTE_SERIAL = "55555555"
|
||||
|
||||
@@ -131,6 +131,14 @@
|
||||
'entity_id': 'sensor.beosound_a5_44444444_battery',
|
||||
'state': '5',
|
||||
}),
|
||||
'charging': dict({
|
||||
'attributes': dict({
|
||||
'device_class': 'battery_charging',
|
||||
'friendly_name': 'Living room Balance Charging',
|
||||
}),
|
||||
'entity_id': 'binary_sensor.beosound_a5_44444444_charging',
|
||||
'state': 'off',
|
||||
}),
|
||||
'config_entry': dict({
|
||||
'data': dict({
|
||||
'host': '192.168.0.4',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# serializer version: 1
|
||||
# name: test_button_event_creation_a5
|
||||
list([
|
||||
'binary_sensor.beosound_a5_44444444_charging',
|
||||
'event.beosound_a5_44444444_bluetooth',
|
||||
'event.beosound_a5_44444444_next',
|
||||
'event.beosound_a5_44444444_play_pause',
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Test the bang_olufsen binary sensor entities."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from mozart_api.models import BatteryState
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_registry import EntityRegistry
|
||||
|
||||
from .conftest import mock_websocket_connection
|
||||
from .const import TEST_BATTERY, TEST_BATTERY_CHARGING_BINARY_SENSOR_ENTITY_ID
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_battery_charging(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: EntityRegistry,
|
||||
mock_mozart_client: AsyncMock,
|
||||
mock_config_entry_a5: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the battery charging time entity."""
|
||||
# Ensure battery entities are created
|
||||
mock_mozart_client.get_battery_state.return_value = TEST_BATTERY
|
||||
|
||||
# Load entry
|
||||
mock_config_entry_a5.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry_a5.entry_id)
|
||||
await mock_websocket_connection(hass, mock_mozart_client)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Initial state is False
|
||||
assert (states := hass.states.get(TEST_BATTERY_CHARGING_BINARY_SENSOR_ENTITY_ID))
|
||||
assert states.state == STATE_OFF
|
||||
|
||||
# Check binary sensor reacts as expected to WebSocket events
|
||||
battery_callback = mock_mozart_client.get_battery_notifications.call_args[0][0]
|
||||
|
||||
battery_callback(BatteryState(is_charging=True))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (states := hass.states.get(TEST_BATTERY_CHARGING_BINARY_SENSOR_ENTITY_ID))
|
||||
assert states.state == STATE_ON
|
||||
@@ -56,7 +56,6 @@ async def test_async_get_config_entry_diagnostics(
|
||||
|
||||
async def test_async_get_config_entry_diagnostics_with_battery(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: EntityRegistry,
|
||||
hass_client: ClientSessionGenerator,
|
||||
mock_config_entry_a5: MockConfigEntry,
|
||||
mock_mozart_client: AsyncMock,
|
||||
|
||||
@@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant
|
||||
from .conftest import mock_websocket_connection
|
||||
from .const import (
|
||||
TEST_BATTERY,
|
||||
TEST_BATTERY_A5_SENSOR_ENTITY_ID,
|
||||
TEST_BATTERY_SENSOR_ENTITY_ID,
|
||||
TEST_REMOTE_BATTERY_LEVEL_SENSOR_ENTITY_ID,
|
||||
TEST_REMOTE_SERIAL,
|
||||
)
|
||||
@@ -34,13 +34,13 @@ async def test_battery_level(
|
||||
await hass.config_entries.async_setup(mock_config_entry_a5.entry_id)
|
||||
# Deliberately avoid triggering a battery notification
|
||||
|
||||
assert (states := hass.states.get(TEST_BATTERY_A5_SENSOR_ENTITY_ID))
|
||||
assert (states := hass.states.get(TEST_BATTERY_SENSOR_ENTITY_ID))
|
||||
assert states.state is STATE_UNKNOWN
|
||||
|
||||
# Check sensor reacts as expected to WebSocket events
|
||||
await mock_websocket_connection(hass, mock_mozart_client)
|
||||
|
||||
assert (states := hass.states.get(TEST_BATTERY_A5_SENSOR_ENTITY_ID))
|
||||
assert (states := hass.states.get(TEST_BATTERY_SENSOR_ENTITY_ID))
|
||||
assert states.state == str(TEST_BATTERY.battery_level)
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ from homeassistant.components.bang_olufsen.const import (
|
||||
)
|
||||
|
||||
from .const import (
|
||||
TEST_BATTERY_A5_SENSOR_ENTITY_ID,
|
||||
TEST_BATTERY_CHARGING_BINARY_SENSOR_ENTITY_ID,
|
||||
TEST_BATTERY_SENSOR_ENTITY_ID,
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID,
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID_2,
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID_3,
|
||||
@@ -52,7 +53,8 @@ def get_a5_entity_ids() -> list[str]:
|
||||
"""Return a list of entity_ids that a Beosound A5 provides."""
|
||||
buttons = [
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID_4,
|
||||
TEST_BATTERY_A5_SENSOR_ENTITY_ID,
|
||||
TEST_BATTERY_SENSOR_ENTITY_ID,
|
||||
TEST_BATTERY_CHARGING_BINARY_SENSOR_ENTITY_ID,
|
||||
*_get_button_entity_ids("beosound_a5_44444444"),
|
||||
]
|
||||
buttons.remove("event.beosound_a5_44444444_microphone")
|
||||
|
||||
Reference in New Issue
Block a user