mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 10:13:52 -05:00
Add Avea device info (#171624)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""Light platform for Avea."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from contextlib import suppress
|
||||
import logging
|
||||
from typing import Any
|
||||
@@ -19,6 +20,7 @@ from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
AddEntitiesCallback,
|
||||
@@ -27,7 +29,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import color as color_util
|
||||
|
||||
from . import AveaConfigEntry
|
||||
from .const import DOMAIN, INTEGRATION_TITLE, UNKNOWN_NAME
|
||||
from .const import DOMAIN, INTEGRATION_TITLE, MODEL, UNKNOWN_NAME
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
UPDATE_EXCEPTIONS = (BleakError, OSError, RuntimeError)
|
||||
@@ -42,6 +44,13 @@ def _normalize_name(name: str | None) -> str | None:
|
||||
return name
|
||||
|
||||
|
||||
def _read_device_info_value(read: Callable[[], str | None]) -> str | None:
|
||||
"""Read a device information value from an Avea bulb."""
|
||||
with suppress(*UPDATE_EXCEPTIONS):
|
||||
return _normalize_name(read())
|
||||
return None
|
||||
|
||||
|
||||
def _ha_brightness_to_avea(brightness: int) -> int:
|
||||
"""Convert Home Assistant brightness to Avea brightness."""
|
||||
return round((brightness / 255) * AVEA_MAX_BRIGHTNESS)
|
||||
@@ -96,7 +105,8 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up the Avea light platform."""
|
||||
async_add_entities(
|
||||
[AveaLight(entry.runtime_data, entry.title)], update_before_add=True
|
||||
[AveaLight(entry.runtime_data, entry.data[CONF_ADDRESS])],
|
||||
update_before_add=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -180,14 +190,42 @@ class AveaLight(LightEntity):
|
||||
"""Representation of an Avea."""
|
||||
|
||||
_attr_color_mode = ColorMode.HS
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
_attr_supported_color_modes = {ColorMode.HS}
|
||||
|
||||
def __init__(self, light: avea.Bulb, entry_title: str) -> None:
|
||||
def __init__(self, light: avea.Bulb, address: str) -> None:
|
||||
"""Initialize an AveaLight."""
|
||||
self._light = light
|
||||
self._attr_name = entry_title
|
||||
self._attr_unique_id = address
|
||||
self._attr_brightness = light.brightness
|
||||
self._last_brightness = 255
|
||||
self._device_info_updated = False
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections={(CONNECTION_BLUETOOTH, address)},
|
||||
model=MODEL,
|
||||
)
|
||||
|
||||
def _update_device_info(self) -> None:
|
||||
"""Fetch device information from the Avea bulb."""
|
||||
device_info = self._attr_device_info
|
||||
assert device_info is not None
|
||||
|
||||
manufacturer = _read_device_info_value(self._light.get_manufacturer_name)
|
||||
hardware_revision = _read_device_info_value(self._light.get_hardware_revision)
|
||||
firmware_version = _read_device_info_value(self._light.get_fw_version)
|
||||
serial_number = _read_device_info_value(self._light.get_serial_number)
|
||||
|
||||
if manufacturer:
|
||||
device_info["manufacturer"] = manufacturer
|
||||
if hardware_revision:
|
||||
device_info["hw_version"] = hardware_revision
|
||||
if firmware_version:
|
||||
device_info["sw_version"] = firmware_version
|
||||
if serial_number:
|
||||
device_info["serial_number"] = serial_number
|
||||
|
||||
self._device_info_updated = True
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Instruct the light to turn on."""
|
||||
@@ -214,6 +252,8 @@ class AveaLight(LightEntity):
|
||||
connected = self._light.connect()
|
||||
|
||||
try:
|
||||
if not self._device_info_updated:
|
||||
self._update_device_info()
|
||||
brightness = self._light.get_brightness()
|
||||
rgb_color = self._light.get_rgb()
|
||||
finally:
|
||||
|
||||
@@ -5,6 +5,9 @@ from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
|
||||
|
||||
from tests.components.bluetooth import generate_advertisement_data, generate_ble_device
|
||||
|
||||
AVEA_FIRMWARE_VERSION = "2.4.6 (135)"
|
||||
AVEA_SERIAL_NUMBER = "FFEEDDCCBBAA"
|
||||
|
||||
AVEA_DISCOVERY_INFO = BluetoothServiceInfoBleak(
|
||||
name="Avea Bulb",
|
||||
address="AA:BB:CC:DD:EE:FF",
|
||||
|
||||
@@ -7,6 +7,7 @@ from unittest.mock import MagicMock, call, patch
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.avea.const import UNKNOWN_NAME
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_HS_COLOR,
|
||||
@@ -15,8 +16,9 @@ from homeassistant.components.light import (
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from . import AVEA_DISCOVERY_INFO
|
||||
from . import AVEA_DISCOVERY_INFO, AVEA_FIRMWARE_VERSION, AVEA_SERIAL_NUMBER
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
@@ -26,10 +28,18 @@ def mock_bulb() -> MagicMock:
|
||||
"""Return a mocked Avea bulb."""
|
||||
bulb = MagicMock()
|
||||
bulb.name = "Unknown"
|
||||
bulb.fw_version = "Unknown"
|
||||
bulb.hardware_revision = "Unknown"
|
||||
bulb.manufacturer_name = "Unknown"
|
||||
bulb.serial_number = "Unknown"
|
||||
bulb.brightness = 0
|
||||
bulb.connect.return_value = True
|
||||
bulb.get_brightness.return_value = 0
|
||||
bulb.get_fw_version.return_value = AVEA_FIRMWARE_VERSION
|
||||
bulb.get_hardware_revision.return_value = "Elgato Avea"
|
||||
bulb.get_manufacturer_name.return_value = "Elgato Systems GmbH"
|
||||
bulb.get_rgb.return_value = (0, 0, 0)
|
||||
bulb.get_serial_number.return_value = AVEA_SERIAL_NUMBER
|
||||
return bulb
|
||||
|
||||
|
||||
@@ -65,6 +75,125 @@ async def test_init_state(
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [ColorMode.HS]
|
||||
|
||||
|
||||
async def test_device_info(
|
||||
device_registry: dr.DeviceRegistry,
|
||||
setup_integration: MagicMock,
|
||||
) -> None:
|
||||
"""Test the device info."""
|
||||
bulb = setup_integration
|
||||
device = device_registry.async_get_device(
|
||||
connections={(dr.CONNECTION_BLUETOOTH, AVEA_DISCOVERY_INFO.address)},
|
||||
)
|
||||
|
||||
assert device is not None
|
||||
assert device.name == "Bedroom"
|
||||
assert device.manufacturer == "Elgato Systems GmbH"
|
||||
assert device.model == "Avea"
|
||||
assert device.hw_version == "Elgato Avea"
|
||||
assert device.sw_version == AVEA_FIRMWARE_VERSION
|
||||
assert device.serial_number == AVEA_SERIAL_NUMBER
|
||||
bulb.get_manufacturer_name.assert_called_once()
|
||||
bulb.get_hardware_revision.assert_called_once()
|
||||
bulb.get_fw_version.assert_called_once()
|
||||
bulb.get_serial_number.assert_called_once()
|
||||
|
||||
|
||||
async def test_device_info_populates_when_connect_fails(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_bulb: MagicMock,
|
||||
) -> None:
|
||||
"""Test device info is populated when the shared connection fails."""
|
||||
mock_bulb.connect.return_value = False
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.avea.async_ble_device_from_address",
|
||||
return_value=AVEA_DISCOVERY_INFO.device,
|
||||
),
|
||||
patch("homeassistant.components.avea.avea.Bulb", return_value=mock_bulb),
|
||||
):
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_bulb.connect.assert_called_once()
|
||||
mock_bulb.get_manufacturer_name.assert_called_once()
|
||||
mock_bulb.get_hardware_revision.assert_called_once()
|
||||
mock_bulb.get_fw_version.assert_called_once()
|
||||
mock_bulb.get_serial_number.assert_called_once()
|
||||
mock_bulb.disconnect.assert_not_called()
|
||||
|
||||
device = device_registry.async_get_device(
|
||||
connections={(dr.CONNECTION_BLUETOOTH, AVEA_DISCOVERY_INFO.address)},
|
||||
)
|
||||
|
||||
assert device is not None
|
||||
assert device.manufacturer == "Elgato Systems GmbH"
|
||||
assert device.model == "Avea"
|
||||
assert device.hw_version == "Elgato Avea"
|
||||
assert device.sw_version == AVEA_FIRMWARE_VERSION
|
||||
assert device.serial_number == AVEA_SERIAL_NUMBER
|
||||
|
||||
|
||||
async def test_device_info_ignores_unknown_values(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_bulb: MagicMock,
|
||||
) -> None:
|
||||
"""Test unknown device info is not populated."""
|
||||
mock_bulb.get_manufacturer_name.return_value = UNKNOWN_NAME
|
||||
mock_bulb.get_hardware_revision.return_value = ""
|
||||
mock_bulb.get_fw_version.return_value = UNKNOWN_NAME
|
||||
mock_bulb.get_serial_number.return_value = ""
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.avea.async_ble_device_from_address",
|
||||
return_value=AVEA_DISCOVERY_INFO.device,
|
||||
),
|
||||
patch("homeassistant.components.avea.avea.Bulb", return_value=mock_bulb),
|
||||
):
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(
|
||||
connections={(dr.CONNECTION_BLUETOOTH, AVEA_DISCOVERY_INFO.address)},
|
||||
)
|
||||
|
||||
assert device is not None
|
||||
assert device.manufacturer is None
|
||||
assert device.model == "Avea"
|
||||
assert device.hw_version is None
|
||||
assert device.sw_version is None
|
||||
assert device.serial_number is None
|
||||
|
||||
|
||||
async def test_device_info_is_read_once(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: MagicMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test device info is read once."""
|
||||
bulb = setup_integration
|
||||
bulb.get_manufacturer_name.reset_mock()
|
||||
bulb.get_hardware_revision.reset_mock()
|
||||
bulb.get_fw_version.reset_mock()
|
||||
bulb.get_serial_number.reset_mock()
|
||||
|
||||
freezer.tick(timedelta(seconds=30))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
bulb.get_manufacturer_name.assert_not_called()
|
||||
bulb.get_hardware_revision.assert_not_called()
|
||||
bulb.get_fw_version.assert_not_called()
|
||||
bulb.get_serial_number.assert_not_called()
|
||||
|
||||
|
||||
async def test_turn_on_and_off(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: MagicMock,
|
||||
|
||||
Reference in New Issue
Block a user