mirror of
https://github.com/bwssytems/ha-bridge.git
synced 2025-12-18 00:10:20 +00:00
Testing HomeAssistant Implementation.
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>3.5.1i</version>
|
<version>3.5.1j</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>HA Bridge</name>
|
<name>HA Bridge</name>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public class DeviceMapTypes {
|
|||||||
public final static String[] HAL_THERMO_SET = { "halThermoSet", "HAL Thermostat"};
|
public final static String[] HAL_THERMO_SET = { "halThermoSet", "HAL Thermostat"};
|
||||||
public final static String[] MQTT_MESSAGE = { "mqttMessage", "MQTT Message"};
|
public final static String[] MQTT_MESSAGE = { "mqttMessage", "MQTT Message"};
|
||||||
public final static String[] EXEC_DEVICE = { "execDevice", "Execute Script/Program"};
|
public final static String[] EXEC_DEVICE = { "execDevice", "Execute Script/Program"};
|
||||||
|
public final static String[] HASS_DEVICE = { "hassDevice", "HomeAssistant Device"};
|
||||||
|
|
||||||
public final static int typeIndex = 0;
|
public final static int typeIndex = 0;
|
||||||
public final static int displayIndex = 1;
|
public final static int displayIndex = 1;
|
||||||
@@ -34,6 +35,7 @@ public class DeviceMapTypes {
|
|||||||
public String[] halThermoSet;
|
public String[] halThermoSet;
|
||||||
public String[] mqttMessage;
|
public String[] mqttMessage;
|
||||||
public String[] execDevice;
|
public String[] execDevice;
|
||||||
|
public String[] hassDevice;
|
||||||
|
|
||||||
public int typeindex;
|
public int typeindex;
|
||||||
public int displayindex;
|
public int displayindex;
|
||||||
@@ -57,6 +59,7 @@ public class DeviceMapTypes {
|
|||||||
this.setTypeindex(typeIndex);
|
this.setTypeindex(typeIndex);
|
||||||
this.setVeraDevice(VERA_DEVICE);
|
this.setVeraDevice(VERA_DEVICE);
|
||||||
this.setVeraScene(VERA_SCENE);
|
this.setVeraScene(VERA_SCENE);
|
||||||
|
this.setHassDevice(HASS_DEVICE);
|
||||||
}
|
}
|
||||||
public String[] getCustomDevice() {
|
public String[] getCustomDevice() {
|
||||||
return customDevice;
|
return customDevice;
|
||||||
@@ -142,6 +145,12 @@ public class DeviceMapTypes {
|
|||||||
public void setExecDevice(String[] execDevice) {
|
public void setExecDevice(String[] execDevice) {
|
||||||
this.execDevice = execDevice;
|
this.execDevice = execDevice;
|
||||||
}
|
}
|
||||||
|
public String[] getHassDevice() {
|
||||||
|
return hassDevice;
|
||||||
|
}
|
||||||
|
public void setHassDevice(String[] hassDevice) {
|
||||||
|
this.hassDevice = hassDevice;
|
||||||
|
}
|
||||||
public int getTypeindex() {
|
public int getTypeindex() {
|
||||||
return typeindex;
|
return typeindex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public class HABridge {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// setup the class to handle the hue emulator rest api
|
// setup the class to handle the hue emulator rest api
|
||||||
theHueMulator = new HueMulator(bridgeSettings.getBridgeSettingsDescriptor(), theResources.getDeviceRepository(), harmonyHome, nestHome, hueHome, mqttHome, udpSender);
|
theHueMulator = new HueMulator(bridgeSettings.getBridgeSettingsDescriptor(), theResources.getDeviceRepository(), harmonyHome, nestHome, hueHome, mqttHome, hassHome, udpSender);
|
||||||
theHueMulator.setupServer();
|
theHueMulator.setupServer();
|
||||||
// wait for the sparkjava initialization of the rest api classes to be complete
|
// wait for the sparkjava initialization of the rest api classes to be complete
|
||||||
awaitInitialization();
|
awaitInitialization();
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.bwssystems.HABridge.api;
|
package com.bwssystems.HABridge.api;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
|
||||||
public class CallItem {
|
public class CallItem {
|
||||||
private String item;
|
private JsonElement item;
|
||||||
private Integer count;
|
private Integer count;
|
||||||
private Integer delay;
|
private Integer delay;
|
||||||
private String type;
|
private String type;
|
||||||
@@ -23,12 +25,12 @@ public class CallItem {
|
|||||||
this.filterIPs = filterIPs;
|
this.filterIPs = filterIPs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getItem() {
|
public JsonElement getItem() {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setItem(String anitem) {
|
public void setItem(JsonElement item) {
|
||||||
item = anitem;
|
this.item = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getCount() {
|
public Integer getCount() {
|
||||||
|
|||||||
@@ -14,11 +14,16 @@ public class CallItemDeserializer implements JsonDeserializer<CallItem> {
|
|||||||
JsonObject jsonObj = json.getAsJsonObject();
|
JsonObject jsonObj = json.getAsJsonObject();
|
||||||
JsonElement jsonElem;
|
JsonElement jsonElem;
|
||||||
jsonElem = jsonObj.get("item");
|
jsonElem = jsonObj.get("item");
|
||||||
aCallItem.setItem(jsonElem.getAsString());
|
aCallItem.setItem(jsonElem);
|
||||||
jsonElem = jsonObj.get("delay");
|
jsonElem = jsonObj.get("delay");
|
||||||
aCallItem.setDelay(jsonElem.getAsInt());
|
aCallItem.setDelay(jsonElem.getAsInt());
|
||||||
jsonElem = jsonObj.get("count");
|
jsonElem = jsonObj.get("count");
|
||||||
aCallItem.setCount(jsonElem.getAsInt());
|
aCallItem.setCount(jsonElem.getAsInt());
|
||||||
|
jsonElem = jsonObj.get("type");
|
||||||
|
aCallItem.setType(jsonElem.getAsString());
|
||||||
|
jsonElem = jsonObj.get("filterIPs");
|
||||||
|
aCallItem.setFilterIPs(jsonElem.getAsString());
|
||||||
|
|
||||||
return aCallItem;
|
return aCallItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ import com.bwssystems.harmony.ButtonPress;
|
|||||||
import com.bwssystems.harmony.HarmonyHandler;
|
import com.bwssystems.harmony.HarmonyHandler;
|
||||||
import com.bwssystems.harmony.HarmonyHome;
|
import com.bwssystems.harmony.HarmonyHome;
|
||||||
import com.bwssystems.harmony.RunActivity;
|
import com.bwssystems.harmony.RunActivity;
|
||||||
|
import com.bwssystems.hass.HassCommand;
|
||||||
|
import com.bwssystems.hass.HassHome;
|
||||||
|
import com.bwssystems.hass.HomeAssistant;
|
||||||
import com.bwssystems.hue.HueDeviceIdentifier;
|
import com.bwssystems.hue.HueDeviceIdentifier;
|
||||||
import com.bwssystems.hue.HueErrorStringSet;
|
import com.bwssystems.hue.HueErrorStringSet;
|
||||||
import com.bwssystems.hue.HueHome;
|
import com.bwssystems.hue.HueHome;
|
||||||
@@ -101,6 +104,7 @@ public class HueMulator implements HueErrorStringSet {
|
|||||||
private Nest theNest;
|
private Nest theNest;
|
||||||
private HueHome myHueHome;
|
private HueHome myHueHome;
|
||||||
private MQTTHome mqttHome;
|
private MQTTHome mqttHome;
|
||||||
|
private HassHome hassHome;
|
||||||
private HttpClient httpClient;
|
private HttpClient httpClient;
|
||||||
private CloseableHttpClient httpclientSSL;
|
private CloseableHttpClient httpclientSSL;
|
||||||
private SSLContext sslcontext;
|
private SSLContext sslcontext;
|
||||||
@@ -114,7 +118,7 @@ public class HueMulator implements HueErrorStringSet {
|
|||||||
// private Gson callItemGson;
|
// private Gson callItemGson;
|
||||||
|
|
||||||
public HueMulator(BridgeSettingsDescriptor theBridgeSettings, DeviceRepository aDeviceRepository,
|
public HueMulator(BridgeSettingsDescriptor theBridgeSettings, DeviceRepository aDeviceRepository,
|
||||||
HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome, MQTTHome aMqttHome,
|
HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome, MQTTHome aMqttHome, HassHome aHassHome,
|
||||||
UDPDatagramSender aUdpDatagramSender) {
|
UDPDatagramSender aUdpDatagramSender) {
|
||||||
httpClient = HttpClients.createDefault();
|
httpClient = HttpClients.createDefault();
|
||||||
// Trust own CA and all self-signed certs
|
// Trust own CA and all self-signed certs
|
||||||
@@ -142,6 +146,10 @@ public class HueMulator implements HueErrorStringSet {
|
|||||||
this.mqttHome = aMqttHome;
|
this.mqttHome = aMqttHome;
|
||||||
else
|
else
|
||||||
this.mqttHome = null;
|
this.mqttHome = null;
|
||||||
|
if (theBridgeSettings.isValidHass())
|
||||||
|
this.hassHome = aHassHome;
|
||||||
|
else
|
||||||
|
this.hassHome = null;
|
||||||
bridgeSettings = theBridgeSettings;
|
bridgeSettings = theBridgeSettings;
|
||||||
theUDPDatagramSender = aUdpDatagramSender;
|
theUDPDatagramSender = aUdpDatagramSender;
|
||||||
hueUser = null;
|
hueUser = null;
|
||||||
@@ -1054,14 +1062,45 @@ public class HueMulator implements HueErrorStringSet {
|
|||||||
+ "\",\"description\": \"Should not get here, no mqtt brokers configured\", \"parameter\": \"/lights/"
|
+ "\",\"description\": \"Should not get here, no mqtt brokers configured\", \"parameter\": \"/lights/"
|
||||||
+ lightId + "state\"}}]";
|
+ lightId + "state\"}}]";
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.HASS_DEVICE[DeviceMapTypes.typeIndex])) {
|
||||||
|
log.debug("executing HUE api request to send message to HomeAssistant: " + url);
|
||||||
|
if (hassHome != null) {
|
||||||
|
HassCommand hassCommand = new Gson().fromJson(callItems[i].getItem(), HassCommand.class);
|
||||||
|
HomeAssistant homeAssistant = hassHome.getHomeAssistant(hassCommand.getHassName());
|
||||||
|
if (homeAssistant == null) {
|
||||||
|
log.warn("Should not get here, no HomeAssistants available");
|
||||||
|
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId
|
||||||
|
+ "\",\"description\": \"Should not get here, no HiomeAssistant clients available\", \"parameter\": \"/lights/"
|
||||||
|
+ lightId + "state\"}}]";
|
||||||
|
}
|
||||||
|
for (int x = 0; x < setCount; x++) {
|
||||||
|
if (x > 0 || i > 0) {
|
||||||
|
Thread.sleep(theDelay);
|
||||||
|
}
|
||||||
|
if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0)
|
||||||
|
theDelay = callItems[i].getDelay();
|
||||||
|
else
|
||||||
|
theDelay = bridgeSettings.getButtonsleep();
|
||||||
|
log.debug("calling HomeAssistant: " + hassCommand.getHassName() + " - "
|
||||||
|
+ hassCommand.getEntityId() + " - " + hassCommand.getState() + " - " + hassCommand.getBri()
|
||||||
|
+ " - iteration: " + String.valueOf(i) + " - count: " + String.valueOf(x));
|
||||||
|
homeAssistant.callCommand(hassCommand);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("Should not get here, no HomeAssistant clients configured");
|
||||||
|
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId
|
||||||
|
+ "\",\"description\": \"Should not get here, no HomeAssistants configured\", \"parameter\": \"/lights/"
|
||||||
|
+ lightId + "state\"}}]";
|
||||||
|
|
||||||
}
|
}
|
||||||
} else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.EXEC_DEVICE[DeviceMapTypes.typeIndex])) {
|
} else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.EXEC_DEVICE[DeviceMapTypes.typeIndex])) {
|
||||||
log.debug("Exec Request called with url: " + url);
|
log.debug("Exec Request called with url: " + url);
|
||||||
String intermediate;
|
String intermediate;
|
||||||
if (callItems[i].getItem().contains("exec://"))
|
if (callItems[i].getItem().getAsString().contains("exec://"))
|
||||||
intermediate = callItems[i].getItem().substring(callItems[i].getItem().indexOf("://") + 3);
|
intermediate = callItems[i].getItem().getAsString().substring(callItems[i].getItem().getAsString().indexOf("://") + 3);
|
||||||
else
|
else
|
||||||
intermediate = callItems[i].getItem();
|
intermediate = callItems[i].getItem().getAsString();
|
||||||
for (int x = 0; x < setCount; x++) {
|
for (int x = 0; x < setCount; x++) {
|
||||||
if (x > 0 || i > 0) {
|
if (x > 0 || i > 0) {
|
||||||
Thread.sleep(theDelay);
|
Thread.sleep(theDelay);
|
||||||
@@ -1090,10 +1129,10 @@ public class HueMulator implements HueErrorStringSet {
|
|||||||
else
|
else
|
||||||
theDelay = bridgeSettings.getButtonsleep();
|
theDelay = bridgeSettings.getButtonsleep();
|
||||||
try {
|
try {
|
||||||
if (callItems[i].getItem().contains("udp://")
|
if (callItems[i].getItem().getAsString().contains("udp://")
|
||||||
|| callItems[i].getItem().contains("tcp://")) {
|
|| callItems[i].getItem().getAsString().contains("tcp://")) {
|
||||||
String intermediate = callItems[i].getItem()
|
String intermediate = callItems[i].getItem().getAsString()
|
||||||
.substring(callItems[i].getItem().indexOf("://") + 3);
|
.substring(callItems[i].getItem().getAsString().indexOf("://") + 3);
|
||||||
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
|
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
|
||||||
String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1);
|
String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1);
|
||||||
String hostAddr = null;
|
String hostAddr = null;
|
||||||
@@ -1116,12 +1155,12 @@ public class HueMulator implements HueErrorStringSet {
|
|||||||
false);
|
false);
|
||||||
sendData = theUrlBody.getBytes();
|
sendData = theUrlBody.getBytes();
|
||||||
}
|
}
|
||||||
if (callItems[i].getItem().contains("udp://")) {
|
if (callItems[i].getItem().getAsString().contains("udp://")) {
|
||||||
log.debug("executing HUE api request to UDP: " + callItems[i].getItem());
|
log.debug("executing HUE api request to UDP: " + callItems[i].getItem().getAsString());
|
||||||
theUDPDatagramSender.sendUDPResponse(new String(sendData), IPAddress,
|
theUDPDatagramSender.sendUDPResponse(new String(sendData), IPAddress,
|
||||||
Integer.parseInt(port));
|
Integer.parseInt(port));
|
||||||
} else if (callItems[i].getItem().contains("tcp://")) {
|
} else if (callItems[i].getItem().getAsString().contains("tcp://")) {
|
||||||
log.debug("executing HUE api request to TCP: " + callItems[i].getItem());
|
log.debug("executing HUE api request to TCP: " + callItems[i].getItem().getAsString());
|
||||||
Socket dataSendSocket = new Socket(IPAddress, Integer.parseInt(port));
|
Socket dataSendSocket = new Socket(IPAddress, Integer.parseInt(port));
|
||||||
DataOutputStream outToClient = new DataOutputStream(
|
DataOutputStream outToClient = new DataOutputStream(
|
||||||
dataSendSocket.getOutputStream());
|
dataSendSocket.getOutputStream());
|
||||||
@@ -1129,9 +1168,9 @@ public class HueMulator implements HueErrorStringSet {
|
|||||||
outToClient.flush();
|
outToClient.flush();
|
||||||
dataSendSocket.close();
|
dataSendSocket.close();
|
||||||
}
|
}
|
||||||
} else if (callItems[i].getItem().contains("exec://")) {
|
} else if (callItems[i].getItem().getAsString().contains("exec://")) {
|
||||||
String intermediate = callItems[i].getItem()
|
String intermediate = callItems[i].getItem().getAsString()
|
||||||
.substring(callItems[i].getItem().indexOf("://") + 3);
|
.substring(callItems[i].getItem().getAsString().indexOf("://") + 3);
|
||||||
String anError = doExecRequest(intermediate,
|
String anError = doExecRequest(intermediate,
|
||||||
calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc),
|
calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc),
|
||||||
lightId);
|
lightId);
|
||||||
@@ -1142,9 +1181,9 @@ public class HueMulator implements HueErrorStringSet {
|
|||||||
} else {
|
} else {
|
||||||
log.debug("executing HUE api request to Http "
|
log.debug("executing HUE api request to Http "
|
||||||
+ (device.getHttpVerb() == null ? "GET" : device.getHttpVerb()) + ": "
|
+ (device.getHttpVerb() == null ? "GET" : device.getHttpVerb()) + ": "
|
||||||
+ callItems[i].getItem());
|
+ callItems[i].getItem().getAsString());
|
||||||
|
|
||||||
String anUrl = replaceIntensityValue(callItems[i].getItem(),
|
String anUrl = replaceIntensityValue(callItems[i].getItem().getAsString(),
|
||||||
calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false);
|
calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false);
|
||||||
String body;
|
String body;
|
||||||
if (stateHasBri || stateHasBriInc)
|
if (stateHasBri || stateHasBriInc)
|
||||||
@@ -1171,7 +1210,7 @@ public class HueMulator implements HueErrorStringSet {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Change device state, Could not send data for network request: "
|
log.warn("Change device state, Could not send data for network request: "
|
||||||
+ callItems[i].getItem() + " with Message: " + e.getMessage());
|
+ callItems[i].getItem().getAsString() + " with Message: " + e.getMessage());
|
||||||
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId
|
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId
|
||||||
+ "\",\"description\": \"Error on calling out to device\", \"parameter\": \"/lights/"
|
+ "\",\"description\": \"Error on calling out to device\", \"parameter\": \"/lights/"
|
||||||
+ lightId + "state\"}}]";
|
+ lightId + "state\"}}]";
|
||||||
|
|||||||
33
src/main/java/com/bwssystems/hass/HassCommand.java
Normal file
33
src/main/java/com/bwssystems/hass/HassCommand.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package com.bwssystems.hass;
|
||||||
|
|
||||||
|
public class HassCommand {
|
||||||
|
private String entityId;
|
||||||
|
private String hassName;
|
||||||
|
private String state;
|
||||||
|
private Integer bri;
|
||||||
|
public String getEntityId() {
|
||||||
|
return entityId;
|
||||||
|
}
|
||||||
|
public void setEntityId(String entityId) {
|
||||||
|
this.entityId = entityId;
|
||||||
|
}
|
||||||
|
public String getHassName() {
|
||||||
|
return hassName;
|
||||||
|
}
|
||||||
|
public void setHassName(String hassName) {
|
||||||
|
this.hassName = hassName;
|
||||||
|
}
|
||||||
|
public String getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
public void setState(String state) {
|
||||||
|
this.state = state;
|
||||||
|
}
|
||||||
|
public Integer getBri() {
|
||||||
|
return bri;
|
||||||
|
}
|
||||||
|
public void setBri(Integer bri) {
|
||||||
|
this.bri = bri;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,9 +15,13 @@ import com.bwssystems.HABridge.NamedIP;
|
|||||||
public class HassHome {
|
public class HassHome {
|
||||||
private static final Logger log = LoggerFactory.getLogger(HassHome.class);
|
private static final Logger log = LoggerFactory.getLogger(HassHome.class);
|
||||||
private Map<String, HomeAssistant> hassMap;
|
private Map<String, HomeAssistant> hassMap;
|
||||||
|
private Boolean validHass;
|
||||||
|
|
||||||
public HassHome(BridgeSettingsDescriptor bridgeSettings) {
|
public HassHome(BridgeSettingsDescriptor bridgeSettings) {
|
||||||
super();
|
super();
|
||||||
|
validHass = bridgeSettings.isValidHass();
|
||||||
|
if(!validHass)
|
||||||
|
return;
|
||||||
hassMap = new HashMap<String,HomeAssistant>();
|
hassMap = new HashMap<String,HomeAssistant>();
|
||||||
if(!bridgeSettings.isValidHass())
|
if(!bridgeSettings.isValidHass())
|
||||||
return;
|
return;
|
||||||
@@ -33,8 +37,25 @@ public class HassHome {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HomeAssistant getHomeAssistant(String aName) {
|
||||||
|
if(!validHass)
|
||||||
|
return null;
|
||||||
|
HomeAssistant aHomeAssistant;
|
||||||
|
if(aName == null || aName.equals("")) {
|
||||||
|
aHomeAssistant = null;
|
||||||
|
log.debug("Cannot get HomeAssistant for name as it is empty.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
aHomeAssistant = hassMap.get(aName);
|
||||||
|
log.debug("Retrieved a HomeAssistant for name: " + aName);
|
||||||
|
}
|
||||||
|
return aHomeAssistant;
|
||||||
|
}
|
||||||
|
|
||||||
public List<HassDevice> getDevices() {
|
public List<HassDevice> getDevices() {
|
||||||
log.debug("consolidating devices for hues");
|
log.debug("consolidating devices for hass");
|
||||||
|
if(!validHass)
|
||||||
|
return null;
|
||||||
List<State> theResponse = null;
|
List<State> theResponse = null;
|
||||||
Iterator<String> keys = hassMap.keySet().iterator();
|
Iterator<String> keys = hassMap.keySet().iterator();
|
||||||
List<HassDevice> deviceList = new ArrayList<HassDevice>();
|
List<HassDevice> deviceList = new ArrayList<HassDevice>();
|
||||||
|
|||||||
@@ -1,30 +1,56 @@
|
|||||||
package com.bwssystems.hass;
|
package com.bwssystems.hass;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.config.CookieSpecs;
|
||||||
|
import org.apache.http.client.config.RequestConfig;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
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.conn.ssl.SSLConnectionSocketFactory;
|
||||||
|
import org.apache.http.entity.ContentType;
|
||||||
|
import org.apache.http.entity.StringEntity;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.impl.client.HttpClients;
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.apache.http.ssl.SSLContexts;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.bwssystems.HABridge.NamedIP;
|
import com.bwssystems.HABridge.NamedIP;
|
||||||
|
import com.bwssystems.HABridge.api.NameValue;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
public class HomeAssistant {
|
public class HomeAssistant {
|
||||||
private static final Logger log = LoggerFactory.getLogger(HomeAssistant.class);
|
private static final Logger log = LoggerFactory.getLogger(HomeAssistant.class);
|
||||||
private NamedIP hassAddress;
|
private NamedIP hassAddress;
|
||||||
private HttpClient httpClient;
|
private HttpClient httpClient;
|
||||||
|
private CloseableHttpClient httpclientSSL;
|
||||||
|
private SSLContext sslcontext;
|
||||||
|
private SSLConnectionSocketFactory sslsf;
|
||||||
|
private RequestConfig globalConfig;
|
||||||
|
|
||||||
public HomeAssistant(NamedIP addressName) {
|
public HomeAssistant(NamedIP addressName) {
|
||||||
super();
|
super();
|
||||||
httpClient = HttpClients.createDefault();
|
httpClient = HttpClients.createDefault();
|
||||||
|
// Trust own CA and all self-signed certs
|
||||||
|
sslcontext = SSLContexts.createDefault();
|
||||||
|
// Allow TLSv1 protocol only
|
||||||
|
sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null,
|
||||||
|
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
|
||||||
|
globalConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
|
||||||
|
httpclientSSL = HttpClients.custom().setSSLSocketFactory(sslsf).setDefaultRequestConfig(globalConfig).build();
|
||||||
hassAddress = addressName;
|
hassAddress = addressName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,13 +62,38 @@ public class HomeAssistant {
|
|||||||
this.hassAddress = hassAddress;
|
this.hassAddress = hassAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean callCommand(HassCommand aCommand) {
|
||||||
|
log.debug("calling HomeAssistant: " + aCommand.getHassName() + " - "
|
||||||
|
+ aCommand.getEntityId() + " - " + aCommand.getState() + " - " + aCommand.getBri());
|
||||||
|
String aUrl = "http://" + hassAddress.getIp() + ":" + hassAddress.getPort() + "/api/services/" + aCommand.getEntityId().substring(0, aCommand.getEntityId().indexOf("."));
|
||||||
|
String aBody = "{\"entity_id\":\"" + aCommand.getEntityId() + "\"";
|
||||||
|
NameValue[] headers = null;
|
||||||
|
if(hassAddress.getPassword() != null && !hassAddress.getPassword().isEmpty()) {
|
||||||
|
headers = new Gson().fromJson("{\"name\":\"x-ha-access\",\"value\":\"" + hassAddress.getPassword() + "\"}", NameValue[].class);
|
||||||
|
}
|
||||||
|
if(aCommand.getState().equalsIgnoreCase("on")) {
|
||||||
|
aUrl = aUrl + "/turn_on";
|
||||||
|
if(aCommand.getBri() != null && aCommand.getBri() > 0)
|
||||||
|
aBody = aBody + ",\"state\":\"on\",\"attributes\":{\"brightness\":" + aCommand.getBri() + "}}";
|
||||||
|
else
|
||||||
|
aBody = aBody + "}";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
aUrl = aUrl + "/turn_off";
|
||||||
|
aBody = aBody + "}";
|
||||||
|
}
|
||||||
|
String theData = doHttpRequest(aUrl, HttpPost.METHOD_NAME, "application/json", aBody, headers);
|
||||||
|
log.debug("call Command return is: <" + theData + ">");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public List<State> getDevices() {
|
public List<State> getDevices() {
|
||||||
List<State> theDeviceStates = null;
|
List<State> theDeviceStates = null;
|
||||||
State[] theHassStates;
|
State[] theHassStates;
|
||||||
String theUrl = null;
|
String theUrl = null;
|
||||||
String theData;
|
String theData;
|
||||||
theUrl = "http://" + hassAddress.getIp() + ":" + hassAddress.getPort() + "/api/states";
|
theUrl = "http://" + hassAddress.getIp() + ":" + hassAddress.getPort() + "/api/states";
|
||||||
theData = doHttpGETRequest(theUrl);
|
theData = doHttpRequest(theUrl, HttpGet.METHOD_NAME, null, null, null);
|
||||||
if(theData != null) {
|
if(theData != null) {
|
||||||
log.debug("GET Hass States - data: " + theData);
|
log.debug("GET Hass States - data: " + theData);
|
||||||
theHassStates = new Gson().fromJson(theData, State[].class);
|
theHassStates = new Gson().fromJson(theData, State[].class);
|
||||||
@@ -58,21 +109,88 @@ public class HomeAssistant {
|
|||||||
return theDeviceStates;
|
return theDeviceStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function executes the url against the hass
|
// This function executes the url from the device repository against the
|
||||||
protected String doHttpGETRequest(String url) {
|
// target as http or https as defined
|
||||||
String theContent = null;
|
protected String doHttpRequest(String url, String httpVerb, String contentType, String body, NameValue[] headers) {
|
||||||
log.debug("calling GET on URL: " + url);
|
HttpUriRequest request = null;
|
||||||
HttpGet httpGet = new HttpGet(url);
|
String theContent = null;
|
||||||
try {
|
URI theURI = null;
|
||||||
HttpResponse response = httpClient.execute(httpGet);
|
ContentType parsedContentType = null;
|
||||||
log.debug("GET on URL responded: " + response.getStatusLine().getStatusCode());
|
StringEntity requestBody = null;
|
||||||
if(response.getStatusLine().getStatusCode() == 200){
|
if (contentType != null && contentType.length() > 0) {
|
||||||
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); //read content for data
|
parsedContentType = ContentType.parse(contentType);
|
||||||
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
|
if (body != null && body.length() > 0)
|
||||||
}
|
requestBody = new StringEntity(body, parsedContentType);
|
||||||
} catch (IOException e) {
|
}
|
||||||
log.error("doHttpGETRequest: Error calling out to HA gateway: " + e.getMessage());
|
try {
|
||||||
}
|
theURI = new URI(url);
|
||||||
return theContent;
|
} catch (URISyntaxException e1) {
|
||||||
}
|
log.warn("Error creating URI http request: " + url + " with message: " + e1.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (HttpGet.METHOD_NAME.equalsIgnoreCase(httpVerb) || httpVerb == null) {
|
||||||
|
request = new HttpGet(theURI);
|
||||||
|
} else if (HttpPost.METHOD_NAME.equalsIgnoreCase(httpVerb)) {
|
||||||
|
HttpPost postRequest = new HttpPost(theURI);
|
||||||
|
if (requestBody != null)
|
||||||
|
postRequest.setEntity(requestBody);
|
||||||
|
request = postRequest;
|
||||||
|
} else if (HttpPut.METHOD_NAME.equalsIgnoreCase(httpVerb)) {
|
||||||
|
HttpPut putRequest = new HttpPut(theURI);
|
||||||
|
if (requestBody != null)
|
||||||
|
putRequest.setEntity(requestBody);
|
||||||
|
request = putRequest;
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.warn("Error creating outbound http request: IllegalArgumentException in log", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
log.debug("Making outbound call in doHttpRequest: " + request);
|
||||||
|
if (headers != null && headers.length > 0) {
|
||||||
|
for (int i = 0; i < headers.length; i++) {
|
||||||
|
request.setHeader(headers[i].getName(), headers[i].getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
HttpResponse response;
|
||||||
|
if (url.startsWith("https"))
|
||||||
|
response = httpclientSSL.execute(request);
|
||||||
|
else
|
||||||
|
response = httpClient.execute(request);
|
||||||
|
log.debug((httpVerb == null ? "GET" : httpVerb) + " execute on URL responded: "
|
||||||
|
+ response.getStatusLine().getStatusCode());
|
||||||
|
if (response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300) {
|
||||||
|
if (response.getEntity() != null) {
|
||||||
|
try {
|
||||||
|
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); // read
|
||||||
|
// content
|
||||||
|
// for
|
||||||
|
// data
|
||||||
|
EntityUtils.consume(response.getEntity()); // close out
|
||||||
|
// inputstream
|
||||||
|
// ignore
|
||||||
|
// content
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug(
|
||||||
|
"Error ocurred in handling response entity after successful call, still responding success. "
|
||||||
|
+ e.getMessage(),
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (theContent == null)
|
||||||
|
theContent = "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
EntityUtils.consume(response.getEntity()); // close out
|
||||||
|
// inputstream
|
||||||
|
// ignore
|
||||||
|
// content
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("Error calling out to HA gateway: IOException in log", e);
|
||||||
|
}
|
||||||
|
return theContent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1811,8 +1811,8 @@ app.controller('MQTTController', function ($scope, $location, $http, bridgeServi
|
|||||||
var currentOff = $scope.device.offUrl;
|
var currentOff = $scope.device.offUrl;
|
||||||
if( $scope.device.mapType == "mqttMessage") {
|
if( $scope.device.mapType == "mqttMessage") {
|
||||||
$scope.device.mapId = $scope.device.mapId + "-" + mqtttopic;
|
$scope.device.mapId = $scope.device.mapId + "-" + mqtttopic;
|
||||||
$scope.device.onUrl = currentOn.substr(0, currentOn.indexOf("]")) + ",{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"}]";
|
$scope.device.onUrl = currentOn.substr(0, currentOn.indexOf("]")) + ",{\"item\":{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"},\"type\":\"mqttMessage\"}]";
|
||||||
$scope.device.offUrl = currentOff.substr(0, currentOff.indexOf("]")) + ",{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"}]";
|
$scope.device.offUrl = currentOff.substr(0, currentOff.indexOf("]")) + ",{\"item\":{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"},\"type\":\"mqttMessage\"}]";
|
||||||
}
|
}
|
||||||
else if ($scope.device.mapType == null || $scope.device.mapType == "") {
|
else if ($scope.device.mapType == null || $scope.device.mapType == "") {
|
||||||
bridgeService.clearDevice();
|
bridgeService.clearDevice();
|
||||||
@@ -1821,8 +1821,8 @@ app.controller('MQTTController', function ($scope, $location, $http, bridgeServi
|
|||||||
$scope.device.name = mqttbroker.clientId + mqtttopic;
|
$scope.device.name = mqttbroker.clientId + mqtttopic;
|
||||||
$scope.device.mapType = "mqttMessage";
|
$scope.device.mapType = "mqttMessage";
|
||||||
$scope.device.mapId = mqttbroker.clientId + "-" + mqtttopic;
|
$scope.device.mapId = mqttbroker.clientId + "-" + mqtttopic;
|
||||||
$scope.device.onUrl = "[{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"}]";
|
$scope.device.onUrl = "[{\"item\":{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"},\"type\":\"mqttMessage\"}]";
|
||||||
$scope.device.offUrl = "[{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"}]";
|
$scope.device.offUrl = "[{\"item\":{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"},\"type\":\"mqttMessage\"}]";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1875,14 +1875,16 @@ app.controller('HassController', function ($scope, $location, $http, bridgeServi
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.buildDeviceUrls = function (hassdevice, dim_control) {
|
$scope.buildDeviceUrls = function (hassdevice, dim_control) {
|
||||||
bridgeService.clearDevice();
|
|
||||||
$scope.device = $scope.bridge.device;
|
$scope.device = $scope.bridge.device;
|
||||||
|
var currentOn = $scope.device.onUrl;
|
||||||
|
var currentDim = $scope.device.dimUrl;
|
||||||
|
var currentOff = $scope.device.offUrl;
|
||||||
if( $scope.device.mapType == "hassDevice" ) {
|
if( $scope.device.mapType == "hassDevice" ) {
|
||||||
$scope.device.mapId = $scope.device.mapId + "-" + hassdevice.deviceState.entity_id;
|
$scope.device.mapId = $scope.device.mapId + "-" + hassdevice.deviceState.entity_id;
|
||||||
$scope.device.onUrl = currentOn.substr(0, currentOn.indexOf("]")) + ",{\"item\":\"{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"state\":\"on\"}\"}]";
|
$scope.device.onUrl = currentOn.substr(0, currentOn.indexOf("]")) + ",{\"item\":{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"},\"type\":\"hassDevice\"}]";
|
||||||
if((dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0))
|
if((dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0))
|
||||||
$scope.device.dimUrl = currentOn.substr(0, currentOn.indexOf("]")) + ",{\"item\":\"{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"state\":\"on\",\"bri\":\"" + dim_control + "\"}\"}]";
|
$scope.device.dimUrl = currentDim.substr(0, currentDim.indexOf("]")) + ",{\"item\":{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\",\"bri\":\"" + dim_control + "\"},\"type\":\"hassDevice\"}]";
|
||||||
$scope.device.offUrl = currentOff.substr(0, currentOff.indexOf("]")) + ",{\"item\":\"{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"state\":\"off\"}\"}]";
|
$scope.device.offUrl = currentOff.substr(0, currentOff.indexOf("]")) + ",{\"item\":{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"off\"},\"type\":\"hassDevice\"}]";
|
||||||
}
|
}
|
||||||
else if ($scope.device.mapType == null || $scope.device.mapType == "") {
|
else if ($scope.device.mapType == null || $scope.device.mapType == "") {
|
||||||
bridgeService.clearDevice();
|
bridgeService.clearDevice();
|
||||||
@@ -1891,10 +1893,10 @@ app.controller('HassController', function ($scope, $location, $http, bridgeServi
|
|||||||
$scope.device.name = hassdevice.deviceState.entity_id;
|
$scope.device.name = hassdevice.deviceState.entity_id;
|
||||||
$scope.device.mapType = "hassDevice";
|
$scope.device.mapType = "hassDevice";
|
||||||
$scope.device.mapId = hassdevice.hassname + "-" + hassdevice.deviceState.entity_id;
|
$scope.device.mapId = hassdevice.hassname + "-" + hassdevice.deviceState.entity_id;
|
||||||
$scope.device.onUrl = "[{\"item\":\"{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"state\":\"on\"}\"}]";
|
$scope.device.onUrl = "[{\"item\":{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"},\"type\":\"hassDevice\"}]";
|
||||||
if((dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0))
|
if((dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0))
|
||||||
$scope.device.dimUrl = "[{\"item\":\"{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"state\":\"on\",\"bri\":\"" + dim_control + "\"}\"}]";
|
$scope.device.dimUrl = "[{\"item\":{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\",\"bri\":\"" + dim_control + "\"},\"type\":\"hassDevice\"}]";
|
||||||
$scope.device.offUrl = "[{\"item\":\"{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"state\":\"off\"}\"}]";
|
$scope.device.offUrl = "[{\"item\":{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"off\"},\"type\":\"hassDevice\"}]";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2006,7 +2008,7 @@ app.controller('HassController', function ($scope, $location, $http, bridgeServi
|
|||||||
function () {
|
function () {
|
||||||
$scope.clearDevice();
|
$scope.clearDevice();
|
||||||
bridgeService.viewDevices();
|
bridgeService.viewDevices();
|
||||||
bridgeService.viewhassdevices();
|
bridgeService.viewHassDevices();
|
||||||
},
|
},
|
||||||
function (error) {
|
function (error) {
|
||||||
bridgeService.displayWarn("Error adding device: " + $scope.device.name, error)
|
bridgeService.displayWarn("Error adding device: " + $scope.device.name, error)
|
||||||
@@ -2019,11 +2021,9 @@ app.controller('HassController', function ($scope, $location, $http, bridgeServi
|
|||||||
var devicesList = [];
|
var devicesList = [];
|
||||||
for(var i = 0; i < $scope.bulk.devices.length; i++) {
|
for(var i = 0; i < $scope.bulk.devices.length; i++) {
|
||||||
for(var x = 0; x < bridgeService.state.hassdevices.length; x++) {
|
for(var x = 0; x < bridgeService.state.hassdevices.length; x++) {
|
||||||
if(bridgeService.state.hassdevices[x].hassdevicename == $scope.bulk.devices[i]) {
|
if(bridgeService.state.hassdevices[x].deviceName == $scope.bulk.devices[i]) {
|
||||||
if(bridgeService.state.hassdevices[x].hassdevicetype == "HVAC")
|
if(bridgeService.state.hassdevices[x].domain == "climate")
|
||||||
$scope.buildHALAutoUrls(bridgeService.state.hassdevices[x]);
|
$scope.buildHassAutoUrls(bridgeService.state.hassdevices[x]);
|
||||||
else if(bridgeService.state.hassdevices[x].hassdevicetype == "HOME")
|
|
||||||
$scope.buildHALHomeUrls(bridgeService.state.hassdevices[x]);
|
|
||||||
else
|
else
|
||||||
$scope.buildDeviceUrls(bridgeService.state.hassdevices[x],dim_control);
|
$scope.buildDeviceUrls(bridgeService.state.hassdevices[x],dim_control);
|
||||||
devicesList[i] = {
|
devicesList[i] = {
|
||||||
|
|||||||
@@ -301,7 +301,7 @@
|
|||||||
placeholder="8123"></td>
|
placeholder="8123"></td>
|
||||||
<td><input id="bridge-settings-next-hass-password"
|
<td><input id="bridge-settings-next-hass-password"
|
||||||
class="form-control" type="password" ng-model="newhasspassword"
|
class="form-control" type="password" ng-model="newhasspassword"
|
||||||
placeholder="MQTT Broker password (opt)"></td>
|
placeholder="Home Assistant password (opt)"></td>
|
||||||
<td><button class="btn btn-success" type="submit"
|
<td><button class="btn btn-success" type="submit"
|
||||||
ng-click="addHasstoSettings(newhassname, newhassip, newhassport, newhasspassword)">Add</button></td>
|
ng-click="addHasstoSettings(newhassname, newhassip, newhassport, newhasspassword)">Add</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user