[nest] Remove WWN support (#15418)

See: https://support.google.com/googlenest/answer/9293712?hl=en

> Starting September 29, 2023, all Works with Nest connections will stop working.

Closes #13525
Closes #14761

Signed-off-by: Wouter Born <github@maindrain.net>
This commit is contained in:
Wouter Born
2023-09-06 17:02:55 +02:00
committed by GitHub
parent c5739eccc9
commit 5a803961d0
80 changed files with 19 additions and 9359 deletions

View File

@@ -1,13 +0,0 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -1,7 +0,0 @@
# Nest Binding Tests
[Nest Labs](https://nest.com/) developed/acquired the Wi-Fi enabled Nest Learning Thermostat, the Nest Protect Smoke+CO detector, and the Nest Cam.
These devices are supported by this binding, which communicates with the Nest API over a secure, RESTful API to Nest's servers.
Monitoring ambient temperature and humidity, changing HVAC mode, changing heat or cool setpoints, monitoring and changing your "home/away" status, and monitoring your Nest Protects and Nest Cams can be accomplished through this binding.
This binding is to test and verify the nest binding.

View File

@@ -1,112 +0,0 @@
-include: ../itest-common.bndrun
Bundle-SymbolicName: ${project.artifactId}
Fragment-Host: org.openhab.binding.nest
-runrequires: \
bnd.identity;id='org.openhab.binding.nest.tests'
# We would like to use the "volatile" storage only
-runblacklist: \
bnd.identity;id='org.openhab.core.storage.json',\
bnd.identity;id='jakarta.ws.rs-api'
#
# done
#
-runbundles: \
org.eclipse.equinox.event;version='[1.4.300,1.4.301)',\
org.osgi.service.event;version='[1.4.0,1.4.1)',\
org.osgi.service.jaxrs;version='[1.0.0,1.0.1)',\
org.hamcrest;version='[2.2.0,2.2.1)',\
org.opentest4j;version='[1.2.0,1.2.1)',\
jakarta.xml.bind-api;version='[2.3.3,2.3.4)',\
com.sun.xml.bind.jaxb-osgi;version='[2.3.3,2.3.4)',\
org.apache.servicemix.specs.activation-api-1.2.1;version='[1.2.1,1.2.2)',\
org.glassfish.hk2.osgi-resource-locator;version='[1.0.3,1.0.4)',\
org.apache.aries.javax.jax.rs-api;version='[1.0.1,1.0.2)',\
jakarta.annotation-api;version='[1.3.5,1.3.6)',\
org.apache.aries.component-dsl.component-dsl;version='[1.2.2,1.2.3)',\
stax2-api;version='[4.2.1,4.2.2)',\
jakarta.inject.jakarta.inject-api;version='[2.0.0,2.0.1)',\
org.glassfish.hk2.external.javax.inject;version='[2.4.0,2.4.1)',\
si-units;version='[2.1.0,2.1.1)',\
si.uom.si-quantity;version='[2.1.0,2.1.1)',\
org.apache.aries.jax.rs.whiteboard;version='[2.0.0,2.0.1)',\
org.osgi.util.function;version='[1.2.0,1.2.1)',\
org.osgi.util.promise;version='[1.2.0,1.2.1)',\
ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
org.jsr-305;version='[3.0.2,3.0.3)',\
com.google.gson;version='[2.9.1,2.9.2)',\
org.objectweb.asm;version='[9.4.0,9.4.1)',\
com.sun.jna;version='[5.12.1,5.12.2)',\
org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\
org.apache.felix.http.servlet-api;version='[1.2.0,1.2.1)',\
org.apache.felix.scr;version='[2.2.4,2.2.5)',\
org.eclipse.jetty.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.io;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.jaas;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.security;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.server;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.servlet;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.util;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.util.ajax;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.websocket.api;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.websocket.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.websocket.common;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.xml;version='[9.4.50,9.4.51)',\
org.ops4j.pax.logging.pax-logging-api;version='[2.2.0,2.2.1)',\
org.ops4j.pax.web.pax-web-api;version='[8.0.15,8.0.16)',\
org.ops4j.pax.web.pax-web-jetty;version='[8.0.15,8.0.16)',\
org.ops4j.pax.web.pax-web-runtime;version='[8.0.15,8.0.16)',\
org.ops4j.pax.web.pax-web-spi;version='[8.0.15,8.0.16)',\
org.ops4j.pax.web.pax-web-tomcat-common;version='[8.0.15,8.0.16)',\
org.osgi.service.component;version='[1.5.0,1.5.1)',\
junit-jupiter-api;version='[5.9.2,5.9.3)',\
junit-jupiter-engine;version='[5.9.2,5.9.3)',\
junit-platform-commons;version='[1.9.2,1.9.3)',\
junit-platform-engine;version='[1.9.2,1.9.3)',\
junit-platform-launcher;version='[1.9.2,1.9.3)',\
net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\
net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\
org.eclipse.jetty.alpn.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.common;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.hpack;version='[9.4.50,9.4.51)',\
org.mockito.junit-jupiter;version='[4.11.0,4.11.1)',\
org.mockito.mockito-core;version='[4.11.0,4.11.1)',\
org.objenesis;version='[3.3.0,3.3.1)',\
xstream;version='[1.4.20,1.4.21)',\
org.apache.aries.spifly.dynamic.bundle;version='[1.3.6,1.3.7)',\
org.objectweb.asm.commons;version='[9.4.0,9.4.1)',\
org.objectweb.asm.tree;version='[9.4.0,9.4.1)',\
org.objectweb.asm.tree.analysis;version='[9.4.0,9.4.1)',\
org.objectweb.asm.util;version='[9.4.0,9.4.1)',\
org.openhab.binding.nest;version='[4.1.0,4.1.1)',\
org.openhab.binding.nest.tests;version='[4.1.0,4.1.1)',\
org.openhab.core;version='[4.1.0,4.1.1)',\
org.openhab.core.auth.oauth2client;version='[4.1.0,4.1.1)',\
org.openhab.core.config.core;version='[4.1.0,4.1.1)',\
org.openhab.core.config.discovery;version='[4.1.0,4.1.1)',\
org.openhab.core.io.console;version='[4.1.0,4.1.1)',\
org.openhab.core.io.net;version='[4.1.0,4.1.1)',\
org.openhab.core.test;version='[4.1.0,4.1.1)',\
org.openhab.core.thing;version='[4.1.0,4.1.1)',\
org.openhab.core.transform;version='[4.1.0,4.1.1)',\
org.openhab.base-fixes;version='[1.0.0,1.0.1)',\
javax.measure.unit-api;version='[2.2.0,2.2.1)',\
org.apiguardian.api;version='[1.1.2,1.1.3)',\
tech.units.indriya;version='[2.2.0,2.2.1)',\
uom-lib-common;version='[2.2.0,2.2.1)',\
com.fasterxml.woodstox.woodstox-core;version='[6.5.1,6.5.2)',\
org.apache.cxf.cxf-core;version='[3.6.1,3.6.2)',\
org.apache.cxf.cxf-rt-frontend-jaxrs;version='[3.6.1,3.6.2)',\
org.apache.cxf.cxf-rt-rs-client;version='[3.6.1,3.6.2)',\
org.apache.cxf.cxf-rt-rs-sse;version='[3.6.1,3.6.2)',\
org.apache.cxf.cxf-rt-security;version='[3.6.1,3.6.2)',\
org.apache.cxf.cxf-rt-transports-http;version='[3.6.1,3.6.2)',\
org.apache.ws.xmlschema.core;version='[2.3.0,2.3.1)',\
io.methvin.directory-watcher;version='[0.18.0,0.18.1)'

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.itests</groupId>
<artifactId>org.openhab.addons.reactor.itests</artifactId>
<version>4.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.nest.tests</artifactId>
<name>openHAB Add-ons :: Integration Tests :: Nest Binding Tests</name>
<dependencies>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.nest</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,98 +0,0 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nest.internal.wwn.dto;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import javax.measure.Unit;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.nest.internal.wwn.WWNUtils;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
/**
* Utility class for working with Nest test data in unit tests.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public final class WWNDataUtil {
public static final String COMPLETE_DATA_FILE_NAME = "top-level-streaming-data.json";
public static final String INCOMPLETE_DATA_FILE_NAME = "top-level-streaming-data-incomplete.json";
public static final String EMPTY_DATA_FILE_NAME = "top-level-streaming-data-empty.json";
public static final String CAMERA1_DEVICE_ID = "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ";
public static final String CAMERA1_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA";
public static final String CAMERA2_DEVICE_ID = "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ";
public static final String CAMERA2_WHERE_ID = "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ";
public static final String SMOKE1_DEVICE_ID = "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV";
public static final String SMOKE1_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg";
public static final String SMOKE2_DEVICE_ID = "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV";
public static final String SMOKE2_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA";
public static final String SMOKE3_DEVICE_ID = "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV";
public static final String SMOKE3_WHERE_ID = "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ";
public static final String SMOKE4_DEVICE_ID = "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV";
public static final String SMOKE4_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw";
public static final String STRUCTURE1_STRUCTURE_ID = "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A";
public static final String THERMOSTAT1_DEVICE_ID = "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV";
public static final String THERMOSTAT1_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw";
private WWNDataUtil() {
// Hidden utility class constructor
}
public static Reader openDataReader(String fileName) {
String packagePath = (WWNDataUtil.class.getPackage().getName()).replaceAll("\\.", "/");
String filePath = "/" + packagePath + "/" + fileName;
InputStream inputStream = WWNDataUtil.class.getClassLoader().getResourceAsStream(filePath);
return new InputStreamReader(inputStream, StandardCharsets.UTF_8);
}
public static <T> T fromJson(String fileName, Class<T> dataClass) throws IOException {
try (Reader reader = openDataReader(fileName)) {
return WWNUtils.fromJson(reader, dataClass);
}
}
public static String fromFile(String fileName, Unit<Temperature> temperatureUnit) throws IOException {
String json = fromFile(fileName);
if (SIUnits.CELSIUS.equals(temperatureUnit)) {
json = json.replace("\"temperature_scale\": \"F\"", "\"temperature_scale\": \"C\"");
} else if (ImperialUnits.FAHRENHEIT.equals(temperatureUnit)) {
json = json.replace("\"temperature_scale\": \"C\"", "\"temperature_scale\": \"F\"");
}
return json;
}
public static String fromFile(String fileName) throws IOException {
try (Reader reader = openDataReader(fileName)) {
return new BufferedReader(reader).lines().parallel().collect(Collectors.joining("\n"));
}
}
}

View File

@@ -1,225 +0,0 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nest.internal.wwn.dto;
import static org.junit.jupiter.api.Assertions.*;
import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.core.library.unit.SIUnits;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Test cases for gson parsing of model classes
*
* @author David Bennett - Initial contribution
* @author Wouter Born - Increase test coverage
*/
@NonNullByDefault
public class WWNGsonParsingTest {
private final Logger logger = LoggerFactory.getLogger(WWNGsonParsingTest.class);
private static void assertEqualDateTime(String expected, Date actual) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
assertEquals(expected, sdf.format(actual));
}
@Test
public void verifyCompleteInput() throws IOException {
WWNTopLevelData topLevel = fromJson("top-level-data.json", WWNTopLevelData.class);
assertEquals(topLevel.getDevices().getThermostats().size(), 1);
assertNotNull(topLevel.getDevices().getThermostats().get(THERMOSTAT1_DEVICE_ID));
assertEquals(topLevel.getDevices().getCameras().size(), 2);
assertNotNull(topLevel.getDevices().getCameras().get(CAMERA1_DEVICE_ID));
assertNotNull(topLevel.getDevices().getCameras().get(CAMERA2_DEVICE_ID));
assertEquals(topLevel.getDevices().getSmokeCoAlarms().size(), 4);
assertNotNull(topLevel.getDevices().getSmokeCoAlarms().get(SMOKE1_DEVICE_ID));
assertNotNull(topLevel.getDevices().getSmokeCoAlarms().get(SMOKE2_DEVICE_ID));
assertNotNull(topLevel.getDevices().getSmokeCoAlarms().get(SMOKE3_DEVICE_ID));
assertNotNull(topLevel.getDevices().getSmokeCoAlarms().get(SMOKE4_DEVICE_ID));
}
@Test
public void verifyCompleteStreamingInput() throws IOException {
WWNTopLevelStreamingData topLevelStreamingData = fromJson("top-level-streaming-data.json",
WWNTopLevelStreamingData.class);
assertEquals("/", topLevelStreamingData.getPath());
WWNTopLevelData data = topLevelStreamingData.getData();
assertEquals(data.getDevices().getThermostats().size(), 1);
assertNotNull(data.getDevices().getThermostats().get(THERMOSTAT1_DEVICE_ID));
assertEquals(data.getDevices().getCameras().size(), 2);
assertNotNull(data.getDevices().getCameras().get(CAMERA1_DEVICE_ID));
assertNotNull(data.getDevices().getCameras().get(CAMERA2_DEVICE_ID));
assertEquals(data.getDevices().getSmokeCoAlarms().size(), 4);
assertNotNull(data.getDevices().getSmokeCoAlarms().get(SMOKE1_DEVICE_ID));
assertNotNull(data.getDevices().getSmokeCoAlarms().get(SMOKE2_DEVICE_ID));
assertNotNull(data.getDevices().getSmokeCoAlarms().get(SMOKE3_DEVICE_ID));
assertNotNull(data.getDevices().getSmokeCoAlarms().get(SMOKE4_DEVICE_ID));
}
@Test
public void verifyThermostat() throws IOException {
WWNThermostat thermostat = fromJson("thermostat-data.json", WWNThermostat.class);
logger.debug("Thermostat: {}", thermostat);
assertTrue(thermostat.isOnline());
assertTrue(thermostat.isCanHeat());
assertTrue(thermostat.isHasLeaf());
assertFalse(thermostat.isCanCool());
assertFalse(thermostat.isFanTimerActive());
assertFalse(thermostat.isLocked());
assertFalse(thermostat.isSunlightCorrectionActive());
assertTrue(thermostat.isSunlightCorrectionEnabled());
assertFalse(thermostat.isUsingEmergencyHeat());
assertEquals(THERMOSTAT1_DEVICE_ID, thermostat.getDeviceId());
assertEquals(Integer.valueOf(15), thermostat.getFanTimerDuration());
assertEqualDateTime("2017-02-02T21:00:06.000Z", thermostat.getLastConnection());
assertEqualDateTime("1970-01-01T00:00:00.000Z", thermostat.getFanTimerTimeout());
assertEquals(Double.valueOf(24.0), thermostat.getEcoTemperatureHigh());
assertEquals(Double.valueOf(12.5), thermostat.getEcoTemperatureLow());
assertEquals(Double.valueOf(22.0), thermostat.getLockedTempMax());
assertEquals(Double.valueOf(20.0), thermostat.getLockedTempMin());
assertEquals(WWNThermostat.Mode.HEAT, thermostat.getMode());
assertEquals("Living Room (Living Room)", thermostat.getName());
assertEquals("Living Room Thermostat (Living Room)", thermostat.getNameLong());
assertEquals(null, thermostat.getPreviousHvacMode());
assertEquals("5.6-7", thermostat.getSoftwareVersion());
assertEquals(WWNThermostat.State.OFF, thermostat.getHvacState());
assertEquals(STRUCTURE1_STRUCTURE_ID, thermostat.getStructureId());
assertEquals(Double.valueOf(15.5), thermostat.getTargetTemperature());
assertEquals(Double.valueOf(24.0), thermostat.getTargetTemperatureHigh());
assertEquals(Double.valueOf(20.0), thermostat.getTargetTemperatureLow());
assertEquals(SIUnits.CELSIUS, thermostat.getTemperatureUnit());
assertEquals(Integer.valueOf(0), thermostat.getTimeToTarget());
assertEquals(THERMOSTAT1_WHERE_ID, thermostat.getWhereId());
assertEquals("Living Room", thermostat.getWhereName());
}
@Test
public void thermostatTimeToTargetSupportedValueParsing() {
assertEquals((Integer) 0, WWNThermostat.parseTimeToTarget("~0"));
assertEquals((Integer) 5, WWNThermostat.parseTimeToTarget("<5"));
assertEquals((Integer) 10, WWNThermostat.parseTimeToTarget("<10"));
assertEquals((Integer) 15, WWNThermostat.parseTimeToTarget("~15"));
assertEquals((Integer) 90, WWNThermostat.parseTimeToTarget("~90"));
assertEquals((Integer) 120, WWNThermostat.parseTimeToTarget(">120"));
}
@Test
public void thermostatTimeToTargetUnsupportedValueParsing() {
assertThrows(NumberFormatException.class, () -> WWNThermostat.parseTimeToTarget("#5"));
}
@Test
public void verifyCamera() throws IOException {
WWNCamera camera = fromJson("camera-data.json", WWNCamera.class);
logger.debug("Camera: {}", camera);
assertTrue(camera.isOnline());
assertEquals("Upstairs", camera.getName());
assertEquals("Upstairs Camera", camera.getNameLong());
assertEquals(STRUCTURE1_STRUCTURE_ID, camera.getStructureId());
assertEquals(CAMERA1_WHERE_ID, camera.getWhereId());
assertTrue(camera.isAudioInputEnabled());
assertFalse(camera.isPublicShareEnabled());
assertFalse(camera.isStreaming());
assertFalse(camera.isVideoHistoryEnabled());
assertEquals("https://camera_app_url", camera.getAppUrl());
assertEquals(CAMERA1_DEVICE_ID, camera.getDeviceId());
assertNull(camera.getLastConnection());
assertEqualDateTime("2017-01-22T08:19:20.000Z", camera.getLastIsOnlineChange());
assertNull(camera.getPublicShareUrl());
assertEquals("https://camera_snapshot_url", camera.getSnapshotUrl());
assertEquals("205-600052", camera.getSoftwareVersion());
assertEquals("https://camera_web_url", camera.getWebUrl());
assertEquals("https://last_event_animated_image_url", camera.getLastEvent().getAnimatedImageUrl());
assertEquals(2, camera.getLastEvent().getActivityZones().size());
assertEquals("id1", camera.getLastEvent().getActivityZones().get(0));
assertEquals("https://last_event_app_url", camera.getLastEvent().getAppUrl());
assertEqualDateTime("2017-01-22T07:40:38.680Z", camera.getLastEvent().getEndTime());
assertEquals("https://last_event_image_url", camera.getLastEvent().getImageUrl());
assertEqualDateTime("2017-01-22T07:40:19.020Z", camera.getLastEvent().getStartTime());
assertEqualDateTime("2017-02-05T07:40:19.020Z", camera.getLastEvent().getUrlsExpireTime());
assertEquals("https://last_event_web_url", camera.getLastEvent().getWebUrl());
assertTrue(camera.getLastEvent().isHasMotion());
assertFalse(camera.getLastEvent().isHasPerson());
assertFalse(camera.getLastEvent().isHasSound());
}
@Test
public void verifySmokeDetector() throws IOException {
WWNSmokeDetector smokeDetector = fromJson("smoke-detector-data.json", WWNSmokeDetector.class);
logger.debug("SmokeDetector: {}", smokeDetector);
assertTrue(smokeDetector.isOnline());
assertEquals(SMOKE1_WHERE_ID, smokeDetector.getWhereId());
assertEquals(SMOKE1_DEVICE_ID, smokeDetector.getDeviceId());
assertEquals("Downstairs", smokeDetector.getName());
assertEquals("Downstairs Nest Protect", smokeDetector.getNameLong());
assertEqualDateTime("2017-02-02T20:53:05.338Z", smokeDetector.getLastConnection());
assertEquals(WWNSmokeDetector.BatteryHealth.OK, smokeDetector.getBatteryHealth());
assertEquals(WWNSmokeDetector.AlarmState.OK, smokeDetector.getCoAlarmState());
assertEquals(WWNSmokeDetector.AlarmState.OK, smokeDetector.getSmokeAlarmState());
assertEquals("3.1rc9", smokeDetector.getSoftwareVersion());
assertEquals(STRUCTURE1_STRUCTURE_ID, smokeDetector.getStructureId());
assertEquals(WWNSmokeDetector.UiColorState.GREEN, smokeDetector.getUiColorState());
}
@Test
public void verifyAccessToken() throws IOException {
WWNAccessTokenData accessToken = fromJson("access-token-data.json", WWNAccessTokenData.class);
logger.debug("AccessTokenData: {}", accessToken);
assertEquals("access_token", accessToken.getAccessToken());
assertEquals(Long.valueOf(315360000L), accessToken.getExpiresIn());
}
@Test
public void verifyStructure() throws IOException {
WWNStructure structure = fromJson("structure-data.json", WWNStructure.class);
logger.debug("Structure: {}", structure);
assertEquals("Home", structure.getName());
assertEquals("US", structure.getCountryCode());
assertEquals("98056", structure.getPostalCode());
assertEquals(WWNStructure.HomeAwayState.HOME, structure.getAway());
assertEqualDateTime("2017-02-02T03:10:08.000Z", structure.getEtaBegin());
assertNull(structure.getEta());
assertNull(structure.getPeakPeriodEndTime());
assertNull(structure.getPeakPeriodStartTime());
assertEquals(STRUCTURE1_STRUCTURE_ID, structure.getStructureId());
assertEquals("America/Los_Angeles", structure.getTimeZone());
assertFalse(structure.isRhrEnrollment());
}
@Test
public void verifyError() throws IOException {
WWNErrorData error = fromJson("error-data.json", WWNErrorData.class);
logger.debug("ErrorData: {}", error);
assertEquals("blocked", error.getError());
assertEquals("https://developer.nest.com/documentation/cloud/error-messages#blocked", error.getType());
assertEquals("blocked", error.getMessage());
assertEquals("bb514046-edc9-4bca-8239-f7a3cfb0925a", error.getInstance());
}
}

View File

@@ -1,83 +0,0 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nest.internal.wwn.handler;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import javax.ws.rs.client.ClientBuilder;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.nest.internal.wwn.config.WWNAccountConfiguration;
import org.openhab.binding.nest.internal.wwn.test.WWNTestAccountHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.osgi.service.jaxrs.client.SseEventSourceFactory;
/**
* Tests cases for {@link WWNAccountHandler}.
*
* @author David Bennett - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class WWNAccountHandlerTest {
private @NonNullByDefault({}) ThingHandler handler;
private @Mock @NonNullByDefault({}) Bridge bridge;
private @Mock @NonNullByDefault({}) ThingHandlerCallback callback;
private @Mock @NonNullByDefault({}) ClientBuilder clientBuilder;
private @Mock @NonNullByDefault({}) Configuration configuration;
private @Mock @NonNullByDefault({}) SseEventSourceFactory eventSourceFactory;
@BeforeEach
public void beforeEach() {
handler = new WWNTestAccountHandler(bridge, clientBuilder, eventSourceFactory, "http://localhost");
handler.setCallback(callback);
}
@Test
public void initializeShouldCallTheCallback() {
when(bridge.getConfiguration()).thenReturn(configuration);
WWNAccountConfiguration bridgeConfig = new WWNAccountConfiguration();
when(configuration.as(eq(WWNAccountConfiguration.class))).thenReturn(bridgeConfig);
bridgeConfig.accessToken = "my token";
// we expect the handler#initialize method to call the callback during execution and
// pass it the thing and a ThingStatusInfo object containing the ThingStatus of the thing.
handler.initialize();
// the argument captor will capture the argument of type ThingStatusInfo given to the
// callback#statusUpdated method.
ArgumentCaptor<ThingStatusInfo> statusInfoCaptor = ArgumentCaptor.forClass(ThingStatusInfo.class);
// verify the interaction with the callback and capture the ThingStatusInfo argument:
verify(callback).statusUpdated(eq(bridge), statusInfoCaptor.capture());
// assert that the ThingStatusInfo given to the callback was build with the UNKNOWN status:
ThingStatusInfo thingStatusInfo = statusInfoCaptor.getValue();
assertThat(thingStatusInfo.getStatus(), is(equalTo(ThingStatus.UNKNOWN)));
}
}

View File

@@ -1,148 +0,0 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nest.internal.wwn.handler;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
import static org.openhab.core.library.types.OnOffType.*;
import java.io.IOException;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.nest.internal.wwn.config.WWNDeviceConfiguration;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.builder.ThingBuilder;
/**
* Tests for {@link WWNCameraHandler}.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public class WWNCameraHandlerTest extends WWNThingHandlerOSGiTest {
private static final ThingUID CAMERA_UID = new ThingUID(THING_TYPE_CAMERA, "camera1");
private static final int CHANNEL_COUNT = 20;
public WWNCameraHandlerTest() {
super(WWNCameraHandler.class);
}
@Override
protected Thing buildThing(Bridge bridge) {
Map<String, Object> properties = Map.of(WWNDeviceConfiguration.DEVICE_ID, CAMERA1_DEVICE_ID);
return ThingBuilder.create(THING_TYPE_CAMERA, CAMERA_UID).withLabel("Test Camera").withBridge(bridge.getUID())
.withChannels(buildChannels(THING_TYPE_CAMERA, CAMERA_UID))
.withConfiguration(new Configuration(properties)).build();
}
@Test
public void completeCameraUpdate() throws IOException {
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
// Camera channel group
assertThatItemHasState(CHANNEL_CAMERA_APP_URL, new StringType("https://camera_app_url"));
assertThatItemHasState(CHANNEL_CAMERA_AUDIO_INPUT_ENABLED, ON);
assertThatItemHasState(CHANNEL_CAMERA_LAST_ONLINE_CHANGE, parseDateTimeType("2017-01-22T08:19:20.000Z"));
assertThatItemHasState(CHANNEL_CAMERA_PUBLIC_SHARE_ENABLED, OFF);
assertThatItemHasState(CHANNEL_CAMERA_PUBLIC_SHARE_URL, new StringType("https://camera_public_share_url"));
assertThatItemHasState(CHANNEL_CAMERA_SNAPSHOT_URL, new StringType("https://camera_snapshot_url"));
assertThatItemHasState(CHANNEL_CAMERA_STREAMING, OFF);
assertThatItemHasState(CHANNEL_CAMERA_VIDEO_HISTORY_ENABLED, OFF);
assertThatItemHasState(CHANNEL_CAMERA_WEB_URL, new StringType("https://camera_web_url"));
// Last event channel group
assertThatItemHasState(CHANNEL_LAST_EVENT_ACTIVITY_ZONES, new StringType("id1,id2"));
assertThatItemHasState(CHANNEL_LAST_EVENT_ANIMATED_IMAGE_URL,
new StringType("https://last_event_animated_image_url"));
assertThatItemHasState(CHANNEL_LAST_EVENT_APP_URL, new StringType("https://last_event_app_url"));
assertThatItemHasState(CHANNEL_LAST_EVENT_END_TIME, parseDateTimeType("2017-01-22T07:40:38.680Z"));
assertThatItemHasState(CHANNEL_LAST_EVENT_HAS_MOTION, ON);
assertThatItemHasState(CHANNEL_LAST_EVENT_HAS_PERSON, OFF);
assertThatItemHasState(CHANNEL_LAST_EVENT_HAS_SOUND, OFF);
assertThatItemHasState(CHANNEL_LAST_EVENT_IMAGE_URL, new StringType("https://last_event_image_url"));
assertThatItemHasState(CHANNEL_LAST_EVENT_START_TIME, parseDateTimeType("2017-01-22T07:40:19.020Z"));
assertThatItemHasState(CHANNEL_LAST_EVENT_URLS_EXPIRE_TIME, parseDateTimeType("2017-02-05T07:40:19.020Z"));
assertThatItemHasState(CHANNEL_LAST_EVENT_WEB_URL, new StringType("https://last_event_web_url"));
assertThatAllItemStatesAreNotNull();
}
@Test
public void incompleteCameraUpdate() throws IOException {
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
assertThatAllItemStatesAreNotNull();
putStreamingEventData(fromFile(INCOMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.UNKNOWN)));
assertThatAllItemStatesAreNull();
}
@Test
public void cameraGone() throws IOException {
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(EMPTY_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.OFFLINE)));
assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.GONE));
}
@Test
public void channelRefresh() throws IOException {
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
assertThatAllItemStatesAreNotNull();
updateAllItemStatesToNull();
assertThatAllItemStatesAreNull();
refreshAllChannels();
assertThatAllItemStatesAreNotNull();
}
@Test
public void handleStreamingCommands() throws IOException {
handleCommand(CHANNEL_CAMERA_STREAMING, ON);
assertNestApiPropertyState(CAMERA1_DEVICE_ID, "is_streaming", "true");
handleCommand(CHANNEL_CAMERA_STREAMING, OFF);
assertNestApiPropertyState(CAMERA1_DEVICE_ID, "is_streaming", "false");
handleCommand(CHANNEL_CAMERA_STREAMING, ON);
assertNestApiPropertyState(CAMERA1_DEVICE_ID, "is_streaming", "true");
}
}

View File

@@ -1,119 +0,0 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nest.internal.wwn.handler;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
import static org.openhab.core.library.types.OnOffType.OFF;
import java.io.IOException;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.nest.internal.wwn.config.WWNDeviceConfiguration;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.builder.ThingBuilder;
/**
* Tests for {@link WWNSmokeDetectorHandler}.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public class WWNSmokeDetectorHandlerTest extends WWNThingHandlerOSGiTest {
private static final ThingUID SMOKE_DETECTOR_UID = new ThingUID(THING_TYPE_SMOKE_DETECTOR, "smoke1");
private static final int CHANNEL_COUNT = 7;
public WWNSmokeDetectorHandlerTest() {
super(WWNSmokeDetectorHandler.class);
}
@Override
protected Thing buildThing(Bridge bridge) {
Map<String, Object> properties = Map.of(WWNDeviceConfiguration.DEVICE_ID, SMOKE1_DEVICE_ID);
return ThingBuilder.create(THING_TYPE_SMOKE_DETECTOR, SMOKE_DETECTOR_UID).withLabel("Test Smoke Detector")
.withBridge(bridge.getUID()).withChannels(buildChannels(THING_TYPE_SMOKE_DETECTOR, SMOKE_DETECTOR_UID))
.withConfiguration(new Configuration(properties)).build();
}
@Test
public void completeSmokeDetectorUpdate() throws IOException {
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
assertThatItemHasState(CHANNEL_CO_ALARM_STATE, new StringType("OK"));
assertThatItemHasState(CHANNEL_LAST_CONNECTION, parseDateTimeType("2017-02-02T20:53:05.338Z"));
assertThatItemHasState(CHANNEL_LAST_MANUAL_TEST_TIME, parseDateTimeType("2016-10-31T23:59:59.000Z"));
assertThatItemHasState(CHANNEL_LOW_BATTERY, OFF);
assertThatItemHasState(CHANNEL_MANUAL_TEST_ACTIVE, OFF);
assertThatItemHasState(CHANNEL_SMOKE_ALARM_STATE, new StringType("OK"));
assertThatItemHasState(CHANNEL_UI_COLOR_STATE, new StringType("GREEN"));
assertThatAllItemStatesAreNotNull();
}
@Test
public void incompleteSmokeDetectorUpdate() throws IOException {
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
assertThatAllItemStatesAreNotNull();
putStreamingEventData(fromFile(INCOMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.UNKNOWN)));
assertThatAllItemStatesAreNull();
}
@Test
public void smokeDetectorGone() throws IOException {
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(EMPTY_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.OFFLINE)));
assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.GONE));
}
@Test
public void channelRefresh() throws IOException {
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
assertThatAllItemStatesAreNotNull();
updateAllItemStatesToNull();
assertThatAllItemStatesAreNull();
refreshAllChannels();
assertThatAllItemStatesAreNotNull();
}
}

View File

@@ -1,135 +0,0 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nest.internal.wwn.handler;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
import static org.openhab.core.library.types.OnOffType.OFF;
import java.io.IOException;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.nest.internal.wwn.config.WWNStructureConfiguration;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.builder.ThingBuilder;
/**
* Tests for {@link WWNStructureHandler}.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public class WWNStructureHandlerTest extends WWNThingHandlerOSGiTest {
private static final ThingUID STRUCTURE_UID = new ThingUID(THING_TYPE_STRUCTURE, "structure1");
private static final int CHANNEL_COUNT = 11;
public WWNStructureHandlerTest() {
super(WWNStructureHandler.class);
}
@Override
protected Thing buildThing(Bridge bridge) {
Map<String, Object> properties = Map.of(WWNStructureConfiguration.STRUCTURE_ID, STRUCTURE1_STRUCTURE_ID);
return ThingBuilder.create(THING_TYPE_STRUCTURE, STRUCTURE_UID).withLabel("Test Structure")
.withBridge(bridge.getUID()).withChannels(buildChannels(THING_TYPE_STRUCTURE, STRUCTURE_UID))
.withConfiguration(new Configuration(properties)).build();
}
@Test
public void completeStructureUpdate() throws IOException {
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
assertThatItemHasState(CHANNEL_AWAY, new StringType("HOME"));
assertThatItemHasState(CHANNEL_CO_ALARM_STATE, new StringType("OK"));
assertThatItemHasState(CHANNEL_COUNTRY_CODE, new StringType("US"));
assertThatItemHasState(CHANNEL_ETA_BEGIN, parseDateTimeType("2017-02-02T03:10:08.000Z"));
assertThatItemHasState(CHANNEL_PEAK_PERIOD_END_TIME, parseDateTimeType("2017-07-01T01:03:08.400Z"));
assertThatItemHasState(CHANNEL_PEAK_PERIOD_START_TIME, parseDateTimeType("2017-06-01T13:31:10.870Z"));
assertThatItemHasState(CHANNEL_POSTAL_CODE, new StringType("98056"));
assertThatItemHasState(CHANNEL_RUSH_HOUR_REWARDS_ENROLLMENT, OFF);
assertThatItemHasState(CHANNEL_SECURITY_STATE, new StringType("OK"));
assertThatItemHasState(CHANNEL_SMOKE_ALARM_STATE, new StringType("OK"));
assertThatItemHasState(CHANNEL_TIME_ZONE, new StringType("America/Los_Angeles"));
assertThatAllItemStatesAreNotNull();
}
@Test
public void incompleteStructureUpdate() throws IOException {
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
assertThatAllItemStatesAreNotNull();
putStreamingEventData(fromFile(INCOMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
assertThatAllItemStatesAreNull();
}
@Test
public void structureGone() throws IOException {
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(EMPTY_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.OFFLINE)));
assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.GONE));
}
@Test
public void channelRefresh() throws IOException {
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
assertThatAllItemStatesAreNotNull();
updateAllItemStatesToNull();
assertThatAllItemStatesAreNull();
refreshAllChannels();
assertThatAllItemStatesAreNotNull();
}
@Test
public void handleAwayCommands() throws IOException {
handleCommand(CHANNEL_AWAY, new StringType("AWAY"));
assertNestApiPropertyState(STRUCTURE1_STRUCTURE_ID, "away", "away");
handleCommand(CHANNEL_AWAY, new StringType("HOME"));
assertNestApiPropertyState(STRUCTURE1_STRUCTURE_ID, "away", "home");
handleCommand(CHANNEL_AWAY, new StringType("AWAY"));
assertNestApiPropertyState(STRUCTURE1_STRUCTURE_ID, "away", "away");
}
}

View File

@@ -1,299 +0,0 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nest.internal.wwn.handler;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
import static org.openhab.core.library.types.OnOffType.*;
import static org.openhab.core.library.unit.ImperialUnits.FAHRENHEIT;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import java.io.IOException;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.nest.internal.wwn.config.WWNDeviceConfiguration;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.builder.ThingBuilder;
/**
* Tests for {@link WWNThermostatHandler}.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public class WWNThermostatHandlerTest extends WWNThingHandlerOSGiTest {
private static final ThingUID THERMOSTAT_UID = new ThingUID(THING_TYPE_THERMOSTAT, "thermostat1");
private static final int CHANNEL_COUNT = 25;
public WWNThermostatHandlerTest() {
super(WWNThermostatHandler.class);
}
@Override
protected Thing buildThing(Bridge bridge) {
Map<String, Object> properties = Map.of(WWNDeviceConfiguration.DEVICE_ID, THERMOSTAT1_DEVICE_ID);
return ThingBuilder.create(THING_TYPE_THERMOSTAT, THERMOSTAT_UID).withLabel("Test Thermostat")
.withBridge(bridge.getUID()).withChannels(buildChannels(THING_TYPE_THERMOSTAT, THERMOSTAT_UID))
.withConfiguration(new Configuration(properties)).build();
}
@Test
public void completeThermostatCelsiusUpdate() throws IOException {
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, CELSIUS));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
assertThatItemHasState(CHANNEL_CAN_COOL, OFF);
assertThatItemHasState(CHANNEL_CAN_HEAT, ON);
assertThatItemHasState(CHANNEL_ECO_MAX_SET_POINT, new QuantityType<>(24, CELSIUS));
assertThatItemHasState(CHANNEL_ECO_MIN_SET_POINT, new QuantityType<>(12.5, CELSIUS));
assertThatItemHasState(CHANNEL_FAN_TIMER_ACTIVE, OFF);
assertThatItemHasState(CHANNEL_FAN_TIMER_DURATION, new QuantityType<>(15, Units.MINUTE));
assertThatItemHasState(CHANNEL_FAN_TIMER_TIMEOUT, parseDateTimeType("1970-01-01T00:00:00.000Z"));
assertThatItemHasState(CHANNEL_HAS_FAN, ON);
assertThatItemHasState(CHANNEL_HAS_LEAF, ON);
assertThatItemHasState(CHANNEL_HUMIDITY, new QuantityType<>(25, Units.PERCENT));
assertThatItemHasState(CHANNEL_LAST_CONNECTION, parseDateTimeType("2017-02-02T21:00:06.000Z"));
assertThatItemHasState(CHANNEL_LOCKED, OFF);
assertThatItemHasState(CHANNEL_LOCKED_MAX_SET_POINT, new QuantityType<>(22, CELSIUS));
assertThatItemHasState(CHANNEL_LOCKED_MIN_SET_POINT, new QuantityType<>(20, CELSIUS));
assertThatItemHasState(CHANNEL_MAX_SET_POINT, new QuantityType<>(24, CELSIUS));
assertThatItemHasState(CHANNEL_MIN_SET_POINT, new QuantityType<>(20, CELSIUS));
assertThatItemHasState(CHANNEL_MODE, new StringType("HEAT"));
assertThatItemHasState(CHANNEL_PREVIOUS_MODE, new StringType("HEAT"));
assertThatItemHasState(CHANNEL_SET_POINT, new QuantityType<>(15.5, CELSIUS));
assertThatItemHasState(CHANNEL_STATE, new StringType("OFF"));
assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ACTIVE, OFF);
assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ENABLED, ON);
assertThatItemHasState(CHANNEL_TEMPERATURE, new QuantityType<>(19, CELSIUS));
assertThatItemHasState(CHANNEL_TIME_TO_TARGET, new QuantityType<>(0, Units.MINUTE));
assertThatItemHasState(CHANNEL_USING_EMERGENCY_HEAT, OFF);
assertThatAllItemStatesAreNotNull();
}
@Test
public void completeThermostatFahrenheitUpdate() throws IOException {
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, FAHRENHEIT));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
assertThatItemHasState(CHANNEL_CAN_COOL, OFF);
assertThatItemHasState(CHANNEL_CAN_HEAT, ON);
assertThatItemHasState(CHANNEL_ECO_MAX_SET_POINT, new QuantityType<>(76, FAHRENHEIT));
assertThatItemHasState(CHANNEL_ECO_MIN_SET_POINT, new QuantityType<>(55, FAHRENHEIT));
assertThatItemHasState(CHANNEL_FAN_TIMER_ACTIVE, OFF);
assertThatItemHasState(CHANNEL_FAN_TIMER_DURATION, new QuantityType<>(15, Units.MINUTE));
assertThatItemHasState(CHANNEL_FAN_TIMER_TIMEOUT, parseDateTimeType("1970-01-01T00:00:00.000Z"));
assertThatItemHasState(CHANNEL_HAS_FAN, ON);
assertThatItemHasState(CHANNEL_HAS_LEAF, ON);
assertThatItemHasState(CHANNEL_HUMIDITY, new QuantityType<>(25, Units.PERCENT));
assertThatItemHasState(CHANNEL_LAST_CONNECTION, parseDateTimeType("2017-02-02T21:00:06.000Z"));
assertThatItemHasState(CHANNEL_LOCKED, OFF);
assertThatItemHasState(CHANNEL_LOCKED_MAX_SET_POINT, new QuantityType<>(72, FAHRENHEIT));
assertThatItemHasState(CHANNEL_LOCKED_MIN_SET_POINT, new QuantityType<>(68, FAHRENHEIT));
assertThatItemHasState(CHANNEL_MAX_SET_POINT, new QuantityType<>(75, FAHRENHEIT));
assertThatItemHasState(CHANNEL_MIN_SET_POINT, new QuantityType<>(68, FAHRENHEIT));
assertThatItemHasState(CHANNEL_MODE, new StringType("HEAT"));
assertThatItemHasState(CHANNEL_PREVIOUS_MODE, new StringType("HEAT"));
assertThatItemHasState(CHANNEL_SET_POINT, new QuantityType<>(60, FAHRENHEIT));
assertThatItemHasState(CHANNEL_STATE, new StringType("OFF"));
assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ACTIVE, OFF);
assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ENABLED, ON);
assertThatItemHasState(CHANNEL_TEMPERATURE, new QuantityType<>(66, FAHRENHEIT));
assertThatItemHasState(CHANNEL_TIME_TO_TARGET, new QuantityType<>(0, Units.MINUTE));
assertThatItemHasState(CHANNEL_USING_EMERGENCY_HEAT, OFF);
assertThatAllItemStatesAreNotNull();
}
@Test
public void incompleteThermostatUpdate() throws IOException {
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
assertThatAllItemStatesAreNotNull();
putStreamingEventData(fromFile(INCOMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.UNKNOWN)));
assertThatAllItemStatesAreNull();
}
@Test
public void thermostatGone() throws IOException {
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(EMPTY_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.OFFLINE)));
assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.GONE));
}
@Test
public void channelRefresh() throws IOException {
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
assertThatAllItemStatesAreNotNull();
updateAllItemStatesToNull();
assertThatAllItemStatesAreNull();
refreshAllChannels();
assertThatAllItemStatesAreNotNull();
}
@Test
public void handleFanTimerActiveCommands() throws IOException {
handleCommand(CHANNEL_FAN_TIMER_ACTIVE, ON);
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_active", "true");
handleCommand(CHANNEL_FAN_TIMER_ACTIVE, OFF);
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_active", "false");
handleCommand(CHANNEL_FAN_TIMER_ACTIVE, ON);
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_active", "true");
}
@Test
public void handleFanTimerDurationCommands() throws IOException {
int[] durations = { 15, 30, 45, 60, 120, 240, 480, 960, 15 };
for (int duration : durations) {
handleCommand(CHANNEL_FAN_TIMER_DURATION, new QuantityType<>(duration, Units.MINUTE));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_duration", String.valueOf(duration));
}
}
@Test
public void handleMaxSetPointCelsiusCommands() throws IOException {
celsiusCommandsTest(CHANNEL_MAX_SET_POINT, "target_temperature_high_c");
}
@Test
public void handleMaxSetPointFahrenheitCommands() throws IOException {
fahrenheitCommandsTest(CHANNEL_MAX_SET_POINT, "target_temperature_high_f");
}
@Test
public void handleMinSetPointCelsiusCommands() throws IOException {
celsiusCommandsTest(CHANNEL_MIN_SET_POINT, "target_temperature_low_c");
}
@Test
public void handleMinSetPointFahrenheitCommands() throws IOException {
fahrenheitCommandsTest(CHANNEL_MIN_SET_POINT, "target_temperature_low_f");
}
@Test
public void handleChannelModeCommands() throws IOException {
handleCommand(CHANNEL_MODE, new StringType("HEAT"));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "heat");
handleCommand(CHANNEL_MODE, new StringType("COOL"));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "cool");
handleCommand(CHANNEL_MODE, new StringType("HEAT_COOL"));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "heat-cool");
handleCommand(CHANNEL_MODE, new StringType("ECO"));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "eco");
handleCommand(CHANNEL_MODE, new StringType("OFF"));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "off");
handleCommand(CHANNEL_MODE, new StringType("HEAT"));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "heat");
}
@Test
public void handleSetPointCelsiusCommands() throws IOException {
celsiusCommandsTest(CHANNEL_SET_POINT, "target_temperature_c");
}
@Test
public void handleSetPointFahrenheitCommands() throws IOException {
fahrenheitCommandsTest(CHANNEL_SET_POINT, "target_temperature_f");
}
private void celsiusCommandsTest(String channelId, String apiPropertyName) throws IOException {
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, CELSIUS));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
handleCommand(channelId, new QuantityType<>(20, CELSIUS));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "20.0");
handleCommand(channelId, new QuantityType<>(21.123, CELSIUS));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "21.0");
handleCommand(channelId, new QuantityType<>(22.541, CELSIUS));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "22.5");
handleCommand(channelId, new QuantityType<>(23.74, CELSIUS));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "23.5");
handleCommand(channelId, new QuantityType<>(23.75, CELSIUS));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "24.0");
handleCommand(channelId, new QuantityType<>(70, FAHRENHEIT));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "21.0");
}
private void fahrenheitCommandsTest(String channelId, String apiPropertyName) throws IOException {
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, FAHRENHEIT));
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
handleCommand(channelId, new QuantityType<>(70, FAHRENHEIT));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "70");
handleCommand(channelId, new QuantityType<>(71.123, FAHRENHEIT));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "71");
handleCommand(channelId, new QuantityType<>(71.541, FAHRENHEIT));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "72");
handleCommand(channelId, new QuantityType<>(72.74, FAHRENHEIT));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "73");
handleCommand(channelId, new QuantityType<>(73.75, FAHRENHEIT));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "74");
handleCommand(channelId, new QuantityType<>(21, CELSIUS));
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "70");
}
}

View File

@@ -1,367 +0,0 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nest.internal.wwn.handler;
import static java.util.Map.entry;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNot.not;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.*;
import static org.openhab.binding.nest.internal.wwn.rest.WWNStreamingRestClient.PUT;
import java.io.IOException;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
import java.util.function.Function;
import javax.ws.rs.client.ClientBuilder;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.ArgumentMatchers;
import org.openhab.binding.nest.internal.wwn.config.WWNAccountConfiguration;
import org.openhab.binding.nest.internal.wwn.test.WWNTestAccountHandler;
import org.openhab.binding.nest.internal.wwn.test.WWNTestApiServlet;
import org.openhab.binding.nest.internal.wwn.test.WWNTestHandlerFactory;
import org.openhab.binding.nest.internal.wwn.test.WWNTestServer;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemFactory;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.items.events.ItemEventFactory;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.test.TestPortUtil;
import org.openhab.core.test.java.JavaOSGiTest;
import org.openhab.core.test.storage.VolatileStorageService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ManagedThingProvider;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingProvider;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.openhab.core.thing.binding.ThingTypeProvider;
import org.openhab.core.thing.binding.builder.BridgeBuilder;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.link.ItemChannelLink;
import org.openhab.core.thing.link.ManagedItemChannelLinkProvider;
import org.openhab.core.thing.type.ChannelDefinition;
import org.openhab.core.thing.type.ChannelGroupDefinition;
import org.openhab.core.thing.type.ChannelGroupType;
import org.openhab.core.thing.type.ChannelGroupTypeRegistry;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeRegistry;
import org.openhab.core.thing.type.ThingType;
import org.openhab.core.thing.type.ThingTypeRegistry;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.jaxrs.client.SseEventSourceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link WWNThingHandlerOSGiTest} is an abstract base class for Nest OSGi based tests.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public abstract class WWNThingHandlerOSGiTest extends JavaOSGiTest {
private static final String SERVER_HOST = "127.0.0.1";
private static final int SERVER_PORT = TestPortUtil.findFreePort();
private static final int SERVER_TIMEOUT = -1;
private static final String REDIRECT_URL = "http://" + SERVER_HOST + ":" + SERVER_PORT;
private final Logger logger = LoggerFactory.getLogger(WWNThingHandlerOSGiTest.class);
private static @Nullable WWNTestServer server;
private static WWNTestApiServlet servlet = new WWNTestApiServlet();
private @NonNullByDefault({}) ChannelTypeRegistry channelTypeRegistry;
private @NonNullByDefault({}) ChannelGroupTypeRegistry channelGroupTypeRegistry;
private @NonNullByDefault({}) ItemFactory itemFactory;
private @NonNullByDefault({}) ItemRegistry itemRegistry;
private @NonNullByDefault({}) EventPublisher eventPublisher;
private @NonNullByDefault({}) ManagedThingProvider managedThingProvider;
private @NonNullByDefault({}) ThingTypeRegistry thingTypeRegistry;
private @NonNullByDefault({}) ManagedItemChannelLinkProvider managedItemChannelLinkProvider;
private @NonNullByDefault({}) VolatileStorageService volatileStorageService = new VolatileStorageService();
protected @NonNullByDefault({}) Bridge bridge;
protected @NonNullByDefault({}) WWNTestAccountHandler bridgeHandler;
protected @NonNullByDefault({}) Thing thing;
protected @NonNullByDefault({}) WWNBaseHandler<?> thingHandler;
private Class<? extends WWNBaseHandler<?>> thingClass;
private @NonNullByDefault({}) WWNTestHandlerFactory nestTestHandlerFactory;
private @NonNullByDefault({}) ClientBuilder clientBuilder;
private @NonNullByDefault({}) SseEventSourceFactory eventSourceFactory;
public WWNThingHandlerOSGiTest(Class<? extends WWNBaseHandler<?>> thingClass) {
this.thingClass = thingClass;
}
@BeforeAll
public static void setUpClass() throws Exception {
ServletHolder holder = new ServletHolder(servlet);
server = new WWNTestServer(SERVER_HOST, SERVER_PORT, SERVER_TIMEOUT, holder);
server.startServer();
}
@AfterAll
public static void tearDownClass() throws Exception {
WWNTestServer testServer = server;
if (testServer != null) {
testServer.stopServer();
}
}
@BeforeEach
public void setUp() throws ItemNotFoundException {
registerService(volatileStorageService);
managedThingProvider = Objects.requireNonNull(getService(ThingProvider.class, ManagedThingProvider.class),
"Could not get ManagedThingProvider");
thingTypeRegistry = Objects.requireNonNull(getService(ThingTypeRegistry.class),
"Could not get ThingTypeRegistry");
channelTypeRegistry = Objects.requireNonNull(getService(ChannelTypeRegistry.class),
"Could not get ChannelTypeRegistry");
channelGroupTypeRegistry = Objects.requireNonNull(getService(ChannelGroupTypeRegistry.class),
"Could not get ChannelGroupTypeRegistry");
eventPublisher = Objects.requireNonNull(getService(EventPublisher.class), "Could not get EventPublisher");
itemFactory = Objects.requireNonNull(getService(ItemFactory.class), "Could not get ItemFactory");
itemRegistry = Objects.requireNonNull(getService(ItemRegistry.class), "Could not get ItemRegistry");
managedItemChannelLinkProvider = Objects.requireNonNull(getService(ManagedItemChannelLinkProvider.class),
"Could not get ManagedItemChannelLinkProvider");
clientBuilder = Objects.requireNonNull(getService(ClientBuilder.class), "Could not get ClientBuilder");
eventSourceFactory = Objects.requireNonNull(getService(SseEventSourceFactory.class),
"Could not get SseEventSourceFactory");
ComponentContext componentContext = mock(ComponentContext.class);
when(componentContext.getBundleContext()).thenReturn(bundleContext);
nestTestHandlerFactory = new WWNTestHandlerFactory(clientBuilder, eventSourceFactory);
nestTestHandlerFactory.activate(componentContext,
Map.of(WWNTestHandlerFactory.REDIRECT_URL_CONFIG_PROPERTY, REDIRECT_URL));
registerService(nestTestHandlerFactory);
ThingTypeProvider thingTypeProvider = mock(ThingTypeProvider.class);
when(thingTypeProvider.getThingType(ArgumentMatchers.any(ThingTypeUID.class), nullable(Locale.class)))
.thenReturn(mock(ThingType.class));
registerService(thingTypeProvider);
nestTestHandlerFactory = Objects.requireNonNull(
getService(ThingHandlerFactory.class, WWNTestHandlerFactory.class),
"Could not get NestTestHandlerFactory");
bridge = buildBridge();
thing = buildThing(bridge);
bridgeHandler = addThing(bridge, WWNTestAccountHandler.class);
thingHandler = addThing(thing, thingClass);
createAndLinkItems();
assertThatAllItemStatesAreNull();
}
@AfterEach
public void tearDown() {
servlet.reset();
servlet.closeConnections();
if (thing != null) {
managedThingProvider.remove(thing.getUID());
}
if (bridge != null) {
managedThingProvider.remove(bridge.getUID());
}
unregisterService(volatileStorageService);
}
protected Bridge buildBridge() {
Map<String, Object> properties = Map.ofEntries( //
entry(WWNAccountConfiguration.ACCESS_TOKEN,
"c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc"),
entry(WWNAccountConfiguration.PINCODE, "64P2XRYT"),
entry(WWNAccountConfiguration.PRODUCT_ID, "8fdf9885-ca07-4252-1aa3-f3d5ca9589e0"),
entry(WWNAccountConfiguration.PRODUCT_SECRET, "QITLR3iyUlWaj9dbvCxsCKp4f"));
return BridgeBuilder.create(WWNTestAccountHandler.THING_TYPE_TEST_BRIDGE, "test_account")
.withLabel("Test Account").withConfiguration(new Configuration(properties)).build();
}
protected abstract Thing buildThing(Bridge bridge);
protected List<Channel> buildChannels(ThingTypeUID thingTypeUID, ThingUID thingUID) {
waitForAssert(() -> assertThat(thingTypeRegistry.getThingType(thingTypeUID), notNullValue()));
ThingType thingType = thingTypeRegistry.getThingType(thingTypeUID);
List<Channel> channels = new ArrayList<>(buildChannels(thingUID, thingType.getChannelDefinitions(), id -> id));
for (ChannelGroupDefinition channelGroupDefinition : thingType.getChannelGroupDefinitions()) {
ChannelGroupType channelGroupType = channelGroupTypeRegistry
.getChannelGroupType(channelGroupDefinition.getTypeUID());
String groupId = channelGroupDefinition.getId();
if (channelGroupType != null) {
channels.addAll(
buildChannels(thingUID, channelGroupType.getChannelDefinitions(), id -> groupId + "#" + id));
}
}
channels.sort((Channel c1, Channel c2) -> c1.getUID().getId().compareTo(c2.getUID().getId()));
return channels;
}
protected List<Channel> buildChannels(ThingUID thingUID, List<ChannelDefinition> channelDefinitions,
Function<String, String> channelIdFunction) {
List<Channel> result = new ArrayList<>();
for (ChannelDefinition channelDefinition : channelDefinitions) {
ChannelType channelType = channelTypeRegistry.getChannelType(channelDefinition.getChannelTypeUID());
if (channelType != null) {
result.add(ChannelBuilder
.create(new ChannelUID(thingUID, channelIdFunction.apply(channelDefinition.getId())),
channelType.getItemType())
.build());
}
}
return result;
}
@SuppressWarnings("unchecked")
protected <T> T addThing(Thing thing, Class<T> thingHandlerClass) {
assertThat(thing.getHandler(), is(nullValue()));
managedThingProvider.add(thing);
waitForAssert(() -> assertThat(thing.getHandler(), notNullValue()));
assertThat(thing.getConfiguration(), is(notNullValue()));
assertThat(thing.getHandler(), is(instanceOf(thingHandlerClass)));
return (T) thing.getHandler();
}
protected String getThingId() {
return thing.getUID().getId();
}
protected ThingUID getThingUID() {
return thing.getUID();
}
protected void putStreamingEventData(String json) throws IOException {
String singleLineJson = json.replaceAll("\n\r\\s+", "").replaceAll("\n\\s+", "").replaceAll("\n\r", "")
.replaceAll("\n", "");
servlet.queueEvent(PUT, singleLineJson);
}
protected void createAndLinkItems() {
thing.getChannels().forEach(c -> {
String itemName = getItemName(c.getUID().getId());
Item item = itemFactory.createItem(c.getAcceptedItemType(), itemName);
if (item != null) {
itemRegistry.add(item);
}
managedItemChannelLinkProvider.add(new ItemChannelLink(itemName, c.getUID()));
});
}
protected void assertThatItemHasState(String channelId, State state) {
waitForAssert(() -> assertThat("Wrong state for item of channel '" + channelId + "' ", getItemState(channelId),
is(state)));
}
protected void assertThatItemHasNotState(String channelId, State state) {
waitForAssert(() -> assertThat("Wrong state for item of channel '" + channelId + "' ", getItemState(channelId),
is(not(state))));
}
protected void assertThatAllItemStatesAreNull() {
thing.getChannels().forEach(c -> assertThatItemHasState(c.getUID().getId(), UnDefType.NULL));
}
protected void assertThatAllItemStatesAreNotNull() {
thing.getChannels().forEach(c -> assertThatItemHasNotState(c.getUID().getId(), UnDefType.NULL));
}
protected ChannelUID getChannelUID(String channelId) {
return new ChannelUID(getThingUID(), channelId);
}
protected String getItemName(String channelId) {
return getThingId() + "_" + channelId.replaceAll("#", "_");
}
private State getItemState(String channelId) {
String itemName = getItemName(channelId);
try {
return itemRegistry.getItem(itemName).getState();
} catch (ItemNotFoundException e) {
throw new AssertionError("Item with name '" + itemName + "' not found");
}
}
protected void logItemStates() {
thing.getChannels().forEach(c -> {
String channelId = c.getUID().getId();
String itemName = getItemName(channelId);
logger.debug("{} = {}", itemName, getItemState(channelId));
});
}
protected void updateAllItemStatesToNull() {
thing.getChannels().forEach(c -> updateItemState(c.getUID().getId(), UnDefType.NULL));
}
protected void refreshAllChannels() {
thing.getChannels().forEach(c -> thingHandler.handleCommand(c.getUID(), RefreshType.REFRESH));
}
protected void handleCommand(String channelId, Command command) {
thingHandler.handleCommand(getChannelUID(channelId), command);
}
protected void updateItemState(String channelId, State state) {
String itemName = getItemName(channelId);
eventPublisher.post(ItemEventFactory.createStateEvent(itemName, state));
}
protected void assertNestApiPropertyState(String nestId, String propertyName, String state) {
waitForAssert(() -> assertThat(servlet.getNestIdPropertyState(nestId, propertyName), is(state)));
}
public static DateTimeType parseDateTimeType(String text) {
try {
return new DateTimeType(Instant.parse(text).atZone(TimeZone.getDefault().toZoneId()));
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("Invalid date time argument: " + text, e);
}
}
}

View File

@@ -1,67 +0,0 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nest.internal.wwn.test;
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.BINDING_ID;
import java.util.Properties;
import java.util.Set;
import javax.ws.rs.client.ClientBuilder;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.nest.internal.wwn.exceptions.InvalidWWNAccessTokenException;
import org.openhab.binding.nest.internal.wwn.handler.WWNAccountHandler;
import org.openhab.binding.nest.internal.wwn.handler.WWNRedirectUrlSupplier;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingTypeUID;
import org.osgi.service.jaxrs.client.SseEventSourceFactory;
/**
* The {@link WWNTestAccountHandler} is a {@link WWNAccountHandler} modified for testing. Using the
* {@link NestTestRedirectUrlSupplier} it will always connect to same provided {@link #redirectUrl}.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public class WWNTestAccountHandler extends WWNAccountHandler {
class NestTestRedirectUrlSupplier extends WWNRedirectUrlSupplier {
NestTestRedirectUrlSupplier(Properties httpHeaders) {
super(httpHeaders);
this.cachedUrl = redirectUrl;
}
@Override
public void resetCache() {
// Skip resetting the URL so the test server keeps being used
}
}
public static final ThingTypeUID THING_TYPE_TEST_BRIDGE = new ThingTypeUID(BINDING_ID, "wwn_test_account");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_TEST_BRIDGE);
private String redirectUrl;
public WWNTestAccountHandler(Bridge bridge, ClientBuilder clientBuilder, SseEventSourceFactory eventSourceFactory,
String redirectUrl) {
super(bridge, clientBuilder, eventSourceFactory);
this.redirectUrl = redirectUrl;
}
@Override
protected WWNRedirectUrlSupplier createRedirectUrlSupplier() throws InvalidWWNAccessTokenException {
return new NestTestRedirectUrlSupplier(getHttpHeaders());
}
}

View File

@@ -1,223 +0,0 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nest.internal.wwn.test;
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
import static org.openhab.binding.nest.internal.wwn.rest.WWNStreamingRestClient.*;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
/**
* The {@link WWNTestApiServlet} mocks the Nest API during tests.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public class WWNTestApiServlet extends HttpServlet {
private static final long serialVersionUID = -5414910055159062745L;
private static final String NEW_LINE = "\n";
private static final String UPDATE_PATHS[] = { NEST_CAMERA_UPDATE_PATH, NEST_SMOKE_ALARM_UPDATE_PATH,
NEST_STRUCTURE_UPDATE_PATH, NEST_THERMOSTAT_UPDATE_PATH };
private final Logger logger = LoggerFactory.getLogger(WWNTestApiServlet.class);
private static class SseEvent {
private String name;
private @Nullable String data;
public SseEvent(String name) {
this.name = name;
}
public SseEvent(String name, String data) {
this.name = name;
this.data = data;
}
public @Nullable String getData() {
return data;
}
public String getName() {
return name;
}
}
private final Map<String, Map<String, String>> nestIdPropertiesMap = new ConcurrentHashMap<>();
private final Map<Thread, Queue<SseEvent>> listenerQueues = new ConcurrentHashMap<>();
private final ThreadLocal<PrintWriter> threadLocalWriter = new ThreadLocal<>();
private final Gson gson = new GsonBuilder().create();
public void closeConnections() {
Set<Thread> threads = listenerQueues.keySet();
listenerQueues.clear();
threads.forEach(Thread::interrupt);
}
public void reset() {
nestIdPropertiesMap.clear();
}
public void queueEvent(String eventName) {
SseEvent event = new SseEvent(eventName);
listenerQueues.forEach((thread, queue) -> queue.add(event));
}
public void queueEvent(String eventName, String data) {
SseEvent event = new SseEvent(eventName, data);
listenerQueues.forEach((thread, queue) -> queue.add(event));
}
@SuppressWarnings("resource")
private void writeEvent(SseEvent event) {
logger.debug("Writing {} event", event.getName());
PrintWriter writer = threadLocalWriter.get();
writer.write("event: ");
writer.write(event.getName());
writer.write(NEW_LINE);
String eventData = event.getData();
if (eventData != null) {
for (String dataLine : eventData.split(NEW_LINE)) {
writer.write("data: ");
writer.write(dataLine);
writer.write(NEW_LINE);
}
}
writer.write(NEW_LINE);
writer.flush();
}
private void writeEvent(String eventName) {
writeEvent(new SseEvent(eventName));
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ArrayBlockingQueue<SseEvent> queue = new ArrayBlockingQueue<>(10);
listenerQueues.put(Thread.currentThread(), queue);
response.setContentType("text/event-stream");
response.setCharacterEncoding("UTF-8");
response.flushBuffer();
logger.debug("Opened event stream to {}:{}", request.getRemoteHost(), request.getRemotePort());
PrintWriter writer = response.getWriter();
threadLocalWriter.set(writer);
writeEvent(OPEN);
while (listenerQueues.containsKey(Thread.currentThread()) && !writer.checkError()) {
try {
SseEvent event = queue.poll(KEEP_ALIVE_MILLIS, TimeUnit.MILLISECONDS);
if (event != null) {
writeEvent(event);
} else {
writeEvent(KEEP_ALIVE);
}
} catch (InterruptedException e) {
logger.debug("Evaluating loop conditions after interrupt");
}
}
listenerQueues.remove(Thread.currentThread());
threadLocalWriter.remove();
writer.close();
logger.debug("Closed event stream to {}:{}", request.getRemoteHost(), request.getRemotePort());
}
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
logger.debug("Received put request: {}", request);
String uri = request.getRequestURI();
String nestId = getNestIdFromURI(uri);
if (nestId == null) {
logger.error("Unsupported URI: {}", uri);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
InputStreamReader reader = new InputStreamReader(request.getInputStream());
Map<String, String> propertiesUpdate = gson.fromJson(reader, new TypeToken<Map<String, String>>() {
}.getType());
Map<String, String> properties = getOrCreateProperties(nestId);
properties.putAll(propertiesUpdate);
gson.toJson(propertiesUpdate, response.getWriter());
response.setStatus(HttpServletResponse.SC_OK);
}
private @Nullable String getNestIdFromURI(@Nullable String uri) {
if (uri == null) {
return null;
}
for (String updatePath : UPDATE_PATHS) {
if (uri.startsWith(updatePath)) {
return uri.replaceAll(updatePath, "");
}
}
return null;
}
private Map<String, String> getOrCreateProperties(String nestId) {
Map<String, String> properties = nestIdPropertiesMap.get(nestId);
if (properties == null) {
properties = new HashMap<>();
nestIdPropertiesMap.put(nestId, properties);
}
return properties;
}
public @Nullable String getNestIdPropertyState(String nestId, String propertyName) {
Map<String, String> properties = nestIdPropertiesMap.get(nestId);
return properties == null ? null : properties.get(propertyName);
}
}

View File

@@ -1,118 +0,0 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nest.internal.wwn.test;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import javax.ws.rs.client.ClientBuilder;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nest.internal.wwn.discovery.WWNDiscoveryService;
import org.openhab.binding.nest.internal.wwn.handler.WWNAccountHandler;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.jaxrs.client.SseEventSourceFactory;
/**
* The {@link WWNTestHandlerFactory} is responsible for creating test things and thing handlers.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public class WWNTestHandlerFactory extends BaseThingHandlerFactory implements ThingHandlerFactory {
public static final String REDIRECT_URL_CONFIG_PROPERTY = "redirect.url";
private final ClientBuilder clientBuilder;
private final SseEventSourceFactory eventSourceFactory;
private final Map<ThingUID, ServiceRegistration<?>> discoveryService = new HashMap<>();
private String redirectUrl = "http://localhost";
@Activate
public WWNTestHandlerFactory(@Reference ClientBuilder clientBuilder,
@Reference SseEventSourceFactory eventSourceFactory) {
this.clientBuilder = clientBuilder;
this.eventSourceFactory = eventSourceFactory;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return WWNTestAccountHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID);
}
@Activate
public void activate(ComponentContext componentContext, Map<String, Object> config) {
super.activate(componentContext);
modified(config);
}
@Modified
public void modified(Map<String, Object> config) {
String url = (String) config.get(REDIRECT_URL_CONFIG_PROPERTY);
if (url != null) {
this.redirectUrl = url;
}
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(WWNTestAccountHandler.THING_TYPE_TEST_BRIDGE)) {
WWNTestAccountHandler handler = new WWNTestAccountHandler((Bridge) thing, clientBuilder, eventSourceFactory,
redirectUrl);
WWNDiscoveryService service = new WWNDiscoveryService();
service.setThingHandler(handler);
// Register the discovery service.
discoveryService.put(handler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), service, new Hashtable<>()));
return handler;
}
return null;
}
/**
* Removes the handler for the specific thing. This also handles disabling the discovery
* service when the bridge is removed.
*/
@Override
protected void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof WWNAccountHandler) {
ServiceRegistration<?> registration = discoveryService.get(thingHandler.getThing().getUID());
if (registration != null) {
// Unregister the discovery service.
WWNDiscoveryService service = (WWNDiscoveryService) bundleContext
.getService(registration.getReference());
service.deactivate();
registration.unregister();
discoveryService.remove(thingHandler.getThing().getUID());
}
}
super.removeHandler(thingHandler);
}
}

