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:
Florian Förderreuther
2017-07-30 16:28:01 +02:00
parent cb9312f6c3
commit 95c342b548
6 changed files with 123 additions and 17 deletions

View File

@@ -1,6 +1,7 @@
package com.bwssystems.HABridge.api.hue;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.dao.GroupDescriptor;
/**
* Created by arm on 4/14/15.
@@ -129,4 +130,24 @@ public class DeviceResponse {
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;
}
}

View File

@@ -70,6 +70,9 @@ public class GroupResponse {
Boolean any_on = false;
int i = 0;
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();
Boolean is_on = device.getValue().getState().isOn();
if (is_on)

View File

@@ -124,10 +124,14 @@ public class DeviceRepository extends BackupHandler {
}
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, DeviceDescriptor> lights = new HashMap<String, DeviceDescriptor>(devices);
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;
if(!light.isInactive()) {
if (light.containsType(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) {

View File

@@ -34,15 +34,16 @@ public class GroupDescriptor{
@SerializedName("comments")
@Expose
private String comments;
@SerializedName("action")
@Expose
private DeviceState action;
@SerializedName("groupState")
@Expose
private GroupState groupState;
@SerializedName("lights")
@Expose
private String[] lights;
@SerializedName("exposeAsLight")
@Expose
private String exposeAsLight;
public String getName() {
@@ -136,4 +137,12 @@ public class GroupDescriptor{
public void setLights(String[] lights) {
this.lights = lights;
}
public void setExposeAsLight(String exposeFor) {
this.exposeAsLight = exposeFor;
}
public String getExposeAsLight() {
return exposeAsLight;
}
}

View File

@@ -115,6 +115,17 @@ public class GroupRepository extends BackupHandler {
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) {
return groups.get(id);
}

View File

@@ -151,7 +151,7 @@ public class HueMulator {
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json");
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
// all scenes configured
@@ -440,7 +440,7 @@ public class HueMulator {
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json");
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);
}
}
// 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)
@@ -991,6 +998,11 @@ public class HueMulator {
if (theErrors != null)
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);
if (device == null) {
// response.status(HttpStatus.SC_NOT_FOUND);
@@ -1069,7 +1081,10 @@ public class HueMulator {
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 url = null;
StateChangeBody theStateChanges = null;
@@ -1161,10 +1176,13 @@ public class HueMulator {
}
for (int i = 0; callItems != null && i < callItems.length; i++) {
if(!filterByRequester(device.getRequesterAddress(), ipAddress) || !filterByRequester(callItems[i].getFilterIPs(), ipAddress)) {
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 (!ignoreRequester) {
if(!filterByRequester(device.getRequesterAddress(), ipAddress) || !filterByRequester(callItems[i].getFilterIPs(), ipAddress)) {
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)
aMultiUtil.setSetCount(callItems[i].getCount());
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);
HueError[] theErrors = null;
theErrors = bridgeSettingMaster.getBridgeSecurity().validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
@@ -1247,16 +1265,24 @@ public class HueMulator {
if(bridgeSettingMaster.getBridgeSecurity().isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
GroupDescriptor group = null;
Integer targetBriInc = null;
Integer targetBri = null;
DeviceState state = null;
Map<String, DeviceResponse> lights = null;
if (groupId.equalsIgnoreCase("0")) {
lights = (Map<String, DeviceResponse>)lightsListHandler(userId, ipAddress);
} else {
GroupDescriptor group = groupRepository.findOne(groupId);
group = groupRepository.findOne(groupId);
if (group == null || group.isInactive()) {
return aGsonHandler.toJson(HueErrorResponse.createResponse("3", "/groups/" + groupId,
"resource, /groups/" + groupId + ", not available", null, null, null).getTheErrors(), HueError[].class);
} 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",
"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 turnOff = false;
if (!(body.contains("\"bri_inc\"") || body.contains("\"bri\""))) {
@@ -1284,23 +1327,38 @@ public class HueMulator {
}
}
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
if (turnOff && !light.getValue().getState().isOn())
continue;
if (turnOn && light.getValue().getState().isOn())
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
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 = "[";
for (String pairStr : body.replaceAll("[{|}]", "").split(",\\s*\"")) {
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 theErrors;
return aGsonHandler.toJson(theErrors);
}
}