Frontend Panel for Diabetics using a Dexcom CGM

So, as a Type 1 Diabetic using a CGM device, I wanted a Lovelace panel to give me all the information i needed in a nice looking detailed layout — This is what I’ve come up with for myself.

This layout uses the following:

  • Dexcom Custom Component This retrieves the BG/Trend data from your Dexcom CGM and creates 2 sensors; one for BG current value, and one for trend direction.
  • Average Custom Component. Found here
  • Custom: mini-graph-card
  • Custom: text-element card
  • A free Nightscout account setup through Heroku (Setup tutorial Here) This is not mandatory, but its what displays the middle colored box on the panel with BG Level and Trending direction within an iframe.You could create a picture-element card here instead with overlaying colors for warning levels if you wanted too — i was lazy.
  • Here are the two images for the Sensor and Transmitter
  • The Theme I’m using is “AMOLED Blue”. It’s in HACS

This panel uses only the 2 dexcom component sensors for all the graphs. The amount of time in the “CGM Graph (time)” is adjusted via the mini-card-graph attributes in Lovelace. Basically everything else is templated sensors.

The Sensor/Transmitter dates and days left are all calculated from the 2 input_datetime’s on the bottom right. I have them converted to 12HR time and date formatted Month Day, Year, but you can just change the timestamp_custom values in the config to suit your needs in displaying. Also, the glucose values are set to mmol/L. You can use whichever you prefer in the Dexcom Custom Component setup. Setup that custom component FIRST and confirm you’re receiving BG data.

Here is what you’ll need to setup (date & time = true)

  • input_datetime.cgm_sensor_start
  • input_datetime.cgm_trans_start

Ok… onto the code.

Config yaml (the sensor templates)

# Average out BG over 90 days (aka eAG value)
  - platform: average
    name: 'Glucose Avg 90d'
    duration:
      days: 90
    entities:
      - sensor.dexcom_glucose_value # This is from the Dexcom Custom Component. I Labeled it this

# Calculate HbA1C % based off 90 day BG averge sensor above
# NOTE: These calculations are based on HbA1C from mmol/L values. 
# The math is different for MG/DL. If you cant figure it out, let me know and ill make a template
# for HbA1C based off MG/DL
  - platform: template
    sensors:
      jons_hba1c_value:
        entity_id: sensor.glucose_avg_90d
        value_template: >
          {% set a = ( states("sensor.glucose_avg_90d") | float + 2.59 ) %}
          {% set a1c = (a / 1.59) %}
          {{a1c}}
 
# Format the Date and Time of the dexcom sensor start to “January 1, 2000 // 1:00 AM” format.
  - platform: template
    sensors:
      cgm_sensor_start:
        friendly_name: "G6 Sensor Started: "
        value_template: >
          {{ state_attr('input_datetime.cgm_sensor_start', 'timestamp') | timestamp_custom('%B %-d, %Y')}} // {{state_attr('input_datetime.cgm_sensor_start', 'timestamp') | timestamp_custom('%-I:%M %p') }}
        icon_template: >
          mdi:leak

# Do the same formatting of the date and time for sensor expiry.
  - platform: template
    sensors:
      cgm_sensor_expire:
        friendly_name: "G6 Sensor Expires on: "
        value_template: >
          {{ (state_attr('input_datetime.cgm_sensor_start', 'timestamp') + 864000) | timestamp_custom('%B %-d, %Y')}} // {{state_attr('input_datetime.cgm_sensor_start', 'timestamp') | timestamp_custom('%-I:%M %p') }}
        icon_template: >
          mdi:leak-off
          
# The Dexcom G6 Sensors only last 10 days before it expires in the dexcom app. 
# This sensor Counts Down from 10 days from the start date and displays it as “X Days” left. 
# When you get down to 2 days remaining, it will change to “Tomorrow...” and then “TODAY!”
  - platform: template
    sensors:
      cgm_sensor_expire_days:
        friendly_name: "G6 Sensor Expires in: "
        value_template: >
          {% set days =  (( as_timestamp(strptime(states.input_datetime.cgm_sensor_start.state, "%Y-%m-%dT%H:%M:%S")) + 864000 - as_timestamp(now()) )/ (3600*24)) | round(1) | int %}
          {%- if days < 1 -%}
          TODAY!
          {%- else -%}
          {%- if days < 2 -%}
          Tomorrow...
          {%- endif -%}
          {%- if days > 1 -%}
          {{ days }} Days
          {%- endif -%}
          {%- endif -%}
        icon_template: >
          mdi:leak-off
          
