Compare commits

...

5 Commits

Author SHA1 Message Date
Admin
e52e0150fc Updated version for release 2015-08-05 16:28:12 -05:00
Admin
561d3cfed5 Updated persistence 2015-08-05 16:21:20 -05:00
Admin
cf92620617 Added persistence for configuration 2015-08-04 16:48:10 -05:00
Admin
5bdbca72c8 updated upnp url contents 2015-07-29 11:53:20 -05:00
Admin
c31d0bc709 updated command lines that were incorrect 2015-07-29 11:02:14 -05:00
9 changed files with 233 additions and 43 deletions

View File

@@ -1,7 +1,5 @@
# amazon-echo-ha-bridge-compact # amazon-echo-ha-bridge-compact
emulates philips hue api to other home automation gateways. The Amazon echo now supports wemo and philip hue... great news if you own any of those devices! Emulates philips hue api to other home automation gateways. The Amazon echo now supports wemo and philip hue.
My house is pretty heavily invested in the z-wave using the Vera as the gateway and thought it would be nice bridge the Amazon Echo to it.
Build Build
----- -----
@@ -11,13 +9,17 @@ mvn install
``` ```
Then locate the jar and start the server with: Then locate the jar and start the server with:
``` ```
java -jar -Dupnp.config.address=192.168.1.Z target/amazon-echo-bridge-compact0.X.Y.jar java -jar amazon-echo-bridge-compact-0.X.Y.jar
``` ```
replace the -Dupnp.config.address value with the server ipv4 address. The server defaults to the first available address on the host. Replace the -Dupnp.config.address=<ip address> value with the server ipv4 address you would like to use.
The server defaults to running on port 8080. If you're already running a server (like openHAB) on 8080, -Dserver.port=XXXX on the command line. The server defaults to running on port 8080. If you're already running a server (like openHAB) on 8080, -Dserver.port=<port> on the command line.
Then configure by going to the /configurator.html url The default location for the db to contain the devices as they are added is "data/devices.db". If you would like a different filename or directory, specify -Dupnp.devices.db=<directory>/<filename> or <filename> if it is the same directory.
The default upnp response port will be 50000 otherwise it can be set with -Dupnp.response.port=<port>.
Then configure by going to the url for the host you are running on or localhost:
``` ```
http://192.168.1.240:8080 http://192.168.1.240:8080
``` ```

View File

@@ -5,7 +5,7 @@
<groupId>com.bwssytems.HABridge</groupId> <groupId>com.bwssytems.HABridge</groupId>
<artifactId>amazon-echo-bridge-compact</artifactId> <artifactId>amazon-echo-bridge-compact</artifactId>
<version>0.1.0</version> <version>0.2.1</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Amazon Echo Bridge Compact</name> <name>Amazon Echo Bridge Compact</name>
@@ -43,6 +43,11 @@
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>2.6.0</version> <version>2.6.0</version>
</dependency> </dependency>
<dependency>
<groupId>com.cedarsoftware</groupId>
<artifactId>json-io</artifactId>
<version>4.1.6</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -2,6 +2,9 @@ package com.bwssytems.HABridge;
import static spark.Spark.*; import static spark.Spark.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -25,8 +28,6 @@ public class AmazonEchoBridge {
* *
* There is a custom upnp listener that is started to handle discovery. * There is a custom upnp listener that is started to handle discovery.
* *
* This application does not store the lights configuration persistently.
*
* *
*/ */
public static void main(String[] args) { public static void main(String[] args) {
@@ -35,25 +36,41 @@ public class AmazonEchoBridge {
HueMulator theHueMulator; HueMulator theHueMulator;
UpnpSettingsResource theSettingResponder; UpnpSettingsResource theSettingResponder;
UpnpListener theUpnpListener; UpnpListener theUpnpListener;
InetAddress address;
String addressString;
String upnpAddressString;
String serverPort;
//get ip address for upnp requests
try {
address = InetAddress.getLocalHost();
addressString = address.getHostAddress();
} catch (UnknownHostException e) {
log.error("Cannot get ip address of this host, Exiting with message: " + e.getMessage(), e);
return;
}
upnpAddressString = System.getProperty("upnp.config.address", addressString);
// sparkjava config directive to set ip address for the web server to listen on // sparkjava config directive to set ip address for the web server to listen on
ipAddress(System.getProperty("upnp.config.address", "0.0.0.0")); // ipAddress("0.0.0.0"); // not used
// sparkjava config directive to set port for the web server to listen on // sparkjava config directive to set port for the web server to listen on
port(Integer.valueOf(System.getProperty("server.port", "8080"))); serverPort = System.getProperty("server.port", "8080");
port(Integer.valueOf(serverPort));
// sparkjava config directive to set html static file location for Jetty // sparkjava config directive to set html static file location for Jetty
staticFileLocation("/public"); staticFileLocation("/public");
log.debug("Starting setup...."); log.info("Starting setup....");
// setup the class to handle the resource setup rest api // setup the class to handle the resource setup rest api
theResources = new DeviceResource(); theResources = new DeviceResource();
// setup the class to handle the hue emulator rest api // setup the class to handle the hue emulator rest api
theHueMulator = new HueMulator(theResources.getDeviceRepository()); theHueMulator = new HueMulator(theResources.getDeviceRepository());
// setup the class to handle the upnp response rest api // setup the class to handle the upnp response rest api
theSettingResponder = new UpnpSettingsResource(); theSettingResponder = new UpnpSettingsResource(upnpAddressString);
// 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();
// start the upnp ssdp discovery listener // start the upnp ssdp discovery listener
theUpnpListener = new UpnpListener(); theUpnpListener = new UpnpListener(upnpAddressString, serverPort);
log.debug("Done setup, application to run...."); log.info("Done setup, application to run....");
theUpnpListener.startListening(); theUpnpListener.startListening();
} }
} }

