Skip to content

Commit

Permalink
Initial work at moving to a GraphQL based API for cloud control
Browse files Browse the repository at this point in the history
Signed-off-by: digitaldan <[email protected]>
  • Loading branch information
digitaldan committed May 6, 2020
1 parent 8aa0b30 commit 87d89d5
Show file tree
Hide file tree
Showing 68 changed files with 2,860 additions and 1,109 deletions.
3 changes: 2 additions & 1 deletion bundles/org.openhab.binding.hydrawise/.classpath
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="module" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
Expand Down
150 changes: 81 additions & 69 deletions bundles/org.openhab.binding.hydrawise/README.md

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion bundles/org.openhab.binding.hydrawise/pom.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.hydrawise-${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>
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>

<feature name="openhab-binding-hydrawise" description="Hydrawise Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.hydrawise/${project.version}</bundle>
</feature>
<feature name="openhab-binding-hydrawise" description="Hydrawise Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.hydrawise/${project.version}</bundle>
</feature>
</features>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.hydrawise.internal;

/**
* The {@link HydrawiseAccountConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Dan Cunningham - Initial contribution
*/
public class HydrawiseAccountConfiguration {

public String refreshToken;

public String userName;

public String password;

public Boolean savePassword;

/**
* refresh interval in seconds.
*/
public Integer refreshInterval;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package org.openhab.binding.hydrawise.internal;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.smarthome.config.core.Configuration;
import org.eclipse.smarthome.core.thing.Bridge;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler;
import org.eclipse.smarthome.core.types.Command;
import org.openhab.binding.hydrawise.internal.api.HydrawiseAuthenticationException;
import org.openhab.binding.hydrawise.internal.api.HydrawiseConnectionException;
import org.openhab.binding.hydrawise.internal.api.graphql.HydrawiseAuthTokenProvider;
import org.openhab.binding.hydrawise.internal.api.graphql.HydrawiseGraphQLClient;
import org.openhab.binding.hydrawise.internal.api.graphql.schema.AuthToken;
import org.openhab.binding.hydrawise.internal.api.graphql.schema.Customer;
import org.openhab.binding.hydrawise.internal.api.graphql.schema.QueryResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@NonNullByDefault
public class HydrawiseAccountHandler extends BaseBridgeHandler implements HydrawiseAuthTokenProvider {
/**
* Minimum amount of time we can poll for updates
*/
protected static final int MIN_REFRESH_SECONDS = 30;
protected static final int DEFAULT_REFRESH_SECONDS = 60;
private final Logger logger = LoggerFactory.getLogger(HydrawiseAccountHandler.class);
private HydrawiseGraphQLClient apiClient;
private @Nullable AuthToken token;
private @Nullable ScheduledFuture<?> pollFuture;
private int refresh;
private @Nullable Customer lastData;
private final List<HydrawiseControllerListener> controllerListeners = new ArrayList<HydrawiseControllerListener>();

public HydrawiseAccountHandler(Bridge bridge, HttpClient httpClient) {
super(bridge);
this.apiClient = new HydrawiseGraphQLClient(httpClient, this);
}

@Override
public void handleCommand(ChannelUID channelUID, Command command) {

}

@Override
public void initialize() {
logger.debug("Handler initialized.");
scheduler.schedule(this::configure, 0, TimeUnit.SECONDS);
}

@Override
public void dispose() {
logger.debug("Handler disposed.");
clearPolling();
}

public void addControllerListeners(HydrawiseControllerListener listener) {
this.controllerListeners.add(listener);
Customer data = lastData;
if (data != null) {
listener.onData(data.controllers);
}
}

public void removeControllerListeners(HydrawiseControllerListener listener) {
this.controllerListeners.remove(listener);
}

public @Nullable HydrawiseGraphQLClient graphQLClient() {
return apiClient;
}

public @Nullable Customer lastData() {
return lastData;
}

private void configure() {
HydrawiseAccountConfiguration config = getConfig().as(HydrawiseAccountConfiguration.class);
// TODO switch to Java 11 String.isBlank
if (StringUtils.isNotBlank(config.userName) && StringUtils.isNotBlank(config.password)) {
if (!config.savePassword) {
Configuration editedConfig = editConfiguration();
editedConfig.remove("password");
updateConfiguration(editedConfig);
}
try {
authTokenUpdated(apiClient.login(config.userName, config.password));
} catch (HydrawiseConnectionException | HydrawiseAuthenticationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
return;
}
} else if (StringUtils.isNotBlank(config.refreshToken)) {
authTokenUpdated(new AuthToken(config.refreshToken));
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Login credentials required.");
return;
}
this.refresh = Math.max(config.refreshInterval != null ? config.refreshInterval : DEFAULT_REFRESH_SECONDS,
MIN_REFRESH_SECONDS);
initPolling(0, refresh);
}

/**
* Starts/Restarts polling with an initial delay. This allows changes in the poll cycle for when commands are sent
* and we need to poll sooner then the next refresh cycle.
*/
private synchronized void initPolling(int initalDelay, int refresh) {
clearPolling();
pollFuture = scheduler.scheduleWithFixedDelay(this::poll, initalDelay, refresh, TimeUnit.SECONDS);
}

/**
* Stops/clears this thing's polling future
*/
private void clearPolling() {
ScheduledFuture<?> localFuture = pollFuture;
if (isFutureValid(localFuture)) {
if (localFuture != null) {
localFuture.cancel(false);
}
}
}

private boolean isFutureValid(@Nullable ScheduledFuture<?> future) {
return future != null && !future.isCancelled();
}

private void poll() {
try {
QueryResponse response = apiClient.queryControllers();
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
lastData = response.data.me;
controllerListeners.forEach(listener -> {
listener.onData(response.data.me.controllers);
});
} catch (HydrawiseConnectionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (HydrawiseAuthenticationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
clearPolling();
}
}

@Override
public @Nullable AuthToken getAuthToken() {
return token;
}

@Override
public void authTokenUpdated(AuthToken token) {
this.token = token;
Configuration editedConfig = editConfiguration();
editedConfig.put(HydrawiseBindingConstants.CONFIG_REFRESHTOKEN, token.refreshToken);
updateConfiguration(editedConfig);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,40 +27,50 @@ public class HydrawiseBindingConstants {
private static final String BINDING_ID = "hydrawise";

// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_CLOUD = new ThingTypeUID(BINDING_ID, "cloud");
public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
public static final ThingTypeUID THING_TYPE_CONTROLLER = new ThingTypeUID(BINDING_ID, "controller");
public static final ThingTypeUID THING_TYPE_LOCAL = new ThingTypeUID(BINDING_ID, "local");

public static final String BASE_IMAGE_URL = "https://app.hydrawise.com/config/images/";
public static final String CONFIG_USERNAME = "userName";
public static final String CONFIG_PASSWORD = "password";
public static final String CONFIG_REFRESHTOKEN = "refreshToken";
public static final String CONFIG_CONTROLLER_ID = "controllerId";

public static final String CHANNEL_GROUP_CONTROLLER = "controller";
public static final String CHANNEL_GROUP_CONTROLLER_SYSTEM = "system";
public static final String CHANNEL_CONTROLLER_LAST_CONTACT = "lastContact";
public static final String CHANNEL_CONTROLLER_STATUS = "status";
public static final String CHANNEL_CONTROLLER_SUMMARY = "summary";
public static final String CHANNEL_CONTROLLER_ONLINE = "online";
public static final String CHANNEL_GROUP_ALLZONES = "allzones";
public static final String CHANNEL_ZONE_RUN_CUSTOM = "runcustom";
public static final String CHANNEL_ZONE_RUN = "run";
public static final String CHANNEL_ZONE_STOP = "stop";
public static final String CHANNEL_ZONE_SUSPEND = "suspend";
public static final String CHANNEL_ZONE_NAME = "name";
public static final String CHANNEL_ZONE_ICON = "icon";
public static final String CHANNEL_ZONE_LAST_WATER = "lastwater";
public static final String CHANNEL_ZONE_TIME = "time";
public static final String CHANNEL_ZONE_STARTTIME = "startTime";
public static final String CHANNEL_ZONE_DURATION = "duration";
public static final String CHANNEL_ZONE_TYPE = "type";
public static final String CHANNEL_ZONE_RUN = "run";
public static final String CHANNEL_ZONE_RUN_CUSTOM = "runcustom";
public static final String CHANNEL_ZONE_NEXT_RUN_TIME_TIME = "nextruntime";
// public static final String CHANNEL_ZONE_STOP = "stop";
public static final String CHANNEL_ZONE_SUSPEND = "suspend";
public static final String CHANNEL_ZONE_SUSPENDUNTIL = "suspendedUntil";
// public static final String CHANNEL_ZONE_LAST_WATER = "lastwater";
public static final String CHANNEL_ZONE_SUMMARY = "summary";
public static final String CHANNEL_ZONE_TIME_LEFT = "timeleft";
public static final String CHANNEL_RUN_ALL_ZONES = "runall";
public static final String CHANNEL_STOP_ALL_ZONES = "stopall";
public static final String CHANNEL_SUSPEND_ALL_ZONES = "suspendall";
// public static final String CHANNEL_RUN_ALL_ZONES = "runall";
// public static final String CHANNEL_STOP_ALL_ZONES = "stopall";
// public static final String CHANNEL_SUSPEND_ALL_ZONES = "suspendall";
public static final String CHANNEL_SENSOR_NAME = "name";
public static final String CHANNEL_SENSOR_INPUT = "input";
public static final String CHANNEL_SENSOR_MODE = "mode";
public static final String CHANNEL_SENSOR_TIMER = "timer";
public static final String CHANNEL_SENSOR_DELAY = "delay";
public static final String CHANNEL_SENSOR_OFFTIMER = "offtimer";
public static final String CHANNEL_SENSOR_OFFLEVEL = "offlevel";
public static final String CHANNEL_SENSOR_ACTIVE = "active";
public static final String CHANNEL_FORECAST_TEMPERATURE_HIGH = "temperaturehigh";
public static final String CHANNEL_FORECAST_TEMPERATURE_LOW = "temperaturelow";
public static final String CHANNEL_FORECAST_CONDITIONS = "conditions";
public static final String CHANNEL_FORECAST_DAY = "day";
public static final String CHANNEL_FORECAST_TIME = "time";
public static final String CHANNEL_FORECAST_HUMIDITY = "humidity";
public static final String CHANNEL_FORECAST_WIND = "wind";
public static final String CHANNEL_FORECAST_ICON = "icon";
Expand Down
Loading

0 comments on commit 87d89d5

Please sign in to comment.