]> git.basschouten.com Git - openhab-addons.git/blob
a3b85ebb51a7a7e4c9e9f1d6e38bed1087119b8a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.gce.internal.handler;
14
15 import java.io.BufferedReader;
16 import java.io.IOException;
17 import java.io.InputStreamReader;
18 import java.io.PrintWriter;
19 import java.net.Socket;
20 import java.net.SocketTimeoutException;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.gce.internal.model.M2MMessageParser;
25 import org.openhab.core.thing.ThingUID;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 /**
30  * The {@link Ipx800DeviceConnector} is responsible for connecting,
31  * reading, writing and disconnecting from the Ipx800.
32  *
33  * @author Seebag - Initial Contribution
34  * @author GaĆ«l L'hopital - Ported and adapted for OH2
35  */
36 @NonNullByDefault
37 public class Ipx800DeviceConnector extends Thread {
38     private final Logger logger = LoggerFactory.getLogger(Ipx800DeviceConnector.class);
39     private static final int DEFAULT_SOCKET_TIMEOUT_MS = 5000;
40     private static final int DEFAULT_RECONNECT_TIMEOUT_MS = 5000;
41     private static final int MAX_KEEPALIVE_FAILURE = 3;
42     private static final String ENDL = "\r\n";
43
44     private final String hostname;
45     private final int portNumber;
46     private @Nullable M2MMessageParser parser;
47
48     private @NonNullByDefault({}) Socket client;
49     private @NonNullByDefault({}) BufferedReader in;
50     private @NonNullByDefault({}) PrintWriter out;
51
52     private int failedKeepalive = 0;
53     private boolean waitingKeepaliveResponse = false;
54
55     public Ipx800DeviceConnector(String hostname, int portNumber, ThingUID uid) {
56         super("OH-binding-" + uid);
57         this.hostname = hostname;
58         this.portNumber = portNumber;
59         setDaemon(true);
60     }
61
62     public synchronized void send(String message) {
63         logger.debug("Sending '{}' to Ipx800", message);
64         out.write(message + ENDL);
65         out.flush();
66     }
67
68     /**
69      * Connect to the ipx800
70      *
71      * @throws IOException
72      */
73     private void connect() throws IOException {
74         disconnect();
75         logger.debug("Connecting {}:{}...", hostname, portNumber);
76         client = new Socket(hostname, portNumber);
77         client.setSoTimeout(DEFAULT_SOCKET_TIMEOUT_MS);
78         client.getInputStream().skip(client.getInputStream().available());
79         in = new BufferedReader(new InputStreamReader(client.getInputStream()));
80         out = new PrintWriter(client.getOutputStream(), true);
81     }
82
83     /**
84      * Disconnect the device
85      */
86     private void disconnect() {
87         logger.debug("Disconnecting");
88
89         if (in != null) {
90             try {
91                 in.close();
92             } catch (IOException ignore) {
93             }
94             this.in = null;
95         }
96         if (out != null) {
97             out.close();
98             this.out = null;
99         }
100         if (client != null) {
101             try {
102                 client.close();
103             } catch (IOException ignore) {
104             }
105             this.client = null;
106         }
107         logger.debug("Disconnected");
108     }
109
110     /**
111      * Stop the device thread
112      */
113     public void dispose() {
114         interrupt();
115         disconnect();
116     }
117
118     /**
119      * Send an arbitrary keepalive command which cause the IPX to send an update.
120      * If we don't receive the update maxKeepAliveFailure time, the connection is closed and reopened
121      */
122     private void sendKeepalive() {
123         if (out != null) {
124             if (waitingKeepaliveResponse) {
125                 failedKeepalive++;
126                 logger.debug("Sending keepalive, attempt {}", failedKeepalive);
127             } else {
128                 failedKeepalive = 0;
129                 logger.debug("Sending keepalive");
130             }
131             out.println("GetIn01");
132             out.flush();
133             waitingKeepaliveResponse = true;
134         }
135     }
136
137     @Override
138     public void run() {
139         try {
140             waitingKeepaliveResponse = false;
141             failedKeepalive = 0;
142             connect();
143             while (!interrupted()) {
144                 if (failedKeepalive > MAX_KEEPALIVE_FAILURE) {
145                     throw new IOException("Max keep alive attempts has been reached");
146                 }
147                 try {
148                     String command = in.readLine();
149                     waitingKeepaliveResponse = false;
150                     if (parser != null) {
151                         parser.unsolicitedUpdate(command);
152                     }
153                 } catch (SocketTimeoutException e) {
154                     handleException(e);
155                 }
156             }
157             disconnect();
158         } catch (IOException e) {
159             handleException(e);
160         }
161         try {
162             Thread.sleep(DEFAULT_RECONNECT_TIMEOUT_MS);
163         } catch (InterruptedException e) {
164             dispose();
165         }
166     }
167
168     private void handleException(Exception e) {
169         if (!interrupted()) {
170             if (e instanceof SocketTimeoutException) {
171                 sendKeepalive();
172                 return;
173             } else if (e instanceof IOException) {
174                 logger.warn("Communication error : '{}', will retry in {} ms", e, DEFAULT_RECONNECT_TIMEOUT_MS);
175             }
176             if (parser != null) {
177                 parser.errorOccurred(e);
178             }
179         }
180     }
181
182     public void setParser(M2MMessageParser parser) {
183         this.parser = parser;
184     }
185 }