2 * Copyright (c) 2010-2022 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;
20 import javax.measure.Unit;
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;
44 * The {@link ChannelUpdaterJob} updates all channel values
46 * @author Stefan Giehl - Initial contribution
49 public class ChannelUpdaterJob implements SchedulerRunnable, Runnable {
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;
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);
64 public Thing getThing() {
70 // connect to heatpump and check if values can be fetched
71 final HeatpumpConnector connector = new HeatpumpConnector(config.ipAddress, config.port);
75 } catch (IOException e) {
76 logger.warn("Could not connect to heatpump (uuid={}, ip={}, port={}): {}", thing.getUID(), config.ipAddress,
77 config.port, e.getMessage());
79 handler.setStatusConnectionError();
83 handler.setStatusOnline();
85 // read all available values
86 Integer[] heatpumpValues = connector.getValues();
88 // read all parameters
89 Integer[] heatpumpParams = connector.getParams();
90 Integer[] heatpumpVisibilities = connector.getVisibilities();
92 for (HeatpumpChannel channel : HeatpumpChannel.values()) {
94 Integer rawValue = getChannelValue(channel, heatpumpValues, heatpumpParams, heatpumpVisibilities);
96 if (rawValue == null) {
100 State value = convertValueToState(rawValue, channel.getItemClass(), channel.getUnit());
103 handleEventType(value, channel);
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(),
113 setExtendedState(heatpumpValues, heatpumpParams, heatpumpVisibilities);
115 updateProperties(heatpumpValues);
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;
124 if (itemClass == DateTimeItem.class && rawValue > 0) {
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());
133 if (itemClass == NumberItem.class) {
135 return new DecimalType(rawValue);
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);
148 logger.debug("Unhandled unit {} configured for a channel.", unit);
149 return new DecimalType(rawValue);
155 private @Nullable Integer getChannelValue(HeatpumpChannel channel, Integer[] heatpumpValues,
156 Integer[] heatpumpParams, Integer[] heatpumpVisibilities) {
157 Integer channelId = channel.getChannelId();
159 if (channelId == null) {
160 return null; // no channel id to read defined (for channels handeled separatly)
163 if (!channel.isVisible(heatpumpVisibilities) && config.showAllChannels) {
164 logger.debug("Channel {} is not available. Skipped updating it", channel.getCommand());
168 Integer rawValue = null;
170 if (channel.isWritable()) {
171 rawValue = heatpumpParams[channelId];
173 if (heatpumpValues.length <= channelId) {
174 return null; // channel not available
176 rawValue = heatpumpValues[channelId];
182 private String getSoftwareVersion(Integer[] heatpumpValues) {
183 StringBuffer softwareVersion = new StringBuffer("");
185 for (int i = 81; i <= 90; i++) {
186 if (heatpumpValues[i] > 0) {
187 softwareVersion.append(Character.toString(heatpumpValues[i]));
191 return softwareVersion.toString();
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);
198 private void handleEventType(State state, HeatpumpChannel heatpumpCommandType) {
199 handler.updateState(heatpumpCommandType.getCommand(), state);
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);
215 if (row1 != null && row1 == 3) {
216 // 3 means error state
217 state = getStateTranslation("errorCodeX", error);
219 state = getStateTranslation("menuStateLine1", row1);
222 var longState = String.format("%s - %s %s - %s", state, getStateTranslation("menuStateLine2", row2),
223 formatHours(time), getStateTranslation("menuStateLine3", row3));
225 handleEventType(new StringType(longState), HeatpumpChannel.CHANNEL_HEATPUMP_STATUS);
228 private void updateProperties(Integer[] heatpumpValues) {
229 String heatpumpType = HeatpumpType.fromCode(heatpumpValues[78]).getName();
231 setProperty("heatpumpType", heatpumpType);
233 // Not sure when Typ 2 should be used
234 // String heatpumpType2 = HeatpumpType.fromCode(heatpumpValues[230]).getName();
235 // setProperty("heatpumpType2", heatpumpType2);
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]));
244 private String getStateTranslation(String name, @Nullable Integer option) {
245 if (option == null) {
249 String translation = translationProvider
250 .getText("channel-type.luxtronikheatpump." + name + ".state.option." + option);
251 return translation == null ? "" : translation;
254 private void setProperty(String name, String value) {
255 handler.updateProperty(name, value);
258 private String formatHours(@Nullable Integer value) {
259 String returnValue = "";
267 returnValue += String.format("%02d:", intVal / 3600);
269 returnValue += String.format("%02d:", intVal / 60);
271 returnValue += String.format("%02d", intVal);