Skip to content

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

🏊 Pool Timer Card

English · Español

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).

Pool Timer Card Preview

Features

  • 🪄 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

Installation

Important

Installing the card is half the setup. The pump is driven server-side by a blueprint, which is what makes the schedule run with every browser closed. Without it the card only acts when you tap it, and nothing enforces the schedule overnight.

There are three steps, not one:

  1. Install the card (below)
  2. Create the required helpers — the card can do this in one click
  3. Install the blueprint

Since v2.12.0 the card checks for the blueprint and tells you if it is missing, so you do not find out the night the pump does not run.

HACS (Recommended)

  1. Open HACS → Frontend → Custom repositories
  2. Add this repo URL, category: Lovelace
  3. Search for "Pool Timer Card" and install
  4. Refresh your browser (Ctrl+F5)

Manual

  1. Copy pool-timer-card.js to your /config/www/ folder
  2. In HA, go to Settings → Dashboards → Resources
  3. Add resource: /local/pool-timer-card.js (JavaScript Module)
  4. Refresh your browser

Required Helpers

🪄 Automatic setup (easiest): when you add the card and any required helper is missing — or the schedule helper's max is below 48 — the card shows a "Create helpers" / "Fix it" button. If you are an admin, one click creates the helpers (and fixes the max) 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.

1. Schedule storage (input_text)

Field Value
Name pool_timer_schedule
Entity ID input_text.pool_timer_schedule
Max length 255 (must be at least 48)
Initial value leave EMPTY

⚠️ Important (length): 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 to 255 to be safe. You can check the current limit in Developer Tools → States (the max attribute).

⛔ Important (persistence — do NOT set an Initial value): if a helper has an Initial value, Home Assistant resets it to that value on every restart instead of restoring the last value. Leaving it empty makes the helper keep its last value across restarts. Setting an initial here was the cause of the schedule resetting to all-off (and the mode reverting to Auto) after every HA restart. The card seeds the schedule automatically on first use.

2. Mode storage (input_select)

Field Value
Name pool_timer_mode
Entity ID input_select.pool_timer_mode
Options Auto, Perm, OFF
Initial value leave EMPTY (do not set Auto; see warning above — otherwise the mode reverts to it on every restart)

3. Action / preset state (input_text) — required for presets & actions

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.

Configuration

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"

Configuration Options

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
preset_entity string ❌ — input_select whose options are your preset names. Set it to store presets on the server (see Server-side presets)
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"

Operation Modes

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)

Presets

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.

Server-side presets

By default presets live in the card's YAML, which means only the card knows them: an automation that wants to apply "Verano" has to repeat its 48-character string, and the two drift apart the moment you edit one.

Set preset_entity and the presets move to Home Assistant instead, where automations and the blueprint can read them:

type: custom:pool-timer-card
entity: switch.pool_pump
preset_entity: input_select.pool_timer_preset

Two kinds of entity back it:

Entity Holds
The input_select you named Its options are the preset names, plus Custom
One input_text per preset That preset's 48-character schedule

The input_text for a preset is found by its friendly name — Pool Timer Preset <name> — not by its entity id. Home Assistant does not guarantee a helper's entity id is derivable from its name, so the name is the link. Rename a helper in Settings and the card treats that preset as missing.

Custom is not a preset. It is the absence of a match: the card selects it when your schedule matches nothing, and the blueprint ignores it.

Migrating

Press Create helpers on the card once. It creates the input_select and one input_text per preset, seeded with the schedules your YAML holds today.

From then on the input_select's options are the preset list. Your presets: block stops deciding which presets exist — it is only consulted to seed a helper you recreate later, matching by name. That is why deleting a preset from the card makes it disappear for good even though its YAML entry is still sitting there.

Nothing breaks if you never migrate: without preset_entity the card reads presets: exactly as before.

Managing presets

With preset_entity set, the card's visual editor can add, edit and delete presets.

Schedules are edited as time ranges, the same notation the YAML presets: uses:

10:00-14:00, 16:00-19:30

The card converts to and from the stored 48-character string for you. Two things follow from that conversion, both deliberate:

  • Times snap to half hours, and the field re-renders from what was actually stored. Type 10:15-14:20 and it comes back as 10:00-14:00 — showing you the schedule the pump will follow rather than the one you typed.
  • Input that does not parse is rejected before anything is written, so a half-typed range never reaches the helper, and from there the pump.

