mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[tplinkrouter] Initial contribution (#13369)
* Initial commit Signed-off-by: Olivier Marceau <hollysaiqs@marceau.ovh>
This commit is contained in:
parent
19b4c95103
commit
73e18424b9
@ -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
|
||||
|
@ -1651,6 +1651,11 @@
|
||||
<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>
|
||||
|
13
bundles/org.openhab.binding.tplinkrouter/NOTICE
Normal file
13
bundles/org.openhab.binding.tplinkrouter/NOTICE
Normal file
@ -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
|
57
bundles/org.openhab.binding.tplinkrouter/README.md
Normal file
57
bundles/org.openhab.binding.tplinkrouter/README.md
Normal file
@ -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"}
|
||||
```
|
17
bundles/org.openhab.binding.tplinkrouter/pom.xml
Normal file
17
bundles/org.openhab.binding.tplinkrouter/pom.xml
Normal file
@ -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>
|
@ -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>
|
@ -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";
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
@ -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
|
@ -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>
|
@ -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>
|
@ -364,6 +364,7 @@
|
||||
<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>
|
||||
|
Loading…
Reference in New Issue
Block a user