diff --git a/bundles/org.openhab.binding.gardena/README.md b/bundles/org.openhab.binding.gardena/README.md index bbf7b6f5501..0402fda0dfb 100644 --- a/bundles/org.openhab.binding.gardena/README.md +++ b/bundles/org.openhab.binding.gardena/README.md @@ -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 ``` diff --git a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/handler/GardenaThingHandler.java b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/handler/GardenaThingHandler.java index cb03291d6f0..1a47ca3fee8 100644 --- a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/handler/GardenaThingHandler.java +++ b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/handler/GardenaThingHandler.java @@ -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 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"; + } + + Channel channel = getThing().getChannel(channelUID.getId()); + String acceptedItemType = channel != null ? channel.getAcceptedItemType() : null; + String baseItemType = StringUtils.substringBefore(acceptedItemType, ":"); + + boolean isNullPropertyValue = PropertyUtils.isNull(device, propertyPath); + + if (isNullPropertyValue) { + return UnDefType.NULL; + } + if (baseItemType == null || acceptedItemType == null) { + return null; } - String acceptedItemType = null; try { - Channel channel = getThing().getChannel(channelUID.getId()); - if (channel != null) { - acceptedItemType = StringUtils.substringBefore(channel.getAcceptedItemType(), ":"); - - if (acceptedItemType != null) { - boolean isNullPropertyValue = PropertyUtils.isNull(device, propertyPath); - boolean isDurationProperty = "duration".equals(propertyName); - - 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); - } + 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; + } + if (acceptedItemType.equals(baseItemType) || unit == null) { + // No UoM or no unit found + return new DecimalType(value); + } else { + return new QuantityType<>(value, unit); + } + } + } + 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. */ diff --git a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/Device.java b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/Device.java index 6ecdfcf1973..244540a289c 100644 --- a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/Device.java +++ b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/Device.java @@ -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 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 index e7d43d321f6..00000000000 --- a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/LocalService.java +++ /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; -} diff --git a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/CommonService.java b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/CommonService.java index d239ec82648..506b08df3eb 100644 --- a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/CommonService.java +++ b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/CommonService.java @@ -12,6 +12,12 @@ */ 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; diff --git a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/MowerService.java b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/MowerService.java index 4d988d3e2d6..13f53ec0cf3 100644 --- a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/MowerService.java +++ b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/MowerService.java @@ -12,6 +12,12 @@ */ 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; } diff --git a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/PowerSocketService.java b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/PowerSocketService.java index 3d52c9185f4..8cdae710f28 100644 --- a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/PowerSocketService.java +++ b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/PowerSocketService.java @@ -12,6 +12,12 @@ */ 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; } diff --git a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/SensorService.java b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/SensorService.java index d9d09e42ba3..6af05319034 100644 --- a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/SensorService.java +++ b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/SensorService.java @@ -12,6 +12,15 @@ */ 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; } diff --git a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/ValveService.java b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/ValveService.java index e4247c259d0..8cfc014b23e 100644 --- a/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/ValveService.java +++ b/bundles/org.openhab.binding.gardena/src/main/java/org/openhab/binding/gardena/internal/model/dto/api/ValveService.java @@ -12,6 +12,12 @@ */ 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; } diff --git a/bundles/org.openhab.binding.gardena/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.gardena/src/main/resources/OH-INF/thing/thing-types.xml index 7584f983f33..c916c85fb9b 100644 --- a/bundles/org.openhab.binding.gardena/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.gardena/src/main/resources/OH-INF/thing/thing-types.xml @@ -592,22 +592,22 @@ Number:Time - Duration in minutes + Duration Number:Dimensionless - Soil humidity in percent - + Soil humidity + Number:Illuminance - Light intensity in Lux - + Light intensity + @@ -621,7 +621,7 @@ Number:Time The operating hours - +