added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<>()));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user