# These Sensor templates follow the same templates for the Transmitter start, expiry, and days left as the G6 Sensor templates above. 
  - platform: template
    sensors:
      cgm_trans_start:
        friendly_name: "G6 Transmitter Started: "
        value_template: >
          {{ state_attr('input_datetime.cgm_trans_start', 'timestamp') | timestamp_custom('%B %-d, %Y')}} // {{state_attr('input_datetime.cgm_trans_start', 'timestamp') | timestamp_custom('%-I:%M %p') }}
        icon_template: >
          mdi:leak

# Format Expiry date/time         
  - platform: template
    sensors:
      cgm_trans_expire:
        friendly_name: "G6 Transmitter Expires on: "
        value_template: >
          {{ (state_attr('input_datetime.cgm_trans_start', 'timestamp') + 7776000) | timestamp_custom('%B %-d, %Y')}} // {{state_attr('input_datetime.cgm_trans_start', 'timestamp') | timestamp_custom('%-I:%M %p') }}
        icon_template: >
          mdi:leak-off
          
# The Dexcom G6 Transmitters have a life of 90 days before they’re “expired” in the Dexcom App. 
# This template Counts Down from 90 Days.
  - platform: template
    sensors:
      cgm_trans_expire_days:
        friendly_name: "G6 Transmitter Expires in: "
        value_template: >
          {% set days =  (( as_timestamp(strptime(states.input_datetime.cgm_trans_start.state, "%Y-%m-%dT%H:%M:%S")) + 7776000 - as_timestamp(now()) )/ (3600*24)) | round(1) | int %}
          {%- if days < 1 -%}
          TODAY!
          {%- else -%}
          {%- if days < 2 -%}
          Tomorrow...
          {%- endif -%}
          {%- if days > 1 -%}
          {{ days }} Days
          {%- endif -%}
          {%- endif -%}
        icon_template: >
          mdi:leak-off
 

Here is the Lovelace yaml
If you are editing your frontend from within the Lovelace UI, you’ll have to adjust the YAML spacing here; otherwise you’ll just get yelled at by Lovelace and nothing will display. You can Paste this into the RAW Configuration Editor though.

