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:
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["bleak", "HueBLE"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["HueBLE==1.0.8"]
|
||||
"requirements": ["HueBLE==2.1.0"]
|
||||
}
|
||||
|
||||
@@ -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
2
requirements_all.txt
generated
@@ -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
|
||||
|
||||
2
requirements_test_all.txt
generated
2
requirements_test_all.txt
generated
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user