From 05c44eef695b3c4622e77702b7bac48a2c1cb6c3 Mon Sep 17 00:00:00 2001 From: "gael@lhopital.org" Date: Mon, 27 Oct 2025 15:34:59 +0100 Subject: [PATCH 1/4] Simplify Thing configuration Adapt to Java 21 and OH5 Signed-off-by: gael@lhopital.org --- .../org.openhab.binding.sagercaster/README.md | 44 +++--- .../internal/SagerCasterBindingConstants.java | 5 +- .../internal/SagerCasterHandlerFactory.java | 9 +- .../internal/caster/SagerPrediction.java | 21 +-- .../internal/caster/SagerWeatherCaster.java | 136 +++++++----------- .../SagerCasterDiscoveryService.java | 31 ++-- .../internal/handler/ExpiringMap.java | 38 +++-- .../internal/handler/SagerCasterHandler.java | 134 +++++++++-------- .../OH-INF/i18n/sagercaster.properties | 2 + .../resources/OH-INF/thing/thing-types.xml | 22 ++- .../resources/OH-INF/update/instructions.xml | 16 +++ 11 files changed, 217 insertions(+), 241 deletions(-) create mode 100644 bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/update/instructions.xml diff --git a/bundles/org.openhab.binding.sagercaster/README.md b/bundles/org.openhab.binding.sagercaster/README.md index c304248895598..436bd07030051 100644 --- a/bundles/org.openhab.binding.sagercaster/README.md +++ b/bundles/org.openhab.binding.sagercaster/README.md @@ -6,7 +6,7 @@ The Sager Weathercaster is a scientific instrument for accurate prediction of th - To operate, this binding will need to use channel values provided by other means (e.g. Weather Binding, Netatmo, a 1-Wire personal weather station...) -- This binding buffers readings for some hours before producing weather forecasts (wind direction and sea level pressure). SagerWeatherCaster needs an observation period of minimum 6 hours. +- This binding buffers readings up to 6 hours before producing reliable weather forecasts (wind direction and sea level pressure). For these reasons, this binding is not a binding in the usual sense. @@ -25,7 +25,6 @@ The binding itself does not require any configuration. | Name | Type | Description | | ------------------ | -------- | -------------------------------------------------------------------- | | location (*) | Location | Latitude and longitude of the desired weather forecast. | -| observation-period | int | Minimum delay (in hours) before producing forecasts. Defaulted to 6. | (*) Only latitude is used by the algorithm. @@ -33,26 +32,27 @@ The binding itself does not require any configuration. The binding will use some input channels, that can be configured directly with profiles (sample below). -| Name | Group | Type | Description | -| ------------------- | ------ | -------------------- | --------------------------------------------------------------- | -| is-raining (*) | input | Switch | On if it is raining, else Off. | -| rain-qtty (*) | input | Number | Any value that give indication of a current rain volume | -| or | input | Number:Speed | Any value that give indication of a current rain volume eg mm/h | -| or | input | Number:Length | Any value that give indication of a current rain volume eg mm | -| cloudiness | input | Number:Dimensionless | Cloud cover percentage | -| wind-speed-beaufort | input | Number | Wind speed expressed using the Beaufort scale | -| pressure | input | Number:Pressure | Sea level pressure | -| wind-angle | input | Number:Angle | Wind direction | -| temperature | input | Number:Temperature | Outside temperature | -| timestamp | output | DateTime | Timestamp of the last forecast update | -| forecast | output | String | Description of the weather forecast | -| velocity | output | String | Description of the expected wind evolution | -| velocity-beaufort | output | Number | Expected wind evolution using the Beaufort scale | -| wind-from | output | String | Expected wind orientation | -| wind-to | output | String | Evolution of the expected wind orientation | -| wind-evolution | output | String | Wind orientation evolution over observation period | -| pressure-trend | output | String | Pressure evolution over observation period | -| temperature-trend | output | String | Temperature evolution over observation period | +| Name | Group | Type | Description | +| ------------------- | ------ | -------------------- | ---------------------------------------------------------------------- | +| is-raining (*) | input | Switch | On if it is raining, else Off. | +| rain-qtty (*) | input | Number | Any value that give indication of a current rain volume | +| or | input | Number:Speed | Any value that give indication of a current rain volume eg mm/h | +| or | input | Number:Length | Any value that give indication of a current rain volume eg mm | +| cloudiness | input | Number:Dimensionless | Cloud cover percentage | +| wind-speed-beaufort | input | Number | Wind speed expressed using the Beaufort scale | +| pressure | input | Number:Pressure | Sea level pressure | +| wind-angle | input | Number:Angle | Wind direction | +| temperature | input | Number:Temperature | Outside temperature | +| timestamp | output | DateTime | Timestamp of the last forecast update | +| forecast | output | String | Description of the weather forecast | +| velocity | output | String | Description of the expected wind evolution | +| velocity-beaufort | output | Number | Expected wind evolution using the Beaufort scale | +| wind-from | output | String | Expected wind orientation | +| wind-to | output | String | Evolution of the expected wind orientation | +| wind-evolution | output | String | Wind orientation evolution over observation period | +| pressure-trend | output | String | Pressure evolution over observation period | +| temperature-trend | output | String | Temperature evolution over observation period | +| reliability | output | Number:Dimensionless | Grows along with the algorithm historical data storage (up to 6 hours) | (*) You may use either is-raining, either rain-qtty depending upon the data available in your system. diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterBindingConstants.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterBindingConstants.java index a3f206bbc37f1..a1a388ae27ffe 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterBindingConstants.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterBindingConstants.java @@ -30,8 +30,8 @@ public class SagerCasterBindingConstants { public static final String LOCAL = "local"; // List of all Thing Type UIDs - public static final ThingTypeUID THING_TYPE_SAGERCASTER = new ThingTypeUID(BINDING_ID, "sagercaster"); - + public static final ThingTypeUID THING_TYPE_SAGERCASTER = new ThingTypeUID(BINDING_ID, BINDING_ID); + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SAGERCASTER); // Configuration elements public static final String CONFIG_LOCATION = "location"; public static final String CONFIG_PERIOD = "observation-period"; @@ -49,6 +49,7 @@ public class SagerCasterBindingConstants { public static final String CHANNEL_PRESSURETREND = "pressure-trend"; public static final String CHANNEL_TEMPERATURETREND = "temperature-trend"; public static final String CHANNEL_TIMESTAMP = "timestamp"; + public static final String CHANNEL_RELIABILITY = "reliability"; // Input channel ids public static final String CHANNEL_CLOUDINESS = "cloudiness"; diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterHandlerFactory.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterHandlerFactory.java index ee4a3269bd713..8c0f3f2cf6088 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterHandlerFactory.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterHandlerFactory.java @@ -12,9 +12,7 @@ */ package org.openhab.binding.sagercaster.internal; -import static org.openhab.binding.sagercaster.internal.SagerCasterBindingConstants.THING_TYPE_SAGERCASTER; - -import java.util.Set; +import static org.openhab.binding.sagercaster.internal.SagerCasterBindingConstants.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -38,13 +36,12 @@ @Component(service = ThingHandlerFactory.class, configurationPid = "binding.sagercaster") @NonNullByDefault public class SagerCasterHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SAGERCASTER); private final WindDirectionStateDescriptionProvider stateDescriptionProvider; private final SagerWeatherCaster sagerWeatherCaster; @Activate - public SagerCasterHandlerFactory(@Reference SagerWeatherCaster sagerWeatherCaster, - @Reference WindDirectionStateDescriptionProvider provider) { + public SagerCasterHandlerFactory(final @Reference SagerWeatherCaster sagerWeatherCaster, + final @Reference WindDirectionStateDescriptionProvider provider) { this.stateDescriptionProvider = provider; this.sagerWeatherCaster = sagerWeatherCaster; } diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerPrediction.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerPrediction.java index 98a3da74db0d2..31987ef68cbc9 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerPrediction.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerPrediction.java @@ -15,35 +15,26 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * This class holds the result of the SagerCaster algorithm + * This record holds the result of the SagerCaster algorithm * * @author Gaƫl L'hopital - Initial contribution */ @NonNullByDefault -public class SagerPrediction { - private final String prediction; - - public SagerPrediction(String sagerCode) { - this.prediction = sagerCode; - } - - public String getSagerCode() { - return prediction; - } +public record SagerPrediction(String sagerCode) { public String getForecast() { - return Character.toString(prediction.charAt(0)); + return Character.toString(sagerCode.charAt(0)); } public String getWindVelocity() { - return Character.toString(prediction.charAt(1)); + return Character.toString(sagerCode.charAt(1)); } public String getWindDirection() { - return Character.toString(prediction.charAt(2)); + return Character.toString(sagerCode.charAt(2)); } public String getWindDirection2() { - return prediction.length() > 3 ? Character.toString(prediction.charAt(3)) : SagerWeatherCaster.UNDEF; + return sagerCode.length() > 3 ? Character.toString(sagerCode.charAt(3)) : SagerWeatherCaster.UNDEF; } } diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerWeatherCaster.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerWeatherCaster.java index 3085a638c1957..bf00fc456d09f 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerWeatherCaster.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerWeatherCaster.java @@ -101,7 +101,7 @@ public class SagerWeatherCaster { // barometer for a period of about 6 hours prior to the forecast. private int nubes = -1; private int currentBeaufort = -1; - private double cloudLevel = -1; + private Integer cloudLevel = -1; private boolean raining = false; @Activate @@ -169,15 +169,12 @@ public int getPressureEvolution() { private void sagerNubesUpdate() { int result; if (!raining) { - if (cloudLevel > 80) { - result = 4; // overcast - } else if (cloudLevel > 50) { - result = 3; // mostly overcast - } else if (cloudLevel > 20) { - result = 2; // partly cloudy - } else { - result = 1; // clear - } + result = switch (cloudLevel) { + case Integer i when i > 80 -> 4; // overcast + case Integer i when i > 50 -> 3; // mostly overcast + case Integer i when i > 20 -> 2; // partly cloudy + default -> 1; // clear + }; } else { result = 5; // raining } @@ -187,38 +184,29 @@ private void sagerNubesUpdate() { } } - private static int sagerPressureLevel(double current) { - if (current > 1029.46) { - return 1; - } else if (current > 1019.3) { - return 2; - } else if (current > 1012.53) { - return 3; - } else if (current > 1005.76) { - return 4; - } else if (current > 999) { - return 5; - } else if (current > 988.8) { - return 6; - } else if (current > 975.28) { - return 7; - } - return 8; + private static int sagerPressureLevel(Double current) { + return switch (current) { + case Double c when c > 1029.46 -> 1; + case Double c when c > 1019.3 -> 2; + case Double c when c > 1012.53 -> 3; + case Double c when c > 1005.76 -> 4; + case Double c when c > 999 -> 5; + case Double c when c > 988.8 -> 6; + case Double c when c > 975.28 -> 7; + default -> 8; + }; } private static int sagerPressureTrend(double current, double historic) { - double evol = current - historic; + Double evol = current - historic; - if (evol > 1.4) { - return 1; // Rising Rapidly - } else if (evol > 0.68) { - return 2; // Rising Slowly - } else if (evol > -0.68) { - return 3; // Normal - } else if (evol > -1.4) { - return 4; // Decreasing Slowly - } - return 5; // Decreasing Rapidly + return switch (evol) { + case Double e when e > 1.4 -> 1; // Rising Rapidly + case Double e when e > 0.68 -> 2; // Rising Slowly + case Double e when e > -0.68 -> 3; // Normal + case Double e when e > -1.4 -> 4; // Decreasing Slowly + default -> 5; // Decreasing Rapidly + }; } private static int sagerWindTrend(double historic, double position) { @@ -324,70 +312,46 @@ private void updatePrediction() { } public String getForecast() { - return Objects.requireNonNullElse(this.prevision.getForecast(), UNDEF); + return prevision instanceof SagerPrediction p ? p.getForecast() : UNDEF; } public String getWindVelocity() { - SagerPrediction prevision = this.prevision; - return prevision != null ? prevision.getWindVelocity() : UNDEF; + return prevision instanceof SagerPrediction p ? p.getWindVelocity() : UNDEF; } public String getWindDirection() { - SagerPrediction prevision = this.prevision; - return prevision != null ? prevision.getWindDirection() : UNDEF; + return prevision instanceof SagerPrediction p ? p.getWindDirection() : UNDEF; } public String getWindDirection2() { - SagerPrediction prevision = this.prevision; - return prevision != null ? prevision.getWindDirection2() : UNDEF; + return prevision instanceof SagerPrediction p ? p.getWindDirection2() : UNDEF; } public String getSagerCode() { - SagerPrediction prevision = this.prevision; - return prevision != null ? prevision.getSagerCode() : UNDEF; + return prevision instanceof SagerPrediction p ? p.sagerCode() : UNDEF; } - public void setLatitude(double latitude) { - if (latitude >= 66.6) { - usedDirections = NPZDIRECTIONS; - } else if (latitude >= 23.5) { - usedDirections = NTZDIRECTIONS; - } else if (latitude >= 0) { - usedDirections = NPZDIRECTIONS; - } else if (latitude > -23.5) { - usedDirections = SPZDIRECTIONS; - } else if (latitude > -66.6) { - usedDirections = STZDIRECTIONS; - } else { - usedDirections = SPZDIRECTIONS; - } + public void setLatitude(Double latitude) { + usedDirections = switch (latitude) { + case Double d when d >= 66.6 -> NPZDIRECTIONS; + case Double d when d >= 23.5 -> NTZDIRECTIONS; + case Double d when d >= 0 -> NPZDIRECTIONS; + case Double d when d > -23.5 -> SPZDIRECTIONS; + case Double d when d > -66.6 -> STZDIRECTIONS; + default -> SPZDIRECTIONS; + }; } public int getPredictedBeaufort() { - int result = currentBeaufort; - switch (getWindVelocity()) { - case "N": - result += 1; - break; - case "F": - result = 4; - break; - case "S": - result = 6; - break; - case "G": - result = 8; - break; - case "W": - result = 10; - break; - case "H": - result = 12; - break; - case "D": - result -= 1; - break; - } - return result; + return switch (getWindVelocity()) { + case "N" -> currentBeaufort + 1; + case "F" -> 4; + case "S" -> 6; + case "G" -> 8; + case "W" -> 10; + case "H" -> 12; + case "D" -> currentBeaufort - 1; + default -> currentBeaufort; + }; } } diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/discovery/SagerCasterDiscoveryService.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/discovery/SagerCasterDiscoveryService.java index e8c4072ea0ac6..d106dfb334778 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/discovery/SagerCasterDiscoveryService.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/discovery/SagerCasterDiscoveryService.java @@ -14,9 +14,6 @@ import static org.openhab.binding.sagercaster.internal.SagerCasterBindingConstants.*; -import java.util.Map; -import java.util.Objects; -import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -60,39 +57,28 @@ public class SagerCasterDiscoveryService extends AbstractDiscoveryService { @Activate public SagerCasterDiscoveryService(final @Reference LocaleProvider localeProvider, final @Reference TranslationProvider i18nProvider, final @Reference LocationProvider locationProvider) { - super(Set.of(THING_TYPE_SAGERCASTER), DISCOVER_TIMEOUT_SECONDS, true); + super(SUPPORTED_THING_TYPES_UIDS, DISCOVER_TIMEOUT_SECONDS, true); this.locationProvider = locationProvider; this.localeProvider = localeProvider; this.i18nProvider = i18nProvider; } - @Override - protected void activate(@Nullable Map configProperties) { - super.activate(configProperties); - } - - @Override - protected void modified(@Nullable Map configProperties) { - super.modified(configProperties); - } - @Override protected void startScan() { logger.debug("Starting Sager Weathercaster discovery scan"); - PointType location = locationProvider.getLocation(); - if (location == null) { + if (locationProvider.getLocation() instanceof PointType location) { + createResults(location); + } else { logger.debug("LocationProvider.getLocation() is not set -> Will not provide any discovery results"); - return; } - createResults(location); } @Override protected void startBackgroundDiscovery() { if (discoveryJob == null) { discoveryJob = scheduler.scheduleWithFixedDelay(() -> { - PointType currentLocation = locationProvider.getLocation(); - if (currentLocation != null && !Objects.equals(currentLocation, previousLocation)) { + if (locationProvider.getLocation() instanceof PointType currentLocation + && !currentLocation.equals(previousLocation)) { logger.debug("Location has been changed from {} to {}: Creating new discovery results", previousLocation, currentLocation); createResults(currentLocation); @@ -106,9 +92,8 @@ protected void startBackgroundDiscovery() { @Override protected void stopBackgroundDiscovery() { - ScheduledFuture localJob = discoveryJob; logger.debug("Stopping Sager Weathercaster background discovery"); - if (localJob != null && !localJob.isCancelled()) { + if (discoveryJob instanceof ScheduledFuture localJob && !localJob.isCancelled()) { if (localJob.cancel(true)) { discoveryJob = null; logger.debug("Stopped SagerCaster device background discovery"); @@ -117,7 +102,7 @@ protected void stopBackgroundDiscovery() { } public void createResults(PointType location) { - String propGeolocation = String.format("%s,%s", location.getLatitude(), location.getLongitude()); + String propGeolocation = "%s,%s".formatted(location.getLatitude(), location.getLongitude()); thingDiscovered(DiscoveryResultBuilder.create(SAGER_CASTER_THING).withLabel("Local Weather Forecast") .withRepresentationProperty(CONFIG_LOCATION).withProperty(CONFIG_LOCATION, propGeolocation).build()); } diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/ExpiringMap.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/ExpiringMap.java index d1fd0b80b67a2..0674d4fd99e73 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/ExpiringMap.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/ExpiringMap.java @@ -12,8 +12,9 @@ */ package org.openhab.binding.sagercaster.internal.handler; +import java.time.Duration; +import java.util.NavigableMap; import java.util.Optional; -import java.util.SortedMap; import java.util.TreeMap; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -26,24 +27,35 @@ */ @NonNullByDefault class ExpiringMap { - private final SortedMap values = new TreeMap<>(); - private Optional agedValue = Optional.empty(); - private long eldestAge = 0; + private final NavigableMap values = new TreeMap<>(); + private final long windowMillis; - public void setObservationPeriod(long eldestAge) { - this.eldestAge = eldestAge; + public ExpiringMap(Duration duration) { + this.windowMillis = duration.toMillis(); } - public void put(T newValue) { + /** + * @param newValue - added to the Map + * @return the eldest value if it existed before insertion + */ + public Optional put(T newValue) { long now = System.currentTimeMillis(); + + // removes all entries > 6h + long cutoff = now - windowMillis; + while (!values.isEmpty() && values.firstKey() < cutoff) { + values.pollFirstEntry(); + } + values.put(now, newValue); - values.keySet().stream().filter(key -> key < now - eldestAge).findFirst().ifPresent(eldest -> { - agedValue = Optional.ofNullable(values.get(eldest)); - values.entrySet().removeIf(map -> map.getKey() <= eldest); - }); + return Optional.ofNullable(values.size() < 2 ? null : values.firstEntry().getValue()); } - public Optional getAgedValue() { - return agedValue; + /** + * @return age of the eldest element in minutes + */ + public long getDataAgeInMin() { + long now = System.currentTimeMillis(); + return Duration.ofMillis(values.isEmpty() ? 0 : now - values.firstKey()).toMinutes(); } } diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java index 5444a8084274f..eddc3f2c50455 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java @@ -15,28 +15,30 @@ import static org.openhab.binding.sagercaster.internal.SagerCasterBindingConstants.*; import static org.openhab.core.library.unit.MetricPrefix.HECTO; -import java.math.BigDecimal; -import java.time.ZonedDateTime; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.TimeUnit; -import javax.measure.quantity.Angle; import javax.measure.quantity.Pressure; import javax.measure.quantity.Temperature; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.sagercaster.internal.WindDirectionStateDescriptionProvider; import org.openhab.binding.sagercaster.internal.caster.SagerWeatherCaster; 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.PercentType; +import org.openhab.core.library.types.PointType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; import org.openhab.core.library.unit.SIUnits; 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.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; @@ -53,22 +55,22 @@ */ @NonNullByDefault public class SagerCasterHandler extends BaseThingHandler { - + private final static Duration ALGORITHM_WINDOW = Duration.ofHours(6); private final Logger logger = LoggerFactory.getLogger(SagerCasterHandler.class); private final SagerWeatherCaster sagerWeatherCaster; private final WindDirectionStateDescriptionProvider stateDescriptionProvider; - private final ExpiringMap pressureCache = new ExpiringMap<>(); - private final ExpiringMap temperatureCache = new ExpiringMap<>(); - private final ExpiringMap bearingCache = new ExpiringMap<>(); + private final ExpiringMap pressureCache = new ExpiringMap<>(ALGORITHM_WINDOW); + private final ExpiringMap temperatureCache = new ExpiringMap<>(ALGORITHM_WINDOW); + private final ExpiringMap bearingCache = new ExpiringMap<>(ALGORITHM_WINDOW); private double currentTemp = 0; private String currentSagerCode = SagerWeatherCaster.UNDEF; - public SagerCasterHandler(Thing thing, WindDirectionStateDescriptionProvider stateDescriptionProvider, - SagerWeatherCaster sagerWeatherCaster) { + public SagerCasterHandler(final Thing thing, final WindDirectionStateDescriptionProvider stateDescriptionProvider, + final SagerWeatherCaster sagerWeatherCaster) { super(thing); this.stateDescriptionProvider = stateDescriptionProvider; this.sagerWeatherCaster = sagerWeatherCaster; @@ -76,18 +78,14 @@ public SagerCasterHandler(Thing thing, WindDirectionStateDescriptionProvider sta @Override public void initialize() { - int observationPeriod = ((BigDecimal) getConfig().get(CONFIG_PERIOD)).intValue(); - long period = TimeUnit.HOURS.toMillis(observationPeriod); - pressureCache.setObservationPeriod(period); - bearingCache.setObservationPeriod(period); - temperatureCache.setObservationPeriod(period); - - String location = (String) getConfig().get(CONFIG_LOCATION); - String latitude = location.split(",")[0]; - sagerWeatherCaster.setLatitude(Double.parseDouble(latitude)); - defineWindDirectionStateDescriptions(); - - updateStatus(ThingStatus.ONLINE); + if (getConfig().get(CONFIG_LOCATION) instanceof String locationString) { + PointType location = new PointType(locationString); + sagerWeatherCaster.setLatitude(location.getLatitude().doubleValue()); + defineWindDirectionStateDescriptions(); + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Location incorrectly configured"); + } } private void defineWindDirectionStateDescriptions() { @@ -95,7 +93,7 @@ private void defineWindDirectionStateDescriptions() { String[] directions = sagerWeatherCaster.getUsedDirections(); for (int i = 0; i < directions.length; i++) { int secondDirection = i < directions.length - 1 ? i + 1 : 0; - String windDescription = directions[i] + " or " + directions[secondDirection] + " winds"; + String windDescription = "%s or %s winds".formatted(directions[i], directions[secondDirection]); options.add(new StateOption(String.valueOf(i + 1), windDescription)); } @@ -105,13 +103,13 @@ private void defineWindDirectionStateDescriptions() { stateDescriptionProvider.setStateOptions(new ChannelUID(thingUID, GROUP_OUTPUT, CHANNEL_WINDTO), options); } + @SuppressWarnings("unchecked") @Override public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof RefreshType) { postNewForecast(); } else { - String id = channelUID.getIdWithoutGroup(); - switch (id) { + switch (channelUID.getIdWithoutGroup()) { case CHANNEL_CLOUDINESS: logger.debug("Cloud level changed, updating forecast"); if (command instanceof QuantityType cloudiness) { @@ -119,13 +117,13 @@ public void handleCommand(ChannelUID channelUID, Command command) { sagerWeatherCaster.setCloudLevel(cloudiness.intValue()); postNewForecast(); }); - break; } + break; case CHANNEL_IS_RAINING: logger.debug("Rain status updated, updating forecast"); if (command instanceof OnOffType isRaining) { scheduler.submit(() -> { - sagerWeatherCaster.setRaining(isRaining == OnOffType.ON); + sagerWeatherCaster.setRaining(OnOffType.ON.equals(isRaining)); postNewForecast(); }); } else { @@ -134,12 +132,11 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_RAIN_QTTY: logger.debug("Rain status updated, updating forecast"); - if (command instanceof QuantityType quantityCommand) { - updateRain(quantityCommand); - } else if (command instanceof DecimalType decimalCommand) { - updateRain(decimalCommand); - } else { - logger.debug("Channel '{}' accepts Number, Number:(Speed|Length) commands.", channelUID); + switch (command) { + case QuantityType quantity -> updateRain(quantity); + case DecimalType decimal -> updateRain(decimal); + default -> + logger.debug("Channel '{}' accepts Number, Number:(Speed|Length) commands.", channelUID); } break; case CHANNEL_WIND_SPEED: @@ -156,13 +153,10 @@ public void handleCommand(ChannelUID channelUID, Command command) { case CHANNEL_PRESSURE: logger.debug("Sea-level pressure updated, updating forecast"); if (command instanceof QuantityType) { - @SuppressWarnings("unchecked") - QuantityType pressQtty = ((QuantityType) command) - .toUnit(HECTO(SIUnits.PASCAL)); - if (pressQtty != null) { - double newPressureValue = pressQtty.doubleValue(); - pressureCache.put(newPressureValue); - pressureCache.getAgedValue().ifPresentOrElse(oldPressure -> scheduler.submit(() -> { + QuantityType pressure = ((QuantityType) command).toUnit(HECTO(SIUnits.PASCAL)); + if (pressure != null) { + double newPressureValue = pressure.doubleValue(); + pressureCache.put(newPressureValue).ifPresentOrElse(oldPressure -> scheduler.submit(() -> { sagerWeatherCaster.setPressure(newPressureValue, oldPressure); updateChannelString(GROUP_OUTPUT, CHANNEL_PRESSURETREND, String.valueOf(sagerWeatherCaster.getPressureEvolution())); @@ -174,13 +168,10 @@ public void handleCommand(ChannelUID channelUID, Command command) { case CHANNEL_TEMPERATURE: logger.debug("Temperature updated"); if (command instanceof QuantityType) { - @SuppressWarnings("unchecked") - QuantityType tempQtty = ((QuantityType) command) - .toUnit(SIUnits.CELSIUS); - if (tempQtty != null) { - currentTemp = tempQtty.doubleValue(); - temperatureCache.put(currentTemp); - temperatureCache.getAgedValue().ifPresent(oldTemperature -> { + QuantityType temperature = ((QuantityType) command).toUnit(SIUnits.CELSIUS); + if (temperature != null) { + currentTemp = temperature.doubleValue(); + temperatureCache.put(currentTemp).ifPresent(oldTemperature -> { double delta = currentTemp - oldTemperature; String trend = (delta > 3) ? "1" : (delta > 0.3) ? "2" : (delta > -0.3) ? "3" : (delta > -3) ? "4" : "5"; @@ -191,19 +182,14 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_WIND_ANGLE: logger.debug("Updated wind direction, updating forecast"); - if (command instanceof QuantityType) { - @SuppressWarnings("unchecked") - QuantityType angleQtty = (QuantityType) command; - int newAngleValue = angleQtty.intValue(); - bearingCache.put(newAngleValue); - bearingCache.getAgedValue().ifPresent(oldAngle -> { - scheduler.submit(() -> { - sagerWeatherCaster.setBearing(newAngleValue, oldAngle); - updateChannelString(GROUP_OUTPUT, CHANNEL_WINDEVOLUTION, - String.valueOf(sagerWeatherCaster.getWindEvolution())); - postNewForecast(); - }); - }); + if (command instanceof QuantityType angle) { + int newAngleValue = angle.intValue(); + bearingCache.put(newAngleValue).ifPresent(oldAngle -> scheduler.submit(() -> { + sagerWeatherCaster.setBearing(newAngleValue, oldAngle); + updateChannelString(GROUP_OUTPUT, CHANNEL_WINDEVOLUTION, + String.valueOf(sagerWeatherCaster.getWindEvolution())); + postNewForecast(); + })); } break; default: @@ -224,7 +210,7 @@ private void postNewForecast() { if (!newSagerCode.equals(currentSagerCode)) { logger.debug("Sager prediction changed to {}", newSagerCode); currentSagerCode = newSagerCode; - updateChannelTimeStamp(GROUP_OUTPUT, CHANNEL_TIMESTAMP, ZonedDateTime.now()); + updateChannelTimeStamp(GROUP_OUTPUT, CHANNEL_TIMESTAMP, Instant.now()); String forecast = sagerWeatherCaster.getForecast(); // Sharpens forecast if current temp is below 2 degrees, likely to be flurries rather than shower forecast += SHOWERS.contains(forecast) ? (currentTemp > 2) ? "1" : "2" : ""; @@ -234,27 +220,39 @@ private void postNewForecast() { updateChannelString(GROUP_OUTPUT, CHANNEL_WINDTO, sagerWeatherCaster.getWindDirection2()); updateChannelString(GROUP_OUTPUT, CHANNEL_VELOCITY, sagerWeatherCaster.getWindVelocity()); updateChannelDecimal(GROUP_OUTPUT, CHANNEL_VELOCITY_BEAUFORT, sagerWeatherCaster.getPredictedBeaufort()); + updateReliability(GROUP_OUTPUT, CHANNEL_RELIABILITY); } } - private void updateChannelTimeStamp(String group, String channelId, ZonedDateTime zonedDateTime) { + private @Nullable ChannelUID getChannelId(String group, String channelId) { ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId); - if (isLinked(id)) { - updateState(id, new DateTimeType(zonedDateTime)); + return isLinked(id) ? id : null; + } + + private void updateChannelTimeStamp(String group, String channelId, Instant instant) { + if (getChannelId(group, channelId) instanceof ChannelUID id) { + updateState(id, new DateTimeType(instant)); } } private void updateChannelString(String group, String channelId, String value) { - ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId); - if (isLinked(id)) { + if (getChannelId(group, channelId) instanceof ChannelUID id) { updateState(id, new StringType(value)); } } private void updateChannelDecimal(String group, String channelId, int value) { - ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId); - if (isLinked(id)) { + if (getChannelId(group, channelId) instanceof ChannelUID id) { updateState(id, new DecimalType(value)); } } + + public void updateReliability(String group, String channelId) { + if (getChannelId(group, channelId) instanceof ChannelUID id) { + long minDuration = Math.min(bearingCache.getDataAgeInMin(), + Math.min(pressureCache.getDataAgeInMin(), temperatureCache.getDataAgeInMin())); + double ratio = minDuration > 0 ? Math.min(1.0, minDuration / (ALGORITHM_WINDOW.toMinutes() * 1.0)) : 0.0; + updateState(id, new PercentType((int) Math.round(ratio * 100.0))); + } + } } diff --git a/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster.properties b/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster.properties index 4087529d939ec..ad2b76df67a5f 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster.properties +++ b/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster.properties @@ -50,6 +50,8 @@ beaufortDescription = Wind speed using Beaufort Scale pressureDescription = Barometric pressure at sea level. timestampChannelLabel = Timestamp timestampChannelDescription = Timestamp of the last weather forecast update. +reliabilityLabel = Algorithm reliability +reliabilityDescription = Grows along with the algorithm historical data storage (up to 6 hours) # channel options diff --git a/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/thing/thing-types.xml index 0bcc4fa8cbec1..7a82a4b652518 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/thing/thing-types.xml @@ -13,6 +13,10 @@ + + 1 + + location @@ -23,12 +27,6 @@ @text/locationDescription - - - @text/observationDescription - 6 - - @@ -73,6 +71,7 @@ @text/tempTrendDescription + @@ -239,4 +238,15 @@ + + Number:Dimensionless + + @text/reliabilityDescription + + Calculation + + + veto + + diff --git a/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/update/instructions.xml new file mode 100644 index 0000000000000..3951be2d89d7d --- /dev/null +++ b/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/update/instructions.xml @@ -0,0 +1,16 @@ + + + + + + + + sagercaster:reliability + + + + + + From 3d1214297696c12f1cd42340df27547a313cfe9c Mon Sep 17 00:00:00 2001 From: "gael@lhopital.org" Date: Mon, 27 Oct 2025 16:44:10 +0100 Subject: [PATCH 2/4] Correcting a bug and code enhancement Signed-off-by: gael@lhopital.org --- .../internal/caster/SagerPrediction.java | 5 ++-- .../internal/caster/SagerWeatherCaster.java | 30 +++++++++---------- .../internal/handler/SagerCasterHandler.java | 29 +++++++++++------- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerPrediction.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerPrediction.java index 31987ef68cbc9..347eb32c21b54 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerPrediction.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerPrediction.java @@ -13,6 +13,7 @@ package org.openhab.binding.sagercaster.internal.caster; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; /** * This record holds the result of the SagerCaster algorithm @@ -34,7 +35,7 @@ public String getWindDirection() { return Character.toString(sagerCode.charAt(2)); } - public String getWindDirection2() { - return sagerCode.length() > 3 ? Character.toString(sagerCode.charAt(3)) : SagerWeatherCaster.UNDEF; + public @Nullable String getWindDirection2() { + return sagerCode.length() > 3 ? Character.toString(sagerCode.charAt(3)) : null; } } diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerWeatherCaster.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerWeatherCaster.java index bf00fc456d09f..cbc57190e0825 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerWeatherCaster.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerWeatherCaster.java @@ -77,7 +77,6 @@ @Component(service = SagerWeatherCaster.class, scope = ServiceScope.SINGLETON) @NonNullByDefault public class SagerWeatherCaster { - public static final String UNDEF = "-"; // Northern Polar Zone & Northern Tropical Zone private static final String[] NPZDIRECTIONS = { "S", "SW", "W", "NW", "N", "NE", "E", "SE" }; // Northern Temperate Zone @@ -221,14 +220,14 @@ private static int sagerWindTrend(double historic, double position) { } private String getCompass() { - double step = 360.0 / NTZDIRECTIONS.length; + double step = 360.0 / usedDirections.length; double b = Math.floor((currentBearing + (step / 2.0)) / step); - return NTZDIRECTIONS[(int) (b % NTZDIRECTIONS.length)]; + return usedDirections[(int) (b % usedDirections.length)]; } private void updatePrediction() { int zWind = Arrays.asList(usedDirections).indexOf(getCompass()); - String d1 = UNDEF; + String d1 = null; switch (zWind) { case 0: if (windEvolution == 3) { @@ -307,28 +306,28 @@ private void updatePrediction() { d1 = "Z"; } } - String forecast = forecaster.getProperty(d1 + sagerPressure + pressureEvolution + nubes); + String forecast = d1 == null ? null : forecaster.getProperty(d1 + sagerPressure + pressureEvolution + nubes); prevision = forecast != null ? new SagerPrediction(forecast) : null; } - public String getForecast() { - return prevision instanceof SagerPrediction p ? p.getForecast() : UNDEF; + public @Nullable String getForecast() { + return prevision instanceof SagerPrediction p ? p.getForecast() : null; } - public String getWindVelocity() { - return prevision instanceof SagerPrediction p ? p.getWindVelocity() : UNDEF; + public @Nullable String getWindVelocity() { + return prevision instanceof SagerPrediction p ? p.getWindVelocity() : null; } - public String getWindDirection() { - return prevision instanceof SagerPrediction p ? p.getWindDirection() : UNDEF; + public @Nullable String getWindDirection() { + return prevision instanceof SagerPrediction p ? p.getWindDirection() : null; } - public String getWindDirection2() { - return prevision instanceof SagerPrediction p ? p.getWindDirection2() : UNDEF; + public @Nullable String getWindDirection2() { + return prevision instanceof SagerPrediction p ? p.getWindDirection2() : null; } - public String getSagerCode() { - return prevision instanceof SagerPrediction p ? p.sagerCode() : UNDEF; + public @Nullable String getSagerCode() { + return prevision instanceof SagerPrediction p ? p.sagerCode() : null; } public void setLatitude(Double latitude) { @@ -351,6 +350,7 @@ public int getPredictedBeaufort() { case "W" -> 10; case "H" -> 12; case "D" -> currentBeaufort - 1; + case null -> currentBeaufort; default -> currentBeaufort; }; } diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java index eddc3f2c50455..5a195c4a5555b 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java @@ -19,6 +19,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import javax.measure.quantity.Pressure; import javax.measure.quantity.Temperature; @@ -44,6 +45,7 @@ import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.openhab.core.types.StateOption; +import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,7 +69,7 @@ public class SagerCasterHandler extends BaseThingHandler { private final ExpiringMap bearingCache = new ExpiringMap<>(ALGORITHM_WINDOW); private double currentTemp = 0; - private String currentSagerCode = SagerWeatherCaster.UNDEF; + private @Nullable String currentSagerCode; public SagerCasterHandler(final Thing thing, final WindDirectionStateDescriptionProvider stateDescriptionProvider, final SagerWeatherCaster sagerWeatherCaster) { @@ -79,10 +81,15 @@ public SagerCasterHandler(final Thing thing, final WindDirectionStateDescription @Override public void initialize() { if (getConfig().get(CONFIG_LOCATION) instanceof String locationString) { - PointType location = new PointType(locationString); - sagerWeatherCaster.setLatitude(location.getLatitude().doubleValue()); - defineWindDirectionStateDescriptions(); - updateStatus(ThingStatus.ONLINE); + try { + PointType location = new PointType(locationString); + sagerWeatherCaster.setLatitude(location.getLatitude().doubleValue()); + defineWindDirectionStateDescriptions(); + updateStatus(ThingStatus.ONLINE); + } catch (IllegalArgumentException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Location incorrectly configured"); + } } else { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Location incorrectly configured"); } @@ -206,10 +213,10 @@ private void updateRain(Number newQtty) { } private void postNewForecast() { - String newSagerCode = sagerWeatherCaster.getSagerCode(); - if (!newSagerCode.equals(currentSagerCode)) { - logger.debug("Sager prediction changed to {}", newSagerCode); - currentSagerCode = newSagerCode; + String newCode = sagerWeatherCaster.getSagerCode(); + if (!Objects.equals(newCode, currentSagerCode)) { + logger.debug("Sager prediction changed to {}", newCode); + currentSagerCode = newCode; updateChannelTimeStamp(GROUP_OUTPUT, CHANNEL_TIMESTAMP, Instant.now()); String forecast = sagerWeatherCaster.getForecast(); // Sharpens forecast if current temp is below 2 degrees, likely to be flurries rather than shower @@ -235,9 +242,9 @@ private void updateChannelTimeStamp(String group, String channelId, Instant inst } } - private void updateChannelString(String group, String channelId, String value) { + private void updateChannelString(String group, String channelId, @Nullable String value) { if (getChannelId(group, channelId) instanceof ChannelUID id) { - updateState(id, new StringType(value)); + updateState(id, value != null ? new StringType(value) : UnDefType.NULL); } } From 6a9ccb99d97d96ad3a778a996f88a2f8145ab3fc Mon Sep 17 00:00:00 2001 From: "gael@lhopital.org" Date: Thu, 30 Oct 2025 10:54:22 +0100 Subject: [PATCH 3/4] Address code review observations Signed-off-by: gael@lhopital.org --- .../sagercaster/internal/SagerCasterBindingConstants.java | 2 +- .../binding/sagercaster/internal/handler/ExpiringMap.java | 2 +- .../sagercaster/internal/handler/SagerCasterHandler.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterBindingConstants.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterBindingConstants.java index a1a388ae27ffe..691601e6ba7c5 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterBindingConstants.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterBindingConstants.java @@ -30,7 +30,7 @@ public class SagerCasterBindingConstants { public static final String LOCAL = "local"; // List of all Thing Type UIDs - public static final ThingTypeUID THING_TYPE_SAGERCASTER = new ThingTypeUID(BINDING_ID, BINDING_ID); + public static final ThingTypeUID THING_TYPE_SAGERCASTER = new ThingTypeUID(BINDING_ID, "sagercaster"); public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SAGERCASTER); // Configuration elements public static final String CONFIG_LOCATION = "location"; diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/ExpiringMap.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/ExpiringMap.java index 0674d4fd99e73..9688e050d5090 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/ExpiringMap.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/ExpiringMap.java @@ -41,7 +41,7 @@ public ExpiringMap(Duration duration) { public Optional put(T newValue) { long now = System.currentTimeMillis(); - // removes all entries > 6h + // removes all entries older than the configured window duration long cutoff = now - windowMillis; while (!values.isEmpty() && values.firstKey() < cutoff) { values.pollFirstEntry(); diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java index 5a195c4a5555b..e401bdfe24c9a 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java @@ -254,7 +254,7 @@ private void updateChannelDecimal(String group, String channelId, int value) { } } - public void updateReliability(String group, String channelId) { + private void updateReliability(String group, String channelId) { if (getChannelId(group, channelId) instanceof ChannelUID id) { long minDuration = Math.min(bearingCache.getDataAgeInMin(), Math.min(pressureCache.getDataAgeInMin(), temperatureCache.getDataAgeInMin())); From e454146dbc5dfd9496264bbac81511b6e5224bb8 Mon Sep 17 00:00:00 2001 From: "gael@lhopital.org" Date: Fri, 31 Oct 2025 08:58:18 +0100 Subject: [PATCH 4/4] Review wording Signed-off-by: gael@lhopital.org --- .../org.openhab.binding.sagercaster/README.md | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/bundles/org.openhab.binding.sagercaster/README.md b/bundles/org.openhab.binding.sagercaster/README.md index 436bd07030051..131e574855a3e 100644 --- a/bundles/org.openhab.binding.sagercaster/README.md +++ b/bundles/org.openhab.binding.sagercaster/README.md @@ -22,9 +22,9 @@ The binding itself does not require any configuration. ### SagerCaster -| Name | Type | Description | -| ------------------ | -------- | -------------------------------------------------------------------- | -| location (*) | Location | Latitude and longitude of the desired weather forecast. | +| Name | Type | Description | +| ------------ | -------- | ------------------------------------------------------ | +| location (*) | Location | Latitude and longitude of the desired weather forecast | (*) Only latitude is used by the algorithm. @@ -32,27 +32,27 @@ The binding itself does not require any configuration. The binding will use some input channels, that can be configured directly with profiles (sample below). -| Name | Group | Type | Description | -| ------------------- | ------ | -------------------- | ---------------------------------------------------------------------- | -| is-raining (*) | input | Switch | On if it is raining, else Off. | -| rain-qtty (*) | input | Number | Any value that give indication of a current rain volume | -| or | input | Number:Speed | Any value that give indication of a current rain volume eg mm/h | -| or | input | Number:Length | Any value that give indication of a current rain volume eg mm | -| cloudiness | input | Number:Dimensionless | Cloud cover percentage | -| wind-speed-beaufort | input | Number | Wind speed expressed using the Beaufort scale | -| pressure | input | Number:Pressure | Sea level pressure | -| wind-angle | input | Number:Angle | Wind direction | -| temperature | input | Number:Temperature | Outside temperature | -| timestamp | output | DateTime | Timestamp of the last forecast update | -| forecast | output | String | Description of the weather forecast | -| velocity | output | String | Description of the expected wind evolution | -| velocity-beaufort | output | Number | Expected wind evolution using the Beaufort scale | -| wind-from | output | String | Expected wind orientation | -| wind-to | output | String | Evolution of the expected wind orientation | -| wind-evolution | output | String | Wind orientation evolution over observation period | -| pressure-trend | output | String | Pressure evolution over observation period | -| temperature-trend | output | String | Temperature evolution over observation period | -| reliability | output | Number:Dimensionless | Grows along with the algorithm historical data storage (up to 6 hours) | +| Name | Group | Type | Description | +| ------------------- | ------ | -------------------- | --------------------------------------------------------------------- | +| is-raining (*) | input | Switch | On if it is raining, else Off. | +| rain-qtty (*) | input | Number | Any value that give indication of a current rain volume | +| or | input | Number:Speed | Any value that give indication of a current rain volume eg mm/h | +| or | input | Number:Length | Any value that give indication of a current rain volume eg mm | +| cloudiness | input | Number:Dimensionless | Cloud cover percentage | +| wind-speed-beaufort | input | Number | Wind speed expressed using the Beaufort scale | +| pressure | input | Number:Pressure | Sea level pressure | +| wind-angle | input | Number:Angle | Wind direction | +| temperature | input | Number:Temperature | Outside temperature | +| timestamp | output | DateTime | Timestamp of the last forecast update | +| forecast | output | String | Description of the weather forecast | +| velocity | output | String | Description of the expected wind evolution | +| velocity-beaufort | output | Number | Expected wind evolution using the Beaufort scale | +| wind-from | output | String | Expected wind orientation | +| wind-to | output | String | Evolution of the expected wind orientation | +| wind-evolution | output | String | Wind orientation evolution over observation period | +| pressure-trend | output | String | Pressure evolution over observation period | +| temperature-trend | output | String | Temperature evolution over observation period | +| reliability | output | Number:Dimensionless | Algorithm efficiency toward buffered data, grows during first 6 hours | (*) You may use either is-raining, either rain-qtty depending upon the data available in your system.