]> git.basschouten.com Git - openhab-addons.git/blob
276679a8a3d833486ad1122ea991424696e903b6
[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.HeatpumpDeviceStatus;
32 import org.openhab.binding.melcloud.internal.config.HeatpumpDeviceConfig;
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.unit.SIUnits;
40 import org.openhab.core.thing.Bridge;
41 import org.openhab.core.thing.Channel;
42 import org.openhab.core.thing.ChannelUID;
43 import org.openhab.core.thing.Thing;
44 import org.openhab.core.thing.ThingStatus;
45 import org.openhab.core.thing.ThingStatusDetail;
46 import org.openhab.core.thing.ThingStatusInfo;
47 import org.openhab.core.thing.binding.BaseThingHandler;
48 import org.openhab.core.thing.binding.ThingHandler;
49 import org.openhab.core.types.Command;
50 import org.openhab.core.types.RefreshType;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 /**
55  * The {@link MelCloudHeatpumpDeviceHandler} is responsible for handling commands, which are
56  * sent to one of the channels.
57  *
58  * @author Wietse van Buitenen - Initial contribution
59  */
60 public class MelCloudHeatpumpDeviceHandler extends BaseThingHandler {
61     private static final long EFFECTIVE_FLAG_POWER = 1L;
62     private static final long EFFECTIVE_FLAG_TEMPERATURE_ZONE1 = 8589934720L;
63     private static final long EFFECTIVE_FLAG_HOTWATER = 65536L;
64
65     private final Logger logger = LoggerFactory.getLogger(MelCloudHeatpumpDeviceHandler.class);
66     private HeatpumpDeviceConfig config;
67     private MelCloudAccountHandler melCloudHandler;
68     private HeatpumpDeviceStatus heatpumpDeviceStatus;
69     private ScheduledFuture<?> refreshTask;
70
71     public MelCloudHeatpumpDeviceHandler(Thing thing) {
72         super(thing);
73     }
74
75     @Override
76     public void initialize() {
77         logger.debug("Initializing {} handler.", getThing().getThingTypeUID());
78
79         Bridge bridge = getBridge();
80         if (bridge == null) {
81             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridge Not set");
82             return;
83         }
84
85         config = getConfigAs(HeatpumpDeviceConfig.class);
86         logger.debug("Heatpump device config: {}", config);
87
88         initializeBridge(bridge.getHandler(), bridge.getStatus());
89     }
90
91     @Override
92     public void dispose() {
93         logger.debug("Running dispose()");
94         if (refreshTask != null) {
95             refreshTask.cancel(true);
96             refreshTask = null;
97         }
98         melCloudHandler = null;
99     }
100
101     @Override
102     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
103         logger.debug("bridgeStatusChanged {} for thing {}", bridgeStatusInfo, getThing().getUID());
104         Bridge bridge = getBridge();
105         if (bridge != null) {
106             initializeBridge(bridge.getHandler(), bridgeStatusInfo.getStatus());
107         }
108     }
109
110     private void initializeBridge(ThingHandler thingHandler, ThingStatus bridgeStatus) {
111         logger.debug("initializeBridge {} for thing {}", bridgeStatus, getThing().getUID());
112
113         if (thingHandler != null && bridgeStatus != null) {
114             melCloudHandler = (MelCloudAccountHandler) thingHandler;
115
116             if (bridgeStatus == ThingStatus.ONLINE) {
117                 updateStatus(ThingStatus.ONLINE);
118                 startAutomaticRefresh();
119             } else {
120                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
121             }
122         } else {
123             updateStatus(ThingStatus.OFFLINE);
124         }
125     }
126
127     @Override
128     public void handleCommand(ChannelUID channelUID, Command command) {
129         logger.debug("Received command '{}' to channel {}", command, channelUID);
130
131         if (command instanceof RefreshType) {
132             logger.debug("Refresh command not supported");
133             return;
134         }
135
136         if (melCloudHandler == null) {
137             logger.warn("No connection to MELCloud available, ignoring command");
138             return;
139         }
140
141         if (heatpumpDeviceStatus == null) {
142             logger.info("No initial data available, bridge is probably offline. Ignoring command");
143             return;
144         }
145
146         HeatpumpDeviceStatus cmdtoSend = getHeatpumpDeviceStatusCopy(heatpumpDeviceStatus);
147         cmdtoSend.setEffectiveFlags(0L);
148
149         switch (channelUID.getId()) {
150             case CHANNEL_POWER:
151                 cmdtoSend.setPower(command == OnOffType.ON);
152                 cmdtoSend.setEffectiveFlags(EFFECTIVE_FLAG_POWER);
153                 break;
154             case CHANNEL_SET_TEMPERATURE_ZONE1:
155                 BigDecimal val = null;
156                 if (command instanceof QuantityType) {
157                     QuantityType<Temperature> quantity = new QuantityType<Temperature>(command.toString())
158                             .toUnit(CELSIUS);
159                     if (quantity != null) {
160                         val = quantity.toBigDecimal().setScale(1, RoundingMode.HALF_UP);
161                         // round nearest .5
162                         double v = Math.round(val.doubleValue() * 2) / 2.0;
163                         cmdtoSend.setSetTemperatureZone1(v);
164                         cmdtoSend.setEffectiveFlags(EFFECTIVE_FLAG_TEMPERATURE_ZONE1);
165                     }
166                 }
167                 if (val == null) {
168                     logger.debug("Can't convert '{}' to set temperature", command);
169                 }
170                 break;
171             case CHANNEL_FORCED_HOTWATERMODE:
172                 cmdtoSend.setForcedHotWaterMode(command == OnOffType.ON);
173                 cmdtoSend.setEffectiveFlags(EFFECTIVE_FLAG_HOTWATER);
174                 break;
175             default:
176                 logger.debug("Read-only or unknown channel {}, skipping update", channelUID);
177         }
178
179         if (cmdtoSend.getEffectiveFlags() > 0) {
180             cmdtoSend.setHasPendingCommand(true);
181             cmdtoSend.setDeviceID(config.deviceID);
182             try {
183                 HeatpumpDeviceStatus newHeatpumpDeviceStatus = melCloudHandler.sendHeatpumpDeviceStatus(cmdtoSend);
184                 updateChannels(newHeatpumpDeviceStatus);
185             } catch (MelCloudLoginException e) {
186                 logger.warn("Command '{}' to channel '{}' failed due to login error, reason {}. ", command, channelUID,
187                         e.getMessage(), e);
188             } catch (MelCloudCommException e) {
189                 logger.warn("Command '{}' to channel '{}' failed, reason {}. ", command, channelUID, e.getMessage(), e);
190             }
191         } else {
192             logger.debug("Nothing to send");
193         }
194     }
195
196     private HeatpumpDeviceStatus getHeatpumpDeviceStatusCopy(HeatpumpDeviceStatus heatpumpDeviceStatus) {
197         HeatpumpDeviceStatus copy = new HeatpumpDeviceStatus();
198         synchronized (this) {
199             copy.setDeviceID(heatpumpDeviceStatus.getDeviceID());
200             copy.setEffectiveFlags(heatpumpDeviceStatus.getEffectiveFlags());
201             copy.setPower(heatpumpDeviceStatus.getPower());
202             copy.setSetTemperatureZone1(heatpumpDeviceStatus.getSetTemperatureZone1());
203             copy.setForcedHotWaterMode(heatpumpDeviceStatus.getForcedHotWaterMode());
204             copy.setHasPendingCommand(heatpumpDeviceStatus.getHasPendingCommand());
205         }
206         return copy;
207     }
208
209     private void startAutomaticRefresh() {
210         if (refreshTask == null || refreshTask.isCancelled()) {
211             refreshTask = scheduler.scheduleWithFixedDelay(this::getDeviceDataAndUpdateChannels, 1,
212                     config.pollingInterval, TimeUnit.SECONDS);
213         }
214     }
215
216     private void getDeviceDataAndUpdateChannels() {
217         if (melCloudHandler.isConnected()) {
218             logger.debug("Update device '{}' channels", getThing().getThingTypeUID());
219             try {
220                 HeatpumpDeviceStatus newHeatpumpDeviceStatus = melCloudHandler
221                         .fetchHeatpumpDeviceStatus(config.deviceID, Optional.ofNullable(config.buildingID));
222                 updateChannels(newHeatpumpDeviceStatus);
223             } catch (MelCloudLoginException e) {
224                 logger.debug("Login error occurred during device '{}' polling, reason {}. ",
225                         getThing().getThingTypeUID(), e.getMessage(), e);
226             } catch (MelCloudCommException e) {
227                 logger.debug("Error occurred during device '{}' polling, reason {}. ", getThing().getThingTypeUID(),
228                         e.getMessage(), e);
229             }
230         } else {
231             logger.debug("Connection to MELCloud is not open, skipping periodic update");
232         }
233     }
234
235     private synchronized void updateChannels(HeatpumpDeviceStatus newHeatpumpDeviceStatus) {
236         heatpumpDeviceStatus = newHeatpumpDeviceStatus;
237         for (Channel channel : getThing().getChannels()) {
238             updateChannels(channel.getUID().getId(), heatpumpDeviceStatus);
239         }
240     }
241
242     private void updateChannels(String channelId, HeatpumpDeviceStatus heatpumpDeviceStatus) {
243         switch (channelId) {
244             case CHANNEL_POWER:
245                 updateState(CHANNEL_POWER, OnOffType.from(heatpumpDeviceStatus.getPower()));
246                 break;
247             case CHANNEL_TANKWATERTEMPERATURE:
248                 updateState(CHANNEL_TANKWATERTEMPERATURE,
249                         new DecimalType(heatpumpDeviceStatus.getTankWaterTemperature()));
250                 break;
251             case CHANNEL_SET_TEMPERATURE_ZONE1:
252                 updateState(CHANNEL_SET_TEMPERATURE_ZONE1,
253                         new QuantityType<>(heatpumpDeviceStatus.getSetTemperatureZone1(), SIUnits.CELSIUS));
254                 break;
255             case CHANNEL_ROOM_TEMPERATURE_ZONE1:
256                 updateState(CHANNEL_ROOM_TEMPERATURE_ZONE1,
257                         new DecimalType(heatpumpDeviceStatus.getRoomTemperatureZone1()));
258                 break;
259             case CHANNEL_FORCED_HOTWATERMODE:
260                 updateState(CHANNEL_FORCED_HOTWATERMODE, OnOffType.from(heatpumpDeviceStatus.getForcedHotWaterMode()));
261                 break;
262             case CHANNEL_LAST_COMMUNICATION:
263                 updateState(CHANNEL_LAST_COMMUNICATION,
264                         new DateTimeType(convertDateTime(heatpumpDeviceStatus.getLastCommunication())));
265                 break;
266             case CHANNEL_NEXT_COMMUNICATION:
267                 updateState(CHANNEL_NEXT_COMMUNICATION,
268                         new DateTimeType(convertDateTime(heatpumpDeviceStatus.getNextCommunication())));
269                 break;
270             case CHANNEL_HAS_PENDING_COMMAND:
271                 updateState(CHANNEL_HAS_PENDING_COMMAND, OnOffType.from(heatpumpDeviceStatus.getHasPendingCommand()));
272                 break;
273             case CHANNEL_OFFLINE:
274                 updateState(CHANNEL_OFFLINE, OnOffType.from(heatpumpDeviceStatus.getOffline()));
275                 break;
276         }
277     }
278
279     private ZonedDateTime convertDateTime(String dateTime) {
280         return ZonedDateTime.ofInstant(LocalDateTime.parse(dateTime, DateTimeFormatter.ISO_LOCAL_DATE_TIME),
281                 ZoneOffset.UTC, ZoneId.systemDefault());
282     }
283 }