2 * Copyright (c) 2010-2024 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.nibeheatpump.internal.connection;
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.List;
20 import java.util.Random;
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;
36 * Connector for testing purposes.
38 * @author Pauli Anttila - Initial contribution
40 public class SimulatorConnector extends NibeHeatPumpBaseConnector {
42 private final Logger logger = LoggerFactory.getLogger(SimulatorConnector.class);
44 private Thread readerThread = null;
46 private final List<byte[]> readQueue = new ArrayList<>();
47 private final List<byte[]> writeQueue = new ArrayList<>();
49 private static final Random RANDOM = new Random();
51 private final ArrayList<ModbusValue> dataReadoutValues = new ArrayList<ModbusValue>() {
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));
76 private final Map<Integer, Integer> cache = Collections.synchronizedMap(new HashMap<>());
78 public SimulatorConnector() {
79 logger.debug("Nibe heatpump Test message listener created");
83 public void connect(NibeHeatPumpConfiguration configuration) {
87 readerThread = new Reader();
93 public void disconnect() {
94 if (readerThread != null) {
95 logger.debug("Interrupt message listener");
96 readerThread.interrupt();
99 } catch (InterruptedException e) {
105 logger.debug("Closed");
109 public void sendDatagram(NibeHeatPumpMessage msg) {
110 logger.debug("Sending request: {}", msg.toHexString());
112 if (msg instanceof ModbusWriteRequestMessage) {
113 writeQueue.add(msg.decodeMessage());
114 } else if (msg instanceof ModbusReadRequestMessage) {
115 readQueue.add(msg.decodeMessage());
117 logger.debug("Ignore PDU: {}", msg.getClass().toString());
120 if (logger.isDebugEnabled()) {
121 logger.debug("Read queue: {}, Write queue: {}", readQueue.size(), writeQueue.size());
125 private class Reader extends Thread {
126 boolean interrupted = false;
129 public void interrupt() {
130 logger.debug("Data listener interupt request received");
137 logger.debug("Data listener simulator started");
141 while (!interrupted) {
144 // simulate CRC error ones a while
145 ModbusDataReadOutMessage dataReadOut = new ModbusDataReadOutMessage.MessageBuilder()
146 .values(dataReadoutValues).build();
147 byte[] data = dataReadOut.decodeMessage();
149 data[data.length - 1] = (byte) (data[data.length - 1] + 1);
150 sendMsgToListeners(data);
153 } else if (i % 100 == 0) {
154 sendErrorToListeners("Simulated error");
157 } else if (i % 10 == 0) {
159 ModbusDataReadOutMessage dataReadOut = new ModbusDataReadOutMessage.MessageBuilder()
160 .values(dataReadoutValues).build();
163 sendMsgToListeners(dataReadOut.decodeMessage());
166 if (!writeQueue.isEmpty()) {
167 byte[] data = writeQueue.remove(0);
169 ModbusWriteRequestMessage writeReq = (ModbusWriteRequestMessage) MessageFactory
171 setCacheValue(writeReq.getCoilAddress(), writeReq.getValue());
172 ModbusWriteResponseMessage writeResp = new ModbusWriteResponseMessage.MessageBuilder()
173 .result(true).build();
175 sendMsgToListeners(writeResp.decodeMessage());
176 } catch (NibeHeatPumpException e) {
177 logger.debug("Simulation error, cause {}", e.getMessage());
179 } else if (!readQueue.isEmpty()) {
180 byte[] data = readQueue.remove(0);
182 ModbusReadRequestMessage readReq = (ModbusReadRequestMessage) MessageFactory
184 ModbusReadResponseMessage readResp = new ModbusReadResponseMessage.MessageBuilder()
185 .coilAddress(readReq.getCoilAddress())
186 .value(getCacheValue(readReq.getCoilAddress())).build();
188 sendMsgToListeners(readResp.decodeMessage());
189 } catch (NibeHeatPumpException e) {
190 logger.debug("Simulation error, cause {}", e.getMessage());
194 if (logger.isDebugEnabled()) {
195 logger.debug("Read queue: {}, Write queue: {}", readQueue.size(), writeQueue.size());
198 } catch (InterruptedException e) {
202 logger.debug("Data listener stopped");
206 private void updateCache() {
207 for (ModbusValue val : dataReadoutValues) {
208 cache.put(val.getCoilAddress(), val.getValue());
212 private int getCacheValue(int coilAddress) {
213 return cache.getOrDefault(coilAddress, 0);
216 private void setCacheValue(int coilAddress, int value) {
217 cache.put(coilAddress, value);
220 private void updateData() {
221 for (ModbusValue val : dataReadoutValues) {
222 switch (val.getCoilAddress()) {
223 case 43005: // Degree Minutes
224 val.setValue(val.getValue() + 5);
226 case 40004: // BT1 Outdoor temp
227 val.setValue((int) random(-100, 100));
229 case 40015: // EB100-EP14-BT10 Brine in temp
230 val.setValue((int) random(200, 600));
232 case 40016: // EB100-EP14-BT11 Brine out temp
233 val.setValue((int) random(200, 600));
235 case 40017: // EB100-EP14-BT12 Cond. out
236 val.setValue((int) random(200, 600));
238 case 40018: // EB100-EP14-BT14 Hot gas temp
239 val.setValue((int) random(200, 600));
241 case 40071: // BT25 external supply temp
242 val.setValue((int) random(200, 600));
248 private static double random(double min, double max) {
249 return min + (RANDOM.nextDouble() * (max - min));