]> git.basschouten.com Git - openhab-addons.git/commitdiff
[gardena] Improve and fix UoM support (#15523)
authormaniac103 <dannybaumann@web.de>
Sat, 2 Sep 2023 21:50:58 +0000 (23:50 +0200)
committerGitHub <noreply@github.com>
Sat, 2 Sep 2023 21:50:58 +0000 (23:50 +0200)
* [gardena] Improve and fix UoM support

Properly convert incoming UoM values for command durations, and output
measurements as UoM values where possible.

* [gardena] Fix signal strength channel value

Previously the binding sent 0..100, but the system expects 0..4 for the
system.signal-strength channel.

* [gardena] Update README
* [gardena] Use actual units in state description where appropriate

Signed-off-by: Danny Baumann <dannybaumann@web.de>
bundles/org.openhab.binding.gardena/README.md
bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/handler/GardenaThingHandler.java
bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/Device.java
bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/LocalService.java [deleted file]
bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/CommonService.java
bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/MowerService.java
bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/PowerSocketService.java
bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/SensorService.java
bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/ValveService.java
bundles/org.openhab.binding.gardena/src/main/resources/OH-INF/thing/thing-types.xml

index bbf7b6f5501a5c41950aa28abb9e45c7af24c3c2..0402fda0dfbe14c9853ae960ffec783e95bdaa99 100644 (file)
@@ -88,10 +88,10 @@ Sensor refresh commands are not yet supported by the Gardena smart system integr
 ```java
 // smart Water Control
 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" }
+Number:Time WC_Valve_Duration             "Last Watering Duration [%d min]" { channel="gardena:water_control:home:myWateringComputer:valve#duration" }
 
-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" }
+Number:Time WC_Valve_cmd_Duration         "Command Duration [%d min]" { channel="gardena:water_control:home:myWateringComputer:valve_commands#commandDuration" }
+Switch  WC_Valve_cmd_OpenWithDuration     "Start Watering Timer" { 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" }
 
 openhab:status WC_Valve_Duration // returns the duration of the last watering request if still active, or 0
@@ -101,7 +101,7 @@ openhab:status WC_Valve_Activity // returns the current valve activity  (CLOSED|
 All channels are read-only, except the command group and the lastUpdate timestamp
 
 ```shell
-openhab:send WC_Valve_cmd_Duration.sendCommand(10) // set the duration for the command to 10min
+openhab:send WC_Valve_cmd_Duration.sendCommand(600) // set the duration for the command to 10min
 openhab:send WC_Valve_cmd_OpenWithDuration.sendCommand(ON) // start watering
 openhab:send WC_Valve_cmd_CloseValve.sendCommand(ON) // stop any active watering
 ```
index cb03291d6f0cbc8041c87a71d696c771553f6e52..1a47ca3fee82549f302c720dadd15c64487c047d 100644 (file)
@@ -16,10 +16,13 @@ import static org.openhab.binding.gardena.internal.GardenaBindingConstants.*;
 
 import java.time.ZonedDateTime;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
+import javax.measure.Unit;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.gardena.internal.GardenaSmart;
@@ -47,6 +50,7 @@ import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.Units;
 import org.openhab.core.thing.Bridge;
 import org.openhab.core.thing.Channel;
 import org.openhab.core.thing.ChannelUID;
@@ -72,6 +76,7 @@ public class GardenaThingHandler extends BaseThingHandler {
     private final Logger logger = LoggerFactory.getLogger(GardenaThingHandler.class);
     private TimeZoneProvider timeZoneProvider;
     private @Nullable ScheduledFuture<?> commandResetFuture;
+    private Map<String, Integer> commandDurations = new HashMap<>();
 
     public GardenaThingHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
         super(thing);
@@ -132,14 +137,17 @@ public class GardenaThingHandler extends BaseThingHandler {
      */
     protected void updateChannel(ChannelUID channelUID) throws GardenaException, AccountHandlerNotAvailableException {
         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);
-                }
+        if (groupId == null) {
+            return;
+        }
+        if (isLocalDurationCommand(channelUID)) {
+            int commandDuration = getCommandDurationSeconds(getDeviceDataItemProperty(channelUID));
+            updateState(channelUID, new QuantityType<>(commandDuration, Units.SECOND));
+        } else if (!groupId.endsWith("_commands")) {
+            Device device = getDevice();
+            State state = convertToState(device, channelUID);
+            if (state != null) {
+                updateState(channelUID, state);
             }
         }
     }
@@ -148,60 +156,66 @@ public class GardenaThingHandler extends BaseThingHandler {
      * Converts a Gardena property value to an openHAB state.
      */
     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();
+        String unitPropertyPath = propertyPath;
 
         if (propertyName.endsWith("_timestamp")) {
             propertyPath += propertyName.replace("_", ".");
         } else {
             propertyPath += propertyName + ".value";
+            unitPropertyPath += propertyName + "Unit";
         }
 
-        String acceptedItemType = null;
-        try {
-            Channel channel = getThing().getChannel(channelUID.getId());
-            if (channel != null) {
-                acceptedItemType = StringUtils.substringBefore(channel.getAcceptedItemType(), ":");
+        Channel channel = getThing().getChannel(channelUID.getId());
+        String acceptedItemType = channel != null ? channel.getAcceptedItemType() : null;
+        String baseItemType = StringUtils.substringBefore(acceptedItemType, ":");
 
-                if (acceptedItemType != null) {
-                    boolean isNullPropertyValue = PropertyUtils.isNull(device, propertyPath);
-                    boolean isDurationProperty = "duration".equals(propertyName);
+        boolean isNullPropertyValue = PropertyUtils.isNull(device, propertyPath);
 
-                    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;
+        if (isNullPropertyValue) {
+            return UnDefType.NULL;
+        }
+        if (baseItemType == null || acceptedItemType == null) {
+            return null;
+        }
+
+        try {
+            switch (baseItemType) {
+                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);
+                        Unit<?> unit = PropertyUtils.getPropertyValue(device, unitPropertyPath, Unit.class);
+                        if (value == null) {
+                            return UnDefType.NULL;
+                        } else {
+                            if ("rfLinkLevel".equals(propertyName)) {
+                                // Gardena gives us link level as 0..100%, while the system.signal-strength
+                                // channel type wants a 0..4 enum
+                                int percent = value.intValue();
+                                value = percent == 100 ? 4 : percent / 20;
+                                unit = 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);
+                            if (acceptedItemType.equals(baseItemType) || unit == null) {
+                                // No UoM or no unit found
+                                return new DecimalType(value);
+                            } else {
+                                return new QuantityType<>(value, unit);
                             }
-                            return UnDefType.NULL;
+                        }
+                    }
+                case "DateTime":
+                    Date date = PropertyUtils.getPropertyValue(device, propertyPath, Date.class);
+                    if (date == null) {
+                        return UnDefType.NULL;
+                    } else {
+                        ZonedDateTime zdt = ZonedDateTime.ofInstant(date.toInstant(), timeZoneProvider.getTimeZone());
+                        return new DateTimeType(zdt);
                     }
-                }
             }
         } catch (GardenaException e) {
             logger.warn("Channel '{}' cannot be updated as device does not contain propertyPath '{}'", channelUID,
@@ -223,8 +237,15 @@ public class GardenaThingHandler extends BaseThingHandler {
                 logger.debug("Refreshing Gardena connection");
                 getGardenaSmart().restartWebsockets();
             } else if (isLocalDurationCommand(channelUID)) {
-                QuantityType<?> quantityType = (QuantityType<?>) command;
-                getDevice().getLocalService(dataItemProperty).commandDuration = quantityType.intValue() * 60;
+                QuantityType<?> commandInSeconds = null;
+                if (command instanceof QuantityType<?> timeCommand) {
+                    commandInSeconds = timeCommand.toUnit(Units.SECOND);
+                }
+                if (commandInSeconds != null) {
+                    commandDurations.put(dataItemProperty, commandInSeconds.intValue());
+                } else {
+                    logger.info("Invalid command '{}' for command duration channel, ignoring.", command);
+                }
             } else if (isOnCommand) {
                 GardenaCommand gardenaCommand = getGardenaCommand(dataItemProperty, channelUID);
                 logger.debug("Received Gardena command: {}, {}", gardenaCommand.getClass().getSimpleName(),
@@ -261,17 +282,15 @@ public class GardenaThingHandler extends BaseThingHandler {
         String commandName = channelUID.getIdWithoutGroup().toUpperCase();
         String groupId = channelUID.getGroupId();
         if (groupId != null) {
+            int commandDuration = getCommandDurationSeconds(dataItemProperty);
             if ("valveSet_commands".equals(groupId)) {
                 return new ValveSetCommand(ValveSetControl.valueOf(commandName));
             } else if (groupId.startsWith("valve") && groupId.endsWith("_commands")) {
-                return new ValveCommand(ValveControl.valueOf(commandName),
-                        getDevice().getLocalService(dataItemProperty).commandDuration);
+                return new ValveCommand(ValveControl.valueOf(commandName), commandDuration);
             } else if ("mower_commands".equals(groupId)) {
-                return new MowerCommand(MowerControl.valueOf(commandName),
-                        getDevice().getLocalService(dataItemProperty).commandDuration);
+                return new MowerCommand(MowerControl.valueOf(commandName), commandDuration);
             } else if ("powerSocket_commands".equals(groupId)) {
-                return new PowerSocketCommand(PowerSocketControl.valueOf(commandName),
-                        getDevice().getLocalService(dataItemProperty).commandDuration);
+                return new PowerSocketCommand(PowerSocketControl.valueOf(commandName), commandDuration);
             }
         }
         throw new GardenaException("Command " + channelUID.getId() + " not found or groupId null");
@@ -308,6 +327,11 @@ public class GardenaThingHandler extends BaseThingHandler {
         throw new GardenaException("Can't extract dataItemProperty from channel group " + channelUID.getGroupId());
     }
 
+    private int getCommandDurationSeconds(String dataItemProperty) {
+        Integer duration = commandDurations.get(dataItemProperty);
+        return duration != null ? duration : 3600;
+    }
+
     /**
      * Returns true, if the channel is the duration command.
      */
index 6ecdfcf1973a2a1d37f7e4c7381aa6b21018c314..244540a289c780d18a5347228d0faefbb7a40ab8 100644 (file)
@@ -15,8 +15,6 @@ 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.CommonService;
@@ -60,25 +58,10 @@ public class Device {
     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.
      */
diff --git a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/LocalService.java b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/LocalService.java
deleted file mode 100644 (file)
index e7d43d3..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Copyright (c) 2010-2023 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;
-}
index d239ec826483299bd5cc938405174fe201bb5699..506b08df3eb9e7c40167c8b4be474e84aee7eaae 100644 (file)
  */
 package org.openhab.binding.gardena.internal.model.dto.api;
 
+import javax.measure.Unit;
+import javax.measure.quantity.Dimensionless;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.openhab.core.library.unit.Units;
+
 /**
  * Represents a Gardena object that is sent via the Gardena API.
  *
@@ -20,8 +26,10 @@ package org.openhab.binding.gardena.internal.model.dto.api;
 public class CommonService {
     public UserDefinedNameWrapper name;
     public TimestampedIntegerValue batteryLevel;
+    public @NonNull Unit<@NonNull Dimensionless> batteryLevelUnit = Units.PERCENT;
     public TimestampedStringValue batteryState;
     public TimestampedIntegerValue rfLinkLevel;
+    public @NonNull Unit<@NonNull Dimensionless> rfLinkLevelUnit = Units.PERCENT;
     public StringValue serial;
     public StringValue modelType;
     public TimestampedStringValue rfLinkState;
index 4d988d3e2d62fb69353bf6ab6af8c00ae491c289..13f53ec0cf396fef4e1651e62c15e1b7d9c5fcd1 100644 (file)
  */
 package org.openhab.binding.gardena.internal.model.dto.api;
 
+import javax.measure.Unit;
+import javax.measure.quantity.Time;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.openhab.core.library.unit.Units;
+
 /**
  * Represents a Gardena object that is sent via the Gardena API.
  *
@@ -23,4 +29,5 @@ public class MowerService {
     public TimestampedStringValue activity;
     public TimestampedStringValue lastErrorCode;
     public IntegerValue operatingHours;
+    public @NonNull Unit<@NonNull Time> operatingHoursUnit = Units.HOUR;
 }
index 3d52c9185f4ea03e1af5a72324c10f4ad2db1d14..8cdae710f28b6b9231d028299dc58ddf6e4d9441 100644 (file)
  */
 package org.openhab.binding.gardena.internal.model.dto.api;
 
+import javax.measure.Unit;
+import javax.measure.quantity.Time;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.openhab.core.library.unit.Units;
+
 /**
  * Represents a Gardena object that is sent via the Gardena API.
  *
@@ -23,4 +29,5 @@ public class PowerSocketService {
     public TimestampedStringValue state;
     public TimestampedStringValue lastErrorCode;
     public TimestampedIntegerValue duration;
+    public @NonNull Unit<@NonNull Time> durationUnit = Units.SECOND;
 }
index d9d09e42ba354105a864466522d3d6e83ba66164..6af053190348d44eaada5c7ed6ca34b645061f52 100644 (file)
  */
 package org.openhab.binding.gardena.internal.model.dto.api;
 
+import javax.measure.Unit;
+import javax.measure.quantity.Dimensionless;
+import javax.measure.quantity.Illuminance;
+import javax.measure.quantity.Temperature;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+
 /**
  * Represents a Gardena object that is sent via the Gardena API.
  *
@@ -19,7 +28,11 @@ package org.openhab.binding.gardena.internal.model.dto.api;
  */
 public class SensorService {
     public TimestampedIntegerValue soilHumidity;
+    public @NonNull Unit<@NonNull Dimensionless> soilHumidityUnit = Units.PERCENT;
     public TimestampedIntegerValue soilTemperature;
+    public @NonNull Unit<@NonNull Temperature> soilTemperatureUnit = SIUnits.CELSIUS;
     public TimestampedIntegerValue ambientTemperature;
+    public @NonNull Unit<@NonNull Temperature> ambientTemperatureUnit = SIUnits.CELSIUS;
     public TimestampedIntegerValue lightIntensity;
+    public @NonNull Unit<@NonNull Illuminance> lightIntensityUnit = Units.LUX;
 }
index e4247c259d0a5c13ee190d8a6c12c6b9483e60f3..8cfc014b23e13cd0c23056a3948e660e70e49850 100644 (file)
  */
 package org.openhab.binding.gardena.internal.model.dto.api;
 
+import javax.measure.Unit;
+import javax.measure.quantity.Time;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.openhab.core.library.unit.Units;
+
 /**
  * Represents a Gardena object that is sent via the Gardena API.
  *
@@ -23,4 +29,5 @@ public class ValveService {
     public TimestampedStringValue state;
     public TimestampedStringValue lastErrorCode;
     public TimestampedIntegerValue duration;
+    public @NonNull Unit<@NonNull Time> durationUnit = Units.SECOND;
 }
index 7584f983f3374f26758e5b9f04be3be334b6d49b..c916c85fb9bc83ffa02f8b6ffd866b59e137f1a6 100644 (file)
        <channel-type id="duration">
                <item-type>Number:Time</item-type>
                <label>Duration</label>
-               <description>Duration in minutes</description>
+               <description>Duration</description>
                <state readOnly="true" pattern="%d min"/>
        </channel-type>
 
        <channel-type id="soilHumidity">
                <item-type>Number:Dimensionless</item-type>
                <label>Soil Humidity</label>
-               <description>Soil humidity in percent</description>
-               <state readOnly="true" pattern="%d %unit%"/>
+               <description>Soil humidity</description>
+               <state readOnly="true" pattern="%d %%"/>
        </channel-type>
 
        <channel-type id="lightIntensity">
                <item-type>Number:Illuminance</item-type>
                <label>Light Intensity</label>
-               <description>Light intensity in Lux</description>
-               <state readOnly="true" pattern="%d lux"/>
+               <description>Light intensity</description>
+               <state readOnly="true" pattern="%d %unit%"/>
        </channel-type>
 
        <channel-type id="temperature">
                <item-type>Number:Time</item-type>
                <label>Operating Hours</label>
                <description>The operating hours</description>
-               <state readOnly="true" pattern="%d %unit%"/>
+               <state readOnly="true" pattern="%d h"/>
        </channel-type>
 
        <channel-type id="batteryState">