From 87d63d7cb3022a0f8a7fbc1cfe0d024de791d4f9 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Sat, 16 Mar 2019 01:22:42 +0100 Subject: [PATCH] Added Google Fit backend --- platypush/backend/google/fit.py | 88 ++++++++++++++++++++++ platypush/message/event/google/__init__.py | 0 platypush/message/event/google/fit.py | 14 ++++ platypush/plugins/google/fit.py | 43 +++++++++-- 4 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 platypush/backend/google/fit.py create mode 100644 platypush/message/event/google/__init__.py create mode 100644 platypush/message/event/google/fit.py diff --git a/platypush/backend/google/fit.py b/platypush/backend/google/fit.py new file mode 100644 index 000000000..985541e04 --- /dev/null +++ b/platypush/backend/google/fit.py @@ -0,0 +1,88 @@ +import time + +from platypush.backend import Backend +from platypush.context import get_plugin +from platypush.message.event.google.fit import GoogleFitEvent + + +class GoogleFitBackend(Backend): + """ + This backend will listen for new Google Fit events (e.g. new weight/height + measurements, new fitness activities etc.) on the specified data streams and + fire an event upon new data. + + Triggers: + + * :class:`platypush.message.event.google.fit.GoogleFitEvent` when a new + data point is received on one of the registered streams. + + Requires: + + * The **google.fit** plugin + (:class:`platypush.plugins.google.fit.GoogleFitPlugin`) enabled. + * The **db** plugin (:class:`platypush.plugins.db`) configured + """ + + _default_poll_seconds = 60 + _default_user_id = 'me' + _last_timestamp_varname = '_GOOGLE_FIT_LAST_TIMESTAMP' + + def __init__(self, data_sources, user_id=_default_user_id, + poll_seconds=_default_poll_seconds, *args, **kwargs): + """ + :param data_sources: Google Fit data source IDs to monitor. You can + get a list of the available data sources through the + :method:`platypush.plugins.google.fit.get_data_sources` action + :type data_sources: list[str] + + :param user_id: Google user ID to track (default: 'me') + :type user_id: str + + :param poll_seconds: How often the backend will query the data sources + for new data points (default: 60 seconds) + :type poll_seconds: float + """ + + super().__init__(*args, **kwargs) + + self.data_sources = data_sources + self.user_id = user_id + self.poll_seconds = poll_seconds + + + def run(self): + super().run() + self.logger.info('Started Google Fit backend on data sources {}'.format( + self.data_sources)) + + while not self.should_stop(): + last_timestamp = float(get_plugin('variable'). + get(self._last_timestamp_varname).output. + get(self._last_timestamp_varname)) or 0 + + for data_source in self.data_sources: + new_last_timestamp = last_timestamp + + for dp in get_plugin('google.fit').get_data( + user_id=self.user_id, data_source_id=data_source).output: + dp_time = dp.get('startTime', 0) + if dp_time > last_timestamp: + self.bus.post(GoogleFitEvent( + user_id=self.user_id, data_source_id=data_source, + data_type=dp.get('dataTypeName'), + start_time=dp.get('startTime'), + end_time=dp.get('endTime'), + modified_time=dp.get('modifiedTime'), + values=dp.get('values'))) + + if dp_time > new_last_timestamp: + new_last_timestamp = dp_time + + last_timestamp = new_last_timestamp + + get_plugin('variable').set(**{ + self._last_timestamp_varname: last_timestamp}) + time.sleep(self.poll_seconds) + + +# vim:sw=4:ts=4:et: diff --git a/platypush/message/event/google/__init__.py b/platypush/message/event/google/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/platypush/message/event/google/fit.py b/platypush/message/event/google/fit.py new file mode 100644 index 000000000..a323ba05f --- /dev/null +++ b/platypush/message/event/google/fit.py @@ -0,0 +1,14 @@ +from platypush.message.event import Event + + +class GoogleFitEvent(Event): + """ + Event triggered upon new Google Fit data points + """ + + def __init__(self, data_source_id, values, *args, **kwargs): + super().__init__(*args, data_source_id=data_source_id, values=values, + **kwargs) + + +# vim:sw=4:ts=4:et: diff --git a/platypush/plugins/google/fit.py b/platypush/plugins/google/fit.py index 7d541c8cd..50e846aed 100644 --- a/platypush/plugins/google/fit.py +++ b/platypush/plugins/google/fit.py @@ -1,5 +1,3 @@ -from apiclient import discovery - from platypush.plugins import action from platypush.plugins.google import GooglePlugin @@ -38,7 +36,7 @@ class GoogleFitPlugin(GooglePlugin): return sources['dataSource'] @action - def get_data(self, data_source_id, user_id=None): + def get_data(self, data_source_id, user_id=None, limit=None): """ Get raw data for the specified data_source_id @@ -47,9 +45,42 @@ class GoogleFitPlugin(GooglePlugin): """ service = self.get_service(service='fitness', version='v1') - return service.users().dataSources().dataPointChanges() \ - .list(dataSourceId=data_source_id, userId=user_id or self.user_id) \ - .execute().get('insertedDataPoint', []) + kwargs = { + 'dataSourceId': data_source_id, + 'userId': user_id or self.user_id, + } + + if limit: + kwargs['limit'] = limit + data_points = [] + + for data_point in service.users().dataSources().dataPointChanges(). \ + list(**kwargs).execute().get('insertedDataPoint', []): + data_point['startTime'] = float(data_point.pop('startTimeNanos'))/1e9 + data_point['endTime'] = float(data_point.pop('endTimeNanos'))/1e9 + data_point['modifiedTime'] = float(data_point.pop('modifiedTimeMillis'))/1e6 + values = [] + + for value in data_point.pop('value'): + if value.get('intVal') is not None: + value = value['intVal'] + elif value.get('fpVal') is not None: + value = value['fpVal'] + elif value.get('stringVal') is not None: + value = value['stringVal'] + elif value.get('mapVal'): + value = { + v['key']: v['value'].get( + 'intVal', v['value'].get( + 'fpVal', v['value'].get('stringVal'))) + for v in value['mapVal'] } + + values.append(value) + + data_point['values'] = values + data_points.append(data_point) + + return data_points # vim:sw=4:ts=4:et: