ESPHome + KL8300 (KL8-3H-FACE3) — Sniffing UART to retrieve water temperature / spa data

Hi Marco,

your topside panel is a KL8-3H-FACE3, exactly the same as mine. Since it’s the panel that carries the LCD bus I sniff (the TM1668 3-wire bus: DIO/WR/CS), the reading side — water temperature, setpoint, and the states of pumps, heater, blower, LED, filtration and panel on/off — should work as-is on your setup. That’s the bulk of what you’d see in the app.

For the commands,the 8-pin KEY connector is standard across the KL8-3H range, so my board plugs in the same way physically. But which function responds to which K×P intersection can differ between your CB16 board and my HV83G. So commands are very likely to work, but the mapping may need a one-time adjustment on your board.
yet, could you send me:

  1. A clear photo of the panel/keypad (to confirm it’s a FACE3 like mine).
  2. Photos of the two connectors on the CB16 board: the 6-pin LCD one and the 8-pin KEY one (to confirm they’re the same JST-XH type).
  3. An overall photo of the board + the exact reference printed on it.
  4. Your spa’s actual functions: how many pumps, blower yes/no, ozonator, LED type.

mapping the matrix onto my model connector 2 KEY :

Hello,

This project is finally complete and operational. As a reminder, the KL8300 spa can be fully controlled via Home Assistant:

  • control of pumps 1 and 2
  • heating
  • filtration
  • LED lighting
  • air blower
  • target temperature setting
  • display of actual and target water temperatures

The unit connects between the control panel and the circuit board using the original connectors—no soldering, no cutting of cables, and the modification is reversible.

Automation for filtration, heating, notifications, etc.

How it looks in Home Assistant:

I also took the opportunity to create a small application that allows you to control the spa locally via a web page on the network:

Hi Francki85,

I have completed my ESP32 interface board for my Beata spa, which uses the KL8-3H-CB16 controller and the KL8-3H-FACE3 display.

I found some interesting differences compared with your setup that might be useful to others.

1. Different keyboard mapping

I disconnected the keyboard and measured the connections for each button. My keyboard uses an 8-pin connector, with the following mapping:

Function KEY pins
System ON/OFF 1–6
Water pump 1 3–6
Water pump 2 4–6
Water pump 3 2–6
Air pump 2–8
Underwater light 3–8
Cleaning 4–8
Preheating 1–8
Temperature 2–5
MEM / Keyboard lock 1–7

I have attached the complete keyboard mapping, including the radio functions.

2. The buttons are not simple dry contacts

With the keyboard disconnected and unpowered, I measured approximately 1 V in diode-test mode across the buttons, with conduction in only one direction. This suggests that the keyboard uses diodes in series with the switches, probably to prevent ghosting when multiple buttons are pressed simultaneously.

My interface uses eight PhotoMOS contacts without additional diodes, so I intend to prevent simultaneous commands in software.

3. A question about the MEM button

My keyboard automatically locks after a period of inactivity, displaying a padlock symbol. Unlocking requires pressing MEM twice.

I noticed that MEM does not appear among the commands in your project. How did you handle this?

Does your controller accept simulated button presses even when the keyboard is locked, or did you find another way to unlock it?

For now, I have reserved one of my eight PhotoMOS channels for MEM, sacrificing the cleaning function.

I have completed the static testing of my interface board and will test it on the spa shortly.

Thanks again for sharing your project. It has been very helpful!

Hi Marco,

Thanks a lot for the very detailed feedback.
Your message was really useful; it made me completely rethink my setup.

  1. Our matrices: very similar, but not identical

Pins My spa Your spa
1–6 On/Off On/Off
3–6 Pump 1 Pump 1
4–6 Pump 2 Pump 2
3–8 Light Light
4–8 Filtration Cleaning
1–8 Blower Preheat
2–8 Heater Air pump
1–5 Temp + Volume +
2–5 - Temp
3–7 / 4–7 Clock / CD TUNE− / Radio
5–6 Radio -