A range crossing midnight round-trips intact: 23:00-01:00 stays one range.

Adding a preset creates the option and its helper, seeded with the schedule currently on the dial — so "+ Add Preset" means "save what I have".

Deleting takes two clicks and removes the option and its helper, so obsolete helpers do not pile up.

The name is read-only. Renaming would mean deleting and recreating the helper, so rename by deleting the preset and creating it again.

Tip

An option whose helper is missing, empty or malformed shows greyed out with a ⚠ and cannot be selected — selecting it would apply nothing while looking like it worked. Press Create helpers to create what is missing, or fill in its schedule.

Warning

Removing an option from Settings → Helpers instead of from the card's editor leaves its input_text behind. Home Assistant has no idea the two are related — only the card's ✕ button knows to delete both.

Editing on touch devices

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.

Quick Actions

Fully configurable timed actions that override the schedule while active and are stored in state_entity, so they survive a dashboard reload.

Configuration

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" preset

Parameters:

  • name — action label (e.g. "Flocculant", "Shock")
  • hours — duration in hours
  • icon — 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

Action flow

  1. Tap an action button → pump runs for the configured hours.
  2. Timer counts down on the card.
  3. When time expires → applies the after behavior.
  4. 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.

Corner Actions (Quick Toggles)

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.

Configuration

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 (for button entities), 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)

Retry Mechanism

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:

  1. Retries with exponential backoff: 2s → 4s → 8s → 16s → 32s
  2. Shows an orange blinking LED during retries
  3. After 5 failed attempts, shows a fast red blinking LED for 10 seconds
  4. Automatically resets to normal monitoring afterwards

Automations

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"

Changing the schedule from an automation

Important

To change the schedule, write the 48-character string to the schedule helper. Do not write a preset name into the state helper — that only changes the label shown in the dropdown, it does not change when the pump runs.

The schedule helper is the single source of truth: 48 characters, one per half hour, starting at 00:00. 1 = pump on, 0 = pump off.

# Weekend schedule: 09:00-13:00 and 16:00-20:00
automation:
  - alias: "Pool weekend schedule"
    trigger:
      - platform: time
        at: "00:05:00"
    condition:
      - condition: time
        weekday: [sat, sun]
    action:
      - service: input_text.set_value
        target:
          entity_id: input_text.pool_timer_schedule
        data:
          value: "000000000000000000111111110000001111111100000000"

Every open card picks the change up immediately, and the blueprint enforces it with every browser closed. The preset dropdown updates on its own: the card matches the new schedule against your configured presets and shows that preset's name, or Custom when it matches none.

To make the bitstring easy to keep in sync with a preset, define presets with segments: instead of schedule: — then the card config and your automation use the exact same string:

presets:
  - name: Summer
    segments: "000000000000000011111111110000001111111100000000"

Tip

Counting characters by hand is error-prone. To get the string for a schedule, set it up on the dial and read the current value of input_text.pool_timer_schedule in Developer tools → States, then paste it into your automation.

Alternating between two schedules (weekday / weekend)

A common need: run a longer schedule at the weekend and a shorter one on working days — but only while you are in "summer", leaving your winter schedule alone.

With server-side presets this is the whole automation, with no schedule string in sight:

alias: Pool - summer schedule (short on weekdays, long at the weekend)
triggers:
  - trigger: time
    at: "00:00:00"
  - trigger: homeassistant
    event: start
conditions:
  # Only while a summer preset is active — leaves Invierno or a hand-drawn
  # schedule alone.
  - condition: state
    entity_id: input_select.pool_timer_preset
    state: ["Verano", "Verano corto"]
actions:
  - action: input_select.select_option
    target:
      entity_id: input_select.pool_timer_preset
    data:
      option: >-
        {{ 'Verano' if now().isoweekday() in [6, 7] else 'Verano corto' }}
mode: single

Edit a preset on the dial and this automation applies the new version at the next switch, because it is the same data.

Without server-side presets (writing the schedule string directly)
alias: Pool - summer schedule (short on weekdays, long at the weekend)
description: >-
  Alternates between the two summer schedules. Only acts when the current
  schedule is already one of them, so a winter or hand-drawn schedule is left
  untouched.

variables:
  # 10:00-13:00 and 16:00-18:30
  summer_short: "000000000000000000001111110000001111100000000000"
  # 10:00-14:00 and 16:00-19:30
  summer_long:  "000000000000000000001111111100001111111000000000"

