From 65bb5004bef409fe598cd1dbcece1d35e8d233af Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Tue, 3 Nov 2020 01:09:27 +0100 Subject: [PATCH] [nest] Add support for Smart Device Management (SDM) API Port nestdeviceaccess add-on to OH3 based on: https://github.com/bhigg-code/openhab-addons/tree/2.5.x/bundles/org.openhab.binding.nestdeviceaccess Rework code so WWN and SDM are supported by existing binding Fixes #8664 Also-by: Brian Higginbotham Signed-off-by: Wouter Born --- bundles/org.openhab.binding.nest/pom.xml | 435 ++++++++++++++ .../org.openhab.binding.nest/sdm/README.md | 159 ++++++ .../sdm/cfg/nestdeviceaccess.cfg | 33 ++ .../sdm/doc/logo-google-nest_480.png | Bin 0 -> 17293 bytes .../sdm/doc/nesthello.jpeg | Bin 0 -> 3169 bytes .../sdm/doc/nestthermostat.jpeg | Bin 0 -> 3849 bytes .../nest/internal/NestHandlerFactory.java | 135 ----- .../discovery/NestDiscoveryService.java | 171 ------ .../internal/sdm/SDMBindingConstants.java | 70 +++ .../internal/sdm/SDMThingHandlerFactory.java | 61 ++ .../binding/nest/internal/sdm/SDMUtility.java | 289 ++++++++++ .../sdm/config/SDMAccountConfiguration.java | 36 ++ .../nest/internal/sdm/data/SDMDoorbell.java | 338 +++++++++++ .../nest/internal/sdm/data/SDMThermostat.java | 469 ++++++++++++++++ .../sdm/discovery/SDMDiscoveryService.java | 309 ++++++++++ .../sdm/handler/SDMAccountHandler.java | 150 +++++ .../sdm/handler/SDMDoorbellHandler.java | 474 ++++++++++++++++ .../sdm/handler/SDMThermostatHandler.java | 529 ++++++++++++++++++ .../WWNBindingConstants.java} | 21 +- .../internal/wwn/WWNThingHandlerFactory.java | 86 +++ .../{NestUtils.java => wwn/WWNUtils.java} | 8 +- .../config/WWNAccountConfiguration.java} | 6 +- .../config/WWNDeviceConfiguration.java} | 6 +- .../config/WWNStructureConfiguration.java} | 6 +- .../data/BaseWWNDevice.java} | 8 +- .../data/WWNAccessTokenData.java} | 8 +- .../data/WWNActivityZone.java} | 8 +- .../Camera.java => wwn/data/WWNCamera.java} | 18 +- .../data/WWNCameraEvent.java} | 8 +- .../data/WWNDevices.java} | 20 +- .../{data/ETA.java => wwn/data/WWNETA.java} | 8 +- .../data/WWNErrorData.java} | 8 +- .../data/WWNIdentifiable.java} | 6 +- .../data/WWNMetadata.java} | 8 +- .../data/WWNSmokeDetector.java} | 10 +- .../data/WWNStructure.java} | 22 +- .../data/WWNThermostat.java} | 10 +- .../data/WWNTopLevelData.java} | 20 +- .../data/WWNTopLevelStreamingData.java} | 12 +- .../Where.java => wwn/data/WWNWhere.java} | 6 +- .../wwn/discovery/WWNDiscoveryService.java | 180 ++++++ .../FailedResolvingWWNUrlException.java} | 10 +- .../FailedRetrievingWWNDataException.java} | 10 +- .../FailedSendingWWNDataException.java} | 10 +- .../InvalidWWNAccessTokenException.java} | 10 +- .../handler/WWNAccountHandler.java} | 126 +++-- .../handler/WWNBaseHandler.java} | 64 ++- .../handler/WWNCameraHandler.java} | 32 +- .../handler/WWNRedirectUrlSupplier.java} | 28 +- .../handler/WWNSmokeDetectorHandler.java} | 25 +- .../handler/WWNStructureHandler.java} | 28 +- .../handler/WWNThermostatHandler.java} | 26 +- .../listener/WWNStreamingDataListener.java} | 14 +- .../listener/WWNThingDataListener.java} | 6 +- .../rest/WWNAuthorizer.java} | 36 +- .../rest/WWNStreamingRequestFilter.java} | 8 +- .../rest/WWNStreamingRestClient.java} | 46 +- .../rest/WWNUpdateRequest.java} | 12 +- .../update/WWNCompositeUpdateHandler.java} | 43 +- .../update/WWNUpdateHandler.java} | 33 +- .../services/io.grpc.LoadBalancerProvider | 2 + .../resources/OH-INF/config/sdm-config.xml | 33 ++ .../config/{config.xml => wwn-config.xml} | 6 +- .../resources/OH-INF/thing/sdm-account.xml | 13 + .../resources/OH-INF/thing/sdm-camera.xml | 34 ++ .../resources/OH-INF/thing/sdm-channels.xml | 194 +++++++ .../resources/OH-INF/thing/sdm-display.xml | 34 ++ .../resources/OH-INF/thing/sdm-doorbell.xml | 35 ++ .../resources/OH-INF/thing/sdm-thermostat.xml | 31 + .../resources/OH-INF/thing/thermostat.xml | 51 -- .../thing/{bridge.xml => wwn-account.xml} | 8 +- .../thing/{camera.xml => wwn-camera.xml} | 12 +- .../thing/{channels.xml => wwn-channels.xml} | 162 +++--- ...ke-detector.xml => wwn-smoke-detector.xml} | 18 +- .../{structure.xml => wwn-structure.xml} | 28 +- .../resources/OH-INF/thing/wwn-thermostat.xml | 51 ++ .../data/WWNDataUtil.java} | 14 +- .../data/WWNGsonParsingTest.java} | 56 +- .../wwn/handler/WWNAccountHandlerTest.java} | 20 +- .../wwn/handler/WWNCameraHandlerTest.java} | 19 +- .../handler/WWNSmokeDetectorHandlerTest.java} | 19 +- .../wwn/handler/WWNStructureHandlerTest.java} | 19 +- .../handler/WWNThermostatHandlerTest.java} | 19 +- .../wwn/handler/WWNThingHandlerOSGiTest.java} | 55 +- .../wwn/test/WWNTestAccountHandler.java} | 25 +- .../wwn/test/WWNTestApiServlet.java} | 12 +- .../wwn/test/WWNTestHandlerFactory.java} | 25 +- .../wwn/test/WWNTestServer.java} | 8 +- .../{ => wwn}/data/access-token-data.json | 0 .../internal/{ => wwn}/data/camera-data.json | 0 .../internal/{ => wwn}/data/error-data.json | 0 .../{ => wwn}/data/smoke-detector-data.json | 0 .../{ => wwn}/data/structure-data.json | 0 .../{ => wwn}/data/thermostat-data.json | 0 .../{ => wwn}/data/top-level-data.json | 0 .../data/top-level-streaming-data-empty.json | 0 .../top-level-streaming-data-incomplete.json | 0 .../data/top-level-streaming-data.json | 0 98 files changed, 4709 insertions(+), 1012 deletions(-) create mode 100644 bundles/org.openhab.binding.nest/sdm/README.md create mode 100644 bundles/org.openhab.binding.nest/sdm/cfg/nestdeviceaccess.cfg create mode 100644 bundles/org.openhab.binding.nest/sdm/doc/logo-google-nest_480.png create mode 100644 bundles/org.openhab.binding.nest/sdm/doc/nesthello.jpeg create mode 100644 bundles/org.openhab.binding.nest/sdm/doc/nestthermostat.jpeg delete mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestHandlerFactory.java delete mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/discovery/NestDiscoveryService.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/SDMBindingConstants.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/SDMThingHandlerFactory.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/SDMUtility.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/config/SDMAccountConfiguration.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/data/SDMDoorbell.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/data/SDMThermostat.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/discovery/SDMDiscoveryService.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/handler/SDMAccountHandler.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/handler/SDMDoorbellHandler.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/handler/SDMThermostatHandler.java rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{NestBindingConstants.java => wwn/WWNBindingConstants.java} (91%) create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/WWNThingHandlerFactory.java rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{NestUtils.java => wwn/WWNUtils.java} (87%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{config/NestBridgeConfiguration.java => wwn/config/WWNAccountConfiguration.java} (88%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{config/NestDeviceConfiguration.java => wwn/config/WWNDeviceConfiguration.java} (85%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{config/NestStructureConfiguration.java => wwn/config/WWNStructureConfiguration.java} (84%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/BaseNestDevice.java => wwn/data/BaseWWNDevice.java} (95%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/AccessTokenData.java => wwn/data/WWNAccessTokenData.java} (89%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/ActivityZone.java => wwn/data/WWNActivityZone.java} (90%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/Camera.java => wwn/data/WWNCamera.java} (94%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/CameraEvent.java => wwn/data/WWNCameraEvent.java} (97%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/NestDevices.java => wwn/data/WWNDevices.java} (82%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/ETA.java => wwn/data/WWNETA.java} (95%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/ErrorData.java => wwn/data/WWNErrorData.java} (94%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/NestIdentifiable.java => wwn/data/WWNIdentifiable.java} (79%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/NestMetadata.java => wwn/data/WWNMetadata.java} (92%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/SmokeDetector.java => wwn/data/WWNSmokeDetector.java} (95%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/Structure.java => wwn/data/WWNStructure.java} (94%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/Thermostat.java => wwn/data/WWNThermostat.java} (98%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/TopLevelData.java => wwn/data/WWNTopLevelData.java} (82%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/TopLevelStreamingData.java => wwn/data/WWNTopLevelStreamingData.java} (85%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{data/Where.java => wwn/data/WWNWhere.java} (94%) create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/discovery/WWNDiscoveryService.java rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{exceptions/FailedResolvingNestUrlException.java => wwn/exceptions/FailedResolvingWWNUrlException.java} (69%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{exceptions/FailedRetrievingNestDataException.java => wwn/exceptions/FailedRetrievingWWNDataException.java} (69%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{exceptions/FailedSendingNestDataException.java => wwn/exceptions/FailedSendingWWNDataException.java} (68%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{exceptions/InvalidAccessTokenException.java => wwn/exceptions/InvalidWWNAccessTokenException.java} (70%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{handler/NestBridgeHandler.java => wwn/handler/WWNAccountHandler.java} (72%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{handler/NestBaseHandler.java => wwn/handler/WWNBaseHandler.java} (71%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{handler/NestCameraHandler.java => wwn/handler/WWNCameraHandler.java} (86%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{handler/NestRedirectUrlSupplier.java => wwn/handler/WWNRedirectUrlSupplier.java} (72%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{handler/NestSmokeDetectorHandler.java => wwn/handler/WWNSmokeDetectorHandler.java} (76%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{handler/NestStructureHandler.java => wwn/handler/WWNStructureHandler.java} (80%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{handler/NestThermostatHandler.java => wwn/handler/WWNThermostatHandler.java} (91%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{listener/NestStreamingDataListener.java => wwn/listener/WWNStreamingDataListener.java} (68%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{listener/NestThingDataListener.java => wwn/listener/WWNThingDataListener.java} (88%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{rest/NestAuthorizer.java => wwn/rest/WWNAuthorizer.java} (59%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{rest/NestStreamingRequestFilter.java => wwn/rest/WWNStreamingRequestFilter.java} (86%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{rest/NestStreamingRestClient.java => wwn/rest/WWNStreamingRestClient.java} (82%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{rest/NestUpdateRequest.java => wwn/rest/WWNUpdateRequest.java} (83%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{update/NestCompositeUpdateHandler.java => wwn/update/WWNCompositeUpdateHandler.java} (69%) rename bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/{update/NestUpdateHandler.java => wwn/update/WWNUpdateHandler.java} (70%) create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/config/sdm-config.xml rename bundles/org.openhab.binding.nest/src/main/resources/OH-INF/config/{config.xml => wwn-config.xml} (92%) create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-account.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-camera.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-channels.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-display.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-doorbell.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-thermostat.xml delete mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/thermostat.xml rename bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/{bridge.xml => wwn-account.xml} (64%) rename bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/{camera.xml => wwn-camera.xml} (70%) rename bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/{channels.xml => wwn-channels.xml} (75%) rename bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/{smoke-detector.xml => wwn-smoke-detector.xml} (59%) rename bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/{structure.xml => wwn-structure.xml} (50%) create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-thermostat.xml rename itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/{data/NestDataUtil.java => wwn/data/WWNDataUtil.java} (90%) rename itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/{data/GsonParsingTest.java => wwn/data/WWNGsonParsingTest.java} (80%) rename itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/{handler/NestBridgeHandlerTest.java => internal/wwn/handler/WWNAccountHandlerTest.java} (81%) rename itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/{handler/NestCameraHandlerTest.java => internal/wwn/handler/WWNCameraHandlerTest.java} (91%) rename itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/{handler/NestSmokeDetectorHandlerTest.java => internal/wwn/handler/WWNSmokeDetectorHandlerTest.java} (87%) rename itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/{handler/NestStructureHandlerTest.java => internal/wwn/handler/WWNStructureHandlerTest.java} (89%) rename itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/{handler/NestThermostatHandlerTest.java => internal/wwn/handler/WWNThermostatHandlerTest.java} (95%) rename itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/{handler/NestThingHandlerOSGiTest.java => internal/wwn/handler/WWNThingHandlerOSGiTest.java} (88%) rename itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/{test/NestTestBridgeHandler.java => internal/wwn/test/WWNTestAccountHandler.java} (65%) rename itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/{test/NestTestApiServlet.java => internal/wwn/test/WWNTestApiServlet.java} (94%) rename itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/{test/NestTestHandlerFactory.java => internal/wwn/test/WWNTestHandlerFactory.java} (80%) rename itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/{test/NestTestServer.java => internal/wwn/test/WWNTestServer.java} (90%) rename itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/{ => wwn}/data/access-token-data.json (100%) rename itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/{ => wwn}/data/camera-data.json (100%) rename itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/{ => wwn}/data/error-data.json (100%) rename itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/{ => wwn}/data/smoke-detector-data.json (100%) rename itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/{ => wwn}/data/structure-data.json (100%) rename itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/{ => wwn}/data/thermostat-data.json (100%) rename itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/{ => wwn}/data/top-level-data.json (100%) rename itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/{ => wwn}/data/top-level-streaming-data-empty.json (100%) rename itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/{ => wwn}/data/top-level-streaming-data-incomplete.json (100%) rename itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/{ => wwn}/data/top-level-streaming-data.json (100%) diff --git a/bundles/org.openhab.binding.nest/pom.xml b/bundles/org.openhab.binding.nest/pom.xml index 7c0e37224b2a3..3bae5a59b4d89 100644 --- a/bundles/org.openhab.binding.nest/pom.xml +++ b/bundles/org.openhab.binding.nest/pom.xml @@ -14,4 +14,439 @@ openHAB Add-ons :: Bundles :: Nest Binding + + com.sun.jndi.dns;resolution:=optional,com.sun.nio.sctp;resolution:=optional,android.net.*;resolution:=optional,com.squareup.okhttp.*;resolution:=optional,com.barchart.udt;resolution:=optional,com.fasterxml.aalto.*;resolution:=optional,com.oracle.svm.core.annotate;resolution:=optional,gnu.io.*;resolution:=optional,lzma.sdk.*;resolution:=optional,net.jpountz.lz4;resolution:=optional,net.jpountz.xxhash;resolution:=optional,org.bouncycastle.*;resolution:=optional,org.conscrypt.*;resolution:=optional,org.eclipse.jetty.*;resolution:=optional,org.jboss.marshalling;resolution:=optional,reactor.blockhound.*;resolution:=optional,sun.net.dns;resolution:=optional,sun.security.ssl;resolution:=optional,sun.security.x509;resolution:=optional,sun.security.util;resolution:=optional,io.perfmark.java9;resolution:=optional,io.grpc.override;resolution:=optional,kotlin.jvm.*;resolution:=optional + + + + + com.barchart.udt + barchart-udt-bundle + 2.3.0 + + + com.fasterxml.jackson.core + jackson-core + 2.10.0 + + + com.google.api + api-common + 1.10.1 + + + com.google.api + gax + 1.60.0 + + + com.google.api + gax-grpc + 1.60.0 + + + com.google.api-client + google-api-client + 1.30.5 + + + com.google.api-client + google-api-client-jackson2 + 1.30.10 + + + com.google.api-client + google-api-client-servlet + 1.30.11 + + + com.google.api.grpc + grpc-google-cloud-pubsub-v1 + 1.90.6 + + + com.google.api.grpc + proto-google-cloud-pubsub-v1 + 1.90.6 + + + com.google.api.grpc + proto-google-common-protos + 2.0.0 + + + com.google.api.grpc + proto-google-iam-v1 + 1.0.2 + + + com.google.auth + google-auth-library-credentials + 0.22.0 + + + com.google.auth + google-auth-library-oauth2-http + 0.22.0 + + + com.google.cloud + google-cloud-pubsub + 1.108.6 + + + com.google.cloud + libraries-bom + 13.4.0 + pom + + + com.google.code.findbugs + jsr305 + 3.0.2 + + + com.google.code.gson + gson + 2.8.5 + + + com.google.http-client + google-http-client + 1.30.1 + + + com.google.http-client + google-http-client-jackson2 + 1.36.0 + + + com.google.oauth-client + google-oauth-client + 1.31.1 + + + com.google.oauth-client + google-oauth-client-servlet + 1.31.1 + + + com.google.protobuf + protobuf-java + 3.13.0 + + + com.google.protobuf + protobuf-java-util + 3.13.0 + + + com.google.protobuf.nano + protobuf-javanano + 3.1.0 + + + com.jcraft + jzlib + 1.1.3 + + + com.ning + compress-lzf + 1.0.4 + + + com.squareup.okio + okio + 2.9.0 + + + com.sun.activation + javax.activation + 1.2.0 + + + commons-codec + commons-codec + 1.15 + + + io.grpc + grpc-alts + 1.33.1 + + + io.grpc + grpc-api + 1.33.1 + + + io.grpc + grpc-auth + 1.33.1 + + + io.grpc + grpc-census + 1.33.1 + + + io.grpc + grpc-context + 1.33.1 + + + io.grpc + grpc-core + 1.33.1 + + + io.grpc + grpc-grpclb + 1.33.1 + + + io.grpc + grpc-netty + 1.33.1 + + + io.grpc + grpc-netty-shaded + 1.33.1 + + + io.grpc + grpc-okhttp + 1.33.1 + + + io.grpc + grpc-protobuf + 1.33.1 + + + io.grpc + grpc-protobuf-lite + 1.33.1 + + + io.grpc + grpc-stub + 1.33.1 + + + io.netty + netty-buffer + 4.1.53.Final + + + io.netty + netty-codec + 4.1.53.Final + + + io.netty + netty-codec-dns + 4.1.53.Final + + + io.netty + netty-codec-haproxy + 4.1.53.Final + + + io.netty + netty-codec-http + 4.1.53.Final + + + io.netty + netty-codec-http2 + 4.1.53.Final + + + io.netty + netty-codec-memcache + 4.1.53.Final + + + io.netty + netty-codec-mqtt + 4.1.53.Final + + + io.netty + netty-codec-redis + 4.1.53.Final + + + io.netty + netty-codec-socks + 4.1.53.Final + + + io.netty + netty-codec-stomp + 4.1.53.Final + + + io.netty + netty-common + 4.1.53.Final + + + io.netty + netty-example + 4.1.53.Final + + + io.netty + netty-handler + 4.1.53.Final + + + io.netty + netty-handler-proxy + 4.1.53.Final + + + io.netty + netty-resolver + 4.1.53.Final + + + io.netty + netty-tcnative + 2.0.34.Final + + + io.netty + netty-transport + 4.1.53.Final + + + io.netty + netty-transport-native-epoll + 4.1.53.Final + + + io.netty + netty-transport-native-unix-common + 4.1.53.Final + + + io.netty + netty-transport-rxtx + 4.1.53.Final + + + io.netty + netty-transport-sctp + 4.1.53.Final + + + io.netty + netty-transport-udt + 4.1.53.Final + + + io.opencensus + opencensus-api + 0.24.0 + + + io.opencensus + opencensus-contrib-grpc-metrics + 0.24.0 + + + io.opencensus + opencensus-contrib-http-util + 0.24.0 + + + io.perfmark + perfmark-api + 0.23.0 + runtime + + + io.perfmark + perfmark-impl + 0.23.0 + + + io.perfmark + perfmark-java6 + 0.23.0 + + + io.perfmark + perfmark-java7 + 0.23.0 + + + javax.annotation + javax.annotation-api + 1.3.2 + + + org.apache.commons + commons-lang3 + 3.11 + + + org.apache.httpcomponents + httpclient + 4.5.13 + + + org.apache.httpcomponents + httpcore + 4.4.13 + + + org.datanucleus + javax.jdo + 3.2.0-m13 + + + org.jetbrains.kotlin + kotlin-reflect + 1.3.31 + runtime + + + org.jetbrains.kotlin + kotlin-stdlib + 1.3.31 + + + org.jetbrains.kotlin + kotlin-stdlib-common + 1.4.10 + + + org.jetbrains.kotlin + kotlin-stdlib-jdk7 + 1.3.31 + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + 1.3.31 + + + org.json + json + 20200518 + + + org.threeten + threetenbp + 1.5.0 + + + diff --git a/bundles/org.openhab.binding.nest/sdm/README.md b/bundles/org.openhab.binding.nest/sdm/README.md new file mode 100644 index 0000000000000..13a69eb01dfc5 --- /dev/null +++ b/bundles/org.openhab.binding.nest/sdm/README.md @@ -0,0 +1,159 @@ +# NestDeviceAccess Binding + +This binding integrates Nest products through the [Google Smart Device Management (SDM) API](https://developers.google.com/home/smart-device-management). + +![Nest Logo](doc/logo-google-nest_480.png) + +_If possible, provide some resources like pictures, a YouTube video, etc. to give an impression of what can be done with this binding. You can place such resources into a `doc` folder next to this README.md._ + +## Supported Things + +The NestDeviceAccess Binding will support things allowed by the Google Smart Device Management (SDM) API. Currently +the binding implements the Thermostat and device traits for Nest products defined at the [SDM traits](https://developers.google.com/nest/device-access/traits). + + +Thermostat Trait - Currently supported (Tested against generation 2 and 3 Nest Thermostats) + +![Nest Thermostat](doc/nestthermostat.jpeg) + +Doorbell Trait - Currently in testing + +![Nest Hello](doc/nesthello.jpeg) + +Camera Trait - (Needs to be implemented) + + +## Discovery + +The NestDeviceAccess binding works through discovery by leveraging the Google SDM API to perform a devices trait call to get all devices allowed by the accessToken. The devices are then enumerated to identify the "Type" of device. If it is a Thermostat or Doorbell "Type" then the device will be added to the inbox. + +Once added to the inbox, the device can be added as a thing. The thing will import several default properties to allow communication with the SDM API. + +Note: You MUST configure the discovery service through the services/cfg folder.. The format is listed under the BINDING CONFIGURATION section of this document. The file must be named nestdeviceaccess.cfg +## Binding Configuration + +'# Configuration for the Nest Device Access Binding +# +'# There is general project information for Google that must be provided in order to discover Nest products +'# The configuration data is a per project configuration and can be changed by the user. +'# A sandbox project created by Brian Higginbotham @BHigg was created and listed below for testing purposes only +'# Use the project at your own risk for testing or create your own project through Google and enable the SDM APIs for individual use. +'# Google Pubsub properties added for Doorbell eventing capabilities. You will need to enable a PubSub in your google project and tie a service account with view access to the pubsub subscription and topic that are created. More instructions can be found on Google's website. +'# Note this project is limited in nature as a sandbox project to 30 API calls/min by Google. +'# +'# +'#projectId is the Google project provided through the project creation process +projectId= + +'#clientId is the Google clientId for your application +clientId= + +'#clientSecret is the Google clientSecret used to fetch the initial and refresh accessTokens +clientSecret= + +'#authorizationToken is used to authorize your devices with the project and provide the user with a unique refresh and first time access token. +authorizationToken= + +'#refreshToken is used to get accessTokens from the application +refreshToken= + +'# NEW PROPERTIES for the Google Pubsub configurations. Optional for Thermostat. Mandatory for Doorbell and Camera + +'#ServiceAccount information that is used to get pubsub information +serviceAccountPath= + +'#SubscriptionId for the PubSub that was enabled.. This is a named value when creating the pubsub subscription +subscriptionId= + +'#pubsubProjectId is a project identifier that was provided to you when creating the pubsub.. ex openhab-nest-int-XXXXXXXXXX +pubsubProjectId= + + +## Thing Configuration + +refreshInterval is used to tell the thing to refresh status (in seconds) and is required. + +## Channels + + +| channel | type | description | +|------------------|--------|-------------------------------------| +|-------- Thermostat Thing----------------------------------------| +| thermostatName | Text | This is the name of the Thermostat | +| thermostatHumidtyPercent | Number:Length | This is the Humidity Percentage | +| thermostatAmbientTemperature | Number:Dimensionless | This is the ambient Temperature | +| thermostatTemperatureCool | Number:Dimensionless | This is the Cool Temperature Reading for the Thermostat (Only valid for Cool and Heat-Cool)| +| thermostatTemperatureHeat | Number:Dimensionless | This is the Heat Temperature Reading for the Thermostat (Only valid for Heat-Cool and Heat)| +| thermostatCurrentMode | Text | This is the current mode of the HVAC +| thermostatCurrentEcoMode | Text | This is the current mode of the Eco Setting for HVAC +| thermostatTargetTemperature | Number:Dimensionless | This is a aggregate temperature setting for the thermostat (Only valid for Heat and Cool) +| thermostatMinTemperature | Number:Dimensionless | This is a setting used for Eco and Heat-Cool HVAC Mode +| thermostatMaxTemperature | Number:Dimensionless | This is a setting used for Eco and Heat-Cool HVAC Mode +| thermostatScaleSetting | Text | This is the Scale setting for the Thermostat (FAHERNHEIT or CELSIUS) +| ----------------------------------------------------------------| +|--------- Doorbell Thing-----------------------------------------| +|doorbellSoundEventImage | Image | This is a generated image based on a Sound event. The event has an event ID that is required to generate the image. | +|doorbellMotionEventImage | Image | This is a generated image based on a Motion event. The event has an event ID that is required to generate the image. | +|doorbellPersonEventImage | Image | This is a generated image based on a Person event. The event has an event ID that is required to generate the image. | +|doorbellChimeEventImage | Image | This is a generated image based on a Chime event. The event has an event ID that is required to generate the image. | +| doorbellChimeLastEventTime | Text | This is the time a door Chime event was last received | +| doorbellChimeEvent | Switch | This is a switch that flips when a Chime event is received | +| doorbellPersonEvent | Switch | This is a switch that flips when a Person event is received | +| doorbellPersonLastEventTime | Text | This is the time a Person was detected by the doorbell | +| doorbellMotionEvent | Switch | This is a switch that flips when a Motion event is received | +| doorbellMotionLastEventTime | Text | This is the time a Motion event was last received | +| doorbellSoundEvent | Switch | This is a switch that flips when a Sound event is received | +| doorbellSoundLastEventTime | Text | This is the time a Sound event was last received | +| doorbellLiveStreamUrl | Text | This is the generated Live Stream URL when a motion is detected. Note: The URL includes a token that can be used to view an rtsps stream..| +| doorbellLiveStreamExpirationTime | Text | This is the Live Stream Expiration time when the token and URL must be generated again | +| doorbellLiveStreamExtensionToken | Text | This is the Live Stream Extension Token that is used to request an extension to the initial LiveStreamUrl embedded Token. | +| doorbellLiveStreamCurrentToken | Text | This is the Live Stream Current Token that is embedded in the LiveStreamURL. | +|-----------------------------------------------------------------| + +## Full Example + +#Demo.sitemap +Frame label="Dining Room Thermostat" icon="temperature"{ + Switch item=NestDiningRoomThermostat_CurrentMode label="HVAC Mode" mappings=[OFF="OFF",COOL="COOL",HEAT="HEAT",HEATCOOL="HEATCOOL"] icon="climate" + Text item=NestDiningRoomThermostat_AmbientTemperature label="Current Ambient Temperature" icon="temperature" + Text item=NestDiningRoomThermostat_HumidityPercentage label="Current Humidity" icon="humidity" + Setpoint item=NestDiningRoomThermostat_TargetTemperatureSetting label="Target Temperature [%d]" minValue=65 maxValue=80 step=1 visibility=[NestDiningRoomThermostat_CurrentMode=="COOL",NestDiningRoomThermostat_ScaleSetting=="FAHRENHEIT"] +} + +Text label="Front Door" icon="door"{ + Frame label="Doorbell" icon="door"{ + Switch item=NestFrontDoorDoorbell_Has_Chime label="Has Chime" mappings=[OFF="OFF",ON="ON"] + Image item=NestFrontDoorDoorbell_ChimeEventImage label="Front Door Image" visibility=[NestFrontDoorDoorbell_Has_Chime=="ON"] + Text item=NestFrontDoorDoorbell_ChimeLastEventTime label="Last Chime Event Time" visibility=[NestFrontDoorDoorbell_Has_Chime=="ON"] + Switch item=NestFrontDoorDoorbell_Has_Sound label="Has Sound" mappings=[OFF="OFF",ON="ON"] + Image item=NestFrontDoorDoorbell_SoundEventImage label="Front Door Image" visibility=[NestFrontDoorDoorbell_Has_Sound=="ON"] + Text item=NestFrontDoorDoorbell_SoundLastEventTime label="Last Chime Event Time" visibility=[NestFrontDoorDoorbell_Has_Sound=="ON"] + Switch item=NestFrontDoorDoorbell_Has_Motion label="Has Motion" mappings=[OFF="OFF",ON="ON"] + Image item=NestFrontDoorDoorbell_MotionEventImage label="Front Door Image" visibility=[NestFrontDoorDoorbell_Has_Motion=="ON"] + Text item=NestFrontDoorDoorbell_MotionLastEventTime label="Last Motion Event Time" visibility=[NestFrontDoorDoorbell_Has_Motion=="ON"] + Switch item=NestFrontDoorDoorbell_Has_Person label="Has Person" mappings=[OFF="OFF",ON="ON"] + Image item=NestFrontDoorDoorbell_PersonEventImage label="Front Door Image" visibility=[NestFrontDoorDoorbell_Has_Person=="ON"] + Text item=NestFrontDoorDoorbell_PersonLastEventTime label="Last Motion Event Time" visibility=[NestFrontDoorDoorbell_Has_Person=="ON"] + Text item=NestFrontDoorDoorbell_LiveStreamExpiration label="Live Stream Expiration" + Text item=NestFrontDoorDoorbell_LiveStreamUrl label="Live Stream URL" + Text item=NestFrontDoorDoorbell_LiveStreamExtensionToken label="Live Steam Extension Token" + Text item=NestFrontDoorDoorbell_LiveStreamCurrentToken label="Live Steam Token" + } + } + +## Any custom content here! + +The NestDeviceAccess Binding is built with a sandbox project in Google. This means that there is a limit of 30 requests to the API/min. This is used for testing. However, if you switch the services/nestdeviceaccess.cfg configuration for the binding to use a different projectId and clientId, you can use this binding on a better project. + +To configure the discovery service, you must place a nestdeviceaccess.cfg in the services dir + +You only need either the authorizationToken or refreshToken. If you use the authorizationToken, the binding will fetch your refreshToken and add it to the openhab log file (Make sure you update the nestdeviceaccess.cfg file with your refreshToken.) Otherwise, go through the linked instructions below and get your refreshToken and update the nestdeviceaccess.cfg file. + +It is pretty easy to see if the nest discovery works, if the parameters are in the nestdeviceaccess.cfg file, when you go to the inbox and try to add a NestDeviceAccess thing, it will start the discovery. Otherwise, it will ask for the parameters manually. + +Make sure you follow the instructions on [Google Nest Authorization instructions](https://developers.google.com/nest/device-access/authorize) in order to get your initial Authorization and Refresh token. You can store those in the nestdeviceaccess.cfg file for configuration of the discovery service. + +If you have a doorbell/camera or want to utilize the eventing capability for devices, including thermostats, then you need to setup a Pubsub in your Google projects. [Google PubSub Creation](https://developers.google.com/nest/device-access/subscribe-to-events) + +I've included a sample project projectId, clientId, and clientSecret in the nestdeviceaccess.cfg for testing purposes only. You can get the authorizationToken per the above instructions and I will output your refreshToken and initial accessToken in the openhab.log file. You will need to update the nestdeviceaccess.cfg file with this data after initial usage. + diff --git a/bundles/org.openhab.binding.nest/sdm/cfg/nestdeviceaccess.cfg b/bundles/org.openhab.binding.nest/sdm/cfg/nestdeviceaccess.cfg new file mode 100644 index 0000000000000..2dd5e1668f9ae --- /dev/null +++ b/bundles/org.openhab.binding.nest/sdm/cfg/nestdeviceaccess.cfg @@ -0,0 +1,33 @@ +# Configuration for the Nest Device Access Binding +# +# There is general project information for Google that must be provided in order to discover Nest products +# The configuration data is a per project configuration and can be changed by the user. +# A sandbox project created by Brian Higginbotham @BHigg was created and listed below for testing purposes only +# Use the project at your own risk for testing or create your own project through Google and enable the SDM APIs for individual use +# Note this project is limited in nature,a sandbox project, to 30 API calls/min by Google. +# +# +#projectId is the Google project provided through the project creation process +projectId=dbc4bd5a-7c1a-4905-abfb-b7e8ea792de7 + +#clientId is the Google clientId for your application +clientId=24189767352-9covhiu5jaiohte4idq9347789sh3g2m.apps.googleusercontent.com + +#clientSecreat is the Google clientSecret used to fetch the initial and refresh accessTokens +clientSecret=lr6PJbnqN2c_fRRVaqd_Fums + +#authorizationToken is used to authorize your devices with the project and provide the user with a unique refresh and first time access token. +authorizationToken= + +#refreshToken is used to get accessTokens from the application +refreshToken= + +#ServiceAccount information that is used to get pubsub information. The contents of the file are made and downloaded from the Google IAM Console +#example: /home/homeam/Documents/Openhab-Nest-Integrations-708223a60202.json +serviceAccountPath= + +#SubscriptionId for the PubSub that was enabled.. This is a named value when creating the pubsub subscription +subscriptionId= + +#pubsubProjectId is a project identifier that was provided to you when creating the pubsub.. ex openhab-nest-int-XXXXXXXXXX +pubsubProjectId= \ No newline at end of file diff --git a/bundles/org.openhab.binding.nest/sdm/doc/logo-google-nest_480.png b/bundles/org.openhab.binding.nest/sdm/doc/logo-google-nest_480.png new file mode 100644 index 0000000000000000000000000000000000000000..1a6356138e9a4319efb962622de17a08435a65e0 GIT binary patch literal 17293 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 literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.binding.nest/sdm/doc/nesthello.jpeg b/bundles/org.openhab.binding.nest/sdm/doc/nesthello.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..c97180b63ad8f4eb9145ca1998800bf60a705403 GIT binary patch literal 3169 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 literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.binding.nest/sdm/doc/nestthermostat.jpeg b/bundles/org.openhab.binding.nest/sdm/doc/nestthermostat.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..a398312e514b92454e2bdc12314c6166262c470f GIT binary patch literal 3849 zcmZuy2T&8()(#+|tVj(dGzCI1A(0N!RHPWH^rf#N(yK;FD2kP;AOT@1B7_z?gc7QN zV5ky0(pgG?AdwdOk30XoS>L{M@674^Kb)!JiLMumv}+q{6OGkX;JYj2q^@D z_mZrVtfZm@908+aW@f&~auLYN3Y6v(;#2&aV2W_qsQ{CbQj$i%lwgvwr|Rj3pK_T~ zK?){=fSq!g)3}pn0Q)%rjt;^=Cjy{nr(2R02Bxz=ow{rQ zdOCXgvpZ$xS;uz? zEkJHDu-q+8Q*)<><)qWIcoARp zm%DY-{#ol6nSmL@!2l6m6yn6et=)FMK@uFtkNJ%VDvZ$eb!tw!cW}nJ!#)Fz=Z?U0 zV6H0gk^GMrx6y(X^&)i{q)>Zo*Jx zKGSted*u;JYeMI)-K$YosCC4k59;cEhuAP4dOW4XwX;!uvKh8?Q5i~YGcSK! zmq;*}#EuXejXuS1q^W<$dd2L!s?m6tm6P;OeY!gQPTJvS+ZY@jH}jNx`%= zY#q6_7WkM&AJweYuWXSh&ip7l?V#cS9%AJOmeqgq%S`@X0j(G|2jfiI8-!m3L?-r_ zzA2_{gfAXN)x5IyOU*Lm>on}A%>*dNU_5B z(fEld)w6;&B%^y{A<5psuWPK8NmZ1i7nb^RO^0wrCnvIJtB30@PO)>s3Zq57n2K7+ zXL3U>968#65w<+&-to$!_wK?1PYgBn4a_iqk>u}Dt5#YS8`d2ZEX?~9wljp2Sl@!m zMwgGw7ABoCT+IzJZk-jm=f-3BhVzR|rVDZ<^4-P}Kk5_hNfwaU>8L#s9qk{n*TTM) z{njw_ktwz$0zJ3u;Yu03g}J|@PKi#ka>{UAAN_*QpLR1^>WB|*KO0eD<2;@)J`bko z%`Zh1&sqd=>JPS>Z6$0PjlB4g8p`@?;;|?1Rm$r>4Bx+j1rkBlfMjO4hjRbo7q_si_YW)Zw|XYO02=(KoJ zV9yCaHz9ioQglV(FB-9~%JxWvY_z;VMSGjrMd297R%xO{lVZlsJk9pG#%z1^G-GS! z8%sVM_aP5YEt{xiR2>@azju5CN!VQKYeYcU5^bzX*dwd-yff4v;!12;-*@YwJN3|T4(W|Rvto^9a%CyOub~<1`H^) zz$5!8hJHu+id6PupbAf9eL?`N$&dEF&;WC|(7IId)EK&(TD_Rqz5|}DIxsr5XH~mf z_I__Tq!%0d>>wlPag)QG7$W$ogEK1gW$KX9U_NiJCl+VqG43t4ZCb5DN8bqc@I?Fo zTcuTG3*Uav!vFdN0J-}p1XL5G%Uqcyv5o(Th1=sy-Qae_#r3qRbW1WibIZkXgWB9K zHy@*ClF}(%xS}m+zI|_dhEh9S#w3>-BNI2ZuSK$D)oHl+re!8GB7b>ac^eGoLsktf zz%3TU2h+-pEvgw88x!T!S=pbqkh>Yd8uC-vAm2zmyH!+*l{ohN=gJB~NG7pMl0~om z+eq!&=7dH3qu?SHUjx7Sj3*przd6pV{g}tpdJ$gugqXls| zl#|3!vi!`r)YemnyQ&Mp`=oE@iJlu$?Zd7!3Bi)RxP|G zdXer>%RuX~uJGByWJSHLMrFNhCFom1rXJ_3T#c1p@Nv18qhNkP&BC5Cv3D-dI?1+?LJ zalEyGr=dEq3EBTL_G^vWd9tWUT@tp$8LeaeFjz1RISwxz8$32~a?H6P!->#0;hk$pM4U%I`J}lT!hQm{how0l zizck3p>LQBwvgVOyBTmZxDDz97`XJyEa`se49wG?y&{CZt!CA3E8HYXniIL>>Sh_m z+}J!qkau;)boMYs)teu5+1^Ke=*Goug@fLw;2tKHucZv{R?k@Oo9yQDqIH3Z2Q79u zrGtf;K<7K>%@#%|w%<`b4;t!N23}U$2JCpEg{&M>h?vGCIU2Xw5(>3=5KLQt3uNK< z2_Pio6!fQ(4X?HP{2Bd`;-0S~ce~^3{_1A%;UVSBZ`A`VS55$T+QWj2P5}K(x5>eV zqLy3HTDICn+aX}r!J&o{T;XJi(~Jhd+NCjWzO>(`^&akPRKq;;K(yEHO0N6nxK@ch z7D>Q3Ln{*fQvX^Ir`4*C37wndVj7H~fvg;2`xct6`-`Xx#a2{DxMbD&xArt)@XyRL zA185_q>mO(P93GCWjEXsfU8s^7O0^MlXZ6?W-tK-zFyy)#`-K_9{05HR8Ov?3aUj>;%f41WpVK$w;N)#r(>wA6xZkQdx4XTTOT9=n?Dvyv?{UL{bC zB}nH71r(d}FqP>wId5Lt)GQQ4-{0|CSn8>YyTK!!lmVNqdtd-v^Hj=5p+^`f7fh{G zhu76Z#y}A9$*B!#WyPBW##e1$V}eh=ik@oFqwRZHj1aCBL*)35S7-0A+wL_Y>IvCi zIfm(Fmc1ES&8xteF#HV%bJy`&e0ZF$+CWW5G`I`)7Vgy?kW!Qa9ATy{IFsl2%YO#8 zKxO>V-e5?m(<2tS9sUn!-@XzVuX&UEw#G;>u3~!W>f8?nt!rxLQ?#oY>3$L6CT?Be zj#vDFQ?$f{OY!B$4=`11NGrZdks!pYWq<2jZ2Zd2h1~*Ay)w^`iIxV4ww(8e4|q&2 zqOulQnB-R-(j}`L-v^KC>1NTE_~pX?t$f-3a9cW_J_)F~Su>&X96vvW25m)lTYzS* zA6dkiSlkb}o@Mqf)qKDXMWn!1XBP~u`$w%^j>J^OJNq-kqx?c^4K|Q%)5`Ay0|AKoXxUmXyhS$1K0d3{&(e*k*hzmODmS$}I1* z7J{EgV7KeNWP}R<`fnrv0IAg5_`G-ePHb1CdEFCeX+i__GHOn`V%9Z2C5o@wJ1_Q$ zJ{EZat=*5Gt5P5QeWW=T&j!f?ZQIp)iGw!xO|!}pQJ2G3FKvEDAM`Jk336u~
m zhmsy_Q*@f5p&kCu)RB-?)1z5q9F5yrz}?h+b(H_zI@-RM#7Fy?s8!kB{JOMhyxW@p|+Q6H&kN#lxn(bj(naN~br zaB8E=I)9^^?xR_Z*Hg*8KQ4zxzgosURcL9}iskcB)Q(j6!1S4O`CqwVCu9EyYIOn2 literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestHandlerFactory.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestHandlerFactory.java deleted file mode 100644 index d7cf754cfb9fb..0000000000000 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestHandlerFactory.java +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (c) 2010-2021 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; - -import static java.util.stream.Collectors.toSet; -import static org.openhab.binding.nest.internal.NestBindingConstants.*; - -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; - -import javax.ws.rs.client.ClientBuilder; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.nest.internal.discovery.NestDiscoveryService; -import org.openhab.binding.nest.internal.handler.NestBridgeHandler; -import org.openhab.binding.nest.internal.handler.NestCameraHandler; -import org.openhab.binding.nest.internal.handler.NestSmokeDetectorHandler; -import org.openhab.binding.nest.internal.handler.NestStructureHandler; -import org.openhab.binding.nest.internal.handler.NestThermostatHandler; -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.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.jaxrs.client.SseEventSourceFactory; - -/** - * The {@link NestHandlerFactory} is responsible for creating things and thing - * handlers. It also sets up the discovery service to track things from the bridge - * when the bridge is created. - * - * @author David Bennett - Initial contribution - */ -@NonNullByDefault -@Component(service = ThingHandlerFactory.class, configurationPid = "binding.nest") -public class NestHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Stream.of(THING_TYPE_THERMOSTAT, - THING_TYPE_CAMERA, THING_TYPE_BRIDGE, THING_TYPE_STRUCTURE, THING_TYPE_SMOKE_DETECTOR).collect(toSet()); - - private final ClientBuilder clientBuilder; - private final SseEventSourceFactory eventSourceFactory; - private final Map> discoveryService = new HashMap<>(); - - @Activate - public NestHandlerFactory(@Reference ClientBuilder clientBuilder, - @Reference SseEventSourceFactory eventSourceFactory) { - this.clientBuilder = clientBuilder; - this.eventSourceFactory = eventSourceFactory; - } - - /** - * The things this factory supports creating. - */ - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); - } - - /** - * Creates a handler for the specific thing. THis also creates the discovery service - * when the bridge is created. - */ - @Override - protected @Nullable ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - if (THING_TYPE_THERMOSTAT.equals(thingTypeUID)) { - return new NestThermostatHandler(thing); - } - - if (THING_TYPE_CAMERA.equals(thingTypeUID)) { - return new NestCameraHandler(thing); - } - - if (THING_TYPE_STRUCTURE.equals(thingTypeUID)) { - return new NestStructureHandler(thing); - } - - if (THING_TYPE_SMOKE_DETECTOR.equals(thingTypeUID)) { - return new NestSmokeDetectorHandler(thing); - } - - if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { - NestBridgeHandler handler = new NestBridgeHandler((Bridge) thing, clientBuilder, eventSourceFactory); - NestDiscoveryService service = new NestDiscoveryService(handler); - service.activate(); - // 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 NestBridgeHandler) { - ServiceRegistration reg = discoveryService.get(thingHandler.getThing().getUID()); - if (reg != null) { - // Unregister the discovery service. - NestDiscoveryService service = (NestDiscoveryService) bundleContext.getService(reg.getReference()); - service.deactivate(); - reg.unregister(); - discoveryService.remove(thingHandler.getThing().getUID()); - } - } - super.removeHandler(thingHandler); - } -} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/discovery/NestDiscoveryService.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/discovery/NestDiscoveryService.java deleted file mode 100644 index 14ca64a2e402e..0000000000000 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/discovery/NestDiscoveryService.java +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Copyright (c) 2010-2021 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.discovery; - -import static org.openhab.binding.nest.internal.NestBindingConstants.*; -import static org.openhab.core.thing.Thing.PROPERTY_FIRMWARE_VERSION; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.nest.internal.config.NestDeviceConfiguration; -import org.openhab.binding.nest.internal.config.NestStructureConfiguration; -import org.openhab.binding.nest.internal.data.BaseNestDevice; -import org.openhab.binding.nest.internal.data.Camera; -import org.openhab.binding.nest.internal.data.SmokeDetector; -import org.openhab.binding.nest.internal.data.Structure; -import org.openhab.binding.nest.internal.data.Thermostat; -import org.openhab.binding.nest.internal.handler.NestBridgeHandler; -import org.openhab.binding.nest.internal.listener.NestThingDataListener; -import org.openhab.core.config.discovery.AbstractDiscoveryService; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * This service connects to the Nest bridge and creates the correct discovery results for Nest devices - * as they are found through the API. - * - * @author David Bennett - Initial contribution - * @author Wouter Born - Add representation properties - */ -@NonNullByDefault -public class NestDiscoveryService extends AbstractDiscoveryService { - - private static final Set SUPPORTED_THING_TYPES = Stream - .of(THING_TYPE_CAMERA, THING_TYPE_THERMOSTAT, THING_TYPE_SMOKE_DETECTOR, THING_TYPE_STRUCTURE) - .collect(Collectors.toSet()); - - private final Logger logger = LoggerFactory.getLogger(NestDiscoveryService.class); - - private final DiscoveryDataListener cameraDiscoveryDataListener = new DiscoveryDataListener<>(Camera.class, - THING_TYPE_CAMERA, this::addDeviceDiscoveryResult); - private final DiscoveryDataListener smokeDetectorDiscoveryDataListener = new DiscoveryDataListener<>( - SmokeDetector.class, THING_TYPE_SMOKE_DETECTOR, this::addDeviceDiscoveryResult); - private final DiscoveryDataListener structureDiscoveryDataListener = new DiscoveryDataListener<>( - Structure.class, THING_TYPE_STRUCTURE, this::addStructureDiscoveryResult); - private final DiscoveryDataListener thermostatDiscoveryDataListener = new DiscoveryDataListener<>( - Thermostat.class, THING_TYPE_THERMOSTAT, this::addDeviceDiscoveryResult); - - @SuppressWarnings("rawtypes") - private final List discoveryDataListeners = Stream.of(cameraDiscoveryDataListener, - smokeDetectorDiscoveryDataListener, structureDiscoveryDataListener, thermostatDiscoveryDataListener) - .collect(Collectors.toList()); - - private final NestBridgeHandler bridge; - - private static class DiscoveryDataListener implements NestThingDataListener { - private Class dataClass; - private ThingTypeUID thingTypeUID; - private BiConsumer onDiscovered; - - private DiscoveryDataListener(Class dataClass, ThingTypeUID thingTypeUID, - BiConsumer onDiscovered) { - this.dataClass = dataClass; - this.thingTypeUID = thingTypeUID; - this.onDiscovered = onDiscovered; - } - - @Override - public void onNewData(T data) { - onDiscovered.accept(data, thingTypeUID); - } - - @Override - public void onUpdatedData(T oldData, T data) { - } - - @Override - public void onMissingData(String nestId) { - } - } - - public NestDiscoveryService(NestBridgeHandler bridge) { - super(SUPPORTED_THING_TYPES, 60, true); - this.bridge = bridge; - } - - @SuppressWarnings("unchecked") - public void activate() { - discoveryDataListeners.forEach(l -> bridge.addThingDataListener(l.dataClass, l)); - addDiscoveryResultsFromLastUpdates(); - } - - @Override - @SuppressWarnings("unchecked") - public void deactivate() { - discoveryDataListeners.forEach(l -> bridge.removeThingDataListener(l.dataClass, l)); - } - - @Override - protected void startScan() { - addDiscoveryResultsFromLastUpdates(); - } - - @SuppressWarnings("unchecked") - private void addDiscoveryResultsFromLastUpdates() { - discoveryDataListeners - .forEach(l -> addDiscoveryResultsFromLastUpdates(l.dataClass, l.thingTypeUID, l.onDiscovered)); - } - - private void addDiscoveryResultsFromLastUpdates(Class dataClass, ThingTypeUID thingTypeUID, - BiConsumer onDiscovered) { - List lastUpdates = bridge.getLastUpdates(dataClass); - lastUpdates.forEach(lastUpdate -> onDiscovered.accept(lastUpdate, thingTypeUID)); - } - - private void addDeviceDiscoveryResult(BaseNestDevice device, ThingTypeUID typeUID) { - ThingUID bridgeUID = bridge.getThing().getUID(); - ThingUID thingUID = new ThingUID(typeUID, bridgeUID, device.getDeviceId()); - logger.debug("Discovered {}", thingUID); - Map properties = new HashMap<>(); - properties.put(NestDeviceConfiguration.DEVICE_ID, device.getDeviceId()); - properties.put(PROPERTY_FIRMWARE_VERSION, device.getSoftwareVersion()); - // @formatter:off - thingDiscovered(DiscoveryResultBuilder.create(thingUID) - .withThingType(typeUID) - .withLabel(device.getNameLong()) - .withBridge(bridgeUID) - .withProperties(properties) - .withRepresentationProperty(NestDeviceConfiguration.DEVICE_ID) - .build() - ); - // @formatter:on - } - - public void addStructureDiscoveryResult(Structure structure, ThingTypeUID typeUID) { - ThingUID bridgeUID = bridge.getThing().getUID(); - ThingUID thingUID = new ThingUID(typeUID, bridgeUID, structure.getStructureId()); - logger.debug("Discovered {}", thingUID); - Map properties = new HashMap<>(); - properties.put(NestStructureConfiguration.STRUCTURE_ID, structure.getStructureId()); - // @formatter:off - thingDiscovered(DiscoveryResultBuilder.create(thingUID) - .withThingType(THING_TYPE_STRUCTURE) - .withLabel(structure.getName()) - .withBridge(bridgeUID) - .withProperties(properties) - .withRepresentationProperty(NestStructureConfiguration.STRUCTURE_ID) - .build() - ); - // @formatter:on - } -} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/SDMBindingConstants.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/SDMBindingConstants.java new file mode 100644 index 0000000000000..5296d1b14ba95 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/SDMBindingConstants.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2010-2021 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.sdm; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link SDMBindingConstants} class defines common constants, which are used across the whole binding. + * + * @author Brian Higginbotham - Initial contribution + */ +@NonNullByDefault +public class SDMBindingConstants { + + private static final String BINDING_ID = "nest"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "sdm_account"); + public static final ThingTypeUID THING_TYPE_CAMERA = new ThingTypeUID(BINDING_ID, "sdm_camera"); + public static final ThingTypeUID THING_TYPE_DISPLAY = new ThingTypeUID(BINDING_ID, "sdm_display"); + public static final ThingTypeUID THING_TYPE_DOORBELL = new ThingTypeUID(BINDING_ID, "sdm_doorbell"); + public static final ThingTypeUID THING_TYPE_THERMOSTAT = new ThingTypeUID(BINDING_ID, "sdm_thermostat"); + + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT, THING_TYPE_CAMERA, + THING_TYPE_DISPLAY, THING_TYPE_DOORBELL, THING_TYPE_THERMOSTAT); + + // List of all Channel ids + public static final String CHANNEL_CHIME_EVENT = "doorbellChimeEvent"; + public static final String CHANNEL_CHIME_EVENT_IMAGE = "doorbellChimeEventImage"; + public static final String CHANNEL_CHIME_LAST_EVENT_TIME = "doorbellChimeLastEventTime"; + public static final String CHANNEL_DOORBELL_NAME = "doorbellName"; + public static final String CHANNEL_LIVE_STREAM_URL = "doorbellLiveStreamUrl"; + public static final String CHANNEL_LIVE_STREAM_CURRENT_TOKEN = "doorbellLiveStreamCurrentToken"; + public static final String CHANNEL_LIVE_STREAM_EXPIRATION_TIME = "doorbellLiveStreamExpirationTime"; + public static final String CHANNEL_LIVE_STREAM_EXTENSION_TOKEN = "doorbellLiveStreamExtensionToken"; + public static final String CHANNEL_MOTION_EVENT = "doorbellMotionEvent"; + public static final String CHANNEL_MOTION_EVENT_IMAGE = "doorbellMotionEventImage"; + public static final String CHANNEL_MOTION_LAST_EVENT_TIME = "doorbellMotionLastEventTime"; + public static final String CHANNEL_PERSON_EVENT = "doorbellPersonEvent"; + public static final String CHANNEL_PERSON_EVENT_IMAGE = "doorbellPersonEventImage"; + public static final String CHANNEL_PERSON_LAST_EVENT_TIME = "doorbellPersonLastEventTime"; + public static final String CHANNEL_SOUND_EVENT = "doorbellSoundEvent"; + public static final String CHANNEL_SOUND_EVENT_IMAGE = "doorbellSoundEventImage"; + public static final String CHANNEL_SOUND_LAST_EVENT_TIME = "doorbellSoundLastEventTime"; + + public static final String CHANNEL_AMBIENT_TEMPERATURE = "thermostatAmbientTemperature"; + public static final String CHANNEL_AMBIENT_HUMIDITY = "thermostatAmbientHumidityPercent"; + public static final String CHANNEL_CURRENT_MODE = "thermostatCurrentMode"; + public static final String CHANNEL_CURRENT_ECO_MODE = "thermostatCurrentEcoMode"; + public static final String CHANNEL_FAN_MODE = "thermostatFanMode"; + public static final String CHANNEL_HVAC_STATUS = "thermostatHVACStatus"; + public static final String CHANNEL_MINIMUM_TEMPERATURE = "thermostatMinimumTemperature"; + public static final String CHANNEL_MAXIMUM_TEMPERATURE = "thermostatMaximumTemperature"; + public static final String CHANNEL_SCALE_SETTING = "thermostatScaleSetting"; + public static final String CHANNEL_TARGET_TEMPERATURE = "thermostatTargetTemperature"; + public static final String CHANNEL_THERMOSTAT_NAME = "thermostatName"; +} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/SDMThingHandlerFactory.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/SDMThingHandlerFactory.java new file mode 100644 index 0000000000000..6285f837c57c2 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/SDMThingHandlerFactory.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010-2021 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.sdm; + +import static org.openhab.binding.nest.internal.sdm.SDMBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.nest.internal.sdm.handler.SDMAccountHandler; +import org.openhab.binding.nest.internal.sdm.handler.SDMDoorbellHandler; +import org.openhab.binding.nest.internal.sdm.handler.SDMThermostatHandler; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Component; + +/** + * The {@link SDMThingHandlerFactory} is responsible for creating things and thing handlers. + * + * @author Brian Higginbotham - Initial contribution + */ +@Component(service = ThingHandlerFactory.class, configurationPid = "binding.nest") +@NonNullByDefault +public class SDMThingHandlerFactory extends BaseThingHandlerFactory { + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thingTypeUID.equals(THING_TYPE_ACCOUNT)) { + return new SDMAccountHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_CAMERA)) { + return new SDMDoorbellHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_DISPLAY)) { + return new SDMDoorbellHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_DOORBELL)) { + return new SDMDoorbellHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_THERMOSTAT)) { + return new SDMThermostatHandler(thing); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/SDMUtility.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/SDMUtility.java new file mode 100644 index 0000000000000..b2560241c14ea --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/SDMUtility.java @@ -0,0 +1,289 @@ +/** + * Copyright (c) 2010-2021 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.sdm; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +import org.openhab.core.thing.Thing; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest; +import com.google.api.client.auth.oauth2.TokenResponse; +import com.google.api.client.auth.oauth2.TokenResponseException; +import com.google.api.client.googleapis.auth.oauth2.GoogleRefreshTokenRequest; +import com.google.api.client.http.BasicAuthentication; +import com.google.api.client.http.ByteArrayContent; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.auth.oauth2.AccessToken; +import com.google.common.net.HttpHeaders; + +/** + * The {@link SDMUtility} is general utility class to help with all Nest SDM APIs + * + * @author Brian Higginbotham - Initial contribution + */ +public class SDMUtility { + + public SDMUtility(Thing thing) { + if (thing != null) { + this.thing = thing; + deviceId = this.thing.getProperties().get("deviceId"); + clientId = this.thing.getProperties().get("clientId"); + clientSecret = this.thing.getProperties().get("clientSecret"); + projectId = this.thing.getProperties().get("projectId"); + refreshToken = this.thing.getProperties().get("refreshToken"); + accessToken = this.thing.getProperties().get("accessToken"); + accessTokenExpiration = this.thing.getProperties().get("accessTokenExpiration"); + serviceAccountPath = this.thing.getProperties().get("serviceAccountPath"); + subscriptionId = this.thing.getProperties().get("subscriptionId"); + pubsubProjectId = this.thing.getProperties().get("pubsubProjectId"); + + SimpleDateFormat format = new SimpleDateFormat("E MMM dd HH:mm:ss z yyyy"); + + try { + if (accessTokenExpiration != null) { + Date date = format.parse(accessTokenExpiration); + googleAccessToken = new AccessToken(accessToken, date); + } else { + googleAccessToken = refreshAccessToken(refreshToken, clientId, clientSecret); + thing.setProperty("accessTokenExpiration", googleAccessToken.getExpirationTime().toString()); + } + } catch (ParseException e) { + logger.debug( + "NestUtility constructor failed to parse date {}.. Let's get the access token without date", + e.getMessage()); + // Added logic with help of Fraltav to test/troubleshoot. Issue dealt with failure in parsing date. + // Fix: Added logic to refreshAccessToken in the parsing exception + try { + googleAccessToken = refreshAccessToken(refreshToken, clientId, clientSecret); + thing.setProperty("accessTokenExpiration", googleAccessToken.getExpirationTime().toString()); + } catch (IOException ex) { + logger.debug( + "NestUtility constructor reporting failure to get the accessToken. Further failures will be expected as the accessToken is incomplete.."); + } + + } catch (IOException e) { + logger.debug("NestUtility constructor failed with exception {}", e.getMessage()); + } + + } + } + + public SDMUtility(String projectId, String clientId, String clientSecret, String refreshToken, + AccessToken accessToken) { + // secondary constructor for null thing and discovery service/non-basehandlers + if (thing == null) { + this.projectId = projectId; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.refreshToken = refreshToken; + this.googleAccessToken = accessToken; + } + } + + Thing thing; + + private final Logger logger = LoggerFactory.getLogger(SDMUtility.class); + + private String deviceId; + private String clientId; + private String clientSecret; + private String projectId; + private String refreshToken; + private String authorizationToken; + private String accessToken; + private String accessTokenExpiration; + private String serviceAccountPath; + private String subscriptionId; + private String pubsubProjectId; + private long accessTokenExpiresIn; + private AccessToken googleAccessToken; + + // helper local vars. Will set global properties file + + public AccessToken getAccessToken() throws IOException { + try { + if (isAccessTokenExpired()) { + // we need to refresh the access token + return refreshAccessToken(refreshToken, clientId, clientSecret); + } else { + return googleAccessToken; + } + } catch (IOException e) { + throw new IOException(e.getMessage()); + } + } + + public AccessToken setAccessToken(String accessToken, int accessTokenExpiresIn) { + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.SECOND, accessTokenExpiresIn); + logger.debug("setAccessToken setting.... {}", calendar.getTime()); + googleAccessToken = new AccessToken(accessToken, calendar.getTime()); + logger.debug("googleAccessToken setting.... {}", googleAccessToken.getExpirationTime()); + return googleAccessToken; + } + + public boolean isAccessTokenExpired() { + return googleAccessToken.getExpirationTime().compareTo(Calendar.getInstance().getTime()) < 0; + } + + public String deviceExecuteCommand(String deviceId, String projectId, String accessToken, String requestBody) + throws IOException { + try { + HttpTransport transport = new NetHttpTransport(); + HttpRequest request = transport.createRequestFactory().buildPostRequest( + new GenericUrl("https://smartdevicemanagement.googleapis.com/v1/enterprises/" + projectId + + "/devices/" + deviceId + ":executeCommand"), + ByteArrayContent.fromString("application/json", requestBody)); + request.getHeaders().set(HttpHeaders.CONTENT_TYPE, "application/json"); + request.getHeaders().set(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken().getTokenValue()); + + HttpResponse response = request.execute(); + return convertStreamtoString(response.getContent()); + } catch (IOException e) { + throw new IOException(e.getMessage()); + } + } + + public String getDeviceInfo(String url) throws IOException { + try { + HttpTransport transport = new NetHttpTransport(); + HttpRequest request = transport.createRequestFactory().buildGetRequest(new GenericUrl(url)); + + request.getHeaders().set(HttpHeaders.CONTENT_TYPE, "application/json"); + request.getHeaders().set(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken().getTokenValue()); + HttpResponse response = request.execute(); + + return convertStreamtoString(response.getContent()); + } catch (IOException e) { + throw new IOException(e.getMessage()); // never should get here unless there is a problem + } + } + + public AccessToken refreshAccessToken(String refreshToken, String clientId, String clientSecret) + throws IOException { + try { + String accessToken; + TokenResponse response = new GoogleRefreshTokenRequest(new NetHttpTransport(), new JacksonFactory(), + refreshToken, clientId, clientSecret).execute(); + // logger.info("Access Token: {}", response.getAccessToken()); + // logger.info("Refresh Token Lifespan: {}", response.getExpiresInSeconds()); + accessToken = response.getAccessToken(); + accessTokenExpiresIn = response.getExpiresInSeconds(); + googleAccessToken = setAccessToken(accessToken, (int) accessTokenExpiresIn); + if (thing != null) { + thing.setProperty("accessTokenExpiresIn", response.getExpiresInSeconds().toString()); + thing.setProperty("accessToken", googleAccessToken.getTokenValue()); + thing.setProperty("accessTokenExpiration", googleAccessToken.getExpirationTime().toString()); + } + return googleAccessToken; + } catch (TokenResponseException e) { + logger.debug("refreshAccessToken threw an exception {}", e.getDetails().getError()); + if (e.getDetails().getErrorDescription() != null) { + logger.debug("refreshAccessToken threw further description {}", e.getDetails().getErrorDescription()); + } + throw new IOException(e.getMessage()); + } + } + + public String[] requestAccessToken(String clientId, String clientSecret, String authorizationToken) + throws IOException { + try { + String accessToken; + String[] tokens = new String[2]; + TokenResponse response = new AuthorizationCodeTokenRequest(new NetHttpTransport(), new JacksonFactory(), + new GenericUrl("https://www.googleapis.com/oauth2/v4/token"), authorizationToken) + .setRedirectUri("https://www.google.com") + .setClientAuthentication(new BasicAuthentication(clientId, clientSecret)).execute(); + accessToken = response.getAccessToken(); + refreshToken = response.getRefreshToken(); + + accessTokenExpiresIn = response.getExpiresInSeconds(); + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.SECOND, (int) accessTokenExpiresIn); + + // Construct a proper AccessToken + googleAccessToken = new AccessToken(accessToken, calendar.getTime()); + + tokens[0] = accessToken; + tokens[1] = refreshToken; + if (thing != null) { + thing.setProperty("accessToken", response.getAccessToken()); + thing.setProperty("refreshToken", response.getRefreshToken()); + thing.setProperty("accessTokenExpiresIn", response.getExpiresInSeconds().toString()); + } + return tokens; + } catch (TokenResponseException e) { + throw new IOException(e.getMessage()); + } + } + + /* Took function from online stackoverflow article... credit: Zapnologica */ + private String convertStreamtoString(InputStream is) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + + String line = ""; + try { + while ((line = reader.readLine()) != null) { + sb.append(line + "\n"); + } + is.close(); + return sb.toString(); + } catch (IOException e) { + throw new IOException(e.getMessage()); + } + } + + public String getDevices() throws IOException { + try { + HttpTransport transport = new NetHttpTransport(); + HttpRequest request = transport.createRequestFactory().buildGetRequest(new GenericUrl( + "https://smartdevicemanagement.googleapis.com/v1/enterprises/" + projectId + "/devices")); + + request.getHeaders().set(HttpHeaders.CONTENT_TYPE, "application/json"); + request.getHeaders().set(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken().getTokenValue()); + HttpResponse response = request.execute(); + + return convertStreamtoString(response.getContent()); + + } catch (IOException e) { + throw new IOException(e.getMessage()); + } + } + + public String getServiceAccountPath() { + return serviceAccountPath; + } + + public String getPubSubProjectId() { + return pubsubProjectId; + } + + public String getSubscriptionId() { + return subscriptionId; + } +} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/config/SDMAccountConfiguration.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/config/SDMAccountConfiguration.java new file mode 100644 index 0000000000000..1a6e7d6de946b --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/config/SDMAccountConfiguration.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2021 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.sdm.config; + +/** + * The {@link SDMAccountConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Brian Higginbotham - Initial contribution + */ +public class SDMAccountConfiguration { + + public String projectId; + public String clientId; + public String clientSecret; + public String authorizationToken; + public String accessToken; + public String refreshToken; + public String accessTokenExpiresIn; + public String deviceId; + public String deviceName; + public String customName; + public String serviceAccountPath; + public String subscriptionId; + public String pubsubProjectId; + public int refreshInterval; +} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/data/SDMDoorbell.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/data/SDMDoorbell.java new file mode 100644 index 0000000000000..4b65c3d3572b6 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/data/SDMDoorbell.java @@ -0,0 +1,338 @@ +/** + * Copyright (c) 2010-2021 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.sdm.data; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.openhab.binding.nest.internal.sdm.SDMUtility; +import org.openhab.core.library.types.RawType; +import org.openhab.core.thing.Thing; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.common.net.HttpHeaders; + +/** + * The {@link SDMDoorbell} is responsible for handling all attributes and features of a Nest Doorbell + * + * @author Brian Higginbotham - Initial contribution + */ +public class SDMDoorbell { + + public SDMDoorbell(Thing thing) { + if (thing != null) { + this.thing = thing; + nestUtility = new SDMUtility(this.thing); + } + } + + public Thing thing; + public SDMUtility nestUtility; + + // Thermostat properties + public String deviceName; + public String deviceType; + public String deviceCustomName; + public String deviceStatus; + public Date cameraSoundEventTime; + private boolean cameraSoundEvent; + private Date cameraMotionEventTime; + private boolean cameraMotionEvent; + private Date cameraPersonEventTime; + private boolean cameraPersonEvent; + private Date cameraChimeEventTime; + private boolean cameraChimeEvent; + private String streamUrl; + private String streamToken; + private Date streamExpiresAt; + private String streamExtensionToken; + public int[] deviceMaxImageResolution; + public int[] deviceMaxVideoResolution; + public String[] deviceVideoResolution; + public String[] deviceParentRelations; + public String[] deviceVideoCodecs; + public String[] deviceAudioCodecs; + public String[] deviceSupportedProtocols; + + public boolean parseDoorbellInfo(String jsonContent) { + JSONObject jo = new JSONObject(jsonContent); + + deviceParentRelations = new String[2]; + deviceType = jo.getString("type"); + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Info").has("customName")) { + deviceCustomName = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Info") + .getString("customName"); + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.CameraLiveStream").has("maxVideoResolution")) { + deviceMaxVideoResolution = new int[2]; + + deviceMaxVideoResolution[0] = jo.getJSONObject("traits") + .getJSONObject("sdm.devices.traits.CameraLiveStream").getJSONObject("maxVideoResolution") + .getInt("width"); + deviceMaxVideoResolution[1] = jo.getJSONObject("traits") + .getJSONObject("sdm.devices.traits.CameraLiveStream").getJSONObject("maxVideoResolution") + .getInt("height"); + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.CameraLiveStream").has("videoCodecs")) { + JSONArray jaVideoCodecs = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.CameraLiveStream") + .getJSONArray("videoCodecs"); + deviceVideoCodecs = new String[jaVideoCodecs.length()]; + for (int nCount = 0; nCount < jaVideoCodecs.length(); nCount++) { + deviceVideoCodecs[nCount] = jaVideoCodecs.getString(nCount); + } + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.CameraLiveStream").has("audioCodecs")) { + JSONArray jaAudioCodecs = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.CameraLiveStream") + .getJSONArray("audioCodecs"); + deviceAudioCodecs = new String[jaAudioCodecs.length()]; + for (int nCount = 0; nCount < jaAudioCodecs.length(); nCount++) { + deviceVideoCodecs[nCount] = jaAudioCodecs.getString(nCount); + } + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.CameraLiveStream").has("supportedProtocols")) { + JSONArray jaSupportedProtocols = jo.getJSONObject("traits") + .getJSONObject("sdm.devices.traits.CameraLiveStream").getJSONArray("supportedProtocols"); + deviceSupportedProtocols = new String[jaSupportedProtocols.length()]; + for (int nCount = 0; nCount < jaSupportedProtocols.length(); nCount++) { + deviceVideoCodecs[nCount] = jaSupportedProtocols.getString(nCount); + } + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.CameraLiveStream").has("maxImageResolution")) { + deviceMaxImageResolution = new int[2]; + deviceMaxImageResolution[0] = jo.getJSONObject("traits") + .getJSONObject("sdm.devices.traits.CameraLiveStream").getJSONObject("maxImageResolution") + .getInt("width"); + deviceMaxImageResolution[1] = jo.getJSONObject("traits") + .getJSONObject("sdm.devices.traits.CameraLiveStream").getJSONObject("maxImageResolution") + .getInt("height"); + } + + JSONArray jaParentRelations = jo.getJSONArray("parentRelations"); + + for (int nCount = 0; nCount < jaParentRelations.length(); nCount++) { + // get Available Modes + deviceParentRelations[0] = jaParentRelations.getJSONObject(nCount).getString("parent"); + deviceParentRelations[1] = jaParentRelations.getJSONObject(nCount).getString("displayName"); + break; + } + deviceName = deviceParentRelations[1]; + + return true; + } + + public String getDevices() throws IOException { + try { + return nestUtility.getDevices(); + } catch (IOException e) { + throw new IOException(e.getMessage()); + } + } + + public boolean initializeDoorbell() throws IOException { + return getDoorbellInfo(); + } + + public boolean setCameraSoundEvent(Date value) { + cameraSoundEventTime = value; + cameraSoundEvent = true; + return true; + } + + public boolean getCameraSoundEvent() { + return cameraSoundEvent; + } + + public Date getCameraSoundEventTime() { + return cameraSoundEventTime; + } + + public boolean setCameraPersonEvent(Date value) { + cameraPersonEventTime = value; + cameraPersonEvent = true; + return true; + } + + public boolean getCameraPersonEvent() { + return cameraPersonEvent; + } + + public Date getCameraPersonEventTime() { + return cameraPersonEventTime; + } + + public boolean setCameraMotionEvent(Date value) { + cameraMotionEventTime = value; + cameraMotionEvent = true; + return true; + } + + public boolean getCameraMotionEvent() { + return cameraMotionEvent; + } + + public Date getCameraMotionEventTime() { + return cameraMotionEventTime; + } + + public boolean setCameraChimeEvent(Date value) { + cameraChimeEventTime = value; + cameraChimeEvent = true; + return true; + } + + public boolean getCameraChimeEvent() { + return cameraChimeEvent; + } + + public Date getCameraChimeEventTime() { + return cameraChimeEventTime; + } + + public int[] getMaxImageResolution() { + return deviceMaxImageResolution; + } + + public String[] getVideoCodecs() { + return deviceVideoCodecs; + } + + public String[] getAudioCodecs() { + return deviceAudioCodecs; + } + + public String[] getSupportedProtocols() { + return deviceSupportedProtocols; + } + + public int[] getMaxVideoResolution() { + return deviceMaxVideoResolution; + } + + public String getDeviceName() { + return deviceName; + } + + public String getCustomName() { + return deviceCustomName; + } + + public String getStreamUrl() { + return streamUrl; + } + + public String getStreamToken() { + return streamToken; + } + + public Date getStreamTokenExpiration() { + return streamExpiresAt; + } + + public String getStreamExensionToken() { + return streamExtensionToken; + } + + public boolean isImageValid(Date messageTime) { + // check if image is stale + Date currentTime = new Date(); + long diffMilliseconds = currentTime.getTime() - messageTime.getTime(); + long diffSeconds = (diffMilliseconds / 1000) % 60; + + return diffSeconds <= 30; + } + + public boolean getCameraLiveStream() throws IOException { + String requestContent = "{\"command\" : \"sdm.devices.commands.CameraLiveStream.GenerateRtspStream\",\"params\" : {}}"; + try { + String jsonContent = nestUtility.deviceExecuteCommand(thing.getProperties().get("deviceId"), + thing.getProperties().get("projectId"), nestUtility.getAccessToken().getTokenValue(), + requestContent); + JSONObject jo = new JSONObject(jsonContent); + streamUrl = jo.getJSONObject("results").getJSONObject("streamUrls").getString("rtspUrl"); + streamToken = jo.getJSONObject("results").getString("streamToken"); + DateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + utcFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + streamExpiresAt = utcFormat.parse(jo.getJSONObject("results").getString("expiresAt")); + streamExtensionToken = jo.getJSONObject("results").getString("streamExtensionToken"); + + return true; + } catch (IOException e) { + throw new IOException(e.getMessage()); + } catch (JSONException e) { + throw new IOException(e.getMessage()); + } catch (ParseException e) { + throw new IOException(e.getMessage()); + } + } + + public RawType getCameraImage(String eventId) throws IOException { + String requestContent = "{\"command\" : \"sdm.devices.commands.CameraEventImage.GenerateImage\",\"params\" : {\"eventId\" : \"" + + eventId + "\"}}"; + try { + String jsonContent = nestUtility.deviceExecuteCommand(thing.getProperties().get("deviceId"), + thing.getProperties().get("projectId"), nestUtility.getAccessToken().getTokenValue(), + requestContent); + JSONObject jo = new JSONObject(jsonContent); + String url = jo.getJSONObject("results").getString("url"); + String token = jo.getJSONObject("results").getString("token"); + // fetch image + HttpTransport transport = new NetHttpTransport(); + HttpRequest request = transport.createRequestFactory().buildGetRequest(new GenericUrl(url + "?width=480")); + request.getHeaders().set(HttpHeaders.CONTENT_TYPE, "image/jpeg"); + request.getHeaders().set(HttpHeaders.AUTHORIZATION, "Basic " + token); + HttpResponse response = request.execute(); + + ByteArrayOutputStream byteImage = new ByteArrayOutputStream(); + response.download(byteImage); + + RawType image = new RawType(byteImage.toByteArray(), "image/jpeg"); + byteImage.close(); + return image; + } catch (IOException e) { + throw new IOException(e.getMessage()); + } catch (Exception e) { + // general exception + throw new IOException(e.getMessage()); + } + } + + public boolean getDoorbellInfo() throws IOException { + try { + String jsonContent; + jsonContent = nestUtility.getDeviceInfo("https://smartdevicemanagement.googleapis.com/v1/enterprises/" + + thing.getProperties().get("projectId") + "/devices/" + thing.getProperties().get("deviceId")); + return parseDoorbellInfo(jsonContent); + + } catch (IOException e) { + throw new IOException(e.getMessage()); + } + } +} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/data/SDMThermostat.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/data/SDMThermostat.java new file mode 100644 index 0000000000000..8d85db046c9fe --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/data/SDMThermostat.java @@ -0,0 +1,469 @@ +/** + * Copyright (c) 2010-2021 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.sdm.data; + +import java.io.IOException; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.openhab.binding.nest.internal.sdm.SDMUtility; +import org.openhab.core.thing.Thing; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link SDMThermostat} is responsible for handling all attributes and features of a Nest Thermostat + * + * @author Brian Higginbotham - Initial contribution + */ +public class SDMThermostat { + + public SDMThermostat(Thing thing) { + if (thing != null) { + this.thing = thing; + nestUtility = new SDMUtility(this.thing); + } + } + + public Thing thing; + public SDMUtility nestUtility; + + private final Logger logger = LoggerFactory.getLogger(SDMThermostat.class); + + // Thermostat properties + public String deviceName; + public String deviceType; + public String deviceCustomName; + public int deviceHumidityPercent; + public String deviceStatus; + public String deviceFan; + public String deviceId; + public String deviceCurrentThermostatMode; + public String[] deviceAvailableThermostatModes; + public String deviceThermostatEcoMode; + public String[] deviceAvailableThermostatEcoModes; + public double deviceCurrentThermostatEcoHeatCelsius; + public double deviceCurrentThermostatEcoCoolCelsius; + public double deviceCurrentThermostatHeatCelsius; + public double deviceCurrentThermostatCoolCelsius; + public String deviceThermostatHVACStatus; + public String deviceTemperatureScaleSetting; + public double deviceAmbientTemperatureSetting; + public double deviceTargetTemperature; // settings used to aggregate target setting on heat/cool + public double deviceMinTemperature; // settings used to aggregate setting for eco and heat-cool + public double deviceMaxTemperature; // settings used to aggregate setting for eco and heat-cool + public String[] deviceParentRelations; + + public boolean parseThermostatInfo(String jsonContent) { + JSONObject jo = new JSONObject(jsonContent); + + deviceParentRelations = new String[2]; + deviceAvailableThermostatEcoModes = new String[2]; // only two known eco modes + deviceAvailableThermostatModes = new String[4]; + + deviceType = jo.getString("type"); + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Info").has("customName")) { + deviceCustomName = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Info") + .getString("customName"); + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Humidity").has("ambientHumidityPercent")) { + deviceHumidityPercent = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Humidity") + .getInt("ambientHumidityPercent"); + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Connectivity").has("status")) { + deviceStatus = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Connectivity") + .getString("status"); + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Fan").has("timerMode")) { + deviceFan = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Fan").getString("timerMode"); + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatMode").has("mode")) { + deviceCurrentThermostatMode = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatMode") + .getString("mode"); + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatMode").has("availableModes")) { + JSONArray jaAvailableModes = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatMode") + .getJSONArray("availableModes"); + + for (int nCount = 0; nCount < jaAvailableModes.length(); nCount++) { + // get Available Modes + deviceAvailableThermostatModes[nCount] = jaAvailableModes.getString(nCount); + } + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatEco").has("mode")) { + deviceThermostatEcoMode = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatEco") + .getString("mode"); + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatEco").has("heatCelsius")) { + deviceCurrentThermostatEcoHeatCelsius = jo.getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatEco").getFloat("heatCelsius"); + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatEco").has("coolCelsius")) { + deviceCurrentThermostatEcoCoolCelsius = jo.getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatEco").getFloat("coolCelsius"); + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatEco").has("availableModes")) { + JSONArray jaAvailableEcoModes = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatEco") + .getJSONArray("availableModes"); + + for (int nCount = 0; nCount < jaAvailableEcoModes.length(); nCount++) { + // get Available Modes + deviceAvailableThermostatEcoModes[nCount] = jaAvailableEcoModes.getString(nCount); + } + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatHvac").has("status")) { + deviceThermostatHVACStatus = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatHvac") + .getString("status"); + } + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Settings").has("temperatureScale")) { + deviceTemperatureScaleSetting = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Settings") + .getString("temperatureScale"); + } + + if ((jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint") + .has("heatCelsius")) + && (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint") + .has("coolCelsius"))) { + deviceCurrentThermostatHeatCelsius = jo.getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint").getFloat("heatCelsius"); + deviceCurrentThermostatCoolCelsius = jo.getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint").getFloat("coolCelsius"); + // logger.debug("Before temp change min {} max {}", deviceCurrentThermostatHeatCelsius, + // deviceCurrentThermostatCoolCelsius); + } // ThermostatMode = HEATCOOL + else if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint") + .has("coolCelsius")) { + deviceCurrentThermostatCoolCelsius = jo.getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint").getFloat("coolCelsius"); + // logger.debug("Before temp change TargetTemp {}", deviceCurrentThermostatCoolCelsius); + } // ThermostatMode = Off or Heater + else if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint") + .has("heatCelsius")) { + deviceCurrentThermostatHeatCelsius = jo.getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint").getFloat("heatCelsius"); + // logger.debug("Before temp change TargetTemp {}", deviceCurrentThermostatHeatCelsius); + } // ThermostatMode = Off or AC + + if (jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Temperature") + .has("ambientTemperatureCelsius")) { + deviceAmbientTemperatureSetting = jo.getJSONObject("traits").getJSONObject("sdm.devices.traits.Temperature") + .getFloat("ambientTemperatureCelsius"); + } + + JSONArray jaParentRelations = jo.getJSONArray("parentRelations"); + + for (int nCount = 0; nCount < jaParentRelations.length(); nCount++) { + // get Available Modes + deviceParentRelations[0] = jaParentRelations.getJSONObject(nCount).getString("parent"); + deviceParentRelations[1] = jaParentRelations.getJSONObject(nCount).getString("displayName"); + break; + } + deviceName = deviceParentRelations[1]; + + // last thing is to aggregate temperature settings for ease of use + if ((deviceCurrentThermostatMode.equalsIgnoreCase("HEAT")) + && (!deviceThermostatEcoMode.equalsIgnoreCase("MANUAL_ECO"))) { + deviceTargetTemperature = deviceCurrentThermostatHeatCelsius; + // logger.debug("updating heat.. {}", deviceTargetTemperature); + } else if ((deviceCurrentThermostatMode.equalsIgnoreCase("COOL")) + && (!deviceThermostatEcoMode.equalsIgnoreCase("MANUAL_ECO"))) { + deviceTargetTemperature = deviceCurrentThermostatCoolCelsius; + // logger.debug("updating cool..{}", deviceTargetTemperature); + } else if (deviceThermostatEcoMode.equalsIgnoreCase("MANUAL_ECO")) { + deviceMinTemperature = deviceCurrentThermostatEcoHeatCelsius; + deviceMaxTemperature = deviceCurrentThermostatEcoCoolCelsius; + // logger.debug("After temp change Eco TargetTemp {} min {} max {}", deviceTargetTemperature, + // deviceMinTemperature, deviceMaxTemperature); + } else if ((deviceCurrentThermostatMode.equalsIgnoreCase("HEATCOOL")) + && (!deviceThermostatEcoMode.equalsIgnoreCase("MANUAL_ECO"))) { + deviceMinTemperature = deviceCurrentThermostatHeatCelsius; + deviceMaxTemperature = deviceCurrentThermostatCoolCelsius; + // logger.debug("After temp change TargetTemp {} min {} max {}", deviceTargetTemperature, + // deviceMinTemperature, + // deviceMaxTemperature); + } + + return true; + } + + public boolean setThermostatMode(String setting) throws IOException { + String jsonContent = "{\"command\" : \"sdm.devices.commands.ThermostatMode.SetMode\",\"params\" : {\"mode\" : \"" + + setting + "\"}}"; + + try { + String jsonResponse = nestUtility.deviceExecuteCommand(thing.getProperties().get("deviceId"), + thing.getProperties().get("projectId"), thing.getProperties().get("accessToken"), jsonContent); + return true; + } catch (IOException e) { + throw new IOException(e.getMessage()); + } + } + + public boolean setThermostatEcoMode(String setting) throws IOException { + String jsonContent = "{\"command\" : \"sdm.devices.commands.ThermostatEco.SetMode\",\"params\" : {\"mode\" : \"" + + setting + "\"}}"; + try { + String jsonResponse = nestUtility.deviceExecuteCommand(thing.getProperties().get("deviceId"), + thing.getProperties().get("projectId"), thing.getProperties().get("accessToken"), jsonContent); + return true; + } catch (IOException e) { + throw new IOException(e.getMessage()); + } + } + + public boolean setThermostatTargetTemperature(double value, double minValue, double maxValue, boolean typeRange) + throws IOException { + String jsonContent = ""; + logger.debug("setThermostatTargetTemperature value {} minValue {} maxValue {} typeRange {}", value, minValue, + maxValue, typeRange); + try { + if (!typeRange) { + if ((getTemperatureScaleSetting().equalsIgnoreCase("FAHRENHEIT"))) { + value = convertToCelsius(value); + } + if (getThermostatMode().equalsIgnoreCase("COOL")) { + jsonContent = "{\"command\" : \"sdm.devices.commands.ThermostatTemperatureSetpoint.SetCool\",\"params\" : {\"coolCelsius\" : " + + String.valueOf(value) + "}}"; + } else if (getThermostatMode().equalsIgnoreCase("HEAT")) { + jsonContent = "{\"command\" : \"sdm.devices.commands.ThermostatTemperatureSetpoint.SetHeat\",\"params\" : {\"heatCelsius\" : " + + String.valueOf(value) + "}}"; + } else { + // INVALID use case for setThermostatTargetTemperature.. + return false; + } + } else { + if ((getTemperatureScaleSetting().equalsIgnoreCase("FAHRENHEIT"))) { + minValue = convertToCelsius(minValue); + maxValue = convertToCelsius(maxValue); + } + if (getThermostatMode().equalsIgnoreCase("HEATCOOL")) { + jsonContent = "{\"command\" : \"sdm.devices.commands.ThermostatTemperatureSetpoint.SetRange\",\"params\" : {\"heatCelsius\" : " + + String.valueOf(minValue) + ",\"coolCelsius\" : " + String.valueOf(maxValue) + "}}"; + } else { + // INVALID use case for setThermostatTargetTemperature.. + return false; + } + } + + nestUtility.deviceExecuteCommand(thing.getProperties().get("deviceId"), + thing.getProperties().get("projectId"), thing.getProperties().get("accessToken"), jsonContent); + return true; + } catch (IOException e) { + throw new IOException(e.getMessage()); + } + } + + public String getDevices() throws IOException { + try { + return nestUtility.getDevices(); + } catch (IOException e) { + throw new IOException(e.getMessage()); + } + } + + public boolean initializeThermostat() throws IOException { + return getThermostatInfo(); + } + + public int getCurrentHumidity() { + return deviceHumidityPercent; + } + + public int setHumidityPercent(int value) { + deviceHumidityPercent = value; + return deviceHumidityPercent; + } + + public String getDeviceName() { + return deviceName; + } + + public String getCustomName() { + return deviceCustomName; + } + + public String getDeviceStatus() { + return deviceStatus; + } + + public String getDeviceFan() { + return deviceFan; + } + + public String setDeviceFan(String value) { + deviceFan = value; + return deviceFan; + } + + public String getThermostatMode() { + return deviceCurrentThermostatMode; + } + + public String setThermostatModeValue(String value) { + deviceCurrentThermostatMode = value; + return deviceCurrentThermostatMode; + } + + public String[] getAvailableThermostatModes() { + return deviceAvailableThermostatModes; + } + + public String[] setAvailableThermostatModes(String[] value) { + deviceAvailableThermostatModes = value.clone(); + return deviceAvailableThermostatModes; + } + + public String setHVACStatus(String value) { + deviceThermostatHVACStatus = value; + return deviceThermostatHVACStatus; + } + + public String setThermostatEcoModeValue(String value) { + deviceThermostatEcoMode = value; + return deviceThermostatEcoMode; + } + + public String getThermostatEcoMode() { + return deviceThermostatEcoMode; + } + + public String[] getAvailableThermostatEcoModes() { + return deviceAvailableThermostatEcoModes; + } + + public String[] setAvailableThermostatEcoModes(String[] value) { + deviceAvailableThermostatEcoModes = value.clone(); + return deviceAvailableThermostatEcoModes; + } + + public double getCurrentThermostatEcoHeatCelsius() { + if (getTemperatureScaleSetting().equalsIgnoreCase("Fahrenheit")) { + return convertToFahrenheit(deviceCurrentThermostatEcoHeatCelsius); + } else { + return deviceCurrentThermostatEcoHeatCelsius; + } + } + + public double getCurrentThermostatEcoCoolCelsius() { + if (getTemperatureScaleSetting().equalsIgnoreCase("Fahrenheit")) { + return convertToFahrenheit(deviceCurrentThermostatEcoCoolCelsius); + } else { + return deviceCurrentThermostatEcoCoolCelsius; + } + } + + public String getThermostatHVACStatus() { + return deviceThermostatHVACStatus; + } + + public String getTemperatureScaleSetting() { + return deviceTemperatureScaleSetting; + } + + public double getAmbientTemperatureSetting() { + if (getTemperatureScaleSetting().equalsIgnoreCase("Fahrenheit")) { + return convertToFahrenheit(deviceAmbientTemperatureSetting); + } else { + return deviceAmbientTemperatureSetting; + } + } + + public double setAmbientTemperatureSetting(double value) { + if (getTemperatureScaleSetting().equalsIgnoreCase("Fahrenheit")) { + deviceAmbientTemperatureSetting = convertToFahrenheit(value); + return deviceAmbientTemperatureSetting; + } else { + deviceAmbientTemperatureSetting = value; + return deviceAmbientTemperatureSetting; + } + } + + public double getCurrentTemperatureHeat() { + if (getTemperatureScaleSetting().equalsIgnoreCase("Fahrenheit")) { + return convertToFahrenheit(deviceCurrentThermostatHeatCelsius); + } else { + return deviceCurrentThermostatHeatCelsius; + } + } + + public double getCurrentTemperatureCool() { + if (getTemperatureScaleSetting().equalsIgnoreCase("Fahrenheit")) { + return convertToFahrenheit(deviceCurrentThermostatCoolCelsius); + } else { + return deviceCurrentThermostatCoolCelsius; + } + } + + public double getTargetTemperature() { + if (getTemperatureScaleSetting().equalsIgnoreCase("Fahrenheit")) { + return convertToFahrenheit(deviceTargetTemperature); + } else { + return deviceTargetTemperature; + } + } + + public double[] getMinMaxTemperature() { + double[] minMaxValue = new double[2]; + if (getTemperatureScaleSetting().equalsIgnoreCase("Fahrenheit")) { + minMaxValue[0] = convertToFahrenheit(deviceMinTemperature); + minMaxValue[1] = convertToFahrenheit(deviceMaxTemperature); + return minMaxValue; + } else { + minMaxValue[0] = deviceMinTemperature; + minMaxValue[1] = deviceMaxTemperature; + return minMaxValue; + } + } + + public double[] setMinMaxTemperatureValue(double[] value) { + double[] minMaxValue = new double[2]; + + deviceMinTemperature = value[0]; + deviceMaxTemperature = value[1]; + minMaxValue[0] = deviceMinTemperature; + minMaxValue[1] = deviceMaxTemperature; + + return minMaxValue.clone(); + } + + private double convertToFahrenheit(double temperature) { + return ((temperature / 5) * 9) + 32; + } + + private double convertToCelsius(double temperature) { + return (temperature - 32) * 5 / 9; + } + + public boolean getThermostatInfo() throws IOException { + try { + String jsonContent; + jsonContent = nestUtility.getDeviceInfo("https://smartdevicemanagement.googleapis.com/v1/enterprises/" + + thing.getProperties().get("projectId") + "/devices/" + thing.getProperties().get("deviceId")); + return parseThermostatInfo(jsonContent); + } catch (IOException e) { + throw new IOException(e.getMessage()); + } + } +} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/discovery/SDMDiscoveryService.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/discovery/SDMDiscoveryService.java new file mode 100644 index 0000000000000..40a90f8762a3f --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/discovery/SDMDiscoveryService.java @@ -0,0 +1,309 @@ +/** + * Copyright (c) 2010-2021 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.sdm.discovery; + +import static org.openhab.binding.nest.internal.sdm.SDMBindingConstants.*; + +import java.io.IOException; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.openhab.binding.nest.internal.sdm.SDMUtility; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.auth.oauth2.AccessToken; + +/** + * The {@link SDMDiscoveryService} is discovering devices from the SDM API + * + * @author Brian Higginbotham - Initial contribution + */ +@Component(service = DiscoveryService.class, immediate = true, configurationPid = "org.openhab.nest") +public class SDMDiscoveryService extends AbstractDiscoveryService { + + SDMUtility nestUtility; + private final Logger logger = LoggerFactory.getLogger(SDMDiscoveryService.class); + private ExecutorService executorService; + private boolean scanning; + private static final int TIMEOUT = 1000; + + private String projectId; + private String clientSecret; + private String clientId; + private String refreshToken; + private String authorizationToken; + private AccessToken googleAccessToken; + private long accessTokenExpiresIn; + private String serviceAccountPath; + private String subscriptionId; + private String pubsubProjectId; + private boolean initialize; + + /** + * Constructs the discovery class using the thing IDs that we can discover. + */ + public SDMDiscoveryService() { + super(SUPPORTED_THING_TYPES_UIDS, 30, false); + } + + @Activate + protected void activate(ComponentContext context) { + Dictionary properties = context.getProperties(); + + if (properties.isEmpty()) { + logger.debug( + "activate has zero configuration properies.. Please import a nestdeviceaccess.cfg per the documentation into the services folder with the appropriate parameters.."); + initialize = false; + } else { + projectId = (String) properties.get("projectId"); + clientSecret = (String) properties.get("clientSecret"); + clientId = (String) properties.get("clientId"); + refreshToken = (String) properties.get("refreshToken"); + authorizationToken = (String) properties.get("authorizationToken"); + serviceAccountPath = (String) properties.get("serviceAccountPath"); + subscriptionId = (String) properties.get("subscriptionId"); + pubsubProjectId = (String) properties.get("pubsubProjectId"); + + if ((projectId == null || clientId == null || clientSecret == null || refreshToken == null) + && (authorizationToken == null)) { + logger.debug( + "activate in the Discovery service does NOT enough data to initialize.. projectId {}\nclientId {}\nclientSecret{}\nrefreshToken {}\nauthorizationToken {}", + projectId, clientId, clientSecret, refreshToken, authorizationToken); + initialize = false; + } else { + initialize = true; + } + } + } + + private void addThing(String devicesType[], String devicesId[], String devicesCustomName[], String devicesName[], + String devicesStatus[]) { + ThingTypeUID typeId = null; + ThingUID deviceThing = null; + Map properties = null; + DiscoveryResult result = null; + + // Let's check type + for (int i = 0; i < devicesType.length; i++) { + + if (devicesName[i].equalsIgnoreCase("")) { + if (devicesCustomName[i].equalsIgnoreCase("")) { + // make up a name for the device + devicesName[i] = "ChangeMe in my Google Account"; + } else { + // set to custom name + devicesName[i] = devicesCustomName[i]; + } + } + switch (devicesType[i]) { + case "sdm.devices.types.THERMOSTAT": + if (devicesStatus[i].equalsIgnoreCase("online")) { + typeId = THING_TYPE_THERMOSTAT; + deviceThing = new ThingUID(typeId, devicesId[i]); + properties = new HashMap<>(13); + properties.put("deviceId", devicesId[i]); + properties.put("deviceName", devicesName[i]); + properties.put("customName", devicesCustomName[i]); + properties.put("refreshToken", refreshToken); + properties.put("clientId", clientId); + properties.put("clientSecret", clientSecret); + properties.put("accessToken", googleAccessToken.getTokenValue()); + properties.put("accessTokenExpiration", googleAccessToken.getExpirationTime().toString()); + properties.put("projectId", projectId); + if (serviceAccountPath.length() > 0) { + properties.put("serviceAccountPath", serviceAccountPath); + } + if (subscriptionId.length() > 0) { + properties.put("subscriptionId", subscriptionId); + } + if (pubsubProjectId.length() > 0) { + properties.put("pubsubProjectId", pubsubProjectId); + } + result = DiscoveryResultBuilder.create(deviceThing).withProperties(properties) + .withLabel("Nest " + devicesName[i] + " Thermostat").build(); + thingDiscovered(result); + logger.info("SDM Discovery adding Thermostat: [{}] to inbox", devicesName[i]); + break; + } + case "sdm.devices.types.DOORBELL": + typeId = THING_TYPE_DOORBELL; + deviceThing = new ThingUID(typeId, devicesId[i]); + properties = new HashMap<>(13); + properties.put("deviceId", devicesId[i]); + properties.put("deviceName", devicesName[i]); + properties.put("customName", devicesCustomName[i]); + properties.put("refreshToken", refreshToken); + properties.put("clientId", clientId); + properties.put("clientSecret", clientSecret); + properties.put("accessToken", googleAccessToken.getTokenValue()); + properties.put("accessTokenExpiration", googleAccessToken.getExpirationTime().toString()); + properties.put("projectId", projectId); + if (serviceAccountPath.length() > 0) { + properties.put("serviceAccountPath", serviceAccountPath); + } + if (subscriptionId.length() > 0) { + properties.put("subscriptionId", subscriptionId); + } + if (pubsubProjectId.length() > 0) { + properties.put("pubsubProjectId", pubsubProjectId); + } + + result = DiscoveryResultBuilder.create(deviceThing).withProperties(properties) + .withLabel("Nest " + devicesName[i] + " Doorbell").build(); + thingDiscovered(result); + logger.info("SDM Discovery adding Doorbell: [{}] to inbox", devicesName[i]); + break; + + case "sdm.devices.types.CAMERA": + typeId = THING_TYPE_CAMERA; + deviceThing = new ThingUID(typeId, devicesId[i]); + properties = new HashMap<>(13); + properties.put("deviceId", devicesId[i]); + properties.put("deviceName", devicesName[i]); + properties.put("customName", devicesCustomName[i]); + properties.put("refreshToken", refreshToken); + properties.put("clientId", clientId); + properties.put("clientSecret", clientSecret); + properties.put("accessToken", googleAccessToken.getTokenValue()); + properties.put("accessTokenExpiration", googleAccessToken.getExpirationTime().toString()); + properties.put("projectId", projectId); + if (serviceAccountPath.length() > 0) { + properties.put("serviceAccountPath", serviceAccountPath); + } + if (subscriptionId.length() > 0) { + properties.put("subscriptionId", subscriptionId); + } + if (pubsubProjectId.length() > 0) { + properties.put("pubsubProjectId", pubsubProjectId); + } + + result = DiscoveryResultBuilder.create(deviceThing).withProperties(properties) + .withLabel("Nest " + devicesName[i] + " Camera").build(); + thingDiscovered(result); + logger.info("SDM Discovery adding Camera: [{}] to inbox", devicesName[i]); + break; + } + } + } + + /** + * {@inheritDoc} + * + * Starts the scan. This discovery will: + * + */ + @Override + protected void startScan() { + if (executorService != null) { + stopScan(); + } + scanning = true; + logger.debug("Starting SDM Discovery"); + + try { + nestUtility = new SDMUtility(projectId, clientId, clientSecret, refreshToken, googleAccessToken); + if ((!authorizationToken.equals("")) && (refreshToken.equals(""))) { + // initial authorization request. We need to get a refresh token + logger.debug("Initial Access Token being retrieved..."); + String[] tokens = new String[2]; + tokens = nestUtility.requestAccessToken(clientId, clientSecret, authorizationToken); + // outputting refreshToken because this is the first time + logger.debug( + "SDM Discovery reporting an initial refreshToken of [{}]. Make sure you write this down or import it into Karaf..", + tokens[1]); + } else { + // accessToken is typically stale.. Getting fresh on initialization + googleAccessToken = nestUtility.refreshAccessToken(refreshToken, clientId, clientSecret); + logger.debug("discovery service expire access {}", googleAccessToken.getExpirationTime()); + } + String url = "https://smartdevicemanagement.googleapis.com/v1/enterprises/" + projectId + "/devices"; + // get devices for discovery + String jsonContent = nestUtility.getDeviceInfo(url); + + JSONObject jo = new JSONObject(jsonContent); + JSONArray ja = jo.getJSONArray("devices"); + + String[][] devicesParentRelations = new String[ja.length()][2]; + String[] devicesType = new String[ja.length()]; + String[] devicesName = new String[ja.length()]; + String[] devicesCustomName = new String[ja.length()]; + String[] devicesId = new String[ja.length()]; + String[] devicesStatus = new String[ja.length()]; + + for (int i = 0; i < ja.length(); i++) { + devicesName[i] = ja.getJSONObject(i).getString("name"); + devicesId[i] = devicesName[i].substring(devicesName[i].lastIndexOf("/") + 1, devicesName[i].length()); + devicesCustomName[i] = ja.getJSONObject(i).getJSONObject("traits") + .getJSONObject("sdm.devices.traits.Info").getString("customName"); + devicesType[i] = ja.getJSONObject(i).getString("type"); + if (ja.getJSONObject(i).getJSONObject("traits").has("sdm.devices.traits.Connectivity")) { + devicesStatus[i] = ja.getJSONObject(i).getJSONObject("traits") + .getJSONObject("sdm.devices.traits.Connectivity").getString("status"); + } + JSONArray jaParentRelations = ja.getJSONObject(i).getJSONArray("parentRelations"); + + for (int nCount = 0; nCount < jaParentRelations.length(); nCount++) { + // get Available Modes + devicesParentRelations[i][0] = jaParentRelations.getJSONObject(nCount).getString("parent"); + devicesParentRelations[i][1] = jaParentRelations.getJSONObject(nCount).getString("displayName"); + break; + } + devicesName[i] = devicesParentRelations[i][1]; // DisplayName + } + + addThing(devicesType, devicesId, devicesCustomName, devicesName, devicesStatus); + + } catch (IOException e) { + logger.debug("discovery reporting exception {}", e.getMessage()); + } + } + + /** + * {@inheritDoc} + * + * Stops the discovery scan. We set {@link #scanning} to false (allowing the listening threads to end naturally + * within {@link #TIMEOUT) * 5 time then shutdown the {@link #executorService} + */ + @Override + protected synchronized void stopScan() { + super.stopScan(); + /* + * if (executorService == null) { + * return; + * } + * + * scanning = false; + * + * try { + * executorService.awaitTermination(TIMEOUT * 5, TimeUnit.MILLISECONDS); + * } catch (InterruptedException e) { + * } + * executorService.shutdown(); + * executorService = null; + */ + } +} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/handler/SDMAccountHandler.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/handler/SDMAccountHandler.java new file mode 100644 index 0000000000000..0295f3e4968d0 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/handler/SDMAccountHandler.java @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2010-2021 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.sdm.handler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.nest.internal.sdm.SDMBindingConstants; +import org.openhab.binding.nest.internal.sdm.SDMUtility; +import org.openhab.binding.nest.internal.sdm.config.SDMAccountConfiguration; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link SDMAccountHandler} is responsible for handling commands, which are sent to one of the channels. + * + * @author Brian Higginbotham - Initial contribution + */ +@NonNullByDefault +public class SDMAccountHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(SDMAccountHandler.class); + + private SDMUtility nestUtility = new SDMUtility(thing); + + private @Nullable SDMAccountConfiguration config; + + public SDMAccountHandler(Thing thing) { + super(thing); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (SDMBindingConstants.CHANNEL_THERMOSTAT_NAME.equals(channelUID.getId())) { + // if (command instanceof RefreshType) { + // TODO: handle data refresh + // } + + // TODO: handle command + + // Note: if communication with thing fails for some reason, + // indicate that by setting the status with detail information: + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + // "Could not control device at IP address x.x.x.x"); + } + } + + @Override + public void initialize() { + logger.debug("Start initializing!"); + config = getConfigAs(SDMAccountConfiguration.class); + config.projectId = thing.getConfiguration().get("projectId").toString(); + config.clientId = thing.getConfiguration().get("clientId").toString(); + config.clientSecret = thing.getConfiguration().get("clientSecret").toString(); + config.authorizationToken = thing.getConfiguration().get("authorizationToken").toString(); + config.accessToken = thing.getConfiguration().get("accessToken").toString(); + config.refreshToken = thing.getConfiguration().get("refreshToken").toString(); + config.accessTokenExpiresIn = thing.getConfiguration().get("accessTokenExpiresIn").toString(); + // verify accesstoken with simple get request + /* + * try { + * if (nestUtility.getStructures(config.projectId, config.accessToken) == 401) { + * // get access token refreshed + * logger.debug("initialize() reporting access token is expired.."); + * if (nestUtility.refreshAccessToken(config.refreshToken, config.clientId, config.clientSecret)) { + * logger.debug("initialize() reporting access token refresh successful.."); + * if (nestUtility.getStructures(config.projectId, config.accessToken) == 200) { + * config.accessToken = nestUtility.accessToken; + * config.accessTokenExpiresIn = nestUtility.accessTokenExpiresIn; + * logger.debug("initialize() reporting call to getStructures successful"); + * } + * } + * } + * + * } catch (IOException e) { + * logger.debug("initialize() {}", e.getMessage()); + * } + * + * if (thing.getConfiguration().get("accessToken") != null) { + * config.accessToken = thing.getConfiguration().get("accessToken").toString(); + * } + * if (thing.getConfiguration().get("refreshToken") != null) { + * config.refreshToken = thing.getConfiguration().get("refreshToken").toString(); + * } + */ + // TODO: Initialize the handler. + // The framework requires you to return from this method quickly. Also, before leaving this method a thing + // status from one of ONLINE, OFFLINE or UNKNOWN must be set. This might already be the real thing status in + // case you can decide it directly. + // In case you can not decide the thing status directly (e.g. for long running connection handshake using WAN + // access or similar) you should set status UNKNOWN here and then decide the real status asynchronously in the + // background. + + // set the thing status to UNKNOWN temporarily and let the background task decide for the real status. + // the framework is then able to reuse the resources from the thing handler initialization. + // we set this upfront to reliably check status updates in unit tests. + + updateStatus(ThingStatus.UNKNOWN); + + // Example for background initialization: + scheduler.execute(() -> { + boolean thingReachable = true; // + /* + * try { + * logger.debug("initialize executing..."); + * if ((config.accessToken == null) && (config.refreshToken == null)) { + * logger.debug("initialize getting access token..."); + * nestUtility.requestAccessToken(config.clientId, config.clientSecret, config.authorizationToken); + * } else { + * logger.info("Initialize reporting a preset access {} and refresh {} token..", config.accessToken, + * config.refreshToken); + * thing.getConfiguration().put("accessToken", config.accessToken); + * thing.getConfiguration().put("refreshToken", config.refreshToken); + * } + * + * } catch (IOException e) { + * logger.debug("Initialize() reporting {}", e.getMessage()); + * } + */ + // when done do: + if (thingReachable) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE); + } + }); + + // logger.debug("Finished initializing!"); + + // Note: When initialization can NOT be done set the status with more details for further + // analysis. See also class ThingStatusDetail for all available status details. + // Add a description to give user information to understand why thing does not work as expected. E.g. + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + // "Can not access device as username and/or password are invalid"); + } +} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/handler/SDMDoorbellHandler.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/handler/SDMDoorbellHandler.java new file mode 100644 index 0000000000000..c8dfa7001b469 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/handler/SDMDoorbellHandler.java @@ -0,0 +1,474 @@ +/** + * Copyright (c) 2010-2021 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.sdm.handler; + +import static org.openhab.binding.nest.internal.sdm.SDMBindingConstants.*; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.json.JSONException; +import org.json.JSONObject; +import org.openhab.binding.nest.internal.sdm.SDMUtility; +import org.openhab.binding.nest.internal.sdm.config.SDMAccountConfiguration; +import org.openhab.binding.nest.internal.sdm.data.SDMDoorbell; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.RawType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.api.gax.core.CredentialsProvider; +import com.google.api.gax.core.ExecutorProvider; +import com.google.api.gax.core.FixedCredentialsProvider; +import com.google.api.gax.core.InstantiatingExecutorProvider; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.pubsub.v1.AckReplyConsumer; +import com.google.cloud.pubsub.v1.MessageReceiver; +import com.google.cloud.pubsub.v1.Subscriber; +import com.google.pubsub.v1.ProjectSubscriptionName; +import com.google.pubsub.v1.PubsubMessage; + +/** + * The {@link SDMDoorbellHandler} is responsible for handling commands, which are sent to one of the channels. + * + * @author Brian Higginbotham - Initial contribution + */ +@NonNullByDefault +public class SDMDoorbellHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(SDMDoorbellHandler.class); + + private SDMUtility utility = new SDMUtility(thing); + + private @NonNullByDefault({}) ScheduledFuture refreshJob; + private @Nullable SDMAccountConfiguration config; + private @Nullable Future future; + private SDMDoorbell nestDoorbell; + + private @NonNullByDefault({}) RawType chimeImage; + private @NonNullByDefault({}) RawType motionImage; + private @NonNullByDefault({}) RawType personImage; + private @NonNullByDefault({}) RawType soundImage; + + public SDMDoorbellHandler(Thing thing) { + super(thing); + nestDoorbell = new SDMDoorbell(thing); // initialize Doorbell with base properties + } + + @Nullable + Subscriber subscriber; + + @Override + public void dispose() { + if (refreshJob != null) { + refreshJob.cancel(true); + } + logger.debug("Shutting down the doorbell handler.."); + if (future != null) { + future.cancel(true); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (CHANNEL_DOORBELL_NAME.equals(channelUID.getId())) { + if (command instanceof RefreshType) { + // TODO: handle data refresh + logger.debug("handleCommand reporting {}", command.toString()); + } + } else if (CHANNEL_SOUND_EVENT.equals(channelUID.getId())) { + if (command.toString() != "REFRESH") { + if (command.toString() == "OFF") { + updateState(CHANNEL_SOUND_EVENT, OnOffType.OFF); + logger.info("Doorbell: {} set switch to {}", thing.getProperties().get("deviceName"), + command.toString()); + } else { + updateState(CHANNEL_SOUND_EVENT, OnOffType.ON); + logger.info("Doorbell: {} set switch to {}", thing.getProperties().get("deviceName"), + command.toString()); + } + } + } else if (CHANNEL_MOTION_EVENT.equals(channelUID.getId())) { + if (command.toString() != "REFRESH") { + if (command.toString() == "OFF") { + updateState(CHANNEL_MOTION_EVENT, OnOffType.OFF); + logger.info("Doorbell: {} set switch to {}", thing.getProperties().get("deviceName"), + command.toString()); + } else { + updateState(CHANNEL_MOTION_EVENT, OnOffType.ON); + logger.info("Doorbell: {} set switch to {}", thing.getProperties().get("deviceName"), + command.toString()); + } + } + } else if (CHANNEL_PERSON_EVENT.equals(channelUID.getId())) { + if (command.toString() != "REFRESH") { + if (command.toString() == "OFF") { + updateState(CHANNEL_PERSON_EVENT, OnOffType.OFF); + logger.info("Doorbell: {} set switch to {}", thing.getProperties().get("deviceName"), + command.toString()); + } else { + updateState(CHANNEL_PERSON_EVENT, OnOffType.ON); + logger.info("Doorbell: {} set switch to {}", thing.getProperties().get("deviceName"), + command.toString()); + } + } + } else if (CHANNEL_CHIME_EVENT.equals(channelUID.getId())) { + if (command.toString() != "REFRESH") { + if (command.toString() == "OFF") { + updateState(CHANNEL_CHIME_EVENT, OnOffType.OFF); + logger.info("Doorbell: {} set switch to {}", thing.getProperties().get("deviceName"), + command.toString()); + } else { + updateState(CHANNEL_CHIME_EVENT, OnOffType.ON); + logger.info("Doorbell: {} set switch to {}", thing.getProperties().get("deviceName"), + command.toString()); + } + } + } + } + + void refreshChannels() { + logger.info("refreshChannels process a timer for Updating thing Channels for:{}", thing.getUID()); + + try { + // refresh status of the device + if (nestDoorbell.getDoorbellInfo()) { + /* + * State statusState = new StringType(nestThermostat.getDeviceStatus()); + * updateState("thermostatStatus", statusState); + * State nameState = new StringType(nestThermostat.getDeviceName()); + * updateState("thermostatName", nameState); + * State humidityState = new DecimalType(nestThermostat.getCurrentHumidity()); + * updateState("thermostatHumidityPercent", humidityState); + * State ambientState = new DecimalType(nestThermostat.getAmbientTemperatureSetting()); + * updateState("thermostatAmbientTemperature", ambientState); + * State setTempHeatState = new DecimalType(nestThermostat.getCurrentTemperatureHeat()); + * updateState("thermostatTemperatureHeat", setTempHeatState); + * State setTempCoolState = new DecimalType(nestThermostat.getCurrentTemperatureCool()); + * updateState("thermostatTemperatureCool", setTempCoolState); + * State setTargetTempState = new DecimalType(nestThermostat.getTargetTemperature()); + * updateState("thermostatTargetTemperature", setTargetTempState); + * State setMinTempState = new DecimalType(nestThermostat.getMinMaxTemperature()[0]); + * updateState("thermostatMinimumTemperature", setMinTempState); + * State setMaxTempState = new DecimalType(nestThermostat.getMinMaxTemperature()[1]); + * updateState("thermostatMaximumTemperature", setMaxTempState); + * State modeState = new StringType(nestThermostat.getThermostatMode()); + * updateState("thermostatCurrentMode", modeState); + * State ecoModeState = new StringType(nestThermostat.getThermostatEcoMode()); + * updateState("thermostatCurrentEcoMode", ecoModeState); + * State scaleSettingState = new StringType(nestThermostat.getTemperatureScaleSetting()); + * updateState("thermostatScaleSetting", scaleSettingState); + */ + } + } catch (IOException e) { + logger.debug("refreshChannels() reporting exception {}", e.getMessage()); + } + } + + @Override + public void initialize() { + config = getConfigAs(SDMAccountConfiguration.class); + config.projectId = thing.getProperties().get("projectId"); + config.clientId = thing.getProperties().get("clientId"); + config.clientSecret = thing.getProperties().get("clientSecret"); + config.accessToken = thing.getProperties().get("accessToken"); + config.refreshToken = thing.getProperties().get("refreshToken"); + config.deviceId = thing.getProperties().get("deviceId"); + config.deviceName = thing.getProperties().get("deviceName"); + config.customName = thing.getProperties().get("customName"); + config.serviceAccountPath = thing.getProperties().get("serviceAccountPath"); + config.subscriptionId = thing.getProperties().get("subscriptionId"); + config.pubsubProjectId = thing.getProperties().get("pubsubProjectId"); + if (thing.getConfiguration().containsKey("refreshInterval")) { + config.refreshInterval = Integer.parseInt(thing.getConfiguration().get("refreshInterval").toString()); + } else { + config.refreshInterval = 300; // default setting + } + + logger.debug("Start initializing device {}", config.deviceName); + + updateStatus(ThingStatus.UNKNOWN); + + // Example for background initialization: + scheduler.execute(() -> { + boolean thingReachable = true; // + try { + if ((utility.getPubSubProjectId() != null) && (utility.getSubscriptionId() != null)) { + logger.debug("starting pubsub doorbell [{}]...", config.deviceName); + future = scheduler.submit(() -> { + pubSubEventHandler(utility.getPubSubProjectId(), utility.getSubscriptionId()); + }); + } else { + logger.info( + "NestDoorBellHandler reporting that PubSub configurations were not defined.. Nest doorbell REQUIRES pubsub for event notifications (i.e. Camera motion and Chime events..) Please follow the README and configure the PubSub topics and configurations..."); + } + nestDoorbell.initializeDoorbell(); + // when done do: + thingReachable = true; // No status on doorbell + if (thingReachable) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE); + } + + } catch (Exception e) { + logger.debug("Initialize() caught an exception {} {}", e.getMessage(), e.getStackTrace().toString()); + } + }); + + // if (refreshJob == null || refreshJob.isCancelled()) { + // refreshJob = scheduler.scheduleWithFixedDelay(this::refreshChannels, 0, config.refreshInterval, + // TimeUnit.SECONDS); + // } + // Note: When initialization can NOT be done set the status with more details for further + // analysis. See also class ThingStatusDetail for all available status details. + // Add a description to give user information to understand why thing does not work as expected. E.g. + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + // "Can not access device as username and/or password are invalid"); + logger.debug("Finished initializing device {}", config.deviceName); + } + + private boolean updateLiveStreamChannels() throws IOException { + try { + nestDoorbell.getCameraLiveStream(); + State streamUrlState = new StringType(nestDoorbell.getStreamUrl()); + updateState(CHANNEL_LIVE_STREAM_URL, streamUrlState); + State streamTokenState = new StringType(nestDoorbell.getStreamToken()); + updateState(CHANNEL_LIVE_STREAM_CURRENT_TOKEN, streamTokenState); + State streamExtensionToken = new StringType(nestDoorbell.getStreamExensionToken()); + updateState(CHANNEL_LIVE_STREAM_EXTENSION_TOKEN, streamExtensionToken); + State streamExpirationTime = new StringType(nestDoorbell.getStreamTokenExpiration().toString()); + updateState(CHANNEL_LIVE_STREAM_EXPIRATION_TIME, streamExpirationTime); + return true; + } catch (IOException e) { + throw new IOException(e.getMessage()); + } + } + + public boolean dispatchMessage(PubsubMessage message) throws IOException { + String messageEventId; + String userId; + String resourceName; + String eventTrait; + String eventSessionId; + String eventId; + Date messageTime; + DateFormat utcFormat; + + try { + // Let's find out what type of message we are dealing with by parsing important artifacts + JSONObject jo = new JSONObject(message.getData().toStringUtf8()); + + messageEventId = jo.getString("eventId").toString(); + userId = jo.getString("userId"); + resourceName = jo.getJSONObject("resourceUpdate").getString("name"); + logger.debug("dispatchMessage processing\ndeviceId [{}]\nname [{}]\nmessageId [{}]", + thing.getProperties().get("deviceId"), + resourceName.substring(resourceName.lastIndexOf("/") + 1, resourceName.length()), + message.getMessageId()); + + // put in place to resolve a condition where sometimes milliseconds is returned and sometimes just seconds + if (jo.getString("timestamp").contains(".")) { + utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + } else { + utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + } + + utcFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + messageTime = utcFormat.parse(jo.getString("timestamp")); + + String deviceId = thing.getProperties().get("deviceId"); + if (deviceId != null && resourceName.contains(deviceId)) { + // find out if it is an event or trait + if (jo.getJSONObject("resourceUpdate").has("events")) { + eventTrait = jo.getJSONObject("resourceUpdate").getJSONObject("events").names().getString(0); + eventSessionId = jo.getJSONObject("resourceUpdate").getJSONObject("events") + .getJSONObject(eventTrait).getString("eventSessionId"); + eventId = jo.getJSONObject("resourceUpdate").getJSONObject("events").getJSONObject(eventTrait) + .getString("eventId"); + + if (jo.getJSONObject("resourceUpdate").getJSONObject("events") + .has("sdm.devices.events.CameraSound.Sound")) { + eventTrait = "sdm.devices.events.CameraSound.Sound"; + nestDoorbell.setCameraSoundEvent(messageTime); + if (nestDoorbell.getCameraSoundEvent()) { + updateState(CHANNEL_SOUND_EVENT, OnOffType.ON); + State newState = new StringType(messageTime.toString()); + updateState(CHANNEL_SOUND_LAST_EVENT_TIME, newState); + if (nestDoorbell.isImageValid(messageTime)) { + soundImage = nestDoorbell.getCameraImage(eventId); + updateState(CHANNEL_SOUND_EVENT_IMAGE, soundImage); + logger.debug("dispatchMessage processed a Sound camera image with data {} and date {}", + soundImage, messageTime); + updateLiveStreamChannels(); + } + + } else { + updateState(CHANNEL_SOUND_EVENT, OnOffType.OFF); + } + } + if (jo.getJSONObject("resourceUpdate").getJSONObject("events") + .has("sdm.devices.events.CameraPerson.Person")) { + eventTrait = "sdm.devices.events.CameraPerson.Person"; + nestDoorbell.setCameraPersonEvent(messageTime); + if (nestDoorbell.getCameraPersonEvent()) { + updateState(CHANNEL_PERSON_EVENT, OnOffType.ON); + State newState = new StringType(messageTime.toString()); + updateState(CHANNEL_PERSON_LAST_EVENT_TIME, newState); + if (nestDoorbell.isImageValid(messageTime)) { + personImage = nestDoorbell.getCameraImage(eventId); + updateState(CHANNEL_PERSON_EVENT_IMAGE, personImage); + logger.debug("dispatchMessage processed a Person camera image with data {} and date {}", + personImage, messageTime); + updateLiveStreamChannels(); + } + + } else { + updateState(CHANNEL_PERSON_EVENT, OnOffType.OFF); + } + } + if (jo.getJSONObject("resourceUpdate").getJSONObject("events") + .has("sdm.devices.events.CameraMotion.Motion")) { + eventTrait = "sdm.devices.events.CameraMotion.Motion"; + nestDoorbell.setCameraMotionEvent(messageTime); + if (nestDoorbell.getCameraMotionEvent()) { + updateState(CHANNEL_MOTION_EVENT, OnOffType.ON); + State newState = new StringType(messageTime.toString()); + updateState(CHANNEL_MOTION_LAST_EVENT_TIME, newState); + if (nestDoorbell.isImageValid(messageTime)) { + motionImage = nestDoorbell.getCameraImage(eventId); + updateState(CHANNEL_MOTION_EVENT_IMAGE, motionImage); + logger.debug("dispatchMessage processed a Motion camera image with data {} and date {}", + motionImage, messageTime); + updateLiveStreamChannels(); + } + } else { + updateState(CHANNEL_MOTION_EVENT, OnOffType.OFF); + } + } + if (jo.getJSONObject("resourceUpdate").getJSONObject("events") + .has("sdm.devices.events.DoorbellChime.Chime")) { + eventTrait = "sdm.devices.events.DoorbellChime.Chime"; + nestDoorbell.setCameraChimeEvent(messageTime); + if (nestDoorbell.getCameraChimeEvent()) { + updateState(CHANNEL_CHIME_EVENT, OnOffType.ON); + State newState = new StringType(messageTime.toString()); + updateState(CHANNEL_CHIME_LAST_EVENT_TIME, newState); + if (nestDoorbell.isImageValid(messageTime)) { + chimeImage = nestDoorbell.getCameraImage(eventId); + updateState(CHANNEL_CHIME_EVENT_IMAGE, chimeImage); + logger.debug("dispatchMessage processed a Chime camera image with data {}", chimeImage); + updateLiveStreamChannels(); + } + } else { + updateState(CHANNEL_CHIME_EVENT, OnOffType.OFF); + } + } + logger.debug("dispatchMessage processed messageId {} successfully", message.getMessageId()); + return true; + + } + } else { + logger.debug("dispatchMessage is skipping a message {} because it doesn't belong to this device..", + message.getMessageId()); + return false; // another deviceID + } + } catch (JSONException e) { + logger.debug("dispatchMessage exception {}", e.getMessage()); + return false; + } catch (IOException e) { + throw new IOException(e.getMessage()); + } catch (Exception e) { + logger.debug("dispatchMessage general exception exception {}", e.getMessage()); + return false; + } + + return false; + } + + public void pubSubEventHandler(String projectId, String subscriptionId) { + ProjectSubscriptionName subscriptionName = ProjectSubscriptionName.of(projectId, subscriptionId); + + // Instantiate an asynchronous message receiver. + // ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5); + MessageReceiver receiver = new MessageReceiver() { + @Override + public void receiveMessage(@Nullable PubsubMessage message, final @Nullable AckReplyConsumer consumer) { + logger.debug("got message: MessageId {}", message.getMessageId()); + try { + if (dispatchMessage(message)) { + consumer.ack(); + } else { + consumer.nack(); + logger.debug("messageReceiver NACK message {}", message.getData().toStringUtf8()); + } + } catch (IOException e) { + logger.debug("receiveMessage threw Exception {}", e.getMessage()); + if (e.getMessage().contains("503")) { + logger.debug( + "The image is no longer valid and we need to acknowledge the message to avoid retries.."); + consumer.ack(); + } else { + logger.debug("Made it to nack.."); + consumer.nack(); + } + } + } + }; + + try { + GoogleCredentials credentials = GoogleCredentials + .fromStream(new FileInputStream(utility.getServiceAccountPath())) + // GoogleCredentials credentials = GoogleCredentials.create(googleAccessToken) + .createScoped(List.of("https://www.googleapis.com/auth/pubsub")); + + CredentialsProvider cred = FixedCredentialsProvider.create(credentials); + + ExecutorProvider executorProvider = InstantiatingExecutorProvider.newBuilder().setExecutorThreadCount(5) + .build(); + subscriber = Subscriber.newBuilder(subscriptionName, receiver).setCredentialsProvider(cred) + .setExecutorProvider(executorProvider).build(); + + // Start the subscriber. + subscriber.startAsync().awaitRunning(); + // Allow the subscriber to run indefinitely unless an unrecoverable error occurs. + subscriber.awaitTerminated(); + subscriber.stopAsync(); // end the async thread + } catch (IllegalStateException e) { + logger.debug("Illegal state exception {}", e.getMessage()); + } catch (FileNotFoundException e) { + logger.debug("FileNotFound exception {}", e.getMessage()); + } catch (IOException e) { + logger.debug("IOException exception {}", e.getMessage()); + } + } +} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/handler/SDMThermostatHandler.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/handler/SDMThermostatHandler.java new file mode 100644 index 0000000000000..d0da87fe58f2e --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/sdm/handler/SDMThermostatHandler.java @@ -0,0 +1,529 @@ +/** + * Copyright (c) 2010-2021 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.sdm.handler; + +import static org.openhab.binding.nest.internal.sdm.SDMBindingConstants.*; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Date; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.openhab.binding.nest.internal.sdm.SDMUtility; +import org.openhab.binding.nest.internal.sdm.config.SDMAccountConfiguration; +import org.openhab.binding.nest.internal.sdm.data.SDMThermostat; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.api.gax.core.CredentialsProvider; +import com.google.api.gax.core.ExecutorProvider; +import com.google.api.gax.core.FixedCredentialsProvider; +import com.google.api.gax.core.InstantiatingExecutorProvider; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.pubsub.v1.AckReplyConsumer; +import com.google.cloud.pubsub.v1.MessageReceiver; +import com.google.cloud.pubsub.v1.Subscriber; +import com.google.pubsub.v1.ProjectSubscriptionName; +import com.google.pubsub.v1.PubsubMessage; + +/** + * The {@link SDMThermostatHandler} is responsible for handling commands, which are sent to one of the channels. + * + * @author Brian Higginbotham - Initial contribution + */ +@NonNullByDefault +public class SDMThermostatHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(SDMThermostatHandler.class); + + private SDMUtility utility = new SDMUtility(thing); + + private @NonNullByDefault({}) ScheduledFuture refreshJob; + private @Nullable Future future; + private @Nullable SDMAccountConfiguration config; + private SDMThermostat nestThermostat; + + public SDMThermostatHandler(Thing thing) { + super(thing); + nestThermostat = new SDMThermostat(thing); // initialize thermostat with base properties + } + + @Override + public void dispose() { + if (refreshJob != null) { + refreshJob.cancel(true); + } + if (future != null) { + future.cancel(true); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // logger.debug("handleCommand reporting {},{}", channelUID.getId(), command.toString()); + try { + if (CHANNEL_THERMOSTAT_NAME.equals(channelUID.getId())) { + if (command instanceof RefreshType) { + // logger.debug("handleCommand reporting {}", command.toString()); + } + + // TODO: handle command + + // Note: if communication with thing fails for some reason, + // indicate that by setting the status with detail information: + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + // "Could not control device at IP address x.x.x.x"); + } else if (CHANNEL_CURRENT_MODE.equals(channelUID.getId())) { + if (command.toString() != "REFRESH") { + if (nestThermostat.setThermostatMode(command.toString())) { + logger.info("Thermostat: {} set command {} to {}", thing.getProperties().get("deviceName"), + channelUID.toString(), command.toString()); + Thread.sleep(3000); // set up to handle delay with changing mode and current settings being + // updated + refreshChannels(); + } + } + } else if (CHANNEL_CURRENT_ECO_MODE.equals(channelUID.getId())) { + if (command.toString() != "REFRESH") { + if (nestThermostat.setThermostatEcoMode(command.toString())) { + logger.info("Thermostat: {} set command {} to {}", thing.getProperties().get("deviceName"), + channelUID.toString(), command.toString()); + Thread.sleep(3000); // set up to handle delay with changing mode and current settings being + // updated + refreshChannels(); + } + } + } else if (CHANNEL_TARGET_TEMPERATURE.equals(channelUID.getId())) { + if (command.toString() != "REFRESH") { + if (nestThermostat.setThermostatTargetTemperature(Double.parseDouble(command.toString()), 0, 0, + false)) { + logger.info("Thermostat: {} set command {} to {}", thing.getProperties().get("deviceName"), + channelUID.toString(), command.toString()); + refreshChannels(); + } + } + } else if (CHANNEL_MINIMUM_TEMPERATURE.equals(channelUID.getId())) { + if (command.toString() != "REFRESH") { + logger.debug("thermostatMinTemp {}", nestThermostat.getMinMaxTemperature()[1]); + if (nestThermostat.setThermostatTargetTemperature(0, Double.parseDouble(command.toString()), + nestThermostat.getMinMaxTemperature()[1], true)) { + logger.info("Thermostat: {} set command {} to {}", thing.getProperties().get("deviceName"), + channelUID.toString(), command.toString()); + refreshChannels(); + } + } + } else if (CHANNEL_MAXIMUM_TEMPERATURE.equals(channelUID.getId())) { + if (command.toString() != "REFRESH") { + if (nestThermostat.setThermostatTargetTemperature(0, nestThermostat.getMinMaxTemperature()[0], + Double.parseDouble(command.toString()), true)) { + logger.info("Thermostat: {} set command {} to {}", thing.getProperties().get("deviceName"), + channelUID.toString(), command.toString()); + refreshChannels(); + } + } + } + } catch (IOException e) { + logger.debug("handleMessage reporting exception {}", e.getMessage()); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + logger.debug("handleMessage reporting exception {}", e.getMessage()); + } + } + + void refreshChannels() { + logger.info("refreshChannels process a timer for Updating thing Channels for:{}", thing.getUID()); + + try { + // refresh status of the device + if (nestThermostat.getThermostatInfo()) { + State statusState = new StringType(nestThermostat.getDeviceStatus()); + updateState("thermostatStatus", statusState); + State nameState = new StringType(nestThermostat.getDeviceName()); + updateState("thermostatName", nameState); + State nameHVACState = new StringType(nestThermostat.getThermostatHVACStatus()); + updateState(CHANNEL_HVAC_STATUS, nameHVACState); + State humidityState = new DecimalType(nestThermostat.getCurrentHumidity()); + updateState("thermostatHumidityPercent", humidityState); + State ambientState = new DecimalType(nestThermostat.getAmbientTemperatureSetting()); + updateState("thermostatAmbientTemperature", ambientState); + State setTempHeatState = new DecimalType(nestThermostat.getCurrentTemperatureHeat()); + updateState("thermostatTemperatureHeat", setTempHeatState); + State setTempCoolState = new DecimalType(nestThermostat.getCurrentTemperatureCool()); + updateState("thermostatTemperatureCool", setTempCoolState); + State setTargetTempState = new DecimalType(nestThermostat.getTargetTemperature()); + updateState("thermostatTargetTemperature", setTargetTempState); + State setMinTempState = new DecimalType(nestThermostat.getMinMaxTemperature()[0]); + updateState("thermostatMinimumTemperature", setMinTempState); + State setMaxTempState = new DecimalType(nestThermostat.getMinMaxTemperature()[1]); + updateState("thermostatMaximumTemperature", setMaxTempState); + State modeState = new StringType(nestThermostat.getThermostatMode()); + updateState("thermostatCurrentMode", modeState); + State ecoModeState = new StringType(nestThermostat.getThermostatEcoMode()); + updateState("thermostatCurrentEcoMode", ecoModeState); + State scaleSettingState = new StringType(nestThermostat.getTemperatureScaleSetting()); + updateState("thermostatScaleSetting", scaleSettingState); + + } + } catch (IOException e) { + logger.debug("refreshChannels() reporting exception {}", e.getMessage()); + } + } + + @Override + public void initialize() { + config = getConfigAs(SDMAccountConfiguration.class); + config.projectId = thing.getProperties().get("projectId"); + config.clientId = thing.getProperties().get("clientId"); + config.clientSecret = thing.getProperties().get("clientSecret"); + config.accessToken = thing.getProperties().get("accessToken"); + config.refreshToken = thing.getProperties().get("refreshToken"); + config.deviceId = thing.getProperties().get("deviceId"); + config.deviceName = thing.getProperties().get("deviceName"); + + if (thing.getConfiguration().containsKey("refreshInterval")) { + config.refreshInterval = Integer.parseInt(thing.getConfiguration().get("refreshInterval").toString()); + } else { + config.refreshInterval = 300; // default setting + } + + updateStatus(ThingStatus.UNKNOWN); + + // Example for background initialization: + scheduler.execute(() -> { + boolean thingReachable = true; // + try { + logger.debug("Start initializing device [{}]", config.deviceName); + + if ((utility.getPubSubProjectId() != null) && (utility.getSubscriptionId() != null)) { + logger.debug("starting pubsub thermostat [{}]...", config.deviceName); + + future = scheduler.submit(() -> { + pubSubEventHandler(utility.getPubSubProjectId(), utility.getSubscriptionId()); + }); + } + nestThermostat.initializeThermostat(); + + thingReachable = nestThermostat.getDeviceStatus().equalsIgnoreCase("ONLINE"); + if (thingReachable) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE); + } + logger.debug("Finished initializing device [{}]", config.deviceName); + } catch (Exception e) { + logger.debug("Initialize() caught an exception {}", e.getMessage()); + } + }); + + if (refreshJob == null || refreshJob.isCancelled()) { + refreshJob = scheduler.scheduleWithFixedDelay(this::refreshChannels, 0, config.refreshInterval, + TimeUnit.SECONDS); + } + // Note: When initialization can NOT be done set the status with more details for further + // analysis. See also class ThingStatusDetail for all available status details. + // Add a description to give user information to understand why thing does not work as expected. E.g. + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + // "Can not access device as username and/or password are invalid"); + } + + public boolean dispatchMessage(PubsubMessage message) throws IOException { + String messageEventId; + String userId; + String resourceName; + String eventTrait; + String eventSessionId; + String eventId; + Date messageTime; + + try { + // Let's find out what type of message we are dealing with by parsing important artifacts + JSONObject jo = new JSONObject(message.getData().toStringUtf8()); + + messageEventId = jo.getString("eventId").toString(); + userId = jo.getString("userId"); + resourceName = jo.getJSONObject("resourceUpdate").getString("name"); + logger.debug("dispatchMessage processing\ndeviceId [{}]\nname [{}]\nmessageId [{}]", + thing.getProperties().get("deviceId"), + resourceName.substring(resourceName.lastIndexOf("/") + 1, resourceName.length()), + message.getMessageId()); + + String deviceId = thing.getProperties().get("deviceId"); + if (deviceId != null && resourceName.contains(deviceId)) { + logger.debug("dispatchMessage found device match for message [{}]", + thing.getProperties().get("deviceName")); + if (jo.getJSONObject("resourceUpdate").has("traits")) { + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .has("sdm.devices.traits.Temperature")) { + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.Temperature").has("ambientTemperatureCelsius")) { + State ambientState = new DecimalType( + nestThermostat.setAmbientTemperatureSetting(jo.getJSONObject("resourceUpdate") + .getJSONObject("traits").getJSONObject("sdm.devices.traits.Temperature") + .getFloat("ambientTemperatureCelsius"))); + + updateState(CHANNEL_AMBIENT_TEMPERATURE, ambientState); + } + } + + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .has("sdm.devices.traits.ThermostatTemperatureSetpoint")) { + if ((jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint").has("heatCelsius")) + && (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint") + .has("coolCelsius")) + && (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .has("sdm.devices.traits.ThermostatMode"))) { + // heatCelsius and coolCelsius only exist in two modes,ThermostatEco and HeatCool. + // Check for heatCool since Eco mode is under another trait + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatMode").getString("mode") + .equalsIgnoreCase("heatcool")) { + // found heatCool. Let's extract data and store + double[] thermostatMinMax = new double[2]; + + thermostatMinMax[0] = jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint") + .getFloat("heatCelsius"); + thermostatMinMax[1] = jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint") + .getFloat("coolCelsius"); + + nestThermostat.setMinMaxTemperatureValue(thermostatMinMax); + State setMinTempState = new DecimalType(nestThermostat.getMinMaxTemperature()[0]); + updateState(CHANNEL_MINIMUM_TEMPERATURE, setMinTempState); + + State setMaxTempState = new DecimalType(nestThermostat.getMinMaxTemperature()[1]); + updateState(CHANNEL_MAXIMUM_TEMPERATURE, setMaxTempState); + } + } else { + if ((jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint") + .has("heatCelsius")) + && (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint") + .has("coolCelsius")) + && ((jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .has("sdm.devices.traits.ThermostatMode")) == false)) { + logger.debug( + "dispatchMessage found a thermostat value without a mode.. We'll set the minmaxvalue"); + // found heatCool. Let's extract data and store + double[] thermostatMinMax = new double[2]; + + thermostatMinMax[0] = jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint") + .getFloat("heatCelsius"); + thermostatMinMax[1] = jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatTemperatureSetpoint") + .getFloat("coolCelsius"); + + nestThermostat.setMinMaxTemperatureValue(thermostatMinMax); + + State setMinTempState = new DecimalType(nestThermostat.getMinMaxTemperature()[0]); + updateState(CHANNEL_MINIMUM_TEMPERATURE, setMinTempState); + + State setMaxTempState = new DecimalType(nestThermostat.getMinMaxTemperature()[1]); + updateState(CHANNEL_MAXIMUM_TEMPERATURE, setMaxTempState); + } + } + } + + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .has("sdm.devices.traits.ThermostatMode")) { + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatMode").has("mode")) { + State modeState = new StringType(nestThermostat + .setThermostatModeValue(jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatMode").getString("mode"))); + updateState(CHANNEL_CURRENT_MODE, modeState); + } + + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatMode").has("availableModes")) { + JSONArray jaAvailableModes = jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatMode").getJSONArray("availableModes"); + String[] deviceAvailableThermostatModes = new String[jaAvailableModes.length()]; + for (int nCount = 0; nCount < jaAvailableModes.length(); nCount++) { + // get Available Modes + deviceAvailableThermostatModes[nCount] = jaAvailableModes.getString(nCount); + } + nestThermostat.setAvailableThermostatModes(deviceAvailableThermostatModes); + } + } + + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .has("sdm.devices.traits.ThermostatEcoMode")) { + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatEcoMode").has("mode")) { + State ecoModeState = new StringType(nestThermostat.setThermostatEcoModeValue( + jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatEcoMode").getString("mode"))); + updateState(CHANNEL_CURRENT_ECO_MODE, ecoModeState); + } + + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatEcoMode").has("availableModes")) { + JSONArray jaAvailableModes = jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatEcoMode") + .getJSONArray("availableModes"); + String[] deviceAvailableThermostatEcoModes = new String[jaAvailableModes.length()]; + for (int nCount = 0; nCount < jaAvailableModes.length(); nCount++) { + // get Available Modes + deviceAvailableThermostatEcoModes[nCount] = jaAvailableModes.getString(nCount); + } + nestThermostat.setAvailableThermostatEcoModes(deviceAvailableThermostatEcoModes); + } + if ((jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatEcoMode").has("heatCelsius")) + && (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatEcoMode").has("coolCelsius")) + && (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatEcoMode").has("mode"))) { + // If Mode is active, we set to this value for our aggregated minmaxvalue + if ((jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatEcoMode").has("coolCelsius")) + && (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatEcoMode").getString("mode")) + .equalsIgnoreCase("manual_eco")) { + double[] thermostatMinMax = new double[2]; + + thermostatMinMax[0] = jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatEcoMode").getFloat("heatCelsius"); + thermostatMinMax[1] = jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatEcoMode").getFloat("coolCelsius"); + logger.debug("We should not be here for ECO mode settings.."); + nestThermostat.setMinMaxTemperatureValue(thermostatMinMax); + State setMinTempState = new DecimalType(thermostatMinMax[0]); + updateState(CHANNEL_MINIMUM_TEMPERATURE, setMinTempState); + + State setMaxTempState = new DecimalType(thermostatMinMax[1]); + updateState(CHANNEL_MAXIMUM_TEMPERATURE, setMaxTempState); + } + } + } + + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits").has("sdm.devices.traits.Fan")) { + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.Fan").has("timerMode")) { + State fanState = new StringType(nestThermostat + .setDeviceFan(jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.Fan").getString("timerMode"))); + updateState(CHANNEL_FAN_MODE, fanState); + } + } + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits").has("sdm.devices.traits.Humidity")) { + // get Humidity properties and set them + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.Humidity").has("ambientHumidityPercent")) { + State humidityState = new DecimalType(nestThermostat.setHumidityPercent(jo + .getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.Humidity").getInt("ambientHumidityPercent"))); + updateState(CHANNEL_AMBIENT_HUMIDITY, humidityState); + } + } + + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .has("sdm.devices.traits.ThermostatHvac")) { + if (jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatHvac").has("status")) { + State hvacStatusState = new StringType(nestThermostat + .setHVACStatus(jo.getJSONObject("resourceUpdate").getJSONObject("traits") + .getJSONObject("sdm.devices.traits.ThermostatHvac").getString("status"))); + updateState(CHANNEL_HVAC_STATUS, hvacStatusState); + } + } + } + logger.debug("dispatchMessage processed messageId [{}] successfully for device [{}]", + message.getMessageId(), thing.getProperties().get("deviceName")); + return true; + } else { + return false; + } + } catch (JSONException e) { + logger.debug("dispatchMessage exception {}", e.getMessage()); + return false; + } catch (Exception e) { + logger.debug("dispatchMessage general exception exception {}", e.getMessage()); + return false; + } + } + + public void pubSubEventHandler(String projectId, String subscriptionId) { + ProjectSubscriptionName subscriptionName = ProjectSubscriptionName.of(projectId, subscriptionId); + + Subscriber subscriber = null; + // Instantiate an asynchronous message receiver. + // ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5); + MessageReceiver receiver = new MessageReceiver() { + @Override + public void receiveMessage(@Nullable PubsubMessage message, final @Nullable AckReplyConsumer consumer) { + logger.debug("got message: MessageId {}", message.getMessageId()); + try { + if (dispatchMessage(message)) { + consumer.ack(); + } else { + consumer.nack(); + logger.debug("messageReceiver NACK message {}", message.getData().toStringUtf8()); + } + } catch (IOException e) { + logger.debug("receiveMessage threw Exception {}", e.getMessage()); + consumer.nack(); + } + } + }; + + try { + GoogleCredentials credentials = GoogleCredentials + .fromStream(new FileInputStream(utility.getServiceAccountPath())) + // GoogleCredentials credentials = GoogleCredentials.create(googleAccessToken) + .createScoped(List.of("https://www.googleapis.com/auth/pubsub")); + + CredentialsProvider cred = FixedCredentialsProvider.create(credentials); + + ExecutorProvider executorProvider = InstantiatingExecutorProvider.newBuilder().setExecutorThreadCount(5) + .build(); + subscriber = Subscriber.newBuilder(subscriptionName, receiver).setCredentialsProvider(cred) + .setExecutorProvider(executorProvider).build(); + // Start the subscriber. + subscriber.startAsync().awaitRunning(); + // Allow the subscriber to run indefinitely unless an unrecoverable error occurs. + subscriber.awaitTerminated(); + subscriber.stopAsync().awaitTerminated(); // end the async thread + } catch (IllegalStateException e) { + logger.debug("Illegal state exception {}", e.getMessage()); + } catch (FileNotFoundException e) { + logger.debug("FileNotFound exception {}", e.getMessage()); + } catch (IOException e) { + logger.debug("IOException exception {}", e.getMessage()); + } + } +} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestBindingConstants.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/WWNBindingConstants.java similarity index 91% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestBindingConstants.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/WWNBindingConstants.java index 2c8f5dd35c2aa..0483eecf0e72a 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestBindingConstants.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/WWNBindingConstants.java @@ -10,21 +10,21 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal; +package org.openhab.binding.nest.internal.wwn; import java.time.Duration; +import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.thing.ThingTypeUID; /** - * The {@link NestBindingConstants} class defines common constants, which are - * used across the whole binding. + * The {@link WWNBindingConstants} class defines common constants which are used for WWN. * * @author David Bennett - Initial contribution */ @NonNullByDefault -public class NestBindingConstants { +public class WWNBindingConstants { public static final String BINDING_ID = "nest"; @@ -56,11 +56,14 @@ public class NestBindingConstants { public static final int MIN_SECONDS_BETWEEN_API_CALLS = 60; // List of all Thing Type UIDs - public static final ThingTypeUID THING_TYPE_THERMOSTAT = new ThingTypeUID(BINDING_ID, "thermostat"); - public static final ThingTypeUID THING_TYPE_CAMERA = new ThingTypeUID(BINDING_ID, "camera"); - public static final ThingTypeUID THING_TYPE_SMOKE_DETECTOR = new ThingTypeUID(BINDING_ID, "smoke_detector"); - public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "account"); - public static final ThingTypeUID THING_TYPE_STRUCTURE = new ThingTypeUID(BINDING_ID, "structure"); + public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "wwn_account"); + public static final ThingTypeUID THING_TYPE_CAMERA = new ThingTypeUID(BINDING_ID, "wwn_camera"); + public static final ThingTypeUID THING_TYPE_SMOKE_DETECTOR = new ThingTypeUID(BINDING_ID, "wwn_smoke_detector"); + public static final ThingTypeUID THING_TYPE_STRUCTURE = new ThingTypeUID(BINDING_ID, "wwn_structure"); + public static final ThingTypeUID THING_TYPE_THERMOSTAT = new ThingTypeUID(BINDING_ID, "wwn_thermostat"); + + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT, THING_TYPE_CAMERA, + THING_TYPE_SMOKE_DETECTOR, THING_TYPE_STRUCTURE, THING_TYPE_THERMOSTAT); // List of all channel group prefixes public static final String CHANNEL_GROUP_CAMERA_PREFIX = "camera#"; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/WWNThingHandlerFactory.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/WWNThingHandlerFactory.java new file mode 100644 index 0000000000000..7443b5e0167a9 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/WWNThingHandlerFactory.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2010-2021 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; + +import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*; + +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.handler.WWNAccountHandler; +import org.openhab.binding.nest.internal.wwn.handler.WWNCameraHandler; +import org.openhab.binding.nest.internal.wwn.handler.WWNSmokeDetectorHandler; +import org.openhab.binding.nest.internal.wwn.handler.WWNStructureHandler; +import org.openhab.binding.nest.internal.wwn.handler.WWNThermostatHandler; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.jaxrs.client.SseEventSourceFactory; + +/** + * The {@link WWNThingHandlerFactory} is responsible for creating WWN things and thing handlers. It also sets up the + * discovery service to track things from the bridge when the bridge is created. + * + * @author David Bennett - Initial contribution + */ +@NonNullByDefault +@Component(service = ThingHandlerFactory.class, configurationPid = "binding.nest") +public class WWNThingHandlerFactory extends BaseThingHandlerFactory { + + private final ClientBuilder clientBuilder; + private final SseEventSourceFactory eventSourceFactory; + + @Activate + public WWNThingHandlerFactory(@Reference ClientBuilder clientBuilder, + @Reference SseEventSourceFactory eventSourceFactory) { + this.clientBuilder = clientBuilder; + this.eventSourceFactory = eventSourceFactory; + } + + /** + * The things this factory supports creating. + */ + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + /** + * Creates a handler for the specific thing. This also creates the discovery service when the bridge is created. + */ + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_ACCOUNT.equals(thingTypeUID)) { + return new WWNAccountHandler((Bridge) thing, clientBuilder, eventSourceFactory); + } else if (THING_TYPE_CAMERA.equals(thingTypeUID)) { + return new WWNCameraHandler(thing); + } else if (THING_TYPE_SMOKE_DETECTOR.equals(thingTypeUID)) { + return new WWNSmokeDetectorHandler(thing); + } else if (THING_TYPE_STRUCTURE.equals(thingTypeUID)) { + return new WWNStructureHandler(thing); + } else if (THING_TYPE_THERMOSTAT.equals(thingTypeUID)) { + return new WWNThermostatHandler(thing); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestUtils.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/WWNUtils.java similarity index 87% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestUtils.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/WWNUtils.java index 7ad59b4c54306..f1dfcae755529 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestUtils.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/WWNUtils.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal; +package org.openhab.binding.nest.internal.wwn; import java.io.Reader; @@ -21,17 +21,17 @@ import com.google.gson.GsonBuilder; /** - * Utility class for sharing utility methods between objects. + * Utility class for sharing WWN utility methods between objects. * * @author Wouter Born - Initial contribution */ @NonNullByDefault -public final class NestUtils { +public final class WWNUtils { private static final Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - private NestUtils() { + private WWNUtils() { // hidden utility class constructor } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestBridgeConfiguration.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/config/WWNAccountConfiguration.java similarity index 88% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestBridgeConfiguration.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/config/WWNAccountConfiguration.java index 0de3318e0f236..edc36ddaacd74 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestBridgeConfiguration.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/config/WWNAccountConfiguration.java @@ -10,18 +10,18 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.config; +package org.openhab.binding.nest.internal.wwn.config; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; /** - * The configuration for the Nest bridge, allowing it to talk to Nest. + * The configuration for the WWN account, allowing it to talk to Nest. * * @author David Bennett - Initial contribution */ @NonNullByDefault -public class NestBridgeConfiguration { +public class WWNAccountConfiguration { public static final String PRODUCT_ID = "productId"; /** Product ID from the Nest product page. */ public String productId = ""; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestDeviceConfiguration.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/config/WWNDeviceConfiguration.java similarity index 85% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestDeviceConfiguration.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/config/WWNDeviceConfiguration.java index 8815397e87999..7e38d57e7d134 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestDeviceConfiguration.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/config/WWNDeviceConfiguration.java @@ -10,18 +10,18 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.config; +package org.openhab.binding.nest.internal.wwn.config; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The configuration for Nest devices. + * The configuration for WWN devices. * * @author Wouter Born - Initial contribution * @author Wouter Born - Add device configuration to allow file based configuration */ @NonNullByDefault -public class NestDeviceConfiguration { +public class WWNDeviceConfiguration { public static final String DEVICE_ID = "deviceId"; /** Device ID which can be retrieved with the Nest API. */ public String deviceId = ""; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestStructureConfiguration.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/config/WWNStructureConfiguration.java similarity index 84% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestStructureConfiguration.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/config/WWNStructureConfiguration.java index afbf67b4df5d0..e42ba230c1f97 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestStructureConfiguration.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/config/WWNStructureConfiguration.java @@ -10,18 +10,18 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.config; +package org.openhab.binding.nest.internal.wwn.config; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The configuration for structures. + * The configuration for WWN structures. * * @author Wouter Born - Initial contribution * @author Wouter Born - Add device configuration to allow file based configuration */ @NonNullByDefault -public class NestStructureConfiguration { +public class WWNStructureConfiguration { public static final String STRUCTURE_ID = "structureId"; /** Structure ID which can be retrieved with the Nest API. */ public String structureId = ""; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/BaseNestDevice.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/BaseWWNDevice.java similarity index 95% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/BaseNestDevice.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/BaseWWNDevice.java index 8f2d23ebabcb8..925d483de2326 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/BaseNestDevice.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/BaseWWNDevice.java @@ -10,17 +10,17 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; import java.util.Date; /** - * Default properties shared across all Nest devices. + * Default properties shared across all WWN devices. * * @author David Bennett - Initial contribution * @author Wouter Born - Add equals and hashCode methods */ -public class BaseNestDevice implements NestIdentifiable { +public class BaseWWNDevice implements WWNIdentifiable { private String deviceId; private String name; @@ -80,7 +80,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - BaseNestDevice other = (BaseNestDevice) obj; + BaseWWNDevice other = (BaseWWNDevice) obj; if (deviceId == null) { if (other.deviceId != null) { return false; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/AccessTokenData.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNAccessTokenData.java similarity index 89% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/AccessTokenData.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNAccessTokenData.java index 504f9947be1b7..66a97f1415fd0 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/AccessTokenData.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNAccessTokenData.java @@ -10,15 +10,15 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; /** - * Deals with the access token data that comes back from Nest when it is requested. + * Deals with the access token data that comes back from WWN when it is requested. * * @author David Bennett - Initial contribution * @author Wouter Born - Add equals and hashCode methods */ -public class AccessTokenData { +public class WWNAccessTokenData { private String accessToken; private Long expiresIn; @@ -42,7 +42,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - AccessTokenData other = (AccessTokenData) obj; + WWNAccessTokenData other = (WWNAccessTokenData) obj; if (accessToken == null) { if (other.accessToken != null) { return false; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ActivityZone.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNActivityZone.java similarity index 90% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ActivityZone.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNActivityZone.java index e548407ec54b7..f690f6e3967fc 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ActivityZone.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNActivityZone.java @@ -10,15 +10,15 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; /** - * The data for a camera activity zone. + * The data for a WWN camera activity zone. * * @author David Bennett - Initial contribution * @author Wouter Born - Extract ActivityZone object from Camera */ -public class ActivityZone { +public class WWNActivityZone { private String name; private int id; @@ -42,7 +42,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - ActivityZone other = (ActivityZone) obj; + WWNActivityZone other = (WWNActivityZone) obj; if (id != other.id) { return false; } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Camera.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNCamera.java similarity index 94% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Camera.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNCamera.java index f45ffabe23d81..2fc5486a20fca 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Camera.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNCamera.java @@ -10,18 +10,18 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; import java.util.Date; import java.util.List; /** - * The data for the camera. + * The data for the WWN camera. * * @author David Bennett - Initial contribution * @author Wouter Born - Add equals and hashCode methods */ -public class Camera extends BaseNestDevice { +public class WWNCamera extends BaseWWNDevice { private Boolean isStreaming; private Boolean isAudioInputEnabled; @@ -30,10 +30,10 @@ public class Camera extends BaseNestDevice { private String webUrl; private String appUrl; private Boolean isPublicShareEnabled; - private List activityZones; + private List activityZones; private String publicShareUrl; private String snapshotUrl; - private CameraEvent lastEvent; + private WWNCameraEvent lastEvent; public Boolean isStreaming() { return isStreaming; @@ -63,7 +63,7 @@ public Boolean isPublicShareEnabled() { return isPublicShareEnabled; } - public List getActivityZones() { + public List getActivityZones() { return activityZones; } @@ -75,7 +75,7 @@ public String getSnapshotUrl() { return snapshotUrl; } - public CameraEvent getLastEvent() { + public WWNCameraEvent getLastEvent() { return lastEvent; } @@ -84,13 +84,13 @@ public boolean equals(Object obj) { if (this == obj) { return true; } - if (!super.equals(obj)) { + if (obj == null || !super.equals(obj)) { return false; } if (getClass() != obj.getClass()) { return false; } - Camera other = (Camera) obj; + WWNCamera other = (WWNCamera) obj; if (activityZones == null) { if (other.activityZones != null) { return false; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/CameraEvent.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNCameraEvent.java similarity index 97% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/CameraEvent.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNCameraEvent.java index 4545d4247be89..ae2f1f1620539 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/CameraEvent.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNCameraEvent.java @@ -10,19 +10,19 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; import java.util.Date; import java.util.List; /** - * The data for a camera event. + * The data for a WWN camera event. * * @author David Bennett - Initial contribution * @author Wouter Born - Extract CameraEvent object from Camera * @author Wouter Born - Add equals, hashCode, toString methods */ -public class CameraEvent { +public class WWNCameraEvent { private Boolean hasSound; private Boolean hasMotion; @@ -91,7 +91,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - CameraEvent other = (CameraEvent) obj; + WWNCameraEvent other = (WWNCameraEvent) obj; if (activityZoneIds == null) { if (other.activityZoneIds != null) { return false; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestDevices.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNDevices.java similarity index 82% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestDevices.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNDevices.java index f17ae0ae96cb7..7e1dd65decf8c 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestDevices.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNDevices.java @@ -10,33 +10,33 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; import java.util.Map; /** - * All the Nest devices broken up by type. + * All the WWN devices broken up by type. * * @author David Bennett - Initial contribution */ -public class NestDevices { +public class WWNDevices { - private Map thermostats; - private Map smokeCoAlarms; - private Map cameras; + private Map thermostats; + private Map smokeCoAlarms; + private Map cameras; /** Id to thermostat mapping */ - public Map getThermostats() { + public Map getThermostats() { return thermostats; } /** Id to camera mapping */ - public Map getCameras() { + public Map getCameras() { return cameras; } /** Id to smoke detector */ - public Map getSmokeCoAlarms() { + public Map getSmokeCoAlarms() { return smokeCoAlarms; } @@ -51,7 +51,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - NestDevices other = (NestDevices) obj; + WWNDevices other = (WWNDevices) obj; if (cameras == null) { if (other.cameras != null) { return false; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ETA.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNETA.java similarity index 95% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ETA.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNETA.java index e3985631181c4..b05815966e799 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ETA.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNETA.java @@ -10,18 +10,18 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; import java.util.Date; /** - * Used to set and update the ETA values for Nest. + * Used to set and update the WWN ETA values. * * @author David Bennett - Initial contribution * @author Wouter Born - Extract ETA object from Structure * @author Wouter Born - Add equals, hashCode, toString methods */ -public class ETA { +public class WWNETA { private String tripId; private Date estimatedArrivalWindowBegin; @@ -62,7 +62,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - ETA other = (ETA) obj; + WWNETA other = (WWNETA) obj; if (estimatedArrivalWindowBegin == null) { if (other.estimatedArrivalWindowBegin != null) { return false; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ErrorData.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNErrorData.java similarity index 94% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ErrorData.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNErrorData.java index 5f6acd7780b9c..32f0d6db6f0a7 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ErrorData.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNErrorData.java @@ -10,16 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; /** - * The data of Nest API errors. + * The data of WWN API errors. * * @author Wouter Born - Initial contribution * @author Wouter Born - Improve exception handling * @author Wouter Born - Add equals and hashCode methods */ -public class ErrorData { +public class WWNErrorData { private String error; private String type; @@ -53,7 +53,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - ErrorData other = (ErrorData) obj; + WWNErrorData other = (WWNErrorData) obj; if (error == null) { if (other.error != null) { return false; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestIdentifiable.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNIdentifiable.java similarity index 79% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestIdentifiable.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNIdentifiable.java index 09952e183739c..a9c95cdb0aac8 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestIdentifiable.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNIdentifiable.java @@ -10,15 +10,15 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; /** - * Interface for uniquely identifiable Nest objects (device or a structure). + * Interface for uniquely identifiable WWN objects (device or a structure). * * @author Wouter Born - Initial contribution * @author Wouter Born - Simplify working with deviceId and structureId */ -public interface NestIdentifiable { +public interface WWNIdentifiable { /** * Returns the identifier that uniquely identifies the Nest object (deviceId or structureId). diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestMetadata.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNMetadata.java similarity index 92% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestMetadata.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNMetadata.java index 3c4530fc1bca1..3846597a827be 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestMetadata.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNMetadata.java @@ -10,15 +10,15 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; /** - * The meta data in the data downloads from Nest. + * The WWN meta data in the data downloads. * * @author David Bennett - Initial contribution * @author Wouter Born - Add equals and hashCode methods */ -public class NestMetadata { +public class WWNMetadata { private String accessToken; private String clientVersion; @@ -42,7 +42,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - NestMetadata other = (NestMetadata) obj; + WWNMetadata other = (WWNMetadata) obj; if (accessToken == null) { if (other.accessToken != null) { return false; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/SmokeDetector.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNSmokeDetector.java similarity index 95% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/SmokeDetector.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNSmokeDetector.java index 8c791ef9cedf4..7d4e37ad6e6c3 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/SmokeDetector.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNSmokeDetector.java @@ -10,19 +10,19 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; import java.util.Date; import com.google.gson.annotations.SerializedName; /** - * Data for the Nest smoke detector. + * Data for the WWN smoke detector. * * @author David Bennett - Initial contribution * @author Wouter Born - Add equals and hashCode methods */ -public class SmokeDetector extends BaseNestDevice { +public class WWNSmokeDetector extends BaseWWNDevice { private BatteryHealth batteryHealth; private AlarmState coAlarmState; @@ -87,13 +87,13 @@ public boolean equals(Object obj) { if (this == obj) { return true; } - if (!super.equals(obj)) { + if (obj == null || !super.equals(obj)) { return false; } if (getClass() != obj.getClass()) { return false; } - SmokeDetector other = (SmokeDetector) obj; + WWNSmokeDetector other = (WWNSmokeDetector) obj; if (batteryHealth != other.batteryHealth) { return false; } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Structure.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNStructure.java similarity index 94% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Structure.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNStructure.java index 3d3eba7390cf0..d6cf859c4bd84 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Structure.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNStructure.java @@ -10,23 +10,23 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; import java.util.Date; import java.util.List; import java.util.Map; -import org.openhab.binding.nest.internal.data.SmokeDetector.AlarmState; +import org.openhab.binding.nest.internal.wwn.data.WWNSmokeDetector.AlarmState; import com.google.gson.annotations.SerializedName; /** - * The structure details from Nest. + * The WWN structure details. * * @author David Bennett - Initial contribution * @author Wouter Born - Add equals and hashCode methods */ -public class Structure implements NestIdentifiable { +public class WWNStructure implements WWNIdentifiable { private String structureId; private List thermostats; @@ -38,13 +38,13 @@ public class Structure implements NestIdentifiable { private Date peakPeriodEndTime; private String timeZone; private Date etaBegin; - private SmokeDetector.AlarmState coAlarmState; - private SmokeDetector.AlarmState smokeAlarmState; + private WWNSmokeDetector.AlarmState coAlarmState; + private WWNSmokeDetector.AlarmState smokeAlarmState; private Boolean rhrEnrollment; - private Map wheres; + private Map wheres; private HomeAwayState away; private String name; - private ETA eta; + private WWNETA eta; private SecurityState wwnSecurityState; @Override @@ -112,11 +112,11 @@ public Boolean isRhrEnrollment() { return rhrEnrollment; } - public Map getWheres() { + public Map getWheres() { return wheres; } - public ETA getEta() { + public WWNETA getEta() { return eta; } @@ -155,7 +155,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - Structure other = (Structure) obj; + WWNStructure other = (WWNStructure) obj; if (away != other.away) { return false; } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Thermostat.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNThermostat.java similarity index 98% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Thermostat.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNThermostat.java index ed2c76a1456d0..2a8e1929358ec 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Thermostat.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNThermostat.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; import static org.openhab.core.library.unit.ImperialUnits.FAHRENHEIT; import static org.openhab.core.library.unit.SIUnits.CELSIUS; @@ -23,12 +23,12 @@ import com.google.gson.annotations.SerializedName; /** - * Gson class to encapsulate the data for the Nest thermostat. + * Gson class to encapsulate the data for the WWN thermostat. * * @author David Bennett - Initial contribution * @author Wouter Born - Add equals and hashCode methods */ -public class Thermostat extends BaseNestDevice { +public class WWNThermostat extends BaseWWNDevice { private Boolean canCool; private Boolean canHeat; @@ -262,13 +262,13 @@ public boolean equals(Object obj) { if (this == obj) { return true; } - if (!super.equals(obj)) { + if (obj == null || !super.equals(obj)) { return false; } if (getClass() != obj.getClass()) { return false; } - Thermostat other = (Thermostat) obj; + WWNThermostat other = (WWNThermostat) obj; if (ambientTemperatureC == null) { if (other.ambientTemperatureC != null) { return false; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/TopLevelData.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNTopLevelData.java similarity index 82% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/TopLevelData.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNTopLevelData.java index 5a2e4255095e1..1bce05d7e9172 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/TopLevelData.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNTopLevelData.java @@ -10,31 +10,31 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; import java.util.Map; /** - * Top level data for all the Nest stuff, this is the format the Nest data comes back from Nest in. + * The top level WWN data that is sent by Nest. * * @author David Bennett - Initial contribution * @author Wouter Born - Add equals and hashCode methods */ -public class TopLevelData { +public class WWNTopLevelData { - private NestDevices devices; - private NestMetadata metadata; - private Map structures; + private WWNDevices devices; + private WWNMetadata metadata; + private Map structures; - public NestDevices getDevices() { + public WWNDevices getDevices() { return devices; } - public NestMetadata getMetadata() { + public WWNMetadata getMetadata() { return metadata; } - public Map getStructures() { + public Map getStructures() { return structures; } @@ -49,7 +49,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - TopLevelData other = (TopLevelData) obj; + WWNTopLevelData other = (WWNTopLevelData) obj; if (devices == null) { if (other.devices != null) { return false; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/TopLevelStreamingData.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNTopLevelStreamingData.java similarity index 85% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/TopLevelStreamingData.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNTopLevelStreamingData.java index f5bc4e11fa79a..7d11e8a0a0513 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/TopLevelStreamingData.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNTopLevelStreamingData.java @@ -10,25 +10,25 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; /** - * The top level data that is sent by Nest to a streaming REST client using SSE. + * The top level WWN data that is sent by Nest to a streaming REST client using SSE. * * @author Wouter Born - Initial contribution * @author Wouter Born - Replace polling with REST streaming * @author Wouter Born - Add equals and hashCode methods */ -public class TopLevelStreamingData { +public class WWNTopLevelStreamingData { private String path; - private TopLevelData data; + private WWNTopLevelData data; public String getPath() { return path; } - public TopLevelData getData() { + public WWNTopLevelData getData() { return data; } @@ -52,7 +52,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - TopLevelStreamingData other = (TopLevelStreamingData) obj; + WWNTopLevelStreamingData other = (WWNTopLevelStreamingData) obj; if (data == null) { if (other.data != null) { return false; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Where.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNWhere.java similarity index 94% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Where.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNWhere.java index 0a5f0b7c909eb..3afe4c062c355 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Where.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNWhere.java @@ -10,14 +10,14 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; /** * @author David Bennett - Initial contribution * @author Wouter Born - Extract Where object from Structure * @author Wouter Born - Add equals, hashCode, toString methods */ -public class Where { +public class WWNWhere { private String whereId; private String name; @@ -40,7 +40,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - Where other = (Where) obj; + WWNWhere other = (WWNWhere) obj; if (name == null) { if (other.name != null) { return false; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/discovery/WWNDiscoveryService.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/discovery/WWNDiscoveryService.java new file mode 100644 index 0000000000000..2c5e0cac021a9 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/discovery/WWNDiscoveryService.java @@ -0,0 +1,180 @@ +/** + * Copyright (c) 2010-2021 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.discovery; + +import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*; +import static org.openhab.core.thing.Thing.PROPERTY_FIRMWARE_VERSION; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.nest.internal.wwn.config.WWNDeviceConfiguration; +import org.openhab.binding.nest.internal.wwn.config.WWNStructureConfiguration; +import org.openhab.binding.nest.internal.wwn.data.BaseWWNDevice; +import org.openhab.binding.nest.internal.wwn.data.WWNCamera; +import org.openhab.binding.nest.internal.wwn.data.WWNSmokeDetector; +import org.openhab.binding.nest.internal.wwn.data.WWNStructure; +import org.openhab.binding.nest.internal.wwn.data.WWNThermostat; +import org.openhab.binding.nest.internal.wwn.handler.WWNAccountHandler; +import org.openhab.binding.nest.internal.wwn.listener.WWNThingDataListener; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This service connects to the Nest account and creates the correct discovery results for devices + * as they are found through the WWN API. + * + * @author David Bennett - Initial contribution + * @author Wouter Born - Add representation properties + */ +@NonNullByDefault +public class WWNDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService { + + private static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_CAMERA, THING_TYPE_THERMOSTAT, + THING_TYPE_SMOKE_DETECTOR, THING_TYPE_STRUCTURE); + + private final Logger logger = LoggerFactory.getLogger(WWNDiscoveryService.class); + + private final DiscoveryDataListener cameraDiscoveryDataListener = new DiscoveryDataListener<>( + WWNCamera.class, THING_TYPE_CAMERA, this::addDeviceDiscoveryResult); + private final DiscoveryDataListener smokeDetectorDiscoveryDataListener = new DiscoveryDataListener<>( + WWNSmokeDetector.class, THING_TYPE_SMOKE_DETECTOR, this::addDeviceDiscoveryResult); + private final DiscoveryDataListener structureDiscoveryDataListener = new DiscoveryDataListener<>( + WWNStructure.class, THING_TYPE_STRUCTURE, this::addStructureDiscoveryResult); + private final DiscoveryDataListener thermostatDiscoveryDataListener = new DiscoveryDataListener<>( + WWNThermostat.class, THING_TYPE_THERMOSTAT, this::addDeviceDiscoveryResult); + + @SuppressWarnings("rawtypes") + private final List discoveryDataListeners = List.of(cameraDiscoveryDataListener, + smokeDetectorDiscoveryDataListener, structureDiscoveryDataListener, thermostatDiscoveryDataListener); + + private @NonNullByDefault({}) WWNAccountHandler accountHandler; + + private static class DiscoveryDataListener implements WWNThingDataListener { + private Class dataClass; + private ThingTypeUID thingTypeUID; + private BiConsumer onDiscovered; + + private DiscoveryDataListener(Class dataClass, ThingTypeUID thingTypeUID, + BiConsumer onDiscovered) { + this.dataClass = dataClass; + this.thingTypeUID = thingTypeUID; + this.onDiscovered = onDiscovered; + } + + @Override + public void onNewData(T data) { + onDiscovered.accept(data, thingTypeUID); + } + + @Override + public void onUpdatedData(T oldData, T data) { + } + + @Override + public void onMissingData(String nestId) { + } + } + + public WWNDiscoveryService() { + super(SUPPORTED_THING_TYPES, 60, true); + } + + @Override + @SuppressWarnings("unchecked") + public void activate() { + discoveryDataListeners.forEach(listener -> accountHandler.addThingDataListener(listener.dataClass, listener)); + addDiscoveryResultsFromLastUpdates(); + } + + @Override + @SuppressWarnings("unchecked") + public void deactivate() { + discoveryDataListeners + .forEach(listener -> accountHandler.removeThingDataListener(listener.dataClass, listener)); + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return accountHandler; + } + + @Override + public void setThingHandler(ThingHandler handler) { + if (handler instanceof WWNAccountHandler) { + accountHandler = (WWNAccountHandler) handler; + } + } + + @Override + protected void startScan() { + addDiscoveryResultsFromLastUpdates(); + } + + @SuppressWarnings("unchecked") + private void addDiscoveryResultsFromLastUpdates() { + discoveryDataListeners.forEach(listener -> addDiscoveryResultsFromLastUpdates(listener.dataClass, + listener.thingTypeUID, listener.onDiscovered)); + } + + private void addDiscoveryResultsFromLastUpdates(Class dataClass, ThingTypeUID thingTypeUID, + BiConsumer onDiscovered) { + List lastUpdates = accountHandler.getLastUpdates(dataClass); + lastUpdates.forEach(lastUpdate -> onDiscovered.accept(lastUpdate, thingTypeUID)); + } + + private void addDeviceDiscoveryResult(BaseWWNDevice device, ThingTypeUID typeUID) { + ThingUID bridgeUID = accountHandler.getThing().getUID(); + ThingUID thingUID = new ThingUID(typeUID, bridgeUID, device.getDeviceId()); + logger.debug("Discovered {}", thingUID); + Map properties = Map.of(WWNDeviceConfiguration.DEVICE_ID, device.getDeviceId(), + PROPERTY_FIRMWARE_VERSION, device.getSoftwareVersion()); + // @formatter:off + thingDiscovered(DiscoveryResultBuilder.create(thingUID) + .withThingType(typeUID) + .withLabel(device.getNameLong()) + .withBridge(bridgeUID) + .withProperties(properties) + .withRepresentationProperty(WWNDeviceConfiguration.DEVICE_ID) + .build() + ); + // @formatter:on + } + + public void addStructureDiscoveryResult(WWNStructure structure, ThingTypeUID typeUID) { + ThingUID bridgeUID = accountHandler.getThing().getUID(); + ThingUID thingUID = new ThingUID(typeUID, bridgeUID, structure.getStructureId()); + logger.debug("Discovered {}", thingUID); + Map properties = Map.of(WWNStructureConfiguration.STRUCTURE_ID, structure.getStructureId()); + // @formatter:off + thingDiscovered(DiscoveryResultBuilder.create(thingUID) + .withThingType(THING_TYPE_STRUCTURE) + .withLabel(structure.getName()) + .withBridge(bridgeUID) + .withProperties(properties) + .withRepresentationProperty(WWNStructureConfiguration.STRUCTURE_ID) + .build() + ); + // @formatter:on + } +} diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedResolvingNestUrlException.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/exceptions/FailedResolvingWWNUrlException.java similarity index 69% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedResolvingNestUrlException.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/exceptions/FailedResolvingWWNUrlException.java index 23d8ed8d95b7d..6e26f27fe88ca 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedResolvingNestUrlException.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/exceptions/FailedResolvingWWNUrlException.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.exceptions; +package org.openhab.binding.nest.internal.wwn.exceptions; /** * Will be thrown when the bridge was unable to resolve the Nest redirect URL. @@ -19,16 +19,16 @@ * @author Wouter Born - Improve exception handling while sending data */ @SuppressWarnings("serial") -public class FailedResolvingNestUrlException extends Exception { - public FailedResolvingNestUrlException(String message) { +public class FailedResolvingWWNUrlException extends Exception { + public FailedResolvingWWNUrlException(String message) { super(message); } - public FailedResolvingNestUrlException(String message, Throwable cause) { + public FailedResolvingWWNUrlException(String message, Throwable cause) { super(message, cause); } - public FailedResolvingNestUrlException(Throwable cause) { + public FailedResolvingWWNUrlException(Throwable cause) { super(cause); } } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedRetrievingNestDataException.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/exceptions/FailedRetrievingWWNDataException.java similarity index 69% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedRetrievingNestDataException.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/exceptions/FailedRetrievingWWNDataException.java index f762c3c696616..fd09c01cf36d2 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedRetrievingNestDataException.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/exceptions/FailedRetrievingWWNDataException.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.exceptions; +package org.openhab.binding.nest.internal.wwn.exceptions; /** * Will be thrown when the bridge was unable to retrieve data. @@ -19,17 +19,17 @@ * @author Martin van Wingerden - Added more centralized handling of failure when retrieving data */ @SuppressWarnings("serial") -public class FailedRetrievingNestDataException extends Exception { +public class FailedRetrievingWWNDataException extends Exception { - public FailedRetrievingNestDataException(String message) { + public FailedRetrievingWWNDataException(String message) { super(message); } - public FailedRetrievingNestDataException(String message, Throwable cause) { + public FailedRetrievingWWNDataException(String message, Throwable cause) { super(message, cause); } - public FailedRetrievingNestDataException(Throwable cause) { + public FailedRetrievingWWNDataException(Throwable cause) { super(cause); } } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedSendingNestDataException.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/exceptions/FailedSendingWWNDataException.java similarity index 68% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedSendingNestDataException.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/exceptions/FailedSendingWWNDataException.java index 02627e87b3e45..370a9df1bdb17 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedSendingNestDataException.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/exceptions/FailedSendingWWNDataException.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.exceptions; +package org.openhab.binding.nest.internal.wwn.exceptions; /** * Will be thrown when the bridge was unable to send data. @@ -19,16 +19,16 @@ * @author Wouter Born - Improve exception handling while sending data */ @SuppressWarnings("serial") -public class FailedSendingNestDataException extends Exception { - public FailedSendingNestDataException(String message) { +public class FailedSendingWWNDataException extends Exception { + public FailedSendingWWNDataException(String message) { super(message); } - public FailedSendingNestDataException(String message, Throwable cause) { + public FailedSendingWWNDataException(String message, Throwable cause) { super(message, cause); } - public FailedSendingNestDataException(Throwable cause) { + public FailedSendingWWNDataException(Throwable cause) { super(cause); } } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/InvalidAccessTokenException.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/exceptions/InvalidWWNAccessTokenException.java similarity index 70% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/InvalidAccessTokenException.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/exceptions/InvalidWWNAccessTokenException.java index ea14ee6af04fa..5435503ebd9d3 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/InvalidAccessTokenException.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/exceptions/InvalidWWNAccessTokenException.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.exceptions; +package org.openhab.binding.nest.internal.wwn.exceptions; /** * Will be thrown when there is no valid access token and it was not possible to refresh it @@ -19,16 +19,16 @@ * @author Martin van Wingerden - Added more centralized handling of invalid access tokens */ @SuppressWarnings("serial") -public class InvalidAccessTokenException extends Exception { - public InvalidAccessTokenException(Exception cause) { +public class InvalidWWNAccessTokenException extends Exception { + public InvalidWWNAccessTokenException(Exception cause) { super(cause); } - public InvalidAccessTokenException(String message, Throwable cause) { + public InvalidWWNAccessTokenException(String message, Throwable cause) { super(message, cause); } - public InvalidAccessTokenException(String message) { + public InvalidWWNAccessTokenException(String message) { super(message); } } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestBridgeHandler.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNAccountHandler.java similarity index 72% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestBridgeHandler.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNAccountHandler.java index 813877c78cc62..39fa3b3237951 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestBridgeHandler.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNAccountHandler.java @@ -10,14 +10,15 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.handler; +package org.openhab.binding.nest.internal.wwn.handler; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.openhab.binding.nest.internal.NestBindingConstants.JSON_CONTENT_TYPE; +import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.JSON_CONTENT_TYPE; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Properties; @@ -30,20 +31,21 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.nest.internal.NestUtils; -import org.openhab.binding.nest.internal.config.NestBridgeConfiguration; -import org.openhab.binding.nest.internal.data.ErrorData; -import org.openhab.binding.nest.internal.data.NestIdentifiable; -import org.openhab.binding.nest.internal.data.TopLevelData; -import org.openhab.binding.nest.internal.exceptions.FailedResolvingNestUrlException; -import org.openhab.binding.nest.internal.exceptions.FailedSendingNestDataException; -import org.openhab.binding.nest.internal.exceptions.InvalidAccessTokenException; -import org.openhab.binding.nest.internal.listener.NestStreamingDataListener; -import org.openhab.binding.nest.internal.listener.NestThingDataListener; -import org.openhab.binding.nest.internal.rest.NestAuthorizer; -import org.openhab.binding.nest.internal.rest.NestStreamingRestClient; -import org.openhab.binding.nest.internal.rest.NestUpdateRequest; -import org.openhab.binding.nest.internal.update.NestCompositeUpdateHandler; +import org.openhab.binding.nest.internal.wwn.WWNUtils; +import org.openhab.binding.nest.internal.wwn.config.WWNAccountConfiguration; +import org.openhab.binding.nest.internal.wwn.data.WWNErrorData; +import org.openhab.binding.nest.internal.wwn.data.WWNIdentifiable; +import org.openhab.binding.nest.internal.wwn.data.WWNTopLevelData; +import org.openhab.binding.nest.internal.wwn.discovery.WWNDiscoveryService; +import org.openhab.binding.nest.internal.wwn.exceptions.FailedResolvingWWNUrlException; +import org.openhab.binding.nest.internal.wwn.exceptions.FailedSendingWWNDataException; +import org.openhab.binding.nest.internal.wwn.exceptions.InvalidWWNAccessTokenException; +import org.openhab.binding.nest.internal.wwn.listener.WWNStreamingDataListener; +import org.openhab.binding.nest.internal.wwn.listener.WWNThingDataListener; +import org.openhab.binding.nest.internal.wwn.rest.WWNAuthorizer; +import org.openhab.binding.nest.internal.wwn.rest.WWNStreamingRestClient; +import org.openhab.binding.nest.internal.wwn.rest.WWNUpdateRequest; +import org.openhab.binding.nest.internal.wwn.update.WWNCompositeUpdateHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.io.net.http.HttpUtil; import org.openhab.core.thing.Bridge; @@ -53,6 +55,7 @@ import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseBridgeHandler; import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.osgi.service.jaxrs.client.SseEventSourceFactory; @@ -60,7 +63,7 @@ import org.slf4j.LoggerFactory; /** - * This bridge handler connects to Nest and handles all the API requests. It pulls down the + * This account handler connects to Nest and handles all the WWN API requests. It pulls down the * updated data, polls the system and does all the co-ordination with the other handlers * to get the data updated to the correct things. * @@ -69,32 +72,32 @@ * @author Wouter Born - Improve exception and URL redirect handling */ @NonNullByDefault -public class NestBridgeHandler extends BaseBridgeHandler implements NestStreamingDataListener { +public class WWNAccountHandler extends BaseBridgeHandler implements WWNStreamingDataListener { private static final int REQUEST_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(30); - private final Logger logger = LoggerFactory.getLogger(NestBridgeHandler.class); + private final Logger logger = LoggerFactory.getLogger(WWNAccountHandler.class); private final ClientBuilder clientBuilder; private final SseEventSourceFactory eventSourceFactory; - private final List nestUpdateRequests = new CopyOnWriteArrayList<>(); - private final NestCompositeUpdateHandler updateHandler = new NestCompositeUpdateHandler( + private final List nestUpdateRequests = new CopyOnWriteArrayList<>(); + private final WWNCompositeUpdateHandler updateHandler = new WWNCompositeUpdateHandler( this::getPresentThingsNestIds); - private @NonNullByDefault({}) NestAuthorizer authorizer; - private @NonNullByDefault({}) NestBridgeConfiguration config; + private @NonNullByDefault({}) WWNAuthorizer authorizer; + private @NonNullByDefault({}) WWNAccountConfiguration config; private @Nullable ScheduledFuture initializeJob; private @Nullable ScheduledFuture transmitJob; - private @Nullable NestRedirectUrlSupplier redirectUrlSupplier; - private @Nullable NestStreamingRestClient streamingRestClient; + private @Nullable WWNRedirectUrlSupplier redirectUrlSupplier; + private @Nullable WWNStreamingRestClient streamingRestClient; /** * Creates the bridge handler to connect to Nest. * * @param bridge The bridge to connect to Nest with. */ - public NestBridgeHandler(Bridge bridge, ClientBuilder clientBuilder, SseEventSourceFactory eventSourceFactory) { + public WWNAccountHandler(Bridge bridge, ClientBuilder clientBuilder, SseEventSourceFactory eventSourceFactory) { super(bridge); this.clientBuilder = clientBuilder; this.eventSourceFactory = eventSourceFactory; @@ -107,8 +110,8 @@ public NestBridgeHandler(Bridge bridge, ClientBuilder clientBuilder, SseEventSou public void initialize() { logger.debug("Initializing Nest bridge handler"); - config = getConfigAs(NestBridgeConfiguration.class); - authorizer = new NestAuthorizer(config); + config = getConfigAs(WWNAccountConfiguration.class); + authorizer = new WWNAuthorizer(config); updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Starting poll query"); initializeJob = scheduler.schedule(() -> { @@ -119,7 +122,7 @@ public void initialize() { logger.debug("Access Token {}", getExistingOrNewAccessToken()); redirectUrlSupplier = createRedirectUrlSupplier(); restartStreamingUpdates(); - } catch (InvalidAccessTokenException e) { + } catch (InvalidWWNAccessTokenException e) { logger.debug("Invalid access token", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Token is invalid and could not be refreshed: " + e.getMessage()); @@ -154,27 +157,27 @@ public void dispose() { this.streamingRestClient = null; } - public boolean addThingDataListener(Class dataClass, NestThingDataListener listener) { + public boolean addThingDataListener(Class dataClass, WWNThingDataListener listener) { return updateHandler.addListener(dataClass, listener); } - public boolean addThingDataListener(Class dataClass, String nestId, NestThingDataListener listener) { + public boolean addThingDataListener(Class dataClass, String nestId, WWNThingDataListener listener) { return updateHandler.addListener(dataClass, nestId, listener); } /** * Adds the update request into the queue for doing something with, send immediately if the queue is empty. */ - public void addUpdateRequest(NestUpdateRequest request) { + public void addUpdateRequest(WWNUpdateRequest request) { nestUpdateRequests.add(request); scheduleTransmitJobForPendingRequests(); } - protected NestRedirectUrlSupplier createRedirectUrlSupplier() throws InvalidAccessTokenException { - return new NestRedirectUrlSupplier(getHttpHeaders()); + protected WWNRedirectUrlSupplier createRedirectUrlSupplier() throws InvalidWWNAccessTokenException { + return new WWNRedirectUrlSupplier(getHttpHeaders()); } - private String getExistingOrNewAccessToken() throws InvalidAccessTokenException { + private String getExistingOrNewAccessToken() throws InvalidWWNAccessTokenException { String accessToken = config.accessToken; if (accessToken == null || accessToken.isEmpty()) { accessToken = authorizer.getNewAccessToken(); @@ -182,8 +185,8 @@ private String getExistingOrNewAccessToken() throws InvalidAccessTokenException config.pincode = ""; // Update and save the access token in the bridge configuration Configuration configuration = editConfiguration(); - configuration.put(NestBridgeConfiguration.ACCESS_TOKEN, config.accessToken); - configuration.put(NestBridgeConfiguration.PINCODE, config.pincode); + configuration.put(WWNAccountConfiguration.ACCESS_TOKEN, config.accessToken); + configuration.put(WWNAccountConfiguration.PINCODE, config.pincode); updateConfiguration(configuration); logger.debug("Retrieved new access token: {}", config.accessToken); return accessToken; @@ -193,7 +196,7 @@ private String getExistingOrNewAccessToken() throws InvalidAccessTokenException } } - protected Properties getHttpHeaders() throws InvalidAccessTokenException { + protected Properties getHttpHeaders() throws InvalidWWNAccessTokenException { Properties httpHeaders = new Properties(); httpHeaders.put("Authorization", "Bearer " + getExistingOrNewAccessToken()); httpHeaders.put("Content-Type", JSON_CONTENT_TYPE); @@ -208,8 +211,8 @@ public List getLastUpdates(Class dataClass) { return updateHandler.getLastUpdates(dataClass); } - private NestRedirectUrlSupplier getOrCreateRedirectUrlSupplier() throws InvalidAccessTokenException { - NestRedirectUrlSupplier localRedirectUrlSupplier = redirectUrlSupplier; + private WWNRedirectUrlSupplier getOrCreateRedirectUrlSupplier() throws InvalidWWNAccessTokenException { + WWNRedirectUrlSupplier localRedirectUrlSupplier = redirectUrlSupplier; if (localRedirectUrlSupplier == null) { localRedirectUrlSupplier = createRedirectUrlSupplier(); redirectUrlSupplier = localRedirectUrlSupplier; @@ -222,12 +225,17 @@ private Set getPresentThingsNestIds() { for (Thing thing : getThing().getThings()) { ThingHandler handler = thing.getHandler(); if (handler != null && thing.getStatusInfo().getStatusDetail() != ThingStatusDetail.GONE) { - nestIds.add(((NestIdentifiable) handler).getId()); + nestIds.add(((WWNIdentifiable) handler).getId()); } } return nestIds; } + @Override + public Collection> getServices() { + return List.of(WWNDiscoveryService.class); + } + /** * Handles an incoming command update */ @@ -239,18 +247,18 @@ public void handleCommand(ChannelUID channelUID, Command command) { } } - private void jsonToPutUrl(NestUpdateRequest request) - throws FailedSendingNestDataException, InvalidAccessTokenException, FailedResolvingNestUrlException { + private void jsonToPutUrl(WWNUpdateRequest request) + throws FailedSendingWWNDataException, InvalidWWNAccessTokenException, FailedResolvingWWNUrlException { try { - NestRedirectUrlSupplier localRedirectUrlSupplier = redirectUrlSupplier; + WWNRedirectUrlSupplier localRedirectUrlSupplier = redirectUrlSupplier; if (localRedirectUrlSupplier == null) { - throw new FailedResolvingNestUrlException("redirectUrlSupplier is null"); + throw new FailedResolvingWWNUrlException("redirectUrlSupplier is null"); } String url = localRedirectUrlSupplier.getRedirectUrl() + request.getUpdatePath(); logger.debug("Putting data to: {}", url); - String jsonContent = NestUtils.toJson(request.getValues()); + String jsonContent = WWNUtils.toJson(request.getValues()); logger.debug("PUT content: {}", jsonContent); ByteArrayInputStream inputStream = new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8)); @@ -258,13 +266,13 @@ private void jsonToPutUrl(NestUpdateRequest request) REQUEST_TIMEOUT); logger.debug("PUT response: {}", jsonResponse); - ErrorData error = NestUtils.fromJson(jsonResponse, ErrorData.class); + WWNErrorData error = WWNUtils.fromJson(jsonResponse, WWNErrorData.class); if (error.getError() != null && !error.getError().isBlank()) { logger.debug("Nest API error: {}", error); logger.warn("Nest API error: {}", error.getMessage()); } } catch (IOException e) { - throw new FailedSendingNestDataException("Failed to send data", e); + throw new FailedSendingWWNDataException("Failed to send data", e); } } @@ -291,16 +299,16 @@ public void onError(String message) { } @Override - public void onNewTopLevelData(TopLevelData data) { + public void onNewTopLevelData(WWNTopLevelData data) { updateHandler.handleUpdate(data); updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Receiving streaming data"); } - public boolean removeThingDataListener(Class dataClass, NestThingDataListener listener) { + public boolean removeThingDataListener(Class dataClass, WWNThingDataListener listener) { return updateHandler.removeListener(dataClass, listener); } - public boolean removeThingDataListener(Class dataClass, String nestId, NestThingDataListener listener) { + public boolean removeThingDataListener(Class dataClass, String nestId, WWNThingDataListener listener) { return updateHandler.removeListener(dataClass, nestId, listener); } @@ -321,14 +329,14 @@ private void scheduleTransmitJobForPendingRequests() { private void startStreamingUpdates() { synchronized (this) { try { - NestStreamingRestClient localStreamingRestClient = new NestStreamingRestClient( + WWNStreamingRestClient localStreamingRestClient = new WWNStreamingRestClient( getExistingOrNewAccessToken(), clientBuilder, eventSourceFactory, getOrCreateRedirectUrlSupplier(), scheduler); localStreamingRestClient.addStreamingDataListener(this); localStreamingRestClient.start(); streamingRestClient = localStreamingRestClient; - } catch (InvalidAccessTokenException e) { + } catch (InvalidWWNAccessTokenException e) { logger.debug("Invalid access token", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Token is invalid and could not be refreshed: " + e.getMessage()); @@ -337,7 +345,7 @@ private void startStreamingUpdates() { } private void stopStreamingUpdates() { - NestStreamingRestClient localStreamingRestClient = streamingRestClient; + WWNStreamingRestClient localStreamingRestClient = streamingRestClient; if (localStreamingRestClient != null) { synchronized (this) { localStreamingRestClient.stop(); @@ -357,24 +365,24 @@ private void transmitQueue() { try { while (!nestUpdateRequests.isEmpty()) { // nestUpdateRequests is a CopyOnWriteArrayList so its iterator does not support remove operations - NestUpdateRequest request = nestUpdateRequests.get(0); + WWNUpdateRequest request = nestUpdateRequests.get(0); jsonToPutUrl(request); nestUpdateRequests.remove(request); } - } catch (InvalidAccessTokenException e) { + } catch (InvalidWWNAccessTokenException e) { logger.debug("Invalid access token", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Token is invalid and could not be refreshed: " + e.getMessage()); - } catch (FailedResolvingNestUrlException e) { + } catch (FailedResolvingWWNUrlException e) { logger.debug("Unable to resolve redirect URL", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); scheduler.schedule(this::restartStreamingUpdates, 5, SECONDS); - } catch (FailedSendingNestDataException e) { + } catch (FailedSendingWWNDataException e) { logger.debug("Error sending data", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); scheduler.schedule(this::restartStreamingUpdates, 5, SECONDS); - NestRedirectUrlSupplier localRedirectUrlSupplier = redirectUrlSupplier; + WWNRedirectUrlSupplier localRedirectUrlSupplier = redirectUrlSupplier; if (localRedirectUrlSupplier != null) { localRedirectUrlSupplier.resetCache(); } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestBaseHandler.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNBaseHandler.java similarity index 71% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestBaseHandler.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNBaseHandler.java index 80b52b0ce01b8..3fafcadce73a2 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestBaseHandler.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNBaseHandler.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.handler; +package org.openhab.binding.nest.internal.wwn.handler; import java.time.Instant; import java.time.ZonedDateTime; @@ -22,12 +22,13 @@ import javax.measure.Quantity; import javax.measure.Unit; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.nest.internal.config.NestDeviceConfiguration; -import org.openhab.binding.nest.internal.data.NestIdentifiable; -import org.openhab.binding.nest.internal.listener.NestThingDataListener; -import org.openhab.binding.nest.internal.rest.NestUpdateRequest; +import org.openhab.binding.nest.internal.wwn.config.WWNDeviceConfiguration; +import org.openhab.binding.nest.internal.wwn.data.WWNIdentifiable; +import org.openhab.binding.nest.internal.wwn.listener.WWNThingDataListener; +import org.openhab.binding.nest.internal.wwn.rest.WWNUpdateRequest; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; @@ -46,7 +47,7 @@ import org.slf4j.LoggerFactory; /** - * Deals with the structures on the Nest API, turning them into a thing in openHAB. + * Deals with the structures on the WWN API, turning them into a thing in openHAB. * * @author David Bennett - Initial contribution * @author Martin van Wingerden - Splitted of NestBaseHandler @@ -55,14 +56,14 @@ * @param the type of update data */ @NonNullByDefault -public abstract class NestBaseHandler extends BaseThingHandler - implements NestThingDataListener, NestIdentifiable { - private final Logger logger = LoggerFactory.getLogger(NestBaseHandler.class); +public abstract class WWNBaseHandler<@NonNull T> extends BaseThingHandler + implements WWNThingDataListener, WWNIdentifiable { + private final Logger logger = LoggerFactory.getLogger(WWNBaseHandler.class); private @Nullable String deviceId; private Class dataClass; - NestBaseHandler(Thing thing, Class dataClass) { + WWNBaseHandler(Thing thing, Class dataClass) { super(thing); this.dataClass = dataClass; } @@ -71,7 +72,7 @@ public abstract class NestBaseHandler extends BaseThingHandler public void initialize() { logger.debug("Initializing handler for {}", getClass().getName()); - NestBridgeHandler handler = getNestBridgeHandler(); + WWNAccountHandler handler = getAccountHandler(); if (handler != null) { boolean success = handler.addThingDataListener(dataClass, getId(), this); logger.debug("Adding {} with ID '{}' as device data listener, result: {}", getClass().getSimpleName(), @@ -83,7 +84,7 @@ public void initialize() { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Waiting for refresh"); - T lastUpdate = getLastUpdate(); + final @Nullable T lastUpdate = getLastUpdate(); if (lastUpdate != null) { update(null, lastUpdate); } @@ -91,14 +92,14 @@ public void initialize() { @Override public void dispose() { - NestBridgeHandler handler = getNestBridgeHandler(); + WWNAccountHandler handler = getAccountHandler(); if (handler != null) { handler.removeThingDataListener(dataClass, getId(), this); } } protected @Nullable T getLastUpdate() { - NestBridgeHandler handler = getNestBridgeHandler(); + WWNAccountHandler handler = getAccountHandler(); if (handler != null) { return handler.getLastUpdate(dataClass, getId()); } @@ -106,10 +107,10 @@ public void dispose() { } protected void addUpdateRequest(String updatePath, String field, Object value) { - NestBridgeHandler handler = getNestBridgeHandler(); + WWNAccountHandler handler = getAccountHandler(); if (handler != null) { // @formatter:off - handler.addUpdateRequest(new NestUpdateRequest.Builder() + handler.addUpdateRequest(new WWNUpdateRequest.Builder() .withBasePath(updatePath) .withIdentifier(getId()) .withAdditionalValue(field, value) @@ -126,15 +127,15 @@ public String getId() { protected String getDeviceId() { String localDeviceId = deviceId; if (localDeviceId == null) { - localDeviceId = getConfigAs(NestDeviceConfiguration.class).deviceId; + localDeviceId = getConfigAs(WWNDeviceConfiguration.class).deviceId; deviceId = localDeviceId; } return localDeviceId; } - protected @Nullable NestBridgeHandler getNestBridgeHandler() { + protected @Nullable WWNAccountHandler getAccountHandler() { Bridge bridge = getBridge(); - return bridge != null ? (NestBridgeHandler) bridge.getHandler() : null; + return bridge != null ? (WWNAccountHandler) bridge.getHandler() : null; } protected abstract State getChannelState(ChannelUID channelUID, T data); @@ -165,23 +166,24 @@ protected State getAsStringTypeOrNull(@Nullable Object value) { return value == null ? UnDefType.NULL : new StringType(value.toString()); } - protected State getAsStringTypeListOrNull(@Nullable Collection values) { + protected State getAsStringTypeListOrNull(@Nullable Collection<@NonNull ?> values) { return values == null || values.isEmpty() ? UnDefType.NULL - : new StringType(values.stream().map(v -> v.toString()).collect(Collectors.joining(","))); + : new StringType(values.stream().map(value -> value.toString()).collect(Collectors.joining(","))); } - protected boolean isNotHandling(NestIdentifiable nestIdentifiable) { + protected boolean isNotHandling(WWNIdentifiable nestIdentifiable) { return !(getId().equals(nestIdentifiable.getId())); } - protected void updateLinkedChannels(T oldData, T data) { - getThing().getChannels().stream().map(c -> c.getUID()).filter(this::isLinked).forEach(channelUID -> { - State newState = getChannelState(channelUID, data); - if (oldData == null || !getChannelState(channelUID, oldData).equals(newState)) { - logger.debug("Updating {}", channelUID); - updateState(channelUID, newState); - } - }); + protected void updateLinkedChannels(@Nullable T oldData, T data) { + getThing().getChannels().stream().map(channel -> channel.getUID()).filter(this::isLinked) + .forEach(channelUID -> { + State newState = getChannelState(channelUID, data); + if (oldData == null || !getChannelState(channelUID, oldData).equals(newState)) { + logger.debug("Updating {}", channelUID); + updateState(channelUID, newState); + } + }); } @Override @@ -200,5 +202,5 @@ public void onMissingData(String nestId) { new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Missing from streaming updates")); } - protected abstract void update(T oldData, T data); + protected abstract void update(@Nullable T oldData, T data); } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestCameraHandler.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNCameraHandler.java similarity index 86% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestCameraHandler.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNCameraHandler.java index 0c2534b96cb24..6975b4201352a 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestCameraHandler.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNCameraHandler.java @@ -10,15 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.handler; +package org.openhab.binding.nest.internal.wwn.handler; -import static org.openhab.binding.nest.internal.NestBindingConstants.*; +import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*; import static org.openhab.core.thing.Thing.PROPERTY_FIRMWARE_VERSION; import static org.openhab.core.types.RefreshType.REFRESH; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.nest.internal.data.Camera; -import org.openhab.binding.nest.internal.data.CameraEvent; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.nest.internal.wwn.data.WWNCamera; +import org.openhab.binding.nest.internal.wwn.data.WWNCameraEvent; import org.openhab.core.library.types.OnOffType; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -30,22 +31,21 @@ import org.slf4j.LoggerFactory; /** - * Handles all the updates to the camera as well as handling the commands that send - * updates to Nest. + * Handles all the updates to the camera as well as handling the commands that send updates to the WWN API. * * @author David Bennett - Initial contribution * @author Wouter Born - Handle channel refresh command */ @NonNullByDefault -public class NestCameraHandler extends NestBaseHandler { - private final Logger logger = LoggerFactory.getLogger(NestCameraHandler.class); +public class WWNCameraHandler extends WWNBaseHandler { + private final Logger logger = LoggerFactory.getLogger(WWNCameraHandler.class); - public NestCameraHandler(Thing thing) { - super(thing, Camera.class); + public WWNCameraHandler(Thing thing) { + super(thing, WWNCamera.class); } @Override - protected State getChannelState(ChannelUID channelUID, Camera camera) { + protected State getChannelState(ChannelUID channelUID, WWNCamera camera) { if (channelUID.getId().startsWith(CHANNEL_GROUP_CAMERA_PREFIX)) { return getCameraChannelState(channelUID, camera); } else if (channelUID.getId().startsWith(CHANNEL_GROUP_LAST_EVENT_PREFIX)) { @@ -56,7 +56,7 @@ protected State getChannelState(ChannelUID channelUID, Camera camera) { } } - protected State getCameraChannelState(ChannelUID channelUID, Camera camera) { + protected State getCameraChannelState(ChannelUID channelUID, WWNCamera camera) { switch (channelUID.getId()) { case CHANNEL_CAMERA_APP_URL: return getAsStringTypeOrNull(camera.getAppUrl()); @@ -82,8 +82,8 @@ protected State getCameraChannelState(ChannelUID channelUID, Camera camera) { } } - protected State getLastEventChannelState(ChannelUID channelUID, Camera camera) { - CameraEvent lastEvent = camera.getLastEvent(); + protected State getLastEventChannelState(ChannelUID channelUID, WWNCamera camera) { + WWNCameraEvent lastEvent = camera.getLastEvent(); if (lastEvent == null) { return UnDefType.NULL; } @@ -120,7 +120,7 @@ protected State getLastEventChannelState(ChannelUID channelUID, Camera camera) { @Override public void handleCommand(ChannelUID channelUID, Command command) { if (REFRESH.equals(command)) { - Camera lastUpdate = getLastUpdate(); + WWNCamera lastUpdate = getLastUpdate(); if (lastUpdate != null) { updateState(channelUID, getChannelState(channelUID, lastUpdate)); } @@ -138,7 +138,7 @@ private void addUpdateRequest(String field, Object value) { } @Override - protected void update(Camera oldCamera, Camera camera) { + protected void update(@Nullable WWNCamera oldCamera, WWNCamera camera) { logger.debug("Updating {}", getThing().getUID()); updateLinkedChannels(oldCamera, camera); diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestRedirectUrlSupplier.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNRedirectUrlSupplier.java similarity index 72% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestRedirectUrlSupplier.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNRedirectUrlSupplier.java index 3d027ced5f32e..e1945cb2d8052 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestRedirectUrlSupplier.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNRedirectUrlSupplier.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.handler; +package org.openhab.binding.nest.internal.wwn.handler; import java.util.Properties; import java.util.concurrent.TimeUnit; @@ -23,33 +23,33 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.openhab.binding.nest.internal.NestBindingConstants; -import org.openhab.binding.nest.internal.exceptions.FailedResolvingNestUrlException; +import org.openhab.binding.nest.internal.wwn.WWNBindingConstants; +import org.openhab.binding.nest.internal.wwn.exceptions.FailedResolvingWWNUrlException; import org.openhab.core.io.net.http.HttpUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Supplies resolved redirect URLs of {@link NestBindingConstants#NEST_URL} so they can be used with HTTP clients that + * Supplies resolved redirect URLs of {@link WWNBindingConstants#NEST_URL} so they can be used with HTTP clients that * do not pass Authorization headers after redirects like the Jetty client used by {@link HttpUtil}. * * @author Wouter Born - Initial contribution * @author Wouter Born - Extract resolving redirect URL from NestBridgeHandler into NestRedirectUrlSupplier */ @NonNullByDefault -public class NestRedirectUrlSupplier { +public class WWNRedirectUrlSupplier { - private final Logger logger = LoggerFactory.getLogger(NestRedirectUrlSupplier.class); + private final Logger logger = LoggerFactory.getLogger(WWNRedirectUrlSupplier.class); protected String cachedUrl = ""; protected Properties httpHeaders; - public NestRedirectUrlSupplier(Properties httpHeaders) { + public WWNRedirectUrlSupplier(Properties httpHeaders) { this.httpHeaders = httpHeaders; } - public String getRedirectUrl() throws FailedResolvingNestUrlException { + public String getRedirectUrl() throws FailedResolvingWWNUrlException { if (cachedUrl.isEmpty()) { cachedUrl = resolveRedirectUrl(); } @@ -61,7 +61,7 @@ public void resetCache() { } /** - * Resolves the redirect URL for calls using the {@link NestBindingConstants#NEST_URL}. + * Resolves the redirect URL for calls using the {@link WWNBindingConstants#NEST_URL}. * * The Jetty client used by {@link HttpUtil} will not pass the Authorization header after a redirect resulting in * "401 Unauthorized error" issues. @@ -70,11 +70,11 @@ public void resetCache() { * * @see https://developers.nest.com/documentation/cloud/how-to-handle-redirects */ - private String resolveRedirectUrl() throws FailedResolvingNestUrlException { + private String resolveRedirectUrl() throws FailedResolvingWWNUrlException { HttpClient httpClient = new HttpClient(new SslContextFactory()); httpClient.setFollowRedirects(false); - Request request = httpClient.newRequest(NestBindingConstants.NEST_URL).method(HttpMethod.GET).timeout(30, + Request request = httpClient.newRequest(WWNBindingConstants.NEST_URL).method(HttpMethod.GET).timeout(30, TimeUnit.SECONDS); for (String httpHeaderKey : httpHeaders.stringPropertyNames()) { request.header(httpHeaderKey, httpHeaders.getProperty(httpHeaderKey)); @@ -86,7 +86,7 @@ private String resolveRedirectUrl() throws FailedResolvingNestUrlException { response = request.send(); httpClient.stop(); } catch (Exception e) { - throw new FailedResolvingNestUrlException("Failed to resolve redirect URL: " + e.getMessage(), e); + throw new FailedResolvingWWNUrlException("Failed to resolve redirect URL: " + e.getMessage(), e); } int status = response.getStatus(); @@ -95,10 +95,10 @@ private String resolveRedirectUrl() throws FailedResolvingNestUrlException { if (status != HttpStatus.TEMPORARY_REDIRECT_307) { logger.debug("Redirect status: {}", status); logger.debug("Redirect response: {}", response.getContentAsString()); - throw new FailedResolvingNestUrlException("Failed to get redirect URL, expected status " + throw new FailedResolvingWWNUrlException("Failed to get redirect URL, expected status " + HttpStatus.TEMPORARY_REDIRECT_307 + " but was " + status); } else if (redirectUrl == null || redirectUrl.isEmpty()) { - throw new FailedResolvingNestUrlException("Redirect URL is empty"); + throw new FailedResolvingWWNUrlException("Redirect URL is empty"); } redirectUrl = redirectUrl.endsWith("/") ? redirectUrl.substring(0, redirectUrl.length() - 1) : redirectUrl; diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestSmokeDetectorHandler.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNSmokeDetectorHandler.java similarity index 76% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestSmokeDetectorHandler.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNSmokeDetectorHandler.java index dc85a485b0276..f3e9105176ded 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestSmokeDetectorHandler.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNSmokeDetectorHandler.java @@ -10,15 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.handler; +package org.openhab.binding.nest.internal.wwn.handler; -import static org.openhab.binding.nest.internal.NestBindingConstants.*; +import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*; import static org.openhab.core.thing.Thing.PROPERTY_FIRMWARE_VERSION; import static org.openhab.core.types.RefreshType.REFRESH; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.nest.internal.data.SmokeDetector; -import org.openhab.binding.nest.internal.data.SmokeDetector.BatteryHealth; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.nest.internal.wwn.data.WWNSmokeDetector; +import org.openhab.binding.nest.internal.wwn.data.WWNSmokeDetector.BatteryHealth; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -29,21 +30,21 @@ import org.slf4j.LoggerFactory; /** - * The smoke detector handler, it handles the data from Nest for the smoke detector. + * The smoke detector handler, it handles the data from WWN for the smoke detector. * * @author David Bennett - Initial contribution * @author Wouter Born - Handle channel refresh command */ @NonNullByDefault -public class NestSmokeDetectorHandler extends NestBaseHandler { - private final Logger logger = LoggerFactory.getLogger(NestSmokeDetectorHandler.class); +public class WWNSmokeDetectorHandler extends WWNBaseHandler { + private final Logger logger = LoggerFactory.getLogger(WWNSmokeDetectorHandler.class); - public NestSmokeDetectorHandler(Thing thing) { - super(thing, SmokeDetector.class); + public WWNSmokeDetectorHandler(Thing thing) { + super(thing, WWNSmokeDetector.class); } @Override - protected State getChannelState(ChannelUID channelUID, SmokeDetector smokeDetector) { + protected State getChannelState(ChannelUID channelUID, WWNSmokeDetector smokeDetector) { switch (channelUID.getId()) { case CHANNEL_CO_ALARM_STATE: return getAsStringTypeOrNull(smokeDetector.getCoAlarmState()); @@ -72,7 +73,7 @@ protected State getChannelState(ChannelUID channelUID, SmokeDetector smokeDetect @Override public void handleCommand(ChannelUID channelUID, Command command) { if (REFRESH.equals(command)) { - SmokeDetector lastUpdate = getLastUpdate(); + WWNSmokeDetector lastUpdate = getLastUpdate(); if (lastUpdate != null) { updateState(channelUID, getChannelState(channelUID, lastUpdate)); } @@ -80,7 +81,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { } @Override - protected void update(SmokeDetector oldSmokeDetector, SmokeDetector smokeDetector) { + protected void update(@Nullable WWNSmokeDetector oldSmokeDetector, WWNSmokeDetector smokeDetector) { logger.debug("Updating {}", getThing().getUID()); updateLinkedChannels(oldSmokeDetector, smokeDetector); diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestStructureHandler.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNStructureHandler.java similarity index 80% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestStructureHandler.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNStructureHandler.java index e77cdc6a89dd3..c2963838a417e 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestStructureHandler.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNStructureHandler.java @@ -10,16 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.handler; +package org.openhab.binding.nest.internal.wwn.handler; -import static org.openhab.binding.nest.internal.NestBindingConstants.*; +import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*; import static org.openhab.core.types.RefreshType.REFRESH; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.nest.internal.config.NestStructureConfiguration; -import org.openhab.binding.nest.internal.data.Structure; -import org.openhab.binding.nest.internal.data.Structure.HomeAwayState; +import org.openhab.binding.nest.internal.wwn.config.WWNStructureConfiguration; +import org.openhab.binding.nest.internal.wwn.data.WWNStructure; +import org.openhab.binding.nest.internal.wwn.data.WWNStructure.HomeAwayState; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -31,23 +31,23 @@ import org.slf4j.LoggerFactory; /** - * Deals with the structures on the Nest API, turning them into a thing in openHAB. + * Deals with the structures on the WWN API, turning them into a thing in openHAB. * * @author David Bennett - Initial contribution * @author Wouter Born - Handle channel refresh command */ @NonNullByDefault -public class NestStructureHandler extends NestBaseHandler { - private final Logger logger = LoggerFactory.getLogger(NestStructureHandler.class); +public class WWNStructureHandler extends WWNBaseHandler { + private final Logger logger = LoggerFactory.getLogger(WWNStructureHandler.class); private @Nullable String structureId; - public NestStructureHandler(Thing thing) { - super(thing, Structure.class); + public WWNStructureHandler(Thing thing) { + super(thing, WWNStructure.class); } @Override - protected State getChannelState(ChannelUID channelUID, Structure structure) { + protected State getChannelState(ChannelUID channelUID, WWNStructure structure) { switch (channelUID.getId()) { case CHANNEL_AWAY: return getAsStringTypeOrNull(structure.getAway()); @@ -85,7 +85,7 @@ public String getId() { private String getStructureId() { String localStructureId = structureId; if (localStructureId == null) { - localStructureId = getConfigAs(NestStructureConfiguration.class).structureId; + localStructureId = getConfigAs(WWNStructureConfiguration.class).structureId; structureId = localStructureId; } return localStructureId; @@ -101,7 +101,7 @@ private String getStructureId() { @Override public void handleCommand(ChannelUID channelUID, Command command) { if (REFRESH.equals(command)) { - Structure lastUpdate = getLastUpdate(); + WWNStructure lastUpdate = getLastUpdate(); if (lastUpdate != null) { updateState(channelUID, getChannelState(channelUID, lastUpdate)); } @@ -116,7 +116,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { } @Override - protected void update(Structure oldStructure, Structure structure) { + protected void update(@Nullable WWNStructure oldStructure, WWNStructure structure) { logger.debug("Updating {}", getThing().getUID()); updateLinkedChannels(oldStructure, structure); diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestThermostatHandler.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNThermostatHandler.java similarity index 91% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestThermostatHandler.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNThermostatHandler.java index 41d9b9470e51d..201841f92d8d1 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestThermostatHandler.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNThermostatHandler.java @@ -10,9 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.handler; +package org.openhab.binding.nest.internal.wwn.handler; -import static org.openhab.binding.nest.internal.NestBindingConstants.*; +import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*; import static org.openhab.core.library.unit.SIUnits.CELSIUS; import static org.openhab.core.thing.Thing.PROPERTY_FIRMWARE_VERSION; import static org.openhab.core.types.RefreshType.REFRESH; @@ -26,8 +26,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.nest.internal.data.Thermostat; -import org.openhab.binding.nest.internal.data.Thermostat.Mode; +import org.openhab.binding.nest.internal.wwn.data.WWNThermostat; +import org.openhab.binding.nest.internal.wwn.data.WWNThermostat.Mode; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; @@ -42,22 +42,22 @@ import org.slf4j.LoggerFactory; /** - * The {@link NestThermostatHandler} is responsible for handling commands, which are + * The {@link WWNThermostatHandler} is responsible for handling commands, which are * sent to one of the channels for the thermostat. * * @author David Bennett - Initial contribution * @author Wouter Born - Handle channel refresh command */ @NonNullByDefault -public class NestThermostatHandler extends NestBaseHandler { - private final Logger logger = LoggerFactory.getLogger(NestThermostatHandler.class); +public class WWNThermostatHandler extends WWNBaseHandler { + private final Logger logger = LoggerFactory.getLogger(WWNThermostatHandler.class); - public NestThermostatHandler(Thing thing) { - super(thing, Thermostat.class); + public WWNThermostatHandler(Thing thing) { + super(thing, WWNThermostat.class); } @Override - protected State getChannelState(ChannelUID channelUID, Thermostat thermostat) { + protected State getChannelState(ChannelUID channelUID, WWNThermostat thermostat) { switch (channelUID.getId()) { case CHANNEL_CAN_COOL: return getAsOnOffTypeOrNull(thermostat.isCanCool()); @@ -125,7 +125,7 @@ protected State getChannelState(ChannelUID channelUID, Thermostat thermostat) { @SuppressWarnings("unchecked") public void handleCommand(ChannelUID channelUID, Command command) { if (REFRESH.equals(command)) { - Thermostat lastUpdate = getLastUpdate(); + WWNThermostat lastUpdate = getLastUpdate(); if (lastUpdate != null) { updateState(channelUID, getChannelState(channelUID, lastUpdate)); } @@ -182,7 +182,7 @@ private void addTemperatureUpdateRequest(String celsiusField, String fahrenheitF } private Unit getTemperatureUnit(Unit fallbackUnit) { - Thermostat lastUpdate = getLastUpdate(); + WWNThermostat lastUpdate = getLastUpdate(); if (lastUpdate != null && lastUpdate.getTemperatureUnit() != null) { return lastUpdate.getTemperatureUnit(); } @@ -204,7 +204,7 @@ private Unit getTemperatureUnit(Unit fallbackUnit) { } @Override - protected void update(Thermostat oldThermostat, Thermostat thermostat) { + protected void update(@Nullable WWNThermostat oldThermostat, WWNThermostat thermostat) { logger.debug("Updating {}", getThing().getUID()); updateLinkedChannels(oldThermostat, thermostat); diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/listener/NestStreamingDataListener.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/listener/WWNStreamingDataListener.java similarity index 68% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/listener/NestStreamingDataListener.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/listener/WWNStreamingDataListener.java index cbfe7960a79b8..b914b2396d978 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/listener/NestStreamingDataListener.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/listener/WWNStreamingDataListener.java @@ -10,20 +10,20 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.listener; +package org.openhab.binding.nest.internal.wwn.listener; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.nest.internal.data.TopLevelData; -import org.openhab.binding.nest.internal.rest.NestStreamingRestClient; +import org.openhab.binding.nest.internal.wwn.data.WWNTopLevelData; +import org.openhab.binding.nest.internal.wwn.rest.WWNStreamingRestClient; /** - * Interface for listeners of events generated by the {@link NestStreamingRestClient}. + * Interface for listeners of events generated by the {@link WWNStreamingRestClient}. * * @author Wouter Born - Initial contribution * @author Wouter Born - Replace polling with REST streaming */ @NonNullByDefault -public interface NestStreamingDataListener { +public interface WWNStreamingDataListener { /** * Authorization has been revoked for a token. @@ -46,7 +46,7 @@ public interface NestStreamingDataListener { void onError(String message); /** - * Initial {@link TopLevelData} or an update is sent. + * Initial {@link WWNTopLevelData} or an update is sent. */ - void onNewTopLevelData(TopLevelData data); + void onNewTopLevelData(WWNTopLevelData data); } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/listener/NestThingDataListener.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/listener/WWNThingDataListener.java similarity index 88% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/listener/NestThingDataListener.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/listener/WWNThingDataListener.java index 9ec8eb4b5346b..4a3455d660354 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/listener/NestThingDataListener.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/listener/WWNThingDataListener.java @@ -10,17 +10,17 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.listener; +package org.openhab.binding.nest.internal.wwn.listener; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * Used to track incoming data for Nest things. + * Used to track incoming data for WWN things. * * @author Wouter Born - Initial contribution */ @NonNullByDefault -public interface NestThingDataListener { +public interface WWNThingDataListener { /** * An initial value for the data was received or the value is send again due to a refresh. diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestAuthorizer.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/rest/WWNAuthorizer.java similarity index 59% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestAuthorizer.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/rest/WWNAuthorizer.java index a0dd8d8f4cd89..63c2d53600ebc 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestAuthorizer.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/rest/WWNAuthorizer.java @@ -10,31 +10,31 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.rest; +package org.openhab.binding.nest.internal.wwn.rest; import java.io.IOException; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.nest.internal.NestBindingConstants; -import org.openhab.binding.nest.internal.NestUtils; -import org.openhab.binding.nest.internal.config.NestBridgeConfiguration; -import org.openhab.binding.nest.internal.data.AccessTokenData; -import org.openhab.binding.nest.internal.exceptions.InvalidAccessTokenException; +import org.openhab.binding.nest.internal.wwn.WWNBindingConstants; +import org.openhab.binding.nest.internal.wwn.WWNUtils; +import org.openhab.binding.nest.internal.wwn.config.WWNAccountConfiguration; +import org.openhab.binding.nest.internal.wwn.data.WWNAccessTokenData; +import org.openhab.binding.nest.internal.wwn.exceptions.InvalidWWNAccessTokenException; import org.openhab.core.io.net.http.HttpUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Retrieves the Nest access token using the OAuth 2.0 protocol using pin-based authorization. + * Retrieves the WWN access token using the OAuth 2.0 protocol using pin-based authorization. * * @author David Bennett - Initial contribution * @author Wouter Born - Improve exception handling */ @NonNullByDefault -public class NestAuthorizer { - private final Logger logger = LoggerFactory.getLogger(NestAuthorizer.class); +public class WWNAuthorizer { + private final Logger logger = LoggerFactory.getLogger(WWNAuthorizer.class); - private final NestBridgeConfiguration config; + private final WWNAccountConfiguration config; /** * Create the helper class for the Nest access token. Also creates the folder @@ -42,24 +42,24 @@ public class NestAuthorizer { * * @param config The configuration to use for the token */ - public NestAuthorizer(NestBridgeConfiguration config) { + public WWNAuthorizer(WWNAccountConfiguration config) { this.config = config; } /** * Get the current access token, refreshing if needed. * - * @throws InvalidAccessTokenException thrown when the access token is invalid and could not be refreshed + * @throws InvalidWWNAccessTokenException thrown when the access token is invalid and could not be refreshed */ - public String getNewAccessToken() throws InvalidAccessTokenException { + public String getNewAccessToken() throws InvalidWWNAccessTokenException { try { String pincode = config.pincode; if (pincode == null || pincode.isBlank()) { - throw new InvalidAccessTokenException("Pincode is empty"); + throw new InvalidWWNAccessTokenException("Pincode is empty"); } // @formatter:off - StringBuilder urlBuilder = new StringBuilder(NestBindingConstants.NEST_ACCESS_TOKEN_URL) + StringBuilder urlBuilder = new StringBuilder(WWNBindingConstants.NEST_ACCESS_TOKEN_URL) .append("?client_id=") .append(config.productId) .append("&client_secret=") @@ -74,16 +74,16 @@ public String getNewAccessToken() throws InvalidAccessTokenException { String responseContentAsString = HttpUtil.executeUrl("POST", urlBuilder.toString(), null, null, "application/x-www-form-urlencoded", 10_000); - AccessTokenData data = NestUtils.fromJson(responseContentAsString, AccessTokenData.class); + WWNAccessTokenData data = WWNUtils.fromJson(responseContentAsString, WWNAccessTokenData.class); logger.debug("Received: {}", data); String accessToken = data.getAccessToken(); if (accessToken == null || accessToken.isBlank()) { - throw new InvalidAccessTokenException("Pincode to obtain access token is already used or invalid)"); + throw new InvalidWWNAccessTokenException("Pincode to obtain access token is already used or invalid)"); } return accessToken; } catch (IOException e) { - throw new InvalidAccessTokenException("Access token request failed", e); + throw new InvalidWWNAccessTokenException("Access token request failed", e); } } } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestStreamingRequestFilter.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/rest/WWNStreamingRequestFilter.java similarity index 86% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestStreamingRequestFilter.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/rest/WWNStreamingRequestFilter.java index b89b97b072b04..83ee467a6a270 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestStreamingRequestFilter.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/rest/WWNStreamingRequestFilter.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.rest; +package org.openhab.binding.nest.internal.wwn.rest; import java.io.IOException; @@ -23,16 +23,16 @@ import org.eclipse.jdt.annotation.Nullable; /** - * Inserts Authorization and Cache-Control headers for requests on the streaming REST API. + * Inserts Authorization and Cache-Control headers for requests on the streaming WWN REST API. * * @author Wouter Born - Initial contribution * @author Wouter Born - Replace polling with REST streaming */ @NonNullByDefault -public class NestStreamingRequestFilter implements ClientRequestFilter { +public class WWNStreamingRequestFilter implements ClientRequestFilter { private final String accessToken; - public NestStreamingRequestFilter(String accessToken) { + public WWNStreamingRequestFilter(String accessToken) { this.accessToken = accessToken; } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestStreamingRestClient.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/rest/WWNStreamingRestClient.java similarity index 82% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestStreamingRestClient.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/rest/WWNStreamingRestClient.java index 3f39b33fd7a49..72802eb73818c 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestStreamingRestClient.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/rest/WWNStreamingRestClient.java @@ -10,9 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.rest; +package org.openhab.binding.nest.internal.wwn.rest; -import static org.openhab.binding.nest.internal.NestBindingConstants.KEEP_ALIVE_MILLIS; +import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.KEEP_ALIVE_MILLIS; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -27,24 +27,24 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.nest.internal.NestUtils; -import org.openhab.binding.nest.internal.data.TopLevelData; -import org.openhab.binding.nest.internal.data.TopLevelStreamingData; -import org.openhab.binding.nest.internal.exceptions.FailedResolvingNestUrlException; -import org.openhab.binding.nest.internal.handler.NestRedirectUrlSupplier; -import org.openhab.binding.nest.internal.listener.NestStreamingDataListener; +import org.openhab.binding.nest.internal.wwn.WWNUtils; +import org.openhab.binding.nest.internal.wwn.data.WWNTopLevelData; +import org.openhab.binding.nest.internal.wwn.data.WWNTopLevelStreamingData; +import org.openhab.binding.nest.internal.wwn.exceptions.FailedResolvingWWNUrlException; +import org.openhab.binding.nest.internal.wwn.handler.WWNRedirectUrlSupplier; +import org.openhab.binding.nest.internal.wwn.listener.WWNStreamingDataListener; import org.osgi.service.jaxrs.client.SseEventSourceFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * A client that generates events based on Nest streaming REST API Server-Sent Events (SSE). + * A client that generates events based on Nest streaming WWN REST API Server-Sent Events (SSE). * * @author Wouter Born - Initial contribution * @author Wouter Born - Replace polling with REST streaming */ @NonNullByDefault -public class NestStreamingRestClient { +public class WWNStreamingRestClient { // Assume connection timeout when 2 keep alive message should have been received private static final long CONNECTION_TIMEOUT_MILLIS = 2 * KEEP_ALIVE_MILLIS + KEEP_ALIVE_MILLIS / 2; @@ -55,25 +55,25 @@ public class NestStreamingRestClient { public static final String OPEN = "open"; public static final String PUT = "put"; - private final Logger logger = LoggerFactory.getLogger(NestStreamingRestClient.class); + private final Logger logger = LoggerFactory.getLogger(WWNStreamingRestClient.class); private final String accessToken; private final ClientBuilder clientBuilder; private final SseEventSourceFactory eventSourceFactory; - private final NestRedirectUrlSupplier redirectUrlSupplier; + private final WWNRedirectUrlSupplier redirectUrlSupplier; private final ScheduledExecutorService scheduler; private final Object startStopLock = new Object(); - private final List listeners = new CopyOnWriteArrayList<>(); + private final List listeners = new CopyOnWriteArrayList<>(); private @Nullable ScheduledFuture checkConnectionJob; private boolean connected; private @Nullable SseEventSource eventSource; private long lastEventTimestamp; - private @Nullable TopLevelData lastReceivedTopLevelData; + private @Nullable WWNTopLevelData lastReceivedTopLevelData; - public NestStreamingRestClient(String accessToken, ClientBuilder clientBuilder, - SseEventSourceFactory eventSourceFactory, NestRedirectUrlSupplier redirectUrlSupplier, + public WWNStreamingRestClient(String accessToken, ClientBuilder clientBuilder, + SseEventSourceFactory eventSourceFactory, WWNRedirectUrlSupplier redirectUrlSupplier, ScheduledExecutorService scheduler) { this.accessToken = accessToken; this.clientBuilder = clientBuilder; @@ -82,8 +82,8 @@ public NestStreamingRestClient(String accessToken, ClientBuilder clientBuilder, this.scheduler = scheduler; } - private SseEventSource createEventSource() throws FailedResolvingNestUrlException { - Client client = clientBuilder.register(new NestStreamingRequestFilter(accessToken)).build(); + private SseEventSource createEventSource() throws FailedResolvingWWNUrlException { + Client client = clientBuilder.register(new WWNStreamingRequestFilter(accessToken)).build(); SseEventSource eventSource = eventSourceFactory.newSource(client.target(redirectUrlSupplier.getRedirectUrl())); eventSource.register(this::onEvent, this::onError); return eventSource; @@ -122,7 +122,7 @@ private void reopenEventSource() { localEventSource.open(); eventSource = localEventSource; - } catch (FailedResolvingNestUrlException e) { + } catch (FailedResolvingWWNUrlException e) { logger.debug("Failed to resolve Nest redirect URL while opening new EventSource"); } } @@ -175,15 +175,15 @@ private void stopCheckConnectionJob(boolean mayInterruptIfRunning) { } } - public boolean addStreamingDataListener(NestStreamingDataListener listener) { + public boolean addStreamingDataListener(WWNStreamingDataListener listener) { return listeners.add(listener); } - public boolean removeStreamingDataListener(NestStreamingDataListener listener) { + public boolean removeStreamingDataListener(WWNStreamingDataListener listener) { return listeners.remove(listener); } - public @Nullable TopLevelData getLastReceivedTopLevelData() { + public @Nullable WWNTopLevelData getLastReceivedTopLevelData() { return lastReceivedTopLevelData; } @@ -214,7 +214,7 @@ private void onEvent(InboundSseEvent inboundEvent) { logger.debug("Event stream opened"); } else if (PUT.equals(name)) { logger.debug("Data has changed (or initial data sent)"); - TopLevelData topLevelData = NestUtils.fromJson(data, TopLevelStreamingData.class).getData(); + WWNTopLevelData topLevelData = WWNUtils.fromJson(data, WWNTopLevelStreamingData.class).getData(); lastReceivedTopLevelData = topLevelData; listeners.forEach(listener -> listener.onNewTopLevelData(topLevelData)); } else { diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestUpdateRequest.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/rest/WWNUpdateRequest.java similarity index 83% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestUpdateRequest.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/rest/WWNUpdateRequest.java index 7b62eb6d250db..e97d1d0902000 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestUpdateRequest.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/rest/WWNUpdateRequest.java @@ -10,21 +10,21 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.rest; +package org.openhab.binding.nest.internal.wwn.rest; import java.util.HashMap; import java.util.Map; /** - * Contains the data needed to do an update request back to Nest. + * Contains the data needed to do an WWN update request back to Nest. * * @author David Bennett - Initial contribution */ -public class NestUpdateRequest { +public class WWNUpdateRequest { private final String updatePath; private final Map values; - private NestUpdateRequest(Builder builder) { + private WWNUpdateRequest(Builder builder) { this.updatePath = builder.basePath + builder.identifier; this.values = builder.values; } @@ -57,8 +57,8 @@ public Builder withAdditionalValue(String field, Object value) { return this; } - public NestUpdateRequest build() { - return new NestUpdateRequest(this); + public WWNUpdateRequest build() { + return new WWNUpdateRequest(this); } } } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/update/NestCompositeUpdateHandler.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/update/WWNCompositeUpdateHandler.java similarity index 69% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/update/NestCompositeUpdateHandler.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/update/WWNCompositeUpdateHandler.java index eacd62329963f..b0c8d009318ff 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/update/NestCompositeUpdateHandler.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/update/WWNCompositeUpdateHandler.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.update; +package org.openhab.binding.nest.internal.wwn.update; import java.util.HashSet; import java.util.List; @@ -20,36 +20,37 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.nest.internal.data.NestIdentifiable; -import org.openhab.binding.nest.internal.data.TopLevelData; -import org.openhab.binding.nest.internal.listener.NestThingDataListener; +import org.openhab.binding.nest.internal.wwn.data.WWNIdentifiable; +import org.openhab.binding.nest.internal.wwn.data.WWNTopLevelData; +import org.openhab.binding.nest.internal.wwn.listener.WWNThingDataListener; /** - * Handles all Nest data updates through delegation to the {@link NestUpdateHandler} for the respective data type. + * Handles all Nest data updates through delegation to the {@link WWNUpdateHandler} for the respective data type. * * @author Wouter Born - Initial contribution */ @NonNullByDefault -public class NestCompositeUpdateHandler { +public class WWNCompositeUpdateHandler { private final Supplier> presentNestIdsSupplier; - private final Map, NestUpdateHandler> updateHandlersMap = new ConcurrentHashMap<>(); + private final Map, WWNUpdateHandler> updateHandlersMap = new ConcurrentHashMap<>(); - public NestCompositeUpdateHandler(Supplier> presentNestIdsSupplier) { + public WWNCompositeUpdateHandler(Supplier> presentNestIdsSupplier) { this.presentNestIdsSupplier = presentNestIdsSupplier; } - public boolean addListener(Class dataClass, NestThingDataListener listener) { + public boolean addListener(Class dataClass, WWNThingDataListener listener) { return getOrCreateUpdateHandler(dataClass).addListener(listener); } - public boolean addListener(Class dataClass, String nestId, NestThingDataListener listener) { + public boolean addListener(Class dataClass, String nestId, WWNThingDataListener listener) { return getOrCreateUpdateHandler(dataClass).addListener(nestId, listener); } - private Set findMissingNestIds(Set updates) { + private Set findMissingNestIds(Set updates) { Set nestIds = updates.stream().map(u -> u.getId()).collect(Collectors.toSet()); Set missingNestIds = presentNestIdsSupplier.get(); missingNestIds.removeAll(nestIds); @@ -64,8 +65,8 @@ public List getLastUpdates(Class dataClass) { return getOrCreateUpdateHandler(dataClass).getLastUpdates(); } - private Set getNestUpdates(TopLevelData data) { - Set updates = new HashSet<>(); + private Set getNestUpdates(WWNTopLevelData data) { + Set updates = new HashSet<>(); if (data.getDevices() != null) { if (data.getDevices().getCameras() != null) { updates.addAll(data.getDevices().getCameras().values()); @@ -84,20 +85,20 @@ private Set getNestUpdates(TopLevelData data) { } @SuppressWarnings("unchecked") - private NestUpdateHandler getOrCreateUpdateHandler(Class dataClass) { - NestUpdateHandler handler = (NestUpdateHandler) updateHandlersMap.get(dataClass); + private <@NonNull T> WWNUpdateHandler getOrCreateUpdateHandler(Class dataClass) { + WWNUpdateHandler handler = (WWNUpdateHandler) updateHandlersMap.get(dataClass); if (handler == null) { - handler = new NestUpdateHandler<>(); + handler = new WWNUpdateHandler<>(); updateHandlersMap.put(dataClass, handler); } return handler; } @SuppressWarnings("unchecked") - public void handleUpdate(TopLevelData data) { - Set updates = getNestUpdates(data); + public void handleUpdate(WWNTopLevelData data) { + Set updates = getNestUpdates(data); updates.forEach(update -> { - Class updateClass = (Class) update.getClass(); + Class updateClass = (Class) update.getClass(); getOrCreateUpdateHandler(updateClass).handleUpdate(updateClass, update.getId(), update); }); @@ -109,11 +110,11 @@ public void handleUpdate(TopLevelData data) { } } - public boolean removeListener(Class dataClass, NestThingDataListener listener) { + public boolean removeListener(Class dataClass, WWNThingDataListener listener) { return getOrCreateUpdateHandler(dataClass).removeListener(listener); } - public boolean removeListener(Class dataClass, String nestId, NestThingDataListener listener) { + public boolean removeListener(Class dataClass, String nestId, WWNThingDataListener listener) { return getOrCreateUpdateHandler(dataClass).removeListener(nestId, listener); } diff --git a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/update/NestUpdateHandler.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/update/WWNUpdateHandler.java similarity index 70% rename from bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/update/NestUpdateHandler.java rename to bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/update/WWNUpdateHandler.java index 9f78942711318..53f3acc71a616 100644 --- a/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/update/NestUpdateHandler.java +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/wwn/update/WWNUpdateHandler.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.update; +package org.openhab.binding.nest.internal.wwn.update; import java.util.ArrayList; import java.util.HashSet; @@ -20,9 +20,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.nest.internal.listener.NestThingDataListener; +import org.openhab.binding.nest.internal.wwn.listener.WWNThingDataListener; /** * Handles the updates of one type of data by notifying listeners of changes and storing the update value. @@ -32,7 +33,7 @@ * @param the type of update data */ @NonNullByDefault -public class NestUpdateHandler { +public class WWNUpdateHandler<@NonNull T> { /** * The ID used for listeners that subscribe to any Nest update. @@ -40,13 +41,13 @@ public class NestUpdateHandler { private static final String ANY_ID = "*"; private final Map lastUpdates = new ConcurrentHashMap<>(); - private final Map>> listenersMap = new ConcurrentHashMap<>(); + private final Map>> listenersMap = new ConcurrentHashMap<>(); - public boolean addListener(NestThingDataListener listener) { + public boolean addListener(WWNThingDataListener listener) { return addListener(ANY_ID, listener); } - public boolean addListener(String nestId, NestThingDataListener listener) { + public boolean addListener(String nestId, WWNThingDataListener listener) { return getOrCreateListeners(nestId).add(listener); } @@ -58,21 +59,21 @@ public List getLastUpdates() { return new ArrayList<>(lastUpdates.values()); } - private Set> getListeners(String nestId) { - Set> listeners = new HashSet<>(); - Set> idListeners = listenersMap.get(nestId); + private Set> getListeners(String nestId) { + Set> listeners = new HashSet<>(); + Set> idListeners = listenersMap.get(nestId); if (idListeners != null) { listeners.addAll(idListeners); } - Set> anyListeners = listenersMap.get(ANY_ID); + Set> anyListeners = listenersMap.get(ANY_ID); if (anyListeners != null) { listeners.addAll(anyListeners); } return listeners; } - private Set> getOrCreateListeners(String nestId) { - Set> listeners = listenersMap.get(nestId); + private Set> getOrCreateListeners(String nestId) { + Set> listeners = listenersMap.get(nestId); if (listeners == null) { listeners = new CopyOnWriteArraySet<>(); listenersMap.put(nestId, listeners); @@ -88,13 +89,13 @@ public void handleMissingNestIds(Set nestIds) { } public void handleUpdate(Class dataClass, String nestId, T update) { - T lastUpdate = getLastUpdate(nestId); + final @Nullable T lastUpdate = getLastUpdate(nestId); lastUpdates.put(nestId, update); notifyListeners(nestId, lastUpdate, update); } private void notifyListeners(String nestId, @Nullable T lastUpdate, T update) { - Set> listeners = getListeners(nestId); + Set> listeners = getListeners(nestId); if (lastUpdate == null) { listeners.forEach(l -> l.onNewData(update)); } else if (!lastUpdate.equals(update)) { @@ -102,11 +103,11 @@ private void notifyListeners(String nestId, @Nullable T lastUpdate, T update) { } } - public boolean removeListener(NestThingDataListener listener) { + public boolean removeListener(WWNThingDataListener listener) { return removeListener(ANY_ID, listener); } - public boolean removeListener(String nestId, NestThingDataListener listener) { + public boolean removeListener(String nestId, WWNThingDataListener listener) { return getOrCreateListeners(nestId).remove(listener); } diff --git a/bundles/org.openhab.binding.nest/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider b/bundles/org.openhab.binding.nest/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider new file mode 100644 index 0000000000000..562947debcb90 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider @@ -0,0 +1,2 @@ +io.grpc.grpclb.GrpclbLoadBalancerProvider +io.grpc.internal.PickFirstLoadBalancerProvider diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/config/sdm-config.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/config/sdm-config.xml new file mode 100644 index 0000000000000..6c713f678a414 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/config/sdm-config.xml @@ -0,0 +1,33 @@ + + + + + + + This is the GCP Project that you created and integrated with the SDM API + + + + This is the oAuth 2.0 Client ID associated with the provided Project ID + + + + This is the Client Secret returned when setting up your oAuth 2.0 Client ID + + + + This is the one time authorization token used to retrieve the Refresh and Access token to the SDM API + + + + + + + This is refresh interval in seconds to update the nest device information + + + + diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/config/wwn-config.xml similarity index 92% rename from bundles/org.openhab.binding.nest/src/main/resources/OH-INF/config/config.xml rename to bundles/org.openhab.binding.nest/src/main/resources/OH-INF/config/wwn-config.xml index 21c880e10fbae..912c90cd680e9 100644 --- a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/config/wwn-config.xml @@ -4,7 +4,7 @@ xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd"> - + The OAuth parameters used when communicating with the Nest API @@ -41,13 +41,13 @@ - + - + diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-account.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-account.xml new file mode 100644 index 0000000000000..cc4837cd3ceda --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-account.xml @@ -0,0 +1,13 @@ + + + + + + An account for using the Smart Device Management (SDM) API + + + + diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-camera.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-camera.xml new file mode 100644 index 0000000000000..03c4106b48371 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-camera.xml @@ -0,0 +1,34 @@ + + + + + + A Nest Camera registered with your SDM account + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-channels.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-channels.xml new file mode 100644 index 0000000000000..4c935a33b4f57 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-channels.xml @@ -0,0 +1,194 @@ + + + + + + Switch + + Toggles whether a doorbell was pressed recently.. Refreshed at normal refresh interval for the Doorbell + + + + Image + + Doorbell Static Image based on a Chime Event + + + + String + + The last time that the Door Chime was last pressed + + + + String + + Includes an rtsps video stream URL from the most recent event.. Refreshed at the next motion detected + + + + String + + Livestream current token value + + + + String + + Livestream token expiration time + + + + String + + Livestream token extension value + + + + Image + + Doorbell Static Image based on a Motion Event + + + + Switch + + Toggles whether Motion was seen at the doorbell recently.. Refreshed at normal refresh interval for the + Doorbell + + + + String + + Toggles whether Motion was seen at the doorbell recently.. Refreshed at normal refresh interval for the + Doorbell + + + + String + + Doorbell Name + + + + Switch + + Toggles whether a person was seen at the doorbell recently.. Refreshed at normal refresh interval for the + Doorbell + + + + Image + + Doorbell Static Image based on a Person Event + + + + String + + The last time that a person was detected at the door + + + + Image + + Doorbell Static Image based on a Sound Event + + + + Switch + + Toggles whether a sound was heard at the doorbell recently.. Refreshed at normal refresh interval for the + Doorbell + + + + String + + Toggles whether a sound was heard at the doorbell recently.. Refreshed at normal refresh interval for the + Doorbell + + + + + Number:Dimensionless + + Lists the current ambient temperature from the thermostat + + + + String + + Lists the current Eco mode from the thermostat + + + + String + + Lists the current mode from the thermostat + + + + String + + Lists the current Fan Mode + + + + Number:Length + + Lists the current humidity percentage from the thermostat + + + + String + + Provides the Thermostat HVAC Status + + + + Number:Dimensionless + + Lists the Maximum Temperature Setting from the thermostat + + + + Number:Dimensionless + + Lists the Minimum Temperature Setting from the thermostat + + + + String + + Thermostat Name + + + + String + + Lists the scale setting from the thermostat + + + + Number:Dimensionless + + Lists the Target Temperature Setting from the thermostat + + + + Number:Dimensionless + + Lists the Cool Temperature Setting from the thermostat + + + + Number:Dimensionless + + Lists the Heat Temperature Setting from the thermostat + + + diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-display.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-display.xml new file mode 100644 index 0000000000000..b6e2ec36c92f1 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-display.xml @@ -0,0 +1,34 @@ + + + + + + A Nest Display registered with your SDM account + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-doorbell.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-doorbell.xml new file mode 100644 index 0000000000000..3d19712729091 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-doorbell.xml @@ -0,0 +1,35 @@ + + + + + + A binding to interact with Nest Device DoorBells + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-thermostat.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-thermostat.xml new file mode 100644 index 0000000000000..69d89b0b2041b --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/sdm-thermostat.xml @@ -0,0 +1,31 @@ + + + + + + A Thermostat to control the various aspects of the house's HVAC system + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/thermostat.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/thermostat.xml deleted file mode 100644 index 816e2b3591591..0000000000000 --- a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/thermostat.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - A Thermostat to control the various aspects of the house's HVAC system - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Nest - - - deviceId - - - - diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-account.xml similarity index 64% rename from bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/bridge.xml rename to bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-account.xml index 60e6e60450793..5594e5293dac2 100644 --- a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/bridge.xml +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-account.xml @@ -4,9 +4,9 @@ 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"> - - - An account for using the Nest REST API - + + + An account for using the Works with Nest (WWN) API + diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/camera.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-camera.xml similarity index 70% rename from bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/camera.xml rename to bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-camera.xml index 67c11800114a2..ec8ee8c49057f 100644 --- a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/camera.xml +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-camera.xml @@ -4,17 +4,17 @@ 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"> - + - + - A Nest Cam registered with your account + A Nest Camera registered with your WWN account - - + + Information about the last camera event (requires Nest Aware subscription) @@ -26,6 +26,6 @@ deviceId - + diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-channels.xml similarity index 75% rename from bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/channels.xml rename to bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-channels.xml index b3d0346ddcbbc..aa03202ff3a37 100644 --- a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-channels.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + DateTime Timestamp of the last successful interaction with Nest @@ -13,7 +13,7 @@ - + String Away state of the structure @@ -25,39 +25,39 @@ - + String Country code of the structure - + String Postal code of the structure - + String The time zone for the structure - + DateTime Peak period start for the Rush Hour Rewards program - + DateTime Peak period end for the Rush Hour Rewards program - + DateTime @@ -66,14 +66,14 @@ - + Switch If rush hour rewards system is enabled or not - + String Security state of the structure @@ -86,166 +86,166 @@ - + Information about the camera - - - - - - - - - + + + + + + + + + - + Switch If the audio input is enabled for this camera - + Switch If the video history is enabled for this camera - + Switch If the public sharing of this camera is enabled - + Switch If the camera is currently streaming - + String The web URL for the camera, allows you to see the camera in a web page - + String The publicly available URL for the camera - + String The URL showing a snapshot of the camera - + String The app URL for the camera, allows you to see the camera in an app - + DateTime Timestamp of the last online status change - + Information about the camera event - - - - - - - - - - - + + + + + + + + + + + - + Switch If sound was detected in the camera event - + Switch If motion was detected in the camera event - + Switch If a person was detected in the camera event - + DateTime Timestamp when the camera event started - + DateTime Timestamp when the camera event ended - + DateTime Timestamp when the camera event URLs expire - + String The web URL for the camera event, allows you to see the camera event in a web page - + String The app URL for the camera event, allows you to see the camera event in an app - + String The URL showing an image for the camera event - + String The URL showing an animated image for the camera event - + String Identifiers for activity zones that detected the event (comma separated) @@ -253,7 +253,7 @@ - + String Current color state of the protect @@ -267,7 +267,7 @@ - + String Carbon monoxide alarm state @@ -280,7 +280,7 @@ - + String Smoke alarm state @@ -293,14 +293,14 @@ - + Switch If the manual test is currently active - + DateTime Timestamp of the last successful manual test @@ -308,7 +308,7 @@ - + Number:Temperature Current temperature @@ -316,7 +316,7 @@ - + Number:Temperature The set point temperature @@ -324,7 +324,7 @@ - + Number:Temperature The max set point temperature @@ -332,7 +332,7 @@ - + Number:Temperature The min set point temperature @@ -340,7 +340,7 @@ - + Number:Temperature The eco range max set point temperature @@ -348,7 +348,7 @@ - + Number:Temperature The eco range min set point temperature @@ -356,7 +356,7 @@ - + Number:Temperature The locked range max set point temperature @@ -364,7 +364,7 @@ - + Number:Temperature The locked range min set point temperature @@ -372,14 +372,14 @@ - + Switch If the thermostat has the temperature locked to only be within a set range - + String Current mode of the Nest thermostat @@ -394,7 +394,7 @@ - + String The previous mode of the Nest thermostat @@ -409,7 +409,7 @@ - + String The active state of the Nest thermostat @@ -422,7 +422,7 @@ - + Number:Dimensionless Indicates the current relative humidity @@ -430,35 +430,35 @@ - + Number:Time Time left to the target temperature approximately - + Switch If the thermostat can actually turn on heating - + Switch If the thermostat can actually turn on cooling - + Switch If the fan timer is engaged - + Number:Time Length of time that the fan is set to run @@ -476,42 +476,42 @@ - + DateTime Timestamp when the fan stops running - + Switch If the thermostat can control the fan - + Switch If the thermostat is currently in a leaf mode - + Switch If sunlight correction is enabled - + Switch If sunlight correction is active - + Switch If the system is currently using emergency heat diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/smoke-detector.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-smoke-detector.xml similarity index 59% rename from bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/smoke-detector.xml rename to bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-smoke-detector.xml index d1fc874898931..72b64a8f9d341 100644 --- a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/smoke-detector.xml +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-smoke-detector.xml @@ -4,22 +4,22 @@ 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"> - + - + The smoke detector/Nest Protect for the account - + - - - - - + + + + + @@ -28,6 +28,6 @@ deviceId - + diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/structure.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-structure.xml similarity index 50% rename from bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/structure.xml rename to bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-structure.xml index 242ea0c316f01..c594e6a690a6d 100644 --- a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/structure.xml +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-structure.xml @@ -4,9 +4,9 @@ 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"> - + - + @@ -15,17 +15,17 @@ structure if you have more than one house - - - - - - - - - - - + + + + + + + + + + + @@ -34,7 +34,7 @@ structureId - + diff --git a/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-thermostat.xml b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-thermostat.xml new file mode 100644 index 0000000000000..a987b6df9df12 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/wwn-thermostat.xml @@ -0,0 +1,51 @@ + + + + + + + + + + A Thermostat to control the various aspects of the house's HVAC system + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Nest + + + deviceId + + + + diff --git a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/data/NestDataUtil.java b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNDataUtil.java similarity index 90% rename from itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/data/NestDataUtil.java rename to itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNDataUtil.java index f37fef5e3770f..daf29820ad1d3 100644 --- a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/data/NestDataUtil.java +++ b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNDataUtil.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; import java.io.BufferedReader; import java.io.IOException; @@ -23,7 +23,7 @@ import javax.measure.Unit; import javax.measure.quantity.Temperature; -import org.openhab.binding.nest.internal.NestUtils; +import org.openhab.binding.nest.internal.wwn.WWNUtils; import org.openhab.core.library.unit.ImperialUnits; import org.openhab.core.library.unit.SIUnits; @@ -32,7 +32,7 @@ * * @author Wouter Born - Increase test coverage */ -public final class NestDataUtil { +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"; @@ -61,20 +61,20 @@ public final class NestDataUtil { public static final String THERMOSTAT1_DEVICE_ID = "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV"; public static final String THERMOSTAT1_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw"; - private NestDataUtil() { + private WWNDataUtil() { // Hidden utility class constructor } public static Reader openDataReader(String fileName) throws UnsupportedEncodingException { - String packagePath = (NestDataUtil.class.getPackage().getName()).replaceAll("\\.", "/"); + String packagePath = (WWNDataUtil.class.getPackage().getName()).replaceAll("\\.", "/"); String filePath = "/" + packagePath + "/" + fileName; - InputStream inputStream = NestDataUtil.class.getClassLoader().getResourceAsStream(filePath); + InputStream inputStream = WWNDataUtil.class.getClassLoader().getResourceAsStream(filePath); return new InputStreamReader(inputStream, "UTF-8"); } public static T fromJson(String fileName, Class dataClass) throws IOException { try (Reader reader = openDataReader(fileName)) { - return NestUtils.fromJson(reader, dataClass); + return WWNUtils.fromJson(reader, dataClass); } } diff --git a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/data/GsonParsingTest.java b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNGsonParsingTest.java similarity index 80% rename from itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/data/GsonParsingTest.java rename to itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNGsonParsingTest.java index edcdcaa801740..facc02e748715 100644 --- a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/data/GsonParsingTest.java +++ b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/data/WWNGsonParsingTest.java @@ -10,10 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.internal.data; +package org.openhab.binding.nest.internal.wwn.data; import static org.junit.jupiter.api.Assertions.*; -import static org.openhab.binding.nest.internal.data.NestDataUtil.*; +import static org.openhab.binding.nest.internal.wwn.data.WWNDataUtil.*; import java.io.IOException; import java.text.SimpleDateFormat; @@ -30,9 +30,9 @@ * @author David Bennett - Initial contribution * @author Wouter Born - Increase test coverage */ -public class GsonParsingTest { +public class WWNGsonParsingTest { - private final Logger logger = LoggerFactory.getLogger(GsonParsingTest.class); + 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'"); @@ -41,7 +41,7 @@ private static void assertEqualDateTime(String expected, Date actual) { @Test public void verifyCompleteInput() throws IOException { - TopLevelData topLevel = fromJson("top-level-data.json", TopLevelData.class); + WWNTopLevelData topLevel = fromJson("top-level-data.json", WWNTopLevelData.class); assertEquals(topLevel.getDevices().getThermostats().size(), 1); assertNotNull(topLevel.getDevices().getThermostats().get(THERMOSTAT1_DEVICE_ID)); @@ -57,12 +57,12 @@ public void verifyCompleteInput() throws IOException { @Test public void verifyCompleteStreamingInput() throws IOException { - TopLevelStreamingData topLevelStreamingData = fromJson("top-level-streaming-data.json", - TopLevelStreamingData.class); + WWNTopLevelStreamingData topLevelStreamingData = fromJson("top-level-streaming-data.json", + WWNTopLevelStreamingData.class); assertEquals("/", topLevelStreamingData.getPath()); - TopLevelData data = topLevelStreamingData.getData(); + WWNTopLevelData data = topLevelStreamingData.getData(); assertEquals(data.getDevices().getThermostats().size(), 1); assertNotNull(data.getDevices().getThermostats().get(THERMOSTAT1_DEVICE_ID)); assertEquals(data.getDevices().getCameras().size(), 2); @@ -77,7 +77,7 @@ public void verifyCompleteStreamingInput() throws IOException { @Test public void verifyThermostat() throws IOException { - Thermostat thermostat = fromJson("thermostat-data.json", Thermostat.class); + WWNThermostat thermostat = fromJson("thermostat-data.json", WWNThermostat.class); logger.debug("Thermostat: {}", thermostat); assertTrue(thermostat.isOnline()); @@ -97,12 +97,12 @@ public void verifyThermostat() throws IOException { assertEquals(Double.valueOf(12.5), thermostat.getEcoTemperatureLow()); assertEquals(Double.valueOf(22.0), thermostat.getLockedTempMax()); assertEquals(Double.valueOf(20.0), thermostat.getLockedTempMin()); - assertEquals(Thermostat.Mode.HEAT, thermostat.getMode()); + 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(Thermostat.State.OFF, thermostat.getHvacState()); + 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()); @@ -115,22 +115,22 @@ public void verifyThermostat() throws IOException { @Test public void thermostatTimeToTargetSupportedValueParsing() { - assertEquals((Integer) 0, Thermostat.parseTimeToTarget("~0")); - assertEquals((Integer) 5, Thermostat.parseTimeToTarget("<5")); - assertEquals((Integer) 10, Thermostat.parseTimeToTarget("<10")); - assertEquals((Integer) 15, Thermostat.parseTimeToTarget("~15")); - assertEquals((Integer) 90, Thermostat.parseTimeToTarget("~90")); - assertEquals((Integer) 120, Thermostat.parseTimeToTarget(">120")); + 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, () -> Thermostat.parseTimeToTarget("#5")); + assertThrows(NumberFormatException.class, () -> WWNThermostat.parseTimeToTarget("#5")); } @Test public void verifyCamera() throws IOException { - Camera camera = fromJson("camera-data.json", Camera.class); + WWNCamera camera = fromJson("camera-data.json", WWNCamera.class); logger.debug("Camera: {}", camera); assertTrue(camera.isOnline()); @@ -166,7 +166,7 @@ public void verifyCamera() throws IOException { @Test public void verifySmokeDetector() throws IOException { - SmokeDetector smokeDetector = fromJson("smoke-detector-data.json", SmokeDetector.class); + WWNSmokeDetector smokeDetector = fromJson("smoke-detector-data.json", WWNSmokeDetector.class); logger.debug("SmokeDetector: {}", smokeDetector); assertTrue(smokeDetector.isOnline()); @@ -175,17 +175,17 @@ public void verifySmokeDetector() throws IOException { assertEquals("Downstairs", smokeDetector.getName()); assertEquals("Downstairs Nest Protect", smokeDetector.getNameLong()); assertEqualDateTime("2017-02-02T20:53:05.338Z", smokeDetector.getLastConnection()); - assertEquals(SmokeDetector.BatteryHealth.OK, smokeDetector.getBatteryHealth()); - assertEquals(SmokeDetector.AlarmState.OK, smokeDetector.getCoAlarmState()); - assertEquals(SmokeDetector.AlarmState.OK, smokeDetector.getSmokeAlarmState()); + 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(SmokeDetector.UiColorState.GREEN, smokeDetector.getUiColorState()); + assertEquals(WWNSmokeDetector.UiColorState.GREEN, smokeDetector.getUiColorState()); } @Test public void verifyAccessToken() throws IOException { - AccessTokenData accessToken = fromJson("access-token-data.json", AccessTokenData.class); + WWNAccessTokenData accessToken = fromJson("access-token-data.json", WWNAccessTokenData.class); logger.debug("AccessTokenData: {}", accessToken); assertEquals("access_token", accessToken.getAccessToken()); @@ -194,13 +194,13 @@ public void verifyAccessToken() throws IOException { @Test public void verifyStructure() throws IOException { - Structure structure = fromJson("structure-data.json", Structure.class); + 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(Structure.HomeAwayState.HOME, structure.getAway()); + assertEquals(WWNStructure.HomeAwayState.HOME, structure.getAway()); assertEqualDateTime("2017-02-02T03:10:08.000Z", structure.getEtaBegin()); assertNull(structure.getEta()); assertNull(structure.getPeakPeriodEndTime()); @@ -212,7 +212,7 @@ public void verifyStructure() throws IOException { @Test public void verifyError() throws IOException { - ErrorData error = fromJson("error-data.json", ErrorData.class); + WWNErrorData error = fromJson("error-data.json", WWNErrorData.class); logger.debug("ErrorData: {}", error); assertEquals("blocked", error.getError()); diff --git a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestBridgeHandlerTest.java b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNAccountHandlerTest.java similarity index 81% rename from itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestBridgeHandlerTest.java rename to itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNAccountHandlerTest.java index dbcc2749a6416..fe9713b5e3547 100644 --- a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestBridgeHandlerTest.java +++ b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNAccountHandlerTest.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.handler; +package org.openhab.binding.nest.internal.wwn.handler; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; @@ -25,10 +25,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.openhab.binding.nest.internal.config.NestBridgeConfiguration; -import org.openhab.binding.nest.internal.handler.NestBridgeHandler; -import org.openhab.binding.nest.internal.handler.NestRedirectUrlSupplier; -import org.openhab.binding.nest.test.NestTestBridgeHandler; +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; @@ -38,12 +36,12 @@ import org.osgi.service.jaxrs.client.SseEventSourceFactory; /** - * Tests cases for {@link NestBridgeHandler}. + * Tests cases for {@link WWNAccountHandler}. * * @author David Bennett - Initial contribution */ @ExtendWith(MockitoExtension.class) -public class NestBridgeHandlerTest { +public class WWNAccountHandlerTest { private ThingHandler handler; @@ -52,11 +50,11 @@ public class NestBridgeHandlerTest { private @Mock ClientBuilder clientBuilder; private @Mock Configuration configuration; private @Mock SseEventSourceFactory eventSourceFactory; - private @Mock NestRedirectUrlSupplier redirectUrlSupplier; + private @Mock WWNRedirectUrlSupplier redirectUrlSupplier; @BeforeEach public void beforeEach() { - handler = new NestTestBridgeHandler(bridge, clientBuilder, eventSourceFactory, "http://localhost"); + handler = new WWNTestAccountHandler(bridge, clientBuilder, eventSourceFactory, "http://localhost"); handler.setCallback(callback); } @@ -64,8 +62,8 @@ public void beforeEach() { @Test public void initializeShouldCallTheCallback() { when(bridge.getConfiguration()).thenReturn(configuration); - NestBridgeConfiguration bridgeConfig = new NestBridgeConfiguration(); - when(configuration.as(eq(NestBridgeConfiguration.class))).thenReturn(bridgeConfig); + 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 diff --git a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestCameraHandlerTest.java b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNCameraHandlerTest.java similarity index 91% rename from itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestCameraHandlerTest.java rename to itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNCameraHandlerTest.java index 3410017c4f9f8..01f77c488241f 100644 --- a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestCameraHandlerTest.java +++ b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNCameraHandlerTest.java @@ -10,12 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.handler; +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.NestBindingConstants.*; -import static org.openhab.binding.nest.internal.data.NestDataUtil.*; +import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*; +import static org.openhab.binding.nest.internal.wwn.data.WWNDataUtil.*; import static org.openhab.core.library.types.OnOffType.*; import java.io.IOException; @@ -23,8 +23,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; -import org.openhab.binding.nest.internal.config.NestDeviceConfiguration; -import org.openhab.binding.nest.internal.handler.NestCameraHandler; +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; @@ -35,23 +34,23 @@ import org.openhab.core.thing.binding.builder.ThingBuilder; /** - * Tests for {@link NestCameraHandler}. + * Tests for {@link WWNCameraHandler}. * * @author Wouter Born - Increase test coverage */ -public class NestCameraHandlerTest extends NestThingHandlerOSGiTest { +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 NestCameraHandlerTest() { - super(NestCameraHandler.class); + public WWNCameraHandlerTest() { + super(WWNCameraHandler.class); } @Override protected Thing buildThing(Bridge bridge) { Map properties = new HashMap<>(); - properties.put(NestDeviceConfiguration.DEVICE_ID, CAMERA1_DEVICE_ID); + properties.put(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)) diff --git a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestSmokeDetectorHandlerTest.java b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNSmokeDetectorHandlerTest.java similarity index 87% rename from itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestSmokeDetectorHandlerTest.java rename to itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNSmokeDetectorHandlerTest.java index 644635f4692b0..a4d7c9d77f3ff 100644 --- a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestSmokeDetectorHandlerTest.java +++ b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNSmokeDetectorHandlerTest.java @@ -10,12 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.handler; +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.NestBindingConstants.*; -import static org.openhab.binding.nest.internal.data.NestDataUtil.*; +import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*; +import static org.openhab.binding.nest.internal.wwn.data.WWNDataUtil.*; import static org.openhab.core.library.types.OnOffType.OFF; import java.io.IOException; @@ -23,8 +23,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; -import org.openhab.binding.nest.internal.config.NestDeviceConfiguration; -import org.openhab.binding.nest.internal.handler.NestSmokeDetectorHandler; +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; @@ -35,23 +34,23 @@ import org.openhab.core.thing.binding.builder.ThingBuilder; /** - * Tests for {@link NestSmokeDetectorHandler}. + * Tests for {@link WWNSmokeDetectorHandler}. * * @author Wouter Born - Increase test coverage */ -public class NestSmokeDetectorHandlerTest extends NestThingHandlerOSGiTest { +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 NestSmokeDetectorHandlerTest() { - super(NestSmokeDetectorHandler.class); + public WWNSmokeDetectorHandlerTest() { + super(WWNSmokeDetectorHandler.class); } @Override protected Thing buildThing(Bridge bridge) { Map properties = new HashMap<>(); - properties.put(NestDeviceConfiguration.DEVICE_ID, SMOKE1_DEVICE_ID); + properties.put(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)) diff --git a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestStructureHandlerTest.java b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNStructureHandlerTest.java similarity index 89% rename from itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestStructureHandlerTest.java rename to itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNStructureHandlerTest.java index 66c074d6f0458..ca598cad4d384 100644 --- a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestStructureHandlerTest.java +++ b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNStructureHandlerTest.java @@ -10,12 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.handler; +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.NestBindingConstants.*; -import static org.openhab.binding.nest.internal.data.NestDataUtil.*; +import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*; +import static org.openhab.binding.nest.internal.wwn.data.WWNDataUtil.*; import static org.openhab.core.library.types.OnOffType.OFF; import java.io.IOException; @@ -23,8 +23,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; -import org.openhab.binding.nest.internal.config.NestStructureConfiguration; -import org.openhab.binding.nest.internal.handler.NestStructureHandler; +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; @@ -35,23 +34,23 @@ import org.openhab.core.thing.binding.builder.ThingBuilder; /** - * Tests for {@link NestStructureHandler}. + * Tests for {@link WWNStructureHandler}. * * @author Wouter Born - Increase test coverage */ -public class NestStructureHandlerTest extends NestThingHandlerOSGiTest { +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 NestStructureHandlerTest() { - super(NestStructureHandler.class); + public WWNStructureHandlerTest() { + super(WWNStructureHandler.class); } @Override protected Thing buildThing(Bridge bridge) { Map properties = new HashMap<>(); - properties.put(NestStructureConfiguration.STRUCTURE_ID, STRUCTURE1_STRUCTURE_ID); + properties.put(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)) diff --git a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestThermostatHandlerTest.java b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNThermostatHandlerTest.java similarity index 95% rename from itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestThermostatHandlerTest.java rename to itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNThermostatHandlerTest.java index 6e5099101492d..e1d3563686682 100644 --- a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestThermostatHandlerTest.java +++ b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNThermostatHandlerTest.java @@ -10,12 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.handler; +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.NestBindingConstants.*; -import static org.openhab.binding.nest.internal.data.NestDataUtil.*; +import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*; +import static org.openhab.binding.nest.internal.wwn.data.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; @@ -25,8 +25,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; -import org.openhab.binding.nest.internal.config.NestDeviceConfiguration; -import org.openhab.binding.nest.internal.handler.NestThermostatHandler; +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; @@ -39,23 +38,23 @@ import org.openhab.core.thing.binding.builder.ThingBuilder; /** - * Tests for {@link NestThermostatHandler}. + * Tests for {@link WWNThermostatHandler}. * * @author Wouter Born - Increase test coverage */ -public class NestThermostatHandlerTest extends NestThingHandlerOSGiTest { +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 NestThermostatHandlerTest() { - super(NestThermostatHandler.class); + public WWNThermostatHandlerTest() { + super(WWNThermostatHandler.class); } @Override protected Thing buildThing(Bridge bridge) { Map properties = new HashMap<>(); - properties.put(NestDeviceConfiguration.DEVICE_ID, THERMOSTAT1_DEVICE_ID); + properties.put(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)) diff --git a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestThingHandlerOSGiTest.java b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNThingHandlerOSGiTest.java similarity index 88% rename from itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestThingHandlerOSGiTest.java rename to itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNThingHandlerOSGiTest.java index 5ac87e1bbfa24..8ca48db845b3d 100644 --- a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestThingHandlerOSGiTest.java +++ b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/handler/WWNThingHandlerOSGiTest.java @@ -10,14 +10,14 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.handler; +package org.openhab.binding.nest.internal.wwn.handler; 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.Mockito.*; -import static org.openhab.binding.nest.internal.rest.NestStreamingRestClient.PUT; +import static org.openhab.binding.nest.internal.wwn.rest.WWNStreamingRestClient.PUT; import java.io.IOException; import java.time.Instant; @@ -36,12 +36,11 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.openhab.binding.nest.internal.config.NestBridgeConfiguration; -import org.openhab.binding.nest.internal.handler.NestBaseHandler; -import org.openhab.binding.nest.test.NestTestApiServlet; -import org.openhab.binding.nest.test.NestTestBridgeHandler; -import org.openhab.binding.nest.test.NestTestHandlerFactory; -import org.openhab.binding.nest.test.NestTestServer; +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; @@ -84,21 +83,21 @@ import org.slf4j.LoggerFactory; /** - * {@link NestThingHandlerOSGiTest} is an abstract base class for Nest OSGi based tests. + * {@link WWNThingHandlerOSGiTest} is an abstract base class for Nest OSGi based tests. * * @author Wouter Born - Increase test coverage */ -public abstract class NestThingHandlerOSGiTest extends JavaOSGiTest { +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(NestThingHandlerOSGiTest.class); + private final Logger logger = LoggerFactory.getLogger(WWNThingHandlerOSGiTest.class); - private static NestTestServer server; - private static NestTestApiServlet servlet = new NestTestApiServlet(); + private static WWNTestServer server; + private static WWNTestApiServlet servlet = new WWNTestApiServlet(); private ChannelTypeRegistry channelTypeRegistry; private ChannelGroupTypeRegistry channelGroupTypeRegistry; @@ -111,23 +110,23 @@ public abstract class NestThingHandlerOSGiTest extends JavaOSGiTest { private VolatileStorageService volatileStorageService = new VolatileStorageService(); protected Bridge bridge; - protected NestTestBridgeHandler bridgeHandler; + protected WWNTestAccountHandler bridgeHandler; protected Thing thing; - protected NestBaseHandler thingHandler; - private Class> thingClass; + protected WWNBaseHandler thingHandler; + private Class> thingClass; - private NestTestHandlerFactory nestTestHandlerFactory; + private WWNTestHandlerFactory nestTestHandlerFactory; private @NonNullByDefault({}) ClientBuilder clientBuilder; private @NonNullByDefault({}) SseEventSourceFactory eventSourceFactory; - public NestThingHandlerOSGiTest(Class> thingClass) { + public WWNThingHandlerOSGiTest(Class> thingClass) { this.thingClass = thingClass; } @BeforeAll public static void setUpClass() throws Exception { ServletHolder holder = new ServletHolder(servlet); - server = new NestTestServer(SERVER_HOST, SERVER_PORT, SERVER_TIMEOUT, holder); + server = new WWNTestServer(SERVER_HOST, SERVER_PORT, SERVER_TIMEOUT, holder); server.startServer(); } @@ -168,18 +167,18 @@ public void setUp() throws ItemNotFoundException { ComponentContext componentContext = mock(ComponentContext.class); when(componentContext.getBundleContext()).thenReturn(bundleContext); - nestTestHandlerFactory = new NestTestHandlerFactory(clientBuilder, eventSourceFactory); + nestTestHandlerFactory = new WWNTestHandlerFactory(clientBuilder, eventSourceFactory); nestTestHandlerFactory.activate(componentContext, - Map.of(NestTestHandlerFactory.REDIRECT_URL_CONFIG_PROPERTY, REDIRECT_URL)); + Map.of(WWNTestHandlerFactory.REDIRECT_URL_CONFIG_PROPERTY, REDIRECT_URL)); registerService(nestTestHandlerFactory); - nestTestHandlerFactory = getService(ThingHandlerFactory.class, NestTestHandlerFactory.class); + nestTestHandlerFactory = getService(ThingHandlerFactory.class, WWNTestHandlerFactory.class); assertThat("Could not get NestTestHandlerFactory", nestTestHandlerFactory, is(notNullValue())); bridge = buildBridge(); thing = buildThing(bridge); - bridgeHandler = addThing(bridge, NestTestBridgeHandler.class); + bridgeHandler = addThing(bridge, WWNTestAccountHandler.class); thingHandler = addThing(thing, thingClass); createAndLinkItems(); @@ -203,13 +202,13 @@ public void tearDown() { protected Bridge buildBridge() { Map properties = new HashMap<>(); - properties.put(NestBridgeConfiguration.ACCESS_TOKEN, + properties.put(WWNAccountConfiguration.ACCESS_TOKEN, "c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc"); - properties.put(NestBridgeConfiguration.PINCODE, "64P2XRYT"); - properties.put(NestBridgeConfiguration.PRODUCT_ID, "8fdf9885-ca07-4252-1aa3-f3d5ca9589e0"); - properties.put(NestBridgeConfiguration.PRODUCT_SECRET, "QITLR3iyUlWaj9dbvCxsCKp4f"); + properties.put(WWNAccountConfiguration.PINCODE, "64P2XRYT"); + properties.put(WWNAccountConfiguration.PRODUCT_ID, "8fdf9885-ca07-4252-1aa3-f3d5ca9589e0"); + properties.put(WWNAccountConfiguration.PRODUCT_SECRET, "QITLR3iyUlWaj9dbvCxsCKp4f"); - return BridgeBuilder.create(NestTestBridgeHandler.THING_TYPE_TEST_BRIDGE, "test_account") + return BridgeBuilder.create(WWNTestAccountHandler.THING_TYPE_TEST_BRIDGE, "test_account") .withLabel("Test Account").withConfiguration(new Configuration(properties)).build(); } diff --git a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestBridgeHandler.java b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/test/WWNTestAccountHandler.java similarity index 65% rename from itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestBridgeHandler.java rename to itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/test/WWNTestAccountHandler.java index bda2051ff79de..028b8ac3040df 100644 --- a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestBridgeHandler.java +++ b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/test/WWNTestAccountHandler.java @@ -10,32 +10,31 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.test; +package org.openhab.binding.nest.internal.wwn.test; -import static org.openhab.binding.nest.internal.NestBindingConstants.BINDING_ID; +import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.BINDING_ID; -import java.util.Collections; import java.util.Properties; import java.util.Set; import javax.ws.rs.client.ClientBuilder; -import org.openhab.binding.nest.internal.exceptions.InvalidAccessTokenException; -import org.openhab.binding.nest.internal.handler.NestBridgeHandler; -import org.openhab.binding.nest.internal.handler.NestRedirectUrlSupplier; +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 NestTestBridgeHandler} is a {@link NestBridgeHandler} modified for testing. Using the + * 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 - Increase test coverage */ -public class NestTestBridgeHandler extends NestBridgeHandler { +public class WWNTestAccountHandler extends WWNAccountHandler { - class NestTestRedirectUrlSupplier extends NestRedirectUrlSupplier { + class NestTestRedirectUrlSupplier extends WWNRedirectUrlSupplier { NestTestRedirectUrlSupplier(Properties httpHeaders) { super(httpHeaders); @@ -48,19 +47,19 @@ public void resetCache() { } } - public static final ThingTypeUID THING_TYPE_TEST_BRIDGE = new ThingTypeUID(BINDING_ID, "test_account"); - public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_TEST_BRIDGE); + public static final ThingTypeUID THING_TYPE_TEST_BRIDGE = new ThingTypeUID(BINDING_ID, "wwn_test_account"); + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_TEST_BRIDGE); private String redirectUrl; - public NestTestBridgeHandler(Bridge bridge, ClientBuilder clientBuilder, SseEventSourceFactory eventSourceFactory, + public WWNTestAccountHandler(Bridge bridge, ClientBuilder clientBuilder, SseEventSourceFactory eventSourceFactory, String redirectUrl) { super(bridge, clientBuilder, eventSourceFactory); this.redirectUrl = redirectUrl; } @Override - protected NestRedirectUrlSupplier createRedirectUrlSupplier() throws InvalidAccessTokenException { + protected WWNRedirectUrlSupplier createRedirectUrlSupplier() throws InvalidWWNAccessTokenException { return new NestTestRedirectUrlSupplier(getHttpHeaders()); } } diff --git a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestApiServlet.java b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/test/WWNTestApiServlet.java similarity index 94% rename from itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestApiServlet.java rename to itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/test/WWNTestApiServlet.java index 1f207a1789a8a..cf2f022bf4875 100644 --- a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestApiServlet.java +++ b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/test/WWNTestApiServlet.java @@ -10,10 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.test; +package org.openhab.binding.nest.internal.wwn.test; -import static org.openhab.binding.nest.internal.NestBindingConstants.*; -import static org.openhab.binding.nest.internal.rest.NestStreamingRestClient.*; +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; @@ -39,11 +39,11 @@ import com.google.gson.reflect.TypeToken; /** - * The {@link NestTestApiServlet} mocks the Nest API during tests. + * The {@link WWNTestApiServlet} mocks the Nest API during tests. * * @author Wouter Born - Increase test coverage */ -public class NestTestApiServlet extends HttpServlet { +public class WWNTestApiServlet extends HttpServlet { private static final long serialVersionUID = -5414910055159062745L; @@ -52,7 +52,7 @@ public class NestTestApiServlet extends HttpServlet { 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(NestTestApiServlet.class); + private final Logger logger = LoggerFactory.getLogger(WWNTestApiServlet.class); private class SseEvent { private String name; diff --git a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestHandlerFactory.java b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/test/WWNTestHandlerFactory.java similarity index 80% rename from itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestHandlerFactory.java rename to itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/test/WWNTestHandlerFactory.java index bafe947b4b27a..20ab22e1bc0b7 100644 --- a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestHandlerFactory.java +++ b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/test/WWNTestHandlerFactory.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.test; +package org.openhab.binding.nest.internal.wwn.test; import java.util.HashMap; import java.util.Hashtable; @@ -20,8 +20,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.nest.internal.discovery.NestDiscoveryService; -import org.openhab.binding.nest.internal.handler.NestBridgeHandler; +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; @@ -38,12 +38,12 @@ import org.osgi.service.jaxrs.client.SseEventSourceFactory; /** - * The {@link NestTestHandlerFactory} is responsible for creating test things and thing handlers. + * The {@link WWNTestHandlerFactory} is responsible for creating test things and thing handlers. * * @author Wouter Born - Increase test coverage */ @NonNullByDefault -public class NestTestHandlerFactory extends BaseThingHandlerFactory implements ThingHandlerFactory { +public class WWNTestHandlerFactory extends BaseThingHandlerFactory implements ThingHandlerFactory { public static final String REDIRECT_URL_CONFIG_PROPERTY = "redirect.url"; @@ -54,7 +54,7 @@ public class NestTestHandlerFactory extends BaseThingHandlerFactory implements T private String redirectUrl = "http://localhost"; @Activate - public NestTestHandlerFactory(@Reference ClientBuilder clientBuilder, + public WWNTestHandlerFactory(@Reference ClientBuilder clientBuilder, @Reference SseEventSourceFactory eventSourceFactory) { this.clientBuilder = clientBuilder; this.eventSourceFactory = eventSourceFactory; @@ -62,7 +62,7 @@ public NestTestHandlerFactory(@Reference ClientBuilder clientBuilder, @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return NestTestBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID); + return WWNTestAccountHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID); } @Activate @@ -82,10 +82,11 @@ public void modified(Map config) { @Override protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (thingTypeUID.equals(NestTestBridgeHandler.THING_TYPE_TEST_BRIDGE)) { - NestTestBridgeHandler handler = new NestTestBridgeHandler((Bridge) thing, clientBuilder, eventSourceFactory, + if (thingTypeUID.equals(WWNTestAccountHandler.THING_TYPE_TEST_BRIDGE)) { + WWNTestAccountHandler handler = new WWNTestAccountHandler((Bridge) thing, clientBuilder, eventSourceFactory, redirectUrl); - NestDiscoveryService service = new NestDiscoveryService(handler); + WWNDiscoveryService service = new WWNDiscoveryService(); + service.setThingHandler(handler); // Register the discovery service. discoveryService.put(handler.getThing().getUID(), bundleContext.registerService(DiscoveryService.class.getName(), service, new Hashtable<>())); @@ -101,11 +102,11 @@ public void modified(Map config) { */ @Override protected void removeHandler(ThingHandler thingHandler) { - if (thingHandler instanceof NestBridgeHandler) { + if (thingHandler instanceof WWNAccountHandler) { ServiceRegistration registration = discoveryService.get(thingHandler.getThing().getUID()); if (registration != null) { // Unregister the discovery service. - NestDiscoveryService service = (NestDiscoveryService) bundleContext + WWNDiscoveryService service = (WWNDiscoveryService) bundleContext .getService(registration.getReference()); service.deactivate(); registration.unregister(); diff --git a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestServer.java b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/test/WWNTestServer.java similarity index 90% rename from itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestServer.java rename to itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/test/WWNTestServer.java index a531f5f734e31..30a8874eac9b8 100644 --- a/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestServer.java +++ b/itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/wwn/test/WWNTestServer.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.nest.test; +package org.openhab.binding.nest.internal.wwn.test; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -27,8 +27,8 @@ * @author Velin Yordanov - initial contribution * @author Wouter Born - Increase test coverage */ -public class NestTestServer { - private final Logger logger = LoggerFactory.getLogger(NestTestServer.class); +public class WWNTestServer { + private final Logger logger = LoggerFactory.getLogger(WWNTestServer.class); private Server server; private String host; @@ -36,7 +36,7 @@ public class NestTestServer { private int timeout; private ServletHolder servletHolder; - public NestTestServer(String host, int port, int timeout, ServletHolder servletHolder) { + public WWNTestServer(String host, int port, int timeout, ServletHolder servletHolder) { this.host = host; this.port = port; this.timeout = timeout; diff --git a/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/access-token-data.json b/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/access-token-data.json similarity index 100% rename from itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/access-token-data.json rename to itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/access-token-data.json diff --git a/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/camera-data.json b/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/camera-data.json similarity index 100% rename from itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/camera-data.json rename to itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/camera-data.json diff --git a/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/error-data.json b/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/error-data.json similarity index 100% rename from itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/error-data.json rename to itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/error-data.json diff --git a/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/smoke-detector-data.json b/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/smoke-detector-data.json similarity index 100% rename from itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/smoke-detector-data.json rename to itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/smoke-detector-data.json diff --git a/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/structure-data.json b/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/structure-data.json similarity index 100% rename from itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/structure-data.json rename to itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/structure-data.json diff --git a/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/thermostat-data.json b/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/thermostat-data.json similarity index 100% rename from itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/thermostat-data.json rename to itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/thermostat-data.json diff --git a/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/top-level-data.json b/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/top-level-data.json similarity index 100% rename from itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/top-level-data.json rename to itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/top-level-data.json diff --git a/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/top-level-streaming-data-empty.json b/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/top-level-streaming-data-empty.json similarity index 100% rename from itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/top-level-streaming-data-empty.json rename to itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/top-level-streaming-data-empty.json diff --git a/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/top-level-streaming-data-incomplete.json b/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/top-level-streaming-data-incomplete.json similarity index 100% rename from itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/top-level-streaming-data-incomplete.json rename to itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/top-level-streaming-data-incomplete.json diff --git a/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/top-level-streaming-data.json b/itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/top-level-streaming-data.json similarity index 100% rename from itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/top-level-streaming-data.json rename to itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/wwn/data/top-level-streaming-data.json