An ESPHome component meant to be used with e-paper displays in order to create informative dashboards.
You can define a number of widgets and specify via lambda functions if they should be displayed, in which order. It is optimized for e-paper displays in that it only refreshes if widget data is old or "stale" of the a previously shown widget should not be shown or viceversa.
There are a few built-in widgets (including a custom widget that you can program yourself), but the plan is to make widgets extensible and implemented by the community.
During development, it is recommended to test using the SDL2 component for the host platform in ESPHome, so that compile times are greatly reduced. Once the design of the dashboard is complete, you can obviously move to an e-paper display.
To test with real Home Assistant data, the host platform can be connected to a HA instance on port 6053, which is forwarded in the devcontainer. However, keep in mind that firewalls may play a part and that you are responsible to facilitate HA to connect to the port on your development machine.
The component is included as follows.
external_components:
- source: github://tetele/espaper-dashboard@main
components: [ espaper_dashboard, espaper_dashboard_widgets ]- id (Optional, ID): Manually specify the ID used for code generation. Required if there are multiple dashboards.
- display_id (Optional, ID): The ID of the display component the dashboard will be output to. Optional if there is only one display.
- background_color (Optional, Color): The color used to fill the background. Defaults to
COLOR_ON. - foreground_color (Optional, Color): The color used to render text. Defaults to
COLOR_OFF. - label_color (Optional, Color): The color used to render text labels. Defaults to
COLOR_OFF. - light_color (Optional, Color): The color used to render elements of the least importance. Defaults to
COLOR_OFF. - dark_color (Optional, Color): The color used to render elements of the second least importance. Defaults to
COLOR_OFF. - default_font (Required, Font): The default font if no other font is specified.
- large_font (Optional, Font): The font used for main information.
- glyph_font (Required, Font): The font containing glyphs, icons and symbols.
- large_glyph_font (Optional, Font): The glyph font used for main information.
- widgets (Required, list): The list of widgets that the dashboard will have to register. Adding a widget here does not guarantee it will be rendered.
espaper_dashboard: # component name
id: my_dashboard # optional
background_color: color_white # customize the rendering
foreground_color: color_black
light_color: color_light_gray
dark_color: color_dark_gray
default_font: default_font
large_font: large_font
glyph_font: glyph_font
widgets: # list of widgets
- id: weather_today
should_draw: !lambda 'return true;' # common to all widgets, optional, defaults to true
width: 480 # common to all widgets, optional, templatable
height: !lambda 'return 200;' # common to all widgets, optional, templatable
type: weather # required
current_condition_sensor_id: weather_current_condition
current_temperature_sensor_id: weather_current_temperature
forecast_sensor_id: weather_hourly_forecast- id (Optional, ID): The ID of the widget
- should_draw (Optional, templatable): A
boolvalue or a lambda returning aboolwhich dictates whether the widget should be rendered or not on the next dashboard update. Defaults totrue(widget will render). - priority (Optional, templatable): A numeric priority template for sorting widgets in the dashboard. The larger the number, the higher the priority, the sooner the widget will be rendered. Defaults to
0. - type (Required): The type of widget. See below
- width (Optional, templatable): The desired width of the widget. All widgets have internal, default sizes based on type.
- height (Optional, templatable): The desired height of the widget. All widgets have internal, default sizes based on type.
Specific configuration:
- temperature_uom (Optional,
string): The unit of measurement for the temperature. Defaults to°C. - current_temperature (Required, templatable): The current temperature to display
- current_condition (Required, templatable): The current weather condition, which must be one of these values.
- forecast (Required, templatable): A list of objects with the forecast. The mandatory format of each object is:
- forecast: a list of objects containing forcasts as
WeatherStatusobjects. Each forecast item contains- title: a string which designates the timeframe that the forecast describes (e.g.
23:00orTuesday) - condition: a string from this list containing the weather condition
- temperature: a numeric value representing the temperature for the forecast
- title: a string which designates the timeframe that the forecast describes (e.g.
- forecast: a list of objects containing forcasts as
Here's an example forecast list:
espaper_dashboard:
widgets:
- type: weather
forecast:
- condition: "sunny"
label: "12:00"
temperature: 26.7
- condition: "sunny"
label: "14:00"
temperature: 28.5
- condition: "sunny"
label: "16:00"
temperature: 29.2
- condition: "partlycloudy"
label: "18:00"
temperature: 28.3
- type: weather
forecast: !lambda |-
return std::vector<espaper_dashboard_widgets::WeatherStatus> {
espaper_dashboard_widgets::WeatherStatus("12:00", 26.7, "sunny"),
espaper_dashboard_widgets::WeatherStatus("14:00", 28.5, "sunny"),
espaper_dashboard_widgets::WeatherStatus("16:00", 29.2, "sunny"),
espaper_dashboard_widgets::WeatherStatus("18:00", 28.3, "partlycloudy")
};You can create such a sensor from HA using a template such as
template:
- triggers:
- trigger: time_pattern
hours: /1
- trigger: event
event_type: manual_event_template_reloaded
actions:
- action: weather.get_forecasts
target:
entity_id: weather.home
data:
type: hourly
response_variable: forecast
sensor:
- name: Hourly weather forecast
state: "{{ now().isoformat() }}"
unique_id: hourly_weather_forecast
attributes:
current_condition: "{{ states('weather.home') }}"
current_temperature: "{{ state_attr('weather.home', 'temperature') }}"
temperature_uom: "{{ state_attr('weather.home', 'temperature_unit') }}"
forecast: >-
{% set forecast_attr = [
{
"title": as_timestamp(forecast['weather.home'].forecast[1].datetime) | timestamp_custom("%H:00"),
"condition": forecast['weather.home'].forecast[1].condition,
"temperature": forecast['weather.home'].forecast[1].temperature,
},
{
"title": as_timestamp(forecast['weather.home'].forecast[3].datetime) | timestamp_custom("%H:00"),
"condition": forecast['weather.home'].forecast[3].condition,
"temperature": forecast['weather.home'].forecast[3].temperature,
},
{
"title": as_timestamp(forecast['weather.home'].forecast[5].datetime) | timestamp_custom("%H:00"),
"condition": forecast['weather.home'].forecast[5].condition,
"temperature": forecast['weather.home'].forecast[5].temperature,
},
{
"title": as_timestamp(forecast['weather.home'].forecast[7].datetime) | timestamp_custom("%H:00"),
"condition": forecast['weather.home'].forecast[7].condition,
"temperature": forecast['weather.home'].forecast[7].temperature,
},
] %}
{{ {'forecast': forecast_attr} | tojson }}Displays a message with an optional icon.
Specific configuration:
- icon (Optional, templatable): The character used for the icon
- message (Required, templatable): The message to display
Displays a custom widget however you want it.
Specific configuration:
- lambda (Required, lambda): A function you define that will be called when the widget needs to be drawn. The function receives 3 input arguments:
- it: A
Displaycomponent that you can use to do the actual drawing likeit.draw_pixel_at(2,3); - start_x: The X coordinate where the widget should be drawn
- start_y: The Y coordinate where the widget should be drawn
- it: A
If data is being imported from another system, we need a way to signal to the dashboard that the currently displayed data is no longer valid. For this purpose, you can mark a widget as "stale" when the data it uses is refreshed.
Parameters:
- widget_id (Required, ID): The ID of the widget you want to mark as stale
There is a shorthand version of this action. The following are equivalent:
actions:
- espaper_dashboard_widget.mark_stale:
widget_id: my_widget
- espaper_dashboard_widget.mark_stale: my_widgetExample implementation:
sensor:
- id: external_sensor
platform: homeassistant
entity_id: sensor.my_sensor
on_value:
then:
- espaper_dashboard_widget.mark_stale: widget_using_sensor
espaper_dashboard:
...
widgets:
- id: widget_using_sensor
...Specifies if the dashboard needs a to be redrawn, i.e. if any of the following conditions are true:
- a previously displayed widget's
should_drawlambda returnsfalse - a previously hidden widget's
should_drawlambda returnstrue - a visible widget's
priorityhas changed - a visible widget was marked as stale using
espaper_dashboard_widget.mark_stale
If only one espaper_dashboard component exists, the component ID is optional. If multiple components exist, the ID must be provided as such
- if:
any:
- espaper_dashboard.needs_redraw: the_id
- espaper_dashboard.needs_redraw:
id: the_idExample implementation:
display:
- id: my_display
...
espaper_dashboard:
...
something:
on_event:
if:
condition:
espaper_dashboard.needs_redraw:
then:
component.update: my_display- Define a dashboard with some widgets
- Add
should_drawandprioritylambdas to those widgets to filter and sort which of them should get drawn - Call
component.updateon theespaper_dashboardcomponent to redraw it, if needed
The dashboard will NOT refresh automatically, you need to call component.update to refresh it.
display:
- id: my_display
# e-paper display
...
espaper_dashboard:
...
widgets:
- id: example_widget
type: message
message: !lambda 'return id(important_message).state;'
should_draw: !lambda 'return (id(the_time).now().hour > 7) && (id(the_time).now().hour < 22);' # don't show during the night
...
text_sensor:
- id: important_message
platform: homeassistant
entity_id: sensor.important_message
on_value:
then:
# this widget depends on the sensor data, so mark it as stale when the sensor value changes
- espaper_dashboard_widget.mark_stale: example_widget
time:
- platform: ...
id: the_time
on_time:
- minutes: /1
then:
- if:
condition:
espaper_dashboard.needs_refresh: # ID can be ommitted if there is only one component instance
then:
- component.update: my_display