Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \
| [MQTT Notifications](./plugins/mqtt-notifications/) | HomeAssistant notifications via MQTT | |
| [On Air Light](./plugins/on-air/) | Broadcast ON AIR tally light, triggered remotely via MQTT / Home Assistant | |

### Productivity (1)

| Plugin | Description | Preview |
|--------|-------------|---------|
| [Pomodoro Timer](./plugins/pomodoro-timer/) | Focus/break timer with MQTT control and Home Assistant discovery | <a href="./plugins/pomodoro-timer/"><img src="./plugins/pomodoro-timer/assets/hero.png" width="240" alt="pomodoro-timer on an LED panel"></a> |

### Custom (3)

| Plugin | Description | Preview |
Expand Down
4 changes: 2 additions & 2 deletions plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -1165,10 +1165,10 @@
"plugin_path": "plugins/pomodoro-timer",
"stars": 0,
"downloads": 0,
"last_updated": "2026-07-29",
"last_updated": "2026-09-02",
"verified": true,
"screenshot": "",
"latest_version": "1.3.4",
"latest_version": "1.3.5",
"icon": "fa-hourglass-half"
}
]
Expand Down
117 changes: 72 additions & 45 deletions plugins/pomodoro-timer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,62 +153,89 @@ durations you configured.

## Plugin Configuration

Settings are edited in the plugin's tab in the LEDMatrix web UI. The **Field**
column is the label you see there; the **Key** is the same setting as it appears
in `config/config.json` under `pomodoro-timer`, which is what you need if you
edit the file directly or read it back in a log line. The full schema is
[`config_schema.json`](config_schema.json).

### Timer

| Field | Default | Description |
|---|---|---|
| **Work Session (minutes)** | `25` | Length of a focus session. Also settable live from Home Assistant. |
| **Short Break (minutes)** | `5` | Break after each work session. |
| **Long Break (minutes)** | `15` | Break after a full set of work sessions. |
| **Sessions Before Long Break** | `4` | How many work sessions make a set — this is the number of dots on screen. |
| **Auto-Start Breaks** | `true` | Roll straight into the break when work ends. Off makes the break wait for a Start/Resume. |
| **Auto-Start Next Work Session** | `false` | Roll straight back into work when a break ends. Off by default so you decide when to go again. |
| **Start Timer When Plugin Is Enabled** | `false` | Begin a work session as soon as the plugin loads. Handy without MQTT. |
| Field | Key | Default | Description |
|---|---|---|---|
| *(header toggle)* | `enabled` | `true` | Master switch. It is the toggle in the tab's header rather than a field in the form. Off removes the timer from the rotation entirely. |
| **Work Session (minutes)** | `work_minutes` | `25` | Length of a focus session (1–180). Also settable live from Home Assistant. |
| **Short Break (minutes)** | `short_break_minutes` | `5` | Break after each work session (1–60). |
| **Long Break (minutes)** | `long_break_minutes` | `15` | Break after a full set of work sessions (1–120). |
| **Sessions Before Long Break** | `sessions_before_long_break` | `4` | How many work sessions make a set (1–12) — this is the number of dots on screen. |
| **Auto-Start Breaks** | `auto_start_breaks` | `true` | Roll straight into the break when work ends. Off makes the break wait for a Start/Resume. |
| **Auto-Start Next Work Session** | `auto_start_work` | `false` | Roll straight back into work when a break ends. Off by default so you decide when to go again. |
| **Start Timer When Plugin Is Enabled** | `auto_start_on_enable` | `false` | Begin a work session as soon as the plugin loads. Handy without MQTT. |

### MQTT

