[hydrawise] Various Fixes (#17345)

* Workaround for a bad response from the Hydrawise API

Signed-off-by: Dan Cunningham <dan@digitaldan.com>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
Dan Cunningham 2024-08-28 15:50:26 -07:00 committed by Ciprian Pascu
parent 91e95268e6
commit 3c01ededa1
12 changed files with 333 additions and 84 deletions

View File

@ -23,6 +23,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
public class HydrawiseConnectionException extends Exception { public class HydrawiseConnectionException extends Exception {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private int code = 0;
private String response = "";
public HydrawiseConnectionException(Exception e) { public HydrawiseConnectionException(Exception e) {
super(e); super(e);
} }
@ -30,4 +33,22 @@ public class HydrawiseConnectionException extends Exception {
public HydrawiseConnectionException(String message) { public HydrawiseConnectionException(String message) {
super(message); super(message);
} }
public HydrawiseConnectionException(String message, int code, String response) {
super(message);
this.code = code;
this.response = response;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
public int getCode() {
return code;
}
public String getResponse() {
return response;
}
} }

View File

@ -36,6 +36,7 @@ import org.openhab.binding.hydrawise.internal.api.HydrawiseCommandException;
import org.openhab.binding.hydrawise.internal.api.HydrawiseConnectionException; import org.openhab.binding.hydrawise.internal.api.HydrawiseConnectionException;
import org.openhab.binding.hydrawise.internal.api.graphql.dto.ControllerStatus; import org.openhab.binding.hydrawise.internal.api.graphql.dto.ControllerStatus;
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Forecast; import org.openhab.binding.hydrawise.internal.api.graphql.dto.Forecast;
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Hardware;
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Mutation; import org.openhab.binding.hydrawise.internal.api.graphql.dto.Mutation;
import org.openhab.binding.hydrawise.internal.api.graphql.dto.MutationResponse; import org.openhab.binding.hydrawise.internal.api.graphql.dto.MutationResponse;
import org.openhab.binding.hydrawise.internal.api.graphql.dto.MutationResponse.MutationResponseStatus; import org.openhab.binding.hydrawise.internal.api.graphql.dto.MutationResponse.MutationResponseStatus;
@ -77,7 +78,8 @@ public class HydrawiseGraphQLClient {
.registerTypeAdapter(ZoneRun.class, new ResponseDeserializer<ZoneRun>()) .registerTypeAdapter(ZoneRun.class, new ResponseDeserializer<ZoneRun>())
.registerTypeAdapter(Forecast.class, new ResponseDeserializer<Forecast>()) .registerTypeAdapter(Forecast.class, new ResponseDeserializer<Forecast>())
.registerTypeAdapter(Sensor.class, new ResponseDeserializer<Forecast>()) .registerTypeAdapter(Sensor.class, new ResponseDeserializer<Forecast>())
.registerTypeAdapter(ControllerStatus.class, new ResponseDeserializer<ControllerStatus>()).create(); .registerTypeAdapter(ControllerStatus.class, new ResponseDeserializer<ControllerStatus>())
.registerTypeAdapter(Hardware.class, new ResponseDeserializer<ControllerStatus>()).create();
private static final String GRAPH_URL = "https://app.hydrawise.com/api/v2/graph"; private static final String GRAPH_URL = "https://app.hydrawise.com/api/v2/graph";
private static final String MUTATION_START_ZONE = "startZone(zoneId: %d) { status }"; private static final String MUTATION_START_ZONE = "startZone(zoneId: %d) { status }";
@ -94,6 +96,7 @@ public class HydrawiseGraphQLClient {
private final HttpClient httpClient; private final HttpClient httpClient;
private final OAuthClientService oAuthService; private final OAuthClientService oAuthService;
private String queryString = ""; private String queryString = "";
private String weatherString = "";
public HydrawiseGraphQLClient(HttpClient httpClient, OAuthClientService oAuthService) { public HydrawiseGraphQLClient(HttpClient httpClient, OAuthClientService oAuthService) {
this.httpClient = httpClient; this.httpClient = httpClient;
@ -110,19 +113,48 @@ public class HydrawiseGraphQLClient {
public @Nullable QueryResponse queryControllers() public @Nullable QueryResponse queryControllers()
throws HydrawiseConnectionException, HydrawiseAuthenticationException { throws HydrawiseConnectionException, HydrawiseAuthenticationException {
try { try {
QueryRequest query = new QueryRequest(getQueryString()); return queryRequest(getQueryString());
String queryJson = gson.toJson(query);
String response = sendGraphQLQuery(queryJson);
try {
return gson.fromJson(response, QueryResponse.class);
} catch (JsonSyntaxException e) {
throw new HydrawiseConnectionException("Invalid Response: " + response);
}
} catch (IOException e) { } catch (IOException e) {
throw new HydrawiseConnectionException(e); throw new HydrawiseConnectionException(e);
} }
} }
/**
* Sends a GrapQL query for controller data
*
* @return QueryResponse
* @throws HydrawiseConnectionException
* @throws HydrawiseAuthenticationException
*/
public @Nullable QueryResponse queryWeather()
throws HydrawiseConnectionException, HydrawiseAuthenticationException {
try {
return queryRequest(getWeatherString());
} catch (IOException e) {
throw new HydrawiseConnectionException(e);
}
}
/**
* Sends a GrapQL query for controller data
*
* @param queryString
* @return QueryResponse
* @throws HydrawiseConnectionException
* @throws HydrawiseAuthenticationException
*/
private @Nullable QueryResponse queryRequest(String queryString)
throws HydrawiseConnectionException, HydrawiseAuthenticationException {
QueryRequest query = new QueryRequest(queryString);
String queryJson = gson.toJson(query);
String response = sendGraphQLQuery(queryJson);
try {
return gson.fromJson(response, QueryResponse.class);
} catch (JsonSyntaxException e) {
throw new HydrawiseConnectionException("Invalid Response: " + response);
}
}
/*** /***
* Stops a given relay * Stops a given relay
* *
@ -313,7 +345,8 @@ public class HydrawiseGraphQLClient {
int statusCode = response.getStatus(); int statusCode = response.getStatus();
if (!HttpStatus.isSuccess(statusCode)) { if (!HttpStatus.isSuccess(statusCode)) {
throw new HydrawiseConnectionException( throw new HydrawiseConnectionException(
"Request failed with HTTP status code: " + statusCode + " response: " + stringResponse); "Request failed with HTTP status code: " + statusCode + " response: " + stringResponse,
statusCode, stringResponse);
} }
return stringResponse; return stringResponse;
} catch (InterruptedException | TimeoutException | OAuthException | IOException e) { } catch (InterruptedException | TimeoutException | OAuthException | IOException e) {
@ -338,15 +371,25 @@ public class HydrawiseGraphQLClient {
private String getQueryString() throws IOException { private String getQueryString() throws IOException {
if (queryString.isBlank()) { if (queryString.isBlank()) {
try (InputStream inputStream = HydrawiseGraphQLClient.class.getClassLoader() queryString = getResourceString("query.graphql");
.getResourceAsStream("query.graphql");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
queryString = bufferedReader.lines().collect(Collectors.joining("\n"));
}
} }
return queryString; return queryString;
} }
private String getWeatherString() throws IOException {
if (weatherString.isBlank()) {
weatherString = getResourceString("weather.graphql");
}
return weatherString;
}
private String getResourceString(String name) throws IOException {
try (InputStream inputStream = HydrawiseGraphQLClient.class.getClassLoader().getResourceAsStream(name);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
return bufferedReader.lines().collect(Collectors.joining("\n"));
}
}
class ResponseDeserializer<T> implements JsonDeserializer<T> { class ResponseDeserializer<T> implements JsonDeserializer<T> {
@Override @Override
@Nullable @Nullable

View File

@ -22,8 +22,8 @@ public class Controller {
public Integer id; public Integer id;
public String name; public String name;
public ControllerStatus status; public ControllerStatus status;
public Hardware hardware;
public Location location; public Location location;
public List<Zone> zones = null; public List<Zone> zones = null;
public List<Sensor> sensors = null; public List<Sensor> sensors = null;
public List<Forecast> forecast = null;
} }

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) 2010-2024 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.api.graphql.dto;
/**
*
* @author Dan Cunningham - Initial contribution
*
*/
public class Hardware {
public String version;
public Model model;
}

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) 2010-2024 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.api.graphql.dto;
/**
*
* @author Dan Cunningham - Initial contribution
*
*/
public class Model {
public Integer maxZones;
public String name;
public String description;
}

View File

@ -12,22 +12,23 @@
*/ */
package org.openhab.binding.hydrawise.internal.discovery; package org.openhab.binding.hydrawise.internal.discovery;
import java.time.Instant; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hydrawise.internal.HydrawiseBindingConstants; import org.openhab.binding.hydrawise.internal.HydrawiseBindingConstants;
import org.openhab.binding.hydrawise.internal.HydrawiseControllerListener; import org.openhab.binding.hydrawise.internal.HydrawiseControllerListener;
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Controller; import org.openhab.binding.hydrawise.internal.api.graphql.dto.Controller;
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Customer; import org.openhab.binding.hydrawise.internal.api.graphql.dto.Customer;
import org.openhab.binding.hydrawise.internal.handler.HydrawiseAccountHandler; import org.openhab.binding.hydrawise.internal.handler.HydrawiseAccountHandler;
import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService; import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.thing.binding.ThingHandlerService;
import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;
/** /**
* *
@ -36,33 +37,44 @@ import org.osgi.service.component.annotations.ServiceScope;
*/ */
@NonNullByDefault @NonNullByDefault
@Component(scope = ServiceScope.PROTOTYPE, service = ThingHandlerService.class) @Component(service = ThingHandlerService.class)
public class HydrawiseCloudControllerDiscoveryService public class HydrawiseCloudControllerDiscoveryService extends AbstractDiscoveryService
extends AbstractThingHandlerDiscoveryService<HydrawiseAccountHandler> implements HydrawiseControllerListener { implements HydrawiseControllerListener, ThingHandlerService {
private static final int TIMEOUT = 5; private static final int TIMEOUT = 5;
@Nullable
HydrawiseAccountHandler handler;
public HydrawiseCloudControllerDiscoveryService() { public HydrawiseCloudControllerDiscoveryService() {
super(HydrawiseAccountHandler.class, Set.of(HydrawiseBindingConstants.THING_TYPE_CONTROLLER), TIMEOUT, true); super(Set.of(HydrawiseBindingConstants.THING_TYPE_CONTROLLER), TIMEOUT, true);
} }
@Override @Override
protected void startScan() { protected void startScan() {
Customer data = thingHandler.lastData(); HydrawiseAccountHandler localHandler = this.handler;
if (data != null) { if (localHandler != null) {
data.controllers.forEach(controller -> addDiscoveryResults(controller)); Customer data = localHandler.lastData();
if (data != null) {
data.controllers.forEach(controller -> addDiscoveryResults(controller));
}
} }
} }
@Override @Override
public void dispose() { public void deactivate() {
super.dispose(); HydrawiseAccountHandler localHandler = this.handler;
removeOlderResults(Instant.now().toEpochMilli(), thingHandler.getThing().getUID()); if (localHandler != null) {
removeOlderResults(new Date().getTime(), localHandler.getThing().getUID());
}
} }
@Override @Override
protected synchronized void stopScan() { protected synchronized void stopScan() {
super.stopScan(); super.stopScan();
removeOlderResults(getTimestampOfLastScan(), thingHandler.getThing().getUID()); HydrawiseAccountHandler localHandler = this.handler;
if (localHandler != null) {
removeOlderResults(getTimestampOfLastScan(), localHandler.getThing().getUID());
}
} }
@Override @Override
@ -71,19 +83,27 @@ public class HydrawiseCloudControllerDiscoveryService
} }
@Override @Override
public void initialize() { public void setThingHandler(ThingHandler handler) {
thingHandler.addControllerListeners(this); this.handler = (HydrawiseAccountHandler) handler;
super.initialize(); this.handler.addControllerListeners(this);
}
@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
} }
private void addDiscoveryResults(Controller controller) { private void addDiscoveryResults(Controller controller) {
String label = String.format("Hydrawise Controller %s", controller.name); HydrawiseAccountHandler localHandler = this.handler;
int id = controller.id; if (localHandler != null) {
ThingUID bridgeUID = thingHandler.getThing().getUID(); String label = String.format("Hydrawise Controller %s", controller.name);
ThingUID thingUID = new ThingUID(HydrawiseBindingConstants.THING_TYPE_CONTROLLER, bridgeUID, int id = controller.id;
String.valueOf(id)); ThingUID bridgeUID = localHandler.getThing().getUID();
thingDiscovered(DiscoveryResultBuilder.create(thingUID).withLabel(label).withBridge(bridgeUID) ThingUID thingUID = new ThingUID(HydrawiseBindingConstants.THING_TYPE_CONTROLLER, bridgeUID,
.withProperty(HydrawiseBindingConstants.CONFIG_CONTROLLER_ID, id) String.valueOf(id));
.withRepresentationProperty(HydrawiseBindingConstants.CONFIG_CONTROLLER_ID).build()); thingDiscovered(DiscoveryResultBuilder.create(thingUID).withLabel(label).withBridge(bridgeUID)
.withProperty(HydrawiseBindingConstants.CONFIG_CONTROLLER_ID, id)
.withRepresentationProperty(HydrawiseBindingConstants.CONFIG_CONTROLLER_ID).build());
}
} }
} }

View File

@ -63,6 +63,7 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access
*/ */
private static final int MIN_REFRESH_SECONDS = 30; private static final int MIN_REFRESH_SECONDS = 30;
private static final int TOKEN_REFRESH_SECONDS = 60; private static final int TOKEN_REFRESH_SECONDS = 60;
private static final int WEATHER_REFRESH_MILLIS = 60 * 60 * 1000; // 1 hour
private static final String BASE_URL = "https://app.hydrawise.com/api/v2/"; private static final String BASE_URL = "https://app.hydrawise.com/api/v2/";
private static final String AUTH_URL = BASE_URL + "oauth/access-token"; private static final String AUTH_URL = BASE_URL + "oauth/access-token";
private static final String CLIENT_SECRET = "zn3CrjglwNV1"; private static final String CLIENT_SECRET = "zn3CrjglwNV1";
@ -77,6 +78,7 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access
private @Nullable ScheduledFuture<?> pollFuture; private @Nullable ScheduledFuture<?> pollFuture;
private @Nullable ScheduledFuture<?> tokenFuture; private @Nullable ScheduledFuture<?> tokenFuture;
private @Nullable Customer lastData; private @Nullable Customer lastData;
private long lastWeatherUpdate;
private int refresh; private int refresh;
public HydrawiseAccountHandler(final Bridge bridge, final HttpClient httpClient, final OAuthFactory oAuthFactory) { public HydrawiseAccountHandler(final Bridge bridge, final HttpClient httpClient, final OAuthFactory oAuthFactory) {
@ -228,6 +230,11 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access
} }
private void poll(boolean retry) { private void poll(boolean retry) {
HydrawiseGraphQLClient apiClient = this.apiClient;
if (apiClient == null) {
logger.debug("apiclient not initalized");
return;
}
try { try {
QueryResponse response = apiClient.queryControllers(); QueryResponse response = apiClient.queryControllers();
if (response == null) { if (response == null) {
@ -240,6 +247,21 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access
if (getThing().getStatus() != ThingStatus.ONLINE) { if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
} }
long currentTime = System.currentTimeMillis();
if (currentTime > lastWeatherUpdate + WEATHER_REFRESH_MILLIS) {
lastWeatherUpdate = currentTime;
try {
QueryResponse weatherResponse = apiClient.queryWeather();
if (weatherResponse != null) {
response.data.me.controllers.forEach(controller -> {
weatherResponse.data.me.controllers.stream().filter(c -> c.id.equals(controller.id))
.findFirst().ifPresent(c -> controller.location.forecast = c.location.forecast);
});
}
} catch (HydrawiseConnectionException e) {
logger.debug("Weather data is not supported", e);
}
}
lastData = response.data.me; lastData = response.data.me;
synchronized (controllerListeners) { synchronized (controllerListeners) {
controllerListeners.forEach(listener -> { controllerListeners.forEach(listener -> {

View File

@ -27,6 +27,8 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.measure.quantity.Speed;
import javax.measure.quantity.Temperature;
import javax.measure.quantity.Volume; import javax.measure.quantity.Volume;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -250,7 +252,7 @@ public class HydrawiseControllerHandler extends BaseThingHandler implements Hydr
updateForecast(controller.location.forecast); updateForecast(controller.location.forecast);
} }
if (controller.zones != null) { if (controller.zones != null) {
updateZones(controller.zones); updateZones(controller.zones, controller.hardware.model.maxZones);
} }
// update values with what the cloud tells us even though the controller may be offline // update values with what the cloud tells us even though the controller may be offline
@ -278,20 +280,22 @@ public class HydrawiseControllerHandler extends BaseThingHandler implements Hydr
: UnDefType.NULL); : UnDefType.NULL);
} }
private void updateZones(List<Zone> zones) { private void updateZones(List<Zone> zones, int maxZones) {
AtomicReference<Boolean> anyRunning = new AtomicReference<>(false); AtomicReference<Boolean> anyRunning = new AtomicReference<>(false);
AtomicReference<Boolean> anySuspended = new AtomicReference<>(false); AtomicReference<Boolean> anySuspended = new AtomicReference<>(false);
for (Zone zone : zones) { for (Zone zone : zones) {
// there are 12 relays per expander, expanders will have a zoneNumber like: // for expansion modules who zones numbers are > 99
// there are maxZones relays per expander, expanders will have a zoneNumber like:
// maxZones = 12
// 10 for expander 0, relay 10 = zone10 // 10 for expander 0, relay 10 = zone10
// 101 for expander 1, relay 1 = zone13 // 101 for expander 1, relay 1 = zone13
// 212 for expander 2, relay 12 = zone36 // 212 for expander 2, relay 12 = zone36
// division of integers in Java give whole numbers, not remainders FYI // division of integers in Java give whole numbers, not remainders FYI
int zoneNumber = ((zone.number.value / 100) * 12) + (zone.number.value % 100); int zoneNumber = zone.number.value <= 99 ? zone.number.value
: ((zone.number.value / 100) * maxZones) + (zone.number.value % 100);
String group = "zone" + zoneNumber; String group = "zone" + zoneNumber;
zoneMaps.put(group, zone); zoneMaps.put(group, zone);
logger.trace("Updateing Zone {} {} ", group, zone.name); logger.trace("Updating Zone {} {} ", group, zone.name);
updateGroupState(group, CHANNEL_ZONE_NAME, new StringType(zone.name)); updateGroupState(group, CHANNEL_ZONE_NAME, new StringType(zone.name));
updateGroupState(group, CHANNEL_ZONE_ICON, new StringType(BASE_IMAGE_URL + zone.icon.fileName)); updateGroupState(group, CHANNEL_ZONE_ICON, new StringType(BASE_IMAGE_URL + zone.icon.fileName));
if (zone.scheduledRuns != null) { if (zone.scheduledRuns != null) {
@ -328,8 +332,9 @@ public class HydrawiseControllerHandler extends BaseThingHandler implements Hydr
updateGroupState(group, CHANNEL_ZONE_SUSPENDUNTIL, UnDefType.UNDEF); updateGroupState(group, CHANNEL_ZONE_SUSPENDUNTIL, UnDefType.UNDEF);
} }
} }
updateGroupState(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_RUN, OnOffType.from(anyRunning.get())); updateGroupState(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_RUN, anyRunning.get() ? OnOffType.ON : OnOffType.OFF);
updateGroupState(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_SUSPEND, OnOffType.from(anySuspended.get())); updateGroupState(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_SUSPEND,
anySuspended.get() ? OnOffType.ON : OnOffType.OFF);
updateGroupState(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_SUSPENDUNTIL, UnDefType.UNDEF); updateGroupState(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_SUSPENDUNTIL, UnDefType.UNDEF);
} }
@ -362,6 +367,7 @@ public class HydrawiseControllerHandler extends BaseThingHandler implements Hydr
int i = 1; int i = 1;
for (Forecast forecast : forecasts) { for (Forecast forecast : forecasts) {
String group = "forecast" + (i++); String group = "forecast" + (i++);
logger.trace("Updating {} {}", group, forecast.time);
updateGroupState(group, CHANNEL_FORECAST_TIME, stringToDateTime(forecast.time)); updateGroupState(group, CHANNEL_FORECAST_TIME, stringToDateTime(forecast.time));
updateGroupState(group, CHANNEL_FORECAST_CONDITIONS, new StringType(forecast.conditions)); updateGroupState(group, CHANNEL_FORECAST_CONDITIONS, new StringType(forecast.conditions));
updateGroupState(group, CHANNEL_FORECAST_HUMIDITY, new DecimalType(forecast.averageHumidity.intValue())); updateGroupState(group, CHANNEL_FORECAST_HUMIDITY, new DecimalType(forecast.averageHumidity.intValue()));
@ -383,12 +389,12 @@ public class HydrawiseControllerHandler extends BaseThingHandler implements Hydr
private void updateTemperature(UnitValue temperature, String group, String channel) { private void updateTemperature(UnitValue temperature, String group, String channel) {
logger.debug("TEMP {} {} {} {}", group, channel, temperature.unit, temperature.value); logger.debug("TEMP {} {} {} {}", group, channel, temperature.unit, temperature.value);
updateGroupState(group, channel, new QuantityType<>(temperature.value, updateGroupState(group, channel, new QuantityType<Temperature>(temperature.value,
"\\u00b0F".equals(temperature.unit) ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS)); temperature.unit.indexOf("F") >= 0 ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS));
} }
private void updateWindspeed(UnitValue wind, String group, String channel) { private void updateWindspeed(UnitValue wind, String group, String channel) {
updateGroupState(group, channel, new QuantityType<>(wind.value, updateGroupState(group, channel, new QuantityType<Speed>(wind.value,
"mph".equals(wind.unit) ? ImperialUnits.MILES_PER_HOUR : SIUnits.KILOMETRE_PER_HOUR)); "mph".equals(wind.unit) ? ImperialUnits.MILES_PER_HOUR : SIUnits.KILOMETRE_PER_HOUR));
} }
@ -439,10 +445,7 @@ public class HydrawiseControllerHandler extends BaseThingHandler implements Hydr
} }
private QuantityType<Volume> waterFlowToQuantityType(Number flow, String units) { private QuantityType<Volume> waterFlowToQuantityType(Number flow, String units) {
double waterFlow = flow.doubleValue(); return new QuantityType<>(flow.doubleValue(),
if ("gals".equals(units)) { "gal".equals(units) ? ImperialUnits.GALLON_LIQUID_US : Units.LITRE);
waterFlow = waterFlow * 3.785;
}
return new QuantityType<>(waterFlow, Units.LITRE);
} }
} }

View File

@ -214,7 +214,8 @@ public class HydrawiseLocalHandler extends BaseThingHandler {
updateGroupState(group, CHANNEL_ZONE_TIME_LEFT, new QuantityType<>(0, Units.SECOND)); updateGroupState(group, CHANNEL_ZONE_TIME_LEFT, new QuantityType<>(0, Units.SECOND));
} }
updateGroupState(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_RUN, OnOffType.from(!status.running.isEmpty())); updateGroupState(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_RUN,
!status.running.isEmpty() ? OnOffType.ON : OnOffType.OFF);
}); });
} }

