Fitbit multi-user scoreboard in Home-Assistant

·

·

Fitbit has been integrated into Home-Assistant as an official integration since 2016, but it has despite many requests always just had the option to add only one single user. For the official integration that is still the case, but not too long ago TheTroubleshooter from the Home-Assistant forum published a solution to the problem on GitHub.

This opens up a lot of possibilities, and one thing I have wanted to make is a “Fitbit scoreboard” for me and my wife. Perhaps this can be an extra motivation to become even more active, competition is at least always fun.


Our current FitBit devices: Fitbit Sense, Fitbit Charge 4 and Fitbit Aria 2


After some days of work, this is what I come up with:

Lovelace

In this blog post I’m going to publish the code, and and try to explain how this works.
In my code longer down in this article fitbit1=UserA and fitbit2=UserB.
There is also possible to add more people to the scoreboard, but requires a little more complex logic and more code. That is however not needed in this household, and because of that not something I have done.

How to integrate multiple Fitbit-accounts in Home-Assistant

It’s not very complicated, just follow the steps in the guide here:
TheTroubleshooter / Home-Assistant / Fitbit – Add Multiple Accounts

I will also add some extra tips to the above guide:

  • Home-Assistant must be accessible on the internet trough HTTPS to pass the Fitbit API authentication.
  • To download the Fitbit1,2,3.. folders from GitHub use this site: https://minhaskamal.github.io/DownGit/
  • If you don’t see the notification to setup the Fitbit sensor in the Notifications section of the menu as described, you may need to add the external URL in Home-Assistant. This can be found under Configuration -> General (More info at https://www.home-assistant.io/docs/configuration/basic/)
Fitbit “Register an application” example

When all steps are complete you should have all accounts integrated.

The Lovelace scoreboard and metrics

On the left I have a scoreboard and other stats. On the right side I have some graphs for steps and calorie consumption.
The Scoreboard is made by a combination of HTML and Jinja2 templates. The Scoreboard will also show a star on who ever has the best result of today for each category.
These stats will be in real-time as fast as the Fitbit API and trackers report data (quite quickly and stably in my experience).
The graphs on the right side is made with the custom Plotly Graph Card card (Available in HACS)

Some of the data shown is not part of the Fitbit API, these are calculated based on different sensor-metrics
Here are some examples:
Active calories in percent of BMR (Basal Metabolic Rate):
sensor.fitbitX_activity_calorie
s / sensor.fitbitX_calories * 100
Lean body mass in KG:
sensor.fitbitX_weight - sensor.fitbitX_body_fat * sensor.fitbitX_weight / 100
Lean body mass in percent:
100 - sensor.fitbitX_body_fat
Body fat in KG:
sensor.fitbitX_body_fat * sensor.fitbitX_weight / 100

The Lovelace graphs

The Steps graph:

type: custom:plotly-graph
title: Steps
entities:
  - entity: sensor.fitbit1_steps
    unit_of_measurement: Steps
    name: UserA
    line:
      color: blue
      shape: line
      width: 2
  - entity: sensor.fitbit2_steps
    unit_of_measurement: Steps
    name: UserB
    line:
      color: magenta
      shape: line
      width: 2
hours_to_show: 24
refresh_interval: 10
layout:
  xaxis:
    rangeselector:
      'y': 1.2
      buttons:
        - count: 12
          step: hour
        - count: 24
          step: hour
        - count: 3
          step: day
        - count: 7
          step: day

The Energy consumption graph:

type: custom:plotly-graph
title: _                Energy consumption
entities:
  - entity: sensor.fitbit1_calories
    unit_of_measurement: kcal
    name: UserA
    line:
      color: blue
      shape: line
      width: 2
  - entity: sensor.fitbit2_calories
    unit_of_measurement: kcal
    name: UserB
    line:
      color: magenta
      shape: line
      width: 2
  - entity: sensor.fitbit1_calories_bmr
    unit_of_measurement: kcal
    name: UserA BMR
    line:
      color: lightblue
      shape: line
      width: 0.5
  - entity: sensor.fitbit2_calories_bmr
    unit_of_measurement: kcal
    name: UserB BMR
    line:
      color: violet
      shape: line
      width: 0.5
hours_to_show: 24
refresh_interval: 10
layout:
  xaxis:
    rangeselector:
      'y': 1.2
      buttons:
        - count: 12
          step: hour
        - count: 24
          step: hour
        - count: 3
          step: day
        - count: 7
          step: day

The Scoreboard and metrics

I know the code below looks super-messy (and it is inevitable when using Jinja2 in a markdown card), but it will look better when imported.

Select ADD CARD -> Manual -> Paste the code -> SHOW CODE EDITOR and it will look as clean like this:

Lovelace Markdown Card Configuration

The Code:

type: markdown
content: "<table border=\"1\" width=100%\">\n\t<caption><h1><ha-icon icon=\"mdi:run-fast\"></ha-icon> Scoreboard</h1></caption>\n\t<thead>\n\t<tr>\n\t\t<th >Category</th>\n\t\t<th>UserA</th>\n\t\t<th>UserB</th>\n\t</tr>\n\t</thead>\n\t<tbody  align=\"center\">\n\t<tr>\n\t\t<td>Steps</td>\n\t\t<td>{% if ( states.sensor.fitbit1_steps.state | int ) > ( states.sensor.fitbit2_steps.state | int ) %}<font color=\"gold\"><ha-icon icon=\"mdi:star\"></ha-icon></font>{% endif %}</br>{{ states(\"sensor.fitbit1_steps\") }} steps</td>\n\t\t<td>{% if ( states.sensor.fitbit1_steps.state | int ) < ( states.sensor.fitbit2_steps.state | int ) %}<font color=\"gold\"><ha-icon icon=\"mdi:star\"></ha-icon></font>{% endif %}</br>{{ states(\"sensor.fitbit2_steps\") }} steps</td>\n\t</tr>\n\t<tr>\n\t\t<td>Elevation.</td>\n\t\t<td>{% if ( states.sensor.fitbit1_elevation.state | int ) > ( states.sensor.fitbit2_elevation.state | int ) %}<font color=\"gold\"><ha-icon icon=\"mdi:star\"></ha-icon></font>{% endif %}\n</br>{{ states(\"sensor.fitbit1_elevation\") }} meters</td>\n\t\t<td>{% if ( states.sensor.fitbit1_elevation.state | int ) < ( states.sensor.fitbit2_elevation.state | int ) %}<font color=\"gold\"><ha-icon icon=\"mdi:star\"></ha-icon></font>{% endif %}\n</br>{{ states(\"sensor.fitbit2_elevation\") }} meters</td>\n\t</tr>\n\t<tr>\n\t\t<td>Distance</td>\n\t\t<td>{% if ( states.sensor.fitbit1_distance.state | float ) > ( states.sensor.fitbit2_distance.state | float ) %}<font color=\"gold\"><ha-icon icon=\"mdi:star\"></ha-icon></font>{% endif %}\n</br>{{ states(\"sensor.fitbit1_distance\") }} km</td>\n\t\t<td>{% if ( states.sensor.fitbit1_distance.state | float ) < ( states.sensor.fitbit2_distance.state | float ) %}<font color=\"gold\"><ha-icon icon=\"mdi:star\"></ha-icon></font>{% endif %}\n</br>{{ states(\"sensor.fitbit2_distance\") }} km</td>\n\t</tr>\n\t<tr>\n\t\t<td>Activity Calories</td>\n\t\t<td>{% if (( states.sensor.fitbit1_activity_calories.state | float ) / (states.sensor.fitbit1_calories.state | float ) * 100) > (( states.sensor.fitbit2_activity_calories.state | float ) / (states.sensor.fitbit2_calories.state | float ) * 100) %}<font color=\"gold\"><ha-icon icon=\"mdi:star\"></ha-icon></font>{% endif %}\n{% if (( states.sensor.fitbit1_activity_calories.state | float ) / (states.sensor.fitbit1_calories.state | float ) * 100) %}</br>{{ (( states.sensor.fitbit1_activity_calories.state | float ) / (states.sensor.fitbit1_calories.state | float ) * 100) | round(0) }}% of BMR ({{states.sensor.fitbit1_activity_calories.state}} kcal){% endif %}</td>\n\t\t<td>{% if (( states.sensor.fitbit1_activity_calories.state | float ) / (states.sensor.fitbit1_calories.state | float ) * 100) < (( states.sensor.fitbit2_activity_calories.state | float ) / (states.sensor.fitbit2_calories.state | float ) * 100) %}<font color=\"gold\"><ha-icon icon=\"mdi:star\"></ha-icon></font>{% endif %}\n{% if (( states.sensor.fitbit2_activity_calories.state | float ) / (states.sensor.fitbit2_calories.state | float ) * 100) %}</br>{{ (( states.sensor.fitbit2_activity_calories.state | float ) / (states.sensor.fitbit2_calories.state | float ) * 100) | round(0) }}% of BMR ({{states.sensor.fitbit2_activity_calories.state}} kcal){% endif %}</td>\n\t</tr>\n\t<tr>\n\t\t<td>Time Asleep</td>\n\t\t<td>{% if ( states.sensor.fitbit1_sleep_minutes_asleep.state | float ) > ( states.sensor.fitbit2_sleep_minutes_asleep.state | float ) %}<font color=\"gold\"><ha-icon icon=\"mdi:star\"></ha-icon></font>{% endif %}\n</br>{{ states(\"sensor.fitbit1_sleep_minutes_asleep\") }} minutes</td>\n\t\t<td>{% if ( states.sensor.fitbit1_sleep_minutes_asleep.state | float ) < ( states.sensor.fitbit2_sleep_minutes_asleep.state | float ) %}<font color=\"gold\"><ha-icon icon=\"mdi:star\"></ha-icon></font>{% endif %}\n</br>{{ states(\"sensor.fitbit2_sleep_minutes_asleep\") }} minutes</td>\n\t</tr>\n</tbody>\n</table>\n\n<table border=\"1\" width=100%\">\n\t<caption><h1><ha-icon icon=\"mdi:human-handsup\"></ha-icon> Body composition</h1></caption>\n\t<thead>\n\t<tr>\n\t\t<th ></th>\n\t\t<th>UserA</th>\n\t\t<th>UserB</th>\n\t</tr>\n\t</thead>\n\t<tbody  align=\"center\">\n\t<tr>\n\t\t<td>Body weight</td>\n\t\t<td>{{ states(\"sensor.fitbit1_weight\") }}KG</td>\n\t\t<td>{{ states(\"sensor.fitbit2_weight\") }}KG</td>\n\t</tr>\n\t<tr>\n\t\t<td>Body mass index</td>\n\t\t<td>{{ states(\"sensor.fitbit1_bmi\") }}</td>\n\t\t<td>{{ states(\"sensor.fitbit2_bmi\") }}</td>\n\t</tr>\n\t<tr>\n\t\t<td>Lean body mass</td>\n\t\t<td>{{ (  (states.sensor.fitbit1_weight.state | float ) - (states.sensor.fitbit1_body_fat.state | float ) * (states.sensor.fitbit1_weight.state | float ) / 100 ) | round(1) }} KG / {{ ( 100 - (states.sensor.fitbit1_body_fat.state | float ) ) | round(1) }}%</td>\n\t\t<td>{{ (  (states.sensor.fitbit2_weight.state | float ) - (states.sensor.fitbit2_body_fat.state | float ) * (states.sensor.fitbit2_weight.state | float ) / 100 ) | round(1) }} KG / {{ ( 100 - (states.sensor.fitbit2_body_fat.state | float ) ) | round(1) }}%</td>\n\t</tr>\n\t<tr>\n\t\t<td>Body fat</td>\n\t\t<td>{{ ( (states.sensor.fitbit1_body_fat.state | float ) * (states.sensor.fitbit1_weight.state | float ) / 100 ) | round(1) }} KG / {{ states(\"sensor.fitbit1_body_fat\") }}%</td>\n\t\t<td>{{ ( (states.sensor.fitbit2_body_fat.state | float ) * (states.sensor.fitbit2_weight.state | float ) / 100 ) | round(1) }} KG / {{ states(\"sensor.fitbit2_body_fat\") }}%</td>\n\t</tr>\n\n</tbody>\n</table>\n\n<table border=\"1\" width=100%\">\n\t<caption><h1><ha-icon icon=\"mdi:heart-pulse \"></ha-icon> Heart health</h1></caption>\n\t<thead>\n\t<tr>\n\t\t<th ></th>\n\t\t<th>UserA</th>\n\t\t<th>UserB</th>\n\t</tr>\n\t</thead>\n\t<tbody  align=\"center\">\n\t<tr>\n\t\t<td>Resting Heart Rate</td>\n\t\t<td>{{ states(\"sensor.fitbit1_resting_heart_rate\") }} bpm</td>\n\t\t<td>{{ states(\"sensor.fitbit2_resting_heart_rate\") }} bpm</td>\n\t</tr>\n\t<tr>\n\t\t<td>Time sedentary</td>\n\t\t<td>{{ states(\"sensor.fitbit1_minutes_sedentary\") }} minutes</td>\n\t\t<td>{{ states(\"sensor.fitbit2_minutes_sedentary\") }} minutes</td>\n\t</tr>\n\t<tr>\n\t\t<td>Time lightly active</td>\n\t\t<td>{{ states(\"sensor.fitbit1_minutes_lightly_active\") }} minutes</td>\n\t\t<td>{{ states(\"sensor.fitbit2_minutes_lightly_active\") }} minutes</td>\n\t</tr>\n\t<tr>\n\t\t<td>Time fairly active</td>\n\t\t<td>{{ states(\"sensor.fitbit1_minutes_fairly_active\") }} minutes</td>\n\t\t<td>{{ states(\"sensor.fitbit2_minutes_fairly_active\") }} minutes</td>\n\t</tr>\n\t<tr>\n\t\t<td>Time very active</td>\n\t\t<td>{{ states(\"sensor.fitbit1_minutes_very_active\") }} minutes</td>\n\t\t<td>{{ states(\"sensor.fitbit2_minutes_very_active\") }} minutes</td>\n\t</tr>\n</tbody>\n</table>"

I am very happy with the solution and hope this can be an inspiration to others.

Share