]> git.basschouten.com Git - openhab-addons.git/commitdiff
[tplinkrouter] Initial contribution (#13369)
authorOlivier <olivierkeke@users.noreply.github.com>
Sat, 15 Oct 2022 19:52:33 +0000 (21:52 +0200)
committerGitHub <noreply@github.com>
Sat, 15 Oct 2022 19:52:33 +0000 (21:52 +0200)
* Initial commit

Signed-off-by: Olivier Marceau <hollysaiqs@marceau.ovh>
17 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.tplinkrouter/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.tplinkrouter/README.md [new file with mode: 0644]
bundles/org.openhab.binding.tplinkrouter/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.tplinkrouter/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterTelenetListener.java [new file with mode: 0644]
bundles/org.openhab.binding.tplinkrouter/src/main/java/org/openhab/binding/tplinkrouter/internal/TpLinkRouterTelnetConnector.java [new file with mode: 0644]
bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/binding/binding.xml [new file with mode: 0644]
bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/i18n/tplinkrouter.properties [new file with mode: 0644]
bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/thing/channel-group-types.xml [new file with mode: 0644]
bundles/org.openhab.binding.tplinkrouter/src/main/resources/OH-INF/thing/thing-types.xml [new file with mode: 0644]
bundles/pom.xml

index 5b91214dddf24d43ca4b5a14eb265ad4861e856c..4b693ba51138b8be04297c9efc558c3b0c4586f9 100644 (file)
 /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
index be5f6fa78569d8a5782bbb7c6e0dc1d880b6b7b7..17696c838254c03d680c40c619ec444285ab41a6 100644 (file)
       <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>
diff --git a/bundles/org.openhab.binding.tplinkrouter/NOTICE b/bundles/org.openhab.binding.tplinkrouter/NOTICE
new file mode 100644 (file)
index 0000000..38d625e
--- /dev/null
@@ -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 (file)
index 0000000..8d8aac4
--- /dev/null
@@ -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" <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"}
+```
diff --git a/bundles/org.openhab.binding.tplinkrouter/pom.xml b/bundles/org.openhab.binding.tplinkrouter/pom.xml
new file mode 100644 (file)
index 0000000..1a8052d
--- /dev/null
@@ -0,0 +1,17 @@
+<?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>
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 (file)
index 0000000..fbc5b41
--- /dev/null
@@ -0,0 +1,9 @@
+<?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>
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 (file)
index 0000000..52665f3
--- /dev/null
@@ -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 (file)
index 0000000..6d39a7c
--- /dev/null
@@ -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 (file)
index 0000000..852fefe
--- /dev/null
@@ -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<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;
+    }
+}
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 (file)
index 0000000..028bf0f
--- /dev/null
@@ -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<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;
+    }
+}
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 (file)
index 0000000..2e00553
--- /dev/null
@@ -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 (file)
index 0000000..81984de
--- /dev/null
@@ -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 (file)
index 0000000..905242a
--- /dev/null
@@ -0,0 +1,9 @@
+<?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>
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 (file)
index 0000000..2421030
--- /dev/null
@@ -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 (file)
index 0000000..85b5bb4
--- /dev/null
@@ -0,0 +1,86 @@
+<?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>
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 (file)
index 0000000..9d50694
--- /dev/null
@@ -0,0 +1,45 @@
+<?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>
index 8ed05c5ad8cb9e76f7387e42497e8afc0900770b..c6272d061a59f4e060e2567962b4629f48e0a889 100644 (file)
     <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>