[gardena] Rewriting the Gardena binding (#8869)

* Rewriting the Gardena binding

Signed-off-by: Gerhard Riegler <gerhard.riegler@gmail.com>
This commit is contained in:
Gerhard Riegler 2020-12-12 20:16:23 +01:00 committed by GitHub
parent 2bcb548381
commit 442a7cd31e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 3020 additions and 4274 deletions

View File

@ -1,23 +1,21 @@
# Gardena Binding
This is the binding for [Gardena smart system](https://www.gardena.com/de/rasenpflege/smartsystem/).
This is the binding for [Gardena smart system](https://www.gardena.com/smart).
This binding allows you to integrate, view and control Gardena smart system devices in the openHAB environment.
## Supported Things
Devices connected to Gardena smart system, currently:
| Thing type | Name |
|--------------------------|--------------------------|
| bridge | smart Garden Gateway |
| mower | smart Sileno(+) Mower |
| watering_computer | smart Water Control |
| sensor | smart Sensor |
| electronic_pressure_pump | smart Pressure Pump |
| power | smart Power |
| ic24 | smart Irrigation Control |
The schedules are not yet integrated!
| Thing type | Name |
|--------------------------|----------------------------------------------------|
| bridge | smart Gateway |
| mower | smart SILENO(+), SILENO city, SILENO life Mower |
| sensor | smart Sensor |
| pump | smart Pressure Pump |
| power | smart Power Adapter |
| water_control | smart Water Control |
| irrigation_control | smart Irrigation Control |
## Discovery
@ -27,109 +25,94 @@ An account must be specified, all things for an account are discovered automatic
There are several settings for an account:
| Name | Required | Description |
|-----------------------|----------|------------------------------------------------------------------------------------------|
| **email** | yes | The email address for logging into the Gardena smart system |
| **password** | yes | The password for logging into the Gardena smart system |
| **sessionTimeout** | no | The timeout in minutes for a session to Gardena smart system (default = 30) |
| **connectionTimeout** | no | The timeout in seconds for connections to Gardena smart system (default = 10) |
| **refresh** | no | The interval in seconds for refreshing the data from Gardena smart system (default = 60) |
| Name | Required | Description |
|-----------------------|----------|-----------------------------------------------------------------------------------------------------|
| **email** | yes | The email address for logging into the Gardena smart system |
| **password** | yes | The password for logging into the Gardena smart system |
| **apiKey** | yes | The Gardena smart system integration API key |
| **connectionTimeout** | no | The timeout in seconds for connections to Gardena smart system integration API (default = 10) |
**Example**
### Obtaining your API Key
1. Goto https://developer.husqvarnagroup.cloud/, sign in using your GARDENA smart system account and accept the terms of use
2. Create and save a new application via the 'Create application' button
3. Connect both _Authentication API_ and _GARDENA smart system API_ to your application via the 'Connect new API' button
4. Copy the application key to use with this binding as _apiKey_
## Examples
### Things
Minimal Thing configuration:
```java
Bridge gardena:account:home [ email="...", password="..." ]
```
Configuration with refresh:
```java
Bridge gardena:account:home [ email="...", password="...", refresh=30 ]
Bridge gardena:account:home [ email="...", password="...", apiKey="..." ]
```
Configuration of multiple bridges:
```java
Bridge gardena:account:home1 [ email="...", password="..." ]
Bridge gardena:account:home2 [ email="...", password="..." ]
Bridge gardena:account:home1 [ email="...", password="...", apiKey="..." ]
Bridge gardena:account:home2 [ email="...", password="...", apiKey="..." ]
```
Once a connection to an account is established, connected Things are discovered automatically.
Alternatively, you can manually configure a Thing:
Alternatively, you can manually configure Things:
```perl
Bridge gardena:account:home [ email="...", password="..." ]
```java
Bridge gardena:account:home [ email="...", password="...", apiKey="..." ]
{
Thing mower myMower [ deviceId="c81ad682-6e45-42ce-bed1-6b4eff5620c8" ]
Thing water_control myWaterControl [ deviceId="c81ad682-6e45-42ce-bed1-6b4eff5620c8" ]
Thing sensor mySensor [ deviceId="c81ad682-6e45-42ce-bed1-6b4eff5620c8" ]
Thing pump myEPP [ deviceId="c81ad682-6e45-42ce-bed1-6b4eff5620c8" ]
Thing power myPowerSocket [ deviceId="c81ad682-6e45-42ce-bed1-6b4eff5620c8" ]
Thing irrigation_control myIrrigationControl [ deviceId="c81ad682-6e45-42ce-bed1-6b4eff5620c8" ]
}
```
## Items
### Items
In the items file, you can link items to channels of your Things:
```java
Number Battery_Level "Battery [%d %%]" {channel="gardena:mower:home:myMower:battery#level"}
Number Mower_Battery_Level "Battery [%d %%]" {channel="gardena:mower:home:myMower:common#batteryLevel"}
```
## Sensor refresh
### Sensor refresh
You can send a REFRESH command to items linked to these Sensor channels:
Sensor refresh commands are not yet supported by the Gardena smart system integration API.
- ambient_temperature#temperature
- soil_temperature#temperature
- humidity#humidity
- light#light
In the console:
```shell
smarthome:send ITEM_NAME REFRESH
```
In scripts:
```
import org.openhab.core.types.RefreshType
...
sendCommand(ITEM_NAME, RefreshType.REFRESH)
```
## Examples
### Example configuration
```
// smart Water Control
Switch Watering_Valve "Valve" { channel="gardena:watering_computer:home:myValve:outlet#valve_open" }
Number Watering_Duration "Duration [%d min]" { channel="gardena:watering_computer:home:myValve:outlet#button_manual_override_time" }
String WC_Valve_Activity "Valve Activity" { channel="gardena:water_control:home:myWateringComputer:valve#activity" }
Number WC_Valve_Duration "Last Watering Duration [%d min]" { channel="gardena:water_control:home:myWateringComputer:valve#duration" }
// smart Power Plug
String Power_Timer "Power Timer [%s]" { channel="gardena:power:home:myPowerplug:power#power_timer" }
Number WC_Valve_cmd_Duration "Command Duration [%d min]" { channel="gardena:water_control:home:myWateringComputer:valve_commands#commandDuration" }
Switch WC_Valve_cmd_OpenWithDuration "Watering Timer [%d min]" { channel="gardena:water_control:home:myWateringComputer:valve_commands#start_seconds_to_override" }
Switch WC_Valve_cmd_CloseValve "Stop Switch" { channel="gardena:water_control:home:myWateringComputer:valve_commands#stop_until_next_task" }
// smart Irrigation Control
Number Watering_Timer_1 "Watering Timer 1 [%d min] { channel="gardena:ic24:home:myIrrigationController:watering#watering_timer_1" }
// smart Pressure Pump
Number Pump_Timer "Pump Timer [%d min] { channel="gardena:electronic_pressure_pump:home:myPressurePump:manual_watering#manual_watering_timer" }
smarthome:status WC_Valve_Duration // returns the duration of the last watering request if still active, or 0
smarthome:status WC_Valve_Activity // returns the current valve activity (CLOSED|MANUAL_WATERING|SCHEDULED_WATERING)
```
All channels are read-only, except the command group and the lastUpdate timestamp
```
smarthome:send WC_Valve_cmd_Duration.sendCommand(10) // set the duration for the command to 10min
smarthome:send WC_Valve_cmd_OpenWithDuration.sendCommand(ON) // start watering
smarthome:send WC_Valve_cmd_CloseValve.sendCommand(ON) // stop any active watering
```
Watering_Duration.sendCommand(30) // 30 minutes
Watering_Valve.sendCommand(ON)
Power_Timer.sendCommand("on")
Power_Timer.sendCommand("off")
Power_Timer.sendCommand("180") // on for 180 seconds
If you send a REFRESH command to the last update timestamp (no matter which thing), **ALL** items from **ALL** things are updated
```
DateTime LastUpdate "LastUpdate [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel="gardena:water_control:home:myWateringComputer:common#lastUpdate_timestamp" }
Watering_Timer_1.sendCommand(0) // turn off watering
Watering_Timer_1.sendCommand(30) // turn on for 30 minutes
Pump_Timer.sendCommand(0) // turn the pump off
Pump_Timer.sendCommand(30) // turn the pump on for 30 minutes
// refresh ALL items
smarthome:send LastUpdate REFRESH
```
### Debugging and Tracing
@ -146,12 +129,21 @@ Set the logging back to normal
log:set INFO org.openhab.binding.gardena
```
**Note:** The Online/Offline status is not always valid. I'm using the ```connection_status``` property Gardena sends for each device, but it seems not to be very reliable.
My watering control for example shows offline, but it is still working.
I have to press the button on the device, then the status changed to online.
My mower always shows online, regardless of whether it is switched on or off.
This is not a binding issue, it must be fixed by Gardena.
When the binding sends a command to a device, it communicates only with the Gardena online service.
It has not control over, whether the command is sent from the online service via your gateway to the device.
**Notes and known limitations:**
When the binding sends a command to a device, it communicates only with the Gardena smart system integration API.
It has no control over whether the command is sent from the online service via your gateway to the device.
It is the same as if you send the command in the Gardena App.
Schedules, sensor-refresh commands, irrigation control master valve configuration etc. are not supported.
This binding relies on the GARDENA smart system integration API.
Further API documentation: https://developer.1689.cloud/apis/GARDENA+smart+system+API
###Troubleshooting
Occasionally it can happen that the API key is no longer valid.
```
HTTP protocol violation: Authentication challenge without WWW-Authenticate header
```
If this error message appears in the log file, simply renew or delete/create a new API key as described in 'Obtaining your API Key' and restart openHAB.

View File

@ -16,32 +16,25 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link GardenaBinding} class defines common constants, which are used across the whole binding.
* The {@link GardenaBindingConstants} class defines common constants, which are used across the whole binding.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class GardenaBindingConstants {
public static final String BINDING_ID = "gardena";
public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
public static final String PROPERTY_MANUFACTURER = "manufacturer";
public static final String PROPERTY_PRODUCT = "product";
public static final String PROPERTY_SERIALNUMBER = "serial_number";
public static final String PROPERTY_SGTIN = "sgtin";
public static final String PROPERTY_VERSION = "version";
public static final String PROPERTY_CATEGORY = "category";
public static final String PROPERTY_SERIALNUMBER = "serial";
public static final String PROPERTY_MODELTYPE = "modelType";
public static final String PROPERTY_CONNECTION_STATUS = "connection_status";
public static final String PROPERTY_CONNECTION_STATUS_UNREACH_VALUE = "status_device_unreachable";
public static final String PROPERTY_STATE = "state";
public static final String CONNECTION_STATUS_ONLINE = "ONLINE";
public static final String ABILITY_DEVICE_INFO = "device_info";
public static final String ABILITY_RADIO = "radio";
public static final String SETTING_LEAKAGE_DETECTION = "leakage_detection";
public static final String SETTING_OPERATION_MODE = "operating_mode";
public static final String SETTING_TURN_ON_PRESSURE = "turn_on_pressure";
public static final String DEVICE_TYPE_MOWER = "mower";
public static final String DEVICE_TYPE_PUMP = "pump";
public static final String DEVICE_TYPE_IRRIGATION_CONTROL = "irrigation_control";
public static final String DEVICE_TYPE_WATER_CONTROL = "water_control";
public static final String DEVICE_TYPE_SENSOR = "sensor";
public static final String DEVICE_TYPE_POWER = "power";
}

View File

@ -12,60 +12,50 @@
*/
package org.openhab.binding.gardena.internal;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.Collection;
import org.openhab.binding.gardena.internal.config.GardenaConfig;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.gardena.internal.exception.GardenaDeviceNotFoundException;
import org.openhab.binding.gardena.internal.exception.GardenaException;
import org.openhab.binding.gardena.internal.model.Device;
import org.openhab.binding.gardena.internal.model.Location;
import org.openhab.binding.gardena.internal.model.Setting;
import org.openhab.binding.gardena.internal.model.dto.Device;
import org.openhab.binding.gardena.internal.model.dto.api.DataItem;
import org.openhab.binding.gardena.internal.model.dto.command.GardenaCommand;
/**
* Describes the methods required for the communication with Gardens Smart Home.
* Describes the methods required for the communication with Gardena smart system.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public interface GardenaSmart {
/**
* Initializes Gardena Smart Home and loads all devices from all locations.
*/
public void init(String id, GardenaConfig config, GardenaSmartEventListener eventListener,
ScheduledExecutorService scheduler) throws GardenaException;
/**
* Disposes Gardena Smart Home.
* Disposes Gardena smart system.
*/
public void dispose();
/**
* Loads all devices from all locations.
* Returns all devices from all locations.
*/
public void loadAllDevices() throws GardenaException;
/**
* Returns all locations.
*/
public Set<Location> getLocations();
public Collection<Device> getAllDevices();
/**
* Returns a device with the given id.
*/
public Device getDevice(String deviceId) throws GardenaException;
public Device getDevice(String deviceId) throws GardenaDeviceNotFoundException;
/**
* Sends a command to Gardena Smart Home.
* Sends a command to Gardena smart system.
*/
public void sendCommand(Device device, GardenaSmartCommandName commandName, Object value) throws GardenaException;
/**
* Sends a setting to Gardena Smart Home.
*/
public void sendSetting(Setting setting, Object value) throws GardenaException;
public void sendCommand(DataItem<?> dataItem, GardenaCommand gardenaCommand) throws GardenaException;
/**
* Returns the id.
*/
public String getId();
/**
* Restarts all WebSocket.
*/
public void restartWebsockets();
}

View File

@ -1,51 +0,0 @@
/**
* 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.gardena.internal;
/**
* A names of all GardenaSmart commands.
*
* @author Gerhard Riegler - Initial contribution
*/
public enum GardenaSmartCommandName {
// mower
PARK_UNTIL_FURTHER_NOTICE,
PARK_UNTIL_NEXT_TIMER,
START_OVERRIDE_TIMER,
START_RESUME_SCHEDULE,
DURATION_PROPERTY,
// sensor
MEASURE_AMBIENT_TEMPERATURE,
MEASURE_LIGHT,
MEASURE_SOIL_HUMIDITY,
MEASURE_SOIL_TEMPERATURE,
// outlet
OUTLET_MANUAL_OVERRIDE_TIME,
OUTLET_VALVE,
// power
POWER_TIMER,
// irrigation control
WATERING_TIMER_VALVE_1,
WATERING_TIMER_VALVE_2,
WATERING_TIMER_VALVE_3,
WATERING_TIMER_VALVE_4,
WATERING_TIMER_VALVE_5,
WATERING_TIMER_VALVE_6,
// pump
PUMP_MANUAL_WATERING_TIMER
}

View File

@ -12,13 +12,15 @@
*/
package org.openhab.binding.gardena.internal;
import org.openhab.binding.gardena.internal.model.Device;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.gardena.internal.model.dto.Device;
/**
* Listener with methods called from events within the {@link GardenaSmart} class.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public interface GardenaSmartEventListener {
/**
@ -32,17 +34,7 @@ public interface GardenaSmartEventListener {
public void onNewDevice(Device device);
/**
* Called when a device has been deleted.
* Called when an unrecoverable error occurs.
*/
public void onDeviceDeleted(Device device);
/**
* Called when the connection is lost to Gardena Smart Home.
*/
public void onConnectionLost();
/**
* Called when the connection is resumed to Gardena Smart Home.
*/
public void onConnectionResumed();
public void onError();
}

View File

