2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.mqtt.homeassistant.internal.component;
15 import java.math.BigDecimal;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.List;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.mqtt.generic.values.DateTimeValue;
23 import org.openhab.binding.mqtt.generic.values.NumberValue;
24 import org.openhab.binding.mqtt.generic.values.TextValue;
25 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
27 import com.google.gson.annotations.SerializedName;
30 * A MQTT vacuum, following the https://www.home-assistant.io/components/vacuum.mqtt/ specification.
32 * @author Stefan Triller - Initial contribution
35 public class Vacuum extends AbstractComponent<Vacuum.ChannelConfiguration> {
36 public static final String VACUUM_STATE_CHANNEL_ID = "state";
37 public static final String VACUUM_COMMAND_CHANNEL_ID = "command";
38 public static final String VACUUM_BATTERY_CHANNEL_ID = "batteryLevel";
39 public static final String VACUUM_FAN_SPEED_CHANNEL_ID = "fanSpeed";
42 public static final String VACUUM_MAIN_BRUSH_CHANNEL_ID = "mainBrushUsage";
43 public static final String VACUUM_SIDE_BRUSH_CHANNEL_ID = "sideBrushUsage";
44 public static final String VACUUM_FILTER_CHANNEL_ID = "filter";
45 public static final String VACUUM_SENSOR_CHANNEL_ID = "sensor";
46 public static final String VACUUM_CURRENT_CLEAN_TIME_CHANNEL_ID = "currentCleanTime";
47 public static final String VACUUM_CURRENT_CLEAN_AREA_CHANNEL_ID = "currentCleanArea";
48 public static final String VACUUM_CLEAN_TIME_CHANNEL_ID = "cleanTime";
49 public static final String VACUUM_CLEAN_AREA_CHANNEL_ID = "cleanArea";
50 public static final String VACUUM_CLEAN_COUNT_CHANNEL_ID = "cleanCount";
52 public static final String VACUUM_LAST_RUN_START_CHANNEL_ID = "lastRunStart";
53 public static final String VACUUM_LAST_RUN_END_CHANNEL_ID = "lastRunEnd";
54 public static final String VACUUM_LAST_RUN_DURATION_CHANNEL_ID = "lastRunDuration";
55 public static final String VACUUM_LAST_RUN_AREA_CHANNEL_ID = "lastRunArea";
56 public static final String VACUUM_LAST_RUN_ERROR_CODE_CHANNEL_ID = "lastRunErrorCode";
57 public static final String VACUUM_LAST_RUN_ERROR_DESCRIPTION_CHANNEL_ID = "lastRunErrorDescription";
58 public static final String VACUUM_LAST_RUN_FINISHED_FLAG_CHANNEL_ID = "lastRunFinishedFlag";
60 public static final String VACUUM_BIN_IN_TIME_CHANNEL_ID = "binInTime";
61 public static final String VACUUM_LAST_BIN_OUT_TIME_CHANNEL_ID = "lastBinOutTime";
62 public static final String VACUUM_LAST_BIN_FULL_TIME_CHANNEL_ID = "lastBinFullTime";
64 public static final String VACUUM_CUSMTOM_COMMAND_CHANNEL_ID = "customCommand";
67 * Configuration class for MQTT component
69 static class ChannelConfiguration extends AbstractChannelConfiguration {
70 ChannelConfiguration() {
74 @SerializedName("command_topic")
75 protected @Nullable String commandTopic;
76 @SerializedName("state_topic")
77 protected String stateTopic = "";
78 @SerializedName("send_command_topic")
79 protected @Nullable String sendCommandTopic; // for custom_command
81 // [start, pause, stop, return_home, battery, status, locate, clean_spot, fan_speed, send_command]
82 @SerializedName("supported_features")
83 protected String[] supportedFeatures = new String[] {};
84 @SerializedName("set_fan_speed_topic")
85 protected @Nullable String setFanSpeedTopic;
86 @SerializedName("fan_speed_list")
87 protected String[] fanSpeedList = new String[] {};
89 @SerializedName("json_attributes_topic")
90 protected @Nullable String jsonAttributesTopic;
91 @SerializedName("json_attributes_template")
92 protected @Nullable String jsonAttributesTemplate;
95 public Vacuum(ComponentFactory.ComponentConfiguration componentConfiguration) {
96 super(componentConfiguration, ChannelConfiguration.class);
98 List<String> features = Arrays.asList(channelConfiguration.supportedFeatures);
100 // features = [start, pause, stop, return_home, status, locate, clean_spot, fan_speed, send_command]
101 ArrayList<String> possibleCommands = new ArrayList<String>();
102 if (features.contains("start")) {
103 possibleCommands.add("start");
106 if (features.contains("stop")) {
107 possibleCommands.add("stop");
110 if (features.contains("pause")) {
111 possibleCommands.add("pause");
114 if (features.contains("return_home")) {
115 possibleCommands.add("return_to_base");
118 if (features.contains("locate")) {
119 possibleCommands.add("locate");
122 TextValue value = new TextValue(possibleCommands.toArray(new String[0]));
123 buildChannel(VACUUM_COMMAND_CHANNEL_ID, value, "Command", componentConfiguration.getUpdateListener())
124 .stateTopic(channelConfiguration.commandTopic).commandTopic(channelConfiguration.commandTopic, false, 1)
127 List<String> vacuumStates = List.of("docked", "cleaning", "returning", "paused", "idle", "error");
128 TextValue valueState = new TextValue(vacuumStates.toArray(new String[0]));
129 buildChannel(VACUUM_STATE_CHANNEL_ID, valueState, "State", componentConfiguration.getUpdateListener())
130 .stateTopic(channelConfiguration.stateTopic, "{{value_json.state}}").build();
132 if (features.contains("battery")) {
133 // build battery level channel (0-100)
134 NumberValue batValue = new NumberValue(BigDecimal.ZERO, new BigDecimal(100), new BigDecimal(1), "%");
135 buildChannel(VACUUM_BATTERY_CHANNEL_ID, batValue, "Battery Level",
136 componentConfiguration.getUpdateListener())
137 .stateTopic(channelConfiguration.stateTopic, "{{value_json.battery_level}}").build();
140 if (features.contains("fan_speed")) {
141 // build fan speed channel with values from channelConfiguration.fan_speed_list
142 TextValue fanValue = new TextValue(channelConfiguration.fanSpeedList);
143 buildChannel(VACUUM_FAN_SPEED_CHANNEL_ID, fanValue, "Fan speed", componentConfiguration.getUpdateListener())
144 .stateTopic(channelConfiguration.stateTopic, "{{value_json.fan_speed}}")
145 .commandTopic(channelConfiguration.setFanSpeedTopic, false, 1).build();
148 // {"mainBrush":"220.6","sideBrush":"120.6","filter":"70.6","sensor":"0.0","currentCleanTime":"0.0","currentCleanArea":"0.0","cleanTime":"79.3","cleanArea":"4439.9","cleanCount":183,"last_run_stats":{"startTime":1613503117000,"endTime":1613503136000,"duration":0,"area":"0.0","errorCode":0,"errorDescription":"No
149 // error","finishedFlag":false},"bin_in_time":1000,"last_bin_out":-1,"last_bin_full":-1,"last_loaded_map":null,"state":"docked","valetudo_state":{"id":8,"name":"Charging"}}
150 if (features.contains("status")) {
151 NumberValue currentCleanTimeValue = new NumberValue(null, null, null, null);
152 buildChannel(VACUUM_CURRENT_CLEAN_TIME_CHANNEL_ID, currentCleanTimeValue, "Current Cleaning Time",
153 componentConfiguration.getUpdateListener())
154 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.currentCleanTime}}")
157 NumberValue currentCleanAreaValue = new NumberValue(null, null, null, null);
158 buildChannel(VACUUM_CURRENT_CLEAN_AREA_CHANNEL_ID, currentCleanAreaValue, "Current Cleaning Area",
159 componentConfiguration.getUpdateListener())
160 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.currentCleanArea}}")
163 NumberValue cleanTimeValue = new NumberValue(null, null, null, null);
164 buildChannel(VACUUM_CLEAN_TIME_CHANNEL_ID, cleanTimeValue, "Cleaning Time",
165 componentConfiguration.getUpdateListener())
166 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.cleanTime}}").build();
168 NumberValue cleanAreaValue = new NumberValue(null, null, null, null);
169 buildChannel(VACUUM_CLEAN_AREA_CHANNEL_ID, cleanAreaValue, "Cleaned Area",
170 componentConfiguration.getUpdateListener())
171 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.cleanArea}}").build();
173 NumberValue cleaCountValue = new NumberValue(null, null, null, null);
174 buildChannel(VACUUM_CLEAN_COUNT_CHANNEL_ID, cleaCountValue, "Cleaning Counter",
175 componentConfiguration.getUpdateListener())
176 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.cleanCount}}").build();
178 DateTimeValue lastStartTime = new DateTimeValue();
179 buildChannel(VACUUM_LAST_RUN_START_CHANNEL_ID, lastStartTime, "Last run start time",
180 componentConfiguration.getUpdateListener())
181 .stateTopic(channelConfiguration.jsonAttributesTopic,
182 "{{value_json.last_run_stats.startTime}}")
185 DateTimeValue lastEndTime = new DateTimeValue();
186 buildChannel(VACUUM_LAST_RUN_END_CHANNEL_ID, lastEndTime, "Last run end time",
187 componentConfiguration.getUpdateListener())
188 .stateTopic(channelConfiguration.jsonAttributesTopic,
189 "{{value_json.last_run_stats.endTime}}")
192 NumberValue lastRunDurationValue = new NumberValue(null, null, null, null);
193 buildChannel(VACUUM_LAST_RUN_DURATION_CHANNEL_ID, lastRunDurationValue, "Last run duration",
194 componentConfiguration.getUpdateListener())
195 .stateTopic(channelConfiguration.jsonAttributesTopic,
196 "{{value_json.last_run_stats.duration}}")
199 NumberValue lastRunAreaValue = new NumberValue(null, null, null, null);
200 buildChannel(VACUUM_LAST_RUN_AREA_CHANNEL_ID, lastRunAreaValue, "Last run area",
201 componentConfiguration.getUpdateListener())
202 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.last_run_stats.area}}")
205 NumberValue lastRunErrorCodeValue = new NumberValue(null, null, null, null);
206 buildChannel(VACUUM_LAST_RUN_ERROR_CODE_CHANNEL_ID, lastRunErrorCodeValue, "Last run error code",
207 componentConfiguration.getUpdateListener())
208 .stateTopic(channelConfiguration.jsonAttributesTopic,
209 "{{value_json.last_run_stats.errorCode}}")
212 TextValue lastRunErrorDescriptionValue = new TextValue();
213 buildChannel(VACUUM_LAST_RUN_ERROR_DESCRIPTION_CHANNEL_ID, lastRunErrorDescriptionValue,
214 "Last run error description", componentConfiguration.getUpdateListener())
215 .stateTopic(channelConfiguration.jsonAttributesTopic,
216 "{{value_json.last_run_stats.errorDescription}}")
219 // true/false doesnt map to ON/OFF => use TextValue instead of OnOffValue
220 TextValue lastRunFinishedFlagValue = new TextValue();
221 buildChannel(VACUUM_LAST_RUN_FINISHED_FLAG_CHANNEL_ID, lastRunFinishedFlagValue, "Last run finished flag",
222 componentConfiguration.getUpdateListener())
223 .stateTopic(channelConfiguration.jsonAttributesTopic,
224 "{{value_json.last_run_stats.finishedFlag}}")
227 // only for valetudo re => advanced channels
228 DateTimeValue binInValue = new DateTimeValue();
229 buildChannel(VACUUM_BIN_IN_TIME_CHANNEL_ID, binInValue, "Bin In Time",
230 componentConfiguration.getUpdateListener())
231 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.bin_in_time}}")
232 .isAdvanced(true).build();
234 DateTimeValue lastBinOutValue = new DateTimeValue();
235 buildChannel(VACUUM_LAST_BIN_OUT_TIME_CHANNEL_ID, lastBinOutValue, "Last Bin Out Time",
236 componentConfiguration.getUpdateListener())
237 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.last_bin_out}}")
238 .isAdvanced(true).build();
240 DateTimeValue lastBinFullValue = new DateTimeValue();
241 buildChannel(VACUUM_LAST_BIN_FULL_TIME_CHANNEL_ID, lastBinFullValue, "Last Bin Full Time",
242 componentConfiguration.getUpdateListener())
243 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.last_bin_full}}")
244 .isAdvanced(true).build();
247 NumberValue mainBrush = new NumberValue(null, null, null, null);
248 buildChannel(VACUUM_MAIN_BRUSH_CHANNEL_ID, mainBrush, "Main brush usage",
249 componentConfiguration.getUpdateListener())
250 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.mainBrush}}").build();
252 NumberValue sideBrush = new NumberValue(null, null, null, null);
253 buildChannel(VACUUM_SIDE_BRUSH_CHANNEL_ID, sideBrush, "Side brush usage",
254 componentConfiguration.getUpdateListener())
255 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.sideBrush}}").build();
257 NumberValue filterValue = new NumberValue(null, null, null, null);
258 buildChannel(VACUUM_FILTER_CHANNEL_ID, filterValue, "Filter time", componentConfiguration.getUpdateListener())
259 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.filter}}").build();
261 NumberValue sensorValue = new NumberValue(null, null, null, null);
262 buildChannel(VACUUM_SENSOR_CHANNEL_ID, sensorValue, "Sensor", componentConfiguration.getUpdateListener())
263 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.sensor}}").build();
265 // if we have a custom command channel for zone cleanup, etc => create text channel
266 if (channelConfiguration.sendCommandTopic != null) {
267 TextValue customCommandValue = new TextValue();
268 buildChannel(VACUUM_CUSMTOM_COMMAND_CHANNEL_ID, customCommandValue, "Custom Command",
269 componentConfiguration.getUpdateListener())
270 .commandTopic(channelConfiguration.sendCommandTopic, false, 1)
271 .stateTopic(channelConfiguration.sendCommandTopic).build();