Last active
June 14, 2020 17:55
-
-
Save craigjmidwinter/f4456186759302d75d3619d642999e5e to your computer and use it in GitHub Desktop.
googlefit steps component for home assistant
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import json | |
import logging | |
import os | |
import time | |
from datetime import datetime, timedelta | |
import httplib2 | |
import requests | |
import voluptuous as vol | |
from apiclient.discovery import build | |
from oauth2client.client import OAuth2WebServerFlow | |
from oauth2client.file import Storage | |
import homeassistant.helpers.config_validation as cv | |
from homeassistant.components.sensor import PLATFORM_SCHEMA | |
from homeassistant.const import (CONF_MONITORED_VARIABLES, CONF_NAME, | |
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS) | |
from homeassistant.helpers.entity import Entity | |
from homeassistant.helpers.event import track_time_change | |
from homeassistant.util import Throttle, convert, dt | |
DOMAIN = 'google_fit' | |
# Change these values | |
CLIENT_ID = 'XXXXXXXXXXXXXXXXXX.apps.googleusercontent.com' | |
CLIENT_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXX' | |
OAUTH_SCOPE = 'https://www.googleapis.com/auth/fitness.activity.read' | |
DATA_SOURCE = "derived:com.google.step_count.delta:com.google.android.gms:estimated_steps" | |
NOTIFICATION_ID = 'google_fit_notification' | |
NOTIFICATION_TITLE = 'Google Fit Setup' | |
TODAY = datetime.today().date() | |
NOW = datetime.today() | |
START = int(time.mktime(TODAY.timetuple())*1000000000) | |
END = int(time.mktime(NOW.timetuple())*1000000000) | |
DATA_SET = "%s-%s" % (START, END) | |
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob' | |
TOKEN_FILE = '.{}.token'.format(DOMAIN) | |
_LOGGER = logging.getLogger(__name__) | |
def nanoseconds(nanotime): | |
""" | |
Convert epoch time with nanoseconds to human-readable. | |
""" | |
dt = datetime.fromtimestamp(nanotime // 1000000000) | |
return dt.strftime('%Y-%m-%d %H:%M:%S') | |
if __name__ == "__main__": | |
# Point of entry in execution mode: | |
dataset = retrieve_data() | |
with open('dataset.txt', 'w') as outfile: | |
json.dump(dataset, outfile) | |
starts = [] | |
ends = [] | |
values = [] | |
for point in dataset["point"]: | |
if int(point["startTimeNanos"]) > START: | |
starts.append(int(point["startTimeNanos"])) | |
ends.append(int(point["endTimeNanos"])) | |
values.append(point['value'][0]['intVal']) | |
def setup_platform(hass, config, add_devices, discovery_info=None): | |
"""Set up the GoogleFitSteps sensor.""" | |
# Create a data fetcher to support all of the configured sensors. Then make | |
# the first call to init the data. | |
token_file = hass.config.path(TOKEN_FILE) | |
if not os.path.isfile(token_file): | |
do_authentication(hass, config) | |
else: | |
fit_service = GoogleFitSteps(hass.config.path(TOKEN_FILE)) | |
add_devices([fit_service], True) | |
class GoogleFitSteps(Entity): | |
"""Implementation of a GoogleFitSteps sensor.""" | |
def __init__(self, token_file): | |
"""Init the Google Fit service.""" | |
self.token_file = token_file | |
self._name = 'steps' | |
@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 | |
def update(self): | |
data = self.retrieve_data() | |
steps = 0 | |
for point in data['point']: | |
steps = steps + point['value'][0]['intVal'] | |
self._state = steps | |
def retrieve_data(self): | |
credentials = Storage(self.token_file).get() | |
http = credentials.authorize(httplib2.Http()) | |
fitness_service = build('fitness', 'v1', http=http, cache_discovery=False) | |
return fitness_service.users().dataSources(). \ | |
datasets(). \ | |
get(userId='me', dataSourceId=DATA_SOURCE, datasetId=DATA_SET). \ | |
execute() | |
def do_authentication(hass, config): | |
"""Notify user of actions and authenticate. | |
Notify user of user_code and verification_url then poll | |
until we have an access token. | |
""" | |
from oauth2client.client import ( | |
OAuth2WebServerFlow, | |
OAuth2DeviceCodeError, | |
FlowExchangeError | |
) | |
oauth = OAuth2WebServerFlow( | |
client_id=CLIENT_ID, | |
client_secret=CLIENT_SECRET, | |
scope='https://www.googleapis.com/auth/fitness.activity.read', | |
redirect_uri='Home-Assistant.io', | |
) | |
try: | |
dev_flow = oauth.step1_get_device_and_user_codes() | |
except OAuth2DeviceCodeError as err: | |
hass.components.persistent_notification.create( | |
'Error: {}<br />You will need to restart hass after fixing.' | |
''.format(err), | |
title=NOTIFICATION_TITLE, | |
notification_id=NOTIFICATION_ID) | |
return False | |
hass.components.persistent_notification.create( | |
'In order to authorize Home-Assistant to view your steps ' | |
'you must visit: <a href="{}" target="_blank">{}</a> and enter ' | |
'code: {}'.format(dev_flow.verification_url, | |
dev_flow.verification_url, | |
dev_flow.user_code), | |
title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID | |
) | |
def step2_exchange(now): | |
"""Keep trying to validate the user_code until it expires.""" | |
if now >= dt.as_local(dev_flow.user_code_expiry): | |
hass.components.persistent_notification.create( | |
'Authenication code expired, please restart ' | |
'Home-Assistant and try again', | |
title=NOTIFICATION_TITLE, | |
notification_id=NOTIFICATION_ID) | |
listener() | |
try: | |
credentials = oauth.step2_exchange(device_flow_info=dev_flow) | |
except FlowExchangeError: | |
# not ready yet, call again | |
return | |
storage = Storage(hass.config.path(TOKEN_FILE)) | |
storage.put(credentials) | |
listener() | |
listener = track_time_change(hass, step2_exchange, | |
second=range(0, 60, dev_flow.interval)) | |
return True |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment