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.tplinkrouter.internal;
15 import java.io.BufferedReader;
16 import java.io.IOException;
17 import java.io.InputStreamReader;
18 import java.io.InterruptedIOException;
19 import java.io.OutputStreamWriter;
20 import java.net.InetSocketAddress;
21 import java.net.Socket;
22 import java.net.SocketTimeoutException;
23 import java.util.concurrent.TimeUnit;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
31 * The {@link TpLinkRouterTelnetConnector} is responsible for the telnet connection.
33 * @author Olivier Marceau - Initial contribution
36 public class TpLinkRouterTelnetConnector {
38 private static final int TIMEOUT_MS = (int) TimeUnit.MINUTES.toMillis(1);
40 private final Logger logger = LoggerFactory.getLogger(TpLinkRouterTelnetConnector.class);
42 private @Nullable Thread telnetClientThread;
43 private @Nullable Socket socket; // use raw socket since commons net usage seems discouraged
44 private @Nullable OutputStreamWriter out;
46 public void connect(TpLinkRouterTelenetListener listener, TpLinkRouterConfiguration config, String thingUID)
48 logger.debug("Connecting to {}", config.hostname);
50 Socket socketLocal = new Socket();
51 socketLocal.connect(new InetSocketAddress(config.hostname, config.port));
52 socketLocal.setSoTimeout(TIMEOUT_MS);
53 socketLocal.setKeepAlive(true);
55 InputStreamReader inputStream = new InputStreamReader(socketLocal.getInputStream());
56 this.out = new OutputStreamWriter(socketLocal.getOutputStream());
57 this.socket = socketLocal;
58 loginAttempt(listener, inputStream, config);
59 Thread clientThread = new Thread(() -> listenInputStream(listener, inputStream, config));
60 clientThread.setName("OH-binding-" + thingUID);
61 this.telnetClientThread = clientThread;
63 logger.debug("TP-Link router telnet client connected to {}", config.hostname);
66 public void dispose() {
67 logger.debug("disposing connector");
68 Thread clientThread = telnetClientThread;
69 if (clientThread != null) {
70 clientThread.interrupt();
71 telnetClientThread = null;
73 Socket socketLocal = socket;
74 if (socketLocal != null) {
77 } catch (IOException e) {
78 logger.debug("Error while disconnecting telnet client", e);
84 public void sendCommand(String command) {
85 logger.debug("sending command: {}", command);
86 OutputStreamWriter output = out;
89 output.write(command + '\n');
91 } catch (IOException e) {
92 logger.warn("Error sending command", e);
95 logger.debug("Cannot send command, no telnet connection");
99 private void listenInputStream(TpLinkRouterTelenetListener listener, InputStreamReader inputStream,
100 TpLinkRouterConfiguration config) {
101 listener.onReaderThreadStarted();
102 BufferedReader in = new BufferedReader(inputStream);
104 while (!Thread.currentThread().isInterrupted()) {
106 String line = in.readLine();
107 if (line != null && !line.isBlank()) {
108 listener.receivedLine(line);
109 if ("CLI exited after timing out".equals(line)) {
110 OutputStreamWriter output = out;
111 if (output != null) {
112 output.write("\n"); // trigger a "username:" prompt
114 loginAttempt(listener, inputStream, config);
118 } catch (SocketTimeoutException e) {
119 logger.trace("Socket timeout");
122 } catch (InterruptedIOException e) {
123 logger.debug("Error in telnet connection ", e);
124 } catch (IOException e) {
125 if (!Thread.currentThread().isInterrupted()) {
126 logger.debug("Error in telnet connection ", e);
127 listener.onReaderThreadStopped();
130 if (Thread.currentThread().isInterrupted()) {
131 logger.debug("Interrupted client thread");
132 listener.onReaderThreadInterrupted();
136 private void loginAttempt(TpLinkRouterTelenetListener listener, InputStreamReader inputStreamReader,
137 TpLinkRouterConfiguration config) throws IOException {
139 StringBuilder word = new StringBuilder();
140 OutputStreamWriter output = out;
141 if (output != null) {
143 while ((charInt = inputStreamReader.read()) != -1) {
144 word.append((char) charInt);
145 logger.trace("received char: {}", (char) charInt);
146 if (word.toString().contains("username:")) {
147 logger.debug("Sending username");
148 output.write(config.username + '\n');
150 word = new StringBuilder();
152 if (word.toString().contains("password:")) {
153 logger.debug("Sending password");
154 output.write(config.password + '\n');
159 } catch (SocketTimeoutException e) {
160 listener.onCommunicationUnavailable();