[synopanalyzer] Incorrect octa reported (#12541)

* Some stations does not report octa dimension, thus leading the binding to incorrect values.
Enhanced discovery process
Code enhancements
SAT corrections
Enhanced Exception catching.

Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2022-04-03 18:55:53 +02:00 committed by GitHub
parent 3520621c1b
commit 94301eee8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 232 additions and 228 deletions

View File

@ -12,7 +12,9 @@ There is exactly one supported thing, which represents a Synop message. It has t
## Discovery ## Discovery
If a system location is set, the nearest availabble Synop station be automatically discovered for this location. If a system location is set, the nearest available Synop station be automatically discovered for this location.
The search radius will expand at each successive scan.
## Thing Configuration ## Thing Configuration
@ -26,7 +28,7 @@ The weather information that is retrieved is available as these channels:
| Channel Type ID | Item Type | Description | | Channel Type ID | Item Type | Description |
|-----------------------|--------------------|--------------------------------------------| |-----------------------|--------------------|--------------------------------------------|
| temperature | Number:Temperature | Current temperature | | temperature | Number:Temperature | Current outdoor temperature |
| pressure | Number:Pressure | Current pressure | | pressure | Number:Pressure | Current pressure |
| wind-speed | Number:Speed | Current wind speed | | wind-speed | Number:Speed | Current wind speed |
| wind-speed-beaufort | Number | Wind speed according to Beaufort scale | | wind-speed-beaufort | Number | Wind speed according to Beaufort scale |

View File

@ -25,14 +25,13 @@ import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
/** /**
* The {@link SynopAnalyzerBinding} class defines common constants, which are * The {@link SynopAnalyzerBinding} class defines common constants used across the whole binding.
* used across the whole binding.
* *
* @author Gaël L'hopital - Initial contribution * @author Gaël L'hopital - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class SynopAnalyzerBindingConstants { public class SynopAnalyzerBindingConstants {
public static final String BINDING_ID = "synopanalyzer"; private static final String BINDING_ID = "synopanalyzer";
// List of all Thing Type UIDs // List of all Thing Type UIDs
public static final ThingTypeUID THING_SYNOP = new ThingTypeUID(BINDING_ID, "synopanalyzer"); public static final ThingTypeUID THING_SYNOP = new ThingTypeUID(BINDING_ID, "synopanalyzer");

View File

@ -14,38 +14,25 @@ package org.openhab.binding.synopanalyzer.internal;
import static org.openhab.binding.synopanalyzer.internal.SynopAnalyzerBindingConstants.THING_SYNOP; import static org.openhab.binding.synopanalyzer.internal.SynopAnalyzerBindingConstants.THING_SYNOP;
import java.io.IOException; import java.util.List;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Hashtable;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.synopanalyzer.internal.discovery.SynopAnalyzerDiscoveryService;
import org.openhab.binding.synopanalyzer.internal.handler.SynopAnalyzerHandler; import org.openhab.binding.synopanalyzer.internal.handler.SynopAnalyzerHandler;
import org.openhab.binding.synopanalyzer.internal.synop.StationDB; import org.openhab.binding.synopanalyzer.internal.stationdb.Station;
import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.binding.synopanalyzer.internal.stationdb.StationDbService;
import org.openhab.core.i18n.LocationProvider; import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/** /**
* The {@link SynopAnalyzerHandlerFactory} is responsible for creating things and thing * The {@link SynopAnalyzerHandlerFactory} is responsible for creating things and thing handlers.
* handlers.
* *
* @author Gaël L'hopital - Initial contribution * @author Gaël L'hopital - Initial contribution
*/ */
@ -53,15 +40,14 @@ import com.google.gson.Gson;
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.synopanalyzer") @Component(service = ThingHandlerFactory.class, configurationPid = "binding.synopanalyzer")
@NonNullByDefault @NonNullByDefault
public class SynopAnalyzerHandlerFactory extends BaseThingHandlerFactory { public class SynopAnalyzerHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(SynopAnalyzerHandlerFactory.class);
private final LocationProvider locationProvider; private final LocationProvider locationProvider;
private final Gson gson = new Gson(); private final List<Station> stationDB;
private @Nullable StationDB stationDB;
private @Nullable ServiceRegistration<?> serviceReg;
@Activate @Activate
public SynopAnalyzerHandlerFactory(@Reference LocationProvider locationProvider) { public SynopAnalyzerHandlerFactory(@Reference StationDbService stationDBService,
@Reference LocationProvider locationProvider) {
this.locationProvider = locationProvider; this.locationProvider = locationProvider;
this.stationDB = stationDBService.getStations();
} }
@Override @Override
@ -74,40 +60,4 @@ public class SynopAnalyzerHandlerFactory extends BaseThingHandlerFactory {
return supportsThingType(thing.getThingTypeUID()) ? new SynopAnalyzerHandler(thing, locationProvider, stationDB) return supportsThingType(thing.getThingTypeUID()) ? new SynopAnalyzerHandler(thing, locationProvider, stationDB)
: null; : null;
} }
@Override
protected void activate(ComponentContext componentContext) {
super.activate(componentContext);
try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("/db/stations.json");
Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8);) {
StationDB stations = gson.fromJson(reader, StationDB.class);
registerDiscoveryService(stations);
this.stationDB = stations;
logger.debug("Discovery service for Synop Stations registered.");
} catch (IOException e) {
logger.warn("Unable to read synop stations database");
}
}
@Override
protected void deactivate(ComponentContext componentContext) {
unregisterDiscoveryService();
super.deactivate(componentContext);
}
private void registerDiscoveryService(StationDB stations) {
SynopAnalyzerDiscoveryService discoveryService = new SynopAnalyzerDiscoveryService(stations, locationProvider);
serviceReg = bundleContext.registerService(DiscoveryService.class.getName(), discoveryService,
new Hashtable<>());
}
private void unregisterDiscoveryService() {
if (serviceReg != null) {
serviceReg.unregister();
serviceReg = null;
}
}
} }