triggers:
  - trigger: time
    at: "00:00:00"
  - trigger: homeassistant
    event: start

conditions:
  - condition: template
    value_template: >-
      {{ states('input_text.pool_timer_schedule') in [summer_short, summer_long] }}

actions:
  - choose:
      - conditions:
          - condition: time
            weekday: [sat, sun]
        sequence:
          - action: input_text.set_value
            target:
              entity_id: input_text.pool_timer_schedule
            data:
              value: "{{ summer_long }}"
      - conditions:
          - condition: time
            weekday: [mon, tue, wed, thu, fri]
        sequence:
          - action: input_text.set_value
            target:
              entity_id: input_text.pool_timer_schedule
            data:
              value: "{{ summer_short }}"

mode: single

Why it is written this way

It compares the schedule string, not the preset name. The state helper does carry a preset field, but since 2.9.5 that name is derived by the card for display only — with no dashboard open nothing keeps it up to date, so an automation that reads it will eventually act on a stale value. The 48-character schedule is the source of truth.

It runs every day at 00:00 and on Home Assistant start, not only on the two switch days. On the other days it writes the value that is already there, which is a no-op. What this buys you is self-healing: if Home Assistant restarts on a Wednesday, or you change something by hand, you are back in sync at the next midnight instead of staying wrong until the following Monday.

The condition is what makes it safe. Without it, this automation would drag you back into a summer schedule every night no matter what you had selected. With it, selecting Invierno or drawing your own schedule simply opts you out until you pick a summer schedule again.

No variable references another variable. Each choose branch reads a single top-level variable. Chained variables (target: "{{ summer_long if ... }}") fail to render on some Home Assistant versions and abort the run silently — the same trap the blueprint warns about in its own comments.

[!TIP] Make the two strings exactly match presets you have configured. When they do, the card's dropdown shows Verano or Verano corto after the automation runs; when they don't, it shows Custom — which is correct, but tells you your automation and your presets have drifted apart.

[!NOTE] The schedule string is duplicated between your presets: config and this automation, and nothing keeps the two in sync: editing a preset on the dial will not change what this automation applies. This is exactly what server-side presets remove — with them, the automation above collapses to a single input_select.select_option.

Required: install the blueprint (browser-independent operation)

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).

1. Import the blueprint

Open your Home Assistant instance and show the blueprint import dialog.

…or in HA: Settings → Automations & scenes → Blueprints → Import blueprint, and paste:

https://github.com/serweck/pool-timer-card/blob/main/blueprints/pool_timer.yaml

2. Create an automation from it

Pick the four entities in the dropdowns — the pump switch and the three helpers (schedule, mode, state). No YAML editing required.

A fifth input, Preset helper, is optional: set it to your preset_entity and selecting a preset applies its schedule server-side, with no dashboard open. Leave it empty if you have not moved presets to the server.

Important

Upgrading from a version before 2.10.0? Re-import the blueprint so the Preset helper input appears. Existing automations keep working untouched until you do — the new input defaults to empty.

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).

Language Support

The card auto-detects your Home Assistant language and shows English or Spanish.

Troubleshooting

The schedule doesn't save / segments reset on reload

  1. Check the helper's max length (most common cause). Go to Developer Tools → States, find input_text.pool_timer_schedule, and look at the max attribute. It must be ≥ 48. If it's lower, HA rejects the 48-character value. Fix it: Settings → Helpers → pool_timer_schedule → ⚙️ → Maximum length → 255.
  2. Confirm the helper accepts writes. In Developer Tools → Actions, run input_text.set_value on the entity with a 48-char value. If the state doesn't change, the helper is misconfigured.
  3. Verify the card points at the right entities (schedule_entity / mode_entity / state_entity in the card config).
  4. Make sure you loaded the latest card — open the browser console (F12) and confirm the POOL-TIMER-CARD vX.Y.Z banner; hard-refresh (Ctrl+Shift+R) if not.

Everything resets to defaults after restarting Home Assistant

This means one or more helpers have an Initial value set. HA resets a helper to its initial value on every restart instead of restoring the last value. Fix each helper: Settings → Devices & Services → Helpers → open pool_timer_schedule, pool_timer_mode (and pool_timer_state) → ⚙️ → clear the Initial value field (leave it empty) → Save. After that they keep their last value across restarts. (Card v2.9.2+ also no longer overwrites the helper during the startup window.)

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).

License

MIT © serweck

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages