]> git.basschouten.com Git - openhab-addons.git/blob
b999380fa5721205e1324208aba458ef29bba6e6
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mqtt.homeassistant.internal.component;
14
15 import java.math.BigDecimal;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.List;
19
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;
26
27 import com.google.gson.annotations.SerializedName;
28
29 /**
30  * A MQTT vacuum, following the https://www.home-assistant.io/components/vacuum.mqtt/ specification.
31  *
32  * @author Stefan Triller - Initial contribution
33  */
34 @NonNullByDefault
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";
40
41     // sensor stats
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";
51
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";
59
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";
63
64     public static final String VACUUM_CUSMTOM_COMMAND_CHANNEL_ID = "customCommand";
65
66     /**
67      * Configuration class for MQTT component
68      */
69     static class ChannelConfiguration extends AbstractChannelConfiguration {
70         ChannelConfiguration() {
71             super("MQTT Vacuum");
72         }
73
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
80
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[] {};
88
89         @SerializedName("json_attributes_topic")
90         protected @Nullable String jsonAttributesTopic;
91         @SerializedName("json_attributes_template")
92         protected @Nullable String jsonAttributesTemplate;
93     }
94
95     public Vacuum(ComponentFactory.ComponentConfiguration componentConfiguration) {
96         super(componentConfiguration, ChannelConfiguration.class);
97
98         List<String> features = Arrays.asList(channelConfiguration.supportedFeatures);
99
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");
104         }
105
106         if (features.contains("stop")) {
107             possibleCommands.add("stop");
108         }
109
110         if (features.contains("pause")) {
111             possibleCommands.add("pause");
112         }
113
114         if (features.contains("return_home")) {
115             possibleCommands.add("return_to_base");
116         }
117
118         if (features.contains("locate")) {
119             possibleCommands.add("locate");
120         }
121
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)
125                 .build();
126
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();
131
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();
138         }
139
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();
146         }
147
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}}")
155                             .build();
156
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}}")
161                             .build();
162
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();
167
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();
172
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();
177
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}}")
183                             .build();
184
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}}")
190                             .build();
191
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}}")
197                             .build();
198
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}}")
203                             .build();
204
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}}")
210                             .build();
211
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}}")
217                             .build();
218
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}}")
225                             .build();
226
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();
233
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();
239
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();
245         }
246
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();
251
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();
256
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();
260
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();
264
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();
272         }
273     }
274 }