From 30dbdea1e94b4db63c1f61727de59811953a870a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Fri, 6 Mar 2026 11:16:45 +0100 Subject: [PATCH] [synopanalyzer] Add IconProvider (#20193) * Add IconProvider Signed-off-by: gael@lhopital.org --- .../org.openhab.binding.synopanalyzer/NOTICE | 8 ++ .../README.md | 54 ++++++++- .../doc/images/beaufort.svg | 6 + .../doc/images/octa.svg | 6 + .../SynopAnalyzerBindingConstants.java | 3 +- .../internal/SynopAnalyzerIconProvider.java | 112 ++++++++++++++++++ .../internal/WindBarbGenerator.java | 104 ++++++++++++++++ .../SynopAnalyzerDiscoveryService.java | 2 +- .../handler/SynopAnalyzerHandler.java | 37 ++++-- .../OH-INF/i18n/synopanalyzer.properties | 7 ++ .../resources/OH-INF/thing/thing-types.xml | 19 ++- .../src/main/resources/icon/beaufort-0.svg | 3 + .../src/main/resources/icon/beaufort-1.svg | 4 + .../src/main/resources/icon/beaufort-10.svg | 4 + .../src/main/resources/icon/beaufort-11.svg | 6 + .../src/main/resources/icon/beaufort-12.svg | 6 + .../src/main/resources/icon/beaufort-2.svg | 5 + .../src/main/resources/icon/beaufort-3.svg | 3 + .../src/main/resources/icon/beaufort-4.svg | 5 + .../src/main/resources/icon/beaufort-5.svg | 5 + .../src/main/resources/icon/beaufort-6.svg | 5 + .../src/main/resources/icon/beaufort-7.svg | 5 + .../src/main/resources/icon/beaufort-8.svg | 5 + .../src/main/resources/icon/beaufort-9.svg | 5 + .../src/main/resources/icon/beaufort.svg | 6 + .../src/main/resources/icon/octa-0.svg | 4 + .../src/main/resources/icon/octa-1.svg | 5 + .../src/main/resources/icon/octa-2.svg | 5 + .../src/main/resources/icon/octa-3.svg | 6 + .../src/main/resources/icon/octa-4.svg | 7 ++ .../src/main/resources/icon/octa-5.svg | 8 ++ .../src/main/resources/icon/octa-6.svg | 10 ++ .../src/main/resources/icon/octa-7.svg | 5 + .../src/main/resources/icon/octa-8.svg | 4 + .../src/main/resources/icon/octa-9.svg | 6 + .../src/main/resources/icon/octa.svg | 6 + 36 files changed, 475 insertions(+), 16 deletions(-) create mode 100644 bundles/org.openhab.binding.synopanalyzer/doc/images/beaufort.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/doc/images/octa.svg create mode 100755 bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/SynopAnalyzerIconProvider.java create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/WindBarbGenerator.java create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-0.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-1.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-10.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-11.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-12.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-2.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-3.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-4.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-5.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-6.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-7.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-8.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-9.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-0.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-1.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-2.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-3.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-4.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-5.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-6.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-7.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-8.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-9.svg create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa.svg diff --git a/bundles/org.openhab.binding.synopanalyzer/NOTICE b/bundles/org.openhab.binding.synopanalyzer/NOTICE index 7392bb9dbf..9bc1f06d3b 100644 --- a/bundles/org.openhab.binding.synopanalyzer/NOTICE +++ b/bundles/org.openhab.binding.synopanalyzer/NOTICE @@ -19,3 +19,11 @@ Based on the commit bed5842 / 3 May 2017 * License: EPL + MIT License * Project: https://github.com/Jonarzz/SynopAnalizer * Source: https://github.com/Jonarzz/SynopAnalizer + +Wind barbs symbols +* License: Creative Commons +* Source: https://commons.wikimedia.org/wiki/Wind_speed + +Cloud Cover in Oktas +* License: Creative Commons +* Source: https://commons.wikimedia.org/wiki/File:Cloud_Cover_in_Oktas.svg diff --git a/bundles/org.openhab.binding.synopanalyzer/README.md b/bundles/org.openhab.binding.synopanalyzer/README.md index 1f41dffa9e..488402d17d 100644 --- a/bundles/org.openhab.binding.synopanalyzer/README.md +++ b/bundles/org.openhab.binding.synopanalyzer/README.md @@ -15,6 +15,15 @@ There is exactly one supported Thing type, which represents a SYNOP message: `sy If a system location is set, the nearest available SYNOP station will be automatically discovered for this location. The search radius expands with each successive scan. +## Provided icon set + +This binding has its own IconProvider and makes available the following list of icons + +| Icon Name | Dynamic | Illustration | +| --------------------------- | ------- | ------------------------------------------------- | +| oh:synopanalyzer:beaufort | Yes | ![Beaufort](doc/images/beaufort.svg) | +| oh:synopanalyzer:octa | Yes | ![Octa](doc/images/octa.svg) | + ## Thing Configuration - `stationId` — The WMO station number (see the [station list](https://www.ogimet.com/gsynop_nav.phtml.en)). @@ -39,6 +48,7 @@ The weather information that is retrieved is available as these channels: | attenuation-factor* | Number | Cloud layer attenuation factor | | time-utc | DateTime | Observation time of the Synop message | | horizontal-visibility | String | Horizontal visibility range | +| wind-barb | Image | Graph of the wind speed and direction | - "Cloud attenuation factor" (Kc) as defined by Kasten and Czeplak (1980). @@ -60,6 +70,48 @@ Number:Pressure Synop_Pressure "Pressure [%.1f %unit%]" { Number:Angle Synop_Wind_Angle "Wind Angle [%d °]" { channel = "synopanalyzer:synopanalyzer:trappes:wind-angle" } String Synop_Wind_Direction "Direction [%s]" { channel = "synopanalyzer:synopanalyzer:trappes:wind-direction" } Number:Speed Synop_Wind_Speed "Wind Speed [%.2f %unit%]" { channel = "synopanalyzer:synopanalyzer:trappes:wind-speed" } -Number Synop_Octa "Octa [%d]/8" { channel = "synopanalyzer:synopanalyzer:trappes:octa" } +Number Synop_Octa "Octa [%d]/8" { channel = "synopanalyzer:synopanalyzer:trappes:octa" } DateTime Synop_time "Observation Time [%1$ta %1$tR]" { channel = "synopanalyzer:synopanalyzer:trappes:time-utc" } ``` + +### Transformations + +octa.map + +```text +0=○ No clouds +1=⌽ A few clouds +2=◔ A few clouds +3=◑ Scattered clouds +4=◑ Scattered clouds +5=◕ Broken sky +6=◕ Broken sky +7=◕ Broken sky +8=● Overcast sky +9=⊗ Obscured sky + +UNDEF=Unknown ⁉ +NULL=Unknown ⁉ +-=Unknown ⁉ +=Unknown ⁉ +``` + +beaufort.scale + +```text +[0..1[=Calm +[1..2[=Very light breeze +[2..3[=Light breeze +[3..4[=Gentle breeze +[4..5[=Moderate breeze +[5..6[=Fresh breeze +[6..7[=Strong breeze +[7..8[=Near gale +[8..9[=Gale +[9..10[=Strong gale +[10..11[=Storm +[11..12[=Violent storm +[12..15[=Hurricane +[..]=Unknown ⁉ +NaN=Not initialized (NaN) +``` diff --git a/bundles/org.openhab.binding.synopanalyzer/doc/images/beaufort.svg b/bundles/org.openhab.binding.synopanalyzer/doc/images/beaufort.svg new file mode 100644 index 0000000000..b52dbab12c --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/doc/images/beaufort.svg @@ -0,0 +1,6 @@ + + + ? + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/doc/images/octa.svg b/bundles/org.openhab.binding.synopanalyzer/doc/images/octa.svg new file mode 100644 index 0000000000..7fc7bc41d2 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/doc/images/octa.svg @@ -0,0 +1,6 @@ + + ? + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/SynopAnalyzerBindingConstants.java b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/SynopAnalyzerBindingConstants.java index d8ef2b26f3..603138aa06 100644 --- a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/SynopAnalyzerBindingConstants.java +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/SynopAnalyzerBindingConstants.java @@ -31,7 +31,7 @@ import org.openhab.core.thing.ThingTypeUID; */ @NonNullByDefault public class SynopAnalyzerBindingConstants { - private static final String BINDING_ID = "synopanalyzer"; + public static final String BINDING_ID = "synopanalyzer"; // List of all Thing Type UIDs public static final ThingTypeUID THING_SYNOP = new ThingTypeUID(BINDING_ID, "synopanalyzer"); @@ -48,6 +48,7 @@ public class SynopAnalyzerBindingConstants { public static final String WIND_STRENGTH = "wind-speed"; public static final String WIND_SPEED_BEAUFORT = "wind-speed-beaufort"; public static final String TIME_UTC = "time-utc"; + public static final String WIND_BARB = "wind-barb"; // Default units public static final Unit TEMPERATURE_UNIT = SIUnits.CELSIUS; diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/SynopAnalyzerIconProvider.java b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/SynopAnalyzerIconProvider.java new file mode 100755 index 0000000000..4ab2f41bd9 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/SynopAnalyzerIconProvider.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2010-2026 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.synopanalyzer.internal; + +import static org.openhab.binding.synopanalyzer.internal.SynopAnalyzerBindingConstants.BINDING_ID; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Locale; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.i18n.TranslationProvider; +import org.openhab.core.ui.icon.IconProvider; +import org.openhab.core.ui.icon.IconSet; +import org.openhab.core.ui.icon.IconSet.Format; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link SynopAnalyzerIconProvider} is the class providing binding related icons. + * + * @author Gaël L'hopital - Initial contribution + */ +@Component(service = { IconProvider.class, SynopAnalyzerIconProvider.class }) +@NonNullByDefault +public class SynopAnalyzerIconProvider implements IconProvider { + private static final String DEFAULT_LABEL = "Synop Analyzer Icons"; + private static final String DEFAULT_DESCRIPTION = "Icons provided for the Synop Analyzer Binding"; + private static final String BEAUFORT_SET = "beaufort"; + private static final String OCTA_SET = "octa"; + private static final Set ICON_SETS = Set.of(BEAUFORT_SET, OCTA_SET); + + private final Logger logger = LoggerFactory.getLogger(SynopAnalyzerIconProvider.class); + private final TranslationProvider i18nProvider; + private final Bundle bundle; + + @Activate + public SynopAnalyzerIconProvider(final BundleContext context, final @Reference TranslationProvider i18nProvider) { + this.i18nProvider = i18nProvider; + this.bundle = context.getBundle(); + } + + @Override + public Set getIconSets() { + return getIconSets(null); + } + + @Override + public Set getIconSets(@Nullable Locale locale) { + String label = getText("label", DEFAULT_LABEL, locale); + String description = getText("description", DEFAULT_DESCRIPTION, locale); + + return Set.of(new IconSet(BINDING_ID, label, description, Set.of(Format.SVG))); + } + + private String getText(String entry, String defaultValue, @Nullable Locale locale) { + String text = locale == null ? null : i18nProvider.getText(bundle, "iconset." + entry, defaultValue, locale); + return text == null ? defaultValue : text; + } + + @Override + public @Nullable Integer hasIcon(String category, String iconSetId, Format format) { + return Format.SVG.equals(format) && iconSetId.equals(BINDING_ID) && ICON_SETS.contains(category) ? 0 : null; + } + + @Override + public @Nullable InputStream getIcon(String category, String iconSetId, @Nullable String state, Format format) { + String resourceWithoutState = "icon/" + category + "." + format.toString(); + if (state == null) { + return getResource(resourceWithoutState); + } + + try { + String resourceWithState = "icon/" + category + "-" + state + "." + format.toString(); + return getResource(resourceWithState); + } catch (IllegalArgumentException e) { + logger.debug("Use icon {} as state {} is not found", resourceWithoutState, state); + return getResource(resourceWithoutState); + } + } + + private @Nullable InputStream getResource(String iconName) { + if (bundle.getEntry(iconName.toLowerCase(Locale.ROOT)) instanceof URL iconResource) { + try (InputStream stream = iconResource.openStream()) { + byte[] icon = stream.readAllBytes(); + return new ByteArrayInputStream(icon); + } catch (IOException e) { + logger.warn("Unable to load resource '{}': {}", iconResource.getPath(), e.getMessage()); + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/WindBarbGenerator.java b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/WindBarbGenerator.java new file mode 100644 index 0000000000..8d669255ea --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/WindBarbGenerator.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2010-2026 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.synopanalyzer.internal; + +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link WindBarbGenerator} generates a SVG representation of wind speed + * and direction + * Inspired by https://github.com/spatialsparks/Leaflet.windbarb + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class WindBarbGenerator { + private static final String FILL_COLOR = "#2B85C7"; + private static final int POINT_RADIUS = 4; // station circle radius + private static final int STROKE_WIDTH = 1; + private static final int STROKE_LENGTH = 45; // hamp length + private static final int BARB_SPACING = 5; + private static final int BARB_HEIGHT = 15; + private static final int VIEW_SIZE = 120; + + public String generateSVG(double speedKnots, double directionDeg) { + int s = (int) (5 * Math.round(speedKnots / 5.0)); + int f50 = s / 50; + int f10 = (s % 50) / 10; + int f5 = (s % 10) / 5; + + double mid = VIEW_SIZE / 2.0; + + StringBuilder sb = new StringBuilder(); + sb.append(""); + + // Rotation depending on wind direction + sb.append(""); + + // Station circle + sb.append(""); + + // Hamp + double lineTopY = mid - POINT_RADIUS - STROKE_LENGTH; + sb.append(""); + + // Start at the end of the hamp + double currentY = lineTopY; + + for (int i = 0; i < f50; i++) { // 50 kt pennants + currentY += BARB_SPACING; + + String points = String.format(Locale.US, "%.1f,%.1f %.1f,%.1f %.1f,%.1f", mid, currentY, mid + BARB_HEIGHT, + currentY - BARB_SPACING, mid, currentY - BARB_SPACING); + + sb.append(""); + + currentY += 2; + } + + if (f50 > 0) { + currentY += (BARB_SPACING / 2.0); + } + + for (int i = 0; i < f10; i++) { // 10 kt barbs + appendBarb(sb, mid, currentY, BARB_HEIGHT, BARB_SPACING); + currentY += BARB_SPACING; + } + + if (f5 == 1) { // 5 kt barb + if (s == 5) { + currentY += BARB_SPACING; + } + appendBarb(sb, mid, currentY, BARB_HEIGHT * 0.5, BARB_SPACING * 0.5); + } + + sb.append(""); + return sb.toString(); + } + + private void appendBarb(StringBuilder sb, double mid, double currentY, double barbHeight, double barbSpacing) { + sb.append(""); + } +} diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/discovery/SynopAnalyzerDiscoveryService.java b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/discovery/SynopAnalyzerDiscoveryService.java index 20df656b96..55d4349a92 100644 --- a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/discovery/SynopAnalyzerDiscoveryService.java +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/discovery/SynopAnalyzerDiscoveryService.java @@ -89,7 +89,7 @@ public class SynopAnalyzerDiscoveryService extends AbstractDiscoveryService { radius = nearest.getKey(); thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_SYNOP, Integer.toString(station.idOmm))) - .withLabel(String.format("Synop : %s", station.usualName)) + .withLabel(String.format("Synop: %s", station.usualName)) .withProperty(SynopAnalyzerConfiguration.STATION_ID, station.idOmm) .withRepresentationProperty(SynopAnalyzerConfiguration.STATION_ID).build()); } else { diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/handler/SynopAnalyzerHandler.java b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/handler/SynopAnalyzerHandler.java index ce5ebf85a1..888a4698e5 100644 --- a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/handler/SynopAnalyzerHandler.java +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/handler/SynopAnalyzerHandler.java @@ -15,6 +15,7 @@ package org.openhab.binding.synopanalyzer.internal.handler; import static org.openhab.binding.synopanalyzer.internal.SynopAnalyzerBindingConstants.*; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -23,6 +24,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -32,6 +34,7 @@ import javax.ws.rs.HttpMethod; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.synopanalyzer.internal.WindBarbGenerator; import org.openhab.binding.synopanalyzer.internal.config.SynopAnalyzerConfiguration; import org.openhab.binding.synopanalyzer.internal.stationdb.Station; import org.openhab.binding.synopanalyzer.internal.synop.Overcast; @@ -46,8 +49,10 @@ import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.PointType; import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.RawType; import org.openhab.core.library.types.StringType; import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -75,6 +80,7 @@ public class SynopAnalyzerHandler extends BaseThingHandler { private static final String OGIMET_SYNOP_PATH = "http://www.ogimet.com/cgi-bin/getsynop?block=%s&begin=%s"; private static final int REQUEST_TIMEOUT_MS = 5000; private static final DateTimeFormatter SYNOP_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHH00"); + private static final WindBarbGenerator WB_GENERATOR = new WindBarbGenerator(); private static final double KASTEN_POWER = 3.4; private static final double OCTA_MAX = 8.0; @@ -82,7 +88,7 @@ public class SynopAnalyzerHandler extends BaseThingHandler { private final LocationProvider locationProvider; private final List stations; - private Optional> refreshJob = Optional.empty(); + private @Nullable ScheduledFuture refreshJob; private @NonNullByDefault({}) String formattedStationId; public SynopAnalyzerHandler(Thing thing, LocationProvider locationProvider, List stations) { @@ -104,8 +110,8 @@ public class SynopAnalyzerHandler extends BaseThingHandler { updateStatus(ThingStatus.UNKNOWN); - refreshJob = Optional.of(scheduler.scheduleWithFixedDelay(this::updateChannels, 0, - configuration.refreshInterval, TimeUnit.MINUTES)); + refreshJob = scheduler.scheduleWithFixedDelay(this::updateChannels, 0, configuration.refreshInterval, + TimeUnit.MINUTES); } private void discoverAttributes(int stationId, @Nullable PointType serverLocation) { @@ -131,13 +137,13 @@ public class SynopAnalyzerHandler extends BaseThingHandler { String answer = HttpUtil.executeUrl(HttpMethod.GET, url, REQUEST_TIMEOUT_MS); List messages = Arrays.asList(answer.split("\n")); if (!messages.isEmpty()) { - String message = messages.get(messages.size() - 1); + String message = messages.getLast(); logger.debug(message); if (message.startsWith(formattedStationId)) { logger.debug("Valid Synop message received"); List messageParts = Arrays.asList(message.split(",")); - String synopMessage = messageParts.get(messageParts.size() - 1); + String synopMessage = messageParts.getLast(); return createSynopObject(synopMessage); } @@ -167,6 +173,7 @@ public class SynopAnalyzerHandler extends BaseThingHandler { private State getChannelState(String channelId, Synop synop) { int octa = synop.getOcta(); Integer direction = synop.getWindDirection(); + QuantityType windSpeed = getWindStrength(synop); switch (channelId) { case HORIZONTAL_VISIBILITY: return new StringType(synop.getHorizontalVisibility().name()); @@ -181,7 +188,7 @@ public class SynopAnalyzerHandler extends BaseThingHandler { return UnDefType.NULL; case OVERCAST: Overcast overcast = Overcast.fromOcta(octa); - return overcast == Overcast.UNDEFINED ? UnDefType.NULL : new StringType(overcast.name()); + return Overcast.UNDEFINED.equals(overcast) ? UnDefType.NULL : new StringType(overcast.name()); case PRESSURE: return new QuantityType<>(synop.getPressure(), PRESSURE_UNIT); case TEMPERATURE: @@ -193,10 +200,16 @@ public class SynopAnalyzerHandler extends BaseThingHandler { : UnDefType.NULL; case WIND_STRENGTH: return getWindStrength(synop); + case WIND_BARB: + if (direction == null) { + return UnDefType.NULL; + } + QuantityType wsKnot = Objects.requireNonNull(windSpeed.toUnit(Units.KNOT)); + String windBarbs = WB_GENERATOR.generateSVG(wsKnot.doubleValue(), direction); + return new RawType(windBarbs.getBytes(StandardCharsets.UTF_8), "image/svg+xml"); case WIND_SPEED_BEAUFORT: - QuantityType wsKpH = getWindStrength(synop).toUnit(SIUnits.KILOMETRE_PER_HOUR); - return wsKpH != null ? new DecimalType(Math.round(Math.pow(wsKpH.floatValue() / 3.01, 0.666666666))) - : UnDefType.NULL; + QuantityType wsKpH = Objects.requireNonNull(windSpeed.toUnit(SIUnits.KILOMETRE_PER_HOUR)); + return new DecimalType(Math.round(Math.pow(wsKpH.floatValue() / 3.01, 0.666666666))); case TIME_UTC: ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); int year = synop.getYear() == 0 ? now.getYear() : synop.getYear(); @@ -236,8 +249,10 @@ public class SynopAnalyzerHandler extends BaseThingHandler { @Override public void dispose() { - refreshJob.ifPresent(job -> job.cancel(true)); - refreshJob = Optional.empty(); + if (refreshJob instanceof ScheduledFuture job) { + job.cancel(true); + } + refreshJob = null; } @Override diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/i18n/synopanalyzer.properties b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/i18n/synopanalyzer.properties index 632b9e5636..ea65091a5c 100644 --- a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/i18n/synopanalyzer.properties +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/i18n/synopanalyzer.properties @@ -34,6 +34,8 @@ channel-type.synopanalyzer.overcast.state.option.CLOUDY = Cloudy channel-type.synopanalyzer.overcast.state.option.SKY_NOT_VISIBLE = Sky is not visible channel-type.synopanalyzer.time-utc.label = Observation Time channel-type.synopanalyzer.time-utc.description = Timestamp when data was observed +channel-type.synopanalyzer.wind-barb.label = Wind Barb +channel-type.synopanalyzer.wind-barb.description = Graph of the wind speed and direction channel-type.synopanalyzer.wind-direction.label = Wind Direction channel-type.synopanalyzer.wind-direction.description = Wind direction channel-type.synopanalyzer.wind-direction.state.option.N = N @@ -54,3 +56,8 @@ channel-type.synopanalyzer.wind-direction.state.option.NW = NW channel-type.synopanalyzer.wind-direction.state.option.NNW = NNW channel-type.synopanalyzer.wind-speed-beaufort.label = Beaufort channel-type.synopanalyzer.wind-speed-beaufort.description = Wind speed in Beaufort Scale + +# iconprovider + +iconset.label = Synop Analyzer Icons +iconset.description = Icons provided for the Synop Analyzer Binding diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/thing/thing-types.xml index a62a9ae795..829479d174 100644 --- a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/thing/thing-types.xml @@ -20,6 +20,7 @@ + @@ -41,7 +42,7 @@ Number Wind speed in Beaufort Scale - Wind + oh:synopanalyzer:beaufort Calculation Speed @@ -84,7 +85,7 @@ Number Cloud cover estimation. - sun_clouds + oh:synopanalyzer:octa @@ -93,6 +94,9 @@ Cloud layer attenuation factor sun_clouds + + Calculation + @@ -129,6 +133,17 @@ Timestamp when data was observed time + + Status + Timestamp + + + + + + Image + + Graph of the wind speed and direction diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-0.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-0.svg new file mode 100644 index 0000000000..1fc31d9118 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-0.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-1.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-1.svg new file mode 100644 index 0000000000..dd369a2108 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-1.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-10.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-10.svg new file mode 100644 index 0000000000..3b282d6e5c --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-10.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-11.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-11.svg new file mode 100644 index 0000000000..f99e460f69 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-11.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-12.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-12.svg new file mode 100644 index 0000000000..599eec64b2 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-12.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-2.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-2.svg new file mode 100644 index 0000000000..1a98edb7ac --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-2.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-3.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-3.svg new file mode 100644 index 0000000000..6d9a681ebc --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-3.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-4.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-4.svg new file mode 100644 index 0000000000..f66b7a0f6b --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-4.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-5.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-5.svg new file mode 100644 index 0000000000..79040f2f79 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-5.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-6.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-6.svg new file mode 100644 index 0000000000..c3e88374af --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-6.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-7.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-7.svg new file mode 100644 index 0000000000..f4618facd2 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-7.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-8.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-8.svg new file mode 100644 index 0000000000..386b8edba8 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-8.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-9.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-9.svg new file mode 100644 index 0000000000..55875820d7 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort-9.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort.svg new file mode 100644 index 0000000000..b52dbab12c --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/beaufort.svg @@ -0,0 +1,6 @@ + + + ? + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-0.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-0.svg new file mode 100644 index 0000000000..2adfac9fe9 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-0.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-1.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-1.svg new file mode 100644 index 0000000000..c03cf34e50 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-1.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-2.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-2.svg new file mode 100644 index 0000000000..ece7ca176c --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-2.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-3.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-3.svg new file mode 100644 index 0000000000..adaafc1a5e --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-3.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-4.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-4.svg new file mode 100644 index 0000000000..8ad87a0c12 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-4.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-5.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-5.svg new file mode 100644 index 0000000000..2a828d2c49 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-5.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-6.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-6.svg new file mode 100644 index 0000000000..40d91a7896 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-6.svg @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-7.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-7.svg new file mode 100644 index 0000000000..66e1a667bc --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-7.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-8.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-8.svg new file mode 100644 index 0000000000..15f5443f8b --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-8.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-9.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-9.svg new file mode 100644 index 0000000000..9a04c34096 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa-9.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa.svg b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa.svg new file mode 100644 index 0000000000..7fc7bc41d2 --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/icon/octa.svg @@ -0,0 +1,6 @@ + + ? + \ No newline at end of file