]> git.basschouten.com Git - openhab-addons.git/blob
66cdb7d1e2d8fcb8055bef81b692f1289b358903
[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.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;
22
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;
30
31 /**
32  * Connector for UDP communication.
33  *
34  * Command for testing:
35  *
36  * @formatter:off
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
42  * @formatter:on
43  *
44  * @author Pauli Anttila - Initial contribution
45  */
46 public class UDPConnector extends NibeHeatPumpBaseConnector {
47
48     private final Logger logger = LoggerFactory.getLogger(UDPConnector.class);
49
50     private Thread readerThread;
51     private NibeHeatPumpConfiguration conf;
52     private DatagramSocket socket;
53
54     public UDPConnector() {
55         logger.debug("Nibe heatpump UDP message listener created");
56     }
57
58     @Override
59     public void connect(NibeHeatPumpConfiguration configuration) throws NibeHeatPumpException {
60         if (isConnected()) {
61             return;
62         }
63         conf = configuration;
64         if (socket == null) {
65             try {
66                 socket = new DatagramSocket(conf.port);
67             } catch (SocketException e) {
68                 throw new NibeHeatPumpException(e);
69             }
70         }
71
72         readerThread = new Reader();
73         readerThread.start();
74         connected = true;
75     }
76
77     @Override
78     public void disconnect() {
79         if (readerThread != null) {
80             logger.debug("Interrupt message listener");
81             readerThread.interrupt();
82             try {
83                 readerThread.join();
84             } catch (InterruptedException e) {
85             }
86         }
87
88         if (socket != null) {
89             socket.close();
90         }
91
92         readerThread = null;
93         connected = false;
94         logger.debug("Closed");
95     }
96
97     @Override
98     public void sendDatagram(NibeHeatPumpMessage msg) throws NibeHeatPumpException {
99         logger.debug("Sending request: {}", msg.toHexString());
100
101         byte data[] = msg.decodeMessage();
102         int port = -1;
103
104         if (msg instanceof ModbusWriteRequestMessage) {
105             port = conf.writeCommandsPort;
106         } else if (msg instanceof ModbusReadRequestMessage) {
107             port = conf.readCommandsPort;
108         } else {
109             logger.trace("Ignore PDU: {}", msg.getClass());
110         }
111
112         if (port > 0) {
113             try (DatagramSocket socket = new DatagramSocket()) {
114                 // Create a packet
115                 DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName(conf.hostName),
116                         port);
117                 socket.send(packet);
118             } catch (IOException e) {
119                 throw new NibeHeatPumpException(e);
120             }
121         }
122     }
123
124     private class Reader extends Thread {
125         boolean interrupted = false;
126
127         @Override
128         public void interrupt() {
129             interrupted = true;
130             super.interrupt();
131         }
132
133         @Override
134         public void run() {
135             logger.debug("Data listener started");
136             while (!interrupted) {
137                 final int packetSize = 255;
138                 try {
139                     if (socket == null) {
140                         socket = new DatagramSocket(conf.port);
141                     }
142                     // Create a packet
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());
152                 }
153             }
154             logger.debug("Data listener stopped");
155         }
156     }
157 }