Skip to content

Washer Delay-to-Super-Off-Peak Button — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: superpowers:executing-plans or subagent-driven-development. Steps use checkbox syntax.

Goal: A one-tap script + dashboard tile (Alexa-exposed) that schedules the LG WashTower washer to start at ~midnight so it runs in super-off-peak (00:00–05:00 ET).

Architecture: One script.washer_delay_super_off_peak (guard → compute delay → set delay + start → confirm), a mushroom-template-card tile in the Overview quick-actions grid that calls it, docs, and a manual Alexa .storage exposure step.

Global Constraints

  • Entities (verified live): number.washer_delayed_start (int 0–19 h), select.washer_operation (start), binary_sensor.washer_remote_start (must be on), notify.mobile_app_louis_iphone + notify.mobile_app_lindsays_iphone.
  • Delay formula: delay = 0 if now().hour < 5 else 24 - now().hour (always 0–19; lands start in 00:00–00:59, or starts now if already super-off-peak).
  • Notify both phones (guard-fail and confirm). No notify group (broken in 2026.6).
  • Delay-start-vs-end is NOT settled — comment in the script + docs that it assumes delay-START; verify on first real cycle.
  • Alexa exposure lives in .storage/homeassistant.exposed_entities (not repo) — manual controller step; also bump the CLAUDE.md Alexa index 19→20.
  • Deploy: scripts.yaml + command_dashboard.yaml → CI deploy-ha (full restart, since a non-automations config file changed).

Task 1: The script + dashboard tile + structural test

Files: Modify scripts.yaml, command_dashboard.yaml, tests/conftest.py (scripts fixture already added for #72), Create tests/test_washer_button.py.

  • [ ] Step 1: Failing test — create tests/test_washer_button.py:
"""Structural checks for the washer delay-to-super-off-peak button (#32)."""

def test_washer_delay_script_present_and_wired(scripts):
    assert "washer_delay_super_off_peak" in scripts, "script missing"
    body = str(scripts["washer_delay_super_off_peak"])
    for ref in ("number.washer_delayed_start", "select.washer_operation",
                "binary_sensor.washer_remote_start"):
        assert ref in body, f"script doesn't reference {ref}"

Run: python3 -m pytest tests/test_washer_button.py -v → FAIL (script missing).

  • [ ] Step 2: Add the script — append to scripts.yaml:
# One-tap: schedule the LG WashTower WASHER to run overnight in super-off-peak (00:00–05:00 ET,
# cheapest tier) — sets number.washer_delayed_start so the cycle STARTS at ~midnight, then starts
# it. Requires Remote Start armed on the unit (load + pick a cycle + press Remote Start). #32.
# VERIFY on first real cycle: assumes washer_delayed_start delays the START (not the END); if LG
# treats it as delay-END, add the cycle time to the delay formula.
washer_delay_super_off_peak:
  alias: "Delay Wash to Super-Off-Peak"
  icon: mdi:washing-machine
  mode: single
  sequence:
    # Guard: Remote Start must be armed on the unit or `start` does nothing.
    - if:
        - condition: state
          entity_id: binary_sensor.washer_remote_start
          state: "off"
      then:
        - action: notify.mobile_app_louis_iphone
          data:
            title: "🧺 Washer not ready"
            message: "Load the washer, pick a cycle, and press Remote Start on the unit  then tap again."
        - action: notify.mobile_app_lindsays_iphone
          data:
            title: "🧺 Washer not ready"
            message: "Load the washer, pick a cycle, and press Remote Start on the unit  then tap again."
        - stop: "washer remote-start not armed"
    - variables:
        delay: "{{ 0 if now().hour < 5 else 24 - now().hour }}"
    - action: number.set_value
      target:
        entity_id: number.washer_delayed_start
      data:
        value: "{{ delay }}"
    - action: select.select_option
      target:
        entity_id: select.washer_operation
      data:
        option: start
    - variables:
        msg: >-
          {{ 'Wash starting now — super-off-peak.' if delay | int == 0
             else 'Wash scheduled — starts ~midnight (super-off-peak), in ' ~ delay ~ 'h.' }}
    - action: notify.mobile_app_louis_iphone
      data:
        title: "🧺 Wash  super-off-peak"
        message: "{{ msg }}"
    - action: notify.mobile_app_lindsays_iphone
      data:
        title: "🧺 Wash  super-off-peak"
        message: "{{ msg }}"

Run: python3 -m pytest tests/test_washer_button.py -v → PASS.

  • [ ] Step 3: Add the dashboard tile — in command_dashboard.yaml, add this mushroom-template-card into the Overview quick-actions grid (the cards: list around line 270, alongside Wake House / Sleep House / All Off / Movie), matching their exact style:
              - type: custom:mushroom-template-card
                icon: mdi:washing-machine
                icon_color: teal
                primary: Wash → Cheap
                layout: vertical
                tap_action:
                  action: call-service
                  service: script.washer_delay_super_off_peak
                card_mod:
                  style: |
                    ha-card { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.13); border-radius: 13px; box-shadow: none; height: 80px; --card-primary-font-size: 11px; }
  • [ ] Step 4: Verifymake lint && make pytest (yamllint clean; suite green incl. the new test and Tier-3 entity-refs — the script's entities + notify services already exist). If Tier-3 flags any of the washer entities as not-in-snapshot, they're real → confirm in entities.txt; do NOT allowlist a typo. Confirm YAML parse: python3 -c "import yaml; d=yaml.safe_load(open('scripts.yaml')); assert 'washer_delay_super_off_peak' in d; print('ok')"

  • [ ] Step 5: Commit

git add scripts.yaml command_dashboard.yaml tests/test_washer_button.py
git commit -m "feat(energy): one-tap washer delay-to-super-off-peak (script + Overview tile) (#32)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"

Task 2: Docs

Files: CLAUDE.md, docs/energy.md, docs/notifications.md.

  • [ ] Step 1: CLAUDE.md — add script.washer_delay_super_off_peak to the scripts/energy notes, and to the Alexa Voice Exposure index (19 → 20 scripts; add to the Utility group). Note the delay-start-vs-end caveat.

  • [ ] Step 2: docs/energy.md — describe the button (overnight super-off-peak; Remote-Start-must-be-armed-first; dryer has no delay-start so it's washer-only). Match the file's style (read a neighbor first).

  • [ ] Step 3: docs/notifications.md — the two pushes (not-ready guidance; scheduled/starting-now confirmation).

  • [ ] Step 4: Gate + commitmake lint && make pytest green.

git add CLAUDE.md docs/energy.md docs/notifications.md
git commit -m "docs(energy): document washer delay-to-super-off-peak button + Alexa exposure (#32)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"

Controller finish (after tasks)

  1. Deploy: merge/push → CI deploy-ha (full restart).
  2. Alexa exposure (manual, .storage): set script.washer_delay_super_off_peakassistants["cloud.alexa"].should_expose = true in /config/.storage/homeassistant.exposed_entities (HA UI Settings → Voice assistants → Alexa, or stop→jq-edit→start), then cloud/alexa/sync (WS) + re-discover in the Alexa app. Verify the live exposed list (the ssh haos 'jq …' one-liner in CLAUDE.md) shows 20.
  3. Verify live: call script.washer_delay_super_off_peak with Remote Start OFF → confirm the guidance push (no start). (Full start-a-real-wash test is the user's on first laundry load, incl. delay-start-vs-end.)
  4. Close #32, board → Done.