From 79bcca2853827adbdd36972c2f7a945bb1dd4e76 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 17 Jul 2023 07:02:42 +0000 Subject: [PATCH] Add wellness sensors to Tractive integration (#96719) * Add sleep sensors * Add minutes rest sensor * Add calories sensor * Add state_class to entity descriptions --- homeassistant/components/tractive/__init__.py | 19 ++++++ homeassistant/components/tractive/const.py | 7 +- homeassistant/components/tractive/sensor.py | 68 ++++++++++++++++++- .../components/tractive/strings.json | 12 ++++ 4 files changed, 102 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tractive/__init__.py b/homeassistant/components/tractive/__init__.py index 96fc718c67d..351b39f61e7 100644 --- a/homeassistant/components/tractive/__init__.py +++ b/homeassistant/components/tractive/__init__.py @@ -24,10 +24,14 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import ( ATTR_BUZZER, + ATTR_CALORIES, ATTR_DAILY_GOAL, ATTR_LED, ATTR_LIVE_TRACKING, ATTR_MINUTES_ACTIVE, + ATTR_MINUTES_DAY_SLEEP, + ATTR_MINUTES_NIGHT_SLEEP, + ATTR_MINUTES_REST, ATTR_TRACKER_STATE, CLIENT, CLIENT_ID, @@ -38,6 +42,7 @@ from .const import ( TRACKER_ACTIVITY_STATUS_UPDATED, TRACKER_HARDWARE_STATUS_UPDATED, TRACKER_POSITION_UPDATED, + TRACKER_WELLNESS_STATUS_UPDATED, ) PLATFORMS = [ @@ -202,6 +207,9 @@ class TractiveClient: if event["message"] == "activity_update": self._send_activity_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"] @@ -264,6 +272,17 @@ class TractiveClient: TRACKER_ACTIVITY_STATUS_UPDATED, event["pet_id"], payload ) + def _send_wellness_update(self, event: dict[str, Any]) -> None: + payload = { + ATTR_CALORIES: event["activity"]["calories"], + ATTR_MINUTES_DAY_SLEEP: event["sleep"]["minutes_day_sleep"], + ATTR_MINUTES_NIGHT_SLEEP: event["sleep"]["minutes_night_sleep"], + ATTR_MINUTES_REST: event["activity"]["minutes_rest"], + } + self._dispatch_tracker_event( + TRACKER_WELLNESS_STATUS_UPDATED, event["pet_id"], payload + ) + def _send_position_update(self, event: dict[str, Any]) -> None: payload = { "latitude": event["position"]["latlong"][0], diff --git a/homeassistant/components/tractive/const.py b/homeassistant/components/tractive/const.py index a87e22c505d..81936ae5d80 100644 --- a/homeassistant/components/tractive/const.py +++ b/homeassistant/components/tractive/const.py @@ -6,11 +6,15 @@ DOMAIN = "tractive" RECONNECT_INTERVAL = timedelta(seconds=10) -ATTR_DAILY_GOAL = "daily_goal" ATTR_BUZZER = "buzzer" +ATTR_CALORIES = "calories" +ATTR_DAILY_GOAL = "daily_goal" ATTR_LED = "led" ATTR_LIVE_TRACKING = "live_tracking" ATTR_MINUTES_ACTIVE = "minutes_active" +ATTR_MINUTES_DAY_SLEEP = "minutes_day_sleep" +ATTR_MINUTES_NIGHT_SLEEP = "minutes_night_sleep" +ATTR_MINUTES_REST = "minutes_rest" ATTR_TRACKER_STATE = "tracker_state" # This client ID was issued by Tractive specifically for Home Assistant. @@ -23,5 +27,6 @@ TRACKABLES = "trackables" TRACKER_HARDWARE_STATUS_UPDATED = f"{DOMAIN}_tracker_hardware_status_updated" TRACKER_POSITION_UPDATED = f"{DOMAIN}_tracker_position_updated" TRACKER_ACTIVITY_STATUS_UPDATED = f"{DOMAIN}_tracker_activity_updated" +TRACKER_WELLNESS_STATUS_UPDATED = f"{DOMAIN}_tracker_wellness_updated" SERVER_UNAVAILABLE = f"{DOMAIN}_server_unavailable" diff --git a/homeassistant/components/tractive/sensor.py b/homeassistant/components/tractive/sensor.py index 24439b489c8..8f56d1a2e9c 100644 --- a/homeassistant/components/tractive/sensor.py +++ b/homeassistant/components/tractive/sensor.py @@ -8,6 +8,7 @@ from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorEntityDescription, + SensorStateClass, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -22,8 +23,12 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import Trackables from .const import ( + ATTR_CALORIES, ATTR_DAILY_GOAL, ATTR_MINUTES_ACTIVE, + ATTR_MINUTES_DAY_SLEEP, + ATTR_MINUTES_NIGHT_SLEEP, + ATTR_MINUTES_REST, ATTR_TRACKER_STATE, CLIENT, DOMAIN, @@ -31,6 +36,7 @@ from .const import ( TRACKABLES, TRACKER_ACTIVITY_STATUS_UPDATED, TRACKER_HARDWARE_STATUS_UPDATED, + TRACKER_WELLNESS_STATUS_UPDATED, ) from .entity import TractiveEntity @@ -107,8 +113,8 @@ class TractiveActivitySensor(TractiveSensor): """Tractive active sensor.""" @callback - def handle_activity_status_update(self, event: dict[str, Any]) -> None: - """Handle activity status update.""" + def handle_status_update(self, event: dict[str, Any]) -> None: + """Handle status update.""" self._attr_native_value = event[self.entity_description.key] self._attr_available = True self.async_write_ha_state() @@ -120,7 +126,30 @@ class TractiveActivitySensor(TractiveSensor): async_dispatcher_connect( self.hass, f"{TRACKER_ACTIVITY_STATUS_UPDATED}-{self._trackable['_id']}", - self.handle_activity_status_update, + self.handle_status_update, + ) + ) + + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{SERVER_UNAVAILABLE}-{self._user_id}", + self.handle_server_unavailable, + ) + ) + + +class TractiveWellnessSensor(TractiveActivitySensor): + """Tractive wellness sensor.""" + + async def async_added_to_hass(self) -> None: + """Handle entity which will be added.""" + + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{TRACKER_WELLNESS_STATUS_UPDATED}-{self._trackable['_id']}", + self.handle_status_update, ) ) @@ -155,6 +184,23 @@ SENSOR_TYPES: tuple[TractiveSensorEntityDescription, ...] = ( icon="mdi:clock-time-eight-outline", native_unit_of_measurement=UnitOfTime.MINUTES, entity_class=TractiveActivitySensor, + state_class=SensorStateClass.TOTAL, + ), + TractiveSensorEntityDescription( + key=ATTR_MINUTES_REST, + translation_key="minutes_rest", + icon="mdi:clock-time-eight-outline", + native_unit_of_measurement=UnitOfTime.MINUTES, + entity_class=TractiveWellnessSensor, + state_class=SensorStateClass.TOTAL, + ), + TractiveSensorEntityDescription( + key=ATTR_CALORIES, + translation_key="calories", + icon="mdi:fire", + native_unit_of_measurement="kcal", + entity_class=TractiveWellnessSensor, + state_class=SensorStateClass.TOTAL, ), TractiveSensorEntityDescription( key=ATTR_DAILY_GOAL, @@ -163,6 +209,22 @@ SENSOR_TYPES: tuple[TractiveSensorEntityDescription, ...] = ( native_unit_of_measurement=UnitOfTime.MINUTES, entity_class=TractiveActivitySensor, ), + TractiveSensorEntityDescription( + key=ATTR_MINUTES_DAY_SLEEP, + translation_key="minutes_day_sleep", + icon="mdi:sleep", + native_unit_of_measurement=UnitOfTime.MINUTES, + entity_class=TractiveWellnessSensor, + state_class=SensorStateClass.TOTAL, + ), + TractiveSensorEntityDescription( + key=ATTR_MINUTES_NIGHT_SLEEP, + translation_key="minutes_night_sleep", + icon="mdi:sleep", + native_unit_of_measurement=UnitOfTime.MINUTES, + entity_class=TractiveWellnessSensor, + state_class=SensorStateClass.TOTAL, + ), ) diff --git a/homeassistant/components/tractive/strings.json b/homeassistant/components/tractive/strings.json index d5aee51ed61..44b0a497881 100644 --- a/homeassistant/components/tractive/strings.json +++ b/homeassistant/components/tractive/strings.json @@ -30,12 +30,24 @@ } }, "sensor": { + "calories": { + "name": "Calories burned" + }, "daily_goal": { "name": "Daily goal" }, "minutes_active": { "name": "Minutes active" }, + "minutes_day_sleep": { + "name": "Day sleep" + }, + "minutes_night_sleep": { + "name": "Night sleep" + }, + "minutes_rest": { + "name": "Minutes rest" + }, "tracker_battery_level": { "name": "Tracker battery" },