A Home Assistant Dashboard ON YOUR WRIST!

After posting this tweet showing off the latest revision to the watch face and complications on my Apple Watch:
https://twitter.com/mike_trites/status/1584898950972203008?s=20&t=zNaOeXn1mDtKapnyftOdlA

I received multiple requests for a tutorial on how to replicate what I had done. So, here it is…




Please note : It should be possible to replicate many of these concepts with the Template Tile (Overview | Home Assistant Companion Docs) and complications (Overview | Home Assistant Companion Docs) on the Android / Wear OS app, but I’m not an Android / Wear OS user, so this tutorial will focus on iOS / watchOS.

While I have various complications to display the weather / temperature outdoors and at a few places indoors, the star of the show is undoubtedly the large Summary complication in the middle. Within that complication there are actually three or four separate sections (depending on how you count):

  • Last Updated Time: For me, this is critical. Because Apple Watch complications only update every 15 minutes or so, the information displayed can sometimes be a little out-of-date. Showing when the last update occurred gives the necessary context to at least see how current the information is.

  • Notification Area: I use this area to display any currently-active modes / scenes / input booleans, as well as any notifications (updates, offline devices, persistent notifications, etc.) that require my attention. Because this area is used for more things than there is room for, it is important to choose conditions that are typically off and/or quickly actioned.

  • Sensor Area (lines 1 & 2): This section is a persistent display of various sensors. The way I’ve set it up, I can include up to 8 different sensors. The key to this is choosing entities that can easily be represented with an emoji and whose states can also be represented as an emoji or a count.

Step one is to mock up the display you want, including the sensors you want to include and which emojis you want to use to represent them. I found it easiest to do this in the Notes app on my phone, as that gives you access to all the emojis and lets you see how each will be displayed on your watch (since emoji implementation differs from platform to platform).

Once you’ve decided on the data you want to display, you need to create the templates to extract that data from Home Assistant. My implementation includes a combination of individual sensor states, counts, and conditional / “if” statements. You probably know that the state of an individual sensor can be queried with {{ states(“<entity_ID>”) }}, but counts are a little more complicated. You first need to create groups that include all the entities that you want to count. For example:

group:
  all_rooms:
    entities:
      - binary_sensor.basement_occupancy
      - binary_sensor.kitchen_occupancy
      - binary_sensor.living_room_occupancy
      - binary_sensor.master_bedroom_occupancy
      - binary_sensor.basement_bathroom_occupancy
      - binary_sensor.upstairs_bathroom_occupancy

From there, there are a couple ways to count the items in the group. I’m not a python / jinja / template expert, so I don’t know which is best. So, here are examples of each:

Example 1:

{{ expand(states.group.all_rooms) | selectattr('state','eq', 'on') | map(attribute='entity_id') | list | count }}

Example 2:

{% set ns = namespace(res=0) %}{% for switch in state_attr('group.all_rooms', 'entity_id') if is_state(switch, 'on') %}{% set ns.res = ns.res + 1 %}{%endfor%}{{  ns.res }}

With these basic building blocks, you can replace the static parts of your mockup with dynamic data from Home Assistant. From here, you’ll want to create three new template sensors – one for each field that makes up the complication. Here are examples from my implementation:

Summary:

sensor:
  - platform: template
    sensors:
      watch_summary:
        friendly_name: "Summary"
        value_template: >-
          At {{ now().strftime('%-I:%M') }}: {{ '✈️' if is_state('input_boolean.away_mode', 'on') }}{{ '🌙' if is_state('input_boolean.night_mode', 'on') }}{{ '🍿' if is_state('input_boolean.movie_night', 'on') }}{{ '👥' if is_state('input_boolean.houseguests', 'on') }}{{ '🏠' if is_state('input_boolean.home_day', 'on') }}{% if states.update | selectattr("state", "eq", "on") | list | count > 0 -%}🔄{%- endif %}{% if states.update | selectattr("state", "eq", "on") | list | count > 1 -%}{{states.update | selectattr("state", "eq", "on") | list | count}}{%- endif %}{% set ns = namespace(res=0) %}{% for switch in state_attr('group.all_devices', 'entity_id') if is_state(switch, 'unavailable') %}{% set ns.res = ns.res + 1 %}{%endfor%}{{ '🚫' if ns.res > 0 }}{{ ns.res if ns.res > 1 }}{% if states("sensor.persistent_notifications") | int > 0 %}⚠️{%- endif %}{{ states("sensor.persistent_notifications") if states("sensor.persistent_notifications") | int  > 1 }}

Line 1:

