]> git.basschouten.com Git - openhab-addons.git/blob
b3dc935abfc74e03d49c8f0cec7701c5b1273f31
[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.nibeheatpump.internal.connection;
14
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Random;
21
22 import org.openhab.binding.nibeheatpump.internal.NibeHeatPumpException;
23 import org.openhab.binding.nibeheatpump.internal.config.NibeHeatPumpConfiguration;
24 import org.openhab.binding.nibeheatpump.internal.message.MessageFactory;
25 import org.openhab.binding.nibeheatpump.internal.message.ModbusDataReadOutMessage;
26 import org.openhab.binding.nibeheatpump.internal.message.ModbusReadRequestMessage;
27 import org.openhab.binding.nibeheatpump.internal.message.ModbusReadResponseMessage;
28 import org.openhab.binding.nibeheatpump.internal.message.ModbusValue;
29 import org.openhab.binding.nibeheatpump.internal.message.ModbusWriteRequestMessage;
30 import org.openhab.binding.nibeheatpump.internal.message.ModbusWriteResponseMessage;
31 import org.openhab.binding.nibeheatpump.internal.message.NibeHeatPumpMessage;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /**
36  * Connector for testing purposes.
37  *
38  * @author Pauli Anttila - Initial contribution
39  */
40 public class SimulatorConnector extends NibeHeatPumpBaseConnector {
41
42     private final Logger logger = LoggerFactory.getLogger(SimulatorConnector.class);
43
44     private Thread readerThread = null;
45
46     private final List<byte[]> readQueue = new ArrayList<>();
47     private final List<byte[]> writeQueue = new ArrayList<>();
48
49     private static final Random RANDOM = new Random();
50
51     private final ArrayList<ModbusValue> dataReadoutValues = new ArrayList<ModbusValue>() {
52         {
53             add(new ModbusValue(43009, 287));
54             add(new ModbusValue(43008, 100));
55             add(new ModbusValue(43005, 976));
56             add(new ModbusValue(40004, 30));
57             add(new ModbusValue(40015, 160));
58             add(new ModbusValue(40016, 120));
59             add(new ModbusValue(40017, 259));
60             add(new ModbusValue(40018, 283));
61             add(new ModbusValue(40071, 276));
62             add(new ModbusValue(40014, 454));
63             add(new ModbusValue(40007, 257));
64             add(new ModbusValue(47381, -80));
65             add(new ModbusValue(47418, 75));
66             add(new ModbusValue(45001, 0));
67             add(new ModbusValue(40008, 269));
68             add(new ModbusValue(40012, 231));
69             add(new ModbusValue(40011, 0));
70             add(new ModbusValue(0xFFFF, 0));
71             add(new ModbusValue(0xFFFF, 0));
72             add(new ModbusValue(0xFFFF, 0));
73         }
74     };
75
76     private final Map<Integer, Integer> cache = Collections.synchronizedMap(new HashMap<>());
77
78     public SimulatorConnector() {
79         logger.debug("Nibe heatpump Test message listener created");
80     }
81
82     @Override
83     public void connect(NibeHeatPumpConfiguration configuration) {
84         if (isConnected()) {
85             return;
86         }
87         readerThread = new Reader();
88         readerThread.start();
89         connected = true;
90     }
91
92     @Override
93     public void disconnect() {
94         if (readerThread != null) {
95             logger.debug("Interrupt message listener");
96             readerThread.interrupt();
97             try {
98                 readerThread.join();
99             } catch (InterruptedException e) {
100             }
101         }
102
103         readerThread = null;
104         connected = false;
105         logger.debug("Closed");
106     }
107
108     @Override
109     public void sendDatagram(NibeHeatPumpMessage msg) {
110         logger.debug("Sending request: {}", msg.toHexString());
111
112         if (msg instanceof ModbusWriteRequestMessage) {
113             writeQueue.add(msg.decodeMessage());
114         } else if (msg instanceof ModbusReadRequestMessage) {
115             readQueue.add(msg.decodeMessage());
116         } else {
117             logger.debug("Ignore PDU: {}", msg.getClass().toString());
118         }
119
120         if (logger.isDebugEnabled()) {
121             logger.debug("Read queue: {}, Write queue: {}", readQueue.size(), writeQueue.size());
122         }
123     }
124
125     private class Reader extends Thread {
126         boolean interrupted = false;
127
128         @Override
129         public void interrupt() {
130             logger.debug("Data listener interupt request received");
131             interrupted = true;
132             super.interrupt();
133         }
134
135         @Override
136         public void run() {
137             logger.debug("Data listener simulator started");
138
139             int i = 1;
140
141             while (!interrupted) {
142                 try {
143                     if (i++ % 60 == 0) {
144                         // simulate CRC error ones a while
145                         ModbusDataReadOutMessage dataReadOut = new ModbusDataReadOutMessage.MessageBuilder()
146                                 .values(dataReadoutValues).build();
147                         byte[] data = dataReadOut.decodeMessage();
148                         // create CRC error
149                         data[data.length - 1] = (byte) (data[data.length - 1] + 1);
150                         sendMsgToListeners(data);
151                         Thread.sleep(1000);
152                         continue;
153                     } else if (i % 100 == 0) {
154                         sendErrorToListeners("Simulated error");
155                         Thread.sleep(1000);
156                         continue;
157                     } else if (i % 10 == 0) {
158                         // ok data
159                         ModbusDataReadOutMessage dataReadOut = new ModbusDataReadOutMessage.MessageBuilder()
160                                 .values(dataReadoutValues).build();
161                         updateData();
162                         updateCache();
163                         sendMsgToListeners(dataReadOut.decodeMessage());
164                     }
165
166                     if (!writeQueue.isEmpty()) {
167                         byte[] data = writeQueue.remove(0);
168                         try {
169                             ModbusWriteRequestMessage writeReq = (ModbusWriteRequestMessage) MessageFactory
170                                     .getMessage(data);
171                             setCacheValue(writeReq.getCoilAddress(), writeReq.getValue());
172                             ModbusWriteResponseMessage writeResp = new ModbusWriteResponseMessage.MessageBuilder()
173                                     .result(true).build();
174                             Thread.sleep(300);
175                             sendMsgToListeners(writeResp.decodeMessage());
176                         } catch (NibeHeatPumpException e) {
177                             logger.debug("Simulation error, cause {}", e.getMessage());
178                         }
179                     } else if (!readQueue.isEmpty()) {
180                         byte[] data = readQueue.remove(0);
181                         try {
182                             ModbusReadRequestMessage readReq = (ModbusReadRequestMessage) MessageFactory
183                                     .getMessage(data);
184                             ModbusReadResponseMessage readResp = new ModbusReadResponseMessage.MessageBuilder()
185                                     .coilAddress(readReq.getCoilAddress())
186                                     .value(getCacheValue(readReq.getCoilAddress())).build();
187                             Thread.sleep(200);
188                             sendMsgToListeners(readResp.decodeMessage());
189                         } catch (NibeHeatPumpException e) {
190                             logger.debug("Simulation error, cause {}", e.getMessage());
191                         }
192                     }
193
194                     if (logger.isDebugEnabled()) {
195                         logger.debug("Read queue: {}, Write queue: {}", readQueue.size(), writeQueue.size());
196                     }
197                     Thread.sleep(800);
198
199                 } catch (InterruptedException e) {
200                 }
201             }
202
203             logger.debug("Data listener stopped");
204         }
205     }
206
207     private void updateCache() {
208         for (ModbusValue val : dataReadoutValues) {
209             cache.put(val.getCoilAddress(), val.getValue());
210         }
211     }
212
213     private int getCacheValue(int coilAddress) {
214         return cache.getOrDefault(coilAddress, 0);
215     }
216
217     private void setCacheValue(int coilAddress, int value) {
218         cache.put(coilAddress, value);
219     }
220
221     private void updateData() {
222         for (ModbusValue val : dataReadoutValues) {
223             switch (val.getCoilAddress()) {
224                 case 43005: // Degree Minutes
225                     val.setValue(val.getValue() + 5);
226                     break;
227                 case 40004: // BT1 Outdoor temp
228                     val.setValue((int) random(-100, 100));
229                     break;
230                 case 40015: // EB100-EP14-BT10 Brine in temp
231                     val.setValue((int) random(200, 600));
232                     break;
233                 case 40016: // EB100-EP14-BT11 Brine out temp
234                     val.setValue((int) random(200, 600));
235                     break;
236                 case 40017: // EB100-EP14-BT12 Cond. out
237                     val.setValue((int) random(200, 600));
238                     break;
239                 case 40018: // EB100-EP14-BT14 Hot gas temp
240                     val.setValue((int) random(200, 600));
241                     break;
242                 case 40071: // BT25 external supply temp
243                     val.setValue((int) random(200, 600));
244                     break;
245             }
246         }
247     }
248
249     private static double random(double min, double max) {
250         return min + (RANDOM.nextDouble() * (max - min));
251     }
252 }