mirror of
https://github.com/bwssytems/ha-bridge.git
synced 2025-12-18 08:13:23 +00:00
Amazon Echo support for groups
So the groups are now somewhat usable with an Amazon Echo. This is a bit of a workaround: The group gets presented to the Echo as another fake light. For that to work you must manually enable this feature for every room by adding "exposeAsLight":"192.168.0.30" to the room in group.db (restart ha-bridge afterwards). Use the ip-address of your echo. No need to do that for other devices, because these can handle rooms directly. The fake light for the group will only be shown/usable to the specified ip-addresses.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package com.bwssystems.HABridge.api.hue;
|
package com.bwssystems.HABridge.api.hue;
|
||||||
|
|
||||||
import com.bwssystems.HABridge.dao.DeviceDescriptor;
|
import com.bwssystems.HABridge.dao.DeviceDescriptor;
|
||||||
|
import com.bwssystems.HABridge.dao.GroupDescriptor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by arm on 4/14/15.
|
* Created by arm on 4/14/15.
|
||||||
@@ -129,4 +130,24 @@ public class DeviceResponse {
|
|||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static DeviceResponse createResponseForVirtualLight(GroupDescriptor group){
|
||||||
|
DeviceResponse response = new DeviceResponse();
|
||||||
|
response.setState(group.getAction());
|
||||||
|
|
||||||
|
response.setName(group.getName());
|
||||||
|
response.setUniqueid("00:17:88:5E:D3:FF-" + String.format("%02X", Integer.parseInt(group.getId())));
|
||||||
|
response.setManufacturername("Philips");
|
||||||
|
response.setType("Extended color light");
|
||||||
|
response.setModelid("LCT010");
|
||||||
|
response.setSwversion("1.15.2_r19181");
|
||||||
|
response.setSwconfigid("F921C859");
|
||||||
|
response.setProductid("Philips-LCT010-1-A19ECLv4");
|
||||||
|
|
||||||
|
response.setLuminaireuniqueid(null);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ public class GroupResponse {
|
|||||||
Boolean any_on = false;
|
Boolean any_on = false;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (Map.Entry<String, DeviceResponse> device : deviceList.entrySet()) {
|
for (Map.Entry<String, DeviceResponse> device : deviceList.entrySet()) {
|
||||||
|
if (Integer.parseInt(device.getKey()) >= 10000) { // don't show fake lights for other groups
|
||||||
|
continue;
|
||||||
|
}
|
||||||
theList[i] = device.getKey();
|
theList[i] = device.getKey();
|
||||||
Boolean is_on = device.getValue().getState().isOn();
|
Boolean is_on = device.getValue().getState().isOn();
|
||||||
if (is_on)
|
if (is_on)
|
||||||
|
|||||||
@@ -124,10 +124,14 @@ public class DeviceRepository extends BackupHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, DeviceResponse> findAllByGroupWithState(String[] lightsInGroup, String anAddress, HueHome myHueHome, Gson aGsonBuilder) {
|
public Map<String, DeviceResponse> findAllByGroupWithState(String[] lightsInGroup, String anAddress, HueHome myHueHome, Gson aGsonBuilder) {
|
||||||
|
return findAllByGroupWithState(lightsInGroup, anAddress, myHueHome, aGsonBuilder, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, DeviceResponse> findAllByGroupWithState(String[] lightsInGroup, String anAddress, HueHome myHueHome, Gson aGsonBuilder, boolean ignoreAddress) {
|
||||||
Map<String, DeviceResponse> deviceResponseMap = new HashMap<String, DeviceResponse>();
|
Map<String, DeviceResponse> deviceResponseMap = new HashMap<String, DeviceResponse>();
|
||||||
Map<String, DeviceDescriptor> lights = new HashMap<String, DeviceDescriptor>(devices);
|
Map<String, DeviceDescriptor> lights = new HashMap<String, DeviceDescriptor>(devices);
|
||||||
lights.keySet().retainAll(Arrays.asList(lightsInGroup));
|
lights.keySet().retainAll(Arrays.asList(lightsInGroup));
|
||||||
for (DeviceDescriptor light : findAllByRequester(anAddress, lights.values())) {
|
for (DeviceDescriptor light : (ignoreAddress ? lights.values() : findAllByRequester(anAddress, lights.values()))) {
|
||||||
DeviceResponse deviceResponse = null;
|
DeviceResponse deviceResponse = null;
|
||||||
if(!light.isInactive()) {
|
if(!light.isInactive()) {
|
||||||
if (light.containsType(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) {
|
if (light.containsType(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) {
|
||||||
|
|||||||
@@ -34,15 +34,16 @@ public class GroupDescriptor{
|
|||||||
@SerializedName("comments")
|
@SerializedName("comments")
|
||||||
@Expose
|
@Expose
|
||||||
private String comments;
|
private String comments;
|
||||||
@SerializedName("action")
|
|
||||||
@Expose
|
|
||||||
private DeviceState action;
|
private DeviceState action;
|
||||||
@SerializedName("groupState")
|
|
||||||
@Expose
|
|
||||||
private GroupState groupState;
|
private GroupState groupState;
|
||||||
|
|
||||||
@SerializedName("lights")
|
@SerializedName("lights")
|
||||||
@Expose
|
@Expose
|
||||||
private String[] lights;
|
private String[] lights;
|
||||||
|
@SerializedName("exposeAsLight")
|
||||||
|
@Expose
|
||||||
|
private String exposeAsLight;
|
||||||
|
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@@ -136,4 +137,12 @@ public class GroupDescriptor{
|
|||||||
public void setLights(String[] lights) {
|
public void setLights(String[] lights) {
|
||||||
this.lights = lights;
|
this.lights = lights;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setExposeAsLight(String exposeFor) {
|
||||||
|
this.exposeAsLight = exposeFor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExposeAsLight() {
|
||||||
|
return exposeAsLight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -115,6 +115,17 @@ public class GroupRepository extends BackupHandler {
|
|||||||
return theReturnList;
|
return theReturnList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<GroupDescriptor> findVirtualLights(String anAddress) {
|
||||||
|
List<GroupDescriptor> list = new ArrayList<GroupDescriptor>();
|
||||||
|
for (GroupDescriptor group : groups.values()) {
|
||||||
|
String expose = group.getExposeAsLight();
|
||||||
|
if (expose != null && expose.contains(anAddress)) {
|
||||||
|
list.add(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
public GroupDescriptor findOne(String id) {
|
public GroupDescriptor findOne(String id) {
|
||||||
return groups.get(id);
|
return groups.get(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ public class HueMulator {
|
|||||||
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
|
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
|
||||||
response.type("application/json");
|
response.type("application/json");
|
||||||
response.status(HttpStatus.SC_OK);
|
response.status(HttpStatus.SC_OK);
|
||||||
return changeGroupState(request.params(":userid"), request.params(":groupid"), request.body(), request.ip());
|
return changeGroupState(request.params(":userid"), request.params(":groupid"), request.body(), request.ip(), false);
|
||||||
});
|
});
|
||||||
// http://ip_address:port/api/{userId}/scenes returns json objects of
|
// http://ip_address:port/api/{userId}/scenes returns json objects of
|
||||||
// all scenes configured
|
// all scenes configured
|
||||||
@@ -440,7 +440,7 @@ public class HueMulator {
|
|||||||
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
|
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
|
||||||
response.type("application/json");
|
response.type("application/json");
|
||||||
response.status(HttpStatus.SC_OK);
|
response.status(HttpStatus.SC_OK);
|
||||||
return changeState(request.params(":userid"), request.params(":id"), request.body(), request.ip());
|
return changeState(request.params(":userid"), request.params(":id"), request.body(), request.ip(), false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -890,6 +890,13 @@ public class HueMulator {
|
|||||||
deviceResponseMap.put(device.getId(), deviceResponse);
|
deviceResponseMap.put(device.getId(), deviceResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle groups which shall be exposed as fake lights to selected devices like amazon echos
|
||||||
|
List<GroupDescriptor> groups = groupRepository.findVirtualLights(requestIp);
|
||||||
|
for (GroupDescriptor group : groups) {
|
||||||
|
deviceResponseMap.put(String.valueOf(Integer.parseInt(group.getId()) + 10000),
|
||||||
|
DeviceResponse.createResponseForVirtualLight(group));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theErrors != null)
|
if (theErrors != null)
|
||||||
@@ -991,6 +998,11 @@ public class HueMulator {
|
|||||||
if (theErrors != null)
|
if (theErrors != null)
|
||||||
return theErrors;
|
return theErrors;
|
||||||
|
|
||||||
|
if (Integer.parseInt(lightId) >= 10000) {
|
||||||
|
GroupDescriptor group = groupRepository.findOne(String.valueOf(Integer.parseInt(lightId) - 10000));
|
||||||
|
return DeviceResponse.createResponseForVirtualLight(group);
|
||||||
|
}
|
||||||
|
|
||||||
DeviceDescriptor device = repository.findOne(lightId);
|
DeviceDescriptor device = repository.findOne(lightId);
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
// response.status(HttpStatus.SC_NOT_FOUND);
|
// response.status(HttpStatus.SC_NOT_FOUND);
|
||||||
@@ -1069,7 +1081,10 @@ public class HueMulator {
|
|||||||
return responseString;
|
return responseString;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String changeState(String userId, String lightId, String body, String ipAddress) {
|
private String changeState(String userId, String lightId, String body, String ipAddress, boolean ignoreRequester) {
|
||||||
|
if (Integer.parseInt(lightId) >= 10000) {
|
||||||
|
return changeGroupState(userId, String.valueOf(Integer.parseInt(lightId) - 10000), body, ipAddress, true);
|
||||||
|
}
|
||||||
String responseString = null;
|
String responseString = null;
|
||||||
String url = null;
|
String url = null;
|
||||||
StateChangeBody theStateChanges = null;
|
StateChangeBody theStateChanges = null;
|
||||||
@@ -1161,10 +1176,13 @@ public class HueMulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; callItems != null && i < callItems.length; i++) {
|
for (int i = 0; callItems != null && i < callItems.length; i++) {
|
||||||
if(!filterByRequester(device.getRequesterAddress(), ipAddress) || !filterByRequester(callItems[i].getFilterIPs(), ipAddress)) {
|
if (!ignoreRequester) {
|
||||||
log.warn("filter for requester address not present in: (device)" + device.getRequesterAddress() + " OR then (item)" + callItems[i].getFilterIPs() + " with request ip of: " + ipAddress);
|
if(!filterByRequester(device.getRequesterAddress(), ipAddress) || !filterByRequester(callItems[i].getFilterIPs(), ipAddress)) {
|
||||||
continue;
|
log.warn("filter for requester address not present in: (device)" + device.getRequesterAddress() + " OR then (item)" + callItems[i].getFilterIPs() + " with request ip of: " + ipAddress);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callItems[i].getCount() != null && callItems[i].getCount() > 0)
|
if (callItems[i].getCount() != null && callItems[i].getCount() > 0)
|
||||||
aMultiUtil.setSetCount(callItems[i].getCount());
|
aMultiUtil.setSetCount(callItems[i].getCount());
|
||||||
else
|
else
|
||||||
@@ -1239,7 +1257,7 @@ public class HueMulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Object changeGroupState(String userId, String groupId, String body, String ipAddress) {
|
private String changeGroupState(String userId, String groupId, String body, String ipAddress, boolean fakeLightResponse) {
|
||||||
log.debug("PUT action to group " + groupId + " from " + ipAddress + " user " + userId + " with body " + body);
|
log.debug("PUT action to group " + groupId + " from " + ipAddress + " user " + userId + " with body " + body);
|
||||||
HueError[] theErrors = null;
|
HueError[] theErrors = null;
|
||||||
theErrors = bridgeSettingMaster.getBridgeSecurity().validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
|
theErrors = bridgeSettingMaster.getBridgeSecurity().validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
|
||||||
@@ -1247,16 +1265,24 @@ public class HueMulator {
|
|||||||
if(bridgeSettingMaster.getBridgeSecurity().isSettingsChanged())
|
if(bridgeSettingMaster.getBridgeSecurity().isSettingsChanged())
|
||||||
bridgeSettingMaster.updateConfigFile();
|
bridgeSettingMaster.updateConfigFile();
|
||||||
|
|
||||||
|
GroupDescriptor group = null;
|
||||||
|
Integer targetBriInc = null;
|
||||||
|
Integer targetBri = null;
|
||||||
|
DeviceState state = null;
|
||||||
Map<String, DeviceResponse> lights = null;
|
Map<String, DeviceResponse> lights = null;
|
||||||
if (groupId.equalsIgnoreCase("0")) {
|
if (groupId.equalsIgnoreCase("0")) {
|
||||||
lights = (Map<String, DeviceResponse>)lightsListHandler(userId, ipAddress);
|
lights = (Map<String, DeviceResponse>)lightsListHandler(userId, ipAddress);
|
||||||
} else {
|
} else {
|
||||||
GroupDescriptor group = groupRepository.findOne(groupId);
|
group = groupRepository.findOne(groupId);
|
||||||
if (group == null || group.isInactive()) {
|
if (group == null || group.isInactive()) {
|
||||||
return aGsonHandler.toJson(HueErrorResponse.createResponse("3", "/groups/" + groupId,
|
return aGsonHandler.toJson(HueErrorResponse.createResponse("3", "/groups/" + groupId,
|
||||||
"resource, /groups/" + groupId + ", not available", null, null, null).getTheErrors(), HueError[].class);
|
"resource, /groups/" + groupId + ", not available", null, null, null).getTheErrors(), HueError[].class);
|
||||||
} else {
|
} else {
|
||||||
lights = repository.findAllByGroupWithState(group.getLights(), ipAddress, myHueHome, aGsonHandler);
|
if (fakeLightResponse) {
|
||||||
|
lights = repository.findAllByGroupWithState(group.getLights(), ipAddress, myHueHome, aGsonHandler, true);
|
||||||
|
} else {
|
||||||
|
lights = repository.findAllByGroupWithState(group.getLights(), ipAddress, myHueHome, aGsonHandler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1272,6 +1298,23 @@ public class HueMulator {
|
|||||||
return aGsonHandler.toJson(HueErrorResponse.createResponse("2", "/groups/" + groupId + "/action",
|
return aGsonHandler.toJson(HueErrorResponse.createResponse("2", "/groups/" + groupId + "/action",
|
||||||
"Could not parse state change body.", null, null, null).getTheErrors(), HueError[].class);
|
"Could not parse state change body.", null, null, null).getTheErrors(), HueError[].class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (group != null) {
|
||||||
|
if (body.contains("\"bri_inc\"")) {
|
||||||
|
targetBriInc = new Integer(theStateChanges.getBri_inc());
|
||||||
|
}
|
||||||
|
else if (body.contains("\"bri\"")) {
|
||||||
|
targetBri = new Integer(theStateChanges.getBri());
|
||||||
|
}
|
||||||
|
|
||||||
|
state = group.getAction();
|
||||||
|
if (state == null) {
|
||||||
|
state = DeviceState.createDeviceState();
|
||||||
|
group.setAction(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
boolean turnOn = false;
|
boolean turnOn = false;
|
||||||
boolean turnOff = false;
|
boolean turnOff = false;
|
||||||
if (!(body.contains("\"bri_inc\"") || body.contains("\"bri\""))) {
|
if (!(body.contains("\"bri_inc\"") || body.contains("\"bri\""))) {
|
||||||
@@ -1284,23 +1327,38 @@ public class HueMulator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Map.Entry<String, DeviceResponse> light : lights.entrySet()) {
|
for (Map.Entry<String, DeviceResponse> light : lights.entrySet()) {
|
||||||
|
log.debug("Processing light" + light.getKey() + ": " + turnOn + " " + turnOff + " " + light.getValue().getState().isOn());
|
||||||
// ignore on/off for devices that are already on/off
|
// ignore on/off for devices that are already on/off
|
||||||
if (turnOff && !light.getValue().getState().isOn())
|
if (turnOff && !light.getValue().getState().isOn())
|
||||||
continue;
|
continue;
|
||||||
if (turnOn && light.getValue().getState().isOn())
|
if (turnOn && light.getValue().getState().isOn())
|
||||||
continue;
|
continue;
|
||||||
changeState(userId, light.getKey(), body, ipAddress);
|
changeState(userId, light.getKey(), body, ipAddress, fakeLightResponse);
|
||||||
}
|
}
|
||||||
// construct success response: one success message per changed property, but not per light
|
// construct success response: one success message per changed property, but not per light
|
||||||
|
if (group != null) { // if not group 0
|
||||||
|
String response = formatSuccessHueResponse(theStateChanges, body, String.valueOf(Integer.parseInt(groupId) + 10000),
|
||||||
|
state, targetBri, targetBriInc, true);
|
||||||
|
group.setAction(state);
|
||||||
|
if (fakeLightResponse) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String successString = "[";
|
String successString = "[";
|
||||||
for (String pairStr : body.replaceAll("[{|}]", "").split(",\\s*\"")) {
|
for (String pairStr : body.replaceAll("[{|}]", "").split(",\\s*\"")) {
|
||||||
String[] pair = pairStr.split(":");
|
String[] pair = pairStr.split(":");
|
||||||
successString += "{\"success\":{ \"address\": \"/groups/" + groupId + "/action/" + pair[0].replaceAll("\"", "").trim() + "\", \"value\": " + pair[1].trim() + "}},";
|
if (fakeLightResponse) {
|
||||||
|
successString += "{\"success\":{ \"/lights/" + String.valueOf(Integer.parseInt(groupId) + 10000) + "/state/" + pair[0].replaceAll("\"", "").trim() + "\": " + pair[1].trim() + "}},";
|
||||||
|
} else {
|
||||||
|
successString += "{\"success\":{ \"address\": \"/groups/" + groupId + "/action/" + pair[0].replaceAll("\"", "").trim() + "\", \"value\": " + pair[1].trim() + "}},";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return (successString.length() == 1) ? "[]" : successString.substring(0, successString.length()-1) + "]";
|
return (successString.length() == 1) ? "[]" : successString.substring(0, successString.length()-1) + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return theErrors;
|
return aGsonHandler.toJson(theErrors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user