From 7e49b9beab313de81ed5d7ffa0c2f46768cbe77b Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sat, 24 May 2025 21:48:47 +0200 Subject: [PATCH 01/13] removed config_entry assignment (#30) This PR resolves deprecation issue reported via #28 Thanks to @WhiteSockedDancer for reporting! --- custom_components/hass_agent/config_flow.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/custom_components/hass_agent/config_flow.py b/custom_components/hass_agent/config_flow.py index 8ee0994..e6bd7b2 100644 --- a/custom_components/hass_agent/config_flow.py +++ b/custom_components/hass_agent/config_flow.py @@ -20,9 +20,8 @@ class OptionsFlowHandler(config_entries.OptionsFlow): - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self) -> None: """Initialize options flow.""" - self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None @@ -66,7 +65,7 @@ def async_get_options_flow( config_entry: config_entries.ConfigEntry, ) -> config_entries.OptionsFlow: """Create the options flow.""" - return OptionsFlowHandler(config_entry) + return OptionsFlowHandler() async def async_step_mqtt(self, discovery_info: MqttServiceInfo) -> FlowResult: """Handle a flow initialized by MQTT discovery.""" From ba56c00abb01aff8ed8401732c3bb3a1e71f2fa0 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sat, 24 May 2025 22:07:44 +0200 Subject: [PATCH 02/13] Fix: add handling of empty MQTT messages (#31) This PR fixes issues (resulting in Home Assistant system log errors) with empty device discovery messages. --- custom_components/hass_agent/__init__.py | 4 ++++ custom_components/hass_agent/config_flow.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/custom_components/hass_agent/__init__.py b/custom_components/hass_agent/__init__.py index b44717d..973bc7c 100644 --- a/custom_components/hass_agent/__init__.py +++ b/custom_components/hass_agent/__init__.py @@ -174,6 +174,10 @@ def get_device_info(): @callback async def updated(message: ReceiveMessage): + if not message.payload: + _logger.debug("received empty update message on '%s', ignoring", message.topic) + return + payload = json.loads(message.payload) cached = hass.data[DOMAIN][entry.entry_id]["apis"] apis = payload["apis"] diff --git a/custom_components/hass_agent/config_flow.py b/custom_components/hass_agent/config_flow.py index e6bd7b2..2d5e9e1 100644 --- a/custom_components/hass_agent/config_flow.py +++ b/custom_components/hass_agent/config_flow.py @@ -69,6 +69,10 @@ def async_get_options_flow( async def async_step_mqtt(self, discovery_info: MqttServiceInfo) -> FlowResult: """Handle a flow initialized by MQTT discovery.""" + if not discovery_info.payload: + _logger.debug("received empty discovery message on '%s', ignoring", discovery_info.topic) + return self.async_abort(reason="not_supported") + device_name = discovery_info.topic.split("hass.agent/devices/")[1] payload = json.loads(discovery_info.payload) From bfb5dbdee0b02491862be6402c772d0e44da848b Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Sun, 25 May 2025 16:03:32 +0200 Subject: [PATCH 03/13] Fix: added empty message check (#32) This PR adds check for empty MQTT message that can result in Home Assistant errors. --- custom_components/hass_agent/media_player.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/custom_components/hass_agent/media_player.py b/custom_components/hass_agent/media_player.py index e0938de..a913b7d 100644 --- a/custom_components/hass_agent/media_player.py +++ b/custom_components/hass_agent/media_player.py @@ -94,6 +94,10 @@ def media_image_local(self) -> str | None: @callback def updated(self, message: ReceiveMessage): """Updates the media player with new data from MQTT""" + if not message.payload: + _logger.debug("received empty update message on '%s', ignoring", message.topic) + return + payload = json.loads(message.payload) self._state = payload["state"].lower() From ecda68ff1ca4a089c2a52c987b1c11f7feca9895 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Thu, 18 Sep 2025 22:24:13 +0200 Subject: [PATCH 04/13] Fix: removed requirement for MQTT integration (#41) This PR removes requirement for the MQTT integration that is causing issue for users using only local api, without any MQTT broker running. Previously added as part of #1 --- custom_components/hass_agent/__init__.py | 53 ------------------------ 1 file changed, 53 deletions(-) diff --git a/custom_components/hass_agent/__init__.py b/custom_components/hass_agent/__init__.py index 973bc7c..b95616f 100644 --- a/custom_components/hass_agent/__init__.py +++ b/custom_components/hass_agent/__init__.py @@ -49,35 +49,6 @@ async def update_device_info(hass: HomeAssistant, entry: ConfigEntry, new_device sw_version=new_device_info["device"]["sw_version"], ) -async def async_wait_for_mqtt_client(hass: HomeAssistant) -> bool: - """Wait for the MQTT client to become available. - Waits when mqtt set up is in progress, - It is not needed that the client is connected. - Returns True if the mqtt client is available. - Returns False when the client is not available. - """ - if not mqtt_config_entry_enabled(hass): - return False - - entry = hass.config_entries.async_entries(DOMAIN)[0] - if entry.state == ConfigEntryState.LOADED: - return True - - state_reached_future: asyncio.Future[bool] - if DATA_MQTT_AVAILABLE not in hass.data: - hass.data[DATA_MQTT_AVAILABLE] = state_reached_future = asyncio.Future() - else: - state_reached_future = hass.data[DATA_MQTT_AVAILABLE] - if state_reached_future.done(): - return state_reached_future.result() - - try: - async with async_timeout.timeout(AVAILABILITY_TIMEOUT): - # Await the client setup or an error state was received - return await state_reached_future - except asyncio.TimeoutError: - return False - async def handle_apis_changed(hass: HomeAssistant, entry: ConfigEntry, apis): _logger.debug("api changed for: %s", entry.unique_id) if apis is not None: @@ -252,28 +223,4 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.http.register_view(MediaPlayerThumbnailView(hass)) - # Make sure MQTT integration is enabled and the client is available - if not await mqtt.async_wait_for_mqtt_client(hass): - _logger.error("MQTT integration is not available") - return False - - # async def _handle_reload(service): - # """Handle reload service call.""" - # _logger.info("Service %s.reload called: reloading integration", DOMAIN) - - # current_entries = hass.config_entries.async_entries(DOMAIN) - - # reload_tasks = [ - # hass.config_entries.async_reload(entry.entry_id) - # for entry in current_entries - # ] - - # await asyncio.gather(*reload_tasks) - - # hass.services.async_register( - # DOMAIN, - # SERVICE_RELOAD, - # _handle_reload, - # ) - return True From 3e30110b1afde585a08b255b2b0af8f6d1415d20 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:43:38 +0200 Subject: [PATCH 05/13] Fix: new device bugs (#42) This PR fixes issues related to adding new LocalAPI only HASS.Agent device. --- custom_components/hass_agent/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/hass_agent/__init__.py b/custom_components/hass_agent/__init__.py index b95616f..7925628 100644 --- a/custom_components/hass_agent/__init__.py +++ b/custom_components/hass_agent/__init__.py @@ -103,7 +103,7 @@ async def handle_apis_changed(hass: HomeAssistant, entry: ConfigEntry, apis): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up HASS.Agent from a config entry.""" - _logger.debug("setting up device from config entry: %s [%s]", entry.data["device"]["name"], entry.unique_id) + _logger.debug("setting up device from config entry: %s [%s]", entry.title, entry.unique_id) hass.data.setdefault(DOMAIN, {}) @@ -135,7 +135,7 @@ def get_device_info(): "media_player": False, # unsupported for the moment } - hass.async_create_background_task(handle_apis_changed(hass, entry, apis)) + hass.async_create_background_task(handle_apis_changed(hass, entry, apis), "hass.agent-api") hass.data[DOMAIN][entry.entry_id]["apis"] = apis else: From ae3ff023ba11d901fcee6ceb923b69b9469f5806 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Mon, 22 Sep 2025 19:36:54 +0200 Subject: [PATCH 06/13] Fix: duplicate devices / device appearing as discovered even though already added (#43) This PR fixes issue where devices would be discovered more than once, even though the unique id (serial number) did not change. --- custom_components/hass_agent/config_flow.py | 18 ++++++------------ custom_components/hass_agent/strings.json | 7 ++++--- .../hass_agent/translations/en.json | 9 +++++---- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/custom_components/hass_agent/config_flow.py b/custom_components/hass_agent/config_flow.py index 2d5e9e1..70c5f00 100644 --- a/custom_components/hass_agent/config_flow.py +++ b/custom_components/hass_agent/config_flow.py @@ -83,13 +83,8 @@ async def async_step_mqtt(self, discovery_info: MqttServiceInfo) -> FlowResult: self._data = {"device": payload["device"], "apis": payload["apis"]} - for config in self._async_current_entries(): - _logger.debug("device: %s, SN: %s, UID: %s", device_name, serial_number, config.unique_id) # TODO(Amadeo): remove - if config.unique_id == serial_number: - _logger.debug("device %s, serial number: %s already configured, ignoring", device_name, serial_number) - return self.async_abort(reason="already_configured") - await self.async_set_unique_id(serial_number) + self._abort_if_unique_id_configured() # "hass.agent/devices/#" is hardcoded in HASS.Agent's manifest assert discovery_info.subscribed_topic == "hass.agent/devices/#" @@ -115,15 +110,17 @@ async def async_step_local_api( # serial number! try: - def get_device_info(): return requests.get(f"{url}/info", timeout=10) response = await self.hass.async_add_executor_job(get_device_info) - + response.raise_for_status() response_json = response.json() - + except Exception: + errors["base"] = "cannot_connect" + else: await self.async_set_unique_id(response_json["serial_number"]) + self._abort_if_unique_id_configured() return self.async_create_entry( title=response_json["device"]["name"], @@ -131,9 +128,6 @@ def get_device_info(): options={CONF_DEFAULT_NOTIFICATION_TITLE: ATTR_TITLE_DEFAULT}, ) - except Exception: - errors["base"] = "cannot_connect" - return self.async_show_form( step_id="local_api", data_schema=vol.Schema( diff --git a/custom_components/hass_agent/strings.json b/custom_components/hass_agent/strings.json index 6900a33..a3a914a 100644 --- a/custom_components/hass_agent/strings.json +++ b/custom_components/hass_agent/strings.json @@ -21,7 +21,8 @@ }, "abort": { "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } }, "options": { @@ -36,8 +37,8 @@ }, "device_automation": { "trigger_type": { - "notifications_mqtt": "When a notification with a specfic action has been pressed (MQTT)", - "notifications_event": "When a notification with a specfic action has been pressed (Event)" + "notifications_mqtt": "[%key:component::hass_agent::device_automation::trigger_type::notifications_mqtt%]", + "notifications_event": "[%key:component::hass_agent::device_automation::trigger_type::notifications_event%]" } } } diff --git a/custom_components/hass_agent/translations/en.json b/custom_components/hass_agent/translations/en.json index ad59414..55c323f 100644 --- a/custom_components/hass_agent/translations/en.json +++ b/custom_components/hass_agent/translations/en.json @@ -2,10 +2,11 @@ "config": { "flow_title": "{name}", "abort": { - "no_devices_found": "No devices found on the network" + "no_devices_found": "No devices found on the network.", + "already_configured": "This device is already configured." }, "error": { - "cannot_connect": "Failed to connect" + "cannot_connect": "Failed to connect to HASS.Agent" }, "step": { "confirm": { @@ -34,8 +35,8 @@ }, "device_automation": { "trigger_type": { - "notifications_mqtt": "When a notification with a specfic action has been pressed (MQTT)", - "notifications_event": "When a notification with a specfic action has been pressed (Event)" + "notifications_mqtt": "When a notification with a specific action has been pressed (MQTT)", + "notifications_event": "When a notification with a specific action has been pressed (Event)" } } } From 02b225ca3ddcdc4cd9c21328552f5c58af5aabce Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Tue, 23 Sep 2025 12:22:37 +0200 Subject: [PATCH 07/13] Fix: device removal/notify unloading error (#44) This PR "fixes" - dirty patch - error where removing device would cause Home Assistant error regarding Notify platform. In addition fixes debug log message failing due to invalid parameter. --- custom_components/hass_agent/__init__.py | 34 +++++++++--------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/custom_components/hass_agent/__init__.py b/custom_components/hass_agent/__init__.py index 7925628..05870ed 100644 --- a/custom_components/hass_agent/__init__.py +++ b/custom_components/hass_agent/__init__.py @@ -72,9 +72,7 @@ async def handle_apis_changed(hass: HomeAssistant, entry: ConfigEntry, apis): else: if is_media_player_loaded: _logger.debug("unloading media player for device: %s [%s]", device.name, entry.unique_id) - await hass.config_entries.async_forward_entry_unload( - entry, Platform.MEDIA_PLAYER - ) + await hass.config_entries.async_forward_entry_unload(entry, Platform.MEDIA_PLAYER) hass.data[DOMAIN][entry.entry_id]["loaded"]["media_player"] = False @@ -93,10 +91,9 @@ async def handle_apis_changed(hass: HomeAssistant, entry: ConfigEntry, apis): hass.data[DOMAIN][entry.entry_id]["loaded"]["notifications"] = True else: if is_notifications_loaded: - _logger.debug("unloading notifications for device: %s [%s]", device.name, entry.unique_id) - await hass.config_entries.async_unload_platforms( - entry, [Platform.NOTIFY] - ) + # _logger.debug("unloading notifications for device: %s [%s]", device.name, entry.unique_id) + # await hass.config_entries.async_unload_platforms(entry, [Platform.NOTIFY]) + # NOTE(Amadeo): disabled due to "ValueError: Config entry was never loaded!" error hass.data[DOMAIN][entry.entry_id]["loaded"]["notifications"] = False @@ -180,29 +177,24 @@ async def updated(message: ReceiveMessage): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - deviceName = entry.data["device"]["name"] - - _logger.debug("unloading device: %s [%s]", deviceName, entry.unique_id) - - # known issue: notify does not always unload + _logger.debug("unloading device: %s [%s]", entry.title, entry.unique_id) + # known issue: notify does not always unload + # NOTE(Amadeo): unloading NOTIFY platform always fails, same happens for example for https://github.com/home-assistant/core/blob/dd7f7be6adee76f2add98dcca8d3ff87bceabf70/homeassistant/components/nfandroidtv/__init__.py loaded = hass.data[DOMAIN][entry.entry_id].get("loaded", None) if loaded is not None: notifications = loaded.get("notifications", False) media_player = loaded.get("media_player", False) - if notifications: - if unload_ok := await hass.config_entries.async_unload_platforms( - entry, [Platform.NOTIFY] - ): - _logger.debug("unloaded notifications for: %s [%s]", deviceName, entry.unique_id) + # if notifications: + # if unload_ok := await hass.config_entries.async_unload_platforms(entry, [Platform.NOTIFY]): + # _logger.debug("unloaded notifications for: %s [%s]", entry.title, entry.unique_id) + # NOTE(Amadeo): disabled due to "ValueError: Config entry was never loaded!" error if media_player: - if unload_ok := await hass.config_entries.async_unload_platforms( - entry, [Platform.MEDIA_PLAYER] - ): - _logger.debug("unloaded media player for: %s [%s]", deviceName, entry.unique_id) + if unload_ok := await hass.config_entries.async_unload_platforms(entry, [Platform.MEDIA_PLAYER]): + _logger.debug("unloaded media player for: %s [%s]", entry.title, entry.unique_id) else: _logger.warning("config entry (%s) with has no apis loaded?", entry.unique_id) From 940ff8c43095746cbc22f3984b474cbe59caee70 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Tue, 23 Sep 2025 12:41:44 +0200 Subject: [PATCH 08/13] Feature: hassfest and HACS verify update (#45) This PR updated workflow files for hassfest and HACS verify GH actions. --- .github/workflows/hassfest.yaml | 2 +- .github/workflows/validate.yaml | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/hassfest.yaml b/.github/workflows/hassfest.yaml index a1be8f5..a9b5b27 100644 --- a/.github/workflows/hassfest.yaml +++ b/.github/workflows/hassfest.yaml @@ -11,5 +11,5 @@ jobs: validate: runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v2" + - uses: "actions/checkout@v4" - uses: home-assistant/actions/hassfest@master diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index db8f82e..9cb7d34 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -1,18 +1,19 @@ -name: Validate +name: Validate HACS on: + #push: + #pull_request: + #schedule: + # - cron: "0 0 * * *" workflow_dispatch: -# push: -# pull_request: -# schedule: -# - cron: "0 0 * * *" + +permissions: {} jobs: - validate: + validate-hacs: runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v2" - name: HACS validation uses: "hacs/action@main" with: - category: "integration" + category: "integration" \ No newline at end of file From 15c2518842283c43fbc90f68a0935248a7f10a0f Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Tue, 23 Sep 2025 13:00:22 +0200 Subject: [PATCH 09/13] Fix: manifest order (#46) Updated manifest to be ordered as hassfest requires. --- custom_components/hass_agent/manifest.json | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/custom_components/hass_agent/manifest.json b/custom_components/hass_agent/manifest.json index c7bc04d..679305f 100644 --- a/custom_components/hass_agent/manifest.json +++ b/custom_components/hass_agent/manifest.json @@ -1,23 +1,23 @@ { "domain": "hass_agent", "name": "HASS.Agent", - "version": "2.1.1", - "config_flow": true, - "documentation": "https://github.com/hass-agent/HASS.Agent-Integration", - "issue_tracker": "https://github.com/hass-agent/HASS.Agent-Integration/issues", - "mqtt": ["hass.agent/devices/#"], - "ssdp": [], - "zeroconf": [], - "homekit": {}, - "dependencies": [ - "mqtt", - "http" - ], "codeowners": [ "@fillefilip8", "@DrR0X-glitch", "@amadeo-alex" ], + "config_flow": true, + "dependencies": [ + "mqtt", + "http" + ], + "documentation": "https://github.com/hass-agent/HASS.Agent-Integration", + "homekit": {}, "iot_class": "local_push", - "loggers": ["custom_components.hass_agent"] + "issue_tracker": "https://github.com/hass-agent/HASS.Agent-Integration/issues", + "loggers": ["custom_components.hass_agent"], + "mqtt": ["hass.agent/devices/#"], + "ssdp": [], + "version": "2.1.1", + "zeroconf": [] } From 1b811abf445ff0be3bcf817a36ae76b63695ea01 Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:14:11 +0100 Subject: [PATCH 10/13] Fix: media player not working after device rename (#51) This PR fixes issue where renaming device would cause the media player entity not to be updated in Home Assistant. Combined with additional change in 2.2.0 version of HASS.Agent should ensure the media player works properly. Potentially related issues (to be verified after this PR is published in one of the beta versions of the integration): Bug: Can't get notifications or mediaplayer to work. Something wrong after reinstall with device id logic #34 Bug: Media Playera is Unavailable after device rename #27 Note: the media player entity ID in Home Assistant will remain the same, this means that history and configured card/dashboards will not require modification. The notify service calls WILL need to be updated though. --- custom_components/hass_agent/__init__.py | 49 +++++++---- custom_components/hass_agent/config_flow.py | 68 ++++++++++----- custom_components/hass_agent/const.py | 1 + custom_components/hass_agent/media_player.py | 25 ++---- custom_components/hass_agent/notify.py | 30 ++++--- custom_components/hass_agent/repairs.py | 45 ++++++++++ custom_components/hass_agent/strings.json | 87 +++++++++++-------- .../hass_agent/translations/en.json | 17 +++- 8 files changed, 212 insertions(+), 110 deletions(-) create mode 100644 custom_components/hass_agent/repairs.py diff --git a/custom_components/hass_agent/__init__.py b/custom_components/hass_agent/__init__.py index 05870ed..481b3d7 100644 --- a/custom_components/hass_agent/__init__.py +++ b/custom_components/hass_agent/__init__.py @@ -1,4 +1,5 @@ """The HASS.Agent integration.""" + from __future__ import annotations import asyncio @@ -21,23 +22,24 @@ ) from homeassistant.const import ( CONF_ID, - CONF_NAME, - CONF_URL, - Platform, + CONF_NAME, + CONF_URL, + Platform, SERVICE_RELOAD, ) from homeassistant.core import HomeAssistant, callback, ServiceCall, async_get_hass from homeassistant.helpers import device_registry as dr from homeassistant.helpers import discovery from homeassistant.helpers.typing import ConfigType -from homeassistant.util import slugify +from homeassistant.util import slugify -from .const import DOMAIN +from .const import DOMAIN, CONF_ORIGINAL_DEVICE_NAME, CONF_DEVICE_NAME PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER] _logger = logging.getLogger(__name__) + async def update_device_info(hass: HomeAssistant, entry: ConfigEntry, new_device_info): device_registry = dr.async_get(hass) device_registry.async_get_or_create( @@ -49,14 +51,12 @@ async def update_device_info(hass: HomeAssistant, entry: ConfigEntry, new_device sw_version=new_device_info["device"]["sw_version"], ) + async def handle_apis_changed(hass: HomeAssistant, entry: ConfigEntry, apis): _logger.debug("api changed for: %s", entry.unique_id) if apis is not None: - device_registry = dr.async_get(hass) - device = device_registry.async_get_device( - identifiers={(DOMAIN, entry.unique_id)} - ) + device = device_registry.async_get_device(identifiers={(DOMAIN, entry.unique_id)}) media_player = apis.get("media_player", False) is_media_player_loaded = hass.data[DOMAIN][entry.entry_id]["loaded"]["media_player"] @@ -71,20 +71,34 @@ async def handle_apis_changed(hass: HomeAssistant, entry: ConfigEntry, apis): hass.data[DOMAIN][entry.entry_id]["loaded"]["media_player"] = True else: if is_media_player_loaded: - _logger.debug("unloading media player for device: %s [%s]", device.name, entry.unique_id) + _logger.debug( + "unloading media player for device: %s [%s]", + device.name, + entry.unique_id, + ) await hass.config_entries.async_forward_entry_unload(entry, Platform.MEDIA_PLAYER) hass.data[DOMAIN][entry.entry_id]["loaded"]["media_player"] = False if notifications and is_notifications_loaded is False: - _logger.debug("loading notifications for device: %s [%s]", device.name, entry.unique_id) + _logger.debug( + "loading notifications for device: %s [%s]", + device.name, + entry.unique_id, + ) hass.async_create_task( discovery.async_load_platform( hass, Platform.NOTIFY, DOMAIN, - {CONF_ID: entry.entry_id, CONF_NAME: device.name}, + { + CONF_ID: entry.entry_id, + CONF_NAME: entry.data[ # Note(Amadeo): CONF_NAME decides of "nofity." name, needs to be set to the original one + CONF_ORIGINAL_DEVICE_NAME + ], + CONF_DEVICE_NAME: device.name, # Note(Amadeo): since CONF_NAME is used for the old name, we need to pass on the changed name for MQTT notify call + }, {}, ) ) @@ -97,6 +111,7 @@ async def handle_apis_changed(hass: HomeAssistant, entry: ConfigEntry, apis): hass.data[DOMAIN][entry.entry_id]["loaded"]["notifications"] = False + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up HASS.Agent from a config entry.""" @@ -174,12 +189,13 @@ async def updated(message: ReceiveMessage): return True + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" _logger.debug("unloading device: %s [%s]", entry.title, entry.unique_id) - # known issue: notify does not always unload + # known issue: notify does not always unload # NOTE(Amadeo): unloading NOTIFY platform always fails, same happens for example for https://github.com/home-assistant/core/blob/dd7f7be6adee76f2add98dcca8d3ff87bceabf70/homeassistant/components/nfandroidtv/__init__.py loaded = hass.data[DOMAIN][entry.entry_id].get("loaded", None) @@ -200,13 +216,12 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: url = entry.data.get(CONF_URL, None) if url is None: - async_unsubscribe_topics( - hass, hass.data[DOMAIN][entry.entry_id]["internal_mqtt"] - ) + async_unsubscribe_topics(hass, hass.data[DOMAIN][entry.entry_id]["internal_mqtt"]) hass.data[DOMAIN].pop(entry.entry_id) - return unload_ok + return True + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up hass_agent integration.""" diff --git a/custom_components/hass_agent/config_flow.py b/custom_components/hass_agent/config_flow.py index 70c5f00..ed3a802 100644 --- a/custom_components/hass_agent/config_flow.py +++ b/custom_components/hass_agent/config_flow.py @@ -1,4 +1,5 @@ """Config flow for HASS.Agent""" + from __future__ import annotations import json import logging @@ -13,8 +14,9 @@ from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_SSL, CONF_URL from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.service_info.mqtt import MqttServiceInfo +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue -from .const import DOMAIN, CONF_DEFAULT_NOTIFICATION_TITLE +from .const import DOMAIN, CONF_DEFAULT_NOTIFICATION_TITLE, CONF_ORIGINAL_DEVICE_NAME, CONF_DEVICE_NAME _logger = logging.getLogger(__name__) @@ -23,14 +25,10 @@ class OptionsFlowHandler(config_entries.OptionsFlow): def __init__(self) -> None: """Initialize options flow.""" - async def async_step_init( - self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + async def async_step_init(self, user_input: dict[str, Any] | None = None) -> FlowResult: """Manage the options.""" if user_input is not None: - user_input[CONF_DEFAULT_NOTIFICATION_TITLE] = user_input[ - CONF_DEFAULT_NOTIFICATION_TITLE - ].strip() + user_input[CONF_DEFAULT_NOTIFICATION_TITLE] = user_input[CONF_DEFAULT_NOTIFICATION_TITLE].strip() return self.async_create_entry(title="", data=user_input) @@ -40,9 +38,7 @@ async def async_step_init( { vol.Optional( CONF_DEFAULT_NOTIFICATION_TITLE, - default=self.config_entry.options.get( - CONF_DEFAULT_NOTIFICATION_TITLE, ATTR_TITLE_DEFAULT - ), + default=self.config_entry.options.get(CONF_DEFAULT_NOTIFICATION_TITLE, ATTR_TITLE_DEFAULT), ): str } ), @@ -70,20 +66,50 @@ def async_get_options_flow( async def async_step_mqtt(self, discovery_info: MqttServiceInfo) -> FlowResult: """Handle a flow initialized by MQTT discovery.""" if not discovery_info.payload: - _logger.debug("received empty discovery message on '%s', ignoring", discovery_info.topic) + _logger.debug( + "received empty discovery message on '%s', ignoring", + discovery_info.topic, + ) return self.async_abort(reason="not_supported") - device_name = discovery_info.topic.split("hass.agent/devices/")[1] - payload = json.loads(discovery_info.payload) + device_name = payload["device"]["name"] serial_number = payload["serial_number"] _logger.debug("found device. Name: %s, Serial Number: %s", device_name, serial_number) self._data = {"device": payload["device"], "apis": payload["apis"]} - await self.async_set_unique_id(serial_number) + entry = await self.async_set_unique_id(serial_number) + if not entry or (CONF_ORIGINAL_DEVICE_NAME not in entry.data): + self._data[CONF_ORIGINAL_DEVICE_NAME] = device_name + + if entry: + reload_required = device_name != entry.title + + self.hass.config_entries.async_update_entry( + entry, + title=payload["device"]["name"], + data={**entry.data, **self._data}, + ) + + if reload_required: + self.hass.config_entries.async_schedule_reload(entry.entry_id) + + async_create_issue( + hass=self.hass, + domain=DOMAIN, + issue_id=f"restart_required_{device_name}", + data={CONF_DEVICE_NAME: device_name}, + is_fixable=True, + severity=IssueSeverity.WARNING, + translation_key="restart_required", + translation_placeholders={ + "name": device_name, + }, + ) + self._abort_if_unique_id_configured() # "hass.agent/devices/#" is hardcoded in HASS.Agent's manifest @@ -93,10 +119,7 @@ async def async_step_mqtt(self, discovery_info: MqttServiceInfo) -> FlowResult: return await self.async_step_confirm() - async def async_step_local_api( - self, user_input: dict[str, Any] | None = None - ) -> FlowResult: - + async def async_step_local_api(self, user_input: dict[str, Any] | None = None) -> FlowResult: errors = {} if user_input is not None: @@ -110,6 +133,7 @@ async def async_step_local_api( # serial number! try: + def get_device_info(): return requests.get(f"{url}/info", timeout=10) @@ -141,14 +165,10 @@ def get_device_info(): errors=errors, ) - async def async_step_user( - self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + async def async_step_user(self, user_input: dict[str, Any] | None = None) -> FlowResult: return await self.async_step_local_api() - async def async_step_confirm( - self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + async def async_step_confirm(self, user_input: dict[str, Any] | None = None) -> FlowResult: """Confirm the setup.""" if user_input is not None: diff --git a/custom_components/hass_agent/const.py b/custom_components/hass_agent/const.py index 10bb4c1..b82f094 100644 --- a/custom_components/hass_agent/const.py +++ b/custom_components/hass_agent/const.py @@ -5,3 +5,4 @@ CONF_ACTION = "action" CONF_DEVICE_NAME = "device_name" CONF_DEFAULT_NOTIFICATION_TITLE = "default_notification_title" +CONF_ORIGINAL_DEVICE_NAME = "original_device_name" diff --git a/custom_components/hass_agent/media_player.py b/custom_components/hass_agent/media_player.py index a913b7d..82c1b6a 100644 --- a/custom_components/hass_agent/media_player.py +++ b/custom_components/hass_agent/media_player.py @@ -9,7 +9,7 @@ from homeassistant.components.mqtt.models import ReceiveMessage from homeassistant.helpers import device_registry as dr -from .const import DOMAIN +from .const import DOMAIN, CONF_ORIGINAL_DEVICE_NAME from homeassistant.components.mqtt.subscription import ( @@ -60,18 +60,14 @@ ) -async def async_setup_entry( - hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback -) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> bool: device_registry = dr.async_get(hass) device = device_registry.async_get_device(identifiers={(DOMAIN, entry.unique_id)}) if device is None: return False - async_add_entities( - [HassAgentMediaPlayerDevice(entry.unique_id, entry.entry_id, device)] - ) + async_add_entities([HassAgentMediaPlayerDevice(entry.unique_id, entry.entry_id, device, entry.data[CONF_ORIGINAL_DEVICE_NAME])]) return True @@ -83,9 +79,7 @@ class HassAgentMediaPlayerDevice(MediaPlayerEntity): def update_thumbnail(self, message: ReceiveMessage): self.hass.data[DOMAIN][self._entry_id]["thumbnail"] = message.payload - self._attr_media_image_url = ( - f"/api/hass_agent/{self.entity_id}/thumbnail.png?time={time.time()}" - ) + self._attr_media_image_url = f"/api/hass_agent/{self.entity_id}/thumbnail.png?time={time.time()}" @property def media_image_local(self) -> str | None: @@ -97,7 +91,7 @@ def updated(self, message: ReceiveMessage): if not message.payload: _logger.debug("received empty update message on '%s', ignoring", message.topic) return - + payload = json.loads(message.payload) self._state = payload["state"].lower() @@ -147,7 +141,7 @@ async def async_will_remove_from_hass(self) -> None: if self._listeners is not None: async_unsubscribe_topics(self.hass, self._listeners) - def __init__(self, unique_id, entry_id, device: dr.DeviceEntry): + def __init__(self, unique_id, entry_id, device: dr.DeviceEntry, original_device_name): """Initialize""" self._entry_id = entry_id self._name = device.name @@ -168,6 +162,7 @@ def __init__(self, unique_id, entry_id, device: dr.DeviceEntry): self._listeners = {} self._last_updated = 0 + self._original_device_name = original_device_name async def _send_command(self, command, data=None): """Send a command""" @@ -212,7 +207,7 @@ def available(self): def volume_level(self): """Return the volume level of the media player (0..1)""" return self._volume_level / 100.0 - + async def async_set_volume_level(self, volume: float) -> None: """Send new volume_level to device.""" volume = round(volume * 100) @@ -278,9 +273,7 @@ async def async_media_previous_track(self): """Send previous track command""" await self._send_command("previous") - async def async_browse_media( - self, media_content_type: str | None = None, media_content_id: str | None = None - ) -> BrowseMedia: + async def async_browse_media(self, media_content_type: str | None = None, media_content_id: str | None = None) -> BrowseMedia: """Implement the websocket media browsing helper.""" # If your media player has no own media sources to browse, route all browse commands # to the media source integration. diff --git a/custom_components/hass_agent/notify.py b/custom_components/hass_agent/notify.py index d084b9a..f1e8656 100644 --- a/custom_components/hass_agent/notify.py +++ b/custom_components/hass_agent/notify.py @@ -24,7 +24,10 @@ CONF_URL, ) -from .const import CONF_DEFAULT_NOTIFICATION_TITLE +from .const import ( + CONF_DEFAULT_NOTIFICATION_TITLE, + CONF_DEVICE_NAME, +) _logger = logging.getLogger(__name__) @@ -36,16 +39,21 @@ def get_service(hass, config, discovery_info=None): entry_id = discovery_info.get(CONF_ID, None) - return HassAgentNotificationService(hass, discovery_info[CONF_NAME], entry_id) + return HassAgentNotificationService( + hass, + discovery_info[CONF_DEVICE_NAME], + discovery_info[CONF_NAME], + entry_id, + ) class HassAgentNotificationService(BaseNotificationService): """Implementation of the HASS Agent notification service""" - def __init__(self, hass, name, entry_id): + def __init__(self, hass, device_name, service_name, entry_id): """Initialize the service.""" - self._service_name = name - self._device_name = name + self._service_name = service_name + self._device_name = device_name self._entry_id = entry_id self._hass = hass @@ -83,9 +91,7 @@ async def async_send_message(self, message: str, **kwargs: Any): elif media_source.is_media_source_id(image): sourced_media = await media_source.async_resolve_media(self.hass, image) - sourced_media = media_source.async_process_play_media_url( - self.hass, sourced_media.url - ) + sourced_media = media_source.async_process_play_media_url(self.hass, sourced_media.url) new_url = sourced_media if new_url is not None: @@ -110,9 +116,7 @@ def send_request(url, data): """Sends the json request""" return requests.post(url, json=data, timeout=10) - response = await self.hass.async_add_executor_job( - send_request, f"{entry.data[CONF_URL]}/notify", payload - ) + response = await self.hass.async_add_executor_job(send_request, f"{entry.data[CONF_URL]}/notify", payload) _logger.debug("Checking result") @@ -169,8 +173,6 @@ def send_request(url, data): response.reason, ) else: - _logger.debug( - "Unknown response %d: %s", response.status_code, response.reason - ) + _logger.debug("Unknown response %d: %s", response.status_code, response.reason) except Exception as ex: _logger.debug("Error sending message: %s", ex) diff --git a/custom_components/hass_agent/repairs.py b/custom_components/hass_agent/repairs.py new file mode 100644 index 0000000..f8cc607 --- /dev/null +++ b/custom_components/hass_agent/repairs.py @@ -0,0 +1,45 @@ +"""Repairs platform for HASS.Agent.""" + +from __future__ import annotations + +from typing import Any + +from homeassistant import data_entry_flow +from homeassistant.components.repairs import RepairsFlow +from homeassistant.core import HomeAssistant +import voluptuous as vol + +from .const import DOMAIN, CONF_DEVICE_NAME + + +class RestartRequiredFixFlow(RepairsFlow): + """Handler for an issue fixing flow.""" + + def __init__(self, issue_id: str, device_name: str) -> None: + self.issue_id = issue_id + self._device_name = device_name + + async def async_step_init(self, user_input: dict[str, str] | None = None) -> data_entry_flow.FlowResult: + """Handle the first step of a fix flow.""" + + return await self.async_step_confirm_restart() + + async def async_step_confirm_restart(self, user_input: dict[str, str] | None = None) -> data_entry_flow.FlowResult: + """Handle the confirm step of a fix flow.""" + if user_input is not None: + await self.hass.services.async_call("homeassistant", "restart") + return self.async_create_entry(title="", data={}) + + return self.async_show_form( + step_id="confirm_restart", + data_schema=vol.Schema({}), + description_placeholders={"name": self._device_name}, + ) + + +async def async_create_fix_flow(hass: HomeAssistant, issue_id: str, data: dict[str, str | int | float | None] | None = None) -> RepairsFlow | None: + """Create flow.""" + if issue_id.startswith("restart_required") and (data is not None): + return RestartRequiredFixFlow(issue_id, str(data[CONF_DEVICE_NAME])) + + return None diff --git a/custom_components/hass_agent/strings.json b/custom_components/hass_agent/strings.json index a3a914a..948ff7d 100644 --- a/custom_components/hass_agent/strings.json +++ b/custom_components/hass_agent/strings.json @@ -1,44 +1,57 @@ { - "config": { - "flow_title": "{name}", - "step": { - "confirm": { - "title": "[%key:common::config_flow::title::confirm%]", - "description": "[%key:common::config_flow::description::confirm%]" - }, - "local_api": { - "title": "[%key:common::config_flow::title::local_api%]", - "description": "[%key:common::config_flow::description::local_api%]", - "data": { - "host": "[%key:common::config_flow::data::host%]", - "port": "[%key:common::config_flow::data::port%]", - "ssl": "[%key:common::config_flow::data::ssl%]" + "config": { + "flow_title": "{name}", + "step": { + "confirm": { + "title": "[%key:common::config_flow::title::confirm%]", + "description": "[%key:common::config_flow::description::confirm%]" + }, + "local_api": { + "title": "[%key:common::config_flow::title::local_api%]", + "description": "[%key:common::config_flow::description::local_api%]", + "data": { + "host": "[%key:common::config_flow::data::host%]", + "port": "[%key:common::config_flow::data::port%]", + "ssl": "[%key:common::config_flow::data::ssl%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } - } }, - "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + "options": { + "step": { + "init": { + "data": { + "default_notification_title": "[%key:common::options::init::default_notification_title%]" + }, + "title": "[%key:common::options::init::title%]" + } + } }, - "abort": { - "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" - } - }, - "options": { - "step": { - "init": { - "data": { - "default_notification_title": "[%key:common::options::init::default_notification_title%]" - }, - "title": "[%key:common::options::init::title%]" + "device_automation": { + "trigger_type": { + "notifications_mqtt": "[%key:component::hass_agent::device_automation::trigger_type::notifications_mqtt%]", + "notifications_event": "[%key:component::hass_agent::device_automation::trigger_type::notifications_event%]" + } + }, + "issues": { + "restart_required": { + "title": "[%key:component::hass_agent::issues::restart_required::title%]", + "fix_flow": { + "step": { + "confirm_restart": { + "title": "[%key:component::hass_agent::issues::restart_required::fix_flow::confirm_restart::title%]", + "description": "[%key:component::hass_agent::issues::restart_required::fix_flow::confirm_restart::description%]" + } + } + } } } - }, - "device_automation": { - "trigger_type": { - "notifications_mqtt": "[%key:component::hass_agent::device_automation::trigger_type::notifications_mqtt%]", - "notifications_event": "[%key:component::hass_agent::device_automation::trigger_type::notifications_event%]" - } - } } diff --git a/custom_components/hass_agent/translations/en.json b/custom_components/hass_agent/translations/en.json index 55c323f..4c620b2 100644 --- a/custom_components/hass_agent/translations/en.json +++ b/custom_components/hass_agent/translations/en.json @@ -35,8 +35,21 @@ }, "device_automation": { "trigger_type": { - "notifications_mqtt": "When a notification with a specific action has been pressed (MQTT)", - "notifications_event": "When a notification with a specific action has been pressed (Event)" + "notifications_mqtt": "When a notification with a specific action has been pressed (MQTT)", + "notifications_event": "When a notification with a specific action has been pressed (Event)" + } + }, + "issues": { + "restart_required": { + "title": "Device renamed - restart required", + "fix_flow": { + "step": { + "confirm_restart": { + "title": "Restart required", + "description": "Device name has been changed, restart of Home Assistant is required to properly reload notify service for {name}, click submit to restart now." + } + } + } } } } From 04d0dbc4562f546ad50c33c209d514a740cb3ddc Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:21:00 +0100 Subject: [PATCH 11/13] Release: 2.1.2 beta1 (#52) Version change + LA02Research reference removal. --- LICENSE | 2 +- custom_components/hass_agent/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 46a6b5c..9bdfa1a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 LAB02 Research +Copyright (c) 2023 HASS.Agent Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/custom_components/hass_agent/manifest.json b/custom_components/hass_agent/manifest.json index 679305f..32c9bc7 100644 --- a/custom_components/hass_agent/manifest.json +++ b/custom_components/hass_agent/manifest.json @@ -18,6 +18,6 @@ "loggers": ["custom_components.hass_agent"], "mqtt": ["hass.agent/devices/#"], "ssdp": [], - "version": "2.1.1", + "version": "2.1.2", "zeroconf": [] } From 04abd41108a7b4859bc269b484325ce9d0800f1b Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Thu, 27 Nov 2025 15:22:41 +0100 Subject: [PATCH 12/13] Fix: quickfix for devices without original name (#53) --- custom_components/hass_agent/media_player.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/custom_components/hass_agent/media_player.py b/custom_components/hass_agent/media_player.py index 82c1b6a..f9c3a56 100644 --- a/custom_components/hass_agent/media_player.py +++ b/custom_components/hass_agent/media_player.py @@ -67,7 +67,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e if device is None: return False - async_add_entities([HassAgentMediaPlayerDevice(entry.unique_id, entry.entry_id, device, entry.data[CONF_ORIGINAL_DEVICE_NAME])]) + original_device_name = entry.data.get(CONF_ORIGINAL_DEVICE_NAME, device.name) + + async_add_entities([HassAgentMediaPlayerDevice(entry.unique_id, entry.entry_id, device, original_device_name)]) return True From 2880542e33d5e7444315f86bcf8f929acf18af8c Mon Sep 17 00:00:00 2001 From: amadeo-alex <68441479+amadeo-alex@users.noreply.github.com> Date: Wed, 14 Jan 2026 08:32:38 +0100 Subject: [PATCH 13/13] Fix: notification service for local API connection not working due to original name introduction (#60) This PR fixes issue reported via #57. --- custom_components/hass_agent/__init__.py | 8 ++++---- custom_components/hass_agent/config_flow.py | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/custom_components/hass_agent/__init__.py b/custom_components/hass_agent/__init__.py index 481b3d7..daca63f 100644 --- a/custom_components/hass_agent/__init__.py +++ b/custom_components/hass_agent/__init__.py @@ -87,6 +87,8 @@ async def handle_apis_changed(hass: HomeAssistant, entry: ConfigEntry, apis): entry.unique_id, ) + original_device_name = entry.data.get(CONF_ORIGINAL_DEVICE_NAME, device.name) + hass.async_create_task( discovery.async_load_platform( hass, @@ -94,10 +96,8 @@ async def handle_apis_changed(hass: HomeAssistant, entry: ConfigEntry, apis): DOMAIN, { CONF_ID: entry.entry_id, - CONF_NAME: entry.data[ # Note(Amadeo): CONF_NAME decides of "nofity." name, needs to be set to the original one - CONF_ORIGINAL_DEVICE_NAME - ], - CONF_DEVICE_NAME: device.name, # Note(Amadeo): since CONF_NAME is used for the old name, we need to pass on the changed name for MQTT notify call + CONF_NAME: original_device_name, # Note(Amadeo): CONF_NAME decides of "nofity." name, needs to be set to the original one + CONF_DEVICE_NAME: device.name, # Note(Amadeo): since CONF_NAME is used for the old name, we need to pass on the changed name for MQTT notify call }, {}, ) diff --git a/custom_components/hass_agent/config_flow.py b/custom_components/hass_agent/config_flow.py index ed3a802..67d3d1a 100644 --- a/custom_components/hass_agent/config_flow.py +++ b/custom_components/hass_agent/config_flow.py @@ -143,7 +143,10 @@ def get_device_info(): except Exception: errors["base"] = "cannot_connect" else: - await self.async_set_unique_id(response_json["serial_number"]) + entry = await self.async_set_unique_id(response_json["serial_number"]) + if not entry or (CONF_ORIGINAL_DEVICE_NAME not in entry.data): + self._data[CONF_ORIGINAL_DEVICE_NAME] = response_json["device"]["name"] + self._abort_if_unique_id_configured() return self.async_create_entry(