Reference: Config options
This page is the cross-cutting reference for every TOML knob in
led-ticker, grouped by config section. Per-widget options live on each
widget’s page — this page covers the top-level blocks that wrap them:
[display], [title], [transitions], [[playlist.section]],
[[source]], [busy_light], and [web].
Field names and defaults match the dataclasses in
src/led_ticker/config.py. For per-widget knobs see Widgets.
Coercion behavior
Section titled “Coercion behavior”Some config slips that would crash startup are silently corrected with a warning. Warnings show up in led-ticker validate output AND in the runtime log — fix the source to silence them.
Numeric strings → numbers. Every integer or float field accepts a string-of-digits in addition to a bare number:
font_size = "25" # works (coerced to 25, warns)font_size = 25 # canonical, no warningApplies to all numeric fields across [display], [[playlist.section]], and per-widget configs — including rows, cols, chain_length, parallel, gpio_slowdown, pwm_bits, pwm_lsb_nanoseconds, pwm_dither_bits, rp1_pio, font_size, font_threshold, hold_time, brightness, content_height, scale, loop_count, scroll_step_ms, start_hold, separator_font_size, and others.
Closed-set enum strings → case-insensitive. Fields with a fixed value set (text_align, text_valign, image_align, scroll_direction, fit, bottom_text_scroll, easing) are case-insensitive. The canonical form is lowercase:
image_align = "Left" # works (coerced to "left", warns)image_align = "left" # canonical, no warningWhat’s NOT coerced. Colors ([r, g, b]), booleans, file paths, and free-text fields stay strict — typos here surface as hard errors rather than silent fixes. The style keys inside inline-table providers (font_color = {style="rainbow", ...}) also stay case-sensitive.
[display]
Section titled “[display]”Hardware and panel-layout settings. Most of these map straight onto
rgbmatrix library options.
| Field | Type | Default | Notes |
|---|---|---|---|
rows | int | 16 | Panel row count (per panel, not total across the chain). 16 for 16x32 panels; 32 for P3 32x64 panels. |
cols | int | 32 | Panel column count (per panel). 32 for 16x32; 64 for P3 32x64. |
chain_length | int | 1 | Number of panels chained together. The smallsign reference uses 5; the bigsign uses 8. |
parallel | int | 1 | Number of parallel panel chains. Both reference builds use 1. |
pixel_mapper_config | string | "" | rgbmatrix Remap string for non-trivial layouts (e.g. the bigsign’s 2x4 vertical-serpentine mapping). Empty string = no remap. |
default_scale | int | 1 | Logical-to-physical scaling. 1 matches the smallsign reference (no scaling). 4 matches the bigsign (ScaledCanvas blows 16-tall content up to 64). |
brightness | int | 100 | Panel brightness, 0-100. Both reference configs use 60. |
gpio_slowdown | int | 1 | GPIO timing slowdown. Smallsign uses 2; bigsign uses 3 (paired with the default RIO backend). Raise on flicker. |
hardware_mapping | string | "adafruit-hat" | rgbmatrix GPIO pinout. "adafruit-hat" covers the Adafruit HAT and bonnet. |
pwm_bits | int | 11 | PWM bit depth. Drop to 8 on the bigsign for ~8x faster refresh at a small color cost. |
pwm_lsb_nanoseconds | int | 130 | PWM LSB timing; raise toward 200+ if colors look wrong. |
pwm_dither_bits | int | 0 | Temporal dithering depth (0=off, 1–2). Spreads PWM energy more evenly across rows; try 1 or 2 if a specific row appears brighter than its neighbors at sub-100% brightness. |
show_refresh_rate | bool | false | Log measured refresh Hz to stderr — useful when tuning the bigsign. |
disable_hardware_pulsing | bool | false | Disable hardware PWM and use a CPU fallback. Roughly 5x slower; almost always leave at false. |
hot_reload | bool | true | Watch config.toml and live-reload sections/widgets/transitions/schedule/brightness without restarting ([display], [busy_light], [plugins], [web] changes still need a restart). Set false to disable. |
rp1_pio | int | 0 | Pi 5 only. 0 = RIO backend (library default — faster refresh, more CPU). 1 = force the PIO backend (lower CPU, slower refresh). Ignored on Pi 4. |
limit_refresh_rate_hz | int | 0 | Cap the hardware refresh rate in Hz. 0 = unlimited. Useful on long chains where uncapped RIO-mode refresh causes visible motion artifacts — try 100 as a starting point. |
multiplexing | int | 0 | Panel scan-row wiring scheme. 0 = direct. Try 1–5 if the bottom half of the panel shows inverted or garbled content. See the rgbmatrix docs for the full list. |
row_address_type | int | 0 | Row address type. 0 = direct (default). Change only if the panel uses a non-standard address decoder (AB-addressed, ABC-shifted, etc.). |
panel_type | string | "" | Driver IC init sequence. Set to "FM6126A" or "FM6127" for panels that use those chips (common on cheap P2/P3 AliExpress panels). Without it, FM6126A panels power up in a bad state and show the bottom half mirrored or garbled. |
led_rgb_sequence | string | "RGB" | RGB channel order. Change to "RBG", "GRB", etc. if colors appear wrong — e.g. purple/blue tint (missing green) often means green and blue are swapped; try "RBG". |
backend | string | "rgbmatrix" | Rendering backend. "rgbmatrix" drives real hardware panels. "headless" runs with no panel (useful for development/testing without a Pi). Plugins may register additional backends. |
schedule | table | (disabled) | Optional brightness schedule. Nested [display.schedule] block — see below. |
These are the common knobs. The full set of rgbmatrix library
options is broader — see CLAUDE.md for the additional Pi-5 tuning
notes and what each setting interacts with.
[display.schedule]
Section titled “[display.schedule]”Optional brightness schedule. Enabled by setting [display.schedule] with enabled = true. See Display scheduling for the full guide.
| Field | Type | Default | Notes |
|---|---|---|---|
enabled | bool | false | Master switch. When false the schedule is not loaded and brightness is never adjusted by the scheduler. |
timezone | string | "" | IANA timezone name (e.g. "America/New_York"). Empty string = system local time. Set this explicitly — fresh Pis are often UTC. |
windows | array | [] | List of [[display.schedule.windows]] tables. Each defines a time range, brightness, and optional day-of-week filter. See below. |
[[display.schedule.windows]]
Section titled “[[display.schedule.windows]]”Each window is a [[display.schedule.windows]] table entry:
| Field | Type | Default | Notes |
|---|---|---|---|
start | string | (required) | Window start time in "HH:MM" 24-hour local wall-clock format. |
end | string | (required) | Window end time. When end < start (e.g. start = "23:00", end = "07:00") the window wraps past midnight. |
brightness | int | (required) | Panel brightness to apply, 0–100. 0 makes the panel appear dark (≈ zero power draw from the LEDs); the Pi keeps running. |
days | string array | [] | Days this window is active: ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]. Empty list means every day. For midnight-wrapping windows (start > end), the window is anchored to its start day — see Day filtering. |
[title]
Section titled “[title]”Title-card timing.
| Field | Type | Default | Notes |
|---|---|---|---|
delay | int | 5 | Seconds the section title (e.g. “#DevOps News”) holds before the section’s first widget runs. |
[transitions]
Section titled “[transitions]”Global defaults for inter-widget and inter-section transitions.
| Field | Type | Default | Notes |
|---|---|---|---|
default | string | "cut" | Fallback inter-widget transition. Overridden per-section by transition or widget_transition. |
duration | float | 0.5 | Default transition duration in seconds. Sprite-trail transitions (pokeball, nyancat, scroll) typically want a longer value. |
easing | string | "linear" | Default easing curve. Accepted values: linear, ease_out, ease_in_out. |
transition_fps | float | null | Animation frames per second for all transitions. null uses the built-in default (20 fps). Raise to 30–40 on large panels to reduce choppiness. Warn if < 5 or > 120. |
between_sections | string / table | "cut" | Fallback transition fired at section boundaries. Lowest priority in the entry-transition chain — overridden by a section’s entry_transition or explicitly-set transition (see Tips below). |
separator_color | list / string | null | Color of the scroll transition’s separator mark. Same shapes as a widget font_color ([r,g,b], "rainbow", "color_cycle", {style=…}). Only honored by type = "scroll"; rejected on other transition types. null defaults to white. |
separator | string | null | The scroll transition’s separator glyph. null = the default dot; "" renders nothing (gap only). Only honored by type = "scroll". |
separator_font | string | null | Font (BDF alias or hires name) for the scroll separator glyph. null uses the default 6×12 BDF font. |
separator_font_size | int | null | Pixel size for a hires separator_font (ignored for BDF). Required when separator_font is a hires font. |
separator_size | int | null | Size (logical px) of the scroll transition’s separator dot. null = the default 2. No effect on a glyph separator. Only honored by type = "scroll". |
Both default and between_sections accept either a bare string
(transition name) or an inline table — e.g. between_sections = { type = "wipe_left", duration = 0.8, easing = "ease_out" }.
[[playlist.section]]
Section titled “[[playlist.section]]”Each [[playlist.section]] is one item in the playlist. Sections run
in order, then the playlist loops.
| Field | Type | Default | Notes |
|---|---|---|---|
mode | string | "ticker" | One of ticker, one_at_a_time, slideshow. See Sections and modes. |
loop_count | int | 1 | How many times to play this section’s widgets before moving on. 0 repeats forever within the section. |
hold_time | float | 3.0 | Seconds to hold each widget in slideshow mode. Ignored by ticker / one_at_a_time. |
continuous_scroll | bool | false | Skip the held pause for overflowing text in scroll modes. |
transition | string / table | inherits global default | Inter-widget transition for slideshow mode. When explicitly set, ALSO becomes the entry transition for this section (see Tips). Accepts a string ("wipe_left") or an inline table — see “Inline-table transition form” below. |
entry_transition | string / table | null | Override the transition used when this section enters (the inter-section boundary). Takes precedence over both transition and the global between_sections. Useful when you want a dramatic entry but a quiet inter-widget swap, or vice versa. Same string / inline-table forms as transition. |
widget_transition | string / table | null | Override the transition used for inter-widget swaps within this section. Takes precedence over transition. Does not affect the section’s entry. Same string / inline-table forms as transition. |
transition_duration | float | inherits global | Per-section override for the transition duration. Does not affect entry_transition or widget_transition (set those durations inside their inline-table form). |
transition_fps | float | null | Per-section animation fps override. Equivalent to setting transition_fps inside the transition = {...} dict. Inherits from [transitions].transition_fps if not set. |
transition_color | [r, g, b] | null | Custom color for color-aware transitions (e.g. color_flash, wipe_*). |
scale | int | display.default_scale | Per-section logical-to-physical scaling override. Useful for letterboxed countdowns at scale = 2 on the bigsign. |
content_height | int | 16 | Logical canvas height in rows. Hard ceiling: content_height × scale ≤ panel_h_real (see Tips). |
bg_color | [r, g, b] | null | Section-level background color. Widgets inherit this when they don’t set their own bg_color. |
scroll_step_ms | int | null (= 50 ms) | Milliseconds per logical pixel of scroll. Lower = faster marquee. Applies to ticker, one_at_a_time, and the post-hold scroll on slideshow mode. |
separator | string | null (= white •) | Per-section override for the bullet between ticker widgets. Empty string "" renders as two spaces (no glyph, minimum gap). Non-empty strings render as-is — include surrounding spaces if you want padding (e.g. " * "). On bigsign (default_scale > 1), the default separator renders as a smooth hi-res circle painted at physical resolution; set explicitly (e.g. separator = "•") to opt out and use BDF rendering instead. Rejected on non-ticker modes. |
transition_colors | [[r,g,b], ...] | null | Multi-color list for transitions that cycle through colors (e.g., wipe_random with multiple sweep colors). Plural form of transition_color. |
separator_font | string | null | Font name (BDF alias or hires file) for the separator glyph. null uses the default 6×12 BDF. Useful for matching the section’s widget font. |
separator_font_size | int | null | Required when separator_font is a hires font. Ignored for BDF (fixed cell height). |
separator_size | int | null | Size (logical px) of the ticker-mode separator circle (radius = size ÷ 2). null = the default 8. Only affects the bigsign hi-res circle; the smallsign BDF • is unaffected. Only honored by mode = "ticker". |
separator_color | [r, g, b] / string / table | null (= white) | Color for the separator. When separator is unset on bigsign, this colors the hi-res circle. When separator is set explicitly, it colors the BDF glyph. Accepts the same shapes as widget font_color: constant [r, g, b], "rainbow", "color_cycle", or {style = "gradient", from = [...], to = [...]}. |
start_hold | float | null (inherits [title] delay) | Pre-roll delay (seconds) before this section’s first widget begins scrolling. Only honored on ticker / one_at_a_time. Setting it on slideshow is a validation error. start_hold = 0.0 makes the section start scrolling immediately, leaving the global [title] delay unchanged for other sections. |
transition_specified | bool | false | Internal flag set by the loader when the user wrote transition = ... in this section’s TOML. You don’t write this — it’s in the dataclass for completeness. |
Inline-table transition form
Section titled “Inline-table transition form”When the transition needs more than just the type, prefer the inline-table form: transition = { type = "wipe_left", duration = 0.8, easing = "ease_out", color = [255, 0, 0] }. The flat sibling form (transition = "wipe_left" + transition_duration = 0.8) is equivalent and remains fully supported.
transition_specified lets the engine tell user-written transition = X apart from the inherited default — the distinction drives entry-transition selection (see Tips).
[playlist.section.title]
Section titled “[playlist.section.title]”A title block accepts the same field surface as a message widget with one alias: color is the title-specific spelling of font_color. Both work, and only on title blocks — writing color = "random" on a regular [[playlist.section.widget]] is a configuration error (the alias doesn’t apply outside titles).
The only other difference from a regular widget is that it’s held for
[title] delay seconds before the section’s first widget runs.
See the message widget page for the full field list.
[[playlist.section.widget]]
Section titled “[[playlist.section.widget]]”Per-widget options live on each widget’s page. See Widgets.
[[source]]
Section titled “[[source]]”Each [[source]] block declares a named value source that widget text = fields can reference with the :id: token syntax. Sources are global — they are not nested inside any section. You can declare as many [[source]] blocks as you need. See Value tokens for the full guide.
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | yes | Token name. Must match [a-z_][a-z0-9_.]* to be usable as a :token: — an id that doesn’t match the pattern is accepted by the validator but won’t be recognized at display time (the :...: renders as literal text). Must not equal an existing emoji slug; duplicate ids are rejected. |
type | string | yes | Source class. Built-in: "clock", "date", "static". Plugins may register additional types via api.source. |
format | string | no | strftime format string. Optional for clock and date (default "%H:%M"); ignored by static. |
timezone | string | no | IANA timezone name (e.g. "America/New_York"). Applies to clock and date. Omit to follow the Pi’s system clock (often UTC on a fresh Pi). |
value | string | see note | Fixed string. Required for static; ignored by clock and date. |
All sources refresh once per second. The version counter increments only when the computed value actually changes, so constant-width clock formats (%H:%M) never cause a layout reflow.
[busy_light]
Section titled “[busy_light]”A persistent corner dot painted as an overlay over every section and transition while a local file exists. Disabled by default. See Busy light for the full guide, including the Docker file_path gotcha.
| Field | Type | Default | Notes |
|---|---|---|---|
enabled | bool | false | Master switch. When false, nothing is built and there is zero per-frame cost. |
file_path | string | "~/.busy" | Light is on while this file exists; ~ expands. Under Docker, use the bind-mounted path /code/config/.busy and toggle config/.busy on the host. |
poll_interval | float | 5.0 | Seconds between file checks. |
corner | string | "top_right" | One of top_left, top_right, bottom_left, bottom_right. Other values rejected at config load. |
color | [r, g, b] | [255, 0, 0] | Three ints, each 0–255. Length/range rejected at config load. |
size | int | 4 | Dot side in physical pixels (>= 1). ~4 on the 16-tall smallsign, ~8–10 on the 64-tall bigsign/longboi. |
source | string | "file" | "file" (poll a local file) or "http" (remote push from a LAN HTTP call). |
http_host | string | "0.0.0.0" | Listen address when source = "http". 0.0.0.0 = all interfaces. |
http_port | int | 8081 | Listen port when source = "http". Range 1–65535. Defaults to 8081 so it doesn’t collide with [web]’s 8080. |
token | string | "" | Shared secret for the HTTP source. Empty = open (trusted LAN only). Prefer LED_TICKER_BUSY_TOKEN in .env; this field is a fallback used only when that env var is unset, and logs a warning when so used. |
ttl_seconds | float | 0.0 | HTTP source: default auto-clear seconds for a state=on that omits ?ttl=. Per-request ?ttl=N overrides it. 0 = stay on until explicit off. |
The optional read-only web status dashboard — a browser UI that shows what the sign is currently displaying, exposes the live config with secrets redacted, and lets you paste a candidate config through the validator. Presence of the [web] block (even empty) enables status publishing in the display process and allows the led-ticker webui sidecar to serve it. Absence disables the feature entirely — existing configs with no [web] block are unaffected. See Web status UI for setup and usage.
| Field | Type | Default | Notes |
|---|---|---|---|
http_host | string | "0.0.0.0" | Interface the sidecar binds. "0.0.0.0" listens on all interfaces (all LAN addresses); "127.0.0.1" limits to localhost only. |
http_port | int | 8080 | HTTP port the sidecar serves. The compose service uses host networking, so the Pi’s port is exposed directly. |
token | string | "" | Optional shared secret. Empty = open (trusted-LAN posture). Non-empty requires an X-Web-Token header or ?token= query parameter on every request. Prefer LED_TICKER_WEB_TOKEN in .env; this field is a fallback used only when that env var is unset, and logs a warning when so used. |
status_path | string | "/run/led-ticker/status.json" | Path where the display process writes its status snapshot; the sidecar reads the same file. Must match between both processes. |
allow_restart | bool | false | Expose a “Restart display” button in the web UI. When true, clicking the button drops a marker file that the display process detects on its next loop pass and calls sys.exit(0); Docker’s restart: unless-stopped brings it back. Set to false (default) to disable the control entirely. |
Transition precedence: entry vs. inter-widget
Section titled “Transition precedence: entry vs. inter-widget”Two independent transition slots fire per section: one when the section enters (inter-section boundary) and one for swaps between widgets within the section. Each slot resolves through its own precedence chain:
Entry transition (when this section appears):
entry_transition → transition (if explicitly set on this section) → global between_sections
Inter-widget transition (swaps within this section):
widget_transition → transition (if explicitly set on this section) → global default
The key implication: writing transition = "pokeball.forward" on a section sets both slots to that transition — it fires when the section enters AND between every widget inside it. This is usually what you want for single-widget sections. For multi-widget sections where you want a dramatic entry but a quiet per-widget swap, use entry_transition + widget_transition independently instead of transition.
If you want the global between_sections to fire on entry, leave transition unset (or use entry_transition explicitly).
content_height × scale ≤ panel_h_real
Section titled “content_height × scale ≤ panel_h_real”The wrapper centers a logical canvas of height content_height × scale on the real panel. On the bigsign at scale = 4 and a 64-tall panel, that means content_height ≤ 16. Push above and the wrapper’s vertical offset goes negative — the logical canvas is taller than the panel — and the top + bottom rows of your content silently clip. BDF text is forgiving because cells sit near the vertical center, but hi-res emoji and large hi-res fonts surface the clip immediately. For per-row breathing room use text_y_offset on the widget rather than over-sizing content_height. See Hardware: Bigsign for the full discussion.