| Field | Default | Description |
|---|---|---|
| **Enable MQTT Control** | `true` | Connect to a broker so the timer can be driven remotely. |
| **Broker Address** | `localhost` | IP or hostname of your MQTT broker. |
| **Broker Port** | `1883` | Use 8883 for TLS. |
| **Username / Password** | *(blank)* | Leave blank for an anonymous broker. |
| **Command Topic** | `ledmatrix/pomodoro/set` | Everything the plugin publishes is derived from this topic's base. |
| **State Topic** | `ledmatrix/pomodoro/state` | `ON` while a session is active, `OFF` when idle. |
| **Enable Home Assistant Auto-Discovery** | `true` | Announce the device to HA over MQTT. |
| **HA Discovery Prefix** | `homeassistant` | Only change this if you changed it in HA. |
| **Device Name in Home Assistant** | `LED Matrix — Pomodoro` | How the device is labelled in HA. |
| **State Publish Interval (seconds)** | `1` | How often the countdown is published while running. Raise it to cut broker traffic — state *changes* are always published immediately. |
| Field | Key | Default | Description |
|---|---|---|---|
| **Enable MQTT Control** | `mqtt_enabled` | `true` | Connect to a broker so the timer can be driven remotely. |
| **Broker Address** | `mqtt_host` | `localhost` | IP or hostname of your MQTT broker. |
| **Broker Port** | `mqtt_port` | `1883` | Use 8883 for TLS. |
| **Username / Password** | `mqtt_username`<br>`mqtt_password` | *(blank)* | Leave blank for an anonymous broker. The password is marked secret, so the web UI masks it. |
| **Command Topic** | `command_topic` | `ledmatrix/pomodoro/set` | Everything the plugin publishes is derived from this topic's base. |
| **State Topic** | `state_topic` | `ledmatrix/pomodoro/state` | `ON` while a session is active, `OFF` when idle. |
| **Enable Home Assistant Auto-Discovery** | `ha_discovery` | `true` | Announce the device to HA over MQTT. |
| **HA Discovery Prefix** | `discovery_prefix` | `homeassistant` | Only change this if you changed it in HA. |
| **Device Name in Home Assistant** | `device_name` | `LED Matrix — Pomodoro` | How the device is labelled in HA. |
| **State Publish Interval (seconds)** | `publish_interval_seconds` | `1` | How often the countdown is published while running (1–60). Raise it to cut broker traffic — state *changes* are always published immediately. |

### Appearance

