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,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>