ESPHome implementation to remotely control Sinilink XY-SA/ST series temperature controllers for Bambu Lab P1S/X1 Carbon 3D printers. The repository now supports both the ESP8285-based XY-WFPOW module and the Waveshare ESP32-C6-Zero from one shared codebase.
This project allows you to automatically manage a chamber heater, link it to print jobs, and control it through Home Assistant or the built-in web interface. It was inspired by the BambuSauna project.
- Supported Devices
- Features
- Filament Presets
- Repository Layout
- Requirements
- References
- Modbus Address Map
- Wiring Diagram and Installation Notes
- Setup
- Troubleshooting
- Known Issues
- Safety
- License
- Contributing
| Device | ESPHome Entry File | Notes |
|---|---|---|
| ESP8285 / XY-WFPOW | esphome/temp_controller_esp8285.yaml |
Original Sinilink Wi-Fi module, built-in status LED, no fan RPM monitoring |
| ESP32-C6-Zero | esphome/temp_controller_esp32.yaml |
External module retrofit, WS2812 status LED, fan RPM monitoring, Wi-Fi protocol sensor |
| Manual selector | esphome/temp_controller.yaml |
Main YAML with package toggles for device and temperature unit |
- ESPHome integration for Home Assistant
- Standalone web interface
- Automatic chamber temperature control based on print state and filament type
- Filament presets plus user-defined mode
- Emergency stop handling
- Over-temperature protection
- Runaway heating detection
- Sensor fault detection
- Modbus communication watchdog
- End-of-job shutoff
- Celsius and Fahrenheit builds
- OTA firmware updates
- RPM monitoring for 3-wire fans with emergency-stop interlock
- Wi-Fi 6 support with protocol diagnostic sensor via include/wifi_protocol_helper.h
- WS2812 RGB status LED (GPIO8) with state-based colors and effects:
| LED State | Color | Effect | Meaning |
|---|---|---|---|
| Emergency Stop | Red | Solid | System emergency stopped |
| WiFi Disconnected | Blue | Fast Pulse | Network connection lost |
| Over Temperature | Red | Strobe | Temperature >60°C warning |
| Heating Active | Orange | Slow Pulse | Heater is running |
| Normal/Idle | Green | Solid (dim) | Everything OK |
The LED updates automatically via state-change triggers (for example Wi-Fi connect/disconnect) plus periodic refresh, providing at-a-glance status without needing to check the web interface or Home Assistant.
When a print starts, the controller reads the active filament type from the Bambu Lab Home Assistant integration and automatically selects the matching chamber temperature preset. You can also select a preset manually (including for pre-heating before a print), or use User-defined mode to set your own thresholds. Selecting Off engages the emergency stop.
Each preset targets the temperature below with a ±1°C hysteresis band (for example, PLA at 22°C starts heating at 21°C and stops at 23°C) to prevent rapid relay cycling. The defaults are defined as substitutions in esphome/settings.yaml — adjust them there for your filament brands, printer, and enclosure.
| Preset | Default Target (°C) | Preset | Default Target (°C) |
|---|---|---|---|
| PLA | 22 | PA | 60 |
| TPU | 25 | PA6 | 80 |
| PETG | 35 | PA12 | 60 |
| PCTG | 35 | PA612 | 25 |
| ABS | 55 | PPA | 70 |
| ASA | 55 | PPS | 80 |
| PC | 80 | PP | 55 |
| PET | 50 | PE | 75 |
| PAHT | 55 |
esphome/settings.yamlcontains the shared substitutions, default build settings, and additional shared configuration options you may want to customize.esphome/packages/controller_shared.yamlcontains the shared controller logic, Modbus entities, automations, and safety behavior.esphome/packages/device_esp8285.yamlcontains ESP8285-only framework, GPIO, and hardware configuration.esphome/packages/device_esp32.yamlcontains ESP32-C6-Zero-only framework, GPIO, fan monitoring, LED logic, and helper include usage.esphome/packages/celsius.yamlandesphome/packages/fahrenheit.yamlcontain unit-specific number ranges and preset math.esphome/temp_controller.yamlis the main entrypoint if you want to switch device and unit by editing one file.include/contains the shared C++ helper headers used by the YAML lambdas (see include/README.md).assets/web/contains the custom web UI CSS and JavaScript assets used by ESPHome's web server.docs/images/contains README screenshots, wiring diagrams, and source artwork;docs/home-assistant-dashboard.mdhas a ready-made Lovelace dashboard.hardware/3d-models/contains printable enclosure and adapter.3mffiles (print in ASA or PC — see 3D Printed Parts).test/contains host-side unit tests for the helper headers (./test/run_tests.sh)..github/workflows/ci.ymlruns the unit tests, validates every device/unit combination, and compiles both device targets on each push.CHANGELOG.mdtracks notable changes for each tagged release.
- ESPHome installed locally (validated against ESPHome 2026.9.0 in CI — see .github/workflows/ci.yml; older releases may need YAML adjustments)
- Home Assistant with the Bambu Lab HA integration
- Bambu P1S / X1 Carbon 3D Printer
- Sinilink XY-SA10/SA30-W AC 110V-250V Temperature Controller (the SA10 is sufficient — its 10A relay rating is roughly five times the ~2A a 250W heater draws; the SA30's higher rating is unnecessary here)
- NOYITO AC 100V-264V to DC 24V 1A Power Supply Module (powers the 24V Fan only)
- AC 120/240V PTC Heater 200-250W (no need to be more powerful than this)
- (2) 3-Way WAGO Connectors
- 16-18Ga silicone wiring (Used: red and black wiring)
- Heat set inserts: (15) M3x4x5 + (1) M2x2.5x3.2
- Screws:
- (7) M3x5MM or 6MM button screws for covers
- (2) M3x25MM hex head screws for lower aux fan screws
- (4) M3x25MM hex head screws for 24V Fan
- (4) M3x4MM button screws for NOYITO AC to DC Power Supply Module
- (2) M3x8MM hex head screws to connect housing to printer bottom
- (4) M4x6MM or 8MM self-tapping screws to hold PTC in housing without fan
- (2) M4x12MM button screws to hold the PTC heater to the front cover
- (2) M4 self-locking nuts to connect the PTC heater to the front cover
- (1) M2x3MM machine screw to hold the wireless module to the housing
- (1) XT30 connector pair set (both ends) (so the chamber heater can be unplugged and removed from the printer)
- Heatshrink tubing (for XT30 connectors)
- Soldering equipment (depending on the installation method)
- Sinilink XY-WFPOW (ESP8285-based) wireless module
- 24V 4020 2-wire fan (Used: SUNON MF40202VX-1000U-A99 with 10.8CFM airflow)
- USB-to-TTL UART programmer (only needed for the initial flash; highly recommend FTDI-based programmers)
Print the enclosure and adapter parts in hardware/3d-models/ in ASA or PC only. The housing sits against a 200-250W PTC heater and a printer that may itself run a heated chamber — PLA and PETG will soften and deform at these temperatures.
- Waveshare ESP32-C6-Zero with 2 x 9-pin headers soldered
- 24V 4020 3-wire fan (Used: SUNON MF40202VX-1000U-G99 with 10.8CFM airflow)
- 1/4W 10K resistor (for 3.3V pull-up power for tach)
- JST MX 1.25mm 4-pin cable to connect ESP32 to Temperature Controller
- Assorted 2.54mm pitch housings and crimp pins to terminate JST MX 4-Pin cable
- USB-C-to-USB-C cable for the initial flash
- Sinilink XY-ST/SA Remote Thermostat Datasheet
- Sinilink XY-WT04 Modbus Communication Protocol
- ESPhome-Sinilink-XY-WFPOW
- Tasmota XY-WFPOW Template
| ADDRESS | TYPE | NAME | RANGE/VALUES | Read/Write | DESCRIPTION |
|---|---|---|---|---|---|
| 0x0000 | U_WORD | Controller Status | 0=Stopped, 1=Active | RW | Main relay/controller state |
| 0x0001 | U_WORD | Temp Sensor Status | 0=Connected, 1=Disc | RO | Temperature sensor connection status |
| 0x0002 | U_WORD | Delay Time Remaining | 0-999 seconds | RO | Countdown timer for delayed start |
| 0x0003 | S_WORD | Current Temperature | -400 to 1100 (*0.1) | RO | Current measured temperature |
| 0x0004 | U_WORD | Temperature Unit | 0=Celsius, 1=Fahrenheit | RW | Display and control temperature unit |
| 0x0005 | U_WORD | Controller Mode | 0=Heating, 1=Cooling | RO | Operating mode |
| 0x0006 | S_WORD | Low Temp Threshold | -400 to 850 (*0.1) | RW | Temperature to start heating/cooling |
| 0x0007 | S_WORD | High Temp Threshold | -400 to 850 (*0.1) | RW | Temperature to stop heating/cooling |
| 0x0008 | S_WORD | High Temp Alarm | -400 to 1100 (*0.1) | RW | High temperature alarm threshold |
| 0x0009 | S_WORD | Low Temp Alarm | -400 to 1100 (*0.1) | RW | Low temperature alarm threshold |
| 0x000A | U_WORD | Delay Start Time | 0-999 seconds | RW | Delay start duration setting |
| 0x000B | S_WORD | Temperature Offset | -100 to 100 (*0.1) | RW | Temperature calibration offset |
| 0x000C | BOOL | Alarm Active | 0=No, 1=Yes | RO | Temperature alarm status |
| 0x000D | BOOL | Alarm Sound | 0=Off, 1=On | RW | Enable/disable alarm beeper |
| 0x000E | BOOL | High Temp Alarm Enable | 0=Off, 1=On | RW | Enable high temperature alarm |
| 0x000F | BOOL | Low Temp Alarm Enable | 0=Off, 1=On | RW | Enable low temperature alarm |
| 0x0010 | BOOL | Delay Start Enable | 0=Off, 1=On | RW | Enable delayed start feature |
| 0x0011 | BOOL | Emergency Stop | 0=Off, 1=On | RW | Emergency stop/disable controller |
| 0x0012 | U_WORD | Modbus Address | 1-247 | RW | Modbus slave address |
| 0x0013 | U_WORD | Modbus Baudrate | 0-6 | RW | Serial communication speed |
| 0x0014 | BOOL | Sleep Mode | 0=Off, 1=On | RW | Display sleep/power saving mode |
| 0x0015 | U_WORD | Backlight Level | 0-7 | RW | Display backlight brightness |
Baudrate map for 0x0013: 0=9600, 1=14400, 2=19200, 3=38400, 4=56000, 5=57600, 6=115200.
Temperature Value Encoding:
- Stored as signed 16-bit integers representing temperature in tenths of degrees
- Actual temperature = register_value * 0.1
- Example: register value 235 = 23.5°C
Safety Notes:
- Always enable high temp alarm (0x000E) for safety
- Set high temp alarm (0x0008) above normal operating range
- Emergency stop (0x0011) immediately disables heating/cooling
Use the original Sinilink XY-WFPOW Wi-Fi module. The basic wiring diagram is shown below.
The ESP32-C6-Zero retrofit uses a separate module and adds fan RPM monitoring and RGB LED status indication.
Follow the official directions on the ESPHome website.
On macOS, the easiest path is Homebrew. If you don't already have Homebrew, install it first:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Then install ESPHome:
brew install esphomegit clone https://github.com/kedube/bambu-chamber-heater
cd bambu-chamber-heaterCopy and edit the secrets file:
cp esphome/secrets-example.yaml esphome/secrets.yamlImportant values include:
wifi_ssidwifi_passwordap_wifi_ssidap_wifi_passwordweb_server_usernameweb_server_passwordota_passwordencryption_keybambu_printer_id
The example file also contains commented-out keys (static_ip, gateway, subnet, dns1, dns2) for an optional static IP configuration — uncomment them (and the matching manual_ip: block in esphome/packages/controller_shared.yaml) if you need one.
Shared substitutions such as device_name, friendly_name, software version, and filament preset defaults are defined in esphome/settings.yaml.
Most runtime behavior, Modbus transport settings, and automations are defined in esphome/packages/controller_shared.yaml.
Use those files for shared configuration changes before editing device-specific packages.
Generate a valid 32-byte encryption key (see: ESPHome.io), and insert it under encryption_key.
If you want one editable selector file, use esphome/temp_controller.yaml and switch the package includes:
packages:
settings: !include settings.yaml
controller_shared: !include packages/controller_shared.yaml
select_units: !include
file: packages/celsius.yaml
#file: packages/fahrenheit.yaml
select_device: !include
file: packages/device_esp8285.yaml
#file: packages/device_esp32.yamlIf you prefer a fixed compile target, use one of these:
esphome/temp_controller_esp8285.yamlesphome/temp_controller_esp32.yaml
esphome config esphome/temp_controller.yamlExample:
esphome run esphome/temp_controller.yamlOnce ESPHome successfully compiles the YAML configuration, it will prompt you to flash the module. For the first flash, the device must be connected to your computer to upload the firmware.
For the ESP8285, use the USB-to-TTL adapter programmer at 3.3V and the XY-WFPOW flashing pins:
GND -> GND
TXD -> RXD
RXD -> TXD
IO0 -> GND
RST -> not connected
3V3 -> VCC
For the ESP32-C6-Zero, you must connect the board via USB-C and flash it directly. It will prompt you after a successful build for where to upload the code. Once running, the onboard WS2812 RGB LED shows live system status — see the LED state table under Features.
INFO Build Info: config_hash=0x694d2e36 build_time_str=2026-04-01 14:02:17 -0400
INFO Successfully compiled program.
Found multiple options for uploading, please choose one:
[1] /dev/cu.usbserial-8320 (USB Serial)
[2] Over The Air (esp32-remote.local)
(number):
After the initial flash, both device types can be updated over the air (OTA) using ESPHome.
A local web server starts on port 80 after installation. You can access it from a browser on your local network using the username and password configured in secrets.yaml.
The web UI's animated background is selectable in esphome/packages/controller_shared.yaml under web_server: → js_include. Three effects ship in assets/web/: pixel-stars.js (default), moving-particles.js, and shooting-stars.js — uncomment the one you want (only one at a time) and re-flash.
Once online, the device should be discovered by the ESPHome integration in Home Assistant. Use the same encryption_key configured in esphome/secrets.yaml.
For a ready-made dashboard layout (gauge, controls, and safety diagnostics), see docs/home-assistant-dashboard.md.
The device boots with Emergency Stop engaged. This is by design — the heater always starts disarmed. Select a filament preset (or clear the Emergency Stop switch) to begin heating.
Emergency Stop is on and selecting a preset doesn't clear it. When the stop was triggered by a fault — thermal runaway, a temperature alarm, a disconnected sensor, Modbus communication loss, a fan failure, the printer going offline, or Home Assistant disconnecting — presets intentionally cannot clear it (the log shows "Emergency stop remains active because it was triggered by a fault condition"). Fix the underlying issue, then manually turn off the Emergency Stop switch in the web UI or Home Assistant to acknowledge the fault. The Emergency Stop Time sensor (System Management group) shows how long ago it tripped, and the device logs record the cause on lines tagged EMERGENCY.
Heater won't turn on. Check, in order: the Filament Temp Preset isn't Off; Emergency Stop is off; Delay Start isn't holding the relay (watch Delay Time Remaining); and the current temperature isn't already above the High Temp Threshold. Note that when the printer reports an inactive state (finish, failed, offline, or idle), the end-of-job shutoff forces the preset back to Off — you can still pre-heat manually by selecting a preset, but a printer state change to inactive will turn it off again.
"No temperature updates" emergency stop (Modbus watchdog). The watchdog trips if no temperature reading arrives for 90 seconds. Verify the UART wiring (TX/RX not swapped), that the controller's baud rate matches the build default of 115200, and that the Modbus address is 1. Turn on the Modbus Debugging switch to log the raw hex frames and confirm whether the controller is responding.
Fan failure emergency stop, or Fan RPM reads 0 (ESP32-C6-Zero). A fan fault trips when RPM stays below 200 for 10 seconds while the heater is active. Confirm the fan is a 3-wire model with a tachometer lead, the tach line has its 10K pull-up to 3.3V, and the JST harness is seated. The thresholds (fan_min_rpm, fan_fail_timeout_seconds) are adjustable in esphome/settings.yaml.
Emergency stop whenever Home Assistant restarts or drops off the network. Losing the Home Assistant API connection (or the printer's _online sensor going off) is treated as a fail-safe condition because print-state automation is no longer available. This is expected; clear the stop once Home Assistant reconnects. Check the WIFI Signal Strength sensor if it happens frequently.
Can't reach the web UI. If the device can't join your Wi-Fi it starts a fallback access point (default SSID chamber-heater, configurable via ap_wifi_ssid) with a captive portal for reconfiguration. The web UI itself is on port 80 and uses the credentials from secrets.yaml.
The Restart button does nothing. Turn on the Restart Confirmation switch first, then press Restart within 10 seconds — this guards against accidental restarts mid-print.
- The Sinilink Modbus addresses for the sleep switch (
0x0014) and backlight level (0x0015) do not seem to have any effect on the backlight settings for XY-SA10/SA30 controllers. - Mixed-material prints will cause the chamber target temperature to fluctuate as the active filament changes.
The author assumes no liability for injury, damage, or loss resulting from wiring errors, improper installation, or misuse of this project. Electrical work can be hazardous. If you are unsure, consult a qualified professional.
This project is licensed under the GNU General Public License v3.0 or later — see LICENSE.
Contributions are welcome for bug fixes, documentation improvements, and new hardware support. Please include testing details when possible, and add user-visible changes to the ## Unreleased section of CHANGELOG.md — the release automation turns that section into the published release notes.
Releases are fully automated by .github/workflows/release.yml:
- Every push to
mainruns CI (unit tests, ESPHome validation for all device/unit combinations, and firmware compiles). - When CI passes, the Release workflow bumps the patch segment of
sw_versioninesphome/settings.yaml, rotates the CHANGELOG's## Unreleasedsection into the new version, and pushes achore(release): <version> [skip ci]commit. - A GitHub release is published for that version, with notes generated from the CHANGELOG section (Highlights), the commit list since the previous tag, and a full-changelog compare link.
To cut a minor or major release, set sw_version yourself in a normal commit (e.g. 1.9.0); because that version has no tag yet, the automation publishes it exactly as set, then resumes patch bumps. To re-publish a release for the current version (for example after a failed run), use the Release workflow's Run workflow button.