View File

@ -13,11 +13,9 @@
package org.openhab.binding.synopanalyzer.internal.config; package org.openhab.binding.synopanalyzer.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.synopanalyzer.internal.handler.SynopAnalyzerHandler;
/** /**
* The {@link SynopAnalyzerConfiguration} is responsible for holding configuration * The {@link SynopAnalyzerConfiguration} holds configuration informations needed for the Synop thing
* informations needed for {@link SynopAnalyzerHandler}
* *
* @author Gaël L'hopital - Initial contribution * @author Gaël L'hopital - Initial contribution
*/ */

View File

@ -14,49 +14,50 @@ package org.openhab.binding.synopanalyzer.internal.discovery;
import static org.openhab.binding.synopanalyzer.internal.SynopAnalyzerBindingConstants.THING_SYNOP; import static org.openhab.binding.synopanalyzer.internal.SynopAnalyzerBindingConstants.THING_SYNOP;
import java.util.Collections; import java.util.Iterator;
import java.util.Comparator; import java.util.List;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Map.Entry;
import java.util.stream.Collectors; import java.util.Set;
import java.util.TreeMap;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.synopanalyzer.internal.config.SynopAnalyzerConfiguration; import org.openhab.binding.synopanalyzer.internal.config.SynopAnalyzerConfiguration;
import org.openhab.binding.synopanalyzer.internal.synop.StationDB; import org.openhab.binding.synopanalyzer.internal.stationdb.Station;
import org.openhab.binding.synopanalyzer.internal.synop.StationDB.Station; import org.openhab.binding.synopanalyzer.internal.stationdb.StationDbService;
import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.i18n.LocationProvider; import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PointType; import org.openhab.core.library.types.PointType;
import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.ThingUID;
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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* The {@link SynopAnalyzerDiscoveryService} creates things based on the configured location. * The {@link SynopAnalyzerDiscoveryService} discovers synop stations based on the configured location.
* *
* @author Gaël L'hopital - Initial Contribution * @author Gaël L'hopital - Initial Contribution
*/ */
@Component(service = DiscoveryService.class)
@NonNullByDefault @NonNullByDefault
public class SynopAnalyzerDiscoveryService extends AbstractDiscoveryService { public class SynopAnalyzerDiscoveryService extends AbstractDiscoveryService {
private static final int DISCOVER_TIMEOUT_SECONDS = 5; private static final int DISCOVER_TIMEOUT_SECONDS = 2;
private final Logger logger = LoggerFactory.getLogger(SynopAnalyzerDiscoveryService.class); private final Logger logger = LoggerFactory.getLogger(SynopAnalyzerDiscoveryService.class);
private final Map<Integer, Double> distances = new HashMap<>();
private final LocationProvider locationProvider; private final LocationProvider locationProvider;
private final StationDB stationDB; private final List<Station> stations;
private double radius = 0;
/** @Activate
* Creates a SynopAnalyzerDiscoveryService with enabled autostart. public SynopAnalyzerDiscoveryService(@Reference StationDbService dBService,
* @Reference LocationProvider locationProvider) {
*/ super(Set.of(THING_SYNOP), DISCOVER_TIMEOUT_SECONDS);
public SynopAnalyzerDiscoveryService(StationDB stationDB, LocationProvider locationProvider) {
super(Collections.singleton(THING_SYNOP), DISCOVER_TIMEOUT_SECONDS);
this.locationProvider = locationProvider; this.locationProvider = locationProvider;
this.stationDB = stationDB; this.stations = dBService.getStations();
} }
@Override @Override
@ -71,23 +72,29 @@ public class SynopAnalyzerDiscoveryService extends AbstractDiscoveryService {
} }
public void createResults(PointType serverLocation) { public void createResults(PointType serverLocation) {
distances.clear(); Map<Double, Station> distances = new TreeMap<>();
stationDB.stations.forEach(s -> { stations.forEach(station -> {
PointType stationLocation = new PointType(s.getLocation()); PointType stationLocation = new PointType(station.getLocation());
DecimalType distance = serverLocation.distanceFrom(stationLocation); double distance = serverLocation.distanceFrom(stationLocation).doubleValue();
distances.put(s.idOmm, distance.doubleValue()); if (distance > radius) {
distances.put(distance, station);
}
}); });
Map<Integer, Double> result = distances.entrySet().stream() Iterator<Entry<Double, Station>> stationIterator = distances.entrySet().iterator();
.sorted(Map.Entry.comparingByValue(Comparator.naturalOrder())).collect(Collectors.toMap( if (stationIterator.hasNext()) {
Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new)); Entry<Double, Station> nearest = stationIterator.next();
Station station = nearest.getValue();
radius = nearest.getKey();
Integer nearestId = result.entrySet().iterator().next().getKey(); thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_SYNOP, Integer.toString(station.idOmm)))
Optional<Station> station = stationDB.stations.stream().filter(s -> s.idOmm == nearestId).findFirst(); .withLabel(String.format("Synop : %s", station.usualName))
thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_SYNOP, nearestId.toString())) .withProperty(SynopAnalyzerConfiguration.STATION_ID, station.idOmm)
.withLabel(String.format("Synop : %s", station.get().usualName)) .withRepresentationProperty(SynopAnalyzerConfiguration.STATION_ID).build());
.withProperty(SynopAnalyzerConfiguration.STATION_ID, nearestId) } else {
.withRepresentationProperty(SynopAnalyzerConfiguration.STATION_ID).build()); logger.info("No Synop station available at a radius higher than {} m - resetting to 0 m", radius);
radius = 0;
}
} }
} }

