From 178d5455064fec8a05f94b7fbe00ce1401a3c43d Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Wed, 20 Dec 2023 14:06:49 +0100 Subject: [PATCH 01/38] Add stacktrace for asyncio.CancelledError --- custom_components/huesyncbox/config_flow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/custom_components/huesyncbox/config_flow.py b/custom_components/huesyncbox/config_flow.py index 39f9523..afbc2ed 100644 --- a/custom_components/huesyncbox/config_flow.py +++ b/custom_components/huesyncbox/config_flow.py @@ -216,11 +216,11 @@ async def _async_register( _LOGGER.exception("Unknown Philips Hue Play HDMI Sync Box error occurred") return False except asyncio.CancelledError: - _LOGGER.debug("_async_register, %s", "asyncio.CancelledError") + _LOGGER.debug("_async_register, asyncio.CancelledError", exc_info=True) cancelled = True finally: # Only gets cancelled when flow is removed, don't call things on flow after that - _LOGGER.debug("_async_register, %s, %s", "finally", cancelled) + _LOGGER.debug("_async_register, finally, %s", cancelled) if not cancelled: # Continue the flow after show progress when the task is done. # To avoid a potential deadlock we create a new task that continues the flow. @@ -252,6 +252,7 @@ async def async_step_link(self, user_input=None) -> FlowResult: registered = self.link_task.result() except asyncio.CancelledError: # Was cancelled, so not registered + _LOGGER.debug("async_step_link, asyncio.CancelledError", exc_info=True) pass except asyncio.InvalidStateError: # Was not done, so not registered, cancel it From 2a1c1289f462066f31045e2e92291f609cdcda2d Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Thu, 25 Jan 2024 19:55:16 +0100 Subject: [PATCH 02/38] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a1d3d1b..f5c4479 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ Custom integration for the Philips Hue Play HDMI Sync Box. ## About +> Please setup the Philips Hue Play HDMI Syncbox with the Hue App first and make sure it works before setting up this integration. + This integration exposes the Philips Hue Play HDMI Sync Box in Home Assistant so it can be used in automations or dashboards. The Philips Hue Play HDMI Sync Box will be discovered automatically in most cases and can be added manually through the `Settings > Devices and Services` menu in Home Assistant if that is not the case. From c2feaded5d5880ecf644649d204ad7a771168d3f Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Thu, 1 Feb 2024 20:58:04 +0100 Subject: [PATCH 03/38] Use asyncio.timeout() instead of 3rd party async-timeout package --- custom_components/huesyncbox/__init__.py | 4 ++-- custom_components/huesyncbox/coordinator.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/huesyncbox/__init__.py b/custom_components/huesyncbox/__init__.py index c8e2b7c..9272e0e 100644 --- a/custom_components/huesyncbox/__init__.py +++ b/custom_components/huesyncbox/__init__.py @@ -1,6 +1,6 @@ """The Philips Hue Play HDMI Sync Box integration.""" +import asyncio import aiohuesyncbox -import async_timeout from homeassistant.components import automation from homeassistant.config_entries import ConfigEntry @@ -83,7 +83,7 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: # Best effort cleanup. User might not even have the device anymore or had it factory reset. # Note that the entry already has been unloaded, so need to create API again try: - async with async_timeout.timeout(10): + async with asyncio.timeout(10): async with aiohuesyncbox.HueSyncBox( entry.data["host"], entry.data["unique_id"], diff --git a/custom_components/huesyncbox/coordinator.py b/custom_components/huesyncbox/coordinator.py index f366391..13b72ed 100644 --- a/custom_components/huesyncbox/coordinator.py +++ b/custom_components/huesyncbox/coordinator.py @@ -1,7 +1,7 @@ """Coordinator for the Philips Hue Play HDMI Sync Box integration.""" +import asyncio import aiohuesyncbox -import async_timeout from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.update_coordinator import ( @@ -33,7 +33,7 @@ async def _async_update_data(self): try: # Note: asyncio.TimeoutError and aiohttp.ClientError are already # handled by the data update coordinator. - async with async_timeout.timeout(5): + async with asyncio.timeout(5): old_device = self.api.device await self.api.update() From 9abb64cec050dde69ef19ebc5d63d3495af57a00 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Thu, 1 Feb 2024 21:28:12 +0100 Subject: [PATCH 04/38] Do not build all branches --- .github/workflows/push.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 4fdb62c..ef44078 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -2,6 +2,9 @@ name: Checks on: push: + branches: + - dev + - master pull_request: schedule: - cron: "0 0 * * *" From ce61c19a3a1431485ca725c3a7f9ed851046bb4f Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Fri, 2 Feb 2024 20:30:27 +0100 Subject: [PATCH 05/38] Split dev deps from test and only use test when testing --- .github/workflows/push.yaml | 1 - requirements_dev.txt | 9 +-------- requirements_test.txt | 9 +++++++++ 3 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 requirements_test.txt diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index ef44078..373b2e5 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -26,7 +26,6 @@ jobs: python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - if [ -f requirements_dev.txt ]; then pip install -r requirements_dev.txt; fi if [ -f requirements_test.txt ]; then pip install -r requirements_test.txt; fi - name: Test with pytest run: | diff --git a/requirements_dev.txt b/requirements_dev.txt index 1758e32..2dfdcb8 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,10 +1,3 @@ --r requirements.txt -mypy==1.4.0 +-r requirements_test.txt homeassistant-stubs==2023.8.0 -pytest-homeassistant-custom-component==0.13.49 - -# Not entirely clear why it is needed as not a requirement for huesyncbox directly -# but the tests fail because HA seems to initialize the zeroconf component which fails due to missing lib. -# Not sure why it started showing up now :/ -zeroconf diff --git a/requirements_test.txt b/requirements_test.txt new file mode 100644 index 0000000..51358d3 --- /dev/null +++ b/requirements_test.txt @@ -0,0 +1,9 @@ +-r requirements.txt +mypy==1.4.0 + +pytest-homeassistant-custom-component==0.13.92 + +# Not entirely clear why it is needed as not a requirement for huesyncbox directly +# but the tests fail because HA seems to initialize the zeroconf component which fails due to missing lib. +# Not sure why it started showing up now :/ +zeroconf From fc100e125f42a959085788928a89c49706a90934 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Fri, 2 Feb 2024 20:37:15 +0100 Subject: [PATCH 06/38] Keep HA at 2023.8.0 as lowest supported version --- requirements_test.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 51358d3..58312b3 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,8 @@ -r requirements.txt + mypy==1.4.0 -pytest-homeassistant-custom-component==0.13.92 +pytest-homeassistant-custom-component==0.13.49 # Not entirely clear why it is needed as not a requirement for huesyncbox directly # but the tests fail because HA seems to initialize the zeroconf component which fails due to missing lib. From ca1f2d0a6ebe73199e1d6979b8906ee22b3644bd Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Fri, 2 Feb 2024 20:42:46 +0100 Subject: [PATCH 07/38] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5c4479..ecb1e2b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Custom integration for the Philips Hue Play HDMI Sync Box. This integration exposes the Philips Hue Play HDMI Sync Box in Home Assistant so it can be used in automations or dashboards. -The Philips Hue Play HDMI Sync Box will be discovered automatically in most cases and can be added manually through the `Settings > Devices and Services` menu in Home Assistant if that is not the case. +The Philips Hue Play HDMI Sync Box will be discovered automatically in most cases and otherwise can be added manually through the `Settings > Devices and Services` menu in Home Assistant. The following features are available: From 70cb972dd0488cb9703521fafeec8cec21861249 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Thu, 18 Apr 2024 20:50:48 +0200 Subject: [PATCH 08/38] Bump aiohuesyncbox to 0.0.27 Has better debug logging --- custom_components/huesyncbox/manifest.json | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/huesyncbox/manifest.json b/custom_components/huesyncbox/manifest.json index 7cb3374..b0ddfcb 100644 --- a/custom_components/huesyncbox/manifest.json +++ b/custom_components/huesyncbox/manifest.json @@ -12,7 +12,7 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/mvdwetering/huesyncbox/issues", "loggers": ["aiohuesyncbox"], - "requirements": ["aiohuesyncbox==0.0.26"], + "requirements": ["aiohuesyncbox==0.0.27"], "version": "0.0.0", "zeroconf": ["_huesync._tcp.local."] } diff --git a/requirements.txt b/requirements.txt index 3b8a3e7..239e796 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ # Must be same as in the manifest -aiohuesyncbox==0.0.26 +aiohuesyncbox==0.0.27 From e64fd14c966f83f1ba73d0c2f4fa3fa9a5b7e937 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sun, 21 Apr 2024 12:03:40 +0200 Subject: [PATCH 09/38] Bump min HA version to 2024.4.0 --- hacs.json | 2 +- requirements_dev.txt | 2 +- requirements_test.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hacs.json b/hacs.json index 579f1e2..f8b2f7f 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,5 @@ { "name": "Philips Hue Play HDMI Sync Box", "render_readme": true, - "homeassistant": "2023.8.0" + "homeassistant": "2024.4.0" } diff --git a/requirements_dev.txt b/requirements_dev.txt index 2dfdcb8..aee72c0 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,3 +1,3 @@ -r requirements_test.txt -homeassistant-stubs==2023.8.0 +homeassistant-stubs==2024.4.0 diff --git a/requirements_test.txt b/requirements_test.txt index 58312b3..ab87b5e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -2,7 +2,7 @@ mypy==1.4.0 -pytest-homeassistant-custom-component==0.13.49 +pytest-homeassistant-custom-component==0.13.111 # Not entirely clear why it is needed as not a requirement for huesyncbox directly # but the tests fail because HA seems to initialize the zeroconf component which fails due to missing lib. From 133cac2109971f71bec60b462a5c17ed7ba61cad Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sun, 21 Apr 2024 13:47:26 +0200 Subject: [PATCH 10/38] Update tests --- tests/test_config_flow.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index e95cad8..13bee88 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -1,7 +1,8 @@ """Test the Philips Hue Play HDMI Sync Box config flow.""" import asyncio from unittest import mock -from unittest.mock import MagicMock, Mock, patch +from unittest.mock import patch +from ipaddress import IPv4Address from homeassistant import config_entries from homeassistant.components import zeroconf @@ -9,7 +10,6 @@ from homeassistant.data_entry_flow import FlowResultType, UnknownFlow import pytest -from custom_components.huesyncbox.config_flow import CannotConnect, InvalidAuth from custom_components import huesyncbox import aiohuesyncbox @@ -227,8 +227,8 @@ async def test_zeroconf_new_box(hass: HomeAssistant, mock_api) -> None: huesyncbox.DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( - host="1.2.3.4", - addresses=["1.2.3.4"], + ip_address=IPv4Address("1.2.3.4"), + ip_addresses=[IPv4Address("1.2.3.4")], port=443, hostname="unique_id.local", type="_huesync._tcp.local.", @@ -307,8 +307,8 @@ async def test_zeroconf_already_configured(hass: HomeAssistant, mock_api) -> Non huesyncbox.DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( - host="1.2.3.4", - addresses=["1.2.3.4"], + ip_address=IPv4Address("1.2.3.4"), + ip_addresses=[IPv4Address("1.2.3.4")], port=443, hostname="unique_id.local", type="_huesync._tcp.local.", From a26903c82b8a08fab32d4a28e4b53b87693db319 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sun, 21 Apr 2024 13:58:09 +0200 Subject: [PATCH 11/38] Update typing info --- custom_components/huesyncbox/config_flow.py | 25 ++++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/custom_components/huesyncbox/config_flow.py b/custom_components/huesyncbox/config_flow.py index afbc2ed..8b13f1c 100644 --- a/custom_components/huesyncbox/config_flow.py +++ b/custom_components/huesyncbox/config_flow.py @@ -7,7 +7,11 @@ import aiohuesyncbox import voluptuous as vol # type: ignore -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult +) from homeassistant.components import zeroconf from homeassistant.const import ( CONF_ACCESS_TOKEN, @@ -18,7 +22,6 @@ CONF_UNIQUE_ID, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .const import DEFAULT_PORT, DOMAIN, REGISTRATION_ID @@ -71,13 +74,13 @@ async def try_connection(connection_info: ConnectionInfo): raise CannotConnect -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class HueSyncBoxConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Philips Hue Play HDMI Sync Box.""" VERSION = 2 link_task: asyncio.Task | None = None - reauth_entry: config_entries.ConfigEntry | None = None + reauth_entry: ConfigEntry | None = None connection_info: ConnectionInfo device_name = "Default syncbox name" @@ -90,7 +93,7 @@ def async_remove(self) -> None: async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" _LOGGER.debug("async_step_user, %s", user_input) if user_input is None: @@ -139,7 +142,7 @@ async def async_step_user( async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" _LOGGER.debug("async_step_zeroconf, %s", discovery_info) @@ -168,7 +171,7 @@ async def async_step_zeroconf( # and it seems to get stuck. Go through intermediate dialog like with reauth. return await self.async_step_zeroconf_confirm() - async def async_step_zeroconf_confirm(self, user_input=None) -> FlowResult: + async def async_step_zeroconf_confirm(self, user_input=None) -> ConfigFlowResult: """Dialog that informs the user that device is found and needs to be linked.""" _LOGGER.debug("async_step_zeroconf_confirm, %s", user_input) if user_input is None: @@ -230,7 +233,7 @@ async def _async_register( self.hass.config_entries.flow.async_configure(flow_id=self.flow_id) ) - async def async_step_link(self, user_input=None) -> FlowResult: + async def async_step_link(self, user_input=None) -> ConfigFlowResult: """Handle the linking step.""" _LOGGER.debug("async_step_link, %s", user_input) assert self.connection_info @@ -261,7 +264,7 @@ async def async_step_link(self, user_input=None) -> FlowResult: next_step_id = "finish" if registered else "abort" return self.async_show_progress_done(next_step_id=next_step_id) - async def async_step_finish(self, user_input=None) -> FlowResult: + async def async_step_finish(self, user_input=None) -> ConfigFlowResult: """Finish flow""" _LOGGER.debug("async_step_finish, %s", user_input) assert self.connection_info @@ -277,7 +280,7 @@ async def async_step_finish(self, user_input=None) -> FlowResult: title=self.device_name, data=asdict(self.connection_info) ) - async def async_step_abort(self, user_input=None) -> FlowResult: + async def async_step_abort(self, user_input=None) -> ConfigFlowResult: """Abort flow""" _LOGGER.debug("async_step_abort, %s", user_input) return self.async_abort(reason="connection_failed") @@ -302,7 +305,7 @@ async def async_step_reauth(self, user_input=None): return await self.async_step_reauth_confirm() - async def async_step_reauth_confirm(self, user_input=None) -> FlowResult: + async def async_step_reauth_confirm(self, user_input=None) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" _LOGGER.debug("async_step_reauth_confirm, %s", user_input) if user_input is None: From 955da3e87827050fb05a01c7a8a087a2f434d6d9 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sun, 21 Apr 2024 13:59:55 +0200 Subject: [PATCH 12/38] Change to Python 3.12 in Github action --- .github/workflows/push.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 373b2e5..b021a1a 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -14,7 +14,7 @@ jobs: runs-on: "ubuntu-latest" strategy: matrix: - python-version: ["3.11"] + python-version: ["3.12"] steps: - uses: "actions/checkout@v3" - name: Set up Python ${{ matrix.python-version }} From 027450f85ca9f5364fd960cdd21276877f7d6b62 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sun, 21 Apr 2024 12:02:52 +0200 Subject: [PATCH 13/38] Move service setup to async_setup --- custom_components/huesyncbox/__init__.py | 16 ++++++------ custom_components/huesyncbox/services.py | 32 ++++++++++-------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/custom_components/huesyncbox/__init__.py b/custom_components/huesyncbox/__init__.py index 9272e0e..7fc7b05 100644 --- a/custom_components/huesyncbox/__init__.py +++ b/custom_components/huesyncbox/__init__.py @@ -11,8 +11,9 @@ entity_registry, ) from homeassistant.helpers import issue_registry +from homeassistant.helpers.typing import ConfigType -from .services import async_register_services, async_unregister_services +from .services import async_register_services from .const import ( DOMAIN, @@ -29,6 +30,12 @@ ] +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the Philips Hue Play HDMI Sync Box integration.""" + hass.data[DOMAIN] = {} + await async_register_services(hass) + return True + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Philips Hue Play HDMI Sync Box from a config entry.""" @@ -57,12 +64,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator = HueSyncBoxCoordinator(hass, api) - hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - await async_register_services(hass) - return True @@ -72,10 +76,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator = hass.data[DOMAIN].pop(entry.entry_id) await coordinator.api.close() - if len(hass.data[DOMAIN]) == 0: - hass.data.pop(DOMAIN) - await async_unregister_services(hass) - return unload_ok diff --git a/custom_components/huesyncbox/services.py b/custom_components/huesyncbox/services.py index 710e934..a8d3894 100644 --- a/custom_components/huesyncbox/services.py +++ b/custom_components/huesyncbox/services.py @@ -59,6 +59,7 @@ async def async_register_services(hass: HomeAssistant): + async def async_set_bridge(call): """ Set bridge, note that this change is not instant. @@ -79,13 +80,12 @@ async def async_set_bridge(call): await coordinator.api.hue.set_bridge(bridge_id, username, clientkey) - if not hass.services.has_service(DOMAIN, SERVICE_SET_BRIDGE): - hass.services.async_register( - DOMAIN, - SERVICE_SET_BRIDGE, - async_set_bridge, - schema=HUESYNCBOX_SET_BRIDGE_SCHEMA, - ) + hass.services.async_register( + DOMAIN, + SERVICE_SET_BRIDGE, + async_set_bridge, + schema=HUESYNCBOX_SET_BRIDGE_SCHEMA, + ) async def async_set_sync_state(call): """Set sync state, allow combining of all options.""" @@ -134,15 +134,9 @@ async def set_state(api: aiohuesyncbox.HueSyncBox, **kwargs): else: raise - if not hass.services.has_service(DOMAIN, SERVICE_SET_SYNC_STATE): - hass.services.async_register( - DOMAIN, - SERVICE_SET_SYNC_STATE, - async_set_sync_state, - schema=HUESYNCBOX_SET_SYNC_STATE_SCHEMA, - ) - - -async def async_unregister_services(hass: HomeAssistant): - hass.services.async_remove(DOMAIN, SERVICE_SET_BRIDGE) - hass.services.async_remove(DOMAIN, SERVICE_SET_SYNC_STATE) + hass.services.async_register( + DOMAIN, + SERVICE_SET_SYNC_STATE, + async_set_sync_state, + schema=HUESYNCBOX_SET_SYNC_STATE_SCHEMA, + ) From 3814edbbc37834d8f880efb8406ac08bcc500855 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sun, 21 Apr 2024 14:07:35 +0200 Subject: [PATCH 14/38] Update tests --- tests/test_init.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index 9758841..031e770 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -60,7 +60,6 @@ async def test_handle_communication_error_during_setup(hass: HomeAssistant, mock async def test_unload_entry(hass: HomeAssistant, mock_api): integration = await setup_integration(hass, mock_api) - integration2 = await setup_integration(hass, mock_api, entry_id="entry_id_2") # Unload first entry await hass.config_entries.async_unload(integration.entry.entry_id) @@ -77,21 +76,6 @@ async def test_unload_entry(hass: HomeAssistant, mock_api): assert hass.services.has_service(huesyncbox.DOMAIN, "set_bridge") assert hass.services.has_service(huesyncbox.DOMAIN, "set_sync_state") - # Unload second entry - await hass.config_entries.async_unload(integration2.entry.entry_id) - await hass.async_block_till_done() - - config_entry = hass.config_entries.async_get_entry(integration2.entry.entry_id) - assert config_entry is not None - assert config_entry.state == ConfigEntryState.NOT_LOADED - - assert mock_api.close.call_count == 2 - - # Check that data and services are cleaned up - assert huesyncbox.DOMAIN not in hass.data - assert not hass.services.has_service(huesyncbox.DOMAIN, "set_bridge") - assert not hass.services.has_service(huesyncbox.DOMAIN, "set_sync_state") - @pytest.mark.parametrize( "side_effect", From 25d2c9a427ee837822b77a26c983d4d14b8d3919 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sun, 21 Apr 2024 14:20:00 +0200 Subject: [PATCH 15/38] Update entity descriptions --- custom_components/huesyncbox/number.py | 2 +- custom_components/huesyncbox/select.py | 2 +- custom_components/huesyncbox/sensor.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/huesyncbox/number.py b/custom_components/huesyncbox/number.py index e3bc20b..6c76e82 100644 --- a/custom_components/huesyncbox/number.py +++ b/custom_components/huesyncbox/number.py @@ -15,7 +15,7 @@ from .helpers import BrightnessRangeConverter, stop_sync_and_retry_on_invalid_state -@dataclass +@dataclass(frozen=True, kw_only=True) class HueSyncBoxNumberEntityDescription(NumberEntityDescription): get_value: Callable[[aiohuesyncbox.HueSyncBox], float] = None # type: ignore[assignment] set_value_fn: Callable[[aiohuesyncbox.HueSyncBox, float], Coroutine] = None # type: ignore[assignment] diff --git a/custom_components/huesyncbox/select.py b/custom_components/huesyncbox/select.py index c3d1e19..69bb695 100644 --- a/custom_components/huesyncbox/select.py +++ b/custom_components/huesyncbox/select.py @@ -21,7 +21,7 @@ INPUTS = ["input1", "input2", "input3", "input4"] -@dataclass +@dataclass(frozen=True, kw_only=True) class HueSyncBoxSelectEntityDescription(SelectEntityDescription): options_fn: Callable[[aiohuesyncbox.HueSyncBox], list[str]] | None = None current_option_fn: Callable[[aiohuesyncbox.HueSyncBox], str] = None # type: ignore[assignment] diff --git a/custom_components/huesyncbox/sensor.py b/custom_components/huesyncbox/sensor.py index 5acf2c7..7d8fd64 100644 --- a/custom_components/huesyncbox/sensor.py +++ b/custom_components/huesyncbox/sensor.py @@ -15,7 +15,7 @@ from .const import DOMAIN -@dataclass +@dataclass(frozen=True, kw_only=True) class HueSyncBoxSensorEntityDescription(SensorEntityDescription): get_value: Callable[[aiohuesyncbox.HueSyncBox], str] = None # type: ignore[assignment] icons: dict[str, str] | None = None From ca94645303c2813b07a8236131262f14ff1d4b0c Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sun, 21 Apr 2024 15:51:59 +0200 Subject: [PATCH 16/38] Use new progress task and remove old cancel handling --- custom_components/huesyncbox/config_flow.py | 35 +++++---------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/custom_components/huesyncbox/config_flow.py b/custom_components/huesyncbox/config_flow.py index 8b13f1c..e23505c 100644 --- a/custom_components/huesyncbox/config_flow.py +++ b/custom_components/huesyncbox/config_flow.py @@ -85,12 +85,6 @@ class HueSyncBoxConfigFlow(ConfigFlow, domain=DOMAIN): connection_info: ConnectionInfo device_name = "Default syncbox name" - @callback - def async_remove(self) -> None: - _LOGGER.debug("async_remove, %s", self.link_task is not None) - if self.link_task: - self.link_task.cancel() - async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: @@ -182,7 +176,6 @@ async def _async_register( self, ha_instance_name: str, connection_info: ConnectionInfo ): _LOGGER.debug("_async_register, %s", connection_info) - cancelled = False try: async with aiohuesyncbox.HueSyncBox( @@ -218,20 +211,7 @@ async def _async_register( except aiohuesyncbox.AiohuesyncboxException: _LOGGER.exception("Unknown Philips Hue Play HDMI Sync Box error occurred") return False - except asyncio.CancelledError: - _LOGGER.debug("_async_register, asyncio.CancelledError", exc_info=True) - cancelled = True - finally: - # Only gets cancelled when flow is removed, don't call things on flow after that - _LOGGER.debug("_async_register, finally, %s", cancelled) - if not cancelled: - # Continue the flow after show progress when the task is done. - # To avoid a potential deadlock we create a new task that continues the flow. - # The task must be completely done so the flow can await the task - # if needed and get the task result. - self.hass.async_create_task( - self.hass.config_entries.flow.async_configure(flow_id=self.flow_id) - ) + async def async_step_link(self, user_input=None) -> ConfigFlowResult: """Handle the linking step.""" @@ -239,30 +219,31 @@ async def async_step_link(self, user_input=None) -> ConfigFlowResult: assert self.connection_info if not self.link_task: + _LOGGER.debug("async_step_link, async_create_task") self.link_task = self.hass.async_create_task( self._async_register( self.hass.config.location_name, self.connection_info ) ) + if not self.link_task.done(): + _LOGGER.debug("async_step_link, async_show_progress") return self.async_show_progress( step_id="link", progress_action="wait_for_button", + progress_task=self.link_task, ) registered = False try: registered = self.link_task.result() except asyncio.CancelledError: - # Was cancelled, so not registered _LOGGER.debug("async_step_link, asyncio.CancelledError", exc_info=True) - pass except asyncio.InvalidStateError: - # Was not done, so not registered, cancel it - self.link_task.cancel() + _LOGGER.debug("async_step_link, asyncio.InvalidStateError", exc_info=True) - next_step_id = "finish" if registered else "abort" - return self.async_show_progress_done(next_step_id=next_step_id) + _LOGGER.debug("async_step_link, asyncio.async_show_progress_done registered=%s", registered) + return self.async_show_progress_done(next_step_id="finish" if registered else "abort") async def async_step_finish(self, user_input=None) -> ConfigFlowResult: """Finish flow""" From f8f23db22e95f2d105e37d8d5ea33cbb16fd7d5f Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sun, 21 Apr 2024 15:52:22 +0200 Subject: [PATCH 17/38] Forgot to stage the switch for entitydescription. Smuggle it in now... --- custom_components/huesyncbox/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/huesyncbox/switch.py b/custom_components/huesyncbox/switch.py index 522c8ce..c355000 100644 --- a/custom_components/huesyncbox/switch.py +++ b/custom_components/huesyncbox/switch.py @@ -12,7 +12,7 @@ from .helpers import stop_sync_and_retry_on_invalid_state -@dataclass +@dataclass(frozen=True, kw_only=True) class HueSyncBoxSwitchEntityDescription(SwitchEntityDescription): is_on: Callable[[aiohuesyncbox.HueSyncBox], bool] = None # type: ignore[assignment] turn_on: Callable[[aiohuesyncbox.HueSyncBox], Coroutine] = None # type: ignore[assignment] From cfea3b6dd3a86c3eb9073947159613ba9ad1ef51 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sun, 21 Apr 2024 16:01:12 +0200 Subject: [PATCH 18/38] Remove unneeded check on cancelled --- custom_components/huesyncbox/config_flow.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/custom_components/huesyncbox/config_flow.py b/custom_components/huesyncbox/config_flow.py index e23505c..ec100d0 100644 --- a/custom_components/huesyncbox/config_flow.py +++ b/custom_components/huesyncbox/config_flow.py @@ -237,8 +237,6 @@ async def async_step_link(self, user_input=None) -> ConfigFlowResult: registered = False try: registered = self.link_task.result() - except asyncio.CancelledError: - _LOGGER.debug("async_step_link, asyncio.CancelledError", exc_info=True) except asyncio.InvalidStateError: _LOGGER.debug("async_step_link, asyncio.InvalidStateError", exc_info=True) From 2d59baaa906c83d836cfa39740b36520b9da1cf1 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sun, 21 Apr 2024 16:06:08 +0200 Subject: [PATCH 19/38] Remove unused import --- custom_components/huesyncbox/config_flow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/huesyncbox/config_flow.py b/custom_components/huesyncbox/config_flow.py index ec100d0..8421f11 100644 --- a/custom_components/huesyncbox/config_flow.py +++ b/custom_components/huesyncbox/config_flow.py @@ -21,7 +21,6 @@ CONF_PORT, CONF_UNIQUE_ID, ) -from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from .const import DEFAULT_PORT, DOMAIN, REGISTRATION_ID From 39dbf19c5b7195edd24789a0f7af5d0d3e1a2e1d Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sun, 21 Apr 2024 17:18:22 +0200 Subject: [PATCH 20/38] Allow for consecutive errors --- custom_components/huesyncbox/coordinator.py | 19 +++++++++++++-- tests/test_coordinator.py | 27 ++++++++++++++++----- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/custom_components/huesyncbox/coordinator.py b/custom_components/huesyncbox/coordinator.py index 13b72ed..3d321fe 100644 --- a/custom_components/huesyncbox/coordinator.py +++ b/custom_components/huesyncbox/coordinator.py @@ -12,6 +12,7 @@ from .const import COORDINATOR_UPDATE_INTERVAL, LOGGER from .helpers import update_config_entry_title, update_device_registry +MAX_CONSECUTIVE_ERRORS = 5 class HueSyncBoxCoordinator(DataUpdateCoordinator): """My custom coordinator.""" @@ -27,6 +28,12 @@ def __init__(self, hass, api: aiohuesyncbox.HueSyncBox): update_interval=COORDINATOR_UPDATE_INTERVAL, ) self.api = api + self._consecutive_errors = 0 + + def _is_consecutive_error_reached(self): + self._consecutive_errors += 1 + LOGGER.debug("Consecutive errors = %s", self._consecutive_errors) + return self._consecutive_errors >= MAX_CONSECUTIVE_ERRORS async def _async_update_data(self): """Fetch data from API endpoint.""" @@ -37,6 +44,7 @@ async def _async_update_data(self): old_device = self.api.device await self.api.update() + self._consecutive_errors = 0 if old_device != self.api.device: await update_device_registry(self.hass, self.config_entry, self.api) @@ -44,10 +52,17 @@ async def _async_update_data(self): self.hass, self.config_entry, self.api.device.name ) - return self.api except aiohuesyncbox.Unauthorized as err: # Raising ConfigEntryAuthFailed will cancel future updates # and start a config flow with SOURCE_REAUTH (async_step_reauth) raise ConfigEntryAuthFailed from err except aiohuesyncbox.RequestError as err: - raise UpdateFailed(f"Error communicating with API: {err}") + LOGGER.debug("aiohuesyncbox.RequestError while updating data: %s", err) + if self._is_consecutive_error_reached(): + raise UpdateFailed(err) + except asyncio.TimeoutError as err: + LOGGER.debug("asyncio.TimeoutError while updating data") + if self._is_consecutive_error_reached(): + raise + + return self.api diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 23ddd2f..466ee38 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -1,6 +1,8 @@ +import asyncio from unittest.mock import Mock import aiohuesyncbox +import pytest from custom_components import huesyncbox from homeassistant.config_entries import SOURCE_REAUTH @@ -59,8 +61,15 @@ async def test_authentication_error_starts_reauth_flow(hass: HomeAssistant, mock assert len(list(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))) == 1 -async def test_communication_error_marks_entities_unavailable( - hass: HomeAssistant, mock_api +@pytest.mark.parametrize( + "side_effect", + [ + (aiohuesyncbox.RequestError), + (asyncio.TimeoutError), + ], +) +async def test_continued_communication_errors_mark_entities_unavailable( + hass: HomeAssistant, mock_api, side_effect ): entity_under_test = "switch.name_power" await setup_integration(hass, mock_api) @@ -70,10 +79,16 @@ async def test_communication_error_marks_entities_unavailable( assert entity is not None assert entity.state == "on" - # Trigger communicaiton error - mock_api.update.side_effect = aiohuesyncbox.RequestError - await force_coordinator_update(hass) + # Setup communication error + mock_api.update.side_effect = side_effect - # Check entities are unavailable + # Trigger 4 updates, entities should be fine + for _ in range(4): + await force_coordinator_update(hass) + entity = hass.states.get(entity_under_test) + assert entity.state == "on" + + # 5th error makes entity unavailable + await force_coordinator_update(hass) entity = hass.states.get(entity_under_test) assert entity.state == "unavailable" From 2bca92f1f9e53499f0b273072ea2d735393011d1 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sun, 21 Apr 2024 17:27:48 +0200 Subject: [PATCH 21/38] Removed unused variable --- custom_components/huesyncbox/coordinator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/huesyncbox/coordinator.py b/custom_components/huesyncbox/coordinator.py index 3d321fe..e4c6bc4 100644 --- a/custom_components/huesyncbox/coordinator.py +++ b/custom_components/huesyncbox/coordinator.py @@ -59,8 +59,8 @@ async def _async_update_data(self): except aiohuesyncbox.RequestError as err: LOGGER.debug("aiohuesyncbox.RequestError while updating data: %s", err) if self._is_consecutive_error_reached(): - raise UpdateFailed(err) - except asyncio.TimeoutError as err: + raise UpdateFailed(err) from err + except asyncio.TimeoutError: LOGGER.debug("asyncio.TimeoutError while updating data") if self._is_consecutive_error_reached(): raise From 3c114e7df7b395506f268f50ae0caaa6f902014b Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Mon, 22 Apr 2024 14:00:33 +0200 Subject: [PATCH 22/38] Cleanup reauth flow a bit in preparation for reconfigure --- custom_components/huesyncbox/config_flow.py | 39 +++++++++++---------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/custom_components/huesyncbox/config_flow.py b/custom_components/huesyncbox/config_flow.py index 8421f11..09c4cb6 100644 --- a/custom_components/huesyncbox/config_flow.py +++ b/custom_components/huesyncbox/config_flow.py @@ -53,6 +53,15 @@ def entry_data_from_connection_info(connection_info: ConnectionInfo): CONF_PATH: connection_info.path, } +def connection_info_from_entry(entry: ConfigEntry) -> ConnectionInfo: + return ConnectionInfo( + entry.data[CONF_HOST], + entry.data[CONF_UNIQUE_ID], + entry.data[CONF_ACCESS_TOKEN], + entry.data[REGISTRATION_ID], + entry.data[CONF_PORT], + entry.data[CONF_PATH], + ) async def try_connection(connection_info: ConnectionInfo): """Validate the connection_info allows us to connect.""" @@ -79,7 +88,10 @@ class HueSyncBoxConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 2 link_task: asyncio.Task | None = None - reauth_entry: ConfigEntry | None = None + config_entry: ConfigEntry | None = None + + reauth: bool = False + reconfigure: bool = False connection_info: ConnectionInfo device_name = "Default syncbox name" @@ -247,12 +259,9 @@ async def async_step_finish(self, user_input=None) -> ConfigFlowResult: _LOGGER.debug("async_step_finish, %s", user_input) assert self.connection_info - if self.reauth_entry: - self.hass.config_entries.async_update_entry( - self.reauth_entry, data=asdict(self.connection_info) - ) - await self.hass.config_entries.async_reload(self.reauth_entry.entry_id) - return self.async_abort(reason="reauth_successful") + if self.reauth: + assert self.config_entry is not None + return self.async_update_reload_and_abort(self.config_entry, data=asdict(self.connection_info)) return self.async_create_entry( title=self.device_name, data=asdict(self.connection_info) @@ -266,20 +275,14 @@ async def async_step_abort(self, user_input=None) -> ConfigFlowResult: async def async_step_reauth(self, user_input=None): """Reauth is triggered when token is not valid anymore, retrigger link flow.""" _LOGGER.debug("async_step_reauth, %s", user_input) - self.reauth_entry = self.hass.config_entries.async_get_entry( + + self.reauth = True + self.config_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] ) - assert self.reauth_entry is not None - - self.connection_info = ConnectionInfo( - self.reauth_entry.data[CONF_HOST], - self.reauth_entry.data[CONF_UNIQUE_ID], - self.reauth_entry.data[CONF_ACCESS_TOKEN], - self.reauth_entry.data[REGISTRATION_ID], - self.reauth_entry.data[CONF_PORT], - self.reauth_entry.data[CONF_PATH], - ) + assert self.config_entry is not None + self.connection_info = connection_info_from_entry(self.config_entry) return await self.async_step_reauth_confirm() From 3ce08c1b56df224a9b677f7f09d66ea7479e4083 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Mon, 22 Apr 2024 15:38:17 +0200 Subject: [PATCH 23/38] Add reconfigure step --- custom_components/huesyncbox/config_flow.py | 140 +++++++++++++----- custom_components/huesyncbox/strings.json | 3 +- .../huesyncbox/translations/en.json | 3 +- tests/test_config_flow.py | 50 ++++--- 4 files changed, 137 insertions(+), 59 deletions(-) diff --git a/custom_components/huesyncbox/config_flow.py b/custom_components/huesyncbox/config_flow.py index 09c4cb6..162dd0d 100644 --- a/custom_components/huesyncbox/config_flow.py +++ b/custom_components/huesyncbox/config_flow.py @@ -1,17 +1,15 @@ """Config flow for Philips Hue Play HDMI Sync Box integration.""" + import asyncio from dataclasses import asdict, dataclass +from enum import Enum, auto, unique import logging from typing import Any import aiohuesyncbox import voluptuous as vol # type: ignore -from homeassistant.config_entries import ( - ConfigEntry, - ConfigFlow, - ConfigFlowResult -) +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.components import zeroconf from homeassistant.const import ( CONF_ACCESS_TOKEN, @@ -34,6 +32,18 @@ } ) +RECONFIGURE_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): str, + } +) + + +class ConfigureReason(Enum): + USER = auto() + REAUTH = auto() + RECONFIGURE = auto() + @dataclass class ConnectionInfo: @@ -53,6 +63,7 @@ def entry_data_from_connection_info(connection_info: ConnectionInfo): CONF_PATH: connection_info.path, } + def connection_info_from_entry(entry: ConfigEntry) -> ConnectionInfo: return ConnectionInfo( entry.data[CONF_HOST], @@ -63,8 +74,9 @@ def connection_info_from_entry(entry: ConfigEntry) -> ConnectionInfo: entry.data[CONF_PATH], ) -async def try_connection(connection_info: ConnectionInfo): - """Validate the connection_info allows us to connect.""" + +async def try_connection(connection_info: ConnectionInfo) -> bool: + """Check if the connection_info allows us to connect.""" async with aiohuesyncbox.HueSyncBox( connection_info.host, @@ -75,9 +87,10 @@ async def try_connection(connection_info: ConnectionInfo): ) as huesyncbox: try: # Just see if the connection works - await huesyncbox.is_registered() - except aiohuesyncbox.Unauthorized: - raise InvalidAuth + return await huesyncbox.is_registered() + # Note that Unauthorized exception can not occur with the call to `is_registered` + # Leave it here to make clear why it is not handled + # except aiohuesyncbox.Unauthorized: except aiohuesyncbox.RequestError: raise CannotConnect @@ -90,8 +103,7 @@ class HueSyncBoxConfigFlow(ConfigFlow, domain=DOMAIN): link_task: asyncio.Task | None = None config_entry: ConfigEntry | None = None - reauth: bool = False - reconfigure: bool = False + configure_reason = ConfigureReason.USER connection_info: ConnectionInfo device_name = "Default syncbox name" @@ -101,45 +113,85 @@ async def async_step_user( ) -> ConfigFlowResult: """Handle the initial step.""" _LOGGER.debug("async_step_user, %s", user_input) + + self.configure_reason = ConfigureReason.USER + return await self.async_step_configure(user_input=user_input) + + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle the reconfigure step.""" + _LOGGER.debug("async_step_reconfigure, %s", user_input) + + self.configure_reason = ConfigureReason.RECONFIGURE + self.config_entry = self.hass.config_entries.async_get_entry( + self.context["entry_id"] + ) + + assert self.config_entry is not None + self.connection_info = connection_info_from_entry(self.config_entry) + + return await self.async_step_configure(user_input=user_input) + + + async def async_step_configure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: if user_input is None: + + data_schema = USER_DATA_SCHEMA + if self.configure_reason is ConfigureReason.RECONFIGURE: + assert self.connection_info is not None + data_schema = self.add_suggested_values_to_schema( + ( + RECONFIGURE_DATA_SCHEMA + ), + asdict(self.connection_info)) + return self.async_show_form( - step_id="user", - data_schema=USER_DATA_SCHEMA, + step_id="configure", + data_schema=data_schema, last_step=False, ) - connection_info = ConnectionInfo( - user_input[CONF_HOST], user_input[CONF_UNIQUE_ID] - ) + if self.configure_reason is ConfigureReason.USER: + connection_info = ConnectionInfo( + user_input[CONF_HOST], user_input[CONF_UNIQUE_ID] + ) + else: + connection_info = self.connection_info + connection_info.host = user_input[CONF_HOST] errors = {} try: - await try_connection(connection_info) + is_registered = await try_connection(connection_info) except CannotConnect: errors["base"] = "cannot_connect" - except InvalidAuth: - # Should not occur as we don't have accesstoken - errors["base"] = "invalid_auth" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - # Protect against setting up existing entries - # But do update the entry so it is possible to update host - # for entries that were setup manually - await self.async_set_unique_id(connection_info.unique_id) - self._abort_if_unique_id_configured( - updates=entry_data_from_connection_info(connection_info) - ) + if self.configure_reason is ConfigureReason.USER: + # Protect against setting up existing entries + await self.async_set_unique_id(connection_info.unique_id) + self._abort_if_unique_id_configured() self.connection_info = connection_info + + if is_registered: + return await self.async_step_finish() return await self.async_step_link() return self.async_show_form( - step_id="user", + step_id="configure", data_schema=self.add_suggested_values_to_schema( - USER_DATA_SCHEMA, asdict(connection_info) + ( + RECONFIGURE_DATA_SCHEMA + if self.configure_reason is ConfigureReason.RECONFIGURE + else USER_DATA_SCHEMA + ), + asdict(connection_info), ), errors=errors, last_step=False, @@ -223,10 +275,9 @@ async def _async_register( _LOGGER.exception("Unknown Philips Hue Play HDMI Sync Box error occurred") return False - async def async_step_link(self, user_input=None) -> ConfigFlowResult: """Handle the linking step.""" - _LOGGER.debug("async_step_link, %s", user_input) + _LOGGER.debug("async_step_link, %s", self.connection_info) assert self.connection_info if not self.link_task: @@ -249,19 +300,32 @@ async def async_step_link(self, user_input=None) -> ConfigFlowResult: try: registered = self.link_task.result() except asyncio.InvalidStateError: - _LOGGER.debug("async_step_link, asyncio.InvalidStateError", exc_info=True) + _LOGGER.exception("async_step_link, asyncio.InvalidStateError") - _LOGGER.debug("async_step_link, asyncio.async_show_progress_done registered=%s", registered) - return self.async_show_progress_done(next_step_id="finish" if registered else "abort") + _LOGGER.debug( + "async_step_link, asyncio.async_show_progress_done registered=%s", + registered, + ) + return self.async_show_progress_done( + next_step_id="finish" if registered else "abort" + ) async def async_step_finish(self, user_input=None) -> ConfigFlowResult: """Finish flow""" _LOGGER.debug("async_step_finish, %s", user_input) assert self.connection_info - if self.reauth: + if self.configure_reason is not ConfigureReason.USER: assert self.config_entry is not None - return self.async_update_reload_and_abort(self.config_entry, data=asdict(self.connection_info)) + return self.async_update_reload_and_abort( + self.config_entry, + data=asdict(self.connection_info), + reason=( + "reauth_successful" + if self.configure_reason is ConfigureReason.REAUTH + else "reconfigure_successful" + ), + ) return self.async_create_entry( title=self.device_name, data=asdict(self.connection_info) @@ -276,7 +340,7 @@ async def async_step_reauth(self, user_input=None): """Reauth is triggered when token is not valid anymore, retrigger link flow.""" _LOGGER.debug("async_step_reauth, %s", user_input) - self.reauth = True + self.configure_reason = ConfigureReason.REAUTH self.config_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] ) diff --git a/custom_components/huesyncbox/strings.json b/custom_components/huesyncbox/strings.json index 9ac7b02..51ced80 100644 --- a/custom_components/huesyncbox/strings.json +++ b/custom_components/huesyncbox/strings.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Device is already configured", "reauth_successful": "Successfully re-linked the Philips Hue Play HDMI Sync Box", - "connection_failed": "Connection failed" + "reconfigure_successful": "Successfully re-configured the Philips Hue Play HDMI Sync Box", + "connection_failed": "Setup failed" }, "error": { "cannot_connect": "Failed to connect", diff --git a/custom_components/huesyncbox/translations/en.json b/custom_components/huesyncbox/translations/en.json index 9ac7b02..51ced80 100644 --- a/custom_components/huesyncbox/translations/en.json +++ b/custom_components/huesyncbox/translations/en.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Device is already configured", "reauth_successful": "Successfully re-linked the Philips Hue Play HDMI Sync Box", - "connection_failed": "Connection failed" + "reconfigure_successful": "Successfully re-configured the Philips Hue Play HDMI Sync Box", + "connection_failed": "Setup failed" }, "error": { "cannot_connect": "Failed to connect", diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 13bee88..b6ad8ff 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Philips Hue Play HDMI Sync Box config flow.""" + import asyncio from unittest import mock from unittest.mock import patch @@ -22,17 +23,22 @@ async def test_user_new_box(hass: HomeAssistant, mock_api) -> None: huesyncbox.DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == FlowResultType.FORM - assert result["step_id"] == "user" + assert result["step_id"] == "configure" # Filling in correct data (at least it maches the schema) # it will start link phase which tries to connect to the API so setup up front - with patch("aiohuesyncbox.HueSyncBox") as huesyncbox_instance, patch( - "custom_components.huesyncbox.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("aiohuesyncbox.HueSyncBox") as huesyncbox_instance, + patch( + "custom_components.huesyncbox.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): # __aenter__ stuff needed because used as context manager huesyncbox_instance.return_value.__aenter__.return_value = mock_api + mock_api.is_registered.return_value = False + # First attempt button not pressed yet, second try return value mock_api.register.side_effect = [aiohuesyncbox.InvalidState, mock.DEFAULT] mock_api.register.return_value = { @@ -79,16 +85,20 @@ async def test_user_new_box(hass: HomeAssistant, mock_api) -> None: assert entries[0].unique_id == "test_unique_id" -async def test_user_update_box_host(hass: HomeAssistant, mock_api) -> None: +async def test_reconfigure_host(hass: HomeAssistant, mock_api) -> None: integration = await setup_integration(hass, mock_api) assert integration.entry.data["host"] != "1.2.3.4" result = await hass.config_entries.flow.async_init( - huesyncbox.DOMAIN, context={"source": config_entries.SOURCE_USER} + huesyncbox.DOMAIN, + context={ + "source": config_entries.SOURCE_RECONFIGURE, + "entry_id": integration.entry.entry_id, + }, ) assert result["type"] == FlowResultType.FORM - assert result["step_id"] == "user" + assert result["step_id"] == "configure" # Provide different host for existing entry, should update with patch("aiohuesyncbox.HueSyncBox.is_registered", return_value=True): @@ -96,7 +106,6 @@ async def test_user_update_box_host(hass: HomeAssistant, mock_api) -> None: result["flow_id"], { "host": "1.2.3.4", - "unique_id": "unique_id", }, ) await hass.async_block_till_done() @@ -108,7 +117,6 @@ async def test_user_update_box_host(hass: HomeAssistant, mock_api) -> None: @pytest.mark.parametrize( "side_effect, error_message", [ - (aiohuesyncbox.Unauthorized, "invalid_auth"), (aiohuesyncbox.RequestError, "cannot_connect"), (Exception, "unknown"), ], @@ -120,7 +128,7 @@ async def test_connection_errors_during_connection_check( huesyncbox.DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == FlowResultType.FORM - assert result["step_id"] == "user" + assert result["step_id"] == "configure" with patch( "aiohuesyncbox.HueSyncBox.is_registered", @@ -136,7 +144,7 @@ async def test_connection_errors_during_connection_check( ) assert result["type"] == FlowResultType.FORM - assert result["step_id"] == "user" + assert result["step_id"] == "configure" assert result["errors"] == {"base": error_message} @@ -146,7 +154,6 @@ async def test_connection_errors_during_connection_check( (aiohuesyncbox.Unauthorized), (aiohuesyncbox.RequestError), (aiohuesyncbox.AiohuesyncboxException), - # (asyncio.CancelledError) can't test because just throwing the exception won't properly cancel the task (asyncio.InvalidStateError), ], ) @@ -157,13 +164,14 @@ async def test_user_box_connection_errors_during_link( huesyncbox.DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == FlowResultType.FORM - assert result["step_id"] == "user" + assert result["step_id"] == "configure" # Filling in correct data (at least it maches the schema) # it will start link phase which tries to connect to the API so setup up front with patch("aiohuesyncbox.HueSyncBox") as huesyncbox_instance: # __aenter__ stuff needed because used as context manager huesyncbox_instance.return_value.__aenter__.return_value = mock_api + mock_api.is_registered.return_value = False mock_api.register.side_effect = side_effect result = await hass.config_entries.flow.async_configure( @@ -195,13 +203,14 @@ async def test_user_box_abort_flow_during_link(hass: HomeAssistant, mock_api) -> huesyncbox.DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == FlowResultType.FORM - assert result["step_id"] == "user" + assert result["step_id"] == "configure" # Filling in correct data (at least it maches the schema) # it will start link phase which tries to connect to the API so setup up front with patch("aiohuesyncbox.HueSyncBox") as huesyncbox_instance: # __aenter__ stuff needed because used as context manager huesyncbox_instance.return_value.__aenter__.return_value = mock_api + mock_api.is_registered.return_value = False mock_api.register.side_effect = aiohuesyncbox.InvalidState result = await hass.config_entries.flow.async_configure( @@ -254,10 +263,13 @@ async def test_zeroconf_new_box(hass: HomeAssistant, mock_api) -> None: # Confirm discovery will start link phase which tries to # connect to the API so setup up front - with patch("aiohuesyncbox.HueSyncBox") as huesyncbox_instance, patch( - "custom_components.huesyncbox.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("aiohuesyncbox.HueSyncBox") as huesyncbox_instance, + patch( + "custom_components.huesyncbox.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): # __aenter__ stuff needed because used as context manager huesyncbox_instance.return_value.__aenter__.return_value = mock_api mock_api.register.return_value = { From e0ebc486a6a434b678a6424266820f0d67951955 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Mon, 22 Apr 2024 15:56:52 +0200 Subject: [PATCH 24/38] Add service icons --- custom_components/huesyncbox/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 custom_components/huesyncbox/icons.json diff --git a/custom_components/huesyncbox/icons.json b/custom_components/huesyncbox/icons.json new file mode 100644 index 0000000..a31f053 --- /dev/null +++ b/custom_components/huesyncbox/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "set_bridge": "mdi:bridge", + "set_sync_state": "mdi:sync" + } +} \ No newline at end of file From e71b577b789c0b53851910ca8619f79999c1a09b Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Thu, 1 Feb 2024 20:08:50 +0100 Subject: [PATCH 25/38] Translation start (not complete) --- .../huesyncbox/translations/nl.json | 245 ++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 custom_components/huesyncbox/translations/nl.json diff --git a/custom_components/huesyncbox/translations/nl.json b/custom_components/huesyncbox/translations/nl.json new file mode 100644 index 0000000..1867029 --- /dev/null +++ b/custom_components/huesyncbox/translations/nl.json @@ -0,0 +1,245 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al ingesteld", + "reauth_successful": "Philips Hue Play HDMI Sync Box kopperling gelukt", + "connection_failed": "Vebinding mislukt" + }, + "error": { + "cannot_connect": "Kan niet verbinden", + "invalid_auth": "Invalid authentication", + "unknown": "Onverwachtte fout" + }, + "step": { + "user": { + "title": "Voor apparaat informatie in", + "description": "De informatie is beschikbaar in de Hue App.\n\nSelecteer de Sync tab en selecteer de Philips Hue Play HDMI Syncbox. Daarna druk boven aan het scherm op het … menu, selecteer Apparaat. Onder Netwerkinfo staat het IP addres en onder Apparaatinfo staat Identificatie.", + "data": { + "host": "IP addres (bijv. 192.168.1.123)", + "unique_id": "Identificatie (bijv. C42996000000)" + } + }, + "reauth_confirm": { + "title": "Integratie opnieuw koppelen", + "description": "De Philips Hue Play HDMI Sync Box moet opnieuw gekoppeld worden" + }, + "zeroconf_confirm": { + "title": "Apparaat gevonden", + "description": "Druk op Volgende om het koppelen van de Philips Hue Play HDMI Sync Box te starten." + } + }, + "progress": { + "wait_for_button": "Druk een paar seconden op de knop van de Philips Hue Play HDMI Sync Box totdat het lampje groen knippert." + } + }, + "entity": { + "number": { + "brightness": { + "name": "Helderheid" + } + }, + "select": { + "hdmi_input": { + "name": "HDMI Ingang" + }, + "entertainment_area": { + "name": "Entertainmentruimte" + }, + "intensity": { + "name": "Intensiteit", + "state": { + "subtle": "Subtiel", + "moderate": "Gematigd", + "high": "Hoog", + "intense": "Extreem" + } + }, + "led_indicator_mode": { + "name": "Led indicator", + "state": { + "normal": "Normal", + "off": "Uit", + "dimmed": "Gedimd" + } + }, + "sync_mode": { + "name": "Sync modus", + "state": { + "video": "Video", + "music": "Muziek", + "game": "Game" + } + } + }, + "sensor": { + "bridge_unique_id": { + "name": "Bridge ID" + }, + "ip_address": { + "name": "IP adres" + }, + "bridge_connection_state": { + "name": "Bridge verbinding", + "state": { + "uninitialized": "Niet geinitialiseert", + "disconnected": "Verbroken", + "connecting": "Verbinden", + "unauthorized": "Niet gekoppeld", + "connected": "Verbonden", + "invalidgroup": "Ongeldige entertainmentruimte", + "streaming": "Synchroniseren", + "busy": "Bezig" + } + }, + "hdmi1_status": { + "name": "HDMI1 status", + "state": { + "unplugged": "Niet verbonden", + "plugged": "Verbonden", + "linked": "Gereed", + "unknown": "Onbekend" + } + }, + "hdmi2_status": { + "name": "HDMI2 status", + "state": { + "unplugged": "Niet verbonden", + "plugged": "Verbonden", + "linked": "Gereed", + "unknown": "Onbekend" + } + }, + "hdmi3_status": { + "name": "HDMI3 status", + "state": { + "unplugged": "Niet verbonden", + "plugged": "Verbonden", + "linked": "Gereed", + "unknown": "Onbekend" + } + }, + "hdmi4_status": { + "name": "HDMI4 status", + "state": { + "unplugged": "Niet verbonden", + "plugged": "Verbonden", + "linked": "Gereed", + "unknown": "Onbekend" + } + }, + "wifi_strength": { + "name": "Wifi kwaliteit", + "state": { + "not_connected": "Niet verbonden", + "weak": "Zwak", + "fair": "Matig", + "good": "Goed", + "excellent": "Uitstekend" + } + } + }, + "switch": { + "power": { + "name": "Power" + }, + "light_sync": { + "name": "Synchroniseren" + }, + "dolby_vision_compatibility": { + "name": "Dolby Vision compatibiliteit" + } + } + }, + "selector": { + "modes": { + "options": { + "video": "Video", + "music": "Muziek", + "game": "Game" + } + }, + "intensities": { + "options": { + "subtle": "Subtle", + "moderate": "Moderate", + "high": "High", + "intense": "Intense" + } + }, + "inputs": { + "options": { + "input1": "HDMI 1", + "input2": "HDMI 2", + "input3": "HDMI 3", + "input4": "HDMI 4" + } + } + }, + "services": { + "set_bridge": { + "name": "Set bridge", + "description": "Set the bridge to be used by the Philips Hue Play HDMI Syncbox. Keep in mind that changing the bridge by the box takes a while (about 15 seconds it seems). After the bridge has changed you might need to select the `entertainment_area` if connectionstate is `invalidgroup` instead of `connected`.", + "fields": { + "bridge_id": { + "name": "Bridge ID", + "description": "ID of the bridge. A hexadecimal code of 16 characters." + }, + "bridge_username": { + "name": "Username", + "description": "Username (a.k.a. application key) valid for the bridge. A long code of random characters." + }, + "bridge_clientkey": { + "name": "Clientkey", + "description": "Clientkey that belongs with the username. A hexadecimal code of 32 characters." + } + } + }, + "set_sync_state": { + "name": "Set light sync state", + "description": "Control the complete light sync state of the Philips Hue Play HDMI Syncbox with one call.", + "fields": { + "power": { + "name": "Power", + "description": "Turn the box on or off." + }, + "sync": { + "name": "Light sync", + "description": "Set light sync state on or off. Setting this to on will also turn on the box." + }, + "brightness": { + "name": "Brightness", + "description": "Brightness value to set." + }, + "mode": { + "name": "Mode", + "description": "Mode to set. Setting the mode will also turn on the box and start light sync." + }, + "intensity": { + "name": "Intensity", + "description": "Intensity to set." + }, + "input": { + "name": "Input", + "description": "Input to select." + }, + "entertainment_area": { + "name": "Entertainment area", + "description": "Entertainment area to select. Name must match _exactly_" + } + } + } + }, + "issues": { + "automations_using_deleted_mediaplayer": { + "title": "Automations using deleted HueSyncBox mediaplayer", + "fix_flow": { + "step": { + "confirm": { + "title": "Update the automations", + "description": "The following automations are using a deleted HueSyncBox mediaplayer with id {media_player_entity}.\n\n{automations}\n\nPress SUBMIT to confirm the automations have been updated and dismiss this repair" + } + } + } + } + } +} \ No newline at end of file From 73aab48ac27b7ed536465d316b9a90625f1c9ea8 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Fri, 2 Feb 2024 20:36:20 +0100 Subject: [PATCH 26/38] Updated a few more --- custom_components/huesyncbox/translations/nl.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/custom_components/huesyncbox/translations/nl.json b/custom_components/huesyncbox/translations/nl.json index 1867029..01284d3 100644 --- a/custom_components/huesyncbox/translations/nl.json +++ b/custom_components/huesyncbox/translations/nl.json @@ -83,7 +83,7 @@ "state": { "uninitialized": "Niet geinitialiseert", "disconnected": "Verbroken", - "connecting": "Verbinden", + "connecting": "Verbinding maken", "unauthorized": "Niet gekoppeld", "connected": "Verbonden", "invalidgroup": "Ongeldige entertainmentruimte", @@ -140,7 +140,7 @@ }, "switch": { "power": { - "name": "Power" + "name": "Ingeshakeld" }, "light_sync": { "name": "Synchroniseren" @@ -160,10 +160,10 @@ }, "intensities": { "options": { - "subtle": "Subtle", - "moderate": "Moderate", - "high": "High", - "intense": "Intense" + "subtle": "Subtiel", + "moderate": "Gematigd", + "high": "Hoog", + "intense": "Extreem" } }, "inputs": { From 43f41b2221c442829f201c4da490874b1e439157 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Mon, 22 Apr 2024 16:16:27 +0200 Subject: [PATCH 27/38] Fix reconfigure step translation --- custom_components/huesyncbox/strings.json | 4 ++-- custom_components/huesyncbox/translations/en.json | 2 +- custom_components/huesyncbox/translations/nl.json | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/custom_components/huesyncbox/strings.json b/custom_components/huesyncbox/strings.json index 51ced80..44240d7 100644 --- a/custom_components/huesyncbox/strings.json +++ b/custom_components/huesyncbox/strings.json @@ -12,9 +12,9 @@ "unknown": "Unexpected error" }, "step": { - "user": { + "configure": { "title": "Enter device information", - "description": "Details can be found in the main Hue App.\n\nSelect the Sync tab and make sure the Philips Hue Play HDMI Syncbox is selected. After that tap the … menu at the top, select Device and then NetworkInfo for the IP address and DeviceInfo for the Identifier.", + "description": "Details can be found in the main Hue App.\n\nSelect the Sync tab and make sure the Philips Hue Play HDMI Syncbox is selected. After that tap the … menu at the top, then select Device and then NetworkInfo for the IP address and DeviceInfo for the Identifier.", "data": { "host": "IP address (e.g. 192.168.1.123)", "unique_id": "Identifier (e.g. C42996000000)" diff --git a/custom_components/huesyncbox/translations/en.json b/custom_components/huesyncbox/translations/en.json index 51ced80..33e0471 100644 --- a/custom_components/huesyncbox/translations/en.json +++ b/custom_components/huesyncbox/translations/en.json @@ -14,7 +14,7 @@ "step": { "user": { "title": "Enter device information", - "description": "Details can be found in the main Hue App.\n\nSelect the Sync tab and make sure the Philips Hue Play HDMI Syncbox is selected. After that tap the … menu at the top, select Device and then NetworkInfo for the IP address and DeviceInfo for the Identifier.", + "description": "Details can be found in the main Hue App.\n\nSelect the Sync tab and make sure the Philips Hue Play HDMI Syncbox is selected. After that tap the … menu at the top, then select Device and then NetworkInfo for the IP address and DeviceInfo for the Identifier.", "data": { "host": "IP address (e.g. 192.168.1.123)", "unique_id": "Identifier (e.g. C42996000000)" diff --git a/custom_components/huesyncbox/translations/nl.json b/custom_components/huesyncbox/translations/nl.json index 01284d3..f6a8f78 100644 --- a/custom_components/huesyncbox/translations/nl.json +++ b/custom_components/huesyncbox/translations/nl.json @@ -2,8 +2,9 @@ "config": { "abort": { "already_configured": "Apparaat is al ingesteld", - "reauth_successful": "Philips Hue Play HDMI Sync Box kopperling gelukt", - "connection_failed": "Vebinding mislukt" + "reauth_successful": "Philips Hue Play HDMI Sync Box koppeling gelukt", + "reconfigure_successful": "Philips Hue Play HDMI Sync Box opnieuw ingesteld", + "connection_failed": "Instellen mislukt" }, "error": { "cannot_connect": "Kan niet verbinden", @@ -11,8 +12,8 @@ "unknown": "Onverwachtte fout" }, "step": { - "user": { - "title": "Voor apparaat informatie in", + "configure": { + "title": "Voer apparaat informatie in", "description": "De informatie is beschikbaar in de Hue App.\n\nSelecteer de Sync tab en selecteer de Philips Hue Play HDMI Syncbox. Daarna druk boven aan het scherm op het … menu, selecteer Apparaat. Onder Netwerkinfo staat het IP addres en onder Apparaatinfo staat Identificatie.", "data": { "host": "IP addres (bijv. 192.168.1.123)", From c8e1545e7d44160a794d420ad0b2a6ac6e024e01 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Mon, 22 Apr 2024 16:16:44 +0200 Subject: [PATCH 28/38] Translate services and misc typos --- .../huesyncbox/translations/nl.json | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/custom_components/huesyncbox/translations/nl.json b/custom_components/huesyncbox/translations/nl.json index f6a8f78..a3dedad 100644 --- a/custom_components/huesyncbox/translations/nl.json +++ b/custom_components/huesyncbox/translations/nl.json @@ -58,7 +58,7 @@ "led_indicator_mode": { "name": "Led indicator", "state": { - "normal": "Normal", + "normal": "Normaal", "off": "Uit", "dimmed": "Gedimd" } @@ -82,7 +82,7 @@ "bridge_connection_state": { "name": "Bridge verbinding", "state": { - "uninitialized": "Niet geinitialiseert", + "uninitialized": "Niet ingesteld", "disconnected": "Verbroken", "connecting": "Verbinding maken", "unauthorized": "Niet gekoppeld", @@ -141,7 +141,7 @@ }, "switch": { "power": { - "name": "Ingeshakeld" + "name": "Ingeschakeld" }, "light_sync": { "name": "Synchroniseren" @@ -178,54 +178,54 @@ }, "services": { "set_bridge": { - "name": "Set bridge", - "description": "Set the bridge to be used by the Philips Hue Play HDMI Syncbox. Keep in mind that changing the bridge by the box takes a while (about 15 seconds it seems). After the bridge has changed you might need to select the `entertainment_area` if connectionstate is `invalidgroup` instead of `connected`.", + "name": "Stel bridge in", + "description": "Stel de bridge in die gebruikt moet worden door de Philips Hue Play HDMI Syncbox. Let op het aanpassen en opnieuw verbinden duurt een tijdje (ongeveer 15 seconden lijkt het). Nadat de bridge verbonden is kan het zijn dat de `entertainment_area` aangepast moet worden. Dit is te zien aaan 'connectionstate' die `invalidgroup` is ipv`connected`.", "fields": { "bridge_id": { "name": "Bridge ID", - "description": "ID of the bridge. A hexadecimal code of 16 characters." + "description": "ID van de bridge. Een code van 16 letters en cijfers." }, "bridge_username": { - "name": "Username", - "description": "Username (a.k.a. application key) valid for the bridge. A long code of random characters." + "name": "Toegangs code", + "description": "Toegangscode die geldig is voor de bridge. Een lange code van willekeurige karakters." }, "bridge_clientkey": { "name": "Clientkey", - "description": "Clientkey that belongs with the username. A hexadecimal code of 32 characters." + "description": "Clientkey die bij de toegangscode hoort. Een code van 32 letters en cijfers." } } }, "set_sync_state": { - "name": "Set light sync state", - "description": "Control the complete light sync state of the Philips Hue Play HDMI Syncbox with one call.", + "name": "Bedien syncbox", + "description": "Bedien alle instellingen van de Philips Hue Play HDMI Syncbox in één keer.", "fields": { "power": { - "name": "Power", - "description": "Turn the box on or off." + "name": "Ingeschakeld", + "description": "Zet be box aan of uit." }, "sync": { "name": "Light sync", - "description": "Set light sync state on or off. Setting this to on will also turn on the box." + "description": "Synchroniseer je lampen. Deze instelling schakeld ook de box aan." }, "brightness": { - "name": "Brightness", - "description": "Brightness value to set." + "name": "Helderheid", + "description": "Helderheid die ingesteld moet worden." }, "mode": { - "name": "Mode", - "description": "Mode to set. Setting the mode will also turn on the box and start light sync." + "name": "Sync modus", + "description": "Modus die ingesteld moet worden. Het zetten van de modus zet de box aan en start light sync." }, "intensity": { - "name": "Intensity", - "description": "Intensity to set." + "name": "Intensiteit", + "description": "Intensiteit om in te stellen." }, "input": { - "name": "Input", - "description": "Input to select." + "name": "Ingang", + "description": "Ingang om te selecteren." }, "entertainment_area": { "name": "Entertainment area", - "description": "Entertainment area to select. Name must match _exactly_" + "description": "Entertainment area de gebruikt moet worden. De naam moet _exact_ gepspeld worden" } } } From e3bf9c5bb56b5ecd43ec966ab39f104c7c88ae25 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Mon, 22 Apr 2024 18:01:09 +0200 Subject: [PATCH 29/38] Small improvement --- custom_components/huesyncbox/translations/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/huesyncbox/translations/nl.json b/custom_components/huesyncbox/translations/nl.json index a3dedad..468c895 100644 --- a/custom_components/huesyncbox/translations/nl.json +++ b/custom_components/huesyncbox/translations/nl.json @@ -56,7 +56,7 @@ } }, "led_indicator_mode": { - "name": "Led indicator", + "name": "Led indicatie", "state": { "normal": "Normaal", "off": "Uit", From 0707df406820960095c91f21256eb24a2696d0ae Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Mon, 22 Apr 2024 18:06:25 +0200 Subject: [PATCH 30/38] Fix key in en translation --- custom_components/huesyncbox/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/huesyncbox/translations/en.json b/custom_components/huesyncbox/translations/en.json index 33e0471..44240d7 100644 --- a/custom_components/huesyncbox/translations/en.json +++ b/custom_components/huesyncbox/translations/en.json @@ -12,7 +12,7 @@ "unknown": "Unexpected error" }, "step": { - "user": { + "configure": { "title": "Enter device information", "description": "Details can be found in the main Hue App.\n\nSelect the Sync tab and make sure the Philips Hue Play HDMI Syncbox is selected. After that tap the … menu at the top, then select Device and then NetworkInfo for the IP address and DeviceInfo for the Identifier.", "data": { From 04722d8a02b7b6deee84d4405e4554058c69be48 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sat, 2 Sep 2023 11:50:23 +0200 Subject: [PATCH 31/38] Remove migration repair creation and cleanup --- custom_components/huesyncbox/__init__.py | 41 ++++++----------- custom_components/huesyncbox/config_flow.py | 2 +- custom_components/huesyncbox/manifest.json | 2 +- custom_components/huesyncbox/strings.json | 13 ------ .../huesyncbox/translations/en.json | 13 ------ tests/test_init.py | 45 ++++++++++++++++++- 6 files changed, 59 insertions(+), 57 deletions(-) diff --git a/custom_components/huesyncbox/__init__.py b/custom_components/huesyncbox/__init__.py index 7fc7b05..2bb4d7a 100644 --- a/custom_components/huesyncbox/__init__.py +++ b/custom_components/huesyncbox/__init__.py @@ -2,7 +2,6 @@ import asyncio import aiohuesyncbox -from homeassistant.components import automation from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -105,6 +104,8 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry): if config_entry.version == 1: migrate_v1_to_v2(hass, config_entry) + if config_entry.version == 2: + migrate_v2_to_v3(hass, config_entry) LOGGER.info( "Migration of ConfigEntry from version %s to version %s successful", @@ -123,36 +124,22 @@ def migrate_v1_to_v2(hass: HomeAssistant, config_entry: ConfigEntry): registry, config_entry.entry_id ) - automations_with_entity = [] for entity in entities: if entity.domain == Platform.MEDIA_PLAYER: registry.async_remove(entity.entity_id) - automations_with_entity = automation.automations_with_entity( - hass, entity.entity_id - ) - - automation_info = [] - for automation_with_entity in automations_with_entity: - if automation_entry := registry.async_get(automation_with_entity): - automation_info.append( - f"{automation_entry.name or automation_entry.original_name} ({automation_with_entity})\n" - ) - - if len(automation_info) > 0: - issue_registry.async_create_issue( - hass, - DOMAIN, - f"automations_using_deleted_mediaplayer_{config_entry.entry_id}", - is_fixable=True, - is_persistent=True, - severity=issue_registry.IssueSeverity.WARNING, - translation_key="automations_using_deleted_mediaplayer", - translation_placeholders={ - "automations": ",".join(automation_info), - "media_player_entity": entity.entity_id, - }, - ) + # There used to be a repair created here + # Removed due to adding dependency on automation config_entry.version = 2 hass.config_entries.async_update_entry(config_entry) + + +def migrate_v2_to_v3(hass: HomeAssistant, config_entry: ConfigEntry): + # Remove any pending repairs + issue_registry.async_delete_issue( + hass, DOMAIN, f"automations_using_deleted_mediaplayer_{config_entry.entry_id}" + ) + + config_entry.version = 3 + hass.config_entries.async_update_entry(config_entry) diff --git a/custom_components/huesyncbox/config_flow.py b/custom_components/huesyncbox/config_flow.py index 162dd0d..cd7fe2c 100644 --- a/custom_components/huesyncbox/config_flow.py +++ b/custom_components/huesyncbox/config_flow.py @@ -98,7 +98,7 @@ async def try_connection(connection_info: ConnectionInfo) -> bool: class HueSyncBoxConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Philips Hue Play HDMI Sync Box.""" - VERSION = 2 + VERSION = 3 link_task: asyncio.Task | None = None config_entry: ConfigEntry | None = None diff --git a/custom_components/huesyncbox/manifest.json b/custom_components/huesyncbox/manifest.json index b0ddfcb..41fca2b 100644 --- a/custom_components/huesyncbox/manifest.json +++ b/custom_components/huesyncbox/manifest.json @@ -6,7 +6,7 @@ "@mvdwetering" ], "config_flow": true, - "dependencies": ["automation"], + "dependencies": [], "documentation": "https://github.com/mvdwetering/huesyncbox", "integration_type": "device", "iot_class": "local_polling", diff --git a/custom_components/huesyncbox/strings.json b/custom_components/huesyncbox/strings.json index 44240d7..950ebac 100644 --- a/custom_components/huesyncbox/strings.json +++ b/custom_components/huesyncbox/strings.json @@ -229,18 +229,5 @@ } } } - }, - "issues": { - "automations_using_deleted_mediaplayer": { - "title": "Automations using deleted HueSyncBox mediaplayer", - "fix_flow": { - "step": { - "confirm": { - "title": "Update the automations", - "description": "The following automations are using a deleted HueSyncBox mediaplayer with id {media_player_entity}.\n\n{automations}\n\nPress SUBMIT to confirm the automations have been updated and dismiss this repair" - } - } - } - } } } \ No newline at end of file diff --git a/custom_components/huesyncbox/translations/en.json b/custom_components/huesyncbox/translations/en.json index 33e0471..be650ea 100644 --- a/custom_components/huesyncbox/translations/en.json +++ b/custom_components/huesyncbox/translations/en.json @@ -229,18 +229,5 @@ } } } - }, - "issues": { - "automations_using_deleted_mediaplayer": { - "title": "Automations using deleted HueSyncBox mediaplayer", - "fix_flow": { - "step": { - "confirm": { - "title": "Update the automations", - "description": "The following automations are using a deleted HueSyncBox mediaplayer with id {media_player_entity}.\n\n{automations}\n\nPress SUBMIT to confirm the automations have been updated and dismiss this repair" - } - } - } - } } } \ No newline at end of file diff --git a/tests/test_init.py b/tests/test_init.py index 031e770..fab32eb 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -134,7 +134,7 @@ async def test_migrate(hass: HomeAssistant, mock_api): # Just check if updated to latest version config_entry = hass.config_entries.async_get_entry(integration.entry.entry_id) assert config_entry is not None - assert config_entry.version == 2 + assert config_entry.version == 3 async def test_migrate_v1_to_v2(hass: HomeAssistant, mock_api): @@ -173,7 +173,7 @@ async def test_migrate_v1_to_v2(hass: HomeAssistant, mock_api): original_name="original name", ) - # Manually trigger v1 to v2 upgrade + # Manually trigger upgrade with patch( "homeassistant.components.automation.automations_with_entity", return_value=[automation_entity.entity_id], @@ -183,6 +183,35 @@ async def test_migrate_v1_to_v2(hass: HomeAssistant, mock_api): # Check results assert er.async_get(mp_entity.entity_id) is None + +async def test_migrate_v2_to_v3(hass: HomeAssistant, mock_api): + # Create v1 entry + mock_config_entry = MockConfigEntry( + version=2, + domain=huesyncbox.DOMAIN, + entry_id="entry_id", + title="HUESYNCBOX TITLE", + data={ + CONF_HOST: "host", + CONF_UNIQUE_ID: "unique_id", + CONF_PORT: 1234, + CONF_PATH: "/api_path", + CONF_ACCESS_TOKEN: "token", + huesyncbox.const.REGISTRATION_ID: "registration_id_value", + }, + ) + mock_config_entry.add_to_hass(hass) + + # Create old migration issue + issue_registry.async_create_issue( + hass, + huesyncbox.DOMAIN, + f"automations_using_deleted_mediaplayer_{mock_config_entry.entry_id}", + is_fixable=True, + is_persistent=True, + severity=issue_registry.IssueSeverity.WARNING, + translation_key="automations_using_deleted_mediaplayer", + ) ir = issue_registry.async_get(hass) assert ( ir.async_get_issue( @@ -191,3 +220,15 @@ async def test_migrate_v1_to_v2(hass: HomeAssistant, mock_api): ) is not None ) + + # Manually trigger upgrade + huesyncbox.migrate_v2_to_v3(hass, mock_config_entry) + + # Check results + assert ( + ir.async_get_issue( + huesyncbox.DOMAIN, + f"automations_using_deleted_mediaplayer_{mock_config_entry.entry_id}", + ) + is None + ) \ No newline at end of file From fc1ddb898070028691b7f8d09f919fefc375f535 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sat, 2 Sep 2023 12:56:16 +0200 Subject: [PATCH 32/38] Remove repairs file --- custom_components/huesyncbox/repairs.py | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 custom_components/huesyncbox/repairs.py diff --git a/custom_components/huesyncbox/repairs.py b/custom_components/huesyncbox/repairs.py deleted file mode 100644 index 81c5f6d..0000000 --- a/custom_components/huesyncbox/repairs.py +++ /dev/null @@ -1,12 +0,0 @@ -from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow -from homeassistant.core import HomeAssistant - - -async def async_create_fix_flow( - hass: HomeAssistant, - issue_id: str, - data: dict[str, str | int | float | None] | None, -) -> RepairsFlow: - """Create flow.""" - assert issue_id.startswith("automations_using_deleted_mediaplayer") - return ConfirmRepairFlow() From d9a053bc28f270250ec24e27e06d29c4be1a5fc4 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Mon, 22 Apr 2024 18:14:49 +0200 Subject: [PATCH 33/38] Use minor config entry increase instead of major --- custom_components/huesyncbox/__init__.py | 8 +++++--- custom_components/huesyncbox/config_flow.py | 3 ++- tests/conftest.py | 1 + tests/test_init.py | 7 ++++--- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/custom_components/huesyncbox/__init__.py b/custom_components/huesyncbox/__init__.py index 2bb4d7a..598c29f 100644 --- a/custom_components/huesyncbox/__init__.py +++ b/custom_components/huesyncbox/__init__.py @@ -105,7 +105,8 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry): if config_entry.version == 1: migrate_v1_to_v2(hass, config_entry) if config_entry.version == 2: - migrate_v2_to_v3(hass, config_entry) + if config_entry.minor_version == 1: + migrate_v2_1_to_v2_2(hass, config_entry) LOGGER.info( "Migration of ConfigEntry from version %s to version %s successful", @@ -135,11 +136,12 @@ def migrate_v1_to_v2(hass: HomeAssistant, config_entry: ConfigEntry): hass.config_entries.async_update_entry(config_entry) -def migrate_v2_to_v3(hass: HomeAssistant, config_entry: ConfigEntry): +def migrate_v2_1_to_v2_2(hass: HomeAssistant, config_entry: ConfigEntry): # Remove any pending repairs issue_registry.async_delete_issue( hass, DOMAIN, f"automations_using_deleted_mediaplayer_{config_entry.entry_id}" ) - config_entry.version = 3 + config_entry.version = 2 + config_entry.minor_version = 2 hass.config_entries.async_update_entry(config_entry) diff --git a/custom_components/huesyncbox/config_flow.py b/custom_components/huesyncbox/config_flow.py index cd7fe2c..700fe26 100644 --- a/custom_components/huesyncbox/config_flow.py +++ b/custom_components/huesyncbox/config_flow.py @@ -98,7 +98,8 @@ async def try_connection(connection_info: ConnectionInfo) -> bool: class HueSyncBoxConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Philips Hue Play HDMI Sync Box.""" - VERSION = 3 + VERSION = 2 + MINOR_VERSION = 2 link_task: asyncio.Task | None = None config_entry: ConfigEntry | None = None diff --git a/tests/conftest.py b/tests/conftest.py index 86b3020..81a6d0d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -116,6 +116,7 @@ async def setup_integration( entry = mock_config_entry or MockConfigEntry( version=2, + minor_version=2, domain=huesyncbox.DOMAIN, entry_id=entry_id, unique_id="unique_id", diff --git a/tests/test_init.py b/tests/test_init.py index fab32eb..e0a72f5 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -134,7 +134,8 @@ async def test_migrate(hass: HomeAssistant, mock_api): # Just check if updated to latest version config_entry = hass.config_entries.async_get_entry(integration.entry.entry_id) assert config_entry is not None - assert config_entry.version == 3 + assert config_entry.version == 2 + assert config_entry.minor_version == 2 async def test_migrate_v1_to_v2(hass: HomeAssistant, mock_api): @@ -184,7 +185,7 @@ async def test_migrate_v1_to_v2(hass: HomeAssistant, mock_api): assert er.async_get(mp_entity.entity_id) is None -async def test_migrate_v2_to_v3(hass: HomeAssistant, mock_api): +async def test_migrate_v2_1_to_v2_2(hass: HomeAssistant, mock_api): # Create v1 entry mock_config_entry = MockConfigEntry( version=2, @@ -222,7 +223,7 @@ async def test_migrate_v2_to_v3(hass: HomeAssistant, mock_api): ) # Manually trigger upgrade - huesyncbox.migrate_v2_to_v3(hass, mock_config_entry) + huesyncbox.migrate_v2_1_to_v2_2(hass, mock_config_entry) # Check results assert ( From 829bf3c6627a77c651f6c75b4e4181789d9537ae Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Mon, 22 Apr 2024 18:22:30 +0200 Subject: [PATCH 34/38] Update tests/test_init.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- tests/test_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_init.py b/tests/test_init.py index e0a72f5..f9b9973 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -232,4 +232,4 @@ async def test_migrate_v2_1_to_v2_2(hass: HomeAssistant, mock_api): f"automations_using_deleted_mediaplayer_{mock_config_entry.entry_id}", ) is None - ) \ No newline at end of file + ) From 2bd78f1dc343bb800337194fc11d9b2b6498a375 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Mon, 22 Apr 2024 18:35:46 +0200 Subject: [PATCH 35/38] Remove issues section from NL translation --- custom_components/huesyncbox/translations/nl.json | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/custom_components/huesyncbox/translations/nl.json b/custom_components/huesyncbox/translations/nl.json index 468c895..0246e05 100644 --- a/custom_components/huesyncbox/translations/nl.json +++ b/custom_components/huesyncbox/translations/nl.json @@ -229,18 +229,5 @@ } } } - }, - "issues": { - "automations_using_deleted_mediaplayer": { - "title": "Automations using deleted HueSyncBox mediaplayer", - "fix_flow": { - "step": { - "confirm": { - "title": "Update the automations", - "description": "The following automations are using a deleted HueSyncBox mediaplayer with id {media_player_entity}.\n\n{automations}\n\nPress SUBMIT to confirm the automations have been updated and dismiss this repair" - } - } - } - } } } \ No newline at end of file From 67d96c8093f07547228b895aa3c35afc97f2bf7d Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Tue, 23 Apr 2024 13:10:40 +0200 Subject: [PATCH 36/38] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ecb1e2b..012c950 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The following features are available: * HDMI Input selection * Brightness control * Entertainment area selection -* HDMI1-4 connection status +* HDMI input connection status * Dolby Vision compatibility on/off * LED indicator mode * Bridge connection status ⁺ @@ -41,11 +41,11 @@ Entities marked with ⁺ are default disabled. ### Behavior -A few notes on behavior when changing entities. +A few notes on behavior when changing entities. Note that this behavior is just how the box reacts when sending these commands, not something coded in this integration. * Enabling light sync will also power on the box * Setting sync mode will also power on the box and start light sync on the selected mode -* When you want to change multiple entities the order is important. For example Intensity applies to the current selected mode. So if you want to change both the `intensity` and `mode` you _first_ have to change the mode and then set the intensity. Otherwise the intensity is applied to the "old" mode. If you want to avoid ordering issues you can use the `set_sync_state` service which will take care of the ordering and is more efficient. +* When you want to change multiple entities the order is important. For example Intensity applies to the current selected mode. So if you want to change both the `intensity` and `mode` you _first_ have to change the mode and then set the intensity. Otherwise the intensity is applied to the "old" mode. If you want to avoid ordering issues you can use the `set_sync_state` service which will take care of the ordering and is more efficient than sending everything separately. ### Services @@ -69,7 +69,7 @@ No functionality is lost it just moved to a different place. ## Installation > **Note** -> The Wifi connection of the Philips Hue Play HDMI Sync Box has to be setup with the official Hue app before it can be added to Home Assistant. +> Please setup the Philips Hue Play HDMI Syncbox with the Hue App first and make sure it works before setting up this integration. ### Home Assistant Community Store (HACS) From fa6fbdd87bdee8926daaa6a9d182b51224304869 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Tue, 23 Apr 2024 11:26:28 +0000 Subject: [PATCH 37/38] Apply some suggestions --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 012c950..33ce204 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Custom integration for the Philips Hue Play HDMI Sync Box. ## About -> Please setup the Philips Hue Play HDMI Syncbox with the Hue App first and make sure it works before setting up this integration. +> Please set up the Philips Hue Play HDMI Syncbox with the Hue App first and make sure it works before setting up this integration. This integration exposes the Philips Hue Play HDMI Sync Box in Home Assistant so it can be used in automations or dashboards. @@ -45,7 +45,7 @@ A few notes on behavior when changing entities. Note that this behavior is just * Enabling light sync will also power on the box * Setting sync mode will also power on the box and start light sync on the selected mode -* When you want to change multiple entities the order is important. For example Intensity applies to the current selected mode. So if you want to change both the `intensity` and `mode` you _first_ have to change the mode and then set the intensity. Otherwise the intensity is applied to the "old" mode. If you want to avoid ordering issues you can use the `set_sync_state` service which will take care of the ordering and is more efficient than sending everything separately. +* When you want to change multiple entities the order is important. For example, Intensity applies to the current selected mode. So if you want to change both the `intensity` and `mode` you _first_ have to change the mode and then set the intensity. Otherwise, the intensity is applied to the "old" mode. If you want to avoid ordering issues you can use the `set_sync_state` service which will take care of the ordering and is more efficient than sending everything separately. ### Services @@ -73,7 +73,7 @@ No functionality is lost it just moved to a different place. ### Home Assistant Community Store (HACS) -HACS is a 3rd party downloader for Home Assistant to easily install and update custom integrations made by the community. More information and installation instructions can be found on their site https://hacs.xyz/ +HACS is a 3rd party downloader for Home Assistant to easily install and update custom integrations made by the community. More information and installation instructions can be found on the [HACS website](https://hacs.xyz/). * Install the integration from within HACS (you can use the search box to find it) * Restart Home Assistant @@ -82,8 +82,8 @@ HACS is a 3rd party downloader for Home Assistant to easily install and update c ### Manually * Install the custom component - * Download the zip from the releases section on Github + * Download the zip from the releases section on GitHub * Unzip it - * Copy it to the custom_components directory of your Home Assistnat install as usual + * Copy it to the custom_components directory of your Home Assistant install as usual * Restart Home Assistant * Devices will be found automatically or can be added manually from the Home Assistant integrations screen From f345801f30c1d068692059b179f19cab1a3fb07e Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Tue, 23 Apr 2024 13:38:17 +0200 Subject: [PATCH 38/38] Update version to 2.1.0 --- custom_components/huesyncbox/manifest.json | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/custom_components/huesyncbox/manifest.json b/custom_components/huesyncbox/manifest.json index 41fca2b..521f519 100644 --- a/custom_components/huesyncbox/manifest.json +++ b/custom_components/huesyncbox/manifest.json @@ -1,7 +1,6 @@ { "domain": "huesyncbox", "name": "Philips Hue Play HDMI Sync Box", - "codeowners": [ "@mvdwetering" ], @@ -11,8 +10,14 @@ "integration_type": "device", "iot_class": "local_polling", "issue_tracker": "https://github.com/mvdwetering/huesyncbox/issues", - "loggers": ["aiohuesyncbox"], - "requirements": ["aiohuesyncbox==0.0.27"], - "version": "0.0.0", - "zeroconf": ["_huesync._tcp.local."] + "loggers": [ + "aiohuesyncbox" + ], + "requirements": [ + "aiohuesyncbox==0.0.27" + ], + "version": "2.1.0", + "zeroconf": [ + "_huesync._tcp.local." + ] }