]> git.basschouten.com Git - openhab-addons.git/blob
28ef252d156da20b674d0c36b45b4c957cbb6eed
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.time.DateTimeException;
17 import java.time.Instant;
18 import java.time.ZoneId;
19 import java.util.HashMap;
20 import java.util.Map;
21
22 import javax.measure.Unit;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.luxtronikheatpump.internal.enums.HeatpumpChannel;
27 import org.openhab.binding.luxtronikheatpump.internal.enums.HeatpumpType;
28 import org.openhab.core.items.Item;
29 import org.openhab.core.library.items.DateTimeItem;
30 import org.openhab.core.library.items.NumberItem;
31 import org.openhab.core.library.items.SwitchItem;
32 import org.openhab.core.library.types.DateTimeType;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.library.types.StringType;
37 import org.openhab.core.library.unit.SIUnits;
38 import org.openhab.core.library.unit.Units;
39 import org.openhab.core.scheduler.SchedulerRunnable;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.types.State;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * The {@link ChannelUpdaterJob} updates all channel values
47  *
48  * @author Stefan Giehl - Initial contribution
49  */
50 @NonNullByDefault
51 public class ChannelUpdaterJob implements SchedulerRunnable, Runnable {
52
53     private final Thing thing;
54     private final LuxtronikHeatpumpConfiguration config;
55     private final LuxtronikTranslationProvider translationProvider;
56     private final Logger logger = LoggerFactory.getLogger(ChannelUpdaterJob.class);
57     private final LuxtronikHeatpumpHandler handler;
58
59     public ChannelUpdaterJob(LuxtronikHeatpumpHandler handler, LuxtronikTranslationProvider translationProvider) {
60         this.translationProvider = translationProvider;
61         this.handler = handler;
62         this.thing = handler.getThing();
63         this.config = this.thing.getConfiguration().as(LuxtronikHeatpumpConfiguration.class);
64     }
65
66     public Thing getThing() {
67         return thing;
68     }
69
70     @Override
71     public void run() {
72         // connect to heatpump and check if values can be fetched
73         final HeatpumpConnector connector = new HeatpumpConnector(config.ipAddress, config.port);
74
75         try {
76             connector.read();
77         } catch (IOException e) {
78             logger.warn("Could not connect to heatpump (uuid={}, ip={}, port={}): {}", thing.getUID(), config.ipAddress,
79                     config.port, e.getMessage());
80
81             handler.setStatusConnectionError();
82             return;
83         }
84
85         handler.setStatusOnline();
86
87         // read all available values
88         Integer[] heatpumpValues = connector.getValues();
89
90         // read all parameters
91         Integer[] heatpumpParams = connector.getParams();
92         Integer[] heatpumpVisibilities = connector.getVisibilities();
93
94         for (HeatpumpChannel channel : HeatpumpChannel.values()) {
95             try {
96                 Integer rawValue = getChannelValue(channel, heatpumpValues, heatpumpParams, heatpumpVisibilities);
97
98                 if (rawValue == null) {
99                     continue;
100                 }
101
102                 State value = convertValueToState(rawValue, channel.getItemClass(), channel.getUnit());
103
104                 if (value != null) {
105                     handleEventType(value, channel);
106                 }
107             } catch (Exception e) {
108                 // an exception should actually not occur, but is catched nevertheless in case it does
109                 // this ensures the remaining channels are updated even if one fails for some reason
110                 logger.warn("An error occurred while updating the channel {}: {}", channel.getCommand(),
111                         e.getMessage());
112             }
113         }
114
115         setExtendedState(heatpumpValues, heatpumpParams, heatpumpVisibilities);
116
117         updateProperties(heatpumpValues);
118     }
119
120     private @Nullable State convertValueToState(Integer rawValue, Class<? extends Item> itemClass,
121             @Nullable Unit<?> unit) {
122         if (itemClass == SwitchItem.class) {
123             return (rawValue == 0) ? OnOffType.OFF : OnOffType.ON;
124         }
125
126         if (itemClass == DateTimeItem.class && rawValue > 0) {
127             try {
128                 Instant instant = Instant.ofEpochSecond(rawValue.longValue());
129                 return new DateTimeType(instant.atZone(ZoneId.of("UTC")));
130             } catch (DateTimeException e) {
131                 logger.warn("Invalid timestamp '{}' received from heatpump: {}", rawValue, e.getMessage());
132             }
133         }
134
135         if (itemClass == NumberItem.class) {
136             if (unit == null) {
137                 return new DecimalType(rawValue);
138             }
139             if (SIUnits.CELSIUS.equals(unit) || Units.KELVIN.equals(unit) || Units.KILOWATT_HOUR.equals(unit)
140                     || Units.PERCENT.equals(unit) || Units.HOUR.equals(unit)) {
141                 return new QuantityType<>((double) rawValue / 10, unit);
142             } else if (Units.HERTZ.equals(unit) || Units.SECOND.equals(unit) || Units.WATT.equals(unit)) {
143                 return new QuantityType<>((double) rawValue, unit);
144             } else if (Units.LITRE_PER_MINUTE.equals(unit)) {
145                 return new QuantityType<>((double) rawValue / 60, unit);
146             } else if (Units.BAR.equals(unit) || Units.VOLT.equals(unit)) {
147                 return new QuantityType<>((double) rawValue / 100, unit);
148             }
149
150             logger.debug("Unhandled unit {} configured for a channel.", unit);
151             return new DecimalType(rawValue);
152         }
153
154         return null;
155     }
156
157     private @Nullable Integer getChannelValue(HeatpumpChannel channel, Integer[] heatpumpValues,
158             Integer[] heatpumpParams, Integer[] heatpumpVisibilities) {
159         Integer channelId = channel.getChannelId();
160
161         if (channelId == null) {
162             return null; // no channel id to read defined (for channels handeled separatly)
163         }
164
165         if (!channel.isVisible(heatpumpVisibilities) && config.showAllChannels) {
166             logger.debug("Channel {} is not available. Skipped updating it", channel.getCommand());
167             return null;
168         }
169
170         Integer rawValue = null;
171
172         if (channel.isWritable()) {
173             rawValue = heatpumpParams[channelId];
174         } else {
175             if (heatpumpValues.length <= channelId) {
176                 return null; // channel not available
177             }
178             rawValue = heatpumpValues[channelId];
179         }
180
181         return rawValue;
182     }
183
184     private static String getSoftwareVersion(Integer[] heatpumpValues) {
185         StringBuffer softwareVersion = new StringBuffer("");
186
187         for (int i = 81; i <= 90; i++) {
188             if (heatpumpValues[i] > 0) {
189                 softwareVersion.append(Character.toString(heatpumpValues[i]));
190             }
191         }
192
193         return softwareVersion.toString();
194     }
195
196     private static String transformIpAddress(int ip) {
197         return String.format("%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
198     }
199
200     private void handleEventType(State state, HeatpumpChannel heatpumpCommandType) {
201         handler.updateState(heatpumpCommandType.getCommand(), state);
202     }
203
204     private void setExtendedState(Integer[] heatpumpValues, Integer[] heatpumpParams, Integer[] heatpumpVisibilities) {
205         Integer row1 = getChannelValue(HeatpumpChannel.CHANNEL_HEATPUMP_HAUPTMENUSTATUS_ZEILE1, heatpumpValues,
206                 heatpumpParams, heatpumpVisibilities);
207         Integer error = getChannelValue(HeatpumpChannel.CHANNEL_HEATPUMP_ERROR_NR0, heatpumpValues, heatpumpParams,
208                 heatpumpVisibilities);
209         Integer row2 = getChannelValue(HeatpumpChannel.CHANNEL_HEATPUMP_HAUPTMENUSTATUS_ZEILE2, heatpumpValues,
210                 heatpumpParams, heatpumpVisibilities);
211         Integer row3 = getChannelValue(HeatpumpChannel.CHANNEL_HEATPUMP_HAUPTMENUSTATUS_ZEILE3, heatpumpValues,
212                 heatpumpParams, heatpumpVisibilities);
213         Integer time = getChannelValue(HeatpumpChannel.CHANNEL_HEATPUMP_HAUPTMENUSTATUS_ZEIT, heatpumpValues,
214                 heatpumpParams, heatpumpVisibilities);
215         String state = "";
216
217         if (row1 != null && row1 == 4) {
218             // 4 means error state
219             state = getStateTranslation("errorCodeX", error);
220         } else {
221             state = getStateTranslation("menuStateLine1", row1);
222         }
223
224         var longState = String.format("%s - %s %s - %s", state, getStateTranslation("menuStateLine2", row2),
225                 formatHours(time), getStateTranslation("menuStateLine3", row3));
226
227         handleEventType(new StringType(longState), HeatpumpChannel.CHANNEL_HEATPUMP_STATUS);
228     }
229
230     public static Map<String, Object> getProperties(Integer[] heatpumpValues) {
231         Map<String, Object> properties = new HashMap<String, Object>();
232
233         String heatpumpType = HeatpumpType.fromCode(heatpumpValues[78]).getName();
234
235         properties.put("heatpumpType", heatpumpType);
236
237         // Not sure when Typ 2 should be used
238         // String heatpumpType2 = HeatpumpType.fromCode(heatpumpValues[230]).getName();
239         // properties.put("heatpumpType2", heatpumpType2);
240
241         properties.put("softwareVersion", getSoftwareVersion(heatpumpValues));
242         properties.put("ipAddress", transformIpAddress(heatpumpValues[91]));
243         properties.put("subnetMask", transformIpAddress(heatpumpValues[92]));
244         properties.put("broadcastAddress", transformIpAddress(heatpumpValues[93]));
245         properties.put("gateway", transformIpAddress(heatpumpValues[94]));
246
247         return properties;
248     }
249
250     private void updateProperties(Integer[] heatpumpValues) {
251         Map<String, Object> properties = getProperties(heatpumpValues);
252
253         for (Map.Entry<String, Object> property : properties.entrySet()) {
254             handler.updateProperty(property.getKey(), property.getValue().toString());
255         }
256     }
257
258     private String getStateTranslation(String name, @Nullable Integer option) {
259         if (option == null) {
260             return "";
261         }
262
263         String translation = translationProvider
264                 .getText("channel-type.luxtronikheatpump." + name + ".state.option." + option);
265         return translation == null ? "" : translation;
266     }
267
268     private String formatHours(@Nullable Integer value) {
269         String returnValue = "";
270
271         if (value == null) {
272             return returnValue;
273         }
274
275         int intVal = value;
276
277         returnValue += String.format("%02d:", intVal / 3600);
278         intVal %= 3600;
279         returnValue += String.format("%02d:", intVal / 60);
280         intVal %= 60;
281         returnValue += String.format("%02d", intVal);
282         return returnValue;
283     }
284 }