@ -12,179 +12,375 @@
*/
package org.openhab.binding.gardena.internal;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.*;
import java.util.concurrent.*;
import org.apache.commons.lang.ObjectUtils;
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.jetty.client.HttpResponseException;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.AbstractTypedContentProvider;
import org.eclipse.jetty.client.util.FormContentProvider;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.Fields;
import org.openhab.binding.gardena.internal.config.GardenaConfig;
import org.openhab.binding.gardena.internal.config.GardenaConfigWrapper;
import org.openhab.binding.gardena.internal.exception.GardenaDeviceNotFoundException;
import org.openhab.binding.gardena.internal.exception.GardenaException;
import org.openhab.binding.gardena.internal.exception.GardenaUnauthorizedException;
import org.openhab.binding.gardena.internal.model.Ability;
import org.openhab.binding.gardena.internal.model.Device;
import org.openhab.binding.gardena.internal.model.Devices;
import org.openhab.binding.gardena.internal.model.Errors;
import org.openhab.binding.gardena.internal.model.Location;
import org.openhab.binding.gardena.internal.model.Locations;
import org.openhab.binding.gardena.internal.model.NoResult;
import org.openhab.binding.gardena.internal.model.Property;
import org.openhab.binding.gardena.internal.model.PropertyValue;
import org.openhab.binding.gardena.internal.model.Session;
import org.openhab.binding.gardena.internal.model.SessionWrapper;
import org.openhab.binding.gardena.internal.model.Setting;
import org.openhab.binding.gardena.internal.model.command.Command;
import org.openhab.binding.gardena.internal.model.command.MowerParkUntilFurtherNoticeCommand;
import org.openhab.binding.gardena.internal.model.command.MowerParkUntilNextTimerCommand;
import org.openhab.binding.gardena.internal.model.command.MowerStartOverrideTimerCommand;
import org.openhab.binding.gardena.internal.model.command.MowerStartResumeScheduleCommand;
import org.openhab.binding.gardena.internal.model.command.SensorMeasureAmbientTemperatureCommand;
import org.openhab.binding.gardena.internal.model.command.SensorMeasureLightCommand;
import org.openhab.binding.gardena.internal.model.command.SensorMeasureSoilHumidityCommand;
import org.openhab.binding.gardena.internal.model.command.SensorMeasureSoilTemperatureCommand;
import org.openhab.binding.gardena.internal.model.command.SettingCommand;
import org.openhab.binding.gardena.internal.model.command.SettingCommandWrapper;
import org.openhab.binding.gardena.internal.model.command.WateringCancelOverrideCommand;
import org.openhab.binding.gardena.internal.model.command.WateringManualOverrideCommand;
import org.openhab.binding.gardena.internal.model.deser.DateDeserializer;
import org.openhab.binding.gardena.internal.model.deser.PropertyValueDeserializer;
import org.openhab.binding.gardena.internal.model.property.BaseProperty;
import org.openhab.binding.gardena.internal.model.property.IrrigationControlWateringProperty;
import org.openhab.binding.gardena.internal.model.property.PropertyWrapper;
import org.openhab.binding.gardena.internal.model.property.StringProperty;
import org.openhab.binding.gardena.internal.model.DataItemDeserializer;
import org.openhab.binding.gardena.internal.model.dto.Device;
import org.openhab.binding.gardena.internal.model.dto.api.*;
import org.openhab.binding.gardena.internal.model.dto.command.GardenaCommand;
import org.openhab.binding.gardena.internal.model.dto.command.GardenaCommandRequest;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.io.net.http.WebSocketFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
/**
* {@link GardenaSmart} implementation to access Gardena Smart Home.
* {@link GardenaSmart} implementation to access Gardena smart system.
*
* @author Gerhard Riegler - Initial contribution
*/
public class GardenaSmartImpl implements GardenaSmart {
@NonNullByDefault
public class GardenaSmartImpl implements GardenaSmart, GardenaSmartWebSocketListener {
private final Logger logger = LoggerFactory.getLogger(GardenaSmartImpl.class);
public static final String DEVICE_CATEGORY_PUMP = "electronic_pressure_pump";
private static final String ABILITY_MOWER = "mower";
private static final String ABILITY_OUTLET = "outlet";
private static final String ABILITY_HUMIDITY = "humidity";
private static final String ABILITY_LIGHT = "light";
private static final String ABILITY_AMBIENT_TEMPERATURE = "ambient_temperature";
private static final String ABILITY_SOIL_TEMPERATURE = "soil_temperature";
private static final String ABILITY_POWER = "power";
private static final String ABILITY_WATERING = "watering";
private static final String ABILITY_MANUAL_WATERING = "manual_watering";
private Gson gson = new GsonBuilder().registerTypeAdapter(DataItem.class, new DataItemDeserializer()).create();
private static final String PROPERTY_BUTTON_MANUAL_OVERRIDE_TIME = "button_manual_override_time";
private static final String PROPERTY_POWER_TIMER = "power_timer";
private static final String PROPERTY_WATERING_TIMER = "watering_timer_";
private static final String PROPERTY_MANUAL_WATERING_TIMER = "manual_watering_timer";
private static final String URL_API_HUSQUARNA = "https://api.authentication.husqvarnagroup.dev/v1";
private static final String URL_API_GARDENA = "https://api.smart.gardena.dev/v1";
private static final String URL_API_TOKEN = URL_API_HUSQUARNA + "/oauth2/token";
private static final String URL_API_WEBSOCKET = URL_API_GARDENA + "/websocket";
private static final String URL_API_LOCATIONS = URL_API_GARDENA + "/locations";
private static final String URL_API_COMMAND = URL_API_GARDENA + "/command";
private static final String DEVICE_CATEGORY_MOWER = "mower";
private static final String DEVICE_CATEGORY_GATEWAY = "gateway";
private static final String DEFAULT_MOWER_DURATION = "180";
private static final String URL = "https://smart.gardena.com";
private static final String URL_LOGIN = URL + "/v1/auth/token";
private static final String URL_LOCATIONS = URL + "/v1/locations/?user_id=";
private static final String URL_DEVICES = URL + "/v1/devices/?locationId=";
private static final String URL_COMMAND = URL + "/v1/devices/%s/abilities/%s/command?locationId=%s";
private static final String URL_PROPERTY = URL + "/v1/devices/%s/abilities/%s/properties/%s?locationId=%s";
private static final String URL_SETTING = URL + "/v1/devices/%s/settings/%s?locationId=%s";
private Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new DateDeserializer())
.registerTypeAdapter(PropertyValue.class, new PropertyValueDeserializer()).create();
private HttpClient httpClient;
private String mowerDuration = DEFAULT_MOWER_DURATION;
private Session session;
private GardenaConfig config;
private String id;
private GardenaConfig config;
private ScheduledExecutorService scheduler;
private ScheduledFuture<?> refreshThreadFuture;
private RefreshDevicesThread refreshDevicesThread = new RefreshDevicesThread();
private GardenaSmartEventListener eventListener;
private Map<String, Device> allDevicesById = new HashMap<>();
private Set<Location> allLocations = new HashSet<>();
private LocationsResponse locationsResponse;
private GardenaSmartEventListener eventListener;
@Override
public void init(String id, GardenaConfig config, GardenaSmartEventListener eventListener,
ScheduledExecutorService scheduler) throws GardenaException {
private HttpClient httpClient;
private List<GardenaSmartWebSocket> webSockets = new ArrayList<>();
private @Nullable PostOAuth2Response token;
private boolean initialized = false;
private WebSocketFactory webSocketFactory;
private Set<Device> devicesToNotify = ConcurrentHashMap.newKeySet();
private @Nullable ScheduledFuture<?> deviceToNotifyFuture;
private @Nullable ScheduledFuture<?> newDeviceFuture;
public GardenaSmartImpl(String id, GardenaConfig config, GardenaSmartEventListener eventListener,
ScheduledExecutorService scheduler, HttpClientFactory httpClientFactory, WebSocketFactory webSocketFactory)
throws GardenaException {
this.id = id;
this.config = config;
this.eventListener = eventListener;
this.scheduler = scheduler;
this.webSocketFactory = webSocketFactory;
if (!config.isValid()) {
throw new GardenaException("Invalid config, no email or password specified");
}
httpClient = new HttpClient(new SslContextFactory(true));
httpClient.setConnectTimeout(config.getConnectionTimeout() * 1000L);
logger.debug("Starting GardenaSmart");
try {
httpClient = httpClientFactory.createHttpClient(id);
httpClient.setConnectTimeout(config.getConnectionTimeout() * 1000L);
httpClient.setIdleTimeout(httpClient.getConnectTimeout());
httpClient.start();
// initially load access token
verifyToken();
locationsResponse = loadLocations();
// assemble devices
for (LocationDataItem location : locationsResponse.data) {
LocationResponse locationResponse = loadLocation(location.id);
if (locationResponse.included != null) {
for (DataItem<?> dataItem : locationResponse.included) {
handleDataItem(dataItem);
}
}
}
for (Device device : allDevicesById.values()) {
device.evaluateDeviceType();
}
startWebsockets();
initialized = true;
} catch (Exception ex) {
throw new GardenaException(ex.getMessage(), ex);
}
}
loadAllDevices();
/**
* Starts the websockets for each location.
*/
private void startWebsockets() throws Exception {
for (LocationDataItem location : locationsResponse.data) {
WebSocketCreatedResponse webSocketCreatedResponse = getWebsocketInfo(location.id);
String socketId = id + "-" + location.attributes.name;
webSockets.add(new GardenaSmartWebSocket(this, webSocketCreatedResponse, config, scheduler,
webSocketFactory, token, socketId));
}
}
/**
* Stops all websockets.
*/
private void stopWebsockets() {
for (GardenaSmartWebSocket webSocket : webSockets) {
webSocket.stop();
}
webSockets.clear();
}
/**
* Communicates with Gardena smart home system and parses the result.
*/
private <T> T executeRequest(HttpMethod method, String url, @Nullable Object content, @Nullable Class<T> result)
throws GardenaException {
try {
AbstractTypedContentProvider contentProvider = null;
String contentType = "application/vnd.api+json";
if (content != null) {
if (content instanceof Fields) {
contentProvider = new FormContentProvider((Fields) content);
contentType = "application/x-www-form-urlencoded";
} else {
contentProvider = new StringContentProvider(gson.toJson(content));
}
}
if (logger.isTraceEnabled()) {
logger.trace(">>> {} {}, data: {}", method, url, content == null ? null : gson.toJson(content));
}
Request request = httpClient.newRequest(url).method(method).header(HttpHeader.CONTENT_TYPE, contentType)
.header(HttpHeader.ACCEPT, "application/vnd.api+json").header(HttpHeader.ACCEPT_ENCODING, "gzip");
if (!URL_API_TOKEN.equals(url)) {
verifyToken();
final PostOAuth2Response token = this.token;
if (token != null) {
request.header("Authorization", token.tokenType + " " + token.accessToken);
request.header("Authorization-provider", token.provider);
}
request.header("X-Api-Key", config.getApiKey());
}
request.content(contentProvider);
ContentResponse contentResponse = request.send();
int status = contentResponse.getStatus();
if (logger.isTraceEnabled()) {
logger.trace("<<< status:{}, {}", status, contentResponse.getContentAsString());
}
if (status != 200 && status != 204 && status != 201 && status != 202) {
throw new GardenaException(String.format("Error %s %s, %s", status, contentResponse.getReason(),
contentResponse.getContentAsString()));
}
if (result == null) {
return (T) null;
}
return (T) gson.fromJson(contentResponse.getContentAsString(), result);
} catch (InterruptedException | TimeoutException | ExecutionException ex) {
throw new GardenaException(ex.getMessage(), ex);
}
}
/**
* Creates or refreshes the access token for the Gardena smart system.
*/
private synchronized void verifyToken() throws GardenaException {
Fields fields = new Fields();
fields.add("client_id", config.getApiKey());
PostOAuth2Response token = this.token;
if (token == null || token.isRefreshTokenExpired()) {
// new token
logger.debug("Gardena API login using password, reason: {}",
token == null ? "no token available" : "refresh token expired");
fields.add("grant_type", "password");
fields.add("username", config.getEmail());
fields.add("password", config.getPassword());
token = executeRequest(HttpMethod.POST, URL_API_TOKEN, fields, PostOAuth2Response.class);
token.postProcess();
this.token = token;
} else if (token.isAccessTokenExpired()) {
// refresh token
logger.debug("Gardena API login using refreshToken, reason: access token expired");
fields.add("grant_type", "refresh_token");
fields.add("refresh_token", token.refreshToken);
try {
PostOAuth2Response tempToken = executeRequest(HttpMethod.POST, URL_API_TOKEN, fields,
PostOAuth2Response.class);
token.accessToken = tempToken.accessToken;
token.expiresIn = tempToken.expiresIn;
token.postProcess();
this.token = token;
} catch (GardenaException ex) {
// refresh token issue
this.token = null;
verifyToken();
}
} else {
logger.debug("Gardena API token valid");
}
logger.debug("{}", token.toString());
}
/**
* Loads all locations.
*/
private LocationsResponse loadLocations() throws GardenaException {
return executeRequest(HttpMethod.GET, URL_API_LOCATIONS, null, LocationsResponse.class);
}
/**
* Loads all devices for a given location.
*/
private LocationResponse loadLocation(String locationId) throws GardenaException {
return executeRequest(HttpMethod.GET, URL_API_LOCATIONS + "/" + locationId, null, LocationResponse.class);
}
/**
* Returns the websocket url for a given location.
*/
private WebSocketCreatedResponse getWebsocketInfo(String locationId) throws GardenaException {
return executeRequest(HttpMethod.POST, URL_API_WEBSOCKET, new CreateWebSocketRequest(locationId),
WebSocketCreatedResponse.class);
}
/**
* Stops the client.
*/
public void dispose() {
logger.debug("Disposing GardenaSmart");
final ScheduledFuture<?> newDeviceFuture = this.newDeviceFuture;
if (newDeviceFuture != null) {
newDeviceFuture.cancel(true);
}
final ScheduledFuture<?> deviceToNotifyFuture = this.deviceToNotifyFuture;
if (deviceToNotifyFuture != null) {
deviceToNotifyFuture.cancel(true);
}
stopWebsockets();
try {
httpClient.stop();
} catch (Exception e) {
// ignore
}
httpClient.destroy();
locationsResponse = new LocationsResponse();
allDevicesById.clear();
initialized = false;
}
/**
* Restarts all websockets.
*/
@Override
public synchronized void restartWebsockets() {
logger.debug("Restarting GardenaSmart Webservice");
stopWebsockets();
try {
startWebsockets();
} catch (Exception ex) {
logger.warn("Restarting GardenaSmart Webservice failed: {}, restarting binding", ex.getMessage());
eventListener.onError();
}
}
/**
* Sets the dataItem from the websocket event into the correct device.
*/
private void handleDataItem(final DataItem<?> dataItem) throws GardenaException {
final String deviceId = dataItem.getDeviceId();
Device device = allDevicesById.get(deviceId);
if (device == null && !(dataItem instanceof LocationDataItem)) {
device = new Device(deviceId);
allDevicesById.put(device.id, device);
if (initialized) {
newDeviceFuture = scheduler.schedule(() -> {
Device newDevice = allDevicesById.get(deviceId);
if (newDevice != null) {
newDevice.evaluateDeviceType();
if (newDevice.deviceType != null) {
eventListener.onNewDevice(newDevice);
}
}
}, 3, TimeUnit.SECONDS);
}
}
if (device != null) {
device.setDataItem(dataItem);
}
}
@Override
public void dispose() {
stopRefreshThread(true);
if (httpClient != null) {
try {
httpClient.stop();
} catch (Exception e) {
// ignore
public void onWebSocketClose() {
restartWebsockets();
}
@Override
public void onWebSocketError() {
eventListener.onError();
}
@Override
public void onWebSocketMessage(String msg) {
try {
DataItem<?> dataItem = gson.fromJson(msg, DataItem.class);
if (dataItem != null) {
handleDataItem(dataItem);
Device device = allDevicesById.get(dataItem.getDeviceId());
if (device != null && device.active) {
devicesToNotify.add(device);
// delay the deviceUpdated event to filter multiple events for the same device dataItem property
if (deviceToNotifyFuture == null) {
deviceToNotifyFuture = scheduler.schedule(() -> {
deviceToNotifyFuture = null;
Iterator<Device> notifyIterator = devicesToNotify.iterator();
while (notifyIterator.hasNext()) {
eventListener.onDeviceUpdated(notifyIterator.next());
notifyIterator.remove();
}
}, 1, TimeUnit.SECONDS);
}
}
}
httpClient.destroy();
} catch (GardenaException | JsonSyntaxException ex) {
logger.warn("Ignoring message: {}", ex.getMessage());
}
allLocations.clear();
allDevicesById.clear();
}
/**
* Schedules the device refresh thread.
*/
private void startRefreshThread() {
refreshThreadFuture = scheduler.scheduleWithFixedDelay(refreshDevicesThread, 6, config.getRefresh(),
TimeUnit.SECONDS);
@Override
public Device getDevice(String deviceId) throws GardenaDeviceNotFoundException {
Device device = allDevicesById.get(deviceId);
if (device == null) {
throw new GardenaDeviceNotFoundException("Device with id " + deviceId + " not found");
}
return device;
}
/**
* Stops the device refresh thread.
*/
private void stopRefreshThread(boolean force) {
if (refreshThreadFuture != null) {
refreshThreadFuture.cancel(force);
}
@Override
public void sendCommand(DataItem<?> dataItem, GardenaCommand gardenaCommand) throws GardenaException {
executeRequest(HttpMethod.PUT, URL_API_COMMAND + "/" + dataItem.id, new GardenaCommandRequest(gardenaCommand),
null);
}
@Override
@ -193,381 +389,7 @@ public class GardenaSmartImpl implements GardenaSmart {
}
@Override
public Set<Location> getLocations() {
return allLocations;
}
@Override
public Device getDevice(String deviceId) throws GardenaException {
Device device = allDevicesById.get(deviceId);
if (device == null) {
throw new GardenaDeviceNotFoundException(
String.format("Device with id '%s' not found on gateway '%s'", deviceId, id));
}
return device;
}
@Override
public void loadAllDevices() throws GardenaException {
stopRefreshThread(false);
try {
allLocations.clear();
allDevicesById.clear();
verifySession();
Locations locations = executeRequest(HttpMethod.GET,
URL_LOCATIONS + session.getSessionAttributes().getUserId(), null, Locations.class);
for (Location location : locations.getLocations()) {
allLocations.add(location);
Devices devices = loadDevices(location);
for (Device device : devices.getDevices()) {
if (DEVICE_CATEGORY_GATEWAY.equals(device.getCategory())) {
location.getDeviceIds().remove(device.getId());
} else {
allDevicesById.put(device.getId(), device);
}
}
}
} finally {
startRefreshThread();
}
}
/**
* Loads all devices for the location, adds virtual properties for commands.
*/
private Devices loadDevices(Location location) throws GardenaException {
Devices devices = executeRequest(HttpMethod.GET, URL_DEVICES + location.getId(), null, Devices.class);
for (Device device : devices.getDevices()) {
device.setLocation(location);
for (Ability ability : device.getAbilities()) {
ability.setDevice(device);
for (Property property : ability.getProperties()) {
property.setAbility(ability);
if (device.getCategory().equals(DEVICE_CATEGORY_PUMP)) {
if (property.getName().equals(PROPERTY_MANUAL_WATERING_TIMER)) {
Integer duration = getIntegerValue(property.getValueAsString());
if (duration == null) {
duration = 0;
}
property.setValue(new PropertyValue(String.valueOf(duration / 60)));
}
}
}
}
for (Setting setting : device.getSettings()) {
setting.setDevice(device);
}
if (DEVICE_CATEGORY_MOWER.equals(device.getCategory())) {
Ability mower = device.getAbility(ABILITY_MOWER);
mower.addProperty(new Property(GardenaSmartCommandName.PARK_UNTIL_NEXT_TIMER, "false"));
mower.addProperty(new Property(GardenaSmartCommandName.PARK_UNTIL_FURTHER_NOTICE, "false"));
mower.addProperty(new Property(GardenaSmartCommandName.START_RESUME_SCHEDULE, "false"));
mower.addProperty(new Property(GardenaSmartCommandName.START_OVERRIDE_TIMER, "false"));
mower.addProperty(new Property(GardenaSmartCommandName.DURATION_PROPERTY, mowerDuration));
}
}
return devices;
}
@Override
public void sendCommand(Device device, GardenaSmartCommandName commandName, Object value) throws GardenaException {
Ability ability = null;
Command command = null;
switch (commandName) {
case PARK_UNTIL_NEXT_TIMER:
ability = device.getAbility(ABILITY_MOWER);
command = new MowerParkUntilNextTimerCommand();
break;
case PARK_UNTIL_FURTHER_NOTICE:
ability = device.getAbility(ABILITY_MOWER);
command = new MowerParkUntilFurtherNoticeCommand();
break;
case START_RESUME_SCHEDULE:
ability = device.getAbility(ABILITY_MOWER);
command = new MowerStartResumeScheduleCommand();
break;
case START_OVERRIDE_TIMER:
ability = device.getAbility(ABILITY_MOWER);
command = new MowerStartOverrideTimerCommand(mowerDuration);
break;
case DURATION_PROPERTY:
if (value == null) {
throw new GardenaException("Command '" + commandName + "' requires a value");
}
mowerDuration = ObjectUtils.toString(value);
return;
case MEASURE_AMBIENT_TEMPERATURE:
ability = device.getAbility(ABILITY_AMBIENT_TEMPERATURE);
command = new SensorMeasureAmbientTemperatureCommand();
break;
case MEASURE_LIGHT:
ability = device.getAbility(ABILITY_LIGHT);
command = new SensorMeasureLightCommand();
break;
case MEASURE_SOIL_HUMIDITY:
ability = device.getAbility(ABILITY_HUMIDITY);
command = new SensorMeasureSoilHumidityCommand();
break;
case MEASURE_SOIL_TEMPERATURE:
ability = device.getAbility(ABILITY_SOIL_TEMPERATURE);
command = new SensorMeasureSoilTemperatureCommand();
break;
case OUTLET_MANUAL_OVERRIDE_TIME:
if (value == null) {
throw new GardenaException("Command '" + commandName + "' requires a value");
}
StringProperty prop = new StringProperty(PROPERTY_BUTTON_MANUAL_OVERRIDE_TIME,
ObjectUtils.toString(value));
executeSetProperty(device, ABILITY_OUTLET, PROPERTY_BUTTON_MANUAL_OVERRIDE_TIME, prop);
break;
case OUTLET_VALVE:
ability = device.getAbility(ABILITY_OUTLET);
if (value != null && value == Boolean.TRUE) {
String wateringDuration = device.getAbility(ABILITY_OUTLET)
.getProperty(PROPERTY_BUTTON_MANUAL_OVERRIDE_TIME).getValueAsString();
command = new WateringManualOverrideCommand(wateringDuration);
} else {
command = new WateringCancelOverrideCommand();
}
break;
case POWER_TIMER:
if (value == null) {
throw new GardenaException("Command '" + commandName + "' requires a value");
}
prop = new StringProperty(PROPERTY_POWER_TIMER, ObjectUtils.toString(value));
executeSetProperty(device, ABILITY_POWER, PROPERTY_POWER_TIMER, prop);
break;
case WATERING_TIMER_VALVE_1:
case WATERING_TIMER_VALVE_2:
case WATERING_TIMER_VALVE_3:
case WATERING_TIMER_VALVE_4:
case WATERING_TIMER_VALVE_5:
case WATERING_TIMER_VALVE_6:
if (value == null) {
throw new GardenaException("Command '" + commandName + "' requires a value");
} else if (!(value instanceof Integer)) {
throw new GardenaException("Watering duration value '" + value + "' not a number");
}
int valveId = Integer.parseInt(StringUtils.right(commandName.toString(), 1));
String wateringTimerProperty = PROPERTY_WATERING_TIMER + valveId;
IrrigationControlWateringProperty irrigationProp = new IrrigationControlWateringProperty(
wateringTimerProperty, (Integer) value, valveId);
executeSetProperty(device, ABILITY_WATERING, wateringTimerProperty, irrigationProp);
break;
case PUMP_MANUAL_WATERING_TIMER:
Integer duration = getIntegerValue(value);
if (duration == null) {
throw new GardenaException("Command '" + commandName + "' requires a number value");
}
prop = new StringProperty(PROPERTY_MANUAL_WATERING_TIMER, String.valueOf(duration * 60));
executeSetProperty(device, ABILITY_MANUAL_WATERING, PROPERTY_MANUAL_WATERING_TIMER, prop);
break;
default:
throw new GardenaException("Unknown command " + commandName);
}
if (command != null) {
stopRefreshThread(false);
executeRequest(HttpMethod.POST, getCommandUrl(device, ability), command, NoResult.class);
startRefreshThread();
}
}
private Integer getIntegerValue(Object value) {
try {
return Integer.valueOf(ObjectUtils.toString(value));
} catch (NumberFormatException ex) {
return null;
}
}
/**
* Sends the new property value for the ability.
*/
private void executeSetProperty(Device device, String ability, String property, BaseProperty value)
throws GardenaException {
String propertyUrl = String.format(URL_PROPERTY, device.getId(), ability, property,
device.getLocation().getId());
stopRefreshThread(false);
executeRequest(HttpMethod.PUT, propertyUrl, new PropertyWrapper(value), NoResult.class);
device.getAbility(ability).getProperty(property).setValue(new PropertyValue(value.getValue()));
startRefreshThread();
}
@Override
public void sendSetting(Setting setting, Object value) throws GardenaException {
SettingCommand settingCommand = new SettingCommand(setting.getName());
settingCommand.setDeviceId(setting.getDevice().getId());
settingCommand.setValue(value);
stopRefreshThread(false);
executeRequest(HttpMethod.PUT, getSettingUrl(setting), new SettingCommandWrapper(settingCommand),
NoResult.class);
startRefreshThread();
}
/**
* Returns the command url.
*/
private String getCommandUrl(Device device, Ability ability) throws GardenaException {
return String.format(URL_COMMAND, device.getId(), ability.getName(), device.getLocation().getId());
}
/**
* Returns the settings url.
*/
private String getSettingUrl(Setting setting) {
Device device = setting.getDevice();
return String.format(URL_SETTING, device.getId(), setting.getId(), device.getLocation().getId());
}
/**
* Communicates with Gardena Smart Home and parses the result.
*/
private synchronized <T> T executeRequest(HttpMethod method, String url, Object contentObject, Class<T> result)
throws GardenaException {
try {
if (logger.isTraceEnabled()) {
logger.trace("{} request: {}", method, url);
if (contentObject != null) {
logger.trace("{} data : {}", method, gson.toJson(contentObject));
}
}
Request request = httpClient.newRequest(url).method(method)
.timeout(config.getConnectionTimeout(), TimeUnit.SECONDS)
.idleTimeout(config.getConnectionTimeout(), TimeUnit.SECONDS)
.header(HttpHeader.CONTENT_TYPE, "application/json").header(HttpHeader.ACCEPT, "application/json")
.header(HttpHeader.ACCEPT_ENCODING, "gzip");
if (contentObject != null) {
StringContentProvider content = new StringContentProvider(gson.toJson(contentObject));
request.content(content);
}
if (!result.equals(SessionWrapper.class)) {
verifySession();
request.header("authorization", "Bearer " + session.getToken());
request.header("authorization-provider", session.getSessionAttributes().getProvider());
}
ContentResponse contentResponse = request.send();
int status = contentResponse.getStatus();
if (logger.isTraceEnabled()) {
logger.trace("Status : {}", status);
logger.trace("Response: {}", contentResponse.getContentAsString());
}
if (status == 500) {
throw new GardenaException(
gson.fromJson(contentResponse.getContentAsString(), Errors.class).toString());
} else if (status != 200 && status != 204 && status != 201) {
throw new GardenaException(String.format("Error %s %s", status, contentResponse.getReason()));
}
if (result == NoResult.class) {
return null;
}
return gson.fromJson(contentResponse.getContentAsString(), result);
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
if (cause instanceof HttpResponseException) {
HttpResponseException responseException = (HttpResponseException) ex.getCause();
int status = responseException.getResponse().getStatus();
if (status == 401) {
throw new GardenaUnauthorizedException(ex.getCause());
}
}
throw new GardenaException(ex.getMessage(), ex);
} catch (Exception ex) {
throw new GardenaException(ex.getMessage(), ex);
}
}
/**
* Verifies the Gardena Smart Home session and reconnects if necessary.
*/
private void verifySession() throws GardenaException {
if (session == null
|| session.getCreated() + (config.getSessionTimeout() * 60000) <= System.currentTimeMillis()) {
logger.trace("(Re)logging in to Gardena Smart Home");
session = executeRequest(HttpMethod.POST, URL_LOGIN, new GardenaConfigWrapper(config), SessionWrapper.class)
.getSession();
}
}
/**
* Thread which refreshes the data from Gardena Smart Home.
*/
private class RefreshDevicesThread implements Runnable {
private boolean connectionLost = false;
@Override
public void run() {
try {
logger.debug("Refreshing gardena device data");
final Map<String, Device> newDevicesById = new HashMap<>();
for (Location location : allLocations) {
Devices devices = loadDevices(location);
for (Device device : devices.getDevices()) {
if (DEVICE_CATEGORY_GATEWAY.equals(device.getCategory())) {
location.getDeviceIds().remove(device.getId());
} else {
newDevicesById.put(device.getId(), device);
}
}
}
if (connectionLost) {
connectionLost = false;
logger.info("Connection resumed to Gardena Smart Home with id '{}'", id);
eventListener.onConnectionResumed();
}
// determine deleted devices
Collection<Device> deletedDevices = allDevicesById.values().stream()
.filter(d -> !newDevicesById.values().contains(d)).collect(Collectors.toSet());
// determine new devices
Collection<Device> newDevices = newDevicesById.values().stream()
.filter(d -> !allDevicesById.values().contains(d)).collect(Collectors.toSet());
// determine updated devices
Collection<Device> updatedDevices = allDevicesById.values().stream().distinct()
.filter(newDevicesById.values()::contains).collect(Collectors.toSet());
allDevicesById = newDevicesById;
for (Device deletedDevice : deletedDevices) {
eventListener.onDeviceDeleted(deletedDevice);
}
for (Device newDevice : newDevices) {
eventListener.onNewDevice(newDevice);
}
for (Device updatedDevice : updatedDevices) {
eventListener.onDeviceUpdated(updatedDevice);
}
} catch (GardenaException ex) {
if (!connectionLost) {
connectionLost = true;
logger.warn("Connection lost to Gardena Smart Home with id '{}'", id);
logger.trace("{}", ex.getMessage(), ex);
eventListener.onConnectionLost();
}
}
}
public Collection<Device> getAllDevices() {
return allDevicesById.values();
}
}

View File

@ -0,0 +1,175 @@
/**
* 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.gardena.internal;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.*;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.frames.PongFrame;
import org.openhab.binding.gardena.internal.config.GardenaConfig;
import org.openhab.binding.gardena.internal.model.dto.api.PostOAuth2Response;
import org.openhab.binding.gardena.internal.model.dto.api.WebSocketCreatedResponse;
import org.openhab.core.io.net.http.WebSocketFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link GardenaSmartWebSocket} implements the websocket for receiving constant updates from the Gardena smart
* system.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
@WebSocket
public class GardenaSmartWebSocket {
private final Logger logger = LoggerFactory.getLogger(GardenaSmartWebSocket.class);
private final GardenaSmartWebSocketListener socketEventListener;
private final long WEBSOCKET_IDLE_TIMEOUT = 300;
private WebSocketSession session;
private WebSocketClient webSocketClient;
private boolean closing;
private Instant lastPong = Instant.now();
private ScheduledExecutorService scheduler;
private @Nullable ScheduledFuture<?> connectionTracker;
private ByteBuffer pingPayload = ByteBuffer.wrap("ping".getBytes(StandardCharsets.UTF_8));
private @Nullable PostOAuth2Response token;
private String socketId;
/**
* Starts the websocket session.
*/
public GardenaSmartWebSocket(GardenaSmartWebSocketListener socketEventListener,
WebSocketCreatedResponse webSocketCreatedResponse, GardenaConfig config, ScheduledExecutorService scheduler,
WebSocketFactory webSocketFactory, @Nullable PostOAuth2Response token, String socketId) throws Exception {
this.socketEventListener = socketEventListener;
this.scheduler = scheduler;
this.token = token;
this.socketId = socketId;
webSocketClient = webSocketFactory.createWebSocketClient(socketId);
webSocketClient.setConnectTimeout(config.getConnectionTimeout() * 1000L);
webSocketClient.setStopTimeout(3000);
webSocketClient.setMaxIdleTimeout(150000);
webSocketClient.start();
logger.debug("Connecting to Gardena Webservice ({})", socketId);
session = (WebSocketSession) webSocketClient
.connect(this, new URI(webSocketCreatedResponse.data.attributes.url)).get();
session.setStopTimeout(3000);
}
/**
* Stops the websocket session.
*/
public synchronized void stop() {
closing = true;
final ScheduledFuture<?> connectionTracker = this.connectionTracker;
if (connectionTracker != null) {
connectionTracker.cancel(true);
}
if (isRunning()) {
logger.debug("Closing Gardena Webservice client ({})", socketId);
try {
session.close();
} catch (Exception ex) {
// ignore
} finally {
try {
webSocketClient.stop();
} catch (Exception e) {
// ignore
}
}
}
}
/**
* Returns true, if the websocket is running.
*/
public synchronized boolean isRunning() {
return session.isOpen();
}
@OnWebSocketConnect
public void onConnect(Session session) {
closing = false;
logger.debug("Connected to Gardena Webservice ({})", socketId);
connectionTracker = scheduler.scheduleWithFixedDelay(this::sendKeepAlivePing, 2, 2, TimeUnit.MINUTES);
}
@OnWebSocketFrame
public void onFrame(Frame pong) {
if (pong instanceof PongFrame) {
lastPong = Instant.now();
logger.trace("Pong received ({})", socketId);
}
}
@OnWebSocketClose
public void onClose(int statusCode, String reason) {
if (!closing) {
logger.debug("Connection to Gardena Webservice was closed ({}): code: {}, reason: {}", socketId, statusCode,
reason);
socketEventListener.onWebSocketClose();
}
}
@OnWebSocketError
public void onError(Throwable cause) {
if (!closing) {
logger.warn("Gardena Webservice error ({}): {}, restarting", socketId, cause.getMessage());
logger.debug("{}", cause.getMessage(), cause);
socketEventListener.onWebSocketError();
}
}
@OnWebSocketMessage
public void onMessage(String msg) {
if (!closing) {
logger.trace("<<< event ({}): {}", socketId, msg);
socketEventListener.onWebSocketMessage(msg);
}
}
/**
* Sends a ping to tell the Gardena smart system that the client is alive.
*/
private void sendKeepAlivePing() {
try {
logger.trace("Sending ping ({})", socketId);
session.getRemote().sendPing(pingPayload);
final PostOAuth2Response accessToken = token;
if ((Instant.now().getEpochSecond() - lastPong.getEpochSecond() > WEBSOCKET_IDLE_TIMEOUT)
|| accessToken == null || accessToken.isAccessTokenExpired()) {
session.close(1000, "Timeout manually closing dead connection (" + socketId + ")");
}
} catch (IOException ex) {
logger.debug("{}", ex.getMessage());
}
}
}

View File

@ -0,0 +1,42 @@
/**
* 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.gardena.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link GardenaSmartWebSocketListener} is called by the {@link GardenaSmartWebSocket} on new Events and if the
* {@link GardenaSmartWebSocket}
* closed the connection.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public interface GardenaSmartWebSocketListener {
/**
* This method is called, when the evenRunner stops abnormally (statuscode <> 1000).
*/
void onWebSocketClose();
/**
* This method is called when the Gardena websocket services throws an onError.
*/
void onWebSocketError();
/**
* This method is called, whenever a new event comes from the Gardena service.
*
* @param msg
*/
void onWebSocketMessage(String msg);
}

View File

@ -12,30 +12,23 @@
*/
package org.openhab.binding.gardena.internal.config;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import com.google.gson.annotations.SerializedName;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The main Gardena config class.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class GardenaConfig {
private static final Integer DEFAULT_SESSION_TIMEOUT = 30;
private static final Integer DEFAULT_CONNECTION_TIMEOUT = 10;
private static final Integer DEFAULT_REFRESH = 60;
@SerializedName("username")
private String email;
private String password;
private @Nullable String email;
private @Nullable String password;
private @Nullable String apiKey;
private transient Integer sessionTimeout = DEFAULT_SESSION_TIMEOUT;
private transient Integer connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
private transient Integer refresh = DEFAULT_REFRESH;
public GardenaConfig() {
}
@ -46,87 +39,78 @@ public class GardenaConfig {
}
/**
* Returns the email to connect to Gardena Smart Home.
* Returns the email to connect to Gardena smart system.
*/
public String getEmail() {
public @Nullable String getEmail() {
return email;
}
/**
* Sets the email to connect to Gardena Smart Home.
* Sets the email to connect to Gardena smart system.
*/
public void setEmail(String email) {
this.email = email;
}
/**
* Returns the password to connect to Gardena Smart Home.
* Returns the password to connect to Gardena smart system.
*/
public String getPassword() {
public @Nullable String getPassword() {
return password;
}
/**
* Sets the password to connect to Gardena Smart Home.
* Sets the password to connect to Gardena smart system.
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Returns the session timeout to Gardena Smart Home.
*/
public int getSessionTimeout() {
return sessionTimeout;
}
/**
* Sets the session timeout to Gardena Smart Home.
*/
public void setSessionTimeout(int sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
/**
* Returns the connection timeout to Gardena Smart Home.
* Returns the connection timeout to Gardena smart system.
*/
public Integer getConnectionTimeout() {
return connectionTimeout;
}
/**
* Sets the connection timeout to Gardena Smart Home.
* Sets the connection timeout to Gardena smart system.
*/
public void setConnectionTimeout(Integer connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
/**
* Returns the refresh interval to fetch new data from Gardena Smart Home.
* Returns the api key.
*/
public Integer getRefresh() {
return refresh;
public @Nullable String getApiKey() {
return apiKey;
}
/**
* Returns the refresh interval to fetch new data from Gardena Smart Home.
* Sets the api key.
*/
public void setRefresh(Integer refresh) {
this.refresh = refresh;
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
/**
* Validate the config, if at least email and password is specified.
* Validate the config if email, password and apiKey is specified.
*/
public boolean isValid() {
return StringUtils.isNotBlank(email) && StringUtils.isNotBlank(password);
final String email = this.email;
final String password = this.password;
final String apiKey = this.apiKey;
return email != null && !email.isBlank() && password != null && !password.isBlank() && apiKey != null
&& !apiKey.isBlank();
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("email", email)
.append("password", StringUtils.isBlank(password) ? "" : StringUtils.repeat("*", password.length()))
.append("sessionTimeout", sessionTimeout).append("connectionTimeout", connectionTimeout)
.append("refresh", refresh).toString();
StringBuilder sb = new StringBuilder(GardenaConfig.class.getSimpleName()).append("[");
sb.append("email: ").append(email).append(", ");
sb.append("connectionTimeout: ").append(connectionTimeout).append(", ");
sb.append("apiKey: ").append(apiKey);
return sb.append("]").toString();
}
}

View File

@ -1,36 +0,0 @@
/**
* 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.gardena.internal.config;
import com.google.gson.annotations.SerializedName;
/**
* GardenaConfgData wrapper for valid Gardena JSON serialization.
*
* @author Gerhard Riegler - Initial contribution
*/
public class GardenaConfigDataWrapper {
@SerializedName("attributes")
private GardenaConfig config;
@SerializedName("type")
private String typeToken = "token";
public GardenaConfigDataWrapper() {
}
public GardenaConfigDataWrapper(GardenaConfig config) {
this.config = config;
}
}

View File

@ -1,32 +0,0 @@
/**
* 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.gardena.internal.config;
import com.google.gson.annotations.SerializedName;
/**
* GardenaConfg wrapper for valid Gardena JSON serialization.
*
* @author Gerhard Riegler - Initial contribution
*/
public class GardenaConfigWrapper {
@SerializedName("data")
private GardenaConfigDataWrapper dataWrapper;
public GardenaConfigWrapper() {
}
public GardenaConfigWrapper(GardenaConfig config) {
this.dataWrapper = new GardenaConfigDataWrapper(config);
}
}

View File

@ -25,8 +25,8 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gardena.internal.GardenaSmart;
import org.openhab.binding.gardena.internal.exception.GardenaException;
import org.openhab.binding.gardena.internal.handler.GardenaAccountHandler;
import org.openhab.binding.gardena.internal.model.Device;
import org.openhab.binding.gardena.internal.model.Location;
import org.openhab.binding.gardena.internal.model.dto.Device;
import org.openhab.binding.gardena.internal.util.PropertyUtils;
import org.openhab.binding.gardena.internal.util.UidUtils;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
@ -41,18 +41,19 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link GardenaDeviceDiscoveryService} is used to discover devices that are connected to Gardena Smart Home.
* The {@link GardenaDeviceDiscoveryService} is used to discover devices that are connected to Gardena smart system.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class GardenaDeviceDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(GardenaDeviceDiscoveryService.class);
private static final int DISCOVER_TIMEOUT_SECONDS = 30;
private static final int DISCOVER_TIMEOUT_SECONDS = 5;
private @NonNullByDefault({}) GardenaAccountHandler accountHandler;
private Future<?> scanFuture;
private @Nullable Future<?> scanFuture;
public GardenaDeviceDiscoveryService() {
super(Collections.unmodifiableSet(Stream.of(new ThingTypeUID(BINDING_ID, "-")).collect(Collectors.toSet())),
@ -94,6 +95,7 @@ public class GardenaDeviceDiscoveryService extends AbstractDiscoveryService
@Override
public void stopScan() {
logger.debug("Stopping Gardena discovery scan");
final Future<?> scanFuture = this.scanFuture;
if (scanFuture != null) {
scanFuture.cancel(true);
}
@ -101,18 +103,15 @@ public class GardenaDeviceDiscoveryService extends AbstractDiscoveryService
}
/**
* Starts a thread which loads all Gardena devices registered in the account
* Starts a thread which loads all Gardena devices registered in the account.
*/
public void loadDevices() {
private void loadDevices() {
if (scanFuture == null) {
scanFuture = scheduler.submit(() -> {
try {
GardenaSmart gardena = accountHandler.getGardenaSmart();
gardena.loadAllDevices();
for (Location location : gardena.getLocations()) {
for (String deviceId : location.getDeviceIds()) {
deviceDiscovered(gardena.getDevice(deviceId));
}
GardenaSmart gardena = accountHandler.getGardenaSmart();
if (gardena != null) {
for (Device device : gardena.getAllDevices()) {
deviceDiscovered(device);
}
for (Thing thing : accountHandler.getThing().getThings()) {
@ -123,11 +122,7 @@ public class GardenaDeviceDiscoveryService extends AbstractDiscoveryService
}
}
logger.debug("Finished Gardena device discovery scan on gateway '{}'",
accountHandler.getGardenaSmart().getId());
} catch (GardenaException ex) {
logger.error("{}", ex.getMessage(), ex);
} finally {
logger.debug("Finished Gardena device discovery scan on gateway '{}'", gardena.getId());
scanFuture = null;
removeOlderResults(getTimestampOfLastScan());
}
@ -141,6 +136,7 @@ public class GardenaDeviceDiscoveryService extends AbstractDiscoveryService
* Waits for the discovery scan to finish and then returns.
*/
public void waitForScanFinishing() {
final Future<?> scanFuture = this.scanFuture;
if (scanFuture != null) {
logger.debug("Waiting for finishing Gardena device discovery scan");
try {
@ -158,19 +154,19 @@ public class GardenaDeviceDiscoveryService extends AbstractDiscoveryService
* Generates the DiscoveryResult from a Gardena device.
*/
public void deviceDiscovered(Device device) {
ThingUID accountUID = accountHandler.getThing().getUID();
ThingUID thingUID = UidUtils.generateThingUID(device, accountHandler.getThing());
if (device.active) {
ThingUID accountUID = accountHandler.getThing().getUID();
ThingUID thingUID = UidUtils.generateThingUID(device, accountHandler.getThing());
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(accountUID)
.withLabel(device.getName()).build();
thingDiscovered(discoveryResult);
}
/**
* Removes the Gardena device.
*/
public void deviceRemoved(Device device) {
ThingUID thingUID = UidUtils.generateThingUID(device, accountHandler.getThing());
thingRemoved(thingUID);
try {
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(accountUID)
.withLabel(PropertyUtils.getPropertyValue(device, "common.attributes.name.value", String.class))
.withProperty("id", device.id).withProperty("type", device.deviceType)
.withRepresentationProperty("id").build();
thingDiscovered(discoveryResult);
} catch (GardenaException ex) {
logger.warn("{}", ex.getMessage());
}
}
}
}

View File

@ -12,12 +12,15 @@
*/
package org.openhab.binding.gardena.internal.exception;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Exception if a device is not found, this happens if a device is requested and the data from Gardena Smart Home has
* Exception if a device is not found, this happens if a device is requested and the data from Gardena smart system has
* not been loaded.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class GardenaDeviceNotFoundException extends GardenaException {
private static final long serialVersionUID = 2704767320916725490L;

View File

@ -14,11 +14,15 @@ package org.openhab.binding.gardena.internal.exception;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Exception if something happens in the communication to Gardena Smart Home.
* Exception if something happens in the communication to Gardena smart system.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class GardenaException extends IOException {
private static final long serialVersionUID = 8568935118878542270L;
@ -31,7 +35,7 @@ public class GardenaException extends IOException {
super(ex);
}
public GardenaException(String message, Throwable cause) {
public GardenaException(@Nullable String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -12,11 +12,14 @@
*/
package org.openhab.binding.gardena.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Exception if the AccountHandler is not available.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class AccountHandlerNotAvailableException extends Exception {
private static final long serialVersionUID = -1895774551653276530L;

View File

@ -16,21 +16,19 @@ import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gardena.internal.GardenaSmart;
import org.openhab.binding.gardena.internal.GardenaSmartEventListener;
import org.openhab.binding.gardena.internal.GardenaSmartImpl;
import org.openhab.binding.gardena.internal.config.GardenaConfig;
import org.openhab.binding.gardena.internal.discovery.GardenaDeviceDiscoveryService;
import org.openhab.binding.gardena.internal.exception.GardenaException;
import org.openhab.binding.gardena.internal.model.Device;
import org.openhab.binding.gardena.internal.model.dto.Device;
import org.openhab.binding.gardena.internal.util.UidUtils;
import org.openhab.core.thing.Bridge;
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.thing.ThingUID;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.io.net.http.WebSocketFactory;
import org.openhab.core.thing.*;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
@ -39,31 +37,31 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link GardenaAccountHandler} is the handler for a Gardena Smart Home access and connects it to the framework.
* The {@link GardenaAccountHandler} is the handler for a Gardena smart system access and connects it to the framework.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class GardenaAccountHandler extends BaseBridgeHandler implements GardenaSmartEventListener {
private final Logger logger = LoggerFactory.getLogger(GardenaAccountHandler.class);
private static final long REINITIALIZE_DELAY_SECONDS = 10;
private final long REINITIALIZE_DELAY_SECONDS = 10;
private GardenaDeviceDiscoveryService discoveryService;
private @Nullable GardenaDeviceDiscoveryService discoveryService;
private GardenaSmart gardenaSmart = new GardenaSmartImpl();
private GardenaConfig gardenaConfig;
private @Nullable GardenaSmart gardenaSmart;
private HttpClientFactory httpClientFactory;
private WebSocketFactory webSocketFactory;
public GardenaAccountHandler(Bridge bridge) {
public GardenaAccountHandler(Bridge bridge, HttpClientFactory httpClientFactory,
WebSocketFactory webSocketFactory) {
super(bridge);
this.httpClientFactory = httpClientFactory;
this.webSocketFactory = webSocketFactory;
}
@Override
public void initialize() {
logger.debug("Initializing Gardena account '{}'", getThing().getUID().getId());
gardenaConfig = getThing().getConfiguration().as(GardenaConfig.class);
logger.debug("{}", gardenaConfig);
initializeGardena();
}
@ -78,26 +76,35 @@ public class GardenaAccountHandler extends BaseBridgeHandler implements GardenaS
final GardenaAccountHandler instance = this;
scheduler.execute(() -> {
try {
GardenaConfig gardenaConfig = getThing().getConfiguration().as(GardenaConfig.class);
logger.debug("{}", gardenaConfig);
String id = getThing().getUID().getId();
gardenaSmart.init(id, gardenaConfig, instance, scheduler);
discoveryService.startScan(null);
discoveryService.waitForScanFinishing();
gardenaSmart = new GardenaSmartImpl(id, gardenaConfig, instance, scheduler, httpClientFactory,
webSocketFactory);
final GardenaDeviceDiscoveryService discoveryService = this.discoveryService;
if (discoveryService != null) {
discoveryService.startScan(null);
discoveryService.waitForScanFinishing();
}
updateStatus(ThingStatus.ONLINE);
} catch (GardenaException ex) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage());
disposeGardena();
scheduleReinitialize();
logger.debug("{}", ex.getMessage(), ex);
logger.warn("{}", ex.getMessage());
}
});
}
/**
* Schedules a reinitialization, if Gardea Smart Home account is not reachable at startup.
* Schedules a reinitialization, if Gardena smart system account is not reachable.
*/
private void scheduleReinitialize() {
scheduler.schedule(() -> {
initializeGardena();
if (getThing().getStatus() != ThingStatus.UNINITIALIZED) {
initializeGardena();
}
}, REINITIALIZE_DELAY_SECONDS, TimeUnit.SECONDS);
}
@ -112,16 +119,20 @@ public class GardenaAccountHandler extends BaseBridgeHandler implements GardenaS
*/
private void disposeGardena() {
logger.debug("Disposing Gardena account '{}'", getThing().getUID().getId());
discoveryService.stopScan();
gardenaSmart.dispose();
final GardenaDeviceDiscoveryService discoveryService = this.discoveryService;
if (discoveryService != null) {
discoveryService.stopScan();
}
final GardenaSmart gardenaSmart = this.gardenaSmart;
if (gardenaSmart != null) {
gardenaSmart.dispose();
}
}
/**
* Returns the Gardena Smart Home implementation.
* Returns the Gardena smart system implementation.
*/
public GardenaSmart getGardenaSmart() {
public @Nullable GardenaSmart getGardenaSmart() {
return gardenaSmart;
}
@ -142,27 +153,27 @@ public class GardenaAccountHandler extends BaseBridgeHandler implements GardenaS
@Override
public void onDeviceUpdated(Device device) {
for (ThingUID thingUID : UidUtils.getThingUIDs(device, getThing())) {
Thing gardenaThing = getThing().getThing(thingUID);
try {
GardenaThingHandler gardenaThingHandler = (GardenaThingHandler) gardenaThing.getHandler();
gardenaThingHandler.updateProperties(device);
for (Channel channel : gardenaThing.getChannels()) {
gardenaThingHandler.updateChannel(channel.getUID());
final Thing gardenaThing;
final GardenaThingHandler gardenaThingHandler;
if ((gardenaThing = getThing().getThing(thingUID)) != null
&& (gardenaThingHandler = (GardenaThingHandler) gardenaThing.getHandler()) != null) {
try {
gardenaThingHandler.updateProperties(device);
for (Channel channel : gardenaThing.getChannels()) {
gardenaThingHandler.updateChannel(channel.getUID());
}
gardenaThingHandler.updateStatus(device);
} catch (GardenaException ex) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, ex.getMessage());
} catch (AccountHandlerNotAvailableException ignore) {
}
gardenaThingHandler.updateSettings(device);
gardenaThingHandler.updateStatus(device);
} catch (GardenaException ex) {
logger.error("There is something wrong with your thing '{}', please check or recreate it: {}",
gardenaThing.getUID(), ex.getMessage());
logger.debug("Gardena exception caught on device update.", ex);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, ex.getMessage());
} catch (AccountHandlerNotAvailableException ignore) {
}
}
}
@Override
public void onNewDevice(Device device) {
final GardenaDeviceDiscoveryService discoveryService = this.discoveryService;
if (discoveryService != null) {
discoveryService.deviceDiscovered(device);
}
@ -170,19 +181,9 @@ public class GardenaAccountHandler extends BaseBridgeHandler implements GardenaS
}
@Override
public void onDeviceDeleted(Device device) {
if (discoveryService != null) {
discoveryService.deviceRemoved(device);
}
}
@Override
public void onConnectionLost() {
public void onError() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Connection lost");
}
@Override
public void onConnectionResumed() {
updateStatus(ThingStatus.ONLINE);
disposeGardena();
scheduleReinitialize();
}
}

View File

@ -12,12 +12,15 @@
*/
package org.openhab.binding.gardena.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link GardenaDeviceConfig} class represents the configuration for a device connected to an Gardena account.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class GardenaDeviceConfig {
public String deviceId;
public @Nullable String deviceId;
}

View File

@ -12,23 +12,43 @@
*/
package org.openhab.binding.gardena.internal.handler;
import static org.openhab.binding.gardena.internal.GardenaBindingConstants.*;
import static org.openhab.binding.gardena.internal.GardenaBindingConstants.BINDING_ID;
import static org.openhab.binding.gardena.internal.GardenaBindingConstants.THING_TYPE_ACCOUNT;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.io.net.http.WebSocketFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link GardenaHandlerFactory} is responsible for creating Gardena things and thing handlers.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.gardena")
public class GardenaHandlerFactory extends BaseThingHandlerFactory {
private HttpClientFactory httpClientFactory;
private WebSocketFactory webSocketFactory;
private TimeZoneProvider timeZoneProvider;
@Activate
public GardenaHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
final @Reference WebSocketFactory webSocketFactory, final @Reference TimeZoneProvider timeZoneProvider) {
this.httpClientFactory = httpClientFactory;
this.webSocketFactory = webSocketFactory;
this.timeZoneProvider = timeZoneProvider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@ -36,11 +56,11 @@ public class GardenaHandlerFactory extends BaseThingHandlerFactory {
}
@Override
protected ThingHandler createHandler(Thing thing) {
protected @Nullable ThingHandler createHandler(Thing thing) {
if (THING_TYPE_ACCOUNT.equals(thing.getThingTypeUID())) {
return new GardenaAccountHandler((Bridge) thing);
return new GardenaAccountHandler((Bridge) thing, httpClientFactory, webSocketFactory);
} else {
return new GardenaThingHandler(thing);
return new GardenaThingHandler(thing, timeZoneProvider);
}
}
}

View File

@ -13,41 +13,37 @@
package org.openhab.binding.gardena.internal.handler;
import static org.openhab.binding.gardena.internal.GardenaBindingConstants.*;
import static org.openhab.binding.gardena.internal.GardenaSmartCommandName.*;
import static org.openhab.binding.gardena.internal.model.dto.command.MowerCommand.MowerControl;
import static org.openhab.binding.gardena.internal.model.dto.command.PowerSocketCommand.PowerSocketControl;
import static org.openhab.binding.gardena.internal.model.dto.command.ValveCommand.ValveControl;
import static org.openhab.binding.gardena.internal.model.dto.command.ValveSetCommand.ValveSetControl;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gardena.internal.GardenaSmart;
import org.openhab.binding.gardena.internal.GardenaSmartCommandName;
import org.openhab.binding.gardena.internal.GardenaSmartImpl;
import org.openhab.binding.gardena.internal.GardenaSmartEventListener;
import org.openhab.binding.gardena.internal.exception.GardenaDeviceNotFoundException;
import org.openhab.binding.gardena.internal.exception.GardenaException;
import org.openhab.binding.gardena.internal.model.Ability;
import org.openhab.binding.gardena.internal.model.Device;
import org.openhab.binding.gardena.internal.model.Setting;
import org.openhab.binding.gardena.internal.util.DateUtils;
import org.openhab.binding.gardena.internal.model.dto.Device;
import org.openhab.binding.gardena.internal.model.dto.api.DataItem;
import org.openhab.binding.gardena.internal.model.dto.command.*;
import org.openhab.binding.gardena.internal.util.PropertyUtils;
import org.openhab.binding.gardena.internal.util.StringUtils;
import org.openhab.binding.gardena.internal.util.UidUtils;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.core.validation.ConfigValidationException;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
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.i18n.TimeZoneProvider;
import org.openhab.core.library.types.*;
import org.openhab.core.thing.*;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.Type;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -57,13 +53,15 @@ import org.slf4j.LoggerFactory;
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class GardenaThingHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(GardenaThingHandler.class);
private final Calendar VALID_DATE_START = DateUtils.parseToCalendar("1970-01-02T00:00Z");
private TimeZoneProvider timeZoneProvider;
private @Nullable ScheduledFuture<?> commandResetFuture;
public GardenaThingHandler(Thing thing) {
public GardenaThingHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
super(thing);
this.timeZoneProvider = timeZoneProvider;
}
@Override
@ -71,7 +69,6 @@ public class GardenaThingHandler extends BaseThingHandler {
try {
Device device = getDevice();
updateProperties(device);
updateSettings(device);
updateStatus(device);
} catch (GardenaException ex) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, ex.getMessage());
@ -80,28 +77,13 @@ public class GardenaThingHandler extends BaseThingHandler {
}
}
/**
* Updates the thing configuration from the Gardena device.
*/
protected void updateSettings(Device device) throws GardenaException {
if (GardenaSmartImpl.DEVICE_CATEGORY_PUMP.equals(device.getCategory())) {
Configuration config = editConfiguration();
if (!equalsSetting(config, device, SETTING_LEAKAGE_DETECTION)
|| !equalsSetting(config, device, SETTING_OPERATION_MODE)
|| !equalsSetting(config, device, SETTING_TURN_ON_PRESSURE)) {
config.put(SETTING_LEAKAGE_DETECTION, device.getSetting(SETTING_LEAKAGE_DETECTION).getValue());
config.put(SETTING_OPERATION_MODE, device.getSetting(SETTING_OPERATION_MODE).getValue());
config.put(SETTING_TURN_ON_PRESSURE,
ObjectUtils.toString(device.getSetting(SETTING_TURN_ON_PRESSURE).getValue()));
updateConfiguration(config);
}
@Override
public void dispose() {
final ScheduledFuture<?> commandResetFuture = this.commandResetFuture;
if (commandResetFuture != null) {
commandResetFuture.cancel(true);
}
}
private boolean equalsSetting(Configuration config, Device device, String key) throws GardenaException {
return config.get(key) != null
&& config.get(key).equals(ObjectUtils.toString(device.getSetting(key).getValue()));
super.dispose();
}
/**
@ -109,22 +91,15 @@ public class GardenaThingHandler extends BaseThingHandler {
*/
protected void updateProperties(Device device) throws GardenaException {
Map<String, String> properties = editProperties();
Ability deviceInfo = device.getAbility(ABILITY_DEVICE_INFO);
setProperty(properties, deviceInfo, PROPERTY_MANUFACTURER);
setProperty(properties, deviceInfo, PROPERTY_PRODUCT);
setProperty(properties, deviceInfo, PROPERTY_SERIALNUMBER);
setProperty(properties, deviceInfo, PROPERTY_SGTIN);
setProperty(properties, deviceInfo, PROPERTY_VERSION);
setProperty(properties, deviceInfo, PROPERTY_CATEGORY);
updateProperties(properties);
}
private void setProperty(Map<String, String> properties, Ability deviceInfo, String propertyName) {
try {
properties.put(propertyName, deviceInfo.getProperty(propertyName).getValueAsString());
} catch (GardenaException ex) {
logger.debug("Ignoring missing device property {}", propertyName);
String serial = PropertyUtils.getPropertyValue(device, "common.attributes.serial.value", String.class);
if (serial != null) {
properties.put(PROPERTY_SERIALNUMBER, serial);
}
String modelType = PropertyUtils.getPropertyValue(device, "common.attributes.modelType.value", String.class);
if (modelType != null) {
properties.put(PROPERTY_MODELTYPE, modelType);
}
updateProperties(properties);
}
@Override
@ -142,177 +117,163 @@ public class GardenaThingHandler extends BaseThingHandler {
* Updates the channel from the Gardena device.
*/
protected void updateChannel(ChannelUID channelUID) throws GardenaException, AccountHandlerNotAvailableException {
Device device = getDevice();
State state = convertToState(device, channelUID);
if (state != null) {
updateState(channelUID, state);
String groupId = channelUID.getGroupId();
if (groupId != null) {
boolean isCommand = groupId.endsWith("_commands");
if (!isCommand || (isCommand && isLocalDurationCommand(channelUID))) {
Device device = getDevice();
State state = convertToState(device, channelUID);
if (state != null) {
updateState(channelUID, state);
}
}
}
}
/**
* Converts a Gardena property value to a openHAB state.
*/
private State convertToState(Device device, ChannelUID channelUID) throws GardenaException {
String abilityName = channelUID.getGroupId();
private @Nullable State convertToState(Device device, ChannelUID channelUID) throws GardenaException {
if (isLocalDurationCommand(channelUID)) {
String dataItemProperty = getDeviceDataItemProperty(channelUID);
return new DecimalType(Math.round(device.getLocalService(dataItemProperty).commandDuration / 60.0));
}
String propertyPath = channelUID.getGroupId() + ".attributes.";
String propertyName = channelUID.getIdWithoutGroup();
if (propertyName.endsWith("_timestamp")) {
propertyPath += propertyName.replace("_", ".");
} else {
propertyPath += propertyName + ".value";
}
String acceptedItemType = null;
try {
String value = device.getAbility(abilityName).getProperty(propertyName).getValueAsString();
Channel channel = getThing().getChannel(channelUID.getId());
if (channel != null) {
acceptedItemType = StringUtils.substringBefore(channel.getAcceptedItemType(), ":");
if (StringUtils.trimToNull(value) == null || StringUtils.equals(value, "N/A")) {
return UnDefType.NULL;
}
if (acceptedItemType != null) {
boolean isNullPropertyValue = PropertyUtils.isNull(device, propertyPath);
boolean isDurationProperty = "duration".equals(propertyName);
switch (getThing().getChannel(channelUID.getId()).getAcceptedItemType()) {
case "String":
return new StringType(value);
case "Number":
if (ABILITY_RADIO.equals(abilityName) && PROPERTY_STATE.equals(propertyName)) {
switch (value) {
case "poor":
return new DecimalType(1);
case "good":
return new DecimalType(2);
case "excellent":
return new DecimalType(4);
default:
return UnDefType.NULL;
}
}
return new DecimalType(value);
case "Switch":
return Boolean.TRUE.toString().equalsIgnoreCase(value) || "on".equalsIgnoreCase(value)
? OnOffType.ON
: OnOffType.OFF;
case "DateTime":
Calendar cal = DateUtils.parseToCalendar(value);
if (cal != null && !cal.before(VALID_DATE_START)) {
return new DateTimeType(ZonedDateTime.ofInstant(cal.toInstant(), ZoneId.systemDefault()));
} else {
if (isNullPropertyValue && !isDurationProperty) {
return UnDefType.NULL;
}
switch (acceptedItemType) {
case "String":
return new StringType(PropertyUtils.getPropertyValue(device, propertyPath, String.class));
case "Number":
if (isNullPropertyValue) {
return new DecimalType(0);
} else {
Number value = PropertyUtils.getPropertyValue(device, propertyPath, Number.class);
// convert duration from seconds to minutes
if (value != null) {
if (isDurationProperty) {
value = Math.round(value.longValue() / 60.0);
}
return new DecimalType(value.longValue());
}
return UnDefType.NULL;
}
case "DateTime":
Date date = PropertyUtils.getPropertyValue(device, propertyPath, Date.class);
if (date != null) {
ZonedDateTime zdt = ZonedDateTime.ofInstant(date.toInstant(),
timeZoneProvider.getTimeZone());
return new DateTimeType(zdt);
}
return UnDefType.NULL;
}
}
}
} catch (GardenaException e) {
logger.warn("Channel '{}' cannot be updated as device does not contain property '{}:{}'", channelUID,
abilityName, propertyName);
}
return null;
}
/**
* Converts an openHAB type to a Gardena command property.
*/
private Object convertFromType(Type type) {
if (type instanceof OnOffType) {
return type == OnOffType.ON ? Boolean.TRUE : Boolean.FALSE;
} else if (type instanceof DecimalType) {
return ((DecimalType) type).intValue();
} else if (type instanceof StringType) {
return ((StringType) type).toFullString();
logger.warn("Channel '{}' cannot be updated as device does not contain propertyPath '{}'", channelUID,
propertyPath);
} catch (ClassCastException ex) {
logger.warn("Value of propertyPath '{}' can not be casted to {}: {}", propertyPath, acceptedItemType,
ex.getMessage());
}
return null;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Command received: {}", command);
try {
GardenaSmartCommandName commandName = getCommandName(channelUID);
logger.debug("Received Gardena command: {}", commandName);
boolean isOnCommand = command instanceof OnOffType && ((OnOffType) command) == OnOffType.ON;
String dataItemProperty = getDeviceDataItemProperty(channelUID);
if (RefreshType.REFRESH == command) {
logger.debug("Refreshing channel '{}'", channelUID);
if (commandName != null && commandName.toString().startsWith("MEASURE_")) {
getGardenaSmart().sendCommand(getDevice(), commandName, null);
logger.debug("Refreshing Gardena connection");
getGardenaSmart().restartWebsockets();
} else if (isLocalDurationCommand(channelUID)) {
QuantityType<?> quantityType = (QuantityType<?>) command;
getDevice().getLocalService(dataItemProperty).commandDuration = quantityType.intValue() * 60;
} else if (isOnCommand) {
GardenaCommand gardenaCommand = getGardenaCommand(dataItemProperty, channelUID);
logger.debug("Received Gardena command: {}, {}", gardenaCommand.getClass().getSimpleName(),
gardenaCommand.attributes.command);
DataItem<?> dataItem = PropertyUtils.getPropertyValue(getDevice(), dataItemProperty, DataItem.class);
if (dataItem == null) {
logger.warn("DataItem {} is empty, ignoring command.", dataItemProperty);
} else {
updateChannel(channelUID);
getGardenaSmart().sendCommand(dataItem, gardenaCommand);
commandResetFuture = scheduler.schedule(() -> {
updateState(channelUID, OnOffType.OFF);
}, 3, TimeUnit.SECONDS);
}
} else if (commandName != null) {
getGardenaSmart().sendCommand(getDevice(), commandName, convertFromType(command));
}
} catch (AccountHandlerNotAvailableException | GardenaDeviceNotFoundException ex) {
// ignore
} catch (Exception ex) {
logger.warn("{}", ex.getMessage(), ex);
logger.warn("{}", ex.getMessage());
final Bridge bridge;
final ThingHandler handler;
if ((bridge = getBridge()) != null && (handler = bridge.getHandler()) != null) {
((GardenaSmartEventListener) handler).onError();
}
}
}
/**
* Returns the Gardena command from the channel.
*/
private GardenaSmartCommandName getCommandName(ChannelUID channelUID) {
switch (channelUID.getId()) {
case "mower#park_until_further_notice":
return PARK_UNTIL_FURTHER_NOTICE;
case "mower#park_until_next_timer":
return PARK_UNTIL_NEXT_TIMER;
case "mower#start_override_timer":
return START_OVERRIDE_TIMER;
case "mower#start_resume_schedule":
return START_RESUME_SCHEDULE;
case "mower#duration_property":
return DURATION_PROPERTY;
case "ambient_temperature#temperature":
return MEASURE_AMBIENT_TEMPERATURE;
case "soil_temperature#temperature":
return MEASURE_SOIL_TEMPERATURE;
case "humidity#humidity":
return MEASURE_SOIL_HUMIDITY;
case "light#light":
return MEASURE_LIGHT;
case "outlet#button_manual_override_time":
return OUTLET_MANUAL_OVERRIDE_TIME;
case "outlet#valve_open":
return OUTLET_VALVE;
case "power#power_timer":
return POWER_TIMER;
case "watering#watering_timer_1":
return WATERING_TIMER_VALVE_1;
case "watering#watering_timer_2":
return WATERING_TIMER_VALVE_2;
case "watering#watering_timer_3":
return WATERING_TIMER_VALVE_3;
case "watering#watering_timer_4":
return WATERING_TIMER_VALVE_4;
case "watering#watering_timer_5":
return WATERING_TIMER_VALVE_5;
case "watering#watering_timer_6":
return WATERING_TIMER_VALVE_6;
case "manual_watering#manual_watering_timer":
return PUMP_MANUAL_WATERING_TIMER;
default:
return null;
private GardenaCommand getGardenaCommand(String dataItemProperty, ChannelUID channelUID)
throws GardenaException, AccountHandlerNotAvailableException {
String commandName = channelUID.getIdWithoutGroup().toUpperCase();
String groupId = channelUID.getGroupId();
if (groupId != null) {
if (groupId.startsWith("valve") && groupId.endsWith("_commands")) {
return new ValveCommand(ValveControl.valueOf(commandName),
getDevice().getLocalService(dataItemProperty).commandDuration);
} else if ("mower_commands".equals(groupId)) {
return new MowerCommand(MowerControl.valueOf(commandName),
getDevice().getLocalService(dataItemProperty).commandDuration);
} else if ("valveSet_commands".equals(groupId)) {
return new ValveSetCommand(ValveSetControl.valueOf(commandName));
} else if ("powerSocket_commands".equals(groupId)) {
return new PowerSocketCommand(PowerSocketControl.valueOf(commandName),
getDevice().getLocalService(dataItemProperty).commandDuration);
}
}
throw new GardenaException("Command " + channelUID.getId() + " not found or groupId null");
}
/**
* Updates the thing status based on the Gardena device status.
*/
protected void updateStatus(Device device) {
String connectionStatus = "";
try {
connectionStatus = device.getAbility(ABILITY_RADIO).getProperty(PROPERTY_CONNECTION_STATUS)
.getValueAsString();
} catch (GardenaException ex) {
// ignore, device has no connection status property
}
boolean isUnreach = PROPERTY_CONNECTION_STATUS_UNREACH_VALUE.equals(connectionStatus);
ThingStatus oldStatus = thing.getStatus();
ThingStatus newStatus = ThingStatus.ONLINE;
ThingStatusDetail newDetail = ThingStatusDetail.NONE;
if (isUnreach) {
if (!CONNECTION_STATUS_ONLINE.equals(device.common.attributes.rfLinkState.value)) {
newStatus = ThingStatus.OFFLINE;
newDetail = ThingStatusDetail.COMMUNICATION_ERROR;
} else if (!device.isConfigurationSynchronized()) {
newStatus = thing.getStatus();
newDetail = ThingStatusDetail.CONFIGURATION_PENDING;
}
if (oldStatus != newStatus || thing.getStatusInfo().getStatusDetail() != newDetail) {
@ -320,32 +281,22 @@ public class GardenaThingHandler extends BaseThingHandler {
}
}
@Override
public void handleConfigurationUpdate(Map<String, Object> configurationParameters)
throws ConfigValidationException {
validateConfigurationParameters(configurationParameters);
try {
GardenaSmart gardena = getGardenaSmart();
Device device = gardena.getDevice(UidUtils.getGardenaDeviceId(getThing()));
for (Entry<String, Object> configurationParmeter : configurationParameters.entrySet()) {
String key = configurationParmeter.getKey();
Object newValue = configurationParmeter.getValue();
if (newValue != null && SETTING_TURN_ON_PRESSURE.equals(key)) {
newValue = new Double((String) newValue);
}
Setting setting = device.getSetting(key);
if (ObjectUtils.notEqual(setting.getValue(), newValue)) {
gardena.sendSetting(setting, newValue);
setting.setValue(newValue);
}
}
updateSettings(device);
} catch (GardenaException | AccountHandlerNotAvailableException ex) {
logger.warn("Error setting thing properties: {}", ex.getMessage(), ex);
/**
* Returns the device property for the dataItem from the channel.
*/
private String getDeviceDataItemProperty(ChannelUID channelUID) throws GardenaException {
String dataItemProperty = StringUtils.substringBeforeLast(channelUID.getGroupId(), "_");
if (dataItemProperty != null) {
return dataItemProperty;
}
throw new GardenaException("Can't extract dataItemProperty from channel group " + channelUID.getGroupId());
}
/**
* Returns true, if the channel is the duration command.
*/
private boolean isLocalDurationCommand(ChannelUID channelUID) {
return "commandDuration".equals(channelUID.getIdWithoutGroup());
}
/**
@ -356,17 +307,20 @@ public class GardenaThingHandler extends BaseThingHandler {
}
/**
* Returns the Gardena Smart Home implementation if the bridge is available.
* Returns the Gardena smart system implementation if the bridge is available.
*/
private GardenaSmart getGardenaSmart() throws AccountHandlerNotAvailableException {
if (getBridge() == null || getBridge().getHandler() == null
|| ((GardenaAccountHandler) getBridge().getHandler()).getGardenaSmart() == null) {
if (thing.getStatus() != ThingStatus.INITIALIZING) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR);
final Bridge bridge;
final ThingHandler handler;
if ((bridge = getBridge()) != null && (handler = bridge.getHandler()) != null) {
final GardenaSmart gardenaSmart = ((GardenaAccountHandler) handler).getGardenaSmart();
if (gardenaSmart != null) {
return gardenaSmart;
}
throw new AccountHandlerNotAvailableException("Gardena AccountHandler not yet available!");
}
return ((GardenaAccountHandler) getBridge().getHandler()).getGardenaSmart();
if (thing.getStatus() != ThingStatus.INITIALIZING) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR);
}
throw new AccountHandlerNotAvailableException("Gardena AccountHandler not yet available!");
}
}

View File

@ -1,87 +0,0 @@
/**
* 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.gardena.internal.model;
import java.util.ArrayList;
import java.util.List;
import org.openhab.binding.gardena.internal.exception.GardenaException;
/**
* Represents a Gardena ability.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Ability {
private String name;
private String type;
private transient Device device;
private List<Property> properties = new ArrayList<>();
/**
* Returns the name of the ability.
*/
public String getName() {
return name;
}
/**
* Returns the type of the ability.
*/
public String getType() {
return type;
}
/**
* Returns a list of properties of the ability.
*/
public List<Property> getProperties() {
return properties;
}
/**
* Adds a property to this ability.
*/
public void addProperty(Property property) {
property.setAbility(this);
properties.add(property);
}
/**
* Returns the property with the specified name.
*/
public Property getProperty(String name) throws GardenaException {
for (Property property : properties) {
if (property.getName().equals(name)) {
return property;
}
}
throw new GardenaException("Property '" + name + "' not found in ability '" + this.name + "'");
}
/**
* Returns the device of the ability.
*/
public Device getDevice() {
return device;
}
/**
* Sets the name of the ability.
*/
public void setDevice(Device device) {
this.device = device;
}
}

View File

@ -0,0 +1,43 @@
/**
* 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.gardena.internal.model;
import java.lang.reflect.Type;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gardena.internal.exception.GardenaException;
import org.openhab.binding.gardena.internal.model.dto.api.DataItem;
import com.google.gson.*;
/**
* Custom deserializer for Gardena DataItems.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class DataItemDeserializer implements JsonDeserializer<DataItem<?>> {
private static Gson gson = new GsonBuilder().create();
@Override
public @Nullable DataItem<?> deserialize(JsonElement element, Type type, JsonDeserializationContext ctx)
throws JsonParseException {
try {
JsonObject jsonObj = element.getAsJsonObject();
return gson.fromJson(element, DataItemFactory.create(jsonObj.get("type").getAsString()));
} catch (GardenaException ex) {
throw new JsonParseException(ex.getMessage(), ex);
}
}
}

View File

@ -0,0 +1,48 @@
/**
* 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.gardena.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.gardena.internal.exception.GardenaException;
import org.openhab.binding.gardena.internal.model.dto.api.*;
/**
* Creates the dataItem object based on the device type.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class DataItemFactory {
public static Class<? extends DataItem<?>> create(String type) throws GardenaException {
switch (type) {
case "LOCATION":
return LocationDataItem.class;
case "DEVICE":
return DeviceDataItem.class;
case "COMMON":
return CommonServiceDataItem.class;
case "MOWER":
return MowerServiceDataItem.class;
case "POWER_SOCKET":
return PowerSocketServiceDataItem.class;
case "VALVE":
return ValveServiceDataItem.class;
case "VALVE_SET":
return ValveSetServiceDataItem.class;
case "SENSOR":
return SensorServiceDataItem.class;
default:
throw new GardenaException("Unknown DataItem type: " + type);
}
}
}

View File

@ -1,147 +0,0 @@
/**
* 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.gardena.internal.model;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.openhab.binding.gardena.internal.exception.GardenaException;
import com.google.gson.annotations.SerializedName;
/**
* Represents a Gardena device.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Device {
private String id;
private String name;
private String description;
private String category;
@SerializedName("configuration_synchronized")
private boolean configurationSynchronized;
private List<Ability> abilities = new ArrayList<>();
@SerializedName("scheduled_events")
private List<ScheduledEvent> scheduledEvents = new ArrayList<>();
private transient Location location;
private List<Setting> settings = new ArrayList<>();
/**
* Returns the id of the device.
*/
public String getId() {
return id;
}
/**
* Returns the name of the device.
*/
public String getName() {
return name;
}
/**
* Returns the description of the device.
*/
public String getDescription() {
return description;
}
/**
* Returns the category of the device.
*/
public String getCategory() {
return category;
}
/**
* Returns true, if all configurations are synchronized.
*/
public boolean isConfigurationSynchronized() {
return configurationSynchronized;
}
/**
* Returns a list of abilities of the device.
*/
public List<Ability> getAbilities() {
return abilities;
}
/**
* Returns a list of scheduled events of the device.
*/
public List<ScheduledEvent> getScheduledEvents() {
return scheduledEvents;
}
/**
* Returns the location of the device.
*/
public Location getLocation() {
return location;
}
/**
* Sets the location of the device.
*/
public void setLocation(Location location) {
this.location = location;
}
/**
* Returns the ability with the specified name.
*/
public Ability getAbility(String name) throws GardenaException {
for (Ability ability : abilities) {
if (ability.getName().equals(name)) {
return ability;
}
}
throw new GardenaException("Ability '" + name + "' not found in device '" + this.name + "'");
}
public List<Setting> getSettings() {
return settings;
}
/**
* Returns the setting with the specified name.
*/
public Setting getSetting(String name) throws GardenaException {
for (Setting setting : settings) {
if (setting.getName().equals(name)) {
return setting;
}
}
throw new GardenaException("Setting '" + name + "' not found in device '" + this.name + "'");
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(id).toHashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Device)) {
return false;
}
Device comp = (Device) obj;
return new EqualsBuilder().append(comp.getId(), id).isEquals();
}
}

View File

@ -1,63 +0,0 @@
/**
* 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.gardena.internal.model;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
/**
* Represents a Gardena error.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Error {
private String id;
private String status;
private String title;
private String detail;
/**
* Returns the id of the error.
*/
public String getId() {
return id;
}
/**
* Returns the status of the error.
*/
public String getStatus() {
return status;
}
/**
* Returns the title of the error.
*/
public String getTitle() {
return title;
}
/**
* Returns the detail of the error.
*/
public String getDetail() {
return detail;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("id", id).append("status", status)
.append("title", title).append("detail", detail).toString();
}
}

View File

@ -1,45 +0,0 @@
/**
* 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.gardena.internal.model;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
/**
* Represents a List of Gardena errors.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Errors {
private List<Error> errors = new ArrayList<>();
/**
* Returns a list of Gardena errors.
*/
public List<Error> getErrors() {
return errors;
}
@Override
public String toString() {
ToStringBuilder tsb = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
for (Error error : errors) {
tsb.append(error);
}
return tsb.toString();
}
}

View File

@ -1,69 +0,0 @@
/**
* 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.gardena.internal.model;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import com.google.gson.annotations.SerializedName;
/**
* Represents a Gardena location.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Location {
private String id;
private String name;
@SerializedName("devices")
public List<String> deviceIds = new ArrayList<>();
/**
* Returns the id of the location.
*/
public String getId() {
return id;
}
/**
* Returns the name of the location.
*/
public String getName() {
return name;
}
/**
* Returns the device ids of the location.
*/
public List<String> getDeviceIds() {
return deviceIds;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(id).toHashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Location)) {
return false;
}
Location comp = (Location) obj;
return new EqualsBuilder().append(comp.getId(), id).isEquals();
}
}

View File

@ -1,117 +0,0 @@
/**
* 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.gardena.internal.model;
import java.util.Date;
import java.util.List;
import org.openhab.binding.gardena.internal.GardenaSmartCommandName;
import com.google.gson.annotations.SerializedName;
/**
* Represents a Gardena property.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Property {
private String name;
private PropertyValue value;
private Date timestamp;
private String unit;
private boolean writeable;
@SerializedName("supported_values")
private List<String> supportedValues;
private transient Ability ability;
public Property() {
}
public Property(GardenaSmartCommandName commandName, String value) {
this.name = commandName.toString().toLowerCase();
this.value = new PropertyValue(value);
}
/**
* Returns the name of the property.
*/
public String getName() {
return name;
}
/**
* Returns the value of the property.
*/
public String getValueAsString() {
return value != null ? value.getValue() : null;
}
/**
* Returns the value of the property.
*/
public PropertyValue getValue() {
return value;
}
/**
* Sets the value of the property.
*/
public void setValue(PropertyValue value) {
this.value = value;
}
/**
* Returns the timestamp of the property.
*/
public Date getTimestamp() {
return timestamp;
}
/**
* Returns the unit of the property.
*/
public String getUnit() {
return unit;
}
/**
* Returns true, if the property is writeable.
*/
public boolean isWriteable() {
return writeable;
}
/**
* Returns a list of supported values.
*/
public List<String> getSupportedValues() {
return supportedValues;
}
/**
* Returns the ability of the property.
*/
public Ability getAbility() {
return ability;
}
/**
* Sets the ability of the property.
*/
public void setAbility(Ability ability) {
this.ability = ability;
}
}

View File

@ -1,43 +0,0 @@
/**
* 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.gardena.internal.model;
/**
* Represents a Gardena property value.
*
* @author Gerhard Riegler - Initial contribution
*/
public class PropertyValue {
private String value;
public PropertyValue() {
}
public PropertyValue(String value) {
this.value = value;
}
/**
* Returns the value of the property.
*/
public String getValue() {
return value;
}
/**
* Sets the value of the property.
*/
public void setValue(String value) {
this.value = value;
}
}

View File

@ -1,40 +0,0 @@
/**
* 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.gardena.internal.model;
import java.util.List;
/**
* Represents a Gardena recurrence.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Recurrence {
private String type;
private List<String> weekdays;
/**
* Returns the type of the recurrence.
*/
public String getType() {
return type;
}
/**
* Returns a list of weekdays.
*/
public List<String> getWeekdays() {
return weekdays;
}
}

View File

@ -1,75 +0,0 @@
/**
* 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.gardena.internal.model;
import com.google.gson.annotations.SerializedName;
/**
* Represents a Gardena scheduled event.
*
* @author Gerhard Riegler - Initial contribution
*/
public class ScheduledEvent {
private String id;
private String type;
@SerializedName("start_at")
private String start;
@SerializedName("end_at")
private String end;
private String weekday;
private Recurrence recurrence = new Recurrence();
/**
* Returns the id of the scheduled event.
*/
public String getId() {
return id;
}
/**
* Returns the type of the scheduled event.
*/
public String getType() {
return type;
}
/**
* Returns the start of the scheduled event.
*/
public String getStart() {
return start;
}
/**
* Returns the end of the scheduled event.
*/
public String getEnd() {
return end;
}
/**
* Returns the weekday of the scheduled event.
*/
public String getWeekday() {
return weekday;
}
/**
* Returns the recurrence of the scheduled event.
*/
public Recurrence getRecurrence() {
return recurrence;
}
}

View File

@ -1,54 +0,0 @@
/**
* 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.gardena.internal.model;
import com.google.gson.annotations.SerializedName;
/**
* Represents a Gardena session.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Session {
@SerializedName("id")
private String token;
private long created;
@SerializedName("attributes")
private SessionAttributes sessionAttributes = new SessionAttributes();
public Session() {
this.created = System.currentTimeMillis();
}
/**
* Returns the token of the session.
*/
public String getToken() {
return token;
}
/**
* Returns the creation timestamp of the session.
*/
public long getCreated() {
return created;
}
/**
* Returns the session attributes.
*/
public SessionAttributes getSessionAttributes() {
return sessionAttributes;
}
}

View File

@ -1,56 +0,0 @@
/**
* 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.gardena.internal.model;
import com.google.gson.annotations.SerializedName;
/**
* Session attributes for valid Gardena JSON serialization.
*
* @author Gerhard Riegler - Initial contribution
*/
public class SessionAttributes {
@SerializedName("user_id")
private String userId;
@SerializedName("provider")
private String provider;
/**
* Returns the user id.
*/
public String getUserId() {
return userId;
}
/**
* Sets the user id.
*/
public void setUserId(String userId) {
this.userId = userId;
}
/**
* Returns the provider.
*/
public String getProvider() {
return provider;
}
/**
* Sets the provider.
*/
public void setProvider(String provider) {
this.provider = provider;
}
}

View File

@ -1,46 +0,0 @@
/**
* 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.gardena.internal.model;
import com.google.gson.annotations.SerializedName;
/**
* Session wrapper for valid Gardena JSON serialization.
*
* @author Gerhard Riegler - Initial contribution
*/
public class SessionWrapper {
@SerializedName("data")
private Session session;
public SessionWrapper() {
}
public SessionWrapper(Session session) {
this.session = session;
}
/**
* Returns the session.
*/
public Session getSession() {
return session;
}
/**
* Sets the session.
*/
public void setSession(Session session) {
this.session = session;
}
}

View File

@ -1,68 +0,0 @@
/**
* 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.gardena.internal.model;
/**
* Represents a Gardena setting.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Setting {
private String name;
private String id;
private Object value;
private transient Device device;
/**
* Returns the name of the setting.
*/
public String getName() {
return name;
}
/**
* Returns the id of the setting.
*/
public String getId() {
return id;
}
/**
* Returns the value of the setting.
*/
public Object getValue() {
return value;
}
/**
* Sets the name of the setting.
*/
public void setValue(Object value) {
this.value = value;
}
/**
* Returns the device of the setting.
*/
public Device getDevice() {
return device;
}
/**
* Sets the name of the setting.
*/
public void setDevice(Device device) {
this.device = device;
}
}

View File

@ -1,94 +0,0 @@
/**
* 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.gardena.internal.model.command;
import com.google.gson.annotations.SerializedName;
/**
* Base class for a Gardena command with parameters.
*
* @author Gerhard Riegler - Initial contribution
*/
public abstract class Command {
@SerializedName(value = "name")
protected String command;
protected CommandParameters parameters;
/**
* Creates a command with the given name.
*/
public Command(String command) {
this.command = command;
}
/**
* Returns the command name.
*/
public String getCommand() {
return command;
}
/**
* Returns the parameters of the command.
*/
public CommandParameters getParameters() {
return parameters;
}
/**
* Sets the parameters of the command.
*/
public void setParameters(CommandParameters parameters) {
this.parameters = parameters;
}
/**
* Class to hold the command parameters.
*
* @author Gerhard Riegler - Initial contribution
*/
public class CommandParameters {
private String duration;
@SerializedName("manual_override")
private String manualOverride;
/**
* Returns the duration parameter.
*/
public String getDuration() {
return duration;
}
/**
* Sets the duration parameter.
*/
public void setDuration(String duration) {
this.duration = duration;
}
/**
* Returns the manual override parameter.
*/
public String getManualOverride() {
return manualOverride;
}
/**
* Sets the manual override parameter.
*/
public void setManualOverride(String manualOverride) {
this.manualOverride = manualOverride;
}
}
}

View File

@ -1,29 +0,0 @@
/**
* 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.gardena.internal.model.command;
/**
* Command to manually override the mower timer.
*
* @author Gerhard Riegler - Initial contribution
*/
public class MowerStartOverrideTimerCommand extends Command {
private static final String COMMAND = "start_override_timer";
public MowerStartOverrideTimerCommand(String durationInMinutes) {
super(COMMAND);
parameters = new CommandParameters();
parameters.setDuration(durationInMinutes);
}
}

View File

@ -1,27 +0,0 @@
/**
* 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.gardena.internal.model.command;
/**
* Sensor command to measure the ambient temperatur.
*
* @author Gerhard Riegler - Initial contribution
*/
public class SensorMeasureAmbientTemperatureCommand extends Command {
private static final String COMMAND = "measure_ambient_temperature";
public SensorMeasureAmbientTemperatureCommand() {
super(COMMAND);
}
}

View File

@ -1,27 +0,0 @@
/**
* 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.gardena.internal.model.command;
/**
* Sensor command to measure the soil humidity.
*
* @author Gerhard Riegler - Initial contribution
*/
public class SensorMeasureSoilHumidityCommand extends Command {
private static final String COMMAND = "measure_soil_humidity";
public SensorMeasureSoilHumidityCommand() {
super(COMMAND);
}
}

View File

@ -1,27 +0,0 @@
/**
* 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.gardena.internal.model.command;
/**
* Sensor command to measure the soil temperature.
*
* @author Gerhard Riegler - Initial contribution
*/
public class SensorMeasureSoilTemperatureCommand extends Command {
private static final String COMMAND = "measure_soil_temperature";
public SensorMeasureSoilTemperatureCommand() {
super(COMMAND);
}
}

View File

@ -1,58 +0,0 @@
/**
* 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.gardena.internal.model.command;
import com.google.gson.annotations.SerializedName;
/**
* Common command to set a device setting.
*
* @author Gerhard Riegler - Initial contribution
*/
public class SettingCommand extends Command {
private Object value;
@SerializedName("device")
private String deviceId;
public SettingCommand(String name) {
super(name);
}
/**
* Returns the value of the setting command.
*/
public Object getValue() {
return value;
}
/**
* Sets the value of the setting command.
*/
public void setValue(Object value) {
this.value = value;
}
/**
* Returns the device id of the setting command.
*/
public String getDeviceId() {
return deviceId;
}
/**
* Sets the device id of the setting command.
*/
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
}

View File

@ -1,36 +0,0 @@
/**
* 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.gardena.internal.model.command;
import com.google.gson.annotations.SerializedName;
/**
* SettingComand wrapper for valid Gardena JSON serialization.
*
* @author Gerhard Riegler - Initial contribution
*/
public class SettingCommandWrapper {
@SerializedName("settings")
private SettingCommand command;
public SettingCommandWrapper(SettingCommand command) {
this.command = command;
}
/**
* Returns the setting command.
*/
public SettingCommand getCommand() {
return command;
}
}

View File

@ -1,30 +0,0 @@
/**
* 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.gardena.internal.model.command;
/**
* Command to start the manual watering.
*
* @author Gerhard Riegler - Initial contribution
*/
public class WateringManualOverrideCommand extends Command {
private static final String COMMAND = "manual_override";
public WateringManualOverrideCommand(String durationInMinutes) {
super(COMMAND);
parameters = new CommandParameters();
parameters.setDuration(durationInMinutes);
parameters.setManualOverride("open");
}
}

View File

@ -1,36 +0,0 @@
/**
* 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.gardena.internal.model.deser;
import java.lang.reflect.Type;
import java.util.Date;
import org.openhab.binding.gardena.internal.util.DateUtils;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
/**
* Custom deserializer for date types.
*
* @author Gerhard Riegler - Initial contribution
*/
public class DateDeserializer implements JsonDeserializer<Date> {
@Override
public Date deserialize(JsonElement element, Type type, JsonDeserializationContext ctx) throws JsonParseException {
return DateUtils.parseToDate(element.getAsString());
}
}

View File

@ -1,70 +0,0 @@
/**
* 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.gardena.internal.model.deser;
import java.lang.reflect.Type;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.gardena.internal.model.PropertyValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
/**
* Custom deserializer for Gardena complex property value type.
*
* @author Gerhard Riegler - Initial contribution
*/
public class PropertyValueDeserializer implements JsonDeserializer<PropertyValue> {
private final Logger logger = LoggerFactory.getLogger(PropertyValueDeserializer.class);
private static final String PROPERTY_DURATION = "duration";
private static final String PROPERTY_TYPE = "type";
private static final String PROPERTY_MAC = "mac";
private static final String PROPERTY_ISCONNECTED = "isconnected";
@Override
public PropertyValue deserialize(JsonElement element, Type type, JsonDeserializationContext ctx)
throws JsonParseException {
if (element.isJsonObject()) {
JsonObject jsonObj = element.getAsJsonObject();
if (jsonObj.has(PROPERTY_DURATION)) {
long duration = jsonObj.get(PROPERTY_DURATION).getAsLong();
if (duration != 0) {
duration = Math.round(duration / 60.0);
}
return new PropertyValue(String.valueOf(duration));
} else if (jsonObj.has(PROPERTY_TYPE)) {
return new PropertyValue(jsonObj.get(PROPERTY_TYPE).getAsString());
} else if (jsonObj.has(PROPERTY_MAC) && jsonObj.has(PROPERTY_ISCONNECTED)) {
// ignore known gateway properties
return new PropertyValue();
} else {
logger.warn("Unsupported json value object, returning empty value");
return new PropertyValue();
}
} else if (element.isJsonArray()) {
JsonArray jsonArray = element.getAsJsonArray();
return new PropertyValue(StringUtils.join(jsonArray.iterator(), ","));
} else {
return new PropertyValue(element.getAsString());
}
}
}

View File

@ -0,0 +1,161 @@
/**
* 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.gardena.internal.model.dto;
import static org.openhab.binding.gardena.internal.GardenaBindingConstants.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.openhab.binding.gardena.internal.exception.GardenaException;
import org.openhab.binding.gardena.internal.model.dto.api.*;
import org.openhab.binding.gardena.internal.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents a Gardena device.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Device {
private final Logger logger = LoggerFactory.getLogger(Device.class);
private transient static final String DEVICE_TYPE_PREFIX = "gardena smart";
public boolean active = true;
public String id;
public String deviceType;
public String location;
public CommonServiceDataItem common;
public MowerServiceDataItem mower;
public PowerSocketServiceDataItem powerSocket;
public SensorServiceDataItem sensor;
public ValveServiceDataItem valve;
public ValveServiceDataItem valveOne;
public ValveServiceDataItem valveTwo;
public ValveServiceDataItem valveThree;
public ValveServiceDataItem valveFour;
public ValveServiceDataItem valveFive;
public ValveServiceDataItem valveSix;
public ValveSetServiceDataItem valveSet;
private Map<String, LocalService> localServices = new HashMap<>();
public Device(String id) {
this.id = id;
}
/**
* Returns the local service or creates one if it does not exist.
*/
public LocalService getLocalService(String key) {
LocalService localService = localServices.get(key);
if (localService == null) {
localService = new LocalService();
localServices.put(key, localService);
localService.commandDuration = 3600;
}
return localService;
}
/**
* Evaluates the device type.
*/
public void evaluateDeviceType() {
if (deviceType == null) {
if (common.attributes.modelType.value.toLowerCase().startsWith(DEVICE_TYPE_PREFIX)) {
String modelType = common.attributes.modelType.value.toLowerCase();
modelType = modelType.substring(14);
deviceType = modelType.replace(" ", "_");
} else {
// workaround: we have to guess the device type, valves cannot be identified if modeType is wrong
if (mower != null) {
deviceType = DEVICE_TYPE_MOWER;
} else if (powerSocket != null) {
deviceType = DEVICE_TYPE_POWER;
} else if (sensor != null) {
deviceType = DEVICE_TYPE_SENSOR;
}
}
if (deviceType == null) {
logger.warn("Can't identify device with id {}, wrong modelType sent from the Gardena API", id);
active = false;
}
}
}
/**
* Assigns the dataItem to the corresponding property.
*/
public void setDataItem(DataItem<?> dataItem) throws GardenaException {
if (dataItem instanceof DeviceDataItem) {
// ignore
} else if (dataItem instanceof LocationDataItem) {
LocationDataItem locationDataItem = (LocationDataItem) dataItem;
if (locationDataItem.attributes != null) {
location = locationDataItem.attributes.name;
}
} else if (dataItem instanceof CommonServiceDataItem) {
common = (CommonServiceDataItem) dataItem;
} else if (dataItem instanceof MowerServiceDataItem) {
mower = (MowerServiceDataItem) dataItem;
} else if (dataItem instanceof PowerSocketServiceDataItem) {
powerSocket = (PowerSocketServiceDataItem) dataItem;
} else if (dataItem instanceof SensorServiceDataItem) {
sensor = (SensorServiceDataItem) dataItem;
} else if (dataItem instanceof ValveSetServiceDataItem) {
valveSet = (ValveSetServiceDataItem) dataItem;
} else if (dataItem instanceof ValveServiceDataItem) {
String valveNumber = StringUtils.substringAfterLast(dataItem.id, ":");
if (valveNumber != null
&& (valveNumber.equals("") || valveNumber.equals("wc") || valveNumber.equals("0"))) {
valve = (ValveServiceDataItem) dataItem;
} else if ("1".equals(valveNumber)) {
valveOne = (ValveServiceDataItem) dataItem;
} else if ("2".equals(valveNumber)) {
valveTwo = (ValveServiceDataItem) dataItem;
} else if ("3".equals(valveNumber)) {
valveThree = (ValveServiceDataItem) dataItem;
} else if ("4".equals(valveNumber)) {
valveFour = (ValveServiceDataItem) dataItem;
} else if ("5".equals(valveNumber)) {
valveFive = (ValveServiceDataItem) dataItem;
} else if ("6".equals(valveNumber)) {
valveSix = (ValveServiceDataItem) dataItem;
} else {
throw new GardenaException("Unknown valveNumber in dataItem with id: " + dataItem.id);
}
} else {
throw new GardenaException("Unknown dataItem with id: " + dataItem.id);
}
if (common != null && common.attributes != null) {
common.attributes.lastUpdate.timestamp = new Date();
}
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Device)) {
return false;
}
Device comp = (Device) obj;
return comp.id.equals(id);
}
}

View File

@ -0,0 +1,23 @@
/**
* 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.gardena.internal.model.dto;
/**
* A local service exists only in openHAB and the state is not saved on restarts.
*
* @author Gerhard Riegler - Initial contribution
*/
public class LocalService {
public Integer commandDuration;
}

View File

@ -0,0 +1,29 @@
/**
* 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.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class CommonService {
public UserDefinedNameWrapper name;
public TimestampedIntegerValue batteryLevel;
public TimestampedStringValue batteryState;
public TimestampedIntegerValue rfLinkLevel;
public StringValue serial;
public StringValue modelType;
public TimestampedStringValue rfLinkState;
public TimestampValue lastUpdate = new TimestampValue();
}

View File

@ -10,18 +10,13 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.command;
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Sensor command to measure the light.
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class SensorMeasureLightCommand extends Command {
private static final String COMMAND = "measure_light";
public SensorMeasureLightCommand() {
super(COMMAND);
}
public class CommonServiceDataItem extends DataItem<CommonService> {
}

View File

@ -10,18 +10,14 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.command;
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Command to cancel the manual watering.
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class WateringCancelOverrideCommand extends Command {
private static final String COMMAND = "cancel_override";
public WateringCancelOverrideCommand() {
super(COMMAND);
}
public class CreateWebSocket {
public String locationId;
}

View File

@ -10,18 +10,13 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.command;
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Command to resume the mower scheduler.
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class MowerStartResumeScheduleCommand extends Command {
private static final String COMMAND = "start_resume_schedule";
public MowerStartResumeScheduleCommand() {
super(COMMAND);
}
public class CreateWebSocketDataItem extends DataItem<CreateWebSocket> {
}

View File

@ -0,0 +1,31 @@
/**
* 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.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class CreateWebSocketRequest {
public CreateWebSocketDataItem data;
public CreateWebSocketRequest(String locationId) {
data = new CreateWebSocketDataItem();
data.id = "wsreq-" + locationId;
data.type = "WEBSOCKET";
data.attributes = new CreateWebSocket();
data.attributes.locationId = locationId;
}
}

View File

@ -0,0 +1,32 @@
/**
* 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.gardena.internal.model.dto.api;
import org.openhab.binding.gardena.internal.util.StringUtils;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class DataItem<T> {
public String id;
public String type;
public String getDeviceId() {
return StringUtils.substringBeforeLast(id, ":");
}
public T attributes;
}

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class DeviceDataItem extends DataItem<Void> {
}

View File

@ -0,0 +1,23 @@
/**
* 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.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class IntegerValue {
public Integer value;
}

View File

@ -10,13 +10,14 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model;
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Empty class that represents a emtpy result from Gardena Smart Home.
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class NoResult {
public class Location {
public String name;
}

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class LocationDataItem extends DataItem<Location> {
}

View File

@ -10,24 +10,17 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model;
package org.openhab.binding.gardena.internal.model.dto.api;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a List of Gardena devices.
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Devices {
private List<Device> devices = new ArrayList<>();
/**
* Returns a list of Gardena devices.
*/
public List<Device> getDevices() {
return devices;
}
public class LocationResponse {
public LocationDataItem data;
public List<DataItem<?>> included;
}

View File

@ -0,0 +1,25 @@
/**
* 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.gardena.internal.model.dto.api;
import java.util.List;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class LocationsResponse {
public List<LocationDataItem> data;
}

View File

@ -10,18 +10,17 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.exception;
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Exception for invalid user and password.
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class GardenaUnauthorizedException extends GardenaException {
private static final long serialVersionUID = 4343137351443555679L;
public GardenaUnauthorizedException(Throwable ex) {
super(ex);
}
public class MowerService {
public TimestampedStringValue state;
public TimestampedStringValue activity;
public TimestampedStringValue lastErrorCode;
public IntegerValue operatingHours;
}

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class MowerServiceDataItem extends DataItem<MowerService> {
}

View File

@ -0,0 +1,71 @@
/**
* 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.gardena.internal.model.dto.api;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import com.google.gson.annotations.SerializedName;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class PostOAuth2Response {
// refresh token is valid 10 days
private transient Instant refreshTokenValidity = Instant.now().plus(10, ChronoUnit.DAYS).minus(1,
ChronoUnit.MINUTES);
private transient Instant accessTokenValidity;
@SerializedName("access_token")
public String accessToken;
// The scope of the token (what you are allowed to do)
public String scope;
// The expire time in seconds for the access token
@SerializedName("expires_in")
public Integer expiresIn;
@SerializedName("refresh_token")
public String refreshToken;
public String provider;
@SerializedName("user_id")
public String userId;
@SerializedName("token_type")
public String tokenType;
public void postProcess() {
accessTokenValidity = Instant.now().plus(expiresIn - 10, ChronoUnit.SECONDS);
}
public boolean isAccessTokenExpired() {
return Instant.now().isAfter(accessTokenValidity);
}
public boolean isRefreshTokenExpired() {
return Instant.now().isAfter(refreshTokenValidity);
}
@Override
public String toString() {
return "Token expiration: accessToken: " + ZonedDateTime.ofInstant(accessTokenValidity, ZoneId.systemDefault())
+ ", refreshToken: " + ZonedDateTime.ofInstant(refreshTokenValidity, ZoneId.systemDefault());
}
}

View File

@ -0,0 +1,26 @@
/**
* 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.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class PowerSocketService {
public TimestampedStringValue activity;
public TimestampedStringValue state;
public TimestampedStringValue lastErrorCode;
public TimestampedIntegerValue duration;
}

View File

@ -0,0 +1,21 @@
/**
* 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.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class PowerSocketServiceDataItem extends DataItem<PowerSocketService> {
}

View File

@ -10,17 +10,16 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.command;
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Command to park a mower until further notice.
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class MowerParkUntilFurtherNoticeCommand extends Command {
private static final String COMMAND = "park_until_further_notice";
public MowerParkUntilFurtherNoticeCommand() {
super(COMMAND);
}
public class SensorService {
public TimestampedIntegerValue soilHumidity;
public TimestampedIntegerValue soilTemperature;
public TimestampedIntegerValue ambientTemperature;
public TimestampedIntegerValue lightIntensity;
}

View File

@ -0,0 +1,21 @@
/**
* 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.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class SensorServiceDataItem extends DataItem<SensorService> {
}

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class StringValue {
public String value;
}

View File

@ -0,0 +1,21 @@
/**
* 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.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class TimestampValue extends TimestampedAttribute {
}

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.dto.api;
import java.util.Date;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public abstract class TimestampedAttribute {
public Date timestamp;
}

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class TimestampedIntegerValue extends TimestampedAttribute {
public Integer value;
}

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class TimestampedStringValue extends TimestampedAttribute {
public String value;
}

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class UserDefinedNameWrapper {
public String value;
}

View File

@ -0,0 +1,26 @@
/**
* 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.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class ValveService {
public UserDefinedNameWrapper name;
public TimestampedStringValue activity;
public TimestampedStringValue state;
public TimestampedStringValue lastErrorCode;
public TimestampedIntegerValue duration;
}

View File

@ -0,0 +1,21 @@
/**
* 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.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class ValveServiceDataItem extends DataItem<ValveService> {
}

View File

@ -0,0 +1,23 @@
/**
* 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.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class ValveSetService {
public TimestampedStringValue state;
public TimestampedStringValue lastErrorCode;
}

View File

@ -0,0 +1,21 @@
/**
* 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.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class ValveSetServiceDataItem extends DataItem<ValveSetService> {
}

View File

@ -0,0 +1,23 @@
/**
* 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.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class WebSocket {
public Integer validity;
public String url;
}

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class WebSocketCreatedResponse {
public WebSocketDataItem data;
}

View File

@ -0,0 +1,21 @@
/**
* 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.gardena.internal.model.dto.api;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class WebSocketDataItem extends DataItem<WebSocket> {
}

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.dto.command;
/**
* Represents a Gardena command object to send to the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public abstract class GardenaCommand {
public String id;
public String type;
public GardenaCommandAttributes attributes;
}

View File

@ -10,24 +10,19 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model;
import java.util.ArrayList;
import java.util.List;
package org.openhab.binding.gardena.internal.model.dto.command;
/**
* Represents a List of Gardena locations.
* Represents a Gardena command object that is sent to the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Locations {
public class GardenaCommandAttributes {
public String command;
public Integer seconds;
private List<Location> locations = new ArrayList<>();
/**
* Returns a list of Gardena locations.
*/
public List<Location> getLocations() {
return locations;
public GardenaCommandAttributes(String command, Integer seconds) {
this.command = command;
this.seconds = seconds;
}
}

View File

@ -10,18 +10,17 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gardena.internal.model.command;
package org.openhab.binding.gardena.internal.model.dto.command;
/**
* Command to park a mower until next timer.
* Represents a Gardena command object that is sent to the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class GardenaCommandRequest {
public GardenaCommand data;
public class MowerParkUntilNextTimerCommand extends Command {
private static final String COMMAND = "park_until_next_timer";
public MowerParkUntilNextTimerCommand() {
super(COMMAND);
public GardenaCommandRequest(GardenaCommand gardenaCommand) {
this.data = gardenaCommand;
}
}

View File

@ -0,0 +1,35 @@
/**
* 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.gardena.internal.model.dto.command;
/**
* Represents a Gardena command object that is sent to the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class MowerCommand extends GardenaCommand {
private static final String COMMAND_TYPE = "MOWER_CONTROL";
public enum MowerControl {
START_SECONDS_TO_OVERRIDE,
START_DONT_OVERRIDE,
PARK_UNTIL_NEXT_TASK,
PARK_UNTIL_FURTHER_NOTICE
}
public MowerCommand(MowerControl mowerControl, Integer seconds) {
this.id = "mcid";
this.type = COMMAND_TYPE;
this.attributes = new GardenaCommandAttributes(mowerControl.name(), seconds);
}
}

View File

@ -0,0 +1,36 @@
/**
* 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.gardena.internal.model.dto.command;
/**
* Represents a Gardena command object that is sent to the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class PowerSocketCommand extends GardenaCommand {
private static final String COMMAND_TYPE = "POWER_SOCKET_CONTROL";
public enum PowerSocketControl {
START_SECONDS_TO_OVERRIDE,
START_OVERRIDE,
STOP_UNTIL_NEXT_TASK,
PAUSE,
UNPAUSE
}
public PowerSocketCommand(PowerSocketControl powerSocketControl, Integer seconds) {
this.id = "pscid";
this.type = COMMAND_TYPE;
this.attributes = new GardenaCommandAttributes(powerSocketControl.name(), seconds);
}
}

View File

@ -0,0 +1,35 @@
/**
* 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.gardena.internal.model.dto.command;
/**
* Represents a Gardena command object that is sent to the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class ValveCommand extends GardenaCommand {
private static final String COMMAND_TYPE = "VALVE_CONTROL";
public static enum ValveControl {
START_SECONDS_TO_OVERRIDE,
STOP_UNTIL_NEXT_TASK,
PAUSE,
UNPAUSE
}
public ValveCommand(ValveControl valveControl, Integer seconds) {
this.id = "vcid";
this.type = COMMAND_TYPE;
this.attributes = new GardenaCommandAttributes(valveControl.name(), seconds);
}
}

View File

@ -0,0 +1,32 @@
/**
* 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.gardena.internal.model.dto.command;
/**
* Represents a Gardena command object that is sent to the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class ValveSetCommand extends GardenaCommand {
private static final String COMMAND_TYPE = "VALVE_SET_CONTROL";
public enum ValveSetControl {
STOP_UNTIL_NEXT_TASK
}
public ValveSetCommand(ValveSetControl valveSetControl) {
this.id = "vscid";
this.type = COMMAND_TYPE;
this.attributes = new GardenaCommandAttributes(valveSetControl.name(), null);
}
}

View File

@ -1,38 +0,0 @@
/**
* 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.gardena.internal.model.property;
/**
* Base class to send properties to Gardena.
*
* @author Gerhard Riegler - Initial contribution
*/
public abstract class BaseProperty {
private String name;
public BaseProperty(String name) {
this.name = name;
}
/**
* Returns the property name.
*/
public String getName() {
return name;
}
/**
* Returns the value of the property.
*/
public abstract String getValue();
}

View File

@ -1,46 +0,0 @@
/**
* 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.gardena.internal.model.property;
import com.google.gson.annotations.SerializedName;
/**
* Represents a Gardena complex property value for the irrigation control.
*
* @author Gerhard Riegler - Initial contribution
*/
public class IrrigationControlWateringProperty extends BaseProperty {
private IrrigationControlWateringValue value = new IrrigationControlWateringValue();
public IrrigationControlWateringProperty(String name, int duration, int valveId) {
super(name);
value.state = duration == 0 ? "idle" : "manual";
value.duration = duration;
value.valveId = valveId;
}
@Override
public String getValue() {
return String.valueOf(value.duration);
}
@SuppressWarnings("unused")
private class IrrigationControlWateringValue {
public String state;
public int duration;
@SerializedName("valve_id")
public int valveId;
}
}

View File

@ -1,46 +0,0 @@
/**
* 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.gardena.internal.model.property;
import com.google.gson.annotations.SerializedName;
/**
* Property wrapper for valid Gardena JSON serialization.
*
* @author Gerhard Riegler - Initial contribution
*/
public class PropertyWrapper {
@SerializedName("properties")
private BaseProperty property;
public PropertyWrapper() {
}
public PropertyWrapper(BaseProperty property) {
this.property = property;
}
/**
* Returns the property.
*/
public BaseProperty getProperty() {
return property;
}
/**
* Sets the property.
*/
public void setProperties(BaseProperty property) {
this.property = property;
}
}

View File

@ -1,35 +0,0 @@
/**
* 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.gardena.internal.model.property;
/**
* Represents a String Gardena property.
*
* @author Gerhard Riegler - Initial contribution
*/
public class StringProperty extends BaseProperty {
private String value;
public StringProperty(String name, String value) {
super(name);
this.value = value;
}
/**
* Returns the property value.
*/
@Override
public String getValue() {
return value;
}
}

View File

@ -1,75 +0,0 @@
/**
* 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.gardena.internal.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class to convert a String to a date or calendar.
*
* @author Gerhard Riegler - Initial contribution
*/
public class DateUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(DateUtils.class);
private static final String[] DATE_FORMATS = new String[] { "yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "yyyy-MM-dd'T'HH:mm'Z'" };
/**
* Converts a string to a Date, trying different date formats used by Gardena.
*/
public static Date parseToDate(String text) {
if (StringUtils.isNotBlank(text)) {
Date parsedDate = null;
for (String dateFormat : DATE_FORMATS) {
try {
parsedDate = new SimpleDateFormat(dateFormat).parse(text);
ZonedDateTime gmt = ZonedDateTime.ofInstant(parsedDate.toInstant(), ZoneOffset.UTC);
LocalDateTime here = gmt.withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
parsedDate = Date.from(here.toInstant(ZoneOffset.UTC));
break;
} catch (ParseException ex) {
}
}
if (parsedDate == null) {
LOGGER.error("Can't parse date {}", text);
}
return parsedDate;
} else {
return null;
}
}
/**
* Converts a string to a Calendar, trying different date formats used by Gardena.
*/
public static Calendar parseToCalendar(String text) {
Date parsedDate = parseToDate(text);
if (parsedDate != null) {
Calendar cal = Calendar.getInstance();
cal.setTime(parsedDate);
return cal;
}
return null;
}
}

View File

@ -0,0 +1,66 @@
/**
* 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.gardena.internal.util;
import java.lang.reflect.Field;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gardena.internal.exception.GardenaException;
/**
* Utility class to read nested properties.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class PropertyUtils {
/**
* Returns true if the property is null.
*/
public static boolean isNull(@Nullable Object instance, String propertyPath) throws GardenaException {
return getPropertyValue(instance, propertyPath, Object.class) == null;
}
/**
* Returns the property value from the object instance, nested properties are possible.
*/
public static <T> @Nullable T getPropertyValue(@Nullable Object instance, String propertyPath, Class<T> resultClass)
throws GardenaException {
String[] properties = propertyPath.split("\\.");
return getPropertyValue(instance, properties, resultClass, 0);
}
/**
* Iterates through the nested properties and returns the field value.
*/
@SuppressWarnings("unchecked")
private static <T> @Nullable T getPropertyValue(@Nullable Object instance, String[] properties,
Class<T> resultClass, int nestedIndex) throws GardenaException {
if (instance == null) {
return null;
}
try {
String propertyName = properties[nestedIndex];
Field field = instance.getClass().getField(propertyName);
Object result = field.get(instance);
if (nestedIndex + 1 < properties.length) {
return getPropertyValue(result, properties, resultClass, nestedIndex + 1);
}
return (T) result;
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
throw new GardenaException(ex.getMessage(), ex);
}
}
}

View File

@ -0,0 +1,60 @@
/**
* 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.gardena.internal.util;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Some String operations from commons lang.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class StringUtils {
/**
* Gets the substring before the first occurrence of a separator.
*/
public static @Nullable String substringBefore(@Nullable String str, String separator) {
if (str != null && !str.isEmpty()) {
int pos = str.indexOf(separator);
return pos == -1 ? str : str.substring(0, pos);
} else {
return str;
}
}
/**
* Gets the substring before the last occurrence of a separator.
*/
public static @Nullable String substringBeforeLast(@Nullable String str, String separator) {
if (str != null && !str.isEmpty()) {
int pos = str.lastIndexOf(separator);
return pos == -1 ? str : str.substring(0, pos);
} else {
return str;
}
}
/**
* Gets the substring after the last occurrence of a separator.
*/
public static @Nullable String substringAfterLast(@Nullable String str, String separator) {
if (str != null && !str.isEmpty()) {
int pos = str.lastIndexOf(separator);
return pos != -1 && pos != str.length() - separator.length() ? str.substring(pos + separator.length()) : "";
} else {
return str;
}
}
}

View File

@ -17,8 +17,9 @@ import static org.openhab.binding.gardena.internal.GardenaBindingConstants.BINDI
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.gardena.internal.handler.GardenaDeviceConfig;
import org.openhab.binding.gardena.internal.model.Device;
import org.openhab.binding.gardena.internal.model.dto.Device;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
@ -29,14 +30,15 @@ import org.openhab.core.thing.ThingUID;
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class UidUtils {
/**
* Generates the ThingUID for the given device in the given account.
*/
public static ThingUID generateThingUID(Device device, Bridge account) {
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, device.getCategory());
return new ThingUID(thingTypeUID, account.getUID(), device.getId());
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, device.deviceType);
return new ThingUID(thingTypeUID, account.getUID(), device.id);
}
/**
@ -49,7 +51,7 @@ public class UidUtils {
if (deviceId == null) {
deviceId = thing.getUID().getId();
}
if (deviceId.equals(device.getId())) {
if (deviceId.equals(device.id)) {
thingUIDs.add(thing.getUID());
}
}

View File

@ -3,6 +3,6 @@
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>Gardena Smart Home Binding</name>
<description>This is the binding for Gardena Smart Home.</description>
<name>Gardena Smart System Binding</name>
<description>This is the binding for Gardena smart system.</description>
</binding:binding>

View File

@ -1,462 +1,287 @@
# binding
binding.gardena.description=Das ist das Binding für Gardena Smart Home Produkte.
binding.gardena.description=Das ist das Binding für Gardena Smart System.
# thing types
thing-type.gardena.bridge.description=ein Gardena Smart Home Online Account
thing-type.gardena.bridge.description=Ein Gardena Smart System Online Account
thing-type.config.gardena.bridge.email.description=Email Addresse für den Gardena Smart Home Login
thing-type.config.gardena.bridge.password.label=Passwort
thing-type.config.gardena.bridge.password.description=Passwort für den Gardena Smart Home Login
thing-type.config.gardena.bridge.sessionTimeout.description=Timeout der Gardena Smart Home Session in Minuten
thing-type.config.gardena.bridge.email.description=Email-Addresse für den Gardena Smart System Login
thing-type.config.gardena.bridge.password.label=Passwort
thing-type.config.gardena.bridge.password.description=Passwort für den Gardena Smart System Login
thing-type.config.gardena.bridge.connectionTimeout.label=Verbindungstimeout
thing-type.config.gardena.bridge.connectionTimeout.description=Timeout des Verbindungsaufbaus zu Gardena Smart Home in Sekunden
thing-type.config.gardena.bridge.refresh.label=Refreshintervall
thing-type.config.gardena.bridge.refresh.description=Der Intervall für die Datenabholung von Gardena Smart Home in Sekunden
thing-type.config.gardena.bridge.connectionTimeout.description=Timeout des Verbindungsaufbaus zum Gardena Smart System in Sekunden
thing-type.config.gardena.bridge.apiKey.description=Der Gardena Smart System Integrations API Schlüssel
thing-type.gardena.mower.label=Gardena Smart Sileno Mäher
thing-type.gardena.mower.description=ein Gardena Smart Sileno Mäher
thing-type.gardena.mower.label=Gardena Smart SILENO Mäher
thing-type.gardena.mower.description=Ein Gardena Smart SILENO Mäher
thing-type.gardena.watering_computer.label=Gardena Smart Bewässerungscomputer
thing-type.gardena.watering_computer.description=ein Gardena Smart Bewässerungscomputer
thing-type.gardena.water_control.label=Gardena Smart Bewässerungscomputer
thing-type.gardena.water_control.description=Ein Gardena Smart Bewässerungscomputer
thing-type.gardena.sensor.label=Gardena Smart Sensor
thing-type.gardena.sensor.description=ein Gardena Smart Sensor
thing-type.gardena.sensor.description=Ein Gardena Smart Sensor
thing-type.gardena.electronic_pressure_pump.label=Gardena Smart Druckpumpe
thing-type.gardena.electronic_pressure_pump.description=Eine Gardena Smart Druckpumpe
thing-type.gardena.pump.label=Gardena Smart Druckpumpe
thing-type.gardena.pump.description=Eine Gardena Smart Druckpumpe
thing-type.gardena.power.label=Gardena Smart Power Zwischenstecker
thing-type.gardena.power.description=Eine Gardena Smart Power Zwischenstecker
thing-type.gardena.power.description=Ein Gardena Smart Power Zwischenstecker
thing-type.gardena.ic24.label=Gardena Smart Bewässerungssteuerung
thing-type.gardena.ic24.description=Eine Gardena Smart Bewässerungssteuerung
thing-type.gardena.irrigation_control.label=Gardena Smart Bewässerungssteuerung
thing-type.gardena.irrigation_control.description=Eine Gardena Smart Bewässerungssteuerung
# config
thing-type.config.gardena.electronic_pressure_pump.leakage_detection.label=Leckage-Erkennung
thing-type.config.gardena.electronic_pressure_pump.leakage_detection.state.option.watering=Bewässerung
thing-type.config.gardena.electronic_pressure_pump.leakage_detection.state.option.washing_machine=Waschmaschine
thing-type.config.gardena.electronic_pressure_pump.leakage_detection.state.option.domestic_water_supply=Häusliche Wasserversorgung
thing-type.config.gardena.electronic_pressure_pump.leakage_detection.state.option.off=Aus
thing-type.config.gardena.electronic_pressure_pump.operating_mode.label=Betriebsmodus
thing-type.config.gardena.electronic_pressure_pump.operating_mode.state.option.automatic=Automatisch
thing-type.config.gardena.electronic_pressure_pump.operating_mode.state.option.scheduled=Zeitgesteuert
thing-type.config.gardena.electronic_pressure_pump.turn_on_pressure.label=Einschaltdruck
thing-type.gardena.irrigation_control.group.valveOne.label=Ventil 1 Eigenschaften
thing-type.gardena.irrigation_control.group.valveOne_commands.label=Ventil 1 Befehle
thing-type.gardena.irrigation_control.group.valveTwo.label=Ventil 2 Eigenschaften
thing-type.gardena.irrigation_control.group.valveTwo_commands.label=Ventil 2 Befehle
thing-type.gardena.irrigation_control.group.valveThree.label=Ventil 3 Eigenschaften
thing-type.gardena.irrigation_control.group.valveThree_commands.label=Ventil 3 Befehle
thing-type.gardena.irrigation_control.group.valveFour.label=Ventil 4 Eigenschaften
thing-type.gardena.irrigation_control.group.valveFour_commands.label=Ventil 4 Befehle
thing-type.gardena.irrigation_control.group.valveFive.label=Ventil 5 Eigenschaften
thing-type.gardena.irrigation_control.group.valveFive_commands.label=Ventil 5 Befehle
thing-type.gardena.irrigation_control.group.valveSix.label=Ventil 6 Eigenschaften
thing-type.gardena.irrigation_control.group.valveSix_commands.label=Ventil 6 Befehle
# channel group
channel-group-type.gardena.deviceInfo.label=Geräteinfo
channel-group-type.gardena.deviceInfo.description=Informationen über das Gerät
channel-group-type.gardena.valveSetProperties.label=Ventilsatz Eigenschaften
channel-group-type.gardena.valveSetProperties.description=Eigenschaften eines Ventilsatzes
channel-group-type.gardena.rechargeableBattery.label=Wiederaufladbare Batterie Info
channel-group-type.gardena.rechargeableBattery.description=Informationen über die wiederaufladbare Batterie
channel-group-type.gardena.valveSetProperties.channel.state_timestamp.label=Status Zeitstempel
channel-group-type.gardena.valveSetProperties.channel.lastErrorCode_timestamp.label=Letzte Fehlermeldung Zeitstempel
channel-group-type.gardena.radio.label=Funkverbindung
channel-group-type.gardena.radio.description=Informationen über die Funkverbindung
channel-group-type.gardena.valveProperties.label=Ventil Eigenschaften
channel-group-type.gardena.valveProperties.description=Eigenschaften eines Ventils
channel-group-type.gardena.valveProperties.channel.state_timestamp.label=Status Zeitstempel
channel-group-type.gardena.valveProperties.channel.activity_timestamp.label=Aktivität Zeitstempel
channel-group-type.gardena.valveProperties.channel.lastErrorCode_timestamp.label=Letzte Fehlermeldung Zeitstempel
channel-group-type.gardena.mower.label=Mähroboter
channel-group-type.gardena.mower.description=Informationen über den Mähroboter
channel-group-type.gardena.sensorProperties.label=Sensor Eigenschaften
channel-group-type.gardena.sensorProperties.description=Eigenschaften eines Sensors
channel-group-type.gardena.sensorProperties.channel.soilHumidity_timestamp.label=Bodenfeuchtigkeit Zeitstempel
channel-group-type.gardena.sensorProperties.channel.soilTemperature.label=Bodentemperatur
channel-group-type.gardena.sensorProperties.channel.soilTemperature_timestamp.label=Bodentemperatur Zeitstempel
channel-group-type.gardena.sensorProperties.channel.ambientTemperature.label=Umgebungstemperatur
channel-group-type.gardena.sensorProperties.channel.ambientTemperature_timestamp.label=Umgebungstemperatur Zeitstempel
channel-group-type.gardena.sensorProperties.channel.lightIntensity_timestamp.label=Lichtintensität Zeitstempel
channel-group-type.gardena.internalTemperature.label=Interne Temperatur
channel-group-type.gardena.internalTemperature.description=Die interne Temperatur des Mähroboters
channel-group-type.gardena.powerSocketProperties.label=Zwischenstecker Eigenschaften
channel-group-type.gardena.powerSocketProperties.description=Eigenschaften eines Zwischensteckers
channel-group-type.gardena.powerSocketProperties.channel.state_timestamp.label=Status Zeitstempel
channel-group-type.gardena.powerSocketProperties.channel.activity_timestamp.label=Aktivität Zeitstempel
channel-group-type.gardena.powerSocketProperties.channel.lastErrorCode_timestamp.label=Letzte Fehlermeldung Zeitstempel
channel-group-type.gardena.powerSocketProperties.channel.duration_timestamp.label=Dauer Zeitstempel
channel-group-type.gardena.battery.label=Batterieinfo
channel-group-type.gardena.battery.description=Informationen über die Batterie
channel-group-type.gardena.mowerProperties.label=Mäher Eigenschaften
channel-group-type.gardena.mowerProperties.description=Eigenschaften eines Mähers
channel-group-type.gardena.mowerProperties.channel.state_timestamp.label=Status Zeitstempel
channel-group-type.gardena.mowerProperties.channel.activity_timestamp.label=Aktivität Zeitstempel
channel-group-type.gardena.mowerProperties.channel.lastErrorCode_timestamp.label=Letzte Fehlermeldung Zeitstempel
channel-group-type.gardena.outlet.label=Wasserauslass
channel-group-type.gardena.outlet.description=Informationen über den Wasserauslass
channel-group-type.gardena.commonProperties.label=Gemeinsame Eigenschaften
channel-group-type.gardena.commonProperties.description=Eigenschaften, die bei allen Geräten gleich sind
channel-group-type.gardena.commonProperties.channel.batteryLevel_timestamp.label=Batterie Zeitstempel
channel-group-type.gardena.commonProperties.channel.batteryState_timestamp.label=Batteriestatus Zeitstempel
channel-group-type.gardena.commonProperties.channel.rfLinkLevel_timestamp.label=Signalstärke Zeitstempel
channel-group-type.gardena.commonProperties.channel.rfLinkState_timestamp.label=Verbindungsstatus Zeitstempel
channel-group-type.gardena.commonProperties.channel.lastUpdate_timestamp.label=Letzte Aktualisierung Zeitstempel
channel-group-type.gardena.ambientTemperature.label=Umgebungstemperatur
channel-group-type.gardena.ambientTemperature.description=Informationen über die Umgebungstemperatur
channel-group-type.gardena.mowerCommands.label=Mäher Befehle
channel-group-type.gardena.mowerCommands.description=Befehle für einen Mäher
channel-group-type.gardena.mowerCommands.channel.commandDuration.label=Mähdauer
channel-group-type.gardena.mowerCommands.channel.start_seconds_to_override.label=Starte Mähen mit Dauer
channel-group-type.gardena.mowerCommands.channel.start_seconds_to_override.description=Manueller Betrieb, verwendet Mähdauer um die Dauer zu definieren
channel-group-type.gardena.mowerCommands.channel.start_dont_override.label=Zeitplan Starten
channel-group-type.gardena.mowerCommands.channel.start_dont_override.description=Automatischer Betrieb
channel-group-type.gardena.mowerCommands.channel.park_until_next_task.label=Parken
channel-group-type.gardena.mowerCommands.channel.park_until_next_task.description=Abbruch der aktuellen Operation und Rückkehr zur Ladestation
channel-group-type.gardena.mowerCommands.channel.park_until_further_notice.label=Zeitplan Pausieren
channel-group-type.gardena.mowerCommands.channel.park_until_further_notice.description=Abbruch der aktuellen Operation, Rückkehr zur Ladestation, Zeitplan ignorieren
channel-group-type.gardena.soilTemperature.label=Bodentemperatur
channel-group-type.gardena.soilTemperature.description=Die Temperatur des Bodens
channel-group-type.gardena.valveCommands.label=Ventil Befehle
channel-group-type.gardena.valveCommands.description=Befehle für ein Ventil
channel-group-type.gardena.valveCommands.channel.commandDuration.label=Bewässerungsdauer
channel-group-type.gardena.valveCommands.channel.start_seconds_to_override.label=Starte Bewässerung mit Dauer
channel-group-type.gardena.valveCommands.channel.start_seconds_to_override.description=Manueller Betrieb, verwendet Bewässerunsdauer um die Dauer zu definieren
channel-group-type.gardena.valveCommands.channel.stop_until_next_task.label=Ventil Schließen, Zeitplan Fortfahren
channel-group-type.gardena.valveCommands.channel.stop_until_next_task.description=Die aktuelle Bewässerung abbrechen, mit dem Zeitplan fortfahren
channel-group-type.gardena.valveCommands.channel.pause.label=Pause bis zur angegebenen Zeit
channel-group-type.gardena.valveCommands.channel.pause.description=Den Automatikbetrieb bis zur angegebenen Zeit überspringen. Der derzeit aktive Vorgang kann abgebrochen werden oder auch nicht (abhängig vom Gerätemodell)
channel-group-type.gardena.valveCommands.channel.unpause.label=Zeitplan Starten
channel-group-type.gardena.valveCommands.channel.unpause.description=Wiederherstellen des automatischen Betriebs, wenn er angehalten wurde
channel-group-type.gardena.humidity.label=Feuchtigkeit
channel-group-type.gardena.humidity.description=Die Feuchtigkeit
channel-group-type.gardena.valveSetCommands.label=Ventilsatz Befehle
channel-group-type.gardena.valveSetCommands.description=Befehle für einen Ventilsatz
channel-group-type.gardena.valveSetCommands.channel.stop_until_next_task.label=Alle Ventile Schließen
channel-group-type.gardena.valveSetCommands.channel.stop_until_next_task.description=Alle Ventile sofort schließen
channel-group-type.gardena.light.label=Helligkeit
channel-group-type.gardena.light.description=Die Helligkeit
channel-group-type.gardena.firmware.label=Firmware
channel-group-type.gardena.firmware.description=Informationen über die Firmware
channel-group-type.gardena.outletTemperature.label=Auslauftemperatur
channel-group-type.gardena.outletTemperature.description=Die Auslauftemperatur
channel-group-type.gardena.pump.label=Pumpe
channel-group-type.gardena.pump.description=Die Pumpe
channel-group-type.gardena.outletPressure.label=Auslassdruck
channel-group-type.gardena.outletPressure.description=Der Auslassdruck
channel-group-type.gardena.outletPressureMax.label=Maximum Auslassdruck
channel-group-type.gardena.outletPressureMax.description=Der maximale Auslassdruck
channel-group-type.gardena.flow.label=Förderung
channel-group-type.gardena.flow.description=Die Förderung
channel-group-type.gardena.manualWatering.label=Manuelle Bewässerung
channel-group-type.gardena.manualWatering.description=Die manuelle Bewässerung
channel-group-type.gardena.scheduling.label=Zeitplan
channel-group-type.gardena.scheduling.description=Der Zeitplan
channel-group-type.gardena.mowerStats.label=Mähroboter Statistiken
channel-group-type.gardena.mowerStats.description=Statistiken über den Mähroboter
channel-group-type.gardena.power.label=Energie
channel-group-type.gardena.power.description=Energie
channel-group-type.gardena.watering.label=Bewässerung
channel-group-type.gardena.watering.description=Die Bewässerung
channel-group-type.gardena.ic24.label=Bewässerungssteuerung
channel-group-type.gardena.ic24.description=Die Bewässerungssteuerung
channel-group-type.gardena.ic24scheduling.label=Zeitplanung
channel-group-type.gardena.ic24scheduling.description=Die Zeitplanung
channel-group-type.gardena.powerSocketCommands.label=Zwischenstecker Befehle
channel-group-type.gardena.powerSocketCommands.description=Befehle für einen Zwischenstecker
channel-group-type.gardena.powerSocketCommands.channel.commandDuration.label=Einschaltdauer
channel-group-type.gardena.powerSocketCommands.channel.start_seconds_to_override.label=Einschalten mit Dauer
channel-group-type.gardena.powerSocketCommands.channel.start_seconds_to_override.description=Manueller Betrieb, verwendet Einschaltdauer um die Dauer zu definieren
channel-group-type.gardena.powerSocketCommands.channel.start_override.label=Manuelles Einschalten
channel-group-type.gardena.powerSocketCommands.channel.start_override.description=Manuelles Ein
channel-group-type.gardena.powerSocketCommands.channel.stop_until_next_task.label=Aus, Weiter mit Zeitplan
channel-group-type.gardena.powerSocketCommands.channel.stop_until_next_task.description=Sofort abschalten, mit dem Zeitplan fortfahren
channel-group-type.gardena.powerSocketCommands.channel.pause.label=Pause bis zur angegebenen Zeit
channel-group-type.gardena.powerSocketCommands.channel.pause.description=Automatischen Betrieb bis zur angegebenen Zeit überspringen. Die derzeit aktive Operation wird NICHT abgebrochen
channel-group-type.gardena.powerSocketCommands.channel.unpause.label=Zeitplan Starten
channel-group-type.gardena.powerSocketCommands.channel.unpause.description=Wiederherstellen des automatischen Betriebs, wenn er angehalten wurde
# channel types
channel-type.gardena.mowerDurationProperty.label=Mähdauer
channel-type.gardena.mowerDurationProperty.description=Mähdauer in Minuten
channel-type.gardena.gardenaCommand.label=Befehl
channel-type.gardena.gardenaCommand.description=Ein Befehl für ein Gerät
channel-type.gardena.parkUntilFurtherNotice.label=Parken und pausieren
channel-type.gardena.parkUntilFurtherNotice.description=Parken und alle Zeitpläne pausieren
channel-type.gardena.powerCommandDuration.label=Einschaltdauer
channel-type.gardena.powerCommandDuration.description=Die Einschaltdauer in Minuten
channel-type.gardena.parkUntilNextTimer.label=Parken
channel-type.gardena.parkUntilNextTimer.description=Bis zum nächsten regulären Zeitplan parken
channel-type.gardena.valveCommandDuration.label=Einschaltdauer
channel-type.gardena.valveCommandDuration.description=Die Einschaltdauer in Minuten
channel-type.gardena.startResumeSchedule.label=Zeitpläne fortsetzen
channel-type.gardena.startResumeSchedule.description=Alle Zeitpläne werden fortgesetze
channel-type.gardena.mowerCommandDuration.label=Einschaltdauer
channel-type.gardena.mowerCommandDuration.description=Die Einschaltdauer in Minuten
channel-type.gardena.startOverrideTimer.label=Manuelles mähen
channel-type.gardena.startOverrideTimer.description=Sofort mähen für die eingestellte Mähdauer
channel-type.gardena.state.label=Status
channel-type.gardena.state.description=Der Gerätestatus
channel-type.gardena.lastTimeOnline.label=Zuletzt online
channel-type.gardena.lastTimeOnline.description=Die Zeit wann das Gerät zuletzt online war
channel-type.gardena.state.option.WARNING=Warnung
channel-type.gardena.state.state.option.ERROR=Fehler
channel-type.gardena.state.state.option.UNAVAILABLE=Nicht verfügbar
channel-type.gardena.rechargeableBatteryStatus.label=Akkuzustand
channel-type.gardena.rechargeableBatteryStatus.description=Der Zustand des Akkus
channel-type.gardena.rechargeableBatteryStatus.state.option.weak=Schwach
channel-type.gardena.rechargeableBatteryStatus.state.option.undefined=Undefiniert
channel-type.gardena.lastErrorCode.label=Letzte Fehlermeldung
channel-type.gardena.lastErrorCode.description=Die letzte Fehlermeldung
channel-type.gardena.charging.label=Batterie wird geladen
channel-type.gardena.charging.description=Batterie wird geladen
channel-type.gardena.lastErrorCode.state.option.UNKNOWN=Unbekannt
channel-type.gardena.lastErrorCode.state.option.NO_MESSAGE=Kein Fehler
channel-type.gardena.lastErrorCode.state.option.OUTSIDE_WORKING_AREA=Außerhalb des Arbeitsbereichs
channel-type.gardena.lastErrorCode.state.option.NO_LOOP_SIGNAL=Kein Schleifensignal
channel-type.gardena.lastErrorCode.state.option.WRONG_LOOP_SIGNAL=Falsches Schleifensignal
channel-type.gardena.lastErrorCode.state.option.LOOP_SENSOR_PROBLEM_FRONT=Problem Schleifensensor, vorne
channel-type.gardena.lastErrorCode.state.option.LOOP_SENSOR_PROBLEM_REAR=Problem Schleifensensor, hinten
channel-type.gardena.lastErrorCode.state.option.LOOP_SENSOR_PROBLEM_LEFT=Problem Schleifensensor, links
channel-type.gardena.lastErrorCode.state.option.LOOP_SENSOR_PROBLEM_RIGHT=Problem Schleifensensor, rechts
channel-type.gardena.lastErrorCode.state.option.WRONG_PIN_CODE=Falscher PIN Code
channel-type.gardena.lastErrorCode.state.option.TRAPPED=Eingeschlossen
channel-type.gardena.lastErrorCode.state.option.UPSIDE_DOWN=Steht auf dem Kopf
channel-type.gardena.lastErrorCode.state.option.EMPTY_BATTERY=Leere Batteriestand
channel-type.gardena.lastErrorCode.state.option.NO_DRIVE=Kein Antrieb
channel-type.gardena.lastErrorCode.state.option.TEMPORARILY_LIFTED=Vorübergehend angehoben
channel-type.gardena.lastErrorCode.state.option.LIFTED=Angehoben
channel-type.gardena.lastErrorCode.state.option.STUCK_IN_CHARGING_STATION=In Ladestation eingeklemmt
channel-type.gardena.lastErrorCode.state.option.CHARGING_STATION_BLOCKED=Ladestation blockiert
channel-type.gardena.lastErrorCode.state.option.COLLISION_SENSOR_PROBLEM_REAR=Problem Stoßsensor hinten
channel-type.gardena.lastErrorCode.state.option.COLLISION_SENSOR_PROBLEM_FRONT=Problem Stoßsensor vorne
channel-type.gardena.lastErrorCode.state.option.WHEEL_MOTOR_BLOCKED_RIGHT=Radmotor rechts blockiert
channel-type.gardena.lastErrorCode.state.option.WHEEL_MOTOR_BLOCKED_LEFT=Radmotor links blockiert
channel-type.gardena.lastErrorCode.state.option.WHEEL_DRIVE_PROBLEM_RIGHT=Problem Antrieb, rechts
channel-type.gardena.lastErrorCode.state.option.WHEEL_DRIVE_PROBLEM_LEFT=Problem Antrieb, links
channel-type.gardena.lastErrorCode.state.option.CUTTING_MOTOR_DRIVE_DEFECT=Schneidmotorantrieb defekt
channel-type.gardena.lastErrorCode.state.option.CUTTING_SYSTEM_BLOCKED=Schneidsystem blockiert
channel-type.gardena.lastErrorCode.state.option.INVALID_SUB_DEVICE_COMBINATION=Fehlerhafte Verbindung
channel-type.gardena.lastErrorCode.state.option.MEMORY_CIRCUIT_PROBLEM=Speicherschaltkreisproblem
channel-type.gardena.lastErrorCode.state.option.CHARGING_SYSTEM_PROBLEM=Problem Ladesystem
channel-type.gardena.lastErrorCode.state.option.STOP_BUTTON_PROBLEM=Stop button Problem
channel-type.gardena.lastErrorCode.state.option.TILT_SENSOR_PROBLEM=Kippsensorproblem
channel-type.gardena.lastErrorCode.state.option.MOWER_TILTED=Mäher gekippt
channel-type.gardena.lastErrorCode.state.option.WHEEL_MOTOR_OVERLOADED_RIGHT=Rechter Radmotor überlastet
channel-type.gardena.lastErrorCode.state.option.WHEEL_MOTOR_OVERLOADED_LEFT=Linker Radmotor überlastet
channel-type.gardena.lastErrorCode.state.option.CHARGING_CURRENT_TOO_HIGH=Ladestrom zu hoch
channel-type.gardena.lastErrorCode.state.option.ELECTRONIC_PROBLEM=Elektronisches Problem
channel-type.gardena.lastErrorCode.state.option.CUTTING_MOTOR_PROBLEM=Schneidmotorisches Problem
channel-type.gardena.lastErrorCode.state.option.LIMITED_CUTTING_HEIGHT_RANGE=Begrenzter Schneidhöhenbereich
channel-type.gardena.lastErrorCode.state.option.CUTTING_HEIGHT_PROBLEM_DRIVE=Schnitthöhenproblem Antrieb
channel-type.gardena.lastErrorCode.state.option.CUTTING_HEIGHT_PROBLEM_CURR=Schnitthöhenproblem
channel-type.gardena.lastErrorCode.state.option.CUTTING_HEIGHT_PROBLEM_DIR=Schnitthöhenproblem Richtung
channel-type.gardena.lastErrorCode.state.option.CUTTING_HEIGHT_BLOCKED=Schnitthöhe blockiert
channel-type.gardena.lastErrorCode.state.option.CUTTING_HEIGHT_PROBLEM=Schnitthöhenproblem
channel-type.gardena.lastErrorCode.state.option.BATTERY_PROBLEM=Batterieproblem
channel-type.gardena.lastErrorCode.state.option.TOO_MANY_BATTERIES=Zu viele Batterien
channel-type.gardena.lastErrorCode.state.option.ALARM_MOWER_SWITCHED_OFF=Alarm! Mäher ausgeschalten
channel-type.gardena.lastErrorCode.state.option.ALARM_MOWER_STOPPED=Alarm! Mäher gestoppt
channel-type.gardena.lastErrorCode.state.option.ALARM_MOWER_LIFTED=Alarm! Mäher angehoben
channel-type.gardena.lastErrorCode.state.option.ALARM_MOWER_TILTED=Alarm! Mäher gekippt
channel-type.gardena.lastErrorCode.state.option.ALARM_MOWER_IN_MOTION=Alarm! Mäher in Bewegung
channel-type.gardena.lastErrorCode.state.option.ALARM_OUTSIDE_GEOFENCE=Alarm! Außerhalb des Geofence
channel-type.gardena.lastErrorCode.state.option.SLIPPED=Mäher rutscht
channel-type.gardena.lastErrorCode.state.option.INVALID_BATTERY_COMBINATION=Ungültige Batterie Kombination
channel-type.gardena.lastErrorCode.state.option.UNINITIALISED=Unbekannter Status des Mähers
channel-type.gardena.lastErrorCode.state.option.WAIT_UPDATING=Wird aktualisiert ...
channel-type.gardena.lastErrorCode.state.option.WAIT_POWER_UP=Wird eingeschaltet ...
channel-type.gardena.lastErrorCode.state.option.OFF_DISABLED=Der Mäher ist ausgeschaltet
channel-type.gardena.lastErrorCode.state.option.OFF_HATCH_OPEN=Deaktiviert. Abdeckung ist offen oder PIN-Code erforderlich
channel-type.gardena.lastErrorCode.state.option.OFF_HATCH_CLOSED=Deaktiviert. Manueller Start erforderlich
channel-type.gardena.lastErrorCode.state.option.PARKED_DAILY_LIMIT_REACHED=Mähwerk hat den Mähvorgang aufgrund der erreichten Tagesgrenze abgeschlossen
channel-type.gardena.lastErrorCode.state.option.TIMER_CANCELLED=Timer abgebrochen
channel-type.gardena.lastErrorCode.state.option.CONCURRENT_LIMIT_REACHED=Ventil kann nicht geöffnet werden, weil höchstens 2 Ventile gleichzeitig geöffnet sein können
channel-type.gardena.lastErrorCode.state.option.NOT_CONNECTED=Es wurde kein Ventil angeschlossen
channel-type.gardena.lastErrorCode.state.option.VALVE_CURRENT_MAX_EXCEEDED=Das Ventil wurde geschlossen, weil das Ventil mehr Strom als erlaubt abzieht
channel-type.gardena.lastErrorCode.state.option.TOTAL_CURRENT_MAX_EXCEEDED=Das Ventil wurde geschlossen, weil der verwendete Gesamtstrom über dem zulässigen Maximum lag.
channel-type.gardena.lastErrorCode.state.option.WATERING_CANCELED=Bewässerung wurde abgebrochen
channel-type.gardena.lastErrorCode.state.option.MASTER_VALVE=Hauptventil ist nicht angeschlossen
channel-type.gardena.lastErrorCode.state.option.WATERING_DURATION_TOO_SHORT=Bewässerungsdauer zu kurz
channel-type.gardena.lastErrorCode.state.option.VALVE_BROKEN=Die elektrische Verbindung zum Ventil ist unterbrochen, oder der Induktor ist beschädigt
channel-type.gardena.lastErrorCode.state.option.FROST_PREVENTS_STARTING=Wegen Frost bleibt das Ventil geschlossen
channel-type.gardena.lastErrorCode.state.option.LOW_BATTERY_PREVENTS_STARTING=Wegen niedriger Batterie bleibt das Ventil geschlossen
channel-type.gardena.lastErrorCode.state.option.VALVE_POWER_SUPPLY_FAILED=Stromversorgung ausgefallen
channel-type.gardena.lastErrorCode.state.option.VOLTAGE_DROP=Es wurde ein Spannungsabfall an der Stromversorgung festgestellt
channel-type.gardena.lastErrorCode.state.option.WRONG_POWER_SUPPLY=Falsches Netzteil angeschlossen
channel-type.gardena.lastErrorCode.state.option.NO_MCU_CONNECTION=Kommunikation mit sekundärer MCU nicht möglich
channel-type.gardena.radioQuality.label=Verbindungsqualität
channel-type.gardena.radioQuality.description=Die Qualität der Funk Verbindung
channel-type.gardena.timestamp.label=Zeitstempel
channel-type.gardena.timestamp.description=Zeitstempel
channel-type.gardena.timestampRefresh.label=Zeitstempel
channel-type.gardena.timestampRefresh.description=Zeitstempel
channel-type.gardena.connectionStatus.label=Verbindungsstatus
channel-type.gardena.connectionStatus.description=Der Status der Verbindung
channel-type.gardena.connectionStatus.state.option.status_device_unreachable=Nicht erreichbar
channel-type.gardena.connectionStatus.state.option.status_device_alive=Erreichbar
channel-type.gardena.connectionStatus.state.option.unknown=Unbekannt
channel-type.gardena.name.label=Name
channel-type.gardena.name.description=Der Name des Gerätes
channel-type.gardena.radioState.label=Verbindungszustand
channel-type.gardena.radioState.description=Der Zustand der Funk Verbindung
channel-type.gardena.radioState.state.option.poor=Schlecht
channel-type.gardena.radioState.state.option.good=Gut
channel-type.gardena.radioState.state.option.excellent=Exzellent
channel-type.gardena.radioState.state.option.undefined=Undefiniert
channel-type.gardena.activity.label=Aktivität
channel-type.gardena.activity.description=Die Aktivität des Gerätes
channel-type.gardena.activity.state.option.OFF=Aus
channel-type.gardena.activity.state.option.FOREVER_ON=Einschalten durch eine manuelle Aktion, kein Ausschalten geplant
channel-type.gardena.activity.state.option.TIME_LIMITED_ON=Einschalten durch eine manuelle Aktion, das Ausschalten ist geplant
channel-type.gardena.activity.state.option.SCHEDULED_ON=Betrieb nach Zeitplan, aktueller Stand ist 'ein'
channel-type.gardena.activity.state.option.CLOSED=Ventil geschlossen
channel-type.gardena.activity.state.option.MANUAL_WATERING=Bewässerung aktiv
channel-type.gardena.activity.state.option.SCHEDULED_WATERING=Bewässerung aktiv
channel-type.gardena.activity.state.option.PAUSED=In einem Wartezustand mit geschlossener Luke
channel-type.gardena.activity.state.option.OK_CUTTING=Mähen
channel-type.gardena.activity.state.option.OK_CUTTING_TIMER_OVERRIDDEN=Mähen
channel-type.gardena.activity.state.option.OK_SEARCHING=Suche Ladestation
channel-type.gardena.activity.state.option.OK_LEAVING=Verlasse Ladestation
channel-type.gardena.activity.state.option.OK_CHARGING=Lädt
channel-type.gardena.activity.state.option.PARKED_TIMER=Nach Timer geparkt
channel-type.gardena.activity.state.option.PARKED_PARK_SELECTED=Bis auf weiteres geparkt
channel-type.gardena.activity.state.option.PARKED_AUTOTIMER=Wegen zu geringer Grashöhe geparkt
channel-type.gardena.activity.state.option.NONE=Keine
channel-type.gardena.manualOperation.label=Manuelle Steuerung
channel-type.gardena.manualOperation.description=Der Mäher wird manuell gesteuert
channel-type.gardena.duration.label=Dauer
channel-type.gardena.duration.description=Die Dauer in Minuten
channel-type.gardena.status.label=Mäherstatus
channel-type.gardena.status.description=Der Status des Mähers
channel-type.gardena.status.state.option.uninitialised=Nicht initialisiert
channel-type.gardena.status.state.option.paused=Pausiert
channel-type.gardena.status.state.option.ok_cutting=Mähen
channel-type.gardena.status.state.option.ok_searching=Suche Ladestation
channel-type.gardena.status.state.option.ok_charging=Lädt
channel-type.gardena.status.state.option.ok_leaving=Mähen
channel-type.gardena.status.state.option.wait_updating=Wird aktualisiert ...
channel-type.gardena.status.state.option.wait_power_up=Wird eingeschaltet ...
channel-type.gardena.status.state.option.parked_timer=Geparkt nach Zeitplan
channel-type.gardena.status.state.option.parked_park_selected=Geparkt
channel-type.gardena.status.state.option.off_disabled=Der Mäher ist ausgeschaltet
channel-type.gardena.status.state.option.off_hatch_open=Deaktiviert. Abdeckung ist offen oder PIN-Code erforderlich
channel-type.gardena.status.state.option.unknown=Unbekannter Status
channel-type.gardena.status.state.option.error=Fehler
channel-type.gardena.status.state.option.error_at_power_up=Neustart ...
channel-type.gardena.status.state.option.off_hatch_closed=Deaktiviert. Manueller Start erforderlich
channel-type.gardena.status.state.option.ok_cutting_timer_overridden=Manuelles Mähen
channel-type.gardena.status.state.option.parked_autotimer=Geparkt durch SensorControl
channel-type.gardena.status.state.option.parked_daily_limit_reached=Abgeschlossen
channel-type.gardena.status.state.option.undefined=Undefiniert
channel-type.gardena.soilHumidity.label=Bodenfeuchtigkeit
channel-type.gardena.soilHumidity.description=Die Bodenfeuchtigkeit in Prozent
channel-type.gardena.error.label=Mäherfehler
channel-type.gardena.error.description=Der Fehler des Mähers
channel-type.gardena.error.state.option.no_message=Kein Fehler
channel-type.gardena.error.state.option.outside_working_area=Außerhalb des Arbeitsbereichs
channel-type.gardena.error.state.option.no_loop_signal=Kein Schleifensignal
channel-type.gardena.error.state.option.wrong_loop_signal=Falsches Schleifensignal
channel-type.gardena.error.state.option.loop_sensor_problem_front=Problem Schleifensensor, vorne
channel-type.gardena.error.state.option.loop_sensor_problem_rear=Problem Schleifensensor, hinten
channel-type.gardena.error.state.option.loop_sensor_problem_left=Problem Schleifensensor, links
channel-type.gardena.error.state.option.loop_sensor_problem_right=Problem Schleifensensor, rechts
channel-type.gardena.error.state.option.wrong_pin_code=Falscher PIN Code
channel-type.gardena.error.state.option.trapped=Eingeschlossen
channel-type.gardena.error.state.option.upside_down=Steht auf dem Kopf
channel-type.gardena.error.state.option.low_battery=Niedriger Batteriestand
channel-type.gardena.error.state.option.empty_battery=empty_battery
channel-type.gardena.error.state.option.no_drive=no_drive
channel-type.gardena.error.state.option.temporarily_lifted=Vorübergehend angehoben
channel-type.gardena.error.state.option.lifted=Angehoben
channel-type.gardena.error.state.option.stuck_in_charging_station=Eingeklemmt in Ladestation
channel-type.gardena.error.state.option.charging_station_blocked=Ladestation blockiert
channel-type.gardena.error.state.option.collision_sensor_problem_rear=Problem Stoßsensor hinten
channel-type.gardena.error.state.option.collision_sensor_problem_front=Problem Stoßsensor vorne
channel-type.gardena.error.state.option.wheel_motor_blocked_right=Radmotor rechts blockiert
channel-type.gardena.error.state.option.wheel_motor_blocked_left=Radmotor links blockiert
channel-type.gardena.error.state.option.wheel_drive_problem_right=Problem Antrieb, rechts
channel-type.gardena.error.state.option.wheel_drive_problem_left=Problem Antrieb, links
channel-type.gardena.error.state.option.cutting_motor_drive_defect=Schneidmotorantrieb defekt
channel-type.gardena.error.state.option.cutting_system_blocked=Schneidsystem blockiert
channel-type.gardena.error.state.option.invalid_sub_device_combination=Fehlerhafte Verbindung
channel-type.gardena.error.state.option.settings_restored=Standardeinstellungen
channel-type.gardena.error.state.option.memory_circuit_problem=Speicherschaltkreisproblem
channel-type.gardena.error.state.option.slope_too_steep=Steigung zu steil
channel-type.gardena.error.state.option.charging_system_problem=Problem Ladesystem
channel-type.gardena.error.state.option.stop_button_problem=Stop button Problem
channel-type.gardena.error.state.option.tilt_sensor_problem=Kippsensorproblem
channel-type.gardena.error.state.option.mower_tilted=Mäher gekippt
channel-type.gardena.error.state.option.wheel_motor_overloaded_right=Rechter Radmotor überlastet
channel-type.gardena.error.state.option.wheel_motor_overloaded_left=Linker Radmotor überlastet
channel-type.gardena.error.state.option.charging_current_too_high=Ladestrom zu hoch
channel-type.gardena.error.state.option.electronic_problem=Elektronisches Problem
channel-type.gardena.error.state.option.cutting_motor_problem=Schneidmotorisches Problem
channel-type.gardena.error.state.option.limited_cutting_height_range=Begrenzter Schneidhöhenbereich
channel-type.gardena.error.state.option.unexpected_cutting_height_adj=Unerwartete Schnitthöhe
channel-type.gardena.error.state.option.cutting_height_problem_drive=Schnitthöhenproblem Antrieb
channel-type.gardena.error.state.option.cutting_height_problem_curr=Schnitthöhenproblem
channel-type.gardena.error.state.option.cutting_height_problem_dir=Schnitthöhenproblem
channel-type.gardena.error.state.option.cutting_height_blocked=Schnitthöhe blockiert
channel-type.gardena.error.state.option.cutting_height_problem=Schnitthöhenproblem
channel-type.gardena.error.state.option.no_response_from_charger=Keine Antwort vom Ladegerät
channel-type.gardena.error.state.option.ultrasonic_problem=Ultraschallproblem
channel-type.gardena.error.state.option.temporary_problem=Vorübergehendes Problem
channel-type.gardena.error.state.option.guide_1_not_found=SK 1 nicht gefunden
channel-type.gardena.error.state.option.guide_2_not_found=SK 2 nicht gefunden
channel-type.gardena.error.state.option.guide_3_not_found=SK 3 nicht gefunden
channel-type.gardena.error.state.option.gps_tracker_module_error=GPS tracker Modul Fehler
channel-type.gardena.error.state.option.weak_gps_signal=Schwaches GPS Signal
channel-type.gardena.error.state.option.difficult_finding_home=Problem die Ladestation zu finden
channel-type.gardena.error.state.option.guide_calibration_accomplished=Kalibration des Suchkabels beendet
channel-type.gardena.error.state.option.guide_calibration_failed=Kalibration des Suchkabels fehlgeschlagen
channel-type.gardena.error.state.option.temporary_battery_problem=Kurzzeitiges Batterieproblem
channel-type.gardena.error.state.option.battery_problem=Batterieproblem
channel-type.gardena.error.state.option.too_many_batteries=Zu viele Batterien
channel-type.gardena.error.state.option.alarm_mower_switched_off=Alarm! Mäher ausgeschalten
channel-type.gardena.error.state.option.alarm_mower_stopped=Alarm! Mäher gestoppt
channel-type.gardena.error.state.option.alarm_mower_lifted=Alarm! Mäher angehoben
channel-type.gardena.error.state.option.alarm_mower_tilted=Alarm! Mäher gekippt
channel-type.gardena.error.state.option.alarm_mower_in_motion=Alarm! Mäher in Bewegung
channel-type.gardena.error.state.option.alarm_outside_geofence=Alarm! Außerhalb des Geofence
channel-type.gardena.error.state.option.connection_changed=Verbindung geändert
channel-type.gardena.error.state.option.connection_not_changed=Verbindung nicht geändert
channel-type.gardena.error.state.option.com_board_not_available=COM board nicht verfügbar
channel-type.gardena.error.state.option.slipped=Rutscht
channel-type.gardena.error.state.option.invalid_battery_combination=Ungültige Batterie Kombination
channel-type.gardena.error.state.option.imbalanced_cutting_disc=Unwuchte Schneidscheibe
channel-type.gardena.error.state.option.safety_function_faulty=Sicherheitsfunktion fehlerhaft
channel-type.gardena.sourceForNextStart.label=Quelle für den nächsten Start
channel-type.gardena.sourceForNextStart.description=Die Quelle für den nächsten Start
channel-type.gardena.sourceForNextStart.state.option.no_source=Keine Quelle
channel-type.gardena.sourceForNextStart.state.option.completed_cutting_daily_limit=Tägliches Mählimit erreicht
channel-type.gardena.sourceForNextStart.state.option.week_timer=Wochen timer
channel-type.gardena.sourceForNextStart.state.option.countdown_timer=Countdown timer
channel-type.gardena.sourceForNextStart.state.option.mower_charging=Mäher lädt
channel-type.gardena.sourceForNextStart.state.option.completed_cutting_autotimer=Fertig gemäht
channel-type.gardena.sourceForNextStart.state.option.undefined=Undefiniert
channel-type.gardena.timestampNextStart.label=Nächster Startzeitpunkt
channel-type.gardena.timestampNextStart.description=Zeitpunkt des nächsten Starts
channel-type.gardena.overrideEndTime.label=Überschreiben der Endzeit
channel-type.gardena.overrideEndTime.description=Überschreiben der Endzeit
channel-type.gardena.lightIntensity.label=Lichtintensität
channel-type.gardena.lightIntensity.description=Die Lichtintensität in Lux
channel-type.gardena.temperature.label=Temperatur
channel-type.gardena.temperature.description=Die Temperatur
channel-type.gardena.temperature.description=Die Temperatur in Grad Celsius
channel-type.gardena.disposableBatteryStatus.label=Einweg Batterie Status
channel-type.gardena.disposableBatteryStatus.description=Der Status der Einweg Batterie
channel-type.gardena.disposableBatteryStatus.state.option.out_of_operation=Ausser Betrieb
channel-type.gardena.disposableBatteryStatus.state.option.replace_now=Kritischer Batteriestand, wechseln Sie jetzt
channel-type.gardena.disposableBatteryStatus.state.option.low=Niedrig
channel-type.gardena.disposableBatteryStatus.state.option.ok=OK
channel-type.gardena.disposableBatteryStatus.state.option.undefined=Undefiniert
channel-type.gardena.operatingHours.label=Betriebsstunden
channel-type.gardena.operatingHours.description=Die Betriebsstunden
channel-type.gardena.valveOpen.label=Ventil geöffnet
channel-type.gardena.valveOpen.description=Ventil geöffnet
channel-type.gardena.batteryState.label=Batteriestatus
channel-type.gardena.batteryState.description=Der Batteriestatus
channel-type.gardena.batteryState.state.option.OK=OK
channel-type.gardena.batteryState.state.option.LOW=Niedrig
channel-type.gardena.batteryState.state.option.REPLACE_NOW=Jetzt ersetzen
channel-type.gardena.batteryState.state.option.OUT_OF_OPERATION=Außer Betrieb
channel-type.gardena.batteryState.state.option.CHARGING=Laden
channel-type.gardena.batteryState.state.option.NO_BATTERY=Keine Batterie
channel-type.gardena.batteryState.state.option.UNKNOWN=Unbekannt
channel-type.gardena.manualOverride.label=Manuelle Steuerung
channel-type.gardena.manualOverride.description=Manuelle Steuerung
channel-type.gardena.manualOverride.state.option.inactive=Inaktiv
channel-type.gardena.manualOverride.state.option.open=Offen
channel-type.gardena.manualOverride.state.option.undefined=Undefiniert
channel-type.gardena.buttonManualOverrideTime.label=Zeit manuelle Steuerung
channel-type.gardena.buttonManualOverrideTime.description=Die Zeit die das Ventil geöffnet ist, wenn der Knopf am Gerät gedrückt wird
channel-type.gardena.outletCommand.label=Ventil
channel-type.gardena.outletCommand.description=Öffnen und Schließen des Ventils
channel-type.gardena.frostWarning.label=Frostwarnung
channel-type.gardena.frostWarning.description=Frostwarnung
channel-type.gardena.frostWarning.state.option.no_frost=Kein Frost
channel-type.gardena.frostWarning.state.option.frost=Frost
channel-type.gardena.frostWarning.state.option.undefined=Undefiniert
channel-type.gardena.humidity.label=Feuchtigkeit
channel-type.gardena.humidity.description=Die Feuchtigkeit
channel-type.gardena.light.label=Helligkeit
channel-type.gardena.light.description=Die Helligkeit
channel-type.gardena.firmwareStatus.label=Status
channel-type.gardena.firmwareStatus.description=Der Firmware Status
channel-type.gardena.firmwareUploadProgress.label=Upload Fortschritt
channel-type.gardena.firmwareUploadProgress.description=Der Firmware upload Fortschritt
channel-type.gardena.firmwareAvailableVersion.label=Verfügbare Version
channel-type.gardena.firmwareAvailableVersion.description=Verfügbare Firmware Version
channel-type.gardena.pumpMode.label=Modus
channel-type.gardena.pumpMode.description=Der Modus
channel-type.gardena.pumpMode.state.option.off=Aus
channel-type.gardena.pumpMode.state.option.auto=Automatik
channel-type.gardena.pumpOnOff.label=Pumpe
channel-type.gardena.pumpOnOff.description=Die Pumpe
channel-type.gardena.pumpOnOff.state.option.off=Aus
channel-type.gardena.pumpOnOff.state.option.on=Ein
channel-type.gardena.turnOnPressure.label=Einschaltdruck
channel-type.gardena.turnOnPressure.description=Der Einschaltdruck
channel-type.gardena.operatingMode.label=Betriebsmodus
channel-type.gardena.operatingMode.description=Der Betriebsmodus
channel-type.gardena.operatingMode.state.option.scheduled=Zeitgesteuert
channel-type.gardena.operatingMode.state.option.automatic=Automatisch
channel-type.gardena.counter.label=Zähler
channel-type.gardena.counter.description=Der Zähler
channel-type.gardena.outletPressure.label=Auslassdruck
channel-type.gardena.outletPressure.description=Der Auslassdruck
channel-type.gardena.flowRate.label=Fördermenge
channel-type.gardena.flowRate.description=Die Fördermenge
channel-type.gardena.flowSinceLastReset.label=Durchfluss seit letztem Reset
channel-type.gardena.flowSinceLastReset.description=Der Durchfluss seit letztem Reset
channel-type.gardena.flowTotal.label=Gesamtdurchfluss
channel-type.gardena.flowTotal.description=Der Gesamtdurchfluss
channel-type.gardena.drippingAlert.label=Leckage-Erkennung
channel-type.gardena.drippingAlert.description=Die Leckage-Erkennung
channel-type.gardena.drippingAlert.state.option.sixty=Sechzig
channel-type.gardena.drippingAlert.state.option.two=Zwei
channel-type.gardena.drippingAlert.state.option.off=Aus
channel-type.gardena.manualWatering.label=Manuelle Bewässerung
channel-type.gardena.manualWatering.description=Die manuelle Bewässerung
channel-type.gardena.manualWateringTimer.label=Manueller Bewässerungs-Timer
channel-type.gardena.manualWateringTimer.description=Der manuelle Bewässerungs-Timer
channel-type.gardena.manualWateringTimer.state.option.0=Aus
channel-type.gardena.lastManualOverrideTime.label=Letzte manuelle Betätigung
channel-type.gardena.lastManualOverrideTime.description=Die letzte manuelle Betätigung
channel-type.gardena.adaptiveSchedulingLastDecision.label=Intelligente Zeitplanung
channel-type.gardena.adaptiveSchedulingLastDecision.description=Die letzte Entscheidung der intelligenten Zeitplanung
channel-type.gardena.adaptiveSchedulingLastDecision.state.option.undefined=Undefiniert
channel-type.gardena.adaptiveSchedulingLastDecision.state.option.watered_sensor_timeout=Keine Sensordaten
channel-type.gardena.adaptiveSchedulingLastDecision.state.option.skipped=Zeiplan ausgesetzt
channel-type.gardena.adaptiveSchedulingLastDecision.state.option.watered=Nach Zeitplan
channel-type.gardena.temperatureMin.label=Minimum Temperatur
channel-type.gardena.temperatureMin.description=Die minimale Temperatur
channel-type.gardena.temperatureMax.label=Maximum Temperatur
channel-type.gardena.temperatureMax.description=Die maximale Temperatur
channel-type.gardena.errorCounter.label=Fehlerzähler
channel-type.gardena.errorCounter.description=Der Fehlerzähler
channel-type.gardena.errorCounterOne.label=Fehlerzähler 1
channel-type.gardena.errorCounterOne.description=Der 1 Fehlerzähler
channel-type.gardena.errorCounterTwo.label=Fehlerzähler 2
channel-type.gardena.errorCounterTwo.description=Der 2 Fehlerzähler
channel-type.gardena.errorCounterThree.label=Fehlerzähler 3
channel-type.gardena.errorCounterThree.description=Der 3 Fehlerzähler
channel-type.gardena.errorCounterFour.label=Fehlerzähler 4
channel-type.gardena.errorCounterFour.description=Der vierte Fehlerzähler
channel-type.gardena.operatingDays.label=Betriebstage
channel-type.gardena.operatingDays.description=Die Betriebstage
channel-type.gardena.scheduledWateringEnd.label=Geplantes Bewässerungsende
channel-type.gardena.scheduledWateringEnd.description=Das geplante Bewässerungsende
channel-type.gardena.scheduledWateringNextStart.label=Nächste geplante Bewässerung
channel-type.gardena.scheduledWateringNextStart.description=Die nächste geplante Bewässerung
channel-type.gardena.cuttingTime.label=Mähdauer
channel-type.gardena.cuttingTime.description=Die gesamte Mähdauer
channel-type.gardena.chargingCycles.label=Ladezyklen
channel-type.gardena.chargingCycles.description=Die Anzahl der Ladezyklen
channel-type.gardena.collisions.label=Kollisionen
channel-type.gardena.collisions.description=Die Anzahl der Kollisionen
channel-type.gardena.runningTime.label=Laufzeit
channel-type.gardena.runningTime.description=Die gesamte Laufzeit
channel-type.gardena.powerTimer.label=Einschaltzeit
channel-type.gardena.powerTimer.description=Die Einschaltzeit
channel-type.gardena.powerTimer.state.option.on=Ein
channel-type.gardena.powerTimer.state.option.off=Aus
channel-type.gardena.powerTimer.state.option.60=1 Minute
channel-type.gardena.powerTimer.state.option.300=5 Minuten
channel-type.gardena.powerTimer.state.option.900=15 Minuten
channel-type.gardena.powerTimer.state.option.3600=1 Stunde
channel-type.gardena.powerTimer.state.option.7200=2 Stunden
channel-type.gardena.powerTimer.state.option.18000=5 Stunden
channel-type.gardena.powerTimer.state.option.43200=12 Stunden
channel-type.gardena.powerTimer.state.option.86400=1 Tag
channel-type.gardena.powerTimer.state.option.259200=3 Tage
channel-type.gardena.powerTimer.state.option.604800=1 Woche
channel-type.gardena.powerTimer.state.option.1209600=2 Wochen
channel-type.gardena.powerError.label=Fehler
channel-type.gardena.powerError.description=Fehler
channel-type.gardena.powerError.state.option.ok=OK
channel-type.gardena.powerError.state.option.timer_cancelled=Timer abgebrochen
channel-type.gardena.powerError.state.option.unknown=Unbekannt
channel-type.gardena.wateringTimer.label=Bewässerungstimer
channel-type.gardena.wateringTimer.description=Der Bewässerungstimer
channel-type.gardena.valvesConnected.label=Angeschlossenen Ventile
channel-type.gardena.valvesConnected.description=Die angeschlossenen Ventile
channel-type.gardena.valvesMasterConfig.label=Ventile Hauptkonfiguration
channel-type.gardena.valvesMasterConfig.description=Die Hauptkonfiguration der Ventile
channel-type.gardena.ic24error.label=Bewässerungssteuerung Fehler
channel-type.gardena.ic24error.description=Der Fehler der Bewässerungssteuerung
channel-type.gardena.ic24error.state.option.voltage_drop=Spannungsabfall
channel-type.gardena.ic24error.state.option.wrong_power_supply=Falsche Stromversorgung
channel-type.gardena.ic24error.state.option.no_mcu_connection=Keine MCU verbunden
channel-type.gardena.ic24error.state.option.unknown=Unbekannt
channel-type.gardena.rfLinkState.label=Verbinungsstatus
channel-type.gardena.rfLinkState.description=Der Verbinungsstatus
channel-type.gardena.rfLinkState.state.option.ONLINE=Online
channel-type.gardena.rfLinkState.state.option.OFFLINE=Offline
channel-type.gardena.rfLinkState.state.option.UNKNOWN=Unbekannt

View File

@ -4,39 +4,31 @@
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">
<!-- Gardena Smart Home Account -->
<!-- Gardena smart system Account -->
<bridge-type id="account">
<label>Gardena Smart Home Account</label>
<description>The Gardena Smart Home Account</description>
<label>Gardena Smart System Account</label>
<description>The Gardena smart system account</description>
<config-description>
<parameter name="email" type="text" required="true">
<label>Email</label>
<description>Email address for logging in to Gardena Smart Home</description>
<description>Email address for logging in to Gardena smart system</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<context>password</context>
<description>Password for logging in to Gardena Smart Home</description>
<description>Password for logging in to Gardena smart system</description>
</parameter>
<parameter name="sessionTimeout" type="integer">
<label>Session Timeout</label>
<description>The timeout in minutes for a session to Gardena Smart Home</description>
<advanced>true</advanced>
<default>30</default>
<parameter name="apiKey" type="text" required="true">
<label>API Key</label>
<description>The Gardena smart system integration API key</description>
</parameter>
<parameter name="connectionTimeout" type="integer">
<parameter name="connectionTimeout" type="integer" min="0" unit="s">
<label>Connection Timeout</label>
<description>The timeout in seconds for connections to Gardena Smart Home</description>
<advanced>true</advanced>
<description>The timeout in seconds for connections to Gardena smart system integration API</description>
<unitLabel>seconds</unitLabel>
<default>10</default>
</parameter>
<parameter name="refresh" type="integer">
<label>Refresh Interval</label>
<description>The interval in seconds for refreshing the data from Gardena Smart Home</description>
<advanced>true</advanced>
<default>60</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>