Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Master #103

Open
wants to merge 17 commits into
base: emulate_multiple_gateways
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ RUN mvn install

EXPOSE 8080

CMD ["bash", "-c", "java -jar target/amazon-echo-bridge-*.jar --upnp.config.address=$(ip route get 8.8.8.8 | egrep -o '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\s*$')"]
CMD ["bash", "-c", "java -Xmx256M -jar target/amazon-echo-bridge-*.jar --upnp.config.address=$(ip route get 8.8.8.8 | egrep -o '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\s*$')"]
20 changes: 18 additions & 2 deletions README.md
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ Amazon Echo Bridge allows you to quickly emulate a Phillips Hue bridge, bringing

Also, with an easy to use POST/PUT REST API, it's never been easier before to get your devices up and running with the Amazon Echo with your own embedded applications!

## Release notes:
v0.4.0
change log:

* require --upnp.config.address= to be specified during startup
* support more than 25 emulated devices currently set to 75, can be increased at 25 device increments by specifying --emulator.portcount= default is 3 thus 3*25 = 75 total devices. Works by taking emulator.baseport and opening n number of ports sequentially from baseport to baseport+portcount
* relaxed http response codes to anything in the 200 to less than 300 http response codes to support misbehaving resources

other notes:
Ive seen some folks able to run this but not able to discover devices. I would recommend checking for devices with duplicate names as i have seen this to cause the echo to reject all devices. The lazy way would be to delete the /data directory and start over.

## Quick Start

There are currently three different ways to run the pre-built jar file:
Expand All @@ -24,8 +35,13 @@ After the application is started and running, you can access the configurator by

Input your devices using the form at the bottom of the page, add command URLs to parse (useful if you use a system like OpenHAB), and save.

Instruct your Amazon Echo to take control of your devices by saying "Alexa, discover my devices" and your all set!
Instruct your Amazon Echo to learn about your devices by saying "Alexa, discover my devices" and your all set!

## Using

You can now control devices with your Amazon Echo by saying "Alexa, Turn on the office light" or other names you have given your configured devices.

To view or remove devices that Alexa knows about, you can use the mobile app Menu / Settings / Connected Home, This is needed if you remove a device from the Amazon Echo Bridge.

## Build

