- The controller is a P25B85 with a PB554 control panel.
- The HA entity displays "Jets Off" and a status of "Ozone or Heating."
- When I switch on the jets via HA, it displays "Low" in HA. No further changes are possible.
- It is possible to toggle the jets using the control panel; however, I have not yet found a way to do this via HA.
- A recording will follow in the coming days.
@old-man Is this my integration? There is an extra entity for controlling the jets but it's listed under climate (because it is actually a fan entity):
You can switch between low and high via the presets:
I'm not sure if this is the best approach though. It sounded cool at first, but a simple dropdown might be the better way. What do you think?
Please also note there is a thermostat entity for controlling the water temperature ![]()
@alexbde Quick feedback on the jets entity, since you asked.
The cleanest HA pattern for a 2-speed jet pump is the fan entity, not
climate. A climate entity is meant for setpoint-based devices
(heating, cooling), so when HA Voice / Google / Alexa receive a "turn
on the jets" intent, they cannot map it naturally to a climate
preset. A fan entity gives you preset_modes ["off", "low", "high"] or
even a percentage attribute (0 / 50 / 100), and the matching Lovelace
fan-card has the right icon, the right speed slider, and works out of
the box with voice.
A select entity is fine for a pure UI dropdown but loses the voice
integration and the semantic meaning (fan icon, speed control,
fan_modes service).
So my suggestion in order:
- fan entity with preset_modes ["off", "low", "high"]
- select entity if you want to stay minimal
- climate is the least suitable, even though it works visually
The thermostat entity for the water temperature is exactly right
under climate, no change needed there.
Whatever you pick, the frame mapping you already have is the hard
part, the entity type is a one-liner change in the platform setup.
@old-man Thanks for the detailed report. Your description matches the
limitation Alex flagged just above: the jets entity in his current
P25B85 integration uses a climate preset structure, which does not
expose the high speed cleanly. A capture from your setup will be very
useful to map the missing command frame and unblock him.
When you get a chance to record, the most useful capture protocol is
this:
- Make sure the spa is in a stable state. Filtration on, no other
change. - Start the RS485 sniffer (nc / dd from your bridge or whatever
logger you use). - Wait 10 seconds with everything idle. This gives you a clean
reference broadcast frame. - On the PB554 panel, switch jets from Off to Low. Wait 10 seconds.
- On the PB554, switch jets from Low to High. Wait 10 seconds.
- On the PB554, switch jets from High back to Off. Wait 10 seconds.
- Stop the capture.
Total around 90 seconds. The 10-second gaps make it much easier to
isolate the command frame from the heartbeat traffic.
The easiest way to share the result is via the new frame analyzer:
Drop the .bin (or xxd .txt) on the page. The Contribute card is opt-in
checked by default and sends the capture anonymously to the
ha-joyonway-multi project. Fill in declared_model = P25B85 and
declared_panel = PB554 in the form. That goes directly into the
shared protocol mapping work that benefits Alex's integration.
Looking forward to the file.
V4.2 of the Joyonway Frame Analyzer is now live
Standalone single-file tool. 100 percent client-side, no backend, no
tracking, no external CDN. Drop a capture on the page and get the
decoded frames in seconds.
What is new
V4.1
- Multi-format upload. Auto-detect binary vs text. Accepts .bin (raw
bytes from nc | dd), .txt, .log, .cap, .hex (xxd output, raw hex,
hex with spaces / commas / 0x prefix, # comment lines ignored). - Contribute card. Opt-in checkbox checked by default. Sends an
anonymous capture to the ha-joyonway-multi project to help build
the unified multi-model integration. No personal data, no IP, just
the raw RS485 bytes.
V4.2
- EN / FR language switch. Floating top-right cyan pill. Default FR.
Persistent across sessions via localStorage.
Confirmed model support in the analyzer (decode only)
- P23B32 (KDy validation, reference)
- P20B29 (Yannickt26, same protocol as P23B32 per KDy)
Partial mapping, captures welcome via the Contribute card
- P25B85 (Alex, in collaboration, see jets capture protocol above)
- P69B133 (Gaet78 reference, mfo38 testing)
- P25B37, P68B123 (no public mapping yet)
Companion HA integration (P23B32 only for now)
Mid-term goal: ha-joyonway-multi, a unified integration that
auto-detects the controller model. Captures sent via the Contribute
card feed the protocol mapping work.
Thanks to KDy, Gaet78, Neuro, Alex, Yannickt26, mfo38, old-man for
the captures, oscilloscope analyses, hardware testing, and feedback.
Bug reports and captures from untested models very welcome.
I did a bit more testing today. This time, I didn't activate any "filter and/or heating program." The jets can only be switched as indicated on the control panel: "low, high, off."
Hello,
I was experiencing communication losses with the W610, requiring removing the power.
I switched to a ZLAN5143D module, and now I have no more problems.
My communication is stable, and it hasn't changed anything in the script.
How do I turn on the "Heater" via HA without using a heating program? The switch on the dashboard keeps reverting to its original state—even when I set the jets to "low."
I have generated the data for the jets and configured it in the Joyonway Frame Analyzer.
A few follow-ups in one shot.
@old-man on the jets isolated test
Good, that isolates the bug clearly: even without filter or heating
program active, the HA-side jets entity cannot drive High at all, the
control panel remains the only working path for low / high / off.
That matches the climate-preset limitation Alex described. The
capture you generated and dropped in the Frame Analyzer is exactly
what we need. Once Alex extends the mapping with the level-2 command
frame, the fan-entity (or select) replacement will expose High
properly in HA.
@Yannickt26 on the ZLAN5143D replacement
Thanks for sharing this, very useful for everyone hitting the W610
freeze pattern. Quick question before I add it to the community
notes: the W610 has both a WiFi mode and an Ethernet RJ45 port. Was
there a reason you were not using the wired RJ45 in the first place?
If your W610 was running over WiFi, the move to ZLAN5143D may have
solved a wireless stability issue rather than a W610 hardware or
firmware limitation, which would change the recommendation for other
users.
If you can also send a screenshot of your ZLAN5143D config page when
you have a moment, that would be perfect for the wiki.
@old-man on the manual Heater question
The HA toggle reverting to its previous state is the classic symptom
of a read-back entity: the integration does not have a dedicated
"heater on manual" command frame mapped, so the heater state in HA
is derived from the 0xB4 broadcast byte that the controller emits.
When you click the switch, HA tries to send something, the spa
controller does not act (or rejects the partial command), and the
next broadcast says "still off", which makes the toggle snap back.
On the reference P23B32 protocol there is no isolated "heater on /
off" command frame either. The heater is driven by the controller
based on the thermostat setpoint vs the current water temperature.
The reliable way to force heating manually is to raise the setpoint
2 to 4 degrees above current water temperature. The controller then
starts the heater on its own, you see it in the next broadcast, and
HA reflects it.
KDy pointed out the other day that the PB555 manual exposes a hidden
"Thermostat manuel" mode (and an "Ozone manuel" mode) reachable via
the SET menu, which actually emits dedicated A1 frames. If your
PB554 has the same hidden menus, capturing those frames would unlock
a real manual heater switch in HA. Worth checking your panel
documentation while you wait for the jets High mapping.
@alexbde related follow-up on entity design: if the P25B85 has no
dedicated manual heater command frame either, the heater switch in
HA might be better exposed as a climate setpoint shortcut, for
example preset "warm boost" = setpoint +3 C, "comfort" = setpoint
nominal, rather than a switch that cannot actually actuate. Same philosophy as the jets: match the entity type to what the controller actually accepts.
Hi everyone,
Thanks for the feedback and the ongoing testing!
Quick UI Clarification (The "Climate" confusion)
@old-man @christopheknap First, a quick clarification on the entity type: my jets pump is already a native HA fan entity (which is why you saw the presets dropdown in the screenshot).
The confusion came from me saying it was "listed under climate." In my German Home Assistant setup, the dashboard automatically grouped the fan entity under the UI category "Raumklima", which translates to "room climate" or "indoor climate." So it is a proper fan entity, but the UI categorized it there. My apologies for the translation confusion!
Update for @old-man
Regarding the jets issues: I have also observed some flaky behavior and state-tracking issues with the fan entity on my physical hardware. I am looking into this next to make sure the commands for low speed, high speed, and off actuate reliably and don't trigger things like the light by accident. I'll keep you posted!
@christopheknap Manual Heating Switch & States
Regarding the manual heater switch design: on the P25B85, the manual heater switch in HA is actually fully functional and behaves exactly like the "heater" button on the spa display panel.
Through extensive testing, I have decoded the full lifecycle of the heater's state machine on the P25B85:
off: Idle, heater disabled entirely.standby: Heater manual mode enabled (armed), waiting for the water temperature to drop below setpoint before firing.circulation: Pre-heat or post-heat circulation (pump running, circle icon on panel).heating: Actively heating/drawing power (flame icon on panel).ozone: Running disinfection cycle.
Toggling the heater switch in HA successfully transitions the controller between off and standby (or heating if setpoint is met), mirroring the display panel button perfectly. I've tested this extensively and it works like a charm, so keeping it as a switch is indeed the correct choice for this model.
Updates on My Integration
I've made major structural updates to the integration over the last few days to focus on stability and bus safety:
- Migration to a New Repository:
I have moved the codebase to a clean, dedicated repository. If you are currently testing the old custom repository, please remove it from HACS and add the new one instead:https://github.com/alexbde/ha-joyonway. - Intent Queue:
The Home Assistant UI is capable of firing commands much faster than the spa controller's RS-485 bus can process them, which can cause bus contention or command collisions. I have implemented a coordinator-levelIntentQueuethat:- Coalesces rapid successive updates (e.g. editing multiple schedule slots within a 300ms window) into a single write.
- Paces all outgoing command frames with a 1.0-second cooldown.
- Rejects redundant command writes to prevent bus congestion.
- Live Verification Suite:
To take Home Assistant and Lovelace UI out of the equation for diagnostic testing, I wrote a comprehensive, standalone Python live verification script (tests/live/test_spa_controls.py). This allows direct validation of:- Basic controls (light, jets, blower).
- Complete schedule matrices (making sure enabled/disabled states write correctly).
- Clock drift detection & automatic synchronization.
- TCP socket drop resilience.
- Safe Cleanup: The script now captures the initial state of the spa before testing and restores everything (schedules, setpoints, switches) back to their original values when finished.
Once I have addressed the jet speed issue @old-man and I are seeing, I will put out a tag for a stable v1.0.0. Thanks again for all the captures and tests!
alexbde,
I tested the new version again today and didn't notice any errors. A small note about the heating statuses: circulation before the actual heating doesn't indicate anything, and what I called cooldown reports preparation for heating. I don't remember the exact messages, but it's the other way around. But of course, that doesn't change anything. I think I've tested everything, including time synchronization.
However, I do have some concerns about the stability of the integration. The response to switching is slow. Perhaps it takes that long to calculate the CRC – I don't know. Sometimes you still have to click the button several times to trigger the desired effect. I've written about this before. If someone wants to control devices from the integration, that's fine, but if they want to use the entities for automation, it's going to be a problem. I think it's related to the way the pre-made frames are sent. I've never shared the full version of my script here, but everything works instantly and always for me. If anyone is interested, I can paste my full script.
christopheknap, old-man
Manual heating works great for Alex and me. It might be a problem, but I haven't tested the heating mode. With ozone, if you set it to "AUTO," the manual ozone switch doesn't work. The same can happen with heating; you first need to set the heating mode to "MANUAL" on the control panel.
@KDy Thank you for the detailed testing and validation! <3
To answer your concern about the switching speed and whether CRC calculation is causing the delay: the CRC-32 calculation uses a pre-computed lookup table and takes less than a microsecond (sub-microsecond) in Python. So it's not the math holding things up.
In fact, if I run the standalone Python live verification script (tests/live/test_spa_controls.py), it calculates the CRC and transmits the command frames to the TCP socket instantly. The state changes apply in milliseconds.
The slowness we are seeing in Home Assistant is almost certainly related to the UI/integration layer. Of course, I want the automation and UI response to feel as crisp as possible. I will dig into the UI and state-update logic over the next few days to see where we can optimize the speed and release a new version.
Hello,
I've never used Wi-Fi; the spa is too far away and doesn't get a signal.
I was having problems with Ethernet, which is why I'm sharing the ZLAN5143D with you, as it solved my problems.
Maybe it can help others.
Alex,
I’ve been thinking about why this is running so slowly and unreliably. I’ve been looking through your code and found this:
max_attempts = 3
and then:
start_time = time.monotonic()
while time.monotonic() - start_time < 4.0:
So the programme sends a frame, waits for a status change for up to 4 seconds, and then, if there’s no status change, repeats this twice more, waiting up to 4 seconds each time? Am I reading this correctly?
That would explain the long response times and, on occasion, the failed attempts to switch on or off.
From what I can see, the frame is assembled and then sent to the TCP-RS485 bridge and on to the bus. So you need to hit a free slot on the bus within those three attempts.
I’ll suggest something else. I’ve already included this image in post 67.
The broadcast frame is marked in red. The empty frame sent to the panel by the controller is marked in green. The controller’s response if nothing is happening is marked in blue.
This response is always:
‘1A 01 20 08 3C AA 10 00 00 6B 73 E4 B9 1D’
I use this frame as a ‘synchronisation frame’:
SYNC_FRAME = bytes.fromhex(‘1A 01 20 08 3C AA 10 00 00 6B 73 E4 B9 1D’
You can, of course, use a different frame for synchronisation, but this one happens to be the same for everyone.
In other words, I read the input until I find the SYNC_FRAME, then introduce a small delay required by the protocol (time.sleep(0.03)), and then send the control frame. This allows me to hit an empty slot on the RS485 bus exactly. There are no collisions, no retransmissions are needed, but if you’ve already implemented them, you can leave them; they’ll simply do nothing. They work 100% for me.
Think about it.
Thanks for the guidance, I’ll test ASAP, upload the frames to the shiny new frame analyzer then report back.
fyi, since the PAC is powered by the SPA itself on AUX port of the controller, I cannot simply power it off so I’ll just unplug it from the CN24 splitter, should be equivalent, right ?
Thank you very much @KDy for your investigations and your idea! You are completely right, I was experiencing bus collisions because the commands were blindly fired against the bus. Your idea is brilliant, here you go: feat: implement RS485 bus collision mitigation via sync frame alignment by alexbde · Pull Request #18 · alexbde/ha-joyonway · GitHub
@old-man I also did a lot of testing around the jets btw. Thank you @christopheknap for the hint with the fan entity. I looked at the docs again and found a way better representation than the on/off toggle with the presets:
("Aus" = off, "Niedrig" = low, "Hoch" = high)
How beautiful is that?
I also tested a lot and all transitions are working for me now. Even the ones which need an intermediate step like low => off.
I'm doing a few last refactorings and testings in the next days. I'm aiming to release version 1.0.0 soon ![]()
@alexbde
The new "Jets" switch is working great for me as well.
Test:
The "Jets" indicator shows "Off" during the "Heating and/or Filter Program," while the "Status" indicator displays either "Ozone" or "Heating." If I switch on the jets—for instance, while the filter program is running—the "Status" switches from "Ozone" to "Circulation," and the "Jets" indicator switches to "Low" or "High." If I switch the jets off again, the "Jets" indicator reverts to "Off," and the "Status" reverts to "Ozone." The jets resume running automatically as long as a program is active.
The filter and heating programs are working for me, too. The only issue is manually toggling the heater via HA: With no program currently running, I raise the temperature by 4 degrees and switch the heater "On"—but after a few seconds, it reverts to its previous state, and the heater does not engage.
I have to give you guys a huge compliment once again: "You are doing a fantastic job!"
Update regarding heating:
When I manually switch the heater from "off" to "on" on the whirlpool's control panel, a new icon appears on the display. I increased the target temperature, and the heater can now be switched on and off via the button without needing a specific program.
In HA, the heater can now also be switched on and off independently of any program.
Great!
Everything's working perfectly. There's no lag at all anymore. Great
@alexbde
Updated to version 1.0.0. Great work! Everything I tested works excellently.







