mirror of
https://github.com/bwssytems/ha-bridge.git
synced 2025-12-16 18:24:36 +00:00
Updated math variable execution to use net.java.dev.eval package. Safer
and more robust than using JavaScript Engine Eval. Also, added checks if a default vera address is uses , "1.1.1.1", that we ignore the vera helpers to not throw errors.
This commit is contained in:
10
README.md
10
README.md
@@ -1,5 +1,5 @@
|
|||||||
# ha-bridge
|
# ha-bridge
|
||||||
Emulates Philips Hue api to other home automation gateways such as an Amazon Echo. The Bridge has helpers to build devices for the gateway for the Vera, Vera Lite or Vera Edge. Alternatively the Bridge supports custom calls as well. The Bridge handles basic commands such as "On", "Off" and "brightness" commands of the hue protocol.
|
Emulates Philips Hue api to other home automation gateways such as an Amazon Echo. The Bridge has helpers to build devices for the gateway for the Vera, Vera Lite or Vera Edge. Alternatively the Bridge supports custom calls as well. The Bridge handles basic commands such as "On", "Off" and "brightness" commands of the hue protocol.
|
||||||
## Build
|
## Build
|
||||||
To customize and build it yourself, build a new jar with maven:
|
To customize and build it yourself, build a new jar with maven:
|
||||||
```
|
```
|
||||||
@@ -9,11 +9,11 @@ Otherwise go to http://www.bwssystems.com/apps.html to download the latest jar f
|
|||||||
## Run
|
## Run
|
||||||
Then locate the jar and start the server with:
|
Then locate the jar and start the server with:
|
||||||
```
|
```
|
||||||
java -jar -Dvera.address=192.168.X.Y ha-bridge-0.X.Y.jar
|
java -jar -Dvera.address=X.Y.Z.A ha-bridge-0.X.Y.jar
|
||||||
```
|
```
|
||||||
## Available Arguments
|
## Available Arguments
|
||||||
### -Dvera.address=`<ip address>`
|
### -Dvera.address=`<ip address>`
|
||||||
The argument for the vera address should be given as it the system does not have a way to find the address. Supply -Dvera.address=X.Y.Z.A on the command line to provide it.
|
The argument for the vera address should be given as it the system does not have a way to find the address. Supply -Dvera.address=X.Y.Z.A on the command line to provide it. If a vera is not used, do not set it.
|
||||||
### -Dupnp.config.address=`<ip address>`
|
### -Dupnp.config.address=`<ip address>`
|
||||||
The server defaults to the first available address on the host. Replace the -Dupnp.config.address=`<ip address>` value with the server ipv4 address you would like to use.
|
The server defaults to the first available address on the host. Replace the -Dupnp.config.address=`<ip address>` value with the server ipv4 address you would like to use.
|
||||||
### -Dserver.port=`<port>`
|
### -Dserver.port=`<port>`
|
||||||
@@ -23,11 +23,11 @@ The default location for the db to contain the devices as they are added is "dat
|
|||||||
### -Dupnp.response.port=`<port>`
|
### -Dupnp.response.port=`<port>`
|
||||||
The upnp response port that will be used. The default is 50000.
|
The upnp response port that will be used. The default is 50000.
|
||||||
### -Dupnp.strict=`<true|false>`
|
### -Dupnp.strict=`<true|false>`
|
||||||
Upnp has been very closed on this platform to try and respond as a hue and there is now a setting to control if it is more open or strict, Add -Dupnp.strict=`<true|false>` to your command line to have the emulator respond to what it thinks is an echo to a hue or any other device. The default is upnp.strict=false.
|
Upnp has been very closed on this platform to try and respond as a hue and there is now a setting to control if it is more open or strict, Add -Dupnp.strict=`<true|false>` to your command line to have the emulator respond to what it thinks is an echo to a hue or any other device. The default is upnp.strict=true.
|
||||||
### -Dtrace.upnp=`<true|false>`
|
### -Dtrace.upnp=`<true|false>`
|
||||||
Turn on tracing for upnp discovery messages. The default is false.
|
Turn on tracing for upnp discovery messages. The default is false.
|
||||||
### -Dvtwo.compatibility=`<true|false>`
|
### -Dvtwo.compatibility=`<true|false>`
|
||||||
Turns on compatibility for upnp detection and response as it was in the original version of amazon-echo-ha-bridge. The default is true.
|
Turns on compatibility for upnp detection and response as it was in the original version of amazon-echo-ha-bridge. The default is false.
|
||||||
## Web Config
|
## Web Config
|
||||||
Configure by going to the url for the host you are running on or localhost with port you have assigned:
|
Configure by going to the url for the host you are running on or localhost with port you have assigned:
|
||||||
```
|
```
|
||||||
|
|||||||
7
pom.xml
7
pom.xml
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<groupId>com.bwssystems.HABridge</groupId>
|
<groupId>com.bwssystems.HABridge</groupId>
|
||||||
<artifactId>ha-bridge</artifactId>
|
<artifactId>ha-bridge</artifactId>
|
||||||
<version>0.4.8</version>
|
<version>0.4.9</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>HA Bridge</name>
|
<name>HA Bridge</name>
|
||||||
@@ -53,6 +53,11 @@
|
|||||||
<artifactId>json-io</artifactId>
|
<artifactId>json-io</artifactId>
|
||||||
<version>4.1.6</version>
|
<version>4.1.6</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.java.dev.eval</groupId>
|
||||||
|
<artifactId>eval</artifactId>
|
||||||
|
<version>0.5</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -61,4 +61,9 @@ public class BridgeSettings {
|
|||||||
this.vtwocompatibility = vtwocompatibility;
|
this.vtwocompatibility = vtwocompatibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean isValidVera() {
|
||||||
|
if(this.veraaddress.contains(Configuration.DEFAULT_VERA_ADDRESS))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/main/java/com/bwssystems/HABridge/Configuration.java
Normal file
8
src/main/java/com/bwssystems/HABridge/Configuration.java
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package com.bwssystems.HABridge;
|
||||||
|
|
||||||
|
public class Configuration {
|
||||||
|
public final static String DEVICE_DB_DIRECTORY = "data/device.db";
|
||||||
|
public final static String UPNP_RESPONSE_PORT = "50000";
|
||||||
|
public final static String DEFAULT_VERA_ADDRESS = "1.1.1.1";
|
||||||
|
public final static String DFAULT_WEB_PORT = "8080";
|
||||||
|
}
|
||||||
@@ -51,17 +51,17 @@ public class HABridge {
|
|||||||
|
|
||||||
bridgeSettings = new BridgeSettings();
|
bridgeSettings = new BridgeSettings();
|
||||||
bridgeSettings.setUpnpConfigAddress(System.getProperty("upnp.config.address", addressString));
|
bridgeSettings.setUpnpConfigAddress(System.getProperty("upnp.config.address", addressString));
|
||||||
bridgeSettings.setUpnpDeviceDb(System.getProperty("upnp.device.db", "data/device.db"));
|
bridgeSettings.setUpnpDeviceDb(System.getProperty("upnp.device.db", Configuration.DEVICE_DB_DIRECTORY));
|
||||||
bridgeSettings.setUpnpResponsePort(System.getProperty("upnp.response.port", "50000"));
|
bridgeSettings.setUpnpResponsePort(System.getProperty("upnp.response.port", Configuration.UPNP_RESPONSE_PORT));
|
||||||
bridgeSettings.setVeraAddress(System.getProperty("vera.address", "192.168.1.100"));
|
bridgeSettings.setVeraAddress(System.getProperty("vera.address", Configuration.DEFAULT_VERA_ADDRESS));
|
||||||
bridgeSettings.setUpnpStrict(Boolean.parseBoolean(System.getProperty("upnp.strict", "false")));
|
bridgeSettings.setUpnpStrict(Boolean.parseBoolean(System.getProperty("upnp.strict", "true")));
|
||||||
bridgeSettings.setTraceupnp(Boolean.parseBoolean(System.getProperty("trace.upnp", "false")));
|
bridgeSettings.setTraceupnp(Boolean.parseBoolean(System.getProperty("trace.upnp", "false")));
|
||||||
bridgeSettings.setVtwocompatibility(Boolean.parseBoolean(System.getProperty("vtwo.compatibility", "true")));
|
bridgeSettings.setVtwocompatibility(Boolean.parseBoolean(System.getProperty("vtwo.compatibility", "false")));
|
||||||
|
|
||||||
// sparkjava config directive to set ip address for the web server to listen on
|
// sparkjava config directive to set ip address for the web server to listen on
|
||||||
// ipAddress("0.0.0.0"); // not used
|
// ipAddress("0.0.0.0"); // not used
|
||||||
// sparkjava config directive to set port for the web server to listen on
|
// sparkjava config directive to set port for the web server to listen on
|
||||||
bridgeSettings.setServerPort(System.getProperty("server.port", "8080"));
|
bridgeSettings.setServerPort(System.getProperty("server.port", Configuration.DFAULT_WEB_PORT));
|
||||||
port(Integer.valueOf(bridgeSettings.getServerPort()));
|
port(Integer.valueOf(bridgeSettings.getServerPort()));
|
||||||
// sparkjava config directive to set html static file location for Jetty
|
// sparkjava config directive to set html static file location for Jetty
|
||||||
staticFileLocation("/public");
|
staticFileLocation("/public");
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.bwssystems.HABridge.dao;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.nio.file.FileSystem;
|
|
||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class DeviceResource {
|
|||||||
public DeviceResource(BridgeSettings theSettings) {
|
public DeviceResource(BridgeSettings theSettings) {
|
||||||
super();
|
super();
|
||||||
deviceRepository = new DeviceRepository(theSettings.getUpnpDeviceDb());
|
deviceRepository = new DeviceRepository(theSettings.getUpnpDeviceDb());
|
||||||
veraInfo = new VeraInfo(theSettings.getVeraAddress());
|
veraInfo = new VeraInfo(theSettings.getVeraAddress(), theSettings.isValidVera());
|
||||||
setupEndpoints();
|
setupEndpoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import com.bwssystems.HABridge.dao.*;
|
|||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import javax.script.ScriptEngineManager;
|
|
||||||
import javax.script.ScriptException;
|
import net.java.dev.eval.Expression;
|
||||||
import javax.script.ScriptEngine;
|
|
||||||
import static spark.Spark.get;
|
import static spark.Spark.get;
|
||||||
import static spark.Spark.post;
|
import static spark.Spark.post;
|
||||||
import static spark.Spark.put;
|
import static spark.Spark.put;
|
||||||
@@ -32,6 +32,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -47,7 +48,6 @@ public class HueMulator {
|
|||||||
private static final String INTENSITY_MATH = "${intensity.math(";
|
private static final String INTENSITY_MATH = "${intensity.math(";
|
||||||
private static final String INTENSITY_MATH_VALUE = "X";
|
private static final String INTENSITY_MATH_VALUE = "X";
|
||||||
private static final String INTENSITY_MATH_CLOSE = ")}";
|
private static final String INTENSITY_MATH_CLOSE = ")}";
|
||||||
private static final String ENGINE_JAVASCRIPT = "JavaScript";
|
|
||||||
private static final String HUE_CONTEXT = "/api";
|
private static final String HUE_CONTEXT = "/api";
|
||||||
|
|
||||||
private DeviceRepository repository;
|
private DeviceRepository repository;
|
||||||
@@ -232,9 +232,10 @@ public class HueMulator {
|
|||||||
/* light weight templating here, was going to use free marker but it was a bit too
|
/* light weight templating here, was going to use free marker but it was a bit too
|
||||||
* heavy for what we were trying to do.
|
* heavy for what we were trying to do.
|
||||||
*
|
*
|
||||||
* currently provides only two variables:
|
* currently provides:
|
||||||
* intensity.byte : 0-255 brightness. this is raw from the echo
|
* intensity.byte : 0-255 brightness. this is raw from the echo
|
||||||
* intensity.percent : 0-100, adjusted for the vera
|
* intensity.percent : 0-100, adjusted for the vera
|
||||||
|
* intensity.math(X*1) : where X is the value from the interface call and can use net.java.dev.eval math
|
||||||
*/
|
*/
|
||||||
protected String replaceIntensityValue(String request, int intensity){
|
protected String replaceIntensityValue(String request, int intensity){
|
||||||
if(request == null){
|
if(request == null){
|
||||||
@@ -248,16 +249,18 @@ public class HueMulator {
|
|||||||
String intensityPercent = String.valueOf(percentBrightness);
|
String intensityPercent = String.valueOf(percentBrightness);
|
||||||
request = request.replace(INTENSITY_PERCENT, intensityPercent);
|
request = request.replace(INTENSITY_PERCENT, intensityPercent);
|
||||||
} else if(request.contains(INTENSITY_MATH)){
|
} else if(request.contains(INTENSITY_MATH)){
|
||||||
|
Map<String, BigDecimal> variables = new HashMap<String, BigDecimal>();
|
||||||
String mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),request.indexOf(INTENSITY_MATH_CLOSE));
|
String mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),request.indexOf(INTENSITY_MATH_CLOSE));
|
||||||
String updatedMath = mathDescriptor.replace(INTENSITY_MATH_VALUE, String.valueOf(intensity));
|
variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity));
|
||||||
ScriptEngineManager mgr = new ScriptEngineManager();
|
|
||||||
ScriptEngine engine = mgr.getEngineByName(ENGINE_JAVASCRIPT);
|
|
||||||
try {
|
try {
|
||||||
log.debug("Math eval is: " + updatedMath);
|
log.debug("Math eval is: " + mathDescriptor + ", Where " + INTENSITY_MATH_VALUE + " is: " + String.valueOf(intensity));
|
||||||
Integer endResult = (Integer) engine.eval(updatedMath);
|
Expression exp = new Expression(mathDescriptor);
|
||||||
|
BigDecimal result = exp.eval(variables);
|
||||||
|
Integer endResult = result.intValue();
|
||||||
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, endResult.toString());
|
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, endResult.toString());
|
||||||
} catch (ScriptException e) {
|
} catch (Exception e) {
|
||||||
log.error("Could not execute Math: " + updatedMath, e);
|
log.error("Could not execute Math: " + mathDescriptor, e);
|
||||||
} }
|
} }
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,14 +26,19 @@ public class VeraInfo {
|
|||||||
private HttpClient httpClient;
|
private HttpClient httpClient;
|
||||||
private static final String SDATA_REQUEST = ":3480/data_request?id=sdata&output_format=json";
|
private static final String SDATA_REQUEST = ":3480/data_request?id=sdata&output_format=json";
|
||||||
private String veraAddressString;
|
private String veraAddressString;
|
||||||
|
private Boolean validVera;
|
||||||
|
|
||||||
public VeraInfo(String addressString) {
|
public VeraInfo(String addressString, Boolean isValidVera) {
|
||||||
super();
|
super();
|
||||||
httpClient = HttpClients.createMinimal();
|
httpClient = HttpClients.createMinimal();
|
||||||
veraAddressString = addressString;
|
veraAddressString = addressString;
|
||||||
|
validVera = isValidVera;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Sdata getSdata() {
|
public Sdata getSdata() {
|
||||||
|
if(!validVera)
|
||||||
|
return new Sdata();
|
||||||
|
|
||||||
String theUrl = "http://" + veraAddressString + SDATA_REQUEST;
|
String theUrl = "http://" + veraAddressString + SDATA_REQUEST;
|
||||||
String theData;
|
String theData;
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||||
<li><a href="http://www.bwssystems.com" target="_blank">Developed by BWS Systems</a></li>
|
<li><a href="http://www.bwssystems.com" target="_blank">Developed by BWS Systems</a></li>
|
||||||
<li><a href="http://www.amazon.com/echo" target="_blank">Amazon Echo</a></li>
|
<li><a href="http://www.amazon.com/echo" target="_blank">Amazon Echo</a></li>
|
||||||
<li><a href="">HA Bridge Version 0.4.8</a></li>
|
<li><a href="">HA Bridge Version 0.4.9</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.viewVeraDevices = function () {
|
this.viewVeraDevices = function () {
|
||||||
|
this.state.error = "";
|
||||||
|
if(BridgeSettings.veraaddress == "1.1.1.1" || BridgeSettings.veraaddress == "")
|
||||||
|
return;
|
||||||
this.state.error = "";
|
this.state.error = "";
|
||||||
return $http.get(this.state.base + "/vera/devices").then(
|
return $http.get(this.state.base + "/vera/devices").then(
|
||||||
function (response) {
|
function (response) {
|
||||||
@@ -137,6 +140,8 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
|
|||||||
|
|
||||||
this.viewVeraScenes = function () {
|
this.viewVeraScenes = function () {
|
||||||
this.state.error = "";
|
this.state.error = "";
|
||||||
|
if(BridgeSettings.veraaddress == "1.1.1.1" || BridgeSettings.veraaddress == "")
|
||||||
|
return;
|
||||||
return $http.get(this.state.base + "/vera/scenes").then(
|
return $http.get(this.state.base + "/vera/scenes").then(
|
||||||
function (response) {
|
function (response) {
|
||||||
self.state.verascenes = response.data;
|
self.state.verascenes = response.data;
|
||||||
@@ -227,6 +232,9 @@ app.controller('ViewingController', function ($scope, $location, $http, $window,
|
|||||||
$scope.BridgeSettings = bridgeService.BridgeSettings;
|
$scope.BridgeSettings = bridgeService.BridgeSettings;
|
||||||
bridgeService.viewDevices();
|
bridgeService.viewDevices();
|
||||||
$scope.bridge = bridgeService.state;
|
$scope.bridge = bridgeService.state;
|
||||||
|
$scope.showVera = true;
|
||||||
|
if(BridgeSettings.veraaddress == "1.1.1.1" || BridgeSettings.veraaddress == "")
|
||||||
|
$scope.showVera = false;
|
||||||
$scope.predicate = '';
|
$scope.predicate = '';
|
||||||
$scope.reverse = true;
|
$scope.reverse = true;
|
||||||
$scope.order = function(predicate) {
|
$scope.order = function(predicate) {
|
||||||
@@ -298,6 +306,9 @@ app.controller('AddingController', function ($scope, $location, $http, bridgeSer
|
|||||||
$scope.vera = {base: "", port: "3480", id: ""};
|
$scope.vera = {base: "", port: "3480", id: ""};
|
||||||
$scope.vera.base = "http://" + BridgeSettings.veraaddress;
|
$scope.vera.base = "http://" + BridgeSettings.veraaddress;
|
||||||
bridgeService.device = $scope.device;
|
bridgeService.device = $scope.device;
|
||||||
|
$scope.showVera = true;
|
||||||
|
if(BridgeSettings.veraaddress == "1.1.1.1" || BridgeSettings.veraaddress == "")
|
||||||
|
$scope.showVera = false;
|
||||||
bridgeService.viewVeraDevices();
|
bridgeService.viewVeraDevices();
|
||||||
bridgeService.viewVeraScenes();
|
bridgeService.viewVeraScenes();
|
||||||
$scope.bridge = bridgeService.state;
|
$scope.bridge = bridgeService.state;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<ul class="nav nav-pills" role="tablist">
|
<ul class="nav nav-pills" role="tablist">
|
||||||
<li role="presentation" class="active"><a href="#">Configuration</a></li>
|
<li role="presentation" class="active"><a href="#">Configuration</a></li>
|
||||||
<li role="presentation"><a href="#/veradevices">Vera Devices</a></li>
|
<li ng-if="showVera" role="presentation"><a href="#/veradevices">Vera Devices</a></li>
|
||||||
<li role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
|
<li ng-if="showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
|
||||||
<li role="presentation"><a href="#/editor">Manual Add</a></li>
|
<li role="presentation"><a href="#/editor">Manual Add</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<ul class="nav nav-pills" role="tablist">
|
<ul class="nav nav-pills" role="tablist">
|
||||||
<li role="presentation"><a href="#">Configuration</a></li>
|
<li role="presentation"><a href="#">Configuration</a></li>
|
||||||
<li role="presentation"><a href="#/veradevices">Vera Devices</a></li>
|
<li ng-if="showVera" role="presentation"><a href="#/veradevices">Vera Devices</a></li>
|
||||||
<li role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
|
<li ng-if="showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
|
||||||
<li role="presentation"><a href="#/editor">Manual Add</a></li>
|
<li role="presentation"><a href="#/editor">Manual Add</a></li>
|
||||||
<li role="presentation" class="active"><a href="#/editdevice">Edit Device</a></li>
|
<li role="presentation" class="active"><a href="#/editdevice">Edit Device</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<ul class="nav nav-pills" role="tablist">
|
<ul class="nav nav-pills" role="tablist">
|
||||||
<li role="presentation"><a href="#">Configuration</a></li>
|
<li role="presentation"><a href="#">Configuration</a></li>
|
||||||
<li role="presentation"><a href="#/veradevices">Vera Devices</a></li>
|
<li ng-if="showVera" role="presentation"><a href="#/veradevices">Vera Devices</a></li>
|
||||||
<li role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
|
<li ng-if="showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
|
||||||
<li role="presentation" class="active"><a href="#/editor">Manual Add</a></li>
|
<li role="presentation" class="active"><a href="#/editor">Manual Add</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user