Skip to content

Commit e14381d

Browse files
authored
[gce] Ensure ressources are freed (openhab#17949)
* Ensure ressources are freed. A bit of code revamp Signed-off-by: clinique <[email protected]>
1 parent 1856aa9 commit e14381d

File tree

9 files changed

+422
-340
lines changed

9 files changed

+422
-340
lines changed

bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ public void setThingHandler(@Nullable ThingHandler handler) {
5858
public void resetCounter(
5959
@ActionInput(name = "counter", label = "Counter", required = true, description = "Id of the counter", type = "java.lang.Integer") Integer counter) {
6060
logger.debug("IPX800 action 'resetCounter' called");
61-
Ipx800v3Handler theHandler = this.handler;
62-
if (theHandler != null) {
61+
if (handler instanceof Ipx800v3Handler theHandler) {
6362
theHandler.resetCounter(counter);
6463
} else {
6564
logger.warn("Method call resetCounter failed because IPX800 action service ThingHandler is null!");
@@ -70,8 +69,7 @@ public void resetCounter(
7069
public void reset(
7170
@ActionInput(name = "placeholder", label = "Placeholder", required = false, description = "This parameter is not used", type = "java.lang.Integer") @Nullable Integer placeholder) {
7271
logger.debug("IPX800 action 'reset' called");
73-
Ipx800v3Handler theHandler = this.handler;
74-
if (theHandler != null) {
72+
if (handler instanceof Ipx800v3Handler theHandler) {
7573
theHandler.reset();
7674
} else {
7775
logger.warn("Method call reset failed because IPX800 action service ThingHandler is null!");

bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java

+108-111
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,18 @@
1818
import java.io.PrintWriter;
1919
import java.net.Socket;
2020
import java.net.SocketTimeoutException;
21-
import java.util.Optional;
21+
import java.net.UnknownHostException;
22+
import java.util.Random;
2223

2324
import org.eclipse.jdt.annotation.NonNullByDefault;
2425
import org.openhab.binding.gce.internal.model.M2MMessageParser;
26+
import org.openhab.binding.gce.internal.model.PortDefinition;
27+
import org.openhab.binding.gce.internal.model.StatusFile;
28+
import org.openhab.binding.gce.internal.model.StatusFileAccessor;
2529
import org.openhab.core.thing.ThingUID;
2630
import org.slf4j.Logger;
2731
import org.slf4j.LoggerFactory;
32+
import org.xml.sax.SAXException;
2833

2934
/**
3035
* The {@link Ipx800DeviceConnector} is responsible for connecting,
@@ -35,156 +40,148 @@
3540
*/
3641
@NonNullByDefault
3742
public class Ipx800DeviceConnector extends Thread {
38-
private static final int DEFAULT_SOCKET_TIMEOUT_MS = 5000;
39-
private static final int DEFAULT_RECONNECT_TIMEOUT_MS = 5000;
43+
private static final int DEFAULT_SOCKET_TIMEOUT_MS = 10000;
4044
private static final int MAX_KEEPALIVE_FAILURE = 3;
41-
private static final String ENDL = "\r\n";
4245

4346
private final Logger logger = LoggerFactory.getLogger(Ipx800DeviceConnector.class);
47+
private final Random randomizer = new Random();
4448

45-
private final String hostname;
46-
private final int portNumber;
47-
48-
private Optional<M2MMessageParser> messageParser = Optional.empty();
49-
private Optional<Socket> socket = Optional.empty();
50-
private Optional<BufferedReader> input = Optional.empty();
51-
private Optional<PrintWriter> output = Optional.empty();
49+
private final M2MMessageParser parser;
50+
private final StatusFileAccessor statusAccessor;
51+
private final Ipx800EventListener listener;
52+
private final Socket socket;
53+
private final BufferedReader input;
54+
private final PrintWriter output;
5255

5356
private int failedKeepalive = 0;
5457
private boolean waitingKeepaliveResponse = false;
58+
private boolean interrupted = false;
5559

56-
public Ipx800DeviceConnector(String hostname, int portNumber, ThingUID uid) {
60+
public Ipx800DeviceConnector(String hostname, int portNumber, ThingUID uid, Ipx800EventListener listener)
61+
throws UnknownHostException, IOException {
5762
super("OH-binding-" + uid);
58-
this.hostname = hostname;
59-
this.portNumber = portNumber;
63+
this.listener = listener;
64+
65+
logger.debug("Connecting to {}:{}...", hostname, portNumber);
66+
Socket socket = new Socket(hostname, portNumber);
67+
socket.setSoTimeout(DEFAULT_SOCKET_TIMEOUT_MS);
68+
this.socket = socket;
69+
70+
output = new PrintWriter(socket.getOutputStream(), true);
71+
input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
72+
parser = new M2MMessageParser(listener);
73+
statusAccessor = new StatusFileAccessor(hostname);
6074
setDaemon(true);
6175
}
6276

77+
/**
78+
*
79+
* Stop the device thread
80+
*/
81+
82+
public void dispose() {
83+
interrupted = true;
84+
}
85+
6386
public synchronized void send(String message) {
64-
output.ifPresentOrElse(out -> {
65-
logger.debug("Sending '{}' to Ipx800", message);
66-
out.write(message + ENDL);
67-
out.flush();
68-
}, () -> logger.warn("Trying to send '{}' while the output stream is closed.", message));
87+
logger.debug("Sending '{}' to Ipx800", message);
88+
output.println(message);
6989
}
7090

7191
/**
72-
* Connect to the ipx800
7392
*
74-
* @throws IOException
93+
* Send a random keepalive command which cause the IPX to send an update.
94+
* If we don't receive the update maxKeepAliveFailure time, the connection
95+
* is closed
7596
*/
76-
private void connect() throws IOException {
77-
disconnect();
7897

79-
logger.debug("Connecting to {}:{}...", hostname, portNumber);
80-
Socket socket = new Socket(hostname, portNumber);
81-
socket.setSoTimeout(DEFAULT_SOCKET_TIMEOUT_MS);
82-
socket.getInputStream().skip(socket.getInputStream().available());
83-
this.socket = Optional.of(socket);
98+
private void sendKeepalive() {
99+
PortDefinition pd = PortDefinition.values()[randomizer.nextInt(PortDefinition.AS_SET.size())];
100+
String command = "%s%d".formatted(pd.m2mCommand, randomizer.nextInt(pd.quantity) + 1);
101+
102+
if (waitingKeepaliveResponse) {
103+
failedKeepalive++;
104+
logger.debug("Sending keepalive {}, attempt {}", command, failedKeepalive);
105+
} else {
106+
failedKeepalive = 0;
107+
logger.debug("Sending keepalive {}", command);
108+
}
109+
110+
output.println(command);
111+
parser.setExpectedResponse(command);
84112

85-
input = Optional.of(new BufferedReader(new InputStreamReader(socket.getInputStream())));
86-
output = Optional.of(new PrintWriter(socket.getOutputStream(), true));
113+
waitingKeepaliveResponse = true;
87114
}
88115

89-
/**
90-
* Disconnect the device
91-
*/
92-
private void disconnect() {
93-
logger.debug("Disconnecting");
116+
@Override
117+
public void run() {
118+
while (!interrupted) {
119+
if (failedKeepalive > MAX_KEEPALIVE_FAILURE) {
120+
interrupted = true;
121+
listener.errorOccurred(new IOException("Max keep alive attempts has been reached"));
122+
}
123+
try {
124+
String command = input.readLine();
125+
waitingKeepaliveResponse = false;
126+
parser.unsolicitedUpdate(command);
127+
} catch (SocketTimeoutException e) {
128+
sendKeepalive();
129+
} catch (IOException e) {
130+
interrupted = true;
131+
listener.errorOccurred(e);
132+
}
133+
}
134+
if (output instanceof PrintWriter out) {
135+
out.close();
136+
}
94137

95-
input.ifPresent(in -> {
138+
if (input instanceof BufferedReader in) {
96139
try {
97140
in.close();
98-
} catch (IOException ignore) {
141+
} catch (IOException e) {
142+
logger.warn("Exception input stream: {}", e.getMessage());
99143
}
100-
input = Optional.empty();
101-
});
102-
103-
output.ifPresent(PrintWriter::close);
104-
output = Optional.empty();
144+
}
105145

106-
socket.ifPresent(client -> {
146+
if (socket instanceof Socket client) {
107147
try {
148+
logger.debug("Closing socket");
108149
client.close();
109-
} catch (IOException ignore) {
150+
} catch (IOException e) {
151+
logger.warn("Exception closing socket: {}", e.getMessage());
110152
}
111-
socket = Optional.empty();
112-
});
153+
}
154+
}
113155

114-
logger.debug("Disconnected");
156+
public StatusFile readStatusFile() throws SAXException, IOException {
157+
return statusAccessor.read();
115158
}
116159

117160
/**
118-
* Stop the device thread
161+
*
162+
* Set output of the device sending the corresponding command
163+
*
164+
* @param targetPort
165+
* @param targetValue
119166
*/
120-
public void dispose() {
121-
interrupt();
122-
disconnect();
167+
public void setOutput(String targetPort, int targetValue, boolean pulse) {
168+
logger.debug("Sending {} to {}", targetValue, targetPort);
169+
String command = "Set%02d%s%s".formatted(Integer.parseInt(targetPort), targetValue, pulse ? "p" : "");
170+
send(command);
123171
}
124172

125173
/**
126-
* Send an arbitrary keepalive command which cause the IPX to send an update.
127-
* If we don't receive the update maxKeepAliveFailure time, the connection is closed and reopened
174+
*
175+
* Resets the counter value to 0
176+
*
177+
* @param targetCounter
128178
*/
129-
private void sendKeepalive() {
130-
output.ifPresent(out -> {
131-
if (waitingKeepaliveResponse) {
132-
failedKeepalive++;
133-
logger.debug("Sending keepalive, attempt {}", failedKeepalive);
134-
} else {
135-
failedKeepalive = 0;
136-
logger.debug("Sending keepalive");
137-
}
138-
out.println("GetIn01");
139-
out.flush();
140-
waitingKeepaliveResponse = true;
141-
});
142-
}
143-
144-
@Override
145-
public void run() {
146-
try {
147-
waitingKeepaliveResponse = false;
148-
failedKeepalive = 0;
149-
connect();
150-
while (!interrupted()) {
151-
if (failedKeepalive > MAX_KEEPALIVE_FAILURE) {
152-
throw new IOException("Max keep alive attempts has been reached");
153-
}
154-
input.ifPresent(in -> {
155-
try {
156-
String command = in.readLine();
157-
waitingKeepaliveResponse = false;
158-
messageParser.ifPresent(parser -> parser.unsolicitedUpdate(command));
159-
} catch (IOException e) {
160-
handleException(e);
161-
}
162-
});
163-
}
164-
disconnect();
165-
} catch (IOException e) {
166-
handleException(e);
167-
}
168-
try {
169-
Thread.sleep(DEFAULT_RECONNECT_TIMEOUT_MS);
170-
} catch (InterruptedException e) {
171-
dispose();
172-
}
173-
}
174-
175-
private void handleException(Exception e) {
176-
if (!interrupted()) {
177-
if (e instanceof SocketTimeoutException) {
178-
sendKeepalive();
179-
return;
180-
} else if (e instanceof IOException) {
181-
logger.warn("Communication error: '{}'. Will retry in {} ms", e, DEFAULT_RECONNECT_TIMEOUT_MS);
182-
}
183-
messageParser.ifPresent(parser -> parser.errorOccurred(e));
184-
}
179+
public void resetCounter(int targetCounter) {
180+
logger.debug("Resetting counter {} to 0", targetCounter);
181+
send("ResetCount%d".formatted(targetCounter));
185182
}
186183

187-
public void setParser(M2MMessageParser parser) {
188-
this.messageParser = Optional.of(parser);
184+
public void resetPLC() {
185+
send("Reset");
189186
}
190187
}

0 commit comments

Comments
 (0)