mirror of
https://github.com/cyberjunky/home-assistant-garmin_connect.git
synced 2026-01-09 12:57:58 -05:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a14a7a108 | ||
|
|
5f9aeb4914 | ||
|
|
5934224198 | ||
|
|
fbbbf92728 | ||
|
|
cd3190a799 | ||
|
|
4015141d23 | ||
|
|
91cdd4ad77 | ||
|
|
cdff89f2ca | ||
|
|
689df3de94 | ||
|
|
8c16842247 | ||
|
|
fa0e60648f | ||
|
|
5a8fd38741 | ||
|
|
f16f96e5de | ||
|
|
25944ba0fe | ||
|
|
d3fa73c5a1 | ||
|
|
252f67ff7f | ||
|
|
b366807571 | ||
|
|
437aae6d34 | ||
|
|
51ccb196c0 | ||
|
|
8de123eff2 | ||
|
|
d4e9a4edc6 | ||
|
|
5a5b42a560 | ||
|
|
e2deaed42b | ||
|
|
78b6641506 | ||
|
|
29f0832e8b | ||
|
|
07473ef701 | ||
|
|
b8156a6a7d | ||
|
|
7546a40012 | ||
|
|
e6f7947e2c | ||
|
|
dda115539f | ||
|
|
304cd1f227 | ||
|
|
6dc64df4d9 | ||
|
|
c3af1cc392 | ||
|
|
4fa9f446de | ||
|
|
3b54e25db5 | ||
|
|
73eb0cea73 | ||
|
|
8099a99077 | ||
|
|
8647e95e30 | ||
|
|
270327d7d8 | ||
|
|
14c6694239 | ||
|
|
3d5200581d | ||
|
|
e4331ecf8c | ||
|
|
47096eabeb | ||
|
|
09e80e4255 | ||
|
|
62377b5d55 | ||
|
|
9136466115 | ||
|
|
3bf77a9056 | ||
|
|
83ceb35666 | ||
|
|
003a4c57ef | ||
|
|
261f41940f |
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Ron Klinkien
|
||||
Copyright (c) 2021-2024 Ron Klinkien
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -112,6 +112,8 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
activity_types = {}
|
||||
sleep_data = {}
|
||||
sleep_score = None
|
||||
hrv_data = {}
|
||||
hrvStatus = None
|
||||
|
||||
try:
|
||||
summary = await self.hass.async_add_executor_job(
|
||||
@@ -120,7 +122,7 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
_LOGGER.debug(f"Summary data: {summary}")
|
||||
|
||||
body = await self.hass.async_add_executor_job(
|
||||
self._api.get_body_composition, date.today().isoformat()
|
||||
self._api.get_body_composition, (date.today()-timedelta(days=7)).isoformat(), date.today().isoformat()
|
||||
)
|
||||
_LOGGER.debug(f"Body data: {body}")
|
||||
|
||||
@@ -130,6 +132,12 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
_LOGGER.debug(f"Activities data: {activities}")
|
||||
summary['lastActivities'] = activities
|
||||
|
||||
badges = await self.hass.async_add_executor_job(
|
||||
self._api.get_earned_badges
|
||||
)
|
||||
_LOGGER.debug(f"Badges data: {badges}")
|
||||
summary['badges'] = badges
|
||||
|
||||
alarms = await self.hass.async_add_executor_job(self._api.get_device_alarms)
|
||||
_LOGGER.debug(f"Alarms data: {alarms}")
|
||||
|
||||
@@ -141,6 +149,10 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
sleep_data = await self.hass.async_add_executor_job(
|
||||
self._api.get_sleep_data, date.today().isoformat())
|
||||
_LOGGER.debug(f"Sleep data: {sleep_data}")
|
||||
|
||||
hrv_data = await self.hass.async_add_executor_job(
|
||||
self._api.get_hrv_data, date.today().isoformat())
|
||||
_LOGGER.debug(f"hrv data: {hrv_data}")
|
||||
except (
|
||||
GarminConnectAuthenticationError,
|
||||
GarminConnectTooManyRequestsError,
|
||||
@@ -179,15 +191,22 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
except KeyError:
|
||||
_LOGGER.debug("Sleep score data is not available")
|
||||
|
||||
try:
|
||||
hrvStatus = hrv_data["hrvSummary"]
|
||||
_LOGGER.debug(f"HRV status: {hrvStatus} ")
|
||||
except KeyError:
|
||||
_LOGGER.debug("HRV data is not available")
|
||||
|
||||
return {
|
||||
**summary,
|
||||
**body["totalAverage"],
|
||||
**body["dateWeightList"][0],
|
||||
"nextAlarm": alarms,
|
||||
"gear": gear,
|
||||
"gear_stats": gear_stats,
|
||||
"activity_types": activity_types,
|
||||
"gear_defaults": gear_defaults,
|
||||
"sleepScore": sleep_score,
|
||||
"hrvStatus": hrvStatus,
|
||||
}
|
||||
|
||||
async def set_active_gear(self, entity, service_data):
|
||||
@@ -257,3 +276,19 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
service_data.data.get("visceral_fat_rating", None),
|
||||
service_data.data.get("bmi", None)
|
||||
)
|
||||
|
||||
async def add_blood_pressure(self, entity, service_data):
|
||||
"""Record a blood pressure measurement"""
|
||||
|
||||
if not await self.async_login():
|
||||
raise IntegrationError(
|
||||
"Failed to login to Garmin Connect, unable to update"
|
||||
)
|
||||
|
||||
await self.hass.async_add_executor_job(
|
||||
self._api.set_blood_pressure,
|
||||
service_data.data.get('systolic'),
|
||||
service_data.data.get('diastolic'),
|
||||
service_data.data.get('pulse'),
|
||||
service_data.data.get('note', None)
|
||||
)
|
||||
|
||||
@@ -137,11 +137,11 @@ GARMIN_ENTITY_LIST = {
|
||||
SensorStateClass.TOTAL,
|
||||
True,
|
||||
],
|
||||
"minHeartRate": ["Min Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.TOTAL, True],
|
||||
"maxHeartRate": ["Max Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.TOTAL, True],
|
||||
"restingHeartRate": ["Resting Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.TOTAL, True],
|
||||
"minAvgHeartRate": ["Min Avg Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.TOTAL, False],
|
||||
"maxAvgHeartRate": ["Max Avg Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.TOTAL, False],
|
||||
"minHeartRate": ["Min Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.MEASUREMENT, True],
|
||||
"maxHeartRate": ["Max Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.MEASUREMENT, True],
|
||||
"restingHeartRate": ["Resting Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.MEASUREMENT, True],
|
||||
"minAvgHeartRate": ["Min Avg Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.MEASUREMENT, False],
|
||||
"maxAvgHeartRate": ["Max Avg Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.MEASUREMENT, False],
|
||||
"abnormalHeartRateAlertsCount": [
|
||||
"Abnormal HR Counts",
|
||||
None,
|
||||
@@ -155,11 +155,11 @@ GARMIN_ENTITY_LIST = {
|
||||
"bpm",
|
||||
"mdi:heart-pulse",
|
||||
None,
|
||||
SensorStateClass.TOTAL,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
False,
|
||||
],
|
||||
"averageStressLevel": ["Avg Stress Level", "lvl", "mdi:flash-alert", None, SensorStateClass.TOTAL, True],
|
||||
"maxStressLevel": ["Max Stress Level", "lvl", "mdi:flash-alert", None, SensorStateClass.TOTAL, True],
|
||||
"averageStressLevel": ["Avg Stress Level", "lvl", "mdi:flash-alert", None, SensorStateClass.MEASUREMENT, True],
|
||||
"maxStressLevel": ["Max Stress Level", "lvl", "mdi:flash-alert", None, SensorStateClass.MEASUREMENT, True],
|
||||
"stressQualifier": ["Stress Qualifier", None, "mdi:flash-alert", None, None, False],
|
||||
"stressDuration": ["Stress Duration", UnitOfTime.MINUTES, "mdi:flash-alert", None, SensorStateClass.TOTAL, False],
|
||||
"restStressDuration": [
|
||||
@@ -223,7 +223,7 @@ GARMIN_ENTITY_LIST = {
|
||||
PERCENTAGE,
|
||||
"mdi:flash-alert",
|
||||
None,
|
||||
SensorStateClass.TOTAL,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
False,
|
||||
],
|
||||
"restStressPercentage": [
|
||||
@@ -231,7 +231,7 @@ GARMIN_ENTITY_LIST = {
|
||||
PERCENTAGE,
|
||||
"mdi:flash-alert",
|
||||
None,
|
||||
SensorStateClass.TOTAL,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
False,
|
||||
],
|
||||
"activityStressPercentage": [
|
||||
@@ -239,7 +239,7 @@ GARMIN_ENTITY_LIST = {
|
||||
PERCENTAGE,
|
||||
"mdi:flash-alert",
|
||||
None,
|
||||
SensorStateClass.TOTAL,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
False,
|
||||
],
|
||||
"uncategorizedStressPercentage": [
|
||||
@@ -247,7 +247,7 @@ GARMIN_ENTITY_LIST = {
|
||||
PERCENTAGE,
|
||||
"mdi:flash-alert",
|
||||
None,
|
||||
SensorStateClass.TOTAL,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
False,
|
||||
],
|
||||
"lowStressPercentage": [
|
||||
@@ -255,7 +255,7 @@ GARMIN_ENTITY_LIST = {
|
||||
PERCENTAGE,
|
||||
"mdi:flash-alert",
|
||||
None,
|
||||
SensorStateClass.TOTAL,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
False,
|
||||
],
|
||||
"mediumStressPercentage": [
|
||||
@@ -263,7 +263,7 @@ GARMIN_ENTITY_LIST = {
|
||||
PERCENTAGE,
|
||||
"mdi:flash-alert",
|
||||
None,
|
||||
SensorStateClass.TOTAL,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
False,
|
||||
],
|
||||
"highStressPercentage": [
|
||||
@@ -271,7 +271,7 @@ GARMIN_ENTITY_LIST = {
|
||||
PERCENTAGE,
|
||||
"mdi:flash-alert",
|
||||
None,
|
||||
SensorStateClass.TOTAL,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
False,
|
||||
],
|
||||
"moderateIntensityMinutes": [
|
||||
@@ -338,9 +338,9 @@ GARMIN_ENTITY_LIST = {
|
||||
SensorStateClass.TOTAL,
|
||||
True,
|
||||
],
|
||||
"averageSpo2": ["Average SPO2", PERCENTAGE, "mdi:diabetes", None, SensorStateClass.TOTAL, True],
|
||||
"lowestSpo2": ["Lowest SPO2", PERCENTAGE, "mdi:diabetes", None, SensorStateClass.TOTAL, True],
|
||||
"latestSpo2": ["Latest SPO2", PERCENTAGE, "mdi:diabetes", None, SensorStateClass.TOTAL, True],
|
||||
"averageSpo2": ["Average SPO2", PERCENTAGE, "mdi:diabetes", None, SensorStateClass.MEASUREMENT, True],
|
||||
"lowestSpo2": ["Lowest SPO2", PERCENTAGE, "mdi:diabetes", None, SensorStateClass.MEASUREMENT, True],
|
||||
"latestSpo2": ["Latest SPO2", PERCENTAGE, "mdi:diabetes", None, SensorStateClass.MEASUREMENT, True],
|
||||
"latestSpo2ReadingTimeLocal": [
|
||||
"Latest SPO2 Time",
|
||||
None,
|
||||
@@ -362,7 +362,7 @@ GARMIN_ENTITY_LIST = {
|
||||
"brpm",
|
||||
"mdi:progress-clock",
|
||||
None,
|
||||
SensorStateClass.TOTAL,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
False,
|
||||
],
|
||||
"lowestRespirationValue": [
|
||||
@@ -370,7 +370,7 @@ GARMIN_ENTITY_LIST = {
|
||||
"brpm",
|
||||
"mdi:progress-clock",
|
||||
None,
|
||||
SensorStateClass.TOTAL,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
False,
|
||||
],
|
||||
"latestRespirationValue": [
|
||||
@@ -378,7 +378,7 @@ GARMIN_ENTITY_LIST = {
|
||||
"brpm",
|
||||
"mdi:progress-clock",
|
||||
None,
|
||||
SensorStateClass.TOTAL,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
False,
|
||||
],
|
||||
"latestRespirationTimeGMT": [
|
||||
@@ -390,16 +390,17 @@ GARMIN_ENTITY_LIST = {
|
||||
False,
|
||||
],
|
||||
"weight": ["Weight", UnitOfMass.KILOGRAMS, "mdi:weight-kilogram", SensorDeviceClass.WEIGHT, SensorStateClass.MEASUREMENT, False],
|
||||
"bmi": ["BMI", "bmi", "mdi:food", None, SensorStateClass.TOTAL, False],
|
||||
"bodyFat": ["Body Fat", PERCENTAGE, "mdi:food", None, SensorStateClass.TOTAL, False],
|
||||
"bodyWater": ["Body Water", PERCENTAGE, "mdi:water-percent", None, SensorStateClass.TOTAL, False],
|
||||
"bmi": ["BMI", "bmi", "mdi:food", None, SensorStateClass.MEASUREMENT, False],
|
||||
"bodyFat": ["Body Fat", PERCENTAGE, "mdi:food", None, SensorStateClass.MEASUREMENT, False],
|
||||
"bodyWater": ["Body Water", PERCENTAGE, "mdi:water-percent", None, SensorStateClass.MEASUREMENT, False],
|
||||
"boneMass": ["Bone Mass", UnitOfMass.KILOGRAMS, "mdi:bone", SensorDeviceClass.WEIGHT, SensorStateClass.MEASUREMENT, False],
|
||||
"muscleMass": ["Muscle Mass", UnitOfMass.KILOGRAMS, "mdi:dumbbell", SensorDeviceClass.WEIGHT, SensorStateClass.MEASUREMENT, False],
|
||||
"physiqueRating": ["Physique Rating", None, "mdi:numeric", None, SensorStateClass.TOTAL, False],
|
||||
"visceralFat": ["Visceral Fat", PERCENTAGE, "mdi:food", None, SensorStateClass.TOTAL, False],
|
||||
"metabolicAge": ["Metabolic Age", UnitOfTime.YEARS, "mdi:calendar-heart", None, SensorStateClass.TOTAL, False],
|
||||
"physiqueRating": ["Physique Rating", None, "mdi:numeric", None, SensorStateClass.MEASUREMENT, False],
|
||||
"visceralFat": ["Visceral Fat", PERCENTAGE, "mdi:food", None, SensorStateClass.MEASUREMENT, False],
|
||||
"metabolicAge": ["Metabolic Age", UnitOfTime.YEARS, "mdi:calendar-heart", None, SensorStateClass.MEASUREMENT, False],
|
||||
"nextAlarm": ["Next Alarm Time", None, "mdi:alarm", SensorDeviceClass.TIMESTAMP, None, True],
|
||||
"lastActivities": ["Last Activities", None, "mdi:numeric", SensorStateClass.TOTAL, None, False],
|
||||
"badges": ["Badges", None, "mdi:numeric", SensorStateClass.TOTAL, None, False],
|
||||
"sleepScore": [
|
||||
"Sleep Score",
|
||||
None,
|
||||
@@ -407,8 +408,15 @@ GARMIN_ENTITY_LIST = {
|
||||
SensorStateClass.TOTAL,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
True,
|
||||
]
|
||||
|
||||
],
|
||||
"hrvStatus": [
|
||||
"HRV Status",
|
||||
None,
|
||||
"mdi:heart-pulse",
|
||||
None,
|
||||
None,
|
||||
True,
|
||||
],
|
||||
}
|
||||
|
||||
GEAR_ICONS = {
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"documentation": "https://github.com/cyberjunky/home-assistant-garmin_connect",
|
||||
"iot_class": "cloud_polling",
|
||||
"issue_tracker": "https://github.com/cyberjunky/home-assistant-garmin_connect/issues",
|
||||
"requirements": ["garminconnect>=0.2.22", "tzlocal"],
|
||||
"version": "0.2.20"
|
||||
"requirements": ["garminconnect>=0.2.24", "tzlocal"],
|
||||
"version": "0.2.23"
|
||||
}
|
||||
|
||||
@@ -104,6 +104,10 @@ async def async_setup_entry(
|
||||
"add_body_composition", BODY_COMPOSITION_SERVICE_SCHEMA, coordinator.add_body_composition
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
"add_blood_pressure", BLOOD_PRESSURE_SERVICE_SCHEMA, coordinator.add_blood_pressure
|
||||
)
|
||||
|
||||
ENTITY_SERVICE_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_ENTITY_ID): str,
|
||||
@@ -131,6 +135,17 @@ BODY_COMPOSITION_SERVICE_SCHEMA = vol.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
BLOOD_PRESSURE_SERVICE_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_ENTITY_ID): str,
|
||||
vol.Required("systolic"): int,
|
||||
vol.Required("diastolic"): int,
|
||||
vol.Required("pulse"): int,
|
||||
vol.Optional("note"): str
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
class GarminConnectSensor(CoordinatorEntity, SensorEntity):
|
||||
"""Representation of a Garmin Connect Sensor."""
|
||||
|
||||
@@ -169,6 +184,12 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
|
||||
if self._type == "lastActivities":
|
||||
return len(self.coordinator.data[self._type])
|
||||
|
||||
if self._type == "badges":
|
||||
return len(self.coordinator.data[self._type])
|
||||
|
||||
if self._type == "hrvStatus":
|
||||
return self.coordinator.data[self._type]["status"]
|
||||
|
||||
if not self.coordinator.data or not self.coordinator.data[self._type]:
|
||||
return None
|
||||
|
||||
@@ -212,11 +233,18 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
|
||||
if self._type == "lastActivities":
|
||||
attributes["last_Activities"] = self.coordinator.data[self._type]
|
||||
|
||||
if self._type == "badges":
|
||||
attributes["badges"] = self.coordinator.data[self._type]
|
||||
|
||||
if self._type == "nextAlarm":
|
||||
attributes["next_alarms"] = calculate_next_active_alarms(
|
||||
self.coordinator.data[self._type]
|
||||
)
|
||||
|
||||
if self._type == "hrvStatus":
|
||||
attributes = {**attributes, **self.coordinator.data[self._type]}
|
||||
del attributes["status"]
|
||||
|
||||
return attributes
|
||||
|
||||
@property
|
||||
@@ -371,4 +399,4 @@ class GarminConnectGearSensor(CoordinatorEntity, SensorEntity):
|
||||
lambda d: d[GEAR.UUID] == self.uuid and d["defaultGear"] is True,
|
||||
self.coordinator.data["gear_defaults"],
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -114,3 +114,36 @@ add_body_composition:
|
||||
entity:
|
||||
integration: garmin_connect
|
||||
device_class: weight
|
||||
|
||||
add_blood_pressure:
|
||||
name: Adds updated blood pressure metrics
|
||||
description: Adds updated blood pressure metrics.
|
||||
fields:
|
||||
entity_id:
|
||||
name: entity
|
||||
description: entity
|
||||
required: true
|
||||
selector:
|
||||
entity:
|
||||
integration: garmin_connect
|
||||
device_class: weight
|
||||
systolic:
|
||||
required: true
|
||||
name: Systolic
|
||||
description: Systolic value
|
||||
example: 120
|
||||
diastolic:
|
||||
required: true
|
||||
name: Diastolic
|
||||
description: Diastolic value
|
||||
example: 80
|
||||
pulse:
|
||||
required: true
|
||||
name: Pulse
|
||||
description: Pulse
|
||||
example: 60
|
||||
notes:
|
||||
required: false
|
||||
name: Notes
|
||||
description: Add a note to the measurement
|
||||
example: 'Measured with Beurer BC54'
|
||||
@@ -15,7 +15,7 @@
|
||||
"password": "Heslo",
|
||||
"username": "Užívateľské meno"
|
||||
},
|
||||
"description": "Zadajte svoje poverenia.",
|
||||
"description": "Zadajte svoje poverovacie údaje.",
|
||||
"title": "Garmin Connect"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{
|
||||
"name": "Garmin Connect",
|
||||
"render_readme": true
|
||||
}
|
||||
"render_readme": true,
|
||||
"homeassistant": "2024.11.0",
|
||||
"hacs": "1.34.0"
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
colorlog==6.8.2
|
||||
homeassistant==2024.1.0
|
||||
pip>=21.0,<24.1
|
||||
ruff==0.3.5
|
||||
mypy==1.8.0
|
||||
pre-commit==3.6.0
|
||||
pylint==3.0.3
|
||||
pip>=24.1.1,<24.2
|
||||
ruff==0.8.3
|
||||
mypy==1.13.0
|
||||
pre-commit==4.0.1
|
||||
pylint==3.3.2
|
||||
types-cachetools
|
||||
@@ -1,3 +1,3 @@
|
||||
-r requirements.txt
|
||||
pre-commit==3.7.0
|
||||
vulture==2.11
|
||||
pre-commit==4.0.1
|
||||
vulture==2.14
|
||||
Reference in New Issue
Block a user