From bbf1a7fd29f8a48560be889ce264878a87fc9e73 Mon Sep 17 00:00:00 2001 From: Kai Kreuzer Date: Sat, 20 Feb 2010 19:23:32 +0100 Subject: [PATCH] Codebase as of https://github.com/openhab/openhab2-addons/tree/c53e4aed2627ec899c083170430399f8925e3345 as an initial commit for the shrunk repo Signed-off-by: Kai Kreuzer --- .github/ISSUE_TEMPLATE/bug_report.md | 47 + .github/ISSUE_TEMPLATE/documentation_issue.md | 17 + .github/ISSUE_TEMPLATE/feature_request.md | 19 + .github/ISSUE_TEMPLATE/usage_question.md | 10 + .github/PULL_REQUEST_TEMPLATE.md | 62 ++ .gitignore | 23 + .travis.yml | 23 + CODEOWNERS | 247 ++++++ CONTRIBUTING.md | 174 ++++ LICENSE | 277 ++++++ README.md | 85 ++ bom/openhab-addons/.project | 17 + bom/openhab-addons/pom.xml | 66 ++ bom/openhab-core-index/.classpath | 27 + bom/openhab-core-index/.project | 23 + bom/openhab-core-index/pom.xml | 41 + bom/pom.xml | 91 ++ bom/runtime-index/.classpath | 27 + bom/runtime-index/.project | 23 + bom/runtime-index/pom.xml | 41 + bom/test-index/.classpath | 27 + bom/test-index/.project | 23 + bom/test-index/pom.xml | 49 ++ buildci.sh | 84 ++ bundles/archetype-settings.xml | 20 + bundles/create_openhab_binding_skeleton.cmd | 40 + bundles/create_openhab_binding_skeleton.sh | 31 + bundles/org.openhab.binding.nest/.classpath | 32 + bundles/org.openhab.binding.nest/.project | 23 + bundles/org.openhab.binding.nest/NOTICE | 13 + bundles/org.openhab.binding.nest/README.md | 260 ++++++ bundles/org.openhab.binding.nest/pom.xml | 17 + .../src/main/feature/feature.xml | 9 + .../nest/internal/NestBindingConstants.java | 148 ++++ .../nest/internal/NestHandlerFactory.java | 135 +++ .../binding/nest/internal/NestUtils.java | 49 ++ .../config/NestBridgeConfiguration.java | 40 + .../config/NestDeviceConfiguration.java | 28 + .../config/NestStructureConfiguration.java | 28 + .../nest/internal/data/AccessTokenData.java | 79 ++ .../nest/internal/data/ActivityZone.java | 74 ++ .../nest/internal/data/BaseNestDevice.java | 167 ++++ .../binding/nest/internal/data/Camera.java | 209 +++++ .../nest/internal/data/CameraEvent.java | 203 +++++ .../binding/nest/internal/data/ETA.java | 108 +++ .../binding/nest/internal/data/ErrorData.java | 106 +++ .../nest/internal/data/NestDevices.java | 96 +++ .../nest/internal/data/NestIdentifiable.java | 27 + .../nest/internal/data/NestMetadata.java | 79 ++ .../nest/internal/data/SmokeDetector.java | 153 ++++ .../binding/nest/internal/data/Structure.java | 311 +++++++ .../nest/internal/data/Thermostat.java | 572 +++++++++++++ .../nest/internal/data/TopLevelData.java | 94 ++ .../internal/data/TopLevelStreamingData.java | 79 ++ .../binding/nest/internal/data/Where.java | 76 ++ .../discovery/NestDiscoveryService.java | 171 ++++ .../FailedResolvingNestUrlException.java | 34 + .../FailedRetrievingNestDataException.java | 35 + .../FailedSendingNestDataException.java | 34 + .../InvalidAccessTokenException.java | 34 + .../internal/handler/NestBaseHandler.java | 204 +++++ .../internal/handler/NestBridgeHandler.java | 383 +++++++++ .../internal/handler/NestCameraHandler.java | 153 ++++ .../handler/NestRedirectUrlSupplier.java | 108 +++ .../handler/NestSmokeDetectorHandler.java | 95 +++ .../handler/NestStructureHandler.java | 128 +++ .../handler/NestThermostatHandler.java | 219 +++++ .../listener/NestStreamingDataListener.java | 52 ++ .../listener/NestThingDataListener.java | 46 + .../nest/internal/rest/NestAuthorizer.java | 89 ++ .../rest/NestStreamingRequestFilter.java | 47 + .../rest/NestStreamingRestClient.java | 232 +++++ .../nest/internal/rest/NestUpdateRequest.java | 64 ++ .../update/NestCompositeUpdateHandler.java | 129 +++ .../internal/update/NestUpdateHandler.java | 114 +++ .../main/resources/OH-INF/binding/binding.xml | 9 + .../main/resources/OH-INF/config/config.xml | 56 ++ .../main/resources/OH-INF/thing/bridge.xml | 12 + .../main/resources/OH-INF/thing/camera.xml | 31 + .../main/resources/OH-INF/thing/channels.xml | 520 ++++++++++++ .../resources/OH-INF/thing/smoke-detector.xml | 33 + .../main/resources/OH-INF/thing/structure.xml | 40 + .../resources/OH-INF/thing/thermostat.xml | 51 ++ .../.classpath | 32 + .../org.openhab.persistence.dynamodb/.project | 23 + .../org.openhab.persistence.dynamodb/NOTICE | 13 + .../README.md | 174 ++++ .../org.openhab.persistence.dynamodb/pom.xml | 115 +++ .../scripts/fetch_sdk.sh | 5 + .../scripts/fetch_sdk_pom.xml | 37 + .../src/main/feature/feature.xml | 11 + .../AbstractBufferedPersistenceService.java | 129 +++ .../internal/AbstractDynamoDBItem.java | 216 +++++ .../internal/DynamoDBBigDecimalItem.java | 95 +++ .../dynamodb/internal/DynamoDBClient.java | 66 ++ .../dynamodb/internal/DynamoDBConfig.java | 195 +++++ .../internal/DynamoDBHistoricItem.java | 58 ++ .../dynamodb/internal/DynamoDBItem.java | 58 ++ .../internal/DynamoDBItemVisitor.java | 29 + .../internal/DynamoDBPersistenceService.java | 569 +++++++++++++ .../dynamodb/internal/DynamoDBQueryUtils.java | 148 ++++ .../dynamodb/internal/DynamoDBStringItem.java | 75 ++ .../internal/DynamoDBTableNameResolver.java | 65 ++ .../main/resources/OH-INF/config/config.xml | 118 +++ ...actDynamoDBItemGetDynamoItemClassTest.java | 95 +++ ...AbstractDynamoDBItemSerializationTest.java | 261 ++++++ .../AbstractTwoItemIntegrationTest.java | 355 ++++++++ .../internal/BaseIntegrationTest.java | 213 +++++ .../internal/CallItemIntegrationTest.java | 83 ++ .../internal/ColorItemIntegrationTest.java | 88 ++ .../internal/ContactItemIntegrationTest.java | 75 ++ .../internal/DateTimeItemIntegrationTest.java | 80 ++ .../internal/DimmerItemIntegrationTest.java | 76 ++ .../dynamodb/internal/DynamoDBConfigTest.java | 221 +++++ .../DynamoDBTableNameResolverTest.java | 38 + .../internal/LocationItemIntegrationTest.java | 78 ++ .../internal/NumberItemIntegrationTest.java | 91 ++ .../internal/PagingIntegrationTest.java | 165 ++++ .../PlayerItemPlayPauseIntegrationTest.java | 76 ++ ...rItemRewindFastForwardIntegrationTest.java | 77 ++ .../RollershutterItemIntegrationTest.java | 88 ++ .../internal/StringItemIntegrationTest.java | 74 ++ .../internal/SwitchItemIntegrationTest.java | 76 ++ .../.classpath | 32 + .../org.openhab.persistence.influxdb/.project | 23 + .../org.openhab.persistence.influxdb/NOTICE | 13 + .../README.md | 48 ++ .../org.openhab.persistence.influxdb/pom.xml | 138 +++ .../src/main/feature/feature.xml | 16 + .../influxdb/InfluxDBPersistenceService.java | 246 ++++++ .../internal/FilterCriteriaQueryCreator.java | 52 ++ .../internal/InfluxDBConfiguration.java | 190 +++++ .../influxdb/internal/InfluxDBConstants.java | 35 + .../internal/InfluxDBHistoricItem.java | 73 ++ .../internal/InfluxDBPersistentItemInfo.java | 58 ++ .../influxdb/internal/InfluxDBRepository.java | 74 ++ .../internal/InfluxDBStateConvertUtils.java | 186 ++++ .../influxdb/internal/InfluxDBVersion.java | 24 + .../influxdb/internal/InfluxPoint.java | 91 ++ .../influxdb/internal/InfluxRow.java | 47 + .../internal/ItemToStorePointCreator.java | 127 +++ .../influxdb/internal/RepositoryFactory.java | 51 ++ .../UnnexpectedConditionException.java | 33 + ...Influx1FilterCriteriaQueryCreatorImpl.java | 96 +++ .../influx1/InfluxDB1RepositoryImpl.java | 211 +++++ ...Influx2FilterCriteriaQueryCreatorImpl.java | 77 ++ .../influx2/InfluxDB2RepositoryImpl.java | 230 +++++ .../main/resources/OH-INF/config/config.xml | 106 +++ .../internal/ConfigurationTestHelper.java | 52 ++ .../InfluxDBPersistenceServiceTest.java | 107 +++ .../InfluxDBStateConvertUtilsTest.java | 88 ++ ...luxFilterCriteriaQueryCreatorImplTest.java | 172 ++++ .../influxdb/internal/ItemTestHelper.java | 30 + .../internal/ItemToStorePointCreatorTest.java | 135 +++ .../org.openhab.persistence.jdbc/.classpath | 32 + .../org.openhab.persistence.jdbc/.gitignore | 2 + bundles/org.openhab.persistence.jdbc/.project | 23 + bundles/org.openhab.persistence.jdbc/NOTICE | 13 + .../org.openhab.persistence.jdbc/README.md | 174 ++++ bundles/org.openhab.persistence.jdbc/pom.xml | 93 ++ .../src/main/feature/feature.xml | 55 ++ .../persistence/jdbc/db/JdbcBaseDAO.java | 563 ++++++++++++ .../persistence/jdbc/db/JdbcDerbyDAO.java | 243 ++++++ .../persistence/jdbc/db/JdbcH2DAO.java | 95 +++ .../persistence/jdbc/db/JdbcHsqldbDAO.java | 125 +++ .../persistence/jdbc/db/JdbcMariadbDAO.java | 106 +++ .../persistence/jdbc/db/JdbcMysqlDAO.java | 111 +++ .../jdbc/db/JdbcPostgresqlDAO.java | 209 +++++ .../persistence/jdbc/db/JdbcSqliteDAO.java | 114 +++ .../jdbc/internal/JdbcConfiguration.java | 383 +++++++++ .../persistence/jdbc/internal/JdbcMapper.java | 418 +++++++++ .../jdbc/internal/JdbcPersistenceService.java | 229 +++++ .../persistence/jdbc/model/ItemVO.java | 167 ++++ .../persistence/jdbc/model/ItemsVO.java | 154 ++++ .../jdbc/model/JdbcHistoricItem.java | 64 ++ .../jdbc/model/JdbcPersistenceItemInfo.java | 65 ++ .../persistence/jdbc/utils/DbMetaData.java | 126 +++ .../persistence/jdbc/utils/MovingAverage.java | 74 ++ .../jdbc/utils/StringUtilsExt.java | 279 ++++++ .../main/resources/OH-INF/config/config.xml | 209 +++++ .../org.openhab.persistence.jpa/.classpath | 32 + bundles/org.openhab.persistence.jpa/.project | 23 + bundles/org.openhab.persistence.jpa/NOTICE | 13 + bundles/org.openhab.persistence.jpa/README.md | 44 + bundles/org.openhab.persistence.jpa/pom.xml | 70 ++ .../src/main/feature/feature.xml | 11 + .../jpa/internal/JpaConfiguration.java | 83 ++ .../jpa/internal/JpaHistoricItem.java | 137 +++ .../jpa/internal/JpaPersistenceService.java | 322 +++++++ .../persistence/jpa/internal/StateHelper.java | 52 ++ .../jpa/internal/model/JpaPersistentItem.java | 106 +++ .../main/resources/META-INF/persistence.xml | 34 + .../org.openhab.persistence.mapdb/.classpath | 27 + .../org.openhab.persistence.mapdb/.project | 23 + bundles/org.openhab.persistence.mapdb/NOTICE | 13 + bundles/org.openhab.persistence.mapdb/pom.xml | 25 + .../src/main/feature/feature.xml | 10 + .../persistence/mapdb/internal/MapDbItem.java | 90 ++ .../internal/MapDbPersistenceService.java | 192 +++++ .../mapdb/internal/StateTypeAdapter.java | 70 ++ .../mapdb/StateTypeAdapterTest.java | 47 + .../.classpath | 27 + .../org.openhab.persistence.mongodb/.project | 23 + .../org.openhab.persistence.mongodb/NOTICE | 13 + .../org.openhab.persistence.mongodb/README.md | 16 + .../org.openhab.persistence.mongodb/pom.xml | 25 + .../src/main/feature/feature.xml | 10 + .../mongodb/internal/MongoDBItem.java | 59 ++ .../internal/MongoDBPersistenceService.java | 353 ++++++++ .../org.openhab.persistence.rrd4j/.classpath | 27 + .../org.openhab.persistence.rrd4j/.project | 23 + bundles/org.openhab.persistence.rrd4j/NOTICE | 13 + .../org.openhab.persistence.rrd4j/README.md | 233 +++++ bundles/org.openhab.persistence.rrd4j/pom.xml | 29 + .../src/main/feature/feature.xml | 11 + .../persistence/rrd4j/internal/RRD4jItem.java | 58 ++ .../internal/RRD4jPersistenceService.java | 610 +++++++++++++ .../internal/charts/RRD4jChartServlet.java | 286 +++++++ .../org.openhab.voice.googletts/.classpath | 43 + bundles/org.openhab.voice.googletts/.project | 23 + bundles/org.openhab.voice.googletts/NOTICE | 13 + bundles/org.openhab.voice.googletts/README.md | 60 ++ bundles/org.openhab.voice.googletts/pom.xml | 17 + .../src/main/feature/feature.xml | 9 + .../internal/AuthenticationException.java | 34 + .../googletts/internal/GoogleCloudAPI.java | 458 ++++++++++ .../googletts/internal/GoogleTTSConfig.java | 61 ++ .../googletts/internal/GoogleTTSService.java | 337 ++++++++ .../googletts/internal/GoogleTTSVoice.java | 102 +++ .../internal/protocol/AudioConfig.java | 112 +++ .../internal/protocol/AudioEncoding.java | 44 + .../internal/protocol/ListVoicesResponse.java | 40 + .../internal/protocol/SsmlVoiceGender.java | 44 + .../internal/protocol/SynthesisInput.java | 60 ++ .../protocol/SynthesizeSpeechRequest.java | 69 ++ .../protocol/SynthesizeSpeechResponse.java | 38 + .../googletts/internal/protocol/Voice.java | 75 ++ .../protocol/VoiceSelectionParams.java | 79 ++ .../main/resources/OH-INF/config/config.xml | 55 ++ bundles/pom.xml | 265 ++++++ features/openhab-addons-external/.project | 17 + features/openhab-addons-external/pom.xml | 71 ++ .../src/main/resources/conf/dynamodb.cfg | 51 ++ .../src/main/resources/conf/influxdb.cfg | 14 + .../src/main/resources/conf/jdbc.cfg | 81 ++ .../src/main/resources/conf/jpa.cfg | 13 + .../src/main/resources/conf/mapdb.cfg | 5 + .../src/main/resources/conf/rrd4j.cfg | 10 + features/openhab-addons/.project | 17 + features/openhab-addons/pom.xml | 78 ++ .../src/main/resources/footer.xml | 2 + .../src/main/resources/header.xml | 18 + features/pom.xml | 128 +++ features/readme.md | 7 + itests/.project | 17 + itests/itest-common.bndrun | 34 + .../org.openhab.binding.nest.tests/.classpath | 32 + .../org.openhab.binding.nest.tests/.project | 23 + itests/org.openhab.binding.nest.tests/NOTICE | 13 + .../org.openhab.binding.nest.tests/README.md | 7 + .../itest.bndrun | 91 ++ .../logback.xml | 16 + itests/org.openhab.binding.nest.tests/pom.xml | 25 + .../nest/handler/NestBridgeHandlerTest.java | 92 ++ .../nest/handler/NestCameraHandlerTest.java | 149 ++++ .../handler/NestSmokeDetectorHandlerTest.java | 120 +++ .../handler/NestStructureHandlerTest.java | 136 +++ .../handler/NestThermostatHandlerTest.java | 300 +++++++ .../handler/NestThingHandlerOSGiTest.java | 361 ++++++++ .../nest/internal/data/GsonParsingTest.java | 223 +++++ .../nest/internal/data/NestDataUtil.java | 96 +++ .../binding/nest/test/NestTestApiServlet.java | 220 +++++ .../nest/test/NestTestBridgeHandler.java | 66 ++ .../nest/test/NestTestHandlerFactory.java | 117 +++ .../binding/nest/test/NestTestServer.java | 88 ++ .../resources/OH-INF/thing/test-bridge.xml | 12 + .../nest/internal/data/access-token-data.json | 4 + .../nest/internal/data/camera-data.json | 33 + .../nest/internal/data/error-data.json | 6 + .../internal/data/smoke-detector-data.json | 17 + .../nest/internal/data/structure-data.json | 112 +++ .../nest/internal/data/thermostat-data.json | 51 ++ .../nest/internal/data/top-level-data.json | 307 +++++++ .../data/top-level-streaming-data-empty.json | 5 + .../top-level-streaming-data-incomplete.json | 45 + .../data/top-level-streaming-data.json | 314 +++++++ .../.classpath | 27 + .../.project | 23 + .../NOTICE | 13 + .../itest.bndrun | 49 ++ .../pom.xml | 25 + .../MapDbPersistenceServiceOSGiTest.java | 133 +++ itests/pom.xml | 147 ++++ licenses/epl-2.0/header.txt | 10 + licenses/epl-2.0/xml-header-style.xml | 13 + logo.png | Bin 0 -> 63677 bytes pom.xml | 800 ++++++++++++++++++ src/etc/NOTICE | 13 + .../checkstyle/ruleset.properties | 5 + .../checkstyle/suppressions.xml | 23 + travis-buddy-failure-template.md | 22 + travis-buddy-success-template.md | 21 + 302 files changed, 29726 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/documentation_issue.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/usage_question.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CODEOWNERS create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bom/openhab-addons/.project create mode 100644 bom/openhab-addons/pom.xml create mode 100644 bom/openhab-core-index/.classpath create mode 100644 bom/openhab-core-index/.project create mode 100644 bom/openhab-core-index/pom.xml create mode 100644 bom/pom.xml create mode 100644 bom/runtime-index/.classpath create mode 100644 bom/runtime-index/.project create mode 100644 bom/runtime-index/pom.xml create mode 100644 bom/test-index/.classpath create mode 100644 bom/test-index/.project create mode 100644 bom/test-index/pom.xml create mode 100755 buildci.sh create mode 100644 bundles/archetype-settings.xml create mode 100644 bundles/create_openhab_binding_skeleton.cmd create mode 100755 bundles/create_openhab_binding_skeleton.sh create mode 100644 bundles/org.openhab.binding.nest/.classpath create mode 100644 bundles/org.openhab.binding.nest/.project create mode 100644 bundles/org.openhab.binding.nest/NOTICE create mode 100644 bundles/org.openhab.binding.nest/README.md create mode 100644 bundles/org.openhab.binding.nest/pom.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestBindingConstants.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestHandlerFactory.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestUtils.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestBridgeConfiguration.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestDeviceConfiguration.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestStructureConfiguration.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/AccessTokenData.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ActivityZone.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/BaseNestDevice.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Camera.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/CameraEvent.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ETA.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ErrorData.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestDevices.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestIdentifiable.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestMetadata.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/SmokeDetector.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Structure.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Thermostat.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/TopLevelData.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/TopLevelStreamingData.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Where.java create 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/exceptions/FailedResolvingNestUrlException.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedRetrievingNestDataException.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedSendingNestDataException.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/InvalidAccessTokenException.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestBaseHandler.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestBridgeHandler.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestCameraHandler.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestRedirectUrlSupplier.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestSmokeDetectorHandler.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestStructureHandler.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestThermostatHandler.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/listener/NestStreamingDataListener.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/listener/NestThingDataListener.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestAuthorizer.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestStreamingRequestFilter.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestStreamingRestClient.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/rest/NestUpdateRequest.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/update/NestCompositeUpdateHandler.java create mode 100644 bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/update/NestUpdateHandler.java create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/bridge.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/camera.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/channels.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/smoke-detector.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/structure.xml create mode 100644 bundles/org.openhab.binding.nest/src/main/resources/OH-INF/thing/thermostat.xml create mode 100644 bundles/org.openhab.persistence.dynamodb/.classpath create mode 100644 bundles/org.openhab.persistence.dynamodb/.project create mode 100644 bundles/org.openhab.persistence.dynamodb/NOTICE create mode 100644 bundles/org.openhab.persistence.dynamodb/README.md create mode 100644 bundles/org.openhab.persistence.dynamodb/pom.xml create mode 100755 bundles/org.openhab.persistence.dynamodb/scripts/fetch_sdk.sh create mode 100644 bundles/org.openhab.persistence.dynamodb/scripts/fetch_sdk_pom.xml create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/java/org/openhab/persistence/dynamodb/internal/AbstractBufferedPersistenceService.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/java/org/openhab/persistence/dynamodb/internal/AbstractDynamoDBItem.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/java/org/openhab/persistence/dynamodb/internal/DynamoDBBigDecimalItem.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/java/org/openhab/persistence/dynamodb/internal/DynamoDBClient.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/java/org/openhab/persistence/dynamodb/internal/DynamoDBConfig.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/java/org/openhab/persistence/dynamodb/internal/DynamoDBHistoricItem.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/java/org/openhab/persistence/dynamodb/internal/DynamoDBItem.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/java/org/openhab/persistence/dynamodb/internal/DynamoDBItemVisitor.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/java/org/openhab/persistence/dynamodb/internal/DynamoDBPersistenceService.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/java/org/openhab/persistence/dynamodb/internal/DynamoDBQueryUtils.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/java/org/openhab/persistence/dynamodb/internal/DynamoDBStringItem.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/java/org/openhab/persistence/dynamodb/internal/DynamoDBTableNameResolver.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/AbstractDynamoDBItemGetDynamoItemClassTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/AbstractDynamoDBItemSerializationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/AbstractTwoItemIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/BaseIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/CallItemIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/ColorItemIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/ContactItemIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/DateTimeItemIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/DimmerItemIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/DynamoDBConfigTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/DynamoDBTableNameResolverTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/LocationItemIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/NumberItemIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/PagingIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/PlayerItemPlayPauseIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/PlayerItemRewindFastForwardIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/RollershutterItemIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/StringItemIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.dynamodb/src/test/java/org/openhab/persistence/dynamodb/internal/SwitchItemIntegrationTest.java create mode 100644 bundles/org.openhab.persistence.influxdb/.classpath create mode 100644 bundles/org.openhab.persistence.influxdb/.project create mode 100644 bundles/org.openhab.persistence.influxdb/NOTICE create mode 100644 bundles/org.openhab.persistence.influxdb/README.md create mode 100644 bundles/org.openhab.persistence.influxdb/pom.xml create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/InfluxDBPersistenceService.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/FilterCriteriaQueryCreator.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/InfluxDBConfiguration.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/InfluxDBConstants.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/InfluxDBHistoricItem.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/InfluxDBPersistentItemInfo.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/InfluxDBRepository.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/InfluxDBStateConvertUtils.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/InfluxDBVersion.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/InfluxPoint.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/InfluxRow.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/ItemToStorePointCreator.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/RepositoryFactory.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/UnnexpectedConditionException.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/influx1/Influx1FilterCriteriaQueryCreatorImpl.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/influx1/InfluxDB1RepositoryImpl.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/influx2/Influx2FilterCriteriaQueryCreatorImpl.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/influx2/InfluxDB2RepositoryImpl.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/org.openhab.persistence.influxdb/src/test/java/org/openhab/persistence/influxdb/internal/ConfigurationTestHelper.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/test/java/org/openhab/persistence/influxdb/internal/InfluxDBPersistenceServiceTest.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/test/java/org/openhab/persistence/influxdb/internal/InfluxDBStateConvertUtilsTest.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/test/java/org/openhab/persistence/influxdb/internal/InfluxFilterCriteriaQueryCreatorImplTest.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/test/java/org/openhab/persistence/influxdb/internal/ItemTestHelper.java create mode 100644 bundles/org.openhab.persistence.influxdb/src/test/java/org/openhab/persistence/influxdb/internal/ItemToStorePointCreatorTest.java create mode 100644 bundles/org.openhab.persistence.jdbc/.classpath create mode 100644 bundles/org.openhab.persistence.jdbc/.gitignore create mode 100644 bundles/org.openhab.persistence.jdbc/.project create mode 100644 bundles/org.openhab.persistence.jdbc/NOTICE create mode 100644 bundles/org.openhab.persistence.jdbc/README.md create mode 100644 bundles/org.openhab.persistence.jdbc/pom.xml create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcBaseDAO.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcDerbyDAO.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcH2DAO.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcHsqldbDAO.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcMariadbDAO.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcMysqlDAO.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcPostgresqlDAO.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcSqliteDAO.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcConfiguration.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcMapper.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/model/ItemVO.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/model/ItemsVO.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/model/JdbcHistoricItem.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/model/JdbcPersistenceItemInfo.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/DbMetaData.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/MovingAverage.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/StringUtilsExt.java create mode 100644 bundles/org.openhab.persistence.jdbc/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/org.openhab.persistence.jpa/.classpath create mode 100644 bundles/org.openhab.persistence.jpa/.project create mode 100644 bundles/org.openhab.persistence.jpa/NOTICE create mode 100644 bundles/org.openhab.persistence.jpa/README.md create mode 100644 bundles/org.openhab.persistence.jpa/pom.xml create mode 100644 bundles/org.openhab.persistence.jpa/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.persistence.jpa/src/main/java/org/openhab/persistence/jpa/internal/JpaConfiguration.java create mode 100644 bundles/org.openhab.persistence.jpa/src/main/java/org/openhab/persistence/jpa/internal/JpaHistoricItem.java create mode 100644 bundles/org.openhab.persistence.jpa/src/main/java/org/openhab/persistence/jpa/internal/JpaPersistenceService.java create mode 100644 bundles/org.openhab.persistence.jpa/src/main/java/org/openhab/persistence/jpa/internal/StateHelper.java create mode 100644 bundles/org.openhab.persistence.jpa/src/main/java/org/openhab/persistence/jpa/internal/model/JpaPersistentItem.java create mode 100644 bundles/org.openhab.persistence.jpa/src/main/resources/META-INF/persistence.xml create mode 100644 bundles/org.openhab.persistence.mapdb/.classpath create mode 100644 bundles/org.openhab.persistence.mapdb/.project create mode 100644 bundles/org.openhab.persistence.mapdb/NOTICE create mode 100644 bundles/org.openhab.persistence.mapdb/pom.xml create mode 100644 bundles/org.openhab.persistence.mapdb/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.persistence.mapdb/src/main/java/org/openhab/persistence/mapdb/internal/MapDbItem.java create mode 100644 bundles/org.openhab.persistence.mapdb/src/main/java/org/openhab/persistence/mapdb/internal/MapDbPersistenceService.java create mode 100644 bundles/org.openhab.persistence.mapdb/src/main/java/org/openhab/persistence/mapdb/internal/StateTypeAdapter.java create mode 100644 bundles/org.openhab.persistence.mapdb/src/test/java/org/openhab/persistence/mapdb/StateTypeAdapterTest.java create mode 100644 bundles/org.openhab.persistence.mongodb/.classpath create mode 100644 bundles/org.openhab.persistence.mongodb/.project create mode 100644 bundles/org.openhab.persistence.mongodb/NOTICE create mode 100644 bundles/org.openhab.persistence.mongodb/README.md create mode 100644 bundles/org.openhab.persistence.mongodb/pom.xml create mode 100644 bundles/org.openhab.persistence.mongodb/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.persistence.mongodb/src/main/java/org/openhab/persistence/mongodb/internal/MongoDBItem.java create mode 100644 bundles/org.openhab.persistence.mongodb/src/main/java/org/openhab/persistence/mongodb/internal/MongoDBPersistenceService.java create mode 100644 bundles/org.openhab.persistence.rrd4j/.classpath create mode 100644 bundles/org.openhab.persistence.rrd4j/.project create mode 100644 bundles/org.openhab.persistence.rrd4j/NOTICE create mode 100644 bundles/org.openhab.persistence.rrd4j/README.md create mode 100644 bundles/org.openhab.persistence.rrd4j/pom.xml create mode 100644 bundles/org.openhab.persistence.rrd4j/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.persistence.rrd4j/src/main/java/org/openhab/persistence/rrd4j/internal/RRD4jItem.java create mode 100644 bundles/org.openhab.persistence.rrd4j/src/main/java/org/openhab/persistence/rrd4j/internal/RRD4jPersistenceService.java create mode 100644 bundles/org.openhab.persistence.rrd4j/src/main/java/org/openhab/persistence/rrd4j/internal/charts/RRD4jChartServlet.java create mode 100644 bundles/org.openhab.voice.googletts/.classpath create mode 100644 bundles/org.openhab.voice.googletts/.project create mode 100644 bundles/org.openhab.voice.googletts/NOTICE create mode 100644 bundles/org.openhab.voice.googletts/README.md create mode 100644 bundles/org.openhab.voice.googletts/pom.xml create mode 100644 bundles/org.openhab.voice.googletts/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/AuthenticationException.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/GoogleCloudAPI.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/GoogleTTSConfig.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/GoogleTTSService.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/GoogleTTSVoice.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/protocol/AudioConfig.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/protocol/AudioEncoding.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/protocol/ListVoicesResponse.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/protocol/SsmlVoiceGender.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/protocol/SynthesisInput.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/protocol/SynthesizeSpeechRequest.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/protocol/SynthesizeSpeechResponse.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/protocol/Voice.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/java/org/openhab/voice/googletts/internal/protocol/VoiceSelectionParams.java create mode 100644 bundles/org.openhab.voice.googletts/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/pom.xml create mode 100644 features/openhab-addons-external/.project create mode 100644 features/openhab-addons-external/pom.xml create mode 100644 features/openhab-addons-external/src/main/resources/conf/dynamodb.cfg create mode 100644 features/openhab-addons-external/src/main/resources/conf/influxdb.cfg create mode 100644 features/openhab-addons-external/src/main/resources/conf/jdbc.cfg create mode 100644 features/openhab-addons-external/src/main/resources/conf/jpa.cfg create mode 100644 features/openhab-addons-external/src/main/resources/conf/mapdb.cfg create mode 100644 features/openhab-addons-external/src/main/resources/conf/rrd4j.cfg create mode 100644 features/openhab-addons/.project create mode 100644 features/openhab-addons/pom.xml create mode 100644 features/openhab-addons/src/main/resources/footer.xml create mode 100644 features/openhab-addons/src/main/resources/header.xml create mode 100644 features/pom.xml create mode 100644 features/readme.md create mode 100644 itests/.project create mode 100644 itests/itest-common.bndrun create mode 100644 itests/org.openhab.binding.nest.tests/.classpath create mode 100644 itests/org.openhab.binding.nest.tests/.project create mode 100644 itests/org.openhab.binding.nest.tests/NOTICE create mode 100644 itests/org.openhab.binding.nest.tests/README.md create mode 100644 itests/org.openhab.binding.nest.tests/itest.bndrun create mode 100644 itests/org.openhab.binding.nest.tests/logback.xml create mode 100644 itests/org.openhab.binding.nest.tests/pom.xml create mode 100644 itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestBridgeHandlerTest.java create mode 100644 itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestCameraHandlerTest.java create mode 100644 itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestSmokeDetectorHandlerTest.java create mode 100644 itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestStructureHandlerTest.java create mode 100644 itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestThermostatHandlerTest.java create mode 100644 itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/handler/NestThingHandlerOSGiTest.java create mode 100644 itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/data/GsonParsingTest.java create mode 100644 itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/internal/data/NestDataUtil.java create mode 100644 itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestApiServlet.java create mode 100644 itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestBridgeHandler.java create mode 100644 itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestHandlerFactory.java create mode 100644 itests/org.openhab.binding.nest.tests/src/main/java/org/openhab/binding/nest/test/NestTestServer.java create mode 100644 itests/org.openhab.binding.nest.tests/src/main/resources/OH-INF/thing/test-bridge.xml create mode 100644 itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/access-token-data.json create mode 100644 itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/camera-data.json create mode 100644 itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/error-data.json create mode 100644 itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/smoke-detector-data.json create mode 100644 itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/structure-data.json create mode 100644 itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/thermostat-data.json create mode 100644 itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/top-level-data.json create mode 100644 itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/top-level-streaming-data-empty.json create mode 100644 itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/top-level-streaming-data-incomplete.json create mode 100644 itests/org.openhab.binding.nest.tests/src/main/resources/org/openhab/binding/nest/internal/data/top-level-streaming-data.json create mode 100644 itests/org.openhab.persistence.mapdb.tests/.classpath create mode 100644 itests/org.openhab.persistence.mapdb.tests/.project create mode 100644 itests/org.openhab.persistence.mapdb.tests/NOTICE create mode 100644 itests/org.openhab.persistence.mapdb.tests/itest.bndrun create mode 100644 itests/org.openhab.persistence.mapdb.tests/pom.xml create mode 100644 itests/org.openhab.persistence.mapdb.tests/src/main/java/org/openhab/persistence/mapdb/MapDbPersistenceServiceOSGiTest.java create mode 100644 itests/pom.xml create mode 100644 licenses/epl-2.0/header.txt create mode 100644 licenses/epl-2.0/xml-header-style.xml create mode 100644 logo.png create mode 100644 pom.xml create mode 100644 src/etc/NOTICE create mode 100644 tools/static-code-analysis/checkstyle/ruleset.properties create mode 100644 tools/static-code-analysis/checkstyle/suppressions.xml create mode 100644 travis-buddy-failure-template.md create mode 100644 travis-buddy-success-template.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..cf8664dbec8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,47 @@ +--- +name: "\U0001F41B Bug report" +about: Something isn't working correctly with an add-on. This is the wrong place for user-interfaces or openHAB Core issues. +labels: bug + +--- + + + + + + + + + + +## Expected Behavior + + + +## Current Behavior + + + + + + + +## Possible Solution + + + +## Steps to Reproduce (for Bugs) + + +1. +2. + +## Context + + + +## Your Environment + +* Version used: (e.g., openHAB and add-on versions) +* Environment name and version (e.g. Chrome 76, Java 8, Node.js 12.9, ...): +* Operating System and version (desktop or mobile, Windows 10, Raspbian Buster, ...): diff --git a/.github/ISSUE_TEMPLATE/documentation_issue.md b/.github/ISSUE_TEMPLATE/documentation_issue.md new file mode 100644 index 00000000000..e6d06dc2605 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation_issue.md @@ -0,0 +1,17 @@ +--- +name: "Documentation issue" +about: Some information within the add-on documentation is wrong or missing +labels: documentation + +--- + + + + + + + + + + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..1766352b9b6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: "Feature request" +about: You think that your favorite add-on should gain another feature +labels: enhancement + +--- + + + + + + + + +## Your Environment + +* Version used: (e.g., openHAB and add-on versions) +* Environment name and version (e.g. Chrome 76, Java 8, Node.js 12.9, ...): +* Operating System and version (desktop or mobile, Windows 10, Raspbian Buster, ...): diff --git a/.github/ISSUE_TEMPLATE/usage_question.md b/.github/ISSUE_TEMPLATE/usage_question.md new file mode 100644 index 00000000000..03bb3504891 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/usage_question.md @@ -0,0 +1,10 @@ +--- +name: "\U0001F914 Support/Usage Question" +about: For usage questions, please use the openHAB community board! +labels: question + +--- + +This is an issue tracker for reporting problems and requesting new features. For usage questions, please use the openHAB community board where there are a lot more people ready to help you out. Thanks! + +https://community.openhab.org/ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..9f105d4fd1b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,62 @@ + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..e56247aebdf --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +.antlr* +.idea +.DS_Store +*.iml +npm-debug.log +.build.log + +.metadata/ +bin/ +target/ +src-gen/ +xtend-gen/ +.history/ + +*/plugin.xml_gen +**/.settings/org.eclipse.* + +bundles/**/src/main/history +features/**/src/main/history +features/**/src/main/feature + +.vscode +.factorypath diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000000..0c09fcac63c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +os: linux +dist: focal + +language: java +jdk: openjdk11 + +cache: + directories: + - $HOME/.m2 + +before_cache: + # remove resolver-status.properties, they change with each run and invalidate the cache + - find $HOME/.m2 -name resolver-status.properties -exec rm {} \; + +notifications: + webhooks: https://www.travisbuddy.com/ + +travisBuddy: + insertMode: update + successBuildLog: true + +install: true +script: ./buildci.sh "$TRAVIS_COMMIT_RANGE" diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000000..225fd8902d0 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,247 @@ +# This file helps GitHub doing automatic review requests for new PRs. +# It should always list the active maintainers of certain add-ons. + +# As a fallback, if no specific maintainer is listed below, assign the PR to the repo maintainers team: +* @openhab/add-ons-maintainers + +# Add-on maintainers: +/bundles/org.openhab.binding.airquality/ @kubawolanin +/bundles/org.openhab.binding.airvisualnode/ @3cky +/bundles/org.openhab.binding.allplay/ @dominicdesu +/bundles/org.openhab.binding.amazondashbutton/ @OLibutzki +/bundles/org.openhab.binding.amazonechocontrol/ @mgeramb +/bundles/org.openhab.binding.ambientweather/ @mhilbush +/bundles/org.openhab.binding.astro/ @gerrieg +/bundles/org.openhab.binding.atlona/ @tmrobert8 +/bundles/org.openhab.binding.autelis/ @digitaldan +/bundles/org.openhab.binding.avmfritz/ @cweitkamp +/bundles/org.openhab.binding.bigassfan/ @mhilbush +/bundles/org.openhab.binding.bluetooth/ @cdjackson @kaikreuzer +/bundles/org.openhab.binding.bluetooth.bluegiga/ @cdjackson @kaikreuzer +/bundles/org.openhab.binding.bluetooth.bluez/ @cdjackson @kaikreuzer +/bundles/org.openhab.binding.bluetooth.blukii/ @kaikreuzer +/bundles/org.openhab.binding.bluetooth.ruuvitag/ @ssalonen +/bundles/org.openhab.binding.boschindego/ @jofleck +/bundles/org.openhab.binding.bosesoundtouch/ @marvkis @tratho +/bundles/org.openhab.binding.buienradar/ @gedejong +/bundles/org.openhab.binding.chromecast/ @kaikreuzer +/bundles/org.openhab.binding.cm11a/ @BobRak +/bundles/org.openhab.binding.coolmasternet/ @projectgus +/bundles/org.openhab.binding.daikin/ @caffineehacker @psmedley +/bundles/org.openhab.binding.darksky/ @cweitkamp +/bundles/org.openhab.binding.deconz/ @davidgraeff +/bundles/org.openhab.binding.denonmarantz/ @jwveldhuis +/bundles/org.openhab.binding.digiplex/ @rmichalak +/bundles/org.openhab.binding.digitalstrom/ @MichaelOchel @msiegele +/bundles/org.openhab.binding.dlinksmarthome/ @MikeJMajor +/bundles/org.openhab.binding.dmx/ @J-N-K +/bundles/org.openhab.binding.doorbird/ @mhilbush +/bundles/org.openhab.binding.dscalarm/ @RSStephens +/bundles/org.openhab.binding.dsmr/ @Hilbrand +/bundles/org.openhab.binding.dwdunwetter/ @limdul79 +/bundles/org.openhab.binding.elerotransmitterstick/ @vbier +/bundles/org.openhab.binding.enocean/ @fruggy83 +/bundles/org.openhab.binding.enturno/ @klocsson +/bundles/org.openhab.binding.evohome/ @Nebula83 +/bundles/org.openhab.binding.exec/ @kgoderis +/bundles/org.openhab.binding.feed/ @svilenvul +/bundles/org.openhab.binding.feican/ @Hilbrand +/bundles/org.openhab.binding.folding/ @fa2k +/bundles/org.openhab.binding.foobot/ @airboxlab @Hilbrand +/bundles/org.openhab.binding.freebox/ @lolodomo +/bundles/org.openhab.binding.fronius/ @trokohl +/bundles/org.openhab.binding.fsinternetradio/ @paphko +/bundles/org.openhab.binding.ftpupload/ @paulianttila +/bundles/org.openhab.binding.gardena/ @gerrieg +/bundles/org.openhab.binding.globalcache/ @mhilbush +/bundles/org.openhab.binding.gpstracker/ @gbicskei +/bundles/org.openhab.binding.groheondus/ @FlorianSW +/bundles/org.openhab.binding.harmonyhub/ @digitaldan +/bundles/org.openhab.binding.hdanywhere/ @kgoderis +/bundles/org.openhab.binding.hdpowerview/ @beowulfe +/bundles/org.openhab.binding.helios/ @kgoderis +/bundles/org.openhab.binding.heos/ @Wire82 +/bundles/org.openhab.binding.homematic/ @FStolte @gerrieg @mdicke2s +/bundles/org.openhab.binding.hpprinter/ @cossey +/bundles/org.openhab.binding.hue/ @cweitkamp +/bundles/org.openhab.binding.hydrawise/ @digitaldan +/bundles/org.openhab.binding.hyperion/ @tavalin +/bundles/org.openhab.binding.iaqualink/ @digitaldan +/bundles/org.openhab.binding.icloud/ @pgfeller +/bundles/org.openhab.binding.ihc/ @paulianttila +/bundles/org.openhab.binding.innogysmarthome/ @ollie-dev +/bundles/org.openhab.binding.ipp/ @peuter +/bundles/org.openhab.binding.irtrans/ @kgoderis +/bundles/org.openhab.binding.jeelink/ @vbier +/bundles/org.openhab.binding.keba/ @kgoderis +/bundles/org.openhab.binding.km200/ @Markinus +/bundles/org.openhab.binding.knx/ @sjka +/bundles/org.openhab.binding.kodi/ @pail23 @cweitkamp +/bundles/org.openhab.binding.konnected/ @volfan6415 +/bundles/org.openhab.binding.kostalinverter/ @cschneider +/bundles/org.openhab.binding.lametrictime/ @syphr42 +/bundles/org.openhab.binding.leapmotion/ @kaikreuzer +/bundles/org.openhab.binding.lghombot/ @FluBBaOfWard +/bundles/org.openhab.binding.lgtvserial/ @fa2k +/bundles/org.openhab.binding.lgwebos/ @sprehn +/bundles/org.openhab.binding.lifx/ @wborn +/bundles/org.openhab.binding.linuxinput/ @t-8ch +/bundles/org.openhab.binding.lirc/ @kabili207 +/bundles/org.openhab.binding.logreader/ @paulianttila +/bundles/org.openhab.binding.loxone/ @ppieczul +/bundles/org.openhab.binding.lutron/ @actong @bobadair +/bundles/org.openhab.binding.mail/ @J-N-K +/bundles/org.openhab.binding.max/ @marcelrv +/bundles/org.openhab.binding.mcp23017/ @aogorek +/bundles/org.openhab.binding.melcloud/ @lucacalcaterra @paulianttila @thewiep +/bundles/org.openhab.binding.meteoblue/ @9037568 +/bundles/org.openhab.binding.meteostick/ @cdjackson +/bundles/org.openhab.binding.miele/ @kgoderis +/bundles/org.openhab.binding.mihome/ @pboos +/bundles/org.openhab.binding.miio/ @marcelrv +/bundles/org.openhab.binding.millheat/ @seime +/bundles/org.openhab.binding.milight/ @davidgraeff +/bundles/org.openhab.binding.minecraft/ @ibaton +/bundles/org.openhab.binding.modbus/ @ssalonen +/bundles/org.openhab.binding.mqtt/ @davidgraeff +/bundles/org.openhab.binding.mqtt.generic/ @davidgraeff +/bundles/org.openhab.binding.mqtt.homeassistant/ @davidgraeff +/bundles/org.openhab.binding.mqtt.homie/ @davidgraeff +/bundles/org.openhab.binding.nanoleaf/ @raepple +/bundles/org.openhab.binding.neato/ @jjlauterbach +/bundles/org.openhab.binding.neeo/ @tmrobert8 +/bundles/org.openhab.binding.neohub/ @andrewfg +/bundles/org.openhab.binding.nest/ @wborn +/bundles/org.openhab.binding.netatmo/ @clinique @cweitkamp @lolodomo +/bundles/org.openhab.binding.network/ @davidgraeff @mettke +/bundles/org.openhab.binding.networkupstools/ @Hilbrand +/bundles/org.openhab.binding.nibeheatpump/ @paulianttila +/bundles/org.openhab.binding.nibeuplink/ @alexf2015 +/bundles/org.openhab.binding.nikobus/ @crnjan +/bundles/org.openhab.binding.nikohomecontrol/ @mherwege +/bundles/org.openhab.binding.ntp/ @marcelrv +/bundles/org.openhab.binding.nuki/ @mkatter +/bundles/org.openhab.binding.oceanic/ @kgoderis +/bundles/org.openhab.binding.omnikinverter/ @hansbogert +/bundles/org.openhab.binding.onebusaway/ @sdwilsh +/bundles/org.openhab.binding.onewiregpio/ @aogorek +/bundles/org.openhab.binding.onewire/ @J-N-K +/bundles/org.openhab.binding.onkyo/ @pail23 @paulianttila +/bundles/org.openhab.binding.opengarage/ @psmedley +/bundles/org.openhab.binding.opensprinkler/ @CrackerStealth @FlorianSW +/bundles/org.openhab.binding.openuv/ @clinique +/bundles/org.openhab.binding.openweathermap/ @cweitkamp +/bundles/org.openhab.binding.orvibo/ @tavalin +/bundles/org.openhab.binding.paradoxalarm/ @theater +/bundles/org.openhab.binding.pentair/ @jsjames +/bundles/org.openhab.binding.phc/ @gnlpfjh +/bundles/org.openhab.binding.pioneeravr/ @Stratehm +/bundles/org.openhab.binding.pixometer/ @Confectrician +/bundles/org.openhab.binding.pjlinkdevice/ @nils +/bundles/org.openhab.binding.plclogo/ @falkena +/bundles/org.openhab.binding.plugwise/ @wborn +/bundles/org.openhab.binding.powermax/ @lolodomo +/bundles/org.openhab.binding.pulseaudio/ @peuter +/bundles/org.openhab.binding.pushbullet/ @hakan42 +/bundles/org.openhab.binding.regoheatpump/ @crnjan +/bundles/org.openhab.binding.rfxcom/ @martinvw @paulianttila +/bundles/org.openhab.binding.rme/ @kgoderis +/bundles/org.openhab.binding.robonect/ @reyem +/bundles/org.openhab.binding.rotel/ @lolodomo +/bundles/org.openhab.binding.rotelra1x/ @fa2k +/bundles/org.openhab.binding.russound/ @tmrobert8 +/bundles/org.openhab.binding.samsungtv/ @paulianttila +/bundles/org.openhab.binding.satel/ @druciak +/bundles/org.openhab.binding.seneye/ @nikotanghe +/bundles/org.openhab.binding.sensebox/ @hakan42 +/bundles/org.openhab.binding.serialbutton/ @kaikreuzer +/bundles/org.openhab.binding.shelly/ @markus7017 +/bundles/org.openhab.binding.siemensrds/ @andrewfg +/bundles/org.openhab.binding.silvercrestwifisocket/ @jmvaz +/bundles/org.openhab.binding.sinope/ @chaton78 +/bundles/org.openhab.binding.sleepiq/ @syphr42 +/bundles/org.openhab.binding.smaenergymeter/ @monnimeter +/bundles/org.openhab.binding.smartmeter/ @msteigenberger +/bundles/org.openhab.binding.snmp/ @J-N-K +/bundles/org.openhab.binding.solaredge/ @alexf2015 +/bundles/org.openhab.binding.solarlog/ @johannrichard +/bundles/org.openhab.binding.somfytahoma/ @octa22 +/bundles/org.openhab.binding.sonos/ @kgoderis @lolodomo +/bundles/org.openhab.binding.sonyaudio/ @freke +/bundles/org.openhab.binding.sonyprojector/ @lolodomo +/bundles/org.openhab.binding.spotify/ @Hilbrand +/bundles/org.openhab.binding.squeezebox/ @digitaldan @mhilbush +/bundles/org.openhab.binding.synopanalyzer/ @clinique +/bundles/org.openhab.binding.systeminfo/ @svilenvul +/bundles/org.openhab.binding.tado/ @dfrommi +/bundles/org.openhab.binding.tankerkoenig/ @dolic @JueBag +/bundles/org.openhab.binding.telegram/ @ZzetT +/bundles/org.openhab.binding.tellstick/ @jarlebh +/bundles/org.openhab.binding.tesla/ @kgoderis +/bundles/org.openhab.binding.toon/ @jongj +/bundles/org.openhab.binding.tplinksmarthome/ @Hilbrand +/bundles/org.openhab.binding.tradfri/ @cweitkamp @kaikreuzer +/bundles/org.openhab.binding.unifi/ @mgbowman +/bundles/org.openhab.binding.urtsi/ @OLibutzki +/bundles/org.openhab.binding.valloxmv/ @bjoernbrings +/bundles/org.openhab.binding.vektiva/ @octa22 +/bundles/org.openhab.binding.velbus/ @cedricboon +/bundles/org.openhab.binding.vitotronic/ @steand +/bundles/org.openhab.binding.volvooncall/ @clinique +/bundles/org.openhab.binding.weathercompany/ @mhilbush +/bundles/org.openhab.binding.weatherunderground/ @lolodomo +/bundles/org.openhab.binding.wemo/ @hmerk +/bundles/org.openhab.binding.wifiled/ @rvt @xylo +/bundles/org.openhab.binding.windcentrale/ @marcelrv +/bundles/org.openhab.binding.xmltv/ @clinique +/bundles/org.openhab.binding.xmppclient/ @pavel-gololobov +/bundles/org.openhab.binding.yamahareceiver/ @davidgraeff @zarusz +/bundles/org.openhab.binding.yeelight/ @claell +/bundles/org.openhab.binding.zoneminder/ @Mr-Eskildsen +/bundles/org.openhab.binding.zway/ @pathec +/bundles/org.openhab.extensionservice.marketplace/ @kaikreuzer +/bundles/org.openhab.extensionservice.marketplace.automation/ @kaikreuzer +/bundles/org.openhab.io.azureiothub/ @nikotanghe +/bundles/org.openhab.io.homekit/ @beowulfe +/bundles/org.openhab.io.hueemulation/ @davidgraeff @digitaldan +/bundles/org.openhab.io.imperihome/ @pdegeus +/bundles/org.openhab.io.javasound/ @kaikreuzer +/bundles/org.openhab.io.mqttembeddedbroker/ @davidgraeff +/bundles/org.openhab.io.neeo/ @tmrobert8 +/bundles/org.openhab.io.openhabcloud/ @kaikreuzer +/bundles/org.openhab.io.transport.modbus/ @ssalonen +/bundles/org.openhab.io.webaudio/ @kaikreuzer +/bundles/org.openhab.persistence.mapdb/ @mkhl +/bundles/org.openhab.persistence.influxdb/ @lujop +/bundles/org.openhab.transform.exec/ @openhab/add-ons-maintainers +/bundles/org.openhab.transform.javascript/ @openhab/add-ons-maintainers +/bundles/org.openhab.transform.jinja/ @jochen314 +/bundles/org.openhab.transform.jsonpath/ @clinique +/bundles/org.openhab.transform.map/ @openhab/add-ons-maintainers +/bundles/org.openhab.transform.regex/ @openhab/add-ons-maintainers +/bundles/org.openhab.transform.scale/ @clinique +/bundles/org.openhab.transform.xpath/ @openhab/add-ons-maintainers +/bundles/org.openhab.transform.xslt/ @openhab/add-ons-maintainers +/bundles/org.openhab.voice.googletts/ @gbicskei +/bundles/org.openhab.voice.mactts/ @kaikreuzer +/bundles/org.openhab.voice.marytts/ @kaikreuzer +/bundles/org.openhab.voice.picotts/ @FlorianSW +/bundles/org.openhab.voice.pollytts/ @hillmanr +/bundles/org.openhab.voice.voicerss/ @JochenHiller +/itests/org.openhab.binding.astro.tests/ @gerrieg +/itests/org.openhab.binding.avmfritz.tests/ @cweitkamp +/itests/org.openhab.binding.feed.tests/ @svilenvul +/itests/org.openhab.binding.hue.tests/ @cweitkamp +/itests/org.openhab.binding.max.tests/ @marcelrv +/itests/org.openhab.binding.mqtt.homeassistant.tests/ @davidgraeff +/itests/org.openhab.binding.mqtt.homie.tests/ @davidgraeff +/itests/org.openhab.binding.nest.tests/ @wborn +/itests/org.openhab.binding.ntp.tests/ @marcelrv +/itests/org.openhab.binding.systeminfo.tests/ @svilenvul +/itests/org.openhab.binding.tradfri.tests/ @cweitkamp @kaikreuzer +/itests/org.openhab.binding.wemo.tests/ @hmerk +/itests/org.openhab.io.hueemulation.tests/ @davidgraeff @digitaldan +/itests/org.openhab.io.mqttembeddedbroker.tests/ @J-N-K +/itests/org.openhab.persistence.mapdb.tests/ @mkhl + +# PLEASE HELP ADDING FURTHER LINES HERE! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..d6715c158c9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,174 @@ +# Contributing to openHAB + +Want to hack on openHAB? Awesome! Here are instructions to get you +started. They are probably not perfect, please let us know if anything +feels wrong or incomplete. + +## Build Environment + +For instructions on setting up your development environment, please +see our dedicated [IDE setup guide](https://www.openhab.org/docs/developer/). + +## Contribution guidelines + +### Pull requests are always welcome + +We are always thrilled to receive pull requests, and do our best to +process them as fast as possible. Not sure if that typo is worth a pull +request? Do it! We will appreciate it. + +If your pull request is not accepted on the first try, don't be +discouraged! If there's a problem with the implementation, hopefully you +received feedback on what to improve. + +We're trying very hard to keep openHAB lean and focused. We don't want it +to do everything for everybody. This means that we might decide against +incorporating a new feature. However, there might be a way to implement +that feature *on top of* openHAB. + +### Discuss your design in the discussion forum + +We recommend discussing your plans [in the discussion forum](https://community.openhab.org/c/add-ons) +before starting to code - especially for more ambitious contributions. +This gives other contributors a chance to point you in the right +direction, give feedback on your design, and maybe point out if someone +else is working on the same thing. + +### Create issues... + +Any significant improvement should be documented as [a GitHub +issue](https://github.com/openhab/openhab-addons/issues?labels=enhancement&page=1&state=open) before anybody +starts working on it. + +### ...but check for existing issues first! + +Please take a moment to check that an issue doesn't already exist +documenting your bug report or improvement proposal. If it does, it +never hurts to add a quick "+1" or "I have this problem too". This will +help prioritize the most common problems and requests. + +### Conventions + +Fork the repo and make changes on your fork in a feature branch. + +Submit unit tests for your changes. openHAB has a great test framework built in; use +it! Take a look at existing tests for inspiration. Run the full test suite on +your branch before submitting a pull request. + +Update the documentation when creating or modifying features. Test +your documentation changes for clarity, concision, and correctness, as +well as a clean documentation build. + +Write clean code. Universally formatted code promotes ease of writing, reading, +and maintenance. + +Pull requests descriptions should be as clear as possible and include a +reference to all the issues that they address. + +Pull requests must not contain commits from other users or branches. + +Commit messages must start with a capitalized and short summary (max. 50 +chars) written in the imperative, followed by an optional, more detailed +explanatory text which is separated from the summary by an empty line. + +Code review comments may be added to your pull request. Discuss, then make the +suggested modifications and push additional commits to your feature branch. Be +sure to post a comment after pushing. The new commits will show up in the pull +request automatically, but the reviewers will not be notified unless you +comment. + +Commits that fix or close an issue should include a reference like `Fixes #XXX`, +which will automatically close the issue when merged. + +### Sign your work + +The sign-off is a simple line at the end of the explanation for the +patch, which certifies that you wrote it or otherwise have the right to +pass it on as an open-source patch. The rules are pretty simple: if you +can certify the below (from +[developercertificate.org](https://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +using your real name (sorry, no pseudonyms or anonymous contributions.) and an +e-mail address under which you can be reached (sorry, no github noreply e-mail +addresses (such as username@users.noreply.github.com) or other non-reachable +addresses are allowed). + +On the command line you can use `git commit -s` to sign off the commit. + +### How can I become a maintainer? + +* Step 1: learn the component inside out +* Step 2: make yourself useful by contributing code, bugfixes, support etc. +* Step 3: volunteer on [the discussion group](https://github.com/openhab/openhab-addons/issues?labels=question&page=1&state=open) + +Don't forget: being a maintainer is a time investment. Make sure you will have time to make yourself available. +You don't have to be a maintainer to make a difference on the project! + +## Community Guidelines + +We want to keep the openHAB community awesome, growing and collaborative. We +need your help to keep it that way. To help with this we have come up with some +general guidelines for the community as a whole: + +* Be nice: Be courteous, respectful and polite to fellow community members: no + regional, racial, gender, or other abuse will be tolerated. We like nice people + way better than mean ones! + +* Encourage diversity and participation: Make everyone in our community + feel welcome, regardless of their background and the extent of their + contributions, and do everything possible to encourage participation in + our community. + +* Keep it legal: Basically, don't get us in trouble. Share only content that + you own, do not share private or sensitive information, and don't break the + law. + +* Stay on topic: Make sure that you are posting to the correct channel + and avoid off-topic discussions. Remember when you update an issue or + respond to an email you are potentially sending to a large number of + people. Please consider this before you update. Also remember that + nobody likes spam. + diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..d3087e4c540 --- /dev/null +++ b/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. diff --git a/README.md b/README.md new file mode 100644 index 00000000000..66b66f03da9 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# openHAB Add-ons + + + +[![Build Status](https://travis-ci.com/openhab/openhab-addons.svg)](https://travis-ci.com/openhab/openhab-addons) +[![EPL-2.0](https://img.shields.io/badge/license-EPL%202-green.svg)](https://opensource.org/licenses/EPL-2.0) +[![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=2164344)](https://www.bountysource.com/teams/openhab/issues?tracker_ids=2164344) + +This repository contains the official set of add-ons that are implemented on top of openHAB Core APIs. +Add-ons that got accepted in here will be maintained (e.g. adapted to new core APIs) +by the [openHAB Add-on maintainers](https://github.com/orgs/openhab/teams/add-ons-maintainers). + +To get started with binding development, follow our guidelines and tutorials over at https://www.openhab.org/docs/developer. + +If you are interested in openHAB Core development, we invite you to come by on https://github.com/openhab/openhab-core. + +## Add-ons in other repositories + +Some add-ons are not in this repository, but still part of the official [openHAB distribution](https://github.com/openhab/openhab-distro). +An incomplete list of other repositories follows below: + +* https://github.com/openhab/org.openhab.binding.zwave +* https://github.com/openhab/org.openhab.binding.zigbee +* https://github.com/openhab/openhab-webui + +## Development / Repository Organization + +openHAB add-ons are [Java](https://en.wikipedia.org/wiki/Java_(programming_language)) `.jar` files. + +The openHAB build system is based on [Maven](https://maven.apache.org/what-is-maven.html). +The official IDE (Integrated development environment) is Eclipse. + +You find the following repository structure: + +``` +. ++-- bom Maven buildsystem: Bill of materials +| +-- openhab-addons Lists all extensions for other repos to reference them +| +-- ... Other boms +| ++-- bundles Official openHAB extensions +| +-- org.openhab.binding.airquality +| +-- org.openhab.binding.astro +| +-- ... +| ++-- features Part of the runtime dependency resolver ("Karaf features") +| ++-- itests Integration tests. Those tests require parts of the framework to run. +| +-- org.openhab.binding.astro.tests +| +-- org.openhab.binding.avmfritz.tests +| +-- ... +| ++-- src/etc Auxilary buildsystem files: The license header for automatic checks for example ++-- tools Static code analyser instructions +| ++-- CODEOWNERS This file assigns people to directories so that they are informed if a pull-request + would modify their add-ons. +``` + +### Command line build + +To build all add-ons from the command-line, type in: + +`mvn clean install` + +Optionally you can skip tests (`-DskipTests`) or skip some static analysis (`-DskipChecks`). +This does improve the build time but could hide problems in your code. +For binding development you want to run that command without skipping checks and tests. +To check if your code is following the [code style](https://www.openhab.org/docs/developer/guidelines.html#b-code-formatting-rules-style) run `mvn spotless:check`. +If Maven prints `[INFO] Spotless check skipped` then run `mvn spotless:check -Dspotless.check.skip=false` instead as the check is not mandatory yet. +To reformat you code run `mvn spotless:apply`. + +Subsequent calls can include the `-o` for offline as in: `mvn clean install -DskipChecks -o` which will be a bit faster. + +For integration tests you might need to run: `mvn clean install -DwithResolver -DskipChecks` + +You find a generated `.jar` file per bundle in the respective bundle `/target` directory. + +### How to develop via an Integrated Development Environment (IDE) + +We have assembled some step-by-step guides for different IDEs on our developer documentation website: + +https://www.openhab.org/docs/developer/#setup-the-development-environment + +Happy coding! diff --git a/bom/openhab-addons/.project b/bom/openhab-addons/.project new file mode 100644 index 00000000000..94762864205 --- /dev/null +++ b/bom/openhab-addons/.project @@ -0,0 +1,17 @@ + + + org.openhab.core.bom.openhab-addons + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml new file mode 100644 index 00000000000..0a21941f676 --- /dev/null +++ b/bom/openhab-addons/pom.xml @@ -0,0 +1,66 @@ + + + + 4.0.0 + + + org.openhab.addons.bom + org.openhab.addons.reactor.bom + 3.0.0-SNAPSHOT + + + org.openhab.addons.bom.openhab-addons + pom + + openHAB Add-ons :: BOM :: openHAB Add-ons + + + + org.openhab.addons.bundles + org.openhab.binding.nest + ${project.version} + + + org.openhab.addons.bundles + org.openhab.persistence.dynamodb + ${project.version} + + + org.openhab.addons.bundles + org.openhab.persistence.influxdb + ${project.version} + + + org.openhab.addons.bundles + org.openhab.persistence.jdbc + ${project.version} + + + org.openhab.addons.bundles + org.openhab.persistence.jpa + ${project.version} + + + org.openhab.addons.bundles + org.openhab.persistence.mapdb + ${project.version} + + + org.openhab.addons.bundles + org.openhab.persistence.mongodb + ${project.version} + + + org.openhab.addons.bundles + org.openhab.persistence.rrd4j + ${project.version} + + + org.openhab.addons.bundles + org.openhab.voice.googletts + ${project.version} + + + + diff --git a/bom/openhab-core-index/.classpath b/bom/openhab-core-index/.classpath new file mode 100644 index 00000000000..4559ca0b258 --- /dev/null +++ b/bom/openhab-core-index/.classpath @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bom/openhab-core-index/.project b/bom/openhab-core-index/.project new file mode 100644 index 00000000000..5b1b0b8ecf0 --- /dev/null +++ b/bom/openhab-core-index/.project @@ -0,0 +1,23 @@ + + + org.openhab.addons.bom.openhab-core-index + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/bom/openhab-core-index/pom.xml b/bom/openhab-core-index/pom.xml new file mode 100644 index 00000000000..b2f50720e2b --- /dev/null +++ b/bom/openhab-core-index/pom.xml @@ -0,0 +1,41 @@ + + + + 4.0.0 + + + org.openhab.addons.bom + org.openhab.addons.reactor.bom + 3.0.0-SNAPSHOT + + + org.openhab.addons.bom.openhab-core-index + + openHAB Add-ons :: BOM :: openHAB Core Index + + + + org.openhab.core.bom + org.openhab.core.bom.openhab-core + ${ohc.version} + pom + compile + true + + + + + + + biz.aQute.bnd + bnd-maven-plugin + + + biz.aQute.bnd + bnd-indexer-maven-plugin + + + + + diff --git a/bom/pom.xml b/bom/pom.xml new file mode 100644 index 00000000000..3fe988a325e --- /dev/null +++ b/bom/pom.xml @@ -0,0 +1,91 @@ + + + + 4.0.0 + + + org.openhab.addons + org.openhab.addons.reactor + 3.0.0-SNAPSHOT + + + org.openhab.addons.bom + org.openhab.addons.reactor.bom + pom + + openHAB Add-ons :: BOM + + + runtime-index + test-index + openhab-core-index + openhab-addons + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + false + + + create-bom + + run + + + + + + + + + + header + + org.openhab.addons.bom + org.openhab.addons.reactor.bom + ${project.version} + + + org.openhab.addons.bom.openhab-addons + pom + + openHAB Add-ons :: BOM :: openHAB Add-ons + + ]]> + + + + ]]> + + org.openhab.addons.bundles + ]]> + + + ]]> + + @dollar{project.version} + ]]> + + + @dollar + $ + + + + + + + + + + + diff --git a/bom/runtime-index/.classpath b/bom/runtime-index/.classpath new file mode 100644 index 00000000000..4559ca0b258 --- /dev/null +++ b/bom/runtime-index/.classpath @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bom/runtime-index/.project b/bom/runtime-index/.project new file mode 100644 index 00000000000..24f1b8fe006 --- /dev/null +++ b/bom/runtime-index/.project @@ -0,0 +1,23 @@ + + + org.openhab.addons.bom.runtime-index + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/bom/runtime-index/pom.xml b/bom/runtime-index/pom.xml new file mode 100644 index 00000000000..6a45ee6658a --- /dev/null +++ b/bom/runtime-index/pom.xml @@ -0,0 +1,41 @@ + + + + 4.0.0 + + + org.openhab.addons.bom + org.openhab.addons.reactor.bom + 3.0.0-SNAPSHOT + + + org.openhab.addons.bom.runtime-index + + openHAB Add-ons :: BOM :: Runtime Index + + + + org.openhab.core.bom + org.openhab.core.bom.runtime + ${ohc.version} + pom + compile + true + + + + + + + biz.aQute.bnd + bnd-maven-plugin + + + biz.aQute.bnd + bnd-indexer-maven-plugin + + + + + diff --git a/bom/test-index/.classpath b/bom/test-index/.classpath new file mode 100644 index 00000000000..4559ca0b258 --- /dev/null +++ b/bom/test-index/.classpath @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bom/test-index/.project b/bom/test-index/.project new file mode 100644 index 00000000000..f87b2752ec5 --- /dev/null +++ b/bom/test-index/.project @@ -0,0 +1,23 @@ + + + org.openhab.addons.bom.test-index + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/bom/test-index/pom.xml b/bom/test-index/pom.xml new file mode 100644 index 00000000000..381be588321 --- /dev/null +++ b/bom/test-index/pom.xml @@ -0,0 +1,49 @@ + + + + 4.0.0 + + + org.openhab.addons.bom + org.openhab.addons.reactor.bom + 3.0.0-SNAPSHOT + + + org.openhab.addons.bom.test-index + + openHAB Add-ons :: BOM :: Test Index + + + + org.openhab.core.bom + org.openhab.core.bom.test + ${ohc.version} + pom + compile + true + + + org.openhab.core.bom + org.openhab.core.bom.test-index + ${ohc.version} + pom + compile + true + + + + + + + biz.aQute.bnd + bnd-maven-plugin + + + biz.aQute.bnd + bnd-indexer-maven-plugin + + + + + diff --git a/buildci.sh b/buildci.sh new file mode 100755 index 00000000000..3e12e312bbf --- /dev/null +++ b/buildci.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +set -o pipefail # exit build with error when pipes fail + +function prevent_timeout() { + local i=0 + while [[ -e /proc/$1 ]]; do + # print zero width char every 3 minutes while building + if [[ "$i" -eq "180" ]]; then printf %b '\u200b'; i=0; else i=$((i+1)); fi + sleep 1 + done +} + +function print_reactor_summary() { + sed -ne '/\[INFO\] Reactor Summary.*:/,$ p' "$1" | sed 's/\[INFO\] //' +} + +function mvnp() { + local command=(mvn $@) + exec "${command[@]}" 2>&1 | # execute, redirect stderr to stdout + stdbuf -o0 grep -vE "Download(ed|ing) from [a-z.]+: https:" | # filter out downloads + tee .build.log | # write output to log + stdbuf -oL grep -aE '^\[INFO\] Building .+ \[.+\]$' | # filter progress + stdbuf -o0 sed -uE 's/^\[INFO\] Building (.*[^ ])[ ]+\[([0-9]+\/[0-9]+)\]$/\2| \1/' | # prefix project name with progress + stdbuf -o0 sed -e :a -e 's/^.\{1,6\}|/ &/;ta' & # right align progress with padding + local pid=$! + prevent_timeout ${pid} & + wait ${pid} +} + +COMMITS=${1:-"master...HEAD"} + +# Determine if this is a single changed addon -> Perform build with tests + integration tests and all SAT checks +CHANGED_BUNDLE_DIR=`git diff --dirstat=files,0 ${COMMITS} bundles/ | sed 's/^[ 0-9.]\+% bundles\///g' | grep -o -P "^([^/]*)" | uniq` +# Determine if this is a single changed itest -> Perform build with tests + integration tests and all SAT checks +# for this we have to remove '.tests' from the folder name. +CHANGED_ITEST_DIR=`git diff --dirstat=files,0 ${COMMITS} itests/ | sed 's/^[ 0-9.]\+% itests\///g' | sed 's/\.tests\///g' | uniq` +CDIR=`pwd` + +# if a bundle and (optionally the linked itests) where changed build the module and its tests +if [[ ! -z "$CHANGED_BUNDLE_DIR" && -e "bundles/$CHANGED_BUNDLE_DIR" && ( "$CHANGED_BUNDLE_DIR" == "$CHANGED_ITEST_DIR" || -z "$CHANGED_ITEST_DIR" ) ]]; then + CHANGED_DIR="$CHANGED_BUNDLE_DIR" +fi + +# if no bundle was changed but only itests +if [[ -z "$CHANGED_BUNDLE_DIR" ]] && [[ -e "bundles/$CHANGED_ITEST_DIR" ]]; then + CHANGED_DIR="$CHANGED_ITEST_DIR" +fi + +if [[ ! -z "$CHANGED_DIR" ]] && [[ -e "bundles/$CHANGED_DIR" ]]; then + echo "Single addon pull request: Building $CHANGED_DIR" + echo "MAVEN_OPTS='-Xms1g -Xmx2g -Dorg.slf4j.simpleLogger.log.org.openhab.tools.analysis.report.ReportUtility=DEBUG -Dorg.slf4j.simpleLogger.defaultLogLevel=WARN'" > ~/.mavenrc + ARTIFACT_ID=$(mvn -f bundles/${CHANGED_DIR}/pom.xml help:evaluate -Dexpression=project.artifactId -q -DforceStdout) + mvn clean install -B -am -pl ":$ARTIFACT_ID" 2>&1 | + stdbuf -o0 grep -vE "Download(ed|ing) from [a-z.]+: https:" | # Filter out Download(s) + stdbuf -o0 grep -v "target/code-analysis" | # filter out some debug code from reporting utility + tee ${CDIR}/.build.log + if [[ $? -ne 0 ]]; then + exit 1 + fi + + # add the postfix to make sure we actually find the correct itest + if [[ -e "itests/$CHANGED_DIR.tests" ]]; then + echo "Single addon pull request: Building itest $CHANGED_DIR" + cd "itests/$CHANGED_DIR.tests" + mvn clean install -B 2>&1 | + stdbuf -o0 grep -vE "Download(ed|ing) from [a-z.]+: https:" | # Filter out Download(s) + stdbuf -o0 grep -v "target/code-analysis" | # filter out some debug code from reporting utility + tee -a ${CDIR}/.build.log + if [[ $? -ne 0 ]]; then + exit 1 + fi + fi +else + echo "Build all" + echo "MAVEN_OPTS='-Xms1g -Xmx2g -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn'" > ~/.mavenrc + mvnp clean install -B -DskipChecks=true + if [[ $? -eq 0 ]]; then + print_reactor_summary .build.log + else + tail -n 1000 .build.log + exit 1 + fi +fi diff --git a/bundles/archetype-settings.xml b/bundles/archetype-settings.xml new file mode 100644 index 00000000000..11fe4bae305 --- /dev/null +++ b/bundles/archetype-settings.xml @@ -0,0 +1,20 @@ + + + + + openHAB-snapshots + + + archetype + https://openhab.jfrog.io/openhab/libs-snapshot + + + + + + openHAB-snapshots + + diff --git a/bundles/create_openhab_binding_skeleton.cmd b/bundles/create_openhab_binding_skeleton.cmd new file mode 100644 index 00000000000..7660d31d539 --- /dev/null +++ b/bundles/create_openhab_binding_skeleton.cmd @@ -0,0 +1,40 @@ +@echo off + +SETLOCAL +SET ARGC=0 + +FOR %%x IN (%*) DO SET /A ARGC+=1 + +IF %ARGC% NEQ 3 ( + echo Usage: %0 BindingIdInCamelCase Author GithubUser + exit /B 1 +) + +SET OpenhabVersion="3.0.0-SNAPSHOT" + +SET BindingIdInCamelCase=%~1 +SET BindingIdInLowerCase=%BindingIdInCamelCase% +SET Author=%~2 +SET GithubUser=%~3 + +call :LoCase BindingIdInLowerCase + +call mvn -s archetype-settings.xml archetype:generate -N -DarchetypeGroupId=org.openhab.core.tools.archetypes -DarchetypeArtifactId=org.openhab.core.tools.archetypes.binding -DarchetypeVersion=%OpenhabVersion% -DgroupId=org.openhab.binding -DartifactId=org.openhab.binding.%BindingIdInLowerCase% -Dpackage=org.openhab.binding.%BindingIdInLowerCase% -Dversion=%OpenhabVersion% -DbindingId=%BindingIdInLowerCase% -DbindingIdCamelCase=%BindingIdInCamelCase% -DvendorName=openHAB -Dnamespace=org.openhab -Dauthor="%Author%" -DgithubUser="%GithubUser%" + +COPY ..\src\etc\NOTICE org.openhab.binding.%BindingIdInLowerCase%\ + +(SET BindingIdInLowerCase=) +(SET BindingIdInCamelCase=) +(SET Author=) +(SET GithubUser=) + +GOTO:EOF + + +:LoCase +:: Subroutine to convert a variable VALUE to all lower case. +:: The argument for this subroutine is the variable NAME. +FOR %%i IN ("A=a" "B=b" "C=c" "D=d" "E=e" "F=f" "G=g" "H=h" "I=i" "J=j" "K=k" "L=l" "M=m" "N=n" "O=o" "P=p" "Q=q" "R=r" "S=s" "T=t" "U=u" "V=v" "W=w" "X=x" "Y=y" "Z=z") DO CALL SET "%1=%%%1:%%~i%%" +GOTO:EOF + +ENDLOCAL diff --git a/bundles/create_openhab_binding_skeleton.sh b/bundles/create_openhab_binding_skeleton.sh new file mode 100755 index 00000000000..cabe1bec881 --- /dev/null +++ b/bundles/create_openhab_binding_skeleton.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +[ $# -lt 3 ] && { echo "Usage: $0 "; exit 1; } + +openHABVersion=3.0.0-SNAPSHOT + +camelcaseId=$1 +id=`echo $camelcaseId | tr '[:upper:]' '[:lower:]'` + +author=$2 +githubUser=$3 + +mvn -s archetype-settings.xml archetype:generate -N \ + -DarchetypeGroupId=org.openhab.core.tools.archetypes \ + -DarchetypeArtifactId=org.openhab.core.tools.archetypes.binding \ + -DarchetypeVersion=$openHABVersion \ + -DgroupId=org.openhab.binding \ + -DartifactId=org.openhab.binding.$id \ + -Dpackage=org.openhab.binding.$id \ + -Dversion=$openHABVersion \ + -DbindingId=$id \ + -DbindingIdCamelCase=$camelcaseId \ + -DvendorName=openHAB \ + -Dnamespace=org.openhab \ + -Dauthor="$author" \ + -DgithubUser="$githubUser" + +directory="org.openhab.binding.$id/" + +cp ../src/etc/NOTICE "$directory" + diff --git a/bundles/org.openhab.binding.nest/.classpath b/bundles/org.openhab.binding.nest/.classpath new file mode 100644 index 00000000000..dd0d0b001a0 --- /dev/null +++ b/bundles/org.openhab.binding.nest/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.nest/.project b/bundles/org.openhab.binding.nest/.project new file mode 100644 index 00000000000..b1767cbf983 --- /dev/null +++ b/bundles/org.openhab.binding.nest/.project @@ -0,0 +1,23 @@ + + + org.openhab.binding.nest + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/bundles/org.openhab.binding.nest/NOTICE b/bundles/org.openhab.binding.nest/NOTICE new file mode 100644 index 00000000000..38d625e3492 --- /dev/null +++ b/bundles/org.openhab.binding.nest/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.nest/README.md b/bundles/org.openhab.binding.nest/README.md new file mode 100644 index 00000000000..627b2058f9d --- /dev/null +++ b/bundles/org.openhab.binding.nest/README.md @@ -0,0 +1,260 @@ +# Nest Binding + +The Nest binding integrates devices by [Nest](https://nest.com) using the [Nest API](https://developers.nest.com/documentation/cloud/get-started) (REST). + +Because the Nest API runs on Nest's servers a connection with the Internet is required for sending and receiving information. +The binding uses HTTPS to connect to the Nest API using ports 443 and 9553. Make sure outbound connections to these ports are not blocked by a firewall. + +> Note: This binding can only be used with Nest devices if you have an existing Nest developer account signed up for the Works with Nest (WWN) program. +New integrations using the WWN program are no longer accepted because WWN is being retired. +To keep using this binding do **NOT** migrate your Nest Account to a Google Account. +For more information see [What's happening at Nest?](https://nest.com/whats-happening/). + +## Supported Things + +The table below lists the Nest binding thing types: + +| Things | Description | Thing Type | +|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|----------------| +| Nest Account | An account for using the Nest REST API | account | +| Nest Cam (Indoor, IQ, Outdoor), Dropcam | A Nest Cam registered with your account | camera | +| Nest Protect | The smoke detector/Nest Protect for the account | smoke_detector | +| Structure | The Nest structure defines the house the account has setup on Nest. You will only have more than one structure if you have more than one house | structure | +| Nest Thermostat (E) | A Thermostat to control the various aspects of the house's HVAC system | thermostat | + +## Authorization + +The Nest API uses OAuth for authorization. +Therefore the binding needs some authorization parameters before it can access your Nest account via the Nest API. + +To get these authorization parameters you first need to sign up as a [Nest Developer](https://developer.nest.com) and [register a new Product](https://developer.nest.com/products/new) (free and instant). + +While registering a new Product (on the Product Details page) make sure to: + +* Leave both "OAuth Redirect URI" fields empty to enable PIN-based authorization. +* Grant all the permissions you intend to use. When in doubt, enable the permission because the binding needs to be reauthorized when permissions change at a later time. + +After creating the Product, your browser shows the Product Overview page. +This page contains the **Product ID** and **Product Secret** authorization parameters that are used by the binding. +Take note of both parameters or keep this page open in a browser tab. +Now copy and paste the "Authorization URL" in a new browser tab. +Accept the permissions and you will be presented the **Pincode** authorization parameter that is also used by the binding. + +You can return to the Product Overview page at a later time by opening the [Products](https://console.developers.nest.com/products) page and selecting your Product. + +## Discovery + +The binding will discover all Nest Things from your account when you add and configure a "Nest Account" Thing. +See the Authorization paragraph above for details on how to obtain the Product ID, Product Secret and Pincode configuration parameters. + +Once the binding has successfully authorized with the Nest API, it obtains an Access Token using the Pincode. +The configured Pincode is cleared because it can only be used once. +The obtained Access Token is saved as an advanced configuration parameter of the "Nest Account". + +You can reuse an Access Token for authorization but not the Pincode. +A new Pincode can again be generated via the "Authorization URL" (see Authorization paragraph). + +## Channels + +### Account Channels + +The account Thing Type does not have any channels. + +### Camera Channels + +**Camera group channels** + +Information about the camera. + +| Channel Type ID | Item Type | Description | Read Write | +|-----------------------|-----------|---------------------------------------------------|:----------:| +| app_url | String | The app URL to see the camera | R | +| audio_input_enabled | Switch | If the audio input is currently enabled | R | +| last_online_change | DateTime | Timestamp of the last online status change | R | +| public_share_enabled | Switch | If public sharing is currently enabled | R | +| public_share_url | String | The URL to see the public share of the camera | R | +| snapshot_url | String | The URL to use for a snapshot of the video stream | R | +| streaming | Switch | If the camera is currently streaming | R/W | +| video_history_enabled | Switch | If the video history is currently enabled | R | +| web_url | String | The web URL to see the camera | R | + +**Last event group channels** + +Information about the last camera event (requires Nest Aware subscription). + +| Channel Type ID | Item Type | Description | Read Write | +|--------------------|-----------|------------------------------------------------------------------------------------|:----------:| +| activity_zones | String | Identifiers for activity zones that detected the event (comma separated) | R | +| animated_image_url | String | The URL showing an animated image for the camera event | R | +| app_url | String | The app URL for the camera event, allows you to see the camera event in an app | R | +| end_time | DateTime | Timestamp when the camera event ended | R | +| has_motion | Switch | If motion was detected in the camera event | R | +| has_person | Switch | If a person was detected in the camera event | R | +| has_sound | Switch | If sound was detected in the camera event | R | +| image_url | String | The URL showing an image for the camera event | R | +| start_time | DateTime | Timestamp when the camera event started | R | +| urls_expire_time | DateTime | Timestamp when the camera event URLs expire | R | +| web_url | String | The web URL for the camera event, allows you to see the camera event in a web page | R | + +### Smoke Detector Channels + +| Channel Type ID | Item Type | Description | Read Write | +|-----------------------|-----------|-----------------------------------------------------------------------------------|:----------:| +| co_alarm_state | String | The carbon monoxide alarm state of the Nest Protect (OK, EMERGENCY, WARNING) | R | +| last_connection | DateTime | Timestamp of the last successful interaction with Nest | R | +| last_manual_test_time | DateTime | Timestamp of the last successful manual test | R | +| low_battery | Switch | Reports whether the battery of the Nest protect is low (if it is battery powered) | R | +| manual_test_active | Switch | Manual test active at the moment | R | +| smoke_alarm_state | String | The smoke alarm state of the Nest Protect (OK, EMERGENCY, WARNING) | R | +| ui_color_state | String | The current color of the ring on the smoke detector (GRAY, GREEN, YELLOW, RED) | R | + +### Structure Channels + +| Channel Type ID | Item Type | Description | Read Write | +|------------------------------|-----------|--------------------------------------------------------------------------------------------------------|:----------:| +| away | String | Away state of the structure (HOME, AWAY) | R/W | +| country_code | String | Country code of the structure ([ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)) | R | +| co_alarm_state | String | Carbon Monoxide alarm state (OK, EMERGENCY, WARNING) | R | +| eta_begin | DateTime | Estimated time of arrival at home, will setup the heat to turn on and be warm | R | +| peak_period_end_time | DateTime | Peak period end for the Rush Hour Rewards program | R | +| peak_period_start_time | DateTime | Peak period start for the Rush Hour Rewards program | R | +| postal_code | String | Postal code of the structure | R | +| rush_hour_rewards_enrollment | Switch | If rush hour rewards system is enabled or not | R | +| security_state | String | Security state of the structure (OK, DETER) | R | +| smoke_alarm_state | String | Smoke alarm state (OK, EMERGENCY, WARNING) | R | +| time_zone | String | The time zone for the structure ([IANA time zone format](https://www.iana.org/time-zones)) | R | + +### Thermostat Channels + +| Channel Type ID | Item Type | Description | Read Write | +|-----------------------------|----------------------|----------------------------------------------------------------------------------------|:----------:| +| can_cool | Switch | If the thermostat can actually turn on cooling | R | +| can_heat | Switch | If the thermostat can actually turn on heating | R | +| eco_max_set_point | Number:Temperature | The eco range max set point temperature | R | +| eco_min_set_point | Number:Temperature | The eco range min set point temperature | R | +| fan_timer_active | Switch | If the fan timer is engaged | R/W | +| fan_timer_duration | Number:Time | Length of time that the fan is set to run (15, 30, 45, 60, 120, 240, 480, 960 minutes) | R/W | +| fan_timer_timeout | DateTime | Timestamp when the fan stops running | R | +| has_fan | Switch | If the thermostat can control the fan | R | +| has_leaf | Switch | If the thermostat is currently in a leaf mode | R | +| humidity | Number:Dimensionless | Indicates the current relative humidity | R | +| last_connection | DateTime | Timestamp of the last successful interaction with Nest | R | +| locked | Switch | If the thermostat has the temperature locked to only be within a set range | R | +| locked_max_set_point | Number:Temperature | The locked range max set point temperature | R | +| locked_min_set_point | Number:Temperature | The locked range min set point temperature | R | +| max_set_point | Number:Temperature | The max set point temperature | R/W | +| min_set_point | Number:Temperature | The min set point temperature | R/W | +| mode | String | Current mode of the Nest thermostat (HEAT, COOL, HEAT_COOL, ECO, OFF) | R/W | +| previous_mode | String | The previous mode of the Nest thermostat (HEAT, COOL, HEAT_COOL, ECO, OFF) | R | +| state | String | The active state of the Nest thermostat (HEATING, COOLING, OFF) | R | +| temperature | Number:Temperature | Current temperature | R | +| time_to_target | Number:Time | Time left to the target temperature approximately | R | +| set_point | Number:Temperature | The set point temperature | R/W | +| sunlight_correction_active | Switch | If sunlight correction is active | R | +| sunlight_correction_enabled | Switch | If sunlight correction is enabled | R | +| using_emergency_heat | Switch | If the system is currently using emergency heat | R | + +Note that the Nest API rounds Thermostat values so they will differ from what shows up in the Nest App. +The Nest API applies the following rounding: + +* degrees Celsius to 0.5 degrees +* degrees Fahrenheit to whole degrees +* humidity to 5% + +## Example + +You can use the discovery functionality of the binding to obtain the deviceId and structureId values for defining Nest things in files. + +Another way to get the deviceId and structureId values is by querying the Nest API yourself. First [obtain an Access Token](https://developers.nest.com/documentation/cloud/sample-code-auth) (or use the Access Token obtained by the binding). +Then use it with one of the [API Read Examples](https://developers.nest.com/documentation/cloud/how-to-read-data). + +### demo.things: + +``` +Bridge nest:account:demo_account [ productId="8fdf9885-ca07-4252-1aa3-f3d5ca9589e0", productSecret="QITLR3iyUlWaj9dbvCxsCKp4f", accessToken="c.6rse1xtRk2UANErcY0XazaqPHgbvSSB6owOrbZrZ6IXrmqhsr9QTmcfaiLX1l0ULvlI5xLp01xmKeiojHqozLQbNM8yfITj1LSdK28zsUft1aKKH2mDlOeoqZKBdVIsxyZk4orH0AvKEZ5aY" ] { + camera fish_cam [ deviceId="qw0NNE8ruxA9AGJkTaFH3KeUiJaONWKiH9Gh3RwwhHClonIexTtufQ" ] + smoke_detector hallway_smoke [ deviceId="Tzvibaa3lLKnHpvpi9OQeCI_z5rfkBAV" ] + structure home [ structureId="20wKjydArmMV3kOluTA7JRcZg8HKBzTR-G_2nRXuIN1Bd6laGLOJQw" ] + thermostat living_thermostat [ deviceId="ZqAKzSv6TO6PjBnOCXf9LSI_z5rfkBAV" ] +} +``` + +### demo.items: + + +``` +/* Camera */ +String Cam_App_URL "App URL [%s]" { channel="nest:camera:demo_account:fish_cam:camera#app_url" } +Switch Cam_Audio_Input_Enabled "Audio Input Enabled" { channel="nest:camera:demo_account:fish_cam:camera#audio_input_enabled" } +DateTime Cam_Last_Online_Change "Last Online Change [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:camera:demo_account:fish_cam:camera#last_online_change" } +String Cam_Snapshot_URL "Snapshot URL [%s]" { channel="nest:camera:demo_account:fish_cam:camera#snapshot_url" } +Switch Cam_Streaming "Streaming" { channel="nest:camera:demo_account:fish_cam:camera#streaming" } +Switch Cam_Public_Share_Enabled "Public Share Enabled" { channel="nest:camera:demo_account:fish_cam:camera#public_share_enabled" } +String Cam_Public_Share_URL "Public Share URL [%s]" { channel="nest:camera:demo_account:fish_cam:camera#public_share_url" } +Switch Cam_Video_History_Enabled "Video History Enabled" { channel="nest:camera:demo_account:fish_cam:camera#video_history_enabled" } +String Cam_Web_URL "Web URL [%s]" { channel="nest:camera:demo_account:fish_cam:camera#web_url" } +String Cam_LE_Activity_Zones "Last Event Activity Zones [%s]" { channel="nest:camera:demo_account:fish_cam:last_event#activity_zones" } +String Cam_LE_Animated_Image_URL "Last Event Animated Image URL [%s]" { channel="nest:camera:demo_account:fish_cam:last_event#animated_image_url" } +String Cam_LE_App_URL "Last Event App URL [%s]" { channel="nest:camera:demo_account:fish_cam:last_event#app_url" } +DateTime Cam_LE_End_Time "Last Event End Time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:camera:demo_account:fish_cam:last_event#end_time" } +Switch Cam_LE_Has_Motion "Last Event Has Motion" { channel="nest:camera:demo_account:fish_cam:last_event#has_motion" } +Switch Cam_LE_Has_Person "Last Event Has Person" { channel="nest:camera:demo_account:fish_cam:last_event#has_person" } +Switch Cam_LE_Has_Sound "Last Event Has Sound" { channel="nest:camera:demo_account:fish_cam:last_event#has_sound" } +String Cam_LE_Image_URL "Last Event Image URL [%s]" { channel="nest:camera:demo_account:fish_cam:last_event#image_url" } +DateTime Cam_LE_Start_Time "Last Event Start Time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:camera:demo_account:fish_cam:last_event#start_time" } +DateTime Cam_LE_URLs_Expire_Time "Last Event URLs Expire Time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:camera:demo_account:fish_cam:last_event#urls_expire_time" } +String Cam_LE_Web_URL "Last Event Web URL [%s]" { channel="nest:camera:demo_account:fish_cam:last_event#web_url" } + +/* Smoke Detector */ +String Smoke_CO_Alarm "CO Alarm [%s]" { channel="nest:smoke_detector:demo_account:hallway_smoke:co_alarm_state" } +Switch Smoke_Battery_Low "Battery Low" { channel="nest:smoke_detector:demo_account:hallway_smoke:low_battery" } +Switch Smoke_Manual_Test "Manual Test" { channel="nest:smoke_detector:demo_account:hallway_smoke:manual_test_active" } +DateTime Smoke_Last_Connection "Last Connection [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:smoke_detector:demo_account:hallway_smoke:last_connection" } +DateTime Smoke_Last_Manual_Test "Last Manual Test [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:smoke_detector:demo_account:hallway_smoke:last_manual_test_time" } +String Smoke_Smoke_Alarm "Smoke Alarm [%s]" { channel="nest:smoke_detector:demo_account:hallway_smoke:smoke_alarm_state" } +String Smoke_UI_Color "UI Color [%s]" { channel="nest:smoke_detector:demo_account:hallway_smoke:ui_color_state" } + +/* Thermostat */ +Switch Thermostat_Can_Cool "Can Cool" { channel="nest:thermostat:demo_account:living_thermostat:can_cool" } +Switch Thermostat_Can_Heat "Can Heat" { channel="nest:thermostat:demo_account:living_thermostat:can_heat" } +Number:Temperature Therm_EMaxSP "Eco Max Set Point [%.1f %unit%]" { channel="nest:thermostat:demo_account:living_thermostat:eco_max_set_point" } +Number:Temperature Therm_EMinSP "Eco Min Set Point [%.1f %unit%]" { channel="nest:thermostat:demo_account:living_thermostat:eco_min_set_point" } +Switch Thermostat_FT_Active "Fan Timer Active" { channel="nest:thermostat:demo_account:living_thermostat:fan_timer_active" } +Number:Time Thermostat_FT_Duration "Fan Timer Duration [%d %unit%]" { channel="nest:thermostat:demo_account:living_thermostat:fan_timer_duration" } +DateTime Thermostat_FT_Timeout "Fan Timer Timeout [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:thermostat:demo_account:living_thermostat:fan_timer_timeout" } +Switch Thermostat_Has_Fan "Has Fan" { channel="nest:thermostat:demo_account:living_thermostat:has_fan" } +Switch Thermostat_Has_Leaf "Has Leaf" { channel="nest:thermostat:demo_account:living_thermostat:has_leaf" } +Number:Dimensionless Therm_Hum "Humidity [%.1f %unit%]" { channel="nest:thermostat:demo_account:living_thermostat:humidity" } +DateTime Thermostat_Last_Conn "Last Connection [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:thermostat:demo_account:living_thermostat:last_connection" } +Switch Thermostat_Locked "Locked" { channel="nest:thermostat:demo_account:living_thermostat:locked" } +Number:Temperature Therm_LMaxSP "Locked Max Set Point [%.1f %unit%]" { channel="nest:thermostat:demo_account:living_thermostat:locked_max_set_point" } +Number:Temperature Therm_LMinSP "Locked Min Set Point [%.1f %unit%]" { channel="nest:thermostat:demo_account:living_thermostat:locked_min_set_point" } +Number:Temperature Therm_Max_SP "Max Set Point [%.1f %unit%]" { channel="nest:thermostat:demo_account:living_thermostat:max_set_point" } +Number:Temperature Therm_Min_SP "Min Set Point [%.1f %unit%]" { channel="nest:thermostat:demo_account:living_thermostat:min_set_point" } +String Thermostat_Mode "Mode [%s]" { channel="nest:thermostat:demo_account:living_thermostat:mode" } +String Thermostat_Previous_Mode "Previous Mode [%s]" { channel="nest:thermostat:demo_account:living_thermostat:previous_mode" } +String Thermostat_State "State [%s]" { channel="nest:thermostat:demo_account:living_thermostat:state" } +Number:Temperature Thermostat_SP "Set Point [%.1f %unit%]" { channel="nest:thermostat:demo_account:living_thermostat:set_point" } +Switch Thermostat_Sunlight_CA "Sunlight Correction Active" { channel="nest:thermostat:demo_account:living_thermostat:sunlight_correction_active" } +Switch Thermostat_Sunlight_CE "Sunlight Correction Enabled" { channel="nest:thermostat:demo_account:living_thermostat:sunlight_correction_enabled" } +Number:Temperature Therm_Temp "Temperature [%.1f %unit%]" { channel="nest:thermostat:demo_account:living_thermostat:temperature" } +Number:Time Therm_Time_To_Target "Time To Target [%d %unit%]" { channel="nest:thermostat:demo_account:living_thermostat:time_to_target" } +Switch Thermostat_Using_Em_Heat "Using Emergency Heat" { channel="nest:thermostat:demo_account:living_thermostat:using_emergency_heat" } + +/* Structure */ +String Home_Away "Away [%s]" { channel="nest:structure:demo_account:home:away" } +String Home_Country_Code "Country Code [%s]" { channel="nest:structure:demo_account:home:country_code" } +String Home_CO_Alarm_State "CO Alarm State [%s]" { channel="nest:structure:demo_account:home:co_alarm_state" } +DateTime Home_ETA "ETA [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:structure:demo_account:home:eta_begin" } +DateTime Home_PP_End_Time "PP End Time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:structure:demo_account:home:peak_period_end_time" } +DateTime Home_PP_Start_Time "PP Start Time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:structure:demo_account:home:peak_period_start_time" } +String Home_Postal_Code "Postal Code [%s]" { channel="nest:structure:demo_account:home:postal_code" } +Switch Home_Rush_Hour_Rewards "Rush Hour Rewards" { channel="nest:structure:demo_account:home:rush_hour_rewards_enrollment" } +String Home_Security_State "Security State [%s]" { channel="nest:structure:demo_account:home:security_state" } +String Home_Smoke_Alarm_State "Smoke Alarm State [%s]" { channel="nest:structure:demo_account:home:smoke_alarm_state" } +String Home_Time_Zone "Time Zone [%s]" { channel="nest:structure:demo_account:home:time_zone" } +``` + +## Attribution + +This documentation contains parts written by John Cocula which were copied from the 1.0 Nest binding. diff --git a/bundles/org.openhab.binding.nest/pom.xml b/bundles/org.openhab.binding.nest/pom.xml new file mode 100644 index 00000000000..6909b1a294f --- /dev/null +++ b/bundles/org.openhab.binding.nest/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.0.0-SNAPSHOT + + + org.openhab.binding.nest + + openHAB Add-ons :: Bundles :: Nest Binding + + diff --git a/bundles/org.openhab.binding.nest/src/main/feature/feature.xml b/bundles/org.openhab.binding.nest/src/main/feature/feature.xml new file mode 100644 index 00000000000..4da2a00a8dc --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.nest/${project.version} + + 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/NestBindingConstants.java new file mode 100644 index 00000000000..99e098b1eda --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestBindingConstants.java @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2010-2020 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 java.time.Duration; + +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. + * + * @author David Bennett - Initial contribution + */ +@NonNullByDefault +public class NestBindingConstants { + + public static final String BINDING_ID = "nest"; + + /** The URL to use to connect to Nest with. */ + public static final String NEST_URL = "https://developer-api.nest.com"; + + /** The URL to get the access token when talking to Nest. */ + public static final String NEST_ACCESS_TOKEN_URL = "https://api.home.nest.com/oauth2/access_token"; + + /** The path to set values on the thermostat when talking to Nest. */ + public static final String NEST_THERMOSTAT_UPDATE_PATH = "/devices/thermostats/"; + + /** The path to set values on the structure when talking to Nest. */ + public static final String NEST_STRUCTURE_UPDATE_PATH = "/structures/"; + + /** The path to set values on the camera when talking to Nest. */ + public static final String NEST_CAMERA_UPDATE_PATH = "/devices/cameras/"; + + /** The path to set values on the camera when talking to Nest. */ + public static final String NEST_SMOKE_ALARM_UPDATE_PATH = "/devices/smoke_co_alarms/"; + + /** The JSON content type used when talking to Nest. */ + public static final String JSON_CONTENT_TYPE = "application/json"; + + /** To keep the streaming REST connection alive Nest sends every 30 seconds a message. */ + public static final long KEEP_ALIVE_MILLIS = Duration.ofSeconds(30).toMillis(); + + /** To avoid API throttling errors (429 Too Many Requests) Nest recommends making at most one call per minute. */ + 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"); + + // List of all channel group prefixes + public static final String CHANNEL_GROUP_CAMERA_PREFIX = "camera#"; + public static final String CHANNEL_GROUP_LAST_EVENT_PREFIX = "last_event#"; + + // List of all Channel IDs + // read only channels (common) + public static final String CHANNEL_LAST_CONNECTION = "last_connection"; + + // read/write channels (thermostat) + public static final String CHANNEL_MODE = "mode"; + public static final String CHANNEL_SET_POINT = "set_point"; + public static final String CHANNEL_MAX_SET_POINT = "max_set_point"; + public static final String CHANNEL_MIN_SET_POINT = "min_set_point"; + public static final String CHANNEL_FAN_TIMER_ACTIVE = "fan_timer_active"; + public static final String CHANNEL_FAN_TIMER_DURATION = "fan_timer_duration"; + + // read only channels (thermostat) + public static final String CHANNEL_ECO_MAX_SET_POINT = "eco_max_set_point"; + public static final String CHANNEL_ECO_MIN_SET_POINT = "eco_min_set_point"; + public static final String CHANNEL_LOCKED = "locked"; + public static final String CHANNEL_LOCKED_MAX_SET_POINT = "locked_max_set_point"; + public static final String CHANNEL_LOCKED_MIN_SET_POINT = "locked_min_set_point"; + public static final String CHANNEL_TEMPERATURE = "temperature"; + public static final String CHANNEL_HUMIDITY = "humidity"; + public static final String CHANNEL_PREVIOUS_MODE = "previous_mode"; + public static final String CHANNEL_STATE = "state"; + public static final String CHANNEL_CAN_HEAT = "can_heat"; + public static final String CHANNEL_CAN_COOL = "can_cool"; + public static final String CHANNEL_FAN_TIMER_TIMEOUT = "fan_timer_timeout"; + public static final String CHANNEL_HAS_FAN = "has_fan"; + public static final String CHANNEL_HAS_LEAF = "has_leaf"; + public static final String CHANNEL_SUNLIGHT_CORRECTION_ENABLED = "sunlight_correction_enabled"; + public static final String CHANNEL_SUNLIGHT_CORRECTION_ACTIVE = "sunlight_correction_active"; + public static final String CHANNEL_TIME_TO_TARGET = "time_to_target"; + public static final String CHANNEL_USING_EMERGENCY_HEAT = "using_emergency_heat"; + + // read/write channels (camera) + public static final String CHANNEL_CAMERA_STREAMING = "camera#streaming"; + + // read only channels (camera) + public static final String CHANNEL_CAMERA_AUDIO_INPUT_ENABLED = "camera#audio_input_enabled"; + public static final String CHANNEL_CAMERA_VIDEO_HISTORY_ENABLED = "camera#video_history_enabled"; + public static final String CHANNEL_CAMERA_WEB_URL = "camera#web_url"; + public static final String CHANNEL_CAMERA_APP_URL = "camera#app_url"; + public static final String CHANNEL_CAMERA_PUBLIC_SHARE_ENABLED = "camera#public_share_enabled"; + public static final String CHANNEL_CAMERA_PUBLIC_SHARE_URL = "camera#public_share_url"; + public static final String CHANNEL_CAMERA_SNAPSHOT_URL = "camera#snapshot_url"; + public static final String CHANNEL_CAMERA_LAST_ONLINE_CHANGE = "camera#last_online_change"; + + public static final String CHANNEL_LAST_EVENT_HAS_SOUND = "last_event#has_sound"; + public static final String CHANNEL_LAST_EVENT_HAS_MOTION = "last_event#has_motion"; + public static final String CHANNEL_LAST_EVENT_HAS_PERSON = "last_event#has_person"; + public static final String CHANNEL_LAST_EVENT_START_TIME = "last_event#start_time"; + public static final String CHANNEL_LAST_EVENT_END_TIME = "last_event#end_time"; + public static final String CHANNEL_LAST_EVENT_URLS_EXPIRE_TIME = "last_event#urls_expire_time"; + public static final String CHANNEL_LAST_EVENT_WEB_URL = "last_event#web_url"; + public static final String CHANNEL_LAST_EVENT_APP_URL = "last_event#app_url"; + public static final String CHANNEL_LAST_EVENT_IMAGE_URL = "last_event#image_url"; + public static final String CHANNEL_LAST_EVENT_ANIMATED_IMAGE_URL = "last_event#animated_image_url"; + public static final String CHANNEL_LAST_EVENT_ACTIVITY_ZONES = "last_event#activity_zones"; + + // read/write channels (smoke detector) + + // read only channels (smoke detector) + public static final String CHANNEL_UI_COLOR_STATE = "ui_color_state"; + public static final String CHANNEL_LOW_BATTERY = "low_battery"; + public static final String CHANNEL_CO_ALARM_STATE = "co_alarm_state"; // Also in structure + public static final String CHANNEL_SMOKE_ALARM_STATE = "smoke_alarm_state"; // Also in structure + public static final String CHANNEL_MANUAL_TEST_ACTIVE = "manual_test_active"; + public static final String CHANNEL_LAST_MANUAL_TEST_TIME = "last_manual_test_time"; + + // read/write channel (structure) + public static final String CHANNEL_AWAY = "away"; + + // read only channels (structure) + public static final String CHANNEL_COUNTRY_CODE = "country_code"; + public static final String CHANNEL_POSTAL_CODE = "postal_code"; + public static final String CHANNEL_PEAK_PERIOD_START_TIME = "peak_period_start_time"; + public static final String CHANNEL_PEAK_PERIOD_END_TIME = "peak_period_end_time"; + public static final String CHANNEL_TIME_ZONE = "time_zone"; + public static final String CHANNEL_ETA_BEGIN = "eta_begin"; + public static final String CHANNEL_RUSH_HOUR_REWARDS_ENROLLMENT = "rush_hour_rewards_enrollment"; + public static final String CHANNEL_SECURITY_STATE = "security_state"; +} 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 new file mode 100644 index 00000000000..0233e4e9fb8 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestHandlerFactory.java @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2010-2020 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/NestUtils.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestUtils.java new file mode 100644 index 00000000000..e3bca80e52e --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/NestUtils.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2010-2020 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 java.io.Reader; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * Utility class for sharing utility methods between objects. + * + * @author Wouter Born - Initial contribution + */ +@NonNullByDefault +public final class NestUtils { + + 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() { + // hidden utility class constructor + } + + public static T fromJson(String json, Class dataClass) { + return GSON.fromJson(json, dataClass); + } + + public static T fromJson(Reader reader, Class dataClass) { + return GSON.fromJson(reader, dataClass); + } + + public static String toJson(Object object) { + return GSON.toJson(object); + } +} 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/config/NestBridgeConfiguration.java new file mode 100644 index 00000000000..f4addfe861d --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestBridgeConfiguration.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2020 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.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. + * + * @author David Bennett - Initial contribution + */ +@NonNullByDefault +public class NestBridgeConfiguration { + public static final String PRODUCT_ID = "productId"; + /** Product ID from the Nest product page. */ + public String productId = ""; + + public static final String PRODUCT_SECRET = "productSecret"; + /** Product secret from the Nest product page. */ + public String productSecret = ""; + + public static final String PINCODE = "pincode"; + /** Product pincode from the Nest authorization page. */ + public @Nullable String pincode; + + public static final String ACCESS_TOKEN = "accessToken"; + /** The access token to use once retrieved from Nest. */ + public @Nullable String accessToken; +} 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/config/NestDeviceConfiguration.java new file mode 100644 index 00000000000..b05978548bd --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestDeviceConfiguration.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2010-2020 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.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The configuration for Nest devices. + * + * @author Wouter Born - Initial contribution + * @author Wouter Born - Add device configuration to allow file based configuration + */ +@NonNullByDefault +public class NestDeviceConfiguration { + 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/config/NestStructureConfiguration.java new file mode 100644 index 00000000000..02eb2122c6b --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/config/NestStructureConfiguration.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2010-2020 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.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The configuration for structures. + * + * @author Wouter Born - Initial contribution + * @author Wouter Born - Add device configuration to allow file based configuration + */ +@NonNullByDefault +public class NestStructureConfiguration { + 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/AccessTokenData.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/AccessTokenData.java new file mode 100644 index 00000000000..fd8eee8586b --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/AccessTokenData.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2010-2020 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.data; + +/** + * Deals with the access token data that comes back from Nest when it is requested. + * + * @author David Bennett - Initial contribution + * @author Wouter Born - Add equals and hashCode methods + */ +public class AccessTokenData { + + private String accessToken; + private Long expiresIn; + + public String getAccessToken() { + return accessToken; + } + + public Long getExpiresIn() { + return expiresIn; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AccessTokenData other = (AccessTokenData) obj; + if (accessToken == null) { + if (other.accessToken != null) { + return false; + } + } else if (!accessToken.equals(other.accessToken)) { + return false; + } + if (expiresIn == null) { + if (other.expiresIn != null) { + return false; + } + } else if (!expiresIn.equals(other.expiresIn)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((accessToken == null) ? 0 : accessToken.hashCode()); + result = prime * result + ((expiresIn == null) ? 0 : expiresIn.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AccessTokenData [accessToken=").append(accessToken).append(", expiresIn=").append(expiresIn) + .append("]"); + return builder.toString(); + } +} 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/data/ActivityZone.java new file mode 100644 index 00000000000..c9cb050ee74 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ActivityZone.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2010-2020 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.data; + +/** + * The data for a camera activity zone. + * + * @author David Bennett - Initial contribution + * @author Wouter Born - Extract ActivityZone object from Camera + */ +public class ActivityZone { + + private String name; + private int id; + + public String getName() { + return name; + } + + public int getId() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ActivityZone other = (ActivityZone) obj; + if (id != other.id) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("CameraActivityZone [name=").append(name).append(", id=").append(id).append("]"); + return builder.toString(); + } +} 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/data/BaseNestDevice.java new file mode 100644 index 00000000000..f5853f83be5 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/BaseNestDevice.java @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2010-2020 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.data; + +import java.util.Date; + +/** + * Default properties shared across all Nest devices. + * + * @author David Bennett - Initial contribution + * @author Wouter Born - Add equals and hashCode methods + */ +public class BaseNestDevice implements NestIdentifiable { + + private String deviceId; + private String name; + private String nameLong; + private Date lastConnection; + private Boolean isOnline; + private String softwareVersion; + private String structureId; + + private String whereId; + + @Override + public String getId() { + return deviceId; + } + + public String getName() { + return name; + } + + public String getDeviceId() { + return deviceId; + } + + public Date getLastConnection() { + return lastConnection; + } + + public Boolean isOnline() { + return isOnline; + } + + public String getNameLong() { + return nameLong; + } + + public String getSoftwareVersion() { + return softwareVersion; + } + + public String getStructureId() { + return structureId; + } + + public String getWhereId() { + return whereId; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + BaseNestDevice other = (BaseNestDevice) obj; + if (deviceId == null) { + if (other.deviceId != null) { + return false; + } + } else if (!deviceId.equals(other.deviceId)) { + return false; + } + if (isOnline == null) { + if (other.isOnline != null) { + return false; + } + } else if (!isOnline.equals(other.isOnline)) { + return false; + } + if (lastConnection == null) { + if (other.lastConnection != null) { + return false; + } + } else if (!lastConnection.equals(other.lastConnection)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (nameLong == null) { + if (other.nameLong != null) { + return false; + } + } else if (!nameLong.equals(other.nameLong)) { + return false; + } + if (softwareVersion == null) { + if (other.softwareVersion != null) { + return false; + } + } else if (!softwareVersion.equals(other.softwareVersion)) { + return false; + } + if (structureId == null) { + if (other.structureId != null) { + return false; + } + } else if (!structureId.equals(other.structureId)) { + return false; + } + if (whereId == null) { + if (other.whereId != null) { + return false; + } + } else if (!whereId.equals(other.whereId)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((deviceId == null) ? 0 : deviceId.hashCode()); + result = prime * result + ((isOnline == null) ? 0 : isOnline.hashCode()); + result = prime * result + ((lastConnection == null) ? 0 : lastConnection.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((nameLong == null) ? 0 : nameLong.hashCode()); + result = prime * result + ((softwareVersion == null) ? 0 : softwareVersion.hashCode()); + result = prime * result + ((structureId == null) ? 0 : structureId.hashCode()); + result = prime * result + ((whereId == null) ? 0 : whereId.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("BaseNestDevice [deviceId=").append(deviceId).append(", name=").append(name) + .append(", nameLong=").append(nameLong).append(", lastConnection=").append(lastConnection) + .append(", isOnline=").append(isOnline).append(", softwareVersion=").append(softwareVersion) + .append(", structureId=").append(structureId).append(", whereId=").append(whereId).append("]"); + return builder.toString(); + } +} 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/data/Camera.java new file mode 100644 index 00000000000..90093a1db63 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Camera.java @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2010-2020 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.data; + +import java.util.Date; +import java.util.List; + +/** + * The data for the camera. + * + * @author David Bennett - Initial contribution + * @author Wouter Born - Add equals and hashCode methods + */ +public class Camera extends BaseNestDevice { + + private Boolean isStreaming; + private Boolean isAudioInputEnabled; + private Date lastIsOnlineChange; + private Boolean isVideoHistoryEnabled; + private String webUrl; + private String appUrl; + private Boolean isPublicShareEnabled; + private List activityZones; + private String publicShareUrl; + private String snapshotUrl; + private CameraEvent lastEvent; + + public Boolean isStreaming() { + return isStreaming; + } + + public Boolean isAudioInputEnabled() { + return isAudioInputEnabled; + } + + public Date getLastIsOnlineChange() { + return lastIsOnlineChange; + } + + public Boolean isVideoHistoryEnabled() { + return isVideoHistoryEnabled; + } + + public String getWebUrl() { + return webUrl; + } + + public String getAppUrl() { + return appUrl; + } + + public Boolean isPublicShareEnabled() { + return isPublicShareEnabled; + } + + public List getActivityZones() { + return activityZones; + } + + public String getPublicShareUrl() { + return publicShareUrl; + } + + public String getSnapshotUrl() { + return snapshotUrl; + } + + public CameraEvent getLastEvent() { + return lastEvent; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Camera other = (Camera) obj; + if (activityZones == null) { + if (other.activityZones != null) { + return false; + } + } else if (!activityZones.equals(other.activityZones)) { + return false; + } + if (appUrl == null) { + if (other.appUrl != null) { + return false; + } + } else if (!appUrl.equals(other.appUrl)) { + return false; + } + if (isAudioInputEnabled == null) { + if (other.isAudioInputEnabled != null) { + return false; + } + } else if (!isAudioInputEnabled.equals(other.isAudioInputEnabled)) { + return false; + } + if (isPublicShareEnabled == null) { + if (other.isPublicShareEnabled != null) { + return false; + } + } else if (!isPublicShareEnabled.equals(other.isPublicShareEnabled)) { + return false; + } + if (isStreaming == null) { + if (other.isStreaming != null) { + return false; + } + } else if (!isStreaming.equals(other.isStreaming)) { + return false; + } + if (isVideoHistoryEnabled == null) { + if (other.isVideoHistoryEnabled != null) { + return false; + } + } else if (!isVideoHistoryEnabled.equals(other.isVideoHistoryEnabled)) { + return false; + } + if (lastEvent == null) { + if (other.lastEvent != null) { + return false; + } + } else if (!lastEvent.equals(other.lastEvent)) { + return false; + } + if (lastIsOnlineChange == null) { + if (other.lastIsOnlineChange != null) { + return false; + } + } else if (!lastIsOnlineChange.equals(other.lastIsOnlineChange)) { + return false; + } + if (publicShareUrl == null) { + if (other.publicShareUrl != null) { + return false; + } + } else if (!publicShareUrl.equals(other.publicShareUrl)) { + return false; + } + if (snapshotUrl == null) { + if (other.snapshotUrl != null) { + return false; + } + } else if (!snapshotUrl.equals(other.snapshotUrl)) { + return false; + } + if (webUrl == null) { + if (other.webUrl != null) { + return false; + } + } else if (!webUrl.equals(other.webUrl)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((activityZones == null) ? 0 : activityZones.hashCode()); + result = prime * result + ((appUrl == null) ? 0 : appUrl.hashCode()); + result = prime * result + ((isAudioInputEnabled == null) ? 0 : isAudioInputEnabled.hashCode()); + result = prime * result + ((isPublicShareEnabled == null) ? 0 : isPublicShareEnabled.hashCode()); + result = prime * result + ((isStreaming == null) ? 0 : isStreaming.hashCode()); + result = prime * result + ((isVideoHistoryEnabled == null) ? 0 : isVideoHistoryEnabled.hashCode()); + result = prime * result + ((lastEvent == null) ? 0 : lastEvent.hashCode()); + result = prime * result + ((lastIsOnlineChange == null) ? 0 : lastIsOnlineChange.hashCode()); + result = prime * result + ((publicShareUrl == null) ? 0 : publicShareUrl.hashCode()); + result = prime * result + ((snapshotUrl == null) ? 0 : snapshotUrl.hashCode()); + result = prime * result + ((webUrl == null) ? 0 : webUrl.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Camera [isStreaming=").append(isStreaming).append(", isAudioInputEnabled=") + .append(isAudioInputEnabled).append(", lastIsOnlineChange=").append(lastIsOnlineChange) + .append(", isVideoHistoryEnabled=").append(isVideoHistoryEnabled).append(", webUrl=").append(webUrl) + .append(", appUrl=").append(appUrl).append(", isPublicShareEnabled=").append(isPublicShareEnabled) + .append(", activityZones=").append(activityZones).append(", publicShareUrl=").append(publicShareUrl) + .append(", snapshotUrl=").append(snapshotUrl).append(", lastEvent=").append(lastEvent) + .append(", getId()=").append(getId()).append(", getName()=").append(getName()) + .append(", getDeviceId()=").append(getDeviceId()).append(", getLastConnection()=") + .append(getLastConnection()).append(", isOnline()=").append(isOnline()).append(", getNameLong()=") + .append(getNameLong()).append(", getSoftwareVersion()=").append(getSoftwareVersion()) + .append(", getStructureId()=").append(getStructureId()).append(", getWhereId()=").append(getWhereId()) + .append("]"); + return builder.toString(); + } +} 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/data/CameraEvent.java new file mode 100644 index 00000000000..e9795fd4c50 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/CameraEvent.java @@ -0,0 +1,203 @@ +/** + * Copyright (c) 2010-2020 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.data; + +import java.util.Date; +import java.util.List; + +/** + * The data for a 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 { + + private Boolean hasSound; + private Boolean hasMotion; + private Boolean hasPerson; + private Date startTime; + private Date endTime; + private Date urlsExpireTime; + private String webUrl; + private String appUrl; + private String imageUrl; + private String animatedImageUrl; + private List activityZoneIds; + + public Boolean isHasSound() { + return hasSound; + } + + public Boolean isHasMotion() { + return hasMotion; + } + + public Boolean isHasPerson() { + return hasPerson; + } + + public Date getStartTime() { + return startTime; + } + + public Date getEndTime() { + return endTime; + } + + public Date getUrlsExpireTime() { + return urlsExpireTime; + } + + public String getWebUrl() { + return webUrl; + } + + public String getAppUrl() { + return appUrl; + } + + public String getImageUrl() { + return imageUrl; + } + + public String getAnimatedImageUrl() { + return animatedImageUrl; + } + + public List getActivityZones() { + return activityZoneIds; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + CameraEvent other = (CameraEvent) obj; + if (activityZoneIds == null) { + if (other.activityZoneIds != null) { + return false; + } + } else if (!activityZoneIds.equals(other.activityZoneIds)) { + return false; + } + if (animatedImageUrl == null) { + if (other.animatedImageUrl != null) { + return false; + } + } else if (!animatedImageUrl.equals(other.animatedImageUrl)) { + return false; + } + if (appUrl == null) { + if (other.appUrl != null) { + return false; + } + } else if (!appUrl.equals(other.appUrl)) { + return false; + } + if (endTime == null) { + if (other.endTime != null) { + return false; + } + } else if (!endTime.equals(other.endTime)) { + return false; + } + if (hasMotion == null) { + if (other.hasMotion != null) { + return false; + } + } else if (!hasMotion.equals(other.hasMotion)) { + return false; + } + if (hasPerson == null) { + if (other.hasPerson != null) { + return false; + } + } else if (!hasPerson.equals(other.hasPerson)) { + return false; + } + if (hasSound == null) { + if (other.hasSound != null) { + return false; + } + } else if (!hasSound.equals(other.hasSound)) { + return false; + } + if (imageUrl == null) { + if (other.imageUrl != null) { + return false; + } + } else if (!imageUrl.equals(other.imageUrl)) { + return false; + } + if (startTime == null) { + if (other.startTime != null) { + return false; + } + } else if (!startTime.equals(other.startTime)) { + return false; + } + if (urlsExpireTime == null) { + if (other.urlsExpireTime != null) { + return false; + } + } else if (!urlsExpireTime.equals(other.urlsExpireTime)) { + return false; + } + if (webUrl == null) { + if (other.webUrl != null) { + return false; + } + } else if (!webUrl.equals(other.webUrl)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((activityZoneIds == null) ? 0 : activityZoneIds.hashCode()); + result = prime * result + ((animatedImageUrl == null) ? 0 : animatedImageUrl.hashCode()); + result = prime * result + ((appUrl == null) ? 0 : appUrl.hashCode()); + result = prime * result + ((endTime == null) ? 0 : endTime.hashCode()); + result = prime * result + ((hasMotion == null) ? 0 : hasMotion.hashCode()); + result = prime * result + ((hasPerson == null) ? 0 : hasPerson.hashCode()); + result = prime * result + ((hasSound == null) ? 0 : hasSound.hashCode()); + result = prime * result + ((imageUrl == null) ? 0 : imageUrl.hashCode()); + result = prime * result + ((startTime == null) ? 0 : startTime.hashCode()); + result = prime * result + ((urlsExpireTime == null) ? 0 : urlsExpireTime.hashCode()); + result = prime * result + ((webUrl == null) ? 0 : webUrl.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Event [hasSound=").append(hasSound).append(", hasMotion=").append(hasMotion) + .append(", hasPerson=").append(hasPerson).append(", startTime=").append(startTime).append(", endTime=") + .append(endTime).append(", urlsExpireTime=").append(urlsExpireTime).append(", webUrl=").append(webUrl) + .append(", appUrl=").append(appUrl).append(", imageUrl=").append(imageUrl).append(", animatedImageUrl=") + .append(animatedImageUrl).append(", activityZoneIds=").append(activityZoneIds).append("]"); + return builder.toString(); + } +} 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/data/ETA.java new file mode 100644 index 00000000000..719f963002b --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ETA.java @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2010-2020 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.data; + +import java.util.Date; + +/** + * Used to set and update the ETA values for Nest. + * + * @author David Bennett - Initial contribution + * @author Wouter Born - Extract ETA object from Structure + * @author Wouter Born - Add equals, hashCode, toString methods + */ +public class ETA { + + private String tripId; + private Date estimatedArrivalWindowBegin; + private Date estimatedArrivalWindowEnd; + + public String getTripId() { + return tripId; + } + + public void setTripId(String tripId) { + this.tripId = tripId; + } + + public Date getEstimatedArrivalWindowBegin() { + return estimatedArrivalWindowBegin; + } + + public void setEstimatedArrivalWindowBegin(Date estimatedArrivalWindowBegin) { + this.estimatedArrivalWindowBegin = estimatedArrivalWindowBegin; + } + + public Date getEstimatedArrivalWindowEnd() { + return estimatedArrivalWindowEnd; + } + + public void setEstimatedArrivalWindowEnd(Date estimatedArrivalWindowEnd) { + this.estimatedArrivalWindowEnd = estimatedArrivalWindowEnd; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ETA other = (ETA) obj; + if (estimatedArrivalWindowBegin == null) { + if (other.estimatedArrivalWindowBegin != null) { + return false; + } + } else if (!estimatedArrivalWindowBegin.equals(other.estimatedArrivalWindowBegin)) { + return false; + } + if (estimatedArrivalWindowEnd == null) { + if (other.estimatedArrivalWindowEnd != null) { + return false; + } + } else if (!estimatedArrivalWindowEnd.equals(other.estimatedArrivalWindowEnd)) { + return false; + } + if (tripId == null) { + if (other.tripId != null) { + return false; + } + } else if (!tripId.equals(other.tripId)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((estimatedArrivalWindowBegin == null) ? 0 : estimatedArrivalWindowBegin.hashCode()); + result = prime * result + ((estimatedArrivalWindowEnd == null) ? 0 : estimatedArrivalWindowEnd.hashCode()); + result = prime * result + ((tripId == null) ? 0 : tripId.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ETA [tripId=").append(tripId).append(", estimatedArrivalWindowBegin=") + .append(estimatedArrivalWindowBegin).append(", estimatedArrivalWindowEnd=") + .append(estimatedArrivalWindowEnd).append("]"); + return builder.toString(); + } +} 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/data/ErrorData.java new file mode 100644 index 00000000000..808553c15cf --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/ErrorData.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2010-2020 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.data; + +/** + * The data of Nest API errors. + * + * @author Wouter Born - Initial contribution + * @author Wouter Born - Improve exception handling + * @author Wouter Born - Add equals and hashCode methods + */ +public class ErrorData { + + private String error; + private String type; + private String message; + private String instance; + + public String getError() { + return error; + } + + public String getType() { + return type; + } + + public String getMessage() { + return message; + } + + public String getInstance() { + return instance; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ErrorData other = (ErrorData) obj; + if (error == null) { + if (other.error != null) { + return false; + } + } else if (!error.equals(other.error)) { + return false; + } + if (instance == null) { + if (other.instance != null) { + return false; + } + } else if (!instance.equals(other.instance)) { + return false; + } + if (message == null) { + if (other.message != null) { + return false; + } + } else if (!message.equals(other.message)) { + return false; + } + if (type == null) { + if (other.type != null) { + return false; + } + } else if (!type.equals(other.type)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((error == null) ? 0 : error.hashCode()); + result = prime * result + ((instance == null) ? 0 : instance.hashCode()); + result = prime * result + ((message == null) ? 0 : message.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ErrorData [error=").append(error).append(", type=").append(type).append(", message=") + .append(message).append(", instance=").append(instance).append("]"); + return builder.toString(); + } +} 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/data/NestDevices.java new file mode 100644 index 00000000000..63506964140 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestDevices.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2010-2020 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.data; + +import java.util.Map; + +/** + * All the Nest devices broken up by type. + * + * @author David Bennett - Initial contribution + */ +public class NestDevices { + + private Map thermostats; + private Map smokeCoAlarms; + private Map cameras; + + /** Id to thermostat mapping */ + public Map getThermostats() { + return thermostats; + } + + /** Id to camera mapping */ + public Map getCameras() { + return cameras; + } + + /** Id to smoke detector */ + public Map getSmokeCoAlarms() { + return smokeCoAlarms; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + NestDevices other = (NestDevices) obj; + if (cameras == null) { + if (other.cameras != null) { + return false; + } + } else if (!cameras.equals(other.cameras)) { + return false; + } + if (smokeCoAlarms == null) { + if (other.smokeCoAlarms != null) { + return false; + } + } else if (!smokeCoAlarms.equals(other.smokeCoAlarms)) { + return false; + } + if (thermostats == null) { + if (other.thermostats != null) { + return false; + } + } else if (!thermostats.equals(other.thermostats)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((cameras == null) ? 0 : cameras.hashCode()); + result = prime * result + ((smokeCoAlarms == null) ? 0 : smokeCoAlarms.hashCode()); + result = prime * result + ((thermostats == null) ? 0 : thermostats.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("NestDevices [thermostats=").append(thermostats).append(", smokeCoAlarms=").append(smokeCoAlarms) + .append(", cameras=").append(cameras).append("]"); + return builder.toString(); + } +} 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/data/NestIdentifiable.java new file mode 100644 index 00000000000..0a0e55a04e7 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestIdentifiable.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2020 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.data; + +/** + * Interface for uniquely identifiable Nest objects (device or a structure). + * + * @author Wouter Born - Initial contribution + * @author Wouter Born - Simplify working with deviceId and structureId + */ +public interface NestIdentifiable { + + /** + * Returns the identifier that uniquely identifies the Nest object (deviceId or structureId). + */ + String getId(); +} 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/data/NestMetadata.java new file mode 100644 index 00000000000..e0da860af1c --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/NestMetadata.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2010-2020 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.data; + +/** + * The meta data in the data downloads from Nest. + * + * @author David Bennett - Initial contribution + * @author Wouter Born - Add equals and hashCode methods + */ +public class NestMetadata { + + private String accessToken; + private String clientVersion; + + public String getAccessToken() { + return accessToken; + } + + public String getClientVersion() { + return clientVersion; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + NestMetadata other = (NestMetadata) obj; + if (accessToken == null) { + if (other.accessToken != null) { + return false; + } + } else if (!accessToken.equals(other.accessToken)) { + return false; + } + if (clientVersion == null) { + if (other.clientVersion != null) { + return false; + } + } else if (!clientVersion.equals(other.clientVersion)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((accessToken == null) ? 0 : accessToken.hashCode()); + result = prime * result + ((clientVersion == null) ? 0 : clientVersion.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("NestMetadata [accessToken=").append(accessToken).append(", clientVersion=") + .append(clientVersion).append("]"); + return builder.toString(); + } +} 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/data/SmokeDetector.java new file mode 100644 index 00000000000..2011a0c9d61 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/SmokeDetector.java @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2010-2020 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.data; + +import java.util.Date; + +import com.google.gson.annotations.SerializedName; + +/** + * Data for the Nest smoke detector. + * + * @author David Bennett - Initial contribution + * @author Wouter Born - Add equals and hashCode methods + */ +public class SmokeDetector extends BaseNestDevice { + + private BatteryHealth batteryHealth; + private AlarmState coAlarmState; + private Date lastManualTestTime; + private AlarmState smokeAlarmState; + private Boolean isManualTestActive; + private UiColorState uiColorState; + + public UiColorState getUiColorState() { + return uiColorState; + } + + public BatteryHealth getBatteryHealth() { + return batteryHealth; + } + + public AlarmState getCoAlarmState() { + return coAlarmState; + } + + public Date getLastManualTestTime() { + return lastManualTestTime; + } + + public AlarmState getSmokeAlarmState() { + return smokeAlarmState; + } + + public Boolean isManualTestActive() { + return isManualTestActive; + } + + public enum BatteryHealth { + @SerializedName("ok") + OK, + @SerializedName("replace") + REPLACE + } + + public enum AlarmState { + @SerializedName("ok") + OK, + @SerializedName("emergency") + EMERGENCY, + @SerializedName("warning") + WARNING + } + + public enum UiColorState { + @SerializedName("gray") + GRAY, + @SerializedName("green") + GREEN, + @SerializedName("yellow") + YELLOW, + @SerializedName("red") + RED + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + SmokeDetector other = (SmokeDetector) obj; + if (batteryHealth != other.batteryHealth) { + return false; + } + if (coAlarmState != other.coAlarmState) { + return false; + } + if (isManualTestActive == null) { + if (other.isManualTestActive != null) { + return false; + } + } else if (!isManualTestActive.equals(other.isManualTestActive)) { + return false; + } + if (lastManualTestTime == null) { + if (other.lastManualTestTime != null) { + return false; + } + } else if (!lastManualTestTime.equals(other.lastManualTestTime)) { + return false; + } + if (smokeAlarmState != other.smokeAlarmState) { + return false; + } + if (uiColorState != other.uiColorState) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((batteryHealth == null) ? 0 : batteryHealth.hashCode()); + result = prime * result + ((coAlarmState == null) ? 0 : coAlarmState.hashCode()); + result = prime * result + ((isManualTestActive == null) ? 0 : isManualTestActive.hashCode()); + result = prime * result + ((lastManualTestTime == null) ? 0 : lastManualTestTime.hashCode()); + result = prime * result + ((smokeAlarmState == null) ? 0 : smokeAlarmState.hashCode()); + result = prime * result + ((uiColorState == null) ? 0 : uiColorState.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("SmokeDetector [batteryHealth=").append(batteryHealth).append(", coAlarmState=") + .append(coAlarmState).append(", lastManualTestTime=").append(lastManualTestTime) + .append(", smokeAlarmState=").append(smokeAlarmState).append(", isManualTestActive=") + .append(isManualTestActive).append(", uiColorState=").append(uiColorState).append(", getId()=") + .append(getId()).append(", getName()=").append(getName()).append(", getDeviceId()=") + .append(getDeviceId()).append(", getLastConnection()=").append(getLastConnection()) + .append(", isOnline()=").append(isOnline()).append(", getNameLong()=").append(getNameLong()) + .append(", getSoftwareVersion()=").append(getSoftwareVersion()).append(", getStructureId()=") + .append(getStructureId()).append(", getWhereId()=").append(getWhereId()).append("]"); + return builder.toString(); + } +} 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/data/Structure.java new file mode 100644 index 00000000000..2d61fb285e3 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Structure.java @@ -0,0 +1,311 @@ +/** + * Copyright (c) 2010-2020 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.data; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.openhab.binding.nest.internal.data.SmokeDetector.AlarmState; + +import com.google.gson.annotations.SerializedName; + +/** + * The structure details from Nest. + * + * @author David Bennett - Initial contribution + * @author Wouter Born - Add equals and hashCode methods + */ +public class Structure implements NestIdentifiable { + + private String structureId; + private List thermostats; + private List smokeCoAlarms; + private List cameras; + private String countryCode; + private String postalCode; + private Date peakPeriodStartTime; + private Date peakPeriodEndTime; + private String timeZone; + private Date etaBegin; + private SmokeDetector.AlarmState coAlarmState; + private SmokeDetector.AlarmState smokeAlarmState; + private Boolean rhrEnrollment; + private Map wheres; + private HomeAwayState away; + private String name; + private ETA eta; + private SecurityState wwnSecurityState; + + @Override + public String getId() { + return structureId; + } + + public HomeAwayState getAway() { + return away; + } + + public void setAway(HomeAwayState away) { + this.away = away; + } + + public String getStructureId() { + return structureId; + } + + public List getThermostats() { + return thermostats; + } + + public List getSmokeCoAlarms() { + return smokeCoAlarms; + } + + public List getCameras() { + return cameras; + } + + public String getCountryCode() { + return countryCode; + } + + public String getPostalCode() { + return postalCode; + } + + public Date getPeakPeriodStartTime() { + return peakPeriodStartTime; + } + + public Date getPeakPeriodEndTime() { + return peakPeriodEndTime; + } + + public String getTimeZone() { + return timeZone; + } + + public Date getEtaBegin() { + return etaBegin; + } + + public AlarmState getCoAlarmState() { + return coAlarmState; + } + + public AlarmState getSmokeAlarmState() { + return smokeAlarmState; + } + + public Boolean isRhrEnrollment() { + return rhrEnrollment; + } + + public Map getWheres() { + return wheres; + } + + public ETA getEta() { + return eta; + } + + public String getName() { + return name; + } + + public SecurityState getWwnSecurityState() { + return wwnSecurityState; + } + + public enum HomeAwayState { + @SerializedName("home") + HOME, + @SerializedName("away") + AWAY, + @SerializedName("unknown") + UNKNOWN + } + + public enum SecurityState { + @SerializedName("ok") + OK, + @SerializedName("deter") + DETER + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Structure other = (Structure) obj; + if (away != other.away) { + return false; + } + if (cameras == null) { + if (other.cameras != null) { + return false; + } + } else if (!cameras.equals(other.cameras)) { + return false; + } + if (coAlarmState != other.coAlarmState) { + return false; + } + if (countryCode == null) { + if (other.countryCode != null) { + return false; + } + } else if (!countryCode.equals(other.countryCode)) { + return false; + } + if (eta == null) { + if (other.eta != null) { + return false; + } + } else if (!eta.equals(other.eta)) { + return false; + } + if (etaBegin == null) { + if (other.etaBegin != null) { + return false; + } + } else if (!etaBegin.equals(other.etaBegin)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (peakPeriodEndTime == null) { + if (other.peakPeriodEndTime != null) { + return false; + } + } else if (!peakPeriodEndTime.equals(other.peakPeriodEndTime)) { + return false; + } + if (peakPeriodStartTime == null) { + if (other.peakPeriodStartTime != null) { + return false; + } + } else if (!peakPeriodStartTime.equals(other.peakPeriodStartTime)) { + return false; + } + if (postalCode == null) { + if (other.postalCode != null) { + return false; + } + } else if (!postalCode.equals(other.postalCode)) { + return false; + } + if (rhrEnrollment == null) { + if (other.rhrEnrollment != null) { + return false; + } + } else if (!rhrEnrollment.equals(other.rhrEnrollment)) { + return false; + } + if (smokeAlarmState != other.smokeAlarmState) { + return false; + } + if (smokeCoAlarms == null) { + if (other.smokeCoAlarms != null) { + return false; + } + } else if (!smokeCoAlarms.equals(other.smokeCoAlarms)) { + return false; + } + if (structureId == null) { + if (other.structureId != null) { + return false; + } + } else if (!structureId.equals(other.structureId)) { + return false; + } + if (thermostats == null) { + if (other.thermostats != null) { + return false; + } + } else if (!thermostats.equals(other.thermostats)) { + return false; + } + if (timeZone == null) { + if (other.timeZone != null) { + return false; + } + } else if (!timeZone.equals(other.timeZone)) { + return false; + } + if (wheres == null) { + if (other.wheres != null) { + return false; + } + } else if (!wheres.equals(other.wheres)) { + return false; + } + if (wwnSecurityState != other.wwnSecurityState) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((away == null) ? 0 : away.hashCode()); + result = prime * result + ((cameras == null) ? 0 : cameras.hashCode()); + result = prime * result + ((coAlarmState == null) ? 0 : coAlarmState.hashCode()); + result = prime * result + ((countryCode == null) ? 0 : countryCode.hashCode()); + result = prime * result + ((eta == null) ? 0 : eta.hashCode()); + result = prime * result + ((etaBegin == null) ? 0 : etaBegin.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((peakPeriodEndTime == null) ? 0 : peakPeriodEndTime.hashCode()); + result = prime * result + ((peakPeriodStartTime == null) ? 0 : peakPeriodStartTime.hashCode()); + result = prime * result + ((postalCode == null) ? 0 : postalCode.hashCode()); + result = prime * result + ((rhrEnrollment == null) ? 0 : rhrEnrollment.hashCode()); + result = prime * result + ((smokeAlarmState == null) ? 0 : smokeAlarmState.hashCode()); + result = prime * result + ((smokeCoAlarms == null) ? 0 : smokeCoAlarms.hashCode()); + result = prime * result + ((structureId == null) ? 0 : structureId.hashCode()); + result = prime * result + ((thermostats == null) ? 0 : thermostats.hashCode()); + result = prime * result + ((timeZone == null) ? 0 : timeZone.hashCode()); + result = prime * result + ((wheres == null) ? 0 : wheres.hashCode()); + result = prime * result + ((wwnSecurityState == null) ? 0 : wwnSecurityState.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Structure [structureId=").append(structureId).append(", thermostats=").append(thermostats) + .append(", smokeCoAlarms=").append(smokeCoAlarms).append(", cameras=").append(cameras) + .append(", countryCode=").append(countryCode).append(", postalCode=").append(postalCode) + .append(", peakPeriodStartTime=").append(peakPeriodStartTime).append(", peakPeriodEndTime=") + .append(peakPeriodEndTime).append(", timeZone=").append(timeZone).append(", etaBegin=").append(etaBegin) + .append(", coAlarmState=").append(coAlarmState).append(", smokeAlarmState=").append(smokeAlarmState) + .append(", rhrEnrollment=").append(rhrEnrollment).append(", wheres=").append(wheres).append(", away=") + .append(away).append(", name=").append(name).append(", eta=").append(eta).append(", wwnSecurityState=") + .append(wwnSecurityState).append("]"); + return builder.toString(); + } +} 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/data/Thermostat.java new file mode 100644 index 00000000000..5ad4f8161b6 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Thermostat.java @@ -0,0 +1,572 @@ +/** + * Copyright (c) 2010-2020 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.data; + +import static org.openhab.core.library.unit.ImperialUnits.FAHRENHEIT; +import static org.openhab.core.library.unit.SIUnits.CELSIUS; + +import java.util.Date; + +import javax.measure.Unit; +import javax.measure.quantity.Temperature; + +import com.google.gson.annotations.SerializedName; + +/** + * Gson class to encapsulate the data for the Nest thermostat. + * + * @author David Bennett - Initial contribution + * @author Wouter Born - Add equals and hashCode methods + */ +public class Thermostat extends BaseNestDevice { + + private Boolean canCool; + private Boolean canHeat; + private Boolean isUsingEmergencyHeat; + private Boolean hasFan; + private Boolean fanTimerActive; + private Date fanTimerTimeout; + private Boolean hasLeaf; + private String temperatureScale; + private Double ambientTemperatureC; + private Double ambientTemperatureF; + private Integer humidity; + private Double targetTemperatureC; + private Double targetTemperatureF; + private Double targetTemperatureHighC; + private Double targetTemperatureHighF; + private Double targetTemperatureLowC; + private Double targetTemperatureLowF; + private Mode hvacMode; + private Mode previousHvacMode; + private State hvacState; + private Double ecoTemperatureHighC; + private Double ecoTemperatureHighF; + private Double ecoTemperatureLowC; + private Double ecoTemperatureLowF; + private Boolean isLocked; + private Double lockedTempMaxC; + private Double lockedTempMaxF; + private Double lockedTempMinC; + private Double lockedTempMinF; + private Boolean sunlightCorrectionEnabled; + private Boolean sunlightCorrectionActive; + private Integer fanTimerDuration; + private String timeToTarget; + private String whereName; + + public Unit getTemperatureUnit() { + if ("C".equals(temperatureScale)) { + return CELSIUS; + } else if ("F".equals(temperatureScale)) { + return FAHRENHEIT; + } else { + return null; + } + } + + public Double getTargetTemperature() { + if (getTemperatureUnit() == CELSIUS) { + return targetTemperatureC; + } else if (getTemperatureUnit() == FAHRENHEIT) { + return targetTemperatureF; + } else { + return null; + } + } + + public Double getTargetTemperatureHigh() { + if (getTemperatureUnit() == CELSIUS) { + return targetTemperatureHighC; + } else if (getTemperatureUnit() == FAHRENHEIT) { + return targetTemperatureHighF; + } else { + return null; + } + } + + public Double getTargetTemperatureLow() { + if (getTemperatureUnit() == CELSIUS) { + return targetTemperatureLowC; + } else if (getTemperatureUnit() == FAHRENHEIT) { + return targetTemperatureLowF; + } else { + return null; + } + } + + public Mode getMode() { + return hvacMode; + } + + public Double getEcoTemperatureHigh() { + if (getTemperatureUnit() == CELSIUS) { + return ecoTemperatureHighC; + } else if (getTemperatureUnit() == FAHRENHEIT) { + return ecoTemperatureHighF; + } else { + return null; + } + } + + public Double getEcoTemperatureLow() { + if (getTemperatureUnit() == CELSIUS) { + return ecoTemperatureLowC; + } else if (getTemperatureUnit() == FAHRENHEIT) { + return ecoTemperatureLowF; + } else { + return null; + } + } + + public Boolean isLocked() { + return isLocked; + } + + public Double getLockedTempMax() { + if (getTemperatureUnit() == CELSIUS) { + return lockedTempMaxC; + } else if (getTemperatureUnit() == FAHRENHEIT) { + return lockedTempMaxF; + } else { + return null; + } + } + + public Double getLockedTempMin() { + if (getTemperatureUnit() == CELSIUS) { + return lockedTempMinC; + } else if (getTemperatureUnit() == FAHRENHEIT) { + return lockedTempMinF; + } else { + return null; + } + } + + public Boolean isCanCool() { + return canCool; + } + + public Boolean isCanHeat() { + return canHeat; + } + + public Boolean isUsingEmergencyHeat() { + return isUsingEmergencyHeat; + } + + public Boolean isHasFan() { + return hasFan; + } + + public Boolean isFanTimerActive() { + return fanTimerActive; + } + + public Date getFanTimerTimeout() { + return fanTimerTimeout; + } + + public Boolean isHasLeaf() { + return hasLeaf; + } + + public Mode getPreviousHvacMode() { + return previousHvacMode; + } + + public State getHvacState() { + return hvacState; + } + + public Boolean isSunlightCorrectionEnabled() { + return sunlightCorrectionEnabled; + } + + public Boolean isSunlightCorrectionActive() { + return sunlightCorrectionActive; + } + + public Integer getFanTimerDuration() { + return fanTimerDuration; + } + + public Integer getTimeToTarget() { + return parseTimeToTarget(timeToTarget); + } + + /* + * Turns the time to target string into a real value. + */ + static Integer parseTimeToTarget(String timeToTarget) { + if (timeToTarget == null) { + return null; + } else if (timeToTarget.startsWith("~") || timeToTarget.startsWith("<") || timeToTarget.startsWith(">")) { + return Integer.valueOf(timeToTarget.substring(1)); + } + return Integer.valueOf(timeToTarget); + } + + public String getWhereName() { + return whereName; + } + + public Double getAmbientTemperature() { + if (getTemperatureUnit() == CELSIUS) { + return ambientTemperatureC; + } else if (getTemperatureUnit() == FAHRENHEIT) { + return ambientTemperatureF; + } else { + return null; + } + } + + public Integer getHumidity() { + return humidity; + } + + public enum Mode { + @SerializedName("heat") + HEAT, + @SerializedName("cool") + COOL, + @SerializedName("heat-cool") + HEAT_COOL, + @SerializedName("eco") + ECO, + @SerializedName("off") + OFF + } + + public enum State { + @SerializedName("heating") + HEATING, + @SerializedName("cooling") + COOLING, + @SerializedName("off") + OFF + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Thermostat other = (Thermostat) obj; + if (ambientTemperatureC == null) { + if (other.ambientTemperatureC != null) { + return false; + } + } else if (!ambientTemperatureC.equals(other.ambientTemperatureC)) { + return false; + } + if (ambientTemperatureF == null) { + if (other.ambientTemperatureF != null) { + return false; + } + } else if (!ambientTemperatureF.equals(other.ambientTemperatureF)) { + return false; + } + if (canCool == null) { + if (other.canCool != null) { + return false; + } + } else if (!canCool.equals(other.canCool)) { + return false; + } + if (canHeat == null) { + if (other.canHeat != null) { + return false; + } + } else if (!canHeat.equals(other.canHeat)) { + return false; + } + if (ecoTemperatureHighC == null) { + if (other.ecoTemperatureHighC != null) { + return false; + } + } else if (!ecoTemperatureHighC.equals(other.ecoTemperatureHighC)) { + return false; + } + if (ecoTemperatureHighF == null) { + if (other.ecoTemperatureHighF != null) { + return false; + } + } else if (!ecoTemperatureHighF.equals(other.ecoTemperatureHighF)) { + return false; + } + if (ecoTemperatureLowC == null) { + if (other.ecoTemperatureLowC != null) { + return false; + } + } else if (!ecoTemperatureLowC.equals(other.ecoTemperatureLowC)) { + return false; + } + if (ecoTemperatureLowF == null) { + if (other.ecoTemperatureLowF != null) { + return false; + } + } else if (!ecoTemperatureLowF.equals(other.ecoTemperatureLowF)) { + return false; + } + if (fanTimerActive == null) { + if (other.fanTimerActive != null) { + return false; + } + } else if (!fanTimerActive.equals(other.fanTimerActive)) { + return false; + } + if (fanTimerDuration == null) { + if (other.fanTimerDuration != null) { + return false; + } + } else if (!fanTimerDuration.equals(other.fanTimerDuration)) { + return false; + } + if (fanTimerTimeout == null) { + if (other.fanTimerTimeout != null) { + return false; + } + } else if (!fanTimerTimeout.equals(other.fanTimerTimeout)) { + return false; + } + if (hasFan == null) { + if (other.hasFan != null) { + return false; + } + } else if (!hasFan.equals(other.hasFan)) { + return false; + } + if (hasLeaf == null) { + if (other.hasLeaf != null) { + return false; + } + } else if (!hasLeaf.equals(other.hasLeaf)) { + return false; + } + if (humidity == null) { + if (other.humidity != null) { + return false; + } + } else if (!humidity.equals(other.humidity)) { + return false; + } + if (hvacMode != other.hvacMode) { + return false; + } + if (hvacState != other.hvacState) { + return false; + } + if (isLocked == null) { + if (other.isLocked != null) { + return false; + } + } else if (!isLocked.equals(other.isLocked)) { + return false; + } + if (isUsingEmergencyHeat == null) { + if (other.isUsingEmergencyHeat != null) { + return false; + } + } else if (!isUsingEmergencyHeat.equals(other.isUsingEmergencyHeat)) { + return false; + } + if (lockedTempMaxC == null) { + if (other.lockedTempMaxC != null) { + return false; + } + } else if (!lockedTempMaxC.equals(other.lockedTempMaxC)) { + return false; + } + if (lockedTempMaxF == null) { + if (other.lockedTempMaxF != null) { + return false; + } + } else if (!lockedTempMaxF.equals(other.lockedTempMaxF)) { + return false; + } + if (lockedTempMinC == null) { + if (other.lockedTempMinC != null) { + return false; + } + } else if (!lockedTempMinC.equals(other.lockedTempMinC)) { + return false; + } + if (lockedTempMinF == null) { + if (other.lockedTempMinF != null) { + return false; + } + } else if (!lockedTempMinF.equals(other.lockedTempMinF)) { + return false; + } + if (previousHvacMode != other.previousHvacMode) { + return false; + } + if (sunlightCorrectionActive == null) { + if (other.sunlightCorrectionActive != null) { + return false; + } + } else if (!sunlightCorrectionActive.equals(other.sunlightCorrectionActive)) { + return false; + } + if (sunlightCorrectionEnabled == null) { + if (other.sunlightCorrectionEnabled != null) { + return false; + } + } else if (!sunlightCorrectionEnabled.equals(other.sunlightCorrectionEnabled)) { + return false; + } + if (targetTemperatureC == null) { + if (other.targetTemperatureC != null) { + return false; + } + } else if (!targetTemperatureC.equals(other.targetTemperatureC)) { + return false; + } + if (targetTemperatureF == null) { + if (other.targetTemperatureF != null) { + return false; + } + } else if (!targetTemperatureF.equals(other.targetTemperatureF)) { + return false; + } + if (targetTemperatureHighC == null) { + if (other.targetTemperatureHighC != null) { + return false; + } + } else if (!targetTemperatureHighC.equals(other.targetTemperatureHighC)) { + return false; + } + if (targetTemperatureHighF == null) { + if (other.targetTemperatureHighF != null) { + return false; + } + } else if (!targetTemperatureHighF.equals(other.targetTemperatureHighF)) { + return false; + } + if (targetTemperatureLowC == null) { + if (other.targetTemperatureLowC != null) { + return false; + } + } else if (!targetTemperatureLowC.equals(other.targetTemperatureLowC)) { + return false; + } + if (targetTemperatureLowF == null) { + if (other.targetTemperatureLowF != null) { + return false; + } + } else if (!targetTemperatureLowF.equals(other.targetTemperatureLowF)) { + return false; + } + if (temperatureScale == null) { + if (other.temperatureScale != null) { + return false; + } + } else if (!temperatureScale.equals(other.temperatureScale)) { + return false; + } + if (timeToTarget == null) { + if (other.timeToTarget != null) { + return false; + } + } else if (!timeToTarget.equals(other.timeToTarget)) { + return false; + } + if (whereName == null) { + if (other.whereName != null) { + return false; + } + } else if (!whereName.equals(other.whereName)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((ambientTemperatureC == null) ? 0 : ambientTemperatureC.hashCode()); + result = prime * result + ((ambientTemperatureF == null) ? 0 : ambientTemperatureF.hashCode()); + result = prime * result + ((canCool == null) ? 0 : canCool.hashCode()); + result = prime * result + ((canHeat == null) ? 0 : canHeat.hashCode()); + result = prime * result + ((ecoTemperatureHighC == null) ? 0 : ecoTemperatureHighC.hashCode()); + result = prime * result + ((ecoTemperatureHighF == null) ? 0 : ecoTemperatureHighF.hashCode()); + result = prime * result + ((ecoTemperatureLowC == null) ? 0 : ecoTemperatureLowC.hashCode()); + result = prime * result + ((ecoTemperatureLowF == null) ? 0 : ecoTemperatureLowF.hashCode()); + result = prime * result + ((fanTimerActive == null) ? 0 : fanTimerActive.hashCode()); + result = prime * result + ((fanTimerDuration == null) ? 0 : fanTimerDuration.hashCode()); + result = prime * result + ((fanTimerTimeout == null) ? 0 : fanTimerTimeout.hashCode()); + result = prime * result + ((hasFan == null) ? 0 : hasFan.hashCode()); + result = prime * result + ((hasLeaf == null) ? 0 : hasLeaf.hashCode()); + result = prime * result + ((humidity == null) ? 0 : humidity.hashCode()); + result = prime * result + ((hvacMode == null) ? 0 : hvacMode.hashCode()); + result = prime * result + ((hvacState == null) ? 0 : hvacState.hashCode()); + result = prime * result + ((isLocked == null) ? 0 : isLocked.hashCode()); + result = prime * result + ((isUsingEmergencyHeat == null) ? 0 : isUsingEmergencyHeat.hashCode()); + result = prime * result + ((lockedTempMaxC == null) ? 0 : lockedTempMaxC.hashCode()); + result = prime * result + ((lockedTempMaxF == null) ? 0 : lockedTempMaxF.hashCode()); + result = prime * result + ((lockedTempMinC == null) ? 0 : lockedTempMinC.hashCode()); + result = prime * result + ((lockedTempMinF == null) ? 0 : lockedTempMinF.hashCode()); + result = prime * result + ((previousHvacMode == null) ? 0 : previousHvacMode.hashCode()); + result = prime * result + ((sunlightCorrectionActive == null) ? 0 : sunlightCorrectionActive.hashCode()); + result = prime * result + ((sunlightCorrectionEnabled == null) ? 0 : sunlightCorrectionEnabled.hashCode()); + result = prime * result + ((targetTemperatureC == null) ? 0 : targetTemperatureC.hashCode()); + result = prime * result + ((targetTemperatureF == null) ? 0 : targetTemperatureF.hashCode()); + result = prime * result + ((targetTemperatureHighC == null) ? 0 : targetTemperatureHighC.hashCode()); + result = prime * result + ((targetTemperatureHighF == null) ? 0 : targetTemperatureHighF.hashCode()); + result = prime * result + ((targetTemperatureLowC == null) ? 0 : targetTemperatureLowC.hashCode()); + result = prime * result + ((targetTemperatureLowF == null) ? 0 : targetTemperatureLowF.hashCode()); + result = prime * result + ((temperatureScale == null) ? 0 : temperatureScale.hashCode()); + result = prime * result + ((timeToTarget == null) ? 0 : timeToTarget.hashCode()); + result = prime * result + ((whereName == null) ? 0 : whereName.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Thermostat [canCool=").append(canCool).append(", canHeat=").append(canHeat) + .append(", isUsingEmergencyHeat=").append(isUsingEmergencyHeat).append(", hasFan=").append(hasFan) + .append(", fanTimerActive=").append(fanTimerActive).append(", fanTimerTimeout=").append(fanTimerTimeout) + .append(", hasLeaf=").append(hasLeaf).append(", temperatureScale=").append(temperatureScale) + .append(", ambientTemperatureC=").append(ambientTemperatureC).append(", ambientTemperatureF=") + .append(ambientTemperatureF).append(", humidity=").append(humidity).append(", targetTemperatureC=") + .append(targetTemperatureC).append(", targetTemperatureF=").append(targetTemperatureF) + .append(", targetTemperatureHighC=").append(targetTemperatureHighC).append(", targetTemperatureHighF=") + .append(targetTemperatureHighF).append(", targetTemperatureLowC=").append(targetTemperatureLowC) + .append(", targetTemperatureLowF=").append(targetTemperatureLowF).append(", hvacMode=").append(hvacMode) + .append(", previousHvacMode=").append(previousHvacMode).append(", hvacState=").append(hvacState) + .append(", ecoTemperatureHighC=").append(ecoTemperatureHighC).append(", ecoTemperatureHighF=") + .append(ecoTemperatureHighF).append(", ecoTemperatureLowC=").append(ecoTemperatureLowC) + .append(", ecoTemperatureLowF=").append(ecoTemperatureLowF).append(", isLocked=").append(isLocked) + .append(", lockedTempMaxC=").append(lockedTempMaxC).append(", lockedTempMaxF=").append(lockedTempMaxF) + .append(", lockedTempMinC=").append(lockedTempMinC).append(", lockedTempMinF=").append(lockedTempMinF) + .append(", sunlightCorrectionEnabled=").append(sunlightCorrectionEnabled) + .append(", sunlightCorrectionActive=").append(sunlightCorrectionActive).append(", fanTimerDuration=") + .append(fanTimerDuration).append(", timeToTarget=").append(timeToTarget).append(", whereName=") + .append(whereName).append(", getId()=").append(getId()).append(", getName()=").append(getName()) + .append(", getDeviceId()=").append(getDeviceId()).append(", getLastConnection()=") + .append(getLastConnection()).append(", isOnline()=").append(isOnline()).append(", getNameLong()=") + .append(getNameLong()).append(", getSoftwareVersion()=").append(getSoftwareVersion()) + .append(", getStructureId()=").append(getStructureId()).append(", getWhereId()=").append(getWhereId()) + .append("]"); + return builder.toString(); + } +} 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/data/TopLevelData.java new file mode 100644 index 00000000000..93be10f94cc --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/TopLevelData.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2010-2020 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.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. + * + * @author David Bennett - Initial contribution + * @author Wouter Born - Add equals and hashCode methods + */ +public class TopLevelData { + + private NestDevices devices; + private NestMetadata metadata; + private Map structures; + + public NestDevices getDevices() { + return devices; + } + + public NestMetadata getMetadata() { + return metadata; + } + + public Map getStructures() { + return structures; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TopLevelData other = (TopLevelData) obj; + if (devices == null) { + if (other.devices != null) { + return false; + } + } else if (!devices.equals(other.devices)) { + return false; + } + if (metadata == null) { + if (other.metadata != null) { + return false; + } + } else if (!metadata.equals(other.metadata)) { + return false; + } + if (structures == null) { + if (other.structures != null) { + return false; + } + } else if (!structures.equals(other.structures)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((devices == null) ? 0 : devices.hashCode()); + result = prime * result + ((metadata == null) ? 0 : metadata.hashCode()); + result = prime * result + ((structures == null) ? 0 : structures.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("TopLevelData [devices=").append(devices).append(", metadata=").append(metadata) + .append(", structures=").append(structures).append("]"); + return builder.toString(); + } +} 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/data/TopLevelStreamingData.java new file mode 100644 index 00000000000..740518161dd --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/TopLevelStreamingData.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2010-2020 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.data; + +/** + * The top level 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 { + + private String path; + private TopLevelData data; + + public String getPath() { + return path; + } + + public TopLevelData getData() { + return data; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + result = prime * result + ((path == null) ? 0 : path.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TopLevelStreamingData other = (TopLevelStreamingData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + if (path == null) { + if (other.path != null) { + return false; + } + } else if (!path.equals(other.path)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("TopLevelStreamingData [path=").append(path).append(", data=").append(data).append("]"); + return builder.toString(); + } +} 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/data/Where.java new file mode 100644 index 00000000000..17f7c5ad797 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/data/Where.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2010-2020 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.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 { + private String whereId; + private String name; + + public String getWhereId() { + return whereId; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Where other = (Where) obj; + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (whereId == null) { + if (other.whereId != null) { + return false; + } + } else if (!whereId.equals(other.whereId)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((whereId == null) ? 0 : whereId.hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Where [whereId=").append(whereId).append(", name=").append(name).append("]"); + return builder.toString(); + } +} 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 new file mode 100644 index 00000000000..fc5d862f585 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/discovery/NestDiscoveryService.java @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2010-2020 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/exceptions/FailedResolvingNestUrlException.java b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedResolvingNestUrlException.java new file mode 100644 index 00000000000..5ea60dcb331 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedResolvingNestUrlException.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2020 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.exceptions; + +/** + * Will be thrown when the bridge was unable to resolve the Nest redirect URL. + * + * @author Wouter Born - Initial contribution + * @author Wouter Born - Improve exception handling while sending data + */ +@SuppressWarnings("serial") +public class FailedResolvingNestUrlException extends Exception { + public FailedResolvingNestUrlException(String message) { + super(message); + } + + public FailedResolvingNestUrlException(String message, Throwable cause) { + super(message, cause); + } + + public FailedResolvingNestUrlException(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/exceptions/FailedRetrievingNestDataException.java new file mode 100644 index 00000000000..26bcf94768f --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedRetrievingNestDataException.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2020 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.exceptions; + +/** + * Will be thrown when the bridge was unable to retrieve data. + * + * @author Martin van Wingerden - Initial contribution + * @author Martin van Wingerden - Added more centralized handling of failure when retrieving data + */ +@SuppressWarnings("serial") +public class FailedRetrievingNestDataException extends Exception { + + public FailedRetrievingNestDataException(String message) { + super(message); + } + + public FailedRetrievingNestDataException(String message, Throwable cause) { + super(message, cause); + } + + public FailedRetrievingNestDataException(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/exceptions/FailedSendingNestDataException.java new file mode 100644 index 00000000000..d73fdc66126 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/FailedSendingNestDataException.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2020 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.exceptions; + +/** + * Will be thrown when the bridge was unable to send data. + * + * @author Wouter Born - Initial contribution + * @author Wouter Born - Improve exception handling while sending data + */ +@SuppressWarnings("serial") +public class FailedSendingNestDataException extends Exception { + public FailedSendingNestDataException(String message) { + super(message); + } + + public FailedSendingNestDataException(String message, Throwable cause) { + super(message, cause); + } + + public FailedSendingNestDataException(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/exceptions/InvalidAccessTokenException.java new file mode 100644 index 00000000000..6c2ec963d13 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/exceptions/InvalidAccessTokenException.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2020 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.exceptions; + +/** + * Will be thrown when there is no valid access token and it was not possible to refresh it + * + * @author Martin van Wingerden - Initial contribution + * @author Martin van Wingerden - Added more centralized handling of invalid access tokens + */ +@SuppressWarnings("serial") +public class InvalidAccessTokenException extends Exception { + public InvalidAccessTokenException(Exception cause) { + super(cause); + } + + public InvalidAccessTokenException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidAccessTokenException(String message) { + super(message); + } +} 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/handler/NestBaseHandler.java new file mode 100644 index 00000000000..d70032bb66d --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestBaseHandler.java @@ -0,0 +1,204 @@ +/** + * Copyright (c) 2010-2020 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.handler; + +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.Collection; +import java.util.Date; +import java.util.TimeZone; +import java.util.stream.Collectors; + +import javax.measure.Quantity; +import javax.measure.Unit; + +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.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Deals with the structures on the Nest API, turning them into a thing in openHAB. + * + * @author David Bennett - Initial contribution + * @author Martin van Wingerden - Splitted of NestBaseHandler + * @author Wouter Born - Add generic update data type + * + * @param the type of update data + */ +@NonNullByDefault +public abstract class NestBaseHandler extends BaseThingHandler + implements NestThingDataListener, NestIdentifiable { + private final Logger logger = LoggerFactory.getLogger(NestBaseHandler.class); + + private @Nullable String deviceId; + private Class dataClass; + + NestBaseHandler(Thing thing, Class dataClass) { + super(thing); + this.dataClass = dataClass; + } + + @Override + public void initialize() { + logger.debug("Initializing handler for {}", getClass().getName()); + + NestBridgeHandler handler = getNestBridgeHandler(); + if (handler != null) { + boolean success = handler.addThingDataListener(dataClass, getId(), this); + logger.debug("Adding {} with ID '{}' as device data listener, result: {}", getClass().getSimpleName(), + getId(), success); + } else { + logger.debug("Unable to add {} with ID '{}' as device data listener because bridge is null", + getClass().getSimpleName(), getId()); + } + + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Waiting for refresh"); + + T lastUpdate = getLastUpdate(); + if (lastUpdate != null) { + update(null, lastUpdate); + } + } + + @Override + public void dispose() { + NestBridgeHandler handler = getNestBridgeHandler(); + if (handler != null) { + handler.removeThingDataListener(dataClass, getId(), this); + } + } + + protected @Nullable T getLastUpdate() { + NestBridgeHandler handler = getNestBridgeHandler(); + if (handler != null) { + return handler.getLastUpdate(dataClass, getId()); + } + return null; + } + + protected void addUpdateRequest(String updatePath, String field, Object value) { + NestBridgeHandler handler = getNestBridgeHandler(); + if (handler != null) { + // @formatter:off + handler.addUpdateRequest(new NestUpdateRequest.Builder() + .withBasePath(updatePath) + .withIdentifier(getId()) + .withAdditionalValue(field, value) + .build()); + // @formatter:on + } + } + + @Override + public String getId() { + return getDeviceId(); + } + + protected String getDeviceId() { + String localDeviceId = deviceId; + if (localDeviceId == null) { + localDeviceId = getConfigAs(NestDeviceConfiguration.class).deviceId; + deviceId = localDeviceId; + } + return localDeviceId; + } + + protected @Nullable NestBridgeHandler getNestBridgeHandler() { + Bridge bridge = getBridge(); + return bridge != null ? (NestBridgeHandler) bridge.getHandler() : null; + } + + protected abstract State getChannelState(ChannelUID channelUID, T data); + + protected State getAsDateTimeTypeOrNull(@Nullable Date date) { + if (date == null) { + return UnDefType.NULL; + } + + long offsetMillis = TimeZone.getDefault().getOffset(date.getTime()); + Instant instant = date.toInstant().plusMillis(offsetMillis); + return new DateTimeType(ZonedDateTime.ofInstant(instant, TimeZone.getDefault().toZoneId())); + } + + protected State getAsDecimalTypeOrNull(@Nullable Integer value) { + return value == null ? UnDefType.NULL : new DecimalType(value); + } + + protected State getAsOnOffTypeOrNull(@Nullable Boolean value) { + return value == null ? UnDefType.NULL : value ? OnOffType.ON : OnOffType.OFF; + } + + protected > State getAsQuantityTypeOrNull(@Nullable Number value, Unit unit) { + return value == null ? UnDefType.NULL : new QuantityType<>(value, unit); + } + + protected State getAsStringTypeOrNull(@Nullable Object value) { + return value == null ? UnDefType.NULL : new StringType(value.toString()); + } + + protected State getAsStringTypeListOrNull(@Nullable Collection values) { + return values == null || values.isEmpty() ? UnDefType.NULL + : new StringType(values.stream().map(v -> v.toString()).collect(Collectors.joining(","))); + } + + protected boolean isNotHandling(NestIdentifiable 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); + } + }); + } + + @Override + public void onNewData(T data) { + update(null, data); + } + + @Override + public void onUpdatedData(T oldData, T data) { + update(oldData, data); + } + + @Override + public void onMissingData(String nestId) { + thing.setStatusInfo( + new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Missing from streaming updates")); + } + + protected abstract void update(T oldData, T data); +} 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/handler/NestBridgeHandler.java new file mode 100644 index 00000000000..4cae482eb3e --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestBridgeHandler.java @@ -0,0 +1,383 @@ +/** + * Copyright (c) 2010-2020 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.handler; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.openhab.binding.nest.internal.NestBindingConstants.JSON_CONTENT_TYPE; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import javax.ws.rs.client.ClientBuilder; + +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.core.config.core.Configuration; +import org.openhab.core.io.net.http.HttpUtil; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.osgi.service.jaxrs.client.SseEventSourceFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This bridge handler connects to Nest and handles all the 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. + * + * @author David Bennett - Initial contribution + * @author Martin van Wingerden - Use listeners not only for discovery but for all data processing + * @author Wouter Born - Improve exception and URL redirect handling + */ +@NonNullByDefault +public class NestBridgeHandler extends BaseBridgeHandler implements NestStreamingDataListener { + + private static final int REQUEST_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(30); + + private final Logger logger = LoggerFactory.getLogger(NestBridgeHandler.class); + + private final ClientBuilder clientBuilder; + private final SseEventSourceFactory eventSourceFactory; + private final List nestUpdateRequests = new CopyOnWriteArrayList<>(); + private final NestCompositeUpdateHandler updateHandler = new NestCompositeUpdateHandler( + this::getPresentThingsNestIds); + + private @NonNullByDefault({}) NestAuthorizer authorizer; + private @NonNullByDefault({}) NestBridgeConfiguration config; + + private @Nullable ScheduledFuture initializeJob; + private @Nullable ScheduledFuture transmitJob; + private @Nullable NestRedirectUrlSupplier redirectUrlSupplier; + private @Nullable NestStreamingRestClient 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) { + super(bridge); + this.clientBuilder = clientBuilder; + this.eventSourceFactory = eventSourceFactory; + } + + /** + * Initialize the connection to Nest. + */ + @Override + public void initialize() { + logger.debug("Initializing Nest bridge handler"); + + config = getConfigAs(NestBridgeConfiguration.class); + authorizer = new NestAuthorizer(config); + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Starting poll query"); + + initializeJob = scheduler.schedule(() -> { + try { + logger.debug("Product ID {}", config.productId); + logger.debug("Product Secret {}", config.productSecret); + logger.debug("Pincode {}", config.pincode); + logger.debug("Access Token {}", getExistingOrNewAccessToken()); + redirectUrlSupplier = createRedirectUrlSupplier(); + restartStreamingUpdates(); + } catch (InvalidAccessTokenException e) { + logger.debug("Invalid access token", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Token is invalid and could not be refreshed: " + e.getMessage()); + } + }, 0, TimeUnit.SECONDS); + + logger.debug("Finished initializing Nest bridge handler"); + } + + /** + * Clean up the handler. + */ + @Override + public void dispose() { + logger.debug("Nest bridge disposed"); + stopStreamingUpdates(); + + ScheduledFuture localInitializeJob = initializeJob; + if (localInitializeJob != null && !localInitializeJob.isCancelled()) { + localInitializeJob.cancel(true); + initializeJob = null; + } + + ScheduledFuture localTransmitJob = transmitJob; + if (localTransmitJob != null && !localTransmitJob.isCancelled()) { + localTransmitJob.cancel(true); + transmitJob = null; + } + + this.authorizer = null; + this.redirectUrlSupplier = null; + this.streamingRestClient = null; + } + + public boolean addThingDataListener(Class dataClass, NestThingDataListener listener) { + return updateHandler.addListener(dataClass, listener); + } + + public boolean addThingDataListener(Class dataClass, String nestId, NestThingDataListener 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) { + nestUpdateRequests.add(request); + scheduleTransmitJobForPendingRequests(); + } + + protected NestRedirectUrlSupplier createRedirectUrlSupplier() throws InvalidAccessTokenException { + return new NestRedirectUrlSupplier(getHttpHeaders()); + } + + private String getExistingOrNewAccessToken() throws InvalidAccessTokenException { + String accessToken = config.accessToken; + if (accessToken == null || accessToken.isEmpty()) { + accessToken = authorizer.getNewAccessToken(); + config.accessToken = accessToken; + 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); + updateConfiguration(configuration); + logger.debug("Retrieved new access token: {}", config.accessToken); + return accessToken; + } else { + logger.debug("Re-using access token from configuration: {}", accessToken); + return accessToken; + } + } + + protected Properties getHttpHeaders() throws InvalidAccessTokenException { + Properties httpHeaders = new Properties(); + httpHeaders.put("Authorization", "Bearer " + getExistingOrNewAccessToken()); + httpHeaders.put("Content-Type", JSON_CONTENT_TYPE); + return httpHeaders; + } + + public @Nullable T getLastUpdate(Class dataClass, String nestId) { + return updateHandler.getLastUpdate(dataClass, nestId); + } + + public List getLastUpdates(Class dataClass) { + return updateHandler.getLastUpdates(dataClass); + } + + private NestRedirectUrlSupplier getOrCreateRedirectUrlSupplier() throws InvalidAccessTokenException { + NestRedirectUrlSupplier localRedirectUrlSupplier = redirectUrlSupplier; + if (localRedirectUrlSupplier == null) { + localRedirectUrlSupplier = createRedirectUrlSupplier(); + redirectUrlSupplier = localRedirectUrlSupplier; + } + return localRedirectUrlSupplier; + } + + private Set getPresentThingsNestIds() { + Set nestIds = new HashSet<>(); + for (Thing thing : getThing().getThings()) { + ThingHandler handler = thing.getHandler(); + if (handler != null && thing.getStatusInfo().getStatusDetail() != ThingStatusDetail.GONE) { + nestIds.add(((NestIdentifiable) handler).getId()); + } + } + return nestIds; + } + + /** + * Handles an incoming command update + */ + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + logger.debug("Refresh command received"); + updateHandler.resendLastUpdates(); + } + } + + private void jsonToPutUrl(NestUpdateRequest request) + throws FailedSendingNestDataException, InvalidAccessTokenException, FailedResolvingNestUrlException { + try { + NestRedirectUrlSupplier localRedirectUrlSupplier = redirectUrlSupplier; + if (localRedirectUrlSupplier == null) { + throw new FailedResolvingNestUrlException("redirectUrlSupplier is null"); + } + + String url = localRedirectUrlSupplier.getRedirectUrl() + request.getUpdatePath(); + logger.debug("Putting data to: {}", url); + + String jsonContent = NestUtils.toJson(request.getValues()); + logger.debug("PUT content: {}", jsonContent); + + ByteArrayInputStream inputStream = new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8)); + String jsonResponse = HttpUtil.executeUrl("PUT", url, getHttpHeaders(), inputStream, JSON_CONTENT_TYPE, + REQUEST_TIMEOUT); + logger.debug("PUT response: {}", jsonResponse); + + ErrorData error = NestUtils.fromJson(jsonResponse, ErrorData.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); + } + } + + @Override + public void onAuthorizationRevoked(String token) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Authorization token revoked: " + token); + } + + @Override + public void onConnected() { + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Streaming data connection established"); + scheduleTransmitJobForPendingRequests(); + } + + @Override + public void onDisconnected() { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Streaming data disconnected"); + } + + @Override + public void onError(String message) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message); + } + + @Override + public void onNewTopLevelData(TopLevelData data) { + updateHandler.handleUpdate(data); + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Receiving streaming data"); + } + + public boolean removeThingDataListener(Class dataClass, NestThingDataListener listener) { + return updateHandler.removeListener(dataClass, listener); + } + + public boolean removeThingDataListener(Class dataClass, String nestId, NestThingDataListener listener) { + return updateHandler.removeListener(dataClass, nestId, listener); + } + + private void restartStreamingUpdates() { + synchronized (this) { + stopStreamingUpdates(); + startStreamingUpdates(); + } + } + + private void scheduleTransmitJobForPendingRequests() { + ScheduledFuture localTransmitJob = transmitJob; + if (!nestUpdateRequests.isEmpty() && (localTransmitJob == null || localTransmitJob.isDone())) { + transmitJob = scheduler.schedule(this::transmitQueue, 0, SECONDS); + } + } + + private void startStreamingUpdates() { + synchronized (this) { + try { + NestStreamingRestClient localStreamingRestClient = new NestStreamingRestClient( + getExistingOrNewAccessToken(), clientBuilder, eventSourceFactory, + getOrCreateRedirectUrlSupplier(), scheduler); + localStreamingRestClient.addStreamingDataListener(this); + localStreamingRestClient.start(); + + streamingRestClient = localStreamingRestClient; + } catch (InvalidAccessTokenException e) { + logger.debug("Invalid access token", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Token is invalid and could not be refreshed: " + e.getMessage()); + } + } + } + + private void stopStreamingUpdates() { + NestStreamingRestClient localStreamingRestClient = streamingRestClient; + if (localStreamingRestClient != null) { + synchronized (this) { + localStreamingRestClient.stop(); + localStreamingRestClient.removeStreamingDataListener(this); + streamingRestClient = null; + } + } + } + + private void transmitQueue() { + if (getThing().getStatus() == ThingStatus.OFFLINE) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Not transmitting events because bridge is OFFLINE"); + return; + } + + try { + while (!nestUpdateRequests.isEmpty()) { + // nestUpdateRequests is a CopyOnWriteArrayList so its iterator does not support remove operations + NestUpdateRequest request = nestUpdateRequests.get(0); + jsonToPutUrl(request); + nestUpdateRequests.remove(request); + } + } catch (InvalidAccessTokenException 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) { + 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) { + logger.debug("Error sending data", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + scheduler.schedule(this::restartStreamingUpdates, 5, SECONDS); + + NestRedirectUrlSupplier localRedirectUrlSupplier = redirectUrlSupplier; + if (localRedirectUrlSupplier != null) { + localRedirectUrlSupplier.resetCache(); + } + } + } +} 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/handler/NestCameraHandler.java new file mode 100644 index 00000000000..c0f9fb53fe9 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestCameraHandler.java @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2010-2020 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.handler; + +import static org.openhab.binding.nest.internal.NestBindingConstants.*; +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.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles all the updates to the camera as well as handling the commands that send + * updates to Nest. + * + * @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 NestCameraHandler(Thing thing) { + super(thing, Camera.class); + } + + @Override + protected State getChannelState(ChannelUID channelUID, Camera camera) { + if (channelUID.getId().startsWith(CHANNEL_GROUP_CAMERA_PREFIX)) { + return getCameraChannelState(channelUID, camera); + } else if (channelUID.getId().startsWith(CHANNEL_GROUP_LAST_EVENT_PREFIX)) { + return getLastEventChannelState(channelUID, camera); + } else { + logger.error("Unsupported channelId '{}'", channelUID.getId()); + return UnDefType.UNDEF; + } + } + + protected State getCameraChannelState(ChannelUID channelUID, Camera camera) { + switch (channelUID.getId()) { + case CHANNEL_CAMERA_APP_URL: + return getAsStringTypeOrNull(camera.getAppUrl()); + case CHANNEL_CAMERA_AUDIO_INPUT_ENABLED: + return getAsOnOffTypeOrNull(camera.isAudioInputEnabled()); + case CHANNEL_CAMERA_LAST_ONLINE_CHANGE: + return getAsDateTimeTypeOrNull(camera.getLastIsOnlineChange()); + case CHANNEL_CAMERA_PUBLIC_SHARE_ENABLED: + return getAsOnOffTypeOrNull(camera.isPublicShareEnabled()); + case CHANNEL_CAMERA_PUBLIC_SHARE_URL: + return getAsStringTypeOrNull(camera.getPublicShareUrl()); + case CHANNEL_CAMERA_SNAPSHOT_URL: + return getAsStringTypeOrNull(camera.getSnapshotUrl()); + case CHANNEL_CAMERA_STREAMING: + return getAsOnOffTypeOrNull(camera.isStreaming()); + case CHANNEL_CAMERA_VIDEO_HISTORY_ENABLED: + return getAsOnOffTypeOrNull(camera.isVideoHistoryEnabled()); + case CHANNEL_CAMERA_WEB_URL: + return getAsStringTypeOrNull(camera.getWebUrl()); + default: + logger.error("Unsupported channelId '{}'", channelUID.getId()); + return UnDefType.UNDEF; + } + } + + protected State getLastEventChannelState(ChannelUID channelUID, Camera camera) { + CameraEvent lastEvent = camera.getLastEvent(); + if (lastEvent == null) { + return UnDefType.NULL; + } + + switch (channelUID.getId()) { + case CHANNEL_LAST_EVENT_ACTIVITY_ZONES: + return getAsStringTypeListOrNull(lastEvent.getActivityZones()); + case CHANNEL_LAST_EVENT_ANIMATED_IMAGE_URL: + return getAsStringTypeOrNull(lastEvent.getAnimatedImageUrl()); + case CHANNEL_LAST_EVENT_APP_URL: + return getAsStringTypeOrNull(lastEvent.getAppUrl()); + case CHANNEL_LAST_EVENT_END_TIME: + return getAsDateTimeTypeOrNull(lastEvent.getEndTime()); + case CHANNEL_LAST_EVENT_HAS_MOTION: + return getAsOnOffTypeOrNull(lastEvent.isHasMotion()); + case CHANNEL_LAST_EVENT_HAS_PERSON: + return getAsOnOffTypeOrNull(lastEvent.isHasPerson()); + case CHANNEL_LAST_EVENT_HAS_SOUND: + return getAsOnOffTypeOrNull(lastEvent.isHasSound()); + case CHANNEL_LAST_EVENT_IMAGE_URL: + return getAsStringTypeOrNull(lastEvent.getImageUrl()); + case CHANNEL_LAST_EVENT_START_TIME: + return getAsDateTimeTypeOrNull(lastEvent.getStartTime()); + case CHANNEL_LAST_EVENT_URLS_EXPIRE_TIME: + return getAsDateTimeTypeOrNull(lastEvent.getUrlsExpireTime()); + case CHANNEL_LAST_EVENT_WEB_URL: + return getAsStringTypeOrNull(lastEvent.getWebUrl()); + default: + logger.error("Unsupported channelId '{}'", channelUID.getId()); + return UnDefType.UNDEF; + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (REFRESH.equals(command)) { + Camera lastUpdate = getLastUpdate(); + if (lastUpdate != null) { + updateState(channelUID, getChannelState(channelUID, lastUpdate)); + } + } else if (CHANNEL_CAMERA_STREAMING.equals(channelUID.getId())) { + // Change the mode. + if (command instanceof OnOffType) { + // Set the mode to be the cmd value. + addUpdateRequest("is_streaming", command == OnOffType.ON); + } + } + } + + private void addUpdateRequest(String field, Object value) { + addUpdateRequest(NEST_CAMERA_UPDATE_PATH, field, value); + } + + @Override + protected void update(Camera oldCamera, Camera camera) { + logger.debug("Updating {}", getThing().getUID()); + + updateLinkedChannels(oldCamera, camera); + updateProperty(PROPERTY_FIRMWARE_VERSION, camera.getSoftwareVersion()); + + ThingStatus newStatus = camera.isOnline() == null ? ThingStatus.UNKNOWN + : camera.isOnline() ? ThingStatus.ONLINE : ThingStatus.OFFLINE; + if (newStatus != thing.getStatus()) { + updateStatus(newStatus); + } + } +} 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/handler/NestRedirectUrlSupplier.java new file mode 100644 index 00000000000..4041b317653 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestRedirectUrlSupplier.java @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2010-2020 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.handler; + +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpHeader; +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.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 + * 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 { + + private final Logger logger = LoggerFactory.getLogger(NestRedirectUrlSupplier.class); + + protected String cachedUrl = ""; + + protected Properties httpHeaders; + + public NestRedirectUrlSupplier(Properties httpHeaders) { + this.httpHeaders = httpHeaders; + } + + public String getRedirectUrl() throws FailedResolvingNestUrlException { + if (cachedUrl.isEmpty()) { + cachedUrl = resolveRedirectUrl(); + } + return cachedUrl; + } + + public void resetCache() { + cachedUrl = ""; + } + + /** + * Resolves the redirect URL for calls using the {@link NestBindingConstants#NEST_URL}. + * + * The Jetty client used by {@link HttpUtil} will not pass the Authorization header after a redirect resulting in + * "401 Unauthorized error" issues. + * + * Note that this workaround currently does not use any configured proxy like {@link HttpUtil} does. + * + * @see https://developers.nest.com/documentation/cloud/how-to-handle-redirects + */ + private String resolveRedirectUrl() throws FailedResolvingNestUrlException { + HttpClient httpClient = new HttpClient(new SslContextFactory()); + httpClient.setFollowRedirects(false); + + Request request = httpClient.newRequest(NestBindingConstants.NEST_URL).method(HttpMethod.GET).timeout(30, + TimeUnit.SECONDS); + for (String httpHeaderKey : httpHeaders.stringPropertyNames()) { + request.header(httpHeaderKey, httpHeaders.getProperty(httpHeaderKey)); + } + + ContentResponse response; + try { + httpClient.start(); + response = request.send(); + httpClient.stop(); + } catch (Exception e) { + throw new FailedResolvingNestUrlException("Failed to resolve redirect URL: " + e.getMessage(), e); + } + + int status = response.getStatus(); + String redirectUrl = response.getHeaders().get(HttpHeader.LOCATION); + + 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 " + + HttpStatus.TEMPORARY_REDIRECT_307 + " but was " + status); + } else if (redirectUrl == null || redirectUrl.isEmpty()) { + throw new FailedResolvingNestUrlException("Redirect URL is empty"); + } + + redirectUrl = redirectUrl.endsWith("/") ? redirectUrl.substring(0, redirectUrl.length() - 1) : redirectUrl; + logger.debug("Redirect URL: {}", redirectUrl); + return 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/handler/NestSmokeDetectorHandler.java new file mode 100644 index 00000000000..c813e03b2f8 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestSmokeDetectorHandler.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2010-2020 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.handler; + +import static org.openhab.binding.nest.internal.NestBindingConstants.*; +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.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The smoke detector handler, it handles the data from Nest 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 NestSmokeDetectorHandler(Thing thing) { + super(thing, SmokeDetector.class); + } + + @Override + protected State getChannelState(ChannelUID channelUID, SmokeDetector smokeDetector) { + switch (channelUID.getId()) { + case CHANNEL_CO_ALARM_STATE: + return getAsStringTypeOrNull(smokeDetector.getCoAlarmState()); + case CHANNEL_LAST_CONNECTION: + return getAsDateTimeTypeOrNull(smokeDetector.getLastConnection()); + case CHANNEL_LAST_MANUAL_TEST_TIME: + return getAsDateTimeTypeOrNull(smokeDetector.getLastManualTestTime()); + case CHANNEL_LOW_BATTERY: + return getAsOnOffTypeOrNull(smokeDetector.getBatteryHealth() == null ? null + : smokeDetector.getBatteryHealth() == BatteryHealth.REPLACE); + case CHANNEL_MANUAL_TEST_ACTIVE: + return getAsOnOffTypeOrNull(smokeDetector.isManualTestActive()); + case CHANNEL_SMOKE_ALARM_STATE: + return getAsStringTypeOrNull(smokeDetector.getSmokeAlarmState()); + case CHANNEL_UI_COLOR_STATE: + return getAsStringTypeOrNull(smokeDetector.getUiColorState()); + default: + logger.error("Unsupported channelId '{}'", channelUID.getId()); + return UnDefType.UNDEF; + } + } + + /** + * Handles any incoming command requests. + */ + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (REFRESH.equals(command)) { + SmokeDetector lastUpdate = getLastUpdate(); + if (lastUpdate != null) { + updateState(channelUID, getChannelState(channelUID, lastUpdate)); + } + } + } + + @Override + protected void update(SmokeDetector oldSmokeDetector, SmokeDetector smokeDetector) { + logger.debug("Updating {}", getThing().getUID()); + + updateLinkedChannels(oldSmokeDetector, smokeDetector); + updateProperty(PROPERTY_FIRMWARE_VERSION, smokeDetector.getSoftwareVersion()); + + ThingStatus newStatus = smokeDetector.isOnline() == null ? ThingStatus.UNKNOWN + : smokeDetector.isOnline() ? ThingStatus.ONLINE : ThingStatus.OFFLINE; + if (newStatus != thing.getStatus()) { + updateStatus(newStatus); + } + } +} 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/handler/NestStructureHandler.java new file mode 100644 index 00000000000..438dfacbc26 --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestStructureHandler.java @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2010-2020 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.handler; + +import static org.openhab.binding.nest.internal.NestBindingConstants.*; +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.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.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Deals with the structures on the Nest 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); + + private @Nullable String structureId; + + public NestStructureHandler(Thing thing) { + super(thing, Structure.class); + } + + @Override + protected State getChannelState(ChannelUID channelUID, Structure structure) { + switch (channelUID.getId()) { + case CHANNEL_AWAY: + return getAsStringTypeOrNull(structure.getAway()); + case CHANNEL_CO_ALARM_STATE: + return getAsStringTypeOrNull(structure.getCoAlarmState()); + case CHANNEL_COUNTRY_CODE: + return getAsStringTypeOrNull(structure.getCountryCode()); + case CHANNEL_ETA_BEGIN: + return getAsDateTimeTypeOrNull(structure.getEtaBegin()); + case CHANNEL_PEAK_PERIOD_END_TIME: + return getAsDateTimeTypeOrNull(structure.getPeakPeriodEndTime()); + case CHANNEL_PEAK_PERIOD_START_TIME: + return getAsDateTimeTypeOrNull(structure.getPeakPeriodStartTime()); + case CHANNEL_POSTAL_CODE: + return getAsStringTypeOrNull(structure.getPostalCode()); + case CHANNEL_RUSH_HOUR_REWARDS_ENROLLMENT: + return getAsOnOffTypeOrNull(structure.isRhrEnrollment()); + case CHANNEL_SECURITY_STATE: + return getAsStringTypeOrNull(structure.getWwnSecurityState()); + case CHANNEL_SMOKE_ALARM_STATE: + return getAsStringTypeOrNull(structure.getSmokeAlarmState()); + case CHANNEL_TIME_ZONE: + return getAsStringTypeOrNull(structure.getTimeZone()); + default: + logger.error("Unsupported channelId '{}'", channelUID.getId()); + return UnDefType.UNDEF; + } + } + + @Override + public String getId() { + return getStructureId(); + } + + private String getStructureId() { + String localStructureId = structureId; + if (localStructureId == null) { + localStructureId = getConfigAs(NestStructureConfiguration.class).structureId; + structureId = localStructureId; + } + return localStructureId; + } + + /** + * Handles updating the details on this structure by sending the request all the way + * to Nest. + * + * @param channelUID the channel to update + * @param command the command to apply + */ + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (REFRESH.equals(command)) { + Structure lastUpdate = getLastUpdate(); + if (lastUpdate != null) { + updateState(channelUID, getChannelState(channelUID, lastUpdate)); + } + } else if (CHANNEL_AWAY.equals(channelUID.getId())) { + // Change the home/away state. + if (command instanceof StringType) { + StringType cmd = (StringType) command; + // Set the mode to be the cmd value. + addUpdateRequest(NEST_STRUCTURE_UPDATE_PATH, "away", HomeAwayState.valueOf(cmd.toString())); + } + } + } + + @Override + protected void update(Structure oldStructure, Structure structure) { + logger.debug("Updating {}", getThing().getUID()); + + updateLinkedChannels(oldStructure, structure); + + if (ThingStatus.ONLINE != thing.getStatus()) { + updateStatus(ThingStatus.ONLINE); + } + } +} 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/handler/NestThermostatHandler.java new file mode 100644 index 00000000000..8dd0abbcf1b --- /dev/null +++ b/bundles/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/handler/NestThermostatHandler.java @@ -0,0 +1,219 @@ +/** + * Copyright (c) 2010-2020 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.handler; + +import static org.openhab.binding.nest.internal.NestBindingConstants.*; +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; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import javax.measure.Unit; +import javax.measure.quantity.Temperature; +import javax.measure.quantity.Time; + +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.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.SmartHomeUnits; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link NestThermostatHandler} 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 NestThermostatHandler(Thing thing) { + super(thing, Thermostat.class); + } + + @Override + protected State getChannelState(ChannelUID channelUID, Thermostat thermostat) { + switch (channelUID.getId()) { + case CHANNEL_CAN_COOL: + return getAsOnOffTypeOrNull(thermostat.isCanCool()); + case CHANNEL_CAN_HEAT: + return getAsOnOffTypeOrNull(thermostat.isCanHeat()); + case CHANNEL_ECO_MAX_SET_POINT: + return getAsQuantityTypeOrNull(thermostat.getEcoTemperatureHigh(), thermostat.getTemperatureUnit()); + case CHANNEL_ECO_MIN_SET_POINT: + return getAsQuantityTypeOrNull(thermostat.getEcoTemperatureLow(), thermostat.getTemperatureUnit()); + case CHANNEL_FAN_TIMER_ACTIVE: + return getAsOnOffTypeOrNull(thermostat.isFanTimerActive()); + case CHANNEL_FAN_TIMER_DURATION: + return getAsQuantityTypeOrNull(thermostat.getFanTimerDuration(), SmartHomeUnits.MINUTE); + case CHANNEL_FAN_TIMER_TIMEOUT: + return getAsDateTimeTypeOrNull(thermostat.getFanTimerTimeout()); + case CHANNEL_HAS_FAN: + return getAsOnOffTypeOrNull(thermostat.isHasFan()); + case CHANNEL_HAS_LEAF: + return getAsOnOffTypeOrNull(thermostat.isHasLeaf()); + case CHANNEL_HUMIDITY: + return getAsQuantityTypeOrNull(thermostat.getHumidity(), SmartHomeUnits.PERCENT); + case CHANNEL_LAST_CONNECTION: + return getAsDateTimeTypeOrNull(thermostat.getLastConnection()); + case CHANNEL_LOCKED: + return getAsOnOffTypeOrNull(thermostat.isLocked()); + case CHANNEL_LOCKED_MAX_SET_POINT: + return getAsQuantityTypeOrNull(thermostat.getLockedTempMax(), thermostat.getTemperatureUnit()); + case CHANNEL_LOCKED_MIN_SET_POINT: + return getAsQuantityTypeOrNull(thermostat.getLockedTempMin(), thermostat.getTemperatureUnit()); + case CHANNEL_MAX_SET_POINT: + return getAsQuantityTypeOrNull(thermostat.getTargetTemperatureHigh(), thermostat.getTemperatureUnit()); + case CHANNEL_MIN_SET_POINT: + return getAsQuantityTypeOrNull(thermostat.getTargetTemperatureLow(), thermostat.getTemperatureUnit()); + case CHANNEL_MODE: + return getAsStringTypeOrNull(thermostat.getMode()); + case CHANNEL_PREVIOUS_MODE: + Mode previousMode = thermostat.getPreviousHvacMode() != null ? thermostat.getPreviousHvacMode() + : thermostat.getMode(); + return getAsStringTypeOrNull(previousMode); + case CHANNEL_STATE: + return getAsStringTypeOrNull(thermostat.getHvacState()); + case CHANNEL_SET_POINT: + return getAsQuantityTypeOrNull(thermostat.getTargetTemperature(), thermostat.getTemperatureUnit()); + case CHANNEL_SUNLIGHT_CORRECTION_ACTIVE: + return getAsOnOffTypeOrNull(thermostat.isSunlightCorrectionActive()); + case CHANNEL_SUNLIGHT_CORRECTION_ENABLED: + return getAsOnOffTypeOrNull(thermostat.isSunlightCorrectionEnabled()); + case CHANNEL_TEMPERATURE: + return getAsQuantityTypeOrNull(thermostat.getAmbientTemperature(), thermostat.getTemperatureUnit()); + case CHANNEL_TIME_TO_TARGET: + return getAsQuantityTypeOrNull(thermostat.getTimeToTarget(), SmartHomeUnits.MINUTE); + case CHANNEL_USING_EMERGENCY_HEAT: + return getAsOnOffTypeOrNull(thermostat.isUsingEmergencyHeat()); + default: + logger.error("Unsupported channelId '{}'", channelUID.getId()); + return UnDefType.UNDEF; + } + } + + /** + * Handle the command to do things to the thermostat, this will change the + * value of a channel by sending the request to Nest. + */ + @Override + @SuppressWarnings("unchecked") + public void handleCommand(ChannelUID channelUID, Command command) { + if (REFRESH.equals(command)) { + Thermostat lastUpdate = getLastUpdate(); + if (lastUpdate != null) { + updateState(channelUID, getChannelState(channelUID, lastUpdate)); + } + } else if (CHANNEL_FAN_TIMER_ACTIVE.equals(channelUID.getId())) { + if (command instanceof OnOffType) { + // Update fan timer active to the command value + addUpdateRequest("fan_timer_active", command == OnOffType.ON); + } + } else if (CHANNEL_FAN_TIMER_DURATION.equals(channelUID.getId())) { + if (command instanceof QuantityType) { + // Update fan timer duration to the command value + QuantityType