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
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!
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.
Emulates philips hue api to other home automation gateways. The Amazon echo now supports wemo and philip hue.
Build
-----
@@ -11,13 +9,17 @@ mvn install
```
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
```

View File

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

View File

@@ -2,6 +2,9 @@ package com.bwssytems.HABridge;
import static spark.Spark.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -25,8 +28,6 @@ public class AmazonEchoBridge {
*
* 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) {
@@ -35,25 +36,41 @@ public class AmazonEchoBridge {
HueMulator theHueMulator;
UpnpSettingsResource theSettingResponder;
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
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
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
staticFileLocation("/public");
log.debug("Starting setup....");
log.info("Starting setup....");
// setup the class to handle the resource setup rest api
theResources = new DeviceResource();
// setup the class to handle the hue emulator rest api
theHueMulator = new HueMulator(theResources.getDeviceRepository());
// 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
awaitInitialization();
// start the upnp ssdp discovery listener
theUpnpListener = new UpnpListener();
log.debug("Done setup, application to run....");
theUpnpListener = new UpnpListener(upnpAddressString, serverPort);
log.info("Done setup, application to run....");
theUpnpListener.startListening();
}
}

View File

@@ -1,24 +1,50 @@
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.HashMap;
import java.util.Map;
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.google.gson.stream.JsonReader;
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 {
Map<String, DeviceDescriptor> devices;
Path repositoryPath;
final Random random = new Random();
final Logger log = LoggerFactory.getLogger(DeviceRepository.class);
public DeviceRepository() {
super();
repositoryPath = Paths.get(System.getProperty("upnp.device.db", "data/device.db"));
String jsonContent = repositoryReader(repositoryPath);
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() {
@@ -32,23 +58,128 @@ public class DeviceRepository {
}
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) {
int id = random.nextInt(Integer.MAX_VALUE);
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) {
if (aDescriptor != null) {
devices.remove(aDescriptor.getId());
JsonTransformer aRenderer = new JsonTransformer();
String jsonValue = aRenderer.render(findAll());
repositoryWriter(jsonValue, repositoryPath);
return "Device with id '" + aDescriptor.getId() + "' deleted";
} else {
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() {
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());
DeviceDescriptor device = new Gson().fromJson(request.body(), DeviceDescriptor.class);
DeviceDescriptor deviceEntry = new DeviceDescriptor();
@@ -75,7 +75,7 @@ public class DeviceResource {
return deviceEntry;
}, new JsonTransformer());
get (API_CONTEXT + "/", "application/json", (request, response) -> {
get (API_CONTEXT, "application/json", (request, response) -> {
List<DeviceDescriptor> deviceList = deviceRepository.findAll();
log.debug("Get all devices");
JsonTransformer aRenderer = new JsonTransformer();

View File

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

View File

@@ -19,10 +19,10 @@ public class UpnpSettingsResource {
"<device>\n" + "<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>\n"
+ "<friendlyName>Amazon-Echo-HA-Bridge (%s)</friendlyName>\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"
+ "<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"
+ "<UDN>uuid:88f6698f-2c83-4393-bd03-cd54a9f8595</UDN>\n" + "<serviceList>\n" + "<service>\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"
+ "</root>\n";
public UpnpSettingsResource() {
public UpnpSettingsResource(String upnpAddress) {
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
get(UPNP_CONTEXT + "/:id/setup.xml", "application/xml", (request, response) -> {
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 filledTemplate = String.format(hueTemplate, hostName, portNumber, hostName);
log.debug("upnp device settings response: " + filledTemplate);
@@ -52,5 +51,13 @@ public class UpnpSettingsResource {
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">
<form class="form-horizontal">
<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"
placeholder="URL to bridge">
<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"
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>
<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>
</div>
</div>

View File

@@ -1,7 +1,7 @@
angular.module('amazonechobridge', [])
.service('bridgeService', ["$http", function ($http) {
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.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.state.error = "";
if (id) {
@@ -87,6 +105,7 @@ angular.module('amazonechobridge', [])
.controller('ViewingController', ["$scope", "bridgeService", function ($scope, bridgeService) {
bridgeService.viewDevices();
bridgeService.viewConfigAddress();
$scope.bridge = bridgeService.state;
$scope.deleteDevice = function (device) {
bridgeService.deleteDevice(device.id);