View File

@@ -1,24 +1,50 @@
package com.bwssytems.HABridge.dao; package com.bwssytems.HABridge.dao;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssytems.HABridge.JsonTransformer;
import com.bwssytems.HABridge.dao.DeviceDescriptor; import com.bwssytems.HABridge.dao.DeviceDescriptor;
import com.google.gson.stream.JsonReader;
import java.util.List; import java.util.List;
import java.util.ListIterator;
/* /*
* This is an in memory list to manage the configured devices. * This is an in memory list to manage the configured devices and saves the list as a JSON string to a file for later
* * loading.
*/ */
public class DeviceRepository { public class DeviceRepository {
Map<String, DeviceDescriptor> devices; Map<String, DeviceDescriptor> devices;
Path repositoryPath;
final Random random = new Random(); final Random random = new Random();
final Logger log = LoggerFactory.getLogger(DeviceRepository.class);
public DeviceRepository() { public DeviceRepository() {
super(); super();
repositoryPath = Paths.get(System.getProperty("upnp.device.db", "data/device.db"));
String jsonContent = repositoryReader(repositoryPath);
devices = new HashMap<String, DeviceDescriptor>(); devices = new HashMap<String, DeviceDescriptor>();
if(jsonContent != null)
{
List<DeviceDescriptor> list = readJsonStream(jsonContent);
ListIterator<DeviceDescriptor> theIterator = list.listIterator();
DeviceDescriptor theDevice = null;
while (theIterator.hasNext()) {
theDevice = theIterator.next();
put(Integer.parseInt(theDevice.getId()), theDevice);
}
}
} }
public List<DeviceDescriptor> findAll() { public List<DeviceDescriptor> findAll() {
@@ -32,23 +58,128 @@ public class DeviceRepository {
} }
public DeviceDescriptor findOne(String id) { public DeviceDescriptor findOne(String id) {
return devices.get(id); return devices.get(id);
}
private void put(int id, DeviceDescriptor aDescriptor) {
devices.put(String.valueOf(id),aDescriptor);
} }
public void save(DeviceDescriptor aDescriptor) { public void save(DeviceDescriptor aDescriptor) {
int id = random.nextInt(Integer.MAX_VALUE); int id = random.nextInt(Integer.MAX_VALUE);
aDescriptor.setId(String.valueOf(id)); aDescriptor.setId(String.valueOf(id));
devices.put(String.valueOf(id),aDescriptor); put(id, aDescriptor);
JsonTransformer aRenderer = new JsonTransformer();
String jsonValue = aRenderer.render(findAll());
repositoryWriter(jsonValue, repositoryPath);
} }
public String delete(DeviceDescriptor aDescriptor) { public String delete(DeviceDescriptor aDescriptor) {
if (aDescriptor != null) { if (aDescriptor != null) {
devices.remove(aDescriptor.getId()); devices.remove(aDescriptor.getId());
JsonTransformer aRenderer = new JsonTransformer();
String jsonValue = aRenderer.render(findAll());
repositoryWriter(jsonValue, repositoryPath);
return "Device with id '" + aDescriptor.getId() + "' deleted"; return "Device with id '" + aDescriptor.getId() + "' deleted";
} else { } else {
return "Device not found"; return "Device not found";
} }
} }
private void repositoryWriter(String content, Path filePath) {
if(Files.exists(filePath) && !Files.isWritable(filePath)){
log.error("Error file is not writable: " + filePath);
return;
}
if(Files.notExists(filePath.getParent())) {
try {
Files.createDirectories(filePath.getParent());
} catch (IOException e) {
log.error("Error creating the directory: " + filePath + " message: " + e.getMessage(), e);
}
}
try {
Files.write(filePath, content.getBytes(), StandardOpenOption.CREATE);
} catch (IOException e) {
log.error("Error writing the file: " + filePath + " message: " + e.getMessage(), e);
}
}
private String repositoryReader(Path filePath) {
String content = null;
if(Files.notExists(filePath) || !Files.isReadable(filePath)){
log.error("Error reading the file: " + filePath + " - Does not exist or is not readable. ");
return null;
}
try {
content = new String(Files.readAllBytes(filePath));
} catch (IOException e) {
log.error("Error reading the file: " + filePath + " message: " + e.getMessage(), e);
}
return content;
}
private List<DeviceDescriptor> readJsonStream(String context) {
JsonReader reader = new JsonReader(new StringReader(context));
List<DeviceDescriptor> theDescriptors = null;
try {
theDescriptors = readDescriptorArray(reader);
} catch (IOException e) {
log.error("Error reading json array: " + context + " message: " + e.getMessage(), e);
} finally {
try {
reader.close();
} catch (IOException e) {
log.error("Error closing json reader: " + context + " message: " + e.getMessage(), e);
}
}
return theDescriptors;
}
public List<DeviceDescriptor> readDescriptorArray(JsonReader reader) throws IOException {
List<DeviceDescriptor> descriptors = new ArrayList<DeviceDescriptor>();
reader.beginArray();
while (reader.hasNext()) {
descriptors.add(readDescriptor(reader));
}
reader.endArray();
return descriptors;
}
public DeviceDescriptor readDescriptor(JsonReader reader) throws IOException {
DeviceDescriptor deviceEntry = new DeviceDescriptor();
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
if (name.equals("id")) {
deviceEntry.setId(reader.nextString());
log.debug("Read a Device - device json id: " + deviceEntry.getId());
} else if (name.equals("name")) {
deviceEntry.setName(reader.nextString());
log.debug("Read a Device - device json name: " + deviceEntry.getName());
} else if (name.equals("deviceType")) {
deviceEntry.setDeviceType(reader.nextString());
log.debug("Read a Device - device json type:" + deviceEntry.getDeviceType());
} else if (name.equals("offUrl")) {
deviceEntry.setOffUrl(reader.nextString());
log.debug("Read a Device - device json off URL:" + deviceEntry.getOffUrl());
} else if (name.equals("onUrl")) {
deviceEntry.setOnUrl(reader.nextString());
log.debug("Read a Device - device json on URL:" + deviceEntry.getOnUrl());
} else {
reader.skipValue();
}
}
reader.endObject();
return deviceEntry;
}
} }

