Added new HTTP pool manager and http handling.

This commit is contained in:
Admin
2017-11-15 14:27:44 -06:00
parent d8d5e8f39a
commit df85c8a349
22 changed files with 311 additions and 132 deletions

View File

@@ -5,7 +5,7 @@
<groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId>
<version>5.0.0rc3</version>
<version>5.0.0rc4</version>
<packaging>jar</packaging>
<name>HA Bridge</name>

View File

@@ -97,6 +97,7 @@ public class HABridge {
bridgeSettings.getBridgeControl().setStop(true);
if(bridgeSettings.getBridgeSettingsDescriptor().isSettingsChanged())
bridgeSettings.save(bridgeSettings.getBridgeSettingsDescriptor());
log.info("Going to close all homes");
homeManager.closeHomes();
udpSender.closeResponseSocket();
udpSender = null;

View File

@@ -4,6 +4,9 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.devicemanagmeent.ResourceHandler;
import com.bwssystems.HABridge.plugins.NestBridge.NestHome;
import com.bwssystems.HABridge.plugins.domoticz.DomoticzHome;
@@ -23,6 +26,7 @@ import com.bwssystems.HABridge.plugins.fibaro.FibaroHome;
import com.bwssystems.HABridge.util.UDPDatagramSender;
public class HomeManager {
private static final Logger log = LoggerFactory.getLogger(HomeManager.class);
Map<String, Home> homeList;
Map<String, Home> resourceList;
@@ -114,8 +118,10 @@ public class HomeManager {
}
public void closeHomes() {
log.info("Manager close homes called....");
Collection<Home> theHomes = homeList.values();
for(Home aHome : theHomes) {
log.info("Closing home: " + aHome.getClass().getCanonicalName());
aHome.closeHome();
}
homeList.clear();

View File

@@ -32,10 +32,13 @@ public class NestHome implements com.bwssystems.HABridge.Home {
private Gson aGsonHandler;
private Boolean isFarenheit;
private Boolean validNest;
private boolean closed;
public NestHome(BridgeSettings bridgeSettings) {
super();
closed = true;
createHome(bridgeSettings);
closed = false;
}
@Override
@@ -93,12 +96,18 @@ public class NestHome implements com.bwssystems.HABridge.Home {
@Override
public void closeHome() {
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
if(theSession != null) {
theNest.endNestSession();
}
theNest = null;
theSession = null;
nestItems = null;
closed = true;
}
@Override

View File

@@ -27,10 +27,13 @@ public class DomoticzHome implements Home {
private Map<String, DomoticzHandler> domoticzs;
private Boolean validDomoticz;
private HTTPHandler httpClient;
private boolean closed;
public DomoticzHome(BridgeSettings bridgeSettings) {
super();
closed = true;
createHome(bridgeSettings);
closed = false;
}
@Override
@@ -162,8 +165,15 @@ public class DomoticzHome implements Home {
}
@Override
public void closeHome() {
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
if(httpClient != null)
httpClient.closeHandler();
closed = true;
}
}

View File

@@ -19,10 +19,13 @@ import com.bwssystems.HABridge.hue.TimeDecode;
public class CommandHome implements Home {
private static final Logger log = LoggerFactory.getLogger(CommandHome.class);
private BridgeSettings theSettings;
private boolean closed;
public CommandHome(BridgeSettings bridgeSettings) {
super();
closed = true;
createHome(bridgeSettings);
closed = false;
}
@Override
@@ -93,8 +96,13 @@ public class CommandHome implements Home {
@Override
public void closeHome() {
// noop
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
closed = true;
}
}

View File

@@ -25,11 +25,14 @@ public class FibaroHome implements Home
private static final Logger log = LoggerFactory.getLogger(FibaroHome.class);
private Map<String, FibaroInfo> fibaros;
private Boolean validFibaro;
private boolean closed;
public FibaroHome(BridgeSettings bridgeSettings)
{
super();
closed = true;
createHome(bridgeSettings);
closed = false;
}
public List<Device> getDevices()
@@ -102,6 +105,12 @@ public class FibaroHome implements Home
@Override
public void closeHome()
{
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
fibaros = null;
closed = true;
}
}

View File

@@ -27,10 +27,13 @@ public class HalHome implements Home {
private static final Logger log = LoggerFactory.getLogger(HalHome.class);
private Map<String, HalInfo> hals;
private Boolean validHal;
private boolean closed;
public HalHome(BridgeSettings bridgeSettings) {
super();
closed = true;
createHome(bridgeSettings);
closed = false;
}
@Override
@@ -194,7 +197,13 @@ public class HalHome implements Home {
@Override
public void closeHome() {
// noop
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
hals = null;
closed = true;
}
}

View File

@@ -32,16 +32,24 @@ public class HarmonyHome implements Home {
private Boolean isDevMode;
private Boolean validHarmony;
private Gson aGsonHandler;
private boolean closed;
public HarmonyHome(BridgeSettings bridgeSettings) {
super();
closed = true;
createHome(bridgeSettings);
closed = false;
}
@Override
public void closeHome() {
if(!validHarmony)
return;
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
if(isDevMode || hubs == null)
return;
Iterator<String> keys = hubs.keySet().iterator();
@@ -51,6 +59,7 @@ public class HarmonyHome implements Home {
}
hubs = null;
closed = true;
}
public HarmonyHandler getHarmonyHandler(String aName) {

View File

@@ -26,10 +26,13 @@ public class HassHome implements Home {
private Map<String, HomeAssistant> hassMap;
private Boolean validHass;
private Gson aGsonHandler;
private boolean closed;
public HassHome(BridgeSettings bridgeSettings) {
super();
closed = true;
createHome(bridgeSettings);
closed = false;
}
@Override
@@ -152,6 +155,11 @@ public class HassHome implements Home {
public void closeHome() {
if(!validHass)
return;
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
if(hassMap == null)
return;
Iterator<String> keys = hassMap.keySet().iterator();
@@ -161,5 +169,6 @@ public class HassHome implements Home {
}
hassMap = null;
closed = true;
}
}

View File

@@ -4,28 +4,15 @@ import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.apache.http.HttpClientConnection;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectionPoolTimeoutException;
import org.apache.http.conn.ConnectionRequest;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpRequestExecutor;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,29 +21,9 @@ import com.bwssystems.HABridge.api.NameValue;
public class HTTPHandler {
private static final Logger log = LoggerFactory.getLogger(HTTPHandler.class);
// private CloseableHttpClient httpClient;
// private RequestConfig globalConfig;
private HttpClientContext context;
private PoolingHttpClientConnectionManager connMgr;
private HttpRoute route;
private HttpClientConnection conn;
private HttpHost theHost;
public HTTPHandler() {
context = HttpClientContext.create();
connMgr = new PoolingHttpClientConnectionManager();
// Increase max total connection to 200
connMgr.setMaxTotal(200);
// Increase default max connection per route to 20
connMgr.setDefaultMaxPerRoute(20);
// Increase max connections for localhost:80 to 50
HttpHost localhost = new HttpHost("locahost", 80);
connMgr.setMaxPerRoute(new HttpRoute(localhost), 50);
route = null;
conn = null;
theHost = null;
// globalConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
// httpClient = HttpClients.custom().setDefaultRequestConfig(globalConfig).build();
super();
}
@@ -68,9 +35,8 @@ public class HTTPHandler {
String theContent = null;
URI theURI = null;
ContentType parsedContentType = null;
ConnectionRequest connRequest = null;
StringEntity requestBody = null;
boolean badResponse = false;
if (contentType != null && !contentType.trim().isEmpty()) {
parsedContentType = ContentType.parse(contentType);
if (body != null && body.length() > 0)
@@ -82,44 +48,7 @@ public class HTTPHandler {
log.warn("Error creating URI http request: " + url + " with message: " + e1.getMessage());
return null;
}
if(route == null) {
theHost = new HttpHost(theURI.getHost(), theURI.getPort());
route = new HttpRoute(theHost);
}
if(conn == null) {
// Request new connection. This can be a long process
connRequest = connMgr.requestConnection(route, null);
// Wait for connection up to 10 sec
try {
conn = connRequest.get(10, TimeUnit.SECONDS);
} catch (ConnectionPoolTimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// If not open
if (!conn.isOpen()) {
// establish connection based on its route info
try {
connMgr.connect(conn, route, 1000, context);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// and mark it as route complete
try {
connMgr.routeComplete(conn, route, context);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
if (httpVerb == null || httpVerb.trim().isEmpty() || HttpGet.METHOD_NAME.equalsIgnoreCase(httpVerb)) {
request = new HttpGet(theURI);
@@ -146,28 +75,12 @@ public class HTTPHandler {
request.setHeader(headers[i].getName(), headers[i].getValue());
}
}
HttpResponse response = null;
HttpRequestExecutor exeRequest = new HttpRequestExecutor();
context.setTargetHost(theHost);
CloseableHttpResponse response = null;
for (int retryCount = 0; retryCount < 2; retryCount++) {
try {
response = exeRequest.execute(request, conn, context);
response = HttpClientPool.getClient().execute(request);
log.debug((httpVerb == null ? "GET" : httpVerb) + " execute (" + retryCount + ") on URL responded: "
+ response.getStatusLine().getStatusCode());
if (response != null && response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300) {
log.debug("Successfull response - The http response is <<<" + theContent + ">>>");
retryCount = 2;
} else if (response != null) {
log.warn("HTTP response code was not an expected successful response of between 200 - 299, the code was: "
+ response.getStatusLine());
if (response.getStatusLine().getStatusCode() == 504) {
log.warn("HTTP response code was 504, retrying...");
} else
retryCount = 2;
badResponse = true;
}
if (response != null && response.getEntity() != null) {
try {
@@ -180,18 +93,28 @@ public class HTTPHandler {
// inputstream
// ignore
// content
if(!badResponse)
theContent = null;
} catch (Exception e) {
log.debug("Error ocurred in handling response entity after successful call, still responding success. "
+ e.getMessage(), e);
}
}
if (response != null && response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300) {
log.debug("Successfull response - The http response is <<<" + theContent + ">>>");
retryCount = 2;
} else if (response != null) {
log.warn("HTTP response code was not an expected successful response of between 200 - 299, the code was: "
+ response.getStatusLine());
if (response.getStatusLine().getStatusCode() == 504) {
log.warn("HTTP response code was 504, retrying...");
} else
retryCount = 2;
theContent = null;
}
} catch (ClientProtocolException e) {
log.warn("Client Protocol Exception received, retyring....");
} catch (HttpException e) {
log.warn("Error calling out to HA gateway: HttpException in log", e);
} catch (IOException e) {
}catch (IOException e) {
log.warn("Error calling out to HA gateway: IOException in log: " + e.getMessage());
retryCount = 2;
}
@@ -205,30 +128,8 @@ public class HTTPHandler {
}
}
}
connMgr.releaseConnection(conn, null, 1, TimeUnit.SECONDS);
return theContent;
}
// public HttpClient getHttpClient() {
// return httpClient;
// }
// public CloseableHttpClient getHttpClient() {
// return httpClient;
// }
public void closeHandler() {
try {
// httpClient.close();
if(conn != null)
conn.close();
connMgr.closeExpiredConnections();
connMgr.shutdown();
} catch (IOException e) {
// noop
}
// httpClient = null;
}
}

View File

@@ -1,5 +1,7 @@
package com.bwssystems.HABridge.plugins.http;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,10 +23,15 @@ import com.google.gson.Gson;
public class HTTPHome implements Home {
private static final Logger log = LoggerFactory.getLogger(HTTPHome.class);
private HTTPHandler anHttpHandler;
protected HttpClientPool thePool;
private boolean closed;
public HTTPHome(BridgeSettings bridgeSettings) {
super();
closed = true;
thePool = new HttpClientPool();
createHome(bridgeSettings);
closed = false;
}
@Override
@@ -101,9 +108,22 @@ public class HTTPHome implements Home {
@Override
public void closeHome() {
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
if(anHttpHandler != null)
anHttpHandler.closeHandler();
anHttpHandler = null;
try {
HttpClientPool.shutdown();
} catch (InterruptedException e) {
log.warn("Error shutting down http pool: " + e.getMessage());;
} catch (IOException e) {
log.warn("Error shutting down http pool: " + e.getMessage());;
}
closed = true;
}
}

View File

@@ -0,0 +1,128 @@
package com.bwssystems.HABridge.plugins.http;
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class HttpClientPool {
private static final Logger log = LoggerFactory.getLogger(HttpClientPool.class);
// Single-element enum to implement Singleton.
private static enum Singleton {
// Just one of me so constructor will be called once.
Client;
// The thread-safe client.
private final CloseableHttpClient threadSafeClient;
// The pool monitor.
private final IdleConnectionMonitorThread monitor;
// The constructor creates it - thus late
private Singleton() {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// Increase max total connection to 200
cm.setMaxTotal(200);
// Increase default max connection per route to 20
cm.setDefaultMaxPerRoute(20);
// Build the client.
threadSafeClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
// Start up an eviction thread.
monitor = new IdleConnectionMonitorThread(cm);
// Don't stop quitting.
monitor.setDaemon(true);
monitor.start();
}
public CloseableHttpClient get() {
return threadSafeClient;
}
}
public static CloseableHttpClient getClient() {
// The thread safe client is held by the singleton.
return Singleton.Client.get();
}
// Watches for stale connections and evicts them.
private static class IdleConnectionMonitorThread extends Thread {
// The manager to watch.
private final PoolingHttpClientConnectionManager cm;
// Use a BlockingQueue to stop everything.
private final BlockingQueue<Stop> stopSignal = new ArrayBlockingQueue<Stop>(1);
// Pushed up the queue.
private static class Stop {
// The return queue.
private final BlockingQueue<Stop> stop = new ArrayBlockingQueue<Stop>(1);
// Called by the process that is being told to stop.
public void stopped() {
// Push me back up the queue to indicate we are now stopped.
stop.add(this);
}
// Called by the process requesting the stop.
public void waitForStopped() throws InterruptedException {
// Wait until the callee acknowledges that it has stopped.
stop.take();
}
}
IdleConnectionMonitorThread(PoolingHttpClientConnectionManager cm) {
super();
this.cm = cm;
}
@Override
public void run() {
try {
// Holds the stop request that stopped the process.
Stop stopRequest;
// Every 5 seconds.
while ((stopRequest = stopSignal.poll(5, TimeUnit.SECONDS)) == null) {
// Close expired connections
cm.closeExpiredConnections();
// Optionally, close connections that have been idle too long.
cm.closeIdleConnections(60, TimeUnit.SECONDS);
// Look at pool stats.
log.debug("Stats: {}", cm.getTotalStats());
}
// Acknowledge the stop request.
stopRequest.stopped();
} catch (InterruptedException ex) {
// terminate
}
}
public void shutdown() throws InterruptedException, IOException {
log.info("Shutting down client pool");
// Signal the stop to the thread.
Stop stop = new Stop();
stopSignal.add(stop);
// Wait for the stop to complete.
stop.waitForStopped();
// Close the pool - Added
Singleton.Client.threadSafeClient.close();
// Close the connection manager.
cm.close();
log.info("Client pool shut down");
}
}
public static void shutdown() throws InterruptedException, IOException {
// Shutdown the monitor.
Singleton.Client.monitor.shutdown();
}
}

View File

@@ -27,11 +27,14 @@ public class HueHome implements Home {
private Boolean validHue;
private Gson aGsonHandler;
private BridgeSettings theBridgeSettings;
private boolean closed;
public HueHome(BridgeSettings bridgeSettings) {
super();
closed = true;
theBridgeSettings = bridgeSettings;
createHome(bridgeSettings);
closed = false;
}
@Override
@@ -135,6 +138,11 @@ public class HueHome implements Home {
public void closeHome() {
if(!validHue)
return;
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
if(hues == null)
return;
Iterator<String> keys = hues.keySet().iterator();
@@ -143,5 +151,6 @@ public class HueHome implements Home {
hues.get(key).closeHue();;
}
hues = null;
closed = true;
}
}

View File

@@ -38,10 +38,13 @@ public class LifxHome implements Home {
private LFXClient client;
private Boolean validLifx;
private Gson aGsonHandler;
private boolean closed;
public LifxHome(BridgeSettings bridgeSettings) {
super();
closed = true;
createHome(bridgeSettings);
closed = false;
}
@Override
@@ -213,7 +216,13 @@ public class LifxHome implements Home {
public void closeHome() {
if(!validLifx)
return;
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
client.close();
closed = true;
}
private static class MyLightListener implements LFXLightCollectionListener {
private static final Logger log = LoggerFactory.getLogger(MyLightListener.class);

View File

@@ -26,16 +26,24 @@ public class MQTTHome implements Home {
private Map<String, MQTTHandler> handlers;
private Boolean validMqtt;
private Gson aGsonHandler;
private boolean closed;
public MQTTHome(BridgeSettings bridgeSettings) {
super();
closed = true;
createHome(bridgeSettings);
closed = false;
}
@Override
public void closeHome() {
if(!validMqtt)
return;
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
log.debug("Shutting down MQTT handlers.");
if(handlers != null && !handlers.isEmpty()) {
for (String key : handlers.keySet()) {
@@ -43,6 +51,7 @@ public class MQTTHome implements Home {
}
}
handlers = null;
closed = false;
}
public MQTTHandler getMQTTHandler(String aName) {

View File

@@ -31,10 +31,13 @@ public class SomfyHome implements Home {
private static final Logger log = LoggerFactory.getLogger(SomfyHome.class);
private Map<String, SomfyInfo> somfys;
private Boolean validSomfy;
private boolean closed;
public SomfyHome(BridgeSettings bridgeSettings) {
super();
closed = true;
createHome(bridgeSettings);
closed = false;
}
public SomfyInfo getSomfyHandler(String somfyName) {
@@ -114,6 +117,12 @@ public class SomfyHome implements Home {
@Override
public void closeHome() {
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
somfys = null;
closed = true;
}
}

View File

@@ -34,11 +34,13 @@ public class TCPHome implements Home {
private byte[] sendData;
private Map<String, Socket> theSockets;
private Gson aGsonHandler;
private boolean closed;
public TCPHome(BridgeSettings bridgeSettings) {
super();
closed = true;
createHome(bridgeSettings);
closed = false;
}
@Override
@@ -145,6 +147,11 @@ public class TCPHome implements Home {
@Override
public void closeHome() {
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
log.debug("Shutting down TCP sockets.");
if(theSockets != null && !theSockets.isEmpty()) {
Iterator<String> keys = theSockets.keySet().iterator();
@@ -157,6 +164,7 @@ public class TCPHome implements Home {
}
}
}
closed = true;
}
}

View File

@@ -26,11 +26,14 @@ public class UDPHome implements Home {
private static final Logger log = LoggerFactory.getLogger(UDPHome.class);
private UDPDatagramSender theUDPDatagramSender;
private byte[] sendData;
private boolean closed;
public UDPHome(BridgeSettings bridgeSettings, UDPDatagramSender aUDPDatagramSender) {
super();
theUDPDatagramSender = aUDPDatagramSender;
closed = true;
createHome(bridgeSettings);
closed = false;
}
@Override
@@ -104,8 +107,12 @@ public class UDPHome implements Home {
@Override
public void closeHome() {
// TODO Auto-generated method stub
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
closed = true;
}
}

View File

@@ -25,10 +25,13 @@ public class VeraHome implements Home {
private static final Logger log = LoggerFactory.getLogger(VeraHome.class);
private Map<String, VeraInfo> veras;
private Boolean validVera;
private boolean closed;
public VeraHome(BridgeSettings bridgeSettings) {
super();
closed = true;
createHome(bridgeSettings);
closed = false;
}
public List<Device> getDevices() {
@@ -107,6 +110,12 @@ public class VeraHome implements Home {
@Override
public void closeHome() {
log.debug("Closing Home.");
if(closed) {
log.debug("Home is already closed....");
return;
}
veras = null;
closed = true;
}
}

View File

@@ -4,8 +4,8 @@
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li role="presentation" class="active"><a href="#!/veradevices">Vera Devices</a></li>
<li role="presentation"><a href="#!/verascenes">Vera Scenes</a></li>
<li role="presentation"><a href="#!/fibarodevices">Fibaro Devices</a></li>
<li role="presentation"><a href="#!/fibaroscenes">Fibaro Scenes</a></li>
<li ng-if="bridge.showFibaro" role="presentation"><a href="#!/fibarodevices">Fibaro Devices</a></li>
<li ng-if="bridge.showFibaro" role="presentation"><a href="#!/fibaroscenes">Fibaro Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a

View File

@@ -4,8 +4,8 @@
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li role="presentation"><a href="#!/veradevices">Vera Devices</a></li>
<li role="presentation" class="active"><a href="#!/verascenes">Vera Scenes</a></li>
<li role="presentation"><a href="#!/fibarodevices">Fibaro Devices</a></li>
<li role="presentation"><a href="#!/fibaroscenes">Fibaro Scenes</a></li>
<li ng-if="bridge.showFibaro" role="presentation"><a href="#!/fibarodevices">Fibaro Devices</a></li>
<li ng-if="bridge.showFibaro" role="presentation"><a href="#!/fibaroscenes">Fibaro Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a