Five buttons are identical. Blower and heater are swapped, and the temperature control is in a different spot. Conclusion: each model has its own button map, which led me to the new version of my board.

  1. MEM and locking
    On my spa, the keypad never locks, so I never needed to handle MEM. That’s a difference between our models—likely due to the KL8-3H firmware.

  2. Diodes
    Good catch! Since your PhotoMOS relays are in parallel with the buttons and you only press one button at a time, the keypad diodes don’t cause any issues. However, don’t add diodes in series with your injection circuit; in my case, they broke the functionality because the keypad bus is used for both scanning and reading.

  3. Two lessons from my v1, useful for your board
    No button should be pressed when the spa powers up. In my setup, my switches were briefly closed while the ESP32 was booting up. The controller was detecting multiple keys pressed at startup and getting stuck in an abnormal mode (both pumps would activate simultaneously). Only a prolonged power cut at the circuit breaker would reset it. I fixed this by adding a 100 kΩ pull-up resistor per channel, which forces the switches to remain open during startup. For your PhotoMOS relays, I would add a 100 kΩ resistor to ground on each control line so the LEDs remain off by default; this avoids issues with GPIO pins that change state during boot (pins 0, 2, 5, 12, 15).
    Regarding the injection line: I used a 680 Ω series resistor per channel.
    No need for 230 V anymore; I now power the circuit using the 5 V supply provided by the spa’s control board via the display ribbon cable. It works perfectly without a mains plug or a separate 230 V power supply. I recommend adding a resettable fuse to this power line to protect the spa’s control board in the event of a short circuit in the module. The setup becomes plug-and-play (DB15 connector).

  4. Thanks to you, I’m creating a much simpler and more universal v3.
    Your idea of ​​using 8 PhotoMOS relays got me thinking. Instead of one switch per key, you only need one switch per pin: 8 pins, 8 switches, all connected on one side to a common point. To press keys 1 and 7, you simply close the switch for pin 1 and the switch for pin 7; this makes all 16 combinations possible, regardless of the keypad model.

I took the concept a step further by using two CD74HCT4051E analog multiplexers. The first selects one pin, the second the other, and their shared outputs are connected via a single 470 Ω resistor.
This results in two circuits instead of three—saving you three CD4066s, nine 2N2222s, and around thirty resistors!
It is impossible to press more than one key at a time, as each multiplexer selects only one pin.
Startup safety is handled by the circuit itself: the 4051s’ enable input is pulled high, so everything remains open until the firmware boots up.
The key mapping is handled in software: one configuration line per model, with no hardware changes required.
For your current board, I would keep the 8 PhotoMOS relays but wire them individually from each pin to a common point, using a single 680 Ω resistor at that common point (not one per pin; otherwise, the signal would pass through two resistors). And never close more than two relays at once.

Thanks again—this kind of exchange really moves the project forward!

Hi Francki85,

I have now installed and successfully tested my interface on the spa. All the keyboard commands are working correctly.

I would like to share a few hardware details which may be useful if you are planning a new PCB revision.

For the keyboard interface I used 4 × Panasonic AQW282EH120 dual PhotoMOS relays, providing 8 isolated bidirectional contacts. Each PhotoMOS LED is driven from the ESP32 through a 390 Ω series resistor.

For the display bus (DIO, WR and CS) I used an SN74LVC4245A level translator, with the Beata side at 5 V and the ESP32 side at 3.3 V. I have already verified that all three signals are correctly received by the ESP32.

The most important change compared with my original design is the way I connected the 8 PhotoMOS outputs.

Instead of assigning one PhotoMOS to each button, I connected them as two 4-way stars:

KEY1 -- PhotoMOS1 --\
KEY2 -- PhotoMOS2 ---\
KEY3 -- PhotoMOS3 ----+---- common centre
KEY4 -- PhotoMOS4 ---/
                      |
KEY5 -- PhotoMOS5 ---\
KEY6 -- PhotoMOS6 ----\
KEY7 -- PhotoMOS7 -----+---- same common centre
KEY8 -- PhotoMOS8 ----/

In other words, all eight PhotoMOS contacts have one side connected to the common star point, while their other sides go individually to KEY1…KEY8.

To simulate any keyboard button, the ESP32 simply closes one PhotoMOS from KEY1–KEY4 and one from KEY5–KEY8 simultaneously. I keep them closed for about 500 ms and then open both.

This gives access to the complete 4 × 4 matrix, i.e. all 16 buttons, using only 8 PhotoMOS channels.

I think this arrangement could be particularly useful for a future PCB because the hardware no longer needs to know the actual button mapping. As long as the controller uses the same 8-pin 4×4 keyboard matrix, the PCB remains the same and the mapping between functions and KEY pairs can be changed entirely in software. This should make the board compatible with different controller versions using this connector arrangement.

I can also confirm that, like you, I successfully powered the complete ESP32 interface directly from the spa/display connector. So far it is working without any power-related problems.

One other difference: I did not need the 100 kΩ pull-down/pull-up resistors you suggested for preventing unwanted commands during ESP32 reset/start-up. I have not observed any false key presses.

