Grydgets allows you to easily create widget-based dashboards that update in real time, showing local and online data. It runs on anything that supports Python, PyGame, and SDL, from the oldest Raspberry Pi to a full-blown modern PC.
Note: while the vast majority of the codebase was originally written by me, my free time has been dwindling more and more. Recent changes have been almost entirely developed with Claude Code. I have reviewed and tested the output, and I am using Grydgets myself 24/7.
git clone https://github.com/iamjackg/grydgets
cd grydgets
# uv (recommended)
uv sync
uv run grydgets
# pip
python3 -m venv venv
venv/bin/pip install .
venv/bin/grydgetsTo update an existing installation:
git pull
uv sync # or: venv/bin/pip install .python main.py still works as a shortcut if you prefer not to install the package.
A Dockerfile and docker-compose configuration are provided for running Grydgets in headless mode.
- Create a
data/directory with your configuration files, fonts, and images:
data/
├── conf.yaml
├── widgets.yaml
├── providers.yaml
├── secrets.yaml # optional
├── OpenSans-Regular.ttf # fonts referenced in widgets.yaml
├── OpenSans-ExtraBold.ttf
└── images/ # images referenced in widgets.yaml
└── weather/
-
Make sure
conf.yamlhas a file output configured (see Outputs). -
Start the container:
docker compose up -dRendered images will appear in data/headless_output/. The notification endpoint is exposed on port 5000.
grydgets [--widgets FILE] [--config-dir DIR]
--widgets— Widget configuration file (default:widgets.yaml)--config-dir— Directory containing config files, fonts, and images. All relative paths are resolved from this directory. Defaults to the current working directory.
Configuration for Grydgets must be stored in a conf.yaml file in its main folder. A sample file is provided in the repo.
graphics:
fps-limit: 10
resolution: [480, 320]
smooth-scaling: true
flip: false
logging:
level: info
server:
port: 5000fps-limit: Maximum frames per second. Defaults to60.resolution: Screen resolution as[width, height].smooth-scaling(optional): Use bilinear filtering for image scaling (true, default) or faster nearest-neighbor (false). Set tofalseon low-power hardware like a Raspberry Pi 2.flip(optional): Rotate the output 180 degrees. Defaults tofalse.
Grydgets uses a pluggable output system. You can configure one or more outputs to control where the rendered dashboard is displayed or sent. Add an outputs list to conf.yaml:
outputs:
- type: window
fullscreen: trueIf no outputs key is present, Grydgets falls back to legacy behavior based on the graphics and headless keys (see Legacy Configuration).
Rules:
- At most one display output (
windoworframebuffer) - Any number of non-display outputs (
file,post) - At least one output is required
- If no display output is configured, SDL runs in dummy mode (no screen needed)
Displays the dashboard in an SDL window.
fullscreen(optional): Run in fullscreen mode. Defaults tofalse.x_display(optional): X display to use (e.g.":0"). Only needed when starting via SSH.
outputs:
- type: window
fullscreen: true
x_display: ":0"Renders directly to a Linux framebuffer device (e.g. SPI screens on Raspberry Pi).
device: Path to the framebuffer device (e.g."/dev/fb1").
outputs:
- type: framebuffer
device: /dev/fb1Saves rendered images to disk at regular intervals. Ideal for web dashboards, monitoring, or timelapse.
output_path(optional): Directory for saved images. Defaults to"./headless_output".render_interval(optional): Seconds between saves. Defaults to60.image_format(optional):png,jpg,jpeg, orbmp. Defaults to"png".filename_pattern(optional): Pattern with{timestamp}and{sequence}placeholders. Defaults to"grydgets_{timestamp}".keep_images(optional): Keep the last N images, deleting older ones.0= unlimited. Defaults to100.create_latest_symlink(optional): Create alatest.{format}symlink to the newest image. Defaults totrue.
outputs:
- type: file
output_path: "/var/www/html/dashboard"
render_interval: 60
image_format: png
keep_images: 1440Pushes the rendered image via HTTP POST to a remote endpoint. Works with any device or service that accepts image uploads — networked displays, smart signage, ingestion APIs, etc.
url: The endpoint to POST to.image_format(optional):png,jpg,jpeg, orbmp. Defaults to"png".trigger(optional): When to push."on_dirty"only pushes when content has changed."interval"pushes on a fixed schedule regardless. Defaults to"on_dirty".min_interval(optional): Minimum seconds between pushes. Defaults to60.auth(optional): Authentication. Supportsbearertoken orbasicusername/password.multipart(optional): Send the image as amultipart/form-dataupload instead of raw bytes. Required for endpoints that expect a browser-style file upload.field_name(optional): The form field name. Defaults to"file".filename(optional): The filename reported in the upload. Defaults toimage.<format>(e.g.image.jpeg).
after_post(optional): An additional HTTP request to fire after a successful upload. Useful for devices that require a separate "apply" or "display" call once the upload is complete.url: The URL to request.method(optional): HTTP method. Defaults to"GET".
By default the POST sends raw image bytes with the appropriate Content-Type header (image/png, image/jpeg, etc.). POSTs run in a background thread and will not block the main loop.
outputs:
- type: post
url: https://display.local/image
image_format: jpeg
trigger: on_dirty
min_interval: 300
auth:
bearer: !secret display_tokenFor devices that use a multipart file upload and require a separate call to display the image:
outputs:
- type: post
url: http://display.local/doUpload?dir=/image/
image_format: jpeg
trigger: on_dirty
min_interval: 60
multipart:
field_name: file
after_post:
url: http://display.local/set?img=/image/image.jpegYou can use multiple outputs simultaneously. For example, display on screen while also pushing to a remote display:
outputs:
- type: window
fullscreen: true
- type: post
url: https://display.local/image
image_format: jpeg
trigger: on_dirty
min_interval: 300Or save to disk and push to a remote endpoint (no display needed):
outputs:
- type: file
output_path: "./snapshots"
render_interval: 300
- type: post
url: https://dashboard-api.example.com/ingest
trigger: interval
min_interval: 60For backwards compatibility, Grydgets still accepts the old graphics display settings and headless key. These are automatically translated to the new output system:
headless.enabled: truebecomes afileoutputgraphics.fb-devicebecomes aframebufferoutput- Otherwise, a
windowoutput is created fromgraphics.fullscreen
If you add an outputs key, the legacy display settings (fullscreen, fb-device, x-display) and headless block are ignored.
Important: Switching between display and non-display modes requires restarting Grydgets. Configuration hot-reload (SIGUSR1) will warn and skip the change if the display mode changes.
Data providers allow you to fetch data in the background and share it across multiple widgets, eliminating redundant API calls. For example, if you have a widget for the weather forecast of each day of the week, a single provider can fetch all weather data once and make it available to all daily widgets.
Providers are configured in providers.yaml:
providers:
hass_calendar:
type: rest
url: !secret hass_calendar_url
headers:
Authorization: !secret hass_bearer_token
json_path: "events" # Extract this from response
jq_expression: 'map(select(.status == "active"))' # Further filter with jq
update_interval: 60 # Fetch every 60 seconds
jitter: 5 # Add random 0-5 second delay
weather_api:
type: rest
url: https://api.weather.com/current
method: GET
auth:
bearer: !secret weather_token
update_interval: 300type: Provider type. Currently onlyrestis supported.url: The URL to fetch from (required).method(optional): HTTP method (GET,POST,PUT,DELETE). Defaults toGET.headers(optional): Dictionary of HTTP headers.params(optional): Dictionary of query parameters.bodyorpayload(optional): Request body for POST/PUT requests.auth(optional): Authentication options (see Authentication Schemes).json_path(optional): Simple JSON path to extract from response (e.g.,"events[0].title").jq_expression(optional): jq expression for complex data transformations (e.g.,'.events[] | select(.active)').update_interval(optional): Seconds between fetches. Defaults to60.jitter(optional): Random seconds (0 to this value) added to update interval. Defaults to0.
Note: If both json_path and jq_expression are provided, json_path is applied first, then jq_expression processes the result. This allows you to pre-filter data before complex transformations.
The tree of widgets that composes your dashboard must be specified in a file called widgets.yaml in the main folder. A
sample file is included in the repository.
The top-level of your widgets.yaml defines options for the implicit main screen, which acts as a ScreenWidget container for your entire dashboard.
background_image(optional): The path to an image file to use as the background for the entire screen.background_color(optional): A color for the screen background, as a list of RGB or RGBA components. Defaults to[0, 0, 0](black).drop_shadow(optional): Iftrue, a drop shadow effect will be applied to the main content of the screen. Defaults tofalse.widgets: A list containing the root widget(s) of your dashboard. Note that theScreenWidgetcurrently only supports a single child widget.
Grydgets, as the name suggests, draws dashboards based on a series of widgets. Widgets are generally of two types: Normal and Container.
Normal widgets draw something specific on the screen: a clock, the result of a REST call, an image, etc.
Container widgets determine where and how other widgets appear. For example, a Grid widget allows you to lay other widgets out in a grid. They can also affect their appearance, for example by adding a label below or above another widget.
Most widgets support the following optional parameters:
name(optional): A unique name for the widget instance. This is used for logging and for identifying notifiable widgets. If not provided, the widget type name is used.
Widgets that make HTTP requests (e.g., rest, restimage, httpflip) often support an auth parameter. This is a dictionary specifying the authentication method:
bearer: A string representing a Bearer token.basic: An object containingusernameandpasswordstrings for Basic authentication.
Example auth configuration:
auth:
bearer: !secret my_bearer_token
# OR
auth:
basic:
username: myuser
password: mypasswordAll Container widgets take a children parameter, specifying the list of widgets they're going to contain.
A widget that allows you to place other widgets in a grid layout.
It supports the following parameters:
rows: The number of rows in the grid.columns: The number of columns in the grid.padding(optional): The amount of padding around each child widget, in pixels. Defaults to0.color(optional): A background color for the grid itself (the "empty" space between widgets or behind the entire grid), as a list of RGB or RGBA components.widget_color(optional): A background color for each child widget's cell, as a list of RGB or RGBA components.corner_radius(optional): The corner radius for the overall grid background, in pixels. Defaults to0.widget_corner_radius(optional): The corner radius for each child widget's background, in pixels. Defaults to0.image_path(optional): The path to an image file to use as the background for the entire grid.drop_shadow(optional): Iftrue, a drop shadow effect will be applied to the child widgets within the grid. Defaults tofalse.row_ratios(optional): A list representing the relative ratio of each row's height. E.g.,[1, 2]means the second row will be twice as tall as the first. If not provided, rows have equal height.column_ratios(optional): A list representing the relative ratio of each column's width. E.g.,[1, 2]means the second column will be twice as wide as the first. If not provided, columns have equal width.
Example:
- widget: grid
rows: 2
columns: 2
padding: 4
color: [50, 50, 50]
widget_color: [70, 70, 70, 180]
corner_radius: 10
widget_corner_radius: 5
row_ratios: [1, 2]
column_ratios: [1, 2]A widget that lets you add a text label above or below another widget. It can only have one child.
It supports the following parameters:
text: The text to display as the label.font_path(optional): The path to a ttf file to use as font for the label text.position(optional):aboveorbelowthe child widget. Defaults toabove.text_size(optional): The size of the label text in pixels.text_color(optional): The color of the label text, as a list of RGB or RGBA components. Defaults to[255, 255, 255](white).
Example:
- widget: label
text: 'Random person'
position: below
text_size: 30
text_color: [255, 255, 0]
children:
- widget: rest # ... some child widgetA widget that will transition between each child widget at a specified interval, with custom easing and transition time.
It supports the following parameters:
interval(optional): How long to wait before switching to the following widget, in seconds. Defaults to5seconds.transition(optional): How long the animation for transitioning to the following widget should last, in seconds. Defaults to1second.ease(optional): Determines the ease factor of the transition animation. Higher values make the transition more abrupt at the beginning/end. Defaults to2.
Example:
- widget: flip
interval: 5
transition: 1
ease: 3
children:
- widget: text # first child
- widget: restimage # second childA specialized flip widget that determines which child widget to display based on an HTTP request response.
It supports the following parameters:
url: The URL to retrieve the value from.mapping: A dictionary where keys are expected response values (or extracted JSON paths) and values are thenameof the child widget to display.default_widget: Thenameof the child widget to display if the response value does not match any entry inmapping.json_path(optional): The path to the json item to extract from the HTTP response. If not provided, the raw response text is used.jq_expression(optional): jq expression to extract the comparison value from the JSON response. If bothjson_pathandjq_expressionare provided,json_pathis applied first.auth(optional): Authentication options (see Authentication Schemes).method(optional): The HTTP method to use (GETorPOST). Defaults toGET.payload(optional): A dictionary representing the JSON payload forPOSTrequests.update_frequency(optional): How often the HTTP request should be made, in seconds. Defaults to30seconds.static(optional): Iftrue, the HTTP request is made only once on startup and never repeated. Useful when the mapped value is known to be fixed. Defaults tofalse.
Inherited from flip widget:
interval(optional): How long to wait before checking for changes, in seconds. Defaults to5seconds.transition(optional): How long the animation for transitioning should last, in seconds. Defaults to1second.ease(optional): Determines the ease factor of the transition animation. Higher values make the transition more abrupt at the beginning/end. Defaults to2.
Example:
- widget: httpflip
default_widget: motioneye-cam
update_frequency: 60
url: "https://homeassistant.example.com/api/template"
method: POST
auth:
bearer: !secret hass_token
payload:
template: '{{ (now() > today_at("18:00")) and (now() - states.switch.sonoff_meter_plug_4_relay.last_changed).seconds < (60*60*2) }}'
mapping:
"False": main-cam
"True": other-cam
children:
- widget: restimage
name: main-cam
url: http://192.168.255.34/image.jpg
- widget: restimage
name: other-cam
url: 'https://motioneye.example.com/picture/13/current'A specialized flip widget that determines which child widget to display based on a time schedule. It inherits all parameters from flip widgets.
It supports the following parameters:
schedule: A dictionary mapping time strings (HH:MMformat) to thenameof the child widget to display at or after that time, until the next scheduled time.interval(optional): How long to wait before checking the schedule again, in seconds. Defaults to5seconds.transition(optional): How long the animation for transitioning to the following widget should last, in seconds. Defaults to1second.ease(optional): Determines the ease factor of the transition animation. Defaults to2.
Example:
- widget: scheduleflip
schedule:
"08:00": morning-widget
"18:00": evening-widget
children:
- widget: text
name: morning-widget
text: "Good Morning!"
- widget: text
name: evening-widget
text: "Good Evening!"A specialized container widget that superimposes a pill-shaped overlay on top of a base widget. Useful for adding badges, status indicators, or additional information overlays on images or complex widgets.
It supports the following parameters:
circular_mask(optional): Iftrue, applies a circular mask to the base (first) widget. Defaults tofalse.widget_background_color(optional): Background color for the masked widget when using circular mask. As RGB or RGBA.pill_background_color(optional): Background color for the pill overlay. As RGB or RGBA. Defaults to transparent.pill_width_percent(optional): Width of the pill as a percentage of container width (0.0-1.0). Defaults to0.8.pill_height_percent(optional): Height of the pill as a percentage of container height (0.0-1.0). Defaults to0.2.pill_position_x(optional): Horizontal center position of the pill (0.0-1.0). Defaults to0.5(centered).pill_position_y(optional): Vertical center position of the pill (0.0-1.0). Defaults to0.8(lower area).pill_corner_radius(optional): Corner radius for the pill shape in pixels. If not specified, the pill is fully rounded (semicircular ends).pill_size_relative_to_circle(optional): Iftrueandcircular_maskis enabled, the pill size is relative to the circle diameter. Defaults tofalse.children: Exactly 2 child widgets. First is the base widget, second is the overlay widget.
Example:
- widget: pill
circular_mask: true
widget_background_color: [40, 0, 40, 150]
pill_background_color: [0, 0, 0, 150]
pill_width_percent: 1.4
pill_height_percent: 0.25
pill_position_y: 0.85
pill_size_relative_to_circle: true
children:
- widget: restimage
url: "file://images/profile.png"
preserve_aspect_ratio: true
- widget: text
text: "Online"
font_path: 'OpenSans-Regular.ttf'
color: [0, 255, 0]
align: centerA container widget that can display a temporary text notification over its main child widget. It can only have one child.
It supports the following parameters:
font_path: The path to a ttf file to use as font for the notification text.padding(optional): The amount of padding around the notification text in pixels. Defaults to0.text_size(optional): The size of the notification text in pixels.color(optional): The default color of the notification text, as a list of RGB or RGBA components. Defaults to[255, 255, 255](white).
To send a notification, send a POST HTTP request to the port configured in conf.yaml.
Example:
- widget: notifiabletext
name: fullscreen-notification
font_path: 'OpenSans-ExtraBold.ttf'
padding: 10
text_size: 100
children:
- widget: grid # ... main content widgetcurl -X POST \
-H "Content-Type: application/json" \
-d '{"widget": "fullscreen-notification", "text": "This is a test notification from curl!", "duration": 10}' \
http://192.168.1.1:5000/notifyA container widget that can display a temporary image notification over its main child widget. It can only have one child.
It supports the following parameters:
- No specific configuration parameters beyond the common
name.
TTo send a notification, send a POST HTTP request to the port configured in conf.yaml with url (of the image) and duration (optional, in seconds).
Example:
- widget: notifiableimage
name: fullscreen-notification-image
children:
- widget: notifiabletext # ... main content widget (which itself could be notifiable)curl -X POST \
-H "Content-Type: application/json" \
-d '{"widget": "fullscreen-notification-image", "url": "https://example.com/your_image.jpg"}' \
http://192.168.1.1:5000/notifyA simple widget that displays some text.
It supports the following parameters:
text(optional): The text to display. Defaults to an empty string''.text_size(optional): The size of the text in pixels. If not provided, it automatically adjusts to fit the widget's height.font_path(optional): The path to a ttf file to use as font. If not provided, Pygame's default font is used.color(optional): The color of the text, as a list of RGB or RGBA components. Defaults to[255, 255, 255](white).padding(optional): The amount of padding around the text in pixels. Defaults to0.align(optional): The horizontal alignment for the text. One ofleft,center, orright. Defaults toleft.vertical_align(optional): The vertical alignment for the text. One oftop,center, orbottom. Defaults totop.
Example:
- widget: text
text: 'Hello Grydgets!'
text_size: 50
font_path: 'OpenSans-Regular.ttf'
color: [0, 255, 0]
align: center
vertical_align: centerA widget that displays a 24-hour clock at the top, and the current date at the bottom.
It supports the following parameters:
time_font_path: The path to a ttf file to use as font for the time.date_font_path: The path to a ttf file to use as font for the date.color(optional): The color of the time and date text, as a list of RGB or RGBA components. Defaults to[255, 255, 255](white).background_color(optional): The background color for the clock widget, as a list of RGB or RGBA components.corner_radius(optional): The corner radius for the clock widget's background, in pixels. Defaults to0.
Example:
- widget: dateclock
time_font_path: 'OpenSans-ExtraBold.ttf'
date_font_path: 'OpenSans-Regular.ttf'
color: [255, 255, 255]
background_color: [0, 0, 0, 160]
corner_radius: 25A widget that makes periodic HTTP requests and displays the response text. It supports JSON extraction and custom formatting of the final text.
It supports the following parameters:
url: The URL to retrieve.json_path(optional): The path to the JSON item to extract from the HTTP response. Supports nested objects and array indexing (e.g.,address.cityoritems[0].name).jq_expression(optional): jq expression for complex data transformations (e.g.,.items[] | select(.active)). If bothjson_pathandjq_expressionare provided,json_pathis applied first.format_string(optional): A Python format string to be used to format the final text. The extracted value is passed as the first argument. Defaults to{}.method(optional): The HTTP method to use (GETorPOST). Defaults toGET.payload(optional): A dictionary representing the JSON payload forPOSTrequests.auth(optional): Authentication options (see Authentication Schemes).update_frequency(optional): How often the HTTP request should be made, in seconds. Defaults to30seconds.static(optional): Iftrue, the HTTP request is made only once on startup and never repeated. Useful when displaying a fixed value. Defaults tofalse.font_path(optional): The path to a ttf file to use as font. If not provided, Pygame's default font is used.text_size(optional): The size of the text in pixels. If not provided, it automatically adjusts to fit the widget's height.color(optional): The color of the text, as a list of RGB or RGBA components. Defaults to[255, 255, 255](white).padding(optional): The amount of padding around the text in pixels. Defaults to0.align(optional): The horizontal alignment for the text. One ofleft,center, orright. Defaults tocenter.vertical_align(optional): The vertical alignment for the text. One oftop,center, orbottom. Defaults tocenter.
Example:
- widget: rest
url: 'https://jsonplaceholder.typicode.com/users/1'
json_path: 'address.city'
format_string: 'lives in {}'
text_size: 70
update_frequency: 60
auth:
bearer: !secret my_api_token
method: GETA widget that displays data from a configured data provider. Unlike rest widgets that make their own HTTP requests, provider widgets read from shared data providers defined in providers.yaml, allowing multiple widgets to efficiently share the same data source.
It supports the following parameters:
providers: A list containing exactly one provider name (e.g.,[hass_calendar]).data_path(optional): JSON path to extract from provider data.jq_expression(optional): jq expression to extract/transform provider data. If both are provided,data_pathis applied first.format_string(optional): Python format string for display. The value is passed as{value}. Defaults to"{value}".fallback_text(optional): Text to show on error or missing data. Defaults to"--".show_errors(optional): Iftrue, displays error messages instead of fallback text. Defaults tofalse.font_path(optional): Path to a ttf font file.text_size(optional): Text size in pixels.color(optional): The color of the text, as a list of RGB or RGBA components. Defaults to[255, 255, 255](white).padding(optional): The amount of padding around the text in pixels. Defaults to0.align(optional): The horizontal alignment for the text. One ofleft,center, orright. Defaults tocenter.vertical_align(optional): Vertical alignment (top,center,bottom). Defaults tocenter.
Example:
providers:
my_calendar:
type: rest
url: !secret calendar_api
update_interval: 60
widgets:
- widget: grid
rows: 3
children:
- widget: provider
providers: [my_calendar]
data_path: "[0].title"
fallback_text: "No events"
- widget: provider
providers: [my_calendar]
data_path: "[0].location"
- widget: provider
providers: [my_calendar]
jq_expression: '.[0].start | strptime("%Y-%m-%d") | strftime("%A")'A widget that renders data from providers using Home Assistant's Jinja2 template engine. This is useful for complex formatting that leverages Home Assistant's powerful template functions and filters.
It supports the following parameters:
providers: A list of provider names (can be multiple, e.g.,[calendar, weather]).template: Jinja2 template string. Each provider's data is available asprovider_<name>(e.g.,provider_calendar,provider_weather).hass_url: Home Assistant instance URL (required).hass_token: Home Assistant authentication token (required).fallback_text(optional): Text to show on error. Defaults to"--".font_path(optional): Path to a ttf font file.text_size(optional): Text size in pixels.vertical_align(optional): Vertical alignment. Defaults tocenter.
Example:
- widget: providertemplate
providers: [hass_calendar, weather_api]
hass_url: !secret hass_url
hass_token: !secret hass_token
template: |
{% set event = provider_hass_calendar[0] %}
{% set weather = provider_weather_api %}
{{ event.title }} at {{ event.start_time | as_timestamp | timestamp_custom('%I:%M %p') }}
Weather: {{ weather.temp }}°F
fallback_text: "Loading..."A specialized flip widget that conditionally displays child widgets based on data from a provider. Similar to httpflip, but reads from a shared provider instead of making its own HTTP requests.
It supports the following parameters:
providers: A list containing exactly one provider name.data_path(optional): JSON path to extract the comparison value from provider data.jq_expression(optional): jq expression to extract the comparison value.mapping: Dictionary mapping values to child widget names.default_widget: Name of the child widget to display by default or when no mapping matches.interval(optional): How often to check the provider for data changes, in seconds. Defaults to5seconds.transition(optional): Transition animation duration in seconds. Defaults to1.ease(optional): Easing factor for transition. Defaults to2.
On provider errors, the widget stays on the currently displayed child (does not switch).
Example:
providers:
camera_switch:
type: rest
url: https://homeassistant.example.com/api/template
method: POST
auth:
bearer: !secret hass_token
payload:
template: '{{ is_state("switch.camera_mode", "on") }}'
update_interval: 10
widgets:
- widget: providerflip
providers: [camera_switch]
default_widget: cam_a
transition: 0.5
mapping:
"True": cam_a
"False": cam_b
children:
- widget: restimage
name: cam_a
url: http://192.168.1.10/image.jpg
- widget: restimage
name: cam_b
url: http://192.168.1.11/image.jpgA widget that displays images from URLs contained in provider data. Similar to restimage, but reads the image URL from a provider. Supports both HTTP/HTTPS URLs and local file paths using the file:// protocol.
It supports the following parameters:
providers: A list containing exactly one provider name.data_path(optional): JSON path to extract the image URL from provider data.jq_expression(optional): jq expression to extract the image URL.fallback_image(optional): Path to a fallback image file to display on error.auth(optional): Authentication for fetching the image from HTTP/HTTPS URLs (not used forfile://URLs).preserve_aspect_ratio(optional): Iftrue, maintains the original image aspect ratio when scaling. Iffalse(default), the image is scaled to fill the container.show_errors(optional): Iftrue, displays error messages instead of a fallback image. Defaults tofalse.
The extracted URL can be:
- HTTP/HTTPS URL:
https://example.com/image.jpg - Local file path:
file:///path/to/image.jpg
Example:
providers:
camera_urls:
type: rest
url: https://api.example.com/cameras
json_path: "active_cameras"
update_interval: 30
widgets:
- widget: providerimage
providers: [camera_urls]
data_path: "[0].image_url"
fallback_image: "camera_offline.png"
# Example with file:// URLs
- widget: providerimage
providers: [local_images]
data_path: "current_image"
# Provider returns: {"current_image": "file:///home/user/images/photo.jpg"}A widget that makes periodic HTTP requests and displays the retrieved image file. It also supports extracting an image URL from a JSON response and retrieving that image. Supports both HTTP/HTTPS URLs and local file paths using the file:// protocol.
It supports the following parameters:
url: The URL to retrieve the image from (HTTP/HTTPS orfile://URL).json_path(optional): The path to the JSON item that contains an image URL to retrieve. If specified, the value at this path will be used as the actual image URL.jq_expression(optional): jq expression to extract the image URL from the JSON response. If bothjson_pathandjq_expressionare provided,json_pathis applied first.auth(optional): Authentication options for HTTP/HTTPS requests (see Authentication Schemes). Not used forfile://URLs.update_frequency(optional): How often the image should be refreshed, in seconds. Defaults to30seconds.static(optional): Iftrue, the image is loaded only once on startup and never re-fetched. Useful for local files or remote images that never change. Defaults tofalse.preserve_aspect_ratio(optional): Iftrue, maintains the original image aspect ratio when scaling. Iffalse(default), the image is scaled to fill the container.
The URL (either directly specified or extracted via json_path/jq_expression) can be:
- HTTP/HTTPS URL:
https://example.com/image.jpg - Local file path:
file:///path/to/image.jpg
Examples:
# HTTP image
- widget: restimage
url: 'https://motioneye.example.com/picture/9/current/'
auth:
basic:
username: camera_user
password: camera_password
update_frequency: 10
# Local file
- widget: restimage
url: 'file:///home/user/images/current.jpg'
update_frequency: 5
# Extract URL from JSON (can return either HTTP or file:// URL)
- widget: restimage
url: 'https://api.example.com/current-image'
json_path: 'image_url'
update_frequency: 10A widget that displays a static image. Currently only accepts binary image data loaded from external code. This widget is primarily used internally by other widgets like NotifiableImageWidget, but can be directly configured with image_data (though this typically requires dynamic injection).
It supports the following parameters:
image_data(optional): Binary contents of the image to display. (Typically set dynamically)preserve_aspect_ratio(optional): Iftrue, maintains the original image aspect ratio when scaling. Iffalse(default), the image is scaled to fill the container.
A widget that renders a bar chart from a list of numeric values sourced from a data provider. Designed to be minimal — no axes or legend — and efficient enough for low-power hardware like the Raspberry Pi.
It supports the following parameters:
providers: A list containing exactly one provider name.data_path(optional): JSON path to extract the list of values from provider data.jq_expression(optional): jq expression that must return a JSON array of numbers.bar_color(optional): Default color of the bars, as a list of RGB or RGBA components. Defaults to[100, 149, 237](cornflower blue).bar_colors(optional): A mapping of label strings to RGB or RGBA colors. Bars whose label matches a key are drawn in the corresponding color, taking priority overbar_color_thresholdsandbar_color.bar_color_thresholds(optional): A list of{above: <value>, color: <RGB/RGBA>}entries. Each bar is colored by the first threshold whoseabovevalue is less than or equal to the bar's value. Checked in descending order. Falls back tobar_colorif no threshold matches.bar_background_colors(optional): A mapping of label strings to RGB or RGBA colors. Draws a full-height background rectangle behind the matching bar. Useful as a visual demarcator — visible even when the bar value is zero.bar_gap(optional): Gap between bars in pixels. Defaults to2.max_value(optional): Fixed maximum value for the chart. If not provided, auto-scales to the maximum value in the data.min_value(optional): Minimum value for the chart. Defaults to0.midline(optional): Iftrue, draws a horizontal marker line at the 50% point behind the bars. Defaults tofalse.midline_thickness(optional): Thickness of the midline in pixels. Defaults to1.midline_color(optional): Color of the midline, as RGB or RGBA. Defaults to[255, 255, 255](white).quartline(optional): Iftrue, draws horizontal marker lines at the 25% and 75% points behind the bars. Defaults tofalse.quartline_thickness(optional): Thickness of the quartlines in pixels. Defaults to1.quartline_color(optional): Color of the quartlines, as RGB or RGBA. Defaults to[255, 255, 255](white).labels_jq_expression(optional): jq expression that returns a JSON array of strings to use as bar labels.labels_data_path(optional): JSON path alternative tolabels_jq_expression.label_font_path(optional): Path to a ttf font file for the labels.label_size(optional): Font size for the labels in pixels. Defaults to12.label_color(optional): Color of the label text, as RGB or RGBA. Defaults to[200, 200, 200].
Example (hourly rain probability for the next 24 hours):
providers:
hourly_weather:
type: rest
url: https://weather.example.com/api/hourly
update_interval: 3600
widgets:
- widget: providerbarchart
providers: [hourly_weather]
jq_expression: "[.forecast[:24][].precipitation_probability]"
labels_jq_expression: "[.forecast[:24][].datetime | .[11:13]]"
bar_color: [100, 149, 237]
bar_color_thresholds:
- above: 70
color: [220, 80, 80]
- above: 40
color: [220, 160, 60]
bar_background_colors:
"00": [255, 255, 255, 25]
bar_gap: 2
max_value: 100
midline: true
midline_color: [255, 255, 255, 120]
quartline: true
quartline_color: [255, 255, 255, 60]
label_font_path: OpenSans-Regular.ttf
label_size: 20Grydgets supports hot-reloading configuration without restarting the application. Send a SIGUSR1 signal to the running process to reload both widget configuration and data providers:
kill -SIGUSR1 <process_id>This will:
- Stop all existing data providers
- Reload
providers.yamland restart providers - Reload
widgets.yamland rebuild the widget tree - Maintain the Flask notification server without interruption
Grydgets supports two methods for extracting data from JSON responses:
json_path - Simple path notation for basic extraction:
json_path: "events[0].title" # Get title of first event
json_path: "user.address.city" # Navigate nested objectsjq_expression - Powerful jq expressions for complex transformations:
jq_expression: '.events[] | select(.priority == "high")' # Filter
jq_expression: '.items | map(.name) | join(", ")' # Transform
jq_expression: '.[0].date | strptime("%Y-%m-%d") | strftime("%B %d")' # FormatCombining both - Use json_path to pre-filter, then jq for complex operations:
json_path: "events" # Extract events array first
jq_expression: 'map(select(.active)) | .[0].title' # Filter and extractThis works in:
- REST widgets (
rest,restimage,httpflip) - Data providers (
providers.yaml) - Provider widgets (
provider,providerflip,providerimage)
Grydgets runs a Flask server on the port specified in conf.yaml (default: 5000) that accepts POST requests to trigger notifications on widgets with the notifiable prefix.
Text Notifications:
curl -X POST -H "Content-Type: application/json" \
-d '{"widget": "fullscreen-notification", "text": "Hello!", "duration": 10}' \
http://localhost:5000/notifyImage Notifications:
curl -X POST -H "Content-Type: application/json" \
-d '{"widget": "image-notification", "url": "https://example.com/image.jpg", "duration": 5}' \
http://localhost:5000/notifyGrydgets supports a secrets.yaml file for storing sensitive configuration data. Use the !secret tag to reference secrets:
# secrets.yaml
hass_token: "your_secret_token_here"
api_key: "your_api_key"
# conf.yaml or widgets.yaml
auth:
bearer: !secret hass_tokenThe secrets.yaml file should not be committed to version control.
