Building a Daily Briefing

If you saw my recent video on building a Daily Briefing in Home Assistant and you are here for some code, thanks for checking out my videos. If you haven’t seen the video you might wan to check it out first:

Building a Daily Briefing has the potential to get crazy real fast, but using macros can help simply things. For example you could have a macro for each domain of information you want to include, say weather or the status of the home security system. If you want to see what I use for my daily briefing you can find it on my github.

If you want to build your own I suggest you start with this basic framework.

{# Daily Briefing #}
{%- macro getGreeting() -%}
  I am sorry, I cannot do that, Dave.
{%- endmacro -%}

{{ getGreeting() }}

This will print out whatever you include in the macro called getGreeting. And if doesn’t have to be static text like this. If you could wrapped in if statements like this:

{# Daily Briefing #}
{%- macro getGreeting() -%}
  {% if now().strftime('%H')|int < 12 %}
    Good morning.
  {% elif now().strftime('%H')|int >= 12 and now().strftime('%H')|int < 17 %}
    Good afternoon.
  {% else %}
    Good evening.
  {% endif %}
Dave
{%- endmacro -%}

{{ getGreeting() }}

This allows you to get really complex with your briefing. And you could continue adding logic and macros for as much of this until you end up with something like:

{# Daily Briefing #}
{%- macro getGreeting() -%}
  {% if now().strftime('%H')|int < 12 %}
    Good morning.
  {% elif now().strftime('%H')|int >= 12 and now().strftime('%H')|int < 17 %}
    Good afternoon.
  {% else %}
    Good evening.
  {% endif %}

  {% if is_state('binary_sensor.morning','on') %} 
      Today is {{states.sensor.today_is.state }}, {{ as_timestamp(now()) | timestamp_custom('%B %d %Y') }}.
  {% else %}
      It is {{ now().strftime("%I:%M %p") }}
  {% endif %}

  Dave.
{%- endmacro -%}

{%- macro getDoorStatus() -%}
  The Pod Bay Doors are Closed.
{%- endmacro -%}

{# a macro that removes all newline characters, empty spaces, and returns formatted text #}
{%- macro cleanup(data) -%}
  {%- for item in data.split("\n")  if item | trim != "" -%}
    {{ item | trim }} {% endfor -%}
{%- endmacro -%}

{# a macro to call all macros :)  #}
{%- macro mother_of_all_macros() -%}
  {{ getGreeting() }}
  {{ getDoorStatus() }}
{%- endmacro -%}
  
  {# Call the macro  #}
{{- cleanup(mother_of_all_macros()) -}}

This now includes some macros to help clean up your message which isn’t needed unless you want to print that message on your lovelace dashboard or send it in a text. The above code outputs a message that looks like:

I hope that shows some of the power in macros, and gives you some ideas on how to build on that idea. Of course the advance stuff is going to require some jinja magic in both if/else statements to help you format a universal briefing, but you could just as easily build it printing the state of current sensors.

It could be as simple as built a macro that prints the current inside temperature using your thermostat:

The temperature inside is {{ state_attr('climate.home','current_temperature') }} degrees.

The reality is you can make this as simple or crazy as you want. The key parts are the code above and storing it somewhere.

In my setup I typically include it in it’s own YAML file like this example one. And I forgot to include in the video that in this file we need a “>” at the start of the file or you will get an error:

But feel free to take that example file and build on it. Once you have it ready you can use it in an automation like this:

- id: 1d8f396a-f6ec-460d-97e3-d11900418f95
    alias: Good Morning Report
    initial_state: true
    trigger:
      - platform: state
        entity_id: binary_sensor.kitchen_occupancy
        to: 'on'
    condition:
      - condition: time
        after: '06:45:00'
        before: '08:30:00'
      - condition: state
        entity_id: input_boolean.good_morning_report
        state: 'off'
    action:
    - service: tts.cloud_say
        data:
          entity_id: media_player.kitchen_display
          message: !include ../templates/speech/daily_briefing.yaml
          cache: true
          language: en-GB
          options: 
            gender: male

Or in a script like this:

morning_briefing:
    sequence:
    - service: tts.cloud_say
        data:
          entity_id: media_player.kitchen_display
          message: !include ../templates/speech/daily_briefing.yaml
          cache: true
          language: en-GB
          options: 
            gender: male

Using the include with a path to the YAML file you created allows you to replace the message contents with whatever your macros output. And keeping them separated could make it easier to maintain.

With any of my my content, if you have questions hit me up here or over on YouTube.

23 thoughts on “Building a Daily Briefing”

  1. Great video and breakdown here! I’ve been looking to do this for a while with my setup. One question: whenever I put the !include ../templates/speech/daily_briefing.yaml in a message line (using tts.google_translate_say), it reads it back literally and doesn’t pull from the .yaml file output. It doesn’t seem to recognize the !include. Any thoughts or ideas?

    1. Brian, One thought – Where did you create your daily_briefing.yaml macro file? Make sure the path is correct relative to where it is called from. In the video he calls the macro file from a package within his config/packages folder. The ‘..’ in the path backs up one folder level, which in his case takes him to the config folder – then it points to his config/templates/speech folder.

      If you are calling the macro file from your configuration.yaml (or another file) in your config folder and the macro file is also in your config folder, you can simply remove the folder path (!include daily_briefing.yaml) since both files are in the same folder.

      Another thought is to check spelling for the !include statement & path vs the actual path and filename. Also he didn’t mention it in the video, but does above in blog, make sure the first line of the daily_briefing.yaml file is the single character >.

      1. Thanks for this recap. I thought I had responded….

        All good stuff. And yeah. I totally forgot the > at the top of the file, and instead of reshooting I just added a call out on screen in the video.

        Also…make sure the path isn’t wrapped in quotes. Other wise it will read it as a string.

  2. Appreciate the help! I keep getting more and more errors in things that used to work. Not sure what I’ve done but it’s time to quit and move on to something else. This clearly isn’t happening. Jeff, your channel is great! Definitely keep it coming!

  3. Well, like any good tinkerer, I couldn’t stay away. I finally figured out my issue: I wasn’t configuring the !include inside my automations.yaml file. I was trying to do it in the automation GUI. Once I figured that out, I figured out my other issue which was an indenting problem. It’s all fixed and runs like a champ! Thanks again for the great content and the help here!

  4. Hi – I appreciate the tutoring – it’s always nice to get this level of instruction, especially for someone as much a newbie as myself. The video was pretty clear, and it also helped to have this blog post. But I’ve run into a problem invoking your macro. I took your very first simple macro, put it into a file called ‘daily_report.yaml’ and added the ‘>’ first line, so the start looks like…

    >
    {# Daily Briefing #}
    {%- macro getGreeting() -%}
    I am sorry, I cannot do that, Dave.
    …etc

    The file is in the /config/ directory for now. Then I built the script via the UI, and as expected when I ran it, it read out the filename, because the UI put apostrophes around the file. Here’s the script as the UI built it…

    alias: Daily report
    sequence:
    – service: notify.alexa_media
    data:
    message: ‘!include daily_report.yaml’
    target: media_player.stephen_s_echo_flex
    data:
    type: tts
    mode: single

    So at this point the script connects to the echo correctly and reads out what’s in the message field.

    Removing the apostrophes isn’t recognized as a change in the UI, so I went into the scripts.yaml file and removed them there and reloaded the scripts. When I went back to try to edit the script in the UI I couldn’t – instead I got a popup with HA’s IP address in it – just the string “192.168.1.21:8123 says” and an OK box.

    When I hit the ‘Check configuration’ button I got
    “Error loading /config/configuration.yaml: expected ”, but found ‘{‘ in “/config/daily_report.yaml”, line 2, column 1″

    … so taking the apostrophes out may be pulling in the include file, but it doesn’t seem to be recognizing or resolving the macro.

    Is there a switch to enable macros somewhere that I don’t know about? Or what else could this be? I’m sure it’s a head-slapper, but I can’t see it.

    Many thanks for any assistance…

    1. You may have to indent the lines under the >. I get the sense from that error it is expecting that. I checked mine and I have them indented. Try that. And yeah, you want to leave off the quotes from the message if it includes a !include.

  5. Got a quick question for anyone else that’s used this method. Did it remove your ability to use the automations GUI? I get a 500 error any time I try to use the GUI. Googling shows it’s something in either the automations or the config yaml files. Just wondering if it’s this or if I have another problem to hunt down. Thanks!

    1. @Brian – I’m not having any problem with automations, scenes or scripts, and have been playing with this a lot over the last 2 days. I think it’s hunting time πŸ™‚

  6. Hi, Jeff. Great stuff throughout all your content. especially for those of us who want to put a little snark in our home automation. but this one is giving me fits…

    Trying to reuse code, and make editing easier. so I want to put templates in a file, as you have here. I have a template that works fine in the template editor. Yay! but when I put it in a yaml file, my configuration is invalid. I’ve pared things down to narrow down the issue, but am stumped…

    So, config/packages/templates/speech_macros.yaml
    empty file -check config, valid.

    Just add the “>” – check config, valid.

    Just add a comment:
    >
    {# test #}

    check config, invalid: Error loading /config/configuration.yaml: expected ”, but found ‘{‘
    in “/config/packages/templates/speech_macros.yaml”, line 2, column 1

    Noticed your templates on github were indented, so tried that
    >
    {# test #}

    check config, invalid: expected a dictionary for dictionary value @ data[‘packages’][‘speech_macros’]

  7. Oh, I think the issue may be the location of the templates folder. Move it out of the packages folder.

    Home Assistant is expecting anything in that packages folder to have normal YAML config directives in it like

    automation:

    I would put it in the config folder. So config/templates/…

    See if that works.

    1. Ironically, i figured that out about 10 minutes before getting the notification of your response πŸ™‚

      Indeed, moving the templates out of the packages fixes the problem.

      Also, something of note that may help other folks: If you have a script that uses an !include template in your scripts.yaml, you will not be able to edit (or, at least, save your edits) in the gui. If you move those same scripts into a package, everything is fine. They also function just fine in scripts.yaml, so you might not notice until you try to edit with the gui later.

      But, if, like you’re like me, you might get confused when you manually edit one of those gui-created scripts to use your template, and everything works fine. But then a couple of days later, you try to create a simple quick script via gui, and get a 500 error when saving, even if the new script is empty.

      1. I have the !include in my automations.yaml and either it has a bad format somewhere or it has the same problem. When I remove the !include I can use the automations GUI. When I put it in, I get the 500 error.

      2. Yeah, It appears that include in the automations.yaml file is causing an issue. Evidently its too advance for the gui editor. That to me sounds like a bug, but I need to do some some research. The soulution might be to move that automation out of the default automations file into a package or something for the time being.

  8. Hello Jeff! Love the channel!

    I have been playing with this include file and ended up placing it the main directory with no luck. When testing the way it was displaying in the automation, I found that the yaml was coming across indented incorrectly (one tab to the right). When I tried to push it one tab to the left in the daily_briefing.yaml, it broke the GUI (same thing Brian saw above) and I received an error when “checking the configuration” in the “server controls” page.

    To get it working, I was able to paste the code directly into the automations.yaml but had to remove the top 2 lines referencing the macro and the bottom section that cleaned up the macro and ran it. This means that it speaks with those weird pauses.

    Might need to find a way to output the macro to a YAML file first then pull it into the automation. I will see if I can get that to work.

    Here is the “action section” of that automation I was able to get working by putting it directly in automation.yaml (only the first few lines of code are shown):
    action:
    – service: notify.alexa_media_media_room
    data:
    message: >-
    {% if now().strftime(‘%H’)|int = 12 and now().strftime(‘%H’)|int < 17 %}
    Good afternoon.
    {% else %}
    Good evening.
    {% endif %}

    {% if is_state('binary_sensor.morning','on') %}

    Today is {{states.sensor.today_is.state }}, {{ as_timestamp(now()) | timestamp_custom(‘%B %d %Y’) }}.

    {% else %}

    It is {{ now().strftime(“%I:%M %p”) }}

    {% endif %}
    .
    .
    .

    1. Looks like the post shifted the code all the way to the left when I posted it. Here is another attempt at displaying the action section of that automation in my Automations.yaml code:

      action:
      – service: notify.alexa_media_media_room
      data:
      message: >-
      {% if now().strftime(‘%H’)|int < 12 %}
      Good morning.
      {% elif now().strftime(‘%H’)|int >= 12 and now().strftime(‘%H’)|int < 17 %}
      Good afternoon.
      {% else %}
      Good evening.
      {% endif %}

      1. I am thinking this might be an easier to way to under stand it.
        I put an example of this exact approach in my GitHub to helping someone in the Discord.

    2. After countless hours of newbie trial and error, this is what worked for me…

      I took a modular approach so I can pick and aggregate different components for different notifications. and yes Jeff, I stole shamelessly (thank you so much!)
      BTW, I will try my best to indent accordingly, but it looks like it keeps getting flattened here when I post:

      Firstly, I have an include for sensors in my configuration.yaml (sensor: !include_dir_merge_list sensors/) that points to my “sensors” directory. In that directory I created a file called “speech.yaml”.

      In that “speech.yaml” I created 6 sensors (for now), each ends with a period for fluidity when combined (I removed all the tags, since they were causing additional delays between segments).

      Here are the sensors I created:
      * speech_intro (Good morning/afternoon/evening.)
      * speech_day_time (It is 11:13 AM.)
      * speech_weather (The Weather in …)
      * speech_sun (You have 7 hours before the sun officially sets.)
      * speech_holiday (Today is also known as National Proofreading Day.)
      * speech_around_house (Around the house, There are currently 10 lights and switches on.)

      Here is the example of the start of speech.yaml and the first sensor (speech_intro):

      ..- platform: template
      ….sensors:
      ……speech_intro:
      ……..value_template: >
      ……….{%- macro getReport() -%}
      …………..{% if now().strftime(‘%H’)|int = 12 and now().strftime(‘%H’)|int < 17 %}
      …………….Good afternoon.
      …………..{% else %}
      …………….Good evening.
      …………..{% endif %}
      ……….{%- endmacro -%}

      ……….{# a macro that removes all newline characters, empty spaces, and returns formatted text #}
      …………{%- macro cleanup(data) -%}
      …………..{%- for item in data.split("\n") if item | trim != "" -%}
      …………….{{ item | trim }} {% endfor -%}
      ……….{%- endmacro -%}

      ……….{# a macro to call all macros πŸ™‚ #}
      …………{%- macro mother_of_all_macros() -%}
      …………..{{ getReport() }}
      …………{%- endmacro -%}

      …………{# Call the macro #}
      …………{{- cleanup(mother_of_all_macros()) -}}

      After that, I piece them together in a service call that can be used in an automation. Here is my test for the services tab, which I later pasted in my automation (I use Alexa for notifications):

      service: notify.alexa_media_media_room
      data:
      ..message: |-
      ……{{states.sensor.speech_intro.state }}
      ……{{states.sensor.speech_day_time.state }}
      ……{{states.sensor.speech_sun.state }}
      ……{{states.sensor.speech_weather.state }}
      ……{{states.sensor.speech_holiday.state }}
      ……{{states.sensor.speech_around_house.state }}
      data:
      type: announce
      method: speak

      Next, I plan on building a script like you have, to specify which Alexa to use based on presence.

      There is probably a much easier way to do this, but I ended up here after I was unable to get the other ways working (my lack of expertise i'm sure).

      1. So…I started doing something like this with my weather info, Mostly because I use it in multiple briefings and I thought it was crazy to have to written out in some many templates.

  9. I thought I would post this here since this article and video inspired me to work on setting this up. I usually use Node Red for my automations and have been working on converting the automation portion. One issue I was having is converting the macro to remove extra white spaces and carriage returns. I also needed to cleanup spoken text for NWS alerts (based upon another Slacker Labs video) and other messages.

    I build the text I want spoken on my media player in a previous node that sets it in msg.ttsa. That is then passed to a function node that contains the following javascript code:

    var ttsat=msg.ttsa;
    ttsat = ttsat.replace(/\.\.\./g, ‘. ‘).replace(/_/g, ‘ ‘).replace(/-/g, ‘ ‘).replace(/\*/g, ”).replace(/[\n\r]/g, ‘ ‘).replace(/\s+/g, ‘ ‘).trim();
    msg.ttsa = ttsat;
    return msg;

    The above replace functions replaces all occurrences of the following character stings with a single space:
    … (replaced with “. “)
    _

    *
    new line or return
    multiple spaces
    and trim all leading & ending spaces.

    For NWS weather alerts, I also have replace(/\* WHERE.*[\n\r]/g, ”) to delete all text between “* WHERE” and first new line or return. This shortens the text spoken since I wouldn’t be getting the alert unless it was for my location. Be sure to use this before you replace all new lines and returns.

    I also have replace(/Cnty/g, ‘County’) to replace all occurrences of the abbreviation.

    To build the text for speech based upon NWS Weather Alert entity with attributes spoken_title and spoken_message, I’m using a Current State node for the entity with the following output properties defined:

    msg.spoken_title = expression: $entity().attributes.spoken_title
    msg.spoken_message = expression: $entity().attributes.spoken_message
    msg.ttsa = expression: spoken_title & ” ” & spoken_message

    Hopefully this will help someone if they want to use Node Red.

  10. Love this video. Have parts of it working. Was curious if I need to ‘restart’ HA to have the template modifications recognized? For example, if I make a change to the daily_briefing.yaml in templates…does the “include” statement pick up that change without needing to reboot? Or do I need to start to load that YAML into memory.

Leave a Reply