diff --git a/homeassistant/components/tractive/__init__.py b/homeassistant/components/tractive/__init__.py index a9c4931a295..483fb19aab1 100644 --- a/homeassistant/components/tractive/__init__.py +++ b/homeassistant/components/tractive/__init__.py @@ -5,10 +5,11 @@ from __future__ import annotations import asyncio from dataclasses import dataclass import logging -from typing import Any, cast +from typing import Any import aiotractive +from homeassistant.components.sensor import DOMAIN as SENSOR_PLATFORM from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_BATTERY_CHARGING, @@ -20,21 +21,20 @@ from homeassistant.const import ( ) from homeassistant.core import Event, HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import ( - ATTR_ACTIVITY_LABEL, - ATTR_CALORIES, ATTR_DAILY_GOAL, ATTR_MINUTES_ACTIVE, ATTR_MINUTES_DAY_SLEEP, ATTR_MINUTES_NIGHT_SLEEP, ATTR_MINUTES_REST, ATTR_POWER_SAVING, - ATTR_SLEEP_LABEL, ATTR_TRACKER_STATE, CLIENT_ID, + DOMAIN, RECONNECT_INTERVAL, SERVER_UNAVAILABLE, SWITCH_KEY_MAP, @@ -42,7 +42,6 @@ from .const import ( TRACKER_HEALTH_OVERVIEW_UPDATED, TRACKER_POSITION_UPDATED, TRACKER_SWITCH_STATUS_UPDATED, - TRACKER_WELLNESS_STATUS_UPDATED, ) PLATFORMS = [ @@ -129,6 +128,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: TractiveConfigEntry) -> ) entry.async_on_unload(tractive.unsubscribe) + # Remove sensor entities that are no longer supported by the Tractive API + entity_reg = er.async_get(hass) + for item in filtered_trackables: + for key in ("activity_label", "calories", "sleep_label"): + if entity_id := entity_reg.async_get_entity_id( + SENSOR_PLATFORM, DOMAIN, f"{item.trackable['_id']}_{key}" + ): + entity_reg.async_remove(entity_id) + return True @@ -207,19 +215,6 @@ class TractiveClient: return not self._listen_task.cancelled() - async def trackable_objects( - self, - ) -> list[aiotractive.trackable_object.TrackableObject]: - """Get list of trackable objects.""" - return cast( - list[aiotractive.trackable_object.TrackableObject], - await self._client.trackable_objects(), - ) - - def tracker(self, tracker_id: str) -> aiotractive.tracker.Tracker: - """Get tracker by id.""" - return self._client.tracker(tracker_id) - def subscribe(self) -> None: """Start event listener coroutine.""" self._listen_task = asyncio.create_task(self._listen()) @@ -242,9 +237,6 @@ class TractiveClient: if event["message"] == "health_overview": self.send_health_overview_update(event) continue - if event["message"] == "wellness_overview": - self._send_wellness_update(event) - continue if ( "hardware" in event and self._last_hw_time != event["hardware"]["time"] @@ -312,26 +304,6 @@ class TractiveClient: TRACKER_SWITCH_STATUS_UPDATED, event["tracker_id"], payload ) - def _send_wellness_update(self, event: dict[str, Any]) -> None: - sleep_day = None - sleep_night = None - if isinstance(event["sleep"], dict): - sleep_day = event["sleep"]["minutes_day_sleep"] - sleep_night = event["sleep"]["minutes_night_sleep"] - payload = { - ATTR_ACTIVITY_LABEL: event["wellness"].get("activity_label"), - ATTR_CALORIES: event["activity"]["calories"], - ATTR_DAILY_GOAL: event["activity"]["minutes_goal"], - ATTR_MINUTES_ACTIVE: event["activity"]["minutes_active"], - ATTR_MINUTES_DAY_SLEEP: sleep_day, - ATTR_MINUTES_NIGHT_SLEEP: sleep_night, - ATTR_MINUTES_REST: event["activity"]["minutes_rest"], - ATTR_SLEEP_LABEL: event["wellness"].get("sleep_label"), - } - self._dispatch_tracker_event( - TRACKER_WELLNESS_STATUS_UPDATED, event["pet_id"], payload - ) - def send_health_overview_update(self, event: dict[str, Any]) -> None: """Handle health_overview events from Tractive API.""" # The health_overview response can be at root level or wrapped in 'content' diff --git a/homeassistant/components/tractive/const.py b/homeassistant/components/tractive/const.py index 45c357c5446..3256dc21874 100644 --- a/homeassistant/components/tractive/const.py +++ b/homeassistant/components/tractive/const.py @@ -6,9 +6,7 @@ DOMAIN = "tractive" RECONNECT_INTERVAL = timedelta(seconds=10) -ATTR_ACTIVITY_LABEL = "activity_label" ATTR_BUZZER = "buzzer" -ATTR_CALORIES = "calories" ATTR_DAILY_GOAL = "daily_goal" ATTR_LED = "led" ATTR_LIVE_TRACKING = "live_tracking" @@ -17,7 +15,6 @@ ATTR_MINUTES_DAY_SLEEP = "minutes_day_sleep" ATTR_MINUTES_NIGHT_SLEEP = "minutes_night_sleep" ATTR_MINUTES_REST = "minutes_rest" ATTR_POWER_SAVING = "power_saving" -ATTR_SLEEP_LABEL = "sleep_label" ATTR_TRACKER_STATE = "tracker_state" # This client ID was issued by Tractive specifically for Home Assistant. @@ -27,7 +24,6 @@ CLIENT_ID = "625e5349c3c3b41c28a669f1" TRACKER_HARDWARE_STATUS_UPDATED = f"{DOMAIN}_tracker_hardware_status_updated" TRACKER_POSITION_UPDATED = f"{DOMAIN}_tracker_position_updated" TRACKER_SWITCH_STATUS_UPDATED = f"{DOMAIN}_tracker_switch_updated" -TRACKER_WELLNESS_STATUS_UPDATED = f"{DOMAIN}_tracker_wellness_updated" TRACKER_HEALTH_OVERVIEW_UPDATED = f"{DOMAIN}_tracker_health_overview_updated" SERVER_UNAVAILABLE = f"{DOMAIN}_server_unavailable" diff --git a/homeassistant/components/tractive/sensor.py b/homeassistant/components/tractive/sensor.py index 8e79d4d48da..09d7ee5f9c0 100644 --- a/homeassistant/components/tractive/sensor.py +++ b/homeassistant/components/tractive/sensor.py @@ -16,7 +16,6 @@ from homeassistant.const import ( ATTR_BATTERY_LEVEL, PERCENTAGE, EntityCategory, - UnitOfEnergy, UnitOfTime, ) from homeassistant.core import HomeAssistant, callback @@ -25,18 +24,14 @@ from homeassistant.helpers.typing import StateType from . import Trackables, TractiveClient, TractiveConfigEntry from .const import ( - ATTR_ACTIVITY_LABEL, - ATTR_CALORIES, ATTR_DAILY_GOAL, ATTR_MINUTES_ACTIVE, ATTR_MINUTES_DAY_SLEEP, ATTR_MINUTES_NIGHT_SLEEP, ATTR_MINUTES_REST, - ATTR_SLEEP_LABEL, ATTR_TRACKER_STATE, TRACKER_HARDWARE_STATUS_UPDATED, TRACKER_HEALTH_OVERVIEW_UPDATED, - TRACKER_WELLNESS_STATUS_UPDATED, ) from .entity import TractiveEntity @@ -126,13 +121,6 @@ SENSOR_TYPES: tuple[TractiveSensorEntityDescription, ...] = ( signal_prefix=TRACKER_HEALTH_OVERVIEW_UPDATED, state_class=SensorStateClass.TOTAL, ), - TractiveSensorEntityDescription( - key=ATTR_CALORIES, - translation_key="calories", - native_unit_of_measurement=UnitOfEnergy.KILO_CALORIE, - signal_prefix=TRACKER_WELLNESS_STATUS_UPDATED, - state_class=SensorStateClass.TOTAL, - ), TractiveSensorEntityDescription( key=ATTR_DAILY_GOAL, translation_key="daily_goal", @@ -153,30 +141,6 @@ SENSOR_TYPES: tuple[TractiveSensorEntityDescription, ...] = ( signal_prefix=TRACKER_HEALTH_OVERVIEW_UPDATED, state_class=SensorStateClass.TOTAL, ), - TractiveSensorEntityDescription( - key=ATTR_SLEEP_LABEL, - translation_key="sleep", - signal_prefix=TRACKER_WELLNESS_STATUS_UPDATED, - value_fn=lambda state: state.lower() if isinstance(state, str) else state, - device_class=SensorDeviceClass.ENUM, - options=[ - "good", - "low", - "ok", - ], - ), - TractiveSensorEntityDescription( - key=ATTR_ACTIVITY_LABEL, - translation_key="activity", - signal_prefix=TRACKER_WELLNESS_STATUS_UPDATED, - value_fn=lambda state: state.lower() if isinstance(state, str) else state, - device_class=SensorDeviceClass.ENUM, - options=[ - "good", - "low", - "ok", - ], - ), ) diff --git a/homeassistant/components/tractive/strings.json b/homeassistant/components/tractive/strings.json index a070996e8db..21720465bb3 100644 --- a/homeassistant/components/tractive/strings.json +++ b/homeassistant/components/tractive/strings.json @@ -33,20 +33,9 @@ } }, "sensor": { - "activity": { - "name": "Activity", - "state": { - "good": "Good", - "low": "Low", - "ok": "OK" - } - }, "activity_time": { "name": "Activity time" }, - "calories": { - "name": "Calories burned" - }, "daily_goal": { "name": "Daily goal" }, @@ -59,14 +48,6 @@ "rest_time": { "name": "Rest time" }, - "sleep": { - "name": "Sleep", - "state": { - "good": "[%key:component::tractive::entity::sensor::activity::state::good%]", - "low": "[%key:component::tractive::entity::sensor::activity::state::low%]", - "ok": "[%key:component::tractive::entity::sensor::activity::state::ok%]" - } - }, "tracker_battery_level": { "name": "Tracker battery" }, diff --git a/tests/components/tractive/conftest.py b/tests/components/tractive/conftest.py index 8354c14fb6f..1a9a865c1c1 100644 --- a/tests/components/tractive/conftest.py +++ b/tests/components/tractive/conftest.py @@ -34,24 +34,6 @@ def mock_tractive_client() -> Generator[AsyncMock]: } entry.runtime_data.client._send_hardware_update(event) - def send_wellness_event( - entry: MockConfigEntry, event: dict[str, Any] | None = None - ): - """Send wellness event.""" - if event is None: - event = { - "pet_id": "pet_id_123", - "sleep": {"minutes_day_sleep": 100, "minutes_night_sleep": 300}, - "wellness": {"activity_label": "ok", "sleep_label": "good"}, - "activity": { - "calories": 999, - "minutes_goal": 200, - "minutes_active": 150, - "minutes_rest": 122, - }, - } - entry.runtime_data.client._send_wellness_update(event) - def send_health_overview_event( entry: MockConfigEntry, event: dict[str, Any] | None = None ): @@ -144,7 +126,6 @@ def mock_tractive_client() -> Generator[AsyncMock]: ) client.send_hardware_event = send_hardware_event - client.send_wellness_event = send_wellness_event client.send_health_overview_event = send_health_overview_event client.send_position_event = send_position_event client.send_switch_event = send_switch_event diff --git a/tests/components/tractive/snapshots/test_sensor.ambr b/tests/components/tractive/snapshots/test_sensor.ambr index 158f08ba887..8324aaf7e01 100644 --- a/tests/components/tractive/snapshots/test_sensor.ambr +++ b/tests/components/tractive/snapshots/test_sensor.ambr @@ -1,65 +1,4 @@ # serializer version: 1 -# name: test_sensor[sensor.test_pet_activity-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'options': list([ - 'good', - 'low', - 'ok', - ]), - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_pet_activity', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Activity', - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Activity', - 'platform': 'tractive', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'activity', - 'unique_id': 'pet_id_123_activity_label', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensor[sensor.test_pet_activity-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'enum', - 'friendly_name': 'Test Pet Activity', - 'options': list([ - 'good', - 'low', - 'ok', - ]), - }), - 'context': , - 'entity_id': 'sensor.test_pet_activity', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'ok', - }) -# --- # name: test_sensor[sensor.test_pet_activity_time-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -113,59 +52,6 @@ 'state': '150', }) # --- -# name: test_sensor[sensor.test_pet_calories_burned-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_pet_calories_burned', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Calories burned', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Calories burned', - 'platform': 'tractive', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'calories', - 'unique_id': 'pet_id_123_calories', - 'unit_of_measurement': , - }) -# --- -# name: test_sensor[sensor.test_pet_calories_burned-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Test Pet Calories burned', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.test_pet_calories_burned', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '999', - }) -# --- # name: test_sensor[sensor.test_pet_daily_goal-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -375,67 +261,6 @@ 'state': '122', }) # --- -# name: test_sensor[sensor.test_pet_sleep-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'options': list([ - 'good', - 'low', - 'ok', - ]), - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_pet_sleep', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Sleep', - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Sleep', - 'platform': 'tractive', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'sleep', - 'unique_id': 'pet_id_123_sleep_label', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensor[sensor.test_pet_sleep-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'enum', - 'friendly_name': 'Test Pet Sleep', - 'options': list([ - 'good', - 'low', - 'ok', - ]), - }), - 'context': , - 'entity_id': 'sensor.test_pet_sleep', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'good', - }) -# --- # name: test_sensor[sensor.test_pet_tracker_battery-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tractive/test_init.py b/tests/components/tractive/test_init.py index 24733868aee..9b84168eec5 100644 --- a/tests/components/tractive/test_init.py +++ b/tests/components/tractive/test_init.py @@ -6,6 +6,7 @@ from unittest.mock import AsyncMock, patch from aiotractive.exceptions import TractiveError, UnauthorizedError import pytest +from homeassistant.components.sensor import DOMAIN as SENSOR_PLATFORM from homeassistant.components.tractive.const import ( ATTR_DAILY_GOAL, ATTR_MINUTES_ACTIVE, @@ -17,6 +18,7 @@ from homeassistant.components.tractive.const import ( from homeassistant.config_entries import ConfigEntryState from homeassistant.const import EVENT_HOMEASSISTANT_STOP, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from . import init_integration @@ -216,3 +218,28 @@ async def test_missing_activity_data( payload = async_dispatcher_send_mock.mock_calls[0][1][2] assert payload[ATTR_DAILY_GOAL] is None assert payload[ATTR_MINUTES_ACTIVE] is None + + +@pytest.mark.parametrize("sensor", ["activity_label", "calories", "sleep_label"]) +async def test_remove_unsupported_sensor_entity( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, + sensor: str, +) -> None: + """Test removing unsupported sensor entity.""" + entity_id = f"sensor.test_pet_{sensor}" + mock_config_entry.add_to_hass(hass) + + entity_registry.async_get_or_create( + SENSOR_PLATFORM, + DOMAIN, + f"pet_id_123_{sensor}", + suggested_object_id=entity_id.rsplit(".", maxsplit=1)[-1], + config_entry=mock_config_entry, + ) + + await init_integration(hass, mock_config_entry) + + assert entity_registry.async_get(entity_id) is None diff --git a/tests/components/tractive/test_sensor.py b/tests/components/tractive/test_sensor.py index 53bd5558505..92dac224a0c 100644 --- a/tests/components/tractive/test_sensor.py +++ b/tests/components/tractive/test_sensor.py @@ -25,7 +25,6 @@ async def test_sensor( await init_integration(hass, mock_config_entry) mock_tractive_client.send_hardware_event(mock_config_entry) - mock_tractive_client.send_wellness_event(mock_config_entry) mock_tractive_client.send_health_overview_event(mock_config_entry) await hass.async_block_till_done() await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)