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.ventaair.internal;
15 import java.io.IOException;
16 import java.math.BigDecimal;
17 import java.util.HashMap;
20 import javax.measure.Unit;
21 import javax.measure.quantity.Dimensionless;
22 import javax.measure.quantity.Temperature;
23 import javax.measure.quantity.Time;
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.types.DecimalType;
42 import org.openhab.core.library.types.OnOffType;
43 import org.openhab.core.library.types.QuantityType;
44 import org.openhab.core.library.unit.ImperialUnits;
45 import org.openhab.core.library.unit.SIUnits;
46 import org.openhab.core.library.unit.Units;
47 import org.openhab.core.thing.ChannelUID;
48 import org.openhab.core.thing.Thing;
49 import org.openhab.core.thing.ThingStatus;
50 import org.openhab.core.thing.ThingStatusDetail;
51 import org.openhab.core.thing.binding.BaseThingHandler;
52 import org.openhab.core.types.Command;
53 import org.openhab.core.types.RefreshType;
54 import org.openhab.core.types.State;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
59 * The {@link VentaThingHandler} is responsible for handling commands, which are
60 * sent to one of the channels.
62 * @author Stefan Triller - Initial contribution
65 public class VentaThingHandler extends BaseThingHandler {
67 private final Logger logger = LoggerFactory.getLogger(VentaThingHandler.class);
69 private VentaAirDeviceConfiguration config = new VentaAirDeviceConfiguration();
71 private @Nullable Communicator communicator;
73 private Map<String, State> channelValueCache = new HashMap<>();
75 public VentaThingHandler(Thing thing) {
80 public void handleCommand(ChannelUID channelUID, Command command) {
81 logger.debug("Handle command={} for channel={} with channelID={}", command, channelUID, channelUID.getId());
82 if (command instanceof RefreshType) {
83 refreshChannelFromCache(channelUID);
87 switch (channelUID.getId()) {
88 case VentaAirBindingConstants.CHANNEL_POWER:
89 if (command instanceof OnOffType) {
90 dispatchActionToDevice(new PowerAction(command == OnOffType.ON));
93 case VentaAirBindingConstants.CHANNEL_FAN_SPEED:
94 if (command instanceof DecimalType) {
95 int fanStage = ((DecimalType) command).intValue();
96 dispatchActionToDevice(new FanAction(fanStage));
99 case VentaAirBindingConstants.CHANNEL_TARGET_HUMIDITY:
100 if (command instanceof DecimalType) {
101 int targetHumidity = ((DecimalType) command).intValue();
102 dispatchActionToDevice(new HumidityAction(targetHumidity));
105 case VentaAirBindingConstants.CHANNEL_TIMER:
106 if (command instanceof DecimalType) {
107 int timer = ((DecimalType) command).intValue();
108 dispatchActionToDevice(new TimerAction(timer));
111 case VentaAirBindingConstants.CHANNEL_SLEEP_MODE:
112 if (command instanceof OnOffType) {
113 dispatchActionToDevice(new SleepModeAction(command == OnOffType.ON));
116 case VentaAirBindingConstants.CHANNEL_BOOST:
117 if (command instanceof OnOffType) {
118 dispatchActionToDevice(new BoostAction(command == OnOffType.ON));
121 case VentaAirBindingConstants.CHANNEL_CHILD_LOCK:
122 if (command instanceof OnOffType) {
123 dispatchActionToDevice(new ChildLockAction(command == OnOffType.ON));
126 case VentaAirBindingConstants.CHANNEL_AUTOMATIC:
127 if (command instanceof OnOffType) {
128 dispatchActionToDevice(new AutomaticAction(command == OnOffType.ON));
138 public void initialize() {
139 config = getConfigAs(VentaAirDeviceConfiguration.class);
141 updateStatus(ThingStatus.UNKNOWN);
143 String configErrorMessage;
144 if ((configErrorMessage = validateConfig()) != null) {
145 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configErrorMessage);
149 Header header = new Header(config.macAddress, config.deviceType.intValue(), config.hash.toString(), "openHAB");
151 communicator = new Communicator(config.ipAddress, header, config.pollingTime, new StateUpdatedCallback());
152 communicator.startPollDataFromDevice(scheduler);
155 private @Nullable String validateConfig() {
156 if (config.ipAddress.isEmpty()) {
157 return "IP address not set";
159 if (config.macAddress.isEmpty()) {
160 return "Mac Address not set, use discovery to find the correct one";
162 if (config.deviceType == BigDecimal.ZERO) {
163 return "Device Type not set, use discovery to find the correct one";
165 if (config.pollingTime.compareTo(BigDecimal.ZERO) <= 0) {
166 return "Polling time has to be larger than 0 seconds";
172 private void dispatchActionToDevice(Action action) {
173 Communicator localCommunicator = communicator;
174 if (localCommunicator != null) {
175 logger.debug("Dispatching Action={} to the device", action.getClass());
177 localCommunicator.sendActionToDevice(action);
178 } catch (IOException e) {
179 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
182 localCommunicator.pollDataFromDevice();
184 logger.error("Should send action={} to device but communicator is not available.", action.getClass());
188 private void refreshChannelFromCache(ChannelUID channelUID) {
189 State cachedState = channelValueCache.get(channelUID.getId());
190 if (cachedState != null) {
191 updateState(channelUID, cachedState);
195 private void updateProperties(Info info) {
196 Thing thing = getThing();
197 thing.setProperty("SWDisplay", info.getSwDisplay());
198 thing.setProperty("SWPower", info.getSwPower());
199 thing.setProperty("SWTouch", info.getSwTouch());
200 thing.setProperty("SWWIFI", info.getSwWIFI());
204 public void dispose() {
205 Communicator localCommunicator = communicator;
206 if (localCommunicator != null) {
207 localCommunicator.stopPollDataFromDevice();
212 class StateUpdatedCallback {
214 * Method to pass the data received from the device to the handler
216 * @param message - message containing the parsed data from the device
218 public void stateUpdated(DeviceInfoMessage message) {
219 if (messageIsEmpty(message)) {
220 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
221 "Please allow openHAB to access your device");
225 AllActions actions = message.getCurrentActions();
227 Unit<Temperature> temperatureUnit = SIUnits.CELSIUS;
229 if (actions != null) {
230 OnOffType powerState = OnOffType.from(actions.isPower());
231 updateState(VentaAirBindingConstants.CHANNEL_POWER, powerState);
232 channelValueCache.put(VentaAirBindingConstants.CHANNEL_POWER, powerState);
234 DecimalType fanspeedState = new DecimalType(actions.getFanSpeed());
235 updateState(VentaAirBindingConstants.CHANNEL_FAN_SPEED, fanspeedState);
236 channelValueCache.put(VentaAirBindingConstants.CHANNEL_FAN_SPEED, fanspeedState);
238 DecimalType targetHumState = new DecimalType(actions.getTargetHum());
239 updateState(VentaAirBindingConstants.CHANNEL_TARGET_HUMIDITY, targetHumState);
240 channelValueCache.put(VentaAirBindingConstants.CHANNEL_TARGET_HUMIDITY, targetHumState);
242 DecimalType timerState = new DecimalType(actions.getTimer());
243 updateState(VentaAirBindingConstants.CHANNEL_TIMER, timerState);
244 channelValueCache.put(VentaAirBindingConstants.CHANNEL_TIMER, timerState);
246 OnOffType sleepModeState = OnOffType.from(actions.isSleepMode());
247 updateState(VentaAirBindingConstants.CHANNEL_SLEEP_MODE, sleepModeState);
248 channelValueCache.put(VentaAirBindingConstants.CHANNEL_SLEEP_MODE, sleepModeState);
250 OnOffType boostState = OnOffType.from(actions.isBoost());
251 updateState(VentaAirBindingConstants.CHANNEL_BOOST, boostState);
252 channelValueCache.put(VentaAirBindingConstants.CHANNEL_BOOST, boostState);
254 OnOffType childLockState = OnOffType.from(actions.isChildLock());
255 updateState(VentaAirBindingConstants.CHANNEL_CHILD_LOCK, childLockState);
256 channelValueCache.put(VentaAirBindingConstants.CHANNEL_CHILD_LOCK, childLockState);
258 OnOffType automaticState = OnOffType.from(actions.isAutomatic());
259 updateState(VentaAirBindingConstants.CHANNEL_AUTOMATIC, automaticState);
260 channelValueCache.put(VentaAirBindingConstants.CHANNEL_AUTOMATIC, automaticState);
262 temperatureUnit = actions.getTempUnit() == 0 ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT;
265 Measurements measurements = message.getMeasurements();
267 if (measurements != null) {
268 QuantityType<Temperature> temperatureState = new QuantityType<>(measurements.getTemperature(),
270 updateState(VentaAirBindingConstants.CHANNEL_TEMPERATURE, temperatureState);
271 channelValueCache.put(VentaAirBindingConstants.CHANNEL_TEMPERATURE, temperatureState);
273 QuantityType<Dimensionless> humidityState = new QuantityType<>(measurements.getHumidity(),
275 updateState(VentaAirBindingConstants.CHANNEL_HUMIDITY, humidityState);
276 channelValueCache.put(VentaAirBindingConstants.CHANNEL_HUMIDITY, humidityState);
278 DecimalType waterLevelState = new DecimalType(measurements.getWaterLevel());
279 updateState(VentaAirBindingConstants.CHANNEL_WATERLEVEL, waterLevelState);
280 channelValueCache.put(VentaAirBindingConstants.CHANNEL_WATERLEVEL, waterLevelState);
282 DecimalType fanRPMstate = new DecimalType(measurements.getFanRpm());
283 updateState(VentaAirBindingConstants.CHANNEL_FAN_RPM, fanRPMstate);
284 channelValueCache.put(VentaAirBindingConstants.CHANNEL_FAN_RPM, fanRPMstate);
287 Info info = message.getInfo();
289 int opHours = info.getOperationT() * 5 / 60;
290 int discReplaceHours = info.getDiscIonT() * 5 / 60;
291 int cleaningHours = info.getCleaningT() * 5 / 60;
293 QuantityType<Time> opHoursState = new QuantityType<Time>(opHours, Units.HOUR);
294 updateState(VentaAirBindingConstants.CHANNEL_OPERATION_TIME, opHoursState);
295 channelValueCache.put(VentaAirBindingConstants.CHANNEL_OPERATION_TIME, opHoursState);
297 QuantityType<Time> discReplaceHoursState = new QuantityType<Time>(2200 - discReplaceHours, Units.HOUR);
298 updateState(VentaAirBindingConstants.CHANNEL_DISC_REPLACE_TIME, discReplaceHoursState);
299 channelValueCache.put(VentaAirBindingConstants.CHANNEL_DISC_REPLACE_TIME, discReplaceHoursState);
301 QuantityType<Time> cleaningHoursState = new QuantityType<Time>(4400 - cleaningHours, Units.HOUR);
302 updateState(VentaAirBindingConstants.CHANNEL_CLEANING_TIME, cleaningHoursState);
303 channelValueCache.put(VentaAirBindingConstants.CHANNEL_CLEANING_TIME, cleaningHoursState);
305 OnOffType cleanModeState = info.isCleanMode() ? OnOffType.ON : OnOffType.OFF;
306 updateState(VentaAirBindingConstants.CHANNEL_CLEAN_MODE, cleanModeState);
307 channelValueCache.put(VentaAirBindingConstants.CHANNEL_CLEAN_MODE, cleanModeState);
309 QuantityType<Time> timerTimePassedState = new QuantityType<Time>(info.getTimerT(), Units.MINUTE);
310 updateState(VentaAirBindingConstants.CHANNEL_TIMER_TIME_PASSED, timerTimePassedState);
311 channelValueCache.put(VentaAirBindingConstants.CHANNEL_TIMER_TIME_PASSED, timerTimePassedState);
313 updateProperties(info);
316 updateStatus(ThingStatus.ONLINE);
319 private boolean messageIsEmpty(DeviceInfoMessage message) {
320 if (message.getCurrentActions() == null && message.getInfo() == null && message.getMeasurements() == null) {
327 * Method to inform the handler about a communication issue
329 public void communicationProblem() {
330 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);