diff --git a/CODEOWNERS b/CODEOWNERS
index 5b91214dd..4b693ba51 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -329,6 +329,7 @@
/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
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index be5f6fa78..17696c838 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -1651,6 +1651,11 @@
org.openhab.binding.touchwand
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.tplinkrouter
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.tplinksmarthome
diff --git a/bundles/org.openhab.binding.tplinkrouter/NOTICE b/bundles/org.openhab.binding.tplinkrouter/NOTICE
new file mode 100644
index 000000000..38d625e34
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/NOTICE
@@ -0,0 +1,13 @@
+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
diff --git a/bundles/org.openhab.binding.tplinkrouter/README.md b/bundles/org.openhab.binding.tplinkrouter/README.md
new file mode 100644
index 000000000..8d8aac4a9
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/README.md
@@ -0,0 +1,57 @@
+# 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" {channel="tplinkrouter:router:myRouter:wifi#status", autoupdate="false"}
+String WifiSSID "Wifi SSID" {channel="tplinkrouter:router:myRouter:wifi#ssid"}
+String BandWidth "Wifi Bandwidth" {channel="tplinkrouter:router:myRouter:wifi#bandwidth"}
+Switch QSS "Wifi QSS" {channel="tplinkrouter:router:myRouter:wifi#qss", autoupdate="false"}
+String SecMode "Wifi Security Mode" {channel="tplinkrouter:router:myRouter:wifi#secMode"}
+String Authentication "Wifi Authentication Mode" {channel="tplinkrouter:router:myRouter:wifi#authentication"}
+String Encryption "Wifi Encryption Mode" {channel="tplinkrouter:router:myRouter:wifi#encryption"}
+```
diff --git a/bundles/org.openhab.binding.tplinkrouter/pom.xml b/bundles/org.openhab.binding.tplinkrouter/pom.xml
new file mode 100644
index 000000000..1a8052da8
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 3.4.0-SNAPSHOT
+
+
+ org.openhab.binding.tplinkrouter
+
+ openHAB Add-ons :: Bundles :: TpLinkRouter Binding
+
+
diff --git a/bundles/org.openhab.binding.tplinkrouter/src/main/feature/feature.xml b/bundles/org.openhab.binding.tplinkrouter/src/main/feature/feature.xml
new file mode 100644
index 000000000..fbc5b4128
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.tplinkrouter/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterBindingConstants.java b/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterBindingConstants.java
new file mode 100644
index 000000000..52665f340
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterBindingConstants.java
@@ -0,0 +1,41 @@
+/**
+ * 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";
+}
diff --git a/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterConfiguration.java b/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterConfiguration.java
new file mode 100644
index 000000000..6d39a7c67
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterConfiguration.java
@@ -0,0 +1,30 @@
+/**
+ * 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;
+}
diff --git a/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterHandler.java b/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterHandler.java
new file mode 100644
index 000000000..852fefe40
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterHandler.java
@@ -0,0 +1,239 @@
+/**
+ * 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 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterHandlerFactory.java b/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterHandlerFactory.java
new file mode 100644
index 000000000..028bf0f87
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterHandlerFactory.java
@@ -0,0 +1,55 @@
+/**
+ * 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 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterTelenetListener.java b/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterTelenetListener.java
new file mode 100644
index 000000000..2e00553af
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterTelenetListener.java
@@ -0,0 +1,51 @@
+/**
+ * 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();
+}
diff --git a/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterTelnetConnector.java b/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterTelnetConnector.java
new file mode 100644
index 000000000..81984de44
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterTelnetConnector.java
@@ -0,0 +1,164 @@
+/**
+ * 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();
+ }
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644
index 000000000..905242ab0
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/binding/binding.xml
@@ -0,0 +1,9 @@
+
+
+
+ TpLinkRouter Binding
+ This is the binding for controlling a TP-Link router with telnet.
+
+
diff --git a/bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/i18n/tplinkrouter.properties b/bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/i18n/tplinkrouter.properties
new file mode 100644
index 000000000..2421030f5
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/i18n/tplinkrouter.properties
@@ -0,0 +1,52 @@
+# 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
diff --git a/bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/thing/channel-group-types.xml b/bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/thing/channel-group-types.xml
new file mode 100644
index 000000000..85b5bb4d1
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/thing/channel-group-types.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+ Wifi channels
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Switch
+
+
+
+ Switch
+
+
+
+ String
+
+
+
+
+ String
+
+
+
+
+ String
+
+
+
+
+
+
+
+
+
+ String
+
+
+
+
+
+
+
+
+
+
+
+
+ String
+
+ Wifi Encryption Mode (only for PSK security mode)
+
+
+
+
+
+
+
+
+
+ String
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644
index 000000000..9d5069441
--- /dev/null
+++ b/bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/thing/thing-types.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ Router device monitored and controlled by telnet connection
+
+
+
+
+
+
+
+ network-address
+
+ Hostname or IP address of the device
+
+
+
+ Port for telnet connection
+ 23
+
+
+
+ User to access the device
+
+
+ password
+
+ Password to access the device
+
+
+
+ Interval the device is polled in sec.
+ 60
+ true
+
+
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 8ed05c5ad..c6272d061 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -364,6 +364,7 @@
org.openhab.binding.tibber
org.openhab.binding.tivo
org.openhab.binding.touchwand
+ org.openhab.binding.tplinkrouter
org.openhab.binding.tplinksmarthome
org.openhab.binding.tr064
org.openhab.binding.tradfri