[hueemulation] Changes to fix pairing and device discovery with Alexa (#9164)
Signed-off-by: Mike Major <mike_j_major@hotmail.com>
This commit is contained in:
parent
be61a70030
commit
ab7ac79fab
@ -167,6 +167,15 @@ You must either
|
|||||||
```
|
```
|
||||||
* or let openHAB run on port 80 (the entire java process requires elevated privileges).
|
* or let openHAB run on port 80 (the entire java process requires elevated privileges).
|
||||||
|
|
||||||
|
* For Amazon Echo the pairing process may fail due to a 302 response from openHAB, this can be resolved by using a reverse proxy to change the request url from `/api` to `/api/`, for example nginx with the following configuration:
|
||||||
|
```
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://localhost:8080/api/;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
Please open port 80 tcp and port 1900 udp in your firewall installation.
|
Please open port 80 tcp and port 1900 udp in your firewall installation.
|
||||||
|
|
||||||
You can test if the hue emulation does its job by enabling pairing mode including the option "Amazon Echo device discovery fix".
|
You can test if the hue emulation does its job by enabling pairing mode including the option "Amazon Echo device discovery fix".
|
||||||
|
|||||||
@ -17,8 +17,8 @@ import java.net.UnknownHostException;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -147,7 +147,7 @@ public class ConfigStore {
|
|||||||
determineHighestAssignedHueID();
|
determineHighestAssignedHueID();
|
||||||
|
|
||||||
if (config.uuid.isEmpty()) {
|
if (config.uuid.isEmpty()) {
|
||||||
config.uuid = UUID.randomUUID().toString();
|
config.uuid = getHueUUID();
|
||||||
writeUUIDFuture = scheduler.schedule(() -> {
|
writeUUIDFuture = scheduler.schedule(() -> {
|
||||||
logger.info("No unique ID assigned yet. Assigning {} and restarting...", config.uuid);
|
logger.info("No unique ID assigned yet. Assigning {} and restarting...", config.uuid);
|
||||||
WriteConfig.setUUID(configAdmin, config.uuid);
|
WriteConfig.setUUID(configAdmin, config.uuid);
|
||||||
@ -158,6 +158,19 @@ public class ConfigStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getHueUUID() {
|
||||||
|
// Hue API example is AA:BB:CC:DD:EE:FF:00:11-XX
|
||||||
|
// XX is generated from the item.
|
||||||
|
final Random r = new Random();
|
||||||
|
int n = r.nextInt(255);
|
||||||
|
final StringBuilder uuid = new StringBuilder(String.format("%02X", n));
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
n = r.nextInt(255);
|
||||||
|
uuid.append(String.format(":%02X", n));
|
||||||
|
}
|
||||||
|
return uuid.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private @Nullable InetAddress byName(@Nullable String address) {
|
private @Nullable InetAddress byName(@Nullable String address) {
|
||||||
if (address == null) {
|
if (address == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -311,7 +324,7 @@ public class ConfigStore {
|
|||||||
metadataRegistry.add(new Metadata(key, String.valueOf(hueId), null));
|
metadataRegistry.add(new Metadata(key, String.valueOf(hueId), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
return String.valueOf(hueId);
|
return String.format("%02X", hueId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isReady() {
|
public boolean isReady() {
|
||||||
|
|||||||
@ -126,7 +126,7 @@ public class HueEmulationService implements EventHandler {
|
|||||||
@Override
|
@Override
|
||||||
public Set<Object> getSingletons() {
|
public Set<Object> getSingletons() {
|
||||||
return Set.of(userManagement, configurationAccess, lightItems, sensors, scenes, schedules, rules,
|
return Set.of(userManagement, configurationAccess, lightItems, sensors, scenes, schedules, rules,
|
||||||
statusResource, accessInterceptor);
|
statusResource, accessInterceptor, requestCleaner);
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<String, String> serviceProperties() {
|
Dictionary<String, String> serviceProperties() {
|
||||||
|
|||||||
@ -95,8 +95,7 @@ class HueEmulationConfigWithRuntime extends Thread implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized CompletableFuture<@Nullable HueEmulationConfigWithRuntime> startNow(
|
public synchronized CompletableFuture<@Nullable HueEmulationConfigWithRuntime> startNow() {
|
||||||
@Nullable HueEmulationConfigWithRuntime ignored) {
|
|
||||||
if (hasAlreadyBeenStarted) {
|
if (hasAlreadyBeenStarted) {
|
||||||
logger.debug("Cannot restart thread");
|
logger.debug("Cannot restart thread");
|
||||||
return future;
|
return future;
|
||||||
|
|||||||
@ -349,8 +349,9 @@ public class UpnpServer extends HttpServlet implements Consumer<HueEmulationConf
|
|||||||
}
|
}
|
||||||
configChangeFuture = root.thenApply(this::createConfiguration)
|
configChangeFuture = root.thenApply(this::createConfiguration)
|
||||||
.thenApplyAsync(this::performAddressTest, executor).thenApply(this::applyConfiguration)
|
.thenApplyAsync(this::performAddressTest, executor).thenApply(this::applyConfiguration)
|
||||||
.thenCompose(config::startNow)
|
.thenCompose(c -> {
|
||||||
.whenComplete((HueEmulationConfigWithRuntime config, @Nullable Throwable e) -> {
|
return c.startNow();
|
||||||
|
}).whenComplete((HueEmulationConfigWithRuntime config, @Nullable Throwable e) -> {
|
||||||
if (e != null) {
|
if (e != null) {
|
||||||
logger.warn("Upnp server: Address test failed", e);
|
logger.warn("Upnp server: Address test failed", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,7 +88,7 @@ public class ItemUIDtoHueIDMappingTests {
|
|||||||
itemRegistry.add(item);
|
itemRegistry.add(item);
|
||||||
|
|
||||||
String hueID = cs.mapItemUIDtoHueID(item);
|
String hueID = cs.mapItemUIDtoHueID(item);
|
||||||
assertThat(hueID, CoreMatchers.is("2"));
|
assertThat(hueID, CoreMatchers.is("02"));
|
||||||
|
|
||||||
HueLightEntry device = cs.ds.lights.get(hueID);
|
HueLightEntry device = cs.ds.lights.get(hueID);
|
||||||
assertThat(device.item, is(item));
|
assertThat(device.item, is(item));
|
||||||
@ -108,7 +108,7 @@ public class ItemUIDtoHueIDMappingTests {
|
|||||||
itemRegistry.add(item);
|
itemRegistry.add(item);
|
||||||
|
|
||||||
String hueID = cs.mapItemUIDtoHueID(item);
|
String hueID = cs.mapItemUIDtoHueID(item);
|
||||||
assertThat(hueID, CoreMatchers.is("10"));
|
assertThat(hueID, CoreMatchers.is("0A"));
|
||||||
|
|
||||||
HueLightEntry device = cs.ds.lights.get(hueID);
|
HueLightEntry device = cs.ds.lights.get(hueID);
|
||||||
assertThat(device.item, is(item));
|
assertThat(device.item, is(item));
|
||||||
|
|||||||
@ -123,7 +123,7 @@ public class UpnpTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UDP thread started?
|
// UDP thread started?
|
||||||
r.startNow(r).get(5, TimeUnit.SECONDS);
|
r.startNow().get(5, TimeUnit.SECONDS);
|
||||||
assertThat(subject.upnpAnnouncementThreadRunning(), is(true));
|
assertThat(subject.upnpAnnouncementThreadRunning(), is(true));
|
||||||
|
|
||||||
// Send M-SEARCH UPNP "packet" and check if the result contains our bridge ID
|
// Send M-SEARCH UPNP "packet" and check if the result contains our bridge ID
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user