Skip to content

Make the Treo pool light component generic: power_cycle_pool_light - #90

Merged
nuttytree merged 1 commit into
masterfrom
f/generic-power-cycle-pool-light
Aug 4, 2026
Merged

nuttytree merged 1 commit into
masterfrom
f/generic-power-cycle-pool-light

Conversation

@nuttytree

Copy link
Copy Markdown
Owner

Plenty of LED pool lights work the way my TREO lights do: no data connection at all, wired to a plain relay, advancing to the next color in a fixed list every time power is briefly interrupted. The component handled that pattern correctly but hard-coded every TREO-specific detail — the eight color names, the 8 in each bounds check, the reset sequence from SR Smith's user guide, and a 200ms COLOR_CHANGE_ON_OFF_TIME constant — so using it with any other light meant forking it and editing C++. That has already happened at least once, for a 16-color Moov light with a 20s reset dwell.

Nothing about the approach was TREO-specific, only the numbers. Those are now configuration.

Changes:

  • Rename the component treo_led_pool_light → power_cycle_pool_light, naming the mechanism rather than one vendor. Namespace and classes follow (power_cycle_pool_light, PowerCyclePoolLightOutput/Effect), and the sources are renamed to match.
  • colors (required, ≥ 2, unique) replaces the hard-coded effect names. The effects list is generated from it, so an effect's 1-based index is the color index the C++ already tracked; entry 1 is the color the light shows after a reset. Bounds checks now use the configured count.
  • reset_sequence (required) replaces the nested set_timeout chain: a list of {state, duration} steps run in order, driven off an index.
  • min_off_time (required) is new behavior, not a parameterized constant. Since a brief power interruption is what advances the color, turning the light off and straight back on could land it on a color nobody asked for. Every output write now goes through set_output_state_(), which timestamps the on→off transition, and a turn-on inside that window is deferred by the remainder rather than dropped. Boot counts as the start of an off period, since the relay was off for an unknown time before it.
  • color_change_time (optional, default 200ms) replaces COLOR_CHANGE_ON_OFF_TIME.
  • Rewrite the component README around the general case, listing the lights this is expected to work with (Moov, Pentair IntelliBrite/GloBrite/MicroBrite, J&J ColorSplash, Jandy/Zodiac WaterColors), and update the two references in the repo README and the CI matrix comment.

Also fixes a bug in the reset: it only turned the light back on when the target color wasn't the first one, so resetting while already on color 1 left the relay off with the light state reading on. The sequence now ends by calling write_state(), which reconciles the output with the light state and steps to the target color if needed, either way.

Upgrade note: pool-lights.yaml carries the TREO values that used to be compiled in, so my device behaves exactly as before. Anyone pulling this component from GitHub has to rename the platform and add the four required options — there is no default vendor, and a config that doesn't set them will fail validation rather than silently assume TREO timings.

Verified with esphome config devices/pool-lights.yaml and a full esp8266 esphome compile of a scratch copy of it (46.7% flash, 41.5% RAM).

Plenty of LED pool lights work the way the TREO lights do: no data
connection at all, wired to a plain relay, advancing to the next color in a
fixed list every time power is briefly interrupted. The component handled
that pattern correctly but hard-coded every TREO-specific detail -- the
eight color names, the 8 in each bounds check, the reset sequence from SR
Smith's user guide, and a 200ms COLOR_CHANGE_ON_OFF_TIME constant -- so
using it with any other light meant forking it and editing C++. That has
already happened at least once, for a 16-color Moov light with a 20s reset
dwell.

Nothing about the approach was TREO-specific, only the numbers. Those are
now configuration.

Changes:

- Rename the component treo_led_pool_light -> power_cycle_pool_light,
  naming the mechanism rather than one vendor. Namespace and classes follow
  (power_cycle_pool_light, PowerCyclePoolLightOutput/Effect), and the
  sources are renamed to match.
- colors (required, >= 2, unique) replaces the hard-coded effect names. The
  effects list is generated from it, so an effect's 1-based index is the
  color index the C++ already tracked; entry 1 is the color the light shows
  after a reset. Bounds checks now use the configured count.
- reset_sequence (required) replaces the nested set_timeout chain: a list of
  {state, duration} steps run in order, driven off an index.
- min_off_time (required) is new behavior, not a parameterized constant.
  Since a brief power interruption is what advances the color, turning the
  light off and straight back on could land it on a color nobody asked for.
  Every output write now goes through set_output_state_(), which timestamps
  the on->off transition, and a turn-on inside that window is deferred by
  the remainder rather than dropped. Boot counts as the start of an off
  period, since the relay was off for an unknown time before it.
- color_change_time (optional, default 200ms) replaces
  COLOR_CHANGE_ON_OFF_TIME.

Also fixes a bug in the reset: it only turned the light back on when the
target color wasn't the first one, so resetting while already on color 1
left the relay off with the light state reading on. The sequence now ends by
calling write_state(), which reconciles the output with the light state and
steps to the target color if needed, either way.

pool-lights.yaml carries the TREO values that used to be compiled in, so the
device behaves exactly as before. Anyone pulling this component from GitHub
must add the four required options; there is no default vendor.

Verified with esphome config on pool-lights.yaml and a full esp8266
esphome compile of a scratch copy of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nuttytree
nuttytree merged commit de9a7a2 into master Aug 4, 2026
7 checks passed
@nuttytree
nuttytree deleted the f/generic-power-cycle-pool-light branch August 4, 2026 18:26
@nuttytree

Copy link
Copy Markdown
Owner Author

@skyfrank — heads up, since I noticed you'd forked this to get the component working with your Moov lights: everything you had to edit in C++ is now YAML configuration, so you should be able to drop the fork and pull power_cycle_pool_light straight from this repo.

The component is renamed (treo_led_pool_light → power_cycle_pool_light, since nothing about the approach was ever TREO-specific), and the color list, reset sequence, and both timings are now config. Translating what you had hard-coded in skyfrank/ESPHome-Devices@e4eed69:

external_components:
  - source: github://nuttytree/ESPHome-Devices
    components: [ power_cycle_pool_light ]

light:
  - platform: power_cycle_pool_light
    id: pool_lights
    name: "Pool Lights"
    output: your_relay_output
    colors:
      - "Evening Sea"
      - "Evening Rivers"
      - "Riviera"
      - "Neutral White"
      - "Rainbow"
      - "River of Colors"
      - "Disco"
      - "Full Colors"
      - "Party"
      - "Sun White"
      - "Red"
      - "Green"
      - "Blue"
      - "Cyan"
      - "Yellow"
      - "Magenta"
    min_off_time: 20s
    reset_sequence:
      - state: "off"
        duration: 20s
      - state: "on"
        duration: 250ms
      - state: "off"
        duration: 250ms
      - state: "on"
        duration: 250ms
      - state: "off"
        duration: 250ms
      - state: "on"
        duration: 250ms
      - state: "off"
        duration: 5.5s

The 16 colors are in your fork's order, so "Evening Sea" is color 1 — the one the light lands on after a reset. color_change_time defaults to 200ms, which is what your fork used, so you can leave it out.

Two things worth checking rather than taking from me:

  • That last reset step. Your fork's comment says "wait for 3 seconds", but COLOR_RESET_FINISH is still at 6750, i.e. 5500ms after the final off — so the code has been doing 5.5s, not 3s. I put 5.5s above to match the behavior you've actually been running. If 3s is what your light's guide calls for, that's a one-line change now.
  • min_off_time. This one is new behavior, not just a parameterized constant: since a brief interruption is what advances the color, the component now refuses to re-energize the output until it has been off this long, deferring the turn-on instead of dropping it. 20s above is conservative — it's the dwell your reset already treats as "definitely a real power off" — but it does mean turning the lights off and back on inside 20s makes you wait. If the Moov documentation gives a real threshold, use that; it's probably well under 20s.

One fix in here that affects you directly: the reset only turned the light back on when the target color wasn't the first one, so running a reset while already on color 1 left the relay off with the light state still reading on. It now ends by reconciling the output with the light state either way.

If the Moov timings turn out to need something the current options can't express, open an issue and I'll take a look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant