Skip to content

Jandy Infinite Water Colors: full iAqualink control (on/off, brightness, color mode, RGB)#526

Open
knap1930 wants to merge 35 commits into
aqualinkd:masterfrom
knap1930:infinite_watercolors
Open

Jandy Infinite Water Colors: full iAqualink control (on/off, brightness, color mode, RGB)#526
knap1930 wants to merge 35 commits into
aqualinkd:masterfrom
knap1930:infinite_watercolors

Conversation

@knap1930

@knap1930 knap1930 commented Jun 5, 2026

Copy link
Copy Markdown

Summary

Completes iAqualink direct control for the LC_JANDYINFINATE (Jandy Infinite Water Colors) light type, replacing the previous stub that fell back to set_iaqualink_aux_state. All four command types are now wired up with correct packet byte layouts confirmed from hardware captures.

  • On/off: [6]=0xFF, [7]=0xFF (on) / [7]=0x00 (off) — dispatched via AQ_SET_IAQLINK_JANDYINFINATE_ONOFFset_iaqualink_light_brightness(0xFF/0x00)
  • Brightness: [6]=0xFF, [7]=percent (25=0x19, 50=0x32, 75=0x4b, 100=0x64) — set_iaqualink_light_brightness
  • Color mode: [6]=mode_index, [7]=0x64 (100% brightness) — set_iaqualink_light_colormode; mode index maps directly to _color_light_options[LC_JANDYINFINATE]
  • RGB: [6]=R, [7]=G, [8]=B, using devID+2 (e.g. lightID=0x61 → RGB device=0x63) — set_iaqualink_light_rgb

Config required: virtual_button_XX_lightID = 61 (hex) in aqualinkd.conf so iAqalnkDevID can resolve the virtual button's device code.

Changes

  • iaqualink.c/h — four set_iaqualink_light_* functions with correct byte layouts; iAqalnkDevID extended to return lightID for LC_JANDYINFINATE virtual buttons
  • aq_programmer.h/c — new AQ_SET_IAQLINK_JANDYINFINATE_ONOFF program type; all iAQL light types wired into dispatch, ptypeName, programtypeDisplayName, and old-programmer error stubs
  • aq_panel.cprogramDeviceLightMode and programDeviceLightBrightness use aq_programmer for all LC_JANDYINFINATE paths (no more direct iaqualink calls from panel layer)
  • aqualink.hLIGHT_RGB action type
  • aq_mqtt.hLIGHT_RGB_TOPIC
  • net_services.c — subscribe to /rgb/set; parse "r,g,b" payload
  • mqtt_discovery.c — RGB-capable light discovery for LC_JANDYINFINATE
  • color_lights.c — added "Custom" (0x10) mode for LC_JANDYINFINATE

Test plan

  • virtual_button_XX_lightID = 61 set in config
  • On/off toggle turns light on ([7]=0xFF) and off ([7]=0x00)
  • Brightness slider sends correct percent byte (25%=0x19 … 100%=0x64)
  • Color mode selector sends correct index byte with [7]=0x64
  • RGB publish to <button>/rgb/set with "r,g,b" payload sends to devID+2
  • Existing brightness/color controls unaffected

🤖 Generated with Claude Code

knap1930 and others added 30 commits June 3, 2026 13:00
…ite Water Colors light

When enable_iaqualink=yes, LC_JANDYINFINATE now uses direct iAqualink commands
instead of the slower AqualinkTouch UI-navigation path:

- Color mode: new AQ_SET_IAQLINK_LIGHTCOLOR_MODE dispatches via set_iaqualink_light_colormode
- Brightness: new AQ_SET_IAQLINK_LIGHT_BRIGHTNESS dispatches via set_iaqualink_light_brightness;
  value=0 turns the light off via set_iaqualink_aux_state; LC_JANDYINFINATE is now allowed
  through the brightness gate in programDeviceLightBrightness
- RGB: new AQ_SET_IAQLINK_LIGHT_RGB dispatches via set_iaqualink_light_rgb (R/G/B packed as
  (r<<16)|(g<<8)|b); MQTT /rgb/set topic accepts "r,g,b" payloads; pre-parsed in
  action_mqtt_message before passing to action_URI

MQTT discovery updated: LC_JANDYINFINATE now emits a brightness+RGB light entity
(HASSIO_DIMMER_RGB_DISCOVER) instead of a plain switch, alongside the existing
color-preset selector entity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…olors light

- Add brightness, red, green, blue fields to clight_detail
- devices_jandy.c: store brightness from 0x31 response byte[38] matched by lightID
- aq_panel.c: store RGB optimistically on send (hardware response bytes for RGB still TBD)
- net_services.c: publish brightness to /brightness and RGB to /rgb when values change

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…NATE

