]> git.basschouten.com Git - openhab-addons.git/blob
56aaac59a13d6a10926ed95e9aa315ec2f4ab97d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.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;
41
42 /**
43  * The {@link HusdataHandler} is responsible for handling commands, which are
44  * sent to one of the channels.
45  *
46  * @author Boris Krivonog - Initial contribution
47  */
48 abstract class HusdataHandler extends BaseThingHandler {
49
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;
55
56     static {
57         MAPPINGS = mappings();
58     }
59
60     protected HusdataHandler(Thing thing) {
61         super(thing);
62     }
63
64     protected abstract RegoConnection createConnection();
65
66     @Override
67     public void initialize() {
68         bufferedReader = null;
69         connection = createConnection();
70
71         updateStatus(ThingStatus.UNKNOWN);
72
73         scheduledRefreshFuture = scheduler.scheduleWithFixedDelay(this::handleDataFromHusdataInterface, 2, 1,
74                 TimeUnit.SECONDS);
75     }
76
77     @Override
78     public void dispose() {
79         super.dispose();
80
81         if (scheduledRefreshFuture != null) {
82             scheduledRefreshFuture.cancel(true);
83             scheduledRefreshFuture = null;
84         }
85
86         if (connection != null) {
87             connection.close();
88             connection = null;
89         }
90     }
91
92     @Override
93     public void handleCommand(ChannelUID channelUID, Command command) {
94     }
95
96     private synchronized void handleDataFromHusdataInterface() {
97         RegoConnection connection = this.connection;
98         if (connection == null) {
99             return;
100         }
101
102         while (!Thread.interrupted()) {
103             try {
104                 if (!connection.isConnected()) {
105                     bufferedReader = null;
106                     connection.connect();
107
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();
113                 }
114
115                 if (bufferedReader == null) {
116                     bufferedReader = new BufferedReader(new InputStreamReader(connection.inputStream()));
117                 }
118
119                 final String line = bufferedReader.readLine();
120                 if (line == null) {
121                     throw new EOFException();
122                 }
123
124                 if (line.isEmpty()) {
125                     continue;
126                 }
127
128                 logger.debug("Got '{}'", line);
129
130                 processReceivedData(line);
131             } catch (SocketTimeoutException e) {
132                 // Do nothing. Just happen to allow the thread to check if it has to stop.
133                 break;
134             } catch (IOException e) {
135                 logger.warn("Processing request failed", e);
136
137                 bufferedReader = null;
138
139                 if (!Thread.interrupted()) {
140                     connection.close();
141                     updateStatus(ThingStatus.OFFLINE);
142                 }
143
144                 break;
145             } catch (Exception e) {
146                 logger.warn("Error occurred during message waiting", e);
147                 break;
148             }
149         }
150
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);
154         }
155     }
156
157     private void processReceivedData(final String line) {
158         if (line.length() != 10) {
159             logger.debug("Unexpected length for '{}'", line);
160             return;
161         }
162
163         if (line.charAt(0) != 'X' || line.charAt(1) != 'R') {
164             logger.debug("Expecting XRxxxxxxxx but got '{}'", line);
165             return;
166         }
167
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));
172
173         logger.debug("dataType = {}, register = {}, value = {}", dataType, register, value);
174
175         updateStatus(ThingStatus.ONLINE);
176
177         int channel = ((dataType & 0x0f) << 12) | (register & 0x0fff);
178         String channelID = MAPPINGS.get(channel);
179         if (channelID == null) {
180             logger.debug("Unsupported register {}.", register);
181             return;
182         }
183
184         if (!isLinked(channelID)) {
185             logger.debug("Ignoring channel {} since it is not linked.", channelID);
186             return;
187         }
188
189         switch (dataType) {
190             case 0x00: // Degrees
191                 updateState(channelID, new QuantityType<>(value / 10.0, Units.DEGREE_ANGLE));
192                 break;
193
194             case 0x02: // Number
195                 updateState(channelID, new DecimalType(value / 10.0));
196                 break;
197
198             case 0x03: // Percent
199                 updateState(channelID, new QuantityType<>(value / 10.0, Units.PERCENT));
200                 break;
201
202             case 0x04: // Ampere
203                 updateState(channelID, new QuantityType<>(value / 10.0, Units.AMPERE));
204                 break;
205
206             case 0x05: // kWh
207                 updateState(channelID, new QuantityType<>(value / 10.0, Units.KILOWATT_HOUR));
208                 break;
209
210             case 0x06: // Hours
211                 updateState(channelID, new QuantityType<>(value, Units.HOUR));
212                 break;
213
214             case 0x07: // Minutes
215                 updateState(channelID, new QuantityType<>(value, Units.MINUTE));
216                 break;
217
218             case 0x09: // kw
219                 updateState(channelID, new QuantityType<>(value, MetricPrefix.KILO(Units.WATT)));
220                 break;
221
222             case 0x01: // Switch
223             case 0x08: // Degree minutes
224             case 0x0A: // Pulses (For S0 El-meter pulse counter)
225                 updateState(channelID, new DecimalType(value));
226                 break;
227
228             default:
229                 logger.debug("Ignoring {} due unsupported data type {}.", channelID, dataType);
230                 break;
231         }
232     }
233
234     private static Map<Integer, String> mappings() {
235         final Map<Integer, String> mappings = new HashMap<>();
236         {
237             // Sensor values
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");
251
252             // Control data
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");
257
258             // Device values
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");
273
274             // Settings
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");
280         }
281
282         return Collections.unmodifiableMap(mappings);
283     }
284 }