View File

@@ -1,73 +0,0 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nest.internal.wwn.test;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
/**
* Embedded jetty server used in the tests.
*
* Based on {@code TestServer} of the FS Internet Radio Binding.
*
* @author Velin Yordanov - Initial contribution
* @author Wouter Born - Increase test coverage
*/
@NonNullByDefault
public class WWNTestServer {
private @Nullable Server server;
private String host;
private int port;
private int timeout;
private ServletHolder servletHolder;
public WWNTestServer(String host, int port, int timeout, ServletHolder servletHolder) {
this.host = host;
this.port = port;
this.timeout = timeout;
this.servletHolder = servletHolder;
}
public void startServer() throws Exception {
Server server = new Server();
ServletHandler handler = new ServletHandler();
handler.addServletWithMapping(servletHolder, "/*");
server.setHandler(handler);
// HTTP connector
ServerConnector http = new ServerConnector(server);
http.setHost(host);
http.setPort(port);
http.setIdleTimeout(timeout);
server.addConnector(http);
server.start();
this.server = server;
}
public void stopServer() throws Exception {
Server server = this.server;
if (server == null) {
return;
}
server.stop();
this.server = null;
}
}

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="nest"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="test_account">
<label>Test Account</label>
<description>An account for testing the Nest binding</description>
<config-description-ref uri="thing-type:nest:account"/>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -1,4 +0,0 @@
{
"access_token": "access_token",
"expires_in": 315360000
}