sensor:
  - platform: template
    sensors:
      watch_line_1:
        friendly_name: "Line 1"
        value_template: >-
          💡{% set ns = namespace(res=0) %}{% for switch in state_attr('group.all_lights', 'entity_id') if is_state(switch, 'on') %}{% set ns.res = ns.res + 1 %}{%endfor%}{{( ns.res | string + " ") |  replace ("1 ", "1  ") }} 🖥️{% set ns = namespace(res=0) %}{% for switch in state_attr('group.all_tvs', 'entity_id') if is_state(switch, 'on') %}{% set ns.res = ns.res + 1 %}{%endfor%}{{( ns.res | string + " ") |  replace ("1 ", "1  ") }} ☢️{% set ns = namespace(res=0) %}{% for switch in state_attr('group.all_fans', 'entity_id') if is_state(switch, 'on') %}{% set ns.res = ns.res + 1 %}{%endfor%}{{( ns.res | string + " ") |  replace ("1 ", "1  ") }} 👩🏼‍🦱{{ '🏠' if is_state('sensor.spouse', 'home') }}{{ '🏢' if is_state('sensor.spouse', 'Work') }}{{ '👟' if is_state('sensor.spouse', 'Stationary') }}{{ '👟' if is_state('sensor.spouse', 'Walking') }}{{ '👟' if is_state('sensor.spouse', 'Unknown') }}{{ '🚘' if is_state('sensor.spouse', 'Automotive') }}

Line 2:

sensor:
  - platform: template
    sensors:
      watch_line_2:
        friendly_name: "Line 2"
        value_template: >-
          🚪{% set ns = namespace(res=0) %}{% for switch in state_attr('group.all_rooms', 'entity_id') if is_state(switch, 'on') %}{% set ns.res = ns.res + 1 %}{%endfor%}{{( ns.res | string + " ") |  replace ("1 ", "1  ") }}{% set ns = namespace(res=0) %}{% for switch in state_attr('group.all_lights', 'entity_id') if is_state(switch, 'on') %}{% set ns.res = ns.res + 1 %}{%endfor%}{{ '   ' if ns.res > 9 }} 🔈{% set ns = namespace(res=0) %}{% for switch in state_attr('group.all_alexas', 'entity_id') if is_state(switch, 'playing') %}{% set ns.res = ns.res + 1 %}{%endfor%}{{( ns.res | string + " ") |  replace ("1 ", "1  ") }} ⛈️{{ states("sensor.advisories") | int + states("sensor.statements") | int + states("sensor.warnings") | int + states("sensor.watches") | int }}  🗑️{{ '🟦' if is_state('sensor.garbage_collection', 'Blue and Green') else '⬜️' }}

Please note : My implementation includes some additional logic to maintain consistent alignment between the top and bottom lines. Essentially, the logic is as follows:

  • If a count exceeds a single digit, add a space to the corresponding section above or below. (I only have a single count where this is possible, so I only have this code in one place.)

  • If a count ends in a ‘1’, add an additional space afterward to account for the narrower character. (This is accomplished with a series of clunky string concatenations and replacements.)

As I said, I’m not a python / jinja / template expert, so please forgive my inelegant code.

Once the template sensors have been created, it’s a simple matter to extract their values with the same {{ states(“<entity_ID>”) }} template mentioned previously and create the complication in the iOS Companion app. This particular complication is the Graphic Rectangular and additional details about creating complications can be found here: https://companion.home-assistant.io/docs/apple-watch/complicationshttps://companion.home-assistant.io/docs/apple-watch/complications

The temperature complications are Graphic Circular and the sensor values can again be extracted with the same {{ states(“<entity_ID>”) }} template. The only additional step is to create the template for the “Gauge” field and here is the code I use for that:

{% if  states("sensor.consolidated_downstairs_temperature")  | float < 18 -%}
0
{% elif  states("sensor.consolidated_downstairs_temperature")  | float > 22 -%}
1
{% else -%}
{{ (states("sensor.consolidated_downstairs_temperature") | float -18) / 4 }}
{% endif-%}

And that’s it! If there’s anything I’ve missed, or anything that’s unclear, let me know.

As a next step, I’m currently building out a set of nested menus through a combination of actions and actionable notifications as illustrated here:

Once I’m done with that, I should have pretty broad visibility / control over my smart home no matter where I am.

19 Likes

This is really cool! I am still using my Apple Watch like I showed in the topic you linked (Advanced control from my Apple Watch using single automations: setting lights, vacuum, temperature, sleep mode, and more!).

I will definitely try some of your ideas!

1 Like

Hello,
how did you get outside temperature gradient?
Thank yoz.

That particular complication comes from another app - in this case the Weather Network app. That being said, the same complication can (mostly) be made with the current version of the Home Assistant app. By using the Graphic Circular complication with the Open Grade Gauge Range Text template, the only thing that would be missing is the actual colour gradient.

I see. I was interested in colour gradient.
Thanks.

Thanks for sharing!