]> git.basschouten.com Git - openhab-addons.git/blob
f84abc36cd0a691b824e20c26a5a1a368c23390d
[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 /**
28  * A MQTT vacuum, following the https://www.home-assistant.io/components/vacuum.mqtt/ specification.
29  *
30  * @author Stefan Triller - Initial contribution
31  */
32 @NonNullByDefault
33 public class Vacuum extends AbstractComponent<Vacuum.ChannelConfiguration> {
34     public static final String VACUUM_STATE_CHANNEL_ID = "state";
35     public static final String VACUUM_COMMAND_CHANNEL_ID = "command";
36     public static final String VACUUM_BATTERY_CHANNEL_ID = "batteryLevel";
37     public static final String VACUUM_FAN_SPEED_CHANNEL_ID = "fanSpeed";
38
39     // sensor stats
40     public static final String VACUUM_MAIN_BRUSH_CHANNEL_ID = "mainBrushUsage";
41     public static final String VACUUM_SIDE_BRUSH_CHANNEL_ID = "sideBrushUsage";
42     public static final String VACUUM_FILTER_CHANNEL_ID = "filter";
43     public static final String VACUUM_SENSOR_CHANNEL_ID = "sensor";
44     public static final String VACUUM_CURRENT_CLEAN_TIME_CHANNEL_ID = "currentCleanTime";
45     public static final String VACUUM_CURRENT_CLEAN_AREA_CHANNEL_ID = "currentCleanArea";
46     public static final String VACUUM_CLEAN_TIME_CHANNEL_ID = "cleanTime";
47     public static final String VACUUM_CLEAN_AREA_CHANNEL_ID = "cleanArea";
48     public static final String VACUUM_CLEAN_COUNT_CHANNEL_ID = "cleanCount";
49
50     public static final String VACUUM_LAST_RUN_START_CHANNEL_ID = "lastRunStart";
51     public static final String VACUUM_LAST_RUN_END_CHANNEL_ID = "lastRunEnd";
52     public static final String VACUUM_LAST_RUN_DURATION_CHANNEL_ID = "lastRunDuration";
53     public static final String VACUUM_LAST_RUN_AREA_CHANNEL_ID = "lastRunArea";
54     public static final String VACUUM_LAST_RUN_ERROR_CODE_CHANNEL_ID = "lastRunErrorCode";
55     public static final String VACUUM_LAST_RUN_ERROR_DESCRIPTION_CHANNEL_ID = "lastRunErrorDescription";
56     public static final String VACUUM_LAST_RUN_FINISHED_FLAG_CHANNEL_ID = "lastRunFinishedFlag";
57
58     public static final String VACUUM_BIN_IN_TIME_CHANNEL_ID = "binInTime";
59     public static final String VACUUM_LAST_BIN_OUT_TIME_CHANNEL_ID = "lastBinOutTime";
60     public static final String VACUUM_LAST_BIN_FULL_TIME_CHANNEL_ID = "lastBinFullTime";
61
62     public static final String VACUUM_CUSMTOM_COMMAND_CHANNEL_ID = "customCommand";
63
64     /**
65      * Configuration class for MQTT component
66      */
67     static class ChannelConfiguration extends AbstractChannelConfiguration {
68         ChannelConfiguration() {
69             super("MQTT Vacuum");
70         }
71
72         protected @Nullable String commandTopic;
73         protected String stateTopic = "";
74         protected @Nullable String sendCommandTopic; // for custom_command
75
76         // [start, pause, stop, return_home, battery, status, locate, clean_spot, fan_speed, send_command]
77         protected String[] supportedFeatures = new String[] {};
78         protected @Nullable String setFanSpeedTopic;
79         protected String[] fanSpeedList = new String[] {};
80
81         protected @Nullable String jsonAttributesTopic;
82         protected @Nullable String jsonAttributesTemplate;
83     }
84
85     public Vacuum(ComponentFactory.ComponentConfiguration componentConfiguration) {
86         super(componentConfiguration, ChannelConfiguration.class);
87
88         List<String> features = Arrays.asList(channelConfiguration.supportedFeatures);
89
90         // features = [start, pause, stop, return_home, status, locate, clean_spot, fan_speed, send_command]
91         ArrayList<String> possibleCommands = new ArrayList<String>();
92         if (features.contains("start")) {
93             possibleCommands.add("start");
94         }
95
96         if (features.contains("stop")) {
97             possibleCommands.add("stop");
98         }
99
100         if (features.contains("pause")) {
101             possibleCommands.add("pause");
102         }
103
104         if (features.contains("return_home")) {
105             possibleCommands.add("return_to_base");
106         }
107
108         if (features.contains("locate")) {
109             possibleCommands.add("locate");
110         }
111
112         TextValue value = new TextValue(possibleCommands.toArray(new String[0]));
113         buildChannel(VACUUM_COMMAND_CHANNEL_ID, value, "Command", componentConfiguration.getUpdateListener())
114                 .stateTopic(channelConfiguration.commandTopic).commandTopic(channelConfiguration.commandTopic, false, 1)
115                 .build();
116
117         List<String> vacuumStates = List.of("docked", "cleaning", "returning", "paused", "idle", "error");
118         TextValue valueState = new TextValue(vacuumStates.toArray(new String[0]));
119         buildChannel(VACUUM_STATE_CHANNEL_ID, valueState, "State", componentConfiguration.getUpdateListener())
120                 .stateTopic(channelConfiguration.stateTopic, "{{value_json.state}}").build();
121
122         if (features.contains("battery")) {
123             // build battery level channel (0-100)
124             NumberValue batValue = new NumberValue(BigDecimal.ZERO, new BigDecimal(100), new BigDecimal(1), "%");
125             buildChannel(VACUUM_BATTERY_CHANNEL_ID, batValue, "Battery Level",
126                     componentConfiguration.getUpdateListener())
127                             .stateTopic(channelConfiguration.stateTopic, "{{value_json.battery_level}}").build();
128         }
129
130         if (features.contains("fan_speed")) {
131             // build fan speed channel with values from channelConfiguration.fan_speed_list
132             TextValue fanValue = new TextValue(channelConfiguration.fanSpeedList);
133             buildChannel(VACUUM_FAN_SPEED_CHANNEL_ID, fanValue, "Fan speed", componentConfiguration.getUpdateListener())
134                     .stateTopic(channelConfiguration.stateTopic, "{{value_json.fan_speed}}")
135                     .commandTopic(channelConfiguration.setFanSpeedTopic, false, 1).build();
136         }
137
138         // {"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
139         // 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"}}
140         if (features.contains("status")) {
141             NumberValue currentCleanTimeValue = new NumberValue(null, null, null, null);
142             buildChannel(VACUUM_CURRENT_CLEAN_TIME_CHANNEL_ID, currentCleanTimeValue, "Current Cleaning Time",
143                     componentConfiguration.getUpdateListener())
144                             .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.currentCleanTime}}")
145                             .build();
146
147             NumberValue currentCleanAreaValue = new NumberValue(null, null, null, null);
148             buildChannel(VACUUM_CURRENT_CLEAN_AREA_CHANNEL_ID, currentCleanAreaValue, "Current Cleaning Area",
149                     componentConfiguration.getUpdateListener())
150                             .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.currentCleanArea}}")
151                             .build();
152
153             NumberValue cleanTimeValue = new NumberValue(null, null, null, null);
154             buildChannel(VACUUM_CLEAN_TIME_CHANNEL_ID, cleanTimeValue, "Cleaning Time",
155                     componentConfiguration.getUpdateListener())
156                             .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.cleanTime}}").build();
157
158             NumberValue cleanAreaValue = new NumberValue(null, null, null, null);
159             buildChannel(VACUUM_CLEAN_AREA_CHANNEL_ID, cleanAreaValue, "Cleaned Area",
160                     componentConfiguration.getUpdateListener())
161                             .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.cleanArea}}").build();
162
163             NumberValue cleaCountValue = new NumberValue(null, null, null, null);
164             buildChannel(VACUUM_CLEAN_COUNT_CHANNEL_ID, cleaCountValue, "Cleaning Counter",
165                     componentConfiguration.getUpdateListener())
166                             .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.cleanCount}}").build();
167
168             DateTimeValue lastStartTime = new DateTimeValue();
169             buildChannel(VACUUM_LAST_RUN_START_CHANNEL_ID, lastStartTime, "Last run start time",
170                     componentConfiguration.getUpdateListener())
171                             .stateTopic(channelConfiguration.jsonAttributesTopic,
172                                     "{{value_json.last_run_stats.startTime}}")
173                             .build();
174
175             DateTimeValue lastEndTime = new DateTimeValue();
176             buildChannel(VACUUM_LAST_RUN_END_CHANNEL_ID, lastEndTime, "Last run end time",
177                     componentConfiguration.getUpdateListener())
178                             .stateTopic(channelConfiguration.jsonAttributesTopic,
179                                     "{{value_json.last_run_stats.endTime}}")
180                             .build();
181
182             NumberValue lastRunDurationValue = new NumberValue(null, null, null, null);
183             buildChannel(VACUUM_LAST_RUN_DURATION_CHANNEL_ID, lastRunDurationValue, "Last run duration",
184                     componentConfiguration.getUpdateListener())
185                             .stateTopic(channelConfiguration.jsonAttributesTopic,
186                                     "{{value_json.last_run_stats.duration}}")
187                             .build();
188
189             NumberValue lastRunAreaValue = new NumberValue(null, null, null, null);
190             buildChannel(VACUUM_LAST_RUN_AREA_CHANNEL_ID, lastRunAreaValue, "Last run area",
191                     componentConfiguration.getUpdateListener())
192                             .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.last_run_stats.area}}")
193                             .build();
194
195             NumberValue lastRunErrorCodeValue = new NumberValue(null, null, null, null);
196             buildChannel(VACUUM_LAST_RUN_ERROR_CODE_CHANNEL_ID, lastRunErrorCodeValue, "Last run error code",
197                     componentConfiguration.getUpdateListener())
198                             .stateTopic(channelConfiguration.jsonAttributesTopic,
199                                     "{{value_json.last_run_stats.errorCode}}")
200                             .build();
201
202             TextValue lastRunErrorDescriptionValue = new TextValue();
203             buildChannel(VACUUM_LAST_RUN_ERROR_DESCRIPTION_CHANNEL_ID, lastRunErrorDescriptionValue,
204                     "Last run error description", componentConfiguration.getUpdateListener())
205                             .stateTopic(channelConfiguration.jsonAttributesTopic,
206                                     "{{value_json.last_run_stats.errorDescription}}")
207                             .build();
208
209             // true/false doesnt map to ON/OFF => use TextValue instead of OnOffValue
210             TextValue lastRunFinishedFlagValue = new TextValue();
211             buildChannel(VACUUM_LAST_RUN_FINISHED_FLAG_CHANNEL_ID, lastRunFinishedFlagValue, "Last run finished flag",
212                     componentConfiguration.getUpdateListener())
213                             .stateTopic(channelConfiguration.jsonAttributesTopic,
214                                     "{{value_json.last_run_stats.finishedFlag}}")
215                             .build();
216
217             // only for valetudo re => advanced channels
218             DateTimeValue binInValue = new DateTimeValue();
219             buildChannel(VACUUM_BIN_IN_TIME_CHANNEL_ID, binInValue, "Bin In Time",
220                     componentConfiguration.getUpdateListener())
221                             .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.bin_in_time}}")
222                             .isAdvanced(true).build();
223
224             DateTimeValue lastBinOutValue = new DateTimeValue();
225             buildChannel(VACUUM_LAST_BIN_OUT_TIME_CHANNEL_ID, lastBinOutValue, "Last Bin Out Time",
226                     componentConfiguration.getUpdateListener())
227                             .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.last_bin_out}}")
228                             .isAdvanced(true).build();
229
230             DateTimeValue lastBinFullValue = new DateTimeValue();
231             buildChannel(VACUUM_LAST_BIN_FULL_TIME_CHANNEL_ID, lastBinFullValue, "Last Bin Full Time",
232                     componentConfiguration.getUpdateListener())
233                             .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.last_bin_full}}")
234                             .isAdvanced(true).build();
235         }
236
237         NumberValue mainBrush = new NumberValue(null, null, null, null);
238         buildChannel(VACUUM_MAIN_BRUSH_CHANNEL_ID, mainBrush, "Main brush usage",
239                 componentConfiguration.getUpdateListener())
240                         .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.mainBrush}}").build();
241
242         NumberValue sideBrush = new NumberValue(null, null, null, null);
243         buildChannel(VACUUM_SIDE_BRUSH_CHANNEL_ID, sideBrush, "Side brush usage",
244                 componentConfiguration.getUpdateListener())
245                         .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.sideBrush}}").build();
246
247         NumberValue filterValue = new NumberValue(null, null, null, null);
248         buildChannel(VACUUM_FILTER_CHANNEL_ID, filterValue, "Filter time", componentConfiguration.getUpdateListener())
249                 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.filter}}").build();
250
251         NumberValue sensorValue = new NumberValue(null, null, null, null);
252         buildChannel(VACUUM_SENSOR_CHANNEL_ID, sensorValue, "Sensor", componentConfiguration.getUpdateListener())
253                 .stateTopic(channelConfiguration.jsonAttributesTopic, "{{value_json.sensor}}").build();
254
255         // if we have a custom command channel for zone cleanup, etc => create text channel
256         if (channelConfiguration.sendCommandTopic != null) {
257             TextValue customCommandValue = new TextValue();
258             buildChannel(VACUUM_CUSMTOM_COMMAND_CHANNEL_ID, customCommandValue, "Custom Command",
259                     componentConfiguration.getUpdateListener())
260                             .commandTopic(channelConfiguration.sendCommandTopic, false, 1)
261                             .stateTopic(channelConfiguration.sendCommandTopic).build();
262         }
263     }
264 }