This is a custom integration for Home Assistant that connects and monitors xTool laser engravers such as P2, F1, M1, and Apparel.
⚠️ This integration is an independent community project.
I am not affiliated with xTool or its employees — but I’d love to collaborate with the xTool team for further testing 😉.
- Native Home Assistant integration (no YAML required)
- Multiple devices supported — each xTool appears as its own device
- Automatic entity creation per device:
binary_sensor.<name>_<device_type>_power→ shows if the machine is reachable/onsensor.<name>_<device_type>_status→ shows the current working state- M1 adds extra sensors:
sensor.<name>_m1_cpu_tempsensor.<name>_m1_water_tempsensor.<name>_m1_purifier
- Typical status values:
Running,Done,Idle,Sleep,Ready,Unavailable,Unknown
- Add this repository as a Custom Repository in HACS.
- Search for “XTool” and install.
- Restart Home Assistant.
- Download or clone this repository.
- Copy the folder
xtoolinto yourconfig/custom_components/directory. - Restart Home Assistant.
- Go to Settings → Devices & Services → Add Integration.
- Search for “XTool”.
- Enter:
- Name → freely chosen (e.g.
Laser1) - IP Address → IP of your xTool device
- Device Type → choose between
P2,F1,M1, orApparel
- Name → freely chosen (e.g.
- Confirm — done ✅
Each device automatically creates the appropriate entities in Home Assistant based on its name and device_type.
Entity IDs are automatically generated using the Name and Device Type you provide during setup:
| Example | Entities Created |
|---|---|
Name: Laser1, Type: F1 |
binary_sensor.laser1_f1_powersensor.laser1_f1_status |
Name: Laser2, Type: P2 |
binary_sensor.laser2_p2_powersensor.laser2_p2_status |
Name: Studio, Type: M1 |
sensor.studio_m1_statussensor.studio_m1_cpu_tempsensor.studio_m1_water_tempsensor.studio_m1_purifier |
Name: Laser3, Type: S1 |
binary_sensor.laser3_s1_powerbinary_sensor.laser3_s1_runningbinary_sensor.laser3_s1_alarmsensor.laser3_s1_statussensor.laser3_s1_firmware_versionsensor.laser3_s1_job_filesensor.laser3_s1_position_xsensor.laser3_s1_position_ysensor.laser3_s1_fan_asensor.laser3_s1_fan_b |
S1 with AP2 Air Cleaner adds these additional entities (using the same name prefix):
| Entity | Description |
|---|---|
binary_sensor.laser3_s1_air_cleaner |
Air cleaner running state |
sensor.laser3_s1_air_cleaner_model |
Air cleaner model |
sensor.laser3_s1_air_cleaner_speed |
Fan speed |
sensor.laser3_s1_air_cleaner_sensor_d |
Particle sensor D reading |
sensor.laser3_s1_air_cleaner_sensor_s |
Particle sensor S reading |
sensor.laser3_s1_pre_filter_remaining |
Pre-filter life remaining (%) |
sensor.laser3_s1_medium_efficiency_filter_remaining |
Medium efficiency filter life remaining (%) |
sensor.laser3_s1_activated_carbon_filter_remaining |
Activated carbon filter life remaining (%) |
sensor.laser3_s1_ultra_dense_carbon_mesh_filter_remaining |
Ultra dense carbon mesh filter life remaining (%) |
sensor.laser3_s1_high_efficiency_filter_remaining |
High efficiency filter life remaining (%) |
| Status | Meaning |
|---|---|
Running |
The laser is currently engraving |
Done |
The engraving job is finished |
Idle |
The machine is idle |
Sleep |
The device is in sleep mode |
Ready |
(M1 only) machine ready for work |
Unavailable |
Device offline or unreachable |
Unknown |
Unknown or invalid response |
| Status | Meaning |
|---|---|
Ready |
Machine ready for work |
Measuring |
Running auto-focus or measurement pass |
Starting |
Job is initializing |
Running |
The laser is currently engraving |
Finishing |
Job is wrapping up |
Idle |
The machine is idle |
Unavailable |
Device offline or unreachable |
Unknown |
Unknown or invalid response |
alias: Laser1 – Exhaust Fan
description: Turn on the exhaust fan when Laser1 (F1) starts engraving
triggers:
- trigger: state
entity_id: sensor.laser1_f1_status
actions:
- choose:
- conditions:
- condition: state
entity_id: sensor.laser1_f1_status
state: Running
sequence:
- service: switch.turn_on
target:
entity_id: switch.exhaust_fan
default:
- service: switch.turn_off
target:
entity_id: switch.exhaust_fan
mode: singlealias: Laser1 – Job Finished
description: Send a mobile notification when the engraving is done
triggers:
- trigger: state
entity_id: sensor.laser1_f1_status
to: Done
actions:
- service: notify.mobile_app_my_phone
data:
title: "xTool Laser1 – Job Completed"
message: "Your engraving on the F1 is done ✅"
mode: singlealias: Blinds – Safe Close with Laser2 Check
description: Prevent blinds from closing if Laser2 (P2) is powered on
trigger:
- platform: state
entity_id: cover.living_room_blinds
to: closing
condition:
- condition: state
entity_id: binary_sensor.laser2_p2_power
state: "on"
action:
- service: cover.stop_cover
target:
entity_id: cover.living_room_blinds
- service: notify.mobile_app_my_phone
data:
message: "⚠️ Blinds closing blocked: Laser2 (P2) is currently powered ON."
mode: single# NOTE: Adjust entity ID prefixes to match your device name from integration setup.
# Check Settings -> Devices -> your S1 device to confirm exact entity IDs.
alias: xTool AP2 - Filter Replacement Warning
description: >
Persistent notification when any AP2 filter drops below 25% remaining.
Critical alert when below 15%.
trigger:
- platform: numeric_state
entity_id: sensor.laser3_s1_pre_filter_remaining
below: 25
id: pre_filter
variables:
filter_name: "Pre-filter"
- platform: numeric_state
entity_id: sensor.laser3_s1_medium_efficiency_filter_remaining
below: 25
id: medium_filter
variables:
filter_name: "Medium Efficiency Filter"
- platform: numeric_state
entity_id: sensor.laser3_s1_activated_carbon_filter_remaining
below: 25
id: carbon_filter
variables:
filter_name: "Activated Carbon Filter"
- platform: numeric_state
entity_id: sensor.laser3_s1_ultra_dense_carbon_mesh_filter_remaining
below: 25
id: dense_carbon_filter
variables:
filter_name: "Ultra Dense Carbon Mesh Filter"
- platform: numeric_state
entity_id: sensor.laser3_s1_high_efficiency_filter_remaining
below: 25
id: hepa_filter
variables:
filter_name: "High Efficiency Filter"
action:
- service: persistent_notification.create
data:
notification_id: "ap2_filter_{{ trigger.id }}"
title: >
{% if trigger.to_state.state | float < 15 %}
Critical: AP2 Filter Replacement Required
{% else %}
AP2 Filter Replacement Warning
{% endif %}
message: >
{% if trigger.to_state.state | float < 15 %}
Critical:
{% endif %}
{{ filter_name }} is at {{ trigger.to_state.state }}% remaining.
mode: parallel
max: 5alias: Laser1 – Audio Notification
description: Play a short audio clip when Laser1 (F1) completes a job
triggers:
- trigger: state
entity_id: sensor.laser1_f1_status
to: Done
actions:
- service: media_player.play_media
target:
entity_id: media_player.living_room_speaker
data:
media_content_id: "https://example.com/sounds/job_done.mp3"
media_content_type: "music"
mode: singleIf you enjoy my projects or find them useful, consider supporting me on Ko-fi!
If you work at xTool or are part of the development team — I’d love to collaborate for extended testing, new model support, or official API insights 😉 Just reach out!