Another side-note about the units and values here, remember, mine is setup for mmol/L standard, so the mini-graph colors reflect mmol/L numbers. If you use mg/dl, just change the values in the color_thresholds to mg/dl numbers. In my case, 2 (low) would be 36 mg, 5 - 10 (normal) would be 90 - 180 mg, and anything about 15 (High) is 270 mg.


  - badges: []
    cards:
      - cards:
          - color_thresholds:
              - color: '#039BE5'
                value: 2
              - color: '#0DA035'
                value: 5
              - color: '#E0B400'
                value: 10
              - color: '#E45E65'
                value: 15
            detail: 1
            entities:
              - entity: sensor.jons_hba1c_value
                show_fill: false
                show_state: true
                smoothing: true
            hours_to_show: 2160
            icon: 'mdi:water'
            line_width: 3
            name: HbA1C Avg - 90 days
            show:
              graph: line
            type: 'custom:mini-graph-card'
            unit: '%'
          - color_thresholds:
              - color: '#039BE5'
                value: 2
              - color: '#0DA035'
                value: 5
              - color: '#E0B400'
                value: 10
              - color: '#E45E65'
                value: 15
            detail: 1
            entities:
              - entity: sensor.glucose_avg_90d
                show_fill: false
                show_state: true
                smoothing: true
            hours_to_show: 2160
            icon: 'mdi:water'
            line_width: 3
            name: eAG Avg - 90 days
            show:
              graph: line
            type: 'custom:mini-graph-card'
            unit: mmol/L
          - aggregate_func: avg
            animate: true
            color_thresholds:
              - color: '#039BE5'
                value: 2
              - color: '#0da035'
                value: 5
              - color: '#e0b400'
                value: 10
              - color: '#e45e65'
                value: 15
            entities:
              - entity: sensor.dexcom_glucose_value
            group_by: date
            hours_to_show: 168
            name: Avg CGM Value Per Day
            show:
              graph: bar
              labels: true
              state: true
            type: 'custom:mini-graph-card'
          - cards:
              - cards:
                  - entity: sensor.dexcom_glucose_trend
                    name: Current Glucose Trend
                    type: entity
                type: horizontal-stack
            type: horizontal-stack
        type: vertical-stack
      - cards:
          - aspect_ratio: 500x220
            type: iframe
            url: 'https://YOUR NIGHTSCOUT APP ID.herokuapp.com/clock/clock-color'
          - elements:
              - style:
                  bottom: 0
                  color: white
                  font-size: 16px
                  line-height: 27px
                  padding: 12px 10px
                  transform: initial
                text: 'Expires:'
                type: 'custom:text-element'
              - style:
                  color: white
                  font-size: 16px
                  line-height: 27px
                  padding: 12px 10px
                  top: 0
                  transform: initial
                text: 'Sensor Start Date:'
                type: 'custom:text-element'
              - entity: sensor.cgm_sensor_expire_days
                style:
                  background-color: 'rgba(0, 0, 0, 0.0)'
                  bottom: 0
                  color: lightblue
                  font-size: 16px
                  left: 0
                  line-height: 32px
                  padding: 0 65px
                  pointer-events: none
                  transform: initial
                  width: 100%
                type: state-label
              - entity: sensor.cgm_sensor_start
                style:
                  background-color: 'rgba(0, 0, 0, 0.0)'
                  color: lightblue
                  font-size: 15px
                  right: 0
                  line-height: 32px
                  padding: 2px 1px
                  top: 0
                  transform: initial
                type: state-label
              - entity: sensor.cgm_sensor_expire
                style:
                  bottom: 0
                  color: lightblue
                  font-size: 15px
                  line-height: 32px
                  padding: 0px 1px
                  pointer-events: none
                  right: 0
                  transform: initial
                type: state-label
            image: /local/g6sensor.jpg
            type: picture-elements
          - cards:
              - elements:
                  - style:
                      bottom: 0
                      color: white
                      font-size: 16px
                      line-height: 27px
                      padding: 12px 10px
                      transform: initial
                    text: 'Expires:'
                    type: 'custom:text-element'
                  - style:
                      color: white
                      font-size: 16px
                      line-height: 27px
                      padding: 12px 10px
                      top: 0
                      transform: initial
                    text: 'Transmitter Start Date:'
                    type: 'custom:text-element'
                  - entity: sensor.cgm_trans_expire_days
                    style:
                      background-color: 'rgba(0, 0, 0, 0.0)'
                      bottom: 0
                      color: lightblue
                      font-size: 16px
                      left: 0
                      line-height: 32px
                      padding: 0 65px
                      pointer-events: none
                      transform: initial
                      width: 100%
                    type: state-label
                  - entity: sensor.cgm_trans_start
                    style:
                      background-color: 'rgba(0, 0, 0, 0.0)'
                      color: lightblue
                      font-size: 15px
                      right: 0
                      line-height: 32px
                      padding: 2px 1px
                      top: 0
                      transform: initial
                    type: state-label
                  - entity: sensor.cgm_trans_expire
                    style:
                      bottom: 0
                      color: lightblue
                      font-size: 15px
                      line-height: 32px
                      padding: 0px 1px
                      pointer-events: none
                      right: 0
                      transform: initial
                    type: state-label
                image: /local/g6transmitter.jpg
                type: picture-elements
            type: horizontal-stack
        type: vertical-stack
      - cards:
          - animate: true
            color_thresholds:
              - color: '#039BE5'
                value: 2
              - color: '#0DA035'
                value: 5
              - color: '#E0B400'
                value: 10
              - color: '#E45E65'
                value: 15
            detail: 2
            entities:
              - entity: sensor.dexcom_glucose_value
                show_fill: true
                show_state: false
                smoothing: true
            hours_to_show: 12
            line_width: 3
            name: CGM Graph - 12 Hours
            points_per_hour: 60
            show:
              graph: line
              labels: true
            type: 'custom:mini-graph-card'
            unit: mmol/L
          - animate: true
            color_thresholds:
              - color: '#c0392b'
                value: 3
              - color: '#08ff2d'
                value: 5
              - color: '#f7ff08'
                value: 10
              - color: '#ff3508'
                value: 15
            detail: 1
            entities:
              - sensor.dexcom_glucose_value
            graph: line
            hours_to_show: 36
            line_width: 3
            name: CGM Graph - 36 Hours
            points_per_hour: 30
            show:
              labels: true
            type: 'custom:mini-graph-card'
          - animate: true
            color_thresholds:
              - color: '#c0392b'
                value: 3
              - color: '#08ff2d'
                value: 5
              - color: '#f7ff08'
                value: 10
              - color: '#ff3508'
                value: 15
            entities:
              - sensor.dexcom_glucose_value
            hours_to_show: 168
            line_width: 3
            name: CGM Graph - 7 Days
            points_per_hour: 5
            show:
              labels: true
              state: true
            type: 'custom:mini-graph-card'
          - cards:
              - entities:
                  - entity: input_datetime.cgm_sensor_start
                  - entity: input_datetime.cgm_trans_start
                show_header_toggle: false
                type: entities
            type: horizontal-stack
        type: vertical-stack
    icon: 'mdi:diabetes'
    path: jon-cgm-data
    title: Jon CGM Data

