Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
1c8dda2
Added code files
cyberjunky Jan 15, 2020
54a86b8
Correctly name init file
cyberjunky Jan 15, 2020
88c8039
Update codeowners
cyberjunky Jan 15, 2020
f8d6f14
Update requirements
cyberjunky Jan 15, 2020
0a6d74a
Added code files
cyberjunky Jan 15, 2020
51b1d5a
Correctly name init file
cyberjunky Jan 15, 2020
d688f80
Update codeowners
cyberjunky Jan 15, 2020
74059c9
Update requirements
cyberjunky Jan 15, 2020
774fe24
Black changes, added to coveragerc
cyberjunky Jan 15, 2020
c2793f9
Merge branch 'garmin-connect' of https://github.com/cyberjunky/home-a…
cyberjunky Jan 15, 2020
72350dc
Removed documentation location for now
cyberjunky Jan 15, 2020
03a7b75
Added documentation url
cyberjunky Jan 15, 2020
3c166ec
Fixed merge
cyberjunky Jan 15, 2020
48783bf
Fixed flake8 syntax
cyberjunky Jan 15, 2020
2a90f7c
Merge branch 'garmin-connect' of https://github.com/cyberjunky/home-a…
cyberjunky Jan 15, 2020
30ada90
Fixed isort
cyberjunky Jan 15, 2020
4aeaa0d
Removed false check and double throttle, applied time format change
cyberjunky Jan 16, 2020
9043a5e
Renamed email to username, used dict, deleted unused type, changed at…
cyberjunky Jan 16, 2020
2880a96
Async and ConfigFlow code
cyberjunky Jan 18, 2020
cd9b34f
Fixes
cyberjunky Jan 18, 2020
42857f6
Added device_class and misc fixes
cyberjunky Jan 19, 2020
ebfa841
isort and pylint fixes
cyberjunky Jan 19, 2020
3dea786
Removed from test requirements
cyberjunky Jan 19, 2020
cad6f49
Merge pull request #1 from cyberjunky/garmin-connect-configflow
cyberjunky Jan 19, 2020
92b46eb
Fixed isort checkblack
cyberjunky Jan 19, 2020
b4357b4
Removed host field
cyberjunky Jan 19, 2020
f7735f4
Fixed coveragerc
cyberjunky Jan 19, 2020
db3f856
Start working test file
cyberjunky Jan 20, 2020
d021103
Added more config_flow tests
cyberjunky Jan 22, 2020
60f5c11
Enable only most used sensors by default
cyberjunky Jan 25, 2020
a2f11ab
Added more default enabled sensors, fixed tests
cyberjunky Jan 25, 2020
cae9406
Fixed isort
cyberjunky Jan 25, 2020
08d0a6d
Test config_flow improvements
cyberjunky Jan 25, 2020
9a6ce6f
Remove unused import
cyberjunky Jan 25, 2020
53b53e2
Removed redundant patch calls
cyberjunky Jan 25, 2020
9a17cc1
Fixed mock return value
cyberjunky Jan 25, 2020
39df719
Updated to garmin_connect 0.1.8 fixed exceptions
cyberjunky Jan 26, 2020
37ee95a
Quick fix test patch to see if rest is error free
cyberjunky Jan 26, 2020
fb90fc0
Fixed mock routine
cyberjunky Jan 26, 2020
282fa4c
Code improvements from PR feedback
cyberjunky Jan 27, 2020
4325416
Fix entity indentifier
cyberjunky Jan 27, 2020
a4bf720
Reverted device identifier
cyberjunky Jan 27, 2020
d8c0839
Fixed abort message
cyberjunky Jan 27, 2020
d91f2de
Test fix
cyberjunky Jan 27, 2020
14453e9
Fixed unique_id MockConfigEntry
cyberjunky Jan 27, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@ omit =
homeassistant/components/frontier_silicon/media_player.py
homeassistant/components/futurenow/light.py
homeassistant/components/garadget/cover.py
homeassistant/components/garmin_connect/__init__.py
homeassistant/components/garmin_connect/const.py
homeassistant/components/garmin_connect/sensor.py
homeassistant/components/gc100/*
homeassistant/components/geniushub/*
homeassistant/components/gearbest/sensor.py
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ homeassistant/components/foursquare/* @robbiet480
homeassistant/components/freebox/* @snoof85
homeassistant/components/fronius/* @nielstron
homeassistant/components/frontend/* @home-assistant/frontend
homeassistant/components/garmin_connect/* @cyberjunky
homeassistant/components/gearbest/* @HerrHofrat
homeassistant/components/geniushub/* @zxdavb
homeassistant/components/geo_rss_events/* @exxamalte
Expand Down
24 changes: 24 additions & 0 deletions homeassistant/components/garmin_connect/.translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"config": {
"abort": {
"already_configured": "This account is already configured."
},
"error": {
"cannot_connect": "Failed to connect, please try again.",
"invalid_auth": "Invalid authentication.",
"too_many_requests": "Too many requests, retry later.",
"unknown": "Unexpected error."
},
"step": {
"user": {
"data": {
"password": "Password",
"username": "Username"
},
"description": "Enter your credentials.",
"title": "Garmin Connect"
}
},
"title": "Garmin Connect"
}
}
108 changes: 108 additions & 0 deletions homeassistant/components/garmin_connect/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""The Garmin Connect integration."""
import asyncio
from datetime import date, timedelta
import logging

from garminconnect import (
Garmin,
GarminConnectAuthenticationError,
GarminConnectConnectionError,
GarminConnectTooManyRequestsError,
)

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.util import Throttle

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

PLATFORMS = ["sensor"]
MIN_SCAN_INTERVAL = timedelta(minutes=10)


async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Garmin Connect component."""
hass.data[DOMAIN] = {}
return True


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up Garmin Connect from a config entry."""
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]

garmin_client = Garmin(username, password)

try:
garmin_client.login()
except (
GarminConnectAuthenticationError,
GarminConnectTooManyRequestsError,
) as err:
_LOGGER.error("Error occured during Garmin Connect login: %s", err)
return False
except (GarminConnectConnectionError) as err:
_LOGGER.error("Error occured during Garmin Connect login: %s", err)
raise ConfigEntryNotReady
except Exception: # pylint: disable=broad-except
_LOGGER.error("Unknown error occured during Garmin Connect login")
return False

garmin_data = GarminConnectData(hass, garmin_client)
hass.data[DOMAIN][entry.entry_id] = garmin_data

for component in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
unload_ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, component)
for component in PLATFORMS
]
)
)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok


class GarminConnectData:
"""Define an object to hold sensor data."""

def __init__(self, hass, client):
"""Initialize."""
self.client = client
self.data = None

@Throttle(MIN_SCAN_INTERVAL)
async def async_update(self):
"""Update data via library."""
today = date.today()

try:
self.data = self.client.get_stats(today.isoformat())
except (
GarminConnectAuthenticationError,
GarminConnectTooManyRequestsError,
) as err:
_LOGGER.error("Error occured during Garmin Connect get stats: %s", err)
return
except (GarminConnectConnectionError) as err:
_LOGGER.error("Error occured during Garmin Connect get stats: %s", err)
return
except Exception: # pylint: disable=broad-except
_LOGGER.error("Unknown error occured during Garmin Connect get stats")
return
72 changes: 72 additions & 0 deletions homeassistant/components/garmin_connect/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Config flow for Garmin Connect integration."""
import logging

from garminconnect import (
Garmin,
GarminConnectAuthenticationError,
GarminConnectConnectionError,
GarminConnectTooManyRequestsError,
)
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME

from .const import DOMAIN # pylint: disable=unused-import

_LOGGER = logging.getLogger(__name__)


class GarminConnectConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Garmin Connect."""

VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL

async def _show_setup_form(self, errors=None):
"""Show the setup form to the user."""
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
),
errors=errors or {},
)

async def async_step_user(self, user_input=None):
"""Handle the initial step."""
if user_input is None:
return await self._show_setup_form()

garmin_client = Garmin(user_input[CONF_USERNAME], user_input[CONF_PASSWORD])

errors = {}
try:
garmin_client.login()
except GarminConnectConnectionError:
errors["base"] = "cannot_connect"
return await self._show_setup_form(errors)
except GarminConnectAuthenticationError:
errors["base"] = "invalid_auth"
return await self._show_setup_form(errors)
except GarminConnectTooManyRequestsError:
errors["base"] = "too_many_requests"
return await self._show_setup_form(errors)
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
return await self._show_setup_form(errors)

unique_id = garmin_client.get_full_name()

await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured()

return self.async_create_entry(
title=unique_id,
data={
CONF_ID: unique_id,
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_PASSWORD: user_input[CONF_PASSWORD],
},
)
Loading