A skeuomorphic 24-hour mechanical pool timer custom card for Home Assistant. Inspired by real analog pool timers, it lets you visually schedule your pool pump with interactive half-hour segments — plus presets (Summer / Winter) and timed treatment actions (flocculant / shock).
- 🪄 One-click setup — auto-creates the required helpers (admin) if missing, and fixes a too-small
max - 🎛️ 48 interactive segments (30 min each) — tap or drag to schedule (on touch devices a vertical swipe scrolls the page, a horizontal drag paints segments)
- ⏰ Real-time clock needle — auto-updates to show current time
- 🔄 3 operation modes: Auto / Perm / OFF
- 🗂️ Presets — one tap to load a full schedule (e.g. Summer / Winter), fully configurable
- 🌀 Flocculant action — circulate for N hours, then lock the pump OFF so the floc settles, until you vacuum the bottom and press resume
- 🧪 Treatment action (shock / product) — run for N hours, then automatically return to the previous mode
- 🔁 Retry with exponential backoff — verifies pump state after commands
- 🌐 Multi-language: English & Spanish (auto-detected from HA)
- 💾 Persistent — schedule, mode, presets and running actions stored in HA helpers; survive dashboard reloads
- 🤖 Automation-friendly — everything is stored in standard helpers
- 📱 Responsive — works on desktop and mobile
- Open HACS → Frontend → Custom repositories
- Add this repo URL, category: Lovelace
- Search for "Pool Timer Card" and install
- Refresh your browser (Ctrl+F5)
- Copy
pool-timer-card.jsto your/config/www/folder - In HA, go to Settings → Dashboards → Resources
- Add resource:
/local/pool-timer-card.js(JavaScript Module) - Refresh your browser
🪄 Automatic setup (easiest): when you add the card and any required helper is missing — or the schedule helper's
maxis below 48 — the card shows a "Create helpers" / "Fix it" button. If you are an admin, one click creates the helpers (and fixes themax) for you via Home Assistant's helper API. Non-admins get a note to ask an admin. You can still create them manually as described below.
Create these helpers in Settings → Devices & Services → Helpers.
| Field | Value |
|---|---|
| Name | pool_timer_schedule |
| Entity ID | input_text.pool_timer_schedule |
| Max length | 255 (must be at least 48) |
| Initial value | 000000000000000000000000000000000000000000000000 |
⚠️ Important: the schedule is stored as a 48-character string. If the helper's maximum length is below 48, Home Assistant silently rejects the value and the schedule will never save. Set it to255to be safe. You can check the current limit in Developer Tools → States (themaxattribute).
| Field | Value |
|---|---|
| Name | pool_timer_mode |
| Entity ID | input_select.pool_timer_mode |
| Options | Auto, Perm, OFF |
| Initial value | Auto |
| Field | Value |
|---|---|
| Name | pool_timer_state |
| Entity ID | input_text.pool_timer_state |
| Max length | 255 |
| Initial value | (leave empty) |
This helper stores a small JSON blob describing the active preset and any
running timed action, e.g. {"preset":"Verano","action":"product","until":1718040000000,"ret":"Auto"}.
It is what lets a flocculant/treatment action resume correctly after a reload.
type: custom:pool-timer-card
entity: switch.pool_pump # Your pump switch entity (required)
name: Pool Timer # Card title (optional)
# Helpers (optional — these are the defaults)
schedule_entity: input_text.pool_timer_schedule
mode_entity: input_select.pool_timer_mode
state_entity: input_text.pool_timer_state
# Timed quick actions (optional — these are the defaults if omitted)
quick_actions:
- name: "Flocculant"
hours: 2
icon: "🌀"
after: "OFF"
- name: "Treatment"
hours: 3
icon: "🧪"
after: "Auto"
# Presets (optional — these are the defaults if omitted)
presets:
- name: Verano
schedule:
- { start: "08:00", end: "13:00" }
- { start: "16:00", end: "20:00" }
- name: Invierno
schedule:
- { start: "10:00", end: "13:00" }
# Corner quick-toggle buttons (optional — omit if you don't need them)
corner_actions:
- name: "Pool Lights"
icon: "💡"
position: "top-right" # tl, tr, bl, br (or top-left, top-right, …)
service: "light"
entity_id: "light.pool_lights"
action: "toggle"
- name: "Start Robot"
icon: "🤖"
position: "bottom-right"
service: "button" # press a one-shot button entity
entity_id: "button.pool_robot_start"
action: "press"| Option | Type | Required | Default | Description |
|---|---|---|---|---|
entity |
string | ✅ | — | Switch entity for the pool pump |
name |
string | ❌ | Pool Timer |
Card title |
schedule_entity |
string | ❌ | input_text.pool_timer_schedule |
Helper storing the 48-segment schedule |
mode_entity |
string | ❌ | input_select.pool_timer_mode |
Helper storing the operation mode |
state_entity |
string | ❌ | input_text.pool_timer_state |
Helper storing preset + running action |
quick_actions |
list | ❌ | Flocculant, Treatment | Array of timed actions with name, hours, icon, after |
flocculant_hours |
number | ❌ | 2 |
(Legacy) Hours for flocculant action |
product_hours |
number | ❌ | 3 |
(Legacy) Hours for treatment action |
presets |
list | ❌ | Verano / Invierno | Named schedules; each has name + schedule ranges |
corner_actions |
list | ❌ | — | Corner quick-toggle buttons; each has name, icon, position, service, entity_id, action (see Corner Actions) |
schedule |
list | ❌ | — | One-time default schedule (used only when the schedule helper is empty) |
A preset can also be given as a raw 48-char string instead of ranges:
presets:
- name: Custom
segments: "111100000000111100000000000000000000000000000000"| Mode | Behavior |
|---|---|
| Auto | Pump follows the programmed schedule segments |
| Perm | Pump is always ON (override) |
| OFF | Pump is always OFF (override) |
Mode selector UI:
- With presets → dropdown menu (faster mode + preset switching)
- Without presets → 3 buttons (original UI)
Use the Presets dropdown (when you have presets configured) to:
- Select a preset to load its full 48-segment schedule and switch to Auto
- Choose "Custom" to edit segments manually on the dial
When you manually edit the dial, the selector switches to Custom so you know
you're in edit mode — unless your edit happens to match a configured preset
exactly, in which case the selector snaps to that preset automatically. Presets
are defined in the card YAML (presets:) and editing does not modify the
original preset definition.
To keep dashboard scrolling smooth, the dial distinguishes gesture intent:
| Gesture | Action |
|---|---|
| Tap a segment | Toggle that half-hour on/off |
| Horizontal drag across the ring | Paint multiple segments |
| Vertical swipe | Scrolls the page (does not edit) |
Editing works in any mode, including OFF.
Fully configurable timed actions that override the schedule while active and
are stored in state_entity, so they survive a dashboard reload.
Define as many actions as you need:
quick_actions:
- name: "Flocculant"
hours: 2
icon: "🌀"
after: "OFF" # Lock pump OFF until manually resumed
- name: "Treatment"
hours: 3
icon: "🧪"
after: "Auto" # Return to Auto mode after
- name: "Filtrado"
hours: 1
icon: "🔄"
after: "Verano" # Return to the "Verano" presetParameters:
name— action label (e.g. "Flocculant", "Shock")hours— duration in hoursicon— emoji for the button (optional)after— what happens when the action finishes:"OFF"— lock the pump OFF (settling state) until user resumes"Auto"— return to Auto mode with the current schedule- preset name (e.g.
"Verano") — load that preset
Legacy format (still works):
flocculant_hours: 2
product_hours: 3- Tap an action button → pump runs for the configured hours.
- Timer counts down on the card.
- When time expires → applies the
afterbehavior. - You can cancel with the Cancel button anytime.
⏱️ Reliability note: the pump is driven server-side by the blueprint, so timed actions transition on time even with no browser open. Make sure you completed Required: install the blueprint — it is part of the setup, not optional.
Quick one-tap buttons positioned in any of the 4 corners of the dial for instant control of auxiliary entities: lights, jacuzzi, pool robot, filtration, etc. No timer — they execute immediately.
You can configure corner actions either from the visual editor (Edit card → Corner Actions section → + Add Corner Action, then set icon, name, corner, entity, service and action per row) or directly in YAML:
corner_actions:
- name: "Jacuzzi"
icon: "🛁"
position: "top-left" # tl, tr, bl, br (or full names: top-left, etc.)
service: "switch" # HA service domain
entity_id: "switch.jacuzzi" # Entity to control
action: "toggle" # toggle, turn_on, turn_off
- name: "Pool Lights"
icon: "💡"
position: "top-right"
service: "light"
entity_id: "light.pool_lights"
action: "toggle"
- name: "Pool Robot"
icon: "🤖"
position: "bottom-left"
service: "switch"
entity_id: "switch.pool_robot"
action: "turn_on"Parameters:
name— button label / tooltip (required)icon— emoji for the button (required)position— corner position (required):"tl"/"top-left","tr"/"top-right","bl"/"bottom-left","br"/"bottom-right"service— HA service domain:switch,light,button,script,scene,automation, etc.entity_id— the entity to control (required)action— what to do:toggle,turn_on,turn_off,press(forbuttonentities),trigger(for automations)
Pressing a button (e.g. a one-shot "start pool robot" button entity): set
service: button and action: press (the card sends button.press). button
and scene are normalized automatically, so even if the action is left at its
default they still fire correctly (button.press / scene.turn_on).
corner_actions:
- name: "Start Robot"
icon: "🤖"
position: "bottom-right"
service: "button"
entity_id: "button.pool_robot_start"
action: "press"Visual feedback:
- Default: icon only, subtle shadow
- On hover: icon scales up, glow effect
- Active (light is ON): icon color changes to green with glow
Limitations:
- Maximum 4 corner actions (one per corner)
- Each position can have at most one button
- Actions execute immediately (no timer, no state persistence)
When the card sends a command to turn the pump ON or OFF, it verifies the actual state after 2 seconds. If the state doesn't match:
- Retries with exponential backoff: 2s → 4s → 8s → 16s → 32s
- Shows an orange blinking LED during retries
- After 5 failed attempts, shows a fast red blinking LED for 10 seconds
- Automatically resets to normal monitoring afterwards
Everything is stored in standard helpers, so you can drive the card from HA.
# Switch to Perm mode when water temperature is high
automation:
- alias: "Pool pump on when hot"
trigger:
- platform: numeric_state
entity_id: sensor.pool_temperature
above: 30
action:
- service: input_select.select_option
target:
entity_id: input_select.pool_timer_mode
data:
option: "Perm"The card is the UI; the actual pump control runs server-side in Home Assistant via the included blueprint. This is what makes the schedule work 24/7 even with every browser/tab closed — without it, the card only reflects state and acts on your taps, but nothing enforces the schedule unattended.
⚠️ The blueprint is part of the setup, not optional. The card no longer drives the pump on a timer; it delegates that to the blueprint, which reads the same three helpers and reproduces the card's exact logic (Auto schedule, Perm/OFF, running actions, the flocculant "settling" lock, and the post-action transition).
…or in HA: Settings → Automations & scenes → Blueprints → Import blueprint, and paste:
https://github.com/serweck/pool-timer-card/blob/main/blueprints/pool_timer.yaml
Pick the four entities in the dropdowns — the pump switch and the three helpers
(schedule, mode, state). No YAML editing required.
That's it. The card stays a pure UI and the blueprint owns the pump; explicit taps on the card still act immediately for a snappy response, and the blueprint reconciles the state every minute (and right after a Home Assistant restart).
The card auto-detects your Home Assistant language and shows English or Spanish.
The schedule doesn't save / segments reset on reload
- Check the helper's max length (most common cause). Go to
Developer Tools → States, find
input_text.pool_timer_schedule, and look at themaxattribute. It must be ≥ 48. If it's lower, HA rejects the 48-character value. Fix it: Settings → Helpers → pool_timer_schedule → ⚙️ → Maximum length →255. - Confirm the helper accepts writes. In Developer Tools → Actions, run
input_text.set_valueon the entity with a 48-char value. If the state doesn't change, the helper is misconfigured. - Verify the card points at the right entities (
schedule_entity/mode_entity/state_entityin the card config). - Make sure you loaded the latest card — open the browser console (F12) and
confirm the
POOL-TIMER-CARD vX.Y.Zbanner; hard-refresh (Ctrl+Shift+R) if not.
Presets or actions don't persist after a reload
Make sure input_text.pool_timer_state exists (helper #3) and its max is ≥ 200.
The mode buttons don't apply
Ensure input_select.pool_timer_mode exists and its options are exactly
Auto, Perm, OFF (case-sensitive).
MIT © serweck
