Support for DEEBOT X8 PRO OMNI
Checks
- [x] I have searched the existing issues and no issue is describing my issue
- [x] I have checked the FAQ
- [x] I have checked the documentation
- [x] I have installed the latest version
The problem
Device "DEEBOT X8 PRO OMNI" not supported. More information at https://github.com/DeebotUniverse/client.py/issues/612: {'did': 'f56f062e-8100-44b8-a027-1166cfa6482f', 'name': 'E0C235557F1FPNV20270', 'class': 'n0vyif', 'resource': '4oLeIYS8', 'company': 'eco-ng', 'bindTs': 1739616918621, 'service': {'jmq': 'jmq-ngiot-eu.dc.ww.ecouser.net', 'mqs': 'api-ngiot.dc-eu.ww.ecouser.net'}, 'deviceName': 'DEEBOT X8 PRO OMNI', 'icon': 'https://portal-ww.ecouser.net/api/pim/file/get/66e3ac63a2928902a25d83a0', 'ota': True, 'UILogicId': 'keplerh_ww_h_keplerh5', 'materialNo': '110-2417-0402', 'pid': '66daaa789dd37cf146cb1d2e', 'product_category': 'DEEBOT', 'model': 'KEPLER_BLACK_AI_INT', 'updateInfo': {'needUpdate': False, 'changeLog': ''}, 'nick': 'Mon pote', 'homeId': '631a26505466082f01388b04', 'homeSort': 1, 'status': 1, 'btName': 'ECOVACS-n0vyif-0270', 'btMac': '28:F5:2B:6C:56:05', 'otaUpgrade': {}}
On which deebot device (vacuum) you have the issue?
DEEBOT X8 PRO OMNI
Which version of the deebot-client are you using?
HA 2025.2.5
Country
Fr
Continent
eu
Anything in the logs that might be useful for us?
Additional information
No response
Any testing we can do or more logs we can provide to get this vacuum supported?
Hello, I have the same vacuum robot and I am experiencing the same issue in Italy (not supported). Let me know if I can help in any way.
Same issue for me with the Ecovacs Deebot X8 Pro in Germany. Would be great if the flagship model would be supported. If I can help, feel free to contact me.
As I don't have the model, I cannot test and ensure it works correctly. Sometimes, Ecovacs changes the API from one model to the other one, and therefore, we cannot make assumptions.
You can search for a similar model in the code base and then try to determine whether that one works, but you may need to modify it slightly. Please be aware that Ecovacs it not sharing anything with this project and so it takes some time until new models are supported. Any contribution is welcome
My model is DEEBOT X8 PRO PLUS AI(In China). Is it similar to X5 Pro Omini (lr4qcs. py). If they are similar, may I ask how to create a symbolic link between the model and similar models, and how to operate it specifically? Please guide me, thank you!
@STRHOME i managed to link X5, X2 and many others to see in my dev box. While a lot of the sensors and functions worked the "Start Cleaning" button and start always threw up this error in the logs.
`Logger: deebot_client.commands.json.common Source: components/ecovacs/vacuum.py:341 First occurred: April 4, 2025 at 9:04:19 PM (2 occurrences) Last logged: April 4, 2025 at 9:04:21 PM
Command "clean" was not successfully. body={'code': 20003, 'msg': 'rcp not support'}`
@LeoDevop May I ask how you linked and do you have detailed steps? thank you!
Writing a python script using CleanV2 instructs the robovac to start cleaning but first copy p1jij8.py and rename as n0vyif.py within deebot_client/hardware/deebot/:
cp p1jij8.py n0vyif.py
import aiohttp
import asyncio
import logging
import time
from deebot_client.api_client import ApiClient
from deebot_client.authentication import Authenticator, create_rest_config
from deebot_client.commands.json.clean import CleanAction, CleanV2
from deebot_client.events import BatteryEvent
from deebot_client.mqtt_client import MqttClient, create_mqtt_config
from deebot_client.util import md5
from deebot_client.device import Device
device_id = md5(str(time.time()))
account_id = "your email or phonenumber (cn)"
password_hash = md5("yourPassword")
country = "DE"
async def main():
async with aiohttp.ClientSession() as session:
logging.basicConfig(level=logging.DEBUG)
rest_config = create_rest_config(session, device_id=device_id, alpha_2_country=country)
authenticator = Authenticator(rest_config, account_id, password_hash)
api_client = ApiClient(authenticator)
devices_ = await api_client.get_devices()
bot = Device(devices_.mqtt[0], authenticator)
mqtt_config = create_mqtt_config(device_id=device_id, country=country)
mqtt = MqttClient(mqtt_config, authenticator)
await bot.initialize(mqtt)
# Execute commands
await bot.execute_command(CleanV2(CleanAction.START))
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.create_task(main())
loop.run_forever()
So far, I've been able to find that the following commands work: Cleaning Mode:
await bot.execute_command(SetWorkMode(WorkMode.MOP_AFTER_VACUUM))
[<WorkMode.VACUUM_AND_MOP: 0>, <WorkMode.VACUUM: 1>, <WorkMode.MOP: 2>, <WorkMode.MOP_AFTER_VACUUM: 3>]
Suction Power:
await bot.execute_command(SetFanSpeed(FanSpeedLevel.MAX_PLUS))
[<FanSpeedLevel.QUIET: 1000>, <FanSpeedLevel.NORMAL: 0>, <FanSpeedLevel.MAX: 1>, <FanSpeedLevel.MAX_PLUS: 2>]
Cleaning Passes:
await bot.execute_command(SetCleanCount(1))
[<1>, <2>]
Start Cleaning:
await bot.execute_command(CleanV2(CleanAction.START))
Maybe someone with a big more Python knowledge can implement these:
Water Flow Rate: customAmount appears to be the set value
Topic: iot/atr/onWaterInfo/<did>/n0vyif/N77sTJy1/j
Payload:
{"body":{"data":{"customAmount":10,"enable":1,"mopCount":2,"sideMop":0,"sweepType":1,"type":1}},"header":{"fwVer":"1.101.0","hwVer":"0.1.1","pri":1,"ts":"1744734574192","tzm":480,"ver":"0.0.1","wkVer":"0.1.54"}}
Cleaning Speed:
Topic: iot/atr/onCustomAreaMode/<did>/n0vyif/N77sTJy1/j
Payload:
{"body":{"data":{"sweepMode":1}},"header":{"fwVer":"1.101.0","hwVer":"0.1.1","pri":1,"ts":"1744734272721","tzm":480,"ver":"0.0.1","wkVer":"0.1.54"}}
I've been able to implement room clean and scenario clean by adding the following to models.py under CleanMode (L83):
FREE_CLEAN = "freeClean"
SCENARIO_CLEAN = "qcClean"
await bot.execute_command(CleanAreaV2(CleanMode.FREE_CLEAN, "1,0"))
await bot.execute_command(CleanAreaV2(CleanMode.SCENARIO_CLEAN, "5638"))
If you add the below to mqtt_client.py under line 248, then when you make a request from your app. you can see the value for each room or scenario:
_LOGGER.info("FULL MQTT MESSAGE:\nTopic: %s\nPayload:\n%s", message.topic, message.payload.decode("utf-8", errors="replace"))
self._last_message_received_at = datetime.now()
For example, freeClean, 1.3 is to clean my Living Room:
{"body":{"data":{"act":"start","content":{"type":"freeClean","value":"1,3"}}},"header":{"channel":"Android","m":"request","pri":2,"reqid":"FhY7rH","ts":"1744735616838","tzc":"Europe/London","tzm":60,"ver":"0.0.22"}}
qcClean, 5395 is to start a scenario:
{"body":{"data":{"content":{"type":"qcClean","value":"5395"},"act":"start"}},"header":{"channel":"Android","m":"request","pri":2,"reqid":"8XEXbC","ts":"1744822549783","tzc":"Europe/London","tzm":60,"ver":"0.0.22"}}
Also added the below to models.py below line 73 to stop and return the robovac to the station:
STOP_AND_RETURN = "stop_and_return"
Will continue to update this as I discover more.
Note: I'm no expert in this - just crashing my way through!
Are there any news on this? I also cant wait for this model to be supported 👼
Hi, I have the Deebot X80 Omni (without PRO) I presume the integration might be quite similar to the pro model, so I don't think a separate ticket needed for this. I am looking really forward for this integration.
Has anyone seen this MCP protocol that uses an API? https://github.com/ecovacs-ai/ecovacs-mcp/blob/main/ecovacs_mcp/robot_mcp_stdio.py I got Claude to access info about my Omni X8 Pro from here and offer to control it. Can we leverage this? More info - https://open.ecovacs.com/#/serviceOverview I got an API key by logging into my ecovacs account
Hello, I have the same vacuum robot and I am experiencing the same issue in Italy (not supported).
I have Deebot X8 Omni PRO. I patched select.py and water-info.py to allow to change water level from 1 to 50.
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py -->import re
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py -->_LOGGER = logging.getLogger(__name__)
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py -->###################
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> # event_bus.notify(WaterAmountEvent(WaterAmount(int(data["amount"]))))
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> amount = data.get("amount")
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> custom_amount = data.get("customAmount")
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py -->
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> if amount is not None:
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> try:
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> event_bus.notify(WaterAmountEvent(WaterAmount(int(amount))))
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> except ValueError:
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> _LOGGER.warning("Unknown water amount value: %s", amount)
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> elif custom_amount is not None:
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> _LOGGER.info("Custom water amount received: %s", custom_amount)
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> event_bus.notify(WaterAmountEvent(int(custom_amount))) # You may define a separate event if needed
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> else:
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> _LOGGER.warning("No amount or customAmount in getWaterInfo payload: %s", data)
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py -->###################
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> # if isinstance(amount, str):
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> # amount = get_enum(WaterAmount, amount)
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> # params["amount"] = amount.value
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> if isinstance(amount, str):
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> match = re.match(r"level[_\s]*(\d+)", amount, re.IGNORECASE)
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> if match:
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> params["customAmount"] = int(match.group(1))
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> else:
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> # Fallback to enum name (e.g., "HIGH")
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> amount = get_enum(WaterAmount, amount)
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> params["amount"] = amount.value
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> elif isinstance(amount, int):
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> params["customAmount"] = amount
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> elif isinstance(amount, WaterAmount):
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> params["amount"] = amount.value
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> else:
# \usr\local\lib\python3.13\site-packages\deebot_client\commands\json\water_info.py --> raise ValueError(f"Unsupported water amount: {amount}")
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --># from deebot_client.events.water_info import WaterAmountEvent
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py -->from deebot_client.events.water_info import WaterAmount,WaterAmountEvent
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py -->###################
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py -->import logging
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py -->_LOGGER = logging.getLogger(__name__)
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py -->
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py -->def current_water_option(e):
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> if isinstance(e.value, WaterAmount):
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> return get_name_key(e.value)
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> try:
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> return f"Level {int(e.value)}"
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> except Exception as ex:
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> _LOGGER.warning("Failed to interpret custom water amount: %s", ex)
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> return None
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py -->
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py -->def get_water_options(water):
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> if getattr(water, "types", None):
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> opts = [get_name_key(amount) for amount in water.types]
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> else:
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> opts = [f"Level {i}" for i in range(1, 51)]
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> return opts
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py -->###################
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> # current_option_fn=lambda e: get_name_key(e.value),
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> # options_fn=lambda water: [get_name_key(amount) for amount in water.types],
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> current_option_fn=current_water_option,
# \usr\src\homeassistant\homeassistant\components\ecovacs\select.py --> options_fn=get_water_options,
This is instead my working n0vyif.py hardware files
from __future__ import annotations
from deebot_client.capabilities import (
Capabilities,
CapabilityClean,
CapabilityCleanAction,
CapabilityCustomCommand,
CapabilityEvent,
CapabilityExecute,
CapabilityExecuteTypes,
CapabilityLifeSpan,
CapabilityMap,
CapabilitySet,
CapabilitySetEnable,
CapabilitySettings,
CapabilitySetTypes,
CapabilityStation,
CapabilityStats,
CapabilityWater,
DeviceType,
)
from deebot_client.commands import StationAction
from deebot_client.commands.json import station_action
from deebot_client.commands.json.advanced_mode import GetAdvancedMode, SetAdvancedMode
from deebot_client.commands.json.auto_empty import GetAutoEmpty, SetAutoEmpty
from deebot_client.commands.json.battery import GetBattery
from deebot_client.commands.json.carpet import (
GetCarpetAutoFanBoost,
SetCarpetAutoFanBoost,
)
from deebot_client.commands.json.charge import Charge
from deebot_client.commands.json.charge_state import GetChargeState
from deebot_client.commands.json.child_lock import GetChildLock, SetChildLock
from deebot_client.commands.json.clean import (
CleanArea,
CleanV2,
GetCleanInfoV2,
)
from deebot_client.commands.json.clean_count import GetCleanCount, SetCleanCount
from deebot_client.commands.json.clean_logs import GetCleanLogs
from deebot_client.commands.json.clean_preference import (
GetCleanPreference,
SetCleanPreference,
)
from deebot_client.commands.json.continuous_cleaning import (
GetContinuousCleaning,
SetContinuousCleaning,
)
from deebot_client.commands.json.custom import CustomCommand
from deebot_client.commands.json.efficiency import GetEfficiencyMode, SetEfficiencyMode
from deebot_client.commands.json.error import GetError
from deebot_client.commands.json.fan_speed import GetFanSpeed, SetFanSpeed
from deebot_client.commands.json.life_span import GetLifeSpan, ResetLifeSpan
from deebot_client.commands.json.map import (
GetCachedMapInfo,
GetMajorMap,
GetMapTrace,
GetMinorMap,
)
from deebot_client.commands.json.multimap_state import (
GetMultimapState,
SetMultimapState,
)
from deebot_client.commands.json.network import GetNetInfo
from deebot_client.commands.json.ota import GetOta, SetOta
from deebot_client.commands.json.play_sound import PlaySound
from deebot_client.commands.json.pos import GetPos
from deebot_client.commands.json.relocation import SetRelocationState
from deebot_client.commands.json.station_state import GetStationState
from deebot_client.commands.json.stats import GetStats, GetTotalStats
from deebot_client.commands.json.sweep_mode import GetSweepMode, SetSweepMode
from deebot_client.commands.json.true_detect import GetTrueDetect, SetTrueDetect
from deebot_client.commands.json.voice_assistant_state import (
GetVoiceAssistantState,
SetVoiceAssistantState,
)
from deebot_client.commands.json.volume import GetVolume, SetVolume
from deebot_client.commands.json.water_info import GetWaterInfo, SetWaterInfo, WaterAmountEvent
from deebot_client.commands.json.work_mode import GetWorkMode, SetWorkMode
from deebot_client.const import DataType
from deebot_client.events import (
AdvancedModeEvent,
AvailabilityEvent,
BatteryEvent,
CachedMapInfoEvent,
CarpetAutoFanBoostEvent,
ChildLockEvent,
CleanCountEvent,
CleanLogEvent,
CleanPreferenceEvent,
ContinuousCleaningEvent,
CustomCommandEvent,
EfficiencyModeEvent,
ErrorEvent,
FanSpeedEvent,
FanSpeedLevel,
LifeSpan,
LifeSpanEvent,
MajorMapEvent,
MapChangedEvent,
MapTraceEvent,
MultimapStateEvent,
NetworkInfoEvent,
OtaEvent,
PositionsEvent,
ReportStatsEvent,
RoomsEvent,
StateEvent,
StationEvent,
StatsEvent,
SweepModeEvent,
TotalStatsEvent,
TrueDetectEvent,
VoiceAssistantStateEvent,
VolumeEvent,
water_info,
WorkMode,
WorkModeEvent,
auto_empty,
)
from deebot_client.events.auto_empty import AutoEmptyEvent
from deebot_client.events.efficiency_mode import EfficiencyMode
from deebot_client.models import StaticDeviceInfo
from deebot_client.util import short_name
from . import DEVICES
DEVICES[short_name(__name__)] = StaticDeviceInfo(
DataType.JSON,
Capabilities(
device_type=DeviceType.VACUUM,
availability=CapabilityEvent(
AvailabilityEvent, [GetBattery(is_available_check=True)]
),
battery=CapabilityEvent(BatteryEvent, [GetBattery()]),
charge=CapabilityExecute(Charge),
clean=CapabilityClean(
action=CapabilityCleanAction(command=CleanV2, area=CleanArea),
continuous=CapabilitySetEnable(
ContinuousCleaningEvent,
[GetContinuousCleaning()],
SetContinuousCleaning,
),
count=CapabilitySet(CleanCountEvent, [GetCleanCount()], SetCleanCount),
log=CapabilityEvent(CleanLogEvent, [GetCleanLogs()]),
preference=CapabilitySetEnable(
CleanPreferenceEvent, [GetCleanPreference()], SetCleanPreference
),
work_mode=CapabilitySetTypes(
event=WorkModeEvent,
get=[GetWorkMode()],
set=SetWorkMode,
types=(
WorkMode.MOP,
WorkMode.MOP_AFTER_VACUUM,
WorkMode.VACUUM,
WorkMode.VACUUM_AND_MOP,
),
),
),
custom=CapabilityCustomCommand(
event=CustomCommandEvent, get=[], set=CustomCommand
),
error=CapabilityEvent(ErrorEvent, [GetError()]),
fan_speed=CapabilitySetTypes(
event=FanSpeedEvent,
get=[GetFanSpeed()],
set=SetFanSpeed,
types=(
FanSpeedLevel.QUIET,
FanSpeedLevel.NORMAL,
FanSpeedLevel.MAX,
FanSpeedLevel.MAX_PLUS,
),
),
life_span=CapabilityLifeSpan(
types=(
LifeSpan.BRUSH,
LifeSpan.FILTER,
LifeSpan.HAND_FILTER,
LifeSpan.SIDE_BRUSH,
LifeSpan.UNIT_CARE,
),
event=LifeSpanEvent,
get=[
GetLifeSpan(
[
LifeSpan.BRUSH,
LifeSpan.FILTER,
LifeSpan.HAND_FILTER,
LifeSpan.SIDE_BRUSH,
LifeSpan.UNIT_CARE,
]
)
],
reset=ResetLifeSpan,
),
map=CapabilityMap(
cached_info=CapabilityEvent(
CachedMapInfoEvent, [GetCachedMapInfo(version=2)]
),
changed=CapabilityEvent(MapChangedEvent, []),
major=CapabilityEvent(MajorMapEvent, [GetMajorMap()]),
minor=CapabilityExecute(GetMinorMap),
multi_state=CapabilitySetEnable(
MultimapStateEvent, [GetMultimapState()], SetMultimapState
),
position=CapabilityEvent(PositionsEvent, [GetPos()]),
relocation=CapabilityExecute(SetRelocationState),
rooms=CapabilityEvent(RoomsEvent, [GetCachedMapInfo(version=2)]),
trace=CapabilityEvent(MapTraceEvent, [GetMapTrace()]),
),
network=CapabilityEvent(NetworkInfoEvent, [GetNetInfo()]),
play_sound=CapabilityExecute(PlaySound),
settings=CapabilitySettings(
advanced_mode=CapabilitySetEnable(
AdvancedModeEvent, [GetAdvancedMode()], SetAdvancedMode
),
carpet_auto_fan_boost=CapabilitySetEnable(
CarpetAutoFanBoostEvent,
[GetCarpetAutoFanBoost()],
SetCarpetAutoFanBoost,
),
child_lock=CapabilitySetEnable(
ChildLockEvent, [GetChildLock()], SetChildLock
),
efficiency_mode=CapabilitySetTypes(
event=EfficiencyModeEvent,
get=[GetEfficiencyMode()],
set=SetEfficiencyMode,
types=(
EfficiencyMode.ENERGY_EFFICIENT_MODE,
EfficiencyMode.STANDARD_MODE,
),
),
ota=CapabilitySetEnable(OtaEvent, [GetOta()], SetOta),
sweep_mode=CapabilitySetEnable(
SweepModeEvent, [GetSweepMode()], SetSweepMode
),
true_detect=CapabilitySetEnable(
TrueDetectEvent, [GetTrueDetect()], SetTrueDetect
),
voice_assistant=CapabilitySetEnable(
VoiceAssistantStateEvent,
[GetVoiceAssistantState()],
SetVoiceAssistantState,
),
volume=CapabilitySet(VolumeEvent, [GetVolume()], SetVolume),
),
state=CapabilityEvent(StateEvent, [GetChargeState(), GetCleanInfoV2()]),
station=CapabilityStation(
action=CapabilityExecuteTypes(
station_action.StationAction, types=(StationAction.EMPTY_DUSTBIN,)
),
auto_empty=CapabilitySetTypes(
event=AutoEmptyEvent,
get=[GetAutoEmpty()],
set=SetAutoEmpty,
types=(
auto_empty.Frequency.AUTO,
auto_empty.Frequency.SMART,
),
),
state=CapabilityEvent(StationEvent, [GetStationState()]),
),
stats=CapabilityStats(
clean=CapabilityEvent(StatsEvent, [GetStats()]),
report=CapabilityEvent(ReportStatsEvent, []),
total=CapabilityEvent(TotalStatsEvent, [GetTotalStats()]),
),
water=CapabilityWater(
amount=CapabilitySet(
event=water_info.WaterAmountEvent,
get=[GetWaterInfo()],
set=SetWaterInfo,
),
mop_attached=CapabilityEvent(water_info.MopAttachedEvent, [GetWaterInfo()]),
),
),
)```
I see three problems so far:
- robot state (docked, cleaning) doesn't seem to update...
- the room id/labels are not part of the entity attributes anymore (while they were with my X1 OMNI)
- I don't see the map
I have mitmproxy configured to read Ecovacs API calls, if needed.
@slflowfoon @ytorres
Unfortunately my X8 broke and got an X9 as a replacement so I can no longer do any further testing
I think X9 is very similar to X8... they have same features/API calls @slflowfoon . Can we work together to make X8 and X9 fully supported on HA? So far also the X9 support has a lot of limitations (e.g. map, water level..)
PR: 1167 will fix this https://github.com/DeebotUniverse/client.py/pull/1167
I use HA 2025.10.3 with Ecovacs-Integration. I can use Vacuum-Card well with this version. Scripts for cleaning single rooms don't work.
action: vacuum.send_command metadata: {} data: command: spot_area params: rooms: 4 target: entity_id: vacuum.deebot_x8_pro_onmi
This leads to no action on my robot.
You will need to use:
- service: vacuum.send_command
target:
entity_id: vacuum.XXX
data_template:
command: clean_V2
params:
act: "start"
content:
{"type":"freeClean","value":"1,1;1,3;1,5"}
if you want to clean room 1, 3 and 5...
You will need to use:
- service: vacuum.send_command target: entity_id: vacuum.XXX data_template: command: clean_V2 params: act: "start" content: {"type":"freeClean","value":"1,1;1,3;1,5"}if you want to clean room 1, 3 and 5...
It worked. Thank you very much
@edenhaus since many of these new vaccums utalize clean_V2, a departure from the other commands documented in the deebotuniverse for room or custom area cleans. Would it be possible to map clean_V2 so that other hacs intigrations like "Xiaomi Lovelace Vacuum Map card" could send the legacy commands and these new vaccums preform the action?
I used this script to start room cleaning for one or more rooms for my Deebot X9 Pro Omni:
alias: Start Room Cleaning
sequence:
- data:
option: >-
{{ iif(is_state('input_select.deebot_x9_pro_omni_work_mode', 'Vacuum &
Mop'), 'vacuum_mop', work_mode | lower) }}
target:
entity_id: select.deebot_x9_pro_omni_work_mode
action: select.select_option
- data:
command: clean_V2
params:
act: start
content:
type: freeClean
value: >
{% set room_map = states.vacuum.deebot_x9_pro_omni.attributes.rooms
%} {# Get user selected rooms (variable 'area' from fields) #} {%
set selected_rooms = area %} {# Initialize a namespace to allow
appending to the list inside the loop #} {% set ns =
namespace(commands=[]) %}
{% if room_map is defined and selected_rooms is defined %}
{% for room_name in selected_rooms %}
{# Convert user-friendly name (e.g., "Guest Bathroom") to map key (e.g., "guest_bathroom") #}
{% set room_key = room_name | lower | replace(' ', '_') %}
{% if room_key in room_map %}
{# Construct the full command part (e.g., "1,2") and append it to the list #}
{% set room_id = room_map[room_key] | string %}
{% set ns.commands = ns.commands + ["1," ~ room_id] %}
{% else %}
{{ log("Room '" ~ room_name ~ "' not found in vacuum map.", level='warning') }}
{% endif %}
{% endfor %}
{% endif %}
{# FINAL OUTPUT: Join the list of full command strings using a
SEMICOLON (;) #} {{ ns.commands | join(';') }}
target:
entity_id: vacuum.deebot_x9_pro_omni
action: vacuum.send_command
mode: single
fields:
work_mode:
name: Work Mode
required: true
selector:
select:
options:
- Vacuum
- Vacuum & Mop
default: Vacuum
description: Select the work mode
area:
selector:
select:
options:
- Guest Bathroom
- Corridor
- Bathroom
- Bedroom
- Kids Room
- Dining Room
- Kitchen
- Living Room
multiple: true
name: Area
description: Select areas to clean
required: true
description: Start room cleaning for one or more rooms
icon: mdi:robot-vacuum
This looks like this in the UI: