]> git.basschouten.com Git - openhab-addons.git/blob
be94581da72e7616cc9444d9bb88cead560bf472
[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.regoheatpump.internal.handler;
14
15 import static org.openhab.binding.regoheatpump.internal.RegoHeatPumpBindingConstants.*;
16
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;
25 import java.util.Map;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.binding.regoheatpump.internal.protocol.RegoConnection;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.QuantityType;
34 import org.openhab.core.library.unit.MetricPrefix;
35 import org.openhab.core.library.unit.Units;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.types.Command;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * The {@link HusdataHandler} is responsible for handling commands, which are
47  * sent to one of the channels.
48  *
49  * @author Boris Krivonog - Initial contribution
50  */
51 @NonNullByDefault
52 abstract class HusdataHandler extends BaseThingHandler {
53
54     private static final Map<Integer, String> MAPPINGS;
55     private final Logger logger = LoggerFactory.getLogger(HusdataHandler.class);
56     private @Nullable RegoConnection connection;
57     private @Nullable ScheduledFuture<?> scheduledRefreshFuture;
58     private @Nullable BufferedReader bufferedReader;
59
60     static {
61         MAPPINGS = mappings();
62     }
63
64     protected HusdataHandler(Thing thing) {
65         super(thing);
66     }
67
68     protected abstract RegoConnection createConnection() throws IOException;
69
70     @Override
71     public void initialize() {
72         bufferedReader = null;
73
74         try {
75             connection = createConnection();
76         } catch (IOException e) {
77             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
78             return;
79         }
80
81         updateStatus(ThingStatus.UNKNOWN);
82
83         scheduledRefreshFuture = scheduler.scheduleWithFixedDelay(this::handleDataFromHusdataInterface, 2, 1,
84                 TimeUnit.SECONDS);
85     }
86
87     @Override
88     public void dispose() {
89         super.dispose();
90
91         ScheduledFuture<?> scheduledRefreshFuture = this.scheduledRefreshFuture;
92         this.scheduledRefreshFuture = null;
93         if (scheduledRefreshFuture != null) {
94             scheduledRefreshFuture.cancel(true);
95         }
96
97         RegoConnection connection = this.connection;
98         this.connection = null;
99         if (connection != null) {
100             connection.close();
101         }
102     }
103
104     @Override
105     public void handleCommand(ChannelUID channelUID, Command command) {
106     }
107
108     private synchronized void handleDataFromHusdataInterface() {
109         RegoConnection connection = this.connection;
110         if (connection == null) {
111             return;
112         }
113
114         while (!Thread.interrupted()) {
115             try {
116                 if (!connection.isConnected()) {
117                     bufferedReader = null;
118                     connection.connect();
119
120                     // Request real-time registers.
121                     logger.debug("Requesting read and dump of real-time registers.");
122                     final OutputStream outputStream = connection.outputStream();
123                     outputStream.write(new String("XR\r\n").getBytes());
124                     outputStream.flush();
125                 }
126
127                 BufferedReader bufferedReader = this.bufferedReader;
128                 if (bufferedReader == null) {
129                     bufferedReader = new BufferedReader(new InputStreamReader(connection.inputStream()));
130                     this.bufferedReader = bufferedReader;
131                 }
132
133                 final String line = bufferedReader.readLine();
134                 if (line == null) {
135                     throw new EOFException();
136                 }
137
138                 if (line.isEmpty()) {
139                     continue;
140                 }
141
142                 logger.debug("Got '{}'", line);
143
144                 processReceivedData(line);
145             } catch (SocketTimeoutException e) {
146                 // Do nothing. Just happen to allow the thread to check if it has to stop.
147                 break;
148             } catch (IOException e) {
149                 logger.warn("Processing request failed", e);
150
151                 bufferedReader = null;
152
153                 if (!Thread.interrupted()) {
154                     connection.close();
155                     updateStatus(ThingStatus.OFFLINE);
156                 }
157
158                 break;
159             } catch (Exception e) {
160                 logger.warn("Error occurred during message waiting", e);
161                 break;
162             }
163         }
164
165         // If state here is still unknown, than something went wrong so set thing status to OFFLINE.
166         if (thing.getStatus() == ThingStatus.UNKNOWN) {
167             updateStatus(ThingStatus.OFFLINE);
168         }
169     }
170
171     private void processReceivedData(final String line) {
172         if (line.length() != 10) {
173             logger.debug("Unexpected length for '{}'", line);
174             return;
175         }
176
177         if (line.charAt(0) != 'X' || line.charAt(1) != 'R') {
178             logger.debug("Expecting XRxxxxxxxx but got '{}'", line);
179             return;
180         }
181
182         int dataType = Integer.parseInt(line.substring(2, 3), 16);
183         int register = Integer.parseInt(line.substring(3, 6), 16);
184         int value = (short) (Integer.parseInt(line.substring(6, 8), 16) * 256
185                 + Integer.parseInt(line.substring(8, 10), 16));
186
187         logger.debug("dataType = {}, register = {}, value = {}", dataType, register, value);
188
189         updateStatus(ThingStatus.ONLINE);
190
191         int channel = ((dataType & 0x0f) << 12) | (register & 0x0fff);
192         String channelID = MAPPINGS.get(channel);
193         if (channelID == null) {
194             logger.debug("Unsupported register {}.", register);
195             return;
196         }
197
198         if (!isLinked(channelID)) {
199             logger.debug("Ignoring channel {} since it is not linked.", channelID);
200             return;
201         }
202
203         switch (dataType) {
204             case 0x00: // Degrees
205                 updateState(channelID, new QuantityType<>(value / 10.0, Units.DEGREE_ANGLE));
206                 break;
207
208             case 0x02: // Number
209                 updateState(channelID, new DecimalType(value / 10.0));
210                 break;
211
212             case 0x03: // Percent
213                 updateState(channelID, new QuantityType<>(value / 10.0, Units.PERCENT));
214                 break;
215
216             case 0x04: // Ampere
217                 updateState(channelID, new QuantityType<>(value / 10.0, Units.AMPERE));
218                 break;
219
220             case 0x05: // kWh
221                 updateState(channelID, new QuantityType<>(value / 10.0, Units.KILOWATT_HOUR));
222                 break;
223
224             case 0x06: // Hours
225                 updateState(channelID, new QuantityType<>(value, Units.HOUR));
226                 break;
227
228             case 0x07: // Minutes
229                 updateState(channelID, new QuantityType<>(value, Units.MINUTE));
230                 break;
231
232             case 0x09: // kw
233                 updateState(channelID, new QuantityType<>(value, MetricPrefix.KILO(Units.WATT)));
234                 break;
235
236             case 0x01: // Switch
237             case 0x08: // Degree minutes
238             case 0x0A: // Pulses (For S0 El-meter pulse counter)
239                 updateState(channelID, new DecimalType(value));
240                 break;
241
242             default:
243                 logger.debug("Ignoring {} due unsupported data type {}.", channelID, dataType);
244                 break;
245         }
246     }
247
248     private static Map<Integer, String> mappings() {
249         final Map<Integer, String> mappings = new HashMap<>();
250         {
251             // Sensor values
252             mappings.put(0x0001, CHANNEL_GROUP_SENSOR_VALUES + "radiatorReturn");
253             mappings.put(0x0002, CHANNEL_GROUP_SENSOR_VALUES + "radiatorForward");
254             mappings.put(0x0003, CHANNEL_GROUP_SENSOR_VALUES + "heatFluidIn");
255             mappings.put(0x0004, CHANNEL_GROUP_SENSOR_VALUES + "heatFluidOut");
256             mappings.put(0x0005, CHANNEL_GROUP_SENSOR_VALUES + "coldFluidIn");
257             mappings.put(0x0006, CHANNEL_GROUP_SENSOR_VALUES + "coldFluidOut");
258             mappings.put(0x0007, CHANNEL_GROUP_SENSOR_VALUES + "outdoor");
259             mappings.put(0x0008, CHANNEL_GROUP_SENSOR_VALUES + "indoor");
260             mappings.put(0x0009, CHANNEL_GROUP_SENSOR_VALUES + "hotWater");
261             mappings.put(0x000A, CHANNEL_GROUP_SENSOR_VALUES + "externalHotWater");
262             mappings.put(0x000B, CHANNEL_GROUP_SENSOR_VALUES + "compressor");
263             mappings.put(0x000E, CHANNEL_GROUP_SENSOR_VALUES + "airIntake");
264             mappings.put(0x0011, CHANNEL_GROUP_SENSOR_VALUES + "pool");
265
266             // Control data
267             mappings.put(0x3104, CHANNEL_GROUP_CONTROL_DATA + "addHeatPowerPercent"); // %
268             mappings.put(0x5104, CHANNEL_GROUP_CONTROL_DATA + "addHeatPowerEnergy"); // kWh
269             mappings.put(0x0107, CHANNEL_GROUP_CONTROL_DATA + "radiatorReturnTarget");
270             mappings.put(0x2108, CHANNEL_GROUP_CONTROL_DATA + "compressorSpeed");
271
272             // Device values
273             mappings.put(0x1A01, CHANNEL_GROUP_DEVICE_VALUES + "compressor");
274             mappings.put(0x1A04, CHANNEL_GROUP_DEVICE_VALUES + "coldFluidPump");
275             mappings.put(0x1A05, CHANNEL_GROUP_DEVICE_VALUES + "heatFluidPump");
276             mappings.put(0x1A06, CHANNEL_GROUP_DEVICE_VALUES + "radiatorPump");
277             mappings.put(0x1A07, CHANNEL_GROUP_DEVICE_VALUES + "switchValve");
278             mappings.put(0x1A08, CHANNEL_GROUP_DEVICE_VALUES + "switchValve2");
279             mappings.put(0x1A09, CHANNEL_GROUP_DEVICE_VALUES + "fan");
280             mappings.put(0x1A0A, CHANNEL_GROUP_DEVICE_VALUES + "highPressostat");
281             mappings.put(0x1A0B, CHANNEL_GROUP_DEVICE_VALUES + "lowPressostat");
282             mappings.put(0x1A0C, CHANNEL_GROUP_DEVICE_VALUES + "heatingCable");
283             mappings.put(0x1A0D, CHANNEL_GROUP_DEVICE_VALUES + "crankCaseHeater");
284             mappings.put(0x1A20, CHANNEL_GROUP_DEVICE_VALUES + "alarm");
285             mappings.put(0xAFF1, CHANNEL_GROUP_DEVICE_VALUES + "elMeter1");
286             mappings.put(0xAFF2, CHANNEL_GROUP_DEVICE_VALUES + "elMeter2");
287
288             // Settings
289             mappings.put(0x0203, CHANNEL_GROUP_SETTINGS + "indoorTempSetting");
290             mappings.put(0x2204, CHANNEL_GROUP_SETTINGS + "curveInflByInTemp");
291             mappings.put(0x0205, CHANNEL_GROUP_SETTINGS + "heatCurve");
292             mappings.put(0x0206, CHANNEL_GROUP_SETTINGS + "heatCurve2");
293             mappings.put(0x0207, CHANNEL_GROUP_SETTINGS + "heatCurveFineAdj");
294         }
295
296         return Collections.unmodifiableMap(mappings);
297     }
298 }