View File

@@ -1,33 +0,0 @@
{
"app_url": "https://camera_app_url",
"device_id": "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
"is_audio_input_enabled": true,
"is_online": true,
"is_public_share_enabled": false,
"is_streaming": false,
"is_video_history_enabled": false,
"last_event": {
"activity_zone_ids": [
"id1",
"id2"
],
"animated_image_url": "https://last_event_animated_image_url",
"app_url": "https://last_event_app_url",
"end_time": "2017-01-22T07:40:38.680Z",
"has_motion": true,
"has_person": false,
"has_sound": false,
"image_url": "https://last_event_image_url",
"start_time": "2017-01-22T07:40:19.020Z",
"urls_expire_time": "2017-02-05T07:40:19.020Z",
"web_url": "https://last_event_web_url"
},
"last_is_online_change": "2017-01-22T08:19:20.000Z",
"name": "Upstairs",
"name_long": "Upstairs Camera",
"snapshot_url": "https://camera_snapshot_url",
"software_version": "205-600052",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"web_url": "https://camera_web_url",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
}

View File

@@ -1,6 +0,0 @@
{
"error": "blocked",
"type": "https://developer.nest.com/documentation/cloud/error-messages#blocked",
"message": "blocked",
"instance": "bb514046-edc9-4bca-8239-f7a3cfb0925a"
}

