Echo Show 5 Dashboards- Keep the screen on, show all Alexa timers, Fun Facts, and recipe page

I breathed new life into my Echo Show 5 with a couple custom dashboards and some integrations! It will stay on this screen thanks to the Keep Silk Open script. It displays a random fun fact chosen from a list of 3500 fun facts that I compiled. It can show all active Alexa timers. And you can go to another page that shows my Mealie page for recipes while cooking. This dashboard was designed for an Echo Show, but it would work even better on an android tablet instead and wouldn’t need the script to keep it open.



Important HACS items:

  • Simple Weather Card - Nice large font weather card in the bottom left
  • Timer Bar Card - For displaying the Echo show timers.
  • Auto entities - For showing all active Alexa/Echo timers from all devices automatically.
  • Mushroom Title Card - Used for the fun facts because it let me control the font and font size.
  • Layout Card - For controlling the dashboard layout precisely to make the forward/back button and the “keep screen alive” thing nice and small. I used the grid-layout for a lot.
  • Alexa Media Player - For Alexa alarms. Not the easiest to set up but worth it.
  • Python script - a python script is used to pick the fun fact from a list of 3500 fun facts that I compiled.

And some honorable other mentions that made this possible and more useful:

  • Mealie - Or whatever webpage you want to show almost full-screen! I use Mealie hosted on my Unraid server for self-hosted recipe management. I wanted to be able to see my recipes on my Echo Show as I cook. The dashboard just renders my local Mealie webpage as an iFrame.
  • Keep Silk Open iframe: https://dagammla.gitlab.io/keep-silk-open/iframe.html - Seen as the small green bubble in the bottom left or top right. This is needed on Echo Show so that the device thinks a video is playing and it doesn’t move to a new screen.
  • The Alexa skill MyPage by XdreaM - This skill lets you open a custom web address via voice command. So I set up an Alexa automation for “Alexa, open mypage and open page 1” and in the MyPage dashboard I set Page 1 to direct to my Timers/FunFact dashboard - ex http://192.168.1.xxx:8123/dashboard-mealie/timers

As a high-level tutorial on how to install this:

  1. Install the HACS items - follow their instructions on how to install. Most are easy, the Alexa Media Player install and connection is the only difficult one.
  2. Make a new Dashboard for these two screens (optional but recommended) - Go to Settings, Dashboards, +Add Dashboard, New dashboard from scratch, give it a name (mine was Mealie because the original inspiration in this was just to display my recipe website all the time) and the url will default to dashboard-name.

If you want to make your own dashboard, skip to step 4 instead:

  1. If you just want to copy-paste my exact setup and then edit your things from there, just go into Edit mode on a page, click the 3 dots in the top right and select “Raw configuration editor”. This will let you control everything about this dashboard - the views/pages, cards, layouts, everything. In mine the blue button jumps between the two dashboard pages, it’s like a “toggle page” button.

Just paste in my block there, here’s my full configuration. There’s more setup that will be needed for the Fun Facts, follow the python stuff further below.