Be sure to pay attention to the entities in my code — specifically the sensor.dexcom_glucose_value, and sensor.dexcom_glucose_trend. These two are the sensors coming from the Dexcom Custom Component — Yours will be different depending on what you label them in hass. All the other entities are created through the config templates, and when first setting this up, I’d suggest leaving them named as is for now. Once every thing is working, then you could go back and change the sensor names.

- IMPORTANT -
As for the HbA1C % and eAG average; remember this does NOT replace getting your HbA1C level checked out via a proper lab blood test. These numbers are approximate based off the data received by your Dexcom CGM. Downtime, errors, etc etc will effect those values. Also, it takes an entire 90 days after starting these sensor templates to get even a remotely close percentage and value. The % and eAG value will bounce around until the 90 days of history data is collected. So remember, even after the 90 days, this is just to give you an idea; not a accurate result to make medical decisions based off.

I had a ton of help from this community setting all this up — I’m far from a templating expert. So if you’re running into errors i may not be the best person to ask. But ill try if anything isn’t working the way out should.

Enjoy.

11 Likes

From a non-dexcom user ( am a freestyle libre user) this looks fantastic.

2 Likes

Thanks. But it has flaws I’ll need to address. Storing BG data for 90 days 24/7 is taking its toll on my Pi at the moment. So some tweaking is needed there.

But in reality, this entire thing is populated mainly from only 2 sensors. My BG Level, and trend. If you can track down a Libre API, you could probably do the same.

I can probably get the info from nightscout anyway!

Move it off the pi to a real computer!

Something like that; probably a cloud server though. I want to keep Hass independent of any of my computers at home — I change those too often.

I moved my Home Assistant from the Pi3 to an Intel NUC running Ubuntu, and have been quite happy.

I wanted to implement your panel in my Lovelace front-end, but where are you getting the sensor and transmitter dates? Also, after making the changes to my sensors.yaml, I get this config error:
“Platform error sensor.average - Integration ‘average’ not found.”
Do you have other dependencies I don’t have installed?

The Sensor and Transmitter dates are manually set in the bottom right of the UI panel. You have to create those two input_datetime’s Just enter the date and time (in the format you see there) and the templates will convert that into “July 19, 2020 // 2:30 PM” type format.

