]> git.basschouten.com Git - openhab-addons.git/blob
1ebd8697e492f38d6418cc8a94eaca986363cdd1
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.luxtronikheatpump.internal;
14
15 import java.io.IOException;
16 import java.util.ArrayList;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Set;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22
23 import javax.measure.Unit;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.openhab.binding.luxtronikheatpump.internal.enums.HeatpumpChannel;
27 import org.openhab.binding.luxtronikheatpump.internal.enums.HeatpumpCoolingOperationMode;
28 import org.openhab.binding.luxtronikheatpump.internal.enums.HeatpumpOperationMode;
29 import org.openhab.binding.luxtronikheatpump.internal.exceptions.InvalidChannelException;
30 import org.openhab.binding.luxtronikheatpump.internal.exceptions.InvalidOperationModeException;
31 import org.openhab.core.config.core.Configuration;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.openhab.core.library.types.QuantityType;
35 import org.openhab.core.thing.Channel;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.thing.binding.ThingHandlerCallback;
42 import org.openhab.core.thing.binding.builder.ThingBuilder;
43 import org.openhab.core.thing.type.ChannelTypeUID;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.RefreshType;
46 import org.openhab.core.types.State;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 /**
51  * The {@link LuxtronikHeatpumpHandler} is responsible for handling commands, which are
52  * sent to one of the channels.
53  *
54  * @author Stefan Giehl - Initial contribution
55  */
56 @NonNullByDefault
57 public class LuxtronikHeatpumpHandler extends BaseThingHandler {
58
59     private final Logger logger = LoggerFactory.getLogger(LuxtronikHeatpumpHandler.class);
60     private final Set<ScheduledFuture<?>> scheduledFutures = new HashSet<>();
61     private static final int RETRY_INTERVAL_SEC = 60;
62     private boolean tiggerChannelUpdate = false;
63     private final LuxtronikTranslationProvider translationProvider;
64     private LuxtronikHeatpumpConfiguration config;
65
66     public LuxtronikHeatpumpHandler(Thing thing, LuxtronikTranslationProvider translationProvider) {
67         super(thing);
68         this.translationProvider = translationProvider;
69         config = new LuxtronikHeatpumpConfiguration();
70     }
71
72     @Override
73     public void updateState(String channelID, State state) {
74         super.updateState(channelID, state);
75     }
76
77     @Override
78     public void updateProperty(String name, String value) {
79         super.updateProperty(name, value);
80     }
81
82     public void setStatusConnectionError() {
83         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
84                 "couldn't establish network connection [host '" + config.ipAddress + "']");
85     }
86
87     public void setStatusOnline() {
88         updateStatus(ThingStatus.ONLINE);
89     }
90
91     @Override
92     public void handleCommand(ChannelUID channelUID, Command command) {
93         String channelId = channelUID.getIdWithoutGroup();
94         logger.debug("Handle command '{}' for channel {}", command, channelId);
95         if (command == RefreshType.REFRESH) {
96             // ignore resresh command as channels will be updated automatically
97             return;
98         }
99
100         HeatpumpChannel channel;
101
102         try {
103             channel = HeatpumpChannel.fromString(channelId);
104         } catch (InvalidChannelException e) {
105             logger.debug("Channel '{}' could not be found for thing {}", channelId, thing.getUID());
106             return;
107         }
108
109         if (!channel.isWritable()) {
110             logger.debug("Channel {} is a read-only channel and cannot handle command '{}'", channelId, command);
111             return;
112         }
113
114         if (command instanceof QuantityType) {
115             QuantityType<?> value = (QuantityType<?>) command;
116
117             Unit<?> unit = channel.getUnit();
118             if (unit != null) {
119                 value = value.toUnit(unit);
120             }
121
122             command = new DecimalType(value.floatValue());
123         }
124
125         if (command instanceof OnOffType) {
126             command = ((OnOffType) command) == OnOffType.ON ? new DecimalType(1) : DecimalType.ZERO;
127         }
128
129         if (!(command instanceof DecimalType)) {
130             logger.warn("Heatpump operation for item {} must be from type: {}. Received {}", channel.getCommand(),
131                     DecimalType.class.getSimpleName(), command.getClass());
132             return;
133         }
134
135         Integer param = channel.getChannelId();
136         Integer value = null;
137
138         switch (channel) {
139             case CHANNEL_EINST_BWTDI_AKT_MO:
140             case CHANNEL_EINST_BWTDI_AKT_DI:
141             case CHANNEL_EINST_BWTDI_AKT_MI:
142             case CHANNEL_EINST_BWTDI_AKT_DO:
143             case CHANNEL_EINST_BWTDI_AKT_FR:
144             case CHANNEL_EINST_BWTDI_AKT_SA:
145             case CHANNEL_EINST_BWTDI_AKT_SO:
146             case CHANNEL_EINST_BWTDI_AKT_AL:
147                 value = ((DecimalType) command).intValue();
148                 break;
149             case CHANNEL_BA_HZ_AKT:
150             case CHANNEL_BA_BW_AKT:
151                 value = ((DecimalType) command).intValue();
152                 try {
153                     // validate the value is valid
154                     HeatpumpOperationMode.fromValue(value);
155                 } catch (InvalidOperationModeException e) {
156                     logger.warn("Heatpump {} mode recevieved invalid value {}: {}", channel.getCommand(), value,
157                             e.getMessage());
158                     return;
159                 }
160                 break;
161             case CHANNEL_EINST_WK_AKT:
162             case CHANNEL_EINST_BWS_AKT:
163             case CHANNEL_EINST_KUCFTL_AKT:
164             case CHANNEL_SOLLWERT_KUCFTL_AKT:
165             case CHANNEL_SOLL_BWS_AKT:
166             case CHANNEL_EINST_HEIZGRENZE_TEMP:
167                 float temperature = ((DecimalType) command).floatValue();
168                 value = (int) (temperature * 10);
169                 break;
170             case CHANNEL_EINST_BWSTYP_AKT:
171                 value = ((DecimalType) command).intValue();
172                 try {
173                     // validate the value is valid
174                     HeatpumpCoolingOperationMode.fromValue(value);
175                 } catch (InvalidOperationModeException e) {
176                     logger.warn("Heatpump {} mode recevieved invalid value {}: {}", channel.getCommand(), value,
177                             e.getMessage());
178                     return;
179                 }
180                 break;
181             case CHANNEL_EINST_KUHL_ZEIT_EIN_AKT:
182             case CHANNEL_EINST_KUHL_ZEIT_AUS_AKT:
183                 float hours = ((DecimalType) command).floatValue();
184                 value = (int) (hours * 10);
185                 break;
186
187             default:
188                 logger.debug("Received unknown channel {}", channelId);
189                 break;
190         }
191
192         if (param != null && value != null) {
193             if (sendParamToHeatpump(param, value)) {
194                 logger.debug("Heat pump mode {} set to {}.", channel.getCommand(), value);
195             } else {
196                 logger.warn("Failed setting heat pump mode {} to {}", channel.getCommand(), value);
197             }
198         } else {
199             logger.warn("No valid value given for Heatpump operation {}", channel.getCommand());
200         }
201     }
202
203     @Override
204     public void initialize() {
205         config = getConfigAs(LuxtronikHeatpumpConfiguration.class);
206
207         if (!config.isValid()) {
208             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
209                     "At least one mandatory configuration field is empty");
210             return;
211         }
212
213         updateStatus(ThingStatus.UNKNOWN);
214
215         synchronized (scheduledFutures) {
216             ScheduledFuture<?> future = scheduler.scheduleWithFixedDelay(this::internalInitialize, 0,
217                     RETRY_INTERVAL_SEC, TimeUnit.SECONDS);
218             scheduledFutures.add(future);
219         }
220     }
221
222     private void internalInitialize() {
223         // connect to heatpump and check if values can be fetched
224         HeatpumpConnector connector = new HeatpumpConnector(config.ipAddress, config.port);
225
226         try {
227             connector.read();
228         } catch (IOException e) {
229             setStatusConnectionError();
230             return;
231         }
232
233         // stop trying to establish a connection for initializing the thing once it was established
234         stopJobs();
235
236         // When thing is initialized the first time or and update was triggered, set the available channels
237         if (thing.getProperties().isEmpty() || tiggerChannelUpdate) {
238             updateChannels(connector);
239         }
240
241         setStatusOnline();
242         restartJobs();
243     }
244
245     @Override
246     protected void updateConfiguration(Configuration configuration) {
247         tiggerChannelUpdate = true;
248         super.updateConfiguration(configuration);
249     }
250
251     @Override
252     public void dispose() {
253         stopJobs();
254     }
255
256     private void updateChannels(HeatpumpConnector connector) {
257         Integer[] visibilityValues = connector.getVisibilities();
258         Integer[] heatpumpValues = connector.getValues();
259         Integer[] heatpumpParams = connector.getParams();
260
261         logger.debug("Updating available channels for thing {}", thing.getUID());
262
263         final ThingHandlerCallback callback = getCallback();
264         if (callback == null) {
265             logger.debug("ThingHandlerCallback is null. Skipping migration of last_update channel.");
266             return;
267         }
268
269         ThingBuilder thingBuilder = editThing();
270         List<Channel> channelList = new ArrayList<>();
271
272         // clear channel list
273         thingBuilder.withoutChannels(thing.getChannels());
274
275         // create list with available channels
276         for (HeatpumpChannel channel : HeatpumpChannel.values()) {
277             Integer channelId = channel.getChannelId();
278             int length = channel.isWritable() ? heatpumpParams.length : heatpumpValues.length;
279             ChannelUID channelUID = new ChannelUID(thing.getUID(), channel.getCommand());
280             ChannelTypeUID channelTypeUID = new ChannelTypeUID(LuxtronikHeatpumpBindingConstants.BINDING_ID,
281                     channel.getCommand());
282             if ((channelId != null && length <= channelId)
283                     || (config.showAllChannels == Boolean.FALSE && !channel.isVisible(visibilityValues))) {
284                 logger.debug("Hiding channel {}", channel.getCommand());
285             } else {
286                 channelList.add(callback.createChannelBuilder(channelUID, channelTypeUID).build());
287             }
288         }
289
290         thingBuilder.withChannels(channelList);
291
292         updateThing(thingBuilder.build());
293     }
294
295     private void restartJobs() {
296         stopJobs();
297
298         synchronized (scheduledFutures) {
299             if (getThing().getStatus() == ThingStatus.ONLINE) {
300                 // Repeat channel update job every configured seconds
301                 Runnable channelUpdaterJob = new ChannelUpdaterJob(this, translationProvider);
302                 ScheduledFuture<?> future = scheduler.scheduleWithFixedDelay(channelUpdaterJob, 0, config.refresh,
303                         TimeUnit.SECONDS);
304                 scheduledFutures.add(future);
305             }
306         }
307     }
308
309     private void stopJobs() {
310         synchronized (scheduledFutures) {
311             for (ScheduledFuture<?> future : scheduledFutures) {
312                 if (!future.isDone()) {
313                     future.cancel(true);
314                 }
315             }
316             scheduledFutures.clear();
317         }
318     }
319
320     /**
321      * Set a parameter on the Luxtronik heatpump.
322      *
323      * @param param
324      * @param value
325      */
326     private boolean sendParamToHeatpump(int param, int value) {
327         HeatpumpConnector connector = new HeatpumpConnector(config.ipAddress, config.port);
328
329         try {
330             return connector.setParam(param, value);
331         } catch (IOException e) {
332             setStatusConnectionError();
333         }
334
335         return false;
336     }
337 }