]> git.basschouten.com Git - openhab-addons.git/blob
4c08b334cab738f7f1024ada29cf735911dce828
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.ventaair.internal;
14
15 import java.io.IOException;
16 import java.math.BigDecimal;
17 import java.util.HashMap;
18 import java.util.Map;
19
20 import javax.measure.Unit;
21 import javax.measure.quantity.Dimensionless;
22 import javax.measure.quantity.Temperature;
23 import javax.measure.quantity.Time;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.ventaair.internal.message.action.Action;
28 import org.openhab.binding.ventaair.internal.message.action.AllActions;
29 import org.openhab.binding.ventaair.internal.message.action.AutomaticAction;
30 import org.openhab.binding.ventaair.internal.message.action.BoostAction;
31 import org.openhab.binding.ventaair.internal.message.action.ChildLockAction;
32 import org.openhab.binding.ventaair.internal.message.action.FanAction;
33 import org.openhab.binding.ventaair.internal.message.action.HumidityAction;
34 import org.openhab.binding.ventaair.internal.message.action.PowerAction;
35 import org.openhab.binding.ventaair.internal.message.action.SleepModeAction;
36 import org.openhab.binding.ventaair.internal.message.action.TimerAction;
37 import org.openhab.binding.ventaair.internal.message.dto.DeviceInfoMessage;
38 import org.openhab.binding.ventaair.internal.message.dto.Header;
39 import org.openhab.binding.ventaair.internal.message.dto.Info;
40 import org.openhab.binding.ventaair.internal.message.dto.Measurements;
41 import org.openhab.core.library.dimension.Density;
42 import org.openhab.core.library.types.DecimalType;
43 import org.openhab.core.library.types.OnOffType;
44 import org.openhab.core.library.types.QuantityType;
45 import org.openhab.core.library.unit.ImperialUnits;
46 import org.openhab.core.library.unit.SIUnits;
47 import org.openhab.core.library.unit.Units;
48 import org.openhab.core.thing.ChannelUID;
49 import org.openhab.core.thing.Thing;
50 import org.openhab.core.thing.ThingStatus;
51 import org.openhab.core.thing.ThingStatusDetail;
52 import org.openhab.core.thing.binding.BaseThingHandler;
53 import org.openhab.core.types.Command;
54 import org.openhab.core.types.RefreshType;
55 import org.openhab.core.types.State;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 /**
60  * The {@link VentaThingHandler} is responsible for handling commands, which are
61  * sent to one of the channels.
62  *
63  * @author Stefan Triller - Initial contribution
64  */
65 @NonNullByDefault
66 public class VentaThingHandler extends BaseThingHandler {
67
68     private final Logger logger = LoggerFactory.getLogger(VentaThingHandler.class);
69
70     private VentaAirDeviceConfiguration config = new VentaAirDeviceConfiguration();
71
72     private @Nullable Communicator communicator;
73
74     private Map<String, State> channelValueCache = new HashMap<>();
75
76     public VentaThingHandler(Thing thing) {
77         super(thing);
78     }
79
80     @Override
81     public void handleCommand(ChannelUID channelUID, Command command) {
82         logger.debug("Handle command={} for channel={} with channelID={}", command, channelUID, channelUID.getId());
83         if (command instanceof RefreshType) {
84             refreshChannelFromCache(channelUID);
85             return;
86         }
87
88         switch (channelUID.getId()) {
89             case VentaAirBindingConstants.CHANNEL_POWER:
90                 if (command instanceof OnOffType) {
91                     dispatchActionToDevice(new PowerAction(command == OnOffType.ON));
92                 }
93                 break;
94             case VentaAirBindingConstants.CHANNEL_FAN_SPEED:
95                 if (command instanceof DecimalType) {
96                     int fanStage = ((DecimalType) command).intValue();
97                     dispatchActionToDevice(new FanAction(fanStage));
98                 }
99                 break;
100             case VentaAirBindingConstants.CHANNEL_TARGET_HUMIDITY:
101                 if (command instanceof DecimalType) {
102                     int targetHumidity = ((DecimalType) command).intValue();
103                     dispatchActionToDevice(new HumidityAction(targetHumidity));
104                 }
105                 break;
106             case VentaAirBindingConstants.CHANNEL_TIMER:
107                 if (command instanceof DecimalType) {
108                     int timer = ((DecimalType) command).intValue();
109                     dispatchActionToDevice(new TimerAction(timer));
110                 }
111                 break;
112             case VentaAirBindingConstants.CHANNEL_SLEEP_MODE:
113                 if (command instanceof OnOffType) {
114                     dispatchActionToDevice(new SleepModeAction(command == OnOffType.ON));
115                 }
116                 break;
117             case VentaAirBindingConstants.CHANNEL_BOOST:
118                 if (command instanceof OnOffType) {
119                     dispatchActionToDevice(new BoostAction(command == OnOffType.ON));
120                 }
121                 break;
122             case VentaAirBindingConstants.CHANNEL_CHILD_LOCK:
123                 if (command instanceof OnOffType) {
124                     dispatchActionToDevice(new ChildLockAction(command == OnOffType.ON));
125                 }
126                 break;
127             case VentaAirBindingConstants.CHANNEL_AUTOMATIC:
128                 if (command instanceof OnOffType) {
129                     dispatchActionToDevice(new AutomaticAction(command == OnOffType.ON));
130                 }
131                 break;
132
133             default:
134                 break;
135         }
136     }
137
138     @Override
139     public void initialize() {
140         config = getConfigAs(VentaAirDeviceConfiguration.class);
141
142         updateStatus(ThingStatus.UNKNOWN);
143
144         String configErrorMessage;
145         if ((configErrorMessage = validateConfig()) != null) {
146             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configErrorMessage);
147             return;
148         }
149
150         Header header = new Header(config.macAddress, config.deviceType.intValue(), config.hash.toString(), "openHAB");
151
152         communicator = new Communicator(config.ipAddress, header, config.pollingTime, new StateUpdatedCallback());
153         communicator.startPollDataFromDevice(scheduler);
154     }
155
156     private @Nullable String validateConfig() {
157         if (config.ipAddress.isEmpty()) {
158             return "IP address not set";
159         }
160         if (config.macAddress.isEmpty()) {
161             return "Mac Address not set, use discovery to find the correct one";
162         }
163         if (BigDecimal.ZERO.equals(config.deviceType)) {
164             return "Device Type not set, use discovery to find the correct one";
165         }
166         if (config.pollingTime.compareTo(BigDecimal.ZERO) <= 0) {
167             return "Polling time has to be larger than 0 seconds";
168         }
169
170         return null;
171     }
172
173     private void dispatchActionToDevice(Action action) {
174         Communicator localCommunicator = communicator;
175         if (localCommunicator != null) {
176             logger.debug("Dispatching Action={} to the device", action.getClass());
177             try {
178                 localCommunicator.sendActionToDevice(action);
179             } catch (IOException e) {
180                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
181                 return;
182             }
183             localCommunicator.pollDataFromDevice();
184         } else {
185             logger.error("Should send action={} to device but communicator is not available.", action.getClass());
186         }
187     }
188
189     private void refreshChannelFromCache(ChannelUID channelUID) {
190         State cachedState = channelValueCache.get(channelUID.getId());
191         if (cachedState != null) {
192             updateState(channelUID, cachedState);
193         }
194     }
195
196     private void updateProperties(Info info) {
197         Thing thing = getThing();
198         thing.setProperty("SWDisplay", info.getSwDisplay());
199         thing.setProperty("SWPower", info.getSwPower());
200         thing.setProperty("SWTouch", info.getSwTouch());
201         thing.setProperty("SWWIFI", info.getSwWIFI());
202     }
203
204     @Override
205     public void dispose() {
206         Communicator localCommunicator = communicator;
207         if (localCommunicator != null) {
208             localCommunicator.stopPollDataFromDevice();
209         }
210         communicator = null;
211     }
212
213     class StateUpdatedCallback {
214         /**
215          * Method to pass the data received from the device to the handler
216          *
217          * @param message - message containing the parsed data from the device
218          */
219         public void stateUpdated(DeviceInfoMessage message) {
220             if (messageIsEmpty(message)) {
221                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
222                         "Please allow openHAB to access your device");
223                 return;
224             }
225
226             AllActions actions = message.getCurrentActions();
227
228             Unit<Temperature> temperatureUnit = SIUnits.CELSIUS;
229
230             if (actions != null) {
231                 OnOffType powerState = OnOffType.from(actions.isPower());
232                 updateState(VentaAirBindingConstants.CHANNEL_POWER, powerState);
233                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_POWER, powerState);
234
235                 DecimalType fanspeedState = new DecimalType(actions.getFanSpeed());
236                 updateState(VentaAirBindingConstants.CHANNEL_FAN_SPEED, fanspeedState);
237                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_FAN_SPEED, fanspeedState);
238
239                 DecimalType targetHumState = new DecimalType(actions.getTargetHum());
240                 updateState(VentaAirBindingConstants.CHANNEL_TARGET_HUMIDITY, targetHumState);
241                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_TARGET_HUMIDITY, targetHumState);
242
243                 DecimalType timerState = new DecimalType(actions.getTimer());
244                 updateState(VentaAirBindingConstants.CHANNEL_TIMER, timerState);
245                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_TIMER, timerState);
246
247                 OnOffType sleepModeState = OnOffType.from(actions.isSleepMode());
248                 updateState(VentaAirBindingConstants.CHANNEL_SLEEP_MODE, sleepModeState);
249                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_SLEEP_MODE, sleepModeState);
250
251                 OnOffType boostState = OnOffType.from(actions.isBoost());
252                 updateState(VentaAirBindingConstants.CHANNEL_BOOST, boostState);
253                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_BOOST, boostState);
254
255                 OnOffType childLockState = OnOffType.from(actions.isChildLock());
256                 updateState(VentaAirBindingConstants.CHANNEL_CHILD_LOCK, childLockState);
257                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_CHILD_LOCK, childLockState);
258
259                 OnOffType automaticState = OnOffType.from(actions.isAutomatic());
260                 updateState(VentaAirBindingConstants.CHANNEL_AUTOMATIC, automaticState);
261                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_AUTOMATIC, automaticState);
262
263                 temperatureUnit = actions.getTempUnit() == 0 ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT;
264             }
265
266             Measurements measurements = message.getMeasurements();
267
268             if (measurements != null) {
269                 QuantityType<Temperature> temperatureState = new QuantityType<>(measurements.getTemperature(),
270                         temperatureUnit);
271                 updateState(VentaAirBindingConstants.CHANNEL_TEMPERATURE, temperatureState);
272                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_TEMPERATURE, temperatureState);
273
274                 QuantityType<Dimensionless> humidityState = new QuantityType<>(measurements.getHumidity(),
275                         Units.PERCENT);
276                 updateState(VentaAirBindingConstants.CHANNEL_HUMIDITY, humidityState);
277                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_HUMIDITY, humidityState);
278
279                 QuantityType<Density> pm25State = new QuantityType<>(measurements.getDust(),
280                         Units.MICROGRAM_PER_CUBICMETRE);
281                 updateState(VentaAirBindingConstants.CHANNEL_PM25, pm25State);
282                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_PM25, pm25State);
283
284                 DecimalType waterLevelState = new DecimalType(measurements.getWaterLevel());
285                 updateState(VentaAirBindingConstants.CHANNEL_WATERLEVEL, waterLevelState);
286                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_WATERLEVEL, waterLevelState);
287
288                 DecimalType fanRPMstate = new DecimalType(measurements.getFanRpm());
289                 updateState(VentaAirBindingConstants.CHANNEL_FAN_RPM, fanRPMstate);
290                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_FAN_RPM, fanRPMstate);
291             }
292
293             Info info = message.getInfo();
294             if (info != null) {
295                 int opHours = info.getOperationT() * 5 / 60;
296                 int discReplaceHours = info.getDiscIonT() * 5 / 60;
297                 int cleaningHours = info.getCleaningT() * 5 / 60;
298
299                 QuantityType<Time> opHoursState = new QuantityType<Time>(opHours, Units.HOUR);
300                 updateState(VentaAirBindingConstants.CHANNEL_OPERATION_TIME, opHoursState);
301                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_OPERATION_TIME, opHoursState);
302
303                 QuantityType<Time> discReplaceHoursState = new QuantityType<Time>(2200 - discReplaceHours, Units.HOUR);
304                 updateState(VentaAirBindingConstants.CHANNEL_DISC_REPLACE_TIME, discReplaceHoursState);
305                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_DISC_REPLACE_TIME, discReplaceHoursState);
306
307                 QuantityType<Time> cleaningHoursState = new QuantityType<Time>(4400 - cleaningHours, Units.HOUR);
308                 updateState(VentaAirBindingConstants.CHANNEL_CLEANING_TIME, cleaningHoursState);
309                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_CLEANING_TIME, cleaningHoursState);
310
311                 OnOffType cleanModeState = info.isCleanMode() ? OnOffType.ON : OnOffType.OFF;
312                 updateState(VentaAirBindingConstants.CHANNEL_CLEAN_MODE, cleanModeState);
313                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_CLEAN_MODE, cleanModeState);
314
315                 QuantityType<Time> timerTimePassedState = new QuantityType<Time>(info.getTimerT(), Units.MINUTE);
316                 updateState(VentaAirBindingConstants.CHANNEL_TIMER_TIME_PASSED, timerTimePassedState);
317                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_TIMER_TIME_PASSED, timerTimePassedState);
318
319                 QuantityType<Time> serviceTimeState = new QuantityType<Time>(info.getServiceT(), Units.MINUTE);
320                 updateState(VentaAirBindingConstants.CHANNEL_SERVICE_TIME, serviceTimeState);
321                 channelValueCache.put(VentaAirBindingConstants.CHANNEL_SERVICE_TIME, serviceTimeState);
322
323                 updateProperties(info);
324             }
325
326             updateStatus(ThingStatus.ONLINE);
327         }
328
329         private boolean messageIsEmpty(DeviceInfoMessage message) {
330             return (message.getCurrentActions() == null && message.getInfo() == null
331                     && message.getMeasurements() == null);
332         }
333
334         /**
335          * Method to inform the handler about a communication issue
336          */
337         public void communicationProblem() {
338             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
339         }
340     }
341 }