| Field | Default | Description |
|---|---|---|
| **Countdown Color** | `phase` | `phase` colors the countdown by what's running; `fixed` always uses the Countdown Text Color. |
| **Colour Theme** | `classic` | `classic` uses the individual phase colours below. `calm` overrides them with a softer palette — warm terracotta, sage, soft indigo. |
| **Work / Short Break / Long Break Color** | red / green / blue | Phase colors. |
| **Idle / Paused Color** | grey / amber | Used when nothing is running and when paused. |
| **How Paused Looks** | `amber` | `amber` switches the countdown to the Paused Colour. `desaturate` keeps the phase's own hue but drains it, so a held timer reads as halted without changing which phase you're in. |
| **Countdown Text Color** | white | Only used when Countdown Color is `fixed`. |
| **Background Color** | black | Panel background. |
| **Countdown Digits** | `seven_segment` | `seven_segment` draws clock-radio style segments sized to the panel. `pixel` uses the display's pixel font. |
| **Show Unlit Segments** | `false` | Faintly light the unlit segments, like a real LED clock. Authentic, but it costs legibility — every digit gains a faint `8` behind it. Ignored when the stroke is only one pixel wide, where the effect would just be noise. |
| **Burndown Indicator** | `perimeter` | `perimeter` drains a ring around the edge of the panel; `bar` empties a bar along the bottom; `segments` puts out a row of blocks one at a time; `none` hides it. |
| **Show Phase Label** | `true` | The phase name above the countdown. |
| **Show Session Dots** | `true` | One pip per session in the set. Completed are solid, upcoming are hollow, and the one you're in is picked out. |
| **Pulse the Current Session Dot** | `true` | Slowly blink the pip for the session you're in, so the row reads as "two done, on the third" rather than just a count. |
| **Work / Short Break / Long Break / Idle / Paused Label** | `FOCUS` / `BREAK` / `LONG BREAK` / `POMODORO` / `PAUSED` | The on-screen text for each state. Blank the Paused Label to keep showing the phase name while paused. |
| **Font** | *(blank)* | Path to a TTF relative to the LEDMatrix root, e.g. `assets/fonts/PressStart2P-Regular.ttf`. Blank uses the display's default font. |
| **Font Size (px)** | `0` | Fix the countdown height in pixels. `0` sizes it automatically to the panel. |
| Field | Key | Default | Description |
|---|---|---|---|
| **Countdown Color** | `color_mode` | `phase` | `phase` colors the countdown by what's running; `fixed` always uses the Countdown Text Color. |
| **Colour Theme** | `color_theme` | `classic` | `classic` uses the individual phase colours below. `calm` overrides them with a softer palette — warm terracotta, sage, soft indigo. |
| **Work / Short Break / Long Break Color** | `work_color`<br>`short_break_color`<br>`long_break_color` | red / green / blue | Phase colors, each `[r, g, b]`. |
| **Idle / Paused Color** | `idle_color`<br>`paused_color` | grey / amber | Used when nothing is running and when paused. |
| **How Paused Looks** | `paused_style` | `amber` | `amber` switches the countdown to the Paused Colour. `desaturate` keeps the phase's own hue but drains it, so a held timer reads as halted without changing which phase you're in. |
| **Countdown Text Color** | `time_color` | white | Only used when Countdown Color is `fixed`. |
| **Background Color** | `background_color` | black | Panel background. |
| **Countdown Digits** | `digit_style` | `seven_segment` | `seven_segment` draws clock-radio style segments sized to the panel. `pixel` uses the display's pixel font. |
| **Show Unlit Segments** | `show_ghost_segments` | `false` | Faintly light the unlit segments, like a real LED clock. Authentic, but it costs legibility — every digit gains a faint `8` behind it. Ignored when the stroke is only one pixel wide, where the effect would just be noise. |
| **Burndown Indicator** | `progress_style` | `perimeter` | `perimeter` drains a ring around the edge of the panel; `bar` empties a bar along the bottom; `segments` puts out a row of blocks one at a time; `none` hides it. |
| **Show Phase Label** | `show_phase_label` | `true` | The phase name above the countdown. |
| **Show Session Dots** | `show_session_dots` | `true` | One pip per session in the set. Completed are solid, upcoming are hollow, and the one you're in is picked out. |
| **Pulse the Current Session Dot** | `pulse_active_pip` | `true` | Slowly blink the pip for the session you're in, so the row reads as "two done, on the third" rather than just a count. |
| **Work / Short Break / Long Break / Idle / Paused Label** | `work_label`<br>`short_break_label`<br>`long_break_label`<br>`idle_label`<br>`paused_label` | `FOCUS` / `BREAK` / `LONG BREAK` / `POMODORO` / `PAUSED` | The on-screen text for each state, up to 24 characters. Blank the Paused Label to keep showing the phase name while paused. |
| **Font** | `font_path` | *(blank)* | Path to a TTF relative to the LEDMatrix root, e.g. `assets/fonts/PressStart2P-Regular.ttf`. Blank uses the display's default font. |
| **Font Size (px)** | `font_size` | `0` | Fix the countdown height in pixels. `0` sizes it automatically to the panel. |

### Behavior

| Field | Default | Description |
|---|---|---|
| **Hold the Display While Running** | `true` | Take over the matrix while a session is active instead of rotating. Off lets the timer take a normal turn in the rotation. |
| **Phase-Change Alert (seconds)** | `8` | How long the timer grabs the display when a phase ends. `0` disables it. |
| **Flash on Phase Change** | `true` | Flash the panel during that alert. |
| **Display Duration (seconds)** | `10` | Time on screen per rotation cycle when it isn't holding the display. |
| Field | Key | Default | Description |
|---|---|---|---|
| **Hold the Display While Running** | `pin_while_running` | `true` | Take over the matrix while a session is active instead of rotating. Off lets the timer take a normal turn in the rotation. |
| **Phase-Change Alert (seconds)** | `alert_seconds` | `8` | How long the timer grabs the display when a phase ends. `0` disables it. |
| **Flash on Phase Change** | `alert_flash` | `true` | Flash the panel during that alert. |
| **Display Duration (seconds)** | `display_duration` | `10` | Time on screen per rotation cycle when it isn't holding the display. |

