This project provides an ESPHome-based RS485 integration for Tylö / Tylo and Helo sauna heaters (Pure, Combi, Elite). It allows Home Assistant to read temperatures, states, and sensor data, and send basic commands, while running in parallel to the original control panel.
This interface was developed for and validated with the Tylö Sense Pure (Pure panel). Basic functions also worked in our tests with Combi and Elite setups, but full compatibility and feature coverage are not guaranteed.
Unofficial project. See Disclaimer & Trademarks.
Last tested with:
- ESPHome: 2026.4
- Framework: ESP-IDF 5.5.4
- Target: ESP32-S3 (esp32-s3-devkitc-1)
Other ESPHome versions may work, but are not guaranteed to be fully compatible. Breaking changes in ESPHome or ESP-IDF may require adjustments to this project.
This project is actively maintained. Breaking changes will be addressed as they arise. Please open an issue if you encounter problems.
This project utilizes an ESP32-S3 (ESP32-S3-WROOM-1) paired with an external RS485-to-UART interface based on the MAX485.
The door sensor used in this project is an ABUS FU7350W.
The external switch with LED feedback used in this project can be found here.
![]() |
|---|
| Lolin S3 / ESP32-S3-DevKitC-1 (no termination) | AtomS3 Lite + Tail485 (termination 68 Ω) | Touch Display (JC3248W535EN) |
|---|---|---|
|
|
|
Important
Some RS485 auto-direction transceivers require a sufficiently low bus impedance to reliably switch into transmit mode.
If reading from the bus works, but writing fails, this is often a sign of insufficient bus termination, not a protocol or timing issue.
Tests have shown that certain auto-direction transceivers require a termination of < 80 Ω to reliably transmit. For example, AtomS3 Lite + Tail485 with 68 Ω termination works reliably, while boards without termination exhibit read-only behavior (TX fails).
| Pin | Signal | |
|---|---|---|
| 1 | A | ![]() |
| 2 | B | |
| 3 | 12V DC | |
| 4 | GND |
| Pos | Unit | Pins used | Comment | Pin 1 | Pin 2 | Pin 3 | Pin 4 |
|---|---|---|---|---|---|---|---|
| 1 - NTC | Temp. sensor in the room | 2, 3 | 10kΩ. May also be connected at Pos 4 - SEC/NTC. | - | 10kΩ | 10kΩ | - |
| 2 - EXT SWITCH | External switch | 3, 4 | Start/Stop. Momentary or latching switch | - | - | Switch | Switch |
| External switch with LED indication | 2, 3, 4 | Start/stop operation. 12VDC max. 40mA | - | LED GND | Switch | Switch / LED 12V | |
| 3 - DOOR SWITCH | Door contact (NO) | 3, 4 | - | - | Switch | Switch | |
| Door contact (NO) with LED indication | 2, 3, 4 | 12VDC max. 40mA. | - | LED GND | Switch | Switch / LED 12V | |
| 4 - SEC/NTC | Combined temperature sensor/cut-out in the room | 2, 3 | Only used for certain products. | Sec | 10kΩ | 10kΩ | Sec |
| 5-8 - RS485 | Control panels | 1, 2, 3, 4 | Tylö Pure control panel, ESP32 (RS485) | A | B | 12V | GND |
| Baud | Bits | Parity | Stop bit |
|---|---|---|---|
| 19200 | 8 | Even | 1 |
These details have been reverse-engineered by analyzing intercepted communication between a heater and its control panel. As such, the list may not be complete or fully accurate. Further testing and validation may be required to confirm all behaviors and formats.
| Start of Frame (SOF) | Address ID | Message Type / Origin | Data | CRC | End of Frame (EOF) |
|---|---|---|---|---|---|
0x98 |
0x40 |
0x06 |
0 Bytes | 2 Bytes | 0x9C |
0x07 |
|||||
Explanation:0x06: Sent by the heater as a Keep-Alive request.0x07: Sent by the control panel as a Keep-Alive acknowledgment.
|
|||||
| Start of Frame (SOF) | Address ID | Message Type / Origin | Code | Data | CRC | End of Frame (EOF) |
|---|---|---|---|---|---|---|
0x98 |
0x40 |
0x06 (Heater Request) |
2 Bytes | 0–4 Bytes | 2 Bytes | 0x9C |
0x07 (Panel Command) |
2 Bytes | |||||
0x08 (Heater Data) |
2 Bytes | |||||
0x09 (Panel Data / ACK?) |
2 Bytes |
| Code | Direction | Description |
|---|---|---|
0x3400 |
Heater → Panel | Light and Heater Status |
0x3801 |
Heater → Panel | Elite/Combi sensor frame (temp/RH data) |
0x4002 |
Both | Bath Time and Maximum Temperature |
0x4003 |
Both | Overheating PCB Limit |
0x4200 |
Both | Date/Time |
0x5200 |
Both | Aux relay |
0x5201 |
Both | Aux relay |
0x5202 |
Both | Aux relay |
0x6000 |
Both | Temperature Data: Current and target sauna temperature |
0x6001 |
Both | Humidity control |
0x7000 |
Panel → Heater | Request Heater Status Changes |
0x7180 |
Heater → Panel | Relay/output bitmap (physical outputs X3–X18) |
0x7280 |
Heater → Panel | Tank level |
0x9000 |
Both | Not-allowed start window |
0x9400 |
Heater → Panel | Total Uptime |
0x9401 |
Heater → Panel | Remaining Bath Time |
0xB000 |
Heater → Panel | Error Codes |
0xB600 |
Heater → Panel | Sensor Errors |
On each bus cycle the heater emits a heartbeat. Under normal conditions the panel follows ≈600 µs later. If the panel needs to issue a command, it skips that heartbeat and sends its command ≈1.6 ms after the heater heartbeat.
We transmit only after a panel EOF. To avoid collisions we wait at least one byte-time at 19 200 baud (8E1 ≈ 0.57 ms) plus a small guard on the PURE model, then start our frame (typically ~615 µs after the panel EOF).
COMBI / ELITE behave slightly differently (likely due to the optional RS-485 humidity/temperature sensor). For these models we transmit ≈7 100 µs after the panel EOF.
Measured timing to send 0x07 (Command) to heater via ESP32.
substitutions:
# — Device / Model -
device_name: tylo
model_name: pure # pure | combi | elite | combi_elite
branch_ref: main # main | dev
# — Area & Logging —
area_name: Sauna
log_level: INFO
# — Board / Flash (configurable) —
board_type: esp32-s3-devkitc-1 # esp32-s3-devkitc-1, lolin_s3
flash_size: 8MB # 4MB | 8MB | 16MB
rgb_led_pin: GPIO38 # devkitc-1 = GPIO38 | Atom S3 lite = GPIO35
led_brightness_connected: 30 # 0..100; 0 = OFF
# — UART pins (if wired differently) —
uart_tx_pin: GPIO02
uart_rx_pin: GPIO01
# — Defaults for bath/temperature —
default_bath_temperature: 94
default_bath_time: 240
default_max_bath_temperature: 110
# — Defaults for Humidity settings —
default_humidity_step: 0 # 0..10 - COMBI
default_humidity_percent: 0 # 0..100 - COMBI_ELITE
# — Climate visuals —
min_temp_c: 60
max_temp_c: 110
# — Secrets from secrets.yaml —
api_encryption_key: !secret api_key_tylo
ota_password: !secret ota_pass_tylo
wifi_ssid_sub: !secret wifi_ssid
wifi_password_sub: !secret wifi_password
# - LED -
led: ${led_brightness_connected != 0}
esphome:
name: ${device_name}
friendly_name: ${device_name}
comment: ${device_name} sauna controller
esp32:
board: ${board_type}
flash_size: ${flash_size}
framework:
type: esp-idf
external_components:
- source:
type: git
url: https://github.com/f-io/esphome-tylo
ref: ${branch_ref}
refresh: 1s
components: [ sauna360 ]
packages:
base:
url: https://github.com/f-io/esphome-tylo
ref: ${branch_ref}
refresh: 1s
files: ["packages/base.yaml"]
led:
url: https://github.com/f-io/esphome-tylo
ref: ${branch_ref}
refresh: 1s
files: ["packages/led/${led}.yaml"]
model:
url: https://github.com/f-io/esphome-tylo
ref: ${branch_ref}
refresh: 1s
files: ["models/${model_name}.yaml"]# Wi-Fi
wifi_ssid: "YOUR_WIFI_SSID"
wifi_password: "YOUR_WIFI_PASSWORD"
# API & OTA for Sauna
api_key_sauna: "REPLACE_WITH_BASE64_KEY"
ota_pass_sauna: "REPLACE_WITH_STRONG_PASSWORD"This project is independent of Sauna360, Tylö and Helo and is not affiliated with, endorsed by, or approved by them. All product names, logos, and brands are the property of their respective owners. Brand names are used solely to describe interoperability/compatibility (nominative use).
Reverse engineering: Protocol details were derived from observing legitimate communication between lawfully acquired devices for the purpose of interoperability. No proprietary firmware, manuals, or copyrighted materials are distributed.
Safety/Liability: Working with heaters and mains-powered equipment is hazardous. Use at your own risk. Usage may affect compliance and may void warranty. The author and contributors assume no liability for any damages or regulatory issues. Follow local electrical and fire-safety regulations. Regulatory requirements vary by country; verify compliance locally.







