2 * Copyright (c) 2010-2021 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.regoheatpump.internal.handler;
15 import static org.openhab.binding.regoheatpump.internal.RegoHeatPumpBindingConstants.*;
17 import java.io.BufferedReader;
18 import java.io.EOFException;
19 import java.io.IOException;
20 import java.io.InputStreamReader;
21 import java.io.OutputStream;
22 import java.net.SocketTimeoutException;
23 import java.util.Collections;
24 import java.util.HashMap;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
29 import org.openhab.binding.regoheatpump.internal.protocol.RegoConnection;
30 import org.openhab.core.library.types.DecimalType;
31 import org.openhab.core.library.types.QuantityType;
32 import org.openhab.core.library.unit.MetricPrefix;
33 import org.openhab.core.library.unit.Units;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.thing.binding.BaseThingHandler;
38 import org.openhab.core.types.Command;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * The {@link HusdataHandler} is responsible for handling commands, which are
44 * sent to one of the channels.
46 * @author Boris Krivonog - Initial contribution
48 abstract class HusdataHandler extends BaseThingHandler {
50 private static final Map<Integer, String> MAPPINGS;
51 private final Logger logger = LoggerFactory.getLogger(HusdataHandler.class);
52 private RegoConnection connection;
53 private ScheduledFuture<?> scheduledRefreshFuture;
54 private BufferedReader bufferedReader;
57 MAPPINGS = mappings();
60 protected HusdataHandler(Thing thing) {
64 protected abstract RegoConnection createConnection();
67 public void initialize() {
68 bufferedReader = null;
69 connection = createConnection();
71 updateStatus(ThingStatus.UNKNOWN);
73 scheduledRefreshFuture = scheduler.scheduleWithFixedDelay(this::handleDataFromHusdataInterface, 2, 1,
78 public void dispose() {
81 if (scheduledRefreshFuture != null) {
82 scheduledRefreshFuture.cancel(true);
83 scheduledRefreshFuture = null;
86 if (connection != null) {
93 public void handleCommand(ChannelUID channelUID, Command command) {
96 private synchronized void handleDataFromHusdataInterface() {
97 RegoConnection connection = this.connection;
98 if (connection == null) {
102 while (!Thread.interrupted()) {
104 if (!connection.isConnected()) {
105 bufferedReader = null;
106 connection.connect();
108 // Request real-time registers.
109 logger.debug("Requesting read and dump of real-time registers.");
110 final OutputStream outputStream = connection.outputStream();
111 outputStream.write(new String("XR\r\n").getBytes());
112 outputStream.flush();
115 if (bufferedReader == null) {
116 bufferedReader = new BufferedReader(new InputStreamReader(connection.inputStream()));
119 final String line = bufferedReader.readLine();
121 throw new EOFException();
124 if (line.isEmpty()) {
128 logger.debug("Got '{}'", line);
130 processReceivedData(line);
131 } catch (SocketTimeoutException e) {
132 // Do nothing. Just happen to allow the thread to check if it has to stop.
134 } catch (IOException e) {
135 logger.warn("Processing request failed", e);
137 bufferedReader = null;
139 if (!Thread.interrupted()) {
141 updateStatus(ThingStatus.OFFLINE);
145 } catch (Exception e) {
146 logger.warn("Error occurred during message waiting", e);
151 // If state here is still unknown, than something went wrong so set thing status to OFFLINE.
152 if (thing.getStatus() == ThingStatus.UNKNOWN) {
153 updateStatus(ThingStatus.OFFLINE);
157 private void processReceivedData(final String line) {
158 if (line.length() != 10) {
159 logger.debug("Unexpected length for '{}'", line);
163 if (line.charAt(0) != 'X' || line.charAt(1) != 'R') {
164 logger.debug("Expecting XRxxxxxxxx but got '{}'", line);
168 int dataType = Integer.parseInt(line.substring(2, 3), 16);
169 int register = Integer.parseInt(line.substring(3, 6), 16);
170 int value = (short) (Integer.parseInt(line.substring(6, 8), 16) * 256
171 + Integer.parseInt(line.substring(8, 10), 16));
173 logger.debug("dataType = {}, register = {}, value = {}", dataType, register, value);
175 updateStatus(ThingStatus.ONLINE);
177 int channel = ((dataType & 0x0f) << 12) | (register & 0x0fff);
178 String channelID = MAPPINGS.get(channel);
179 if (channelID == null) {
180 logger.debug("Unsupported register {}.", register);
184 if (!isLinked(channelID)) {
185 logger.debug("Ignoring channel {} since it is not linked.", channelID);
190 case 0x00: // Degrees
191 updateState(channelID, new QuantityType<>(value / 10.0, Units.DEGREE_ANGLE));
195 updateState(channelID, new DecimalType(value / 10.0));
198 case 0x03: // Percent
199 updateState(channelID, new QuantityType<>(value / 10.0, Units.PERCENT));
203 updateState(channelID, new QuantityType<>(value / 10.0, Units.AMPERE));
207 updateState(channelID, new QuantityType<>(value / 10.0, Units.KILOWATT_HOUR));
211 updateState(channelID, new QuantityType<>(value, Units.HOUR));
214 case 0x07: // Minutes
215 updateState(channelID, new QuantityType<>(value, Units.MINUTE));
219 updateState(channelID, new QuantityType<>(value, MetricPrefix.KILO(Units.WATT)));
223 case 0x08: // Degree minutes
224 case 0x0A: // Pulses (For S0 El-meter pulse counter)
225 updateState(channelID, new DecimalType(value));
229 logger.debug("Ignoring {} due unsupported data type {}.", channelID, dataType);
234 private static Map<Integer, String> mappings() {
235 final Map<Integer, String> mappings = new HashMap<>();
238 mappings.put(0x0001, CHANNEL_GROUP_SENSOR_VALUES + "radiatorReturn");
239 mappings.put(0x0002, CHANNEL_GROUP_SENSOR_VALUES + "radiatorForward");
240 mappings.put(0x0003, CHANNEL_GROUP_SENSOR_VALUES + "heatFluidIn");
241 mappings.put(0x0004, CHANNEL_GROUP_SENSOR_VALUES + "heatFluidOut");
242 mappings.put(0x0005, CHANNEL_GROUP_SENSOR_VALUES + "coldFluidIn");
243 mappings.put(0x0006, CHANNEL_GROUP_SENSOR_VALUES + "coldFluidOut");
244 mappings.put(0x0007, CHANNEL_GROUP_SENSOR_VALUES + "outdoor");
245 mappings.put(0x0008, CHANNEL_GROUP_SENSOR_VALUES + "indoor");
246 mappings.put(0x0009, CHANNEL_GROUP_SENSOR_VALUES + "hotWater");
247 mappings.put(0x000A, CHANNEL_GROUP_SENSOR_VALUES + "externalHotWater");
248 mappings.put(0x000B, CHANNEL_GROUP_SENSOR_VALUES + "compressor");
249 mappings.put(0x000E, CHANNEL_GROUP_SENSOR_VALUES + "airIntake");
250 mappings.put(0x0011, CHANNEL_GROUP_SENSOR_VALUES + "pool");
253 mappings.put(0x3104, CHANNEL_GROUP_CONTROL_DATA + "addHeatPowerPercent"); // %
254 mappings.put(0x5104, CHANNEL_GROUP_CONTROL_DATA + "addHeatPowerEnergy"); // kWh
255 mappings.put(0x0107, CHANNEL_GROUP_CONTROL_DATA + "radiatorReturnTarget");
256 mappings.put(0x2108, CHANNEL_GROUP_CONTROL_DATA + "compressorSpeed");
259 mappings.put(0x1A01, CHANNEL_GROUP_DEVICE_VALUES + "compressor");
260 mappings.put(0x1A04, CHANNEL_GROUP_DEVICE_VALUES + "coldFluidPump");
261 mappings.put(0x1A05, CHANNEL_GROUP_DEVICE_VALUES + "heatFluidPump");
262 mappings.put(0x1A06, CHANNEL_GROUP_DEVICE_VALUES + "radiatorPump");
263 mappings.put(0x1A07, CHANNEL_GROUP_DEVICE_VALUES + "switchValve");
264 mappings.put(0x1A08, CHANNEL_GROUP_DEVICE_VALUES + "switchValve2");
265 mappings.put(0x1A09, CHANNEL_GROUP_DEVICE_VALUES + "fan");
266 mappings.put(0x1A0A, CHANNEL_GROUP_DEVICE_VALUES + "highPressostat");
267 mappings.put(0x1A0B, CHANNEL_GROUP_DEVICE_VALUES + "lowPressostat");
268 mappings.put(0x1A0C, CHANNEL_GROUP_DEVICE_VALUES + "heatingCable");
269 mappings.put(0x1A0D, CHANNEL_GROUP_DEVICE_VALUES + "crankCaseHeater");
270 mappings.put(0x1A20, CHANNEL_GROUP_DEVICE_VALUES + "alarm");
271 mappings.put(0xAFF1, CHANNEL_GROUP_DEVICE_VALUES + "elMeter1");
272 mappings.put(0xAFF2, CHANNEL_GROUP_DEVICE_VALUES + "elMeter2");
275 mappings.put(0x0203, CHANNEL_GROUP_SETTINGS + "indoorTempSetting");
276 mappings.put(0x2204, CHANNEL_GROUP_SETTINGS + "curveInflByInTemp");
277 mappings.put(0x0205, CHANNEL_GROUP_SETTINGS + "heatCurve");
278 mappings.put(0x0206, CHANNEL_GROUP_SETTINGS + "heatCurve2");
279 mappings.put(0x0207, CHANNEL_GROUP_SETTINGS + "heatCurveFineAdj");
282 return Collections.unmodifiableMap(mappings);