From 8cb48615afcf0faed54ca9684ee8167fe3445ce4 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 7 May 2016 12:46:45 -0700 Subject: [PATCH] Fitbit Sensor Take Deux (#2002) * Fitbit Sensor * Add configurator image --- .coveragerc | 1 + .../www_static/images/config_fitbit_app.png | Bin 0 -> 46660 bytes homeassistant/components/sensor/fitbit.py | 372 ++++++++++++++++++ requirements_all.txt | 3 + 4 files changed, 376 insertions(+) create mode 100644 homeassistant/components/frontend/www_static/images/config_fitbit_app.png create mode 100644 homeassistant/components/sensor/fitbit.py diff --git a/.coveragerc b/.coveragerc index 2a91d1f17b1..028aacead28 100644 --- a/.coveragerc +++ b/.coveragerc @@ -148,6 +148,7 @@ omit = homeassistant/components/sensor/dht.py homeassistant/components/sensor/efergy.py homeassistant/components/sensor/eliqonline.py + homeassistant/components/sensor/fitbit.py homeassistant/components/sensor/forecast.py homeassistant/components/sensor/glances.py homeassistant/components/sensor/google_travel_time.py diff --git a/homeassistant/components/frontend/www_static/images/config_fitbit_app.png b/homeassistant/components/frontend/www_static/images/config_fitbit_app.png new file mode 100644 index 0000000000000000000000000000000000000000..271a0c6dd47984dfa638572b82cddec82539061c GIT binary patch literal 46660 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 literal 0 HcmV?d00001 diff --git a/homeassistant/components/sensor/fitbit.py b/homeassistant/components/sensor/fitbit.py new file mode 100644 index 00000000000..f928b9a78e9 --- /dev/null +++ b/homeassistant/components/sensor/fitbit.py @@ -0,0 +1,372 @@ +""" +Support for the Fitbit API. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.fitbit/ +""" +import os +import json +import logging +import datetime +import time + +from homeassistant.const import HTTP_OK +from homeassistant.util import Throttle +from homeassistant.helpers.entity import Entity +from homeassistant.loader import get_component + +_LOGGER = logging.getLogger(__name__) +REQUIREMENTS = ["fitbit==0.2.2"] +DEPENDENCIES = ["http"] + +ICON = "mdi:walk" + +_CONFIGURING = {} + +# Return cached results if last scan was less then this time ago. +MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(minutes=30) + +FITBIT_AUTH_START = "/auth/fitbit" +FITBIT_AUTH_CALLBACK_PATH = "/auth/fitbit/callback" + +DEFAULT_CONFIG = { + "client_id": "CLIENT_ID_HERE", + "client_secret": "CLIENT_SECRET_HERE" +} + +FITBIT_CONFIG_FILE = "fitbit.conf" + +FITBIT_RESOURCES_LIST = { + "activities/activityCalories": "cal", + "activities/calories": "cal", + "activities/caloriesBMR": "cal", + "activities/distance": "", + "activities/elevation": "", + "activities/floors": "floors", + "activities/heart": "bpm", + "activities/minutesFairlyActive": "minutes", + "activities/minutesLightlyActive": "minutes", + "activities/minutesSedentary": "minutes", + "activities/minutesVeryActive": "minutes", + "activities/steps": "steps", + "activities/tracker/activityCalories": "cal", + "activities/tracker/calories": "cal", + "activities/tracker/distance": "", + "activities/tracker/elevation": "", + "activities/tracker/floors": "floors", + "activities/tracker/minutesFairlyActive": "minutes", + "activities/tracker/minutesLightlyActive": "minutes", + "activities/tracker/minutesSedentary": "minutes", + "activities/tracker/minutesVeryActive": "minutes", + "activities/tracker/steps": "steps", + "body/bmi": "BMI", + "body/fat": "%", + "sleep/awakeningsCount": "times awaken", + "sleep/efficiency": "%", + "sleep/minutesAfterWakeup": "minutes", + "sleep/minutesAsleep": "minutes", + "sleep/minutesAwake": "minutes", + "sleep/minutesToFallAsleep": "minutes", + "sleep/startTime": "start time", + "sleep/timeInBed": "time in bed", + "body/weight": "" +} + +FITBIT_DEFAULT_RESOURCE_LIST = ["activities/steps"] + +FITBIT_MEASUREMENTS = { + "en_US": { + "duration": "ms", + "distance": "mi", + "elevation": "ft", + "height": "in", + "weight": "lbs", + "body": "in", + "liquids": "fl. oz.", + "blood glucose": "mg/dL", + }, + "en_UK": { + "duration": "milliseconds", + "distance": "kilometers", + "elevation": "meters", + "height": "centimeters", + "weight": "stone", + "body": "centimeters", + "liquids": "millileters", + "blood glucose": "mmol/l" + }, + "metric": { + "duration": "milliseconds", + "distance": "kilometers", + "elevation": "meters", + "height": "centimeters", + "weight": "kilograms", + "body": "centimeters", + "liquids": "millileters", + "blood glucose": "mmol/l" + } +} + + +def config_from_file(filename, config=None): + """Small configuration file management function.""" + if config: + # We"re writing configuration + try: + with open(filename, "w") as fdesc: + fdesc.write(json.dumps(config)) + except IOError as error: + _LOGGER.error("Saving config file failed: %s", error) + return False + return config + else: + # We"re reading config + if os.path.isfile(filename): + try: + with open(filename, "r") as fdesc: + return json.loads(fdesc.read()) + except IOError as error: + _LOGGER.error("Reading config file failed: %s", error) + # This won"t work yet + return False + else: + return {} + + +def request_app_setup(hass, config, add_devices, config_path, + discovery_info=None): + """Assist user with configuring the Fitbit dev application.""" + configurator = get_component("configurator") + + # pylint: disable=unused-argument + def fitbit_configuration_callback(callback_data): + """The actions to do when our configuration callback is called.""" + config_path = hass.config.path(FITBIT_CONFIG_FILE) + if os.path.isfile(config_path): + config_file = config_from_file(config_path) + if config_file == DEFAULT_CONFIG: + error_msg = ("You didn't correctly modify fitbit.conf", + " please try again") + configurator.notify_errors(_CONFIGURING["fitbit"], error_msg) + else: + setup_platform(hass, config, add_devices, discovery_info) + else: + setup_platform(hass, config, add_devices, discovery_info) + + start_url = "{}{}".format(hass.config.api.base_url, FITBIT_AUTH_START) + + description = """Please create a Fitbit developer app at + https://dev.fitbit.com/apps/new. + For the OAuth 2.0 Application Type choose Personal. + Set the Callback URL to {}. + They will provide you a Client ID and secret. + These need to be saved into the file located at: {}. + Then come back here and hit the below button. + """.format(start_url, config_path) + + submit = "I have saved my Client ID and Client Secret into fitbit.conf." + + _CONFIGURING["fitbit"] = configurator.request_config( + hass, "Fitbit", fitbit_configuration_callback, + description=description, submit_caption=submit, + description_image="/static/images/config_fitbit_app.png" + ) + + +def request_oauth_completion(hass): + """Request user complete Fitbit OAuth2 flow.""" + configurator = get_component("configurator") + if "fitbit" in _CONFIGURING: + configurator.notify_errors( + _CONFIGURING["fitbit"], "Failed to register, please try again.") + + return + + # pylint: disable=unused-argument + def fitbit_configuration_callback(callback_data): + """The actions to do when our configuration callback is called.""" + + start_url = "{}{}".format(hass.config.api.base_url, FITBIT_AUTH_START) + + description = "Please authorize Fitbit by visiting {}".format(start_url) + + _CONFIGURING["fitbit"] = configurator.request_config( + hass, "Fitbit", fitbit_configuration_callback, + description=description, + submit_caption="I have authorized Fitbit." + ) + +# pylint: disable=too-many-locals + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Fitbit sensor.""" + config_path = hass.config.path(FITBIT_CONFIG_FILE) + if os.path.isfile(config_path): + config_file = config_from_file(config_path) + if config_file == DEFAULT_CONFIG: + request_app_setup(hass, config, add_devices, config_path, + discovery_info=None) + return False + else: + config_file = config_from_file(config_path, DEFAULT_CONFIG) + request_app_setup(hass, config, add_devices, config_path, + discovery_info=None) + return False + + if "fitbit" in _CONFIGURING: + get_component("configurator").request_done(_CONFIGURING.pop("fitbit")) + + import fitbit + + access_token = config_file.get("access_token") + refresh_token = config_file.get("refresh_token") + if None not in (access_token, refresh_token): + authd_client = fitbit.Fitbit(config.get("client_id"), + config.get("client_secret"), + access_token=access_token, + refresh_token=refresh_token) + + if int(time.time()) - config_file.get("last_saved_at", 0) > 3600: + authd_client.client.refresh_token() + + authd_client.system = authd_client.user_profile_get()["user"]["locale"] + + dev = [] + for resource in config.get("monitored_resources", + FITBIT_DEFAULT_RESOURCE_LIST): + dev.append(FitbitSensor(authd_client, config_path, resource)) + add_devices(dev) + + else: + oauth = fitbit.api.FitbitOauth2Client(config.get("client_id"), + config.get("client_secret")) + + redirect_uri = "{}{}".format(hass.config.api.base_url, + FITBIT_AUTH_CALLBACK_PATH) + + def _start_fitbit_auth(handler, path_match, data): + """Start Fitbit OAuth2 flow.""" + url, _ = oauth.authorize_token_url(redirect_uri=redirect_uri, + scope=["activity", "heartrate", + "nutrition", "profile", + "settings", "sleep", + "weight"]) + handler.send_response(301) + handler.send_header("Location", url) + handler.end_headers() + + def _finish_fitbit_auth(handler, path_match, data): + """Finish Fitbit OAuth2 flow.""" + response_message = """Fitbit has been successfully authorized! + You can close this window now!""" + from oauthlib.oauth2.rfc6749.errors import MismatchingStateError + from oauthlib.oauth2.rfc6749.errors import MissingTokenError + if data.get("code") is not None: + try: + oauth.fetch_access_token(data.get("code"), redirect_uri) + except MissingTokenError as error: + _LOGGER.error("Missing token: %s", error) + response_message = """Something went wrong when + attempting authenticating with Fitbit. The error + encountered was {}. Please try again!""".format(error) + except MismatchingStateError as error: + _LOGGER.error("Mismatched state, CSRF error: %s", error) + response_message = """Something went wrong when + attempting authenticating with Fitbit. The error + encountered was {}. Please try again!""".format(error) + else: + _LOGGER.error("Unknown error when authing") + response_message = """Something went wrong when + attempting authenticating with Fitbit. + An unknown error occurred. Please try again! + """ + + html_response = """Fitbit Auth +

{}

""".format(response_message) + + html_response = html_response.encode("utf-8") + + handler.send_response(HTTP_OK) + handler.write_content(html_response, content_type="text/html") + + config_contents = { + "access_token": oauth.token["access_token"], + "refresh_token": oauth.token["refresh_token"], + "client_id": oauth.client_id, + "client_secret": oauth.client_secret + } + if not config_from_file(config_path, config_contents): + _LOGGER.error("failed to save config file") + + setup_platform(hass, config, add_devices, discovery_info=None) + + hass.http.register_path("GET", FITBIT_AUTH_START, _start_fitbit_auth) + hass.http.register_path("GET", FITBIT_AUTH_CALLBACK_PATH, + _finish_fitbit_auth) + + request_oauth_completion(hass) + + +# pylint: disable=too-few-public-methods +class FitbitSensor(Entity): + """Implementation of a Fitbit sensor.""" + + def __init__(self, client, config_path, resource_type): + """Initialize the Uber sensor.""" + self.client = client + self.config_path = config_path + self.resource_type = resource_type + pretty_resource = self.resource_type.replace("activities/", "") + pretty_resource = pretty_resource.replace("/", " ") + pretty_resource = pretty_resource.title() + if pretty_resource == "Body Bmi": + pretty_resource = "BMI" + self._name = pretty_resource + unit_type = FITBIT_RESOURCES_LIST[self.resource_type] + if unit_type == "": + split_resource = self.resource_type.split("/") + measurement_system = FITBIT_MEASUREMENTS[self.client.system] + unit_type = measurement_system[split_resource[-1]] + self._unit_of_measurement = unit_type + self._state = 0 + self.update() + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit_of_measurement + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return ICON + + # pylint: disable=too-many-branches + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Get the latest data from the Fitbit API and update the states.""" + container = self.resource_type.replace("/", "-") + response = self.client.time_series(self.resource_type, period="7d") + self._state = response[container][-1].get("value") + if self.resource_type == "activities/heart": + self._state = response[container][-1].get("restingHeartRate") + config_contents = { + "access_token": self.client.client.token["access_token"], + "refresh_token": self.client.client.token["refresh_token"], + "client_id": self.client.client.client_id, + "client_secret": self.client.client.client_secret, + "last_saved_at": int(time.time()) + } + if not config_from_file(self.config_path, config_contents): + _LOGGER.error("failed to save config file") diff --git a/requirements_all.txt b/requirements_all.txt index f15e2c4f5dc..ddfc5a0802f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -53,6 +53,9 @@ evohomeclient==0.2.5 # homeassistant.components.feedreader feedparser==5.2.1 +# homeassistant.components.sensor.fitbit +fitbit==0.2.2 + # homeassistant.components.notify.free_mobile freesms==0.1.0