- LOG_NOTICE marker in set_iaqualink_light_brightness so you can correlate
  when the command fires vs. subsequent iAqualink status packet changes
- In CMD_IAQ_AUX_STATUS handler: when a device entry matches an LC_JANDYINFINATE
  light label, dump the full raw entry bytes (+ 8 bytes after the label) at
  NOTICE level so we can see if brightness/RGB data trails each entry
- Catch-all else branch logs any unknown iAqualink status packet command byte

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bit4 (packet[status+3]) in CMD_IAQ_AUX_STATUS carries the current brightness
(0-100) for LC_JANDYINFINATE lights. Store it in light->brightness and set
is_dirty so net_services.c publishes the updated value to MQTT.

Confirmed by hardware capture: 40% → 0x28, 90% → 0x5a.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ode byte

Status+0 (bit1) is 0x06 for this light type rather than the expected 0x01 for
on — it may carry the color mode. Logging all four bytes at NOTICE so the user
can capture logs while cycling through color presets to identify the field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cket

Confirmed by hardware capture:
  packet[status+0] = color mode index (0=off, 1=Alpine White, 2=Sky Blue, …)
  packet[status+3] = brightness percent (0-100)

Store both in clight_detail and set is_dirty to trigger MQTT publish.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rom 0x72 packet

Hardware capture confirms:
  packet[status+0] = color mode index reported by the device
  0x10 (16) = custom RGB color — device reports this when RGB command was used

Add "Custom" at index 16 in the LC_JANDYINFINATE color array so the
MQTT /program/name topic and HA color mode selector show "Custom" instead
of an empty string when in custom RGB mode.

RGB channel values (R/G/B) are NOT reported in the 0x72 status packet;
the optimistic state stored at send time is the correct approach.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…B mode

Helps locate where (if anywhere) the device reports back R/G/B values.
Set a distinctive color, capture the log, and grep for the known byte values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
logPacket exits early unless log level >= LOG_DEBUG_SERIAL; replace with
a manual 16-bytes-per-row hex dump using LOG(IAQL_LOG, LOG_NOTICE) so the
full packet appears in the notice log regardless of serial debug settings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RGB channel values are not reported back in the 0x72 status packet —
confirmed by capturing full packet dumps across three different custom
colors (red, green, blue) which produced identical packet bytes.
Optimistic state (store at send time) is the correct approach.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… mode is encoded there

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pool Light is an aux device and doesn't appear in the 0x71 (1Touch) packet,
so LED state was never being updated. packet[status+0]==0 means off, non-zero
means on — update button->led->state from the 0x72 handler alongside the
existing color mode and brightness updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three bugs fixed:
1. Remove hardcoded 'value = 3' debug leftover that overrode every color
   mode command to Cobalt Blue regardless of what was requested
2. Add iAqualink turn-on path for virtual button LC_JANDYINFINATE:
   USE_LAST_VALUE now uses AQ_SET_IAQLINK_LIGHTCOLOR_MODE with lastValue
   instead of falling through to the error "needs AqualinkTouch protocol"
3. Add iAqualink turn-off path for virtual button LC_JANDYINFINATE:
   value==0 now calls set_iaqualink_aux_state(button, false) instead of
   falling through to the same error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CMD_IAQ_PAGE_BUTTON (0x24) packets written to master (dest 0x00) are the
iAqualink command sends. Bypass the LOG_DEBUG_SERIAL early-exit guard and
log them at NOTICE so they're visible without enabling serial debug logging.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two-part fix:
1. aq_serial.c send_packet: bypass the LOG_DEBUG_SERIAL gate so logPacketWrite
   is called for CMD_IAQ_PAGE_BUTTON (0x24) packets destined for master (0x00),
   which are the iAqualnk sendCmd packets
2. packetLogger.c _logPacket: bypass the early-exit and log at NOTICE level
   for the same packet type, so they appear in the normal notice log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LOG_LARGEMSG silently drops messages if msg_level > getLogLevel(from).
RSSD_LOG may be configured below NOTICE. Switch to IAQL_LOG for the
iAqualnk sendCmd case so the level check passes against the right channel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…htness save

1. aqualinkd.c: add LOG(IAQL_LOG, LOG_NOTICE) directly in the IAQUALNK
   case when a full (>2 byte) command is sent via send_jandy_command.
   This is a more reliable path than going through the packet-logger
   infrastructure (send_packet -> logPacketWrite -> _logPacket).

2. devices_jandy.c: restore the brightness update loop in
   processPacketFromJandyLight that was removed during RGB investigation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bypass the logging infrastructure entirely to confirm:
1. push_iaqualink_cmd: is the queue being populated when commands are sent?
2. caculate_ack_packet IAQUALNK case: is the dispatch case being reached, and what size does get_iaqualink_cmd return?