Both dashboards from Raw Configuration Editor
views:
  - type: custom:grid-layout
    path: timers
    title: Timers
    icon: mdi:av-timer
    cards:
      - type: iframe
        url: https://dagammla.gitlab.io/keep-silk-open/iframe.html
        aspect_ratio: '0.01'
        view_layout:
          grid-area: keepopen
      - type: custom:simple-weather-card
        entity: weather.forecast_home_2
        primary_info:
          - extrema
          - wind_speed
        secondary_info:
          - precipitation
          - precipitation_probability
        name: ' '
        backdrop:
          day: var(--primary-color)
          night: '#40445a'
        card_mod:
          style: |
            ha-card {
              font-size: 24px;
              font-weight: bold;
             }
        view_layout:
          grid-area: b
      - show_name: true
        show_icon: true
        type: button
        tap_action:
          action: navigate
          navigation_path: /dashboard-mealie/mealie
        icon: mdi:arrow-left-bold-circle
        show_state: false
        view_layout:
          grid-area: back
        card_mod:
          style: |
            ha-card {
              --ha-card-border-radius: 0px; 
              --ha-card-background: teal; 
            }
      - type: vertical-stack
        cards:
          - type: conditional
            conditions:
              - condition: state
                entity: sensor.echo_show_next_timer
                state_not: unknown
            card:
              type: custom:gap-card
              height: 40
          - type: conditional
            conditions:
              - condition: or
                conditions:
                  - condition: state
                    entity: sensor.bedroom_echo_next_timer
                    state_not: unknown
                  - condition: state
                    entity: sensor.study_echo_next_timer
                    state_not: unknown
                  - condition: state
                    entity: sensor.garage_echo_next_timer
                    state_not: unknown
                  - condition: state
                    entity: sensor.echo_show_next_timer
                    state_not: unknown
            card:
              type: custom:auto-entities
              filter:
                template: >
                  {%- set results = namespace(timers = []) -%}

                  {%- set state_map = {
                    "ON": "active",
                    "PAUSED": "paused",
                  } -%}

                  {%- set icon_map = {
                    "ON": "mdi:timer",
                    "ringing": "mdi:timer-alert",
                    "PAUSED": "mdi:timer-pause",
                  } -%}

                  {%- for entity_id in integration_entities('alexa_media') if
                  entity_id is contains 'next_timer' and states(entity_id) !=
                  'unavailable' -%}
                    {%- set multiple = not loop.first or not loop.last -%}
                    {%- set suffix = '' if not multiple
                                else ' – ' + state_attr(entity_id, 'friendly_name')
                                    | replace(' timers', '') -%}
                          
                    {%- for kvp in state_attr(entity_id, 'sorted_all') | from_json if kvp[1].status != "OFF" -%}
                      {%- set t= kvp[1] -%}
                      {%- set label  = t.timerLabel if t.timerLabel else 
                          timedelta(milliseconds = t.originalDurationInMillis) | string | regex_replace('^0:0*', '') + ' timer' -%}   
                      {%- set results.timers = results.timers + [{
                        'type': 'custom:timer-bar-card',
                        'icon': icon_map[t.status],
                        'duration': {'fixed': t.originalDurationInMillis | int / 1000, 'units': 'seconds'},
                        'end_time': {'fixed': (t.triggerTime | int / 1000) | timestamp_local },
                        'name': label,
                        'bar_width': '40%',
                        'bar_height': '40px',
                        'bar_radius': '30px',
                        'guess_mode': 'true',
                        'layout': 'normal',
                        'modifications': [ 
                          { 'elapsed': '90%', 
                            'bar_foreground': '#ff4242' },
                          ],
                        'state': {'fixed': state_map[t.status] | default(t.status)},
                      }] -%}
                    {%- endfor -%}
                  {%- endfor -%} {{ results.timers }}
              card:
                type: custom:timer-bar-card
                card_mod:
                  style: |
                    ha-card {
                      font-size: 20px;
                      font-weight: bold;
                    }
              show_empty: false
          - type: conditional
            conditions:
              - condition: and
                conditions:
                  - condition: state
                    entity: sensor.bedroom_echo_next_timer
                    state: unknown
                  - condition: state
                    entity: sensor.study_echo_next_timer
                    state: unknown
                  - condition: state
                    entity: sensor.garage_echo_next_timer
                    state: unknown
                  - condition: state
                    entity: sensor.echo_show_next_timer
                    state: unknown
            card:
              type: custom:mushroom-title-card
              title: '{{ states(''input_text.fun_fact_short_text'') }} '
              subtitle: ''
              alignment: center
              card_mod:
                style: |
                  ha-card {
                    --title-font-size: 27px !important;
                    font-family: 'Comic Sans MS'; 
                    font-weight: bold; 
                    --title-line-height: 110% !important;
                  }
          - type: conditional
            conditions:
              - condition: and
                conditions:
                  - condition: state
                    entity: sensor.bedroom_echo_next_timer
                    state: unknown
                  - condition: state
                    entity: sensor.study_echo_next_timer
                    state: unknown
                  - condition: state
                    entity: sensor.garage_echo_next_timer
                    state: unknown
                  - condition: state
                    entity: sensor.echo_show_next_timer
                    state: unknown
            card:
              type: custom:mushroom-chips-card
              chips:
                - type: entity
                  entity: person.jacob_thompson
                  content_info: name
                  icon_color: pink
                  tap_action:
                    action: perform-action
                    perform_action: automation.trigger
                    target:
                      entity_id: automation.ai_test1
                    data:
                      skip_condition: true
                  name: New
                  icon: mdi:autorenew
              alignment: end
        view_layout:
          grid-area: a
      - type: custom:simple-clock-card
        use_military: false
        hide_seconds: true
        bold_clock: true
        font_size: 2.8rem
        paddingLeft_size: 0px
        paddingRight_size: 0px
        paddingTop_size: 30px
        paddingBottom_size: 0px
        view_layout:
          grid-area: c
    layout:
      grid-template-columns: 20px auto auto auto auto
      grid-template-rows: 75% 25%
      grid-template-areas: |
        'back a a a a'
        'keepopen     b b c c'
      height: 320px
  - type: custom:grid-layout
    path: mealie
    title: Mealie
    icon: ''
    cards:
      - type: iframe
        url: http://192.168.1.80:4849/g/home
        view_layout:
          grid-area: a
      - type: iframe
        url: https://dagammla.gitlab.io/keep-silk-open/iframe.html
        view_layout:
          grid-area: b
      - show_name: true
        show_icon: true
        type: button
        tap_action:
          action: navigate
          navigation_path: /dashboard-mealie/timers
        icon: mdi:arrow-right-bold-circle
        show_state: false
        view_layout:
          grid-area: c
        card_mod:
          style: |
            ha-card {
              --ha-card-border-radius: 0px; 
              --ha-card-background: teal; 
            }
            .icon {
              color: white; /* Change to your desired color */
            }
    layout:
      max_cols: 2
      grid-template-columns: auto 30px
      grid-template-rows: repeat(2, 1fr)
      grid-template-areas: |
        'a b'
        'a c'

