added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.groheondus</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,20 @@
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
grohe-ondus-api-java
* License: MIT License
* Project: https://github.com/FlorianSW/grohe-ondus-api-java
* Source: https://github.com/FlorianSW/grohe-ondus-api-java

View File

@@ -0,0 +1,139 @@
# GROHE ONDUS Binding
The GROHE ONDUS Binding provides access to data collected by a GROHE ONDUS appliance, such as an [GROHE Sense Guard](https://www.grohe.de/de_de/smarthome/grohe-sense-guard/).
The binding uses the REST API interface (the same as used by the Android App) to retrieve the collected data.
## Supported Things
This binding should support all appliances from GROHE, however, only the GROHE Sense Guard is tested with it.
| Thing type | Name |
|--------------------------|--------------------------|
| account | GROHE ONDUS Account |
| senseguard | GROHE SENSE Guard device |
| sense | GROHE SENSE device |
## Discovery
The binding requires you to create at least one Account thing as a bridge manually.
The discovery process will look through all locations and rooms of your configured GROHE account and adds each found appliance as a new thing automatically to the inbox.
## Binding Configuration
This binding does not require any configuration outside of things.
## Thing Configuration
There is only one thing and one bridge that needs to be configured together to get this binding to work, see the full example section for a self-explaining example.
### Account Bridge
The `groheondus:account` bridge is used to configure the API interface for a specific account, which is used to access the collected and saved data of your GROHE account.
You can either use your username and password combination for logging in into your GROHE account, in which case both parameters, `username` as well as `password`, are required arguments and refer to the same login credentials you used during setting up your GROHE account or while logging into the app.
Alternatively you can use a so called `refresh token` to grant openHAB access to your account without having to share your credentials with the system.
For that you need to obtain such `refresh token` from the GROHE ONDUS Api (see more on that below) and paste this string into the respective input field on the account management page you can reach from `http://<your-openHAB-domain-and-port>/groheondus`.
On this site you can also delete a previously saved `refresh token`.
The GROHE ONDUS binding also refreshes this refresh token in order to ensure that you stay logged in.
### Appliance
The `groheondus:sense` and `groheondus:senseguard` things are used to retrieve information of a specific appliance from GROHE.
This appliance needs to be connected with your GROHE ONDUS account as configured in the corresponding Account Bridge.
The appliance needs to be configured with the unique appliance ID (with the `applianceId` configuration) as well as the `roomId`
and the `locationId`. Once the account bridge is configured, the appliances in your account will be discovered as Appliance things.
| Configuration | Default value | Description |
|--------------------------|--------------------------|-------------------------------------------------------|
| applianceId | '' | Unique ID of the appliance in the GROHE ONDUS account |
| roomId | '' | ID of the room the appliance is in |
| locationId | '' | ID of the location (building) the appliance is in |
| pollingInterval | Retrieved from API, | Interval in seconds to get new data from the API |
| | usually 900 | The `sense` thing uses 900 by default |
#### Channels
##### senseguard
| Channel | Type | Description |
|--------------------------|--------------------------|-------------------------------------------------------|
| name | String | The name of the appliance |
| pressure | Number:Pressure | The pressure of your water supply |
| temperature_guard | Number:Temperature | The ambient temperature of the appliance |
| valve_open | Switch | Valve switch |
| waterconsumption | Number | The amount of water used in a specific timeframe |
##### sense
| Channel | Type | Description |
|--------------------------|--------------------------|-------------------------------------------------------|
| name | String | The name of the appliance |
| humidity | Number:Dimensionless | The humidity measured by the appliance |
| temperature | Number:Temperature | The ambient temperature of the appliance |
| battery | Number | The battery level of the appliance |
## Full Example
Things file:
````
Bridge groheondus:account:account1 [ username="user@example.com", password="YourStrongPasswordHere!" ] {
groheondus:senseguard:550e8400-e29b-11d4-a716-446655440000 [ applianceId="550e8400-e29b-11d4-a716-446655440000", roomId=456, locationId=123 ] {
Channels:
Type number : waterconsumption [
timeframe=3
]
}
groheondus:sense:550e8400-e29b-11d4-a716-446655440000 [ applianceId="444e8400-e29b-11d4-a716-446655440000", roomId=456, locationId=123 ]
}
````
Items file:
````
String Name_Sense_Guard "Appliance Name" {channel="groheondus:senseguard:groheondus:appliance:550e8400-e29b-11d4-a716-446655440000:name"}
Number:Pressure Pressure_Sense_Guard "Pressure [%.1f %unit%]" {channel="groheondus:senseguard:groheondus:appliance:550e8400-e29b-11d4-a716-446655440000:pressure"}
Number:Temperature Temperature_Sense_Guard "Temperature [%.1f %unit%]" {channel="groheondus:senseguard:groheondus:appliance:550e8400-e29b-11d4-a716-446655440000:temperature_guard"}
String Name_Sense "Temperature [%.1f %unit%]" {channel="groheondus:sense:groheondus:appliance:444e8400-e29b-11d4-a716-446655440000:name"}
Number:Temperature Temperature_Sense "Temperature [%.1f %unit%]" {channel="groheondus:sense:groheondus:appliance:444e8400-e29b-11d4-a716-446655440000:temperature"}
Number Humidity_Sense "Humidity [%.1f %unit%]" {channel="groheondus:sense:groheondus:appliance:444e8400-e29b-11d4-a716-446655440000:humidity"}
````
## Obtaining a `refresh token`
Actually obtaining a `refresh token` from the GROHE ONDUS Api requires some manual steps.
In order to more deeply understand what is happening during the process, you can read more information about the OAuth2/OIDC (OpenID Connect) login flow by searching for these terms in your favorite search engine.
Here is a short step-by-step guide on how to obtain a refresh token:
1. Open a new tab in your Internet browser
2. Open the developer console of your browser (mostly possible by pressing F12)
3. Select the network tab of the developer console (which shows you the network request done by the browser)
4. Open the following URL: https://idp2-apigw.cloud.grohe.com/v3/iot/oidc/login
5. You will automatically being redirected to the GROHE ONDUS login page, login there
6. After logging in successfully, nothing should happen, except a failed request to a page starting with `token?`
7. Click on this request (the URL in the request overview should start with `ondus://idp2-apigw.cloud.grohe.com/v3/iot/oidc/token?` or something like that
8. Copy the whole request URL (which should contain a lot of stuff, like a `state` parameter and so on)
9. Open a new tab in your Internet browser and paste the URL into the address bar (do not hit ENTER or start the navigation to this page, yet)
10. Replace the `ondus://` part of the URL with `https://` and hit ENTER
11. The response of the page should be plain text with a so called `JSON object`. Somewhere in the text should be a `refresh_token` string, select the string after this `refresh_token` text, which is encapsulated with `"`.
E.g.: If the response of the page looks like this:
````
{
"access_token": "the_access_token",
"expires_in":3600,
"refresh_expires_in":15552000,
"refresh_token":"the_refresh_token",
"token_type":"bearer",
"id_token":"the_id_token",
"not-before-policy":0,
"session_state":"a-state",
"scope":"",
"tandc_accepted":true,
"partialLogin":false
}
````
Then the `refresh_token` value you should copy would be: `the_refresh_token`.
This value is the `refresh token` you should save as described above.

View File

@@ -0,0 +1,42 @@
<?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.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.groheondus</artifactId>
<name>openHAB Add-ons :: Bundles :: GROHE ONDUS Binding</name>
<properties>
<dep.noembedding>commons-text,commons-lang3</dep.noembedding>
</properties>
<dependencies>
<dependency>
<groupId>org.grohe</groupId>
<artifactId>ondus-api</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.groheondus-${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-groheondus" description="GROHE ONDUS Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature dependency="true">openhab.tp-jackson</feature>
<bundle dependency="true">mvn:org.apache.commons/commons-text/1.6</bundle>
<bundle dependency="true">mvn:org.apache.commons/commons-lang3/3.8.1</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.groheondus/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,139 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groheondus.internal;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.groheondus.internal.handler.GroheOndusAccountHandler;
import org.osgi.service.http.HttpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Florian Schmidt - Initial contribution
*/
@NonNullByDefault
public class AccountServlet extends HttpServlet {
private static final long serialVersionUID = -6321196284331950479L;
private final Logger logger = LoggerFactory.getLogger(AccountServlet.class);
private HttpService httpService;
private String bridgeId;
private GroheOndusAccountHandler accountHandler;
public AccountServlet(HttpService httpService, String bridgeId, GroheOndusAccountHandler accountHandler) {
this.httpService = httpService;
this.bridgeId = bridgeId;
this.accountHandler = accountHandler;
try {
httpService.registerServlet(servletUrl(), this, null, httpService.createDefaultHttpContext());
} catch (Exception e) {
logger.warn("Register servlet fails", e);
}
}
private String servletUrl() throws UnsupportedEncodingException {
return "/groheondus/" + URLEncoder.encode(bridgeId, StandardCharsets.UTF_8.name());
}
@Override
protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
throws ServletException, IOException {
if (req == null || resp == null) {
return;
}
resp.addHeader("content-type", "text/html;charset=UTF-8");
StringBuilder htmlString = new StringBuilder();
htmlString.append("<html>");
htmlString.append("<head>");
htmlString.append("<title>Set refresh token</title>");
htmlString.append("</head>");
htmlString.append("<body>");
htmlString.append("<header>");
htmlString.append("<h1>Set refresh token for accout: ");
htmlString.append(bridgeId);
htmlString.append("</h1>");
htmlString.append("</header>");
htmlString.append("<div>Has refresh token: ");
if (this.accountHandler.hasRefreshToken()) {
htmlString.append("yes");
htmlString.append(
"<input type=\"submit\" value=\"Delete\" onclick=\"fetch(window.location.href, {method: 'DELETE'}).then(window.location.reload())\">");
} else {
htmlString.append("no");
}
htmlString.append("</div>");
htmlString.append("<form method=\"post\">");
htmlString.append("<label for=\"refreshToken\">Refresh Token: </label>");
htmlString.append("<input type=\"text\" id=\"refreshToken\" autocomplete=\"off\" name=\"refreshToken\">");
htmlString.append("<input type=\"submit\" value=\"Save\">");
htmlString.append("</form>");
htmlString.append("</body>");
htmlString.append("</html>");
resp.getWriter().write(htmlString.toString());
}
@Override
protected void doPost(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
throws ServletException, IOException {
if (req == null) {
return;
}
if (resp == null) {
return;
}
Map<String, String[]> map = req.getParameterMap();
this.accountHandler.setRefreshToken(map.get("refreshToken")[0]);
resp.addHeader("Location", "/groheondus");
resp.setStatus(HttpStatus.MOVED_TEMPORARILY_302);
}
@Override
protected void doDelete(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
throws ServletException, IOException {
if (req == null) {
return;
}
if (resp == null) {
return;
}
this.accountHandler.deleteRefreshToken();
resp.setStatus(HttpStatus.OK_200);
}
public void dispose() {
try {
httpService.unregister(servletUrl());
} catch (UnsupportedEncodingException e) {
logger.warn("Unregistration of servlet failed", e);
}
}
}

View File

@@ -0,0 +1,114 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groheondus.internal;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.core.thing.Thing;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ServiceScope;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Florian Schmidt - Initial contribution
*/
@NonNullByDefault
@Component(service = AccountsServlet.class, scope = ServiceScope.SINGLETON)
public class AccountsServlet extends HttpServlet {
private static final long serialVersionUID = -9183159739446995608L;
private static final String SERVLET_URL = "/groheondus";
private final Logger logger = LoggerFactory.getLogger(AccountsServlet.class);
private final List<Thing> accounts = new ArrayList<>();
private HttpService httpService;
@Activate
public AccountsServlet(@Reference HttpService httpService) {
this.httpService = httpService;
try {
httpService.registerServlet(SERVLET_URL, this, null, httpService.createDefaultHttpContext());
} catch (ServletException | NamespaceException e) {
logger.warn("Register servlet fails", e);
}
}
public void addAccount(Thing accountThing) {
accounts.add(accountThing);
}
public void removeAccount(Thing accountThing) {
accounts.remove(accountThing);
}
public void deactivate() {
httpService.unregister(SERVLET_URL);
}
@Override
protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
throws ServletException, IOException {
if (req == null || resp == null) {
return;
}
StringBuilder htmlString = new StringBuilder();
htmlString.append("<html>");
htmlString.append("<head>");
htmlString.append("<title>GROHE Ondus Account login</title>");
htmlString.append("</head>");
htmlString.append("<body>");
if (accounts.isEmpty()) {
htmlString.append(
"Please first create an GROHE ONDUS account thing in openHAB in order to log into this account.");
} else {
htmlString.append(
"You've the following GROHE ONDUS account things, click on the one you want to manage:<br />");
htmlString.append("<ul>");
accounts.forEach(account -> {
String accountId = account.getUID().getId();
htmlString.append("<li>");
htmlString.append("<a href=\"");
htmlString.append(SERVLET_URL);
htmlString.append("/");
htmlString.append(accountId);
htmlString.append("\">");
htmlString.append(accountId);
htmlString.append("</a>");
htmlString.append("</li>");
});
htmlString.append("</ul>");
}
htmlString.append("</body>");
htmlString.append("</html>");
resp.setStatus(HttpStatus.OK_200);
resp.getWriter().write(htmlString.toString());
}
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groheondus.internal;
/**
* @author Florian Schmidt and Arne Wohlert - Initial contribution
*/
public class GroheOndusAccountConfiguration {
public String username;
public String password;
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groheondus.internal;
/**
* @author Florian Schmidt and Arne Wohlert - Initial contribution
*/
public class GroheOndusApplianceConfiguration {
public String applianceId;
public int roomId;
public int locationId;
public int pollingInterval;
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groheondus.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* @author Florian Schmidt and Arne Wohlert - Initial contribution
*/
@NonNullByDefault
public class GroheOndusBindingConstants {
private static final String BINDING_ID = "groheondus";
public static final ThingTypeUID THING_TYPE_BRIDGE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
public static final ThingTypeUID THING_TYPE_SENSEGUARD = new ThingTypeUID(BINDING_ID, "senseguard");
public static final ThingTypeUID THING_TYPE_SENSE = new ThingTypeUID(BINDING_ID, "sense");
public static final String CHANNEL_NAME = "name";
public static final String CHANNEL_PRESSURE = "pressure";
public static final String CHANNEL_TEMPERATURE_GUARD = "temperature_guard";
public static final String CHANNEL_VALVE_OPEN = "valve_open";
public static final String CHANNEL_WATERCONSUMPTION = "waterconsumption";
public static final String CHANNEL_TEMPERATURE = "temperature";
public static final String CHANNEL_HUMIDITY = "humidity";
public static final String CHANNEL_BATTERY = "battery";
public static final String CHANNEL_CONFIG_TIMEFRAME = "timeframe";
}

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groheondus.internal.discovery;
import static org.openhab.binding.groheondus.internal.GroheOndusBindingConstants.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.grohe.ondus.api.OndusService;
import org.grohe.ondus.api.model.BaseAppliance;
import org.openhab.binding.groheondus.internal.handler.GroheOndusAccountHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Florian Schmidt - Initial contribution
*/
@NonNullByDefault
public class GroheOndusDiscoveryService extends AbstractDiscoveryService {
private static final String PROPERTY_APPLIANCE_ID = "applianceId";
private static final String PROPERTY_ROOM_ID = "roomId";
private static final String PROPERTY_LOCATION_ID = "locationId";
private final Logger logger = LoggerFactory.getLogger(GroheOndusDiscoveryService.class);
private final GroheOndusAccountHandler bridgeHandler;
public GroheOndusDiscoveryService(GroheOndusAccountHandler bridgeHandler) {
super(Collections
.unmodifiableSet(Stream.of(THING_TYPE_SENSE, THING_TYPE_SENSEGUARD).collect(Collectors.toSet())), 30);
logger.debug("initialize discovery service");
this.bridgeHandler = bridgeHandler;
this.activate(null);
}
@Override
protected void startScan() {
OndusService service;
try {
service = bridgeHandler.getService();
} catch (IllegalStateException e) {
logger.debug("No instance of OndusService given.", e);
return;
}
List<BaseAppliance> discoveredAppliances = new ArrayList<>();
try {
discoveredAppliances = service.appliances();
} catch (IOException e) {
logger.debug("Could not discover appliances.", e);
return;
}
discoveredAppliances.forEach(appliance -> {
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
ThingUID thingUID = null;
switch (appliance.getType()) {
case org.grohe.ondus.api.model.guard.Appliance.TYPE:
thingUID = new ThingUID(THING_TYPE_SENSEGUARD, bridgeUID, appliance.getApplianceId());
break;
case org.grohe.ondus.api.model.sense.Appliance.TYPE:
thingUID = new ThingUID(THING_TYPE_SENSE, bridgeUID, appliance.getApplianceId());
break;
default:
return;
}
Map<String, Object> properties = new HashMap<>();
properties.put(PROPERTY_LOCATION_ID, appliance.getRoom().getLocation().getId());
properties.put(PROPERTY_ROOM_ID, appliance.getRoom().getId());
properties.put(PROPERTY_APPLIANCE_ID, appliance.getApplianceId());
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
.withBridge(bridgeUID).withLabel(appliance.getName())
.withRepresentationProperty(PROPERTY_APPLIANCE_ID).build();
thingDiscovered(discoveryResult);
});
}
}

View File

@@ -0,0 +1,165 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groheondus.internal.handler;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.security.auth.login.LoginException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.grohe.ondus.api.OndusService;
import org.openhab.binding.groheondus.internal.AccountServlet;
import org.openhab.binding.groheondus.internal.GroheOndusAccountConfiguration;
import org.openhab.core.storage.Storage;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.osgi.service.http.HttpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Florian Schmidt and Arne Wohlert - Initial contribution
*/
@NonNullByDefault
public class GroheOndusAccountHandler extends BaseBridgeHandler {
private static final String STORAGE_KEY_REFRESH_TOKEN = "refreshToken";
private final Logger logger = LoggerFactory.getLogger(GroheOndusAccountHandler.class);
private HttpService httpService;
private Storage<String> storage;
private @Nullable AccountServlet accountServlet;
private @Nullable OndusService ondusService;
private @Nullable ScheduledFuture<?> refreshTokenFuture;
public GroheOndusAccountHandler(Bridge bridge, HttpService httpService, Storage<String> storage) {
super(bridge);
this.httpService = httpService;
this.storage = storage;
}
public OndusService getService() {
OndusService ret = this.ondusService;
if (ret == null) {
throw new IllegalStateException("OndusService requested, which is null (UNINITIALIZED)");
}
return ret;
}
public void deleteRefreshToken() {
this.storage.remove(STORAGE_KEY_REFRESH_TOKEN);
this.initialize();
if (refreshTokenFuture != null) {
refreshTokenFuture.cancel(true);
}
}
public void setRefreshToken(String refreshToken) {
this.storage.put(STORAGE_KEY_REFRESH_TOKEN, refreshToken);
this.initialize();
}
private void scheduleTokenRefresh() {
if (ondusService != null) {
Instant expiresAt = ondusService.authorizationExpiresAt();
Duration between = Duration.between(Instant.now(), expiresAt);
refreshTokenFuture = scheduler.schedule(() -> {
OndusService ondusService = this.ondusService;
if (ondusService == null) {
logger.warn("Trying to refresh Ondus account without a service being present.");
return;
}
try {
setRefreshToken(ondusService.refreshAuthorization());
} catch (Exception e) {
logger.warn("Could not refresh authorization for GROHE ONDUS account", e);
}
}, between.getSeconds(), TimeUnit.SECONDS);
}
}
public boolean hasRefreshToken() {
return this.storage.containsKey(STORAGE_KEY_REFRESH_TOKEN);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Nothing to do for bridge
}
@Override
public void dispose() {
super.dispose();
if (ondusService != null) {
ondusService = null;
}
if (accountServlet != null) {
accountServlet.dispose();
}
if (refreshTokenFuture != null) {
refreshTokenFuture.cancel(true);
}
}
@Override
public void initialize() {
GroheOndusAccountConfiguration config = getConfigAs(GroheOndusAccountConfiguration.class);
if (this.accountServlet == null) {
this.accountServlet = new AccountServlet(httpService, this.getThing().getUID().getId(), this);
}
if ((config.username == null || config.password == null) && !this.hasRefreshToken()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"Need username/password or refreshToken");
return;
}
updateStatus(ThingStatus.UNKNOWN);
try {
if (storage.containsKey(STORAGE_KEY_REFRESH_TOKEN)) {
ondusService = OndusService.login(storage.get(STORAGE_KEY_REFRESH_TOKEN));
scheduleTokenRefresh();
} else {
// TODO: That's probably really inefficient, internally the loginWebform method acquires a refresh
// token, maybe there should be a way to obtain this token here, somehow.
ondusService = OndusService.loginWebform(config.username, config.password);
}
updateStatus(ThingStatus.ONLINE);
scheduler.submit(() -> getThing().getThings().forEach(thing -> {
GroheOndusBaseHandler thingHandler = (GroheOndusBaseHandler) thing.getHandler();
if (thingHandler != null) {
thingHandler.updateChannels();
}
}));
} catch (LoginException e) {
logger.debug("Grohe api login failed", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Login failed");
} catch (IOException e) {
logger.debug("Communication error while logging into the grohe api", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}

View File

@@ -0,0 +1,163 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groheondus.internal.handler;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.grohe.ondus.api.OndusService;
import org.grohe.ondus.api.model.BaseAppliance;
import org.grohe.ondus.api.model.Location;
import org.grohe.ondus.api.model.Room;
import org.openhab.binding.groheondus.internal.GroheOndusApplianceConfiguration;
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.thing.binding.BridgeHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Florian Schmidt - Initial contribution
*/
@NonNullByDefault
public abstract class GroheOndusBaseHandler<T extends BaseAppliance, M> extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(GroheOndusBaseHandler.class);
protected @Nullable GroheOndusApplianceConfiguration config;
private final int applianceType;
public GroheOndusBaseHandler(Thing thing, int applianceType) {
super(thing);
this.applianceType = applianceType;
}
@Override
public void initialize() {
config = getConfigAs(GroheOndusApplianceConfiguration.class);
OndusService ondusService = getOndusService();
if (ondusService == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
"No initialized OndusService available from bridge.");
return;
}
@Nullable
T appliance = getAppliance(ondusService);
if (appliance == null) {
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, "Could not load appliance");
return;
}
int pollingInterval = getPollingInterval(appliance);
scheduler.scheduleWithFixedDelay(this::updateChannels, 0, pollingInterval, TimeUnit.SECONDS);
updateStatus(ThingStatus.UNKNOWN);
}
@Override
public void channelLinked(ChannelUID channelUID) {
super.channelLinked(channelUID);
OndusService ondusService = getOndusService();
if (ondusService == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
"No initialized OndusService available from bridge.");
return;
}
@Nullable
T appliance = getAppliance(ondusService);
if (appliance == null) {
return;
}
updateChannel(channelUID, appliance, getLastDataPoint(appliance));
}
public void updateChannels() {
OndusService ondusService = getOndusService();
if (ondusService == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
"No initialized OndusService available from bridge.");
return;
}
@Nullable
T appliance = getAppliance(ondusService);
if (appliance == null) {
return;
}
M measurement = getLastDataPoint(appliance);
getThing().getChannels().forEach(channel -> updateChannel(channel.getUID(), appliance, measurement));
updateStatus(ThingStatus.ONLINE);
}
protected abstract M getLastDataPoint(T appliance);
protected abstract void updateChannel(ChannelUID channelUID, T appliance, M measurement);
public @Nullable OndusService getOndusService() {
Bridge bridge = getBridge();
if (bridge == null) {
return null;
}
BridgeHandler handler = bridge.getHandler();
if (!(handler instanceof GroheOndusAccountHandler)) {
return null;
}
try {
return ((GroheOndusAccountHandler) handler).getService();
} catch (IllegalStateException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return null;
}
}
protected Room getRoom() {
return new Room(config.roomId, getLocation());
}
protected Location getLocation() {
return new Location(config.locationId);
}
protected @Nullable T getAppliance(OndusService ondusService) {
try {
BaseAppliance appliance = ondusService.getAppliance(getRoom(), config.applianceId).orElse(null);
if (appliance.getType() != getType()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Thing is not a GROHE SENSE Guard device.");
return null;
}
return (T) appliance;
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
logger.debug("Could not load appliance", e);
}
return null;
}
protected abstract int getPollingInterval(T appliance);
private int getType() {
return this.applianceType;
}
}

View File

@@ -0,0 +1,114 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groheondus.internal.handler;
import static org.openhab.binding.groheondus.internal.GroheOndusBindingConstants.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.groheondus.internal.AccountsServlet;
import org.openhab.binding.groheondus.internal.discovery.GroheOndusDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.storage.StorageService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.http.HttpService;
/**
* @author Florian Schmidt and Arne Wohlert - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.groheondus", service = ThingHandlerFactory.class)
public class GroheOndusHandlerFactory extends BaseThingHandlerFactory {
private final Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
private HttpService httpService;
private StorageService storageService;
private AccountsServlet accountsServlet;
@Activate
public GroheOndusHandlerFactory(@Reference HttpService httpService, @Reference StorageService storageService,
@Reference AccountsServlet accountsServlet) {
this.httpService = httpService;
this.storageService = storageService;
this.accountsServlet = accountsServlet;
}
private static final Collection<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Arrays.asList(THING_TYPE_SENSEGUARD,
THING_TYPE_SENSE, THING_TYPE_BRIDGE_ACCOUNT);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_BRIDGE_ACCOUNT.equals(thingTypeUID)) {
GroheOndusAccountHandler handler = new GroheOndusAccountHandler((Bridge) thing, httpService,
storageService.getStorage(thing.getUID().toString(),
FrameworkUtil.getBundle(getClass()).adapt(BundleWiring.class).getClassLoader()));
onAccountCreated(thing, handler);
return handler;
} else if (THING_TYPE_SENSEGUARD.equals(thingTypeUID)) {
return new GroheOndusSenseGuardHandler(thing);
} else if (THING_TYPE_SENSE.equals(thingTypeUID)) {
return new GroheOndusSenseHandler(thing);
}
return null;
}
private void onAccountCreated(Thing thing, GroheOndusAccountHandler handler) {
registerDeviceDiscoveryService(handler);
if (this.accountsServlet != null) {
this.accountsServlet.addAccount(thing);
}
}
@Override
protected synchronized void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof GroheOndusAccountHandler) {
ServiceRegistration<?> serviceReg = discoveryServiceRegs.remove(thingHandler.getThing().getUID());
if (serviceReg != null) {
serviceReg.unregister();
}
}
}
private synchronized void registerDeviceDiscoveryService(GroheOndusAccountHandler handler) {
GroheOndusDiscoveryService discoveryService = new GroheOndusDiscoveryService(handler);
discoveryServiceRegs.put(handler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
}

View File

@@ -0,0 +1,225 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groheondus.internal.handler;
import static org.openhab.binding.groheondus.internal.GroheOndusBindingConstants.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.grohe.ondus.api.OndusService;
import org.grohe.ondus.api.model.BaseApplianceCommand;
import org.grohe.ondus.api.model.BaseApplianceData;
import org.grohe.ondus.api.model.guard.Appliance;
import org.grohe.ondus.api.model.guard.ApplianceCommand;
import org.grohe.ondus.api.model.guard.ApplianceData;
import org.grohe.ondus.api.model.guard.ApplianceData.Data;
import org.grohe.ondus.api.model.guard.ApplianceData.Measurement;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.Channel;
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.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Florian Schmidt and Arne Wohlert - Initial contribution
*/
@NonNullByDefault
public class GroheOndusSenseGuardHandler<T, M> extends GroheOndusBaseHandler<Appliance, Data> {
private static final int MIN_API_TIMEFRAME_DAYS = 1;
private static final int MAX_API_TIMEFRAME_DAYS = 90;
private static final int DEFAULT_TIMEFRAME_DAYS = 1;
private final Logger logger = LoggerFactory.getLogger(GroheOndusSenseGuardHandler.class);
public GroheOndusSenseGuardHandler(Thing thing) {
super(thing, Appliance.TYPE);
}
@Override
protected int getPollingInterval(Appliance appliance) {
if (config.pollingInterval > 0) {
return config.pollingInterval;
}
return appliance.getConfig().getMeasurementTransmissionIntervall();
}
@Override
protected void updateChannel(ChannelUID channelUID, Appliance appliance, Data dataPoint) {
String channelId = channelUID.getIdWithoutGroup();
State newState;
switch (channelId) {
case CHANNEL_NAME:
newState = new StringType(appliance.getName());
break;
case CHANNEL_PRESSURE:
newState = new QuantityType<>(getLastMeasurement(dataPoint).getPressure(), SmartHomeUnits.BAR);
break;
case CHANNEL_TEMPERATURE_GUARD:
newState = new QuantityType<>(getLastMeasurement(dataPoint).getTemperatureGuard(), SIUnits.CELSIUS);
break;
case CHANNEL_VALVE_OPEN:
newState = getValveOpenType(appliance);
break;
case CHANNEL_WATERCONSUMPTION:
newState = sumWaterCosumption(dataPoint);
break;
default:
throw new IllegalArgumentException("Channel " + channelUID + " not supported.");
}
if (newState != null) {
updateState(channelUID, newState);
}
}
private DecimalType sumWaterCosumption(Data dataPoint) {
Double waterConsumption = dataPoint.getWithdrawals().stream()
.mapToDouble(withdrawal -> withdrawal.getWaterconsumption()).sum();
return new DecimalType(waterConsumption);
}
private Measurement getLastMeasurement(Data dataPoint) {
List<Measurement> measurementList = dataPoint.getMeasurement();
return measurementList.isEmpty() ? new Measurement() : measurementList.get(measurementList.size() - 1);
}
@Nullable
private OnOffType getValveOpenType(Appliance appliance) {
OndusService service = getOndusService();
if (service == null) {
return null;
}
Optional<BaseApplianceCommand> commandOptional;
try {
commandOptional = service.applianceCommand(appliance);
} catch (IOException e) {
logger.debug("Could not get appliance command", e);
return null;
}
if (!commandOptional.isPresent()) {
return null;
}
if (commandOptional.get().getType() != Appliance.TYPE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Thing is not a GROHE SENSE Guard device.");
return null;
}
return ((ApplianceCommand) commandOptional.get()).getCommand().getValveOpen() ? OnOffType.ON : OnOffType.OFF;
}
@Override
protected Data getLastDataPoint(Appliance appliance) {
if (getOndusService() == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
"No initialized OndusService available from bridge.");
return new Data();
}
ApplianceData applianceData = getApplianceData(appliance);
if (applianceData == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Could not load data from API.");
return new Data();
}
return applianceData.getData();
}
private @Nullable ApplianceData getApplianceData(Appliance appliance) {
Instant from = fromTime();
Instant to = Instant.now();
OndusService service = getOndusService();
if (service == null) {
return null;
}
try {
BaseApplianceData applianceData = service.applianceData(appliance, from, to).orElse(null);
if (applianceData.getType() != Appliance.TYPE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Thing is not a GROHE SENSE Guard device.");
return null;
}
return (ApplianceData) applianceData;
} catch (IOException e) {
logger.debug("Could not load appliance data", e);
}
return null;
}
private Instant fromTime() {
Instant from = Instant.now().minus(DEFAULT_TIMEFRAME_DAYS, ChronoUnit.DAYS);
Channel waterconsumptionChannel = this.thing.getChannel(CHANNEL_WATERCONSUMPTION);
if (waterconsumptionChannel == null) {
return from;
}
Object timeframeConfig = waterconsumptionChannel.getConfiguration().get(CHANNEL_CONFIG_TIMEFRAME);
if (!(timeframeConfig instanceof BigDecimal)) {
return from;
}
int timeframe = ((BigDecimal) timeframeConfig).intValue();
if (timeframe < MIN_API_TIMEFRAME_DAYS && timeframe > MAX_API_TIMEFRAME_DAYS) {
logger.info(
"timeframe configuration of waterconsumption channel needs to be a number between 1 to 90, got {}",
timeframe);
return from;
}
return Instant.now().minus(timeframe, ChronoUnit.DAYS);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (!CHANNEL_VALVE_OPEN.equals(channelUID.getIdWithoutGroup())) {
return;
}
if (!(command instanceof OnOffType)) {
logger.debug("Invalid command received for channel. Expected OnOffType, received {}.",
command.getClass().getName());
return;
}
OnOffType openClosedCommand = (OnOffType) command;
boolean openState = openClosedCommand == OnOffType.ON;
OndusService service = getOndusService();
if (service == null) {
return;
}
Appliance appliance = getAppliance(service);
if (appliance == null) {
return;
}
try {
service.setValveOpen(appliance, openState);
updateChannels();
} catch (IOException e) {
logger.debug("Could not update valve open state", e);
}
}
}

View File

@@ -0,0 +1,158 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groheondus.internal.handler;
import static org.openhab.binding.groheondus.internal.GroheOndusBindingConstants.*;
import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.grohe.ondus.api.OndusService;
import org.grohe.ondus.api.model.ApplianceStatus;
import org.grohe.ondus.api.model.BaseApplianceData;
import org.grohe.ondus.api.model.sense.Appliance;
import org.grohe.ondus.api.model.sense.ApplianceData;
import org.grohe.ondus.api.model.sense.ApplianceData.Measurement;
import org.openhab.core.library.types.DecimalType;
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.SmartHomeUnits;
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.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Florian Schmidt - Initial contribution
*/
@NonNullByDefault
public class GroheOndusSenseHandler<T, M> extends GroheOndusBaseHandler<Appliance, Measurement> {
private static final int DEFAULT_POLLING_INTERVAL = 900;
private final Logger logger = LoggerFactory.getLogger(GroheOndusSenseHandler.class);
public GroheOndusSenseHandler(Thing thing) {
super(thing, Appliance.TYPE);
}
@Override
protected int getPollingInterval(Appliance appliance) {
if (config.pollingInterval > 0) {
return config.pollingInterval;
}
return DEFAULT_POLLING_INTERVAL;
}
@Override
protected void updateChannel(ChannelUID channelUID, Appliance appliance, Measurement measurement) {
String channelId = channelUID.getIdWithoutGroup();
State newState;
switch (channelId) {
case CHANNEL_NAME:
newState = new StringType(appliance.getName());
break;
case CHANNEL_TEMPERATURE:
newState = new QuantityType<>(measurement.getTemperature(), SIUnits.CELSIUS);
break;
case CHANNEL_HUMIDITY:
newState = new QuantityType<>(measurement.getHumidity(), SmartHomeUnits.PERCENT);
break;
case CHANNEL_BATTERY:
newState = new DecimalType(getBatteryStatus(appliance));
break;
default:
throw new IllegalArgumentException("Channel " + channelUID + " not supported.");
}
if (newState != null) {
updateState(channelUID, newState);
}
}
@Override
protected Measurement getLastDataPoint(Appliance appliance) {
if (getOndusService() == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
"No initialized OndusService available from bridge.");
return new Measurement();
}
ApplianceData applianceData = getApplianceData(appliance);
if (applianceData == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Could not load data from API.");
return new Measurement();
}
List<Measurement> measurementList = applianceData.getData().getMeasurement();
return measurementList.isEmpty() ? new Measurement() : measurementList.get(measurementList.size() - 1);
}
private int getBatteryStatus(Appliance appliance) {
OndusService ondusService = getOndusService();
if (ondusService == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
"No initialized OndusService available from bridge.");
return -1;
}
Optional<ApplianceStatus> applianceStatusOptional;
try {
applianceStatusOptional = ondusService.applianceStatus(appliance);
if (!applianceStatusOptional.isPresent()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not load data from API.");
return -1;
}
return applianceStatusOptional.get().getBatteryStatus();
} catch (IOException e) {
logger.debug("Could not load appliance status", e);
}
return -1;
}
private @Nullable ApplianceData getApplianceData(Appliance appliance) {
Instant yesterday = Instant.now().minus(1, ChronoUnit.DAYS);
Instant today = Instant.now();
OndusService service = getOndusService();
if (service == null) {
return null;
}
try {
BaseApplianceData applianceData = service.applianceData(appliance, yesterday, today).orElse(null);
if (applianceData.getType() != Appliance.TYPE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Thing is not a GROHE SENSE device.");
return null;
}
return (ApplianceData) applianceData;
} catch (IOException e) {
logger.debug("Could not load appliance data", e);
}
return null;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="groheondus" 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>GROHE ONDUS Binding</name>
<description>Provides an integration for GROHE Appliances in openHAB</description>
<author>Florian Schmidt and Arne Wohlert</author>
</binding:binding>

View File

@@ -0,0 +1,11 @@
# binding
binding.groheondus.name = GROHE ONDUS
binding.groheondus.description = Stellt eine Integration mit Geräten von GROHE in openHAB zur Verfügung.
# thing types
thing-type.groheondus.senseguard.label = GROHE SENSE Guard Gerät
thing-type.groheondus.sense.label = GROHE SENSE Gerät
thing-type.groheondus.senseguard.description = Ein GROHE SENSE Guard
thing-type.groheondus.sense.description = Ein GROHE SENSE
thing-type.groheondus.account.label = GROHE ONDUS Account
thing-type.groheondus.account.description = Dies ist die Schnittstelle zum GROHE ONDUS Account wie sie von der App verwendet wird.

View File

@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="groheondus"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="account">
<label>GROHE ONDUS Account</label>
<description>This is an interface to the GROHE ONDUS Account as it is used by the app. If username and password are
not set, you can configure to use a `refreshToken` to login. Read the README to get more info.</description>
<config-description>
<parameter name="username" type="text" required="false">
<label>Username</label>
<description>Username as used in the GROHE ONDUS App, usually your e-mail address.</description>
<required>true</required>
</parameter>
<parameter name="password" type="text" required="false">
<label>Password</label>
<required>true</required>
<context>password</context>
<description>Password as used in the GROHE ONDUS App.</description>
</parameter>
</config-description>
</bridge-type>
<thing-type id="senseguard">
<supported-bridge-type-refs>
<bridge-type-ref id="account"/>
</supported-bridge-type-refs>
<label>GROHE SENSE GUARD Appliance</label>
<description>A SENSE GUARD device</description>
<channels>
<channel id="name" typeId="name"/>
<channel id="pressure" typeId="pressure"/>
<channel id="temperature_guard" typeId="temperature_guard"/>
<channel id="waterconsumption" typeId="waterconsumption"/>
<channel id="valve_open" typeId="valve_open"/>
</channels>
<representation-property>applianceId</representation-property>
<config-description>
<parameter name="applianceId" type="text" required="true">
<label>Appliance ID</label>
<description>The UUID of the appliance as retrieved from the GROHE ONDUS API.</description>
</parameter>
<parameter name="roomId" type="integer" required="true">
<label>Room ID</label>
<description>The ID of the room the appliance is in as retrieved from the GROHE ONDUS API.</description>
</parameter>
<parameter name="locationId" type="integer" required="true">
<label>Location ID</label>
<description>The ID of the location the room is in as retrieved from the GROHE ONDUS API.</description>
</parameter>
<parameter name="pollingInterval" type="integer" required="false">
<label>Polling Interval</label>
<description>The interval in seconds used to poll the API for new data. Defaults to the configuration of the
appliance itself as retrieved from the API, usually 15 minutes.</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="sense">
<supported-bridge-type-refs>
<bridge-type-ref id="account"/>
</supported-bridge-type-refs>
<label>GROHE SENSE Appliance</label>
<description>A SENSE device</description>
<channels>
<channel id="name" typeId="name"/>
<channel id="humidity" typeId="humidity"/>
<channel id="temperature" typeId="temperature"/>
<channel id="battery" typeId="system.battery-level"/>
</channels>
<representation-property>applianceId</representation-property>
<config-description>
<parameter name="applianceId" type="text" required="true">
<label>Appliance ID</label>
<description>The UUID of the appliance as retrieved from the GROHE ONDUS API.</description>
</parameter>
<parameter name="roomId" type="integer" required="true">
<label>Room ID</label>
<description>The ID of the room the appliance is in as retrieved from the GROHE ONDUS API.</description>
</parameter>
<parameter name="locationId" type="integer" required="true">
<label>Location ID</label>
<description>The ID of the location the room is in as retrieved from the GROHE ONDUS API.</description>
</parameter>
<parameter name="pollingInterval" type="integer" required="false">
<label>Polling Interval</label>
<description>The interval in seconds used to poll the API for new data.</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="name">
<item-type>String</item-type>
<label>Appliance Name</label>
<description>The name of the appliance</description>
</channel-type>
<channel-type id="pressure">
<item-type>Number:Pressure</item-type>
<label>Pressure</label>
<description>The pressure of your water supply</description>
</channel-type>
<channel-type id="temperature_guard">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>The ambient temperature of the appliance</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="valve_open">
<item-type>Switch</item-type>
<label>Valve Open</label>
<description>Valve switch</description>
</channel-type>
<channel-type id="humidity">
<item-type>Number:Dimensionless</item-type>
<label>Humidity</label>
<description>The humidity reported by the device</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>The temperature reported by the device</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="waterconsumption">
<item-type>Number</item-type>
<label>Water Consumption</label>
<description>The amount of water consumed in the given time period.</description>
<state readOnly="true"/>
<config-description>
<parameter name="timeframe" type="integer" min="1" max="90" step="1" required="true">
<label>Timeframe</label>
<description>The timeframe in days to get the water consumption of</description>
<default>1</default>
</parameter>
</config-description>
</channel-type>
</thing:thing-descriptions>