mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-29 12:34:21 +02:00
[ecovacs] Add Deebot T30 (PRO) OMNI and support Scenario Cleaning (#17162)
* [ecovacs] Add Deebot T30 (PRO) OMNI Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
This commit is contained in:
@@ -16,6 +16,7 @@ At this point, the following devices are fully supported and verified to be work
|
||||
- Deebot OZMO 950
|
||||
- Deebot OZMO Slim 10/11
|
||||
- Deebot N8 series
|
||||
- Deebot T30 OMNI
|
||||
|
||||
The following devices will likely work because they are using similar protocols as the above ones:
|
||||
|
||||
@@ -92,33 +93,34 @@ In case a particular channel is not supported by a given device (see remarks), i
|
||||
Remarks:
|
||||
|
||||
- [1] See [section below](#command-channel-actions)
|
||||
- [2] Possible states: 'cleaning', 'pause', 'stop', 'drying', 'washing', 'returning' and 'charging' (where 'drying' and 'washing' are only available on newer models with auto empty station)
|
||||
- [3] Possible states: 'auto', 'edge', 'spot', 'spotArea', 'customArea', 'singleRoom' (some of which depend on device capabilities)
|
||||
- [2] Possible states: `cleaning`, `pause`, `stop`, `autoEmpty`, `drying`, `washing`, `returning` and `charging` (where `autoEmpty`, `drying` and `washing` are only available on newer models with auto empty station)
|
||||
- [3] Possible states: `auto`, `edge`, `spot`, `spotArea`, `customArea`, `singleRoom`, and `sceneClean` (some of which depend on device capabilities)
|
||||
- [4] Current cleaning status is only valid if the device is currently cleaning
|
||||
- [5] Only valid for 'spot', 'spotArea' and 'customArea' cleaning modes; value can be used for 'spotArea' and 'customArea' commands (see below)
|
||||
- [5] Only valid for `spot`, `spotArea`, `customArea`, and `sceneClean` cleaning modes; value can be used for `spotArea`, `customArea`, or `sceneClean` commands (see below)
|
||||
- [6] Only present if device has a mopping system
|
||||
- [7] Only present on newer generation devices (Deebot OZMO 950 and newer)
|
||||
- [8] Only present if device has a main brush
|
||||
- [9] Only present on newer generation devices (Deebot N8/T8 or newer)
|
||||
- [10] Only present if device has a dustbin auto empty station; supports both on/off command (to turn on/off the setting) and the string 'trigger' (to trigger immediate auto empty)
|
||||
- [11] Only present if device can control power level. Possible values vary by device: 'normal' and 'high' are always supported, 'silent' and 'higher' are supported for some models
|
||||
- [10] Only present if device has a dustbin auto empty station; supports both on/off command (to turn on/off the setting) and the string `trigger` (to trigger immediate auto empty)
|
||||
- [11] Only present if device can control power level. Possible values vary by device: `normal` and `high` are always supported, `silent` and `higher` are supported for some models
|
||||
- [12] Only present if device supports True Detect 3D
|
||||
- [13] Only present if device has voice reporting
|
||||
- [14] Only present if device has a mopping system. Possible values include 'low', 'medium', 'high' and 'veryhigh'
|
||||
- [14] Only present if device has a mopping system. Possible values include `low`, `medium`, `high` and `veryhigh`
|
||||
|
||||
## Command Channel Actions
|
||||
|
||||
The following actions are supported by the `command` channel:
|
||||
|
||||
| Name | Action | Remarks |
|
||||
|--------------|-------------------------------------------|------------------------------------------------------|
|
||||
| `clean` | Start cleaning in automatic mode. | |
|
||||
| `spotArea` | Start cleaning specific rooms. | <ul><li>Only if supported by device, which can be recognized by `spotArea` being present in the list of possible states of the `current-cleaning-mode` channel.</li><li>Format: `spotArea:<room IDs>`, where `room IDs` is a semicolon separated list of room letters as shown in Ecovacs' app, so a valid command could e.g. be `spotArea:A;D;E`.</li><li>If you want to run 2 clean passes, amend `:x2` to the command, e.g. `spotArea:A;C;B:x2`.</li></ul> |
|
||||
| `customArea` | Start cleaning specific areas. | <ul><li>Only if supported by device, which can be recognized by `customArea` being present in the list of possible states of the `current-cleaning-mode` channel.</li><li>Format: `customArea:<x1>;<y1>;<x2>;<y2>`, where the parameters are coordinates (in mm) relative to the map.</li><li>The coordinates can be obtained from the `current-cleaning-spot-definition` channel when starting a custom area run from the app.</li><li>If you want to run 2 clean passes, amend `:x2` to the command, e.g. `customArea:100;100;1000;1000:x2`.</li></ul> |
|
||||
| `pause` | Pause cleaning if it's currently active. | If the device is idle, the command is ignored. |
|
||||
| `resume` | Resume cleaning if it's currently paused. | If the device is not paused, the command is ignored. |
|
||||
| `stop` | Stop cleaning immediately. | |
|
||||
| `charge` | Send device to charging station. | |
|
||||
| Name | Action | Remarks |
|
||||
|--------------|---------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `clean` | Start cleaning in automatic mode. | |
|
||||
| `spotArea` | Start cleaning specific rooms. | <ul><li>Only if supported by device, which can be recognized by `spotArea` being present in the list of possible states of the `current-cleaning-mode` channel.</li><li>Format: `spotArea:<room IDs>`, where `room IDs` is a semicolon separated list of room letters as shown in Ecovacs' app, so a valid command could e.g. be `spotArea:A;D;E`.</li><li>If you want to run 2 clean passes, append `:x2` to the command, e.g. `spotArea:A;C;B:x2`.</li></ul> |
|
||||
| `customArea` | Start cleaning specific areas. | <ul><li>Only if supported by device, which can be recognized by `customArea` being present in the list of possible states of the `current-cleaning-mode` channel.</li><li>Format: `customArea:<x1>;<y1>;<x2>;<y2>`, where the parameters are coordinates (in mm) relative to the map.</li><li>The coordinates can be obtained from the `current-cleaning-spot-definition` channel when starting a custom area run from the app.</li><li>If you want to run 2 clean passes, append `:x2` to the command, e.g. `customArea:100;100;1000;1000:x2`.</li></ul> |
|
||||
| `sceneClean` | Start cleaning using a predefined scenario. | <ul><li>Only if supported by device, which can be recognized by `sceneClean` being present in the list of possible states of the `current-cleaning-mode` channel.</li><li>Format: `sceneClean:<scenarioId>`. The `scenarioID` can be obtained from the `current-cleaning-spot-definition` channel when starting a scenario run from the app. Example: `sceneClean:5318`.</li></ul> |
|
||||
| `pause` | Pause cleaning if it's currently active. | If the device is idle, the command is ignored. |
|
||||
| `resume` | Resume cleaning if it's currently paused. | If the device is not paused, the command is ignored. |
|
||||
| `stop` | Stop cleaning immediately. | |
|
||||
| `charge` | Send device to charging station. | |
|
||||
|
||||
## Rule actions
|
||||
|
||||
|
||||
+7
-2
@@ -85,6 +85,7 @@ public class EcovacsBindingConstants {
|
||||
public static final String CMD_STOP = "stop";
|
||||
public static final String CMD_SPOT_AREA = "spotArea";
|
||||
public static final String CMD_CUSTOM_AREA = "customArea";
|
||||
public static final String CMD_SCENE_CLEAN = "sceneClean";
|
||||
|
||||
public static final StateOptionMapping<CleanMode> CLEAN_MODE_MAPPING = StateOptionMapping.of(
|
||||
new StateOptionEntry<>(CleanMode.AUTO, "auto"),
|
||||
@@ -93,8 +94,12 @@ public class EcovacsBindingConstants {
|
||||
new StateOptionEntry<>(CleanMode.SPOT_AREA, "spotArea", DeviceCapability.SPOT_AREA_CLEANING),
|
||||
new StateOptionEntry<>(CleanMode.CUSTOM_AREA, "customArea", DeviceCapability.CUSTOM_AREA_CLEANING),
|
||||
new StateOptionEntry<>(CleanMode.SINGLE_ROOM, "singleRoom", DeviceCapability.SINGLE_ROOM_CLEANING),
|
||||
new StateOptionEntry<>(CleanMode.PAUSE, "pause"), new StateOptionEntry<>(CleanMode.STOP, "stop"),
|
||||
new StateOptionEntry<>(CleanMode.WASHING, "washing"), new StateOptionEntry<>(CleanMode.DRYING, "drying"),
|
||||
new StateOptionEntry<>(CleanMode.SCENE_CLEAN, "sceneClean", DeviceCapability.SCENARIO_CLEANING),
|
||||
new StateOptionEntry<>(CleanMode.PAUSE, "pause"), //
|
||||
new StateOptionEntry<>(CleanMode.STOP, "stop"), //
|
||||
new StateOptionEntry<>(CleanMode.AUTO_EMPTY, "autoEmpty"),
|
||||
new StateOptionEntry<>(CleanMode.WASHING, "washing"), //
|
||||
new StateOptionEntry<>(CleanMode.DRYING, "drying"),
|
||||
new StateOptionEntry<>(CleanMode.RETURNING, "returning"));
|
||||
|
||||
public static final StateOptionMapping<MoppingWaterAmount> WATER_AMOUNT_MAPPING = StateOptionMapping.of(
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Jimmy Tanagra - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SceneCleaningCommand extends AbstractAreaCleaningCommand {
|
||||
public SceneCleaningCommand(String sceneDefinition, int cleanPasses) {
|
||||
super("sceneClean", sceneDefinition, cleanPasses);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -59,7 +59,8 @@ class JsonReportParser implements ReportParser {
|
||||
} catch (JsonSyntaxException e) {
|
||||
// The onFwBuryPoint-bd_sysinfo sends a JSON array instead of the expected JsonResponsePayloadBody object.
|
||||
// Since we don't do anything with it anyway, just ignore it
|
||||
logger.debug("{}: Got invalid JSON message payload, ignoring: {}", device.getSerialNumber(), payload, e);
|
||||
logger.debug("{}: Got invalid JSON data ({}), ignoring payload: {}", device.getSerialNumber(),
|
||||
e.getMessage(), payload);
|
||||
response = null;
|
||||
}
|
||||
if (response == null) {
|
||||
|
||||
+7
-3
@@ -33,25 +33,29 @@ public enum CleanMode {
|
||||
CUSTOM_AREA,
|
||||
@SerializedName("singleRoom")
|
||||
SINGLE_ROOM,
|
||||
@SerializedName("sceneClean")
|
||||
SCENE_CLEAN,
|
||||
@SerializedName("pause")
|
||||
PAUSE,
|
||||
@SerializedName("stop")
|
||||
STOP,
|
||||
@SerializedName(value = "going", alternate = { "goCharging" })
|
||||
RETURNING,
|
||||
@SerializedName("autoEmpty")
|
||||
AUTO_EMPTY,
|
||||
@SerializedName("washing")
|
||||
WASHING,
|
||||
@SerializedName("drying")
|
||||
@SerializedName(value = "drying", alternate = { "spin-dry" })
|
||||
DRYING,
|
||||
@SerializedName("idle")
|
||||
IDLE;
|
||||
|
||||
public boolean isActive() {
|
||||
return this == AUTO || this == EDGE || this == SPOT || this == SPOT_AREA || this == CUSTOM_AREA
|
||||
|| this == SINGLE_ROOM;
|
||||
|| this == SINGLE_ROOM || this == SCENE_CLEAN;
|
||||
}
|
||||
|
||||
public boolean isIdle() {
|
||||
return this == IDLE || this == DRYING || this == WASHING;
|
||||
return this == IDLE || this == DRYING || this == WASHING || this == AUTO_EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -35,6 +35,8 @@ public enum DeviceCapability {
|
||||
CUSTOM_AREA_CLEANING,
|
||||
@SerializedName("single_room_cleaning")
|
||||
SINGLE_ROOM_CLEANING,
|
||||
@SerializedName("scenario_cleaning")
|
||||
SCENARIO_CLEANING,
|
||||
@SerializedName("clean_speed_control")
|
||||
CLEAN_SPEED_CONTROL,
|
||||
@SerializedName("mapping")
|
||||
|
||||
+25
-18
@@ -55,6 +55,7 @@ import org.openhab.binding.ecovacs.internal.api.commands.GoChargingCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.PauseCleaningCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.PlaySoundCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.ResumeCleaningCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.SceneCleaningCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.SetContinuousCleaningCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.SetDefaultCleanPassesCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.SetDustbinAutoEmptyCommand;
|
||||
@@ -530,6 +531,7 @@ public class EcovacsVacuumHandler extends BaseThingHandler implements EcovacsDev
|
||||
} catch (ConfigurationException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getRawMessage());
|
||||
} catch (EcovacsApiException e) {
|
||||
logger.debug("API Exception: {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -673,7 +675,8 @@ public class EcovacsVacuumHandler extends BaseThingHandler implements EcovacsDev
|
||||
// Some devices already report charging state while returning to charging station, make sure to not report
|
||||
// charging in that case. The same applies for models with pad washing/drying station, as those states imply
|
||||
// the device being charging.
|
||||
if (cleanMode != CleanMode.RETURNING && cleanMode != CleanMode.WASHING && cleanMode != CleanMode.DRYING) {
|
||||
if (cleanMode != CleanMode.RETURNING && cleanMode != CleanMode.WASHING && cleanMode != CleanMode.DRYING
|
||||
&& cleanMode != CleanMode.AUTO_EMPTY) {
|
||||
return "charging";
|
||||
}
|
||||
}
|
||||
@@ -693,21 +696,15 @@ public class EcovacsVacuumHandler extends BaseThingHandler implements EcovacsDev
|
||||
if (charging) {
|
||||
return CMD_CHARGE;
|
||||
}
|
||||
switch (cleanMode) {
|
||||
case AUTO:
|
||||
return CMD_AUTO_CLEAN;
|
||||
case SPOT_AREA:
|
||||
return CMD_SPOT_AREA;
|
||||
case PAUSE:
|
||||
return CMD_PAUSE;
|
||||
case STOP:
|
||||
return CMD_STOP;
|
||||
case RETURNING:
|
||||
return CMD_CHARGE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
return switch (cleanMode) {
|
||||
case AUTO -> CMD_AUTO_CLEAN;
|
||||
case SPOT_AREA -> CMD_SPOT_AREA;
|
||||
case SCENE_CLEAN -> CMD_SCENE_CLEAN;
|
||||
case PAUSE -> CMD_PAUSE;
|
||||
case STOP -> CMD_STOP;
|
||||
case RETURNING -> CMD_CHARGE;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private State stringToState(@Nullable String value) {
|
||||
@@ -755,7 +752,7 @@ public class EcovacsVacuumHandler extends BaseThingHandler implements EcovacsDev
|
||||
device.hasCapability(DeviceCapability.FREE_CLEAN_FOR_SPOT_AREA));
|
||||
}
|
||||
} else {
|
||||
logger.info("{}: spotArea command needs to have the form spotArea:<room1>[;<room2>][;<...roomX>][:x2]",
|
||||
logger.warn("{}: spotArea command needs to have the form spotArea:<room1>[;<room2>][;<...roomX>][:x2]",
|
||||
serialNumber);
|
||||
}
|
||||
}
|
||||
@@ -769,9 +766,19 @@ public class EcovacsVacuumHandler extends BaseThingHandler implements EcovacsDev
|
||||
return new CustomAreaCleaningCommand(String.join(",", splittedAreaDef), passes);
|
||||
}
|
||||
}
|
||||
logger.info("{}: customArea command needs to have the form customArea:<x1>;<y1>;<x2>;<y2>[:x2]",
|
||||
logger.warn("{}: customArea command needs to have the form customArea:<x1>;<y1>;<x2>;<y2>[:x2]",
|
||||
serialNumber);
|
||||
}
|
||||
if (command.startsWith(CMD_SCENE_CLEAN) && device.hasCapability(DeviceCapability.SCENARIO_CLEANING)) {
|
||||
String[] splitted = command.split(":");
|
||||
if (splitted.length == 2) {
|
||||
String scenarioId = splitted[1];
|
||||
// Setting passes > 1 does not seem to have any effect (tested on T30).
|
||||
return new SceneCleaningCommand(scenarioId, 1);
|
||||
}
|
||||
logger.warn("{}: {} command needs to have the form {}:<scenarioId>", serialNumber, CMD_SCENE_CLEAN,
|
||||
CMD_SCENE_CLEAN);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -124,6 +124,7 @@ ecovacs.cleaning-mode.spot = Spot cleaning
|
||||
ecovacs.cleaning-mode.spotArea = Spot area cleaning
|
||||
ecovacs.cleaning-mode.customArea = Custom area cleaning
|
||||
ecovacs.cleaning-mode.singleRoom = Single room cleaning
|
||||
ecovacs.cleaning-mode.sceneClean = Scenario cleaning
|
||||
|
||||
# error codes
|
||||
|
||||
|
||||
@@ -151,6 +151,7 @@
|
||||
<option value="spotArea">@text/ecovacs.cleaning-mode.spotArea</option>
|
||||
<option value="customArea">@text/ecovacs.cleaning-mode.customArea</option>
|
||||
<option value="singleRoom">@text/ecovacs.cleaning-mode.singleRoom</option>
|
||||
<option value="sceneClean">@text/ecovacs.cleaning-mode.sceneClean</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
+37
@@ -615,5 +615,42 @@
|
||||
"modelName": "DEEBOT X2 PRO OMNI",
|
||||
"deviceClass": "ip3mmy",
|
||||
"deviceClassLink": "e6ofmn"
|
||||
},
|
||||
|
||||
{
|
||||
"modelName": "DEEBOT T30 OMNI",
|
||||
"deviceClass": "z4lvk7",
|
||||
"protoVersion": "json_v2",
|
||||
"usesMqtt": true,
|
||||
"capabilities": [
|
||||
"mopping_system",
|
||||
"main_brush",
|
||||
"spot_area_cleaning",
|
||||
"custom_area_cleaning",
|
||||
"scenario_cleaning",
|
||||
"clean_speed_control",
|
||||
"voice_reporting",
|
||||
"read_network_info",
|
||||
"unit_care_lifespan",
|
||||
"true_detect_3d",
|
||||
"mapping",
|
||||
"auto_empty_station",
|
||||
"uses_clean_results_log_api"
|
||||
]
|
||||
},
|
||||
{
|
||||
"modelName": "DEEBOT T30 PRO OMNI",
|
||||
"deviceClass": "4vhygi",
|
||||
"deviceClassLink": "z4lvk7"
|
||||
},
|
||||
{
|
||||
"modelName": "DEEBOT T30 PRO OMNI",
|
||||
"deviceClass": "3w7j5e",
|
||||
"deviceClassLink": "z4lvk7"
|
||||
},
|
||||
{
|
||||
"modelName": "DEEBOT T30 PRO OMNI",
|
||||
"deviceClass": "tlthqk",
|
||||
"deviceClassLink": "z4lvk7"
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user