diff --git a/CHANGELOG.md b/CHANGELOG.md index 756a020..c599ed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.2.1 + +- fixed the GPS stream listener blocking Home Assistant startup wrap-up by using a config-entry background task, as reported in [#16](https://github.com/openhop-dev/openHop-HA-Integration/issues/16) + ## 1.2.0 - added aggregate parent radio status and problem entities with a boolean or unknown radio error attribute without exposing raw error text or inferring per-radio health from api connectivity diff --git a/custom_components/pymc_repeater/coordinator.py b/custom_components/pymc_repeater/coordinator.py index 389510a..88fc045 100644 --- a/custom_components/pymc_repeater/coordinator.py +++ b/custom_components/pymc_repeater/coordinator.py @@ -71,7 +71,11 @@ async def async_start_runtime(self) -> None: self.hass, self._async_hourly_update_check, minute=1, second=0 ) if self._gps_stream_task is None: - self._gps_stream_task = self.hass.async_create_task(self._async_gps_stream_loop()) + self._gps_stream_task = self.config_entry.async_create_background_task( + self.hass, + self._async_gps_stream_loop(), + "pymc_repeater_gps_stream", + ) async def async_stop_runtime(self) -> None: """Stop background runtime tasks.""" diff --git a/custom_components/pymc_repeater/manifest.json b/custom_components/pymc_repeater/manifest.json index dae406f..944b868 100644 --- a/custom_components/pymc_repeater/manifest.json +++ b/custom_components/pymc_repeater/manifest.json @@ -10,5 +10,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/openhop-dev/openHop-HA-Integration/issues", "requirements": [], - "version": "1.2.0" + "version": "1.2.1" } diff --git a/tests/test_dev_api_alignment.py b/tests/test_dev_api_alignment.py index a906949..d0f5b5a 100644 --- a/tests/test_dev_api_alignment.py +++ b/tests/test_dev_api_alignment.py @@ -358,14 +358,16 @@ def test_integration_polling_is_configurable_and_defaults_to_15_seconds(self) -> self.assertIn("async_update_listeners()", coordinator) self.assertNotIn("async_set_updated_data", coordinator) - def test_release_metadata_and_changelog_are_v1_2_0(self) -> None: + def test_release_metadata_and_changelog_are_v1_2_1(self) -> None: manifest = json.loads((COMPONENT / "manifest.json").read_text(encoding="utf-8")) changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8") workflow = (ROOT / ".github/workflows/python-smoke.yaml").read_text( encoding="utf-8" ) - self.assertEqual(manifest["version"], "1.2.0") + self.assertEqual(manifest["version"], "1.2.1") + self.assertIn("## 1.2.1", changelog) + self.assertIn("[#16](https://github.com/openhop-dev/openHop-HA-Integration/issues/16)", changelog) self.assertIn("## 1.1.6", changelog) self.assertIn("## Unreleased", changelog) self.assertIn("## 1.2.0", changelog) diff --git a/tests/test_hourly_update_check.py b/tests/test_hourly_update_check.py index 5e71eaa..c3a5fcd 100644 --- a/tests/test_hourly_update_check.py +++ b/tests/test_hourly_update_check.py @@ -70,8 +70,27 @@ async def request(method, path, **kwargs): async def fetch_all(): return {'update_status': {'state': 'checking'}} api.async_fetch_all = fetch_all - self.hass = SimpleNamespace(async_create_task=create_task) - entry = SimpleNamespace(options={}, entry_id='test', async_on_unload=self.unload_callbacks.append) + self.tracked_tasks = [] + self.background_tasks = [] + + def tracked_task(coro): + task = create_task(coro) + self.tracked_tasks.append(task) + return task + + def background_task(hass, coro, name): + self.assertIs(hass, self.hass) + task = create_task(coro) + task.set_name(name) + self.background_tasks.append(task) + return task + + self.hass = SimpleNamespace(async_create_task=tracked_task) + entry = SimpleNamespace( + options={}, entry_id='test', + async_on_unload=self.unload_callbacks.append, + async_create_background_task=background_task, + ) ns.update(DataUpdateCoordinator=FakeCoordinator, asyncio=asyncio, contextlib=contextlib, datetime=datetime, timedelta=timedelta, timezone=timezone, json=json, _LOGGER=logging.getLogger('hourly_test'), @@ -117,6 +136,25 @@ async def test_local_hourly_registration_no_startup_check_or_duplicate(self): self.assertTrue(any(isinstance(n, ast.ImportFrom) and n.module == 'homeassistant.helpers.event' and any(a.name == 'async_track_time_change' for a in n.names) for n in imports.body)) + async def test_gps_listener_is_background_work_and_stops_before_restart(self): + """Issue #16: a persistent GPS listener must not enter startup work.""" + await self.start() + await self.coordinator.async_start_runtime() + await asyncio.sleep(0) + task = self.coordinator._gps_stream_task + self.assertFalse(task.done()) + self.assertEqual(self.tracked_tasks, []) + self.assertEqual(self.background_tasks, [task]) + self.assertEqual(task.get_name(), 'pymc_repeater_gps_stream') + await self.coordinator.async_stop_runtime() + self.assertTrue(task.cancelled()) + self.assertIsNone(self.coordinator._gps_stream_task) + await self.coordinator.async_stop_runtime() + await self.start() + self.assertIsNot(self.coordinator._gps_stream_task, task) + self.assertEqual(len(self.background_tasks), 2) + self.assertEqual(self.tracked_tasks, []) + async def test_hourly_check_is_not_forced_then_refreshes_cached_status(self): action = await self.start() await action(datetime(2026, 9, 8, 12, 1, tzinfo=timezone.utc))