mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[pegelonline] Initial contribution (#16831)
* initial version Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
This commit is contained in:
parent
5e157262c5
commit
7fa43ea8b1
@ -278,6 +278,7 @@
|
||||
/bundles/org.openhab.binding.orvibo/ @tavalin
|
||||
/bundles/org.openhab.binding.panasonicbdp/ @mlobstein
|
||||
/bundles/org.openhab.binding.paradoxalarm/ @theater
|
||||
/bundles/org.openhab.binding.pegelonline/ @weymann
|
||||
/bundles/org.openhab.binding.pentair/ @jsjames
|
||||
/bundles/org.openhab.binding.phc/ @gnlpfjh
|
||||
/bundles/org.openhab.binding.pilight/ @stefanroellin @niklasdoerfler
|
||||
|
@ -1381,6 +1381,11 @@
|
||||
<artifactId>org.openhab.binding.paradoxalarm</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.pegelonline</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.pentair</artifactId>
|
||||
|
13
bundles/org.openhab.binding.pegelonline/NOTICE
Normal file
13
bundles/org.openhab.binding.pegelonline/NOTICE
Normal file
@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
122
bundles/org.openhab.binding.pegelonline/README.md
Normal file
122
bundles/org.openhab.binding.pegelonline/README.md
Normal file
@ -0,0 +1,122 @@
|
||||
# PegelOnline Binding
|
||||
|
||||
Binding to observe water level from german rivers.
|
||||
Data is provided by german **Water-Route and Shipping Agency** [WSV](https://www.pegelonline.wsv.de/).
|
||||
Goal is to monitor actual water levels from rivers nearby your home.
|
||||
In case of changing water levels the corresponding warning level is lowered or raised.
|
||||
|
||||
## Supported Things
|
||||
|
||||
| Label | Description | ID |
|
||||
|---------------------|---------------------------------------------------------------------------------|---------|
|
||||
| Measurement Station | Station providing water level measurements | station |
|
||||
|
||||
|
||||
## Discovery
|
||||
|
||||
In case your home location coordinates are set the discovery will recognize all measurement stations within a radius of 50 km.
|
||||
Found Things are added in your Inbox.
|
||||
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
Thing configuration contains 3 sections
|
||||
|
||||
* [Station selection](station_selection)
|
||||
* [Warning Levels of selected station](warning_levels)
|
||||
* [Refresh rate](configuration_parameters)
|
||||
|
||||
### Station selection
|
||||
|
||||
Stations can be selected with an Universally Unique Identifier (uuid).
|
||||
It's automatically added by the Discovery.
|
||||
Configure a station manually using [list of all available stations](https://pegelonline.wsv.de/gast/pegeltabelle) or [stations.json](https://www.pegelonline.wsv.de/webservices/rest-api/v2/stations.json) and choose the uuid of your desired measurement station.
|
||||
|
||||
### Warning Levels
|
||||
|
||||
<img align="right" src="./doc/Marburg.png" width="450" height="500"/>
|
||||
|
||||
Each station has specific warning levels
|
||||
|
||||
* Warning Levels 1 (*lowest*) to 3 (*highest*)
|
||||
* Flooding Levels
|
||||
|
||||
Unfortunately these levels cannot be queried automatically.
|
||||
Please select your [federal state](https://www.hochwasserzentralen.de/) and check if which levels they provide.
|
||||
The picture shows the levels of [measurement station Marburg of federal state Hesse](https://www.hlnug.de/static/pegel/wiskiweb2/stations/25830056/station.html?v=20210802152952)
|
||||
|
||||
If you cannot evaluate warning or flooding levels leave the parameter empty.
|
||||
|
||||
### Configuration parameters
|
||||
|
||||
| configuration | content | unit | description | required | default |
|
||||
|------------------|-----------|------|---------------------------|----------|---------|
|
||||
| uuid | text | - | Unique Station Identifier | X | N/A |
|
||||
| warningLevel1 | integer | cm | Warning Level 1 | | N/A |
|
||||
| warningLevel2 | integer | cm | Warning Level 2 | | N/A |
|
||||
| warningLevel3 | integer | cm | Warning Level 3 | | N/A |
|
||||
| hq10 | integer | cm | Decade Flooding | | N/A |
|
||||
| hq100 | integer | cm | Century Flooding | | N/A |
|
||||
| hqExtreme | integer | cm | Extreme Flooding | | N/A |
|
||||
| refreshInterval | integer | min | Refresh Interval | X | 15 |
|
||||
|
||||
## Channels
|
||||
|
||||
|
||||
| channel id | type | description |
|
||||
|----------------------|----------------------|--------------------------------|
|
||||
| timestamp | DateTime | Last Measurement |
|
||||
| level | Number:Length | Water Level |
|
||||
| trend | Number | Water Level Trend |
|
||||
| warning | Number | Current Warning |
|
||||
|
||||
### Trend
|
||||
|
||||
Possible values:
|
||||
|
||||
* 1 : Rising
|
||||
* 0 : Steady
|
||||
* -1 : Lowering
|
||||
|
||||
### Warning
|
||||
|
||||
Current warning according to configuration
|
||||
|
||||
* 0 : No Warning
|
||||
* 1 : Warning level 1
|
||||
* 2 : Warning Level 2
|
||||
* 3 : Warning Level 3
|
||||
* 4 : Decade Flooding
|
||||
* 5 : Century Flooding
|
||||
* 6 : Extreme Flooding
|
||||
|
||||
## Full Example
|
||||
|
||||
### Things
|
||||
|
||||
```java
|
||||
Thing pegelonline:station:giessen "Measurement Station Giessen" [
|
||||
uuid="4b386a6a-996e-4a4a-a440-15d6b40226d4",
|
||||
refreshInterval=15,
|
||||
warningLevel1=550,
|
||||
warningLevel2=600,
|
||||
warningLevel3=650,
|
||||
hq10=732,
|
||||
hq100=786
|
||||
]
|
||||
```
|
||||
|
||||
### Items
|
||||
|
||||
```java
|
||||
DateTime Lahn_Giessen_Timestamp "Measurement timestamp Lahn Giessen" {channel="pegelonline:station:giessen:timestamp" }
|
||||
Number:Length Lahn_Giessen_Level "Water Level Lahn Giessen]" {channel="pegelonline:station:giessen:level" }
|
||||
Number Lahn_Giessen_Trend "Water Level Trend Lahn Giessen" {channel="pegelonline:station:giessen:trend"}
|
||||
Number Lahn_Giessen_Warning "Warning Level Lahn Giessen" {channel="pegelonline:station:giessen:warning"}
|
||||
```
|
||||
|
||||
|
||||
## Links
|
||||
|
||||
[PegelOnline API Documentation](https://www.pegelonline.wsv.de/webservice/dokuRestapi#caching)
|
||||
|
BIN
bundles/org.openhab.binding.pegelonline/doc/Marburg.png
Normal file
BIN
bundles/org.openhab.binding.pegelonline/doc/Marburg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 179 KiB |
17
bundles/org.openhab.binding.pegelonline/pom.xml
Normal file
17
bundles/org.openhab.binding.pegelonline/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>4.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.pegelonline</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: PegelOnline Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.pegelonline-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-pegelonline" description="PegelOnline Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.pegelonline/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.pegelonline.internal;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* The {@link PegelOnlineBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PegelOnlineBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "pegelonline";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID STATION_THING = new ThingTypeUID(BINDING_ID, "station");
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(STATION_THING);
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String TIMESTAMP_CHANNEL = "timestamp";
|
||||
public static final String LEVEL_CHANNEL = "level";
|
||||
public static final String TREND_CHANNEL = "trend";
|
||||
public static final String WARNING_CHANNEL = "warning";
|
||||
|
||||
public static final int NO_WARNING = 0;
|
||||
public static final int WARN_LEVEL_1 = 1;
|
||||
public static final int WARN_LEVEL_2 = 2;
|
||||
public static final int WARN_LEVEL_3 = 3;
|
||||
public static final int HQ10 = 4;
|
||||
public static final int HQ100 = 5;
|
||||
public static final int HQ_EXTREME = 6;
|
||||
|
||||
public static final Gson GSON = new Gson();
|
||||
|
||||
public static final String STATIONS_URI = "https://www.pegelonline.wsv.de/webservices/rest-api/v2/stations";
|
||||
public static final double DISCOVERY_RADIUS = 50;
|
||||
public static final PointType UNDEF_LOCATION = PointType.valueOf("-1,-1");
|
||||
|
||||
public static final String SPACE = " ";
|
||||
public static final String UNDERLINE = "_";
|
||||
public static final String HYPHEN = " - ";
|
||||
public static final String EMPTY = "";
|
||||
public static final String UNKNOWN = "Unknown";
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.pegelonline.internal;
|
||||
|
||||
import static org.openhab.binding.pegelonline.internal.PegelOnlineBindingConstants.STATION_THING;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.pegelonline.internal.handler.PegelOnlineHandler;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
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.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link PegelOnlineHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.pegelonline", service = ThingHandlerFactory.class)
|
||||
public class PegelOnlineHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
|
||||
@Activate
|
||||
public PegelOnlineHandlerFactory(final @Reference HttpClientFactory hcf, final @Reference LocationProvider lp) {
|
||||
httpClientFactory = hcf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return PegelOnlineBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (STATION_THING.equals(thingTypeUID)) {
|
||||
return new PegelOnlineHandler(thing, httpClientFactory.getCommonHttpClient());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.pegelonline.internal.config;
|
||||
|
||||
import static org.openhab.binding.pegelonline.internal.PegelOnlineBindingConstants.*;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link PegelOnlineConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PegelOnlineConfiguration {
|
||||
public String uuid = UNKNOWN;
|
||||
public int warningLevel1 = Integer.MAX_VALUE;
|
||||
public int warningLevel2 = Integer.MAX_VALUE;
|
||||
public int warningLevel3 = Integer.MAX_VALUE;
|
||||
public int hq10 = Integer.MAX_VALUE;
|
||||
public int hq100 = Integer.MAX_VALUE;
|
||||
public int hqExtreme = Integer.MAX_VALUE;
|
||||
public int refreshInterval = 15;
|
||||
|
||||
public boolean uuidCheck() {
|
||||
// https://stackoverflow.com/questions/20041051/how-to-judge-a-string-is-uuid-type
|
||||
return uuid.matches("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if configured warning levels are in ascending order
|
||||
*
|
||||
* @return true if ascending, false otherwise
|
||||
*/
|
||||
public boolean warningCheck() {
|
||||
TreeMap<Integer, Integer> warnMap = this.getWarnings();
|
||||
Entry<Integer, Integer> currentEntry = warnMap.firstEntry();
|
||||
Entry<Integer, Integer> nextEntry = warnMap.higherEntry(currentEntry.getKey());
|
||||
while (nextEntry != null) {
|
||||
// ignore non configured values
|
||||
if (nextEntry.getKey() != Integer.MAX_VALUE) {
|
||||
if (nextEntry.getValue() < currentEntry.getValue()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
currentEntry = nextEntry;
|
||||
nextEntry = warnMap.higherEntry(currentEntry.getKey());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate sorted map with level height and warning level based on configuration
|
||||
*
|
||||
* @return TreeMap with keys containing level height and values containing warning level
|
||||
*/
|
||||
public TreeMap<Integer, Integer> getWarnings() {
|
||||
TreeMap<Integer, Integer> warnMap = new TreeMap<>();
|
||||
warnMap.put(0, NO_WARNING);
|
||||
warnMap.put(warningLevel1, WARN_LEVEL_1);
|
||||
warnMap.put(warningLevel2, WARN_LEVEL_2);
|
||||
warnMap.put(warningLevel3, WARN_LEVEL_3);
|
||||
warnMap.put(hq10, HQ10);
|
||||
warnMap.put(hq100, HQ100);
|
||||
warnMap.put(hqExtreme, HQ_EXTREME);
|
||||
return warnMap;
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.pegelonline.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.pegelonline.internal.PegelOnlineBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.openhab.binding.pegelonline.internal.dto.Station;
|
||||
import org.openhab.binding.pegelonline.internal.handler.PegelOnlineHandler;
|
||||
import org.openhab.binding.pegelonline.internal.utils.Utils;
|
||||
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.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
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;
|
||||
|
||||
/**
|
||||
* {@link PegelDiscovery} Discovery of measurement stations
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.pegelonline")
|
||||
public class PegelDiscovery extends AbstractDiscoveryService implements ThingHandlerService {
|
||||
private final Logger logger = LoggerFactory.getLogger(PegelDiscovery.class);
|
||||
private Optional<PegelOnlineHandler> handler = Optional.empty();
|
||||
private PointType homeLocation = UNDEF_LOCATION;
|
||||
private HttpClientFactory httpClientFactory;
|
||||
|
||||
@Activate
|
||||
public PegelDiscovery(final @Reference HttpClientFactory hcf, final @Reference LocationProvider lp) {
|
||||
super(SUPPORTED_THING_TYPES_UIDS, 10, false);
|
||||
httpClientFactory = hcf;
|
||||
PointType location = lp.getLocation();
|
||||
if (location != null) {
|
||||
homeLocation = location;
|
||||
} else {
|
||||
logger.debug("No home location found");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
double homeLat = homeLocation.getLatitude().doubleValue();
|
||||
double homeLon = homeLocation.getLongitude().doubleValue();
|
||||
try {
|
||||
ContentResponse cr = httpClientFactory.getCommonHttpClient().GET(STATIONS_URI);
|
||||
Station[] stationArray = GSON.fromJson(cr.getContentAsString(), Station[].class);
|
||||
if (stationArray != null) {
|
||||
for (Station station : stationArray) {
|
||||
double distance = Utils.calculateDistance(homeLat, homeLon, station.latitude, station.longitude);
|
||||
if (distance < DISCOVERY_RADIUS) {
|
||||
logger.trace("Station in range {},{}", station.longname, station.water.shortname);
|
||||
reportResult(station);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.trace("No stations found in discovery");
|
||||
}
|
||||
} catch (ExecutionException | TimeoutException | InterruptedException e) {
|
||||
logger.trace("Exception during station discovery: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void reportResult(Station s) {
|
||||
String label = "Pegel Station " + Utils.toTitleCase(s.shortname) + " / " + Utils.toTitleCase(s.water.shortname);
|
||||
Map<String, Object> properties = new HashMap<String, Object>();
|
||||
properties.put("agency", s.agency);
|
||||
properties.put("km", s.km);
|
||||
properties.put("river", s.water.longname);
|
||||
properties.put("station", s.longname);
|
||||
properties.put("uuid", s.uuid);
|
||||
properties.put("location", s.latitude + "," + s.longitude);
|
||||
ThingUID uid = new ThingUID(STATION_THING, s.uuid);
|
||||
thingDiscovered(DiscoveryResultBuilder.create(uid).withRepresentationProperty("uuid").withLabel(label)
|
||||
.withProperties(properties).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof PegelOnlineHandler pegelOnlineHandler) {
|
||||
handler = Optional.of(pegelOnlineHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return handler.orElse(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.pegelonline.internal.dto;
|
||||
|
||||
/**
|
||||
* {@link Measure} DTO for water level measurements
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Measure {
|
||||
public String timestamp; // "2021-07-31T19:00:00+02:00",
|
||||
public double value; // ":238.0,
|
||||
public int trend; // -1,
|
||||
public String stateMnwMhw; // "normal",
|
||||
public String stateNswHsw; // "unknown"
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.pegelonline.internal.dto;
|
||||
|
||||
/**
|
||||
* {@link Station} DTO for measurement Station
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Station {
|
||||
public String uuid; // "47174d8f-1b8e-4599-8a59-b580dd55bc87",
|
||||
public long number; // "48900237",
|
||||
public String shortname; // "EITZE",
|
||||
public String longname; // "EITZE",
|
||||
public double km; // 9.56,
|
||||
public String agency; // : "WSA VERDEN",
|
||||
public double longitude; // 9.27676943537587,
|
||||
public double latitude; // 52.90406541008721,
|
||||
public Water water;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.pegelonline.internal.dto;
|
||||
|
||||
/**
|
||||
* {@link Station} DTO for river naming
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Water {
|
||||
public String shortname; // "ALLER",
|
||||
public String longname; // "ALLER"
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.pegelonline.internal.handler;
|
||||
|
||||
import static org.openhab.binding.pegelonline.internal.PegelOnlineBindingConstants.*;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.openhab.binding.pegelonline.internal.config.PegelOnlineConfiguration;
|
||||
import org.openhab.binding.pegelonline.internal.dto.Measure;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.MetricPrefix;
|
||||
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.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link PegelOnlineHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PegelOnlineHandler extends BaseThingHandler {
|
||||
private static final String STATIONS_URI = "https://www.pegelonline.wsv.de/webservices/rest-api/v2/stations";
|
||||
private final Logger logger = LoggerFactory.getLogger(PegelOnlineHandler.class);
|
||||
private Optional<PegelOnlineConfiguration> configuration = Optional.empty();
|
||||
private Optional<ScheduledFuture<?>> schedule = Optional.empty();
|
||||
private Optional<Measure> cache = Optional.empty();
|
||||
private TreeMap<Integer, Integer> warnMap = new TreeMap<>();
|
||||
private String stationUUID = UNKNOWN;
|
||||
private HttpClient httpClient;
|
||||
|
||||
public PegelOnlineHandler(Thing thing, HttpClient hc) {
|
||||
super(thing);
|
||||
httpClient = hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
if (cache.isPresent()) {
|
||||
Measure m = cache.get();
|
||||
if (LEVEL_CHANNEL.equals(channelUID.getId())) {
|
||||
updateChannelState(LEVEL_CHANNEL, QuantityType.valueOf(m.value, MetricPrefix.CENTI(SIUnits.METRE)));
|
||||
} else if (TREND_CHANNEL.equals(channelUID.getId())) {
|
||||
updateChannelState(TREND_CHANNEL, DecimalType.valueOf(Integer.toString(m.trend)));
|
||||
} else if (TIMESTAMP_CHANNEL.equals(channelUID.getId())) {
|
||||
updateChannelState(TIMESTAMP_CHANNEL, DateTimeType.valueOf(m.timestamp));
|
||||
} else if (WARNING_CHANNEL.equals(channelUID.getId())) {
|
||||
updateChannelState(WARNING_CHANNEL,
|
||||
DecimalType.valueOf(Integer.toString(warnMap.floorEntry((int) m.value).getValue())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
PegelOnlineConfiguration config = getConfigAs(PegelOnlineConfiguration.class);
|
||||
stationUUID = config.uuid;
|
||||
if (!config.uuidCheck()) {
|
||||
String description = "@text/pegelonline.handler.status.uuid [\"" + stationUUID + "\"]";
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, description);
|
||||
return;
|
||||
}
|
||||
if (!config.warningCheck()) {
|
||||
String description = "@text/pegelonline.handler.status.warning";
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, description);
|
||||
return;
|
||||
}
|
||||
warnMap = config.getWarnings();
|
||||
configuration = Optional.of(config);
|
||||
String description = "@text/pegelonline.handler.status.wait-feedback";
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, description);
|
||||
schedule = Optional.of(scheduler.scheduleWithFixedDelay(this::performMeasurement, 0,
|
||||
configuration.get().refreshInterval, TimeUnit.MINUTES));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
warnMap.clear();
|
||||
if (schedule.isPresent()) {
|
||||
schedule.get().cancel(true);
|
||||
}
|
||||
schedule = Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateConfiguration(Configuration configuration) {
|
||||
super.updateConfiguration(configuration);
|
||||
}
|
||||
|
||||
void performMeasurement() {
|
||||
try {
|
||||
ContentResponse cr = httpClient.GET(STATIONS_URI + "/" + stationUUID + "/W/currentmeasurement.json");
|
||||
int responseStatus = cr.getStatus();
|
||||
if (responseStatus == 200) {
|
||||
String content = cr.getContentAsString();
|
||||
Measure measureDto = GSON.fromJson(content, Measure.class);
|
||||
if (isValid(measureDto) && measureDto != null) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
updateChannels(measureDto);
|
||||
} else {
|
||||
String description = "@text/pegelonline.handler.status.json-error [\"" + content + "\"]";
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, description);
|
||||
}
|
||||
} else if (responseStatus == 404) {
|
||||
// 404 respoonse shows station isn't found
|
||||
String description = "@text/pegelonline.handler.status.uuid-not-found [\"" + stationUUID + "\"]";
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, description);
|
||||
} else {
|
||||
String description = "@text/pegelonline.handler.status.http-status [\"" + responseStatus + "\"]";
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, description);
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
String description = "@text/pegelonline.handler.status.http-exception [\"" + e.getMessage() + "\"]";
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, description);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValid(@Nullable Measure measureDto) {
|
||||
if (measureDto != null) {
|
||||
if (measureDto.timestamp != null) {
|
||||
try {
|
||||
DateTimeType.valueOf(measureDto.timestamp);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.trace("Error converting {} into DateTime: {}", measureDto.timestamp, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void updateChannels(Measure measureDto) {
|
||||
cache = Optional.of(measureDto);
|
||||
updateChannelState(TIMESTAMP_CHANNEL, DateTimeType.valueOf(measureDto.timestamp));
|
||||
updateChannelState(LEVEL_CHANNEL, QuantityType.valueOf(measureDto.value, MetricPrefix.CENTI(SIUnits.METRE)));
|
||||
updateChannelState(TREND_CHANNEL, DecimalType.valueOf(Integer.toString(measureDto.trend)));
|
||||
updateChannelState(WARNING_CHANNEL,
|
||||
DecimalType.valueOf(Integer.toString(warnMap.floorEntry((int) measureDto.value).getValue())));
|
||||
}
|
||||
|
||||
private void updateChannelState(String channel, State st) {
|
||||
updateState(new ChannelUID(thing.getUID(), channel), st);
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.pegelonline.internal.utils;
|
||||
|
||||
import static org.openhab.binding.pegelonline.internal.PegelOnlineBindingConstants.UNKNOWN;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link Utils} Utilities for binding
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Utils {
|
||||
public static final int EARTH_RADIUS = 6371;
|
||||
|
||||
/**
|
||||
* Calculate the Distance Using Equirectangular Distance Approximation
|
||||
*
|
||||
* @param lat1 - Latitude of coordinate 1
|
||||
* @param lon1 - Longitude of coordinate 1
|
||||
* @param lat2 - Latitude of coordinate 2
|
||||
* @param lon2 - Longitude of coordinate 2
|
||||
* @return distance in km
|
||||
*
|
||||
* @see https://www.baeldung.com/java-find-distance-between-points#equirectangular-distance-approximation
|
||||
*
|
||||
*/
|
||||
public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
|
||||
double lat1Rad = Math.toRadians(lat1);
|
||||
double lat2Rad = Math.toRadians(lat2);
|
||||
double lon1Rad = Math.toRadians(lon1);
|
||||
double lon2Rad = Math.toRadians(lon2);
|
||||
|
||||
double x = (lon2Rad - lon1Rad) * Math.cos((lat1Rad + lat2Rad) / 2);
|
||||
double y = (lat2Rad - lat1Rad);
|
||||
double distance = Math.sqrt(x * x + y * y) * EARTH_RADIUS;
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts String from "all upper case" into "title case" after space and hyphen
|
||||
*
|
||||
* @param input - string to convert
|
||||
* @return title case string
|
||||
*/
|
||||
public static String toTitleCase(@Nullable String input) {
|
||||
if (input == null) {
|
||||
return toTitleCase(UNKNOWN);
|
||||
} else {
|
||||
StringBuffer titleCaseString = new StringBuffer();
|
||||
for (String string : StringUtils.splitByCharacterType(input)) {
|
||||
String converted = StringUtils.capitalize(string.toLowerCase());
|
||||
titleCaseString.append(converted);
|
||||
}
|
||||
return titleCaseString.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<addon:addon id="pegelonline" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
|
||||
|
||||
<type>binding</type>
|
||||
<name>PegelOnline Binding</name>
|
||||
<description>This is the binding for PegelOnline.</description>
|
||||
<connection>cloud</connection>
|
||||
<countries>de</countries>
|
||||
|
||||
</addon:addon>
|
@ -0,0 +1,58 @@
|
||||
# add-on
|
||||
|
||||
addon.pegelonline.name = PegelOnline Binding
|
||||
addon.pegelonline.description = This is the binding for PegelOnline.
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.pegelonline.station.label = Measurement Station
|
||||
thing-type.pegelonline.station.description = Station providing water level measurements
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.pegelonline.station.hq10.label = Decade Flooding
|
||||
thing-type.config.pegelonline.station.hq10.description = Water level of decade flooding 10-20 years
|
||||
thing-type.config.pegelonline.station.hq100.label = Century Flooding
|
||||
thing-type.config.pegelonline.station.hq100.description = Water level of century flooding in ~ 100 years
|
||||
thing-type.config.pegelonline.station.hqExtreme.label = Extreme Flooding
|
||||
thing-type.config.pegelonline.station.hqExtreme.description = Water level of extra ordinary flooding > 200 years
|
||||
thing-type.config.pegelonline.station.refreshInterval.label = Refresh Interval
|
||||
thing-type.config.pegelonline.station.refreshInterval.description = Interval measurement polling in minutes.
|
||||
thing-type.config.pegelonline.station.uuid.label = Station Identifier
|
||||
thing-type.config.pegelonline.station.uuid.description = Unique Station Identifier
|
||||
thing-type.config.pegelonline.station.warningLevel1.label = Warning Level 1
|
||||
thing-type.config.pegelonline.station.warningLevel1.description = Water level triggering level 1 warning
|
||||
thing-type.config.pegelonline.station.warningLevel2.label = Warning Level 2
|
||||
thing-type.config.pegelonline.station.warningLevel2.description = Water level triggering level 2 warning
|
||||
thing-type.config.pegelonline.station.warningLevel3.label = Warning Level 3
|
||||
thing-type.config.pegelonline.station.warningLevel3.description = Water level triggering level 3 warning
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.pegelonline.level.label = Water Level
|
||||
channel-type.pegelonline.timestamp.label = Last Measurement
|
||||
channel-type.pegelonline.timestamp.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM
|
||||
channel-type.pegelonline.trend.label = Water Level Trend
|
||||
channel-type.pegelonline.trend.state.option.-1 = Lowering
|
||||
channel-type.pegelonline.trend.state.option.0 = Steady
|
||||
channel-type.pegelonline.trend.state.option.1 = Rising
|
||||
channel-type.pegelonline.warning.label = Warning Level
|
||||
channel-type.pegelonline.warning.state.option.0 = No warning
|
||||
channel-type.pegelonline.warning.state.option.1 = Warning Level 1
|
||||
channel-type.pegelonline.warning.state.option.2 = Warning Level 2
|
||||
channel-type.pegelonline.warning.state.option.3 = Warning Level 3
|
||||
channel-type.pegelonline.warning.state.option.4 = Decade Flooding
|
||||
channel-type.pegelonline.warning.state.option.5 = Century Flooding
|
||||
channel-type.pegelonline.warning.state.option.6 = Extreme Flooding
|
||||
|
||||
# channel types
|
||||
|
||||
pegelonline.handler.status.uuid = Unique Identifier {0} not valid
|
||||
pegelonline.handler.status.warning = Warnings shall be entered in increasing order
|
||||
pegelonline.handler.status.flooding = Flooding Levels shall be entered in increasing order
|
||||
pegelonline.handler.status.wait-feedback = Wait for first feedback
|
||||
pegelonline.handler.status.uuid-not-found = No station found for uuid {0}
|
||||
pegelonline.handler.status.uuid-verification = Verification for uuid {0} ongoing. Next try in 1 minute.
|
||||
pegelonline.handler.status.http-status = HTTP status {0} received
|
||||
pegelonline.handler.status.http-exception = Exception {0}
|
||||
pegelonline.handler.status.json-error = Error parsing {0}
|
@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="pegelonline"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="station">
|
||||
<label>Measurement Station</label>
|
||||
<description>Station providing water level measurements</description>
|
||||
|
||||
<channels>
|
||||
<channel id="timestamp" typeId="timestamp"/>
|
||||
<channel id="level" typeId="level"/>
|
||||
<channel id="trend" typeId="trend"/>
|
||||
<channel id="warning" typeId="warning"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>uuid</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="uuid" type="text" required="true">
|
||||
<label>Station Identifier</label>
|
||||
<description>Unique Station Identifier</description>
|
||||
</parameter>
|
||||
<parameter name="warningLevel1" type="integer">
|
||||
<label>Warning Level 1</label>
|
||||
<description>Water level triggering level 1 warning</description>
|
||||
</parameter>
|
||||
<parameter name="warningLevel2" type="integer">
|
||||
<label>Warning Level 2</label>
|
||||
<description>Water level triggering level 2 warning</description>
|
||||
</parameter>
|
||||
<parameter name="warningLevel3" type="integer">
|
||||
<label>Warning Level 3</label>
|
||||
<description>Water level triggering level 3 warning</description>
|
||||
</parameter>
|
||||
<parameter name="hq10" type="integer">
|
||||
<label>Decade Flooding</label>
|
||||
<description>Water level of decade flooding 10-20 years</description>
|
||||
</parameter>
|
||||
<parameter name="hq100" type="integer">
|
||||
<label>Century Flooding</label>
|
||||
<description>Water level of century flooding in ~ 100 years</description>
|
||||
</parameter>
|
||||
<parameter name="hqExtreme" type="integer">
|
||||
<label>Extreme Flooding</label>
|
||||
<description>Water level of extra ordinary flooding > 200 years</description>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" unit="m" min="1" required="true">
|
||||
<label>Refresh Interval</label>
|
||||
<default>15</default>
|
||||
<description>Interval measurement polling in minutes.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
|
||||
<channel-type id="timestamp">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Measurement</label>
|
||||
<state pattern="%1$tA, %1$td.%1$tm. %1$tH:%1$tM" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="level">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Water Level</label>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="trend">
|
||||
<item-type>Number</item-type>
|
||||
<label>Water Level Trend</label>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="-1">Lowering</option>
|
||||
<option value="0">Steady</option>
|
||||
<option value="1">Rising</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="warning">
|
||||
<item-type>Number</item-type>
|
||||
<label>Warning Level</label>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">No warning</option>
|
||||
<option value="1">Warning Level 1</option>
|
||||
<option value="2">Warning Level 2</option>
|
||||
<option value="3">Warning Level 3</option>
|
||||
<option value="4">Decade Flooding</option>
|
||||
<option value="5">Century Flooding</option>
|
||||
<option value="6">Extreme Flooding</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.pegelonline.internal.handler;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelGroupUID;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandlerCallback;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.TimeSeries;
|
||||
import org.openhab.core.types.TimeSeries.Policy;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link CallbackMock} is a helper for unit tests to receive callbacks
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CallbackMock implements ThingHandlerCallback {
|
||||
private Map<String, State> stateMap = new HashMap<>();
|
||||
private @Nullable ThingStatusInfo thingStatus;
|
||||
|
||||
public @Nullable ThingStatusInfo getThingStatus() {
|
||||
synchronized (this) {
|
||||
while (thingStatus == null) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return thingStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateUpdated(ChannelUID channelUID, State state) {
|
||||
stateMap.put(channelUID.getAsString(), state);
|
||||
}
|
||||
|
||||
public State getState(String channelUID) {
|
||||
State val = stateMap.get(channelUID);
|
||||
if (val == null) {
|
||||
return UnDefType.UNDEF;
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendTimeSeries(ChannelUID channelUID, TimeSeries timeSeries) {
|
||||
}
|
||||
|
||||
public TimeSeries getTimeSeries(String cuid) {
|
||||
return new TimeSeries(Policy.REPLACE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void statusUpdated(Thing thing, ThingStatusInfo thingStatus) {
|
||||
synchronized (this) {
|
||||
this.thingStatus = thingStatus;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void thingUpdated(Thing thing) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfigurationParameters(Thing thing, Map<String, Object> configurationParameters) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfigurationParameters(Channel channel, Map<String, Object> configurationParameters) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConfigDescription getConfigDescription(ChannelTypeUID channelTypeUID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConfigDescription getConfigDescription(ThingTypeUID thingTypeUID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configurationUpdated(Thing thing) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrateThingType(Thing thing, ThingTypeUID thingTypeUID, Configuration configuration) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelTriggered(Thing thing, ChannelUID channelUID, String event) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelBuilder createChannelBuilder(ChannelUID channelUID, ChannelTypeUID channelTypeUID) {
|
||||
return ChannelBuilder.create(channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelBuilder editChannel(Thing thing, ChannelUID channelUID) {
|
||||
return ChannelBuilder.create(channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChannelBuilder> createChannelBuilders(ChannelGroupUID channelGroupUID,
|
||||
ChannelGroupTypeUID channelGroupTypeUID) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChannelLinked(ChannelUID channelUID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Bridge getBridge(ThingUID bridgeUID) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,375 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.pegelonline.internal.handler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.openhab.binding.pegelonline.internal.PegelOnlineBindingConstants.*;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.pegelonline.internal.config.PegelOnlineConfiguration;
|
||||
import org.openhab.binding.pegelonline.internal.dto.Measure;
|
||||
import org.openhab.binding.pegelonline.internal.dto.Station;
|
||||
import org.openhab.binding.pegelonline.internal.util.FileReader;
|
||||
import org.openhab.binding.pegelonline.internal.utils.Utils;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.internal.ThingImpl;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link PegelTest} Test helper utils
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class PegelTest {
|
||||
public static final String TEST_STATION_UUID = "1ebd0f94-cc06-445c-8e73-43fe2b8c72dc";
|
||||
|
||||
@Test
|
||||
void testConfigurationValidations() {
|
||||
PegelOnlineConfiguration config = new PegelOnlineConfiguration();
|
||||
assertFalse(config.uuidCheck(), config.uuid);
|
||||
config.uuid = "abc@";
|
||||
assertFalse(config.uuidCheck(), config.uuid);
|
||||
config.uuid = "abc d";
|
||||
assertFalse(config.uuidCheck(), config.uuid);
|
||||
config.uuid = "1234567a-abc1-efd9-cdf3-0123456789ab";
|
||||
assertTrue(config.uuidCheck(), config.uuid);
|
||||
assertTrue(config.warningCheck(), "Warnings");
|
||||
|
||||
String content = FileReader.readFileInString("src/test/resources/stations.json");
|
||||
Station[] stationArray = GSON.fromJson(content, Station[].class);
|
||||
assertNotNull(stationArray);
|
||||
for (Station station : stationArray) {
|
||||
config.uuid = station.uuid;
|
||||
assertTrue(config.uuidCheck(), config.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameConversion() {
|
||||
String stationName = "EIDER-SPERRWERK BP";
|
||||
String conversion = Utils.toTitleCase(stationName);
|
||||
assertEquals("Eider-Sperrwerk Bp", conversion, "Station Name");
|
||||
|
||||
String content = FileReader.readFileInString("src/test/resources/stations.json");
|
||||
Station[] stationArray = GSON.fromJson(content, Station[].class);
|
||||
assertNotNull(stationArray);
|
||||
for (Station station : stationArray) {
|
||||
assertTrue(Character.isUpperCase(Utils.toTitleCase(station.shortname).charAt(0)),
|
||||
"First Character Upper Case");
|
||||
assertTrue(Character.isUpperCase(Utils.toTitleCase(station.water.shortname).charAt(0)),
|
||||
"First Character Upper Case");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDistance() {
|
||||
// Frankfurt Main: 50.117461111005, 8.639069127891485
|
||||
String content = FileReader.readFileInString("src/test/resources/stations.json");
|
||||
Station[] stationArray = GSON.fromJson(content, Station[].class);
|
||||
assertNotNull(stationArray);
|
||||
int hitCounter = 0;
|
||||
for (Station station : stationArray) {
|
||||
double distance = Utils.calculateDistance(50.117461111005, 8.639069127891485, station.latitude,
|
||||
station.longitude);
|
||||
if (distance < 50) {
|
||||
hitCounter++;
|
||||
assertTrue(station.water.shortname.equals("RHEIN") || station.water.shortname.equals("MAIN"),
|
||||
"RHEIN or MAIN");
|
||||
}
|
||||
}
|
||||
assertEquals(11, hitCounter, "Meassurement Stations around FRA");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMeasureObject() {
|
||||
String content = FileReader.readFileInString("src/test/resources/measure.json");
|
||||
Measure measure = GSON.fromJson(content, Measure.class);
|
||||
if (measure != null) {
|
||||
assertEquals("2021-08-01T16:00:00+02:00", measure.timestamp, "Timestamp");
|
||||
assertEquals(238, measure.value, "Level");
|
||||
assertEquals(-1, measure.trend, "Trend");
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void test404Status() {
|
||||
String stationContent = FileReader.readFileInString("src/test/resources/stations.json");
|
||||
ContentResponse stationResponse = mock(ContentResponse.class);
|
||||
when(stationResponse.getStatus()).thenReturn(200);
|
||||
when(stationResponse.getContentAsString()).thenReturn(stationContent);
|
||||
|
||||
String content = "{}";
|
||||
ContentResponse measureResponse = mock(ContentResponse.class);
|
||||
when(measureResponse.getStatus()).thenReturn(404);
|
||||
when(measureResponse.getContentAsString()).thenReturn(content);
|
||||
|
||||
HttpClient httpClientMock = mock(HttpClient.class);
|
||||
try {
|
||||
when(httpClientMock.GET(STATIONS_URI + "/" + TEST_STATION_UUID + "/W/currentmeasurement.json"))
|
||||
.thenReturn(measureResponse);
|
||||
when(httpClientMock.GET(STATIONS_URI)).thenReturn(stationResponse);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
fail();
|
||||
}
|
||||
|
||||
CallbackMock callback = new CallbackMock();
|
||||
ThingImpl ti = new ThingImpl(new ThingTypeUID("pegelonline:station"), "test");
|
||||
PegelOnlineHandler handler = new PegelOnlineHandler(ti, httpClientMock);
|
||||
Configuration config = new Configuration();
|
||||
config.put("uuid", TEST_STATION_UUID);
|
||||
handler.setCallback(callback);
|
||||
handler.updateConfiguration(config);
|
||||
handler.initialize();
|
||||
handler.performMeasurement();
|
||||
ThingStatusInfo tsi = callback.getThingStatus();
|
||||
assertNotNull(tsi);
|
||||
assertEquals(ThingStatus.OFFLINE, tsi.getStatus(), "Status");
|
||||
assertEquals(ThingStatusDetail.CONFIGURATION_ERROR, tsi.getStatusDetail(), "Detail");
|
||||
String description = tsi.getDescription();
|
||||
assertNotNull(description);
|
||||
assertEquals("@text/pegelonline.handler.status.uuid-not-found [\"" + TEST_STATION_UUID + "\"]", description,
|
||||
"Description");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWrongContent() {
|
||||
String stationContent = FileReader.readFileInString("src/test/resources/stations.json");
|
||||
ContentResponse stationResponse = mock(ContentResponse.class);
|
||||
when(stationResponse.getStatus()).thenReturn(200);
|
||||
when(stationResponse.getContentAsString()).thenReturn(stationContent);
|
||||
|
||||
String content = "{}";
|
||||
ContentResponse measureResponse = mock(ContentResponse.class);
|
||||
when(measureResponse.getStatus()).thenReturn(200);
|
||||
when(measureResponse.getContentAsString()).thenReturn(content);
|
||||
|
||||
HttpClient httpClientMock = mock(HttpClient.class);
|
||||
try {
|
||||
when(httpClientMock.GET(STATIONS_URI + "/" + TEST_STATION_UUID + "/W/currentmeasurement.json"))
|
||||
.thenReturn(measureResponse);
|
||||
when(httpClientMock.GET(STATIONS_URI)).thenReturn(stationResponse);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
fail();
|
||||
}
|
||||
|
||||
CallbackMock callback = new CallbackMock();
|
||||
ThingImpl ti = new ThingImpl(new ThingTypeUID("pegelonline:station"), "test");
|
||||
PegelOnlineHandler handler = new PegelOnlineHandler(ti, httpClientMock);
|
||||
Configuration config = new Configuration();
|
||||
config.put("uuid", TEST_STATION_UUID);
|
||||
handler.setCallback(callback);
|
||||
handler.updateConfiguration(config);
|
||||
handler.initialize();
|
||||
handler.performMeasurement();
|
||||
ThingStatusInfo tsi = callback.getThingStatus();
|
||||
assertNotNull(tsi);
|
||||
assertEquals(ThingStatus.OFFLINE, tsi.getStatus(), "Status");
|
||||
assertEquals(ThingStatusDetail.COMMUNICATION_ERROR, tsi.getStatusDetail(), "Detail");
|
||||
String description = tsi.getDescription();
|
||||
assertNotNull(description);
|
||||
assertEquals("@text/pegelonline.handler.status.json-error [\"{}\"]", description, "Description");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongConfiguration() {
|
||||
CallbackMock callback = new CallbackMock();
|
||||
PegelOnlineHandler handler = getConfiguredHandler(callback, 99);
|
||||
|
||||
Configuration config = new Configuration();
|
||||
config.put("uuid", " ");
|
||||
handler.updateConfiguration(new Configuration(config));
|
||||
handler.initialize();
|
||||
|
||||
ThingStatusInfo tsi = callback.getThingStatus();
|
||||
assertNotNull(tsi);
|
||||
assertEquals(ThingStatus.OFFLINE, tsi.getStatus(), "Status");
|
||||
assertEquals(ThingStatusDetail.CONFIGURATION_ERROR, tsi.getStatusDetail(), "Detail");
|
||||
String description = tsi.getDescription();
|
||||
assertNotNull(description);
|
||||
assertEquals("@text/pegelonline.handler.status.uuid [\" \"]", description, "Description");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInconsistentLevels() {
|
||||
CallbackMock callback = new CallbackMock();
|
||||
PegelOnlineHandler handler = getConfiguredHandler(callback, 99);
|
||||
|
||||
Configuration config = new Configuration();
|
||||
config.put("uuid", TEST_STATION_UUID);
|
||||
config.put("warningLevel1", 100);
|
||||
config.put("warningLevel2", 200);
|
||||
config.put("warningLevel3", 150);
|
||||
handler.updateConfiguration(config);
|
||||
handler.initialize();
|
||||
|
||||
ThingStatusInfo tsi = callback.getThingStatus();
|
||||
assertNotNull(tsi);
|
||||
assertEquals(ThingStatus.OFFLINE, tsi.getStatus(), "Status");
|
||||
assertEquals(ThingStatusDetail.CONFIGURATION_ERROR, tsi.getStatusDetail(), "Detail");
|
||||
String description = tsi.getDescription();
|
||||
assertNotNull(description);
|
||||
assertEquals("@text/pegelonline.handler.status.warning", description, "Description");
|
||||
|
||||
handler.dispose();
|
||||
config = new Configuration();
|
||||
config.put("uuid", TEST_STATION_UUID);
|
||||
config.put("warningLevel1", 100);
|
||||
config.put("warningLevel2", 200);
|
||||
config.put("warningLevel3", 300);
|
||||
config.put("hqExtreme", 600);
|
||||
handler.updateConfiguration(new Configuration(config));
|
||||
handler.initialize();
|
||||
|
||||
tsi = callback.getThingStatus();
|
||||
assertNotNull(tsi);
|
||||
assertEquals(ThingStatus.UNKNOWN, tsi.getStatus(), "Status");
|
||||
assertEquals(ThingStatusDetail.NONE, tsi.getStatusDetail(), "Detail");
|
||||
description = tsi.getDescription();
|
||||
assertNotNull(description);
|
||||
assertEquals("@text/pegelonline.handler.status.wait-feedback", description, "Description");
|
||||
|
||||
handler.dispose();
|
||||
config = new Configuration();
|
||||
config.put("uuid", TEST_STATION_UUID);
|
||||
config.put("warningLevel1", 100);
|
||||
config.put("warningLevel2", 200);
|
||||
config.put("warningLevel3", 300);
|
||||
config.put("hq10", 100);
|
||||
config.put("hq100", 200);
|
||||
config.put("hqExtreme", 150);
|
||||
handler.updateConfiguration(new Configuration(config));
|
||||
handler.initialize();
|
||||
|
||||
tsi = callback.getThingStatus();
|
||||
assertNotNull(tsi);
|
||||
assertEquals(ThingStatus.OFFLINE, tsi.getStatus(), "Status");
|
||||
assertEquals(ThingStatusDetail.CONFIGURATION_ERROR, tsi.getStatusDetail(), "Detail");
|
||||
description = tsi.getDescription();
|
||||
assertNotNull(description);
|
||||
assertEquals("@text/pegelonline.handler.status.warning", description, "Description");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongResponse() {
|
||||
String measureContent = "{}";
|
||||
ContentResponse measureResponse = mock(ContentResponse.class);
|
||||
when(measureResponse.getStatus()).thenReturn(500);
|
||||
when(measureResponse.getContentAsString()).thenReturn(measureContent);
|
||||
HttpClient httpClientMock = mock(HttpClient.class);
|
||||
try {
|
||||
when(httpClientMock.GET(STATIONS_URI + "/" + TEST_STATION_UUID + "/W/currentmeasurement.json"))
|
||||
.thenReturn(measureResponse);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
fail();
|
||||
}
|
||||
|
||||
CallbackMock callback = new CallbackMock();
|
||||
ThingImpl ti = new ThingImpl(new ThingTypeUID("pegelonline:station"), "test");
|
||||
PegelOnlineHandler handler = new PegelOnlineHandler(ti, httpClientMock);
|
||||
Configuration config = new Configuration();
|
||||
config.put("uuid", TEST_STATION_UUID);
|
||||
handler.setCallback(callback);
|
||||
handler.updateConfiguration(config);
|
||||
handler.initialize();
|
||||
handler.performMeasurement();
|
||||
ThingStatusInfo tsi = callback.getThingStatus();
|
||||
assertNotNull(tsi);
|
||||
assertEquals(ThingStatus.OFFLINE, tsi.getStatus(), "Status");
|
||||
assertEquals(ThingStatusDetail.COMMUNICATION_ERROR, tsi.getStatusDetail(), "Detail");
|
||||
String description = tsi.getDescription();
|
||||
assertNotNull(description);
|
||||
assertEquals("@text/pegelonline.handler.status.http-status [\"500\"]", description, "Description");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWarnings() {
|
||||
CallbackMock callback = new CallbackMock();
|
||||
PegelOnlineHandler handler = getConfiguredHandler(callback, 99);
|
||||
handler.initialize();
|
||||
handler.performMeasurement();
|
||||
State state = callback.getState("pegelonline:station:test:warning");
|
||||
assertTrue(state instanceof DecimalType);
|
||||
assertEquals(NO_WARNING, ((DecimalType) state).intValue(), "No warning");
|
||||
|
||||
handler = getConfiguredHandler(callback, 100);
|
||||
handler.initialize();
|
||||
handler.performMeasurement();
|
||||
state = callback.getState("pegelonline:station:test:warning");
|
||||
assertTrue(state instanceof DecimalType);
|
||||
assertEquals(WARN_LEVEL_1, ((DecimalType) state).intValue(), "Warn Level 1");
|
||||
|
||||
handler = getConfiguredHandler(callback, 299);
|
||||
handler.initialize();
|
||||
handler.performMeasurement();
|
||||
state = callback.getState("pegelonline:station:test:warning");
|
||||
assertTrue(state instanceof DecimalType);
|
||||
assertEquals(WARN_LEVEL_2, ((DecimalType) state).intValue(), "Warn Level 2");
|
||||
|
||||
handler = getConfiguredHandler(callback, 1000);
|
||||
handler.initialize();
|
||||
handler.performMeasurement();
|
||||
state = callback.getState("pegelonline:station:test:warning");
|
||||
assertTrue(state instanceof DecimalType);
|
||||
assertEquals(HQ_EXTREME, ((DecimalType) state).intValue(), "HQ extreme");
|
||||
}
|
||||
|
||||
private PegelOnlineHandler getConfiguredHandler(CallbackMock callback, int levelSimulation) {
|
||||
String stationContent = FileReader.readFileInString("src/test/resources/stations.json");
|
||||
ContentResponse stationResponse = mock(ContentResponse.class);
|
||||
when(stationResponse.getStatus()).thenReturn(200);
|
||||
when(stationResponse.getContentAsString()).thenReturn(stationContent);
|
||||
|
||||
String measureContent = "{ \"timestamp\": \"2021-08-01T16:00:00+02:00\", \"value\": " + levelSimulation
|
||||
+ ", \"trend\": -1}";
|
||||
ContentResponse measureResponse = mock(ContentResponse.class);
|
||||
when(measureResponse.getStatus()).thenReturn(200);
|
||||
when(measureResponse.getContentAsString()).thenReturn(measureContent);
|
||||
HttpClient httpClientMock = mock(HttpClient.class);
|
||||
try {
|
||||
when(httpClientMock.GET(STATIONS_URI + "/" + TEST_STATION_UUID + "/W/currentmeasurement.json"))
|
||||
.thenReturn(measureResponse);
|
||||
when(httpClientMock.GET(STATIONS_URI)).thenReturn(stationResponse);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
fail();
|
||||
}
|
||||
|
||||
ThingImpl ti = new ThingImpl(new ThingTypeUID("pegelonline:station"), "test");
|
||||
PegelOnlineHandler handler = new PegelOnlineHandler(ti, httpClientMock);
|
||||
Configuration config = new Configuration();
|
||||
config.put("uuid", TEST_STATION_UUID);
|
||||
config.put("warningLevel1", 100);
|
||||
config.put("warningLevel2", 200);
|
||||
config.put("warningLevel3", 300);
|
||||
config.put("hq10", 400);
|
||||
config.put("hq100", 500);
|
||||
config.put("hqExtreme", 600);
|
||||
handler.setCallback(callback);
|
||||
handler.updateConfiguration(config);
|
||||
return handler;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.pegelonline.internal.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.pegelonline.internal.PegelOnlineBindingConstants;
|
||||
|
||||
/**
|
||||
* The {@link FileReader} Helper Util to read test resource files
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FileReader {
|
||||
|
||||
public static String readFileInString(String filename) {
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filename), "UTF-8"));) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
String sCurrentLine;
|
||||
|
||||
while ((sCurrentLine = br.readLine()) != null) {
|
||||
buf.append(sCurrentLine);
|
||||
}
|
||||
return buf.toString();
|
||||
} catch (IOException e) {
|
||||
// fail if file cannot be read
|
||||
assertTrue(false, e.getMessage());
|
||||
}
|
||||
return PegelOnlineBindingConstants.UNKNOWN;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"timestamp": "2021-08-01T16:00:00+02:00",
|
||||
"value": 238.0,
|
||||
"trend": -1,
|
||||
"stateMnwMhw": "normal",
|
||||
"stateNswHsw": "unknown"
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
[
|
||||
{
|
||||
"uuid": "4e7a6cfa-7548-4f7f-a97a-eb0694881003",
|
||||
"number": "25830056",
|
||||
"shortname": "Marburg",
|
||||
"longname": "MARBURG",
|
||||
"km": -38.7,
|
||||
"agency": "REGIERUNGSPRÄSIDIUM GIESSEN ABTEILUNG STAATLICHES UMWELTAMT MARBURG",
|
||||
"longitude": 8.764488839485487,
|
||||
"latitude": 50.798715477809225,
|
||||
"water": {
|
||||
"shortname": "LAHN",
|
||||
"longname": "LAHN"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uuid": "4b386a6a-996e-4a4a-a440-15d6b40226d4",
|
||||
"number": "25800100",
|
||||
"shortname": "GIESSEN KLÄRWERK",
|
||||
"longname": "GIESSEN KLÄRWERK",
|
||||
"km": -3.21,
|
||||
"agency": "WSA MOSEL-SAAR-LAHN",
|
||||
"longitude": 8.64860169166119,
|
||||
"latitude": 50.575037651225514,
|
||||
"water": {
|
||||
"shortname": "LAHN",
|
||||
"longname": "LAHN"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uuid": "32807065-b887-49f0-935a-80033e5f3cb0",
|
||||
"number": "25800200",
|
||||
"shortname": "LEUN NEU",
|
||||
"longname": "LEUN NEU",
|
||||
"km": 25.1,
|
||||
"agency": "WSA MOSEL-SAAR-LAHN",
|
||||
"longitude": 8.355230130810975,
|
||||
"latitude": 50.545120232764674,
|
||||
"water": {
|
||||
"shortname": "LAHN",
|
||||
"longname": "LAHN"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uuid": "89038b42-8181-48df-a0cd-2ca3913f2d68",
|
||||
"number": "25800440",
|
||||
"shortname": "LIMBURG SCHLEUSE UP",
|
||||
"longname": "LIMBURG SCHLEUSE UP",
|
||||
"km": 76.611,
|
||||
"agency": "WSA MOSEL-SAAR-LAHN",
|
||||
"longitude": 8.065188851061134,
|
||||
"latitude": 50.39151276997554,
|
||||
"water": {
|
||||
"shortname": "LAHN",
|
||||
"longname": "LAHN"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uuid": "eadedeb6-c31e-483f-b6c4-ca0153359ad7",
|
||||
"number": "25800500",
|
||||
"shortname": "DIEZ HAFEN",
|
||||
"longname": "DIEZ HAFEN",
|
||||
"km": 83.7,
|
||||
"agency": "WSA MOSEL-SAAR-LAHN",
|
||||
"longitude": 8.005066992072132,
|
||||
"latitude": 50.3723880903084,
|
||||
"water": {
|
||||
"shortname": "LAHN",
|
||||
"longname": "LAHN"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uuid": "64f735fd-88b6-42ea-9cdd-dc18d3806c34",
|
||||
"number": "25800600",
|
||||
"shortname": "KALKOFEN NEU",
|
||||
"longname": "KALKOFEN NEU",
|
||||
"km": 106.4,
|
||||
"agency": "WSA MOSEL-SAAR-LAHN",
|
||||
"longitude": 7.8898156192725235,
|
||||
"latitude": 50.31783177830708,
|
||||
"water": {
|
||||
"shortname": "LAHN",
|
||||
"longname": "LAHN"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uuid": "6b6b31e2-e5c7-4c85-8405-b8d0b6e158c4",
|
||||
"number": "25800800",
|
||||
"shortname": "LAHNSTEIN SCHLEUSE UP",
|
||||
"longname": "LAHNSTEIN SCHLEUSE UP",
|
||||
"km": 135.986,
|
||||
"agency": "WSA MOSEL-SAAR-LAHN",
|
||||
"longitude": 7.612956624441373,
|
||||
"latitude": 50.30803174924558,
|
||||
"water": {
|
||||
"shortname": "LAHN",
|
||||
"longname": "LAHN"
|
||||
}
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load Diff
@ -312,6 +312,7 @@
|
||||
<module>org.openhab.binding.orvibo</module>
|
||||
<module>org.openhab.binding.panasonicbdp</module>
|
||||
<module>org.openhab.binding.paradoxalarm</module>
|
||||
<module>org.openhab.binding.pegelonline</module>
|
||||
<module>org.openhab.binding.pentair</module>
|
||||
<module>org.openhab.binding.phc</module>
|
||||
<module>org.openhab.binding.pilight</module>
|
||||
|
Loading…
Reference in New Issue
Block a user