Fine-tuning settings are collected into a collapsed **Advanced Settings**
section below the main fields in the web UI. They are listed above alongside
everything else.

### A minimal configuration

```json
{
"pomodoro-timer": {
"enabled": true,
"work_minutes": 25,
"short_break_minutes": 5,
"long_break_minutes": 15,
"sessions_before_long_break": 4,
"mqtt_enabled": true,
"mqtt_host": "homeassistant.local"
}
}
```

---

Expand Down
10 changes: 8 additions & 2 deletions plugins/pomodoro-timer/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "pomodoro-timer",
"name": "Pomodoro Timer",
"version": "1.3.4",
"version": "1.3.5",
"author": "ChuckBuilds",
"description": "A configurable Pomodoro focus/break timer for your matrix. Set the work and break lengths, then start, pause, skip, or reset it over MQTT \u2014 with Home Assistant auto-discovery so the whole timer shows up as a device with no YAML.",
"entry_point": "manager.py",
Expand All @@ -22,6 +22,12 @@
">=2.0.0"
],
"versions": [
{
"released": "2026-09-02",
"version": "1.3.5",
"notes": "Documentation. Every setting in the README now carries its config.json key alongside the web-UI label, so the tables can be used when editing the file directly rather than only when clicking through the form; all 46 schema properties are covered. Corrects two claims: 'enabled' is the tab's header toggle rather than a form field, and the advanced settings appear in a collapsed 'Advanced Settings' section. No functional change.",
"ledmatrix_min_version": "2.0.0"
},
{
"released": "2026-08-15",
"version": "1.3.4",
Expand Down Expand Up @@ -88,7 +94,7 @@
"license": "GPL-3.0",
"homepage": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/pomodoro-timer",
"config_schema": "config_schema.json",
"last_updated": "2026-07-29",
"last_updated": "2026-09-02",
"stars": 0,
"downloads": 0,
"verified": true,
Expand Down
25 changes: 21 additions & 4 deletions scripts/update_readme_previews.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
The "Available Plugins" tables in ``README.md`` carry a thumbnail of each
plugin's hero screenshot. This script rewrites that column from what is
actually on disk: a plugin gets a thumbnail once
``docs/assets/<plugin-id>/hero.png`` exists, and an empty cell until then.
``docs/assets/<plugin-id>/hero.png`` (or ``plugins/<plugin-id>/assets/hero.png``,
for plugins that ship their own renderer) exists, and an empty cell until then.

Run it after adding a plugin's README screenshots::

Expand Down Expand Up @@ -40,14 +41,30 @@
SEPARATOR = "|--------|-------------|---------|"


def hero_path(plugin_id: str) -> str | None:
"""Where this plugin's hero image lives, as a repo-relative URL.

Most plugins render theirs into ``docs/assets/<id>/``. A few carry their own
renderer and keep the image beside the plugin, so look there too rather than
leaving those rows blank.
"""
shared = ASSETS_ROOT / plugin_id / "hero.png"
if shared.is_file():
return f"./docs/assets/{plugin_id}/hero.png"
local = REPO_ROOT / "plugins" / plugin_id / "assets" / "hero.png"
if local.is_file():
return f"./plugins/{plugin_id}/assets/hero.png"
return None


def preview_cell(plugin_id: str) -> str:
"""The Preview cell for one plugin: a thumbnail, or empty if none yet."""
hero = ASSETS_ROOT / plugin_id / "hero.png"
if not hero.is_file():
hero = hero_path(plugin_id)
if hero is None:
return " "
return (
f' <a href="./plugins/{plugin_id}/">'
f'<img src="./docs/assets/{plugin_id}/hero.png" width="{THUMB_WIDTH}"'
f'<img src="{hero}" width="{THUMB_WIDTH}"'
f' alt="{plugin_id} on an LED panel"></a> '
)

Expand Down
Loading