[boschindego] Add new channels (#13040)
Fixes #12938 Fixes #13017 Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
parent
53d72dcecb
commit
5e4ca2568c
|
@ -8,25 +8,31 @@ His [Java Library](https://github.com/zazaz-de/iot-device-bosch-indego-controlle
|
||||||
|
|
||||||
Currently the binding supports ***indego*** mowers as a thing type with these configuration parameters:
|
Currently the binding supports ***indego*** mowers as a thing type with these configuration parameters:
|
||||||
|
|
||||||
| Parameter | Description | Default |
|
| Parameter | Description | Default |
|
||||||
|--------------------|-----------------------------------------------------------------|---------|
|
|-----------------------|-------------------------------------------------------------------------|---------|
|
||||||
| username | Username for the Bosch Indego account | |
|
| username | Username for the Bosch Indego account | |
|
||||||
| password | Password for the Bosch Indego account | |
|
| password | Password for the Bosch Indego account | |
|
||||||
| refresh | The number of seconds between refreshing device state | 180 |
|
| refresh | The number of seconds between refreshing device state | 180 |
|
||||||
| cuttingTimeRefresh | The number of minutes between refreshing last/next cutting time | 60 |
|
| cuttingTimeMapRefresh | The number of minutes between refreshing last/next cutting time and map | 60 |
|
||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|
||||||
| Channel | Item Type | Description |
|
| Channel | Item Type | Description | Writeable |
|
||||||
|--------------|-------------|-------------------------------------------------------------------------------------------------------------------------------------|
|
|--------------------|--------------------------|-------------------------------------------------------------------------------------------------------------------------------------|-----------|
|
||||||
| state | Number | You can send commands to this channel to control the mower and read the simplified state from it (1=mow, 2=return to dock, 3=pause) |
|
| state | Number | You can send commands to this channel to control the mower and read the simplified state from it (1=mow, 2=return to dock, 3=pause) | Yes |
|
||||||
| errorcode | Number | Error code of the mower (0=no error, readonly) |
|
| errorcode | Number | Error code of the mower (0=no error) | |
|
||||||
| statecode | Number | Detailed state of the mower (readonly) |
|
| statecode | Number | Detailed state of the mower | |
|
||||||
| textualstate | String | State as a text. (readonly) |
|
| textualstate | String | State as a text. | |
|
||||||
| ready | Number | Shows if the mower is ready to mow (1=ready, 0=not ready, readonly) |
|
| ready | Number | Shows if the mower is ready to mow (1=ready, 0=not ready) | |
|
||||||
| mowed | Dimmer | Cut grass in percent (readonly) |
|
| mowed | Dimmer | Cut grass in percent | |
|
||||||
| lastCutting | DateTime | Last cutting time (readonly) |
|
| lastCutting | DateTime | Last cutting time | |
|
||||||
| nextCutting | DateTime | Next scheduled cutting time (readonly) |
|
| nextCutting | DateTime | Next scheduled cutting time | |
|
||||||
|
| batteryVoltage | Number:ElectricPotential | Battery voltage reported by the device | |
|
||||||
|
| batteryLevel | Number | Battery level as a percentage (0-100%) | |
|
||||||
|
| lowBattery | Switch | Low battery warning with possible values on (low battery) and off (battery ok) | |
|
||||||
|
| batteryTemperature | Number:Temperature | Battery temperature reported by the device | |
|
||||||
|
| gardenSize | Number:Area | Garden size mapped by the device | |
|
||||||
|
| gardenMap | Image | Garden map mapped by the device | |
|
||||||
|
|
||||||
### State Codes
|
### State Codes
|
||||||
|
|
||||||
|
@ -81,6 +87,12 @@ Number Indego_Ready { channel="boschindego:indego:lawnmower:ready" }
|
||||||
Dimmer Indego_Mowed { channel="boschindego:indego:lawnmower:mowed" }
|
Dimmer Indego_Mowed { channel="boschindego:indego:lawnmower:mowed" }
|
||||||
DateTime Indego_LastCutting { channel="boschindego:indego:lawnmower:lastCutting" }
|
DateTime Indego_LastCutting { channel="boschindego:indego:lawnmower:lastCutting" }
|
||||||
DateTime Indego_NextCutting { channel="boschindego:indego:lawnmower:nextCutting" }
|
DateTime Indego_NextCutting { channel="boschindego:indego:lawnmower:nextCutting" }
|
||||||
|
Number:ElectricPotential Indego_BatteryVoltage { channel="boschindego:indego:lawnmower:batteryVoltage" }
|
||||||
|
Number Indego_BatteryLevel { channel="boschindego:indego:lawnmower:batteryLevel" }
|
||||||
|
Switch Indego_LowBattery { channel="boschindego:indego:lawnmower:lowBattery" }
|
||||||
|
Number:Temperature Indego_BatteryTemperature { channel="boschindego:indego:lawnmower:batteryTemperature" }
|
||||||
|
Number:Area Indego_GardenSize { channel="boschindego:indego:lawnmower:gardenSize" }
|
||||||
|
Image Indego_GardenMap { channel="boschindego:indego:lawnmower:gardenMap" }
|
||||||
```
|
```
|
||||||
|
|
||||||
### `indego.sitemap` File
|
### `indego.sitemap` File
|
||||||
|
|
|
@ -40,6 +40,12 @@ public class BoschIndegoBindingConstants {
|
||||||
public static final String READY = "ready";
|
public static final String READY = "ready";
|
||||||
public static final String LAST_CUTTING = "lastCutting";
|
public static final String LAST_CUTTING = "lastCutting";
|
||||||
public static final String NEXT_CUTTING = "nextCutting";
|
public static final String NEXT_CUTTING = "nextCutting";
|
||||||
|
public static final String BATTERY_VOLTAGE = "batteryVoltage";
|
||||||
|
public static final String BATTERY_LEVEL = "batteryLevel";
|
||||||
|
public static final String LOW_BATTERY = "lowBattery";
|
||||||
|
public static final String BATTERY_TEMPERATURE = "batteryTemperature";
|
||||||
|
public static final String GARDEN_SIZE = "gardenSize";
|
||||||
|
public static final String GARDEN_MAP = "gardenMap";
|
||||||
|
|
||||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_INDEGO);
|
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_INDEGO);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,12 +38,15 @@ import org.openhab.binding.boschindego.internal.dto.response.AuthenticationRespo
|
||||||
import org.openhab.binding.boschindego.internal.dto.response.DeviceCalendarResponse;
|
import org.openhab.binding.boschindego.internal.dto.response.DeviceCalendarResponse;
|
||||||
import org.openhab.binding.boschindego.internal.dto.response.DeviceStateResponse;
|
import org.openhab.binding.boschindego.internal.dto.response.DeviceStateResponse;
|
||||||
import org.openhab.binding.boschindego.internal.dto.response.LocationWeatherResponse;
|
import org.openhab.binding.boschindego.internal.dto.response.LocationWeatherResponse;
|
||||||
|
import org.openhab.binding.boschindego.internal.dto.response.OperatingDataResponse;
|
||||||
import org.openhab.binding.boschindego.internal.dto.response.PredictiveLastCuttingResponse;
|
import org.openhab.binding.boschindego.internal.dto.response.PredictiveLastCuttingResponse;
|
||||||
import org.openhab.binding.boschindego.internal.dto.response.PredictiveNextCuttingResponse;
|
import org.openhab.binding.boschindego.internal.dto.response.PredictiveNextCuttingResponse;
|
||||||
import org.openhab.binding.boschindego.internal.exceptions.IndegoAuthenticationException;
|
import org.openhab.binding.boschindego.internal.exceptions.IndegoAuthenticationException;
|
||||||
import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
|
import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
|
||||||
import org.openhab.binding.boschindego.internal.exceptions.IndegoInvalidCommandException;
|
import org.openhab.binding.boschindego.internal.exceptions.IndegoInvalidCommandException;
|
||||||
import org.openhab.binding.boschindego.internal.exceptions.IndegoInvalidResponseException;
|
import org.openhab.binding.boschindego.internal.exceptions.IndegoInvalidResponseException;
|
||||||
|
import org.openhab.binding.boschindego.internal.exceptions.IndegoUnreachableException;
|
||||||
|
import org.openhab.core.library.types.RawType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -216,6 +219,9 @@ public class IndegoController {
|
||||||
// This will currently not happen because "WWW-Authenticate" header is missing; see below.
|
// This will currently not happen because "WWW-Authenticate" header is missing; see below.
|
||||||
throw new IndegoAuthenticationException("Context rejected");
|
throw new IndegoAuthenticationException("Context rejected");
|
||||||
}
|
}
|
||||||
|
if (status == HttpStatus.GATEWAY_TIMEOUT_504) {
|
||||||
|
throw new IndegoUnreachableException("Gateway timeout");
|
||||||
|
}
|
||||||
if (!HttpStatus.isSuccess(status)) {
|
if (!HttpStatus.isSuccess(status)) {
|
||||||
throw new IndegoException("The request failed with error: " + status);
|
throw new IndegoException("The request failed with error: " + status);
|
||||||
}
|
}
|
||||||
|
@ -256,6 +262,93 @@ public class IndegoController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps {@link #getRawRequest(String)} into an authenticated session.
|
||||||
|
*
|
||||||
|
* @param path the relative path to which the request should be sent
|
||||||
|
* @return the raw data from the response
|
||||||
|
* @throws IndegoAuthenticationException if request was rejected as unauthorized
|
||||||
|
* @throws IndegoException if any communication or parsing error occurred
|
||||||
|
*/
|
||||||
|
private RawType getRawRequestWithAuthentication(String path) throws IndegoAuthenticationException, IndegoException {
|
||||||
|
if (!session.isValid()) {
|
||||||
|
authenticate();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
logger.debug("Session {} valid, skipping authentication", session);
|
||||||
|
return getRawRequest(path);
|
||||||
|
} catch (IndegoAuthenticationException e) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Context rejected", e);
|
||||||
|
} else {
|
||||||
|
logger.debug("Context rejected: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
session.invalidate();
|
||||||
|
authenticate();
|
||||||
|
return getRawRequest(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a GET request to the server and returns the raw response.
|
||||||
|
*
|
||||||
|
* @param path the relative path to which the request should be sent
|
||||||
|
* @return the raw data from the response
|
||||||
|
* @throws IndegoAuthenticationException if request was rejected as unauthorized
|
||||||
|
* @throws IndegoException if any communication or parsing error occurred
|
||||||
|
*/
|
||||||
|
private RawType getRawRequest(String path) throws IndegoAuthenticationException, IndegoException {
|
||||||
|
try {
|
||||||
|
Request request = httpClient.newRequest(BASE_URL + path).method(HttpMethod.GET).header(CONTEXT_HEADER_NAME,
|
||||||
|
session.getContextId());
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("GET request for {}", BASE_URL + path);
|
||||||
|
}
|
||||||
|
ContentResponse response = sendRequest(request);
|
||||||
|
int status = response.getStatus();
|
||||||
|
if (status == HttpStatus.UNAUTHORIZED_401) {
|
||||||
|
// This will currently not happen because "WWW-Authenticate" header is missing; see below.
|
||||||
|
throw new IndegoAuthenticationException("Context rejected");
|
||||||
|
}
|
||||||
|
if (!HttpStatus.isSuccess(status)) {
|
||||||
|
throw new IndegoException("The request failed with error: " + status);
|
||||||
|
}
|
||||||
|
byte[] data = response.getContent();
|
||||||
|
if (data == null) {
|
||||||
|
throw new IndegoInvalidResponseException("No data returned");
|
||||||
|
}
|
||||||
|
String contentType = response.getMediaType();
|
||||||
|
if (contentType == null || contentType.isEmpty()) {
|
||||||
|
throw new IndegoInvalidResponseException("No content-type returned");
|
||||||
|
}
|
||||||
|
logger.debug("Media download response: type {}, length {}", contentType, data.length);
|
||||||
|
|
||||||
|
return new RawType(data, contentType);
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
throw new IndegoInvalidResponseException("Error parsing response", e);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new IndegoException(e);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
throw new IndegoException(e);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Throwable cause = e.getCause();
|
||||||
|
if (cause != null && cause instanceof HttpResponseException) {
|
||||||
|
Response response = ((HttpResponseException) cause).getResponse();
|
||||||
|
if (response.getStatus() == HttpStatus.UNAUTHORIZED_401) {
|
||||||
|
/*
|
||||||
|
* When contextId is not valid, the service will respond with HTTP code 401 without
|
||||||
|
* any "WWW-Authenticate" header, violating RFC 7235. Jetty will then throw
|
||||||
|
* HttpResponseException. We need to handle this in order to attempt
|
||||||
|
* reauthentication.
|
||||||
|
*/
|
||||||
|
throw new IndegoAuthenticationException("Context rejected", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IndegoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps {@link #putRequest(String, Object)} into an authenticated session.
|
* Wraps {@link #putRequest(String, Object)} into an authenticated session.
|
||||||
*
|
*
|
||||||
|
@ -381,6 +474,30 @@ public class IndegoController {
|
||||||
DeviceStateResponse.class);
|
DeviceStateResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries the device operating data from the server.
|
||||||
|
* Server will request this directly from the device, so operation might be slow.
|
||||||
|
*
|
||||||
|
* @return the device state
|
||||||
|
* @throws IndegoAuthenticationException if request was rejected as unauthorized
|
||||||
|
* @throws IndegoException if any communication or parsing error occurred
|
||||||
|
*/
|
||||||
|
public OperatingDataResponse getOperatingData() throws IndegoAuthenticationException, IndegoException {
|
||||||
|
return getRequestWithAuthentication(SERIAL_NUMBER_SUBPATH + this.getSerialNumber() + "/operatingData",
|
||||||
|
OperatingDataResponse.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries the map generated by the device from the server.
|
||||||
|
*
|
||||||
|
* @return the garden map
|
||||||
|
* @throws IndegoAuthenticationException if request was rejected as unauthorized
|
||||||
|
* @throws IndegoException if any communication or parsing error occurred
|
||||||
|
*/
|
||||||
|
public RawType getMap() throws IndegoAuthenticationException, IndegoException {
|
||||||
|
return getRawRequestWithAuthentication(SERIAL_NUMBER_SUBPATH + this.getSerialNumber() + "/map");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queries the calendar.
|
* Queries the calendar.
|
||||||
*
|
*
|
||||||
|
|
|
@ -25,5 +25,5 @@ public class BoschIndegoConfiguration {
|
||||||
public @Nullable String username;
|
public @Nullable String username;
|
||||||
public @Nullable String password;
|
public @Nullable String password;
|
||||||
public long refresh = 180;
|
public long refresh = 180;
|
||||||
public long cuttingTimeRefresh = 60;
|
public long cuttingTimeMapRefresh = 60;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.boschindego.internal.dto;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Battery data.
|
||||||
|
*
|
||||||
|
* @author Jacob Laursen - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Battery {
|
||||||
|
public double voltage;
|
||||||
|
|
||||||
|
public int cycles;
|
||||||
|
|
||||||
|
public double discharge;
|
||||||
|
|
||||||
|
@SerializedName("ambient_temp")
|
||||||
|
public int ambientTemperature;
|
||||||
|
|
||||||
|
@SerializedName("battery_temp")
|
||||||
|
public int batteryTemperature;
|
||||||
|
|
||||||
|
public int percent;
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.boschindego.internal.dto;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Garden data.
|
||||||
|
*
|
||||||
|
* @author Jacob Laursen - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Garden {
|
||||||
|
public long id;
|
||||||
|
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
@SerializedName("signal_id")
|
||||||
|
public byte signalId;
|
||||||
|
|
||||||
|
public int size;
|
||||||
|
|
||||||
|
@SerializedName("inner_bounds")
|
||||||
|
public int innerBounds;
|
||||||
|
|
||||||
|
public int cuts;
|
||||||
|
|
||||||
|
public int runtime;
|
||||||
|
|
||||||
|
public int charge;
|
||||||
|
|
||||||
|
public int bumps;
|
||||||
|
|
||||||
|
public int stops;
|
||||||
|
|
||||||
|
@SerializedName("last_mow")
|
||||||
|
public int lastMow;
|
||||||
|
|
||||||
|
@SerializedName("map_cell_size")
|
||||||
|
public int mapCellSize;
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.boschindego.internal.dto.response;
|
||||||
|
|
||||||
|
import org.openhab.binding.boschindego.internal.dto.Battery;
|
||||||
|
import org.openhab.binding.boschindego.internal.dto.Garden;
|
||||||
|
import org.openhab.binding.boschindego.internal.dto.response.runtime.DeviceStateRuntimes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response for operating data.
|
||||||
|
*
|
||||||
|
* @author Jacob Laursen - Initial contribution
|
||||||
|
*/
|
||||||
|
public class OperatingDataResponse {
|
||||||
|
public DeviceStateRuntimes runtime;
|
||||||
|
|
||||||
|
public Battery battery;
|
||||||
|
|
||||||
|
public Garden garden;
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.boschindego.internal.exceptions;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link IndegoUnreachableException} is thrown on gateway timeout, which
|
||||||
|
* means that Bosch services cannot connect to the device.
|
||||||
|
*
|
||||||
|
* @author Jacob Laursen - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class IndegoUnreachableException extends IndegoException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -7952585411438042139L;
|
||||||
|
|
||||||
|
public IndegoUnreachableException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IndegoUnreachableException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,13 +28,18 @@ import org.openhab.binding.boschindego.internal.IndegoController;
|
||||||
import org.openhab.binding.boschindego.internal.config.BoschIndegoConfiguration;
|
import org.openhab.binding.boschindego.internal.config.BoschIndegoConfiguration;
|
||||||
import org.openhab.binding.boschindego.internal.dto.DeviceCommand;
|
import org.openhab.binding.boschindego.internal.dto.DeviceCommand;
|
||||||
import org.openhab.binding.boschindego.internal.dto.response.DeviceStateResponse;
|
import org.openhab.binding.boschindego.internal.dto.response.DeviceStateResponse;
|
||||||
|
import org.openhab.binding.boschindego.internal.dto.response.OperatingDataResponse;
|
||||||
import org.openhab.binding.boschindego.internal.exceptions.IndegoAuthenticationException;
|
import org.openhab.binding.boschindego.internal.exceptions.IndegoAuthenticationException;
|
||||||
import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
|
import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
|
||||||
import org.openhab.core.i18n.TimeZoneProvider;
|
import org.openhab.core.i18n.TimeZoneProvider;
|
||||||
import org.openhab.core.library.types.DateTimeType;
|
import org.openhab.core.library.types.DateTimeType;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
import org.openhab.core.library.types.PercentType;
|
import org.openhab.core.library.types.PercentType;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
import org.openhab.core.library.types.StringType;
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.library.unit.SIUnits;
|
||||||
|
import org.openhab.core.library.unit.Units;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.ThingStatus;
|
import org.openhab.core.thing.ThingStatus;
|
||||||
|
@ -63,7 +68,7 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||||
|
|
||||||
private @NonNullByDefault({}) IndegoController controller;
|
private @NonNullByDefault({}) IndegoController controller;
|
||||||
private @Nullable ScheduledFuture<?> statePollFuture;
|
private @Nullable ScheduledFuture<?> statePollFuture;
|
||||||
private @Nullable ScheduledFuture<?> cuttingTimePollFuture;
|
private @Nullable ScheduledFuture<?> cuttingTimeMapPollFuture;
|
||||||
private boolean propertiesInitialized;
|
private boolean propertiesInitialized;
|
||||||
private int previousStateCode;
|
private int previousStateCode;
|
||||||
|
|
||||||
|
@ -96,10 +101,11 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||||
controller = new IndegoController(httpClient, username, password);
|
controller = new IndegoController(httpClient, username, password);
|
||||||
|
|
||||||
updateStatus(ThingStatus.UNKNOWN);
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
this.statePollFuture = scheduler.scheduleWithFixedDelay(this::refreshStateWithExceptionHandling, 0,
|
this.statePollFuture = scheduler.scheduleWithFixedDelay(this::refreshStateAndOperatingDataWithExceptionHandling,
|
||||||
config.refresh, TimeUnit.SECONDS);
|
0, config.refresh, TimeUnit.SECONDS);
|
||||||
this.cuttingTimePollFuture = scheduler.scheduleWithFixedDelay(this::refreshCuttingTimesWithExceptionHandling, 0,
|
this.cuttingTimeMapPollFuture = scheduler.scheduleWithFixedDelay(
|
||||||
config.cuttingTimeRefresh, TimeUnit.MINUTES);
|
this::refreshCuttingTimesAndMapWithExceptionHandling, 0, config.cuttingTimeMapRefresh,
|
||||||
|
TimeUnit.MINUTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -110,21 +116,21 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||||
pollFuture.cancel(true);
|
pollFuture.cancel(true);
|
||||||
}
|
}
|
||||||
this.statePollFuture = null;
|
this.statePollFuture = null;
|
||||||
pollFuture = this.cuttingTimePollFuture;
|
pollFuture = this.cuttingTimeMapPollFuture;
|
||||||
if (pollFuture != null) {
|
if (pollFuture != null) {
|
||||||
pollFuture.cancel(true);
|
pollFuture.cancel(true);
|
||||||
}
|
}
|
||||||
this.cuttingTimePollFuture = null;
|
this.cuttingTimeMapPollFuture = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
logger.debug("handleCommand {} for channel {}", command, channelUID);
|
||||||
try {
|
try {
|
||||||
if (command == RefreshType.REFRESH) {
|
if (command == RefreshType.REFRESH) {
|
||||||
handleRefreshCommand(channelUID.getId());
|
handleRefreshCommand(channelUID.getId());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command instanceof DecimalType && channelUID.getId().equals(STATE)) {
|
if (command instanceof DecimalType && channelUID.getId().equals(STATE)) {
|
||||||
sendCommand(((DecimalType) command).intValue());
|
sendCommand(((DecimalType) command).intValue());
|
||||||
}
|
}
|
||||||
|
@ -144,11 +150,21 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||||
case ERRORCODE:
|
case ERRORCODE:
|
||||||
case STATECODE:
|
case STATECODE:
|
||||||
case READY:
|
case READY:
|
||||||
this.refreshState();
|
refreshState();
|
||||||
break;
|
break;
|
||||||
case LAST_CUTTING:
|
case LAST_CUTTING:
|
||||||
case NEXT_CUTTING:
|
case NEXT_CUTTING:
|
||||||
this.refreshCuttingTimes();
|
refreshCuttingTimes();
|
||||||
|
break;
|
||||||
|
case BATTERY_LEVEL:
|
||||||
|
case LOW_BATTERY:
|
||||||
|
case BATTERY_VOLTAGE:
|
||||||
|
case BATTERY_TEMPERATURE:
|
||||||
|
case GARDEN_SIZE:
|
||||||
|
refreshOperatingData();
|
||||||
|
break;
|
||||||
|
case GARDEN_MAP:
|
||||||
|
refreshMap();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,14 +194,13 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||||
logger.debug("Sending command {}", command);
|
logger.debug("Sending command {}", command);
|
||||||
updateState(TEXTUAL_STATE, UnDefType.UNDEF);
|
updateState(TEXTUAL_STATE, UnDefType.UNDEF);
|
||||||
controller.sendCommand(command);
|
controller.sendCommand(command);
|
||||||
state = controller.getState();
|
refreshState();
|
||||||
updateStatus(ThingStatus.ONLINE);
|
|
||||||
updateState(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshStateWithExceptionHandling() {
|
private void refreshStateAndOperatingDataWithExceptionHandling() {
|
||||||
try {
|
try {
|
||||||
refreshState();
|
refreshState();
|
||||||
|
refreshOperatingData();
|
||||||
} catch (IndegoAuthenticationException e) {
|
} catch (IndegoAuthenticationException e) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
"@text/offline.comm-error.authentication-failure");
|
"@text/offline.comm-error.authentication-failure");
|
||||||
|
@ -201,7 +216,6 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceStateResponse state = controller.getState();
|
DeviceStateResponse state = controller.getState();
|
||||||
updateStatus(ThingStatus.ONLINE);
|
|
||||||
updateState(state);
|
updateState(state);
|
||||||
|
|
||||||
// When state code changed, refresh cutting times immediately.
|
// When state code changed, refresh cutting times immediately.
|
||||||
|
@ -211,15 +225,9 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshCuttingTimesWithExceptionHandling() {
|
private void refreshOperatingData() throws IndegoAuthenticationException, IndegoException {
|
||||||
try {
|
updateOperatingData(controller.getOperatingData());
|
||||||
refreshCuttingTimes();
|
updateStatus(ThingStatus.ONLINE);
|
||||||
} catch (IndegoAuthenticationException e) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
|
||||||
"@text/offline.comm-error.authentication-failure");
|
|
||||||
} catch (IndegoException e) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshCuttingTimes() throws IndegoAuthenticationException, IndegoException {
|
private void refreshCuttingTimes() throws IndegoAuthenticationException, IndegoException {
|
||||||
|
@ -244,6 +252,24 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void refreshCuttingTimesAndMapWithExceptionHandling() {
|
||||||
|
try {
|
||||||
|
refreshCuttingTimes();
|
||||||
|
refreshMap();
|
||||||
|
} catch (IndegoAuthenticationException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.comm-error.authentication-failure");
|
||||||
|
} catch (IndegoException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshMap() throws IndegoAuthenticationException, IndegoException {
|
||||||
|
if (isLinked(GARDEN_MAP)) {
|
||||||
|
updateState(GARDEN_MAP, controller.getMap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateState(DeviceStateResponse state) {
|
private void updateState(DeviceStateResponse state) {
|
||||||
DeviceStatus deviceStatus = DeviceStatus.fromCode(state.state);
|
DeviceStatus deviceStatus = DeviceStatus.fromCode(state.state);
|
||||||
int status = getStatusFromCommand(deviceStatus.getAssociatedCommand());
|
int status = getStatusFromCommand(deviceStatus.getAssociatedCommand());
|
||||||
|
@ -260,6 +286,14 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||||
updateState(TEXTUAL_STATE, new StringType(deviceStatus.getMessage(translationProvider)));
|
updateState(TEXTUAL_STATE, new StringType(deviceStatus.getMessage(translationProvider)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateOperatingData(OperatingDataResponse operatingData) {
|
||||||
|
updateState(BATTERY_VOLTAGE, new QuantityType<>(operatingData.battery.voltage, Units.VOLT));
|
||||||
|
updateState(BATTERY_LEVEL, new DecimalType(operatingData.battery.percent));
|
||||||
|
updateState(LOW_BATTERY, OnOffType.from(operatingData.battery.percent < 20));
|
||||||
|
updateState(BATTERY_TEMPERATURE, new QuantityType<>(operatingData.battery.batteryTemperature, SIUnits.CELSIUS));
|
||||||
|
updateState(GARDEN_SIZE, new QuantityType<>(operatingData.garden.size, SIUnits.SQUARE_METRE));
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isReadyToMow(DeviceStatus deviceStatus, int error) {
|
private boolean isReadyToMow(DeviceStatus deviceStatus, int error) {
|
||||||
return deviceStatus.isReadyToMow() && error == 0;
|
return deviceStatus.isReadyToMow() && error == 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ thing-type.boschindego.indego.description = Indego which supports the connect fe
|
||||||
|
|
||||||
# thing types config
|
# thing types config
|
||||||
|
|
||||||
thing-type.config.boschindego.indego.cuttingTimeRefresh.label = Cutting Time Refresh Interval
|
thing-type.config.boschindego.indego.cuttingTimeMapRefresh.label = Cutting Time/Map Refresh Interval
|
||||||
thing-type.config.boschindego.indego.cuttingTimeRefresh.description = The number of minutes between refreshing last/next cutting time.
|
thing-type.config.boschindego.indego.cuttingTimeMapRefresh.description = The number of minutes between refreshing last/next cutting time and map.
|
||||||
thing-type.config.boschindego.indego.password.label = Password
|
thing-type.config.boschindego.indego.password.label = Password
|
||||||
thing-type.config.boschindego.indego.password.description = Password for the Bosch Indego account.
|
thing-type.config.boschindego.indego.password.description = Password for the Bosch Indego account.
|
||||||
thing-type.config.boschindego.indego.refresh.label = Refresh Interval
|
thing-type.config.boschindego.indego.refresh.label = Refresh Interval
|
||||||
|
@ -21,8 +21,16 @@ thing-type.config.boschindego.indego.username.description = Username for the Bos
|
||||||
|
|
||||||
# channel types
|
# channel types
|
||||||
|
|
||||||
|
channel-type.boschindego.batteryTemperature.label = Battery Temperature
|
||||||
|
channel-type.boschindego.batteryTemperature.description = Battery temperature reported by the device
|
||||||
|
channel-type.boschindego.batteryVoltage.label = Battery Voltage
|
||||||
|
channel-type.boschindego.batteryVoltage.description = Battery voltage reported by the device
|
||||||
channel-type.boschindego.errorcode.label = Error Code
|
channel-type.boschindego.errorcode.label = Error Code
|
||||||
channel-type.boschindego.errorcode.description = 0 = no error
|
channel-type.boschindego.errorcode.description = 0 = no error
|
||||||
|
channel-type.boschindego.gardenMap.label = Garden Map
|
||||||
|
channel-type.boschindego.gardenMap.description = Garden map mapped by the device
|
||||||
|
channel-type.boschindego.gardenSize.label = Garden Size
|
||||||
|
channel-type.boschindego.gardenSize.description = Garden size mapped by the device
|
||||||
channel-type.boschindego.lastCutting.label = Last Cutting
|
channel-type.boschindego.lastCutting.label = Last Cutting
|
||||||
channel-type.boschindego.lastCutting.description = Last cutting time
|
channel-type.boschindego.lastCutting.description = Last cutting time
|
||||||
channel-type.boschindego.mowed.label = Cut Grass
|
channel-type.boschindego.mowed.label = Cut Grass
|
||||||
|
@ -44,6 +52,7 @@ channel-type.boschindego.textualstate.label = Textual State
|
||||||
# thing status descriptions
|
# thing status descriptions
|
||||||
|
|
||||||
offline.comm-error.authentication-failure = The login credentials are wrong or another client is connected to your Indego account
|
offline.comm-error.authentication-failure = The login credentials are wrong or another client is connected to your Indego account
|
||||||
|
offline.comm-error.unreachable = Device is unreachable
|
||||||
offline.conf-error.missing-password = Password missing
|
offline.conf-error.missing-password = Password missing
|
||||||
offline.conf-error.missing-username = Username missing
|
offline.conf-error.missing-username = Username missing
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,12 @@
|
||||||
<channel id="ready" typeId="ready"/>
|
<channel id="ready" typeId="ready"/>
|
||||||
<channel id="lastCutting" typeId="lastCutting"/>
|
<channel id="lastCutting" typeId="lastCutting"/>
|
||||||
<channel id="nextCutting" typeId="nextCutting"/>
|
<channel id="nextCutting" typeId="nextCutting"/>
|
||||||
|
<channel id="batteryVoltage" typeId="batteryVoltage"/>
|
||||||
|
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||||
|
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||||
|
<channel id="batteryTemperature" typeId="batteryTemperature"/>
|
||||||
|
<channel id="gardenSize" typeId="gardenSize"/>
|
||||||
|
<channel id="gardenMap" typeId="gardenMap"/>
|
||||||
</channels>
|
</channels>
|
||||||
<config-description>
|
<config-description>
|
||||||
<parameter name="username" type="text" required="true">
|
<parameter name="username" type="text" required="true">
|
||||||
|
@ -32,9 +38,9 @@
|
||||||
<description>The number of seconds between refreshing device state.</description>
|
<description>The number of seconds between refreshing device state.</description>
|
||||||
<default>180</default>
|
<default>180</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="cuttingTimeRefresh" type="integer" min="1">
|
<parameter name="cuttingTimeMapRefresh" type="integer" min="1">
|
||||||
<label>Cutting Time Refresh Interval</label>
|
<label>Cutting Time/Map Refresh Interval</label>
|
||||||
<description>The number of minutes between refreshing last/next cutting time.</description>
|
<description>The number of minutes between refreshing last/next cutting time and map.</description>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
<default>60</default>
|
<default>60</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
@ -132,5 +138,34 @@
|
||||||
<category>Time</category>
|
<category>Time</category>
|
||||||
<state readOnly="true"/>
|
<state readOnly="true"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
<channel-type id="batteryVoltage" advanced="true">
|
||||||
|
<item-type>Number:ElectricPotential</item-type>
|
||||||
|
<label>Battery Voltage</label>
|
||||||
|
<description>Battery voltage reported by the device</description>
|
||||||
|
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="batteryTemperature" advanced="true">
|
||||||
|
<item-type>Number:Temperature</item-type>
|
||||||
|
<label>Battery Temperature</label>
|
||||||
|
<description>Battery temperature reported by the device</description>
|
||||||
|
<category>Temperature</category>
|
||||||
|
<tags>
|
||||||
|
<tag>Measurement</tag>
|
||||||
|
<tag>Temperature</tag>
|
||||||
|
</tags>
|
||||||
|
<state pattern="%d %unit%" readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="gardenSize">
|
||||||
|
<item-type>Number:Area</item-type>
|
||||||
|
<label>Garden Size</label>
|
||||||
|
<description>Garden size mapped by the device</description>
|
||||||
|
<state pattern="%d %unit%" readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="gardenMap">
|
||||||
|
<item-type>Image</item-type>
|
||||||
|
<label>Garden Map</label>
|
||||||
|
<description>Garden map mapped by the device</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
|
Loading…
Reference in New Issue