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:
- 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.
- 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:
- 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:
- 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.
- 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
- 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
- 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. - 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.
- 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()
- 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