2 * Copyright (c) 2010-2023 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.io.IOException;
16 import java.io.InterruptedIOException;
17 import java.net.DatagramPacket;
18 import java.net.DatagramSocket;
19 import java.net.InetAddress;
20 import java.net.SocketException;
21 import java.util.Arrays;
23 import org.openhab.binding.nibeheatpump.internal.NibeHeatPumpException;
24 import org.openhab.binding.nibeheatpump.internal.config.NibeHeatPumpConfiguration;
25 import org.openhab.binding.nibeheatpump.internal.message.ModbusReadRequestMessage;
26 import org.openhab.binding.nibeheatpump.internal.message.ModbusWriteRequestMessage;
27 import org.openhab.binding.nibeheatpump.internal.message.NibeHeatPumpMessage;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
32 * Connector for UDP communication.
34 * Command for testing:
37 * OK: echo -e "\x5C\x00\x20\x68\x50\x01\xA8\x1F\x01\x00\xA8\x64\x00\xFD\xA7\xD0\x03\x44\x9C\x1E\x00\x4F\x9C\xA0\x00\x50\x9C\x78\x00\x51\x9C\x03\x01\x52\x9C\x1B\x01\x87\x9C\x14\x01\x4E\x9C\xC6\x01\x47\x9C\x01\x01\x15\xB9\xB0\xFF\x3A\xB9\x4B\x00\xC9\xAF\x00\x00\x48\x9C\x0D\x01\x4C\x9C\xE7\x00\x4B\x9C\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\x45" | nc -4u -w1 localhost 9999
38 * Special len: echo -e "\x5C\x00\x20\x68\x51\x44\x9C\x25\x00\x48\x9C\xFC\x00\x4C\x9C\xF1\x00\x4E\x9C\xC7\x01\x4D\x9C\x0B\x02\x4F\x9C\x25\x00\x50\x9C\x33\x00\x51\x9C\x0B\x01\x52\x9C\x5C\x5C\x01\x56\x9C\x31\x00\xC9\xAF\x00\x00\x01\xA8\x0C\x01\xFD\xA7\x16\xFA\xFA\xA9\x07\x00\x98\xA9\x1B\x1B\xFF\xFF\x00\x00\xA0\xA9\xCA\x02\xFF\xFF\x00\x00\x9C\xA9\x92\x12\xFF\xFF\x00\x00\xBE" | nc -4u -w1 localhost 9999
39 * Special len: echo -e "\x5C\x00\x20\x68\x52\x44\x9C\x25\x00\x48\x9C\xFE\x00\x4C\x9C\xF2\x00\x4E\x9C\xD4\x01\x4D\x9C\xFB\x01\x4F\x9C\x25\x00\x50\x9C\x37\x00\x51\x9C\x0D\x01\x52\x9C\x5C\x5C\x01\x56\x9C\x32\x00\xC9\xAF\x00\x00\x01\xA8\x0C\x01\xFD\xA7\x12\xFA\xFA\xA9\x07\x00\x98\xA9\x5C\x5C\x1B\xFF\xFF\x00\x00\xA0\xA9\xD1\x02\xFF\xFF\x00\x00\x9C\xA9\xB4\x12\xFF\xFF\x00\x00\x7F" | nc -4u -w1 localhost 9999
40 * Special CRC: echo -e "\x5C\x00\x20\x68\x50\x44\x9C\x26\x00\x48\x9C\xF6\x00\x4C\x9C\xF1\x00\x4E\x9C\xD6\x01\x4D\x9C\x0C\x02\x4F\x9C\x45\x00\x50\x9C\x3F\x00\x51\x9C\xF1\x00\x52\x9C\x04\x01\x56\x9C\xD5\x00\xC9\xAF\x00\x00\x01\xA8\x0C\x01\xFD\xA7\x99\xFA\xFA\xA9\x02\x00\x98\xA9\x1A\x1B\xFF\xFF\x00\x00\xA0\xA9\xCA\x02\xFF\xFF\x00\x00\x9C\xA9\x92\x12\xFF\xFF\x00\x00\xC5" | nc -4u -w1 localhost 9999
41 * CRC failure: echo -e "\x5C\x00\x20\x68\x50\x01\xA8\x1F\x01\x00\xA8\x64\x00\xFD\xA7\xD0\x03\x44\x9C\x1E\x00\x4F\x9C\xA0\x00\x50\x9C\x78\x00\x51\x9C\x03\x01\x52\x9C\x1B\x01\x87\x9C\x14\x01\x4E\x9C\xC6\x01\x47\x9C\x01\x01\x15\xB9\xB0\xFF\x3A\xB9\x4B\x00\xC9\xAF\x00\x00\x48\x9C\x0D\x01\x4C\x9C\xE7\x00\x4B\x9C\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\x44" | nc -4u -w1 localhost 9999
44 * @author Pauli Anttila - Initial contribution
46 public class UDPConnector extends NibeHeatPumpBaseConnector {
48 private final Logger logger = LoggerFactory.getLogger(UDPConnector.class);
50 private Thread readerThread;
51 private NibeHeatPumpConfiguration conf;
52 private DatagramSocket socket;
54 public UDPConnector() {
55 logger.debug("Nibe heatpump UDP message listener created");
59 public void connect(NibeHeatPumpConfiguration configuration) throws NibeHeatPumpException {
66 socket = new DatagramSocket(conf.port);
67 } catch (SocketException e) {
68 throw new NibeHeatPumpException(e);
72 readerThread = new Reader();
78 public void disconnect() {
79 if (readerThread != null) {
80 logger.debug("Interrupt message listener");
81 readerThread.interrupt();
84 } catch (InterruptedException e) {
94 logger.debug("Closed");
98 public void sendDatagram(NibeHeatPumpMessage msg) throws NibeHeatPumpException {
99 logger.debug("Sending request: {}", msg.toHexString());
101 byte data[] = msg.decodeMessage();
104 if (msg instanceof ModbusWriteRequestMessage) {
105 port = conf.writeCommandsPort;
106 } else if (msg instanceof ModbusReadRequestMessage) {
107 port = conf.readCommandsPort;
109 logger.trace("Ignore PDU: {}", msg.getClass());
113 try (DatagramSocket socket = new DatagramSocket()) {
115 DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName(conf.hostName),
118 } catch (IOException e) {
119 throw new NibeHeatPumpException(e);
124 private class Reader extends Thread {
125 boolean interrupted = false;
128 public void interrupt() {
135 logger.debug("Data listener started");
136 while (!interrupted) {
137 final int packetSize = 255;
139 if (socket == null) {
140 socket = new DatagramSocket(conf.port);
143 DatagramPacket packet = new DatagramPacket(new byte[packetSize], packetSize);
144 // Receive a packet (blocking)
145 socket.receive(packet);
146 sendMsgToListeners(Arrays.copyOfRange(packet.getData(), 0, packet.getLength()));
147 } catch (InterruptedIOException e) {
148 Thread.currentThread().interrupt();
149 logger.error("Interrupted via InterruptedIOException");
150 } catch (IOException e) {
151 sendErrorToListeners(e.getMessage());
154 logger.debug("Data listener stopped");