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