]> git.basschouten.com Git - openhab-addons.git/blob
4c1c4ddcfbac4a566550c23c38cf5887c2dd921d
[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.siemenshvac.internal.handler;
14
15 import java.math.BigDecimal;
16 import java.util.Locale;
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.locks.Lock;
20 import java.util.concurrent.locks.ReentrantLock;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.siemenshvac.internal.converter.ConverterException;
25 import org.openhab.binding.siemenshvac.internal.converter.ConverterFactory;
26 import org.openhab.binding.siemenshvac.internal.converter.ConverterTypeException;
27 import org.openhab.binding.siemenshvac.internal.converter.TypeConverter;
28 import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
29 import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataRegistry;
30 import org.openhab.binding.siemenshvac.internal.network.SiemensHvacCallback;
31 import org.openhab.binding.siemenshvac.internal.network.SiemensHvacConnector;
32 import org.openhab.binding.siemenshvac.internal.network.SiemensHvacRequestListener.ErrorSource;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.thing.Bridge;
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.ThingStatusInfo;
41 import org.openhab.core.thing.binding.BaseThingHandler;
42 import org.openhab.core.thing.binding.ThingHandlerCallback;
43 import org.openhab.core.thing.type.ChannelType;
44 import org.openhab.core.thing.type.ChannelTypeRegistry;
45 import org.openhab.core.types.Command;
46 import org.openhab.core.types.RefreshType;
47 import org.openhab.core.types.State;
48 import org.openhab.core.types.StateDescription;
49 import org.openhab.core.types.Type;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 import com.google.gson.JsonObject;
54
55 /**
56  * The {@link SiemensHvacHandler} is responsible for handling commands, which are
57  * sent to one of the channels.
58  *
59  * @author Laurent ARNAL - Initial contribution
60  */
61 @NonNullByDefault
62 public class SiemensHvacHandlerImpl extends BaseThingHandler {
63
64     private Lock lockObj = new ReentrantLock();
65
66     private final Logger logger = LoggerFactory.getLogger(SiemensHvacHandlerImpl.class);
67
68     private @Nullable ScheduledFuture<?> pollingJob = null;
69
70     private final @Nullable SiemensHvacConnector hvacConnector;
71     private final @Nullable SiemensHvacMetadataRegistry metaDataRegistry;
72     private final ChannelTypeRegistry channelTypeRegistry;
73
74     private long lastWrite = 0;
75
76     public SiemensHvacHandlerImpl(Thing thing, @Nullable SiemensHvacConnector hvacConnector,
77             @Nullable SiemensHvacMetadataRegistry metaDataRegistry, ChannelTypeRegistry channelTypeRegistry) {
78         super(thing);
79
80         this.hvacConnector = hvacConnector;
81         this.metaDataRegistry = metaDataRegistry;
82         this.channelTypeRegistry = channelTypeRegistry;
83     }
84
85     @Override
86     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
87     }
88
89     @Override
90     public void initialize() {
91         updateStatus(ThingStatus.UNKNOWN);
92         pollingJob = scheduler.scheduleWithFixedDelay(this::pollingCode, 0, 5, TimeUnit.SECONDS);
93     }
94
95     @Override
96     public void dispose() {
97         ScheduledFuture<?> lcPollingJob = pollingJob;
98         if (lcPollingJob != null) {
99             lcPollingJob.cancel(true);
100             pollingJob = null;
101         }
102     }
103
104     private void pollingCode() {
105         Bridge lcBridge = getBridge();
106
107         if (lcBridge == null) {
108             return;
109         }
110
111         if (lcBridge.getStatus() == ThingStatus.OFFLINE) {
112             if (!ThingStatusDetail.COMMUNICATION_ERROR.equals(lcBridge.getStatusInfo().getStatusDetail())) {
113                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
114                 return;
115             }
116         }
117
118         if (lcBridge.getStatus() != ThingStatus.ONLINE) {
119             if (!ThingStatusDetail.COMMUNICATION_ERROR.equals(lcBridge.getStatusInfo().getStatusDetail())) {
120                 logger.debug("Bridge is not ready, don't enter polling for now!");
121                 return;
122             }
123         }
124
125         long start = System.currentTimeMillis();
126         var chList = this.getThing().getChannels();
127
128         SiemensHvacConnector lcHvacConnector = hvacConnector;
129         if (lcHvacConnector != null) {
130             int previousRequestCount = lcHvacConnector.getRequestCount();
131             int previousErrorCount = lcHvacConnector.getErrorCount();
132
133             logger.debug("readChannels:");
134             for (Channel channel : chList) {
135                 readChannel(channel);
136             }
137
138             logger.debug("WaitAllPendingRequest:Start waiting()");
139             lcHvacConnector.waitAllPendingRequest();
140             long end = System.currentTimeMillis();
141             logger.debug("WaitAllPendingRequest:All request done(): {}", (end - start) / 1000.00);
142
143             int newRequestCount = lcHvacConnector.getRequestCount();
144             int newErrorCount = lcHvacConnector.getErrorCount();
145
146             int requestCount = newRequestCount - previousRequestCount;
147             int errorCount = newErrorCount - previousErrorCount;
148
149             double errorRate = (double) errorCount / requestCount * 100.00;
150
151             if (errorRate > 50) {
152                 SiemensHvacBridgeThingHandler bridgeHandler = (SiemensHvacBridgeThingHandler) lcBridge.getHandler();
153
154                 if (lcHvacConnector.getErrorSource() == ErrorSource.ErrorBridge) {
155                     if (bridgeHandler != null) {
156                         bridgeHandler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
157                                 String.format("Communication ErrorRate to gateway is too high: %f", errorRate));
158                     }
159                 } else if (lcHvacConnector.getErrorSource() == ErrorSource.ErrorThings) {
160                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
161                             String.format("Communication ErrorRate to thing is too high: %f", errorRate));
162                 }
163             } else {
164                 updateStatus(ThingStatus.ONLINE);
165
166                 SiemensHvacBridgeThingHandler bridgeHandler = (SiemensHvacBridgeThingHandler) lcBridge.getHandler();
167
168                 // Automatically recover from communication errors if errorRate is ok.
169                 if (bridgeHandler != null) {
170                     if (ThingStatusDetail.COMMUNICATION_ERROR
171                             .equals(bridgeHandler.getThing().getStatusInfo().getStatusDetail())) {
172                         bridgeHandler.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "");
173                     }
174                 }
175             }
176
177             lcHvacConnector.displayRequestStats();
178         }
179     }
180
181     private void readChannel(Channel channel) {
182         ThingHandlerCallback cb = this.getCallback();
183         boolean isLink = false;
184
185         if (cb != null) {
186             isLink = cb.isChannelLinked(channel.getUID());
187         }
188
189         if (!isLink) {
190             return;
191         }
192
193         logger.debug("readChannel: {}", channel.getDescription());
194
195         ChannelType tp = channelTypeRegistry.getChannelType(channel.getChannelTypeUID());
196
197         if (tp == null) {
198             return;
199         }
200
201         String id = channel.getProperties().get("id");
202         String uid = channel.getUID().getId();
203         String type = tp.getItemType();
204
205         if (id == null) {
206             id = (String) channel.getConfiguration().getProperties().get("id");
207         }
208
209         if (id == null) {
210             logger.debug("pollingCode: Id is null {} ", channel);
211             return;
212         }
213         if (type == null) {
214             logger.debug("pollingCode: type is null {} ", channel);
215             return;
216         }
217
218         readDp(id, uid, tp, type, true);
219     }
220
221     public void decodeReadDp(@Nullable JsonObject response, @Nullable String uid, @Nullable String dp, ChannelType tp,
222             @Nullable String type) {
223         SiemensHvacMetadataRegistry lcMetaDataRegistry = metaDataRegistry;
224         if (lcMetaDataRegistry == null) {
225             return;
226         }
227
228         if (response != null && response.has("Data")) {
229             JsonObject subResult = (JsonObject) response.get("Data");
230
231             String updateKey = "" + uid;
232
233             String typer = null;
234
235             if (subResult.has("Type")) {
236                 typer = subResult.get("Type").getAsString().trim();
237             }
238
239             try {
240                 if (typer != null) {
241                     TypeConverter converter = ConverterFactory.getConverter(typer);
242
243                     Locale local = lcMetaDataRegistry.getUserLocale();
244                     if (local == null) {
245                         local = Locale.getDefault();
246                     }
247
248                     State state = converter.convertFromBinding(subResult, tp, local);
249                     updateState(updateKey, state);
250                 }
251             } catch (ConverterTypeException ex) {
252                 logger.warn("{}, for uid : {}, please check the item type", ex.getMessage(), uid);
253             } catch (ConverterException ex) {
254                 logger.warn("{}, for uid: {}, please check the item type", ex.getMessage(), uid);
255             }
256
257         }
258     }
259
260     private void readDp(String dp, String uid, ChannelType tp, String type, boolean async) {
261         SiemensHvacConnector lcHvacConnector = hvacConnector;
262
263         if ("-1".equals(dp)) {
264             return;
265         }
266
267         try {
268             lockObj.lock();
269
270             logger.trace("Start read: {}", dp);
271             String request = "api/menutree/read_datapoint.json?Id=" + dp;
272
273             logger.debug("siemensHvac:ReadDp:DoRequest(): {}", request);
274
275             if (async) {
276                 if (lcHvacConnector != null) {
277                     lcHvacConnector.doRequest(request, new SiemensHvacCallback() {
278
279                         @Override
280                         public void execute(java.net.URI uri, int status, @Nullable Object response) {
281                             // prevent async read if we just write so we have no overlaps
282                             long now = System.currentTimeMillis();
283                             if (now - lastWrite < 5000) {
284                                 return;
285                             }
286
287                             logger.trace("End read: {}", dp);
288
289                             if (response instanceof JsonObject jsonResponse) {
290                                 decodeReadDp(jsonResponse, uid, dp, tp, type);
291                             }
292                         }
293                     });
294                 }
295             } else {
296                 if (lcHvacConnector != null) {
297                     JsonObject js = lcHvacConnector.doRequest(request);
298                     decodeReadDp(js, uid, dp, tp, type);
299                 }
300             }
301         } finally {
302             logger.trace("End read: {}", dp);
303             lockObj.unlock();
304         }
305     }
306
307     private void writeDp(String dp, Type dpVal, ChannelType tp, String type) {
308         SiemensHvacConnector lcHvacConnector = hvacConnector;
309
310         if (lcHvacConnector != null) {
311             lcHvacConnector.displayRequestStats();
312         }
313
314         if ("-1".equals(dp)) {
315             return;
316         }
317
318         try {
319             lockObj.lock();
320             logger.trace("Start write: {}", dp);
321             lastWrite = System.currentTimeMillis();
322
323             Object valUpdate = "0";
324
325             try {
326                 TypeConverter converter = ConverterFactory.getConverter(type);
327
328                 valUpdate = converter.convertToBinding(dpVal, tp);
329                 if (valUpdate != null) {
330                     String request = String.format("api/menutree/write_datapoint.json?Id=%s&Value=%s&Type=%s", dp,
331                             valUpdate, type);
332
333                     if (lcHvacConnector != null) {
334                         logger.trace("Write request for: {} ", valUpdate);
335                         JsonObject response = lcHvacConnector.doRequest(request);
336
337                         logger.trace("Write request response: {} ", response);
338                     }
339
340                 } else {
341                     logger.debug("Failed to get converted state from datapoint '{}'", dp);
342                 }
343             } catch (ConverterTypeException ex) {
344                 logger.warn("{}, please check the item type and the commands in your scripts", ex.getMessage());
345             } catch (ConverterException ex) {
346                 logger.warn("{}, please check the item type and the commands in your scripts", ex.getMessage());
347             }
348         } finally {
349             logger.debug("End write: {}", dp);
350             lockObj.unlock();
351         }
352     }
353
354     private Command applyState(ChannelType tp, Command command) {
355         StateDescription sd = tp.getState();
356         Command result = command;
357
358         if (sd != null) {
359             BigDecimal maxb = sd.getMaximum();
360             BigDecimal minb = sd.getMinimum();
361             BigDecimal step = sd.getStep();
362             boolean doMods = false;
363
364             if (command instanceof DecimalType decimalCommand) {
365                 double v1 = decimalCommand.doubleValue();
366
367                 if (step != null) {
368                     doMods = true;
369                     int divider = 1;
370
371                     if (step.doubleValue() == 0.5) {
372                         divider = 2;
373                     } else if (step.doubleValue() == 0.1) {
374                         divider = 10;
375                     } else if (step.doubleValue() == 0.02) {
376                         divider = 50;
377                     } else if (step.doubleValue() == 0.01) {
378                         divider = 100;
379                     }
380                     v1 = v1 * divider;
381                     v1 = Math.floor(v1);
382                     v1 = v1 / divider;
383                 }
384
385                 if (minb != null && v1 < minb.floatValue()) {
386                     doMods = true;
387                     v1 = minb.floatValue();
388                 }
389                 if (maxb != null && v1 > maxb.floatValue()) {
390                     doMods = true;
391                     v1 = maxb.floatValue();
392                 }
393
394                 if (doMods) {
395                     result = new DecimalType(v1);
396                 }
397             }
398         }
399         return result;
400     }
401
402     @Override
403     public void handleCommand(ChannelUID channelUID, Command command) {
404         SiemensHvacMetadataRegistry lcMetaDataRegistry = metaDataRegistry;
405         logger.debug("handleCommand");
406         if (command instanceof RefreshType) {
407             var channel = this.getThing().getChannel(channelUID);
408             if (channel != null) {
409                 readChannel(channel);
410             }
411         } else {
412             Channel channel = getThing().getChannel(channelUID);
413             if (channel == null) {
414                 return;
415             }
416
417             Command commandVar = command;
418             ChannelType tp = channelTypeRegistry.getChannelType(channel.getChannelTypeUID());
419
420             if (tp == null) {
421                 return;
422             }
423
424             String type = tp.getItemType();
425             String dptType = "";
426             String id = channel.getProperties().get("id");
427             SiemensHvacMetadataDataPoint md = null;
428
429             if (id == null) {
430                 id = (String) channel.getConfiguration().getProperties().get("id");
431             }
432
433             if (lcMetaDataRegistry != null) {
434                 md = (SiemensHvacMetadataDataPoint) lcMetaDataRegistry.getDptMap(id);
435                 if (md != null) {
436                     id = "" + md.getId();
437                     dptType = md.getDptType();
438                 }
439             }
440
441             if (command instanceof State commandState) {
442                 commandVar = applyState(tp, commandVar);
443                 this.updateState(channelUID, commandState);
444             }
445
446             if (id != null && type != null) {
447                 writeDp(id, commandVar, tp, dptType);
448             }
449         }
450     }
451 }