Jandy AquaLink RS
Driver: jandy
Control Jandy AquaLink RS pool/spa controllers over their RS485 bus via a serial-to-Ethernet bridge (e.g. USR-TCP232). The driver speaks the raw #-command protocol, supports circuit on/off and heater setpoints, and monitors per-zone state, temperature, and setpoint. On connect it subscribes to asynchronous change pushes (#COSMSGS=1), so most updates arrive unsolicited rather than from polling.
Prerequisites
- A Jandy AquaLink RS system wired to a serial-to-Ethernet bridge on the RS485 bus.
- The bridge reachable on the LAN from the GEM server (typically TCP port 23).
Setup
- Connect the serial-to-Ethernet bridge to the Jandy AquaLink RS485 bus.
- In GEM, System → Devices → Add Device with driver
jandy. Enter the bridgeipandport. - Let the controller run for a minute so registers populate the device
discovered_addresseslist, then run thesync_zonescommand to auto-create one zone per register. - (Optional) For a composite zone whose state, temperature, and setpoint live at different registers, create it by hand and set
jandy_state_address/jandy_temperature_address/jandy_setpoint_addressinstead of relying on the zone address.
Addressing
A zone can be addressed two ways:
- Single register (auto-create path). Put the AquaLink register directly in the zone address field (e.g.
S01,POOLTMP,POOLHTR). The driver classifies it by value/name as on/off, temperature, or setpoint. This is whatsync_zonescreates — one register per zone. - Composite zone (hand-built). Set one or more of
jandy_state_address/jandy_temperature_address/jandy_setpoint_addressto route state, temperature, and setpoint to different registers. Each explicit attribute overrides just that one role; any role left unset falls back to the address field.
If a jandy_*_address attribute is set, that register wins for its role; otherwise the zone address is treated as the register.
sync_zones
sync_zones reads device.discovered_addresses and creates one zone per register, then:
- classifies each register as on/off (
water_onoffcontrol), setpoint (water_onoff_setpoint), or temperature (water_temperature); - places every register under the water subsystem — it does not split pool vs. spa or create subsystems. The
watersubsystem must already exist; any register is skipped with a warning if it doesn't; - is idempotent — registers already represented by an existing zone (by address or any
jandy_*_addressattribute) are skipped, never modified.
Run it any time after discovered_addresses has populated. If it returns "no discovered addresses yet", let the controller run longer so registers appear on the wire, then re-run.
Attributes
Device
| Attribute | Required | Default | Description |
|---|---|---|---|
ip | yes | — | Serial bridge IP. |
port | yes | — | Serial bridge TCP port. |
polling_rate | no | 3000 | Delay (ms) between individual register reads in the heartbeat sweep. RS485 is single-master — don't set below the panel's own poll cadence. |
heartbeat_interval | no | 60000 | How often (ms) to re-sweep configured zone registers as a sanity check against the COSMSGS push subscription. |
command_throttle | no | 1000 | Minimum spacing (ms) between commands. |
discovered_addresses | — (read-only) | — | Comma-separated registers seen on the wire. Auto-populated; the menu for hand-wiring zones and the source list sync_zones builds from. |
Zone
| Attribute | Description |
|---|---|
jandy_state_address | AquaLink register for on/off state (composite override). |
jandy_temperature_address | AquaLink register for temperature (composite override). |
jandy_setpoint_address | AquaLink register for the heater/temperature setpoint (composite override). |
Commands
| Command | Args | Effect |
|---|---|---|
get | address | Query the value at an AquaLink register. |
on | address | Turn on the circuit at the register (or the zone's state register). |
off | address | Turn off the circuit at the register (or the zone's state register). |
setpoint | address, setpoint | Set the heater/temperature setpoint (or the zone's setpoint register). |
set | address, value | Set an arbitrary register to a value. |
passthrough | command | Send a raw AquaLink command string. |
reset | — | Reset the AquaLink controller. |
sync_zones | — | Auto-create one zone per discovered register (idempotent — see above). |
Known behavior
- On every (re)connect the driver sends
#COSMSGS=1so the bridge pushes register changes asynchronously; most state updates come in unsolicited. A slow heartbeat sweep re-reads each configured register as a fallback in case the push subscription silently dies. - Temperatures are reported as
75 For23 C; the driver normalizes both and converts Celsius to Fahrenheit for storage. - on/off/setpoint commands schedule a delayed re-poll of the matching register as a belt-and-suspenders confirmation.
Troubleshooting
| Symptom | Check |
|---|---|
jandy error response in logs / no zone updates | Verify the register address — invalid AquaLink registers are echoed back with a leading ?. |
| Status updates lag minutes behind reality | Lower polling_rate (default 3000 ms) — but watch for bus contention with the AquaLink panel itself. |
| Setpoint command silently does nothing | Confirm a setpoint register resolves for the zone (its address classifies as a setpoint, or jandy_setpoint_address is set) and that the register supports writes — some are read-only. |
no discovered addresses yet from sync_zones | Let the controller run so registers appear in discovered_addresses, then run sync_zones again. |
sync_zones skips every register / creates no zones | Confirm a water subsystem exists — sync_zones places all Jandy zones under it and skips registers (logging could not resolve subsystem) when it's missing. |