2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.luxtronikheatpump.internal;
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;
22 import javax.measure.Unit;
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;
46 * The {@link ChannelUpdaterJob} updates all channel values
48 * @author Stefan Giehl - Initial contribution
51 public class ChannelUpdaterJob implements SchedulerRunnable, Runnable {
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;
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);
66 public Thing getThing() {
72 // connect to heatpump and check if values can be fetched
73 final HeatpumpConnector connector = new HeatpumpConnector(config.ipAddress, config.port);
77 } catch (IOException e) {
78 logger.warn("Could not connect to heatpump (uuid={}, ip={}, port={}): {}", thing.getUID(), config.ipAddress,
79 config.port, e.getMessage());
81 handler.setStatusConnectionError();
85 handler.setStatusOnline();
87 // read all available values
88 Integer[] heatpumpValues = connector.getValues();
90 // read all parameters
91 Integer[] heatpumpParams = connector.getParams();
92 Integer[] heatpumpVisibilities = connector.getVisibilities();
94 for (HeatpumpChannel channel : HeatpumpChannel.values()) {
96 Integer rawValue = getChannelValue(channel, heatpumpValues, heatpumpParams, heatpumpVisibilities);
98 if (rawValue == null) {
102 State value = convertValueToState(rawValue, channel.getItemClass(), channel.getUnit());
105 handleEventType(value, channel);
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(),
115 setExtendedState(heatpumpValues, heatpumpParams, heatpumpVisibilities);
117 updateProperties(heatpumpValues);
120 private @Nullable State convertValueToState(Integer rawValue, Class<? extends Item> itemClass,
121 @Nullable Unit<?> unit) {
122 if (itemClass == SwitchItem.class) {
123 return OnOffType.from(rawValue != 0);
126 if (itemClass == DateTimeItem.class && rawValue > 0) {
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());
135 if (itemClass == NumberItem.class) {
137 return new DecimalType(rawValue);
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);
150 logger.debug("Unhandled unit {} configured for a channel.", unit);
151 return new DecimalType(rawValue);
157 private @Nullable Integer getChannelValue(HeatpumpChannel channel, Integer[] heatpumpValues,
158 Integer[] heatpumpParams, Integer[] heatpumpVisibilities) {
159 Integer channelId = channel.getChannelId();
161 if (channelId == null) {
162 return null; // no channel id to read defined (for channels handeled separatly)
165 if (!channel.isVisible(heatpumpVisibilities) && config.showAllChannels) {
166 logger.debug("Channel {} is not available. Skipped updating it", channel.getCommand());
170 Integer rawValue = null;
172 if (channel.isWritable()) {
173 rawValue = heatpumpParams[channelId];
175 if (heatpumpValues.length <= channelId) {
176 return null; // channel not available
178 rawValue = heatpumpValues[channelId];
184 private static String getSoftwareVersion(Integer[] heatpumpValues) {
185 StringBuffer softwareVersion = new StringBuffer("");
187 for (int i = 81; i <= 90; i++) {
188 if (heatpumpValues[i] > 0) {
189 softwareVersion.append(Character.toString(heatpumpValues[i]));
193 return softwareVersion.toString();
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);
200 private void handleEventType(State state, HeatpumpChannel heatpumpCommandType) {
201 handler.updateState(heatpumpCommandType.getCommand(), state);
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);
217 if (row1 != null && row1 == 4) {
218 // 4 means error state
219 state = getStateTranslation("errorCodeX", error);
221 state = getStateTranslation("menuStateLine1", row1);
224 var longState = String.format("%s - %s %s - %s", state, getStateTranslation("menuStateLine2", row2),
225 formatHours(time), getStateTranslation("menuStateLine3", row3));
227 handleEventType(new StringType(longState), HeatpumpChannel.CHANNEL_HEATPUMP_STATUS);
230 public static Map<String, Object> getProperties(Integer[] heatpumpValues) {
231 Map<String, Object> properties = new HashMap<String, Object>();
233 String heatpumpType = HeatpumpType.fromCode(heatpumpValues[78]).getName();
235 properties.put("heatpumpType", heatpumpType);
237 // Not sure when Typ 2 should be used
238 // String heatpumpType2 = HeatpumpType.fromCode(heatpumpValues[230]).getName();
239 // properties.put("heatpumpType2", heatpumpType2);
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]));
250 private void updateProperties(Integer[] heatpumpValues) {
251 Map<String, Object> properties = getProperties(heatpumpValues);
253 for (Map.Entry<String, Object> property : properties.entrySet()) {
254 handler.updateProperty(property.getKey(), property.getValue().toString());
258 private String getStateTranslation(String name, @Nullable Integer option) {
259 if (option == null) {
263 String translation = translationProvider
264 .getText("channel-type.luxtronikheatpump." + name + ".state.option." + option);
265 return translation == null ? "" : translation;
268 private String formatHours(@Nullable Integer value) {
269 String returnValue = "";
277 returnValue += String.format("%02d:", intVal / 3600);
279 returnValue += String.format("%02d:", intVal / 60);
281 returnValue += String.format("%02d", intVal);