/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.ddwrt.internal;
import java.io.IOException;
import java.util.Dictionary;
import java.util.HashMap;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.telnet.TelnetClient;
import org.openhab.binding.ddwrt.DDWRTBindingProvider;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.binding.BindingProvider;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The DD-WRT binding connects to a DD-WRT router and switch the WIFI devices.
*
* @author Markus Eckhardt
* @author Kai Kreuzer
* @author Jan N. Klug
*
* @since 1.9.0
*/
public class DDWRTBinding extends AbstractActiveBinding<DDWRTBindingProvider> implements ManagedService {
private static HashMap<String, String> queryMap = new HashMap<>();
static {
queryMap.put(DDWRTBindingProvider.TYPE_ROUTER_TYPE, "nvram get DD_BOARD");
queryMap.put(DDWRTBindingProvider.TYPE_WLAN_24, "ifconfig");
queryMap.put(DDWRTBindingProvider.TYPE_WLAN_50, "ifconfig");
queryMap.put(DDWRTBindingProvider.TYPE_WLAN_GUEST, "ifconfig");
}
@Override
public void bindingChanged(BindingProvider provider, String itemName) {
super.bindingChanged(provider, itemName);
conditionalDeActivate();
}
private void conditionalDeActivate() {
logger.trace("DD-WRT conditional deActivate: {}", bindingsExist());
if (bindingsExist()) {
activate();
} else {
deactivate();
}
}
private static final Logger logger = LoggerFactory.getLogger(DDWRTBinding.class);
/* The IP address to connect to */
protected static String ip;
/* The port to connect to, default 23 */
protected static String port;
/* The password of the DD-WRT to access via Telnet */
protected static String password;
/* The username, if used for telnet connections */
protected static String username;
/* The interface for 2.4 GHz network, e.g. ath0 */
protected static String interface_24;
/* The interface for 5.0 GHz network, e.g. ath1 */
protected static String interface_50;
/* The interface for guest network, e.g. virt_ath0 */
protected static String interface_guest;
@Override
public void activate() {
super.activate();
setProperlyConfigured(true);
logger.debug("DD-WRT binding has been started.");
}
@Override
public void deactivate() {
super.deactivate();
logger.debug("DD-WRT binding has been stopped.");
}
@Override
public void internalReceiveCommand(String itemName, Command command) {
logger.trace("internalReceiveCommand");
if (StringUtils.isNotBlank(password)) {
String type = null;
for (DDWRTBindingProvider provider : providers) {
type = provider.getType(itemName);
if (type != null) {
break;
}
}
logger.trace("DD-WRT type: {}", type);
if (type == null) {
return;
}
TelnetCommandThread thread = new TelnetCommandThread(type, command);
thread.start();
}
}
protected void addBindingProvider(DDWRTBindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(DDWRTBindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("rawtypes")
public void updated(Dictionary config) throws ConfigurationException {
logger.info("Update DD-WRT Binding configuration ...");
if (config == null) {
return;
} else {
if (config.isEmpty()) {
throw new RuntimeException("No properties in openhab.cfg set!");
}
String ip = (String) config.get("ip");
if (StringUtils.isNotBlank(ip)) {
if (!ip.equals(DDWRTBinding.ip)) {
// only do something if the ip has changed
DDWRTBinding.ip = ip;
String port = (String) config.get("port");
if (!StringUtils.isNotBlank(port)) {
port = "23";
}
DDWRTBinding.port = port;
conditionalDeActivate();
}
}
String username = (String) config.get("username");
if (StringUtils.isNotBlank(username)) {
DDWRTBinding.username = username;
}
String password = (String) config.get("password");
if (StringUtils.isNotBlank(password)) {
DDWRTBinding.password = password;
}
String interface_24 = (String) config.get("interface_24");
if (StringUtils.isNotBlank(interface_24)) {
DDWRTBinding.interface_24 = interface_24;
}
String interface_50 = (String) config.get("interface_50");
if (StringUtils.isNotBlank(interface_50)) {
DDWRTBinding.interface_50 = interface_50;
}
String interface_guest = (String) config.get("interface_guest");
if (StringUtils.isNotBlank(interface_guest)) {
DDWRTBinding.interface_guest = interface_guest;
}
}
}
private static class TelnetCommandThread extends Thread {
private static HashMap<String, String> commandMap = new HashMap<>();
static {
commandMap.put(DDWRTBindingProvider.TYPE_ROUTER_TYPE, "nvram get DD_BOARD");
commandMap.put(DDWRTBindingProvider.TYPE_WLAN_24, "ifconfig");
commandMap.put(DDWRTBindingProvider.TYPE_WLAN_50, "ifconfig");
commandMap.put(DDWRTBindingProvider.TYPE_WLAN_GUEST, "ifconfig");
}
public TelnetCommandThread(String type, Command command) {
super();
this.type = type;
this.command = command;
}
private String type;
private Command command;
@Override
public void run() {
try {
TelnetClient client = new TelnetClient();
logger.trace("TelnetCommandThread IP ({})", ip);
client.connect(ip);
String state = null;
if (command == OnOffType.ON) {
state = "up";
} else {
state = "down";
}
String cmdString = null;
if (commandMap.containsKey(type)) {
if (type.startsWith(DDWRTBindingProvider.TYPE_ROUTER_TYPE)) {
cmdString = commandMap.get(type);
} else if (type.startsWith(DDWRTBindingProvider.TYPE_WLAN_24) && !interface_24.isEmpty()) {
cmdString = commandMap.get(type) + " " + interface_24 + " " + state;
} else if (type.startsWith(DDWRTBindingProvider.TYPE_WLAN_50) && !interface_50.isEmpty()) {
cmdString = commandMap.get(type) + " " + interface_50 + " " + state;
} else if (type.startsWith(DDWRTBindingProvider.TYPE_WLAN_GUEST) && !interface_guest.isEmpty()) {
cmdString = commandMap.get(type) + " " + interface_guest + " " + state;
}
}
if (cmdString == null) {
return;
}
logger.trace("TelnetCommandThread command ({})", cmdString);
/*
* This is a approach with receive/send in serial way. This
* could be done via a sperate thread but for just sending one
* command it is not necessary
*/
logger.trace("TelnetCommandThread Username ({})", username);
if (username != null) {
receive(client); // user:
send(client, username);
}
receive(client); // password:
logger.trace("TelnetCommandThread password ({})", password);
send(client, password);
receive(client); // welcome text
send(client, cmdString);
Thread.sleep(1000L); // response not needed - may be interesting
// for reading status
// There is a DD-WRT problem on restarting of virtual networks. So we have to restart the lan service.
if (type.startsWith(DDWRTBindingProvider.TYPE_WLAN_GUEST) && !interface_guest.isEmpty()
&& command == OnOffType.ON) {
cmdString = "stopservice lan && startservice lan";
send(client, cmdString);
Thread.sleep(25000L); // response not needed but time for restarting
}
logger.trace("TelnetCommandThread ok send");
client.disconnect();
} catch (Exception e) {
logger.warn("Error processing command", e);
}
}
private void send(TelnetClient client, String data) {
logger.trace("Sending data ({})...", data);
try {
data += "\r\n";
client.getOutputStream().write(data.getBytes());
client.getOutputStream().flush();
} catch (IOException e) {
logger.warn("Error sending data", e);
}
}
private String receive(TelnetClient client) {
StringBuffer strBuffer;
try {
strBuffer = new StringBuffer();
byte[] buf = new byte[4096];
int len = 0;
Thread.sleep(750L);
while ((len = client.getInputStream().read(buf)) != 0) {
strBuffer.append(new String(buf, 0, len));
Thread.sleep(750L);
if (client.getInputStream().available() == 0) {
break;
}
}
return strBuffer.toString();
} catch (Exception e) {
logger.warn("Error receiving data", e);
}
return null;
}
}
@Override
protected void execute() {
logger.trace("execute");
if (password == null) {
return;
} else if (StringUtils.isBlank(password)) {
logger.error("Password mustn't be empty!");
return;
}
try {
TelnetClient client = null;
for (DDWRTBindingProvider provider : providers) {
for (String item : provider.getItemNames()) {
String query = null;
String type = provider.getType(item);
if (queryMap.containsKey(type)) {
if (type.startsWith(DDWRTBindingProvider.TYPE_ROUTER_TYPE)) {
query = queryMap.get(type);
} else if (type.startsWith(DDWRTBindingProvider.TYPE_WLAN_24) && !interface_24.isEmpty()) {
query = queryMap.get(type) + " " + interface_24 + " | grep UP";
} else if (type.startsWith(DDWRTBindingProvider.TYPE_WLAN_50) && !interface_50.isEmpty()) {
query = queryMap.get(type) + " " + interface_50 + " | grep UP";
} else if (type.startsWith(DDWRTBindingProvider.TYPE_WLAN_GUEST)
&& !interface_guest.isEmpty()) {
query = queryMap.get(type) + " " + interface_guest + " | grep UP";
}
} else {
continue;
}
if (query == null) {
continue;
}
logger.trace("execute query ({}) ({}) ({})", query, ip, username);
if (client == null) {
client = new TelnetClient();
client.connect(ip);
if (username != null) {
receive(client);
send(client, username);
}
receive(client);
send(client, password);
receive(client);
}
send(client, query);
String answer = receive(client);
String[] lines = answer.split("\r\n");
if (lines.length >= 2) {
answer = lines[1].trim();
}
Class<? extends Item> itemType = provider.getItemType(item);
State state = null;
if (itemType.isAssignableFrom(SwitchItem.class)) {
if (lines.length > 2) {
if (lines[1].contains("UP")) {
state = OnOffType.ON;
} else {
state = OnOffType.OFF;
}
} else {
state = OnOffType.OFF;
}
} else if (itemType.isAssignableFrom(NumberItem.class)) {
state = new DecimalType(answer);
} else if (itemType.isAssignableFrom(StringItem.class)) {
state = new StringType(answer);
}
if (state != null) {
eventPublisher.postUpdate(item, state);
}
}
}
if (client != null) {
client.disconnect();
}
} catch (Exception e) {
logger.warn("Could not get item state ", e);
}
}
@Override
protected long getRefreshInterval() {
return 60000L;
}
@Override
protected String getName() {
return "DD-WRT Binding";
}
/**
* Send line via Telnet to DD-WRT
*
* @param client
* the telnet client
* @param data
* the data to send
*/
private static void send(TelnetClient client, String data) {
try {
data += "\r\n";
client.getOutputStream().write(data.getBytes());
client.getOutputStream().flush();
} catch (IOException e) {
logger.warn("Error sending data", e);
}
}
/**
* Receive answer from DD-WRT - careful! This blocks if there is no answer
* from DD-WRT
*
* @param client
* the telnet client
* @return
*/
private static String receive(TelnetClient client) {
StringBuffer strBuffer;
try {
strBuffer = new StringBuffer();
byte[] buf = new byte[4096];
int len = 0;
Thread.sleep(750L);
while ((len = client.getInputStream().read(buf)) != 0) {
strBuffer.append(new String(buf, 0, len));
Thread.sleep(750L);
if (client.getInputStream().available() == 0) {
break;
}
}
return strBuffer.toString();
} catch (Exception e) {
logger.warn("Error receiving data", e);
}
return null;
}
}