Fix Garmin Connect integration with python-garminconnect-aio (#50865)
This commit is contained in:
parent
6ba2ee5cef
commit
a0b3d0863b
8 changed files with 67 additions and 56 deletions
|
@ -1,8 +1,8 @@
|
||||||
"""The Garmin Connect integration."""
|
"""The Garmin Connect integration."""
|
||||||
from datetime import date, timedelta
|
from datetime import date
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from garminconnect import (
|
from garminconnect_aio import (
|
||||||
Garmin,
|
Garmin,
|
||||||
GarminConnectAuthenticationError,
|
GarminConnectAuthenticationError,
|
||||||
GarminConnectConnectionError,
|
GarminConnectConnectionError,
|
||||||
|
@ -13,25 +13,27 @@ from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DEFAULT_UPDATE_INTERVAL, DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PLATFORMS = ["sensor"]
|
PLATFORMS = ["sensor"]
|
||||||
MIN_SCAN_INTERVAL = timedelta(minutes=10)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Garmin Connect from a config entry."""
|
"""Set up Garmin Connect from a config entry."""
|
||||||
username = entry.data[CONF_USERNAME]
|
|
||||||
password = entry.data[CONF_PASSWORD]
|
|
||||||
|
|
||||||
garmin_client = Garmin(username, password)
|
websession = async_get_clientsession(hass)
|
||||||
|
username: str = entry.data[CONF_USERNAME]
|
||||||
|
password: str = entry.data[CONF_PASSWORD]
|
||||||
|
|
||||||
|
garmin_client = Garmin(websession, username, password)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await hass.async_add_executor_job(garmin_client.login)
|
await garmin_client.login()
|
||||||
except (
|
except (
|
||||||
GarminConnectAuthenticationError,
|
GarminConnectAuthenticationError,
|
||||||
GarminConnectTooManyRequestsError,
|
GarminConnectTooManyRequestsError,
|
||||||
|
@ -73,38 +75,29 @@ class GarminConnectData:
|
||||||
self.client = client
|
self.client = client
|
||||||
self.data = None
|
self.data = None
|
||||||
|
|
||||||
async def _get_combined_alarms_of_all_devices(self):
|
@Throttle(DEFAULT_UPDATE_INTERVAL)
|
||||||
"""Combine the list of active alarms from all garmin devices."""
|
|
||||||
alarms = []
|
|
||||||
devices = await self.hass.async_add_executor_job(self.client.get_devices)
|
|
||||||
for device in devices:
|
|
||||||
device_settings = await self.hass.async_add_executor_job(
|
|
||||||
self.client.get_device_settings, device["deviceId"]
|
|
||||||
)
|
|
||||||
alarms += device_settings["alarms"]
|
|
||||||
return alarms
|
|
||||||
|
|
||||||
@Throttle(MIN_SCAN_INTERVAL)
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Update data via library."""
|
"""Update data via API wrapper."""
|
||||||
today = date.today()
|
today = date.today()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.data = await self.hass.async_add_executor_job(
|
summary = await self.client.get_user_summary(today.isoformat())
|
||||||
self.client.get_stats_and_body, today.isoformat()
|
body = await self.client.get_body_composition(today.isoformat())
|
||||||
)
|
|
||||||
self.data["nextAlarm"] = await self._get_combined_alarms_of_all_devices()
|
self.data = {
|
||||||
|
**summary,
|
||||||
|
**body["totalAverage"],
|
||||||
|
}
|
||||||
|
self.data["nextAlarm"] = await self.client.get_device_alarms()
|
||||||
except (
|
except (
|
||||||
GarminConnectAuthenticationError,
|
GarminConnectAuthenticationError,
|
||||||
GarminConnectTooManyRequestsError,
|
GarminConnectTooManyRequestsError,
|
||||||
GarminConnectConnectionError,
|
GarminConnectConnectionError,
|
||||||
) as err:
|
) as err:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Error occurred during Garmin Connect get activity request: %s", err
|
"Error occurred during Garmin Connect update requests: %s", err
|
||||||
)
|
)
|
||||||
return
|
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception(
|
_LOGGER.exception(
|
||||||
"Unknown error occurred during Garmin Connect get activity request"
|
"Unknown error occurred during Garmin Connect update requests"
|
||||||
)
|
)
|
||||||
return
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""Config flow for Garmin Connect integration."""
|
"""Config flow for Garmin Connect integration."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from garminconnect import (
|
from garminconnect_aio import (
|
||||||
Garmin,
|
Garmin,
|
||||||
GarminConnectAuthenticationError,
|
GarminConnectAuthenticationError,
|
||||||
GarminConnectConnectionError,
|
GarminConnectConnectionError,
|
||||||
|
@ -11,6 +11,7 @@ import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
@ -37,11 +38,15 @@ class GarminConnectConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return await self._show_setup_form()
|
return await self._show_setup_form()
|
||||||
|
|
||||||
garmin_client = Garmin(user_input[CONF_USERNAME], user_input[CONF_PASSWORD])
|
websession = async_get_clientsession(self.hass)
|
||||||
|
|
||||||
|
garmin_client = Garmin(
|
||||||
|
websession, user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
|
||||||
|
)
|
||||||
|
|
||||||
errors = {}
|
errors = {}
|
||||||
try:
|
try:
|
||||||
await self.hass.async_add_executor_job(garmin_client.login)
|
username = await garmin_client.login()
|
||||||
except GarminConnectConnectionError:
|
except GarminConnectConnectionError:
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
return await self._show_setup_form(errors)
|
return await self._show_setup_form(errors)
|
||||||
|
@ -56,15 +61,13 @@ class GarminConnectConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
errors["base"] = "unknown"
|
errors["base"] = "unknown"
|
||||||
return await self._show_setup_form(errors)
|
return await self._show_setup_form(errors)
|
||||||
|
|
||||||
unique_id = garmin_client.get_full_name()
|
await self.async_set_unique_id(username)
|
||||||
|
|
||||||
await self.async_set_unique_id(unique_id)
|
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=unique_id,
|
title=username,
|
||||||
data={
|
data={
|
||||||
CONF_ID: unique_id,
|
CONF_ID: username,
|
||||||
CONF_USERNAME: user_input[CONF_USERNAME],
|
CONF_USERNAME: user_input[CONF_USERNAME],
|
||||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"""Constants for the Garmin Connect integration."""
|
"""Constants for the Garmin Connect integration."""
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
DEVICE_CLASS_TIMESTAMP,
|
DEVICE_CLASS_TIMESTAMP,
|
||||||
LENGTH_METERS,
|
LENGTH_METERS,
|
||||||
|
@ -8,7 +10,8 @@ from homeassistant.const import (
|
||||||
)
|
)
|
||||||
|
|
||||||
DOMAIN = "garmin_connect"
|
DOMAIN = "garmin_connect"
|
||||||
ATTRIBUTION = "Data provided by garmin.com"
|
ATTRIBUTION = "connect.garmin.com"
|
||||||
|
DEFAULT_UPDATE_INTERVAL = timedelta(minutes=10)
|
||||||
|
|
||||||
GARMIN_ENTITY_LIST = {
|
GARMIN_ENTITY_LIST = {
|
||||||
"totalSteps": ["Total Steps", "steps", "mdi:walk", None, True],
|
"totalSteps": ["Total Steps", "steps", "mdi:walk", None, True],
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"domain": "garmin_connect",
|
"domain": "garmin_connect",
|
||||||
"name": "Garmin Connect",
|
"name": "Garmin Connect",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/garmin_connect",
|
"documentation": "https://www.home-assistant.io/integrations/garmin_connect",
|
||||||
"requirements": ["garminconnect==0.1.19"],
|
"requirements": ["garminconnect_aio==0.1.1"],
|
||||||
"codeowners": ["@cyberjunky"],
|
"codeowners": ["@cyberjunky"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"iot_class": "cloud_polling"
|
"iot_class": "cloud_polling"
|
||||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from garminconnect import (
|
from garminconnect_aio import (
|
||||||
GarminConnectAuthenticationError,
|
GarminConnectAuthenticationError,
|
||||||
GarminConnectConnectionError,
|
GarminConnectConnectionError,
|
||||||
GarminConnectTooManyRequestsError,
|
GarminConnectTooManyRequestsError,
|
||||||
|
|
|
@ -635,7 +635,7 @@ gTTS==2.2.2
|
||||||
garages-amsterdam==2.1.1
|
garages-amsterdam==2.1.1
|
||||||
|
|
||||||
# homeassistant.components.garmin_connect
|
# homeassistant.components.garmin_connect
|
||||||
garminconnect==0.1.19
|
garminconnect_aio==0.1.1
|
||||||
|
|
||||||
# homeassistant.components.geniushub
|
# homeassistant.components.geniushub
|
||||||
geniushub-client==0.6.30
|
geniushub-client==0.6.30
|
||||||
|
|
|
@ -341,7 +341,7 @@ gTTS==2.2.2
|
||||||
garages-amsterdam==2.1.1
|
garages-amsterdam==2.1.1
|
||||||
|
|
||||||
# homeassistant.components.garmin_connect
|
# homeassistant.components.garmin_connect
|
||||||
garminconnect==0.1.19
|
garminconnect_aio==0.1.1
|
||||||
|
|
||||||
# homeassistant.components.geo_json_events
|
# homeassistant.components.geo_json_events
|
||||||
# homeassistant.components.usgs_earthquakes_feed
|
# homeassistant.components.usgs_earthquakes_feed
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""Test the Garmin Connect config flow."""
|
"""Test the Garmin Connect config flow."""
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from garminconnect import (
|
from garminconnect_aio import (
|
||||||
GarminConnectAuthenticationError,
|
GarminConnectAuthenticationError,
|
||||||
GarminConnectConnectionError,
|
GarminConnectConnectionError,
|
||||||
GarminConnectTooManyRequestsError,
|
GarminConnectTooManyRequestsError,
|
||||||
|
@ -15,7 +15,7 @@ from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
MOCK_CONF = {
|
MOCK_CONF = {
|
||||||
CONF_ID: "First Lastname",
|
CONF_ID: "my@email.address",
|
||||||
CONF_USERNAME: "my@email.address",
|
CONF_USERNAME: "my@email.address",
|
||||||
CONF_PASSWORD: "mypassw0rd",
|
CONF_PASSWORD: "mypassw0rd",
|
||||||
}
|
}
|
||||||
|
@ -23,27 +23,33 @@ MOCK_CONF = {
|
||||||
|
|
||||||
@pytest.fixture(name="mock_garmin_connect")
|
@pytest.fixture(name="mock_garmin_connect")
|
||||||
def mock_garmin():
|
def mock_garmin():
|
||||||
"""Mock Garmin."""
|
"""Mock Garmin Connect."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.garmin_connect.config_flow.Garmin",
|
"homeassistant.components.garmin_connect.config_flow.Garmin",
|
||||||
) as garmin:
|
) as garmin:
|
||||||
garmin.return_value.get_full_name.return_value = MOCK_CONF[CONF_ID]
|
garmin.return_value.login.return_value = MOCK_CONF[CONF_ID]
|
||||||
yield garmin.return_value
|
yield garmin.return_value
|
||||||
|
|
||||||
|
|
||||||
async def test_show_form(hass):
|
async def test_show_form(hass):
|
||||||
"""Test that the form is served with no input."""
|
"""Test that the form is served with no input."""
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
assert result["step_id"] == "user"
|
assert result["errors"] == {}
|
||||||
|
assert result["step_id"] == config_entries.SOURCE_USER
|
||||||
|
|
||||||
|
|
||||||
async def test_step_user(hass, mock_garmin_connect):
|
async def test_step_user(hass):
|
||||||
"""Test registering an integration and finishing flow works."""
|
"""Test registering an integration and finishing flow works."""
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
|
"homeassistant.components.garmin_connect.Garmin.login",
|
||||||
|
return_value="my@email.address",
|
||||||
|
), patch(
|
||||||
"homeassistant.components.garmin_connect.async_setup_entry", return_value=True
|
"homeassistant.components.garmin_connect.async_setup_entry", return_value=True
|
||||||
):
|
):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
@ -95,12 +101,18 @@ async def test_unknown_error(hass, mock_garmin_connect):
|
||||||
assert result["errors"] == {"base": "unknown"}
|
assert result["errors"] == {"base": "unknown"}
|
||||||
|
|
||||||
|
|
||||||
async def test_abort_if_already_setup(hass, mock_garmin_connect):
|
async def test_abort_if_already_setup(hass):
|
||||||
"""Test abort if already setup."""
|
"""Test abort if already setup."""
|
||||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONF, unique_id=MOCK_CONF[CONF_ID])
|
MockConfigEntry(
|
||||||
entry.add_to_hass(hass)
|
domain=DOMAIN, data=MOCK_CONF, unique_id=MOCK_CONF[CONF_ID]
|
||||||
|
).add_to_hass(hass)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.garmin_connect.config_flow.Garmin", autospec=True
|
||||||
|
) as garmin:
|
||||||
|
garmin.return_value.login.return_value = MOCK_CONF[CONF_ID]
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_CONF
|
DOMAIN, context={"source": "user"}, data=MOCK_CONF
|
||||||
)
|
)
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
assert result["reason"] == "already_configured"
|
assert result["reason"] == "already_configured"
|
||||||
|
|
Loading…
Add table
Reference in a new issue