2 * Copyright (c) 2010-2023 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.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;
60 * The {@link VentaThingHandler} is responsible for handling commands, which are
61 * sent to one of the channels.
63 * @author Stefan Triller - Initial contribution
66 public class VentaThingHandler extends BaseThingHandler {
68 private final Logger logger = LoggerFactory.getLogger(VentaThingHandler.class);
70 private VentaAirDeviceConfiguration config = new VentaAirDeviceConfiguration();
72 private @Nullable Communicator communicator;
74 private Map<String, State> channelValueCache = new HashMap<>();
76 public VentaThingHandler(Thing thing) {
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);
88 switch (channelUID.getId()) {
89 case VentaAirBindingConstants.CHANNEL_POWER:
90 if (command instanceof OnOffType) {
91 dispatchActionToDevice(new PowerAction(command == OnOffType.ON));
94 case VentaAirBindingConstants.CHANNEL_FAN_SPEED:
95 if (command instanceof DecimalType decimalCommand) {
96 int fanStage = decimalCommand.intValue();
97 dispatchActionToDevice(new FanAction(fanStage));
100 case VentaAirBindingConstants.CHANNEL_TARGET_HUMIDITY:
101 if (command instanceof DecimalType decimalCommand) {
102 int targetHumidity = decimalCommand.intValue();
103 dispatchActionToDevice(new HumidityAction(targetHumidity));
106 case VentaAirBindingConstants.CHANNEL_TIMER:
107 if (command instanceof DecimalType decimalCommand) {
108 int timer = decimalCommand.intValue();
109 dispatchActionToDevice(new TimerAction(timer));
112 case VentaAirBindingConstants.CHANNEL_SLEEP_MODE:
113 if (command instanceof OnOffType) {
114 dispatchActionToDevice(new SleepModeAction(command == OnOffType.ON));
117 case VentaAirBindingConstants.CHANNEL_BOOST:
118 if (command instanceof OnOffType) {
119 dispatchActionToDevice(new BoostAction(command == OnOffType.ON));
122 case VentaAirBindingConstants.CHANNEL_CHILD_LOCK:
123 if (command instanceof OnOffType) {
124 dispatchActionToDevice(new ChildLockAction(command == OnOffType.ON));
127 case VentaAirBindingConstants.CHANNEL_AUTOMATIC:
128 if (command instanceof OnOffType) {
129 dispatchActionToDevice(new AutomaticAction(command == OnOffType.ON));
139 public void initialize() {
140 config = getConfigAs(VentaAirDeviceConfiguration.class);
142 updateStatus(ThingStatus.UNKNOWN);
144 String configErrorMessage;
145 if ((configErrorMessage = validateConfig()) != null) {
146 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configErrorMessage);
150 Header header = new Header(config.macAddress, config.deviceType.intValue(), config.hash.toString(), "openHAB");
152 communicator = new Communicator(config.ipAddress, header, config.pollingTime, new StateUpdatedCallback());
153 communicator.startPollDataFromDevice(scheduler);
156 private @Nullable String validateConfig() {
157 if (config.ipAddress.isEmpty()) {
158 return "IP address not set";
160 if (config.macAddress.isEmpty()) {
161 return "Mac Address not set, use discovery to find the correct one";
163 if (BigDecimal.ZERO.equals(config.deviceType)) {
164 return "Device Type not set, use discovery to find the correct one";
166 if (config.pollingTime.compareTo(BigDecimal.ZERO) <= 0) {
167 return "Polling time has to be larger than 0 seconds";
173 private void dispatchActionToDevice(Action action) {
174 Communicator localCommunicator = communicator;
175 if (localCommunicator != null) {
176 logger.debug("Dispatching Action={} to the device", action.getClass());
178 localCommunicator.sendActionToDevice(action);
179 } catch (IOException e) {
180 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
183 localCommunicator.pollDataFromDevice();
185 logger.error("Should send action={} to device but communicator is not available.", action.getClass());
189 private void refreshChannelFromCache(ChannelUID channelUID) {
190 State cachedState = channelValueCache.get(channelUID.getId());
191 if (cachedState != null) {
192 updateState(channelUID, cachedState);
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());
205 public void dispose() {
206 Communicator localCommunicator = communicator;
207 if (localCommunicator != null) {
208 localCommunicator.stopPollDataFromDevice();
213 class StateUpdatedCallback {
215 * Method to pass the data received from the device to the handler
217 * @param message - message containing the parsed data from the device
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");
226 AllActions actions = message.getCurrentActions();
228 Unit<Temperature> temperatureUnit = SIUnits.CELSIUS;
230 if (actions != null) {
231 OnOffType powerState = OnOffType.from(actions.isPower());
232 updateState(VentaAirBindingConstants.CHANNEL_POWER, powerState);
233 channelValueCache.put(VentaAirBindingConstants.CHANNEL_POWER, powerState);
235 DecimalType fanspeedState = new DecimalType(actions.getFanSpeed());
236 updateState(VentaAirBindingConstants.CHANNEL_FAN_SPEED, fanspeedState);
237 channelValueCache.put(VentaAirBindingConstants.CHANNEL_FAN_SPEED, fanspeedState);
239 DecimalType targetHumState = new DecimalType(actions.getTargetHum());
240 updateState(VentaAirBindingConstants.CHANNEL_TARGET_HUMIDITY, targetHumState);
241 channelValueCache.put(VentaAirBindingConstants.CHANNEL_TARGET_HUMIDITY, targetHumState);
243 DecimalType timerState = new DecimalType(actions.getTimer());
244 updateState(VentaAirBindingConstants.CHANNEL_TIMER, timerState);
245 channelValueCache.put(VentaAirBindingConstants.CHANNEL_TIMER, timerState);
247 OnOffType sleepModeState = OnOffType.from(actions.isSleepMode());
248 updateState(VentaAirBindingConstants.CHANNEL_SLEEP_MODE, sleepModeState);
249 channelValueCache.put(VentaAirBindingConstants.CHANNEL_SLEEP_MODE, sleepModeState);
251 OnOffType boostState = OnOffType.from(actions.isBoost());
252 updateState(VentaAirBindingConstants.CHANNEL_BOOST, boostState);
253 channelValueCache.put(VentaAirBindingConstants.CHANNEL_BOOST, boostState);
255 OnOffType childLockState = OnOffType.from(actions.isChildLock());
256 updateState(VentaAirBindingConstants.CHANNEL_CHILD_LOCK, childLockState);
257 channelValueCache.put(VentaAirBindingConstants.CHANNEL_CHILD_LOCK, childLockState);
259 OnOffType automaticState = OnOffType.from(actions.isAutomatic());
260 updateState(VentaAirBindingConstants.CHANNEL_AUTOMATIC, automaticState);
261 channelValueCache.put(VentaAirBindingConstants.CHANNEL_AUTOMATIC, automaticState);
263 temperatureUnit = actions.getTempUnit() == 0 ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT;
266 Measurements measurements = message.getMeasurements();
268 if (measurements != null) {
269 QuantityType<Temperature> temperatureState = new QuantityType<>(measurements.getTemperature(),
271 updateState(VentaAirBindingConstants.CHANNEL_TEMPERATURE, temperatureState);
272 channelValueCache.put(VentaAirBindingConstants.CHANNEL_TEMPERATURE, temperatureState);
274 QuantityType<Dimensionless> humidityState = new QuantityType<>(measurements.getHumidity(),
276 updateState(VentaAirBindingConstants.CHANNEL_HUMIDITY, humidityState);
277 channelValueCache.put(VentaAirBindingConstants.CHANNEL_HUMIDITY, humidityState);
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);
284 DecimalType waterLevelState = new DecimalType(measurements.getWaterLevel());
285 updateState(VentaAirBindingConstants.CHANNEL_WATERLEVEL, waterLevelState);
286 channelValueCache.put(VentaAirBindingConstants.CHANNEL_WATERLEVEL, waterLevelState);
288 DecimalType fanRPMstate = new DecimalType(measurements.getFanRpm());
289 updateState(VentaAirBindingConstants.CHANNEL_FAN_RPM, fanRPMstate);
290 channelValueCache.put(VentaAirBindingConstants.CHANNEL_FAN_RPM, fanRPMstate);
293 Info info = message.getInfo();
295 int opHours = info.getOperationT() * 5 / 60;
296 int discReplaceHours = info.getDiscIonT() * 5 / 60;
297 int cleaningHours = info.getCleaningT() * 5 / 60;
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);
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);
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);
311 OnOffType cleanModeState = OnOffType.from(info.isCleanMode());
312 updateState(VentaAirBindingConstants.CHANNEL_CLEAN_MODE, cleanModeState);
313 channelValueCache.put(VentaAirBindingConstants.CHANNEL_CLEAN_MODE, cleanModeState);
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);
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);
323 updateProperties(info);
326 updateStatus(ThingStatus.ONLINE);
329 private boolean messageIsEmpty(DeviceInfoMessage message) {
330 return (message.getCurrentActions() == null && message.getInfo() == null
331 && message.getMeasurements() == null);
335 * Method to inform the handler about a communication issue
337 public void communicationProblem() {
338 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);