Expand All @@ -49,7 +65,7 @@ To build the jar file yourself, make your changes and simply run Maven like this
mvn install
```

Then locate the jar and start the server using the instructions above.
Then locate the jar and start the server using the instructions above. By default maven will put the jar file in the target directory. ```java -jar target/amazon-echo-bridge-*.jar```

## POST/PUT REST API

Expand Down
119 changes: 61 additions & 58 deletions pom.xml
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,67 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.armzilla.ha</groupId>
<artifactId>amazon-echo-bridge</artifactId>
<version>0.2.1</version>
<packaging>jar</packaging>
<groupId>com.armzilla.ha</groupId>
<artifactId>amazon-echo-bridge</artifactId>
<version>0.4.0</version>
<packaging>jar</packaging>

<name>Amazon Echo Bridge</name>
<description>Emulates a Philips Hue bridge to allow the Amazon Echo to integrate seamlessly into various home automation systems.</description>
<name>Amazon Echo Bridge</name>
<description>Emulates a Philips Hue bridge to allow the Amazon Echo to integrate seamlessly into various home
automation systems.
</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.6.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>com.armzilla.ha.SpringbootEntry</start-class>
<java.version>1.8</java.version>
</properties>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>com.armzilla.ha.SpringbootEntry</start-class>
<java.version>1.8</java.version>
<httpclient.version>4.5.1</httpclient.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.6</version>
</dependency>
<!--<dependency>--> <!-- was going to use this templating urls but that got bloated -->
<!--<groupId>org.freemarker</groupId>-->
<!--<artifactId>freemarker</artifactId>-->
<!--<version>2.3.22</version>-->
<!--</dependency>-->
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
22 changes: 22 additions & 0 deletions src/main/java/com/armzilla/ha/ConfigChecker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.armzilla.ha;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
* Created by arm on 9/16/16.
*/
@Component
public class ConfigChecker implements InitializingBean {

@Value("${upnp.config.address}")
private String responseAddress;

@Override
public void afterPropertiesSet() throws Exception {
if(responseAddress == null || responseAddress.isEmpty() ){
throw new IllegalArgumentException("please provide the IP(v4) address of the interface you want the bridge to listen on using --upnp.config.address=<ipadress>");
}
}
}
3 changes: 1 addition & 2 deletions src/main/java/com/armzilla/ha/SpringbootEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
@ComponentScan
public class SpringbootEntry {

public static void main(String[] args) {
SpringApplication.run(SpringbootEntry.class, args);
}

}
41 changes: 41 additions & 0 deletions src/main/java/com/armzilla/ha/TomcatConnectorBean.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.armzilla.ha;

import org.apache.catalina.connector.Connector;
import org.apache.coyote.http11.Http11NioProtocol;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
* Created by arm on 9/12/15.
*/
@Component

public class TomcatConnectorBean {
@Value("${emulator.portbase}")
private int portBase;
@Value("${emulator.portcount}")
private int portCount;
@Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = null;
for(int i = 0; i < portCount; i ++) {
if(tomcat == null){
tomcat = new TomcatEmbeddedServletContainerFactory(portBase + i);
}else{
tomcat.addAdditionalTomcatConnectors(createConnector(portBase + i));
}
}
return tomcat;
}

private Connector createConnector(int portNumber) {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
connector.setScheme("http");
connector.setPort(portNumber);
return connector;
}
}
2 changes: 0 additions & 2 deletions src/main/java/com/armzilla/ha/api/hue/HueApiResponse.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.armzilla.ha.api.hue;

import com.armzilla.ha.api.hue.DeviceResponse;

import java.util.Map;

/**
Expand Down
9 changes: 6 additions & 3 deletions src/main/java/com/armzilla/ha/dao/DeviceDescriptor.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.armzilla.ha.dao;

import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
* Created by arm on 4/13/15.
*/
@Document(indexName = "device", type = "devicedescriptor", shards = 1, replicas = 0, refreshInterval = "-1")
@Entity
@Table(name = "devices")

public class DeviceDescriptor{
@Id
private String id;
Expand Down
5 changes: 2 additions & 3 deletions src/main/java/com/armzilla/ha/dao/DeviceRepository.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package com.armzilla.ha.dao;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.data.repository.CrudRepository;

import java.util.List;

/**
* Created by arm on 4/13/15.
*/
public interface DeviceRepository extends ElasticsearchRepository<DeviceDescriptor, String> {
public interface DeviceRepository extends CrudRepository<DeviceDescriptor, String> {
Page<DeviceDescriptor> findByDeviceType(String type, Pageable request);
List<DeviceDescriptor> findAll();
DeviceDescriptor findOne(String id);
Expand Down
20 changes: 15 additions & 5 deletions src/main/java/com/armzilla/ha/hue/HueMulator.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpHeaders;
Expand Down Expand Up @@ -50,6 +51,10 @@ public class HueMulator {
private HttpClient httpClient;
private ObjectMapper mapper;

@Value("${emulator.portbase}")
private int portBase;
@Value("${emulator.portcount}")
private int portCount;

public HueMulator(){
httpClient = HttpClients.createDefault(); //patched for now, moving away from HueMulator doing work
Expand All @@ -60,8 +65,10 @@ public HueMulator(){

@RequestMapping(value = "/{userId}/lights", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<Map<String, String>> getUpnpConfiguration(@PathVariable(value = "userId") String userId, HttpServletRequest request) {
log.info("hue lights list requested: " + userId + " from " + request.getRemoteAddr());
Page<DeviceDescriptor> deviceList = repository.findByDeviceType("switch", new PageRequest(0,100));
log.info("hue lights list requested: " + userId + " from " + request.getRemoteAddr() + request.getLocalPort());

int pageNumber = request.getLocalPort()-portBase;
Page<DeviceDescriptor> deviceList = repository.findByDeviceType("switch", new PageRequest(pageNumber, 25));
Map<String, String> deviceResponseMap = new HashMap<>();
for (DeviceDescriptor device : deviceList) {
deviceResponseMap.put(device.getId(), device.getName());
Expand All @@ -71,13 +78,15 @@ public ResponseEntity<Map<String, String>> getUpnpConfiguration(@PathVariable(va

@RequestMapping(value = "/*", method = RequestMethod.POST, produces = "application/json")
public ResponseEntity<String> postAPI(HttpServletRequest request) {
log.info("registered device: " + request.toString());
return new ResponseEntity<String>("[{\"success\":{\"username\":\"lights\"}}]", HttpStatus.OK);
}

@RequestMapping(value = "/{userId}", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<HueApiResponse> getApi(@PathVariable(value = "userId") String userId, HttpServletRequest request) {
log.info("hue api root requested: " + userId + " from " + request.getRemoteAddr());
Page<DeviceDescriptor> descriptorList = repository.findByDeviceType("switch", new PageRequest(0, 100));
int pageNumber = request.getLocalPort()-portBase;
Page<DeviceDescriptor> descriptorList = repository.findByDeviceType("switch", new PageRequest(pageNumber, 25));
if (descriptorList == null) {
return new ResponseEntity<>(null, null, HttpStatus.NOT_FOUND);
}
Expand Down Expand Up @@ -201,8 +210,9 @@ protected boolean doHttpRequest(String url, String httpVerb, String contentType,
try {
HttpResponse response = httpClient.execute(request);
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
log.info("GET on URL responded: " + response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() == 200){
int httpResponseCode = response.getStatusLine().getStatusCode();
log.info("GET on URL responded: " + httpResponseCode);
if(httpResponseCode >= 200 && httpResponseCode < 300){ //had complaints that some apps do not respond back with pure 200
return true;
}
} catch (IOException e) {
Expand Down
Loading