These should appear on stderr regardless of log level configuration.
Remove once the root cause is identified.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reverts the five commits that attempted to surface CMD_IAQ_PAGE_BUTTON
(0x24) packets at NOTICE level. None of the approaches worked reliably:
  056952d - packetLogger.c initial NOTICE changes
  f567753 - aq_serial.c gate bypass
  c14733b - packetLogger.c IAQL_LOG channel fix
  b46d2c0 - direct LOG in aqualinkd.c IAQUALNK dispatch
  f8bc4b9 - fprintf diagnostics

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LC_JANDYINFINATE lights are virtual buttons, which get rssd_code=NUL by
default. iAqalnkDevID returns 0xFF for virtual buttons with NUL rssd_code
because NUL doesn't match any RS_SA_* case in the switch.

Fix: track the entry index in the 0x72 AUX status packet loop. The bytes
at packet[5 + entryIndex] are the IAQ device codes for each entry (same
layout as the 0x71 1TOUCH_STATUS packet). On first match, store the code
in button->rssd_code. iAqalnkDevID already returns rssd_code directly for
virtual buttons with a non-NUL rssd_code (line 119-121).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…x72)

The full 0x72 hex dump was added to hunt for RGB values in the packet.
That investigation concluded RGB is not reported back. The dump code was
left in and is causing crashes when in custom RGB mode (new_mode == 0x10).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…vice code

iAqalnkDevID now checks clight_detail->lightID for virtual PROGRAM_LIGHT
buttons with lightType LC_JANDYINFINATE, so the device code comes from
the user-configured `virtual_button_XX_lightID` rather than returning
0xFF (which caused "Couldn't find iaqualink keycode" errors).

Also adds set_iaqualink_jandyinfinate_onoff() which sends the correct
command bytes for on/off ([6]=0xFF, [7]=0xFF/0x00) based on captured
packets, replacing the two set_iaqualink_aux_state() calls in aq_panel.c
that sent the wrong byte pattern for this light type.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
USE_LAST_VALUE path was calling AQ_SET_IAQLINK_LIGHTCOLOR_MODE with
value=1, producing _fullcmd[6]=0x01 which doesn't match the panel's
expected on/off command bytes ([6]=0xFF, [7]=0xFF for on).
Now calls set_iaqualink_jandyinfinate_onoff(button, true) directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Log the isON flag and _fullcmd bytes in set_iaqualink_jandyinfinate_onoff,
and log entry into the value==0 branch in programDeviceLightMode, to
diagnose why the OFF command sends ON bytes (0xff|0xff) instead of (0xff|0x00).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Brightness packets: [6]=0xFF (constant), [7]=percent (25=0x19, 50=0x32,
75=0x4b, 100=0x64). Was incorrectly writing value into [6] and leaving [7]=0.
Also clear [7] after push to match on/off cleanup pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Color mode packets: [6]=mode_index, [7]=0x64 (100% brightness).
Was setting [6]=value but leaving [7]=0. The mode index maps directly
to the _color_light_options array (Caribbean Blue=4, Spring Green=5, etc).
Also clear [7] after push.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RGB commands use devID+2 (0x61 light → 0x63 RGB sub-device). R/G/B
values at [6]/[7]/[8] were already correct from captures (red≈FF,21,0F;
green≈00,FF,10; blue≈00,2C,FF).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
knap1930 and others added 5 commits June 5, 2026 09:12
Add AQ_SET_IAQLINK_JANDYINFINATE_ONOFF program type and replace three
direct calls to set_iaqualink_jandyinfinate_onoff() in aq_panel.c with
aq_programmer(). Consistent with how brightness, color mode, and RGB are
dispatched. Wire up ptypeName, programtypeDisplayName, and old-programmer
error stub to match the pattern of the other iAQLink light types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ght_onoff

Move it next to the other light functions (colormode/brightness/rgb) in
iaqualink.c and update declaration in iaqualink.h. Rename to match the
consistent set_iaqualink_light_* naming pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both produce identical packets ([6]=0xFF, [7]=0x00), so the value==0
special-case in programDeviceLightBrightness is redundant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ON/OFF are just special brightness values on the wire ([7]=0xFF=on,
[7]=0x00=off). AQ_SET_IAQLINK_JANDYINFINATE_ONOFF now dispatches
directly through set_iaqualink_light_brightness.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread source/iaqualink.c
return;
}

// RGB command uses devID+2 (e.g. lightID=0x61 → RGB devID=0x63)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I question if this is right or the best way to do this...

@sfeakes

sfeakes commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Thanks for this. Sorry it’s taken me so long to get to but there is a lot of changes and to some core areas of the code so needed to spend a lot of time going over it to make sure it doesn’t break other things. The lights have a few bugs in Jandy firmware so there is a lot of mess in the code to overcome those bugs. There are a new things I still need to validate but will be merging it soon.

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.

2 participants