From c059dfdb676d4ef736c52421cb236919d525f6e8 Mon Sep 17 00:00:00 2001
From: Matt Schmitt <schmittx@users.noreply.github.com>
Date: Sat, 19 Aug 2017 16:47:32 -0400
Subject: [PATCH] Update Fitbit sensor (icons, formatting, client update)
 (#9031)

* Update fitbit.py

Add variable icon for battery status, clean up formatting for resource
names and values

* Update fitbit.py and requirements_all.txt

Fix PR comments and update client

* Update fitbit.py

Add dict map for battery levels and use icon util
---
 homeassistant/components/sensor/fitbit.py | 165 ++++++++++++++--------
 requirements_all.txt                      |   2 +-
 2 files changed, 107 insertions(+), 60 deletions(-)

diff --git a/homeassistant/components/sensor/fitbit.py b/homeassistant/components/sensor/fitbit.py
index 23bf93fde2a..ca5a77fcdd0 100644
--- a/homeassistant/components/sensor/fitbit.py
+++ b/homeassistant/components/sensor/fitbit.py
@@ -17,9 +17,10 @@ from homeassistant.components.http import HomeAssistantView
 from homeassistant.components.sensor import PLATFORM_SCHEMA
 from homeassistant.const import ATTR_ATTRIBUTION
 from homeassistant.helpers.entity import Entity
+from homeassistant.util.icon import icon_for_battery_level
 import homeassistant.helpers.config_validation as cv
 
-REQUIREMENTS = ['fitbit==0.2.3']
+REQUIREMENTS = ['fitbit==0.3.0']
 
 _CONFIGURING = {}
 _LOGGER = logging.getLogger(__name__)
@@ -31,6 +32,7 @@ ATTR_CLIENT_SECRET = 'client_secret'
 ATTR_LAST_SAVED_AT = 'last_saved_at'
 
 CONF_MONITORED_RESOURCES = 'monitored_resources'
+CONF_CLOCK_FORMAT = 'clock_format'
 CONF_ATTRIBUTION = 'Data provided by Fitbit.com'
 
 DEPENDENCIES = ['http']
@@ -48,40 +50,50 @@ DEFAULT_CONFIG = {
 }
 
 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': '%',
-    'devices/battery': 'level',
-    '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': ''
+    'activities/activityCalories': ['Activity Calories', 'cal', 'fire'],
+    'activities/calories': ['Calories', 'cal', 'fire'],
+    'activities/caloriesBMR': ['Calories BMR', 'cal', 'fire'],
+    'activities/distance': ['Distance', '', 'map-marker'],
+    'activities/elevation': ['Elevation', '', 'walk'],
+    'activities/floors': ['Floors', 'floors', 'walk'],
+    'activities/heart': ['Resting Heart Rate', 'bpm', 'heart-pulse'],
+    'activities/minutesFairlyActive':
+        ['Minutes Fairly Active', 'minutes', 'walk'],
+    'activities/minutesLightlyActive':
+        ['Minutes Lightly Active', 'minutes', 'walk'],
+    'activities/minutesSedentary':
+        ['Minutes Sedentary', 'minutes', 'seat-recline-normal'],
+    'activities/minutesVeryActive': ['Minutes Very Active', 'minutes', 'run'],
+    'activities/steps': ['Steps', 'steps', 'walk'],
+    'activities/tracker/activityCalories':
+        ['Tracker Activity Calories', 'cal', 'fire'],
+    'activities/tracker/calories': ['Tracker Calories', 'cal', 'fire'],
+    'activities/tracker/distance': ['Tracker Distance', '', 'map-marker'],
+    'activities/tracker/elevation': ['Tracker Elevation', '', 'walk'],
+    'activities/tracker/floors': ['Tracker Floors', 'floors', 'walk'],
+    'activities/tracker/minutesFairlyActive':
+        ['Tracker Minutes Fairly Active', 'minutes', 'walk'],
+    'activities/tracker/minutesLightlyActive':
+        ['Tracker Minutes Lightly Active', 'minutes', 'walk'],
+    'activities/tracker/minutesSedentary':
+        ['Tracker Minutes Sedentary', 'minutes', 'seat-recline-normal'],
+    'activities/tracker/minutesVeryActive':
+        ['Tracker Minutes Very Active', 'minutes', 'run'],
+    'activities/tracker/steps': ['Tracker Steps', 'steps', 'walk'],
+    'body/bmi': ['BMI', 'BMI', 'human'],
+    'body/fat': ['Body Fat', '%', 'human'],
+    'body/weight': ['Weight', '', 'human'],
+    'devices/battery': ['Battery', None, None],
+    'sleep/awakeningsCount':
+        ['Awakenings Count', 'times awaken', 'sleep'],
+    'sleep/efficiency': ['Sleep Efficiency', '%', 'sleep'],
+    'sleep/minutesAfterWakeup': ['Minutes After Wakeup', 'minutes', 'sleep'],
+    'sleep/minutesAsleep': ['Sleep Minutes Asleep', 'minutes', 'sleep'],
+    'sleep/minutesAwake': ['Sleep Minutes Awake', 'minutes', 'sleep'],
+    'sleep/minutesToFallAsleep':
+        ['Sleep Minutes to Fall Asleep', 'minutes', 'sleep'],
+    'sleep/startTime': ['Sleep Start Time', None, 'clock'],
+    'sleep/timeInBed': ['Sleep Time in Bed', 'minutes', 'hotel']
 }
 
 FITBIT_MEASUREMENTS = {
@@ -120,9 +132,18 @@ FITBIT_MEASUREMENTS = {
     }
 }
 
+BATTERY_LEVELS = {
+    'High': 100,
+    'Medium': 50,
+    'Low': 20,
+    'Empty': 0
+}
+
 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
     vol.Optional(CONF_MONITORED_RESOURCES, default=FITBIT_DEFAULT_RESOURCES):
         vol.All(cv.ensure_list, [vol.In(FITBIT_RESOURCES_LIST)]),
+    vol.Optional(CONF_CLOCK_FORMAT, default='24H'):
+        vol.In(['12H', '24H'])
 })
 
 
@@ -257,6 +278,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
 
         dev = []
         registered_devs = authd_client.get_devices()
+        clock_format = config.get(CONF_CLOCK_FORMAT)
         for resource in config.get(CONF_MONITORED_RESOURCES):
 
             # monitor battery for all linked FitBit devices
@@ -264,11 +286,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
                 for dev_extra in registered_devs:
                     dev.append(FitbitSensor(
                         authd_client, config_path, resource,
-                        hass.config.units.is_metric, dev_extra))
+                        hass.config.units.is_metric, clock_format, dev_extra))
             else:
                 dev.append(FitbitSensor(
                     authd_client, config_path, resource,
-                    hass.config.units.is_metric))
+                    hass.config.units.is_metric, clock_format))
         add_devices(dev, True)
 
     else:
@@ -361,34 +383,24 @@ class FitbitSensor(Entity):
     """Implementation of a Fitbit sensor."""
 
     def __init__(self, client, config_path, resource_type,
-                 is_metric, extra=None):
+                 is_metric, clock_format, extra=None):
         """Initialize the Fitbit sensor."""
         self.client = client
         self.config_path = config_path
         self.resource_type = resource_type
+        self.is_metric = is_metric
+        self.clock_format = clock_format
         self.extra = extra
-        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'
-        elif pretty_resource == 'Heart':
-            pretty_resource = 'Resting Heart Rate'
-        elif pretty_resource == 'Devices Battery':
-            if self.extra:
-                pretty_resource = \
-                    '{0} Battery'.format(self.extra.get('deviceVersion'))
-            else:
-                pretty_resource = 'Battery'
-
-        self._name = pretty_resource
-        unit_type = FITBIT_RESOURCES_LIST[self.resource_type]
+        self._name = FITBIT_RESOURCES_LIST[self.resource_type][0]
+        if self.extra:
+            self._name = '{0} Battery'.format(self.extra.get('deviceVersion'))
+        unit_type = FITBIT_RESOURCES_LIST[self.resource_type][1]
         if unit_type == "":
             split_resource = self.resource_type.split('/')
             try:
                 measurement_system = FITBIT_MEASUREMENTS[self.client.system]
             except KeyError:
-                if is_metric:
+                if self.is_metric:
                     measurement_system = FITBIT_MEASUREMENTS['metric']
                 else:
                     measurement_system = FITBIT_MEASUREMENTS['en_US']
@@ -414,9 +426,11 @@ class FitbitSensor(Entity):
     @property
     def icon(self):
         """Icon to use in the frontend, if any."""
-        if self.resource_type == 'devices/battery':
-            return 'mdi:battery-50'
-        return 'mdi:walk'
+        if self.resource_type == 'devices/battery' and self.extra:
+            battery_level = BATTERY_LEVELS[self.extra.get('battery')]
+            return icon_for_battery_level(battery_level=battery_level,
+                                          charging=None)
+        return 'mdi:{}'.format(FITBIT_RESOURCES_LIST[self.resource_type][2])
 
     @property
     def device_state_attributes(self):
@@ -438,7 +452,40 @@ class FitbitSensor(Entity):
         else:
             container = self.resource_type.replace("/", "-")
             response = self.client.time_series(self.resource_type, period='7d')
-            self._state = response[container][-1].get('value')
+            raw_state = response[container][-1].get('value')
+            if self.resource_type == 'activities/distance':
+                self._state = format(float(raw_state), '.2f')
+            elif self.resource_type == 'activities/tracker/distance':
+                self._state = format(float(raw_state), '.2f')
+            elif self.resource_type == 'body/bmi':
+                self._state = format(float(raw_state), '.1f')
+            elif self.resource_type == 'body/fat':
+                self._state = format(float(raw_state), '.1f')
+            elif self.resource_type == 'body/weight':
+                self._state = format(float(raw_state), '.1f')
+            elif self.resource_type == 'sleep/startTime':
+                if raw_state == '':
+                    self._state = '-'
+                elif self.clock_format == '12H':
+                    hours, minutes = raw_state.split(':')
+                    hours, minutes = int(hours), int(minutes)
+                    setting = 'AM'
+                    if hours > 12:
+                        setting = 'PM'
+                        hours -= 12
+                    elif hours == 0:
+                        hours = 12
+                    self._state = '{}:{} {}'.format(hours, minutes, setting)
+                else:
+                    self._state = raw_state
+            else:
+                if self.is_metric:
+                    self._state = raw_state
+                else:
+                    try:
+                        self._state = '{0:,}'.format(int(raw_state))
+                    except TypeError:
+                        self._state = raw_state
 
         if self.resource_type == 'activities/heart':
             self._state = response[container][-1]. \
diff --git a/requirements_all.txt b/requirements_all.txt
index 3f661c29f78..d89e93bef7e 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -219,7 +219,7 @@ fedexdeliverymanager==1.0.3
 feedparser==5.2.1
 
 # homeassistant.components.sensor.fitbit
-fitbit==0.2.3
+fitbit==0.3.0
 
 # homeassistant.components.sensor.fixer
 fixerio==0.1.1
-- 
GitLab