diff --git a/custom_components/garmin_connect/__init__.py b/custom_components/garmin_connect/__init__.py index 2caa67e..c8a80fa 100644 --- a/custom_components/garmin_connect/__init__.py +++ b/custom_components/garmin_connect/__init__.py @@ -1,6 +1,8 @@ """The Garmin Connect integration.""" from datetime import date import logging +import asyncio +from collections.abc import Awaitable from garminconnect import ( Garmin, @@ -97,6 +99,16 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator): self._api.get_body_composition, date.today().isoformat() ) alarms = await self.hass.async_add_executor_job(self._api.get_device_alarms) + gear = await self.hass.async_add_executor_job( + self._api.get_gear, summary["userProfileId"] + ) + tasks: list[Awaitable] = [ + self.hass.async_add_executor_job( + self._api.get_gear_stats, gear_item["uuid"] + ) + for gear_item in gear + ] + gear_stats = await asyncio.gather(*tasks) except ( GarminConnectAuthenticationError, GarminConnectTooManyRequestsError, @@ -111,4 +123,6 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator): **summary, **body["totalAverage"], "nextAlarm": alarms, + "gear": gear, + "gear_stats": gear_stats, } diff --git a/custom_components/garmin_connect/const.py b/custom_components/garmin_connect/const.py index b2c25d4..9781d4c 100644 --- a/custom_components/garmin_connect/const.py +++ b/custom_components/garmin_connect/const.py @@ -354,3 +354,10 @@ GARMIN_ENTITY_LIST = { "metabolicAge": ["Metabolic Age", TIME_YEARS, "mdi:calendar-heart", None, False], "nextAlarm": ["Next Alarm Time", None, "mdi:alarm", None, True], } + +GEAR_ICONS = { + "Shoes": "mdi:shoe-sneaker", + "Bike": "mdi:bike", + "Other": "mdi:basketball", + "Golf Clubs": "mdi:golf", +} diff --git a/custom_components/garmin_connect/sensor.py b/custom_components/garmin_connect/sensor.py index f1bcccd..c8a522c 100644 --- a/custom_components/garmin_connect/sensor.py +++ b/custom_components/garmin_connect/sensor.py @@ -2,10 +2,16 @@ from __future__ import annotations import logging +from operator import itemgetter from homeassistant.components.sensor import SensorEntity, SensorStateClass from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_ATTRIBUTION, CONF_ID, DEVICE_CLASS_TIMESTAMP +from homeassistant.const import ( + ATTR_ATTRIBUTION, + CONF_ID, + DEVICE_CLASS_TIMESTAMP, + LENGTH_KILOMETERS, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import ( @@ -18,6 +24,7 @@ from .const import ( DATA_COORDINATOR, DOMAIN as GARMIN_DOMAIN, GARMIN_ENTITY_LIST, + GEAR_ICONS, ) _LOGGER = logging.getLogger(__name__) @@ -60,6 +67,19 @@ async def async_setup_entry( ) ) + for gear_item in coordinator.data["gear"]: + entities.append( + GarminConnectGearSensor( + coordinator, + unique_id, + gear_item["uuid"], + gear_item["gearTypeName"], + gear_item["displayName"], + None, + True, + ) + ) + async_add_entities(entities) @@ -155,3 +175,100 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity): and self.coordinator.data and self._type in self.coordinator.data ) + + +class GarminConnectGearSensor(CoordinatorEntity, SensorEntity): + """Representation of a Garmin Connect Sensor.""" + + def __init__( + self, + coordinator, + unique_id, + uuid, + sensor_type, + name, + device_class: None, + enabled_default: bool = True, + ): + """Initialize a Garmin Connect sensor.""" + super().__init__(coordinator) + + self._unique_id = unique_id + self._type = sensor_type + self._uuid = uuid + self._device_class = device_class + self._enabled_default = enabled_default + + self._attr_name = name + self._attr_device_class = self._device_class + self._attr_icon = GEAR_ICONS[sensor_type] + self._attr_native_unit_of_measurement = LENGTH_KILOMETERS + self._attr_unique_id = f"{self._unique_id}_{self._uuid}" + self._attr_state_class = SensorStateClass.TOTAL + + @property + def native_value(self): + """Return the state of the sensor.""" + if not self.coordinator.data or not self.get_stats(): + return None + + value = self.get_stats()["totalDistance"] + return round(value / 1000, 2) + + @property + def extra_state_attributes(self): + """Return attributes for sensor.""" + gear = self.get_gear() + stats = self.get_stats() + + if not self.coordinator.data or not gear or not stats: + return {} + + attributes = { + "last_synced": self.coordinator.data["lastSyncTimestampGMT"], + "total_activities": stats["totalActivities"], + "create_date": stats["createDate"], + "update_date": stats["updateDate"], + "date_begin": gear["dateBegin"], + "date_end": gear["dateEnd"], + "gear_make_name": gear["gearMakeName"], + "gear_model_name": gear["gearModelName"], + "gear_status_name": gear["gearStatusName"], + "custom_make_model": gear["customMakeModel"], + "maximum_meters": gear["maximumMeters"], + } + return attributes + + @property + def device_info(self) -> DeviceInfo: + """Return device information.""" + return { + "identifiers": {(GARMIN_DOMAIN, self._unique_id)}, + "name": "Garmin Connect", + "manufacturer": "Garmin Connect", + } + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return self._enabled_default + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return ( + super().available + and self.coordinator.data + and self.get_gear() + # and any(g["uuid"] == self._uuid for g in self.coordinator.data["gear"]) + ) + + def get_stats(self): + for gear_stats_item in self.coordinator.data["gear_stats"]: + if gear_stats_item["uuid"] == self._uuid: + return gear_stats_item + + def get_gear(self): + for gear_item in self.coordinator.data["gear"]: + if gear_item["uuid"] == self._uuid: + return gear_item