/bundles/org.openhab.binding.tibber/ @kjoglum
/bundles/org.openhab.binding.tivo/ @mlobstein
/bundles/org.openhab.binding.touchwand/ @roieg
+/bundles/org.openhab.binding.tplinkrouter/ @olivierkeke
/bundles/org.openhab.binding.tplinksmarthome/ @Hilbrand
/bundles/org.openhab.binding.tr064/ @openhab/add-ons-maintainers
/bundles/org.openhab.binding.tradfri/ @cweitkamp @kaikreuzer
<artifactId>org.openhab.binding.touchwand</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.binding.tplinkrouter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.tplinksmarthome</artifactId>
--- /dev/null
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
--- /dev/null
+# tplinkrouter Binding
+
+The tplinkrouter Binding allows monitoring and controlling TP-Link routers.
+
+The binding uses a telnet connection to communicate with the router.
+
+At the moment only wifi part is supported and `TD-W9970` is the only model tested.
+This binding may work with other TP-Link router provided that they use the same telnet API.
+
+## Supported Things
+
+This binding provides only the `router` Thing.
+
+## Thing Configuration
+
+### `router` Thing Configuration
+
+| Name | Type | Description | Default | Required | Advanced |
+|-----------------|---------|-----------------------------------------------|---------|----------|----------|
+| hostname | text | Hostname or IP address of the device | N/A | yes | no |
+| port | integer | Port for telnet connection | 23 | no | no |
+| username | text | Username to access the router (same as WebUI) | N/A | yes | no |
+| password | text | Password to access the device (same as WebUI) | N/A | yes | no |
+| refreshInterval | integer | Interval the device is polled in sec. | 60 | no | yes |
+
+## Channels
+
+| Channel | Type | Read/Write | Description |
+|-----------------------|--------|------------|------------------------------------------|
+| `wifi#status` | Switch | RW | State of the wifi |
+| `wifi#ssid` | String | R | SSID of the wifi network |
+| `wifi#bandwidth` | String | R | Bandwidth of the wifi network |
+| `wifi#qss` | Switch | RW | Quick Security Setup of the wifi network |
+| `wifi#secMode` | String | R | Security Mode of the wifi network |
+| `wifi#authentication` | String | R | Authentication Mode of the wifi network |
+| `wifi#encryption` | String | R | Encryption Mode of the wifi network |
+| `wifi#key` | String | R | Password of the wifi network |
+
+## Full Example
+
+`.things` configuration file:
+
+```
+Thing tplinkrouter:router:myRouter [hostname="192.168.0.1", username="admin", password="myPassword"]
+```
+
+`.items` configuration file:
+
+```
+Switch Wifi "Wifi" <QualityOfService> {channel="tplinkrouter:router:myRouter:wifi#status", autoupdate="false"}
+String WifiSSID "Wifi SSID" <QualityOfService> {channel="tplinkrouter:router:myRouter:wifi#ssid"}
+String BandWidth "Wifi Bandwidth" <QualityOfService> {channel="tplinkrouter:router:myRouter:wifi#bandwidth"}
+Switch QSS "Wifi QSS" <QualityOfService> {channel="tplinkrouter:router:myRouter:wifi#qss", autoupdate="false"}
+String SecMode "Wifi Security Mode" <QualityOfService> {channel="tplinkrouter:router:myRouter:wifi#secMode"}
+String Authentication "Wifi Authentication Mode" <QualityOfService> {channel="tplinkrouter:router:myRouter:wifi#authentication"}
+String Encryption "Wifi Encryption Mode" <QualityOfService> {channel="tplinkrouter:router:myRouter:wifi#encryption"}
+```
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+ <version>3.4.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>org.openhab.binding.tplinkrouter</artifactId>
+
+ <name>openHAB Add-ons :: Bundles :: TpLinkRouter Binding</name>
+
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.tplinkrouter-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
+ <repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
+
+ <feature name="openhab-binding-tplinkrouter" description="TpLinkRouter Binding" version="${project.version}">
+ <feature>openhab-runtime-base</feature>
+ <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.tplinkrouter/${project.version}</bundle>
+ </feature>
+</features>
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.tplinkrouter.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link TpLinkRouterBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Olivier Marceau - Initial contribution
+ */
+@NonNullByDefault
+public class TpLinkRouterBindingConstants {
+
+ private static final String BINDING_ID = "tplinkrouter";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_ROUTER = new ThingTypeUID(BINDING_ID, "router");
+
+ // List of all Channel ids
+ public static final String WIFI_STATUS = "wifi#status";
+ public static final String WIFI_SSID = "wifi#ssid";
+ public static final String WIFI_BANDWIDTH = "wifi#bandwidth";
+ public static final String WIFI_QSS = "wifi#qss";
+ public static final String WIFI_SECMODE = "wifi#secMode";
+ public static final String WIFI_AUTHENTICATION = "wifi#authentication";
+ public static final String WIFI_ENCRYPTION = "wifi#encryption";
+ public static final String WIFI_KEY = "wifi#key";
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.tplinkrouter.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link TpLinkRouterConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Olivier Marceau - Initial contribution
+ */
+@NonNullByDefault
+public class TpLinkRouterConfiguration {
+
+ public String hostname = "";
+ public int port = 23;
+ public String username = "";
+ public String password = "";
+ public int refreshInterval = 60;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.tplinkrouter.internal;
+
+import static org.openhab.binding.tplinkrouter.internal.TpLinkRouterBindingConstants.*;
+
+import java.io.IOException;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link TpLinkRouterHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Olivier Marceau - Initial contribution
+ */
+@NonNullByDefault
+public class TpLinkRouterHandler extends BaseThingHandler implements TpLinkRouterTelenetListener {
+
+ private static final long RECONNECT_DELAY = TimeUnit.MINUTES.toMillis(1);
+
+ private static final String REFRESH_CMD = "wlctl show";
+ private static final String WIFI_ON_CMD = "wlctl set --switch on";
+ private static final String WIFI_OFF_CMD = "wlctl set --switch off";
+ private static final String QSS_ON_CMD = "wlctl set --qss on";
+ private static final String QSS_OFF_CMD = "wlctl set --qss off";
+
+ private final Logger logger = LoggerFactory.getLogger(TpLinkRouterHandler.class);
+
+ private final TpLinkRouterTelnetConnector connector = new TpLinkRouterTelnetConnector();
+ private final BlockingQueue<ChannelUIDCommand> commandQueue = new ArrayBlockingQueue<>(1);
+
+ private TpLinkRouterConfiguration config = new TpLinkRouterConfiguration();
+ private @Nullable ScheduledFuture<?> scheduledFuture;
+
+ public TpLinkRouterHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ if (WIFI_STATUS.equals(channelUID.getId())) {
+ try {
+ commandQueue.put(new ChannelUIDCommand(channelUID, command));
+ } catch (InterruptedException e) {
+ logger.warn("Got exception", e);
+ Thread.currentThread().interrupt();
+ }
+ if (command instanceof RefreshType) {
+ connector.sendCommand(REFRESH_CMD);
+ } else if (command == OnOffType.ON) {
+ connector.sendCommand(WIFI_ON_CMD);
+ } else if (command == OnOffType.OFF) {
+ connector.sendCommand(WIFI_OFF_CMD);
+ }
+ } else if (WIFI_QSS.equals(channelUID.getId())) {
+ try {
+ commandQueue.put(new ChannelUIDCommand(channelUID, command));
+ } catch (InterruptedException e) {
+ logger.warn("Got exception", e);
+ Thread.currentThread().interrupt();
+ }
+ if (command instanceof RefreshType) {
+ connector.sendCommand(REFRESH_CMD);
+ } else if (command == OnOffType.ON) {
+ connector.sendCommand(QSS_ON_CMD);
+ } else if (command == OnOffType.OFF) {
+ connector.sendCommand(QSS_OFF_CMD);
+ }
+ }
+ }
+
+ @Override
+ public void initialize() {
+ config = getConfigAs(TpLinkRouterConfiguration.class);
+ updateStatus(ThingStatus.UNKNOWN);
+ scheduler.execute(this::createConnection);
+ }
+
+ @Override
+ public void dispose() {
+ ScheduledFuture<?> scheduledFutureLocal = scheduledFuture;
+ if (scheduledFutureLocal != null) {
+ scheduledFutureLocal.cancel(true);
+ scheduledFuture = null;
+ }
+ commandQueue.clear();
+ connector.dispose();
+ super.dispose();
+ }
+
+ private void createConnection() {
+ connector.dispose();
+ try {
+ connector.connect(this, config, this.getThing().getUID().getAsString());
+ } catch (IOException e) {
+ logger.debug("Error while connecting, will retry in {} ms", RECONNECT_DELAY);
+ scheduler.schedule(this::createConnection, RECONNECT_DELAY, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Override
+ public void receivedLine(String line) {
+ logger.debug("Received line: {}", line);
+ Pattern pattern = Pattern.compile("(\\w+)=(.+)");
+ Matcher matcher = pattern.matcher(line);
+ if (matcher.find()) {
+ String label = matcher.group(1);
+ String value = matcher.group(2);
+ switch (label) {
+ case "Status":
+ if ("Disabled".equals(value)) {
+ updateState(WIFI_STATUS, OnOffType.OFF);
+ } else if ("Up".equals(value)) {
+ updateState(WIFI_STATUS, OnOffType.ON);
+ } else {
+ logger.warn("Unsupported value {} for label {}", value, label);
+ }
+ break;
+ case "SSID":
+ updateState(WIFI_SSID, StringType.valueOf(value));
+ break;
+ case "bandWidth":
+ updateState(WIFI_BANDWIDTH, StringType.valueOf(value));
+ break;
+ case "QSS":
+ if ("Disabled".equals(value)) {
+ updateState(WIFI_QSS, OnOffType.OFF);
+ } else if ("Enable".equals(value)) {
+ updateState(WIFI_QSS, OnOffType.ON);
+ } else {
+ logger.warn("Unsupported value {} for label {}", value, label);
+ }
+ break;
+ case "SecMode":
+ String[] parts = value.split("\\s|-");
+ updateState(WIFI_SECMODE, StringType.valueOf(parts[0]));
+ updateState(WIFI_AUTHENTICATION, StringType.valueOf(parts[1]));
+ if (parts.length >= 3) {
+ updateState(WIFI_ENCRYPTION, StringType.valueOf(parts[2]));
+ } else {
+ updateState(WIFI_ENCRYPTION, StringType.EMPTY);
+ }
+ break;
+ case "Key":
+ updateState(WIFI_KEY, StringType.valueOf(value));
+ break;
+ default:
+ logger.debug("Unrecognized label {}", label);
+ }
+ } else if ("cmd:SUCC".equals(line)) {
+ ChannelUIDCommand channelUIDCommand = commandQueue.poll();
+ if (channelUIDCommand != null && channelUIDCommand.getCommand() instanceof State) {
+ updateState(channelUIDCommand.getChannelUID(), (State) channelUIDCommand.getCommand());
+ }
+ } else if ("Login incorrect. Try again.".equals(line)) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Login or password incorrect");
+ }
+ }
+
+ @Override
+ public void onReaderThreadStopped() {
+ updateStatus(ThingStatus.OFFLINE);
+ logger.debug("try to reconnect in {} ms", RECONNECT_DELAY);
+ scheduler.schedule(this::createConnection, RECONNECT_DELAY, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onReaderThreadInterrupted() {
+ updateStatus(ThingStatus.OFFLINE);
+ }
+
+ @Override
+ public void onReaderThreadStarted() {
+ scheduledFuture = scheduler.scheduleWithFixedDelay(() -> {
+ handleCommand(new ChannelUID(getThing().getUID(), WIFI_STATUS), RefreshType.REFRESH);
+ }, 0, config.refreshInterval, TimeUnit.SECONDS);
+ updateStatus(ThingStatus.ONLINE);
+ }
+
+ @Override
+ public void onCommunicationUnavailable() {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Connection not available. Check if there is not another open connection.");
+ }
+}
+
+/**
+ * Stores a command with associated channel
+ *
+ * @author Olivier Marceau - Initial contribution
+ */
+@NonNullByDefault
+class ChannelUIDCommand {
+ private final ChannelUID channelUID;
+ private final Command command;
+
+ public ChannelUIDCommand(ChannelUID channelUID, Command command) {
+ this.channelUID = channelUID;
+ this.command = command;
+ }
+
+ public ChannelUID getChannelUID() {
+ return channelUID;
+ }
+
+ public Command getCommand() {
+ return command;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.tplinkrouter.internal;
+
+import static org.openhab.binding.tplinkrouter.internal.TpLinkRouterBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * The {@link TpLinkRouterHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Olivier Marceau - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.tplinkrouter", service = ThingHandlerFactory.class)
+public class TpLinkRouterHandlerFactory extends BaseThingHandlerFactory {
+
+ private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ROUTER);
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (THING_TYPE_ROUTER.equals(thingTypeUID)) {
+ return new TpLinkRouterHandler(thing);
+ }
+
+ return null;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.tplinkrouter.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link TpLinkRouterTelenetListener} defines listener for telnet events.
+ *
+ * @author Olivier Marceau - Initial contribution
+ */
+@NonNullByDefault
+public interface TpLinkRouterTelenetListener {
+
+ /**
+ * The telnet client has received a line.
+ *
+ * @param line the received line
+ */
+ void receivedLine(String line);
+
+ /**
+ * The telnet client encountered an IO error.
+ */
+ void onReaderThreadStopped();
+
+ /**
+ * The telnet client has been interrupted.
+ */
+ void onReaderThreadInterrupted();
+
+ /**
+ * The telnet client has successfully connected to the receiver.
+ */
+ void onReaderThreadStarted();
+
+ /**
+ * The telnet socket is unavailable.
+ */
+ void onCommunicationUnavailable();
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.tplinkrouter.internal;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.io.OutputStreamWriter;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link TpLinkRouterTelnetConnector} is responsible for the telnet connection.
+ *
+ * @author Olivier Marceau - Initial contribution
+ */
+@NonNullByDefault
+public class TpLinkRouterTelnetConnector {
+
+ private static final int TIMEOUT_MS = (int) TimeUnit.MINUTES.toMillis(1);
+
+ private final Logger logger = LoggerFactory.getLogger(TpLinkRouterTelnetConnector.class);
+
+ private @Nullable Thread telnetClientThread;
+ private @Nullable Socket socket; // use raw socket since commons net usage seems discouraged
+ private @Nullable OutputStreamWriter out;
+
+ public void connect(TpLinkRouterTelenetListener listener, TpLinkRouterConfiguration config, String thingUID)
+ throws IOException {
+ logger.debug("Connecting to {}", config.hostname);
+
+ Socket socketLocal = new Socket();
+ socketLocal.connect(new InetSocketAddress(config.hostname, config.port));
+ socketLocal.setSoTimeout(TIMEOUT_MS);
+ socketLocal.setKeepAlive(true);
+
+ InputStreamReader inputStream = new InputStreamReader(socketLocal.getInputStream());
+ this.out = new OutputStreamWriter(socketLocal.getOutputStream());
+ this.socket = socketLocal;
+ loginAttempt(listener, inputStream, config);
+ Thread clientThread = new Thread(() -> listenInputStream(listener, inputStream, config));
+ clientThread.setName("OH-binding-" + thingUID);
+ this.telnetClientThread = clientThread;
+ clientThread.start();
+ logger.debug("TP-Link router telnet client connected to {}", config.hostname);
+ }
+
+ public void dispose() {
+ logger.debug("disposing connector");
+ Thread clientThread = telnetClientThread;
+ if (clientThread != null) {
+ clientThread.interrupt();
+ telnetClientThread = null;
+ }
+ Socket socketLocal = socket;
+ if (socketLocal != null) {
+ try {
+ socketLocal.close();
+ } catch (IOException e) {
+ logger.debug("Error while disconnecting telnet client", e);
+ }
+ socket = null;
+ }
+ }
+
+ public void sendCommand(String command) {
+ logger.debug("sending command: {}", command);
+ OutputStreamWriter output = out;
+ if (output != null) {
+ try {
+ output.write(command + '\n');
+ output.flush();
+ } catch (IOException e) {
+ logger.warn("Error sending command", e);
+ }
+ } else {
+ logger.debug("Cannot send command, no telnet connection");
+ }
+ }
+
+ private void listenInputStream(TpLinkRouterTelenetListener listener, InputStreamReader inputStream,
+ TpLinkRouterConfiguration config) {
+ listener.onReaderThreadStarted();
+ BufferedReader in = new BufferedReader(inputStream);
+ try {
+ while (!Thread.currentThread().isInterrupted()) {
+ try {
+ String line = in.readLine();
+ if (line != null && !line.isBlank()) {
+ listener.receivedLine(line);
+ if ("CLI exited after timing out".equals(line)) {
+ OutputStreamWriter output = out;
+ if (output != null) {
+ output.write("\n"); // trigger a "username:" prompt
+ output.flush();
+ loginAttempt(listener, inputStream, config);
+ }
+ }
+ }
+ } catch (SocketTimeoutException e) {
+ logger.trace("Socket timeout");
+ }
+ }
+ } catch (InterruptedIOException e) {
+ logger.debug("Error in telnet connection ", e);
+ } catch (IOException e) {
+ if (!Thread.currentThread().isInterrupted()) {
+ logger.debug("Error in telnet connection ", e);
+ listener.onReaderThreadStopped();
+ }
+ }
+ if (Thread.currentThread().isInterrupted()) {
+ logger.debug("Interrupted client thread");
+ listener.onReaderThreadInterrupted();
+ }
+ }
+
+ private void loginAttempt(TpLinkRouterTelenetListener listener, InputStreamReader inputStreamReader,
+ TpLinkRouterConfiguration config) throws IOException {
+ int charInt;
+ StringBuilder word = new StringBuilder();
+ OutputStreamWriter output = out;
+ if (output != null) {
+ try {
+ while ((charInt = inputStreamReader.read()) != -1) {
+ word.append((char) charInt);
+ logger.trace("received char: {}", (char) charInt);
+ if (word.toString().contains("username:")) {
+ logger.debug("Sending username");
+ output.write(config.username + '\n');
+ output.flush();
+ word = new StringBuilder();
+ }
+ if (word.toString().contains("password:")) {
+ logger.debug("Sending password");
+ output.write(config.password + '\n');
+ output.flush();
+ break;
+ }
+ }
+ } catch (SocketTimeoutException e) {
+ listener.onCommunicationUnavailable();
+ }
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="tplinkrouter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
+
+ <name>TpLinkRouter Binding</name>
+ <description>This is the binding for controlling a TP-Link router with telnet.</description>
+
+</binding:binding>
--- /dev/null
+# binding
+
+binding.tplinkrouter.name = TpLinkRouter Binding
+binding.tplinkrouter.description = This is the binding for controlling a TP-Link router with telnet.
+
+# thing types
+
+thing-type.tplinkrouter.router.label = Router
+thing-type.tplinkrouter.router.description = Router device monitored and controlled by telnet connection
+
+# thing types config
+
+thing-type.config.tplinkrouter.router.hostname.label = Hostname
+thing-type.config.tplinkrouter.router.hostname.description = Hostname or IP address of the device
+thing-type.config.tplinkrouter.router.password.label = Password
+thing-type.config.tplinkrouter.router.password.description = Password to access the device
+thing-type.config.tplinkrouter.router.port.label = Port
+thing-type.config.tplinkrouter.router.port.description = Port for telnet connection
+thing-type.config.tplinkrouter.router.refreshInterval.label = Refresh Interval
+thing-type.config.tplinkrouter.router.refreshInterval.description = Interval the device is polled in sec.
+thing-type.config.tplinkrouter.router.username.label = Username
+thing-type.config.tplinkrouter.router.username.description = User to access the device
+
+# channel group types
+
+channel-group-type.tplinkrouter.wifiGroupType.label = Wifi
+channel-group-type.tplinkrouter.wifiGroupType.description = Wifi channels
+
+# channel types
+
+channel-type.tplinkrouter.authentication.label = Wifi Authentication Mode
+channel-type.tplinkrouter.authentication.state.option.AUTO = AUTO
+channel-type.tplinkrouter.authentication.state.option.OPEN = OPEN
+channel-type.tplinkrouter.authentication.state.option.SHARED = SHARED
+channel-type.tplinkrouter.authentication.state.option.WPA = WPA
+channel-type.tplinkrouter.authentication.state.option.WPA2 = WPA2
+channel-type.tplinkrouter.bandwidth.label = Wifi BandWidth
+channel-type.tplinkrouter.bandwidth.state.option.Auto = Auto
+channel-type.tplinkrouter.bandwidth.state.option.20M = 20 MHz
+channel-type.tplinkrouter.bandwidth.state.option.40M = 40 MHz
+channel-type.tplinkrouter.encryption.label = Wifi Encryption Mode
+channel-type.tplinkrouter.encryption.description = Wifi Encryption Mode (only for PSK security mode)
+channel-type.tplinkrouter.encryption.state.option.AUTO = AUTO
+channel-type.tplinkrouter.encryption.state.option.TKIP = TKIP
+channel-type.tplinkrouter.encryption.state.option.AES = AES
+channel-type.tplinkrouter.key.label = Wifi Key
+channel-type.tplinkrouter.qss.label = Wifi QSS
+channel-type.tplinkrouter.security-mode.label = Wifi Security Mode
+channel-type.tplinkrouter.security-mode.state.option.WEP = WEP
+channel-type.tplinkrouter.security-mode.state.option.WPA = PSK
+channel-type.tplinkrouter.ssid.label = Wifi SSID
+channel-type.tplinkrouter.status.label = Wifi Status
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="tplinkrouter"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-group-type id="wifiGroupType">
+ <label>Wifi</label>
+ <description>Wifi channels</description>
+ <channels>
+ <channel id="status" typeId="status"/>
+ <channel id="ssid" typeId="ssid"/>
+ <channel id="bandwidth" typeId="bandwidth"/>
+ <channel id="qss" typeId="qss"/>
+ <channel id="secMode" typeId="security-mode"/>
+ <channel id="authentication" typeId="authentication"/>
+ <channel id="encryption" typeId="encryption"/>
+ <channel id="key" typeId="key"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-type id="qss">
+ <item-type>Switch</item-type>
+ <label>Wifi QSS</label>
+ </channel-type>
+ <channel-type id="status">
+ <item-type>Switch</item-type>
+ <label>Wifi Status</label>
+ </channel-type>
+ <channel-type id="ssid">
+ <item-type>String</item-type>
+ <label>Wifi SSID</label>
+ <state pattern="%s" readOnly="true"/>
+ </channel-type>
+ <channel-type id="key">
+ <item-type>String</item-type>
+ <label>Wifi Key</label>
+ <state pattern="%s" readOnly="true"/>
+ </channel-type>
+ <channel-type id="security-mode">
+ <item-type>String</item-type>
+ <label>Wifi Security Mode</label>
+ <state readOnly="true">
+ <options>
+ <option value="WEP">WEP</option>
+ <option value="WPA">PSK</option>
+ </options>
+ </state>
+ </channel-type>
+ <channel-type id="authentication">
+ <item-type>String</item-type>
+ <label>Wifi Authentication Mode</label>
+ <state readOnly="true">
+ <options>
+ <option value="AUTO">AUTO</option>
+ <option value="OPEN">OPEN</option>
+ <option value="SHARED">SHARED</option>
+ <option value="WPA">WPA</option>
+ <option value="WPA2">WPA2</option>
+ </options>
+ </state>
+ </channel-type>
+ <channel-type id="encryption">
+ <item-type>String</item-type>
+ <label>Wifi Encryption Mode</label>
+ <description>Wifi Encryption Mode (only for PSK security mode)</description>
+ <state readOnly="true">
+ <options>
+ <option value="AUTO">AUTO</option>
+ <option value="TKIP">TKIP</option>
+ <option value="AES">AES</option>
+ </options>
+ </state>
+ </channel-type>
+ <channel-type id="bandwidth">
+ <item-type>String</item-type>
+ <label>Wifi BandWidth</label>
+ <state readOnly="true">
+ <options>
+ <option value="Auto">Auto</option>
+ <option value="20M">20 MHz</option>
+ <option value="40M">40 MHz</option>
+ </options>
+ </state>
+ </channel-type>
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="tplinkrouter"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <thing-type id="router">
+
+ <label>Router</label>
+ <description>Router device monitored and controlled by telnet connection</description>
+
+ <channel-groups>
+ <channel-group id="wifi" typeId="wifiGroupType"/>
+ </channel-groups>
+
+ <config-description>
+ <parameter name="hostname" type="text" required="true">
+ <context>network-address</context>
+ <label>Hostname</label>
+ <description>Hostname or IP address of the device</description>
+ </parameter>
+ <parameter name="port" type="integer">
+ <label>Port</label>
+ <description>Port for telnet connection</description>
+ <default>23</default>
+ </parameter>
+ <parameter name="username" type="text" required="true">
+ <label>Username</label>
+ <description>User to access the device</description>
+ </parameter>
+ <parameter name="password" type="text" required="true">
+ <context>password</context>
+ <label>Password</label>
+ <description>Password to access the device</description>
+ </parameter>
+ <parameter name="refreshInterval" type="integer" unit="s" min="1">
+ <label>Refresh Interval</label>
+ <description>Interval the device is polled in sec.</description>
+ <default>60</default>
+ <advanced>true</advanced>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+</thing:thing-descriptions>
<module>org.openhab.binding.tibber</module>
<module>org.openhab.binding.tivo</module>
<module>org.openhab.binding.touchwand</module>
+ <module>org.openhab.binding.tplinkrouter</module>
<module>org.openhab.binding.tplinksmarthome</module>
<module>org.openhab.binding.tr064</module>
<module>org.openhab.binding.tradfri</module>