mirror of
https://github.com/bwssytems/ha-bridge.git
synced 2025-12-18 00:10:20 +00:00
First cut at HAL itegration
This commit is contained in:
2
pom.xml
2
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>2.0.7</version>
|
<version>2.0.7-hal</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>HA Bridge</name>
|
<name>HA Bridge</name>
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ public class BridgeSettings extends BackupHandler {
|
|||||||
theBridgeSettings.setHarmonyconfigured(theBridgeSettings.isValidHarmony());
|
theBridgeSettings.setHarmonyconfigured(theBridgeSettings.isValidHarmony());
|
||||||
theBridgeSettings.setNestConfigured(theBridgeSettings.isValidNest());
|
theBridgeSettings.setNestConfigured(theBridgeSettings.isValidNest());
|
||||||
theBridgeSettings.setHueconfigured(theBridgeSettings.isValidHue());
|
theBridgeSettings.setHueconfigured(theBridgeSettings.isValidHue());
|
||||||
|
theBridgeSettings.setHalconfigured(theBridgeSettings.isValidHal());
|
||||||
if(serverPortOverride != null)
|
if(serverPortOverride != null)
|
||||||
theBridgeSettings.setServerPort(serverPortOverride);
|
theBridgeSettings.setServerPort(serverPortOverride);
|
||||||
setupParams(Paths.get(theBridgeSettings.getConfigfile()), ".cfgbk", "habridge.config-");
|
setupParams(Paths.get(theBridgeSettings.getConfigfile()), ".cfgbk", "habridge.config-");
|
||||||
@@ -171,27 +172,7 @@ public class BridgeSettings extends BackupHandler {
|
|||||||
|
|
||||||
private void _loadConfig(Path aPath) {
|
private void _loadConfig(Path aPath) {
|
||||||
String jsonContent = configReader(aPath);
|
String jsonContent = configReader(aPath);
|
||||||
BridgeSettingsDescriptor aBridgeSettings = new Gson().fromJson(jsonContent, BridgeSettingsDescriptor.class);
|
theBridgeSettings = new Gson().fromJson(jsonContent, BridgeSettingsDescriptor.class);
|
||||||
theBridgeSettings.setButtonsleep(aBridgeSettings.getButtonsleep());
|
|
||||||
theBridgeSettings.setUpnpConfigAddress(aBridgeSettings.getUpnpConfigAddress());
|
|
||||||
theBridgeSettings.setServerPort(aBridgeSettings.getServerPort());
|
|
||||||
theBridgeSettings.setUpnpResponsePort(aBridgeSettings.getUpnpResponsePort());
|
|
||||||
theBridgeSettings.setUpnpDeviceDb(aBridgeSettings.getUpnpDeviceDb());
|
|
||||||
theBridgeSettings.setVeraAddress(aBridgeSettings.getVeraAddress());
|
|
||||||
theBridgeSettings.setHarmonyAddress(aBridgeSettings.getHarmonyAddress());
|
|
||||||
theBridgeSettings.setHarmonyUser(aBridgeSettings.getHarmonyUser());
|
|
||||||
theBridgeSettings.setHarmonyPwd(aBridgeSettings.getHarmonyPwd());
|
|
||||||
theBridgeSettings.setUpnpStrict(aBridgeSettings.isUpnpStrict());
|
|
||||||
theBridgeSettings.setTraceupnp(aBridgeSettings.isTraceupnp());
|
|
||||||
theBridgeSettings.setNestuser(aBridgeSettings.getNestuser());
|
|
||||||
theBridgeSettings.setNestpwd(aBridgeSettings.getNestpwd());
|
|
||||||
theBridgeSettings.setVeraconfigured(aBridgeSettings.isValidVera());
|
|
||||||
theBridgeSettings.setHarmonyconfigured(aBridgeSettings.isValidHarmony());
|
|
||||||
theBridgeSettings.setNestConfigured(aBridgeSettings.isValidNest());
|
|
||||||
theBridgeSettings.setNumberoflogmessages(aBridgeSettings.getNumberoflogmessages());
|
|
||||||
theBridgeSettings.setFarenheit(aBridgeSettings.isFarenheit());
|
|
||||||
theBridgeSettings.setHueaddress(aBridgeSettings.getHueaddress());
|
|
||||||
theBridgeSettings.setHueconfigured(aBridgeSettings.isValidHue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save(BridgeSettingsDescriptor newBridgeSettings) {
|
public void save(BridgeSettingsDescriptor newBridgeSettings) {
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ public class BridgeSettingsDescriptor {
|
|||||||
private Integer numberoflogmessages;
|
private Integer numberoflogmessages;
|
||||||
private IpList hueaddress;
|
private IpList hueaddress;
|
||||||
private boolean hueconfigured;
|
private boolean hueconfigured;
|
||||||
|
private IpList haladdress;
|
||||||
|
private String haltoken;
|
||||||
|
private boolean halconfigured;
|
||||||
|
|
||||||
public BridgeSettingsDescriptor() {
|
public BridgeSettingsDescriptor() {
|
||||||
super();
|
super();
|
||||||
@@ -167,6 +170,24 @@ public class BridgeSettingsDescriptor {
|
|||||||
public void setHueconfigured(boolean hueconfigured) {
|
public void setHueconfigured(boolean hueconfigured) {
|
||||||
this.hueconfigured = hueconfigured;
|
this.hueconfigured = hueconfigured;
|
||||||
}
|
}
|
||||||
|
public IpList getHaladdress() {
|
||||||
|
return haladdress;
|
||||||
|
}
|
||||||
|
public void setHaladdress(IpList haladdress) {
|
||||||
|
this.haladdress = haladdress;
|
||||||
|
}
|
||||||
|
public String getHaltoken() {
|
||||||
|
return haltoken;
|
||||||
|
}
|
||||||
|
public void setHaltoken(String haltoken) {
|
||||||
|
this.haltoken = haltoken;
|
||||||
|
}
|
||||||
|
public boolean isHalconfigured() {
|
||||||
|
return halconfigured;
|
||||||
|
}
|
||||||
|
public void setHalconfigured(boolean halconfigured) {
|
||||||
|
this.halconfigured = halconfigured;
|
||||||
|
}
|
||||||
public Boolean isValidVera() {
|
public Boolean isValidVera() {
|
||||||
if(this.getVeraAddress() == null || this.getVeraAddress().getDevices().size() <= 0)
|
if(this.getVeraAddress() == null || this.getVeraAddress().getDevices().size() <= 0)
|
||||||
return false;
|
return false;
|
||||||
@@ -202,4 +223,14 @@ public class BridgeSettingsDescriptor {
|
|||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
public Boolean isValidHal() {
|
||||||
|
if(this.getHaladdress() == null || this.getHaladdress().getDevices().size() <= 0)
|
||||||
|
return false;
|
||||||
|
List<NamedIP> devicesList = this.getHaladdress().getDevices();
|
||||||
|
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
|
||||||
|
return false;
|
||||||
|
if(this.getHaltoken() == null || this.getHaltoken().equals(""))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import com.bwssystems.HABridge.dao.DeviceDescriptor;
|
|||||||
import com.bwssystems.HABridge.dao.DeviceRepository;
|
import com.bwssystems.HABridge.dao.DeviceRepository;
|
||||||
import com.bwssystems.HABridge.dao.ErrorMessage;
|
import com.bwssystems.HABridge.dao.ErrorMessage;
|
||||||
import com.bwssystems.NestBridge.NestHome;
|
import com.bwssystems.NestBridge.NestHome;
|
||||||
|
import com.bwssystems.hal.HalHome;
|
||||||
import com.bwssystems.harmony.HarmonyHome;
|
import com.bwssystems.harmony.HarmonyHome;
|
||||||
import com.bwssystems.hue.HueHome;
|
import com.bwssystems.hue.HueHome;
|
||||||
import com.bwssystems.luupRequests.Device;
|
import com.bwssystems.luupRequests.Device;
|
||||||
@@ -40,6 +41,7 @@ public class DeviceResource {
|
|||||||
private HarmonyHome myHarmonyHome;
|
private HarmonyHome myHarmonyHome;
|
||||||
private NestHome nestHome;
|
private NestHome nestHome;
|
||||||
private HueHome hueHome;
|
private HueHome hueHome;
|
||||||
|
private HalHome halHome;
|
||||||
private static final Set<String> supportedVerbs = new HashSet<>(Arrays.asList("get", "put", "post"));
|
private static final Set<String> supportedVerbs = new HashSet<>(Arrays.asList("get", "put", "post"));
|
||||||
|
|
||||||
public DeviceResource(BridgeSettingsDescriptor theSettings, HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome) {
|
public DeviceResource(BridgeSettingsDescriptor theSettings, HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome) {
|
||||||
@@ -64,7 +66,13 @@ public class DeviceResource {
|
|||||||
this.hueHome = aHueHome;
|
this.hueHome = aHueHome;
|
||||||
else
|
else
|
||||||
this.hueHome = null;
|
this.hueHome = null;
|
||||||
setupEndpoints();
|
|
||||||
|
if(theSettings.isValidHal())
|
||||||
|
this.hueHome = aHueHome;
|
||||||
|
else
|
||||||
|
this.halHome = null;
|
||||||
|
|
||||||
|
setupEndpoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeviceRepository getDeviceRepository() {
|
public DeviceRepository getDeviceRepository() {
|
||||||
@@ -262,6 +270,16 @@ public class DeviceResource {
|
|||||||
return hueHome.getDevices();
|
return hueHome.getDevices();
|
||||||
}, new JsonTransformer());
|
}, new JsonTransformer());
|
||||||
|
|
||||||
|
get (API_CONTEXT + "/hal/devices", "application/json", (request, response) -> {
|
||||||
|
log.debug("Get hal items");
|
||||||
|
if(halHome == null) {
|
||||||
|
response.status(HttpStatus.SC_NOT_FOUND);
|
||||||
|
return new ErrorMessage("A Hal is not available.");
|
||||||
|
}
|
||||||
|
response.status(HttpStatus.SC_OK);
|
||||||
|
return halHome.getDevices();
|
||||||
|
}, new JsonTransformer());
|
||||||
|
|
||||||
get (API_CONTEXT + "/backup/available", "application/json", (request, response) -> {
|
get (API_CONTEXT + "/backup/available", "application/json", (request, response) -> {
|
||||||
log.debug("Get backup filenames");
|
log.debug("Get backup filenames");
|
||||||
response.status(HttpStatus.SC_OK);
|
response.status(HttpStatus.SC_OK);
|
||||||
|
|||||||
15
src/main/java/com/bwssystems/hal/DeviceElements.java
Normal file
15
src/main/java/com/bwssystems/hal/DeviceElements.java
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package com.bwssystems.hal;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DeviceElements {
|
||||||
|
private List<DeviceName> DeviceElements;
|
||||||
|
|
||||||
|
public List<DeviceName> getDeviceElements() {
|
||||||
|
return DeviceElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceElements(List<DeviceName> deviceElements) {
|
||||||
|
DeviceElements = deviceElements;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/main/java/com/bwssystems/hal/DeviceName.java
Normal file
14
src/main/java/com/bwssystems/hal/DeviceName.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.bwssystems.hal;
|
||||||
|
|
||||||
|
public class DeviceName {
|
||||||
|
private String DeviceName;
|
||||||
|
|
||||||
|
public String getDeviceName() {
|
||||||
|
return DeviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceName(String deviceName) {
|
||||||
|
DeviceName = deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
32
src/main/java/com/bwssystems/hal/HalDevice.java
Normal file
32
src/main/java/com/bwssystems/hal/HalDevice.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package com.bwssystems.hal;
|
||||||
|
|
||||||
|
public class HalDevice {
|
||||||
|
private String haldevicetype;
|
||||||
|
private String haldevicename;
|
||||||
|
private String haladdress;
|
||||||
|
private String halname;
|
||||||
|
public String getHaldevicetype() {
|
||||||
|
return haldevicetype;
|
||||||
|
}
|
||||||
|
public void setHaldevicetype(String haldevicetype) {
|
||||||
|
this.haldevicetype = haldevicetype;
|
||||||
|
}
|
||||||
|
public String getHaldevicename() {
|
||||||
|
return haldevicename;
|
||||||
|
}
|
||||||
|
public void setHaldevicename(String haldevicename) {
|
||||||
|
this.haldevicename = haldevicename;
|
||||||
|
}
|
||||||
|
public String getHaladdress() {
|
||||||
|
return haladdress;
|
||||||
|
}
|
||||||
|
public void setHaladdress(String haladdress) {
|
||||||
|
this.haladdress = haladdress;
|
||||||
|
}
|
||||||
|
public String getHalname() {
|
||||||
|
return halname;
|
||||||
|
}
|
||||||
|
public void setHalname(String halname) {
|
||||||
|
this.halname = halname;
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/main/java/com/bwssystems/hal/HalHome.java
Normal file
60
src/main/java/com/bwssystems/hal/HalHome.java
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package com.bwssystems.hal;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
|
||||||
|
import com.bwssystems.HABridge.NamedIP;
|
||||||
|
|
||||||
|
public class HalHome {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(HalHome.class);
|
||||||
|
private Map<String, HalInfo> hals;
|
||||||
|
|
||||||
|
public HalHome(BridgeSettingsDescriptor bridgeSettings) {
|
||||||
|
super();
|
||||||
|
hals = new HashMap<String, HalInfo>();
|
||||||
|
if(!bridgeSettings.isValidHal())
|
||||||
|
return;
|
||||||
|
Iterator<NamedIP> theList = bridgeSettings.getHarmonyAddress().getDevices().iterator();
|
||||||
|
while(theList.hasNext()) {
|
||||||
|
NamedIP aHal = theList.next();
|
||||||
|
try {
|
||||||
|
hals.put(aHal.getName(), new HalInfo(aHal, bridgeSettings.getHaltoken()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Cannot get harmony client (" + aHal.getName() + ") setup, Exiting with message: " + e.getMessage(), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<HalDevice> getDevices() {
|
||||||
|
log.debug("consolidating devices for hues");
|
||||||
|
Iterator<String> keys = hals.keySet().iterator();
|
||||||
|
List<HalDevice> deviceList = new ArrayList<HalDevice>();
|
||||||
|
while(keys.hasNext()) {
|
||||||
|
String key = keys.next();
|
||||||
|
List<HalDevice> theResponse = hals.get(key).getLights();
|
||||||
|
if(theResponse != null) {
|
||||||
|
Iterator<HalDevice> devices = theResponse.iterator();
|
||||||
|
while(devices.hasNext()) {
|
||||||
|
HalDevice theDevice = devices.next();
|
||||||
|
HalDevice aNewHalDevice = new HalDevice();
|
||||||
|
aNewHalDevice.setHaldevicetype(theDevice.getHaldevicetype());
|
||||||
|
aNewHalDevice.setHaldevicename(theDevice.getHaldevicename());
|
||||||
|
aNewHalDevice.setHaladdress(hals.get(key).getHalAddress().getIp());
|
||||||
|
aNewHalDevice.setHalname(key);
|
||||||
|
deviceList.add(aNewHalDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
log.warn("Cannot get lights for Hal with name: " + key);
|
||||||
|
}
|
||||||
|
return deviceList;
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/main/java/com/bwssystems/hal/HalInfo.java
Normal file
89
src/main/java/com/bwssystems/hal/HalInfo.java
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package com.bwssystems.hal;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.bwssystems.HABridge.NamedIP;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
public class HalInfo {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(HalInfo.class);
|
||||||
|
private static final String LIGHTS_REQUEST = "/DeviceData!DeviceCmd=GetNames!DeviceType=Light?Token=";
|
||||||
|
private HttpClient httpClient;
|
||||||
|
private NamedIP halAddress;
|
||||||
|
private String theToken;
|
||||||
|
|
||||||
|
public HalInfo(NamedIP addressName, String aGivenToken) {
|
||||||
|
super();
|
||||||
|
httpClient = HttpClients.createDefault();
|
||||||
|
halAddress = addressName;
|
||||||
|
theToken = aGivenToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<HalDevice> getLights() {
|
||||||
|
DeviceElements theHalApiResponse = null;
|
||||||
|
List<HalDevice> deviceList = null;
|
||||||
|
|
||||||
|
String theUrl = null;
|
||||||
|
String theData;
|
||||||
|
theUrl = "http://" + halAddress.getIp() + LIGHTS_REQUEST + theToken;
|
||||||
|
theData = doHttpGETRequest(theUrl);
|
||||||
|
if(theData != null) {
|
||||||
|
log.debug("GET HalApiResponse - data: " + theData);
|
||||||
|
theHalApiResponse = new Gson().fromJson(theData, DeviceElements.class);
|
||||||
|
deviceList = new ArrayList<HalDevice>();
|
||||||
|
|
||||||
|
Iterator<DeviceName> theDeviceNames = theHalApiResponse.getDeviceElements().iterator();
|
||||||
|
while(theDeviceNames.hasNext()) {
|
||||||
|
DeviceName theDevice = theDeviceNames.next();
|
||||||
|
HalDevice aNewHalDevice = new HalDevice();
|
||||||
|
aNewHalDevice.setHaldevicetype("lights");
|
||||||
|
aNewHalDevice.setHaldevicename(theDevice.getDeviceName());
|
||||||
|
deviceList.add(aNewHalDevice);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.warn("GET HalApiResponse for " + halAddress.getName() + " - returned null, no data.");
|
||||||
|
}
|
||||||
|
return deviceList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function executes the url against the hal
|
||||||
|
protected String doHttpGETRequest(String url) {
|
||||||
|
String theContent = null;
|
||||||
|
log.debug("calling GET on URL: " + url);
|
||||||
|
HttpGet httpGet = new HttpGet(url);
|
||||||
|
try {
|
||||||
|
HttpResponse response = httpClient.execute(httpGet);
|
||||||
|
log.debug("GET on URL responded: " + response.getStatusLine().getStatusCode());
|
||||||
|
if(response.getStatusLine().getStatusCode() == 200){
|
||||||
|
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); //read content for data
|
||||||
|
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("doHttpGETRequest: Error calling out to HA gateway: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return theContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NamedIP getHalAddress() {
|
||||||
|
return halAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHalAddress(NamedIP halAddress) {
|
||||||
|
this.halAddress = halAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -34,6 +34,9 @@ app.config(function ($routeProvider) {
|
|||||||
}).when('/huedevices', {
|
}).when('/huedevices', {
|
||||||
templateUrl: 'views/huedevice.html',
|
templateUrl: 'views/huedevice.html',
|
||||||
controller: 'HueController'
|
controller: 'HueController'
|
||||||
|
}).when('/haldevices', {
|
||||||
|
templateUrl: 'views/haldevice.html',
|
||||||
|
controller: 'HalController'
|
||||||
}).otherwise({
|
}).otherwise({
|
||||||
templateUrl: 'views/configuration.html',
|
templateUrl: 'views/configuration.html',
|
||||||
controller: 'ViewingController'
|
controller: 'ViewingController'
|
||||||
@@ -47,7 +50,7 @@ app.run( function (bridgeService) {
|
|||||||
|
|
||||||
app.service('bridgeService', function ($http, $window, ngToast) {
|
app.service('bridgeService', function ($http, $window, ngToast) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.state = {base: window.location.origin + "/api/devices", bridgelocation: window.location.origin, systemsbase: window.location.origin + "/system", huebase: window.location.origin + "/api", configs: [], backups: [], devices: [], device: [], mapandid: [], type: "", settings: [], myToastMsg: [], logMsgs: [], loggerInfo: [], olddevicename: "", logShowAll: false, isInControl: false, showVera: false, showHarmony: false, showNest: false, showHue: false, habridgeversion: ""};
|
this.state = {base: window.location.origin + "/api/devices", bridgelocation: window.location.origin, systemsbase: window.location.origin + "/system", huebase: window.location.origin + "/api", configs: [], backups: [], devices: [], device: [], mapandid: [], type: "", settings: [], myToastMsg: [], logMsgs: [], loggerInfo: [], olddevicename: "", logShowAll: false, isInControl: false, showVera: false, showHarmony: false, showNest: false, showHue: false, showHal: false, habridgeversion: ""};
|
||||||
|
|
||||||
this.displayWarn = function(errorTitle, error) {
|
this.displayWarn = function(errorTitle, error) {
|
||||||
var toastContent = errorTitle;
|
var toastContent = errorTitle;
|
||||||
@@ -155,6 +158,11 @@ app.service('bridgeService', function ($http, $window, ngToast) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateShowHal = function () {
|
||||||
|
this.state.showHal = self.state.settings.halconfigured;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.loadBridgeSettings = function () {
|
this.loadBridgeSettings = function () {
|
||||||
return $http.get(this.state.systemsbase + "/settings").then(
|
return $http.get(this.state.systemsbase + "/settings").then(
|
||||||
function (response) {
|
function (response) {
|
||||||
@@ -163,6 +171,7 @@ app.service('bridgeService', function ($http, $window, ngToast) {
|
|||||||
self.updateShowHarmony();
|
self.updateShowHarmony();
|
||||||
self.updateShowNest();
|
self.updateShowNest();
|
||||||
self.updateShowHue();
|
self.updateShowHue();
|
||||||
|
self.updateShowHal();
|
||||||
},
|
},
|
||||||
function (error) {
|
function (error) {
|
||||||
self.displayWarn("Load Bridge Settings Error: ", error);
|
self.displayWarn("Load Bridge Settings Error: ", error);
|
||||||
@@ -292,6 +301,19 @@ app.service('bridgeService', function ($http, $window, ngToast) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.viewHalDevices = function () {
|
||||||
|
if(!this.state.showHal)
|
||||||
|
return;
|
||||||
|
return $http.get(this.state.base + "/hal/devices").then(
|
||||||
|
function (response) {
|
||||||
|
self.state.haldevices = response.data;
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
self.displayWarn("Get Hal Devices Error: ", error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
this.updateLogLevels = function(logComponents) {
|
this.updateLogLevels = function(logComponents) {
|
||||||
return $http.put(this.state.systemsbase + "/logmgmt/update", logComponents ).then(
|
return $http.put(this.state.systemsbase + "/logmgmt/update", logComponents ).then(
|
||||||
function (response) {
|
function (response) {
|
||||||
@@ -616,6 +638,22 @@ app.controller('SystemController', function ($scope, $location, $http, $window,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
$scope.addHaltoSettings = function (newhalname, newhalip) {
|
||||||
|
if($scope.bridge.settings.haladdress == null) {
|
||||||
|
$scope.bridge.settings.haladdress = { devices: [] };
|
||||||
|
}
|
||||||
|
var newhal = {name: newhalname, ip: newhalip }
|
||||||
|
$scope.bridge.settings.haladdress.devices.push(newhal);
|
||||||
|
$scope.newhalname = null;
|
||||||
|
$scope.newhalip = null;
|
||||||
|
};
|
||||||
|
$scope.removeHaltoSettings = function (halname, halip) {
|
||||||
|
for(var i = $scope.bridge.settings.haladdress.devices.length - 1; i >= 0; i--) {
|
||||||
|
if($scope.bridge.settings.haladdress.devices[i].name === halname && $scope.bridge.settings.haladdress.devices[i].ip === halip) {
|
||||||
|
$scope.bridge.settings.haladdress.devices.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
$scope.bridgeReinit = function () {
|
$scope.bridgeReinit = function () {
|
||||||
$scope.isInControl = false;
|
$scope.isInControl = false;
|
||||||
bridgeService.reinit();
|
bridgeService.reinit();
|
||||||
@@ -1270,6 +1308,131 @@ app.controller('HueController', function ($scope, $location, $http, bridgeServic
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.controller('HalController', function ($scope, $location, $http, bridgeService, ngDialog) {
|
||||||
|
$scope.bridge = bridgeService.state;
|
||||||
|
$scope.device = $scope.bridge.device;
|
||||||
|
$scope.device_dim_control = "";
|
||||||
|
$scope.bulk = { devices: [] };
|
||||||
|
bridgeService.viewHalDevices();
|
||||||
|
$scope.imgButtonsUrl = "glyphicon glyphicon-plus";
|
||||||
|
$scope.buttonsVisible = false;
|
||||||
|
|
||||||
|
$scope.clearDevice = function () {
|
||||||
|
bridgeService.clearDevice();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.buildDeviceUrls = function (haldevice, dim_control) {
|
||||||
|
bridgeService.clearDevice();
|
||||||
|
$scope.device.deviceType = "switch";
|
||||||
|
$scope.device.name = haldevice.haldevicename;
|
||||||
|
$scope.device.targetDevice = haldevice.halname;
|
||||||
|
$scope.device.mapType = "halDevice";
|
||||||
|
$scope.device.mapId = haldevice.haldevicename + "-" + haldevice.halname;
|
||||||
|
if(dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0)
|
||||||
|
$scope.device.dimUrl = "http://" + haldevice.veraaddress
|
||||||
|
+ "/DeviceService!DeviceName="
|
||||||
|
+ haldevice.haldevicename
|
||||||
|
+ "!DeviceCmd=SetDevice!DeviceValue=Dim!DevicePercent="
|
||||||
|
+ dim_control
|
||||||
|
+ "?Token="
|
||||||
|
+ $scope.bridge.settings.haltoken;
|
||||||
|
else
|
||||||
|
$scope.device.dimUrl = "http://" + veradevice.veraaddress
|
||||||
|
+ "/DeviceService!DeviceName="
|
||||||
|
+ haldevice.haldevicename
|
||||||
|
+ "!DeviceCmd=SetDevice!DeviceValue=On?Token="
|
||||||
|
+ $scope.bridge.settings.haltoken;
|
||||||
|
$scope.device.onUrl = "http://" + veradevice.veraaddress
|
||||||
|
+ "/DeviceService!DeviceName="
|
||||||
|
+ haldevice.haldevicename
|
||||||
|
+ "!DeviceCmd=SetDevice!DeviceValue=On?Token="
|
||||||
|
+ $scope.bridge.settings.haltoken;;
|
||||||
|
$scope.device.offUrl = "http://" + veradevice.veraaddress
|
||||||
|
+ "/DeviceService!DeviceName="
|
||||||
|
+ haldevice.haldevicename
|
||||||
|
+ "!DeviceCmd=SetDevice!DeviceValue=Off?Token="
|
||||||
|
+ $scope.bridge.settings.haltoken;;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addDevice = function () {
|
||||||
|
if($scope.device.name == "" && $scope.device.onUrl == "")
|
||||||
|
return;
|
||||||
|
bridgeService.addDevice($scope.device).then(
|
||||||
|
function () {
|
||||||
|
$scope.clearDevice();
|
||||||
|
bridgeService.viewDevices();
|
||||||
|
bridgeService.viewHalDevices();
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
bridgeService.displayWarn("Error adding device: " + $scope.device.name, error)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.bulkAddDevices = function(dim_control) {
|
||||||
|
var devicesList = [];
|
||||||
|
for(var i = 0; i < $scope.bulk.devices.length; i++) {
|
||||||
|
for(var x = 0; x < bridgeService.state.veradevices.length; x++) {
|
||||||
|
if(bridgeService.state.veradevices[x].id == $scope.bulk.devices[i]) {
|
||||||
|
$scope.buildDeviceUrls(bridgeService.state.veradevices[x],dim_control);
|
||||||
|
devicesList[i] = {
|
||||||
|
name: $scope.device.name,
|
||||||
|
mapId: $scope.device.mapId,
|
||||||
|
mapType: $scope.device.mapType,
|
||||||
|
deviceType: $scope.device.deviceType,
|
||||||
|
targetDevice: $scope.device.targetDevice,
|
||||||
|
onUrl: $scope.device.onUrl,
|
||||||
|
offUrl: $scope.device.offUrl,
|
||||||
|
headers: $scope.device.headers,
|
||||||
|
httpVerb: $scope.device.httpVerb,
|
||||||
|
contentType: $scope.device.contentType,
|
||||||
|
contentBody: $scope.device.contentBody,
|
||||||
|
contentBodyOff: $scope.device.contentBodyOff
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bridgeService.bulkAddDevice(devicesList);
|
||||||
|
$scope.clearDevice();
|
||||||
|
bridgeService.viewDevices();
|
||||||
|
bridgeService.HalVeraDevices();
|
||||||
|
$scope.bulk = { devices: [] };
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.toggleSelection = function toggleSelection(deviceId) {
|
||||||
|
var idx = $scope.bulk.devices.indexOf(deviceId);
|
||||||
|
|
||||||
|
// is currently selected
|
||||||
|
if (idx > -1) {
|
||||||
|
$scope.bulk.devices.splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// is newly selected
|
||||||
|
else {
|
||||||
|
$scope.bulk.devices.push(deviceId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.toggleButtons = function () {
|
||||||
|
$scope.buttonsVisible = !$scope.buttonsVisible;
|
||||||
|
if($scope.buttonsVisible)
|
||||||
|
$scope.imgButtonsUrl = "glyphicon glyphicon-minus";
|
||||||
|
else
|
||||||
|
$scope.imgButtonsUrl = "glyphicon glyphicon-plus";
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.deleteDeviceByMapId = function (id, mapType) {
|
||||||
|
$scope.bridge.mapandid = { id, mapType };
|
||||||
|
ngDialog.open({
|
||||||
|
template: 'deleteMapandIdDialog',
|
||||||
|
controller: 'DeleteMapandIdDialogCtrl',
|
||||||
|
className: 'ngdialog-theme-default'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
app.controller('EditController', function ($scope, $location, $http, bridgeService) {
|
app.controller('EditController', function ($scope, $location, $http, bridgeService) {
|
||||||
$scope.bridge = bridgeService.state;
|
$scope.bridge = bridgeService.state;
|
||||||
$scope.device = $scope.bridge.device;
|
$scope.device = $scope.bridge.device;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li>
|
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li>
|
||||||
<li ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
|
<li ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
|
||||||
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</a></li>
|
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</a></li>
|
||||||
|
<li ng-if="bridge.showHal" role="presentation"><a href="#/haldevices">HAL Devices</a></li>
|
||||||
<li role="presentation"><a href="#/editor">Manual Add</a></li>
|
<li role="presentation"><a href="#/editor">Manual Add</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
160
src/main/resources/public/views/haldevice.html
Normal file
160
src/main/resources/public/views/haldevice.html
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<ul class="nav nav-pills" role="tablist">
|
||||||
|
<li role="presentation"><a href="#">Bridge Devices</a></li>
|
||||||
|
<li role="presentation"><a href="#/system">Bridge Control</a></li>
|
||||||
|
<li role="presentation"><a href="#/logs">Logs</a></li>
|
||||||
|
<li ng-if="bridge.showVera" role="presentation"><a href="#/veradevices">Vera Devices</a></li>
|
||||||
|
<li ng-if="bridge.showVera" role="presentation"><a href="#/verascenes">Vera 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 href="#/harmonydevices">Harmony Devices</a></li>
|
||||||
|
<li ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
|
||||||
|
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</a></li>
|
||||||
|
<li role="presentation" class="active"><a href="#/haldevices">HAL Devices</a></li>
|
||||||
|
<li role="presentation"><a href="#/editor">Manual Add</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="panel panel-default bridgeServer">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h2 class="panel-title">HAL Device List ({{bridge.haldevices.length}})</h2>
|
||||||
|
</div>
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<p class="text-muted">For any HAL Device, use the action buttons to generate the device addition information below automatically.
|
||||||
|
Then you can modify the name to anything you want that will be the keyword for Alexa. Click the 'Add Bridge Device' to finish that selection setup.
|
||||||
|
The 'Already Configured HAL Devices' list below will show what is already setup for your HAL.</p>
|
||||||
|
<p>Also, use this select menu for which type of dim
|
||||||
|
control you would like to be generated:
|
||||||
|
<select name="device-dim-control" id="device-dim-control" ng-model="device_dim_control">
|
||||||
|
<option value="">none</option>
|
||||||
|
<option value="${intensity.byte}">Pass-thru Value</option>
|
||||||
|
<option value="${intensity.percent}">Percentage</option>
|
||||||
|
<option value="${intensity.math(X*1)}">Custom Math</option>
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
<p>Use the check boxes by the names to use the bulk addition feature. Select your items and dim control type if wanted, then click bulk add below.
|
||||||
|
Your items will be added with on and off or dim and off if selected with the name of the device from the HAL.
|
||||||
|
</p>
|
||||||
|
<scrollable-table watch="bridge.haldevices">
|
||||||
|
<table class="table table-bordered table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Row</th>
|
||||||
|
<th sortable-header col="name">Name</th>
|
||||||
|
<th sortable-header col="category">Category</th>
|
||||||
|
<th sortable-header col="halname">HAL</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr ng-repeat="haldevice in bridge.haldevices | availableHalDeviceId">
|
||||||
|
<td>{{$index+1}}</td>
|
||||||
|
<td><input type="checkbox" name="bulk.devices[]" value="{{haldevice.haldevicename}}" ng-checked="bulk.devices.indexOf(haldevice.haldevicename) > -1" ng-click="toggleSelection(haldevice.haldevicename)"> {{haldevice.haldevicename}}</td>
|
||||||
|
<td>{{haldevice.haldevicetype}}</td>
|
||||||
|
<td>{{haldevice.halname}}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-success" type="submit"
|
||||||
|
ng-click="buildDeviceUrls(haldevice, device_dim_control)">Generate Bridge Device</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</scrollable-table>
|
||||||
|
<p>
|
||||||
|
<button class="btn btn-success" type="submit"
|
||||||
|
ng-click="bulkAddDevices(device_dim_control)">Bulk Add ({{bulk.devices.length}})</button>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h2 class="panel-title">Already Configured HAL Devices <a ng-click="toggleButtons()"><span class={{imgButtonsUrl}} aria-hidden="true"></span></a></a></h2>
|
||||||
|
</div>
|
||||||
|
<ul ng-if="buttonsVisible" class="list-group">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<scrollable-table watch="bridge.haldevices">
|
||||||
|
<table class="table table-bordered table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Row</th>
|
||||||
|
<th sortable-header col="name">Name</th>
|
||||||
|
<th sortable-header col="category">Category</th>
|
||||||
|
<th sortable-header col="halname">HAL</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr ng-repeat="haldevice in bridge.haldevices | unavailableHalDeviceId">
|
||||||
|
<td>{{$index+1}}</td>
|
||||||
|
<td>{{haldevice.haldevicename}}</td>
|
||||||
|
<td>{{haldevice.haldevicetype}}</td>
|
||||||
|
<td>{{haldevice.halname}}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-danger" type="submit"
|
||||||
|
ng-click="deleteDeviceByMapId(haldevice.haldevicename, 'halDevice')">Delete</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</scrollable-table>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="panel panel-default bridgeServer">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h2 class="panel-title">Add Bridge Device for a HAL Device</h2>
|
||||||
|
</div>
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="col-xs-8 col-sm-7">
|
||||||
|
<input type="text" class="form-control" id="device-name"
|
||||||
|
ng-model="device.name" placeholder="Device Name">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary" ng-click="addDevice()">
|
||||||
|
Add Bridge Device</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
|
||||||
|
URL </label>
|
||||||
|
|
||||||
|
<div class="col-xs-8 col-sm-7">
|
||||||
|
<textarea rows="3" class="form-control" id="device-on-url"
|
||||||
|
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-danger" ng-click="clearDevice()">
|
||||||
|
Clear Device</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-xs-12 col-sm-2 control-label" for="device-dim-url">Dim
|
||||||
|
URL </label>
|
||||||
|
|
||||||
|
<div class="col-xs-8 col-sm-7">
|
||||||
|
<textarea rows="3" class="form-control" id="device-dim-url"
|
||||||
|
ng-model="device.dimUrl" placeholder="URL to dim device"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-xs-12 col-sm-2 control-label"
|
||||||
|
for="device-off-url">Off URL </label>
|
||||||
|
|
||||||
|
<div class="col-xs-8 col-sm-7">
|
||||||
|
<textarea rows="3" class="form-control" id="device-off-url"
|
||||||
|
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<script type="text/ng-template" id="deleteMapandIdDialog">
|
||||||
|
<div class="ngdialog-message">
|
||||||
|
<h2>Device Map and Id?</h2>
|
||||||
|
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
|
||||||
|
<p>Are you Sure?</p>
|
||||||
|
</div>
|
||||||
|
<div class="ngdialog-buttons mt">
|
||||||
|
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
@@ -169,6 +169,38 @@
|
|||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>HAL Names and IP Addresses</td>
|
||||||
|
<td><table class="table table-bordered table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>IP</th>
|
||||||
|
<th>Manage</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr ng-repeat="hal in bridge.settings.haladdress.devices">
|
||||||
|
<td>{{hal.name}}</td>
|
||||||
|
<td>{{hal.ip}}</td>
|
||||||
|
<td><button class="btn btn-danger" type="submit"
|
||||||
|
ng-click="removeHaltoSettings(hal.name, hal.ip)">Del</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input id="bridge-settings-next-hal-name" class="form-control" type="text"
|
||||||
|
ng-model="newhalname" placeholder="A Hal"></td>
|
||||||
|
<td><input id="bridge-settings-next-hal-ip" class="form-control" type="text"
|
||||||
|
ng-model="newhalip" placeholder="192.168.1.3"></td>
|
||||||
|
<td><button class="btn btn-success" type="submit"
|
||||||
|
ng-click="addHaltoSettings(newhalname, newhalip)">Add</button></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>HAL Token</td>
|
||||||
|
<td><input id="bridge-settings-haltoken" class="form-control" type="password"
|
||||||
|
ng-model="bridge.settings.halpwd" placeholder="thetoken"></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Nest Username</td>
|
<td>Nest Username</td>
|
||||||
<td><input id="bridge-settings-nestuser" class="form-control" type="text"
|
<td><input id="bridge-settings-nestuser" class="form-control" type="text"
|
||||||
|
|||||||
Reference in New Issue
Block a user