View File

@@ -1,17 +0,0 @@
{
"battery_health": "ok",
"co_alarm_state": "ok",
"device_id": "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV",
"is_manual_test_active": false,
"is_online": true,
"last_connection": "2017-02-02T20:53:05.338Z",
"locale": "en-US",
"name": "Downstairs",
"name_long": "Downstairs Nest Protect",
"smoke_alarm_state": "ok",
"software_version": "3.1rc9",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"ui_color_state": "green",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg",
"where_name": "Downstairs"
}

View File

@@ -1,112 +0,0 @@
{
"smoke_co_alarms": [
"p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
"p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
"p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
"p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV"
],
"name": "Home",
"country_code": "US",
"postal_code": "98056",
"time_zone": "America/Los_Angeles",
"away": "home",
"thermostats": [
"G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV"
],
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"rhr_enrollment": false,
"co_alarm_state": "ok",
"smoke_alarm_state": "ok",
"eta_begin": "2017-02-02T03:10:08.000Z",
"wwn_security_state": "ok",
"wheres": {
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg",
"name": "Basement"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ",
"name": "Bedroom"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw",
"name": "Den"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g",
"name": "Dining Room"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg",
"name": "Downstairs"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg",
"name": "Entryway"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA",
"name": "Family Room"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw",
"name": "Hallway"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA",
"name": "Kids Room"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA",
"name": "Kitchen"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
"name": "Living Room"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw",
"name": "Master Bedroom"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q",
"name": "Office"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA",
"name": "Upstairs"
},
"6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ": {
"where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ",
"name": "Downstairs Kitchen"
},
"qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ": {
"where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ",
"name": "Garage"
},
"8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ": {
"where_id": "8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ",
"name": "Frog"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA",
"name": "Backyard"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA",
"name": "Driveway"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g",
"name": "Front Yard"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ": {
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ",
"name": "Outside"
}
},
"cameras": [
"_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
"VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ"
]
}

