Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions homeassistant/components/hue_ble/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down
41 changes: 16 additions & 25 deletions homeassistant/components/hue_ble/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
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
Expand All @@ -20,7 +20,7 @@
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__)
Expand All @@ -41,32 +41,22 @@ async def validate_input(hass: HomeAssistant, address: str) -> Error | None:

try:
light = HueBleLight(ble_device)

await light.connect()

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:
get_available_color_modes(light)
await light.poll_state()

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:
Expand Down Expand Up @@ -129,6 +119,7 @@ async def async_step_confirm(
CONF_NAME: self._discovery_info.name,
CONF_MAC: self._discovery_info.address,
"url_pairing_mode": URL_PAIRING_MODE,
"url_factory_reset": URL_FACTORY_RESET,
},
)

Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/hue_ble/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion homeassistant/components/hue_ble/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def _state_change_callback(self) -> None:

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."""
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/hue_ble/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
"iot_class": "local_push",
"loggers": ["bleak", "HueBLE"],
"quality_scale": "bronze",
"requirements": ["HueBLE==1.0.8"]
"requirements": ["HueBLE==2.1.0"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/hue_ble/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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})."
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

109 changes: 29 additions & 80 deletions tests/components/hue_ble/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -18,22 +23,13 @@
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."""

Expand All @@ -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 (
Expand All @@ -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(
Expand All @@ -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",
Expand All @@ -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,
),
],
Expand All @@ -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."""
Expand Down Expand Up @@ -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"],
Expand All @@ -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(
Expand Down
Loading
Loading