View File

@ -33,8 +33,8 @@ import javax.ws.rs.HttpMethod;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.synopanalyzer.internal.config.SynopAnalyzerConfiguration; import org.openhab.binding.synopanalyzer.internal.config.SynopAnalyzerConfiguration;
import org.openhab.binding.synopanalyzer.internal.stationdb.Station;
import org.openhab.binding.synopanalyzer.internal.synop.Overcast; import org.openhab.binding.synopanalyzer.internal.synop.Overcast;
import org.openhab.binding.synopanalyzer.internal.synop.StationDB;
import org.openhab.binding.synopanalyzer.internal.synop.Synop; import org.openhab.binding.synopanalyzer.internal.synop.Synop;
import org.openhab.binding.synopanalyzer.internal.synop.SynopLand; import org.openhab.binding.synopanalyzer.internal.synop.SynopLand;
import org.openhab.binding.synopanalyzer.internal.synop.SynopMobile; import org.openhab.binding.synopanalyzer.internal.synop.SynopMobile;
@ -51,6 +51,7 @@ import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType; import org.openhab.core.types.RefreshType;
@ -75,16 +76,16 @@ public class SynopAnalyzerHandler extends BaseThingHandler {
private static final double OCTA_MAX = 8.0; private static final double OCTA_MAX = 8.0;
private final Logger logger = LoggerFactory.getLogger(SynopAnalyzerHandler.class); private final Logger logger = LoggerFactory.getLogger(SynopAnalyzerHandler.class);
private @Nullable ScheduledFuture<?> executionJob;
private @NonNullByDefault({}) String formattedStationId;
private final LocationProvider locationProvider; private final LocationProvider locationProvider;
private final @Nullable StationDB stationDB; private final List<Station> stations;
public SynopAnalyzerHandler(Thing thing, LocationProvider locationProvider, @Nullable StationDB stationDB) { private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
private @NonNullByDefault({}) String formattedStationId;
public SynopAnalyzerHandler(Thing thing, LocationProvider locationProvider, List<Station> stations) {
super(thing); super(thing);
this.locationProvider = locationProvider; this.locationProvider = locationProvider;
this.stationDB = stationDB; this.stations = stations;
} }
@Override @Override
@ -94,26 +95,23 @@ public class SynopAnalyzerHandler extends BaseThingHandler {
logger.info("Scheduling Synop update thread to run every {} minute for Station '{}'", logger.info("Scheduling Synop update thread to run every {} minute for Station '{}'",
configuration.refreshInterval, formattedStationId); configuration.refreshInterval, formattedStationId);
StationDB stations = stationDB; if (thing.getProperties().isEmpty()) {
if (thing.getProperties().isEmpty() && stations != null) { discoverAttributes(configuration.stationId, locationProvider.getLocation());
discoverAttributes(stations, configuration.stationId);
} }
updateStatus(ThingStatus.UNKNOWN); updateStatus(ThingStatus.UNKNOWN);
executionJob = scheduler.scheduleWithFixedDelay(this::updateSynopChannels, 0, configuration.refreshInterval, refreshJob = Optional.of(scheduler.scheduleWithFixedDelay(this::updateChannels, 0,
TimeUnit.MINUTES); configuration.refreshInterval, TimeUnit.MINUTES));
} }
protected void discoverAttributes(StationDB stations, int stationId) { private void discoverAttributes(int stationId, @Nullable PointType serverLocation) {
stations.stations.stream().filter(s -> stationId == s.idOmm).findFirst().ifPresent(s -> { stations.stream().filter(s -> stationId == s.idOmm).findFirst().ifPresent(station -> {
Map<String, String> properties = new HashMap<>(); Map<String, String> properties = new HashMap<>(
properties.put("Usual name", s.usualName); Map.of("Usual name", station.usualName, "Location", station.getLocation()));
properties.put("Location", s.getLocation());
PointType stationLocation = new PointType(s.getLocation());
PointType serverLocation = locationProvider.getLocation();
if (serverLocation != null) { if (serverLocation != null) {
PointType stationLocation = new PointType(station.getLocation());
DecimalType distance = serverLocation.distanceFrom(stationLocation); DecimalType distance = serverLocation.distanceFrom(stationLocation);
properties.put("Distance", new QuantityType<>(distance, SIUnits.METRE).toString()); properties.put("Distance", new QuantityType<>(distance, SIUnits.METRE).toString());
@ -149,33 +147,35 @@ public class SynopAnalyzerHandler extends BaseThingHandler {
return Optional.empty(); return Optional.empty();
} }
private void updateSynopChannels() { private void updateChannels() {
logger.debug("Updating device channels"); logger.debug("Updating device channels");
Optional<Synop> synop = getLastAvailableSynop(); getLastAvailableSynop().ifPresentOrElse(synop -> {
updateStatus(synop.isPresent() ? ThingStatus.ONLINE : ThingStatus.OFFLINE); updateStatus(ThingStatus.ONLINE);
synop.ifPresent(theSynop -> {
getThing().getChannels().forEach(channel -> { getThing().getChannels().forEach(channel -> {
String channelId = channel.getUID().getId(); String channelId = channel.getUID().getId();
if (isLinked(channelId)) { if (isLinked(channelId)) {
updateState(channelId, getChannelState(channelId, theSynop)); updateState(channelId, getChannelState(channelId, synop));
} }
}); });
}); }, () -> updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "No Synop message available"));
} }
private State getChannelState(String channelId, Synop synop) { private State getChannelState(String channelId, Synop synop) {
int octa = synop.getOcta();
switch (channelId) { switch (channelId) {
case HORIZONTAL_VISIBILITY: case HORIZONTAL_VISIBILITY:
return new StringType(synop.getHorizontalVisibility().name()); return new StringType(synop.getHorizontalVisibility().name());
case OCTA: case OCTA:
return new DecimalType(Math.max(0, synop.getOcta())); return octa >= 0 ? new DecimalType(octa) : UnDefType.NULL;
case ATTENUATION_FACTOR: case ATTENUATION_FACTOR:
double kc = Math.max(0, Math.min(synop.getOcta(), OCTA_MAX)) / OCTA_MAX; if (octa >= 0) {
kc = 1 - 0.75 * Math.pow(kc, KASTEN_POWER); double kc = Math.max(0, Math.min(octa, OCTA_MAX)) / OCTA_MAX;
return new DecimalType(kc); kc = 1 - 0.75 * Math.pow(kc, KASTEN_POWER);
return new DecimalType(kc);
}
return UnDefType.NULL;
case OVERCAST: case OVERCAST:
int octa = synop.getOcta();
Overcast overcast = Overcast.fromOcta(octa); Overcast overcast = Overcast.fromOcta(octa);
return overcast == Overcast.UNDEFINED ? UnDefType.NULL : new StringType(overcast.name()); return overcast == Overcast.UNDEFINED ? UnDefType.NULL : new StringType(overcast.name());
case PRESSURE: case PRESSURE:
@ -190,15 +190,14 @@ public class SynopAnalyzerHandler extends BaseThingHandler {
return getWindStrength(synop); return getWindStrength(synop);
case WIND_SPEED_BEAUFORT: case WIND_SPEED_BEAUFORT:
QuantityType<Speed> wsKpH = getWindStrength(synop).toUnit(SIUnits.KILOMETRE_PER_HOUR); QuantityType<Speed> wsKpH = getWindStrength(synop).toUnit(SIUnits.KILOMETRE_PER_HOUR);
return (wsKpH != null) ? new DecimalType(Math.round(Math.pow(wsKpH.floatValue() / 3.01, 0.666666666))) return wsKpH != null ? new DecimalType(Math.round(Math.pow(wsKpH.floatValue() / 3.01, 0.666666666)))
: UnDefType.NULL; : UnDefType.NULL;
case TIME_UTC: case TIME_UTC:
ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
int year = synop.getYear() == 0 ? now.getYear() : synop.getYear(); int year = synop.getYear() == 0 ? now.getYear() : synop.getYear();
int month = synop.getMonth() == 0 ? now.getMonth().getValue() : synop.getMonth(); int month = synop.getMonth() == 0 ? now.getMonth().getValue() : synop.getMonth();
ZonedDateTime zdt = ZonedDateTime.of(year, month, synop.getDay(), synop.getHour(), 0, 0, 0, return new DateTimeType(
ZoneOffset.UTC); ZonedDateTime.of(year, month, synop.getDay(), synop.getHour(), 0, 0, 0, ZoneOffset.UTC));
return new DateTimeType(zdt);
default: default:
logger.error("Unsupported channel Id '{}'", channelId); logger.error("Unsupported channel Id '{}'", channelId);
return UnDefType.UNDEF; return UnDefType.UNDEF;
@ -232,17 +231,14 @@ public class SynopAnalyzerHandler extends BaseThingHandler {
@Override @Override
public void dispose() { public void dispose() {
ScheduledFuture<?> job = this.executionJob; refreshJob.ifPresent(job -> job.cancel(true));
if (job != null && !job.isCancelled()) { refreshJob = Optional.empty();
job.cancel(true);
}
executionJob = null;
} }
@Override @Override
public void handleCommand(ChannelUID channelUID, Command command) { public void handleCommand(ChannelUID channelUID, Command command) {
if (command == RefreshType.REFRESH) { if (command == RefreshType.REFRESH) {
updateSynopChannels(); updateChannels();
} }
} }
} }

View File

@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2022 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.stationdb;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link Station} is a DTO for stations.json database.
*
* @author Gaël L'hopital - Initial Contribution
*/
@NonNullByDefault
public class Station {
public int idOmm;
public String usualName = "";
private double latitude;
private double longitude;
public String getLocation() {
return Double.toString(latitude) + "," + Double.toString(longitude);
}
}

View File

@ -0,0 +1,63 @@
/**
* Copyright (c) 2010-2022 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.stationdb;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
/**
* The {@link StationDbService} makes available a list of known Synop stations.
*
* @author Gaël L'hopital - Initial Contribution
*/
@Component(service = StationDbService.class)
@NonNullByDefault
public class StationDbService {
private final Logger logger = LoggerFactory.getLogger(StationDbService.class);
private List<Station> stations;
@Activate
public StationDbService() {
try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("/db/stations.json");
Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8);) {
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
stations = Arrays.asList(gson.fromJson(reader, Station[].class));
} catch (IOException | JsonSyntaxException | JsonIOException e) {
logger.warn("Unable to load station list : {}", e.getMessage());
stations = List.of();
}
}
public List<Station> getStations() {
return stations;
}
}

View File

@ -12,11 +12,14 @@
*/ */
package org.openhab.binding.synopanalyzer.internal.synop; package org.openhab.binding.synopanalyzer.internal.synop;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* The {@link WindDirections} enum possible overcast descriptions * The {@link WindDirections} enum possible overcast descriptions
* *
* @author Gaël L'hopital - Initial contribution * @author Gaël L'hopital - Initial contribution
*/ */
@NonNullByDefault
public enum Overcast { public enum Overcast {
UNDEFINED, UNDEFINED,
CLEAR_SKY, CLEAR_SKY,

View File

@ -1,46 +0,0 @@
/**
* Copyright (c) 2010-2022 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.synop;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* The {@link StationDB} creates is a DTO for stations.json database.
*
* @author Gaël L'hopital - Initial Contribution
*/
public class StationDB {
public class Station {
public String country;
public String pack;
@SerializedName("id_omm")
public int idOmm;
@SerializedName("numer_sta")
public long numerSta;
@SerializedName("usual_name")
public String usualName;
public double latitude;
public double longitude;
public double elevation;
@SerializedName("station_type")
public int stationType;
public String getLocation() {
return Double.toString(latitude) + "," + Double.toString(longitude);
}
}
public List<Station> stations;
}

View File

@ -30,14 +30,14 @@ import org.openhab.core.library.unit.Units;
@NonNullByDefault @NonNullByDefault
public abstract class Synop { public abstract class Synop {
protected static final int INITIAL_VALUE = -1000; protected static final int INITIAL_VALUE = -1000;
protected static final char PLUS_SIGN_TEMPERATURE = '0'; private static final char PLUS_SIGN_TEMPERATURE = '0';
protected static final char MINUS_SIGN_TEMPERATURE = '1'; private static final char MINUS_SIGN_TEMPERATURE = '1';
/* /*
* WS - WIND SPEED * WS - WIND SPEED
*/ */
protected static final int WS_WILDTYPE_IN_MPS = 0; private static final int WS_WILDTYPE_IN_MPS = 0;
protected static final int WS_ANEMOMETER_IN_MPS = 1; private static final int WS_ANEMOMETER_IN_MPS = 1;
/* /*
* HV - HORIZONTAL VISIBILITY [IN KILOMETERS] * HV - HORIZONTAL VISIBILITY [IN KILOMETERS]
@ -59,12 +59,12 @@ public abstract class Synop {
* 99 - > 50 km * 99 - > 50 km
* HP - high precision * HP - high precision
*/ */
protected static final int HV_LESS_THAN_1_LIMIT = 10; private static final int HV_LESS_THAN_1_LIMIT = 10;
protected static final int HV_LESS_THAN_10_LIMIT = 60; private static final int HV_LESS_THAN_10_LIMIT = 60;
protected static final int HV_LESS_THAN_50_LIMIT = 84; private static final int HV_LESS_THAN_50_LIMIT = 84;
protected static final int HV_LESS_THAN_1_HP_LIMIT = 93; private static final int HV_LESS_THAN_1_HP_LIMIT = 93;
protected static final int HV_LESS_THAN_10_HP_LIMIT = 96; private static final int HV_LESS_THAN_10_HP_LIMIT = 96;
protected static final int HV_LESS_THAN_50_HP_LIMIT = 98; private static final int HV_LESS_THAN_50_HP_LIMIT = 98;
public static enum HorizontalVisibility { public static enum HorizontalVisibility {
UNDEFINED, UNDEFINED,
@ -74,7 +74,7 @@ public abstract class Synop {
MORE_THAN_50 MORE_THAN_50
} }
private final int VALID_STRING_LENGTH = 5; private static final int VALID_STRING_LENGTH = 5;
protected final List<String> stringArray; protected final List<String> stringArray;
@ -107,6 +107,14 @@ public abstract class Synop {
setPressure(); setPressure();
} }
protected abstract void setTemperatureString();
protected abstract void setHorizontalVisibilityInt();
protected abstract void setPressureString();
protected abstract void setWindString();
private void setDateHourAndWindIndicator() { private void setDateHourAndWindIndicator() {
String dayHourAndWindIndicator = ""; String dayHourAndWindIndicator = "";
@ -147,9 +155,7 @@ public abstract class Synop {
private void setHorizontalVisibility() { private void setHorizontalVisibility() {
setHorizontalVisibilityInt(); setHorizontalVisibilityInt();
if (horizontalVisibilityInt != INITIAL_VALUE) { if (horizontalVisibilityInt != INITIAL_VALUE) {
if (horizontalVisibilityInt < HV_LESS_THAN_1_LIMIT || horizontalVisibilityInt < HV_LESS_THAN_1_HP_LIMIT) { if (horizontalVisibilityInt < HV_LESS_THAN_1_LIMIT || horizontalVisibilityInt < HV_LESS_THAN_1_HP_LIMIT) {
horizontalVisibility = HorizontalVisibility.LESS_THAN_1; horizontalVisibility = HorizontalVisibility.LESS_THAN_1;
} else if (horizontalVisibilityInt < HV_LESS_THAN_10_LIMIT } else if (horizontalVisibilityInt < HV_LESS_THAN_10_LIMIT
@ -166,8 +172,6 @@ public abstract class Synop {
} }
} }
protected abstract void setHorizontalVisibilityInt();
private void setTemperature() { private void setTemperature() {
setTemperatureString(); setTemperatureString();
temperature = INITIAL_VALUE; temperature = INITIAL_VALUE;
@ -183,12 +187,11 @@ public abstract class Synop {
} }
} }
protected abstract void setTemperatureString();
private void setWindAndOvercast() { private void setWindAndOvercast() {
setWindString(); setWindString();
if (windString != null) { String localWind = windString;
String gustyFlag = windString.substring(0, 2); if (localWind != null) {
String gustyFlag = localWind.substring(0, 2);
if ("00".equals(gustyFlag)) { if ("00".equals(gustyFlag)) {
setWindSpeed(true); setWindSpeed(true);
} else { } else {
@ -203,18 +206,20 @@ public abstract class Synop {
} }
private void setOcta() { private void setOcta() {
if (windString != null) { String localWind = windString;
octa = Character.getNumericValue(windString.charAt(0)); if (localWind != null) {
octa = Character.getNumericValue(localWind.charAt(0));
} else { } else {
octa = -1; octa = -1;
} }
} }
private void setWindDirection() { private void setWindDirection() {
if (windString != null) { String localWind = windString;
String windDirectionString = windString.substring(1, 3); if (localWind != null) {
String windDirectionString = localWind.substring(1, 3);
if (windDirectionString.equals("99") || windDirectionString.equals("||")) { if ("99".equals(windDirectionString) || "||".equals(windDirectionString)) {
windDirection = INITIAL_VALUE; windDirection = INITIAL_VALUE;
} else { } else {
try { try {
@ -228,8 +233,9 @@ public abstract class Synop {
private void setWindSpeed(boolean gustyWind) { private void setWindSpeed(boolean gustyWind) {
String speedString = null; String speedString = null;
if (windString != null) { String localWind = windString;
speedString = windString.substring(gustyWind ? 2 : 3, 5); if (localWind != null) {
speedString = localWind.substring(gustyWind ? 2 : 3, 5);
try { try {
windSpeed = Integer.parseInt(speedString); windSpeed = Integer.parseInt(speedString);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
@ -238,19 +244,14 @@ public abstract class Synop {
} }
} }
protected abstract void setWindString();
private void setPressure() { private void setPressure() {
setPressureString(); setPressureString();
String localPressure = pressureString;
if (pressureString != null) { if (localPressure != null) {
String pressureTemp = localPressure.substring(1, 5);
String pressureTemp = pressureString.substring(1, 5);
if (pressureTemp.charAt(0) == '0') { if (pressureTemp.charAt(0) == '0') {
pressureTemp = '1' + pressureTemp; pressureTemp = '1' + pressureTemp;
} }
try { try {
pressure = (float) Integer.parseInt(pressureTemp) / 10; pressure = (float) Integer.parseInt(pressureTemp) / 10;
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
@ -259,8 +260,6 @@ public abstract class Synop {
} }
} }
protected abstract void setPressureString();
protected boolean isValidString(String str) { protected boolean isValidString(String str) {
return (str.length() == VALID_STRING_LENGTH); return (str.length() == VALID_STRING_LENGTH);
} }

View File

@ -12,11 +12,14 @@
*/ */
package org.openhab.binding.synopanalyzer.internal.synop; package org.openhab.binding.synopanalyzer.internal.synop;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* The {@link WindDirections} enum possible wind directions * The {@link WindDirections} enum possible wind directions
* *
* @author Gaël L'hopital - Initial contribution * @author Gaël L'hopital - Initial contribution
*/ */
@NonNullByDefault
public enum WindDirections { public enum WindDirections {
N, N,
NNE, NNE,
@ -39,8 +42,8 @@ public enum WindDirections {
* Returns the wind direction based on degree. * Returns the wind direction based on degree.
*/ */
public static WindDirections getWindDirection(int degree) { public static WindDirections getWindDirection(int degree) {
double step = 360.0 / WindDirections.values().length; double step = 360.0 / values().length;
double b = Math.floor((degree + (step / 2.0)) / step); double b = Math.floor((degree + (step / 2.0)) / step);
return WindDirections.values()[(int) (b % WindDirections.values().length)]; return values()[(int) (b % values().length)];
} }
} }

View File

@ -1,5 +1,4 @@
{ [
"stations":[
{ {
"country":"FR", "country":"FR",
"pack":"RADOME", "pack":"RADOME",
@ -2420,5 +2419,4 @@
"elevation":1133, "elevation":1133,
"station_type":1 "station_type":1
} }
] ]
}