View File

@ -38,7 +38,8 @@
<label>Hydrawise Controller Thing</label> <label>Hydrawise Controller Thing</label>
<description>Hydrawise connected irrigation controller</description> <description>Hydrawise connected irrigation controller</description>
<!-- Until we have https://github.com/eclipse/smarthome/issues/1118 fixed, we need to list all possible channel groups. <!-- Until we have https://github.com/eclipse/smarthome/issues/1118 fixed, we need to list
all possible channel groups.
Once this is fixed we can dynamically add them to the thing and not list them here. --> Once this is fixed we can dynamically add them to the thing and not list them here. -->
<channel-groups> <channel-groups>
@ -230,6 +231,78 @@
<label>Zone 36</label> <label>Zone 36</label>
<description>Sprinkler Zone 36</description> <description>Sprinkler Zone 36</description>
</channel-group> </channel-group>
<channel-group id="zone37" typeId="zone">
<label>Zone 37</label>
<description>Sprinkler Zone 37</description>
</channel-group>
<channel-group id="zone38" typeId="zone">
<label>Zone 38</label>
<description>Sprinkler Zone 38</description>
</channel-group>
<channel-group id="zone39" typeId="zone">
<label>Zone 39</label>
<description>Sprinkler Zone 39</description>
</channel-group>
<channel-group id="zone40" typeId="zone">
<label>Zone 40</label>
<description>Sprinkler Zone 40</description>
</channel-group>
<channel-group id="zone41" typeId="zone">
<label>Zone 41</label>
<description>Sprinkler Zone 41</description>
</channel-group>
<channel-group id="zone42" typeId="zone">
<label>Zone 42</label>
<description>Sprinkler Zone 42</description>
</channel-group>
<channel-group id="zone43" typeId="zone">
<label>Zone 43</label>
<description>Sprinkler Zone 43</description>
</channel-group>
<channel-group id="zone44" typeId="zone">
<label>Zone 44</label>
<description>Sprinkler Zone 44</description>
</channel-group>
<channel-group id="zone45" typeId="zone">
<label>Zone 45</label>
<description>Sprinkler Zone 45</description>
</channel-group>
<channel-group id="zone46" typeId="zone">
<label>Zone 46</label>
<description>Sprinkler Zone 46</description>
</channel-group>
<channel-group id="zone47" typeId="zone">
<label>Zone 47</label>
<description>Sprinkler Zone 47</description>
</channel-group>
<channel-group id="zone48" typeId="zone">
<label>Zone 48</label>
<description>Sprinkler Zone 48</description>
</channel-group>
<channel-group id="zone49" typeId="zone">
<label>Zone 49</label>
<description>Sprinkler Zone 49</description>
</channel-group>
<channel-group id="zone50" typeId="zone">
<label>Zone 50</label>
<description>Sprinkler Zone 50</description>
</channel-group>
<channel-group id="zone51" typeId="zone">
<label>Zone 51</label>
<description>Sprinkler Zone 51</description>
</channel-group>
<channel-group id="zone52" typeId="zone">
<label>Zone 52</label>
<description>Sprinkler Zone 52</description>
</channel-group>
<channel-group id="zone53" typeId="zone">
<label>Zone 53</label>
<description>Sprinkler Zone 53</description>
</channel-group>
<channel-group id="zone54" typeId="zone">
<label>Zone 54</label>
<description>Sprinkler Zone 54</description>
</channel-group>
</channel-groups> </channel-groups>
<config-description> <config-description>
<parameter name="controllerId" type="integer" required="true"> <parameter name="controllerId" type="integer" required="true">

View File

@ -5,41 +5,26 @@
controllers { controllers {
id id
name name
status { status {
summary summary
online online
lastContact { lastContact {
timestamp timestamp
} }
} }
hardware {
version
model {
maxZones
name
description
}
}
location { location {
coordinates { coordinates {
latitude latitude
longitude longitude
} }
forecast(days: 3) {
time
updateTime
conditions
averageWindSpeed {
value
unit
}
highTemperature {
value
unit
}
lowTemperature {
value
unit
}
probabilityOfPrecipitation
precipitation {
value
unit
}
averageHumidity
}
} }
zones { zones {
id id

View File

@ -0,0 +1,34 @@
{
me {
email
lastContact
controllers {
id
location {
forecast(days: 3) {
time
updateTime
conditions
averageWindSpeed {
value
unit
}
highTemperature {
value
unit
}
lowTemperature {
value
unit
}
probabilityOfPrecipitation
precipitation {
value
unit
}
averageHumidity
}
}
}
}
}