A Home Assistant custom integration for the xTool S1 laser engraver. Control your laser, monitor jobs in real time, and manage saved jobs, all from your phone without the XCS desktop app.
Disclaimer: This is an independent community project and is not affiliated with xTool. Operating a laser cutter is inherently dangerous. Use at your own risk.
- Real-time monitoring via WebSocket push (status, position, alarm)
- Job control: Stop, Pause and Resume buttons that work from HA
- Job management: save jobs from the laser, re-start them later with a confirmation dialog showing material, thickness and laser module
- Fill light control: dimmable light entity with standby detection
- XCS coexistence: works alongside the xTool Creative Space desktop app
- Graceful offline: a powered-off laser is normal, not an error
- Open HACS > Custom repositories
- Add
https://github.com/hilman2/ha-xtool-s1, type Integration - Install, then restart Home Assistant
- Settings > Devices & Services > Add Integration > xTool S1
- Run a network scan or enter the laser's IP manually
Copy custom_components/xtool_s1/ into your HA custom_components/ folder
and restart Home Assistant.
The config flow offers two ways to find the laser:
- Network scan: enter a CIDR range (e.g.
192.168.1.0/24) and the integration sends a UDP discovery broadcast on port 20000. Every S1 on the network replies with its IP and name. - Manual IP: enter the laser's IP address or hostname directly.
The integration connects via WebSocket (port 8081) and verifies the device by reading its serial number. No credentials are required.
Settings > Devices & Services > xTool S1 > Delete
This removes the integration, all entities, and the device. Saved jobs (from the job management card) are retained in HA storage and can be cleaned up manually if needed.
All entities are created automatically per device.
| Name | Description |
|---|---|
| Status | Machine state: idle, ready, measuring, running, paused, ... |
| Last job outcome | idle / running / paused / completed / aborted |
| Installed tool | Detected laser head (e.g. Diode 40 W) |
| Working time | Total working hours (lifetime) |
| Session count | Total number of job starts (lifetime) |
| Job file | Currently loaded job filename |
| Light brightness | Fill light percentage (0 when standby) |
Diagnostic sensors (hidden by default)
| Name | Description |
|---|---|
| Firmware version | Main board firmware |
| Serial number | Device serial |
| Tool type, Tool power, Tool capabilities | Raw tool metadata |
| Tool working time | Accumulated working seconds of the current tool type |
| Tool offset X / Y | Physical mounting offset |
| Position X / Y / Z, Probe Z | Head coordinates |
| Standby time, Auxiliary firmware 1/2, Tool firmware | Additional diagnostics |
| Name | Description |
|---|---|
| Running | A job is actively executing |
| Paused | The current job is paused |
| Alarm | Machine reports an alarm (e.g. lid open) |
| Job armed | Job preloaded, waiting for physical Start button |
| Last job aborted | Previous job ended via Stop (sticky) |
| Connection | WebSocket is connected (diagnostic) |
| Type | Name | Description |
|---|---|---|
| Light | Fill light | Dimmable interior light, detects standby auto-off |
| Button | Stop | Abort the running job |
| Button | Pause | Pause the running job |
| Button | Resume | Resume a paused job |
| Button | Create debug export | Write the current raw protocol ring buffer to a downloadable JSON file (diagnostic) |
The integration can save jobs from the laser, store them in Home Assistant, and re-run them later. This turns the xTool S1 into a standalone batch machine: prepare a job once in XCS, save it, then repeat it as many times as you want from your phone. No computer needed after the initial setup.
- Design and run your job in XCS as usual
- Open the xTool S1 Jobs card in your HA dashboard
- Tap Save job
- Fill in the form:
- Title: a short name (e.g. "Phone stand cutout")
- Description: notes for your future self (e.g. "Birch 3 mm, vector cut")
- Material: the material you used (e.g. "Birch plywood")
- Thickness: material thickness in mm
- Tap Save
The integration downloads the gcode directly from the laser's SD card and stores it together with the metadata you entered. The currently installed laser module (e.g. Diode 40 W) is detected and saved automatically.
- Open the xTool S1 Jobs card
- Find the job in the list and tap Start
- A confirmation dialog appears showing material, thickness and laser module. Verify that the correct material is loaded and the right laser head is installed.
- Tap Confirm & Start
- The integration uploads the gcode to the laser and triggers the start sequence
- Press the physical Start button on the device to begin
If the laser module has changed since the job was saved (e.g. you saved with the 40 W diode but the 2 W IR head is now installed), the integration blocks the start with a clear error message.
Cutting 20 identical phone stands from plywood:
- Design the cut in XCS, run it once, save it in HA
- Remove the finished piece, load new material
- On your phone: tap Start on the saved job, confirm, press the button on the laser
- Repeat steps 2-3 for all 20 pieces
No computer involved after the initial save.
The card auto-registers on installation. Add it to any dashboard:
type: custom:xtool-s1-jobs-cardThe card uses these services under the hood. You can also call them from automations or scripts:
| Service | Description |
|---|---|
xtool_s1.save_job |
Download and store the current job |
xtool_s1.start_job |
Upload a saved job and trigger the start sequence |
xtool_s1.delete_job |
Remove a saved job |
xtool_s1.list_jobs |
List all saved jobs with metadata |
The S1 firmware can kick WebSocket clients when the xTool Creative Space desktop app is active. This integration handles it gracefully:
| Mode | Trigger | Behaviour |
|---|---|---|
| Normal | WebSocket healthy | Real-time push updates |
| Coexist | 3+ WS kicks in 30 s | HTTP heartbeat only; sensors show cached values; light and buttons keep working |
| Offline | Device powered off | All dynamic values reset to off/zero; info sensors keep last known values; no errors in HA |
All write operations (light, stop, pause, resume, job upload) go through HTTP and are unaffected by WebSocket issues.
Tip: If XCS is connected via USB instead of WiFi, the WebSocket runs without interruption.
| Source | Interval | Content |
|---|---|---|
| WebSocket push (port 8081) | Real-time | Status, position, alarm, job file, M22/M323 state |
| Coordinator watchdog poll | 30 s | Reconnects dead sockets, sends M303 ping |
| M2008 stats request | 5 min | Lifetime working time, session count, standby time |
Logfile tail (/gcode/logs.txt) |
10 min | Per-tool working times; truncates file if > 1 MB |
- Job start requires physical button: after uploading and triggering a job via HA, the user must press the Start button on the device. This is a firmware safety feature.
- XCS over WiFi causes WebSocket kicks: the XCS desktop app polls aggressively and the S1 firmware kicks competing clients. The integration detects this and switches to coexist mode automatically. Use XCS over USB to avoid this entirely.
- No HACS store icon: HACS does not yet support local brand assets for custom integrations. The icon shows correctly in HA's own integration and device pages.
- SD card unavailable during jobs: the logfile and gcode download endpoints return errors while a job is running. The integration caches the last known values.
- Single device per entry: each config entry represents one laser. Add multiple entries for multiple devices.
| Symptom | Cause | Fix |
|---|---|---|
| All sensors show 0 / off | Laser is powered off | Normal, turn on the laser |
| Sensors flicker or go stale | XCS app is open over WiFi | Close XCS or connect it via USB |
| "Connection" sensor stays off | Wrong IP or laser not on network | Reconfigure with correct IP |
| Job card shows no jobs | No jobs saved yet | Save a job after running one in XCS |
| Start button does nothing | Laser not connected via WebSocket | Check connection sensor; restart integration if needed |
For protocol-level troubleshooting, use the device button
Create debug export. It writes a JSON snapshot under Home
Assistant's /local/xtool_s1_debug/ directory and opens a persistent
notification with a direct download link.
Each export contains:
- The current coordinator mode and parsed device state
- The last 250 raw HTTP / WebSocket frames from an in-memory ring buffer
- Binary WebSocket payloads as hex so firmware quirks stay visible
The ring buffer rotates automatically in memory, so normal operation does not spam the Home Assistant log.
trigger:
- platform: state
entity_id: binary_sensor.xtool_s1_running
from: "on"
to: "off"
action:
- action: notify.mobile_app
data:
title: xTool S1
message: Job finished.trigger:
- platform: state
entity_id: binary_sensor.xtool_s1_running
action:
- action: "switch.turn_{{ 'on' if trigger.to_state.state == 'on' else 'off' }}"
target:
entity_id: switch.workshop_exhaust_fantrigger:
- platform: state
entity_id: binary_sensor.xtool_s1_alarm
to: "on"
action:
- action: notify.persistent_notification
data:
title: xTool S1 alarm
message: Lid open or safety stop triggered../scripts/test.sh # 266 tests, parallel, 100% coverage gate
./scripts/lint.sh # black + ruffCI runs on every push: black, ruff, hassfest, HACS validation, pytest. Tagged releases re-run CI before publishing.