From 9d38ee47bfab20bc47ed573b954f9e3e3d189659 Mon Sep 17 00:00:00 2001 From: tomshaffner <48473431+tomshaffner@users.noreply.github.com> Date: Sun, 6 Dec 2020 20:37:50 -0500 Subject: [PATCH 1/6] Add lbs weight sensor too Adding a pounds (LBS) weight sensor as well. Incredibly clunky approach (Just renamed the current weight class with KG and copied it to make an LBS version), but gets the job done for the moment. --- custom_components/google_fit/sensor.py | 69 +++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/custom_components/google_fit/sensor.py b/custom_components/google_fit/sensor.py index 9a97fe3..d4780ce 100644 --- a/custom_components/google_fit/sensor.py +++ b/custom_components/google_fit/sensor.py @@ -51,7 +51,8 @@ # Google Fit API URL. API_VERSION = 'v1' API_USER_ID = 'me' -WEIGHT = 'weight' +WEIGHT_LBS = 'weight LBS' +WEIGHT_KG = 'weight KG' HEIGHT = 'height' DISTANCE = 'distance' STEPS = 'steps' @@ -69,14 +70,17 @@ 'https://www.googleapis.com/auth/fitness.activity.read', 'https://www.googleapis.com/auth/fitness.location.read'] + def _today_dataset_start(): today = datetime.today().date() return int(time.mktime(today.timetuple()) * 1000000000) + def _today_dataset_end(): now = datetime.today() return int(time.mktime(now.timetuple()) * 1000000000) + def _get_client(token_file): """Get the Google Fit service with the storage file token. @@ -99,6 +103,7 @@ def _get_client(token_file): 'fitness', API_VERSION, http=http, cache_discovery=False) return service + def setup(hass, config): """Set up the Google Fit platform.""" name = config.get(const.CONF_NAME) @@ -179,7 +184,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): token_file = hass.config.path(TOKEN_FILE) client = _get_client(token_file) - add_devices([GoogleFitWeightSensor(client, name), + add_devices([GoogleFitWeightLbsSensor(client,name), + GoogleFitWeightKGSensor(client, name), GoogleFitHeartRateSensor(client, name), GoogleFitHeightSensor(client, name), GoogleFitStepsSensor(client, name), @@ -300,7 +306,8 @@ def _get_dataset_from_last_update(self, source): get(userId=API_USER_ID, dataSourceId=source, datasetId=dataset). \ execute() -class GoogleFitWeightSensor(GoogleFitSensor): + +class GoogleFitWeightKGSensor(GoogleFitSensor): @property def unit_of_measurement(self): """Returns the unit of measurement.""" @@ -314,7 +321,7 @@ def icon(self): @property def _name_suffix(self): """Returns the name suffix of the sensor.""" - return WEIGHT + return WEIGHT_KG @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_UPDATES) def update(self): @@ -361,6 +368,55 @@ def update(self): self._attributes = {} +class GoogleFitWeightLbsSensor(GoogleFitWeightKGSensor): + @property + def unit_of_measurement(self): + """Returns the unit of measurement.""" + return const.MASS_POUNDS + + @property + def icon(self): + """Return the icon.""" + return 'mdi:weight-pound' + + @property + def _name_suffix(self): + """Returns the name suffix of the sensor.""" + return WEIGHT_LBS + + @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Extracts the relevant data points for from the Fitness API.""" + if not self._client: + return + + weight_datasources = self._get_datasources('com.google.weight') + + weight_datapoints = {} + for datasource in weight_datasources: + datasource_id = datasource.get('dataStreamId') + weight_request = self._client.users().dataSources().\ + dataPointChanges().list( + userId=API_USER_ID, + dataSourceId=datasource_id, + ) + weight_data = weight_request.execute() + weight_inserted_datapoints = weight_data.get('insertedDataPoint') + + for datapoint in weight_inserted_datapoints: + point_value = datapoint.get('value') + if not point_value: + continue + weight = point_value[0].get('fpVal') + if not weight: + continue + weight = round(weight * 2.20462262185, 2) # Convert to pounds while rounding + last_update_milis = int(datapoint.get('modifiedTimeMillis', 0)) + if not last_update_milis: + continue + weight_datapoints[last_update_milis] = weight + + class GoogleFitHeightSensor(GoogleFitSensor): @property def unit_of_measurement(self): @@ -462,8 +518,6 @@ def update(self): self._attributes = {} - - class GoogleFitStepsSensor(GoogleFitSensor): DATA_SOURCE = "derived:com.google.step_count.delta:" \ "com.google.android.gms:estimated_steps" @@ -597,6 +651,7 @@ def update(self): _LOGGER.debug("Distance %s", self._state) self._attributes = {} + class GoogleFitSleepSensor(GoogleFitSensor): @property @@ -657,4 +712,4 @@ def update(self): else: self._state = "" self._attributes = {} - self._last_updated = time.time() \ No newline at end of file + self._last_updated = time.time() From 658f903f5f929b3d4fa027f789e6d2e8ab01449a Mon Sep 17 00:00:00 2001 From: tomshaffner <48473431+tomshaffner@users.noreply.github.com> Date: Sun, 6 Dec 2020 20:38:35 -0500 Subject: [PATCH 2/6] Oops - copy/paste error Actual final code, as tested and used --- custom_components/google_fit/sensor.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/custom_components/google_fit/sensor.py b/custom_components/google_fit/sensor.py index d4780ce..8489bfa 100644 --- a/custom_components/google_fit/sensor.py +++ b/custom_components/google_fit/sensor.py @@ -368,7 +368,7 @@ def update(self): self._attributes = {} -class GoogleFitWeightLbsSensor(GoogleFitWeightKGSensor): +class GoogleFitWeightLbsSensor(GoogleFitSensor): @property def unit_of_measurement(self): """Returns the unit of measurement.""" @@ -416,6 +416,18 @@ def update(self): continue weight_datapoints[last_update_milis] = weight + if weight_datapoints: + time_updates = list(weight_datapoints.keys()) + time_updates.sort(reverse=True) + + last_time_update = time_updates[0] + last_weight = weight_datapoints[last_time_update] + + self._last_updated = round(last_time_update / 1000) + self._state = last_weight + _LOGGER.debug("Last weight %s", last_weight) + self._attributes = {} + class GoogleFitHeightSensor(GoogleFitSensor): @property From 0d0ad8c17c2b8137c045ee66632cd224d8fbe26a Mon Sep 17 00:00:00 2001 From: tomshaffner <48473431+tomshaffner@users.noreply.github.com> Date: Sun, 6 Dec 2020 20:42:42 -0500 Subject: [PATCH 3/6] Changing naming to lowercase Looks better in the UI --- custom_components/google_fit/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/google_fit/sensor.py b/custom_components/google_fit/sensor.py index 8489bfa..0fbf111 100644 --- a/custom_components/google_fit/sensor.py +++ b/custom_components/google_fit/sensor.py @@ -51,8 +51,8 @@ # Google Fit API URL. API_VERSION = 'v1' API_USER_ID = 'me' -WEIGHT_LBS = 'weight LBS' -WEIGHT_KG = 'weight KG' +WEIGHT_LBS = 'weight (lbs)' +WEIGHT_KG = 'weight (kg)' HEIGHT = 'height' DISTANCE = 'distance' STEPS = 'steps' From 7e49194faff2e2c641fbae6fc26d3c5cba384f7b Mon Sep 17 00:00:00 2001 From: tomshaffner <48473431+tomshaffner@users.noreply.github.com> Date: Tue, 8 Dec 2020 14:21:36 -0500 Subject: [PATCH 4/6] Adding Mi/KM split too Splitting out Mi/KM too. This has been tested already as well. --- custom_components/google_fit/sensor.py | 46 ++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/custom_components/google_fit/sensor.py b/custom_components/google_fit/sensor.py index 0fbf111..ba596da 100644 --- a/custom_components/google_fit/sensor.py +++ b/custom_components/google_fit/sensor.py @@ -27,8 +27,8 @@ CONF_CLIENT_SECRET = 'client_secret' DEFAULT_NAME = 'Google Fit' ICON = 'mdi:heart-pulse' -MIN_TIME_BETWEEN_SCANS = timedelta(minutes=10) -MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) +MIN_TIME_BETWEEN_SCANS = timedelta(minutes=8) +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=8) SENSOR_NAME = '{} {}' @@ -54,7 +54,8 @@ WEIGHT_LBS = 'weight (lbs)' WEIGHT_KG = 'weight (kg)' HEIGHT = 'height' -DISTANCE = 'distance' +DISTANCE_KM = 'distance (km)' +DISTANCE_MI = 'distance (mi)' STEPS = 'steps' MOVE_TIME = 'move time' CALORIES = 'calories' @@ -192,7 +193,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): GoogleFitSleepSensor(client, name), GoogleFitMoveTimeSensor(client, name), GoogleFitCaloriesSensor(client, name), - GoogleFitDistanceSensor(client, name)], True) + GoogleFitDistanceKmSensor(client, name), + GoogleFitDistanceMiSensor(client, name)], True) class GoogleFitSensor(entity.Entity): @@ -631,14 +633,14 @@ def update(self): self._attributes = {} -class GoogleFitDistanceSensor(GoogleFitSensor): +class GoogleFitDistanceKmSensor(GoogleFitSensor): DATA_SOURCE = "derived:com.google.distance.delta:" \ "com.google.android.gms:merge_distance_delta" @property def _name_suffix(self): """Returns the name suffix of the sensor.""" - return DISTANCE + return DISTANCE_KM @property def unit_of_measurement(self): @@ -663,6 +665,38 @@ def update(self): _LOGGER.debug("Distance %s", self._state) self._attributes = {} +class GoogleFitDistanceMiSensor(GoogleFitSensor): + DATA_SOURCE = "derived:com.google.distance.delta:" \ + "com.google.android.gms:merge_distance_delta" + + @property + def _name_suffix(self): + """Returns the name suffix of the sensor.""" + return DISTANCE_MI + + @property + def unit_of_measurement(self): + """Returns the unit of measurement.""" + return const.LENGTH_MILES + + @property + def icon(self): + """Return the icon.""" + return 'mdi:map-marker-distance' + + @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Extracts the relevant data points for from the Fitness API.""" + values = [] + for point in self._get_dataset(self.DATA_SOURCE)["point"]: + if int(point["startTimeNanos"]) > _today_dataset_start(): + values.append(point['value'][0]['fpVal']) + + self._last_updated = time.time() + self._state = round(sum(values) / 1000 * 0.62137119, 2) + _LOGGER.debug("Distance %s", self._state) + self._attributes = {} + class GoogleFitSleepSensor(GoogleFitSensor): From c3d2ea99cf0a6cf3e80db6b1d8f84c58baa813ce Mon Sep 17 00:00:00 2001 From: tomshaffner <48473431+tomshaffner@users.noreply.github.com> Date: Tue, 8 Dec 2020 23:42:18 -0500 Subject: [PATCH 5/6] Adding meditation sensor Code has been tested and functions. --- custom_components/google_fit/sensor.py | 61 ++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/custom_components/google_fit/sensor.py b/custom_components/google_fit/sensor.py index ba596da..c5379ec 100644 --- a/custom_components/google_fit/sensor.py +++ b/custom_components/google_fit/sensor.py @@ -60,6 +60,7 @@ MOVE_TIME = 'move time' CALORIES = 'calories' SLEEP = 'sleep' +MEDITATION = 'meditation' HEARTRATE = 'heart rate' TOKEN_FILE = '' @@ -79,7 +80,7 @@ def _today_dataset_start(): def _today_dataset_end(): now = datetime.today() - return int(time.mktime(now.timetuple()) * 1000000000) + return int(time.mktime(now.timetuple()) * 1000000000 + 1) def _get_client(token_file): @@ -191,6 +192,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): GoogleFitHeightSensor(client, name), GoogleFitStepsSensor(client, name), GoogleFitSleepSensor(client, name), + GoogleFitMeditationSensor(client, name), GoogleFitMoveTimeSensor(client, name), GoogleFitCaloriesSensor(client, name), GoogleFitDistanceKmSensor(client, name), @@ -698,8 +700,7 @@ def update(self): self._attributes = {} -class GoogleFitSleepSensor(GoogleFitSensor): - +class GoogleFitSleepSensor(GoogleFitSensor): @property def _name_suffix(self): """Returns the name suffix of the sensor.""" @@ -759,3 +760,57 @@ def update(self): self._state = "" self._attributes = {} self._last_updated = time.time() + + +class GoogleFitMeditationSensor(GoogleFitSensor): + @property + def _name_suffix(self): + """Returns the name suffix of the sensor.""" + return MEDITATION + + @property + def unit_of_measurement(self): + """Returns the unit of measurement.""" + return MEDITATION + + @property + def icon(self): + """Return the icon.""" + return 'mdi:meditation' + + @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Extracts the relevant data points for from the Fitness API.""" + yesterday = datetime.now().replace(hour=17,minute=0,second=0,microsecond=0) + yesterday = yesterday - timedelta(days=1) + starttime = yesterday.isoformat("T") + "Z" + today = datetime.now().replace(hour=11,minute=0,second=0,microsecond=0) + endtime = today.isoformat("T") + "Z" + _LOGGER.debug("Starttime %s, Endtime %s", starttime, endtime) + meditation_dataset = self._client.users().sessions().list(userId='me',fields='session',startTime=starttime,endTime=endtime).execute() + starts = [] + ends = [] + meditation = [] + _LOGGER.debug("Meditation dataset %s", meditation_dataset) + for point in meditation_dataset["session"]: + if int(point["activityType"]) == 45 : # https://developers.google.com/fit/rest/v1/reference/activity-types + starts.append(int(point["startTimeMillis"])) + ends.append(int(point["endTimeMillis"])) + meditation_start = datetime.fromtimestamp(int(point["startTimeMillis"]) / 1000) + meditation_end = datetime.fromtimestamp(int(point["endTimeMillis"]) / 1000) + _LOGGER.debug("Meditation dataset Total %s", (meditation_end - meditation_start)) + meditation.append(meditation_end - meditation_start) + + if len(starts) != 0 or len(ends) != 0: + start_time = datetime.fromtimestamp(round(min(starts) / 1000)) + end_time = datetime.fromtimestamp(round(max(ends) / 1000)) + total_meditation = sum(meditation,timedelta()) + total_meditation=total_meditation-timedelta(microseconds=total_meditation.microseconds) + state_dict = dict({'first_start_time': str(start_time), 'last_end_time': str(end_time), 'total_meditation': str(total_meditation)}) + self._state = str(total_meditation) + self._attributes = state_dict + self._last_updated = time.time() + else: + self._state = "" + self._attributes = {} + self._last_updated = time.time() From e4a07f585d26e9a46ae242940dd1c692357b0831 Mon Sep 17 00:00:00 2001 From: tomshaffner <48473431+tomshaffner@users.noreply.github.com> Date: Wed, 9 Dec 2020 00:01:54 -0500 Subject: [PATCH 6/6] Changed Meditation units to Minutes --- custom_components/google_fit/sensor.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/custom_components/google_fit/sensor.py b/custom_components/google_fit/sensor.py index c5379ec..9764425 100644 --- a/custom_components/google_fit/sensor.py +++ b/custom_components/google_fit/sensor.py @@ -771,7 +771,7 @@ def _name_suffix(self): @property def unit_of_measurement(self): """Returns the unit of measurement.""" - return MEDITATION + return 'min' @property def icon(self): @@ -805,9 +805,8 @@ def update(self): start_time = datetime.fromtimestamp(round(min(starts) / 1000)) end_time = datetime.fromtimestamp(round(max(ends) / 1000)) total_meditation = sum(meditation,timedelta()) - total_meditation=total_meditation-timedelta(microseconds=total_meditation.microseconds) state_dict = dict({'first_start_time': str(start_time), 'last_end_time': str(end_time), 'total_meditation': str(total_meditation)}) - self._state = str(total_meditation) + self._state = total_meditation.total_seconds() // 60 self._attributes = state_dict self._last_updated = time.time() else: