mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[boschshc] Initial contribution - Bindings for Bosch Smart Home devices (#8629)
* Initial code from create_openhab_binding_skeleton.sh Signed-off-by: Stefan Kaestle <stefan@mad-kow.de> Signed-off-by: Christian Oeing <christian.oeing@slashgames.org> Signed-off-by: Gerd Zanker <gerd.zanker@web.de> Co-authored-by: Stefan Kaestle <stefan@mad-kow.de> Co-authored-by: Gerd Zanker <gerd.zanker@web.de> Co-authored-by: Christian Oeing <christian.oeing@scalamat.de> Co-authored-by: Hilbrand Bouwkamp <hilbrand@h72.nl> Co-authored-by: Fabian Wolter <github@fabian-wolter.de> Co-authored-by: Connor Petty <mistercpp2000+gitsignoff@gmail.com>
This commit is contained in:
parent
2afb06948a
commit
2a5bdf3b47
@ -34,6 +34,7 @@
|
||||
/bundles/org.openhab.binding.bluetooth.roaming/ @cpmeister
|
||||
/bundles/org.openhab.binding.bluetooth.ruuvitag/ @ssalonen
|
||||
/bundles/org.openhab.binding.boschindego/ @jofleck
|
||||
/bundles/org.openhab.binding.boschshc/ @stefan-kaestle @coeing @GerdZanker
|
||||
/bundles/org.openhab.binding.bosesoundtouch/ @marvkis @tratho
|
||||
/bundles/org.openhab.binding.bsblan/ @hypetsch
|
||||
/bundles/org.openhab.binding.bticinosmarther/ @MrRonfo
|
||||
|
@ -156,6 +156,11 @@
|
||||
<artifactId>org.openhab.binding.boschindego</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.boschshc</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.bosesoundtouch</artifactId>
|
||||
|
52
bundles/org.openhab.binding.boschshc/DEVELOPERS.md
Normal file
52
bundles/org.openhab.binding.boschshc/DEVELOPERS.md
Normal file
@ -0,0 +1,52 @@
|
||||
# For Developers
|
||||
|
||||
## Build
|
||||
|
||||
To only build the Bosch SHC binding code execute
|
||||
|
||||
mvn -pl :org.openhab.binding.boschshc install
|
||||
|
||||
## Execute
|
||||
|
||||
After compiling a new ``org.openhab.binding.boschshc.jar``
|
||||
copy it into the ``addons`` folder of your openHAB test instance.
|
||||
|
||||
For the first time the jar is loaded automatically as a bundle.
|
||||
|
||||
It should also be reloaded automatically when the jar changed.
|
||||
|
||||
To reload the bundle manually you need to execute:
|
||||
|
||||
bundle:update "openHAB Add-ons :: Bundles :: BoschSHC Binding"
|
||||
|
||||
or get the ID and update the bundle using the ID:
|
||||
|
||||
bundle:list
|
||||
-> Get ID for "openHAB Add-ons :: Bundles :: BoschSHC Binding"
|
||||
bundle:update <ID>
|
||||
|
||||
|
||||
## Debugging
|
||||
|
||||
To get debug output and traces of the Bosch SHC binding code
|
||||
add the following lines into ``userdata/etc/log4j2.xml`` Loggers XML section.
|
||||
|
||||
<!-- Bosch SHC for debugging -->
|
||||
<Logger level="TRACE" name="org.openhab.binding.boschshc"/>
|
||||
|
||||
## Pairing and Certificates
|
||||
|
||||
We need secured and paired connection from the openHAB binding instance to the Bosch SHC.
|
||||
|
||||
Read more about the pairing process in [register a new client to the bosch smart home controller](https://github.com/BoschSmartHome/bosch-shc-api-docs/tree/master/postman#register-a-new-client-to-the-bosch-smart-home-controller)
|
||||
|
||||
A precondition for the secured connection to the Bosch SHC is a self singed key + certificate.
|
||||
The key + certificate will be created and stored with the public Bosch SHC certificates in a Java Key store (jks).
|
||||
|
||||
The public certificates files are from https://github.com/BoschSmartHome/bosch-shc-api-docs/tree/master/best_practice.
|
||||
File copies stored in ``src/main/resource``.
|
||||
|
||||
All three certificates and the key will be used for the HTTPS connection between
|
||||
this openHAB binding and the Bosch SHC.
|
||||
|
||||
During pairing the openHAB binding will exchange the self singed certificate with SHC.
|
21
bundles/org.openhab.binding.boschshc/NOTICE
Normal file
21
bundles/org.openhab.binding.boschshc/NOTICE
Normal file
@ -0,0 +1,21 @@
|
||||
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
|
||||
|
||||
## Third-party Content
|
||||
|
||||
bcpkix-jdk15on
|
||||
bcprov-jdk15on
|
||||
* License: Bouncy Castle License
|
||||
* Project: https://www.bouncycastle.org
|
||||
* Source: https://github.com/bcgit/bc-java
|
170
bundles/org.openhab.binding.boschshc/README.md
Normal file
170
bundles/org.openhab.binding.boschshc/README.md
Normal file
@ -0,0 +1,170 @@
|
||||
# BoschSHC Binding
|
||||
|
||||
Binding for the Bosch Smart Home Controller.
|
||||
|
||||
- [BoschSHC Binding](#boschshc-binding)
|
||||
- [Supported Things](#supported-things)
|
||||
- [Bosch In-Wall switches & Bosch Smart Plugs](#bosch-in-wall-switches--bosch-smart-plugs)
|
||||
- [Bosch TwinGuard smoke detector](#bosch-twinguard-smoke-detector)
|
||||
- [Bosch Window/Door contacts](#bosch-windowdoor-contacts)
|
||||
- [Bosch Motion Detector](#bosch-motion-detector)
|
||||
- [Bosch Shutter Control in-wall](#bosch-shutter-control-in-wall)
|
||||
- [Bosch Thermostat](#bosch-thermostat)
|
||||
- [Bosch Climate Control](#bosch-climate-control)
|
||||
- [Limitations](#limitations)
|
||||
- [Discovery](#discovery)
|
||||
- [Binding Configuration](#binding-configuration)
|
||||
- [Getting the device IDs](#getting-the-device-ids)
|
||||
- [Thing Configuration](#thing-configuration)
|
||||
- [Item Configuration](#item-configuration)
|
||||
|
||||
## Supported Things
|
||||
|
||||
### Bosch In-Wall switches & Bosch Smart Plugs
|
||||
|
||||
**Thing Type ID**: `in-wall-switch`
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
|--------------------|---------------|----------------------------------------------|
|
||||
| power-switch | Switch | Current state of the switch. |
|
||||
| power-consumption | Number:Power | Current power consumption (W) of the device. |
|
||||
| energy-consumption | Number:Energy | Energy consumption of the device. |
|
||||
|
||||
### Bosch TwinGuard smoke detector
|
||||
|
||||
**Thing Type ID**: `twinguard`
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
|--------------------|----------------------|---------------------------------------------------------------------------------------------------|
|
||||
| temperature | Number:Temperature | Current measured temperature. |
|
||||
| temperature-rating | String | Rating of the currently measured temperature. |
|
||||
| humidity | Number:Dimensionless | Current measured humidity. |
|
||||
| humidity-rating | String | Rating of current measured humidity. |
|
||||
| purity | Number:Dimensionless | Purity of the air (ppm). Range from 500 to 5500 ppm. A higher value indicates a higher pollution. |
|
||||
| purity-rating | String | Rating of current measured purity. |
|
||||
| air-description | String | Overall description of the air quality. |
|
||||
| combined-rating | String | Combined rating of the air quality. |
|
||||
|
||||
### Bosch Window/Door contacts
|
||||
|
||||
**Thing Type ID**: `window-contact`
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
|-----------------|-----------|------------------------------|
|
||||
| contact | Contact | Contact state of the device. |
|
||||
|
||||
### Bosch Motion Detector
|
||||
|
||||
**Thing Type ID**: `motion-detector`
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
|-----------------|-----------|--------------------------------|
|
||||
| latest-motion | DateTime | The date of the latest motion. |
|
||||
|
||||
### Bosch Shutter Control in-wall
|
||||
|
||||
**Thing Type ID**: `shutter-control`
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
|-----------------|---------------|------------------------------------------|
|
||||
| level | Rollershutter | Current open ratio (0 to 100, Step 0.5). |
|
||||
|
||||
### Bosch Thermostat
|
||||
|
||||
**Thing Type ID**: `thermostat`
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
|-----------------------|----------------------|------------------------------------------------|
|
||||
| temperature | Number:Temperature | Current measured temperature. |
|
||||
| valve-tappet-position | Number:Dimensionless | Current open ratio of valve tappet (0 to 100). |
|
||||
|
||||
### Bosch Climate Control
|
||||
|
||||
**Thing Type ID**: `climate-control`
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
|----------------------|--------------------|-------------------------------|
|
||||
| temperature | Number:Temperature | Current measured temperature. |
|
||||
| setpoint-temperature | Number:Temperature | Desired temperature. |
|
||||
|
||||
## Limitations
|
||||
|
||||
- Discovery of Things
|
||||
- Discovery of Bridge
|
||||
|
||||
## Discovery
|
||||
|
||||
Configuration via configuration files or UI (see below).
|
||||
|
||||
## Bridge Configuration
|
||||
|
||||
You need to provide the IP address and the system password of your Bosch Smart Home Controller.
|
||||
The IP address of the controller is visible in the Bosch Smart Home Mobile App (More -> System -> Smart Home Controller) or in your network router UI.
|
||||
The system password is set by you during your initial registration steps in the _Bosch Smart Home App_.
|
||||
|
||||
A keystore file with a self signed certificate is created automatically.
|
||||
This certificate is used for pairing between the Bridge and the Bosch SHC.
|
||||
|
||||
*Press and hold the Bosch Smart Home Controller Bridge button until the LED starts blinking after you save your settings for pairing*.
|
||||
|
||||
## Getting the device IDs
|
||||
|
||||
Bosch IDs for found devices are displayed in the openHAB log on bootup (`OPENHAB_FOLDER/userdata/logs/openhab.log`)
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
2020-08-11 12:42:49.490 [INFO ] [chshc.internal.BoschSHCBridgeHandler] - Found device: name=Heizung id=hdm:HomeMaticIP:3014F711A000XXXXXXXXXXXX
|
||||
2020-08-11 12:42:49.495 [INFO ] [chshc.internal.BoschSHCBridgeHandler] - Found device: name=-RoomClimateControl- id=roomClimateControl_hz_1
|
||||
2020-08-11 12:42:49.497 [INFO ] [chshc.internal.BoschSHCBridgeHandler] - Found device: name=-VentilationService- id=ventilationService
|
||||
2020-08-11 12:42:49.498 [INFO ] [chshc.internal.BoschSHCBridgeHandler] - Found device: name=Großes Fenster id=hdm:HomeMaticIP:3014F711A000XXXXXXXXXXXX
|
||||
2020-08-11 12:42:49.501 [INFO ] [chshc.internal.BoschSHCBridgeHandler] - Found device: name=-IntrusionDetectionSystem- id=intrusionDetectionSystem
|
||||
2020-08-11 12:42:49.502 [INFO ] [chshc.internal.BoschSHCBridgeHandler] - Found device: name=Rollladen id=hdm:HomeMaticIP:3014F711A000XXXXXXXXXXXX
|
||||
2020-08-11 12:42:49.502 [INFO ] [chshc.internal.BoschSHCBridgeHandler] - Found device: name=Heizung id=hdm:HomeMaticIP:3014F711A000XXXXXXXXXXXX
|
||||
2020-08-11 12:42:49.503 [INFO ] [chshc.internal.BoschSHCBridgeHandler] - Found device: name=Heizung Haus id=hdm:ICom:819410185:HC1
|
||||
2020-08-11 12:42:49.503 [INFO ] [chshc.internal.BoschSHCBridgeHandler] - Found device: name=-RoomClimateControl- id=roomClimateControl_hz_6
|
||||
2020-08-11 12:42:49.504 [INFO ] [chshc.internal.BoschSHCBridgeHandler] - Found device: name=PhilipsHueBridgeManager id=hdm:PhilipsHueBridge:PhilipsHueBridgeManager
|
||||
2020-08-11 12:42:49.505 [INFO ] [chshc.internal.BoschSHCBridgeHandler] - Found device: name=Rollladen id=hdm:HomeMaticIP:3014F711A000XXXXXXXXXXXX
|
||||
2020-08-11 12:42:49.506 [INFO ] [chshc.internal.BoschSHCBridgeHandler] - Found device: name=Rollladen id=hdm:HomeMaticIP:3014F711A000XXXXXXXXXXXX
|
||||
2020-08-11 12:42:49.507 [INFO ] [chshc.internal.BoschSHCBridgeHandler] - Found device: name=Central Heating id=hdm:ICom:819410185
|
||||
```
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
You define your Bosch devices by adding them either to a `.things` file in your `$OPENHAB_CONF/things` folder like this:
|
||||
|
||||
```
|
||||
Bridge boschshc:shc:1 [ ipAddress="192.168.x.y", password="XXXXXXXXXX" ] {
|
||||
Thing in-wall-switch bathroom "Bathroom" [ id="hdm:HomeMaticIP:3014F711A000XXXXXXXXXXXX" ]
|
||||
Thing in-wall-switch bedroom "Bedroom" [ id="hdm:HomeMaticIP:3014F711A000XXXXXXXXXXXX" ]
|
||||
Thing in-wall-switch kitchen "Kitchen" [ id="hdm:HomeMaticIP:3014F711A000XXXXXXXXXXXX" ]
|
||||
Thing in-wall-switch corridor "Corridor" [ id="hdm:HomeMaticIP:3014F711A000XXXXXXXXXXXX" ]
|
||||
Thing in-wall-switch livingroom "Living Room" [ id="hdm:HomeMaticIP:3014F711A000XXXXXXXXXXXX" ]
|
||||
|
||||
Thing in-wall-switch coffeemachine "Coffee Machine" [ id="hdm:HomeMaticIP:3014F711A0000XXXXXXXXXXXX" ]
|
||||
|
||||
Thing twinguard tg-corridor "Twinguard Smoke Detector" [ id="hdm:ZigBee:000d6f000XXXXXXX" ]
|
||||
Thing window-contact window-kitchen "Window Kitchen" [ id="hdm:HomeMaticIP:3014F711A00000XXXXXXXXXX" ]
|
||||
Thing window-contact entrance "Entrance door" [ id="hdm:HomeMaticIP:3014F711A00000XXXXXXXXXX" ]
|
||||
|
||||
Thing motion-detector motion-corridor "Bewegungsmelder" [ id="hdm:ZigBee:000d6f000XXXXXXX" ]
|
||||
}
|
||||
```
|
||||
|
||||
Or by adding them via UI: Settings -> Things -> "+" -> Bosch Smart Home Binding.
|
||||
|
||||
## Item Configuration
|
||||
|
||||
You define the items which should be linked to your Bosch devices via a `.items` file in your `$OPENHAB_CONF/items` folder like this:
|
||||
|
||||
```
|
||||
Switch Bosch_Bathroom "Bath Room" { channel="boschshc:in-wall-switch:1:bathroom:power-switch" }
|
||||
Switch Bosch_Bedroom "Bed Room" { channel="boschshc:in-wall-switch:1:bedroom:power-switch" }
|
||||
Switch Bosch_Kitchen "Kitchen" { channel="boschshc:in-wall-switch:1:kitchen:power-switch" }
|
||||
Switch Bosch_Corridor "Corridor" { channel="boschshc:in-wall-switch:1:corridor:power-switch" }
|
||||
Switch Bosch_Living_Room "Living Room" { channel="boschshc:in-wall-switch:1:livingroom:power-switch" }
|
||||
|
||||
Switch Bosch_Lelit "Lelit" { channel="boschshc:in-wall-switch:1:coffeemachine:power-switch" }
|
||||
```
|
||||
|
||||
Or by adding them via UI: Settings -> Items -> "+".
|
32
bundles/org.openhab.binding.boschshc/pom.xml
Normal file
32
bundles/org.openhab.binding.boschshc/pom.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.boschshc</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: BoschSHC Binding</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk15on</artifactId>
|
||||
<version>1.52</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.52</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.boschshc-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-boschshc" description="BoschSHC Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.boschshc/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link BoschSHCBindingConstants} class defines common constants, which
|
||||
* are used across the whole binding.
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
* @author Christian Oeing - added Shutter Control, ThermostatHandler
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BoschSHCBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "boschshc";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_SHC = new ThingTypeUID(BINDING_ID, "shc");
|
||||
|
||||
public static final ThingTypeUID THING_TYPE_INWALL_SWITCH = new ThingTypeUID(BINDING_ID, "in-wall-switch");
|
||||
public static final ThingTypeUID THING_TYPE_TWINGUARD = new ThingTypeUID(BINDING_ID, "twinguard");
|
||||
public static final ThingTypeUID THING_TYPE_WINDOW_CONTACT = new ThingTypeUID(BINDING_ID, "window-contact");
|
||||
public static final ThingTypeUID THING_TYPE_MOTION_DETECTOR = new ThingTypeUID(BINDING_ID, "motion-detector");
|
||||
public static final ThingTypeUID THING_TYPE_SHUTTER_CONTROL = new ThingTypeUID(BINDING_ID, "shutter-control");
|
||||
public static final ThingTypeUID THING_TYPE_THERMOSTAT = new ThingTypeUID(BINDING_ID, "thermostat");
|
||||
public static final ThingTypeUID THING_TYPE_CLIMATE_CONTROL = new ThingTypeUID(BINDING_ID, "climate-control");
|
||||
|
||||
// List of all Channel IDs
|
||||
// Auto-generated from thing-types.xml via script, don't modify
|
||||
public static final String CHANNEL_POWER_SWITCH = "power-switch";
|
||||
public static final String CHANNEL_TEMPERATURE = "temperature";
|
||||
public static final String CHANNEL_TEMPERATURE_RATING = "temperature-rating";
|
||||
public static final String CHANNEL_HUMIDITY = "humidity";
|
||||
public static final String CHANNEL_HUMIDITY_RATING = "humidity-rating";
|
||||
public static final String CHANNEL_ENERGY_CONSUMPTION = "energy-consumption";
|
||||
public static final String CHANNEL_POWER_CONSUMPTION = "power-consumption";
|
||||
public static final String CHANNEL_PURITY = "purity";
|
||||
public static final String CHANNEL_AIR_DESCRIPTION = "air-description";
|
||||
public static final String CHANNEL_PURITY_RATING = "purity-rating";
|
||||
public static final String CHANNEL_COMBINED_RATING = "combined-rating";
|
||||
public static final String CHANNEL_CONTACT = "contact";
|
||||
public static final String CHANNEL_LATEST_MOTION = "latest-motion";
|
||||
public static final String CHANNEL_LEVEL = "level";
|
||||
public static final String CHANNEL_VALVE_TAPPET_POSITION = "valve-tappet-position";
|
||||
public static final String CHANNEL_SETPOINT_TEMPERATURE = "setpoint-temperature";
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link BoschSHCConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BoschSHCConfiguration {
|
||||
/**
|
||||
* ID of the device as returned by the controller.
|
||||
*/
|
||||
public @Nullable String id;
|
||||
}
|
@ -0,0 +1,299 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.BoschSHCBridgeHandler;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
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.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* The {@link BoschSHCHandler} represents Bosch Things. Each type of device
|
||||
* inherits from this abstract thing handler.
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
* @author Christian Oeing - refactorings of e.g. server registration
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class BoschSHCHandler extends BaseThingHandler {
|
||||
|
||||
/**
|
||||
* Service State for a Bosch device.
|
||||
*/
|
||||
class DeviceService<TState extends BoschSHCServiceState> {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param service Service which belongs to the device.
|
||||
* @param affectedChannels Channels which are affected by the state of this service.
|
||||
*/
|
||||
public DeviceService(BoschSHCService<TState> service, Collection<String> affectedChannels) {
|
||||
this.service = service;
|
||||
this.affectedChannels = affectedChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service which belongs to the device.
|
||||
*/
|
||||
public final BoschSHCService<TState> service;
|
||||
|
||||
/**
|
||||
* Channels which are affected by the state of this service.
|
||||
*/
|
||||
public final Collection<String> affectedChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable gson instance to convert a class to json string and back in derived classes.
|
||||
*/
|
||||
protected static final Gson GSON = new Gson();
|
||||
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
/**
|
||||
* Bosch SHC configuration loaded from openHAB configuration.
|
||||
*/
|
||||
private @Nullable BoschSHCConfiguration config;
|
||||
|
||||
/**
|
||||
* Services of the device.
|
||||
*/
|
||||
private List<DeviceService<? extends BoschSHCServiceState>> services = new ArrayList<>();
|
||||
|
||||
public BoschSHCHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique id of the Bosch device.
|
||||
*
|
||||
* @return Unique id of the Bosch device.
|
||||
*/
|
||||
public @Nullable String getBoschID() {
|
||||
BoschSHCConfiguration config = this.config;
|
||||
if (config != null) {
|
||||
return config.id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this handler. Use this method to register all services of the device with
|
||||
* {@link #registerService(BoschSHCService)}.
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
this.config = getConfigAs(BoschSHCConfiguration.class);
|
||||
|
||||
try {
|
||||
this.initializeServices();
|
||||
|
||||
// Mark immediately as online - if the bridge is online, the thing is too.
|
||||
this.updateStatus(ThingStatus.ONLINE);
|
||||
} catch (BoschSHCException e) {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the refresh command of all registered services. Override it to handle custom commands (e.g. to update
|
||||
* states of services).
|
||||
*
|
||||
* @param channelUID {@link ChannelUID} of the channel to which the command was sent
|
||||
* @param command {@link Command}
|
||||
*/
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
// Refresh state of services that affect the channel
|
||||
for (DeviceService<? extends BoschSHCServiceState> deviceService : this.services) {
|
||||
if (deviceService.affectedChannels.contains(channelUID.getIdWithoutGroup())) {
|
||||
try {
|
||||
deviceService.service.refreshState();
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException | BoschSHCException e) {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
String.format("Error when trying to refresh state from service %s: %s",
|
||||
deviceService.service.getServiceName(), e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an update which is received from the bridge.
|
||||
*
|
||||
* @param serviceName Name of service the update came from.
|
||||
* @param stateData Current state of device service. Serialized as JSON.
|
||||
*/
|
||||
public void processUpdate(String serviceName, JsonElement stateData) {
|
||||
// Check services of device to correctly
|
||||
for (DeviceService<? extends BoschSHCServiceState> deviceService : this.services) {
|
||||
BoschSHCService<? extends BoschSHCServiceState> service = deviceService.service;
|
||||
if (serviceName.equals(service.getServiceName())) {
|
||||
service.onStateUpdate(stateData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be used by handlers to create their required services.
|
||||
*/
|
||||
protected void initializeServices() throws BoschSHCException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bridge handler for this thing handler.
|
||||
*
|
||||
* @return Bridge handler for this thing handler. Null if no or an invalid bridge was set in the configuration.
|
||||
* @throws BoschSHCException If bridge for handler is not set or an invalid bridge is set.
|
||||
*/
|
||||
protected BoschSHCBridgeHandler getBridgeHandler() throws BoschSHCException {
|
||||
Bridge bridge = this.getBridge();
|
||||
if (bridge == null) {
|
||||
throw new BoschSHCException(String.format("No valid bridge set for %s", this.getThing()));
|
||||
}
|
||||
BoschSHCBridgeHandler bridgeHandler = (BoschSHCBridgeHandler) bridge.getHandler();
|
||||
if (bridgeHandler == null) {
|
||||
throw new BoschSHCException(String.format("Bridge of %s has no valid bridge handler", this.getThing()));
|
||||
}
|
||||
return bridgeHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the Bosch Smart Home Controller for the state of the service with the specified name.
|
||||
*
|
||||
* @note Use services instead of directly requesting a state.
|
||||
*
|
||||
* @param stateName Name of the service to query
|
||||
* @param classOfT Class to convert the resulting JSON to
|
||||
*/
|
||||
protected <T extends BoschSHCServiceState> @Nullable T getState(String stateName, Class<T> classOfT) {
|
||||
String deviceId = this.getBoschID();
|
||||
if (deviceId == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
BoschSHCBridgeHandler bridgeHandler = this.getBridgeHandler();
|
||||
return bridgeHandler.getState(deviceId, stateName, classOfT);
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException | BoschSHCException e) {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
String.format("Error when trying to refresh state from service %s: %s", stateName, e.getMessage()));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and registers a new service for this device.
|
||||
*
|
||||
* @param <TService> Type of service.
|
||||
* @param <TState> Type of service state.
|
||||
* @param newService Supplier function to create a new instance of the service.
|
||||
* @param stateUpdateListener Function to call when a state update was received
|
||||
* from the device.
|
||||
* @param affectedChannels Channels which are affected by the state of this
|
||||
* service.
|
||||
* @return Instance of registered service.
|
||||
* @throws BoschSHCException
|
||||
*/
|
||||
protected <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> TService createService(
|
||||
Supplier<TService> newService, Consumer<TState> stateUpdateListener, Collection<String> affectedChannels)
|
||||
throws BoschSHCException {
|
||||
TService service = newService.get();
|
||||
this.registerService(service, stateUpdateListener, affectedChannels);
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a service for this device.
|
||||
*
|
||||
* @param <TService> Type of service.
|
||||
* @param <TState> Type of service state.
|
||||
* @param service Service to register.
|
||||
* @param stateUpdateListener Function to call when a state update was received
|
||||
* from the device.
|
||||
* @param affectedChannels Channels which are affected by the state of this
|
||||
* service.
|
||||
* @throws BoschSHCException If bridge for handler is not set or an invalid bridge is set.
|
||||
* @throws BoschSHCException If no device id is set.
|
||||
*/
|
||||
protected <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> void registerService(
|
||||
TService service, Consumer<TState> stateUpdateListener, Collection<String> affectedChannels)
|
||||
throws BoschSHCException {
|
||||
BoschSHCBridgeHandler bridgeHandler = this.getBridgeHandler();
|
||||
|
||||
String deviceId = this.getBoschID();
|
||||
if (deviceId == null) {
|
||||
throw new BoschSHCException(
|
||||
String.format("Could not register service for %s, no device id set", this.getThing()));
|
||||
}
|
||||
|
||||
service.initialize(bridgeHandler, deviceId, stateUpdateListener);
|
||||
this.registerService(service, affectedChannels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state of a device service.
|
||||
* Sets the status of the device to offline if setting the state fails.
|
||||
*
|
||||
* @param <TService> Type of service.
|
||||
* @param <TState> Type of service state.
|
||||
* @param service Service to set state for.
|
||||
* @param state State to set.
|
||||
*/
|
||||
protected <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> void updateServiceState(
|
||||
TService service, TState state) {
|
||||
try {
|
||||
service.setState(state);
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, String.format(
|
||||
"Error when trying to update state for service %s: %s", service.getServiceName(), e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a service of this device.
|
||||
*
|
||||
* @param service Service which belongs to this device
|
||||
* @param affectedChannels Channels which are affected by the state of this
|
||||
* service
|
||||
*/
|
||||
private <TState extends BoschSHCServiceState> void registerService(BoschSHCService<TState> service,
|
||||
Collection<String> affectedChannels) {
|
||||
this.services.add(new DeviceService<TState>(service, affectedChannels));
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices;
|
||||
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_CLIMATE_CONTROL;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_INWALL_SWITCH;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_MOTION_DETECTOR;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_SHC;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_THERMOSTAT;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_TWINGUARD;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.BoschSHCBridgeHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.climatecontrol.ClimateControlHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.inwallswitch.BoschInWallSwitchHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.motiondetector.MotionDetectorHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.shuttercontrol.ShutterControlHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.thermostat.ThermostatHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.twinguard.BoschTwinguardHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.windowcontact.WindowContactHandler;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link BoschSHCHandlerFactory} is responsible for creating things and
|
||||
* thing handlers.
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
* @author Christian Oeing - Added Shutter Control and ThermostatHandler; refactored handler mapping
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.boschshc", service = ThingHandlerFactory.class)
|
||||
public class BoschSHCHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static class ThingTypeHandlerMapping {
|
||||
public ThingTypeUID thingTypeUID;
|
||||
public Function<Thing, BaseThingHandler> handlerSupplier;
|
||||
|
||||
public ThingTypeHandlerMapping(ThingTypeUID thingTypeUID, Function<Thing, BaseThingHandler> handlerSupplier) {
|
||||
this.thingTypeUID = thingTypeUID;
|
||||
this.handlerSupplier = handlerSupplier;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Collection<ThingTypeHandlerMapping> SUPPORTED_THING_TYPES = List.of(
|
||||
new ThingTypeHandlerMapping(THING_TYPE_SHC, thing -> new BoschSHCBridgeHandler((Bridge) thing)),
|
||||
new ThingTypeHandlerMapping(THING_TYPE_INWALL_SWITCH, BoschInWallSwitchHandler::new),
|
||||
new ThingTypeHandlerMapping(THING_TYPE_TWINGUARD, BoschTwinguardHandler::new),
|
||||
new ThingTypeHandlerMapping(THING_TYPE_WINDOW_CONTACT, WindowContactHandler::new),
|
||||
new ThingTypeHandlerMapping(THING_TYPE_MOTION_DETECTOR, MotionDetectorHandler::new),
|
||||
new ThingTypeHandlerMapping(THING_TYPE_SHUTTER_CONTROL, ShutterControlHandler::new),
|
||||
new ThingTypeHandlerMapping(THING_TYPE_THERMOSTAT, ThermostatHandler::new),
|
||||
new ThingTypeHandlerMapping(THING_TYPE_CLIMATE_CONTROL, ClimateControlHandler::new));
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES.stream().anyMatch(mapping -> mapping.thingTypeUID.equals(thingTypeUID));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
// Search for mapping for thing type and return handler for it if found. Otherwise return null.
|
||||
return SUPPORTED_THING_TYPES.stream().filter(mapping -> mapping.thingTypeUID.equals(thingTypeUID)).findFirst()
|
||||
.<@Nullable BaseThingHandler> map(mapping -> mapping.handlerSupplier.apply(thing)).orElse(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge;
|
||||
|
||||
import static org.eclipse.jetty.http.HttpMethod.GET;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* HTTP client using own context with private & Bosch Certs
|
||||
* to pair and connect to the Bosch Smart Home Controller.
|
||||
*
|
||||
* @author Gerd Zanker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BoschHttpClient extends HttpClient {
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(BoschHttpClient.class);
|
||||
|
||||
private final String ipAddress;
|
||||
private final String systemPassword;
|
||||
|
||||
public BoschHttpClient(String ipAddress, String systemPassword, SslContextFactory sslContextFactory) {
|
||||
super(sslContextFactory);
|
||||
this.ipAddress = ipAddress;
|
||||
this.systemPassword = systemPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pairing URL for the Bosch SHC clients, using port 8443.
|
||||
* See https://github.com/BoschSmartHome/bosch-shc-api-docs/blob/master/postman/README.md
|
||||
*
|
||||
* @return URL for pairing
|
||||
*/
|
||||
public String getPairingUrl() {
|
||||
return String.format("https://%s:8443/smarthome/clients", this.ipAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Bosch SHC URL for the endpoint, using port 8444.
|
||||
*
|
||||
* @param endpoint a endpoint, see https://apidocs.bosch-smarthome.com/local/index.html
|
||||
* @return Bosch SHC URL for passed endpoint
|
||||
*/
|
||||
public String getBoschShcUrl(String endpoint) {
|
||||
return String.format("https://%s:8444/%s", this.ipAddress, endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SmartHome URL for the endpoint - shortcut of {@link BoschSslUtil::getBoschShcUrl()}
|
||||
*
|
||||
* @param endpoint a endpoint, see https://apidocs.bosch-smarthome.com/local/index.html
|
||||
* @return SmartHome URL for passed endpoint
|
||||
*/
|
||||
public String getBoschSmartHomeUrl(String endpoint) {
|
||||
return this.getBoschShcUrl(String.format("smarthome/%s", endpoint));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a device & service URL.
|
||||
* see https://apidocs.bosch-smarthome.com/local/index.html
|
||||
*
|
||||
* @param serviceName the name of the service
|
||||
* @param deviceId the device identifier
|
||||
* @return SmartHome URL for passed endpoint
|
||||
*/
|
||||
public String getServiceUrl(String serviceName, String deviceId) {
|
||||
return this.getBoschSmartHomeUrl(String.format("devices/%s/services/%s/state", deviceId, serviceName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Bosch SHC can be accessed.
|
||||
*
|
||||
* @return true if HTTP access was successful
|
||||
* @throws InterruptedException in case of an interrupt
|
||||
*/
|
||||
public boolean isAccessPossible() throws InterruptedException {
|
||||
try {
|
||||
String url = this.getBoschSmartHomeUrl("devices");
|
||||
Request request = this.createRequest(url, GET);
|
||||
ContentResponse contentResponse = request.send();
|
||||
String content = contentResponse.getContentAsString();
|
||||
logger.debug("Access check response complete: {} - return code: {}", content, contentResponse.getStatus());
|
||||
return true;
|
||||
} catch (TimeoutException | ExecutionException | NullPointerException e) {
|
||||
logger.debug("Access check response failed because of {}!", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pairs this client with the Bosch SHC.
|
||||
* Press pairing button on the Bosch Smart Home Controller!
|
||||
*
|
||||
* @return true if pairing was successful, otherwise false
|
||||
* @throws InterruptedException in case of an interrupt
|
||||
*/
|
||||
public boolean doPairing() throws InterruptedException {
|
||||
logger.trace("Starting pairing openHAB Client with Bosch SmartHomeController!");
|
||||
logger.trace("Please press the Bosch SHC button until LED starts blinking");
|
||||
|
||||
ContentResponse contentResponse;
|
||||
try {
|
||||
String publicCert = getCertFromSslContextFactory();
|
||||
logger.trace("Pairing with SHC {}", ipAddress);
|
||||
|
||||
// JSON Rest content
|
||||
Map<String, String> items = new HashMap<>();
|
||||
items.put("@type", "client");
|
||||
items.put("id", BoschSslUtil.getBoschShcClientId()); // Client Id contains the unique OpenHab instance Id
|
||||
items.put("name", "oss_OpenHAB_Binding"); // Client name according to
|
||||
// https://github.com/BoschSmartHome/bosch-shc-api-docs#terms-and-conditions
|
||||
items.put("primaryRole", "ROLE_RESTRICTED_CLIENT");
|
||||
items.put("certificate", "-----BEGIN CERTIFICATE-----\r" + publicCert + "\r-----END CERTIFICATE-----");
|
||||
|
||||
String url = this.getPairingUrl();
|
||||
Request request = this.createRequest(url, HttpMethod.POST, items).header("Systempassword",
|
||||
Base64.getEncoder().encodeToString(this.systemPassword.getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
contentResponse = request.send();
|
||||
|
||||
logger.trace("Pairing response complete: {} - return code: {}", contentResponse.getContentAsString(),
|
||||
contentResponse.getStatus());
|
||||
if (201 == contentResponse.getStatus()) {
|
||||
logger.debug("Pairing successful.");
|
||||
return true;
|
||||
} else {
|
||||
logger.info("Pairing failed with response status {}.", contentResponse.getStatus());
|
||||
return false;
|
||||
}
|
||||
} catch (TimeoutException | CertificateEncodingException | KeyStoreException | NullPointerException e) {
|
||||
logger.warn("Pairing failed with exception {}", e.getMessage());
|
||||
return false;
|
||||
} catch (ExecutionException e) {
|
||||
// javax.net.ssl.SSLHandshakeException: General SSLEngine problem
|
||||
// => usually the pairing failed, because hardware button was not pressed.
|
||||
logger.trace("Pairing failed - Details: {}", e.getMessage());
|
||||
logger.warn("Pairing failed. Was the Bosch SHC button pressed?");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a HTTP request.
|
||||
*
|
||||
* @param url for the HTTP request
|
||||
* @param method for the HTTP request
|
||||
* @return created HTTP request instance
|
||||
*/
|
||||
public Request createRequest(String url, HttpMethod method) {
|
||||
return this.createRequest(url, method, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a HTTP request.
|
||||
*
|
||||
* @param url for the HTTP request
|
||||
* @param method for the HTTP request
|
||||
* @param content for the HTTP request
|
||||
* @return created HTTP request instance
|
||||
*/
|
||||
public Request createRequest(String url, HttpMethod method, @Nullable Object content) {
|
||||
Request request = this.newRequest(url).method(method).header("Content-Type", "application/json");
|
||||
if (content != null) {
|
||||
String body = GSON.toJson(content);
|
||||
logger.trace("create request for {} and content {}", url, body);
|
||||
request = request.content(new StringContentProvider(body));
|
||||
} else {
|
||||
logger.trace("create request for {}", url);
|
||||
}
|
||||
|
||||
// Set default timeout
|
||||
request.timeout(10, TimeUnit.SECONDS);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request and expects a response of the specified type.
|
||||
*
|
||||
* @param request Request to send
|
||||
* @param responseContentClass Type of expected response
|
||||
* @throws ExecutionException in case of invalid HTTP request result
|
||||
* @throws TimeoutException in case of an HTTP request timeout
|
||||
* @throws InterruptedException in case of an interrupt
|
||||
*/
|
||||
public <TContent> TContent sendRequest(Request request, Class<TContent> responseContentClass)
|
||||
throws InterruptedException, TimeoutException, ExecutionException {
|
||||
ContentResponse contentResponse = request.send();
|
||||
|
||||
logger.debug("BoschHttpClient: response complete: {} - return code: {}", contentResponse.getContentAsString(),
|
||||
contentResponse.getStatus());
|
||||
|
||||
try {
|
||||
@Nullable
|
||||
TContent content = GSON.fromJson(contentResponse.getContentAsString(), responseContentClass);
|
||||
if (content == null) {
|
||||
throw new ExecutionException(String.format("Received no content in response, expected type %s",
|
||||
responseContentClass.getName()), null);
|
||||
}
|
||||
return content;
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new ExecutionException(String.format("Received invalid content in response, expected type %s: %s",
|
||||
responseContentClass.getName(), e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getCertFromSslContextFactory() throws KeyStoreException, CertificateEncodingException {
|
||||
Certificate cert = this.getSslContextFactory().getKeyStore()
|
||||
.getCertificate(BoschSslUtil.getBoschShcServerId(ipAddress));
|
||||
return Base64.getEncoder().encodeToString(cert.getEncoded());
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link BoschSHCBridgeConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BoschSHCBridgeConfiguration {
|
||||
|
||||
/**
|
||||
* IP address of the Bosch Smart Home Controller
|
||||
*/
|
||||
public String ipAddress = "";
|
||||
|
||||
/**
|
||||
* Password of the Bosch Smart Home Controller. Set during initialization via the Bosch app.
|
||||
*/
|
||||
public String password = "";
|
||||
}
|
@ -0,0 +1,410 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge;
|
||||
|
||||
import static org.eclipse.jetty.http.HttpMethod.GET;
|
||||
import static org.eclipse.jetty.http.HttpMethod.PUT;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.*;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException;
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.dto.JsonRestExceptionResponse;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* Representation of a connection with a Bosch Smart Home Controller bridge.
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
* @author Gerd Zanker - added HttpClient with pairing support
|
||||
* @author Christian Oeing - refactorings of e.g. server registration
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BoschSHCBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(BoschSHCBridgeHandler.class);
|
||||
|
||||
/**
|
||||
* gson instance to convert a class to json string and back.
|
||||
*/
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* Handler to do long polling.
|
||||
*/
|
||||
private final LongPolling longPolling;
|
||||
|
||||
private @Nullable BoschHttpClient httpClient;
|
||||
|
||||
private @Nullable ScheduledFuture<?> scheduledPairing;
|
||||
|
||||
public BoschSHCBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
|
||||
this.longPolling = new LongPolling(this.scheduler, this::handleLongPollResult, this::handleLongPollFailure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// Read configuration
|
||||
BoschSHCBridgeConfiguration config = getConfigAs(BoschSHCBridgeConfiguration.class);
|
||||
|
||||
if (config.ipAddress.isEmpty()) {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No IP address set");
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.password.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No system password set");
|
||||
return;
|
||||
}
|
||||
|
||||
SslContextFactory factory;
|
||||
try {
|
||||
// prepare SSL key and certificates
|
||||
factory = new BoschSslUtil(config.ipAddress).getSslContextFactory();
|
||||
} catch (PairingFailedException e) {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error-ssl");
|
||||
return;
|
||||
}
|
||||
|
||||
// Instantiate HttpClient with the SslContextFactory
|
||||
BoschHttpClient httpClient = this.httpClient = new BoschHttpClient(config.ipAddress, config.password, factory);
|
||||
|
||||
// Start http client
|
||||
try {
|
||||
httpClient.start();
|
||||
} catch (Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
String.format("Could not create http connection to controller: %s", e.getMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize bridge in the background.
|
||||
// Start initial access the first time
|
||||
scheduleInitialAccess(httpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// Cancel scheduled pairing.
|
||||
ScheduledFuture<?> scheduledPairing = this.scheduledPairing;
|
||||
if (scheduledPairing != null) {
|
||||
scheduledPairing.cancel(true);
|
||||
this.scheduledPairing = null;
|
||||
}
|
||||
|
||||
// Stop long polling.
|
||||
this.longPolling.stop();
|
||||
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient != null) {
|
||||
try {
|
||||
httpClient.stop();
|
||||
} catch (Exception e) {
|
||||
logger.debug("HttpClient failed on bridge disposal: {}", e.getMessage());
|
||||
}
|
||||
this.httpClient = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the initial access.
|
||||
* Use a delay if pairing fails and next retry is scheduled.
|
||||
*/
|
||||
private void scheduleInitialAccess(BoschHttpClient httpClient) {
|
||||
this.scheduledPairing = scheduler.schedule(() -> initialAccess(httpClient), 15, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the initial access.
|
||||
* Uses the HTTP Bosch SHC client
|
||||
* to check if access if possible
|
||||
* pairs this Bosch SHC Bridge with the SHC if necessary
|
||||
* and starts the first log poll.
|
||||
*/
|
||||
private void initialAccess(BoschHttpClient httpClient) {
|
||||
logger.debug("Initializing Bosch SHC Bridge: {} - HTTP client is: {} - version: 2020-04-05", this, httpClient);
|
||||
|
||||
try {
|
||||
// check access and pair if necessary
|
||||
if (!httpClient.isAccessPossible()) {
|
||||
// update status already if access is not possible
|
||||
this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE,
|
||||
"@text/offline.conf-error-pairing");
|
||||
if (!httpClient.doPairing()) {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error-pairing");
|
||||
}
|
||||
// restart initial access - needed also in case of successful pairing to check access again
|
||||
scheduleInitialAccess(httpClient);
|
||||
} else {
|
||||
// print rooms and devices if things are reachable
|
||||
boolean thingReachable = true;
|
||||
thingReachable &= this.getRooms();
|
||||
thingReachable &= this.getDevices();
|
||||
|
||||
if (thingReachable) {
|
||||
this.updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
// Start long polling
|
||||
try {
|
||||
this.longPolling.start(httpClient);
|
||||
} catch (LongPollingFailedException e) {
|
||||
this.handleLongPollFailure(e);
|
||||
}
|
||||
} else {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"@text/offline.not-reachable");
|
||||
// restart initial access
|
||||
scheduleInitialAccess(httpClient);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE,
|
||||
String.format("Pairing was interrupted: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of connected devices from the Smart-Home Controller
|
||||
*
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
private boolean getDevices() throws InterruptedException {
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug("Sending http request to Bosch to request clients: {}", httpClient);
|
||||
String url = httpClient.getBoschSmartHomeUrl("devices");
|
||||
ContentResponse contentResponse = httpClient.createRequest(url, GET).send();
|
||||
|
||||
String content = contentResponse.getContentAsString();
|
||||
logger.debug("Response complete: {} - return code: {}", content, contentResponse.getStatus());
|
||||
|
||||
Type collectionType = new TypeToken<ArrayList<Device>>() {
|
||||
}.getType();
|
||||
ArrayList<Device> devices = gson.fromJson(content, collectionType);
|
||||
|
||||
if (devices != null) {
|
||||
for (Device d : devices) {
|
||||
// Write found devices into openhab.log until we have implemented auto discovery
|
||||
logger.info("Found device: name={} id={}", d.name, d.id);
|
||||
if (d.deviceSerivceIDs != null) {
|
||||
for (String s : d.deviceSerivceIDs) {
|
||||
logger.info(".... service: {}", s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
logger.debug("HTTP request failed with exception {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleLongPollResult(LongPollResult result) {
|
||||
for (DeviceStatusUpdate update : result.result) {
|
||||
if (update != null && update.state != null) {
|
||||
logger.debug("Got update for {}", update.deviceId);
|
||||
|
||||
boolean handled = false;
|
||||
|
||||
Bridge bridge = this.getThing();
|
||||
for (Thing childThing : bridge.getThings()) {
|
||||
// All children of this should implement BoschSHCHandler
|
||||
ThingHandler baseHandler = childThing.getHandler();
|
||||
if (baseHandler != null && baseHandler instanceof BoschSHCHandler) {
|
||||
BoschSHCHandler handler = (BoschSHCHandler) baseHandler;
|
||||
String deviceId = handler.getBoschID();
|
||||
|
||||
handled = true;
|
||||
logger.debug("Registered device: {} - looking for {}", deviceId, update.deviceId);
|
||||
|
||||
if (deviceId != null && update.deviceId.equals(deviceId)) {
|
||||
logger.debug("Found child: {} - calling processUpdate with {}", handler, update.state);
|
||||
handler.processUpdate(update.id, update.state);
|
||||
}
|
||||
} else {
|
||||
logger.warn("longPoll: child handler for {} does not implement Bosch SHC handler", baseHandler);
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
logger.debug("Could not find a thing for device ID: {}", update.deviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLongPollFailure(Throwable e) {
|
||||
logger.warn("Long polling failed", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Long polling failed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of rooms from the Smart-Home controller
|
||||
*
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
private boolean getRooms() throws InterruptedException {
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient != null) {
|
||||
try {
|
||||
logger.debug("Sending http request to Bosch to request rooms");
|
||||
String url = httpClient.getBoschSmartHomeUrl("rooms");
|
||||
ContentResponse contentResponse = httpClient.createRequest(url, GET).send();
|
||||
|
||||
String content = contentResponse.getContentAsString();
|
||||
logger.debug("Response complete: {} - return code: {}", content, contentResponse.getStatus());
|
||||
|
||||
Type collectionType = new TypeToken<ArrayList<Room>>() {
|
||||
}.getType();
|
||||
|
||||
ArrayList<Room> rooms = gson.fromJson(content, collectionType);
|
||||
|
||||
if (rooms != null) {
|
||||
for (Room r : rooms) {
|
||||
logger.info("Found room: {}", r.name);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
logger.warn("HTTP request failed: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the Bosch Smart Home Controller for the state of the given thing.
|
||||
*
|
||||
* @param deviceId Id of device to get state for
|
||||
* @param stateName Name of the state to query
|
||||
* @param stateClass Class to convert the resulting JSON to
|
||||
* @throws ExecutionException
|
||||
* @throws TimeoutException
|
||||
* @throws InterruptedException
|
||||
* @throws BoschSHCException
|
||||
*/
|
||||
public <T extends BoschSHCServiceState> @Nullable T getState(String deviceId, String stateName, Class<T> stateClass)
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient == null) {
|
||||
logger.warn("HttpClient not initialized");
|
||||
return null;
|
||||
}
|
||||
|
||||
String url = httpClient.getServiceUrl(stateName, deviceId);
|
||||
Request request = httpClient.createRequest(url, GET).header("Accept", "application/json");
|
||||
|
||||
logger.debug("refreshState: Requesting \"{}\" from Bosch: {} via {}", stateName, deviceId, url);
|
||||
|
||||
ContentResponse contentResponse = request.send();
|
||||
|
||||
String content = contentResponse.getContentAsString();
|
||||
logger.debug("refreshState: Request complete: [{}] - return code: {}", content, contentResponse.getStatus());
|
||||
|
||||
int statusCode = contentResponse.getStatus();
|
||||
if (statusCode != 200) {
|
||||
JsonRestExceptionResponse errorResponse = gson.fromJson(content, JsonRestExceptionResponse.class);
|
||||
if (errorResponse != null) {
|
||||
throw new BoschSHCException(String.format(
|
||||
"State request for service %s of device %s failed with status code %d and error code %s",
|
||||
stateName, deviceId, errorResponse.statusCode, errorResponse.errorCode));
|
||||
} else {
|
||||
throw new BoschSHCException(
|
||||
String.format("State request for service %s of device %s failed with status code %d", stateName,
|
||||
deviceId, statusCode));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
T state = gson.fromJson(content, stateClass);
|
||||
if (state == null) {
|
||||
throw new BoschSHCException(String.format("Received invalid, expected type %s", stateClass.getName()));
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a state change for a device to the controller
|
||||
*
|
||||
* @param deviceId Id of device to change state for
|
||||
* @param serviceName Name of service of device to change state for
|
||||
* @param state New state data to set for service
|
||||
*
|
||||
* @return Response of request
|
||||
* @throws InterruptedException
|
||||
* @throws ExecutionException
|
||||
* @throws TimeoutException
|
||||
*/
|
||||
public <T extends BoschSHCServiceState> @Nullable Response putState(String deviceId, String serviceName, T state)
|
||||
throws InterruptedException, TimeoutException, ExecutionException {
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient == null) {
|
||||
logger.warn("HttpClient not initialized");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create request
|
||||
String url = httpClient.getServiceUrl(serviceName, deviceId);
|
||||
Request request = httpClient.createRequest(url, PUT, state);
|
||||
|
||||
// Send request
|
||||
Response response = request.send();
|
||||
return response;
|
||||
}
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.KeyStore;
|
||||
import java.security.Security;
|
||||
import java.security.Signature;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException;
|
||||
import org.openhab.core.OpenHAB;
|
||||
import org.openhab.core.id.InstanceUUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* SSL context utility.
|
||||
*
|
||||
* @author Gerd Zanker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BoschSslUtil {
|
||||
|
||||
private static final String OSS_OPENHAB_BINDING = "oss_openhab_binding";
|
||||
private static final String KEYSTORE_PASSWORD = "openhab";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(BoschSslUtil.class);
|
||||
|
||||
private final String boschShcServerID;
|
||||
private final String keystorePath;
|
||||
|
||||
/**
|
||||
* Returns unique ID for this Bosch SmartHomeController client.
|
||||
*
|
||||
* @return unique string containing the openhab UUID.
|
||||
*/
|
||||
public static String getBoschShcClientId() {
|
||||
return OSS_OPENHAB_BINDING + "_" + InstanceUUID.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ID for passed Bosch SmartHomeController server.
|
||||
*
|
||||
* @param shcServerID the ip address of the SHC server
|
||||
* @return unique string containing the server id
|
||||
*/
|
||||
public static String getBoschShcServerId(String shcServerID) {
|
||||
return OSS_OPENHAB_BINDING + "_" + shcServerID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param boschShcServerID the ip address of the SHC server
|
||||
*/
|
||||
public BoschSslUtil(String boschShcServerID) {
|
||||
this.boschShcServerID = boschShcServerID;
|
||||
this.keystorePath = getKeystorePath();
|
||||
}
|
||||
|
||||
/// Returns unique ID for Bosch SmartHomeController server.
|
||||
public String getBoschShcServerId() {
|
||||
return BoschSslUtil.getBoschShcServerId(boschShcServerID);
|
||||
}
|
||||
|
||||
/// Returns the unique keystore for each Bosch Smart Home Controller server.
|
||||
public String getKeystorePath() {
|
||||
return Paths.get(OpenHAB.getUserDataFolder(), "etc", getBoschShcServerId() + ".jks").toString();
|
||||
}
|
||||
|
||||
public SslContextFactory getSslContextFactory() throws PairingFailedException {
|
||||
// Instantiate and configure the SslContextFactory
|
||||
SslContextFactory sslContextFactory = new SslContextFactory.Client.Client(true); // Accept all certificates
|
||||
|
||||
// during pairing the cert from this keystore is accessed by HTTP client via name
|
||||
sslContextFactory.setKeyStore(getKeyStoreAndCreateIfNecessary());
|
||||
|
||||
// Keystore for managing the keys that have been used to pair with the SHC
|
||||
// https://www.eclipse.org/jetty/javadoc/9.4.12.v20180830/org/eclipse/jetty/util/ssl/SslContextFactory.html
|
||||
sslContextFactory.setKeyStorePath(keystorePath);
|
||||
sslContextFactory.setKeyStorePassword(KEYSTORE_PASSWORD);
|
||||
|
||||
// Bosch is using a self signed certificate
|
||||
sslContextFactory.setTrustAll(true);
|
||||
sslContextFactory.setValidateCerts(false);
|
||||
sslContextFactory.setValidatePeerCerts(false);
|
||||
sslContextFactory.setEndpointIdentificationAlgorithm(null);
|
||||
|
||||
return sslContextFactory;
|
||||
}
|
||||
|
||||
public KeyStore getKeyStoreAndCreateIfNecessary() throws PairingFailedException {
|
||||
try {
|
||||
File file = new File(keystorePath);
|
||||
if (!file.exists()) {
|
||||
// create new keystore
|
||||
logger.info("Creating new keystore {} because it doesn't exist.", keystorePath);
|
||||
return createKeyStore(keystorePath);
|
||||
} else {
|
||||
// load keystore as a first check
|
||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||
try (FileInputStream keystoreStream = new FileInputStream(file)) {
|
||||
keyStore.load(keystoreStream, KEYSTORE_PASSWORD.toCharArray());
|
||||
}
|
||||
logger.debug("Using existing keystore {}", keystorePath);
|
||||
return keyStore;
|
||||
}
|
||||
} catch (OperatorCreationException | GeneralSecurityException | IOException e) {
|
||||
logger.debug("Exception during keystore creation {}", e.getMessage());
|
||||
throw new PairingFailedException("Can not create or load keystore file: " + keystorePath
|
||||
+ ". Check path, write access and JKS content.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private X509Certificate generateClientCertificate(KeyPair keyPair)
|
||||
throws GeneralSecurityException, OperatorCreationException {
|
||||
final String dirName = "CN=" + getBoschShcClientId() + ", O=openHAB, L=None, ST=None, C=None";
|
||||
logger.debug("Creating a new self signed certificate: {}", dirName);
|
||||
final Instant now = Instant.now();
|
||||
final Date notBefore = Date.from(now);
|
||||
final Date notAfter = Date.from(now.plus(Duration.ofDays(365 * 10)));
|
||||
X500Name name = new X500Name(dirName);
|
||||
|
||||
// create the certificate
|
||||
X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(name, // Issuer
|
||||
BigInteger.valueOf(now.toEpochMilli()), notBefore, notAfter, name, // Subject
|
||||
keyPair.getPublic() // Public key to be associated with the certificate
|
||||
);
|
||||
// and sign it
|
||||
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSA").build(keyPair.getPrivate());
|
||||
return new JcaX509CertificateConverter().setProvider(new BouncyCastleProvider())
|
||||
.getCertificate(certificateBuilder.build(contentSigner));
|
||||
}
|
||||
|
||||
private KeyStore createKeyStore(String keystore)
|
||||
throws IOException, OperatorCreationException, GeneralSecurityException {
|
||||
// create a new keystore
|
||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||
keyStore.load(null, null);
|
||||
|
||||
// create new key pair for BoschSHC binding
|
||||
logger.debug("Creating new keypair");
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
|
||||
kpg.initialize(2048);
|
||||
KeyPair keyPair = kpg.generateKeyPair();
|
||||
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
Signature signer = Signature.getInstance("SHA256withRSA", "BC");
|
||||
signer.initSign(keyPair.getPrivate());
|
||||
signer.update("Hello openHAB".getBytes(StandardCharsets.UTF_8));
|
||||
signer.sign();
|
||||
|
||||
X509Certificate cert = generateClientCertificate(keyPair);
|
||||
|
||||
logger.debug("Adding keyEntry '{}' with self signed certificate to keystore", getBoschShcServerId());
|
||||
keyStore.setKeyEntry(getBoschShcServerId(), keyPair.getPrivate(), KEYSTORE_PASSWORD.toCharArray(),
|
||||
new Certificate[] { cert });
|
||||
|
||||
// add Bosch Certs
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
|
||||
logger.debug("Adding Issuing CA to keystore");
|
||||
try (BufferedInputStream streamIssuingCA = new BufferedInputStream(
|
||||
this.getClass().getResourceAsStream("SmartHomeControllerIssuingCA.pem"))) {
|
||||
Certificate certIssuingCA = cf.generateCertificate(streamIssuingCA);
|
||||
keyStore.setCertificateEntry("Smart Home Controller Issuing CA", certIssuingCA);
|
||||
}
|
||||
|
||||
logger.debug("Adding root CA to keystore");
|
||||
try (BufferedInputStream streamRootCa = new BufferedInputStream(
|
||||
this.getClass().getResourceAsStream("SmartHomeControllerProductiveRootCA.pem"))) {
|
||||
Certificate certRooCA = cf.generateCertificate(streamRootCa);
|
||||
keyStore.setCertificateEntry("Smart Home Controller Productive Root CA", certRooCA);
|
||||
}
|
||||
|
||||
logger.debug("Storing keystore to file {}", keystore);
|
||||
try (FileOutputStream keystoreStream = new FileOutputStream(keystore)) {
|
||||
keyStore.store(keystoreStream, KEYSTORE_PASSWORD.toCharArray());
|
||||
}
|
||||
|
||||
return keyStore;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Payload as POST data for triggering a RPC call on the Bosch Smart Home Controller.
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class JsonRpcRequest {
|
||||
|
||||
public String jsonrpc;
|
||||
public String method;
|
||||
public String[] params;
|
||||
|
||||
public JsonRpcRequest(String jsonrpc, String method, String[] params) {
|
||||
this.jsonrpc = jsonrpc;
|
||||
this.method = method;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public JsonRpcRequest() {
|
||||
this("", "", new String[0]);
|
||||
}
|
||||
|
||||
public String getJsonrpc() {
|
||||
return jsonrpc;
|
||||
}
|
||||
|
||||
public void setJsonrpc(String jsonrpc) {
|
||||
this.jsonrpc = jsonrpc;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public void setMethod(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public String[] getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public void setParams(String[] params) {
|
||||
this.params = params;
|
||||
}
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge;
|
||||
|
||||
import static org.eclipse.jetty.http.HttpMethod.POST;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollError;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* Handles the long polling to the Smart Home Controller.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LongPolling {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LongPolling.class);
|
||||
|
||||
/**
|
||||
* gson instance to convert a class to json string and back.
|
||||
*/
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* Executor to schedule long polls.
|
||||
*/
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
/**
|
||||
* Handler for long poll results.
|
||||
*/
|
||||
private final Consumer<LongPollResult> handleResult;
|
||||
|
||||
/**
|
||||
* Handler for unrecoverable.
|
||||
*/
|
||||
private final Consumer<Throwable> handleFailure;
|
||||
|
||||
/**
|
||||
* Current running long polling request.
|
||||
*/
|
||||
private @Nullable Request request;
|
||||
|
||||
/**
|
||||
* Indicates if long polling was aborted.
|
||||
*/
|
||||
private boolean aborted = false;
|
||||
|
||||
public LongPolling(ScheduledExecutorService scheduler, Consumer<LongPollResult> handleResult,
|
||||
Consumer<Throwable> handleFailure) {
|
||||
this.scheduler = scheduler;
|
||||
this.handleResult = handleResult;
|
||||
this.handleFailure = handleFailure;
|
||||
}
|
||||
|
||||
public void start(BoschHttpClient httpClient) throws LongPollingFailedException {
|
||||
// Subscribe to state updates.
|
||||
String subscriptionId = this.subscribe(httpClient);
|
||||
this.executeLongPoll(httpClient, subscriptionId);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// Abort long polling.
|
||||
this.aborted = true;
|
||||
Request request = this.request;
|
||||
if (request != null) {
|
||||
request.abort(new AbortLongPolling());
|
||||
this.request = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to events and store the subscription ID needed for long polling.
|
||||
*
|
||||
* @param httpClient Http client to use for sending subscription request
|
||||
* @return Subscription id
|
||||
*/
|
||||
private String subscribe(BoschHttpClient httpClient) throws LongPollingFailedException {
|
||||
try {
|
||||
String url = httpClient.getBoschShcUrl("remote/json-rpc");
|
||||
JsonRpcRequest request = new JsonRpcRequest("2.0", "RE/subscribe",
|
||||
new String[] { "com/bosch/sh/remote/*", null });
|
||||
logger.debug("Subscribe: Sending request: {} - using httpClient {}", gson.toJson(request), httpClient);
|
||||
Request httpRequest = httpClient.createRequest(url, POST, request);
|
||||
SubscribeResult response = httpClient.sendRequest(httpRequest, SubscribeResult.class);
|
||||
|
||||
logger.debug("Subscribe: Got subscription ID: {} {}", response.getResult(), response.getJsonrpc());
|
||||
String subscriptionId = response.getResult();
|
||||
return subscriptionId;
|
||||
} catch (TimeoutException | ExecutionException | InterruptedException e) {
|
||||
throw new LongPollingFailedException("Error on subscribe request", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeLongPoll(BoschHttpClient httpClient, String subscriptionId) {
|
||||
scheduler.execute(() -> this.longPoll(httpClient, subscriptionId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start long polling the home controller. Once a long poll resolves, a new one is started.
|
||||
*/
|
||||
private void longPoll(BoschHttpClient httpClient, String subscriptionId) {
|
||||
logger.debug("Sending long poll request");
|
||||
|
||||
JsonRpcRequest requestContent = new JsonRpcRequest("2.0", "RE/longPoll", new String[] { subscriptionId, "20" });
|
||||
String url = httpClient.getBoschShcUrl("remote/json-rpc");
|
||||
Request request = httpClient.createRequest(url, POST, requestContent);
|
||||
|
||||
// Long polling responds after 20 seconds with an empty response if no update has happened.
|
||||
// 10 second threshold was added to not time out if response from controller takes a bit longer than 20 seconds.
|
||||
request.timeout(30, TimeUnit.SECONDS);
|
||||
|
||||
this.request = request;
|
||||
LongPolling longPolling = this;
|
||||
request.send(new BufferingResponseListener() {
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
Throwable failure = result != null ? result.getFailure() : null;
|
||||
if (failure != null) {
|
||||
if (failure instanceof ExecutionException) {
|
||||
if (failure.getCause() instanceof AbortLongPolling) {
|
||||
logger.debug("Canceling long polling for subscription id {} because it was aborted",
|
||||
subscriptionId);
|
||||
} else {
|
||||
longPolling.handleFailure.accept(new LongPollingFailedException(
|
||||
"Unexpected exception during long polling request", failure));
|
||||
}
|
||||
} else {
|
||||
longPolling.handleFailure.accept(new LongPollingFailedException(
|
||||
"Unexpected exception during long polling request", failure));
|
||||
}
|
||||
} else {
|
||||
longPolling.onLongPollResponse(httpClient, subscriptionId, this.getContentAsString());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onLongPollResponse(BoschHttpClient httpClient, String subscriptionId, String content) {
|
||||
// Check if thing is still online
|
||||
if (this.aborted) {
|
||||
logger.debug("Canceling long polling for subscription id {} because it was aborted", subscriptionId);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Long poll response: {}", content);
|
||||
|
||||
String nextSubscriptionId = subscriptionId;
|
||||
|
||||
LongPollResult longPollResult = gson.fromJson(content, LongPollResult.class);
|
||||
if (longPollResult != null && longPollResult.result != null) {
|
||||
this.handleResult.accept(longPollResult);
|
||||
} else {
|
||||
logger.warn("Long poll response contained no results: {}", content);
|
||||
|
||||
// Check if we got a proper result from the SHC
|
||||
LongPollError longPollError = gson.fromJson(content, LongPollError.class);
|
||||
|
||||
if (longPollError != null && longPollError.error != null) {
|
||||
logger.warn("Got long poll error: {} (code: {})", longPollError.error.message,
|
||||
longPollError.error.code);
|
||||
|
||||
if (longPollError.error.code == LongPollError.SUBSCRIPTION_INVALID) {
|
||||
logger.warn("Subscription {} became invalid, subscribing again", subscriptionId);
|
||||
try {
|
||||
nextSubscriptionId = this.subscribe(httpClient);
|
||||
} catch (LongPollingFailedException e) {
|
||||
this.handleFailure.accept(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute next run.
|
||||
this.executeLongPoll(httpClient, nextSubscriptionId);
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private class AbortLongPolling extends BoschSHCException {
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Represents a single devices connected to the Bosch Smart Home Controller.
|
||||
*
|
||||
* Example from Json:
|
||||
*
|
||||
* {
|
||||
* "@type":"device",
|
||||
* "rootDeviceId":"64-da-a0-02-14-9b",
|
||||
* "id":"hdm:HomeMaticIP:3014F711A00004953859F31B",
|
||||
* "deviceServiceIds":["PowerMeter","PowerSwitch","PowerSwitchProgram","Routing"],
|
||||
* "manufacturer":"BOSCH",
|
||||
* "roomId":"hz_3",
|
||||
* "deviceModel":"PSM",
|
||||
* "serial":"3014F711A00004953859F31B",
|
||||
* "profile":"GENERIC",
|
||||
* "name":"Coffee Machine",
|
||||
* "status":"AVAILABLE",
|
||||
* "childDeviceIds":[]
|
||||
* }
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
public class Device {
|
||||
|
||||
@SerializedName("@type")
|
||||
public String type;
|
||||
|
||||
public String rootDeviceId;
|
||||
public String id;
|
||||
public List<String> deviceSerivceIDs;
|
||||
public String manufacturer;
|
||||
public String roomId;
|
||||
public String deviceModel;
|
||||
public String serial;
|
||||
public String profile;
|
||||
public String name;
|
||||
public String status;
|
||||
public List<String> childDeviceIds;
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge.dto;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Represents a device status update as represented by the Smart Home
|
||||
* Controller.
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
* @author Christian Oeing - refactorings of e.g. server registration
|
||||
*/
|
||||
public class DeviceStatusUpdate {
|
||||
/**
|
||||
* Url path of the service the update came from.
|
||||
*/
|
||||
public String path;
|
||||
|
||||
/**
|
||||
* The type of message.
|
||||
*/
|
||||
@SerializedName("@type")
|
||||
public String type;
|
||||
|
||||
/**
|
||||
* Name of service the update came from.
|
||||
*/
|
||||
public String id;
|
||||
|
||||
/**
|
||||
* Current state of device. Serialized as JSON.
|
||||
*/
|
||||
public JsonElement state;
|
||||
|
||||
/**
|
||||
* Id of device the update is for.
|
||||
*/
|
||||
public String deviceId;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.deviceId + "state: " + this.type;
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge.dto;
|
||||
|
||||
/**
|
||||
* Error response of the Controller for a Long Poll API call.
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
public class LongPollError {
|
||||
|
||||
public static final int SUBSCRIPTION_INVALID = -32001;
|
||||
|
||||
/**
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "error": {
|
||||
* "code":-32001,
|
||||
* "message":"No subscription with id: e8fei62b0-0"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
public class ErrorInfo {
|
||||
public int code;
|
||||
public String message;
|
||||
}
|
||||
|
||||
public String jsonrpc;
|
||||
public ErrorInfo error;
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Response of the Controller for a Long Poll API call.
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
public class LongPollResult {
|
||||
|
||||
/**
|
||||
* {"result":[
|
||||
* ..{
|
||||
* ...."path":"/devices/hdm:HomeMaticIP:3014F711A0001916D859A8A9/services/PowerSwitch",
|
||||
* ...."@type":"DeviceServiceData",
|
||||
* ...."id":"PowerSwitch",
|
||||
* ...."state":{
|
||||
* ......"@type":"powerSwitchState",
|
||||
* ......"switchState":"ON"
|
||||
* ....},
|
||||
* ...."deviceId":"hdm:HomeMaticIP:3014F711A0001916D859A8A9"}
|
||||
* ],"jsonrpc":"2.0"}
|
||||
*/
|
||||
|
||||
public ArrayList<DeviceStatusUpdate> result;
|
||||
public String jsonrpc;
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* A room as represented by the controller.
|
||||
*
|
||||
* Json example:
|
||||
* {"@type":"room","id":"hz_1","iconId":"icon_room_bedroom","name":"Bedroom"}
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
public class Room {
|
||||
|
||||
@SerializedName("@type")
|
||||
public String type;
|
||||
|
||||
public String id;
|
||||
public String name;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge.dto;
|
||||
|
||||
/**
|
||||
* Response of the Controller for a Long Poll API call.
|
||||
*
|
||||
* The result field will contain the subscription ID needed for further API calls (e.g. the long polling call)
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
public class SubscribeResult {
|
||||
private String result;
|
||||
private String jsonrpc;
|
||||
|
||||
public String getResult() {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
public String getJsonrpc() {
|
||||
return this.jsonrpc;
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.climatecontrol;
|
||||
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SETPOINT_TEMPERATURE;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.services.roomclimatecontrol.RoomClimateControlService;
|
||||
import org.openhab.binding.boschshc.internal.services.roomclimatecontrol.dto.RoomClimateControlServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.temperaturelevel.TemperatureLevelService;
|
||||
import org.openhab.binding.boschshc.internal.services.temperaturelevel.dto.TemperatureLevelServiceState;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* A virtual device which controls up to six Bosch Smart Home radiator thermostats in a room.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class ClimateControlHandler extends BoschSHCHandler {
|
||||
|
||||
private RoomClimateControlService roomClimateControlService;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param thing The Bosch Smart Home device that should be handled.
|
||||
*/
|
||||
public ClimateControlHandler(Thing thing) {
|
||||
super(thing);
|
||||
this.roomClimateControlService = new RoomClimateControlService();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeServices() throws BoschSHCException {
|
||||
super.createService(TemperatureLevelService::new, this::updateChannels, List.of(CHANNEL_TEMPERATURE));
|
||||
super.registerService(this.roomClimateControlService, this::updateChannels,
|
||||
List.of(CHANNEL_SETPOINT_TEMPERATURE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
super.handleCommand(channelUID, command);
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_SETPOINT_TEMPERATURE:
|
||||
if (command instanceof QuantityType<?>) {
|
||||
updateSetpointTemperature((QuantityType<?>) command);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channels which are linked to the {@link TemperatureLevelService} of the device.
|
||||
*
|
||||
* @param state Current state of {@link TemperatureLevelService}.
|
||||
*/
|
||||
private void updateChannels(TemperatureLevelServiceState state) {
|
||||
super.updateState(CHANNEL_TEMPERATURE, state.getTemperatureState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channels which are linked to the {@link RoomClimateControlService} of the device.
|
||||
*
|
||||
* @param state Current state of {@link RoomClimateControlService}.
|
||||
*/
|
||||
private void updateChannels(RoomClimateControlServiceState state) {
|
||||
super.updateState(CHANNEL_SETPOINT_TEMPERATURE, state.getSetpointTemperatureState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the desired temperature for the device.
|
||||
*
|
||||
* @param quantityType Command which contains the new desired temperature.
|
||||
*/
|
||||
private void updateSetpointTemperature(QuantityType<?> quantityType) {
|
||||
QuantityType<?> celsiusType = quantityType.toUnit(SIUnits.CELSIUS);
|
||||
if (celsiusType == null) {
|
||||
logger.debug("Could not convert quantity command to celsius");
|
||||
return;
|
||||
}
|
||||
|
||||
double setpointTemperature = celsiusType.doubleValue();
|
||||
this.updateServiceState(this.roomClimateControlService,
|
||||
new RoomClimateControlServiceState(setpointTemperature));
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.inwallswitch;
|
||||
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.measure.quantity.Energy;
|
||||
import javax.measure.quantity.Power;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.inwallswitch.dto.PowerMeterState;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.services.powerswitch.PowerSwitchService;
|
||||
import org.openhab.binding.boschshc.internal.services.powerswitch.PowerSwitchState;
|
||||
import org.openhab.binding.boschshc.internal.services.powerswitch.dto.PowerSwitchServiceState;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Represents Bosch in-wall switches.
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BoschInWallSwitchHandler extends BoschSHCHandler {
|
||||
|
||||
private final PowerSwitchService powerSwitchService;
|
||||
|
||||
public BoschInWallSwitchHandler(Thing thing) {
|
||||
super(thing);
|
||||
this.powerSwitchService = new PowerSwitchService();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeServices() throws BoschSHCException {
|
||||
super.initializeServices();
|
||||
|
||||
this.registerService(this.powerSwitchService, this::updateChannels, List.of(CHANNEL_POWER_SWITCH));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
super.handleCommand(channelUID, command);
|
||||
|
||||
logger.debug("Handle command for: {} - {}", channelUID.getThingUID(), command);
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_POWER_CONSUMPTION: {
|
||||
PowerMeterState state = this.getState("PowerMeter", PowerMeterState.class);
|
||||
if (state != null) {
|
||||
updatePowerMeterState(state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CHANNEL_ENERGY_CONSUMPTION:
|
||||
// Nothing to do here, since the same update is received from POWER_CONSUMPTION
|
||||
break;
|
||||
default:
|
||||
logger.warn("Received refresh request for unsupported channel: {}", channelUID);
|
||||
}
|
||||
} else {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_POWER_SWITCH:
|
||||
if (command instanceof OnOffType) {
|
||||
updatePowerSwitchState((OnOffType) command);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updatePowerMeterState(PowerMeterState state) {
|
||||
logger.debug("Parsed power meter state of {}: energy {} - power {}", this.getBoschID(), state.energyConsumption,
|
||||
state.energyConsumption);
|
||||
|
||||
updateState(CHANNEL_POWER_CONSUMPTION, new QuantityType<Power>(state.powerConsumption, Units.WATT));
|
||||
updateState(CHANNEL_ENERGY_CONSUMPTION, new QuantityType<Energy>(state.energyConsumption, Units.WATT_HOUR));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channels which are linked to the {@link PowerSwitchService} of the device.
|
||||
*
|
||||
* @param state Current state of {@link PowerSwitchService}.
|
||||
*/
|
||||
private void updateChannels(PowerSwitchServiceState state) {
|
||||
State powerState = OnOffType.from(state.switchState.toString());
|
||||
super.updateState(CHANNEL_POWER_SWITCH, powerState);
|
||||
}
|
||||
|
||||
private void updatePowerSwitchState(OnOffType command) {
|
||||
PowerSwitchServiceState state = new PowerSwitchServiceState();
|
||||
state.switchState = PowerSwitchState.valueOf(command.toFullString());
|
||||
this.updateServiceState(this.powerSwitchService, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processUpdate(String id, JsonElement state) {
|
||||
super.processUpdate(id, state);
|
||||
|
||||
logger.debug("in-wall switch: received update: ID {} state {}", id, state);
|
||||
|
||||
if (id.equals("PowerMeter")) {
|
||||
PowerMeterState powerMeterState = GSON.fromJson(state, PowerMeterState.class);
|
||||
if (powerMeterState == null) {
|
||||
logger.warn("Received unknown update in in-wall switch: {}", state);
|
||||
} else {
|
||||
updatePowerMeterState(powerMeterState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.inwallswitch.dto;
|
||||
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
|
||||
/**
|
||||
* PowerMeterState
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
public class PowerMeterState extends BoschSHCServiceState {
|
||||
|
||||
public PowerMeterState() {
|
||||
super("powerMeterState");
|
||||
}
|
||||
|
||||
public double energyConsumption;
|
||||
public double powerConsumption;
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.motiondetector;
|
||||
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_LATEST_MOTION;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.motiondetector.dto.LatestMotionState;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* MotionDetectorHandler
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MotionDetectorHandler extends BoschSHCHandler {
|
||||
|
||||
public MotionDetectorHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("Handle command for: {} - {}", channelUID.getThingUID(), command);
|
||||
|
||||
if (CHANNEL_LATEST_MOTION.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
LatestMotionState state = this.getState("LatestMotion", LatestMotionState.class);
|
||||
if (state != null) {
|
||||
updateLatestMotionState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateLatestMotionState(LatestMotionState state) {
|
||||
DateTimeType date = new DateTimeType(state.latestMotionDetected);
|
||||
updateState(CHANNEL_LATEST_MOTION, date);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processUpdate(String id, JsonElement state) {
|
||||
logger.debug("Motion detector: received update: {} {}", id, state);
|
||||
|
||||
@Nullable
|
||||
LatestMotionState latestMotionState = GSON.fromJson(state, LatestMotionState.class);
|
||||
if (latestMotionState == null) {
|
||||
logger.warn("Received unknown update in in-wall switch: {}", state);
|
||||
return;
|
||||
}
|
||||
updateLatestMotionState(latestMotionState);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.motiondetector.dto;
|
||||
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
|
||||
/**
|
||||
* {
|
||||
* "result": [
|
||||
* {
|
||||
* "path": "/devices/hdm:ZigBee:000d6f0004b95a62/services/LatestMotion",
|
||||
* "@type": "DeviceServiceData",
|
||||
* "id": "LatestMotion",
|
||||
* "state": {
|
||||
* "latestMotionDetected": "2020-04-03T19:02:19.054Z",
|
||||
* "@type": "latestMotionState"
|
||||
* },
|
||||
* "deviceId": "hdm:ZigBee:000d6f0004b95a62"
|
||||
* }
|
||||
* ],
|
||||
* "jsonrpc": "2.0"
|
||||
* }
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
public class LatestMotionState extends BoschSHCServiceState {
|
||||
|
||||
public LatestMotionState() {
|
||||
super("latestMotionState");
|
||||
}
|
||||
|
||||
public String latestMotionDetected;
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.shuttercontrol;
|
||||
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_LEVEL;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.services.shuttercontrol.OperationState;
|
||||
import org.openhab.binding.boschshc.internal.services.shuttercontrol.ShutterControlService;
|
||||
import org.openhab.binding.boschshc.internal.services.shuttercontrol.dto.ShutterControlServiceState;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* Handler for a shutter control device
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShutterControlHandler extends BoschSHCHandler {
|
||||
/**
|
||||
* Utility functions to convert data between Bosch things and openHAB items
|
||||
*/
|
||||
static final class DataConversion {
|
||||
public static int levelToOpenPercentage(double level) {
|
||||
return (int) Math.round((1 - level) * 100);
|
||||
}
|
||||
|
||||
public static double openPercentageToLevel(double openPercentage) {
|
||||
return (100 - openPercentage) / 100.0;
|
||||
}
|
||||
}
|
||||
|
||||
private ShutterControlService shutterControlService;
|
||||
|
||||
public ShutterControlHandler(Thing thing) {
|
||||
super(thing);
|
||||
this.shutterControlService = new ShutterControlService();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeServices() throws BoschSHCException {
|
||||
super.initializeServices();
|
||||
|
||||
this.registerService(this.shutterControlService, this::updateChannels, List.of(CHANNEL_LEVEL));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
super.handleCommand(channelUID, command);
|
||||
|
||||
if (command instanceof UpDownType) {
|
||||
// Set full close/open as target state
|
||||
UpDownType upDownType = (UpDownType) command;
|
||||
ShutterControlServiceState state = new ShutterControlServiceState();
|
||||
if (upDownType == UpDownType.UP) {
|
||||
state.level = 1.0;
|
||||
} else if (upDownType == UpDownType.DOWN) {
|
||||
state.level = 0.0;
|
||||
} else {
|
||||
logger.warn("Received unknown UpDownType command: {}", upDownType);
|
||||
return;
|
||||
}
|
||||
this.updateServiceState(this.shutterControlService, state);
|
||||
} else if (command instanceof StopMoveType) {
|
||||
StopMoveType stopMoveType = (StopMoveType) command;
|
||||
if (stopMoveType == StopMoveType.STOP) {
|
||||
// Set STOPPED operation state
|
||||
ShutterControlServiceState state = new ShutterControlServiceState();
|
||||
state.operationState = OperationState.STOPPED;
|
||||
this.updateServiceState(this.shutterControlService, state);
|
||||
}
|
||||
} else if (command instanceof PercentType) {
|
||||
// Set specific level
|
||||
PercentType percentType = (PercentType) command;
|
||||
double level = DataConversion.openPercentageToLevel(percentType.doubleValue());
|
||||
this.updateServiceState(this.shutterControlService, new ShutterControlServiceState(level));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChannels(ShutterControlServiceState state) {
|
||||
if (state.level != null) {
|
||||
// Convert level to open ratio
|
||||
int openPercentage = DataConversion.levelToOpenPercentage(state.level);
|
||||
updateState(CHANNEL_LEVEL, new PercentType(openPercentage));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.thermostat;
|
||||
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_VALVE_TAPPET_POSITION;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.services.temperaturelevel.TemperatureLevelService;
|
||||
import org.openhab.binding.boschshc.internal.services.temperaturelevel.dto.TemperatureLevelServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.valvetappet.ValveTappetService;
|
||||
import org.openhab.binding.boschshc.internal.services.valvetappet.dto.ValveTappetServiceState;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
/**
|
||||
* Handler for a thermostat device.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class ThermostatHandler extends BoschSHCHandler {
|
||||
|
||||
public ThermostatHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeServices() throws BoschSHCException {
|
||||
this.createService(TemperatureLevelService::new, this::updateChannels, List.of(CHANNEL_TEMPERATURE));
|
||||
this.createService(ValveTappetService::new, this::updateChannels, List.of(CHANNEL_VALVE_TAPPET_POSITION));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channels which are linked to the {@link TemperatureLevelService} of the device.
|
||||
*
|
||||
* @param state Current state of {@link TemperatureLevelService}.
|
||||
*/
|
||||
private void updateChannels(TemperatureLevelServiceState state) {
|
||||
super.updateState(CHANNEL_TEMPERATURE, state.getTemperatureState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channels which are linked to the {@link ValveTappetService} of the device.
|
||||
*
|
||||
* @param state Current state of {@link ValveTappetService}.
|
||||
*/
|
||||
private void updateChannels(ValveTappetServiceState state) {
|
||||
super.updateState(CHANNEL_VALVE_TAPPET_POSITION, state.getPositionState());
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.twinguard;
|
||||
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*;
|
||||
|
||||
import javax.measure.quantity.Dimensionless;
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.twinguard.dto.AirQualityLevelState;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link BoschSHCHandler} is responsible for handling commands for the TwinGuard handler.
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BoschTwinguardHandler extends BoschSHCHandler {
|
||||
|
||||
public BoschTwinguardHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
Bridge bridge = this.getBridge();
|
||||
|
||||
if (bridge != null) {
|
||||
logger.debug("Handle command for: {} - {}", channelUID.getThingUID(), command);
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
AirQualityLevelState state = this.getState("AirQualityLevel", AirQualityLevelState.class);
|
||||
if (state != null) {
|
||||
updateAirQualityState(state);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Bridge is NUL");
|
||||
}
|
||||
}
|
||||
|
||||
void updateAirQualityState(AirQualityLevelState state) {
|
||||
updateState(CHANNEL_TEMPERATURE, new QuantityType<Temperature>(state.temperature, SIUnits.CELSIUS));
|
||||
updateState(CHANNEL_TEMPERATURE_RATING, new StringType(state.temperatureRating));
|
||||
updateState(CHANNEL_HUMIDITY, new QuantityType<Dimensionless>(state.humidity, Units.ONE));
|
||||
updateState(CHANNEL_HUMIDITY_RATING, new StringType(state.humidityRating));
|
||||
updateState(CHANNEL_PURITY, new QuantityType<Dimensionless>(state.purity, Units.ONE));
|
||||
updateState(CHANNEL_AIR_DESCRIPTION, new StringType(state.description));
|
||||
updateState(CHANNEL_PURITY_RATING, new StringType(state.purityRating));
|
||||
updateState(CHANNEL_COMBINED_RATING, new StringType(state.combinedRating));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processUpdate(String id, JsonElement state) throws JsonSyntaxException {
|
||||
logger.debug("Twinguard: received update: {} {}", id, state);
|
||||
|
||||
AirQualityLevelState parsed = GSON.fromJson(state, AirQualityLevelState.class);
|
||||
if (parsed == null) {
|
||||
logger.warn("Received unknown update in in-wall switch: {}", state);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Parsed switch state of {}: {}", this.getBoschID(), parsed);
|
||||
updateAirQualityState(parsed);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.twinguard.dto;
|
||||
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
|
||||
/**
|
||||
* Represents the state of a device as reported from the Smart Home Controller
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
public class AirQualityLevelState extends BoschSHCServiceState {
|
||||
|
||||
public AirQualityLevelState() {
|
||||
super("airQualityLevelState");
|
||||
}
|
||||
|
||||
/*
|
||||
* {"maxTemperature":25,"minTemperature":20,"custom":false,"name":"HALLWAY","maxHumidity":60,"minHumidity":40,
|
||||
* "maxPurity":1000}
|
||||
*/
|
||||
class ComfortZone {
|
||||
double maxTemperature;
|
||||
double minTemperature;
|
||||
boolean custom;
|
||||
String name;
|
||||
double maxHumidity;
|
||||
double minHumidity;
|
||||
double maxPurity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {"temperatureRating":"GOOD","humidityRating":"MEDIUM","purity":620,"comfortZone":....,"@type":"airQualityLevelState",
|
||||
* "purityRating":"GOOD","temperature":23.77,"description":"LITTLE_DRY","humidity":32.69,"combinedRating":"MEDIUM"}
|
||||
*/
|
||||
|
||||
public String temperatureRating;
|
||||
public String humidityRating;
|
||||
|
||||
public int purity;
|
||||
|
||||
public ComfortZone comfortZone;
|
||||
|
||||
public String purityRating;
|
||||
|
||||
public double temperature;
|
||||
public String description;
|
||||
|
||||
public double humidity;
|
||||
public String combinedRating;
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.windowcontact;
|
||||
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_CONTACT;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.services.shuttercontact.ShutterContactService;
|
||||
import org.openhab.binding.boschshc.internal.services.shuttercontact.ShutterContactState;
|
||||
import org.openhab.binding.boschshc.internal.services.shuttercontact.dto.ShutterContactServiceState;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link BoschSHCHandler} is responsible for handling Bosch window/door contacts.
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WindowContactHandler extends BoschSHCHandler {
|
||||
|
||||
public WindowContactHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeServices() throws BoschSHCException {
|
||||
this.createService(ShutterContactService::new, this::updateChannels, List.of(CHANNEL_CONTACT));
|
||||
}
|
||||
|
||||
private void updateChannels(ShutterContactServiceState state) {
|
||||
State contact = state.value == ShutterContactState.CLOSED ? OpenClosedType.CLOSED : OpenClosedType.OPEN;
|
||||
updateState(CHANNEL_CONTACT, contact);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.exceptions;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception class for Bosch Smart Home controller errors.
|
||||
*
|
||||
* @author Gerd Zanker - Initial contribution
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@NonNullByDefault
|
||||
public class BoschSHCException extends Exception {
|
||||
public BoschSHCException() {
|
||||
}
|
||||
|
||||
public BoschSHCException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BoschSHCException(String message, Throwable e) {
|
||||
super(message, e);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.exceptions;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Thrown if the long polling failed
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@NonNullByDefault
|
||||
public class LongPollingFailedException extends BoschSHCException {
|
||||
public LongPollingFailedException(String message, Throwable e) {
|
||||
super(message, e);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.exceptions;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Thrown if the pairing failed multiple times
|
||||
*
|
||||
* @author Gerd Zanker - Initial contribution
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@NonNullByDefault
|
||||
public class PairingFailedException extends BoschSHCException {
|
||||
public PairingFailedException() {
|
||||
}
|
||||
|
||||
public PairingFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PairingFailedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.BoschSHCBridgeHandler;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Base class of a service of a Bosch Smart Home device.
|
||||
* The services of the devices and their official APIs can be found here: https://apidocs.bosch-smarthome.com/local/
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class BoschSHCService<TState extends BoschSHCServiceState> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(BoschSHCService.class);
|
||||
|
||||
/**
|
||||
* Unique service name
|
||||
*/
|
||||
private final String serviceName;
|
||||
|
||||
/**
|
||||
* Class of service state
|
||||
*/
|
||||
private final Class<TState> stateClass;
|
||||
|
||||
/**
|
||||
* gson instance to convert a class to json string and back.
|
||||
*/
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* Bridge to use for communication from/to the device
|
||||
*/
|
||||
private @Nullable BoschSHCBridgeHandler bridgeHandler;
|
||||
|
||||
/**
|
||||
* Id of device the service belongs to
|
||||
*/
|
||||
private @Nullable String deviceId;
|
||||
|
||||
/**
|
||||
* Function to call after receiving state updates from the device
|
||||
*/
|
||||
private @Nullable Consumer<TState> stateUpdateListener;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param serviceName Unique name of the service.
|
||||
* @param stateClass State class that this service uses for data transfers from/to the device.
|
||||
*/
|
||||
protected BoschSHCService(String serviceName, Class<TState> stateClass) {
|
||||
this.serviceName = serviceName;
|
||||
this.stateClass = stateClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the service
|
||||
*
|
||||
* @param bridgeHandler Bridge to use for communication from/to the device
|
||||
* @param deviceId Id of device this service is for
|
||||
* @param stateUpdateListener Function to call when a state update was received from the device.
|
||||
*/
|
||||
public void initialize(BoschSHCBridgeHandler bridgeHandler, String deviceId,
|
||||
@Nullable Consumer<TState> stateUpdateListener) {
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
this.deviceId = deviceId;
|
||||
this.stateUpdateListener = stateUpdateListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique name of this service.
|
||||
*
|
||||
* @return Unique name of the service.
|
||||
*/
|
||||
public String getServiceName() {
|
||||
return this.serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class of the state this service provides.
|
||||
*
|
||||
* @return Class of the state this service provides.
|
||||
*/
|
||||
public Class<TState> getStateClass() {
|
||||
return this.stateClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the current state of the service and updates it.
|
||||
*
|
||||
* @throws ExecutionException
|
||||
* @throws TimeoutException
|
||||
* @throws InterruptedException
|
||||
* @throws BoschSHCException
|
||||
*/
|
||||
public void refreshState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
@Nullable
|
||||
TState state = this.getState();
|
||||
if (state != null) {
|
||||
this.onStateUpdate(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the current state of the device with the specified id.
|
||||
*
|
||||
* @return Current state of the device.
|
||||
* @throws ExecutionException
|
||||
* @throws TimeoutException
|
||||
* @throws InterruptedException
|
||||
* @throws BoschSHCException
|
||||
*/
|
||||
public @Nullable TState getState()
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
String deviceId = this.deviceId;
|
||||
if (deviceId == null) {
|
||||
return null;
|
||||
}
|
||||
BoschSHCBridgeHandler bridgeHandler = this.bridgeHandler;
|
||||
if (bridgeHandler == null) {
|
||||
return null;
|
||||
}
|
||||
return bridgeHandler.getState(deviceId, this.serviceName, this.stateClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the device with the specified id.
|
||||
*
|
||||
* @param state State to set.
|
||||
* @throws InterruptedException
|
||||
* @throws ExecutionException
|
||||
* @throws TimeoutException
|
||||
*/
|
||||
public void setState(TState state) throws InterruptedException, TimeoutException, ExecutionException {
|
||||
String deviceId = this.deviceId;
|
||||
if (deviceId == null) {
|
||||
return;
|
||||
}
|
||||
BoschSHCBridgeHandler bridgeHandler = this.bridgeHandler;
|
||||
if (bridgeHandler == null) {
|
||||
return;
|
||||
}
|
||||
bridgeHandler.putState(deviceId, this.serviceName, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* A state update was received from the bridge
|
||||
*
|
||||
* @param stateData Current state of service. Serialized as JSON.
|
||||
*/
|
||||
public void onStateUpdate(JsonElement stateData) {
|
||||
@Nullable
|
||||
TState state = gson.fromJson(stateData, this.stateClass);
|
||||
if (state == null) {
|
||||
this.logger.warn("Received invalid, expected type {}", this.stateClass.getName());
|
||||
return;
|
||||
}
|
||||
this.onStateUpdate(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* A state update was received from the bridge.
|
||||
*
|
||||
* @param state Current state of service as an instance of the state class.
|
||||
*/
|
||||
private void onStateUpdate(TState state) {
|
||||
Consumer<TState> stateUpdateListener = this.stateUpdateListener;
|
||||
if (stateUpdateListener != null) {
|
||||
stateUpdateListener.accept(state);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Base Bosch Smart Home Controller service state.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
public class BoschSHCServiceState {
|
||||
@SerializedName("@type")
|
||||
private final String type;
|
||||
|
||||
protected BoschSHCServiceState(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.dto;
|
||||
|
||||
/**
|
||||
* Generic error response of the Bosch REST API.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
public class JsonRestExceptionResponse extends BoschSHCServiceState {
|
||||
public JsonRestExceptionResponse() {
|
||||
super("JsonRestExceptionResponseEntity");
|
||||
this.errorCode = "";
|
||||
this.statusCode = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The error code of the occurred Exception.
|
||||
*/
|
||||
public String errorCode;
|
||||
|
||||
/**
|
||||
* The HTTP status of the error.
|
||||
*/
|
||||
public int statusCode;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.powerswitch;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
|
||||
import org.openhab.binding.boschshc.internal.services.powerswitch.dto.PowerSwitchServiceState;
|
||||
|
||||
/**
|
||||
* Service to get and set the state of a power switch.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PowerSwitchService extends BoschSHCService<PowerSwitchServiceState> {
|
||||
|
||||
public PowerSwitchService() {
|
||||
super("PowerSwitch", PowerSwitchServiceState.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.powerswitch;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Possible states of a power switch.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum PowerSwitchState {
|
||||
ON,
|
||||
OFF
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.powerswitch.dto;
|
||||
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.powerswitch.PowerSwitchState;
|
||||
|
||||
/**
|
||||
* Represents the state of a power switch device as reported from the Smart Home Controller
|
||||
*
|
||||
* @author Stefan Kästle - Initial contribution
|
||||
* @author Christian Oeing - Adjustments to match general service state structure
|
||||
*/
|
||||
public class PowerSwitchServiceState extends BoschSHCServiceState {
|
||||
|
||||
public PowerSwitchServiceState() {
|
||||
super("powerSwitchState");
|
||||
}
|
||||
|
||||
/**
|
||||
* Current state of power switch.
|
||||
*/
|
||||
public PowerSwitchState switchState;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.roomclimatecontrol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
|
||||
import org.openhab.binding.boschshc.internal.services.roomclimatecontrol.dto.RoomClimateControlServiceState;
|
||||
|
||||
/**
|
||||
* Service of a virtual device which controls the radiator thermostats in a room.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RoomClimateControlService extends BoschSHCService<RoomClimateControlServiceState> {
|
||||
public RoomClimateControlService() {
|
||||
super("RoomClimateControl", RoomClimateControlServiceState.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.roomclimatecontrol.dto;
|
||||
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* State for {@link RoomClimateControlService} to get and set the desired temperature of a room.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
public class RoomClimateControlServiceState extends BoschSHCServiceState {
|
||||
|
||||
private static final String TYPE = "climateControlState";
|
||||
|
||||
public RoomClimateControlServiceState() {
|
||||
super(TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param setpointTemperature Desired temperature (in degree celsius).
|
||||
*/
|
||||
public RoomClimateControlServiceState(double setpointTemperature) {
|
||||
super(TYPE);
|
||||
this.setpointTemperature = setpointTemperature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Desired temperature (in degree celsius).
|
||||
*
|
||||
* @apiNote Min: 5.0, Max: 30.0.
|
||||
* @apiNote Can be set in 0.5 steps.
|
||||
*/
|
||||
private double setpointTemperature;
|
||||
|
||||
/**
|
||||
* Desired temperature state to set for a thing.
|
||||
*
|
||||
* @return Desired temperature state to set for a thing.
|
||||
*/
|
||||
public State getSetpointTemperatureState() {
|
||||
return new QuantityType<Temperature>(this.setpointTemperature, SIUnits.CELSIUS);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.shuttercontact;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
|
||||
import org.openhab.binding.boschshc.internal.services.shuttercontact.dto.ShutterContactServiceState;
|
||||
|
||||
/**
|
||||
* Service to get the state of shutters.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShutterContactService extends BoschSHCService<ShutterContactServiceState> {
|
||||
public ShutterContactService() {
|
||||
super("ShutterContact", ShutterContactServiceState.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.shuttercontact;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Possible values for shutter contacts.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ShutterContactState {
|
||||
OPEN,
|
||||
CLOSED;
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.shuttercontact.dto;
|
||||
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.shuttercontact.ShutterContactState;
|
||||
|
||||
/**
|
||||
* State for the shutter contact service
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
public class ShutterContactServiceState extends BoschSHCServiceState {
|
||||
/**
|
||||
* Current state of shutter contact.
|
||||
*/
|
||||
public ShutterContactState value;
|
||||
|
||||
public ShutterContactServiceState() {
|
||||
super("shutterContactState");
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.shuttercontrol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Operation State.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum OperationState {
|
||||
MOVING,
|
||||
STOPPED;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.shuttercontrol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
|
||||
import org.openhab.binding.boschshc.internal.services.shuttercontrol.dto.ShutterControlServiceState;
|
||||
|
||||
/**
|
||||
* Service to control the shutters of a device.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShutterControlService extends BoschSHCService<ShutterControlServiceState> {
|
||||
public ShutterControlService() {
|
||||
super("ShutterControl", ShutterControlServiceState.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.shuttercontrol.dto;
|
||||
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.shuttercontrol.OperationState;
|
||||
|
||||
/**
|
||||
* State for a shutter control device
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
public class ShutterControlServiceState extends BoschSHCServiceState {
|
||||
/**
|
||||
* Current open ratio of shutter (0.0 [closed] to 1.0 [open])
|
||||
*/
|
||||
public Double level;
|
||||
|
||||
/**
|
||||
* Current operation state of shutter
|
||||
*/
|
||||
public OperationState operationState;
|
||||
|
||||
public ShutterControlServiceState() {
|
||||
super("shutterControlState");
|
||||
this.operationState = OperationState.STOPPED;
|
||||
}
|
||||
|
||||
public ShutterControlServiceState(double level) {
|
||||
this();
|
||||
this.level = level;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.temperaturelevel;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
|
||||
import org.openhab.binding.boschshc.internal.services.temperaturelevel.dto.TemperatureLevelServiceState;
|
||||
|
||||
/**
|
||||
* TemperatureLevel service.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TemperatureLevelService extends BoschSHCService<TemperatureLevelServiceState> {
|
||||
public TemperatureLevelService() {
|
||||
super("TemperatureLevel", TemperatureLevelServiceState.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.temperaturelevel.dto;
|
||||
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* TemperatureLevel service state.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
public class TemperatureLevelServiceState extends BoschSHCServiceState {
|
||||
|
||||
public TemperatureLevelServiceState() {
|
||||
super("temperatureLevelState");
|
||||
}
|
||||
|
||||
/**
|
||||
* Current temperature (in degree celsius)
|
||||
*/
|
||||
private double temperature;
|
||||
|
||||
/**
|
||||
* Current temperature state to set for a thing.
|
||||
*
|
||||
* @return Current temperature state to use for a thing.
|
||||
*/
|
||||
public State getTemperatureState() {
|
||||
return new QuantityType<Temperature>(this.temperature, SIUnits.CELSIUS);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.valvetappet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
|
||||
import org.openhab.binding.boschshc.internal.services.valvetappet.dto.ValveTappetServiceState;
|
||||
|
||||
/**
|
||||
* Valve Tappet service.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ValveTappetService extends BoschSHCService<ValveTappetServiceState> {
|
||||
public ValveTappetService() {
|
||||
super("ValveTappet", ValveTappetServiceState.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.services.valvetappet.dto;
|
||||
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* Valve Tappet service state.
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
public class ValveTappetServiceState extends BoschSHCServiceState {
|
||||
public ValveTappetServiceState() {
|
||||
super("valveTappetState");
|
||||
}
|
||||
|
||||
/**
|
||||
* Current open percentage of valve tappet (0 [closed] - 100 [open]).
|
||||
*/
|
||||
private int position;
|
||||
|
||||
/**
|
||||
* Current position state of valve tappet.
|
||||
*/
|
||||
public State getPositionState() {
|
||||
return new DecimalType(this.position);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="boschshc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Bosch Smart Home Binding</name>
|
||||
<description>This is the binding for Bosch Smart Home Controller.</description>
|
||||
<author>Stefan Kästle</author>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
<config-description uri="thing-type:boschshc:bridge">
|
||||
<parameter name="ipAddress" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Network Address</label>
|
||||
<description>Network address of the Bosch Smart Home Controller.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="true">
|
||||
<label>System Password</label>
|
||||
<context>password</context>
|
||||
<description>The system password of the Bosch Smart Home Controller necessary for pairing.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
<config-description uri="thing-type:boschshc:device">
|
||||
<parameter name="id" type="text" required="true">
|
||||
<label>Device ID</label>
|
||||
<description>Unique ID of the device.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
@ -0,0 +1,5 @@
|
||||
|
||||
# Thing status offline descriptions
|
||||
offline.conf-error-pairing = Press pairing button on the Bosch Smart Home Controller.
|
||||
offline.not-reachable = Smart Home Controller is not reachable.
|
||||
offline.conf-error-ssl = The SSL connection to the Bosch Smart Home Controller is not possible.
|
@ -0,0 +1,9 @@
|
||||
# binding
|
||||
binding.boschshc.name = Bosch Smart Home Controller Binding
|
||||
binding.boschshc.description = Dieses Binding integriert das Bosch Smart Home System. Durch diese können die Bosch Smart Home Geräte verwendet werden.
|
||||
|
||||
|
||||
# Thing status offline descriptions
|
||||
offline.conf-error-pairing = Bitte betätigen Sie den Taster am Bosch Smart Home Controller zum automatischen Verbinden.
|
||||
offline.not-reachable = Smart Home Controller ist nicht erreichbar.
|
||||
offline.conf-error-ssl = Die SSL Verbindung zum Bosch Smart Home Controller ist nicht möglich.
|
@ -0,0 +1,262 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="boschshc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<!-- Bosch Bridge -->
|
||||
<bridge-type id="shc">
|
||||
<label>Smart Home Controller</label>
|
||||
<description>The Bosch SHC Bridge representing the Bosch Smart Home Controller.</description>
|
||||
|
||||
<config-description-ref uri="thing-type:boschshc:bridge"/>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="in-wall-switch">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="shc"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>In-wall Switch</label>
|
||||
<description>Bosch In-wall switch for light control</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power-switch" typeId="system.power"/>
|
||||
<channel id="power-consumption" typeId="power-consumption"/>
|
||||
<channel id="energy-consumption" typeId="energy-consumption"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:boschshc:device"/>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="twinguard">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="shc"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>TwinGuard</label>
|
||||
<description>Bosch TwinGuard environmental sensor</description>
|
||||
|
||||
<channels>
|
||||
<channel id="temperature" typeId="temperature"/>
|
||||
<channel id="temperature-rating" typeId="temperature-rating"/>
|
||||
<channel id="humidity" typeId="humidity"/>
|
||||
<channel id="humidity-rating" typeId="humidity-rating"/>
|
||||
<channel id="purity" typeId="purity"/>
|
||||
<channel id="air-description" typeId="air-description"/>
|
||||
<channel id="purity-rating" typeId="purity-rating"/>
|
||||
<channel id="combined-rating" typeId="combined-rating"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:boschshc:device"/>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="window-contact">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="shc"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Window/Door Contact</label>
|
||||
<description>Bosch Contact for windows and doors</description>
|
||||
|
||||
<channels>
|
||||
<channel id="contact" typeId="contact"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:boschshc:device"/>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="motion-detector">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="shc"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Motion Detector</label>
|
||||
<description>Bosch Motion Detector</description>
|
||||
|
||||
<channels>
|
||||
<channel id="latest-motion" typeId="latest-motion"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:boschshc:device"/>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shutter-control">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="shc"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Shutter Control</label>
|
||||
<description>Bosch Shutter Control</description>
|
||||
|
||||
<channels>
|
||||
<channel id="level" typeId="level"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:boschshc:device"/>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="thermostat">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="shc"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Thermostat</label>
|
||||
<description>Bosch Thermostat</description>
|
||||
|
||||
<channels>
|
||||
<channel id="temperature" typeId="temperature"/>
|
||||
<channel id="valve-tappet-position" typeId="valve-tappet-position"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:boschshc:device"/>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="climate-control">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="shc"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Climate Control</label>
|
||||
<description>Bosch Climate Control. This is a virtual device which is automatically created for all rooms that have
|
||||
thermostats in it.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="temperature" typeId="temperature"/>
|
||||
<channel id="setpoint-temperature" typeId="setpoint-temperature"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:boschshc:device"/>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<description>Current measured temperature.</description>
|
||||
<state min="0" max="40" step="0.5" pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="temperature-rating">
|
||||
<item-type>String</item-type>
|
||||
<label>Temperature Rating</label>
|
||||
<description>Rating of the currently measured temperature.</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="GOOD">Good Temperature</option>
|
||||
<option value="MEDIUM">Medium Temperature</option>
|
||||
<option value="BAD">Bad Temperature</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="humidity">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Humidity</label>
|
||||
<description>Current measured humidity.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="humidity-rating">
|
||||
<item-type>String</item-type>
|
||||
<label>Humidity Rating</label>
|
||||
<description>Rating of current measured humidity.</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="GOOD">Good Humidity</option>
|
||||
<option value="MEDIUM">Medium Humidity</option>
|
||||
<option value="BAD">Bad Humidity</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="energy-consumption">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Energy consumption (Wh)</label>
|
||||
<description>Energy consumption of the device.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="power-consumption">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Power consumption (W)</label>
|
||||
<description>Current power consumption of the device.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="purity">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Purity</label>
|
||||
<description>Purity of the air. A higher value indicates a higher pollution.</description>
|
||||
<state min="500" max="5500" pattern="%.1f ppm" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="air-description">
|
||||
<item-type>String</item-type>
|
||||
<label>Description</label>
|
||||
<description>Overall description of the air quality.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="purity-rating">
|
||||
<item-type>String</item-type>
|
||||
<label>Purity Rating</label>
|
||||
<description>Rating of the air purity.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="combined-rating">
|
||||
<item-type>String</item-type>
|
||||
<label>Combined Rating</label>
|
||||
<description>Combined rating of the air quality.</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="GOOD">Good Quality</option>
|
||||
<option value="MEDIUM">Medium Quality</option>
|
||||
<option value="BAD">Bad Quality</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="contact">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Window/Door contact</label>
|
||||
<description>A window and door contact.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="latest-motion">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Latest motion</label>
|
||||
<description>Timestamp of the latest motion.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="level">
|
||||
<item-type>Rollershutter</item-type>
|
||||
<label>Level</label>
|
||||
<description>Current open ratio (0 to 100).</description>
|
||||
<state min="0" max="100" step="0.5" readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="valve-tappet-position">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Valve Tappet Position</label>
|
||||
<description>Current open ratio (0 to 100).</description>
|
||||
<state min="0" max="100" step="1" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="setpoint-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Setpoint Temperature</label>
|
||||
<description>Desired temperature.</description>
|
||||
<state min="5" max="30" step="0.5" pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,30 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFETCCAvmgAwIBAgIUR8y7kFBqVMZCYZdSQWVuVJgSAqYwDQYJKoZIhvcNAQEL
|
||||
BQAwYzELMAkGA1UEBhMCREUxITAfBgNVBAoMGEJvc2NoIFRoZXJtb3RlY2huaWsg
|
||||
R21iSDExMC8GA1UEAwwoU21hcnQgSG9tZSBDb250cm9sbGVyIFByb2R1Y3RpdmUg
|
||||
Um9vdCBDQTAeFw0xNTA4MTgwNzI0MjFaFw0yNTA4MTYwNzI0MjFaMFsxCzAJBgNV
|
||||
BAYTAkRFMSEwHwYDVQQKDBhCb3NjaCBUaGVybW90ZWNobmlrIEdtYkgxKTAnBgNV
|
||||
BAMMIFNtYXJ0IEhvbWUgQ29udHJvbGxlciBJc3N1aW5nIENBMIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsBNK3PPd/E9jbf3YkZIDtfIl2Vo0Nx7oeOsh
|
||||
F0L9tZwqC3+85ymB5LgFBOoHpr7tTFRb4elyPsfyv/GfXuJmDIxVAWBn/pxFzODa
|
||||
J3DGJ2kvwipvMNp7IxXHhK10YsG8AaT0QaeaYGq1GRp5uNZafwAOOkrrQfwtG+za
|
||||
Qn9qUxLYBrB++RN/5mk4Z7gyrq7fi84T23yMOtVkdb+mlb9qStQ3mllglqrRlJQo
|
||||
MKdQxe24Farg6N3y7h5bxLJEEXGqGExDNwR46ep+4Ys7W2QeD/2LXwYvKQ+wO70+
|
||||
BNxnikkq8kPcq8694HMsfzUTBrxuHQGi6td9o+3CW01AOEvV0wIDAQABo4HEMIHB
|
||||
MBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFHy1ci5zZEQaHLDAaYFYez8R
|
||||
FHsXMB8GA1UdIwQYMBaAFOFQaxE4w2eoyE+f6oXGTxH1V1Y+MA4GA1UdDwEB/wQE
|
||||
AwIBBjBbBgNVHR8EVDBSMFCgTqBMhkpodHRwczovLzI5Lm1jZy5lc2NyeXB0LmNv
|
||||
bS9jcmw/aWQ9ZTE1MDZiMTEzOGMzNjdhOGM4NGY5ZmVhODVjNjRmMTFmNTU3NTYz
|
||||
ZTANBgkqhkiG9w0BAQsFAAOCAgEAZpp9kE7Qy6tcQrfW4DJAqEcUhzg4zncJYxpb
|
||||
dTn/o5TvH/uPVOfoxJgtsTFtsY/ytcPJReLcgmqrRN1gTNefdXylJr688hFyhf1Z
|
||||
xGDoZG8MuzM9QXaHC6UNFzaeZj46ZYfdJiUtDXsYN82opGE6GhBju5JOLoFd2vYK
|
||||
qUnVKWqdrN0KkihClry6NcfiLEA70m00pNtsVZyVGyk7DP4ErVF5K3j40T5v4ZJl
|
||||
Q9ri/V97zyqXeIti8kZdla7kzJBFbGEumlUyVPRpoxdpnvWM7AgTOXXsh2sCFAA1
|
||||
0hUHVOwBZCylaNUXjKMtnA938ykhNCx+OCd2NpZBf3qB6+w2MS7dQuRvMsDJcnLq
|
||||
X80QHJzXpmDsXEiwKyvmZnZbiAgoOiUSe2O6OaGsDRW8UBzi+wm42pxgbDnAcGUu
|
||||
r9Cf5y0+SFS0aQkqcWbJYwPy+LQi2MJGkv34FxTOCqygluzZt+w5xZyq5PcpPNm5
|
||||
1s4Ps2+psvNhcAG3EHRF9vBnlr1MCVU04XYig54HeNGFIQQAFWFFR/9DgnH/cFLf
|
||||
gPoJEZV/VZtsOjy/EsqYZMFJBzJEtKOiTCKDe+pVirDB9zrcVsJG8LGiLd7266e9
|
||||
1Eg5GjNiavG7ninMOWSJLfW4xPD6S3zxDAYjsPDJbMFqEFIF2ZvyYC1mVeflB/WM
|
||||
xnZ+67w=
|
||||
-----END CERTIFICATE-----
|
@ -0,0 +1,33 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFujCCA6KgAwIBAgIUIbQ+BIVcGVD29UIe+Sv6/+Qy/OUwDQYJKoZIhvcNAQEL
|
||||
BQAwYzELMAkGA1UEBhMCREUxITAfBgNVBAoMGEJvc2NoIFRoZXJtb3RlY2huaWsg
|
||||
R21iSDExMC8GA1UEAwwoU21hcnQgSG9tZSBDb250cm9sbGVyIFByb2R1Y3RpdmUg
|
||||
Um9vdCBDQTAeFw0xNTA4MTgwNzIwMTNaFw0zNTA4MTQwNzIwMTNaMGMxCzAJBgNV
|
||||
BAYTAkRFMSEwHwYDVQQKDBhCb3NjaCBUaGVybW90ZWNobmlrIEdtYkgxMTAvBgNV
|
||||
BAMMKFNtYXJ0IEhvbWUgQ29udHJvbGxlciBQcm9kdWN0aXZlIFJvb3QgQ0EwggIi
|
||||
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCcFmt1vu85lfXMl66Ix32tmEbc
|
||||
n4bt6Oa6QIiT6zJIR2DsE85c42H8XogATWiqfp3FTbmfIIijfoj9JL6uyFkw0yrT
|
||||
qfttw9KD8DRIV973F1UyAP8wPxpdt2QPJCBMmqymC6h2oT7eS6hRIMbY3SFLa5lO
|
||||
4EQ10uflZnY9Yv7kTzeuEw1qWqd8kHhfDBq3k2N90oopt47ghDQ/qUmne19xp0jQ
|
||||
fXFA6hfudNcU9vuZ6hvObm25++ySmRKvtuY+O/CmLVnUJngpKQWJCnYOv3/Z5StZ
|
||||
5aVvLR028ozc1oqdL8fVeaJX8xIdBsSjB+gOaauEYodJzVfeLdXVb8R4CqVighci
|
||||
EUuwZVhzdtA5qs2O9jLJv6JFiD+uuRn8Ip1uYiajYqkRzR2egKWFfhZvV6Yk2zuw
|
||||
s8FUtagtYRwKCp+F+f+PCryLcBcnyc7iVm0Xo7kQAjzoDql4vmXQybmP6kU9qzmD
|
||||
xEG02s6FHVn1X1X4htXc/+Wh0/0850T+Up2HeN+ZN92BubI8yM62mecvfx08vSb1
|
||||
5AviYkQQE37KzGeKYYbciEMeVu5sLx/lN6YIcyHY5kTUsU7SCzw7vTTsNjTzuzYa
|
||||
l2fudHS8lOHaAwvZP//14cM+N9beQqLzxS7jdmFQxtToyzdbgL1OekO58fiqti4W
|
||||
d88bnmMBZsl3bR9b5QIDAQABo2YwZDASBgNVHRMBAf8ECDAGAQH/AgECMB0GA1Ud
|
||||
DgQWBBThUGsROMNnqMhPn+qFxk8R9VdWPjAfBgNVHSMEGDAWgBThUGsROMNnqMhP
|
||||
n+qFxk8R9VdWPjAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAEp2
|
||||
bQei/KQGrnsnqugeseDVKNTOFp5o0xYz8gXEWzRCuAIo/sYKcFWziquajJWuCt/9
|
||||
CexNFWkYtV95EbunyE+wijQcnOehGSZ2gWnZiQU2fu1Y4aA5g3LlB61ljnbhX4SE
|
||||
tLs31iTdjPFcWMx+rsS3+qfuOiOqQbliTykG+p/ULVLLPDCmzL/MHg3w5AiGB8k5
|
||||
i1npzDKJKpLFGFWEnECYKhPi93rLfdgmOEFalIoFB96/upm6bfOWbNvsdIspFVGe
|
||||
3zSjWUvveHe9mm+VTq9aldwy/J0/81oFF7C5CmlB31sDwfY+qF5/mHKfPbrnWTIi
|
||||
QAiZJxXrbmeWX9JVutRbokP1UTX63ghH+BNab/E1D020JVkimMf2Vg1/5WR2gdkN
|
||||
S4j+f//uVKuCr7bPGWzcADeURlyCmW/O2CNfln+T/0YFg2lET9PAEDkZ7Js3I/4f
|
||||
+Dy58LwjdQYI3Z6qKA9h0Cfgy6KOA8Omyw3QmdTAAd0EgABQ/vxNVL3Q4Oh8Eiff
|
||||
ZVrpFWLgMxeRckHTMqG9SfGBdZQCO7XPz7mb/8Da6prEfw4VKvdh9llvatWeB1V1
|
||||
vqixwFVuHIWKxIiR8GXZEjIQXBmeuzdgIceYcw12HYHLUifFozaNtjxMcPcIALKz
|
||||
GrR4oS2tFVZCjwF4vPAt15fsbEx/F/NfaO6SAFz8
|
||||
-----END CERTIFICATE-----
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link BoschHttpClient}.
|
||||
*
|
||||
* @author Gerd Zanker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class BoschHttpClientTest {
|
||||
|
||||
@Nullable
|
||||
private BoschHttpClient httpClient;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
BoschSslUtilTest.prepareTempFolderForKeyStore();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws PairingFailedException {
|
||||
SslContextFactory sslFactory = new BoschSslUtil("127.0.0.1").getSslContextFactory();
|
||||
httpClient = new BoschHttpClient("127.0.0.1", "dummy", sslFactory);
|
||||
assertNotNull(httpClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPairingUrl() {
|
||||
assertEquals("https://127.0.0.1:8443/smarthome/clients", httpClient.getPairingUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBoschShcUrl() {
|
||||
assertEquals("https://127.0.0.1:8444/testEndpoint", httpClient.getBoschShcUrl("testEndpoint"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBoschSmartHomeUrl() {
|
||||
assertEquals("https://127.0.0.1:8444/smarthome/endpointForTest",
|
||||
httpClient.getBoschSmartHomeUrl("endpointForTest"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getServiceUrl() {
|
||||
assertEquals("https://127.0.0.1:8444/smarthome/devices/testDevice/services/testService/state",
|
||||
httpClient.getServiceUrl("testService", "testDevice"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAccessPossible() throws InterruptedException {
|
||||
assertFalse(httpClient.isAccessPossible());
|
||||
}
|
||||
|
||||
@Test
|
||||
void doPairing() throws InterruptedException {
|
||||
assertFalse(httpClient.doPairing());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createRequest() {
|
||||
Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET);
|
||||
assertNotNull(request);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createRequestWithObject() {
|
||||
Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET, "someData");
|
||||
assertNotNull(request);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendRequest() {
|
||||
Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET);
|
||||
// Null pointer exception is expected, because localhost will not answer request
|
||||
assertThrows(NullPointerException.class, () -> {
|
||||
httpClient.sendRequest(request, SubscribeResult.class);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyStore;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link BoschSslUtil}.
|
||||
*
|
||||
* @author Gerd Zanker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class BoschSslUtilTest {
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
prepareTempFolderForKeyStore();
|
||||
}
|
||||
|
||||
public static void prepareTempFolderForKeyStore() {
|
||||
// Use temp folder for userdata folder
|
||||
String tmpDir = System.getProperty("java.io.tmpdir");
|
||||
tmpDir = tmpDir != null ? tmpDir : "/tmp";
|
||||
System.setProperty("openhab.userdata", tmpDir);
|
||||
// prepare temp folder on local drive
|
||||
File tempDir = Paths.get(tmpDir, "etc").toFile();
|
||||
if (!tempDir.exists()) {
|
||||
assertTrue(tempDir.mkdirs());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBoschShcClientId() {
|
||||
// OpenSource Bosch SHC clients needs start with oss
|
||||
assertTrue(BoschSslUtil.getBoschShcClientId().startsWith("oss"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBoschShcServerId() {
|
||||
// OpenSource Bosch SHC clients needs start with oss
|
||||
assertTrue(BoschSslUtil.getBoschShcServerId("localhost").startsWith("oss"));
|
||||
assertTrue(BoschSslUtil.getBoschShcServerId("localhost").contains("localhost"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getKeystorePath() {
|
||||
BoschSslUtil sslUtil = new BoschSslUtil("123.45.67.89");
|
||||
assertTrue(sslUtil.getKeystorePath().endsWith(".jks"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the keyStore can be created if it doesn't exist.
|
||||
*/
|
||||
@Test
|
||||
void keyStoreAndFactory() throws PairingFailedException {
|
||||
BoschSslUtil sslUtil1 = new BoschSslUtil("127.0.0.1");
|
||||
|
||||
// remote old, existing jks
|
||||
File keyStoreFile = new File(sslUtil1.getKeystorePath());
|
||||
keyStoreFile.deleteOnExit();
|
||||
if (keyStoreFile.exists()) {
|
||||
assertTrue(keyStoreFile.delete());
|
||||
}
|
||||
|
||||
assertFalse(keyStoreFile.exists());
|
||||
|
||||
BoschSslUtil sslUtil2 = new BoschSslUtil("127.0.0.1");
|
||||
// fist call where keystore is created
|
||||
KeyStore keyStore = sslUtil2.getKeyStoreAndCreateIfNecessary();
|
||||
assertNotNull(keyStore);
|
||||
|
||||
assertTrue(keyStoreFile.exists());
|
||||
|
||||
// second call where keystore is reopened
|
||||
KeyStore keyStore2 = sslUtil2.getKeyStoreAndCreateIfNecessary();
|
||||
assertNotNull(keyStore2);
|
||||
|
||||
// basic test if a SSL factory instance can be created
|
||||
SslContextFactory factory = sslUtil2.getSslContextFactory();
|
||||
assertNotNull(factory);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.bridge.dto;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* Unit tests for LongPollResult
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LongPollResultTest {
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@Test
|
||||
public void noResultsForErrorResult() {
|
||||
LongPollResult longPollResult = gson.fromJson(
|
||||
"{\"jsonrpc\":\"2.0\", \"error\": { \"code\":-32001, \"message\":\"No subscription with id: e8fei62b0-0\" } }",
|
||||
LongPollResult.class);
|
||||
assertNotEquals(null, longPollResult);
|
||||
if (longPollResult != null) {
|
||||
assertEquals(null, longPollResult.result);
|
||||
}
|
||||
}
|
||||
}
|
@ -65,6 +65,7 @@
|
||||
<module>org.openhab.binding.bluetooth.roaming</module>
|
||||
<module>org.openhab.binding.bluetooth.ruuvitag</module>
|
||||
<module>org.openhab.binding.boschindego</module>
|
||||
<module>org.openhab.binding.boschshc</module>
|
||||
<module>org.openhab.binding.bosesoundtouch</module>
|
||||
<module>org.openhab.binding.bsblan</module>
|
||||
<module>org.openhab.binding.bticinosmarther</module>
|
||||
|
Loading…
Reference in New Issue
Block a user