View File

@@ -38,7 +38,7 @@ public class DeviceResource {
private void setupEndpoints() { private void setupEndpoints() {
log.debug("Setting up endpoints"); log.debug("Setting up endpoints");
post(API_CONTEXT + "/", "application/json", (request, response) -> { post(API_CONTEXT, "application/json", (request, response) -> {
log.debug("Create a Device - request body: " + request.body()); log.debug("Create a Device - request body: " + request.body());
DeviceDescriptor device = new Gson().fromJson(request.body(), DeviceDescriptor.class); DeviceDescriptor device = new Gson().fromJson(request.body(), DeviceDescriptor.class);
DeviceDescriptor deviceEntry = new DeviceDescriptor(); DeviceDescriptor deviceEntry = new DeviceDescriptor();
@@ -75,7 +75,7 @@ public class DeviceResource {
return deviceEntry; return deviceEntry;
}, new JsonTransformer()); }, new JsonTransformer());
get (API_CONTEXT + "/", "application/json", (request, response) -> { get (API_CONTEXT, "application/json", (request, response) -> {
List<DeviceDescriptor> deviceList = deviceRepository.findAll(); List<DeviceDescriptor> deviceList = deviceRepository.findAll();
log.debug("Get all devices"); log.debug("Get all devices");
JsonTransformer aRenderer = new JsonTransformer(); JsonTransformer aRenderer = new JsonTransformer();

View File

@@ -21,11 +21,11 @@ public class UpnpListener {
private String responseAddress; private String responseAddress;
public UpnpListener() { public UpnpListener(String upnpAddress, String upnpServerPort) {
super(); super();
upnpResponsePort = Integer.valueOf(System.getProperty("upnp.response.port", "50000")); upnpResponsePort = Integer.valueOf(System.getProperty("upnp.response.port", "50000"));
httpServerPort = Integer.valueOf(System.getProperty("server.port", "8080")); httpServerPort = Integer.valueOf(upnpServerPort);
responseAddress = System.getProperty("upnp.config.address", "192.168.14.136"); responseAddress = upnpAddress;
} }
public void startListening(){ public void startListening(){

View File

@@ -19,10 +19,10 @@ public class UpnpSettingsResource {
"<device>\n" + "<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>\n" "<device>\n" + "<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>\n"
+ "<friendlyName>Amazon-Echo-HA-Bridge (%s)</friendlyName>\n" + "<friendlyName>Amazon-Echo-HA-Bridge (%s)</friendlyName>\n"
+ "<manufacturer>Royal Philips Electronics</manufacturer>\n" + "<manufacturer>Royal Philips Electronics</manufacturer>\n"
+ "<manufacturerURL>http://www.armzilla..com</manufacturerURL>\n" + "<manufacturerURL>http://www.bwssystems..com</manufacturerURL>\n"
+ "<modelDescription>Hue Emulator for Amazon Echo bridge</modelDescription>\n" + "<modelDescription>Hue Emulator for Amazon Echo bridge</modelDescription>\n"
+ "<modelName>Philips hue bridge 2012</modelName>\n" + "<modelNumber>929000226503</modelNumber>\n" + "<modelName>Philips hue bridge 2012</modelName>\n" + "<modelNumber>929000226503</modelNumber>\n"
+ "<modelURL>http://www.armzilla.com/amazon-echo-ha-bridge</modelURL>\n" + "<modelURL>http://www.bwssystems.com/amazon-echo-bridge-compact</modelURL>\n"
+ "<serialNumber>01189998819991197253</serialNumber>\n" + "<serialNumber>01189998819991197253</serialNumber>\n"
+ "<UDN>uuid:88f6698f-2c83-4393-bd03-cd54a9f8595</UDN>\n" + "<serviceList>\n" + "<service>\n" + "<UDN>uuid:88f6698f-2c83-4393-bd03-cd54a9f8595</UDN>\n" + "<serviceList>\n" + "<service>\n"
+ "<serviceType>(null)</serviceType>\n" + "<serviceId>(null)</serviceId>\n" + "<serviceType>(null)</serviceType>\n" + "<serviceId>(null)</serviceId>\n"
@@ -35,16 +35,15 @@ public class UpnpSettingsResource {
+ "<depth>24</depth>\n" + "<url>hue_logo_3.png</url>\n" + "</icon>\n" + "</iconList>\n" + "</device>\n" + "<depth>24</depth>\n" + "<url>hue_logo_3.png</url>\n" + "</icon>\n" + "</iconList>\n" + "</device>\n"
+ "</root>\n"; + "</root>\n";
public UpnpSettingsResource() { public UpnpSettingsResource(String upnpAddress) {
super(); super();
setupListener(); setupListener(upnpAddress);
} }
private void setupListener () { private void setupListener (String hostName) {
// http://ip_address:port/upnp/:id/setup.xml which returns the xml configuration for the location of the hue emulator // http://ip_address:port/upnp/:id/setup.xml which returns the xml configuration for the location of the hue emulator
get(UPNP_CONTEXT + "/:id/setup.xml", "application/xml", (request, response) -> { get(UPNP_CONTEXT + "/:id/setup.xml", "application/xml", (request, response) -> {
log.info("upnp device settings requested: " + request.params(":id") + " from " + request.ip()); log.info("upnp device settings requested: " + request.params(":id") + " from " + request.ip());
String hostName = System.getProperty("upnp.config.address", "192.168.1.1");
String portNumber = Integer.toString(request.port()); String portNumber = Integer.toString(request.port());
String filledTemplate = String.format(hueTemplate, hostName, portNumber, hostName); String filledTemplate = String.format(hueTemplate, hostName, portNumber, hostName);
log.debug("upnp device settings response: " + filledTemplate); log.debug("upnp device settings response: " + filledTemplate);
@@ -52,5 +51,13 @@ public class UpnpSettingsResource {
return filledTemplate; return filledTemplate;
} ); } );
get(UPNP_CONTEXT + "/configaddress", "application/xml", (request, response) -> {
log.info("upnp config address requested: from " + request.ip());
response.status(201);
return hostName;
} );
} }
} }

View File

@@ -40,17 +40,26 @@
<div class="panel-body"> <div class="panel-body">
<form class="form-horizontal"> <form class="form-horizontal">
<label class="col-xs-12 col-sm-3 control-label" for="bridge-base">Bridge server</label> <div class="form-group">
<label class="col-xs-12 col-sm-3 control-label" for="bridge-base">Bridge server</label>
<div class="col-xs-8 col-sm-7">
<input id="bridge-base" class="form-control" type="text" ng-model="bridge.base" <div class="col-xs-8 col-sm-7">
placeholder="URL to bridge"> <input id="bridge-base" class="form-control" type="text" ng-model="bridge.base"
placeholder="URL to bridge">
</div>
<button type="submit" class="col-xs-2 col-sm-1 btn btn-primary"
ng-click="setBridgeUrl(bridge.base)">Load
</button>
<button type="submit" class="col-xs-2 col-sm-1 btn btn-primary" ng-click="testUrl(bridge.base)">Go
</button>
</div>
<div class="form-group">
<label class="col-xs-12 col-sm-3 control-label" for="bridge-base">upnp.config.address</label>
<div class="col-xs-8 col-sm-7">
<input id="bridge-base" class="form-control" type="text" ng-model="bridge.upnpconfigaddress"
placeholder="upnp config address setting" readonly>
</div>
</div> </div>
<button type="submit" class="col-xs-2 col-sm-1 btn btn-primary"
ng-click="setBridgeUrl(bridge.base)">Load
</button>
<button type="submit" class="col-xs-2 col-sm-1 btn btn-primary" ng-click="testUrl(bridge.base)">Go
</button>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,7 @@
angular.module('amazonechobridge', []) angular.module('amazonechobridge', [])
.service('bridgeService', ["$http", function ($http) { .service('bridgeService', ["$http", function ($http) {
var self = this; var self = this;
this.state = {base: window.location.origin + "/api/devices/", devices: [], error: ""}; this.state = {base: window.location.origin + "/api/devices", upnpbase: window.location.origin + "/upnp/configaddress", devices: [], error: ""};
this.viewDevices = function () { this.viewDevices = function () {
this.state.error = ""; this.state.error = "";
@@ -21,6 +21,24 @@ angular.module('amazonechobridge', [])
); );
}; };
this.viewConfigAddress = function () {
this.state.error = "";
return $http.get(this.state.upnpbase).then(
function (response) {
self.state.upnpconfigaddress = response.data;
},
function (error) {
if (error.data) {
self.state.error = error.data.message;
} else {
self.state.error = "If you're not seeing any address, you may be running into problems with CORS. " +
"You can work around this by running a fresh launch of Chrome with the --disable-web-security flag.";
}
console.log(error);
}
);
};
this.addDevice = function (id, name, type, onUrl, offUrl) { this.addDevice = function (id, name, type, onUrl, offUrl) {
this.state.error = ""; this.state.error = "";
if (id) { if (id) {
@@ -87,6 +105,7 @@ angular.module('amazonechobridge', [])
.controller('ViewingController', ["$scope", "bridgeService", function ($scope, bridgeService) { .controller('ViewingController', ["$scope", "bridgeService", function ($scope, bridgeService) {
bridgeService.viewDevices(); bridgeService.viewDevices();
bridgeService.viewConfigAddress();
$scope.bridge = bridgeService.state; $scope.bridge = bridgeService.state;
$scope.deleteDevice = function (device) { $scope.deleteDevice = function (device) {
bridgeService.deleteDevice(device.id); bridgeService.deleteDevice(device.id);