This closes #8, closes #9, closes #10 and closes #11.

Finished device, scene and activity tracking, updated upnp handling,
updated HUE API config handling and test on and off calls.
This commit is contained in:
Admin
2015-11-18 16:31:11 -06:00
parent d8b6232ac1
commit 314ae58ebd
11 changed files with 210 additions and 102 deletions

View File

@@ -5,7 +5,7 @@
<groupId>com.bwssystems.HABridge</groupId> <groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId> <artifactId>ha-bridge</artifactId>
<version>1.0.9b</version> <version>1.1.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>HA Bridge</name> <name>HA Bridge</name>

View File

@@ -178,6 +178,12 @@ public class DeviceRepository {
} else if (name.equals("name")) { } else if (name.equals("name")) {
deviceEntry.setName(reader.nextString()); deviceEntry.setName(reader.nextString());
log.debug("Read a Device - device json name: " + deviceEntry.getName()); log.debug("Read a Device - device json name: " + deviceEntry.getName());
} else if (name.equals("mapType")) {
deviceEntry.setMapType(reader.nextString());
log.debug("Read a Device - device json name: " + deviceEntry.getMapType());
} else if (name.equals("mapId")) {
deviceEntry.setMapId(reader.nextString());
log.debug("Read a Device - device json name: " + deviceEntry.getMapId());
} else if (name.equals("deviceType")) { } else if (name.equals("deviceType")) {
deviceEntry.setDeviceType(reader.nextString()); deviceEntry.setDeviceType(reader.nextString());
log.debug("Read a Device - device json type:" + deviceEntry.getDeviceType()); log.debug("Read a Device - device json type:" + deviceEntry.getDeviceType());

View File

@@ -143,8 +143,9 @@ public class DeviceResource {
}, new JsonTransformer()); }, new JsonTransformer());
delete (API_CONTEXT + "/:id", "application/json", (request, response) -> { delete (API_CONTEXT + "/:id", "application/json", (request, response) -> {
log.debug("Delete a device"); String anId = request.params(":id");
DeviceDescriptor deleted = deviceRepository.findOne(request.params(":id")); log.debug("Delete a device: " + anId);
DeviceDescriptor deleted = deviceRepository.findOne(anId);
if(deleted == null) if(deleted == null)
response.status(HttpStatus.SC_NOT_FOUND); response.status(HttpStatus.SC_NOT_FOUND);
else else

View File

@@ -76,14 +76,7 @@ public class UpnpListener {
byte[] buf = new byte[1024]; byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length); DatagramPacket packet = new DatagramPacket(buf, buf.length);
upnpMulticastSocket.receive(packet); upnpMulticastSocket.receive(packet);
String packetString = new String(packet.getData()); if(isSSDPDiscovery(packet)){
if(packetString != null && packetString.contains("M-SEARCH")) {
if(traceupnp)
log.info("Traceupnp: SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
else
log.debug("Got SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
}
if(isSSDPDiscovery(packetString)){
sendUpnpResponse(responseSocket, packet.getAddress(), packet.getPort()); sendUpnpResponse(responseSocket, packet.getAddress(), packet.getPort());
} }
} }
@@ -97,17 +90,22 @@ public class UpnpListener {
} }
/** /**
* very naive ssdp discovery packet detection * ssdp discovery packet detection
* @param body
* @return
*/ */
protected boolean isSSDPDiscovery(String body){ protected boolean isSSDPDiscovery(DatagramPacket packet){
// log.debug("Check if this is a MAN ssdp-discover packet for a upnp basic device: " + body); //Only respond to discover request for strict upnp form
//Only respond to discover request for upnp basic device from echo, the others are for the wemo String packetString = new String(packet.getData());
if(body != null && body.contains("M-SEARCH") && body.contains("\"ssdp:discover\"")){ if(packetString != null && packetString.startsWith("M-SEARCH * HTTP/1.1") && packetString.contains("\"ssdp:discover\"")){
if(traceupnp) if(traceupnp) {
log.info("Traceupnp: SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
log.info("Traceupnp: isSSDPDiscovery found message to be an M-SEARCH message."); log.info("Traceupnp: isSSDPDiscovery found message to be an M-SEARCH message.");
if(strict && body.startsWith("M-SEARCH * HTTP/1.1") && body.contains("MAN: \"ssdp:discover\"") && (body.contains("ST: urn:schemas-upnp-org:device:basic:1") || body.contains("ST: upnp:rootdevice") || body.contains("ST: ssdp:all"))) }
else {
log.debug("Got SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
log.debug("Found message to be an M-SEARCH message.");
}
if(strict && (packetString.contains("ST: urn:schemas-upnp-org:device:basic:1") || packetString.contains("ST: upnp:rootdevice") || packetString.contains("ST: ssdp:all")))
{ {
if(traceupnp) if(traceupnp)
log.info("Traceupnp: isSSDPDiscovery found message to be valid under strict rules - strict: " + strict); log.info("Traceupnp: isSSDPDiscovery found message to be valid under strict rules - strict: " + strict);
@@ -134,7 +132,7 @@ public class UpnpListener {
"USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1\r\n\r\n"; "USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1\r\n\r\n";
protected void sendUpnpResponse(DatagramSocket socket, InetAddress requester, int sourcePort) throws IOException { protected void sendUpnpResponse(DatagramSocket socket, InetAddress requester, int sourcePort) throws IOException {
String discoveryResponse = null; String discoveryResponse = null;
discoveryResponse = String.format(discoveryTemplate, responseAddress, httpServerPort, getRandomUUIDString()); discoveryResponse = String.format(discoveryTemplate, responseAddress, httpServerPort);
if(traceupnp) if(traceupnp)
log.info("Traceupnp: sendUpnpResponse: " + discoveryResponse); log.info("Traceupnp: sendUpnpResponse: " + discoveryResponse);
else else
@@ -142,8 +140,4 @@ public class UpnpListener {
DatagramPacket response = new DatagramPacket(discoveryResponse.getBytes(), discoveryResponse.length(), requester, sourcePort); DatagramPacket response = new DatagramPacket(discoveryResponse.getBytes(), discoveryResponse.length(), requester, sourcePort);
socket.send(response); socket.send(response);
} }
protected String getRandomUUIDString(){
return "88f6698f-2c83-4393-bd03-cd54a9f8595"; // https://xkcd.com/221/
}
} }

View File

@@ -54,7 +54,7 @@ public class HarmonyServer {
log.debug("something is very wrong as dummyProvider is not null..."); log.debug("something is very wrong as dummyProvider is not null...");
if(mySettings.isDevMode()) if(mySettings.isDevMode())
modeString = " (development mode)"; modeString = " (development mode)";
if(noopCalls) else if(noopCalls)
modeString = " (no op calls to harmony)"; modeString = " (no op calls to harmony)";
log.info("setup initiated " + modeString + "...."); log.info("setup initiated " + modeString + "....");
if(mySettings.isDevMode()) if(mySettings.isDevMode())

View File

@@ -191,7 +191,7 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
} }
); );
}; };
this.viewVeraScenes = function () { this.viewVeraScenes = function () {
this.state.error = ""; this.state.error = "";
if(!this.state.showVera) if(!this.state.showVera)
@@ -209,7 +209,7 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
} }
); );
}; };
this.viewHarmonyActivities = function () { this.viewHarmonyActivities = function () {
this.state.error = ""; this.state.error = "";
if(!this.state.showHarmony) if(!this.state.showHarmony)
@@ -246,11 +246,12 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
); );
}; };
this.findHarmonyActivityEntry = function(activityId) { this.findDeviceByMapId = function(id) {
for(var i = 0; i < this.state.devices.length; i++) { for(var i = 0; i < this.state.devices.length; i++) {
if(this.state.devices[i].mapId == activityId) if(this.state.devices[i].mapId == id)
return true; return true;
} }
return false;
}; };
this.addDevice = function (device) { this.addDevice = function (device) {
@@ -327,6 +328,13 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
); );
}; };
this.deleteDeviceByMapId = function (id) {
for(var i = 0; i < this.state.devices.length; i++) {
if(this.state.devices[i].mapId == id)
return self.deleteDevice(this.state.devices[i].id);
}
};
this.editDevice = function (device) { this.editDevice = function (device) {
self.state.device = device; self.state.device = device;
}; };
@@ -410,7 +418,13 @@ app.controller('AddingController', function ($scope, $location, $http, bridgeSer
bridgeService.updateShowHarmony(); bridgeService.updateShowHarmony();
$scope.device = bridgeService.state.device; $scope.device = bridgeService.state.device;
$scope.activitiesVisible = false; $scope.activitiesVisible = false;
$scope.imgButtonsUrl = "glyphicon glyphicon-plus";
$scope.buttonsVisible = false;
$scope.imgActivitiesUrl = "glyphicon glyphicon-plus"; $scope.imgActivitiesUrl = "glyphicon glyphicon-plus";
$scope.devicesVisible = false;
$scope.imgDevicesUrl = "glyphicon glyphicon-plus";
$scope.scenesVisible = false;
$scope.imgScenesUrl = "glyphicon glyphicon-plus";
$scope.predicate = ''; $scope.predicate = '';
$scope.reverse = true; $scope.reverse = true;
$scope.device_dim_control = ""; $scope.device_dim_control = "";
@@ -537,6 +551,7 @@ app.controller('AddingController', function ($scope, $location, $http, bridgeSer
} }
); );
} }
$scope.toggleActivities = function () { $scope.toggleActivities = function () {
$scope.activitiesVisible = !$scope.activitiesVisible; $scope.activitiesVisible = !$scope.activitiesVisible;
if($scope.activitiesVisible) if($scope.activitiesVisible)
@@ -545,15 +560,43 @@ app.controller('AddingController', function ($scope, $location, $http, bridgeSer
$scope.imgActivitiesUrl = "glyphicon glyphicon-plus"; $scope.imgActivitiesUrl = "glyphicon glyphicon-plus";
}; };
$scope.toggleButtons = function () {
$scope.buttonsVisible = !$scope.buttonsVisible;
if($scope.buttonsVisible)
$scope.imgButtonsUrl = "glyphicon glyphicon-minus";
else
$scope.imgButtonsUrl = "glyphicon glyphicon-plus";
};
$scope.toggleDevices = function () {
$scope.devicesVisible = !$scope.devicesVisible;
if($scope.devicesVisible)
$scope.imgDevicesUrl = "glyphicon glyphicon-minus";
else
$scope.imgDevicesUrl = "glyphicon glyphicon-plus";
};
$scope.toggleScenes = function () {
$scope.scenesVisible = !$scope.scenesVisible;
if($scope.scenesVisible)
$scope.imgScenesUrl = "glyphicon glyphicon-minus";
else
$scope.imgScenesUrl = "glyphicon glyphicon-plus";
};
$scope.deleteDeviceByMapId = function (id) {
bridgeService.deleteDeviceByMapId(id);
};
}); });
app.filter('availableActivity', function(bridgeService) { app.filter('availableId', function(bridgeService) {
return function(input) { return function(input) {
var out = []; var out = [];
if(input == null) if(input == null)
return out; return out;
for (var i = 0; i < input.length; i++) { for (var i = 0; i < input.length; i++) {
if(!bridgeService.findHarmonyActivityEntry(input[i].id)){ if(!bridgeService.findDeviceByMapId(input[i].id)){
out.push(input[i]); out.push(input[i]);
} }
} }
@@ -561,13 +604,13 @@ app.filter('availableActivity', function(bridgeService) {
} }
}); });
app.filter('unavailableActivity', function(bridgeService) { app.filter('unavailableId', function(bridgeService) {
return function(input) { return function(input) {
var out = []; var out = [];
if(input == null) if(input == null)
return out; return out;
for (var i = 0; i < input.length; i++) { for (var i = 0; i < input.length; i++) {
if(bridgeService.findHarmonyActivityEntry(input[i].id)){ if(bridgeService.findDeviceByMapId(input[i].id)){
out.push(input[i]); out.push(input[i]);
} }
} }
@@ -575,6 +618,21 @@ app.filter('unavailableActivity', function(bridgeService) {
} }
}); });
app.filter('configuredButtons', function() {
return function(input) {
var out = [];
if(input == null)
return out;
for (var i = 0; i < input.length; i++) {
if(input[i].mapType == "harmonyButton"){
out.push(input[i]);
}
}
return out;
}
});
app.controller('ErrorsController', function ($scope, bridgeService) { app.controller('ErrorsController', function ($scope, bridgeService) {
$scope.bridge = bridgeService.state; $scope.bridge = bridgeService.state;
}); });

View File

@@ -30,7 +30,7 @@
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="harmonyactivity in bridge.harmonyactivities | availableActivity | orderBy:predicate:reverse"> <tr ng-repeat="harmonyactivity in bridge.harmonyactivities | availableId | orderBy:predicate:reverse">
<td>{{harmonyactivity.label}}</td> <td>{{harmonyactivity.label}}</td>
<td>{{harmonyactivity.id}}</td> <td>{{harmonyactivity.id}}</td>
<td> <td>
@@ -58,11 +58,14 @@
<a href="" ng-click="order('id')">Id</a> <a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th> </th>
<th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="harmonyactivity in bridge.harmonyactivities | unavailableActivity | orderBy:predicate:reverse"> <tr ng-repeat="harmonyactivity in bridge.harmonyactivities | unavailableId | orderBy:predicate:reverse">
<td>{{harmonyactivity.label}}</td> <td>{{harmonyactivity.label}}</td>
<td>{{harmonyactivity.id}}</td> <td>{{harmonyactivity.id}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(harmonyactivity.id)">Delete</button></td>
</tr> </tr>
</table> </table>
</li> </li>

View File

@@ -58,6 +58,41 @@
</table> </table>
</li> </li>
</ul> </ul>
<div class="panel-heading">
<h2 class="panel-title">Already Configured Harmony Buttons <a ng-click="toggleButtons()"><span class={{imgButtonsUrl}} aria-hidden="true"></span></a></h2>
</div>
<ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Device Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('mapId')">Harmony Device-Button On-Button Off</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="device in bridge.devices | configuredButtons | orderBy:predicate:reverse">
<td>{{device.name}}</td>
<td>{{device.id}}</td>
<td>{{device.mapId}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(device.mapId)">Delete</button>
</td>
</tr>
</table>
</li>
</ul>
</div> </div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error"> <div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel-heading"> <div class="panel-heading">

View File

@@ -1,64 +0,0 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation" class="active"><a href="#">Configuration</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 role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
<div ng-controller="ErrorsController">
<div ng-if="bridge.error"
class="alert alert-warning alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h2 ng-show='bridge.error != ""'>ERROR</h2>
<div ng-show='bridge.error != ""'>{{bridge.error}}</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Current devices</h2>
</div>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>
<a href="" ng-click="order('id')">ID</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span></th>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span></th>
<th>
<a href="" ng-click="order('deviceType')">Type</a>
<span class="sortorder" ng-show="predicate === 'deviceType'" ng-class="{reverse:reverse}"></span></th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="device in bridge.devices | orderBy:predicate:reverse">
<td>{{device.id}}</td>
<td>{{device.name}}</td>
<td>{{device.deviceType}}</td>
<td>
<button class="btn btn-info" type="submit"
ng-click="testUrl(device, 'on')">Test ON</button>
<button class="btn btn-info" type="submit"
ng-click="testUrl(device, 'off')">Test OFF</button>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</td>
</tr>
</table>
</div>
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<a href="#/show"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
<h1 class="panel-title">Bridge settings</h1>
</div>
</div>

View File

@@ -45,7 +45,7 @@
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="veradevice in bridge.veradevices | orderBy:predicate:reverse"> <tr ng-repeat="veradevice in bridge.veradevices | availableId | orderBy:predicate:reverse">
<td>{{veradevice.name}}</td> <td>{{veradevice.name}}</td>
<td>{{veradevice.id}}</td> <td>{{veradevice.id}}</td>
<td>{{veradevice.category}}</td> <td>{{veradevice.category}}</td>
@@ -59,6 +59,46 @@
</table> </table>
</li> </li>
</ul> </ul>
<div class="panel-heading">
<h2 class="panel-title">Already Configured Vera Devices <a ng-click="toggleDevices()"><span class={{imgDevicesUrl}} aria-hidden="true"></a></h2>
</div>
<ul ng-if="devicesVisible" class="list-group">
<li class="list-group-item">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('category')">Category</a>
<span class="sortorder" ng-show="predicate === 'category'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('room')">Room</a>
<span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="veradevice in bridge.veradevices | unavailableId | orderBy:predicate:reverse">
<td>{{veradevice.name}}</td>
<td>{{veradevice.id}}</td>
<td>{{veradevice.category}}</td>
<td>{{veradevice.room}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(harmonyactivity.id)">Delete</button>
</td>
</tr>
</table>
</li>
</ul>
</div> </div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error"> <div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel-heading"> <div class="panel-heading">

View File

@@ -34,7 +34,7 @@
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="verascene in bridge.verascenes | orderBy:predicate:reverse"> <tr ng-repeat="verascene in bridge.verascenes | availableId | orderBy:predicate:reverse">
<td>{{verascene.name}}</td> <td>{{verascene.name}}</td>
<td>{{verascene.id}}</td> <td>{{verascene.id}}</td>
<td>{{verascene.room}}</td> <td>{{verascene.room}}</td>
@@ -47,6 +47,41 @@
</table> </table>
</li> </li>
</ul> </ul>
<div class="panel-heading">
<h2 class="panel-title">Already Configured Vera Scenes <a ng-click="toggleScenes()"><span class={{imgScenesUrl}} aria-hidden="true"></a></h2>
</div>
<ul ng-if="scenesVisible" class="list-group">
<li class="list-group-item">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('room')">Room</a>
<span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="verascene in bridge.verascenes | unavailableId | orderBy:predicate:reverse">
<td>{{verascene.name}}</td>
<td>{{verascene.id}}</td>
<td>{{verascene.room}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(harmonyactivity.id)">Delete</button>
</td>
</tr>
</table>
</li>
</ul>
</div> </div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error"> <div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel-heading"> <div class="panel-heading">