]> git.basschouten.com Git - openhab-addons.git/blob
0448b8c46c94318d4ed183c9b17a0d9af0448c65
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.melcloud.internal.handler;
14
15 import static org.openhab.binding.melcloud.internal.MelCloudBindingConstants.*;
16 import static org.openhab.core.library.unit.SIUnits.CELSIUS;
17
18 import java.math.BigDecimal;
19 import java.math.RoundingMode;
20 import java.time.LocalDateTime;
21 import java.time.ZoneId;
22 import java.time.ZoneOffset;
23 import java.time.ZonedDateTime;
24 import java.time.format.DateTimeFormatter;
25 import java.util.Optional;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
28
29 import javax.measure.quantity.Temperature;
30
31 import org.openhab.binding.melcloud.internal.api.json.DeviceStatus;
32 import org.openhab.binding.melcloud.internal.config.AcDeviceConfig;
33 import org.openhab.binding.melcloud.internal.exceptions.MelCloudCommException;
34 import org.openhab.binding.melcloud.internal.exceptions.MelCloudLoginException;
35 import org.openhab.core.library.types.DateTimeType;
36 import org.openhab.core.library.types.DecimalType;
37 import org.openhab.core.library.types.OnOffType;
38 import org.openhab.core.library.types.QuantityType;
39 import org.openhab.core.library.types.StringType;
40 import org.openhab.core.library.unit.SIUnits;
41 import org.openhab.core.thing.Bridge;
42 import org.openhab.core.thing.Channel;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingStatus;
46 import org.openhab.core.thing.ThingStatusDetail;
47 import org.openhab.core.thing.ThingStatusInfo;
48 import org.openhab.core.thing.binding.BaseThingHandler;
49 import org.openhab.core.thing.binding.ThingHandler;
50 import org.openhab.core.types.Command;
51 import org.openhab.core.types.RefreshType;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 /**
56  * The {@link MelCloudDeviceHandler} is responsible for handling commands, which are
57  * sent to one of the channels.
58  *
59  * @author Luca Calcaterra - Initial contribution
60  * @author Pauli Anttila - Refactoring
61  */
62
63 public class MelCloudDeviceHandler extends BaseThingHandler {
64
65     private static final int EFFECTIVE_FLAG_POWER = 0x01;
66     private static final int EFFECTIVE_FLAG_OPERATION_MODE = 0x02;
67     private static final int EFFECTIVE_FLAG_TEMPERATURE = 0x04;
68     private static final int EFFECTIVE_FLAG_FAN_SPEED = 0x08;
69     private static final int EFFECTIVE_FLAG_VANE_VERTICAL = 0x10;
70     private static final int EFFECTIVE_FLAG_VANE_HORIZONTAL = 0x100;
71
72     private final Logger logger = LoggerFactory.getLogger(MelCloudDeviceHandler.class);
73     private AcDeviceConfig config;
74     private MelCloudAccountHandler melCloudHandler;
75     private DeviceStatus deviceStatus;
76     private ScheduledFuture<?> refreshTask;
77
78     public MelCloudDeviceHandler(Thing thing) {
79         super(thing);
80     }
81
82     @Override
83     public void initialize() {
84         logger.debug("Initializing {} handler.", getThing().getThingTypeUID());
85
86         Bridge bridge = getBridge();
87         if (bridge == null) {
88             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridge Not set");
89             return;
90         }
91
92         config = getConfigAs(AcDeviceConfig.class);
93         logger.debug("A.C. device config: {}", config);
94
95         initializeBridge(bridge.getHandler(), bridge.getStatus());
96     }
97
98     @Override
99     public void dispose() {
100         logger.debug("Running dispose()");
101         if (refreshTask != null) {
102             refreshTask.cancel(true);
103             refreshTask = null;
104         }
105         melCloudHandler = null;
106     }
107
108     @Override
109     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
110         logger.debug("bridgeStatusChanged {} for thing {}", bridgeStatusInfo, getThing().getUID());
111         Bridge bridge = getBridge();
112         if (bridge != null) {
113             initializeBridge(bridge.getHandler(), bridgeStatusInfo.getStatus());
114         }
115     }
116
117     private void initializeBridge(ThingHandler thingHandler, ThingStatus bridgeStatus) {
118         logger.debug("initializeBridge {} for thing {}", bridgeStatus, getThing().getUID());
119
120         if (thingHandler != null && bridgeStatus != null) {
121             melCloudHandler = (MelCloudAccountHandler) thingHandler;
122
123             if (bridgeStatus == ThingStatus.ONLINE) {
124                 updateStatus(ThingStatus.ONLINE);
125                 startAutomaticRefresh();
126             } else {
127                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
128             }
129         } else {
130             updateStatus(ThingStatus.OFFLINE);
131         }
132     }
133
134     @Override
135     public void handleCommand(ChannelUID channelUID, Command command) {
136         logger.debug("Received command '{}' to channel {}", command, channelUID);
137
138         if (command instanceof RefreshType) {
139             logger.debug("Refresh command not supported");
140             return;
141         }
142
143         if (melCloudHandler == null) {
144             logger.warn("No connection to MELCloud available, ignoring command");
145             return;
146         }
147
148         if (deviceStatus == null) {
149             logger.info("No initial data available, bridge is probably offline. Ignoring command");
150             return;
151         }
152
153         DeviceStatus cmdtoSend = getDeviceStatusCopy(deviceStatus);
154         cmdtoSend.setEffectiveFlags(0);
155
156         switch (channelUID.getId()) {
157             case CHANNEL_POWER:
158                 cmdtoSend.setPower(command == OnOffType.ON);
159                 cmdtoSend.setEffectiveFlags(EFFECTIVE_FLAG_POWER);
160                 break;
161             case CHANNEL_OPERATION_MODE:
162                 cmdtoSend.setOperationMode(Integer.parseInt(command.toString()));
163                 cmdtoSend.setEffectiveFlags(EFFECTIVE_FLAG_OPERATION_MODE);
164                 break;
165             case CHANNEL_SET_TEMPERATURE:
166                 BigDecimal val = null;
167                 if (command instanceof QuantityType) {
168                     QuantityType<Temperature> quantity = new QuantityType<Temperature>(command.toString())
169                             .toUnit(CELSIUS);
170                     if (quantity != null) {
171                         val = quantity.toBigDecimal().setScale(1, RoundingMode.HALF_UP);
172                         // round nearest .5
173                         double v = Math.round(val.doubleValue() * 2) / 2.0;
174                         cmdtoSend.setSetTemperature(v);
175                         cmdtoSend.setEffectiveFlags(EFFECTIVE_FLAG_TEMPERATURE);
176                     }
177                 }
178                 if (val == null) {
179                     logger.debug("Can't convert '{}' to set temperature", command);
180                 }
181                 break;
182             case CHANNEL_FAN_SPEED:
183                 cmdtoSend.setSetFanSpeed(Integer.parseInt(command.toString()));
184                 cmdtoSend.setEffectiveFlags(EFFECTIVE_FLAG_FAN_SPEED);
185                 break;
186             case CHANNEL_VANE_VERTICAL:
187                 cmdtoSend.setVaneVertical(Integer.parseInt(command.toString()));
188                 cmdtoSend.setEffectiveFlags(EFFECTIVE_FLAG_VANE_VERTICAL);
189                 break;
190             case CHANNEL_VANE_HORIZONTAL:
191                 cmdtoSend.setVaneHorizontal(Integer.parseInt(command.toString()));
192                 cmdtoSend.setEffectiveFlags(EFFECTIVE_FLAG_VANE_HORIZONTAL);
193                 break;
194             default:
195                 logger.debug("Read-only or unknown channel {}, skipping update", channelUID);
196         }
197
198         if (cmdtoSend.getEffectiveFlags() > 0) {
199             cmdtoSend.setHasPendingCommand(true);
200             cmdtoSend.setDeviceID(config.deviceID);
201             try {
202                 DeviceStatus newDeviceStatus = melCloudHandler.sendDeviceStatus(cmdtoSend);
203                 updateChannels(newDeviceStatus);
204             } catch (MelCloudLoginException e) {
205                 logger.warn("Command '{}' to channel '{}' failed due to login error, reason {}. ", command, channelUID,
206                         e.getMessage(), e);
207             } catch (MelCloudCommException e) {
208                 logger.warn("Command '{}' to channel '{}' failed, reason {}. ", command, channelUID, e.getMessage(), e);
209             }
210         } else {
211             logger.debug("Nothing to send");
212         }
213     }
214
215     private DeviceStatus getDeviceStatusCopy(DeviceStatus deviceStatus) {
216         DeviceStatus copy = new DeviceStatus();
217         synchronized (this) {
218             copy.setPower(deviceStatus.getPower());
219             copy.setOperationMode(deviceStatus.getOperationMode());
220             copy.setSetTemperature(deviceStatus.getSetTemperature());
221             copy.setSetFanSpeed(deviceStatus.getSetFanSpeed());
222             copy.setVaneVertical(deviceStatus.getVaneVertical());
223             copy.setVaneHorizontal(deviceStatus.getVaneHorizontal());
224             copy.setEffectiveFlags(deviceStatus.getEffectiveFlags());
225             copy.setHasPendingCommand(deviceStatus.getHasPendingCommand());
226             copy.setDeviceID(deviceStatus.getDeviceID());
227         }
228         return copy;
229     }
230
231     private void startAutomaticRefresh() {
232         if (refreshTask == null || refreshTask.isCancelled()) {
233             refreshTask = scheduler.scheduleWithFixedDelay(this::getDeviceDataAndUpdateChannels, 1,
234                     config.pollingInterval, TimeUnit.SECONDS);
235         }
236     }
237
238     private void getDeviceDataAndUpdateChannels() {
239         if (melCloudHandler.isConnected()) {
240             logger.debug("Update device '{}' channels", getThing().getThingTypeUID());
241             try {
242                 DeviceStatus newDeviceStatus = melCloudHandler.fetchDeviceStatus(config.deviceID,
243                         Optional.ofNullable(config.buildingID));
244                 updateChannels(newDeviceStatus);
245             } catch (MelCloudLoginException e) {
246                 logger.debug("Login error occurred during device '{}' polling, reason {}. ",
247                         getThing().getThingTypeUID(), e.getMessage(), e);
248             } catch (MelCloudCommException e) {
249                 logger.debug("Error occurred during device '{}' polling, reason {}. ", getThing().getThingTypeUID(),
250                         e.getMessage(), e);
251             }
252         } else {
253             logger.debug("Connection to MELCloud is not open, skipping periodic update");
254         }
255     }
256
257     private synchronized void updateChannels(DeviceStatus newDeviceStatus) {
258         deviceStatus = newDeviceStatus;
259         for (Channel channel : getThing().getChannels()) {
260             updateChannels(channel.getUID().getId(), deviceStatus);
261         }
262     }
263
264     private void updateChannels(String channelId, DeviceStatus deviceStatus) {
265         switch (channelId) {
266             case CHANNEL_POWER:
267                 updateState(CHANNEL_POWER, OnOffType.from(deviceStatus.getPower()));
268                 break;
269             case CHANNEL_OPERATION_MODE:
270                 updateState(CHANNEL_OPERATION_MODE, new StringType(deviceStatus.getOperationMode().toString()));
271                 break;
272             case CHANNEL_SET_TEMPERATURE:
273                 updateState(CHANNEL_SET_TEMPERATURE,
274                         new QuantityType<>(deviceStatus.getSetTemperature(), SIUnits.CELSIUS));
275                 break;
276             case CHANNEL_FAN_SPEED:
277                 updateState(CHANNEL_FAN_SPEED, new StringType(deviceStatus.getSetFanSpeed().toString()));
278                 break;
279             case CHANNEL_VANE_HORIZONTAL:
280                 updateState(CHANNEL_VANE_HORIZONTAL, new StringType(deviceStatus.getVaneHorizontal().toString()));
281                 break;
282             case CHANNEL_VANE_VERTICAL:
283                 updateState(CHANNEL_VANE_VERTICAL, new StringType(deviceStatus.getVaneVertical().toString()));
284                 break;
285             case CHANNEL_OFFLINE:
286                 updateState(CHANNEL_OFFLINE, OnOffType.from(deviceStatus.getOffline()));
287                 break;
288             case CHANNEL_HAS_PENDING_COMMAND:
289                 updateState(CHANNEL_HAS_PENDING_COMMAND, OnOffType.from(deviceStatus.getHasPendingCommand()));
290                 break;
291             case CHANNEL_ROOM_TEMPERATURE:
292                 updateState(CHANNEL_ROOM_TEMPERATURE, new DecimalType(deviceStatus.getRoomTemperature()));
293                 break;
294             case CHANNEL_LAST_COMMUNICATION:
295                 updateState(CHANNEL_LAST_COMMUNICATION,
296                         new DateTimeType(convertDateTime(deviceStatus.getLastCommunication())));
297                 break;
298             case CHANNEL_NEXT_COMMUNICATION:
299                 updateState(CHANNEL_NEXT_COMMUNICATION,
300                         new DateTimeType(convertDateTime(deviceStatus.getNextCommunication())));
301                 break;
302         }
303     }
304
305     private ZonedDateTime convertDateTime(String dateTime) {
306         return ZonedDateTime.ofInstant(LocalDateTime.parse(dateTime, DateTimeFormatter.ISO_LOCAL_DATE_TIME),
307                 ZoneOffset.UTC, ZoneId.systemDefault());
308     }
309 }