As for the Average error; my bad, forgot to mention the custom Average Component. You can find that Here

Thanks, I’ll get to that shortly.

Thanks for putting this together and sharing. I’m going to try to setup a similar card as I use the dexcom from my wife as she’s the type 1 diabetic.
I have the value and trend working for a while but really like the enhancements you’ve done.

We use the mg/dl numbers so I’d like a second opinion if my formula for determining the average glucose number is correct. I found the formula here

value_template: >
  {% set a = ( states("sensor.dexcom_glucose_avg_90d") | float + 46.7 ) %}
  {% set a1c = (a / 28.7) %}
  {{a1c | round(2) }}

The second thought that i’d like to implement and maybe anyone else is the fact that from time to time the dexcom status is shown as “unknown” and thus no data is being displayed or shared. I’m trying to create an automation to send a push message to iOS. I have the notification working but having trouble with the value_template for the “unknown” portion. In the example below I want to be notified every 5 minutes. This time pattern works without issue as I’ve used it many times in other automations so the real issue are the two conditions below.

  - alias: "Dexcom Unknown Notification" 
    trigger:
      platform: time_pattern
      minutes: /5 # repeat every 5 min.
    condition: 
      - condition: template
        value_template: >-
          {{ state_attr('sensor.dexcom_glucose_value' 'attr') == unknown, unavailable, none}}
      -  condition: template
         value_template: >-
           {{ state_attr('sensor.dexcom_glucose_trend' 'attr') == unknown, unavailable, none}}
    action:
      - service: notify.mobile_app_iphone
        data:
          message: |
            DEXCOM BLOOD SUGAR IS UNKNOWN
            {{now().strftime("Date: %m-%d-%Y  Time: %I:%M:%S  %p")}}

Since I’m not a template expert if someone could school me into combining both “value” and “trend” attributes into one line of code would be great. Could it be as simple as an ‘or’ statement between them?

Any thoughts or recommendations would be much appreciated.

Thanks again for doing this. I’m having two initial issues that I’m not sure how to solve.

1.) The two pictures. I see in the raw config editor the path is local/g6sensor.
I don’t have this and have them stored in config/www and have changed the raw config editor to point to that path but still have nothing.

2.) I’m not following the portion about manually setting the input of the date time in the UI. Setting the dates of the sensor and transmitter as they are displayed but I’m just not getting it.

Thanks for any input!

As for the images. Those are placed in a custom card called “text element” (I think, I’ll confirm later). It basically works off the picture element card concept but allows CSS placement of text etc.

My images are located in /www folder. “/local/“ is basically /www. So don’t have it in /www/local/. But you need the custom card. It’s in HACS.

As for the manual times and dates. I’m not sure what you’re having a problem with exactly. When you manually update those date/times it enters those values into an input_datetime, and that is read by all the templates throughout to display the expiry dates and times — like on the sensor and transmitter images. The template converts the dates and times to human readable outputs. So that’s why you only have to place those dates and times in that one place, the rest it auto populated.

Start by downloading and installing that custom card so you get rid of the red error boxes. That way we can see if other errors are coming up.

You know if its possible change format on year, day and month? I want to have it like this on the frontend: “Day-Month-Year”
2020-11-02_14h04_13

Thanks but I’m still struggling with this.

I believe I have the pictures stored correctly as they display behind the red boxes. The snippet from my configuration.yaml is the mini-graph-card info. Seems the links for the mini-graph-card and text-element point to the same location.

I’m not understanding how or where to get the manual date and times. I understand that this is a manual entry but not sure where to input the datetime info.

My templates are stored in the sensor.yaml file

Custom Card Configuration.yaml

Would you mind explaining how you got this to work? I’m struggling with this to get any start date shown. I believe I know how to format the date time aspect but simply cannot get it to display.

I did this in my configuraiton.yaml but know it’s wrong.

  timestamp_custom:
    - input_datetime:
    - input_datetime.cgm_sensor_start
    - input_datetime.cgm_trans_start

Any help/thoughts are welcome

