Skip to content

Commit

Permalink
[synopanalyzer] Incorrect octa reported (openhab#12541)
Browse files Browse the repository at this point in the history
* 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>
Signed-off-by: Andras Uhrin <andras.uhrin@gmail.com>
  • Loading branch information
clinique authored and andrasU committed Nov 12, 2022
1 parent 86b87be commit fe7ae72
Show file tree
Hide file tree
Showing 13 changed files with 231 additions and 227 deletions.
6 changes: 4 additions & 2 deletions bundles/org.openhab.binding.synopanalyzer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ There is exactly one supported thing, which represents a Synop message. It has t

## 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

Expand All @@ -26,7 +28,7 @@ The weather information that is retrieved is available as these channels:

| Channel Type ID | Item Type | Description |
|-----------------------|--------------------|--------------------------------------------|
| temperature | Number:Temperature | Current temperature |
| temperature | Number:Temperature | Current outdoor temperature |
| pressure | Number:Pressure | Current pressure |
| wind-speed | Number:Speed | Current wind speed |
| wind-speed-beaufort | Number | Wind speed according to Beaufort scale |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,13 @@
import org.openhab.core.thing.ThingTypeUID;

/**
* The {@link SynopAnalyzerBinding} class defines common constants, which are
* used across the whole binding.
* The {@link SynopAnalyzerBinding} class defines common constants used across the whole binding.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class SynopAnalyzerBindingConstants {
public static final String BINDING_ID = "synopanalyzer";
private static final String BINDING_ID = "synopanalyzer";

// List of all Thing Type UIDs
public static final ThingTypeUID THING_SYNOP = new ThingTypeUID(BINDING_ID, "synopanalyzer");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,54 +14,40 @@

import static org.openhab.binding.synopanalyzer.internal.SynopAnalyzerBindingConstants.THING_SYNOP;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Hashtable;
import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
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.synop.StationDB;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.binding.synopanalyzer.internal.stationdb.Station;
import org.openhab.binding.synopanalyzer.internal.stationdb.StationDbService;
import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
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.Component;
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
* handlers.
* The {@link SynopAnalyzerHandlerFactory} is responsible for creating things and thing handlers.
*
* @author Gaël L'hopital - Initial contribution
*/

@Component(service = ThingHandlerFactory.class, configurationPid = "binding.synopanalyzer")
@NonNullByDefault
public class SynopAnalyzerHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(SynopAnalyzerHandlerFactory.class);
private final LocationProvider locationProvider;
private final Gson gson = new Gson();
private @Nullable StationDB stationDB;
private @Nullable ServiceRegistration<?> serviceReg;
private final List<Station> stationDB;

@Activate
public SynopAnalyzerHandlerFactory(@Reference LocationProvider locationProvider) {
public SynopAnalyzerHandlerFactory(@Reference StationDbService stationDBService,
@Reference LocationProvider locationProvider) {
this.locationProvider = locationProvider;
this.stationDB = stationDBService.getStations();
}

@Override
Expand All @@ -74,40 +60,4 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return supportsThingType(thing.getThingTypeUID()) ? new SynopAnalyzerHandler(thing, locationProvider, stationDB)
: 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@
package org.openhab.binding.synopanalyzer.internal.config;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.synopanalyzer.internal.handler.SynopAnalyzerHandler;

/**
* The {@link SynopAnalyzerConfiguration} is responsible for holding configuration
* informations needed for {@link SynopAnalyzerHandler}
* The {@link SynopAnalyzerConfiguration} holds configuration informations needed for the Synop thing
*
* @author Gaël L'hopital - Initial contribution
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,49 +14,50 @@

import static org.openhab.binding.synopanalyzer.internal.SynopAnalyzerBindingConstants.THING_SYNOP;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.synopanalyzer.internal.config.SynopAnalyzerConfiguration;
import org.openhab.binding.synopanalyzer.internal.synop.StationDB;
import org.openhab.binding.synopanalyzer.internal.synop.StationDB.Station;
import org.openhab.binding.synopanalyzer.internal.stationdb.Station;
import org.openhab.binding.synopanalyzer.internal.stationdb.StationDbService;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PointType;
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.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
*/
@Component(service = DiscoveryService.class)
@NonNullByDefault
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 Map<Integer, Double> distances = new HashMap<>();
private final LocationProvider locationProvider;
private final StationDB stationDB;
private final List<Station> stations;
private double radius = 0;

/**
* Creates a SynopAnalyzerDiscoveryService with enabled autostart.
*
*/
public SynopAnalyzerDiscoveryService(StationDB stationDB, LocationProvider locationProvider) {
super(Collections.singleton(THING_SYNOP), DISCOVER_TIMEOUT_SECONDS);
@Activate
public SynopAnalyzerDiscoveryService(@Reference StationDbService dBService,
@Reference LocationProvider locationProvider) {
super(Set.of(THING_SYNOP), DISCOVER_TIMEOUT_SECONDS);
this.locationProvider = locationProvider;
this.stationDB = stationDB;
this.stations = dBService.getStations();
}

@Override
Expand All @@ -71,23 +72,29 @@ public void startScan() {
}

public void createResults(PointType serverLocation) {
distances.clear();
Map<Double, Station> distances = new TreeMap<>();

stationDB.stations.forEach(s -> {
PointType stationLocation = new PointType(s.getLocation());
DecimalType distance = serverLocation.distanceFrom(stationLocation);
distances.put(s.idOmm, distance.doubleValue());
stations.forEach(station -> {
PointType stationLocation = new PointType(station.getLocation());
double distance = serverLocation.distanceFrom(stationLocation).doubleValue();
if (distance > radius) {
distances.put(distance, station);
}
});

Map<Integer, Double> result = distances.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.naturalOrder())).collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new));
Iterator<Entry<Double, Station>> stationIterator = distances.entrySet().iterator();
if (stationIterator.hasNext()) {
Entry<Double, Station> nearest = stationIterator.next();
Station station = nearest.getValue();
radius = nearest.getKey();

Integer nearestId = result.entrySet().iterator().next().getKey();
Optional<Station> station = stationDB.stations.stream().filter(s -> s.idOmm == nearestId).findFirst();
thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_SYNOP, nearestId.toString()))
.withLabel(String.format("Synop : %s", station.get().usualName))
.withProperty(SynopAnalyzerConfiguration.STATION_ID, nearestId)
.withRepresentationProperty(SynopAnalyzerConfiguration.STATION_ID).build());
thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_SYNOP, Integer.toString(station.idOmm)))
.withLabel(String.format("Synop : %s", station.usualName))
.withProperty(SynopAnalyzerConfiguration.STATION_ID, station.idOmm)
.withRepresentationProperty(SynopAnalyzerConfiguration.STATION_ID).build());
} else {
logger.info("No Synop station available at a radius higher than {} m - resetting to 0 m", radius);
radius = 0;
}
}
}
Loading

0 comments on commit fe7ae72

Please sign in to comment.