View File

@@ -1,51 +0,0 @@
{
"ambient_temperature_c": 19.0,
"ambient_temperature_f": 66,
"away_temperature_high_c": 24.0,
"away_temperature_high_f": 76,
"away_temperature_low_c": 12.5,
"away_temperature_low_f": 55,
"can_cool": false,
"can_heat": true,
"device_id": "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV",
"eco_temperature_high_c": 24.0,
"eco_temperature_high_f": 76,
"eco_temperature_low_c": 12.5,
"eco_temperature_low_f": 55,
"fan_timer_active": false,
"fan_timer_duration": 15,
"fan_timer_timeout": "1970-01-01T00:00:00.000Z",
"has_fan": true,
"has_leaf": true,
"humidity": 25,
"hvac_mode": "heat",
"hvac_state": "off",
"is_locked": false,
"is_online": true,
"is_using_emergency_heat": false,
"label": "Living Room",
"last_connection": "2017-02-02T21:00:06.000Z",
"locale": "en-GB",
"locked_temp_max_c": 22.0,
"locked_temp_max_f": 72,
"locked_temp_min_c": 20.0,
"locked_temp_min_f": 68,
"name": "Living Room (Living Room)",
"name_long": "Living Room Thermostat (Living Room)",
"previous_hvac_mode": "",
"software_version": "5.6-7",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"sunlight_correction_active": false,
"sunlight_correction_enabled": true,
"target_temperature_c": 15.5,
"target_temperature_f": 60,
"target_temperature_high_c": 24.0,
"target_temperature_high_f": 75,
"target_temperature_low_c": 20.0,
"target_temperature_low_f": 68,
"temperature_scale": "C",
"time_to_target": "~0",
"time_to_target_training": "ready",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
"where_name": "Living Room"
}