You have to create those input_datetime inputs. Goto Menu > Configuration > helpers
And create two datetime variables. To make things easier, call the first one “cgm_sensor_start” and the other “cgm_trans_start”.

You don’t have those set up, that’s why you’re getting those “entity not available” errors there.

You also still need to goto HACS and install the custom “text element” card. That’s why you’re getting those red box errors with the images. (Assuming you have HACS installed). If not, Google how to install HACS into home assistant and go from there.

1 Like

:+1:
Got it and thank you.
Now to move on to automate for when there is not data or the BS is unknown. Since the sensor expires tomorrow, I’m going to test the automation of no data / unknown.

Ultimately I want to create automations for when the BS is low during a certain time period (middle of the night while sleeping) so that when a low BS occurs the automation will notify our Alexa and Alexa will speak a message. I’m wanting and really needing to do this because this CGM is for my wife who is more than 70% deaf and she cannot hear the iOS low notification but somehow can hear the Alexa just fine as she uses that for an alarm to wake.

I’d be careful relying on Alexa to always work in such an emergency situation. My echos routinely just don’t output the sounds I have automated for some reason.

In my case, I have lights in my bedroom come on at 100% and strobe (as well as Alexa alarms) and they won’t stop until I trip a motion sensor in my kitchen (meaning I got up and delt with it). It won’t trigger again unless 30min passes and my levels haven’t started trending upwards again.

As a safety precaution, I would install the phone app “SugarMate”. It’s a 3rd party app like dexcoms, but it has many more features — including a phone call when you reach certain levels.

Thanks again and will investigate those options too.

For anyone using this terrific frontend panel (thanks CaptainSweatpants - Jon) I’ve added two automations for when there is no data. They both seem to provide the same results but are slightly different and I cannot tell if one is better than the other. I’m using these automations to notify iOS phone via push message.

I’m far from any expert with templates and have learned so much from many using this forum.

AUTOMATION ONE:

    trigger:
    - platform: time_pattern
      minutes: /15
    condition: 
      condition: template
      # if check for no data every 15 minutes. Dexcom data updates normally occur every 5 - 6 minutes
      value_template: '{{ ( ( ( ( ( as_timestamp(now()) ) - ( as_timestamp(states.sensor.dexcom_glucose_value.last_changed) ) | int ) | round(0) ) / 60 ) | int ) > 10 }}'
    action: 
    - service: notify.mobile_app_iphone
      data:
        message: |
         DEXCOM LAST KNOWN VALUE: '{{states('sensor.dexcom_glucose_value')  }}'
         NO DEXCOM DATA UPDATES FOR:             
         {{(now() - states.sensor.dexcom_glucose_value.last_updated) .total_seconds() | int |  timestamp_custom("%H Hrs: %M Min: %S Sec:",false) }}       
         {{now().strftime("Date: %m-%d-%Y  Time: %I:%M:%S  %p")}}

AUTOMATION TWO:

  - alias: "Dexcom No Glucose Data Notification"
    trigger:
      - platform: state
        entity_id: sensor.dexcom_glucose_value
      - platform: state
        entity_id: sensor.time # using this entity will trigger the action every minute once the last changed condition is met. Added a delay in action
    condition:
      condition: template
      value_template: >-
        {% set temp_now = as_timestamp(now()) %}
        {% set temp_lastchanged_absolute = as_timestamp(states.sensor.dexcom_glucose_value.last_changed) %}
        {% set temp_lastchanged_sec = ((temp_now - temp_lastchanged_absolute | int) | round(0)) %}
        {% set temp_lastchanged_min = (temp_lastchanged_sec / 60 ) | int %}
        {% if temp_lastchanged_min > 15 %}
          true
        {% else %}
          false
        {% endif %}
    action: 
    - delay:
      minutes: 15
    - service: notify.mobile_app_iphone
      data:
        message: |
          DEXCOM NO DATA ALERT!
          CGM LAST KNOWN VALUE: '{{states('sensor.dexcom_glucose_value')  }}'
          {{now().strftime("Date: %m-%d-%Y  Time: %I:%M:%S  %p")}}

Here’s what the iOS push message looks like.

2 Likes