2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.tplinkrouter.internal;
15 import static org.openhab.binding.tplinkrouter.internal.TpLinkRouterBindingConstants.*;
17 import java.io.IOException;
18 import java.util.concurrent.ArrayBlockingQueue;
19 import java.util.concurrent.BlockingQueue;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.core.library.types.OnOffType;
28 import org.openhab.core.library.types.StringType;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.thing.binding.BaseThingHandler;
34 import org.openhab.core.types.Command;
35 import org.openhab.core.types.RefreshType;
36 import org.openhab.core.types.State;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * The {@link TpLinkRouterHandler} is responsible for handling commands, which are
42 * sent to one of the channels.
44 * @author Olivier Marceau - Initial contribution
47 public class TpLinkRouterHandler extends BaseThingHandler implements TpLinkRouterTelenetListener {
49 private static final long RECONNECT_DELAY = TimeUnit.MINUTES.toMillis(1);
51 private static final String REFRESH_CMD = "wlctl show";
52 private static final String WIFI_ON_CMD = "wlctl set --switch on";
53 private static final String WIFI_OFF_CMD = "wlctl set --switch off";
54 private static final String QSS_ON_CMD = "wlctl set --qss on";
55 private static final String QSS_OFF_CMD = "wlctl set --qss off";
57 private final Logger logger = LoggerFactory.getLogger(TpLinkRouterHandler.class);
59 private final TpLinkRouterTelnetConnector connector = new TpLinkRouterTelnetConnector();
60 private final BlockingQueue<ChannelUIDCommand> commandQueue = new ArrayBlockingQueue<>(1);
62 private TpLinkRouterConfiguration config = new TpLinkRouterConfiguration();
63 private @Nullable ScheduledFuture<?> scheduledFuture;
65 public TpLinkRouterHandler(Thing thing) {
70 public void handleCommand(ChannelUID channelUID, Command command) {
71 if (WIFI_STATUS.equals(channelUID.getId())) {
73 commandQueue.put(new ChannelUIDCommand(channelUID, command));
74 } catch (InterruptedException e) {
75 logger.warn("Got exception", e);
76 Thread.currentThread().interrupt();
78 if (command instanceof RefreshType) {
79 connector.sendCommand(REFRESH_CMD);
80 } else if (command == OnOffType.ON) {
81 connector.sendCommand(WIFI_ON_CMD);
82 } else if (command == OnOffType.OFF) {
83 connector.sendCommand(WIFI_OFF_CMD);
85 } else if (WIFI_QSS.equals(channelUID.getId())) {
87 commandQueue.put(new ChannelUIDCommand(channelUID, command));
88 } catch (InterruptedException e) {
89 logger.warn("Got exception", e);
90 Thread.currentThread().interrupt();
92 if (command instanceof RefreshType) {
93 connector.sendCommand(REFRESH_CMD);
94 } else if (command == OnOffType.ON) {
95 connector.sendCommand(QSS_ON_CMD);
96 } else if (command == OnOffType.OFF) {
97 connector.sendCommand(QSS_OFF_CMD);
103 public void initialize() {
104 config = getConfigAs(TpLinkRouterConfiguration.class);
105 updateStatus(ThingStatus.UNKNOWN);
106 scheduler.execute(this::createConnection);
110 public void dispose() {
111 ScheduledFuture<?> scheduledFutureLocal = scheduledFuture;
112 if (scheduledFutureLocal != null) {
113 scheduledFutureLocal.cancel(true);
114 scheduledFuture = null;
116 commandQueue.clear();
121 private void createConnection() {
124 connector.connect(this, config, this.getThing().getUID().getAsString());
125 } catch (IOException e) {
126 logger.debug("Error while connecting, will retry in {} ms", RECONNECT_DELAY);
127 scheduler.schedule(this::createConnection, RECONNECT_DELAY, TimeUnit.MILLISECONDS);
132 public void receivedLine(String line) {
133 logger.debug("Received line: {}", line);
134 Pattern pattern = Pattern.compile("(\\w+)=(.+)");
135 Matcher matcher = pattern.matcher(line);
136 if (matcher.find()) {
137 String label = matcher.group(1);
138 String value = matcher.group(2);
141 if ("Disabled".equals(value)) {
142 updateState(WIFI_STATUS, OnOffType.OFF);
143 } else if ("Up".equals(value)) {
144 updateState(WIFI_STATUS, OnOffType.ON);
146 logger.warn("Unsupported value {} for label {}", value, label);
150 updateState(WIFI_SSID, StringType.valueOf(value));
153 updateState(WIFI_BANDWIDTH, StringType.valueOf(value));
156 if ("Disabled".equals(value)) {
157 updateState(WIFI_QSS, OnOffType.OFF);
158 } else if ("Enable".equals(value)) {
159 updateState(WIFI_QSS, OnOffType.ON);
161 logger.warn("Unsupported value {} for label {}", value, label);
165 String[] parts = value.split("\\s|-");
166 updateState(WIFI_SECMODE, StringType.valueOf(parts[0]));
167 updateState(WIFI_AUTHENTICATION, StringType.valueOf(parts[1]));
168 if (parts.length >= 3) {
169 updateState(WIFI_ENCRYPTION, StringType.valueOf(parts[2]));
171 updateState(WIFI_ENCRYPTION, StringType.EMPTY);
175 updateState(WIFI_KEY, StringType.valueOf(value));
178 logger.debug("Unrecognized label {}", label);
180 } else if ("cmd:SUCC".equals(line)) {
181 ChannelUIDCommand channelUIDCommand = commandQueue.poll();
182 if (channelUIDCommand != null && channelUIDCommand.getCommand() instanceof State) {
183 updateState(channelUIDCommand.getChannelUID(), (State) channelUIDCommand.getCommand());
185 } else if ("Login incorrect. Try again.".equals(line)) {
186 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Login or password incorrect");
191 public void onReaderThreadStopped() {
192 updateStatus(ThingStatus.OFFLINE);
193 logger.debug("try to reconnect in {} ms", RECONNECT_DELAY);
194 scheduler.schedule(this::createConnection, RECONNECT_DELAY, TimeUnit.MILLISECONDS);
198 public void onReaderThreadInterrupted() {
199 updateStatus(ThingStatus.OFFLINE);
203 public void onReaderThreadStarted() {
204 scheduledFuture = scheduler.scheduleWithFixedDelay(() -> {
205 handleCommand(new ChannelUID(getThing().getUID(), WIFI_STATUS), RefreshType.REFRESH);
206 }, 0, config.refreshInterval, TimeUnit.SECONDS);
207 updateStatus(ThingStatus.ONLINE);
211 public void onCommunicationUnavailable() {
212 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
213 "Connection not available. Check if there is not another open connection.");
218 * Stores a command with associated channel
220 * @author Olivier Marceau - Initial contribution
223 class ChannelUIDCommand {
224 private final ChannelUID channelUID;
225 private final Command command;
227 public ChannelUIDCommand(ChannelUID channelUID, Command command) {
228 this.channelUID = channelUID;
229 this.command = command;
232 public ChannelUID getChannelUID() {
236 public Command getCommand() {