]> git.basschouten.com Git - openhab-addons.git/blob
852fefe40717fba347beaab4acdb7feb2dd92a99
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.tplinkrouter.internal;
14
15 import static org.openhab.binding.tplinkrouter.internal.TpLinkRouterBindingConstants.*;
16
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;
24
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;
39
40 /**
41  * The {@link TpLinkRouterHandler} is responsible for handling commands, which are
42  * sent to one of the channels.
43  *
44  * @author Olivier Marceau - Initial contribution
45  */
46 @NonNullByDefault
47 public class TpLinkRouterHandler extends BaseThingHandler implements TpLinkRouterTelenetListener {
48
49     private static final long RECONNECT_DELAY = TimeUnit.MINUTES.toMillis(1);
50
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";
56
57     private final Logger logger = LoggerFactory.getLogger(TpLinkRouterHandler.class);
58
59     private final TpLinkRouterTelnetConnector connector = new TpLinkRouterTelnetConnector();
60     private final BlockingQueue<ChannelUIDCommand> commandQueue = new ArrayBlockingQueue<>(1);
61
62     private TpLinkRouterConfiguration config = new TpLinkRouterConfiguration();
63     private @Nullable ScheduledFuture<?> scheduledFuture;
64
65     public TpLinkRouterHandler(Thing thing) {
66         super(thing);
67     }
68
69     @Override
70     public void handleCommand(ChannelUID channelUID, Command command) {
71         if (WIFI_STATUS.equals(channelUID.getId())) {
72             try {
73                 commandQueue.put(new ChannelUIDCommand(channelUID, command));
74             } catch (InterruptedException e) {
75                 logger.warn("Got exception", e);
76                 Thread.currentThread().interrupt();
77             }
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);
84             }
85         } else if (WIFI_QSS.equals(channelUID.getId())) {
86             try {
87                 commandQueue.put(new ChannelUIDCommand(channelUID, command));
88             } catch (InterruptedException e) {
89                 logger.warn("Got exception", e);
90                 Thread.currentThread().interrupt();
91             }
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);
98             }
99         }
100     }
101
102     @Override
103     public void initialize() {
104         config = getConfigAs(TpLinkRouterConfiguration.class);
105         updateStatus(ThingStatus.UNKNOWN);
106         scheduler.execute(this::createConnection);
107     }
108
109     @Override
110     public void dispose() {
111         ScheduledFuture<?> scheduledFutureLocal = scheduledFuture;
112         if (scheduledFutureLocal != null) {
113             scheduledFutureLocal.cancel(true);
114             scheduledFuture = null;
115         }
116         commandQueue.clear();
117         connector.dispose();
118         super.dispose();
119     }
120
121     private void createConnection() {
122         connector.dispose();
123         try {
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);
128         }
129     }
130
131     @Override
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);
139             switch (label) {
140                 case "Status":
141                     if ("Disabled".equals(value)) {
142                         updateState(WIFI_STATUS, OnOffType.OFF);
143                     } else if ("Up".equals(value)) {
144                         updateState(WIFI_STATUS, OnOffType.ON);
145                     } else {
146                         logger.warn("Unsupported value {} for label {}", value, label);
147                     }
148                     break;
149                 case "SSID":
150                     updateState(WIFI_SSID, StringType.valueOf(value));
151                     break;
152                 case "bandWidth":
153                     updateState(WIFI_BANDWIDTH, StringType.valueOf(value));
154                     break;
155                 case "QSS":
156                     if ("Disabled".equals(value)) {
157                         updateState(WIFI_QSS, OnOffType.OFF);
158                     } else if ("Enable".equals(value)) {
159                         updateState(WIFI_QSS, OnOffType.ON);
160                     } else {
161                         logger.warn("Unsupported value {} for label {}", value, label);
162                     }
163                     break;
164                 case "SecMode":
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]));
170                     } else {
171                         updateState(WIFI_ENCRYPTION, StringType.EMPTY);
172                     }
173                     break;
174                 case "Key":
175                     updateState(WIFI_KEY, StringType.valueOf(value));
176                     break;
177                 default:
178                     logger.debug("Unrecognized label {}", label);
179             }
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());
184             }
185         } else if ("Login incorrect. Try again.".equals(line)) {
186             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Login or password incorrect");
187         }
188     }
189
190     @Override
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);
195     }
196
197     @Override
198     public void onReaderThreadInterrupted() {
199         updateStatus(ThingStatus.OFFLINE);
200     }
201
202     @Override
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);
208     }
209
210     @Override
211     public void onCommunicationUnavailable() {
212         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
213                 "Connection not available. Check if there is not another open connection.");
214     }
215 }
216
217 /**
218  * Stores a command with associated channel
219  *
220  * @author Olivier Marceau - Initial contribution
221  */
222 @NonNullByDefault
223 class ChannelUIDCommand {
224     private final ChannelUID channelUID;
225     private final Command command;
226
227     public ChannelUIDCommand(ChannelUID channelUID, Command command) {
228         this.channelUID = channelUID;
229         this.command = command;
230     }
231
232     public ChannelUID getChannelUID() {
233         return channelUID;
234     }
235
236     public Command getCommand() {
237         return command;
238     }
239 }