1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-20 02:48:57 +00:00

Bump HueBLE to 2.1.0 (#158197)

This commit is contained in:
Harvey
2025-12-08 18:55:55 +00:00
committed by GitHub
parent 6f5507670f
commit 33e09c4967
10 changed files with 72 additions and 123 deletions

View File

@@ -2,7 +2,7 @@
import logging
from HueBLE import HueBleLight
from HueBLE import ConnectionError, HueBleError, HueBleLight
from homeassistant.components.bluetooth import (
async_ble_device_from_address,
@@ -38,8 +38,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: HueBLEConfigEntry) -> bo
light = HueBleLight(ble_device)
if not await light.connect() or not await light.poll_state():
raise ConfigEntryNotReady("Device found but unable to connect.")
try:
await light.connect()
await light.poll_state()
except ConnectionError as e:
raise ConfigEntryNotReady("Device found but unable to connect.") from e
except HueBleError as e:
raise ConfigEntryNotReady(
"Device found and connected but unable to poll values from it."
) from e
entry.runtime_data = light

View File

@@ -6,7 +6,7 @@ from enum import Enum
import logging
from typing import Any
from HueBLE import HueBleLight
from HueBLE import ConnectionError, HueBleError, HueBleLight, PairingError
import voluptuous as vol
from homeassistant.components import bluetooth
@@ -20,7 +20,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr
from .const import DOMAIN, URL_PAIRING_MODE
from .const import DOMAIN, URL_FACTORY_RESET, URL_PAIRING_MODE
from .light import get_available_color_modes
_LOGGER = logging.getLogger(__name__)
@@ -41,32 +41,22 @@ async def validate_input(hass: HomeAssistant, address: str) -> Error | None:
try:
light = HueBleLight(ble_device)
await light.connect()
get_available_color_modes(light)
await light.poll_state()
if light.authenticated is None:
_LOGGER.warning(
"Unable to determine if light authenticated, proceeding anyway"
)
elif not light.authenticated:
return Error.INVALID_AUTH
if not light.connected:
return Error.CANNOT_CONNECT
try:
get_available_color_modes(light)
except HomeAssistantError:
return Error.NOT_SUPPORTED
_, errors = await light.poll_state()
if len(errors) != 0:
_LOGGER.warning("Errors raised when connecting to light: %s", errors)
return Error.CANNOT_CONNECT
except Exception:
except ConnectionError as e:
_LOGGER.exception("Error connecting to light")
return (
Error.INVALID_AUTH
if type(e.__cause__) is PairingError
else Error.CANNOT_CONNECT
)
except HueBleError:
_LOGGER.exception("Unexpected error validating light connection")
return Error.UNKNOWN
except HomeAssistantError:
return Error.NOT_SUPPORTED
else:
return None
finally:
@@ -129,6 +119,7 @@ class HueBleConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_NAME: self._discovery_info.name,
CONF_MAC: self._discovery_info.address,
"url_pairing_mode": URL_PAIRING_MODE,
"url_factory_reset": URL_FACTORY_RESET,
},
)

View File

@@ -2,3 +2,4 @@
DOMAIN = "hue_ble"
URL_PAIRING_MODE = "https://www.home-assistant.io/integrations/hue_ble#initial-setup"
URL_FACTORY_RESET = "https://www.philips-hue.com/en-gb/support/article/how-to-factory-reset-philips-hue-lights/000004"

View File

@@ -113,7 +113,7 @@ class HueBLELight(LightEntity):
async def async_update(self) -> None:
"""Fetch latest state from light and make available via properties."""
await self._api.poll_state(run_callbacks=True)
await self._api.poll_state()
async def async_turn_on(self, **kwargs: Any) -> None:
"""Set properties then turn the light on."""

View File

@@ -15,5 +15,5 @@
"iot_class": "local_push",
"loggers": ["bleak", "HueBLE"],
"quality_scale": "bronze",
"requirements": ["HueBLE==1.0.8"]
"requirements": ["HueBLE==2.1.0"]
}

View File

@@ -14,7 +14,7 @@
},
"step": {
"confirm": {
"description": "Do you want to set up {name} ({mac})?. Make sure the light is [made discoverable to voice assistants]({url_pairing_mode})."
"description": "Do you want to set up {name} ({mac})?. Make sure the light is [made discoverable to voice assistants]({url_pairing_mode}) or has been [factory reset]({url_factory_reset})."
}
}
}

2
requirements_all.txt generated
View File

@@ -22,7 +22,7 @@ HAP-python==5.0.0
HATasmota==0.10.1
# homeassistant.components.hue_ble
HueBLE==1.0.8
HueBLE==2.1.0
# homeassistant.components.mastodon
Mastodon.py==2.1.2

View File

@@ -22,7 +22,7 @@ HAP-python==5.0.0
HATasmota==0.10.1
# homeassistant.components.hue_ble
HueBLE==1.0.8
HueBLE==2.1.0
# homeassistant.components.mastodon
Mastodon.py==2.1.2

View File

@@ -2,11 +2,16 @@
from unittest.mock import AsyncMock, PropertyMock, patch
from HueBLE import ConnectionError, HueBleError, PairingError
import pytest
from homeassistant import config_entries
from homeassistant.components.hue_ble.config_flow import Error
from homeassistant.components.hue_ble.const import DOMAIN, URL_PAIRING_MODE
from homeassistant.components.hue_ble.const import (
DOMAIN,
URL_FACTORY_RESET,
URL_PAIRING_MODE,
)
from homeassistant.config_entries import SOURCE_BLUETOOTH
from homeassistant.const import CONF_MAC, CONF_NAME
from homeassistant.core import HomeAssistant
@@ -18,22 +23,13 @@ from . import HUE_BLE_SERVICE_INFO, TEST_DEVICE_MAC, TEST_DEVICE_NAME
from tests.common import MockConfigEntry
from tests.components.bluetooth import BLEDevice, generate_ble_device
AUTH_ERROR = ConnectionError()
AUTH_ERROR.__cause__ = PairingError()
@pytest.mark.parametrize(
("mock_authenticated"),
[
(True,),
(None),
],
ids=[
"normal",
"unknown_auth",
],
)
async def test_bluetooth_form(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_authenticated: bool | None,
) -> None:
"""Test bluetooth discovery form."""
@@ -48,6 +44,7 @@ async def test_bluetooth_form(
CONF_NAME: TEST_DEVICE_NAME,
CONF_MAC: TEST_DEVICE_MAC,
"url_pairing_mode": URL_PAIRING_MODE,
"url_factory_reset": URL_FACTORY_RESET,
}
with (
@@ -65,17 +62,7 @@ async def test_bluetooth_form(
),
patch(
"homeassistant.components.hue_ble.config_flow.HueBleLight.poll_state",
side_effect=[(True, [])],
),
patch(
"homeassistant.components.hue_ble.config_flow.HueBleLight.connected",
new_callable=PropertyMock,
return_value=True,
),
patch(
"homeassistant.components.hue_ble.config_flow.HueBleLight.authenticated",
new_callable=PropertyMock,
return_value=mock_authenticated,
side_effect=[True],
),
):
result = await hass.config_entries.flow.async_configure(
@@ -96,8 +83,6 @@ async def test_bluetooth_form(
"mock_return_device",
"mock_scanner_count",
"mock_connect",
"mock_authenticated",
"mock_connected",
"mock_support_on_off",
"mock_poll_state",
"error",
@@ -106,71 +91,57 @@ async def test_bluetooth_form(
(
None,
0,
None,
True,
True,
True,
True,
(True, []),
None,
Error.NO_SCANNERS,
),
(
None,
1,
None,
True,
True,
True,
True,
(True, []),
None,
Error.NOT_FOUND,
),
(
generate_ble_device(TEST_DEVICE_NAME, TEST_DEVICE_MAC),
1,
AUTH_ERROR,
True,
False,
True,
True,
(True, []),
None,
Error.INVALID_AUTH,
),
(
generate_ble_device(TEST_DEVICE_NAME, TEST_DEVICE_MAC),
1,
ConnectionError,
True,
True,
False,
True,
(True, []),
None,
Error.CANNOT_CONNECT,
),
(
generate_ble_device(TEST_DEVICE_NAME, TEST_DEVICE_MAC),
1,
True,
True,
True,
None,
False,
(True, []),
None,
Error.NOT_SUPPORTED,
),
(
generate_ble_device(TEST_DEVICE_NAME, TEST_DEVICE_MAC),
1,
None,
True,
True,
True,
True,
(True, ["ERROR!"]),
Error.CANNOT_CONNECT,
HueBleError,
Error.UNKNOWN,
),
(
generate_ble_device(TEST_DEVICE_NAME, TEST_DEVICE_MAC),
1,
Exception,
HueBleError,
None,
None,
True,
True,
(True, []),
Error.UNKNOWN,
),
],
@@ -189,11 +160,9 @@ async def test_bluetooth_form_exception(
mock_setup_entry: AsyncMock,
mock_return_device: BLEDevice | None,
mock_scanner_count: int,
mock_connect: Exception | bool,
mock_authenticated: bool | None,
mock_connected: bool,
mock_connect: Exception | None,
mock_support_on_off: bool,
mock_poll_state: Exception | tuple[bool, list[Exception]],
mock_poll_state: Exception | None,
error: Error,
) -> None:
"""Test bluetooth discovery form with errors."""
@@ -228,16 +197,6 @@ async def test_bluetooth_form_exception(
"homeassistant.components.hue_ble.config_flow.HueBleLight.poll_state",
side_effect=[mock_poll_state],
),
patch(
"homeassistant.components.hue_ble.config_flow.HueBleLight.connected",
new_callable=PropertyMock,
return_value=mock_connected,
),
patch(
"homeassistant.components.hue_ble.config_flow.HueBleLight.authenticated",
new_callable=PropertyMock,
return_value=mock_authenticated,
),
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
@@ -262,17 +221,7 @@ async def test_bluetooth_form_exception(
),
patch(
"homeassistant.components.hue_ble.config_flow.HueBleLight.poll_state",
side_effect=[(True, [])],
),
patch(
"homeassistant.components.hue_ble.config_flow.HueBleLight.connected",
new_callable=PropertyMock,
return_value=True,
),
patch(
"homeassistant.components.hue_ble.config_flow.HueBleLight.authenticated",
new_callable=PropertyMock,
return_value=True,
side_effect=[True],
),
):
result = await hass.config_entries.flow.async_configure(

View File

@@ -3,6 +3,7 @@
from unittest.mock import patch
from bleak.backends.device import BLEDevice
from HueBLE import ConnectionError, HueBleError
import pytest
from homeassistant.components.hue_ble.const import DOMAIN
@@ -29,29 +30,29 @@ from tests.components.bluetooth import generate_ble_device
None,
2,
True,
True,
None,
"The light was not found.",
),
(
None,
0,
True,
True,
None,
"No Bluetooth scanners are available to search for the light.",
),
(
generate_ble_device(TEST_DEVICE_MAC, TEST_DEVICE_NAME),
2,
False,
True,
ConnectionError,
"Device found but unable to connect.",
),
(
generate_ble_device(TEST_DEVICE_MAC, TEST_DEVICE_NAME),
2,
True,
False,
"Device found but unable to connect.",
HueBleError,
"Device found and connected but unable to poll values from it.",
),
],
ids=["no_device", "no_scanners", "error_connect", "error_poll"],
@@ -61,8 +62,8 @@ async def test_setup_error(
caplog: pytest.LogCaptureFixture,
ble_device: BLEDevice | None,
scanner_count: int,
connect_result: bool,
poll_state_result: bool,
connect_result: Exception | None,
poll_state_result: Exception | None,
message: str,
) -> None:
"""Test that ConfigEntryNotReady is raised if there is an error condition."""
@@ -80,11 +81,11 @@ async def test_setup_error(
),
patch(
"homeassistant.components.hue_ble.HueBleLight.connect",
return_value=connect_result,
side_effect=[connect_result],
),
patch(
"homeassistant.components.hue_ble.HueBleLight.poll_state",
return_value=poll_state_result,
side_effect=[poll_state_result],
),
):
assert await async_setup_component(hass, DOMAIN, {}) is True
@@ -111,11 +112,11 @@ async def test_setup(
),
patch(
"homeassistant.components.hue_ble.HueBleLight.connect",
return_value=True,
return_value=None,
),
patch(
"homeassistant.components.hue_ble.HueBleLight.poll_state",
return_value=True,
return_value=None,
),
):
assert await async_setup_component(hass, DOMAIN, {}) is True