If you want to make the dashboard pages yourself but you want to know how to get the iFrame helper small so that it keeps the screen on but doesn’t take up too much of the display:

  1. Go to the new dashboard, add a View (click the + button), and set the Layout to “Grid (layout-card)”. Give it a title/icon/url. Set the subview like in the image below (or customize to what you want) - this is how we set up the skinny side columns for the forward/back button for changing screens, and how tall the bottom bar is.
  2. Then you can add cards where you want. To get them into the grid spot you want just go into the YAML editor for a card and add these lines:
view_layout:
  grid-area: a
  1. For the Keep Silk Alive just add a card of type “Webpage” (which is iframe), set the url to https://dagammla.gitlab.io/keep-silk-open/iframe.html, and add the view_layout to the “keepopen” area like so:
- type: iframe
        url: https://dagammla.gitlab.io/keep-silk-open/iframe.html
        aspect_ratio: '0.01'
        view_layout:
          grid-area: keepopen

Fun Fact setup

  1. Make an input_text helper. I named mine “Fun Fact Short Text” so its ID is input_text.fun_fact_short_text. Go to Devices, go to the Helpers tab, +Create Helper, Text, and set maximum length to 255.
  2. For initial Python Script setup follow the official HA documentation - Add the line “python_script:” in your configuration.yaml file, make the folder “python_scripts”, and make a new file “get_random_line.py” or whatever you want to call it. More on this down below. I highly recommend installing the Visual Studio Code Editor add-on, and install the Python extension in it.
  3. Copy and paste my script from below. In home assistant python scripts run in RestrictedPython, so you can’t import anything like import random or import os or read files. So as a workaround I just pasted all my facts into a single variable in the script and let it choose a line from that variable. Sure it made a very long script, but it works!
    After any script edits you need to reload it either by the command pallette in VS code (click search bar, show and run commands, “Home Assistant: Reload Scripts”), or by going in HA to Developer Tools, Actions, “Python Scripts: Reload” and Perform Action.
    Full script here with the fun facts: get_random_line.py
Get Random Facts Python Script
# FYI:  In home assistant, Python Scripts run in RestrictedPython
# So you can't import anything, here's roughly what is included:
# datetime, sorted, time, dt_util, logger.  That's about it. 
# See this: https://community.home-assistant.io/t/open-is-not-defined-in-python-scripts/185086
# https://github.com/home-assistant/core/blob/38455395778bb948867052d03b09492077fad3fb/homeassistant/components/python_script/__init__.py#L181
#
# As a workaround I could call it as a shell command instead https://www.home-assistant.io/integrations/shell_command/
# ... but that seems like more work and this works.  So meh. 




# THIS WORKS TO SET THE VALUE OF THE TEXT HELPER!! 
# service_data = {"entity_id": "input_text.fun_fact_short_text", "value": "Hello world"}
# hass.services.call("input_text", "set_value", service_data)

# Generate a random number without needing to import random
def generate_random_number(min_value, max_value):
    """
    Generates a pseudo-random integer within the specified range.

    This function uses the current time in milliseconds as a seed for 
    generating a relatively unpredictable number. 

    Note: 
        - This is not cryptographically secure. 
        - The quality of randomness is limited by the resolution of the system clock.

    Args:
        min_value: The minimum value of the desired range (inclusive).
        max_value: The maximum value of the desired range (inclusive).

    Returns:
        A pseudo-random integer within the specified range.
    """
    seed = int(time.time() * 1000)  # Get current time in milliseconds
    # Simple linear congruential generator (LCG) - a basic pseudo-random number generator
    a = 1664525  # Multiplier
    c = 1013904223  # Increment
    m = 2**32  # Modulus

    # Initialize the LCG with the seed
    x = seed

    # Generate a number within the range [0, m)
    x = (a * x + c) % m 

    # Scale the number to the desired range
    random_number = int((x / m) * (max_value - min_value + 1)) + min_value

    return random_number

def get_random_line_from_variable(text):
    """
    Gets a random line from a string containing multiple lines.

    Args:
    text: A string containing multiple lines, separated by newline characters.

    Returns:
    A randomly selected line from the string, without the newline character.
    """
    lines = text.splitlines()
    if not lines:
        return None  # Handle empty string

    # Get the number of lines
    num_lines = len(lines) 

    # Get a random index using our custom function
    random_index = generate_random_number(0, num_lines - 1) 
    return lines[random_index].strip()

def set_fun_fact():
    random_line = get_random_line_from_variable(my_text)
    # logger.warning(random_line) 

    service_data = {"entity_id": "input_text.fun_fact_short_text", "value": random_line}
    hass.services.call("input_text", "set_value", service_data)

# Store my text file below here so I don't have to import os 
my_text= """Fun Fact 1
Fun fact 2
Fun fact 3
etc. 
"""

set_fun_fact()

  1. Set up an automation to run our python script. Since the script updates the input helper itself you don’t need to return anything from the script.
    My automation triggers off a mmwave sensor in my kitchen, but you could trigger it by time or whatever you want.
action: python_script.get_random_line
metadata: {}
data: {}
enabled: true

1 Like