View File

@@ -1,307 +0,0 @@
{
"devices": {
"cameras": {
"_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ": {
"app_url": "https://camera_app_url",
"device_id": "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
"is_audio_input_enabled": true,
"is_online": false,
"is_public_share_enabled": false,
"is_streaming": false,
"is_video_history_enabled": false,
"last_event": {
"activity_zone_ids": [
"id1",
"id2"
],
"animated_image_url": "https://last_event_animated_image_url",
"app_url": "https://last_event_app_url",
"end_time": "2017-01-22T07:40:38.680Z",
"has_motion": true,
"has_person": false,
"has_sound": false,
"image_url": "https://last_event_image_url",
"start_time": "2017-01-22T07:40:19.020Z",
"urls_expire_time": "2017-02-05T07:40:19.020Z",
"web_url": "https://last_event_web_url"
},
"last_is_online_change": "2017-01-22T08:19:20.000Z",
"name": "Upstairs",
"name_long": "Upstairs Camera",
"snapshot_url": "https://camera_snapshot_url",
"software_version": "205-600052",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"web_url": "https://camera_web_url",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
},
"VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ": {
"app_url": "nestmobile://cameras/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
"device_id": "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ",
"is_audio_input_enabled": true,
"is_online": false,
"is_public_share_enabled": false,
"is_streaming": false,
"is_video_history_enabled": false,
"last_event": {
"end_time": "2016-11-20T07:02:46.860Z",
"has_motion": true,
"has_person": false,
"has_sound": false,
"start_time": "2016-11-20T07:02:27.260Z"
},
"last_is_online_change": "2016-11-20T07:03:42.000Z",
"name": "Garage",
"name_long": "Garage Camera",
"snapshot_url": "https://www.dropcam.com/api/wwn.get_snapshot/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
"software_version": "205-600052",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"web_url": "https://home.nest.com/cameras/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
"where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ"
}
},
"smoke_co_alarms": {
"p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV": {
"battery_health": "ok",
"co_alarm_state": "ok",
"device_id": "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV",
"is_manual_test_active": false,
"is_online": true,
"last_connection": "2017-02-02T20:53:05.338Z",
"locale": "en-US",
"name": "Downstairs",
"name_long": "Downstairs Nest Protect",
"smoke_alarm_state": "ok",
"software_version": "3.1rc9",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"ui_color_state": "green",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg",
"where_name": "Downstairs"
},
"p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV": {
"battery_health": "ok",
"co_alarm_state": "ok",
"device_id": "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
"is_manual_test_active": false,
"is_online": true,
"last_connection": "2017-02-02T20:35:50.051Z",
"last_manual_test_time": "1970-01-01T00:00:00.000Z",
"locale": "en-US",
"name": "Upstairs",
"name_long": "Upstairs Nest Protect",
"smoke_alarm_state": "ok",
"software_version": "3.1rc9",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"ui_color_state": "green",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA",
"where_name": "Upstairs"
},
"p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV": {
"battery_health": "ok",
"co_alarm_state": "ok",
"device_id": "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
"is_manual_test_active": false,
"is_online": true,
"last_connection": "2017-02-02T11:04:18.804Z",
"last_manual_test_time": "1970-01-01T00:00:00.000Z",
"locale": "en-US",
"name": "Downstairs Kitchen",
"name_long": "Downstairs Kitchen Nest Protect",
"smoke_alarm_state": "ok",
"software_version": "3.1rc9",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"ui_color_state": "green",
"where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ",
"where_name": "Downstairs Kitchen"
},
"p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV": {
"battery_health": "ok",
"co_alarm_state": "ok",
"device_id": "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
"is_manual_test_active": false,
"is_online": true,
"last_connection": "2017-02-02T13:30:34.187Z",
"last_manual_test_time": "1970-01-01T00:00:00.000Z",
"locale": "en-US",
"name": "Living Room",
"name_long": "Living Room Nest Protect",
"smoke_alarm_state": "ok",
"software_version": "3.1rc9",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"ui_color_state": "green",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
"where_name": "Living Room"
}
},
"thermostats": {
"G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV": {
"ambient_temperature_c": 19.0,
"ambient_temperature_f": 66,
"away_temperature_high_c": 24.0,
"away_temperature_high_f": 76,
"away_temperature_low_c": 12.5,
"away_temperature_low_f": 55,
"can_cool": false,
"can_heat": true,
"device_id": "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV",
"eco_temperature_high_c": 24.0,
"eco_temperature_high_f": 76,
"eco_temperature_low_c": 12.5,
"eco_temperature_low_f": 55,
"fan_timer_active": false,
"fan_timer_duration": 15,
"fan_timer_timeout": "1970-01-01T00:00:00.000Z",
"has_fan": true,
"has_leaf": true,
"humidity": 25,
"hvac_mode": "heat",
"hvac_state": "off",
"is_locked": false,
"is_online": true,
"is_using_emergency_heat": false,
"label": "Living Room",
"last_connection": "2017-02-02T21:00:06.000Z",
"locale": "en-GB",
"locked_temp_max_c": 22.0,
"locked_temp_max_f": 72,
"locked_temp_min_c": 20.0,
"locked_temp_min_f": 68,
"name": "Living Room (Living Room)",
"name_long": "Living Room Thermostat (Living Room)",
"previous_hvac_mode": "",
"software_version": "5.6-7",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"sunlight_correction_active": false,
"sunlight_correction_enabled": true,
"target_temperature_c": 15.5,
"target_temperature_f": 60,
"target_temperature_high_c": 24.0,
"target_temperature_high_f": 75,
"target_temperature_low_c": 20.0,
"target_temperature_low_f": 68,
"temperature_scale": "C",
"time_to_target": "~0",
"time_to_target_training": "ready",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
"where_name": "Living Room"
}
}
},
"metadata": {
"access_token": "c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
"client_version": 1
},
"structures": {
"ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A": {
"away": "home",
"cameras": [
"_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
"VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ"
],
"co_alarm_state": "ok",
"country_code": "US",
"eta_begin": "2017-02-02T03:10:08.000Z",
"name": "Home",
"postal_code": "98056",
"rhr_enrollment": false,
"smoke_alarm_state": "ok",
"smoke_co_alarms": [
"p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
"p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
"p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
"p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV"
],
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"thermostats": [
"G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV"
],
"time_zone": "America/Los_Angeles",
"wheres": {
"6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ": {
"name": "Downstairs Kitchen",
"where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ"
},
"8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ": {
"name": "Frog",
"where_id": "8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ"
},
"qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ": {
"name": "Garage",
"where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA": {
"name": "Family Room",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA": {
"name": "Kitchen",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw": {
"name": "Hallway",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg": {
"name": "Basement",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA": {
"name": "Kids Room",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw": {
"name": "Master Bedroom",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg": {
"name": "Downstairs",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA": {
"name": "Driveway",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw": {
"name": "Den",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ": {
"name": "Bedroom",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg": {
"name": "Entryway",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA": {
"name": "Upstairs",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw": {
"name": "Living Room",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ": {
"name": "Outside",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g": {
"name": "Dining Room",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA": {
"name": "Backyard",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q": {
"name": "Office",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g": {
"name": "Front Yard",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g"
}
},
"wwn_security_state": "ok"
}
}
}

View File

@@ -1,45 +0,0 @@
{
"path": "/",
"data": {
"devices": {
"cameras": {
"_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ": {
"device_id": "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ"
},
"VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ": {
"device_id": "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ"
}
},
"smoke_co_alarms": {
"p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV": {
"device_id": "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV"
},
"p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV": {
"device_id": "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV"
},
"p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV": {
"device_id": "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV"
},
"p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV": {
"device_id": "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV"
}
},
"thermostats": {
"G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV": {
"device_id": "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV"
},
"OTQoylk2h5Ld3cfpm3esR0qx-iQr8PMV": {
"device_id": "OTQoylk2h5Ld3cfpm3esR0qx-iQr8PMV"
}
}
},
"structures": {
"ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A": {
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A"
},
"SylKI7puaWd56ILAcJ46LzmtdZc3L4wGzScs8yLc5zccJofBIW9KTJ": {
"structure_id": "SylKI7puaWd56ILAcJ46LzmtdZc3L4wGzScs8yLc5zccJofBIW9KTJ"
}
}
}
}

View File

@@ -1,314 +0,0 @@
{
"path": "/",
"data": {
"devices": {
"cameras": {
"_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ": {
"app_url": "https://camera_app_url",
"device_id": "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
"is_audio_input_enabled": true,
"is_online": true,
"is_public_share_enabled": false,
"is_streaming": false,
"is_video_history_enabled": false,
"last_event": {
"activity_zone_ids": [
"id1",
"id2"
],
"animated_image_url": "https://last_event_animated_image_url",
"app_url": "https://last_event_app_url",
"end_time": "2017-01-22T07:40:38.680Z",
"has_motion": true,
"has_person": false,
"has_sound": false,
"image_url": "https://last_event_image_url",
"start_time": "2017-01-22T07:40:19.020Z",
"urls_expire_time": "2017-02-05T07:40:19.020Z",
"web_url": "https://last_event_web_url"
},
"last_is_online_change": "2017-01-22T08:19:20.000Z",
"name": "Upstairs",
"name_long": "Upstairs Camera",
"public_share_url": "https://camera_public_share_url",
"snapshot_url": "https://camera_snapshot_url",
"software_version": "205-600052",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"web_url": "https://camera_web_url",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
},
"VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ": {
"app_url": "nestmobile://cameras/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
"device_id": "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ",
"is_audio_input_enabled": true,
"is_online": false,
"is_public_share_enabled": false,
"is_streaming": false,
"is_video_history_enabled": false,
"last_event": {
"end_time": "2016-11-20T07:02:46.860Z",
"has_motion": true,
"has_person": false,
"has_sound": false,
"start_time": "2016-11-20T07:02:27.260Z"
},
"last_is_online_change": "2016-11-20T07:03:42.000Z",
"name": "Garage",
"name_long": "Garage Camera",
"snapshot_url": "https://www.dropcam.com/api/wwn.get_snapshot/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
"software_version": "205-600052",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"web_url": "https://home.nest.com/cameras/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
"where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ"
}
},
"smoke_co_alarms": {
"p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV": {
"battery_health": "ok",
"co_alarm_state": "ok",
"device_id": "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV",
"is_manual_test_active": false,
"is_online": true,
"last_connection": "2017-02-02T20:53:05.338Z",
"last_manual_test_time": "2016-10-31T23:59:59.000Z",
"locale": "en-US",
"name": "Downstairs",
"name_long": "Downstairs Nest Protect",
"smoke_alarm_state": "ok",
"software_version": "3.1rc9",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"ui_color_state": "green",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg",
"where_name": "Downstairs"
},
"p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV": {
"battery_health": "ok",
"co_alarm_state": "ok",
"device_id": "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
"is_manual_test_active": false,
"is_online": true,
"last_connection": "2017-02-02T20:35:50.051Z",
"last_manual_test_time": "1970-01-01T00:00:00.000Z",
"locale": "en-US",
"name": "Upstairs",
"name_long": "Upstairs Nest Protect",
"smoke_alarm_state": "ok",
"software_version": "3.1rc9",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"ui_color_state": "green",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA",
"where_name": "Upstairs"
},
"p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV": {
"battery_health": "ok",
"co_alarm_state": "ok",
"device_id": "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
"is_manual_test_active": false,
"is_online": true,
"last_connection": "2017-02-02T11:04:18.804Z",
"last_manual_test_time": "1970-01-01T00:00:00.000Z",
"locale": "en-US",
"name": "Downstairs Kitchen",
"name_long": "Downstairs Kitchen Nest Protect",
"smoke_alarm_state": "ok",
"software_version": "3.1rc9",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"ui_color_state": "green",
"where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ",
"where_name": "Downstairs Kitchen"
},
"p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV": {
"battery_health": "ok",
"co_alarm_state": "ok",
"device_id": "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
"is_manual_test_active": false,
"is_online": true,
"last_connection": "2017-02-02T13:30:34.187Z",
"last_manual_test_time": "1970-01-01T00:00:00.000Z",
"locale": "en-US",
"name": "Living Room",
"name_long": "Living Room Nest Protect",
"smoke_alarm_state": "ok",
"software_version": "3.1rc9",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"ui_color_state": "green",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
"where_name": "Living Room"
}
},
"thermostats": {
"G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV": {
"ambient_temperature_c": 19.0,
"ambient_temperature_f": 66,
"away_temperature_high_c": 24.0,
"away_temperature_high_f": 76,
"away_temperature_low_c": 12.5,
"away_temperature_low_f": 55,
"can_cool": false,
"can_heat": true,
"device_id": "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV",
"eco_temperature_high_c": 24.0,
"eco_temperature_high_f": 76,
"eco_temperature_low_c": 12.5,
"eco_temperature_low_f": 55,
"fan_timer_active": false,
"fan_timer_duration": 15,
"fan_timer_timeout": "1970-01-01T00:00:00.000Z",
"has_fan": true,
"has_leaf": true,
"humidity": 25,
"hvac_mode": "heat",
"hvac_state": "off",
"is_locked": false,
"is_online": true,
"is_using_emergency_heat": false,
"label": "Living Room",
"last_connection": "2017-02-02T21:00:06.000Z",
"locale": "en-GB",
"locked_temp_max_c": 22.0,
"locked_temp_max_f": 72,
"locked_temp_min_c": 20.0,
"locked_temp_min_f": 68,
"name": "Living Room (Living Room)",
"name_long": "Living Room Thermostat (Living Room)",
"previous_hvac_mode": "",
"software_version": "5.6-7",
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"sunlight_correction_active": false,
"sunlight_correction_enabled": true,
"target_temperature_c": 15.5,
"target_temperature_f": 60,
"target_temperature_high_c": 24.0,
"target_temperature_high_f": 75,
"target_temperature_low_c": 20.0,
"target_temperature_low_f": 68,
"temperature_scale": "C",
"time_to_target": "~0",
"time_to_target_training": "ready",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
"where_name": "Living Room"
}
}
},
"metadata": {
"access_token": "c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
"client_version": 1
},
"structures": {
"ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A": {
"away": "home",
"cameras": [
"_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
"VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ"
],
"co_alarm_state": "ok",
"country_code": "US",
"eta_begin": "2017-02-02T03:10:08.000Z",
"name": "Home",
"peak_period_end_time": "2017-07-01T01:03:08.400Z",
"peak_period_start_time": "2017-06-01T13:31:10.870Z",
"postal_code": "98056",
"rhr_enrollment": false,
"smoke_alarm_state": "ok",
"smoke_co_alarms": [
"p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
"p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
"p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
"p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV"
],
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
"thermostats": [
"G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV"
],
"time_zone": "America/Los_Angeles",
"wheres": {
"6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ": {
"name": "Downstairs Kitchen",
"where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ"
},
"8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ": {
"name": "Frog",
"where_id": "8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ"
},
"qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ": {
"name": "Garage",
"where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA": {
"name": "Family Room",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA": {
"name": "Kitchen",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw": {
"name": "Hallway",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg": {
"name": "Basement",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA": {
"name": "Kids Room",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw": {
"name": "Master Bedroom",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg": {
"name": "Downstairs",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA": {
"name": "Driveway",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw": {
"name": "Den",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ": {
"name": "Bedroom",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg": {
"name": "Entryway",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA": {
"name": "Upstairs",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw": {
"name": "Living Room",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ": {
"name": "Outside",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g": {
"name": "Dining Room",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA": {
"name": "Backyard",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q": {
"name": "Office",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q"
},
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g": {
"name": "Front Yard",
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g"
}
},
"wwn_security_state": "ok"
}
}
}
}

View File

@@ -29,7 +29,6 @@
<module>org.openhab.binding.mqtt.homeassistant.tests</module>
<module>org.openhab.binding.mqtt.homie.tests</module>
<module>org.openhab.binding.mqtt.ruuvigateway.tests</module>
<module>org.openhab.binding.nest.tests</module>
<module>org.openhab.binding.ntp.tests</module>
<module>org.openhab.binding.systeminfo.tests</module>
<module>org.openhab.binding.tradfri.tests</module>