I suspect this may be because of the different switching device. With the CD4066, during ESP32 reset its control input can be left floating/high-impedance, potentially allowing an undefined switch state. With the PhotoMOS solution, an undefined/high-impedance ESP32 GPIO does not provide the LED current required to turn the relay on, so the contact naturally remains open. Each PhotoMOS input LED in my circuit has a 390 Ω series resistor.

Finally, I have now tested the display interface as well. DIO, WR and CS are all active and correctly reach the ESP32 through the SN74LVC4245A, so my next step is to integrate your display decoding code rather than reverse-engineering the protocol again.

Thanks again for sharing your work. The project is now working very well on my CB16 controller.

Hi Francky85,

I have now completed and tested the ESP32 interface on my KL8-3H-CB16 board. The 4×4 keyboard emulation using the 8 PhotoMOS in the dual-star configuration works correctly: all 16 keys can be controlled from Home Assistant.

I have now started working on the display interface. I connected:

DIO → GPIO16
WR → GPIO17
CS → GPIO18

through the SN74LVC4245A level translator.

The signals are definitely present and the ESP32 can capture them. With an interrupt-based sniffer I consistently receive 17-bit frames, currently alternating between:

A5 F4 01
A5 F6 01

However, when I unlock the keyboard (the lock symbol disappears from the display) or switch the underwater light on, I see no change in these captured frames. So I am clearly not yet decoding/capturing the complete display data.

I remember that you had already decoded the display protocol in your project. Unfortunately the GitHub repository/link I found for your code now returns a 404.

Would you be able to share your latest ESP32/ESPHome source code for decoding the KL8 display (or a working link to the repository)?

I can then adapt only the GPIO assignments and hardware-specific parts to my CB16 interface. It would save us from reverse-engineering again something that you have already solved.

Thanks again!

Hi Marco,

It’s great that the 16 keys are controlled via HA—that’s excellent! And you’re right about the 100 kΩ resistors; with PhotoMOS relays, a floating pin can’t trigger the LED. The issue was specific to the CD4066 setup (using spare parts I had lying around :slightly_smiling_face:) in my V1.

The good news is that we’re using the same protocol. Your A5 F4 / A5 F6 corresponds exactly to my A5 7C / A5 7E; this represents writing to position p5 of the display RAM, where bit 0x02 toggles every 500 ms (the flashing separator).

I suspect that 17th bit is a capture artifact, or perhaps you’re counting one extra edge (likely the final WR edge after CS goes high, or the CS edge itself). If you don’t see the LED or the padlock icon change, it’s probably because those updates occur in bursts mixing 1-byte and 2-byte transactions—which your analyzer might miss if it’s expecting fixed-length frames.

A transaction encompasses everything that happens between the CS line going low and then high again. I only read DIO when CS is low, and I reset everything on every CS edge.
DIO is read on the rising edge of WR, assembled LSB first. I also read DIO on the falling edge, which is essential for distinguishing between a 3 and a 9 in the ones digit.
1 byte = command (39 at the start of a burst, 89 at the end, plus 09, A1, C1, 01, etc.).
2 bytes = write operation (address followed by data). Address decoding (bit-reversed): 05=p0, 85=p1, 45=p2, C5=p3, 25=p4, A5=p5, 65=p6, E5=p7, 15=p8, 95=p9, 55=p10, D5=p11, 35=p12, B5=p13, 75=p14, F5=p15.

What works for me to identify the bits is keeping a copy of the p0–p15 RAM and logging every change. I turn each piece of equipment on and off from the panel and note which position and bit change. You can do the same for the locked/unlocked padlock state.
On my spa, it’s:
heater = p0 bit 0x10, blower = p8, panel = p12, pumps = p13, pump 3 = p14 bit 7, LED = p7 (read on the falling edge). Since our panels are identical, the settings might be the same for you.

The displayed number: tens in p4, units in p3. Tables (rising edge masked by 0xFC):

Tens: 7C=0, 0C=1, F8=2, BC=3, 8C=4
Units: 7C=0, 0C=1, F8=2, BC=3 or 9, 8C=4, B4=5, F4=6, 1C=7, FC=8. For BC, check the byte read on the falling edge: 5F = 3, DF = 9.

As for the repository, I’ve closed it. But here are the essentials of the protocol so you can move forward.

Francki

Hi all, the github repo is down. is there a reason for it?

Hello,

Yes, I’ve closed it: the project has evolved significantly since then (new circuit board, completely overhauled firmware, direct power supply from the spa, compatibility extended to other control panels), and the published version was no longer representative.

I’ll post updates here when the new version is ready.