added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.weatherunderground/.classpath
Normal file
32
bundles/org.openhab.binding.weatherunderground/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.weatherunderground/.project
Normal file
23
bundles/org.openhab.binding.weatherunderground/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.weatherunderground</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
26
bundles/org.openhab.binding.weatherunderground/NOTICE
Normal file
26
bundles/org.openhab.binding.weatherunderground/NOTICE
Normal file
@@ -0,0 +1,26 @@
|
||||
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
|
||||
|
||||
== Third Party Dependencies
|
||||
|
||||
This bundle contains functionality, which references external Internet services that are
|
||||
offered under their respective terms of use. Please read these carefully before using this bundle.
|
||||
|
||||
=== Weather Underground API
|
||||
|
||||
The Weather Underground API at https://www.wunderground.com/weather/api/ is an Internet service
|
||||
offered by The Weather Underground, LLC (WUL) that provides weather information for any location worldwide.
|
||||
This service is free of charge but there is a daily limit and minute rate limit to the number of requests
|
||||
that can be made to the API for free. It requires attribution, has rate limits and their
|
||||
Terms of Use at https://www.wunderground.com/weather/api/d/terms.html have to be accepted.
|
||||
169
bundles/org.openhab.binding.weatherunderground/README.md
Normal file
169
bundles/org.openhab.binding.weatherunderground/README.md
Normal file
@@ -0,0 +1,169 @@
|
||||
---
|
||||
layout: documentation
|
||||
---
|
||||
|
||||
{% include base.html %}
|
||||
|
||||
# WeatherUnderground Binding
|
||||
|
||||
This binding uses the [Weather Underground service](https://www.wunderground.com/weather/api/) for providing weather information for any location worldwide.
|
||||
|
||||
The Weather Underground API is provided by The Weather Underground, LLC (WUL) free of charge but there is a daily limit and minute rate limit to the number of requests that can be made to the API for free (until 2018/12/31).
|
||||
WUL will monitor your daily usage of the API to determine if you have exceeded the free-use threshold by using an API key. You may exceed this threshold only if you are or become a fee paying subscriber.
|
||||
By using this binding, you confirm that you agree with the [Weather Underground API terms and conditions of use](https://www.wunderground.com/weather/api/d/terms.html).
|
||||
|
||||
To use this binding, you first need to [register and get your API key](https://www.wunderground.com/weather/api/d/pricing.html) .
|
||||
|
||||
## Supported Things
|
||||
|
||||
There are exactly two supported thing types. The first one is the bridge thing, which represents the connection to the Weather Underground service through the API key. It has the id `bridge`. The second one is the weather thing, which represents the weather information for an observed location. It has the id `weather`. Each `weather` thing uses a `bridge` thing ; it cannot be set online if no `bridge` thing is defined.
|
||||
|
||||
## Discovery
|
||||
|
||||
If a system location is set, "Local Weather" will be automatically discovered for this location.
|
||||
|
||||
If the system location is changed, the background discovery updates the configuration of "Local Weather" automatically.
|
||||
|
||||
If a bridge is correctly configured, the discovered thing will automatically go online.
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
The binding has no configuration options, all configuration is done at Thing and Channel levels.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The bridge only has one configuration parameter:
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|------------------------------------------------------------------------- |
|
||||
| apikey | API key to access the Weather Underground service. Mandatory. |
|
||||
|
||||
The thing has a few configuration parameters:
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|------------------------------------------------------------------------- |
|
||||
| location | Location to be considered by the Weather Underground service. Mandatory. |
|
||||
| language | Language to be used by the Weather Underground service. Optional, the default is to use the language from the system locale. |
|
||||
| refresh | Refresh interval in minutes. Optional, the default value is 30 minutes and the minimum value is 5 minutes. |
|
||||
|
||||
For the location parameter, different syntaxes are possible:
|
||||
|
||||
| Syntax | Example |
|
||||
|-------------------------|----------------- |
|
||||
| US state/city | CA/San_Francisco |
|
||||
| US zipcode | 60290 |
|
||||
| country/city | Australia/Sydney |
|
||||
| latitude,longitude | 37.8,-122.4 |
|
||||
| airport code | KJFK |
|
||||
| PWS id | pws:KCASANFR70 |
|
||||
|
||||
It can happen that the service is not able to determine the station to use, for example when you select as location a city in which several stations are registered. In this case, the thing configuration will fail because the service will not return the data expected by the binding. The best solution in this case is to use as location latitude and longitude, the service will automatically select a station from this position.
|
||||
|
||||
For the language parameter Weather Underground uses a special set of language codes which are different from ISO 639-1 standard, for example for German use `DL` or Swedish use `SW`. See [Weather Underground language support documentation](https://www.wunderground.com/weather/api/d/docs?d=language-support) for a detailed list.
|
||||
|
||||
## Channels
|
||||
|
||||
The weather information that is retrieved is available as these channels:
|
||||
|
||||
| Channel Group ID | Channel ID | Item Type | Description |
|
||||
|------------------|------------|--------------|-------------------------|
|
||||
| Current | location | String | Weather observation location |
|
||||
| Current | stationId | String | Weather station identifier |
|
||||
| Current | observationTime | DateTime | Observation date and time |
|
||||
| Current | conditions | String | Weather conditions |
|
||||
| Current | temperature | Number:Temperature | Temperature |
|
||||
| Current | relativeHumidity | Number:Dimensionless | Relative humidity |
|
||||
| Current | windDirection | String | Wind direction |
|
||||
| Current | windDirectionDegrees | Number:Angle | Wind direction as an angle |
|
||||
| Current | windSpeed | Number:Speed | Wind speed |
|
||||
| Current | windGust | Number:Speed | Wind gust |
|
||||
| Current | pressure | Number:Pressure | Pressure |
|
||||
| Current | pressureTrend | String | Pressure trend ("up", "stable" or "down") | |
|
||||
| Current | dewPoint | Number:Temperature | Dew Point temperature |
|
||||
| Current | heatIndex | Number:Temperature | Heat Index |
|
||||
| Current | windChill | Number:Temperature | Wind chill temperature |
|
||||
| Current | feelingTemperature | Number:Temperature | Feeling temperature |
|
||||
| Current | visibility | Number:Length | Visibility |
|
||||
| Current | solarRadiation | Number:Intensity | Solar radiation |
|
||||
| Current | UVIndex | Number | UV Index |
|
||||
| Current | precipitationDay | Number:Length | Rain fall during the day |
|
||||
| Current | precipitationHour | Number:Length | Rain fall during the last hour |
|
||||
| Current | icon | Image | Icon representing the weather current conditions |
|
||||
| Current | iconKey | String | Key used in the icon URL |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | forecastTime | DateTime | Forecast date and time |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | conditions | String | Weather forecast conditions |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | minTemperature | Number:Temperature | Minimum temperature |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | maxTemperature | Number:Temperature | Maximum temperature |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | relativeHumidity | Number:Dimensionless | Relative humidity |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | probaPrecipitation | Number:Dimensionless | Probability of precipitation |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | precipitationDay | Number:Length | Rain fall |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | snow | Number:Length | Snow fall |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | maxWindDirection | String | Maximum wind direction | |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | maxWindDirectionDegrees | Number:Angle | Maximum wind direction as an angle | |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | maxWindSpeed | Number:Speed | Maximum wind speed |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | averageWindDirection | String | Average wind direction | |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | averageWindDirectionDegrees | Number:Angle | Average wind direction as an angle |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | averageWindSpeed | Number:Speed | Average wind speed |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | icon | Image | Icon representing the weather forecast conditions |
|
||||
| forecastToday forecastTomorrow forecastDay2 ... forecastDay9 | iconKey | String | Key used in the icon URL |
|
||||
|
||||
|
||||
## Full Example
|
||||
|
||||
demo.things:
|
||||
|
||||
```
|
||||
Bridge weatherunderground:bridge:api "API" [ apikey="XXXXXXXXXXXX" ] {
|
||||
Thing weather paris "Météo Paris" [ location="France/Paris", language="FR", refresh=15 ]
|
||||
}
|
||||
```
|
||||
|
||||
demo.items:
|
||||
|
||||
```
|
||||
String Conditions "Conditions [%s]" {channel="weatherunderground:weather:api:paris:current#conditions"}
|
||||
Image Icon "Icon" {channel="weatherunderground:weather:api:paris:current#icon"}
|
||||
String IconKey "Icon key [%s]" {channel="weatherunderground:weather:api:paris:current#iconKey"}
|
||||
DateTime ObservationTime "Observation time [%1$tH:%1$tM]" <clock> {channel="weatherunderground:weather:api:paris:current#observationTime"}
|
||||
String ObservationLocation "Location [%s]" {channel="weatherunderground:weather:api:paris:current#location"}
|
||||
String Station "Station [%s]" {channel="weatherunderground:weather:api:paris:current#stationId"}
|
||||
|
||||
Number:Temperature Temperature "Current temperature [%.1f %unit%]" <temperature> {channel="weatherunderground:weather:api:paris:current#temperature"}
|
||||
Number:Temperature FeelTemp "Feeling temperature [%.1f %unit%]" <temperature> {channel="weatherunderground:weather:api:paris:current#feelingTemperature"}
|
||||
|
||||
Number:Dimensionless Humidity "Humidity [%d %%]" <humidity> {channel="weatherunderground:weather:api:paris:current#relativeHumidity"}
|
||||
Number:Pressure Pressure "Pressure [%.0f %unit%]" {channel="weatherunderground:weather:api:paris:current#pressure"}
|
||||
String PressureTrend "Pressure trend [%s]" {channel="weatherunderground:weather:api:paris:current#pressureTrend"}
|
||||
|
||||
Number:Length RainD "Rain [%.1f &unit%]" <rain> {channel="weatherunderground:weather:api:paris:current#precipitationDay"}
|
||||
Number:Length RainH "Rain [%.1f %unit%/h]" <rain> {channel="weatherunderground:weather:api:paris:current#precipitationHour"}
|
||||
|
||||
String WindDirection "Wind direction [%s]" <wind> {channel="weatherunderground:weather:api:paris:current#windDirection"}
|
||||
Number:Angle WindDirection2 "Wind direction [%.0f %unit%]" <wind> {channel="weatherunderground:weather:api:paris:current#windDirectionDegrees"}
|
||||
Number:Speed WindSpeed "Wind speed [%.1f %unit%]" <wind> {channel="weatherunderground:weather:api:paris:current#windSpeed"}
|
||||
Number:Speed WindGust "Wind gust [%.1f %unit%]" <wind> {channel="weatherunderground:weather:api:paris:current#windGust"}
|
||||
|
||||
Number:Temperature DewPoint "Dew Point [%.1f %unit%]" <temperature> {channel="weatherunderground:weather:api:paris:current#dewPoint"}
|
||||
Number:Temperature HeatIndex "Heat Index [%.1f %unit%]" <temperature> {channel="weatherunderground:weather:api:paris:current#heatIndex"}
|
||||
Number:Temperature WindChill "Wind Chill [%.1f %unit%]" <temperature> {channel="weatherunderground:weather:api:paris:current#windChill"}
|
||||
Number:Length Visibility "Visibility [%.1f %unit%]" {channel="weatherunderground:weather:api:paris:current#visibility"}
|
||||
Number:Intensity SolarRadiation "Solar Radiation [%.2f %unit%]" {channel="weatherunderground:weather:api:paris:current#solarRadiation"}
|
||||
Number UV "UV Index [%.1f]" {channel="weatherunderground:weather:api:paris:current#UVIndex"}
|
||||
|
||||
DateTime ForecastTime "Forecast time [%1$tH:%1$tM]" <clock> {channel="weatherunderground:weather:api:paris:forecastToday#forecastTime"}
|
||||
String ForecastCondition "Forecast conditions [%s]" {channel="weatherunderground:weather:api:paris:forecastToday#conditions"}
|
||||
Image ForecastIcon "Forecast icon" {channel="weatherunderground:weather:api:paris:forecastToday#icon"}
|
||||
String ForecastIconKey "Forecast icon key [%s]" {channel="weatherunderground:weather:api:paris:forecastToday#iconKey"}
|
||||
Number:Temperature ForecastTempMin "Forecast min temp [%.1f %unit%]" <temperature> {channel="weatherunderground:weather:api:paris:forecastToday#minTemperature"}
|
||||
Number:Temperature ForecastTempMax "Forecast max temp [%.1f %unit%]" <temperature> {channel="weatherunderground:weather:api:paris:forecastToday#maxTemperature"}
|
||||
Number:Dimensionless ForecastHumidity "Forecast Humidity [%d %unit%]" <humidity> {channel="weatherunderground:weather:api:paris:forecastToday#relativeHumidity"}
|
||||
Number:Dimensionless ForecastProbaPrecip "Proba precip [%d %unit%]" <rain> {channel="weatherunderground:weather:api:paris:forecastToday#probaPrecipitation"}
|
||||
Number:Length ForecastRain "Rain [%.1f %unit%]" <rain> {channel="weatherunderground:weather:api:paris:forecastToday#precipitationDay"}
|
||||
Number:Length ForecastSnow "Snow [%.2f %unit%]" <rain> {channel="weatherunderground:weather:api:paris:forecastToday#snow"}
|
||||
String ForecastMaxWindDirection "Max wind direction [%s]" <wind> {channel="weatherunderground:weather:api:paris:forecastToday#maxWindDirection"}
|
||||
Number:Angle ForecastMaxWindDirection2 "Max wind direction [%.0f %unit%]" <wind> {channel="weatherunderground:weather:api:paris:forecastToday#maxWindDirectionDegrees"}
|
||||
Number:Speed ForecastMaxWindSpeed "Max wind speed [%.1f %unit%]" <wind> {channel="weatherunderground:weather:api:paris:forecastToday#maxWindSpeed"}
|
||||
String ForecastAvgWindDirection "Avg wind direction [%s]" <wind> {channel="weatherunderground:weather:api:paris:forecastToday#averageWindDirection"}
|
||||
Number:Angle ForecastAvgWindDirection2 "Avg wind direction [%.0f %unit%]" <wind> {channel="weatherunderground:weather:api:paris:forecastToday#averageWindDirectionDegrees"}
|
||||
Number:Speed ForecastAvgWindSpeed "Avg wind speed [%.1f %unit%]" <wind> {channel="weatherunderground:weather:api:paris:forecastToday#averageWindSpeed"}
|
||||
```
|
||||
17
bundles/org.openhab.binding.weatherunderground/pom.xml
Normal file
17
bundles/org.openhab.binding.weatherunderground/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.weatherunderground</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: WeatherUnderground Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.weatherunderground-${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-weatherunderground" description="WeatherUnderground Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.weatherunderground/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundBinding} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
* @author Theo Giovanna - Added a bridge for the API key
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WeatherUndergroundBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "weatherunderground";
|
||||
|
||||
public static final String LOCAL = "local";
|
||||
|
||||
// List all Thing Type UIDs, related to the WeatherUnderground Binding
|
||||
public static final ThingTypeUID THING_TYPE_WEATHER = new ThingTypeUID(BINDING_ID, "weather");
|
||||
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
|
||||
|
||||
public static final Set<ThingTypeUID> BRIDGE_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_BRIDGE);
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>(Arrays.asList(THING_TYPE_WEATHER));
|
||||
|
||||
// Channel configuration Properties
|
||||
public static final String PROPERTY_SOURCE_UNIT = "SourceUnit";
|
||||
|
||||
// Bridge config properties
|
||||
public static final String APIKEY = "apikey";
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal;
|
||||
|
||||
import static org.openhab.binding.weatherunderground.internal.WeatherUndergroundBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.weatherunderground.internal.discovery.WeatherUndergroundDiscoveryService;
|
||||
import org.openhab.binding.weatherunderground.internal.handler.WeatherUndergroundBridgeHandler;
|
||||
import org.openhab.binding.weatherunderground.internal.handler.WeatherUndergroundHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
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.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
* @author Theo Giovanna - Added a bridge for the API key
|
||||
* @author Laurent Garnier - Registration of the discovery service updated
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.weatherunderground")
|
||||
public class WeatherUndergroundHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
|
||||
.of(BRIDGE_THING_TYPES_UIDS, WeatherUndergroundBindingConstants.SUPPORTED_THING_TYPES_UIDS)
|
||||
.flatMap(x -> x.stream()).collect(Collectors.toSet());
|
||||
|
||||
private final Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
|
||||
private final LocaleProvider localeProvider;
|
||||
private final LocationProvider locationProvider;
|
||||
private final UnitProvider unitProvider;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
@Activate
|
||||
public WeatherUndergroundHandlerFactory(final @Reference LocaleProvider localeProvider,
|
||||
final @Reference LocationProvider locationProvider, final @Reference UnitProvider unitProvider,
|
||||
final @Reference TimeZoneProvider timeZoneProvider) {
|
||||
this.localeProvider = localeProvider;
|
||||
this.locationProvider = locationProvider;
|
||||
this.unitProvider = unitProvider;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(THING_TYPE_WEATHER)) {
|
||||
return new WeatherUndergroundHandler(thing, localeProvider, unitProvider, timeZoneProvider);
|
||||
}
|
||||
|
||||
if (thingTypeUID.equals(THING_TYPE_BRIDGE)) {
|
||||
WeatherUndergroundBridgeHandler handler = new WeatherUndergroundBridgeHandler((Bridge) thing);
|
||||
registerDiscoveryService(handler.getThing().getUID());
|
||||
return handler;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof WeatherUndergroundBridgeHandler) {
|
||||
unregisterDiscoveryService(thingHandler.getThing().getUID());
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void registerDiscoveryService(ThingUID bridgeUID) {
|
||||
WeatherUndergroundDiscoveryService discoveryService = new WeatherUndergroundDiscoveryService(bridgeUID,
|
||||
localeProvider, locationProvider);
|
||||
discoveryService.activate(null);
|
||||
discoveryServiceRegs.put(bridgeUID,
|
||||
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||
}
|
||||
|
||||
private synchronized void unregisterDiscoveryService(ThingUID bridgeUID) {
|
||||
ServiceRegistration<?> serviceReg = discoveryServiceRegs.remove(bridgeUID);
|
||||
if (serviceReg != null) {
|
||||
WeatherUndergroundDiscoveryService service = (WeatherUndergroundDiscoveryService) bundleContext
|
||||
.getService(serviceReg.getReference());
|
||||
serviceReg.unregister();
|
||||
if (service != null) {
|
||||
service.deactivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal.config;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundConfiguration} is the class used to match the
|
||||
* thing configuration.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
public class WeatherUndergroundConfiguration {
|
||||
|
||||
public static final String LOCATION = "location";
|
||||
public static final String LANGUAGE = "language";
|
||||
|
||||
public String location;
|
||||
public String language;
|
||||
public Integer refresh;
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.weatherunderground.internal.WeatherUndergroundBindingConstants.*;
|
||||
import static org.openhab.binding.weatherunderground.internal.config.WeatherUndergroundConfiguration.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.weatherunderground.internal.handler.WeatherUndergroundHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundDiscoveryService} creates things based on the configured location.
|
||||
*
|
||||
* @author Laurent Garnier - Initial Contribution
|
||||
* @author Laurent Garnier - Consider locale (language) when discovering a new thing
|
||||
*/
|
||||
public class WeatherUndergroundDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(WeatherUndergroundDiscoveryService.class);
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_WEATHER);
|
||||
private static final int DISCOVER_TIMEOUT_SECONDS = 2;
|
||||
private static final int LOCATION_CHANGED_CHECK_INTERVAL = 60;
|
||||
|
||||
private final LocaleProvider localeProvider;
|
||||
private final LocationProvider locationProvider;
|
||||
private ScheduledFuture<?> discoveryJob;
|
||||
private PointType previousLocation;
|
||||
private String previousLanguage;
|
||||
private String previousCountry;
|
||||
|
||||
private final ThingUID bridgeUID;
|
||||
|
||||
/**
|
||||
* Creates a WeatherUndergroundDiscoveryService with enabled autostart.
|
||||
*/
|
||||
|
||||
public WeatherUndergroundDiscoveryService(ThingUID bridgeUID, LocaleProvider localeProvider,
|
||||
LocationProvider locationProvider) {
|
||||
super(SUPPORTED_THING_TYPES, DISCOVER_TIMEOUT_SECONDS, true);
|
||||
this.bridgeUID = bridgeUID;
|
||||
this.localeProvider = localeProvider;
|
||||
this.locationProvider = locationProvider;
|
||||
}
|
||||
|
||||
/* We override this method to allow a call from the thing handler factory */
|
||||
@Override
|
||||
public void activate(@Nullable Map<@NonNull String, @Nullable Object> configProperties) {
|
||||
super.activate(configProperties);
|
||||
}
|
||||
|
||||
/* We override this method to allow a call from the thing handler factory */
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Starting Weather Underground discovery scan");
|
||||
PointType location = locationProvider.getLocation();
|
||||
if (location == null) {
|
||||
logger.debug("LocationProvider.getLocation() is not set -> Will not provide any discovery results");
|
||||
return;
|
||||
}
|
||||
createResults(location, localeProvider.getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
logger.debug("Starting Weather Underground device background discovery");
|
||||
if (discoveryJob == null || discoveryJob.isCancelled()) {
|
||||
discoveryJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||
PointType currentLocation = locationProvider.getLocation();
|
||||
String currentLanguage = localeProvider.getLocale().getLanguage();
|
||||
String currentCountry = localeProvider.getLocale().getCountry();
|
||||
if (currentLocation != null) {
|
||||
boolean update = false;
|
||||
if (!Objects.equals(currentLocation, previousLocation)) {
|
||||
logger.debug("Location has been changed from {} to {}: Creating new discovery result",
|
||||
previousLocation, currentLocation);
|
||||
update = true;
|
||||
} else if (!Objects.equals(currentLanguage, previousLanguage)) {
|
||||
logger.debug("Language has been changed from {} to {}: Creating new discovery result",
|
||||
previousLanguage, currentLanguage);
|
||||
update = true;
|
||||
} else if (!Objects.equals(currentCountry, previousCountry)) {
|
||||
logger.debug("Country has been changed from {} to {}: Creating new discovery result",
|
||||
previousCountry, currentCountry);
|
||||
update = true;
|
||||
}
|
||||
if (update) {
|
||||
createResults(currentLocation, localeProvider.getLocale());
|
||||
previousLocation = currentLocation;
|
||||
previousLanguage = currentLanguage;
|
||||
previousCountry = currentCountry;
|
||||
}
|
||||
}
|
||||
}, 0, LOCATION_CHANGED_CHECK_INTERVAL, TimeUnit.SECONDS);
|
||||
logger.debug("Scheduled Weather Underground location-changed job every {} seconds",
|
||||
LOCATION_CHANGED_CHECK_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
logger.debug("Stopping Weather Underground device background discovery");
|
||||
if (discoveryJob != null && !discoveryJob.isCancelled()) {
|
||||
discoveryJob.cancel(true);
|
||||
discoveryJob = null;
|
||||
logger.debug("Stopped Weather Underground device background discovery");
|
||||
}
|
||||
}
|
||||
|
||||
private void createResults(PointType location, Locale locale) {
|
||||
ThingUID localWeatherThing = new ThingUID(THING_TYPE_WEATHER, bridgeUID, LOCAL);
|
||||
Map<String, Object> properties = new HashMap<>(3);
|
||||
properties.put(LOCATION, String.format("%s,%s", location.getLatitude(), location.getLongitude()));
|
||||
String lang = WeatherUndergroundHandler.getCodeFromLanguage(locale);
|
||||
if (!lang.isEmpty()) {
|
||||
properties.put(LANGUAGE, lang);
|
||||
}
|
||||
thingDiscovered(DiscoveryResultBuilder.create(localWeatherThing).withLabel("Local Weather")
|
||||
.withProperties(properties).withBridge(bridgeUID).build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.weatherunderground.internal.WeatherUndergroundBindingConstants;
|
||||
import org.openhab.binding.weatherunderground.internal.json.WeatherUndergroundJsonData;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundBridgeHandler} is responsible for handling the
|
||||
* bridge things created to use the Weather Underground Service. This way, the
|
||||
* API key may be entered only once.
|
||||
*
|
||||
* @author Theo Giovanna - Initial Contribution
|
||||
* @author Laurent Garnier - refactor bridge/thing handling
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WeatherUndergroundBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(WeatherUndergroundBridgeHandler.class);
|
||||
private final Gson gson;
|
||||
private static final String URL = "http://api.wunderground.com/api/%APIKEY%/";
|
||||
public static final int FETCH_TIMEOUT_MS = 30000;
|
||||
|
||||
@Nullable
|
||||
private ScheduledFuture<?> controlApiKeyJob;
|
||||
|
||||
private String apikey = "";
|
||||
|
||||
public WeatherUndergroundBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
gson = new Gson();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing weatherunderground bridge handler.");
|
||||
Configuration config = getThing().getConfiguration();
|
||||
|
||||
// Check if an api key has been provided during the bridge creation
|
||||
Object configApiKey = config.get(WeatherUndergroundBindingConstants.APIKEY);
|
||||
if (configApiKey == null || !(configApiKey instanceof String) || ((String) configApiKey).trim().isEmpty()) {
|
||||
logger.debug("Setting thing '{}' to OFFLINE: Parameter 'apikey' must be configured.", getThing().getUID());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error-missing-apikey");
|
||||
} else {
|
||||
apikey = ((String) configApiKey).trim();
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
startControlApiKeyJob();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the job controlling the API key
|
||||
*/
|
||||
private void startControlApiKeyJob() {
|
||||
if (controlApiKeyJob == null || controlApiKeyJob.isCancelled()) {
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
WeatherUndergroundJsonData result = null;
|
||||
String errorDetail = null;
|
||||
String error = null;
|
||||
String statusDescr = null;
|
||||
boolean resultOk = false;
|
||||
|
||||
// Check if the provided api key is valid for use with the weatherunderground service
|
||||
try {
|
||||
String urlStr = URL.replace("%APIKEY%", getApikey());
|
||||
// Run the HTTP request and get the JSON response from Weather Underground
|
||||
String response = null;
|
||||
try {
|
||||
response = HttpUtil.executeUrl("GET", urlStr, FETCH_TIMEOUT_MS);
|
||||
logger.debug("apiResponse = {}", response);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Catch Illegal character in path at index XX: http://api.wunderground.com/...
|
||||
error = "Error creating URI";
|
||||
errorDetail = e.getMessage();
|
||||
statusDescr = "@text/offline.uri-error";
|
||||
}
|
||||
// Map the JSON response to an object
|
||||
result = gson.fromJson(response, WeatherUndergroundJsonData.class);
|
||||
if (result.getResponse() == null) {
|
||||
error = "Error in Weather Underground response";
|
||||
errorDetail = "missing response sub-object";
|
||||
statusDescr = "@text/offline.comm-error-response";
|
||||
} else if (result.getResponse().getErrorDescription() != null) {
|
||||
if ("keynotfound".equals(result.getResponse().getErrorType())) {
|
||||
error = "API key has to be fixed";
|
||||
errorDetail = result.getResponse().getErrorDescription();
|
||||
statusDescr = "@text/offline.comm-error-invalid-api-key";
|
||||
} else if ("invalidquery".equals(result.getResponse().getErrorType())) {
|
||||
// The API key provided is valid
|
||||
resultOk = true;
|
||||
} else {
|
||||
error = "Error in Weather Underground response";
|
||||
errorDetail = result.getResponse().getErrorDescription();
|
||||
statusDescr = "@text/offline.comm-error-response";
|
||||
}
|
||||
} else {
|
||||
resultOk = true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
error = "Error running Weather Underground request";
|
||||
errorDetail = e.getMessage();
|
||||
statusDescr = "@text/offline.comm-error-running-request";
|
||||
} catch (JsonSyntaxException e) {
|
||||
error = "Error parsing Weather Underground response";
|
||||
errorDetail = e.getMessage();
|
||||
statusDescr = "@text/offline.comm-error-parsing-response";
|
||||
}
|
||||
|
||||
// Update the thing status
|
||||
if (resultOk) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
logger.debug("Setting thing '{}' to OFFLINE: Error '{}': {}", getThing().getUID(), error,
|
||||
errorDetail);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, statusDescr);
|
||||
}
|
||||
}
|
||||
};
|
||||
controlApiKeyJob = scheduler.schedule(runnable, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing weatherunderground bridge handler.");
|
||||
|
||||
if (controlApiKeyJob != null && !controlApiKeyJob.isCancelled()) {
|
||||
controlApiKeyJob.cancel(true);
|
||||
controlApiKeyJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// not needed
|
||||
}
|
||||
|
||||
public String getApikey() {
|
||||
return apikey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,681 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal.handler;
|
||||
|
||||
import static org.openhab.core.library.unit.MetricPrefix.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.ZoneId;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.measure.Quantity;
|
||||
import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.weatherunderground.internal.config.WeatherUndergroundConfiguration;
|
||||
import org.openhab.binding.weatherunderground.internal.json.WeatherUndergroundJsonCurrent;
|
||||
import org.openhab.binding.weatherunderground.internal.json.WeatherUndergroundJsonData;
|
||||
import org.openhab.binding.weatherunderground.internal.json.WeatherUndergroundJsonForecast;
|
||||
import org.openhab.binding.weatherunderground.internal.json.WeatherUndergroundJsonForecastDay;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
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.types.StringType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
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.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundHandler} is responsible for handling the
|
||||
* weather things created to use the Weather Underground Service.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
* @author Theo Giovanna - Added a bridge for the API key
|
||||
* @author Laurent Garnier - refactor bridge/thing handling
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WeatherUndergroundHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(WeatherUndergroundHandler.class);
|
||||
|
||||
private static final int DEFAULT_REFRESH_PERIOD = 30;
|
||||
private static final String URL_QUERY = "http://api.wunderground.com/api/%APIKEY%/%FEATURES%/%SETTINGS%/q/%QUERY%.json";
|
||||
private static final String FEATURE_CONDITIONS = "conditions";
|
||||
private static final String FEATURE_FORECAST10DAY = "forecast10day";
|
||||
private static final String FEATURE_GEOLOOKUP = "geolookup";
|
||||
private static final Set<String> USUAL_FEATURES = Stream.of(FEATURE_CONDITIONS, FEATURE_FORECAST10DAY)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
private static final Map<String, @Nullable String> LANG_ISO_TO_WU_CODES = new HashMap<>();
|
||||
// Codes from https://www.wunderground.com/weather/api/d/docs?d=language-support
|
||||
static {
|
||||
LANG_ISO_TO_WU_CODES.put("AF", "AF");
|
||||
LANG_ISO_TO_WU_CODES.put("SQ", "AL");
|
||||
LANG_ISO_TO_WU_CODES.put("AR", "AR");
|
||||
LANG_ISO_TO_WU_CODES.put("HY", "HY");
|
||||
LANG_ISO_TO_WU_CODES.put("AZ", "AZ");
|
||||
LANG_ISO_TO_WU_CODES.put("EU", "EU");
|
||||
LANG_ISO_TO_WU_CODES.put("BE", "BY");
|
||||
LANG_ISO_TO_WU_CODES.put("BG", "BU");
|
||||
LANG_ISO_TO_WU_CODES.put("MY", "MY");
|
||||
LANG_ISO_TO_WU_CODES.put("CA", "CA");
|
||||
// Chinese - Simplified => CN
|
||||
LANG_ISO_TO_WU_CODES.put("ZH", "TW");
|
||||
LANG_ISO_TO_WU_CODES.put("HR", "CR");
|
||||
LANG_ISO_TO_WU_CODES.put("CS", "CZ");
|
||||
LANG_ISO_TO_WU_CODES.put("DA", "DK");
|
||||
LANG_ISO_TO_WU_CODES.put("DV", "DV");
|
||||
LANG_ISO_TO_WU_CODES.put("NL", "NL");
|
||||
LANG_ISO_TO_WU_CODES.put("EN", "EN");
|
||||
LANG_ISO_TO_WU_CODES.put("EO", "EO");
|
||||
LANG_ISO_TO_WU_CODES.put("ET", "ET");
|
||||
LANG_ISO_TO_WU_CODES.put("FA", "FA");
|
||||
LANG_ISO_TO_WU_CODES.put("FI", "FI");
|
||||
LANG_ISO_TO_WU_CODES.put("FR", "FR");
|
||||
LANG_ISO_TO_WU_CODES.put("GL", "GZ");
|
||||
LANG_ISO_TO_WU_CODES.put("DE", "DL");
|
||||
LANG_ISO_TO_WU_CODES.put("KA", "KA");
|
||||
LANG_ISO_TO_WU_CODES.put("EL", "GR");
|
||||
LANG_ISO_TO_WU_CODES.put("GU", "GU");
|
||||
LANG_ISO_TO_WU_CODES.put("HT", "HT");
|
||||
LANG_ISO_TO_WU_CODES.put("HE", "IL");
|
||||
LANG_ISO_TO_WU_CODES.put("HI", "HI");
|
||||
LANG_ISO_TO_WU_CODES.put("HU", "HU");
|
||||
LANG_ISO_TO_WU_CODES.put("IS", "IS");
|
||||
LANG_ISO_TO_WU_CODES.put("IO", "IO");
|
||||
LANG_ISO_TO_WU_CODES.put("ID", "ID");
|
||||
LANG_ISO_TO_WU_CODES.put("GA", "IR");
|
||||
LANG_ISO_TO_WU_CODES.put("IT", "IT");
|
||||
LANG_ISO_TO_WU_CODES.put("JA", "JP");
|
||||
LANG_ISO_TO_WU_CODES.put("JV", "JW");
|
||||
LANG_ISO_TO_WU_CODES.put("KM", "KM");
|
||||
LANG_ISO_TO_WU_CODES.put("KO", "KR");
|
||||
LANG_ISO_TO_WU_CODES.put("KU", "KU");
|
||||
LANG_ISO_TO_WU_CODES.put("LA", "LA");
|
||||
LANG_ISO_TO_WU_CODES.put("LV", "LV");
|
||||
LANG_ISO_TO_WU_CODES.put("LT", "LT");
|
||||
// Low German => ND
|
||||
LANG_ISO_TO_WU_CODES.put("MK", "MK");
|
||||
LANG_ISO_TO_WU_CODES.put("MT", "MT");
|
||||
// Mandinka => GM
|
||||
LANG_ISO_TO_WU_CODES.put("MI", "MI");
|
||||
LANG_ISO_TO_WU_CODES.put("MR", "MR");
|
||||
LANG_ISO_TO_WU_CODES.put("MN", "MN");
|
||||
LANG_ISO_TO_WU_CODES.put("NO", "NO");
|
||||
LANG_ISO_TO_WU_CODES.put("OC", "OC");
|
||||
LANG_ISO_TO_WU_CODES.put("PS", "PS");
|
||||
// Plautdietsch => GN
|
||||
LANG_ISO_TO_WU_CODES.put("PL", "PL");
|
||||
LANG_ISO_TO_WU_CODES.put("PT", "BR");
|
||||
LANG_ISO_TO_WU_CODES.put("PA", "PA");
|
||||
LANG_ISO_TO_WU_CODES.put("RO", "RO");
|
||||
LANG_ISO_TO_WU_CODES.put("RU", "RU");
|
||||
LANG_ISO_TO_WU_CODES.put("SR", "SR");
|
||||
LANG_ISO_TO_WU_CODES.put("SK", "SK");
|
||||
LANG_ISO_TO_WU_CODES.put("SL", "SL");
|
||||
LANG_ISO_TO_WU_CODES.put("ES", "SP");
|
||||
LANG_ISO_TO_WU_CODES.put("SW", "SI");
|
||||
LANG_ISO_TO_WU_CODES.put("SV", "SW");
|
||||
// Swiss => CH
|
||||
LANG_ISO_TO_WU_CODES.put("TL", "TL");
|
||||
LANG_ISO_TO_WU_CODES.put("TT", "TT");
|
||||
LANG_ISO_TO_WU_CODES.put("TH", "TH");
|
||||
LANG_ISO_TO_WU_CODES.put("TR", "TR");
|
||||
LANG_ISO_TO_WU_CODES.put("TK", "TK");
|
||||
LANG_ISO_TO_WU_CODES.put("UK", "UA");
|
||||
LANG_ISO_TO_WU_CODES.put("UZ", "UZ");
|
||||
LANG_ISO_TO_WU_CODES.put("VI", "VU");
|
||||
LANG_ISO_TO_WU_CODES.put("CY", "CY");
|
||||
LANG_ISO_TO_WU_CODES.put("WO", "SN");
|
||||
// Yiddish - transliterated => JI
|
||||
LANG_ISO_TO_WU_CODES.put("YI", "YI");
|
||||
}
|
||||
private static final Map<String, @Nullable String> LANG_COUNTRY_TO_WU_CODES = new HashMap<>();
|
||||
static {
|
||||
LANG_COUNTRY_TO_WU_CODES.put("en-GB", "LI"); // British English
|
||||
LANG_COUNTRY_TO_WU_CODES.put("fr-CA", "FC"); // French Canadian
|
||||
}
|
||||
|
||||
private final LocaleProvider localeProvider;
|
||||
private final UnitProvider unitProvider;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private final Gson gson;
|
||||
private final Map<String, Integer> forecastMap;
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
|
||||
private @Nullable WeatherUndergroundJsonData weatherData;
|
||||
|
||||
private @Nullable WeatherUndergroundBridgeHandler bridgeHandler;
|
||||
|
||||
public WeatherUndergroundHandler(Thing thing, LocaleProvider localeProvider, UnitProvider unitProvider,
|
||||
TimeZoneProvider timeZoneProvider) {
|
||||
super(thing);
|
||||
this.localeProvider = localeProvider;
|
||||
this.unitProvider = unitProvider;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
gson = new Gson();
|
||||
forecastMap = initForecastDayMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing WeatherUnderground handler for thing {}", getThing().getUID());
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
initializeThingHandler(null, null);
|
||||
} else {
|
||||
initializeThingHandler(bridge.getHandler(), bridge.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
logger.debug("bridgeStatusChanged {}", bridgeStatusInfo);
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
initializeThingHandler(null, bridgeStatusInfo.getStatus());
|
||||
} else {
|
||||
initializeThingHandler(bridge.getHandler(), bridgeStatusInfo.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeThingHandler(@Nullable ThingHandler bridgeHandler, @Nullable ThingStatus bridgeStatus) {
|
||||
logger.debug("initializeThingHandler {}", getThing().getUID());
|
||||
if (bridgeHandler != null && bridgeStatus != null) {
|
||||
if (bridgeStatus == ThingStatus.ONLINE) {
|
||||
this.bridgeHandler = (WeatherUndergroundBridgeHandler) bridgeHandler;
|
||||
|
||||
WeatherUndergroundConfiguration config = getConfigAs(WeatherUndergroundConfiguration.class);
|
||||
|
||||
logger.debug("config location = {}", config.location);
|
||||
logger.debug("config language = {}", config.language);
|
||||
logger.debug("config refresh = {}", config.refresh);
|
||||
|
||||
boolean validConfig = true;
|
||||
String errors = "";
|
||||
String statusDescr = null;
|
||||
|
||||
if (config.location == null || config.location.trim().isEmpty()) {
|
||||
errors += " Parameter 'location' must be configured.";
|
||||
statusDescr = "@text/offline.conf-error-missing-location";
|
||||
validConfig = false;
|
||||
}
|
||||
if (config.language != null) {
|
||||
if (config.language.trim().length() != 2) {
|
||||
errors += " Parameter 'language' must be 2 letters.";
|
||||
statusDescr = "@text/offline.conf-error-syntax-language";
|
||||
validConfig = false;
|
||||
}
|
||||
}
|
||||
if (config.refresh != null && config.refresh < 5) {
|
||||
errors += " Parameter 'refresh' must be at least 5 minutes.";
|
||||
statusDescr = "@text/offline.conf-error-min-refresh";
|
||||
validConfig = false;
|
||||
}
|
||||
errors = errors.trim();
|
||||
|
||||
if (validConfig) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
startAutomaticRefresh();
|
||||
} else {
|
||||
logger.debug("Setting thing '{}' to OFFLINE: {}", getThing().getUID(), errors);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, statusDescr);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the job refreshing the weather data
|
||||
*/
|
||||
private void startAutomaticRefresh() {
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Request new weather data to the Weather Underground service
|
||||
updateWeatherData(USUAL_FEATURES);
|
||||
|
||||
// Update all channels from the updated weather data
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
updateChannel(channel.getUID().getId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WeatherUndergroundConfiguration config = getConfigAs(WeatherUndergroundConfiguration.class);
|
||||
int period = (config.refresh != null) ? config.refresh.intValue() : DEFAULT_REFRESH_PERIOD;
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(runnable, 0, period, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing WeatherUnderground handler.");
|
||||
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
if (job != null) {
|
||||
job.cancel(true);
|
||||
}
|
||||
refreshJob = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateChannel(channelUID.getId());
|
||||
} else {
|
||||
logger.debug("The Weather Underground binding is a read-only binding and cannot handle command {}",
|
||||
command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the channel from the last Weather Underground data retrieved
|
||||
*
|
||||
* @param channelId the id identifying the channel to be updated
|
||||
*/
|
||||
private void updateChannel(String channelId) {
|
||||
if (isLinked(channelId)) {
|
||||
State state = null;
|
||||
WeatherUndergroundJsonData data = weatherData;
|
||||
if (data != null) {
|
||||
if (channelId.startsWith("current")) {
|
||||
state = updateCurrentObservationChannel(channelId, data.getCurrent());
|
||||
} else if (channelId.startsWith("forecast")) {
|
||||
state = updateForecastChannel(channelId, data.getForecast());
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Update channel {} with state {}", channelId, (state == null) ? "null" : state.toString());
|
||||
|
||||
// Update the channel
|
||||
if (state != null) {
|
||||
updateState(channelId, state);
|
||||
} else {
|
||||
updateState(channelId, UnDefType.NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable State updateCurrentObservationChannel(String channelId, WeatherUndergroundJsonCurrent current) {
|
||||
WUQuantity quantity;
|
||||
String channelTypeId = getChannelTypeId(channelId);
|
||||
switch (channelTypeId) {
|
||||
case "location":
|
||||
return undefOrState(current.getLocation(), new StringType(current.getLocation()));
|
||||
case "stationId":
|
||||
return undefOrState(current.getStationId(), new StringType(current.getStationId()));
|
||||
case "observationTime":
|
||||
ZoneId zoneId = timeZoneProvider.getTimeZone();
|
||||
return undefOrState(current.getObservationTime(zoneId),
|
||||
new DateTimeType(current.getObservationTime(zoneId)));
|
||||
case "conditions":
|
||||
return undefOrState(current.getConditions(), new StringType(current.getConditions()));
|
||||
case "temperature":
|
||||
quantity = getTemperature(current.getTemperatureC(), current.getTemperatureF());
|
||||
return undefOrQuantity(quantity);
|
||||
case "relativeHumidity":
|
||||
return undefOrState(current.getRelativeHumidity(),
|
||||
new QuantityType<>(current.getRelativeHumidity(), SmartHomeUnits.PERCENT));
|
||||
case "windDirection":
|
||||
return undefOrState(current.getWindDirection(), new StringType(current.getWindDirection()));
|
||||
case "windDirectionDegrees":
|
||||
return undefOrState(current.getWindDirectionDegrees(),
|
||||
new QuantityType<>(current.getWindDirectionDegrees(), SmartHomeUnits.DEGREE_ANGLE));
|
||||
case "windSpeed":
|
||||
quantity = getSpeed(current.getWindSpeedKmh(), current.getWindSpeedMph());
|
||||
return undefOrQuantity(quantity);
|
||||
case "windGust":
|
||||
quantity = getSpeed(current.getWindGustKmh(), current.getWindGustMph());
|
||||
return undefOrQuantity(quantity);
|
||||
case "pressure":
|
||||
quantity = getPressure(current.getPressureHPa(), current.getPressureInHg());
|
||||
return undefOrQuantity(quantity);
|
||||
case "pressureTrend":
|
||||
return undefOrState(current.getPressureTrend(), new StringType(current.getPressureTrend()));
|
||||
case "dewPoint":
|
||||
quantity = getTemperature(current.getDewPointC(), current.getDewPointF());
|
||||
return undefOrQuantity(quantity);
|
||||
case "heatIndex":
|
||||
quantity = getTemperature(current.getHeatIndexC(), current.getHeatIndexF());
|
||||
return undefOrQuantity(quantity);
|
||||
case "windChill":
|
||||
quantity = getTemperature(current.getWindChillC(), current.getWindChillF());
|
||||
return undefOrQuantity(quantity);
|
||||
case "feelingTemperature":
|
||||
quantity = getTemperature(current.getFeelingTemperatureC(), current.getFeelingTemperatureF());
|
||||
return undefOrQuantity(quantity);
|
||||
case "visibility":
|
||||
quantity = getWUQuantity(KILO(SIUnits.METRE), ImperialUnits.MILE, current.getVisibilityKm(),
|
||||
current.getVisibilityMi());
|
||||
return undefOrQuantity(quantity);
|
||||
case "solarRadiation":
|
||||
return undefOrQuantity(new WUQuantity(current.getSolarRadiation(), SmartHomeUnits.IRRADIANCE));
|
||||
case "UVIndex":
|
||||
return undefOrDecimal(current.getUVIndex());
|
||||
case "precipitationDay":
|
||||
quantity = getPrecipitation(current.getPrecipitationDayMm(), current.getPrecipitationDayIn());
|
||||
return undefOrQuantity(quantity);
|
||||
case "precipitationHour":
|
||||
quantity = getPrecipitation(current.getPrecipitationHourMm(), current.getPrecipitationHourIn());
|
||||
return undefOrQuantity(quantity);
|
||||
case "iconKey":
|
||||
return undefOrState(current.getIconKey(), new StringType(current.getIconKey()));
|
||||
case "icon":
|
||||
State icon = HttpUtil.downloadImage(current.getIcon().toExternalForm());
|
||||
if (icon == null) {
|
||||
logger.debug("Failed to download the content of URL {}", current.getIcon().toExternalForm());
|
||||
return null;
|
||||
}
|
||||
return icon;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable State updateForecastChannel(String channelId, WeatherUndergroundJsonForecast forecast) {
|
||||
WUQuantity quantity;
|
||||
int day = getDay(channelId);
|
||||
WeatherUndergroundJsonForecastDay dayForecast = forecast.getSimpleForecast(day);
|
||||
|
||||
String channelTypeId = getChannelTypeId(channelId);
|
||||
switch (channelTypeId) {
|
||||
case "forecastTime":
|
||||
ZoneId zoneId = timeZoneProvider.getTimeZone();
|
||||
return undefOrState(dayForecast.getForecastTime(zoneId),
|
||||
new DateTimeType(dayForecast.getForecastTime(zoneId)));
|
||||
case "conditions":
|
||||
return undefOrState(dayForecast.getConditions(), new StringType(dayForecast.getConditions()));
|
||||
case "minTemperature":
|
||||
quantity = getTemperature(dayForecast.getMinTemperatureC(), dayForecast.getMinTemperatureF());
|
||||
return undefOrQuantity(quantity);
|
||||
case "maxTemperature":
|
||||
quantity = getTemperature(dayForecast.getMaxTemperatureC(), dayForecast.getMaxTemperatureF());
|
||||
return undefOrQuantity(quantity);
|
||||
case "relativeHumidity":
|
||||
return undefOrState(dayForecast.getRelativeHumidity(),
|
||||
new QuantityType<>(dayForecast.getRelativeHumidity(), SmartHomeUnits.PERCENT));
|
||||
case "probaPrecipitation":
|
||||
return undefOrState(dayForecast.getProbaPrecipitation(),
|
||||
new QuantityType<>(dayForecast.getProbaPrecipitation(), SmartHomeUnits.PERCENT));
|
||||
case "precipitationDay":
|
||||
quantity = getPrecipitation(dayForecast.getPrecipitationDayMm(), dayForecast.getPrecipitationDayIn());
|
||||
return undefOrQuantity(quantity);
|
||||
case "snow":
|
||||
quantity = getWUQuantity(CENTI(SIUnits.METRE), ImperialUnits.INCH, dayForecast.getSnowCm(),
|
||||
dayForecast.getSnowIn());
|
||||
return undefOrQuantity(quantity);
|
||||
case "maxWindDirection":
|
||||
return undefOrState(dayForecast.getMaxWindDirection(),
|
||||
new StringType(dayForecast.getMaxWindDirection()));
|
||||
case "maxWindDirectionDegrees":
|
||||
return undefOrState(dayForecast.getMaxWindDirectionDegrees(),
|
||||
new QuantityType<>(dayForecast.getMaxWindDirectionDegrees(), SmartHomeUnits.DEGREE_ANGLE));
|
||||
case "maxWindSpeed":
|
||||
quantity = getSpeed(dayForecast.getMaxWindSpeedKmh(), dayForecast.getMaxWindSpeedMph());
|
||||
return undefOrQuantity(quantity);
|
||||
case "averageWindDirection":
|
||||
return undefOrState(dayForecast.getAverageWindDirection(),
|
||||
new StringType(dayForecast.getAverageWindDirection()));
|
||||
case "averageWindDirectionDegrees":
|
||||
return undefOrState(dayForecast.getAverageWindDirectionDegrees(),
|
||||
new QuantityType<>(dayForecast.getAverageWindDirectionDegrees(), SmartHomeUnits.DEGREE_ANGLE));
|
||||
case "averageWindSpeed":
|
||||
quantity = getSpeed(dayForecast.getAverageWindSpeedKmh(), dayForecast.getAverageWindSpeedMph());
|
||||
return undefOrQuantity(quantity);
|
||||
case "iconKey":
|
||||
return undefOrState(dayForecast.getIconKey(), new StringType(dayForecast.getIconKey()));
|
||||
case "icon":
|
||||
State icon = HttpUtil.downloadImage(dayForecast.getIcon().toExternalForm());
|
||||
if (icon == null) {
|
||||
logger.debug("Failed to download the content of URL {}", dayForecast.getIcon().toExternalForm());
|
||||
return null;
|
||||
}
|
||||
return icon;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable State undefOrState(@Nullable Object value, State state) {
|
||||
return value == null ? null : state;
|
||||
}
|
||||
|
||||
private @Nullable <T extends Quantity<T>> State undefOrQuantity(WUQuantity quantity) {
|
||||
return quantity.value == null ? null : new QuantityType<>(quantity.value, quantity.unit);
|
||||
}
|
||||
|
||||
private @Nullable State undefOrDecimal(@Nullable Number value) {
|
||||
return value == null ? null : new DecimalType(value.doubleValue());
|
||||
}
|
||||
|
||||
private int getDay(String channelId) {
|
||||
String channel = channelId.split("#")[0];
|
||||
|
||||
return forecastMap.get(channel);
|
||||
}
|
||||
|
||||
private String getChannelTypeId(String channelId) {
|
||||
return channelId.substring(channelId.indexOf("#") + 1);
|
||||
}
|
||||
|
||||
private Map<String, Integer> initForecastDayMap() {
|
||||
Map<String, Integer> forecastMap = new HashMap<>();
|
||||
forecastMap.put("forecastToday", Integer.valueOf(1));
|
||||
forecastMap.put("forecastTomorrow", Integer.valueOf(2));
|
||||
forecastMap.put("forecastDay2", Integer.valueOf(3));
|
||||
forecastMap.put("forecastDay3", Integer.valueOf(4));
|
||||
forecastMap.put("forecastDay4", Integer.valueOf(5));
|
||||
forecastMap.put("forecastDay5", Integer.valueOf(6));
|
||||
forecastMap.put("forecastDay6", Integer.valueOf(7));
|
||||
forecastMap.put("forecastDay7", Integer.valueOf(8));
|
||||
forecastMap.put("forecastDay8", Integer.valueOf(9));
|
||||
forecastMap.put("forecastDay9", Integer.valueOf(10));
|
||||
return forecastMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request new current conditions and forecast 10 days to the Weather Underground service
|
||||
* and store the data in weatherData
|
||||
*
|
||||
* @param features the list of features to be requested
|
||||
* @return true if success or false in case of error
|
||||
*/
|
||||
private boolean updateWeatherData(Set<String> features) {
|
||||
WeatherUndergroundJsonData result = null;
|
||||
boolean resultOk = false;
|
||||
String error = null;
|
||||
String errorDetail = null;
|
||||
String statusDescr = null;
|
||||
|
||||
// Request new weather data to the Weather Underground service
|
||||
|
||||
try {
|
||||
WeatherUndergroundConfiguration config = getConfigAs(WeatherUndergroundConfiguration.class);
|
||||
|
||||
String urlStr = URL_QUERY.replace("%FEATURES%", String.join("/", features));
|
||||
|
||||
String lang = config.language == null ? "" : config.language.trim();
|
||||
if (lang.isEmpty()) {
|
||||
// If language is not set in the configuration, you try deducing it from the system language
|
||||
lang = getCodeFromLanguage(localeProvider.getLocale());
|
||||
logger.debug("Use language deduced from system locale {}: {}", localeProvider.getLocale().getLanguage(),
|
||||
lang);
|
||||
}
|
||||
if (lang.isEmpty()) {
|
||||
urlStr = urlStr.replace("%SETTINGS%", "");
|
||||
} else {
|
||||
urlStr = urlStr.replace("%SETTINGS%", "lang:" + lang.toUpperCase());
|
||||
}
|
||||
|
||||
String location = config.location == null ? "" : config.location.trim();
|
||||
urlStr = urlStr.replace("%QUERY%", location);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("URL = {}", urlStr.replace("%APIKEY%", "***"));
|
||||
}
|
||||
|
||||
urlStr = urlStr.replace("%APIKEY%", bridgeHandler.getApikey());
|
||||
|
||||
// Run the HTTP request and get the JSON response from Weather Underground
|
||||
String response = null;
|
||||
try {
|
||||
response = HttpUtil.executeUrl("GET", urlStr, WeatherUndergroundBridgeHandler.FETCH_TIMEOUT_MS);
|
||||
logger.debug("weatherData = {}", response);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// catch Illegal character in path at index XX: http://api.wunderground.com/...
|
||||
error = "Error creating URI with location parameter: '" + location + "'";
|
||||
errorDetail = e.getMessage();
|
||||
statusDescr = "@text/offline.uri-error";
|
||||
}
|
||||
|
||||
// Map the JSON response to an object
|
||||
result = gson.fromJson(response, WeatherUndergroundJsonData.class);
|
||||
if (result.getResponse() == null) {
|
||||
errorDetail = "missing response sub-object";
|
||||
} else if (result.getResponse().getErrorDescription() != null) {
|
||||
if ("keynotfound".equals(result.getResponse().getErrorType())) {
|
||||
error = "API key has to be fixed";
|
||||
statusDescr = "@text/offline.comm-error-invalid-api-key";
|
||||
}
|
||||
errorDetail = result.getResponse().getErrorDescription();
|
||||
} else {
|
||||
resultOk = true;
|
||||
for (String feature : features) {
|
||||
if (feature.equals(FEATURE_CONDITIONS) && result.getCurrent() == null) {
|
||||
resultOk = false;
|
||||
errorDetail = "missing current_observation sub-object";
|
||||
} else if (feature.equals(FEATURE_FORECAST10DAY) && result.getForecast() == null) {
|
||||
resultOk = false;
|
||||
errorDetail = "missing forecast sub-object";
|
||||
} else if (feature.equals(FEATURE_GEOLOOKUP) && result.getLocation() == null) {
|
||||
resultOk = false;
|
||||
errorDetail = "missing location sub-object";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!resultOk && error == null) {
|
||||
error = "Error in Weather Underground response";
|
||||
statusDescr = "@text/offline.comm-error-response";
|
||||
}
|
||||
} catch (IOException e) {
|
||||
error = "Error running Weather Underground request";
|
||||
errorDetail = e.getMessage();
|
||||
statusDescr = "@text/offline.comm-error-running-request";
|
||||
} catch (JsonSyntaxException e) {
|
||||
error = "Error parsing Weather Underground response";
|
||||
errorDetail = e.getMessage();
|
||||
statusDescr = "@text/offline.comm-error-parsing-response";
|
||||
}
|
||||
|
||||
// Update the thing status
|
||||
if (resultOk) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
weatherData = result;
|
||||
} else {
|
||||
logger.debug("Setting thing '{}' to OFFLINE: Error '{}': {}", getThing().getUID(), error, errorDetail);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, statusDescr);
|
||||
weatherData = null;
|
||||
}
|
||||
|
||||
return resultOk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WU code associated to a language
|
||||
*
|
||||
* @param locale the locale settings with language and country
|
||||
* @return the associated WU code or an empty string if not found
|
||||
*/
|
||||
public static String getCodeFromLanguage(Locale locale) {
|
||||
String key = locale.getLanguage() + "-" + locale.getCountry();
|
||||
String language = LANG_COUNTRY_TO_WU_CODES.get(key);
|
||||
if (language == null) {
|
||||
language = LANG_ISO_TO_WU_CODES.get(locale.getLanguage().toUpperCase());
|
||||
}
|
||||
return language != null ? language : "";
|
||||
}
|
||||
|
||||
private WUQuantity getTemperature(BigDecimal siValue, BigDecimal imperialValue) {
|
||||
return getWUQuantity(SIUnits.CELSIUS, ImperialUnits.FAHRENHEIT, siValue, imperialValue);
|
||||
}
|
||||
|
||||
private WUQuantity getSpeed(BigDecimal siValue, BigDecimal imperialValue) {
|
||||
return getWUQuantity(SIUnits.KILOMETRE_PER_HOUR, ImperialUnits.MILES_PER_HOUR, siValue, imperialValue);
|
||||
}
|
||||
|
||||
private WUQuantity getPressure(BigDecimal siValue, BigDecimal imperialValue) {
|
||||
return getWUQuantity(HECTO(SIUnits.PASCAL), ImperialUnits.INCH_OF_MERCURY, siValue, imperialValue);
|
||||
}
|
||||
|
||||
private WUQuantity getPrecipitation(BigDecimal siValue, BigDecimal imperialValue) {
|
||||
return getWUQuantity(MILLI(SIUnits.METRE), ImperialUnits.INCH, siValue, imperialValue);
|
||||
}
|
||||
|
||||
private <T extends Quantity<T>> WUQuantity getWUQuantity(Unit<T> siUnit, Unit<T> imperialUnit, BigDecimal siValue,
|
||||
BigDecimal imperialValue) {
|
||||
boolean isSI = unitProvider.getMeasurementSystem().equals(SIUnits.getInstance());
|
||||
return new WUQuantity(isSI ? siValue : imperialValue, isSI ? siUnit : imperialUnit);
|
||||
}
|
||||
|
||||
private class WUQuantity {
|
||||
private WUQuantity(BigDecimal value, Unit<?> unit) {
|
||||
this.value = value;
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
private final Unit<?> unit;
|
||||
private final BigDecimal value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,568 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal.json;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URL;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundJsonCurrent} is the Java class used
|
||||
* to map the entry "current_observation" from the JSON response to a Weather
|
||||
* Underground request.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
public class WeatherUndergroundJsonCurrent {
|
||||
|
||||
// Commented members indicate properties returned by the API not used by the binding
|
||||
|
||||
// private Object image;
|
||||
|
||||
// private Location display_location;
|
||||
private Location observation_location;
|
||||
// private Object estimated;
|
||||
|
||||
private String station_id;
|
||||
|
||||
// private String observation_time;
|
||||
// private String observation_time_rfc822;
|
||||
private String observation_epoch;
|
||||
// private String local_time_rfc822;
|
||||
// private String local_epoch;
|
||||
// private String local_tz_short;
|
||||
// private String local_tz_long;
|
||||
// private String local_tz_offset;
|
||||
|
||||
private String weather;
|
||||
|
||||
// private String ;temperature_string;
|
||||
private BigDecimal temp_f;
|
||||
private BigDecimal temp_c;
|
||||
|
||||
private String relative_humidity;
|
||||
|
||||
// private String wind_string;
|
||||
private String wind_dir;
|
||||
private BigDecimal wind_degrees;
|
||||
private BigDecimal wind_mph;
|
||||
private String wind_gust_mph;
|
||||
private BigDecimal wind_kph;
|
||||
private String wind_gust_kph;
|
||||
|
||||
private String pressure_mb;
|
||||
private String pressure_in;
|
||||
private String pressure_trend;
|
||||
|
||||
// private String dewpoint_string;
|
||||
private BigDecimal dewpoint_f;
|
||||
private BigDecimal dewpoint_c;
|
||||
|
||||
// private String heat_index_string;
|
||||
private String heat_index_f;
|
||||
private String heat_index_c;
|
||||
|
||||
// private String windchill_string;
|
||||
private String windchill_f;
|
||||
private String windchill_c;
|
||||
|
||||
// private String feelslike_string;
|
||||
private String feelslike_f;
|
||||
private String feelslike_c;
|
||||
|
||||
private String visibility_mi;
|
||||
private String visibility_km;
|
||||
|
||||
private String solarradiation;
|
||||
private String UV;
|
||||
|
||||
// private String precip_1hr_string;
|
||||
private String precip_1hr_in;
|
||||
private String precip_1hr_metric;
|
||||
// private String precip_today_string;
|
||||
private String precip_today_in;
|
||||
private String precip_today_metric;
|
||||
|
||||
private String icon;
|
||||
private String icon_url;
|
||||
// private String forecast_url;
|
||||
// private String history_url;
|
||||
// private String ob_url;
|
||||
|
||||
// private String nowcast;
|
||||
|
||||
public WeatherUndergroundJsonCurrent() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the observation location (full name)
|
||||
*
|
||||
* Used to update the channel current#location
|
||||
*
|
||||
* @return the observation location or null if not defined
|
||||
*/
|
||||
public String getLocation() {
|
||||
return (observation_location == null) ? null : observation_location.getFull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the station ID
|
||||
*
|
||||
* Used to update the channel current#stationId
|
||||
*
|
||||
* @return the station ID or null if not defined
|
||||
*/
|
||||
public String getStationId() {
|
||||
return station_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the observation date and time
|
||||
*
|
||||
* Used to update the channel current#observationTime
|
||||
*
|
||||
* @return the observation date and time or null if not defined
|
||||
*/
|
||||
public ZonedDateTime getObservationTime(ZoneId zoneId) {
|
||||
return WeatherUndergroundJsonUtils.convertToZonedDateTime(observation_epoch, zoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current weather conditions
|
||||
*
|
||||
* Used to update the channel current#conditions
|
||||
*
|
||||
* @return the current weather conditions or null if not defined
|
||||
*/
|
||||
public String getConditions() {
|
||||
return weather;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current temperature in degrees Celsius
|
||||
*
|
||||
* Used to update the channel current#temperature
|
||||
*
|
||||
* @return the current temperature in degrees Celsius or null if not defined
|
||||
*/
|
||||
public BigDecimal getTemperatureC() {
|
||||
return temp_c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current temperature in degrees Fahrenheit
|
||||
*
|
||||
* Used to update the channel current#temperature
|
||||
*
|
||||
* @return the current temperature in degrees Fahrenheit or null if not defined
|
||||
*/
|
||||
public BigDecimal getTemperatureF() {
|
||||
return temp_f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current relative humidity
|
||||
*
|
||||
* Used to update the channel current#relativeHumidity
|
||||
*
|
||||
* @return the current relative humidity or null if not defined
|
||||
*/
|
||||
public Integer getRelativeHumidity() {
|
||||
if (relative_humidity != null && !relative_humidity.isEmpty() && !relative_humidity.equalsIgnoreCase("N/A")) {
|
||||
return WeatherUndergroundJsonUtils.convertToInteger(relative_humidity.replace("%", ""));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wind direction as a text
|
||||
*
|
||||
* Used to update the channel current#windDirection
|
||||
*
|
||||
* @return the wind direction or null if not defined
|
||||
*/
|
||||
public String getWindDirection() {
|
||||
return wind_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wind direction in degrees
|
||||
*
|
||||
* Used to update the channel current#windDirectionDegrees
|
||||
*
|
||||
* @return the wind direction in degrees or null if not defined
|
||||
*/
|
||||
public BigDecimal getWindDirectionDegrees() {
|
||||
return wind_degrees;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wind speed in km/h
|
||||
*
|
||||
* Used to update the channel current#windSpeed
|
||||
*
|
||||
* @return the wind speed in km/h or null if not defined
|
||||
*/
|
||||
public BigDecimal getWindSpeedKmh() {
|
||||
return wind_kph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wind speed in mph
|
||||
*
|
||||
* Used to update the channel current#windSpeed
|
||||
*
|
||||
* @return the wind speed in mph or null if not defined
|
||||
*/
|
||||
public BigDecimal getWindSpeedMph() {
|
||||
return wind_mph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wind gust in km/h
|
||||
*
|
||||
* Used to update the channel current#windGust
|
||||
*
|
||||
* @return the wind gust in km/h or null if not defined
|
||||
*/
|
||||
public BigDecimal getWindGustKmh() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(wind_gust_kph);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wind gust in mph
|
||||
*
|
||||
* Used to update the channel current#windGust
|
||||
*
|
||||
* @return the wind gust in mph or null if not defined
|
||||
*/
|
||||
public BigDecimal getWindGustMph() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(wind_gust_mph);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pressure in hPa
|
||||
*
|
||||
* Used to update the channel current#pressure
|
||||
*
|
||||
* @return the pressure in hPa or null if not defined
|
||||
*/
|
||||
public BigDecimal getPressureHPa() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(pressure_mb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pressure in inHg
|
||||
*
|
||||
* Used to update the channel current#pressure
|
||||
*
|
||||
* @return the pressure in inHg or null if not defined
|
||||
*/
|
||||
public BigDecimal getPressureInHg() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(pressure_in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pressure trend
|
||||
*
|
||||
* Used to update the channel current#pressureTrend
|
||||
*
|
||||
* @return the pressure trend or null if not defined
|
||||
*/
|
||||
public String getPressureTrend() {
|
||||
return WeatherUndergroundJsonUtils.convertToTrend(pressure_trend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dew point temperature in degrees Celsius
|
||||
*
|
||||
* Used to update the channel current#dewPoint
|
||||
*
|
||||
* @return the dew point temperature in degrees Celsius or null if not defined
|
||||
*/
|
||||
public BigDecimal getDewPointC() {
|
||||
return dewpoint_c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dew point temperature in degrees Fahrenheit
|
||||
*
|
||||
* Used to update the channel current#dewPoint
|
||||
*
|
||||
* @return the dew point temperature in degrees Fahrenheit or null if not defined
|
||||
*/
|
||||
public BigDecimal getDewPointF() {
|
||||
return dewpoint_f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the heat index in degrees Celsius
|
||||
*
|
||||
* Used to update the channel current#heatIndex
|
||||
*
|
||||
* @return the heat index in degrees Celsius or null if not defined
|
||||
*/
|
||||
public BigDecimal getHeatIndexC() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(heat_index_c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the heat index in degrees Fahrenheit
|
||||
*
|
||||
* Used to update the channel current#heatIndex
|
||||
*
|
||||
* @return the heat index in degrees Fahrenheit or null if not defined
|
||||
*/
|
||||
public BigDecimal getHeatIndexF() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(heat_index_f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wind chill temperature in degrees Celsius
|
||||
*
|
||||
* Used to update the channel current#windChill
|
||||
*
|
||||
* @return the wind chill temperature in degrees Celsius or null if not defined
|
||||
*/
|
||||
public BigDecimal getWindChillC() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(windchill_c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wind chill temperature in degrees Fahrenheit
|
||||
*
|
||||
* Used to update the channel current#windChill
|
||||
*
|
||||
* @return the wind chill temperature in degrees Fahrenheit or null if not defined
|
||||
*/
|
||||
public BigDecimal getWindChillF() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(windchill_f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the feeling temperature in degrees Celsius
|
||||
*
|
||||
* Used to update the channel current#feelingTemperature
|
||||
*
|
||||
* @return the feeling temperature in degrees Celsius or null if not defined
|
||||
*/
|
||||
public BigDecimal getFeelingTemperatureC() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(feelslike_c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the feeling temperature in degrees Fahrenheit
|
||||
*
|
||||
* Used to update the channel current#feelingTemperature
|
||||
*
|
||||
* @return the feeling temperature in degrees Fahrenheit or null if not defined
|
||||
*/
|
||||
public BigDecimal getFeelingTemperatureF() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(feelslike_f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the visibility in kilometers
|
||||
*
|
||||
* Used to update the channel current#visibility
|
||||
*
|
||||
* @return the visibility in kilometers or null if not defined
|
||||
*/
|
||||
public BigDecimal getVisibilityKm() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(visibility_km);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the visibility in miles
|
||||
*
|
||||
* Used to update the channel current#visibility
|
||||
*
|
||||
* @return the visibility in miles or null if not defined
|
||||
*/
|
||||
public BigDecimal getVisibilityMi() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(visibility_mi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the precipitation in the last hour in millimeters
|
||||
*
|
||||
* Used to update the channel current#precipitationHour
|
||||
*
|
||||
* @return the precipitation in the last hour in millimeters or null if not defined
|
||||
*/
|
||||
public BigDecimal getPrecipitationHourMm() {
|
||||
BigDecimal result = WeatherUndergroundJsonUtils.convertToBigDecimal(precip_1hr_metric);
|
||||
if ((result != null) && (result.doubleValue() < 0.0)) {
|
||||
result = BigDecimal.ZERO;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the precipitation in the last hour in inches
|
||||
*
|
||||
* Used to update the channel current#precipitationHour
|
||||
*
|
||||
* @return the precipitation in the last hour in inches or null if not defined
|
||||
*/
|
||||
public BigDecimal getPrecipitationHourIn() {
|
||||
BigDecimal result = WeatherUndergroundJsonUtils.convertToBigDecimal(precip_1hr_in);
|
||||
if ((result != null) && (result.doubleValue() < 0.0)) {
|
||||
result = BigDecimal.ZERO;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the precipitation for the full day in millimeters
|
||||
*
|
||||
* Used to update the channel current#precipitationDay
|
||||
*
|
||||
* @return the precipitation for the full day in millimeters or null if not defined
|
||||
*/
|
||||
public BigDecimal getPrecipitationDayMm() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(precip_today_metric);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the precipitation for the full day in inches
|
||||
*
|
||||
* Used to update the channel current#precipitationDay
|
||||
*
|
||||
* @return the precipitation for the full day in inches or null if not defined
|
||||
*/
|
||||
public BigDecimal getPrecipitationDayIn() {
|
||||
return WeatherUndergroundJsonUtils.convertToBigDecimal(precip_today_in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the solar radiation in Watts/sq. m
|
||||
*
|
||||
* Used to update the channel current#solarRadiation
|
||||
*
|
||||
* @return the solar radiation or null if not defined or negative
|
||||
*/
|
||||
public BigDecimal getSolarRadiation() {
|
||||
BigDecimal value = WeatherUndergroundJsonUtils.convertToBigDecimal(solarradiation);
|
||||
// We check that the index is not negative
|
||||
if (value != null && value.signum() == -1) {
|
||||
value = null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UV Index
|
||||
*
|
||||
* Used to update the channel current#UVIndex
|
||||
*
|
||||
* @return the UV Index or null if not defined or negative
|
||||
*/
|
||||
public BigDecimal getUVIndex() {
|
||||
BigDecimal value = WeatherUndergroundJsonUtils.convertToBigDecimal(UV);
|
||||
// We check that the index is not negative
|
||||
if (value != null && value.signum() == -1) {
|
||||
value = null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the icon URL representing the current weather conditions
|
||||
*
|
||||
* Used to update the channel current#icon
|
||||
*
|
||||
* @return the icon URL representing the current weather conditions or null if not defined
|
||||
*/
|
||||
public URL getIcon() {
|
||||
return WeatherUndergroundJsonUtils.getValidUrl(icon_url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the icon key used in the URL representing the current weather conditions
|
||||
*
|
||||
* Used to update the channel current#iconKey
|
||||
*
|
||||
* @return the icon key used in the URL representing the current weather conditions
|
||||
*/
|
||||
public String getIconKey() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
class Location {
|
||||
private String full;
|
||||
private String city;
|
||||
private String state;
|
||||
private String state_name;
|
||||
private String country;
|
||||
private String country_iso3166;
|
||||
private String zip;
|
||||
private String magic;
|
||||
private String wmo;
|
||||
private String latitude;
|
||||
private String longitude;
|
||||
private String elevation;
|
||||
|
||||
Location() {
|
||||
}
|
||||
|
||||
public String getFull() {
|
||||
return full;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public String getStateName() {
|
||||
return state_name;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
public String getCountryIso3166() {
|
||||
return country_iso3166;
|
||||
}
|
||||
|
||||
public String getZip() {
|
||||
return zip;
|
||||
}
|
||||
|
||||
public String getMagic() {
|
||||
return magic;
|
||||
}
|
||||
|
||||
public String getWmo() {
|
||||
return wmo;
|
||||
}
|
||||
|
||||
public String getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public String getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public String getElevation() {
|
||||
return elevation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal.json;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundJsonData} is the Java class used to map the JSON
|
||||
* response to a Weather Underground request.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
public class WeatherUndergroundJsonData {
|
||||
|
||||
private WeatherUndergroundJsonResponse response;
|
||||
private WeatherUndergroundJsonCurrent current_observation;
|
||||
private WeatherUndergroundJsonForecast forecast;
|
||||
private WeatherUndergroundJsonLocation location;
|
||||
|
||||
public WeatherUndergroundJsonData() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link WeatherUndergroundJsonResponse} object
|
||||
*
|
||||
* @return the {@link WeatherUndergroundJsonResponse} object
|
||||
*/
|
||||
public WeatherUndergroundJsonResponse getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link WeatherUndergroundJsonLocation} object
|
||||
*
|
||||
* @return the {@link WeatherUndergroundJsonLocation} object
|
||||
*/
|
||||
public WeatherUndergroundJsonLocation getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link WeatherUndergroundJsonForecast} object
|
||||
*
|
||||
* @return the {@link WeatherUndergroundJsonForecast} object
|
||||
*/
|
||||
public WeatherUndergroundJsonForecast getForecast() {
|
||||
return forecast;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link WeatherUndergroundJsonCurrent} object
|
||||
*
|
||||
* Used to update the channels current#xxx
|
||||
*
|
||||
* @return the {@link WeatherUndergroundJsonCurrent} object
|
||||
*/
|
||||
public WeatherUndergroundJsonCurrent getCurrent() {
|
||||
return current_observation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal.json;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundJsonError} is the Java class used
|
||||
* to map the entry "response.error" from the JSON response to a Weather Underground
|
||||
* request.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
public class WeatherUndergroundJsonError {
|
||||
|
||||
private String type;
|
||||
private String description;
|
||||
|
||||
public WeatherUndergroundJsonError() {
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal.json;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundJsonForecast} is the Java class used
|
||||
* to map the entry "forecast" from the JSON response to a Weather Underground
|
||||
* request.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
public class WeatherUndergroundJsonForecast {
|
||||
|
||||
// Commented members indicate properties returned by the API not used by the binding
|
||||
|
||||
// private Object txt_forecast;
|
||||
private WeatherUndergroundJsonSimpleForecast simpleforecast;
|
||||
|
||||
public WeatherUndergroundJsonForecast() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link WeatherUndergroundJsonForecastDay} object for a given day
|
||||
*
|
||||
* @return the {@link WeatherUndergroundJsonForecastDay} object for the day
|
||||
*/
|
||||
public WeatherUndergroundJsonForecastDay getSimpleForecast(int day) {
|
||||
return (simpleforecast == null) ? null : simpleforecast.getForecastDay(day);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal.json;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URL;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundJsonForecastDay} is the Java class used
|
||||
* to map the list element of the entry "forecast.simpleforecast.forecastday"
|
||||
* from the JSON response to a Weather Underground request.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
public class WeatherUndergroundJsonForecastDay {
|
||||
|
||||
// Commented members indicate properties returned by the API not used by the binding
|
||||
|
||||
private ForecastDate date;
|
||||
private Integer period;
|
||||
|
||||
private ForecastTemperature high;
|
||||
private ForecastTemperature low;
|
||||
|
||||
private String conditions;
|
||||
|
||||
private String icon;
|
||||
private String icon_url;
|
||||
// private String skyicon;
|
||||
|
||||
private Integer pop;
|
||||
|
||||
private ForecastPrecipitation qpf_allday;
|
||||
// private ForecastPrecipitation qpf_day;
|
||||
// private ForecastPrecipitation qpf_night;
|
||||
private ForecastPrecipitation snow_allday;
|
||||
// private ForecastPrecipitation snow_day;
|
||||
// private ForecastPrecipitation snow_night;
|
||||
|
||||
private ForecastWind maxwind;
|
||||
private ForecastWind avewind;
|
||||
|
||||
private Integer avehumidity;
|
||||
// private Integer minhumidity;
|
||||
// private Integer maxhumidity;
|
||||
|
||||
public WeatherUndergroundJsonForecastDay() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forecast date and time
|
||||
*
|
||||
* Used to update the channel forecastDayX#forecastTime
|
||||
*
|
||||
* @return the forecast date and time or null if not defined
|
||||
*/
|
||||
public ZonedDateTime getForecastTime(ZoneId zoneId) {
|
||||
return WeatherUndergroundJsonUtils.convertToZonedDateTime((date == null) ? null : date.getEpoch(), zoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the period number
|
||||
*
|
||||
* @return the period number
|
||||
*/
|
||||
public Integer getPeriod() {
|
||||
return period;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weather forecast conditions
|
||||
*
|
||||
* Used to update the channel forecastDayX#conditions
|
||||
*
|
||||
* @return the weather forecast conditions or null if not defined
|
||||
*/
|
||||
public String getConditions() {
|
||||
return conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the icon URL representing the weather forecast conditions
|
||||
*
|
||||
* Used to update the channel forecastDayX#icon
|
||||
*
|
||||
* @return the icon URL representing the weather forecast conditions or null if not defined
|
||||
*/
|
||||
public URL getIcon() {
|
||||
return WeatherUndergroundJsonUtils.getValidUrl(icon_url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the icon key used in the URL representing the weather forecast conditions
|
||||
*
|
||||
* Used to update the channel forecastDayX#iconKey
|
||||
*
|
||||
* @return the icon key used in the URL representing the weather forecast conditions
|
||||
*/
|
||||
public String getIconKey() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum temperature in degrees Celsius
|
||||
*
|
||||
* Used to update the channel forecastDayX#minTemperature
|
||||
*
|
||||
* @return the minimum temperature in degrees Celsius or null if not defined
|
||||
*/
|
||||
public BigDecimal getMinTemperatureC() {
|
||||
return (low == null) ? null : WeatherUndergroundJsonUtils.convertToBigDecimal(low.getCelsius());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum temperature in degrees Fahrenheit
|
||||
*
|
||||
* Used to update the channel forecastDayX#minTemperature
|
||||
*
|
||||
* @return the minimum temperature in degrees Fahrenheit or null if not defined
|
||||
*/
|
||||
public BigDecimal getMinTemperatureF() {
|
||||
return (low == null) ? null : WeatherUndergroundJsonUtils.convertToBigDecimal(low.getFahrenheit());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum temperature in degrees Celsius
|
||||
*
|
||||
* Used to update the channel forecastDayX#maxTemperature
|
||||
*
|
||||
* @return the maximum temperature in degrees Celsius or null if not defined
|
||||
*/
|
||||
public BigDecimal getMaxTemperatureC() {
|
||||
return (high == null) ? null : WeatherUndergroundJsonUtils.convertToBigDecimal(high.getCelsius());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum temperature in degrees Fahrenheit
|
||||
*
|
||||
* Used to update the channel forecastDayX#maxTemperature
|
||||
*
|
||||
* @return the maximum temperature in degrees Fahrenheit or null if not defined
|
||||
*/
|
||||
public BigDecimal getMaxTemperatureF() {
|
||||
return (high == null) ? null : WeatherUndergroundJsonUtils.convertToBigDecimal(high.getFahrenheit());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relative humidity
|
||||
*
|
||||
* Used to update the channel forecastDayX#relativeHumidity
|
||||
*
|
||||
* @return the relative humidity or null if not defined
|
||||
*/
|
||||
public Integer getRelativeHumidity() {
|
||||
return avehumidity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the probability of precipitation
|
||||
*
|
||||
* Used to update the channel forecastDayX#probaPrecipitation
|
||||
*
|
||||
* @return the probability of precipitation or null if not defined
|
||||
*/
|
||||
public Integer getProbaPrecipitation() {
|
||||
return pop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the precipitation for the full day in millimeters
|
||||
*
|
||||
* Used to update the channel forecastDayX#precipitationDay
|
||||
*
|
||||
* @return the precipitation for the full day in millimeters or null if not defined
|
||||
*/
|
||||
public BigDecimal getPrecipitationDayMm() {
|
||||
return (qpf_allday == null) ? null : qpf_allday.mm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the precipitation for the full day in inches
|
||||
*
|
||||
* Used to update the channel forecastDayX#precipitation
|
||||
*
|
||||
* @return the precipitation for the full day in inches or null if not defined
|
||||
*/
|
||||
public BigDecimal getPrecipitationDayIn() {
|
||||
return (qpf_allday == null) ? null : qpf_allday.in;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of snow for the full day in centimeters
|
||||
*
|
||||
* Used to update the channel forecastDayX#snow
|
||||
*
|
||||
* @return the amount of snow for the full day in centimeters or null if not defined
|
||||
*/
|
||||
public BigDecimal getSnowCm() {
|
||||
return (snow_allday == null) ? null : snow_allday.cm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of snow for the full day in inches
|
||||
*
|
||||
* Used to update the channel forecastDayX#snow
|
||||
*
|
||||
* @return the amount of snow for the full day in inches or null if not defined
|
||||
*/
|
||||
public BigDecimal getSnowIn() {
|
||||
return (snow_allday == null) ? null : snow_allday.in;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum wind direction as a text
|
||||
*
|
||||
* Used to update the channel forecastDayX#maxWindDirection
|
||||
*
|
||||
* @return the maximum wind direction or null if not defined
|
||||
*/
|
||||
public String getMaxWindDirection() {
|
||||
return (maxwind == null) ? null : maxwind.getDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum wind direction in degrees
|
||||
*
|
||||
* Used to update the channel forecastDayX#maxWindDirectionDegrees
|
||||
*
|
||||
* @return the maximum wind direction in degrees or null if not defined
|
||||
*/
|
||||
public BigDecimal getMaxWindDirectionDegrees() {
|
||||
return (maxwind == null) ? null : WeatherUndergroundJsonUtils.convertToBigDecimal(maxwind.getDegrees());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum wind speed in km/h
|
||||
*
|
||||
* Used to update the channel forecastDayX#maxWindSpeed
|
||||
*
|
||||
* @return the maximum wind speed in km/h or null if not defined
|
||||
*/
|
||||
public BigDecimal getMaxWindSpeedKmh() {
|
||||
return (maxwind == null) ? null : maxwind.getKph();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum wind speed in mph
|
||||
*
|
||||
* Used to update the channel forecastDayX#maxWindSpeed
|
||||
*
|
||||
* @return the maximum wind speed in mph or null if not defined
|
||||
*/
|
||||
public BigDecimal getMaxWindSpeedMph() {
|
||||
return (maxwind == null) ? null : maxwind.getMph();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the average wind direction as a text
|
||||
*
|
||||
* Used to update the channel forecastDayX#averageWindDirection
|
||||
*
|
||||
* @return the average wind direction or null if not defined
|
||||
*/
|
||||
public String getAverageWindDirection() {
|
||||
return (avewind == null) ? null : avewind.getDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the average wind direction in degrees
|
||||
*
|
||||
* Used to update the channel forecastDayX#averageWindDirectionDegrees
|
||||
*
|
||||
* @return the average wind direction in degrees or null if not defined
|
||||
*/
|
||||
public BigDecimal getAverageWindDirectionDegrees() {
|
||||
return (avewind == null) ? null : WeatherUndergroundJsonUtils.convertToBigDecimal(avewind.getDegrees());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the average wind speed in km/h
|
||||
*
|
||||
* Used to update the channel forecastDayX#averageWindSpeed
|
||||
*
|
||||
* @return the average wind speed in km/h or null if not defined
|
||||
*/
|
||||
public BigDecimal getAverageWindSpeedKmh() {
|
||||
return (avewind == null) ? null : avewind.getKph();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the average wind speed in mph
|
||||
*
|
||||
* Used to update the channel forecastDayX#averageWindSpeed
|
||||
*
|
||||
* @return the average wind speed in mph or null if not defined
|
||||
*/
|
||||
public BigDecimal getAverageWindSpeedMph() {
|
||||
return (avewind == null) ? null : avewind.getMph();
|
||||
}
|
||||
|
||||
class ForecastDate {
|
||||
|
||||
// Commented members indicate properties returned by the API not used by the binding
|
||||
|
||||
private String epoch;
|
||||
// private String pretty;
|
||||
// private Integer day;
|
||||
// private Integer month;
|
||||
// private Integer year;
|
||||
// private Integer yday;
|
||||
// private Integer hour;
|
||||
// private String min;
|
||||
// private Integer sec;
|
||||
// private String isdst;
|
||||
// private String monthname;
|
||||
// private String monthname_short;
|
||||
// private String weekday_short;
|
||||
// private String weekday;
|
||||
// private String ampm;
|
||||
// private String tz_short;
|
||||
// private String tz_long;
|
||||
|
||||
ForecastDate() {
|
||||
}
|
||||
|
||||
public String getEpoch() {
|
||||
return epoch;
|
||||
}
|
||||
}
|
||||
|
||||
class ForecastTemperature {
|
||||
private String fahrenheit;
|
||||
private String celsius;
|
||||
|
||||
ForecastTemperature() {
|
||||
}
|
||||
|
||||
public String getFahrenheit() {
|
||||
return fahrenheit;
|
||||
}
|
||||
|
||||
public String getCelsius() {
|
||||
return celsius;
|
||||
}
|
||||
}
|
||||
|
||||
class ForecastPrecipitation {
|
||||
private BigDecimal in;
|
||||
private BigDecimal mm;
|
||||
private BigDecimal cm;
|
||||
|
||||
ForecastPrecipitation() {
|
||||
}
|
||||
|
||||
public BigDecimal getIn() {
|
||||
return in;
|
||||
}
|
||||
|
||||
public BigDecimal getMm() {
|
||||
return mm;
|
||||
}
|
||||
|
||||
public BigDecimal getCm() {
|
||||
return cm;
|
||||
}
|
||||
}
|
||||
|
||||
class ForecastWind {
|
||||
private BigDecimal mph;
|
||||
private BigDecimal kph;
|
||||
private String dir;
|
||||
private String degrees;
|
||||
|
||||
ForecastWind() {
|
||||
}
|
||||
|
||||
public BigDecimal getMph() {
|
||||
return mph;
|
||||
}
|
||||
|
||||
public BigDecimal getKph() {
|
||||
return kph;
|
||||
}
|
||||
|
||||
public String getDir() {
|
||||
return dir;
|
||||
}
|
||||
|
||||
public String getDegrees() {
|
||||
return degrees;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal.json;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundJsonLocation} is the Java class used
|
||||
* to map the entry "location" from the JSON response to a Weather
|
||||
* Underground request.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
public class WeatherUndergroundJsonLocation {
|
||||
|
||||
// Commented members indicate properties returned by the API not used by the binding
|
||||
|
||||
private String type;
|
||||
private String country;
|
||||
private String country_iso3166;
|
||||
private String country_name;
|
||||
private String state;
|
||||
private String city;
|
||||
private String tz_short;
|
||||
private String tz_long;
|
||||
private String lat;
|
||||
private String lon;
|
||||
private String zip;
|
||||
private String magic;
|
||||
private String wmo;
|
||||
private String l;
|
||||
private String requesturl;
|
||||
private String wuiurl;
|
||||
// private Object nearby_weather_stations;
|
||||
|
||||
public WeatherUndergroundJsonLocation() {
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
public String getCountryIso3166() {
|
||||
return country_iso3166;
|
||||
}
|
||||
|
||||
public String getCountryName() {
|
||||
return country_name;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public String getTzShort() {
|
||||
return tz_short;
|
||||
}
|
||||
|
||||
public String getTzLong() {
|
||||
return tz_long;
|
||||
}
|
||||
|
||||
public String getLat() {
|
||||
return lat;
|
||||
}
|
||||
|
||||
public String getLon() {
|
||||
return lon;
|
||||
}
|
||||
|
||||
public String getZip() {
|
||||
return zip;
|
||||
}
|
||||
|
||||
public String getMagic() {
|
||||
return magic;
|
||||
}
|
||||
|
||||
public String getWmo() {
|
||||
return wmo;
|
||||
}
|
||||
|
||||
public String getL() {
|
||||
return l;
|
||||
}
|
||||
|
||||
public URL getRequesturl() {
|
||||
return WeatherUndergroundJsonUtils.getValidUrl(requesturl);
|
||||
}
|
||||
|
||||
public URL getWuiurl() {
|
||||
return WeatherUndergroundJsonUtils.getValidUrl(wuiurl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal.json;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundJsonResponse} is the Java class used
|
||||
* to map the entry "response" from the JSON response to a Weather Underground
|
||||
* request.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
public class WeatherUndergroundJsonResponse {
|
||||
|
||||
// Commented members indicate properties returned by the API not used by the binding
|
||||
|
||||
// private String version;
|
||||
// private String termsofService;
|
||||
// private Object features;
|
||||
private WeatherUndergroundJsonError error;
|
||||
|
||||
public WeatherUndergroundJsonResponse() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error type returned by the Weather Underground service
|
||||
*
|
||||
* @return the error type or null if no error
|
||||
*/
|
||||
public String getErrorType() {
|
||||
return (error == null) ? null : error.getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error description returned by the Weather Underground service
|
||||
*
|
||||
* @return the error description or null if no error
|
||||
*/
|
||||
public String getErrorDescription() {
|
||||
return (error == null) ? null : error.getDescription();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundJsonSimpleForecast} is the Java class used
|
||||
* to map the entry "forecast.simpleforecast" from the JSON response
|
||||
* to a Weather Underground request.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
public class WeatherUndergroundJsonSimpleForecast {
|
||||
|
||||
private List<WeatherUndergroundJsonForecastDay> forecastday;
|
||||
|
||||
public WeatherUndergroundJsonSimpleForecast() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link WeatherUndergroundJsonForecastDay} object for a given day
|
||||
*
|
||||
* @return the {@link WeatherUndergroundJsonForecastDay} object for the day
|
||||
*/
|
||||
public WeatherUndergroundJsonForecastDay getForecastDay(int day) {
|
||||
for (WeatherUndergroundJsonForecastDay forecast : forecastday) {
|
||||
if (forecast.getPeriod().intValue() == day) {
|
||||
return forecast;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.weatherunderground.internal.json;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link WeatherUndergroundJsonUtils} class contains utilities methods.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
public class WeatherUndergroundJsonUtils {
|
||||
|
||||
private static final String TREND_UP = "up";
|
||||
private static final String TREND_DOWN = "down";
|
||||
private static final String TREND_STABLE = "stable";
|
||||
|
||||
/**
|
||||
* Convert a string representing an Epoch value into a Calendar object
|
||||
*
|
||||
* @param value the Epoch value as a string
|
||||
*
|
||||
* @return the ZonedDateTime object representing the date and time of the Epoch
|
||||
* or null in case of conversion error
|
||||
*/
|
||||
public static ZonedDateTime convertToZonedDateTime(String value, ZoneId zoneId) {
|
||||
if (isValid(value)) {
|
||||
try {
|
||||
Instant epochSeconds = Instant.ofEpochSecond(Long.valueOf(value));
|
||||
return ZonedDateTime.ofInstant(epochSeconds, zoneId);
|
||||
} catch (DateTimeException e) {
|
||||
LoggerFactory.getLogger(WeatherUndergroundJsonUtils.class).debug("Cannot convert {} to ZonedDateTime",
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string representing an integer value into an Integer object
|
||||
*
|
||||
* @param value the integer value as a string
|
||||
*
|
||||
* @return the Integer object representing the value or null in case of conversion error
|
||||
*/
|
||||
public static Integer convertToInteger(String value) {
|
||||
Integer result = null;
|
||||
if (isValid(value)) {
|
||||
try {
|
||||
result = Integer.valueOf(value.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
LoggerFactory.getLogger(WeatherUndergroundJsonUtils.class).debug("Cannot convert {} to Integer", value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string representing a decimal value into a BigDecimal object
|
||||
*
|
||||
* @param value the decimal value as a string
|
||||
*
|
||||
* @return the BigDecimal object representing the value or null in case of conversion error
|
||||
*/
|
||||
public static BigDecimal convertToBigDecimal(String value) {
|
||||
BigDecimal result = null;
|
||||
if (isValid(value)) {
|
||||
result = new BigDecimal(value.trim());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean isValid(String value) {
|
||||
return (value != null) && !value.isEmpty() && !value.equalsIgnoreCase("N/A") && !value.equalsIgnoreCase("NA")
|
||||
&& !value.equals("-") && !value.equals("--");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string representing a URL into a URL object
|
||||
*
|
||||
* @param url the URL as a string
|
||||
*
|
||||
* @return the URL object representing the URL or null in case of invalid URL
|
||||
*/
|
||||
public static URL getValidUrl(String url) {
|
||||
URL validUrl = null;
|
||||
if (url != null && !url.trim().isEmpty()) {
|
||||
try {
|
||||
validUrl = new URL(url.trim());
|
||||
} catch (MalformedURLException e) {
|
||||
}
|
||||
}
|
||||
return validUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string representing a decimal value into a pressure trend constant
|
||||
*
|
||||
* @param value the decimal value as a string
|
||||
*
|
||||
* @return the pressure trend constant representing the value or null in case of conversion error
|
||||
*/
|
||||
public static String convertToTrend(String value) {
|
||||
String result = null;
|
||||
if (isValid(value)) {
|
||||
try {
|
||||
int val = Integer.valueOf(value.trim());
|
||||
if (val < 0) {
|
||||
result = TREND_DOWN;
|
||||
} else if (val > 0) {
|
||||
result = TREND_UP;
|
||||
} else {
|
||||
result = TREND_STABLE;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
LoggerFactory.getLogger(WeatherUndergroundJsonUtils.class).debug("Cannot convert {} to Integer", value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="weatherunderground" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>WeatherUnderground Binding</name>
|
||||
<description>The Weather Underground Binding requests the Weather Underground Service to show weather data.</description>
|
||||
<author>Laurent Garnier</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,13 @@
|
||||
# Thing status description
|
||||
offline.comm-error-invalid-api-key = The API key is invalid.
|
||||
offline.comm-error-running-request = An error occurred while running the Weather Underground request.
|
||||
offline.comm-error-parsing-response = An error occurred while parsing the response to the Weather Underground request.
|
||||
offline.comm-error-response = An error was detected in the response to the Weather Underground request; please check the logs for more details.
|
||||
offline.conf-error-missing-apikey = The "API key" parameter must be configured.
|
||||
offline.conf-error-missing-location = The "location" parameter must be configured.
|
||||
offline.conf-error-syntax-language = The value of the "language" parameter must contain 2 letters.
|
||||
offline.conf-error-min-refresh = The minimum value of the "refresh interval" parameter must be 5 minutes.
|
||||
offline.uri-error = Could not create URI to fetch data, please check if the location parameter is correct.
|
||||
|
||||
# Discovery result
|
||||
discovery.weatherunderground.weather.local.label = Local Weather
|
||||
@@ -0,0 +1,138 @@
|
||||
# binding
|
||||
binding.weatherunderground.name = Extension WeatherUnderground
|
||||
binding.weatherunderground.description = L'extension Weather Underground interroge le service Weather Underground pour récupérer des données météo.
|
||||
|
||||
# thing types
|
||||
thing-type.weatherunderground.bridge.label = Connexion au service
|
||||
thing-type.weatherunderground.bridge.description = Repr<E9>sente une connexion au service Weather Underground.
|
||||
thing-type.weatherunderground.weather.label = Informations météo
|
||||
thing-type.weatherunderground.weather.description = Présente diverses données météo fournies par le service Weather Underground.
|
||||
|
||||
# thing type configuration
|
||||
thing-type.config.weatherunderground.bridge.apikey.label = Clé d'accès
|
||||
thing-type.config.weatherunderground.bridge.apikey.description = La clé d'accès au service Weather Underground.
|
||||
thing-type.config.weatherunderground.weather.location.label = Emplacement des données météo
|
||||
thing-type.config.weatherunderground.weather.location.description = Plusieurs syntaxes sont possibles. Merci de consulter la documentation de l'extension pour plus d'information.
|
||||
thing-type.config.weatherunderground.weather.language.label = Langue
|
||||
thing-type.config.weatherunderground.weather.language.description = La langue à utiliser par le service Weather Underground.
|
||||
thing-type.config.weatherunderground.weather.language.option.LI = Anglais britannique
|
||||
thing-type.config.weatherunderground.weather.language.option.NL = Néerlandais
|
||||
thing-type.config.weatherunderground.weather.language.option.EN = Anglais
|
||||
thing-type.config.weatherunderground.weather.language.option.FR = Français
|
||||
thing-type.config.weatherunderground.weather.language.option.FC = Français canadien
|
||||
thing-type.config.weatherunderground.weather.language.option.DL = Allemand
|
||||
thing-type.config.weatherunderground.weather.language.option.IT = Italien
|
||||
thing-type.config.weatherunderground.weather.language.option.BR = Portugais
|
||||
thing-type.config.weatherunderground.weather.language.option.RU = Russe
|
||||
thing-type.config.weatherunderground.weather.language.option.SP = Espagnol
|
||||
thing-type.config.weatherunderground.weather.refresh.label = Fréquence de rafraîchissement
|
||||
thing-type.config.weatherunderground.weather.refresh.description = La fréquence de rafraîchissement des données en minutes.
|
||||
|
||||
# channel group types
|
||||
channel-group-type.weatherunderground.current.label = Météo actuelle
|
||||
channel-group-type.weatherunderground.current.description = La météo actuelle.
|
||||
channel-group-type.weatherunderground.forecast.label = Météo prévue
|
||||
channel-group-type.weatherunderground.forecast.description = La météo prévue.
|
||||
|
||||
# channel groups
|
||||
thing-type.weatherunderground.weather.group.forecastToday.label = Météo d'aujourd'hui
|
||||
thing-type.weatherunderground.weather.group.forecastToday.description = La météo prévue aujourd'hui.
|
||||
thing-type.weatherunderground.weather.group.forecastTomorrow.label = Météo de demain
|
||||
thing-type.weatherunderground.weather.group.forecastTomorrow.description = La météo prévue demain.
|
||||
thing-type.weatherunderground.weather.group.forecastDay2.label = Météo dans 2 jours
|
||||
thing-type.weatherunderground.weather.group.forecastDay2.description = La météo prévue dans 2 jours.
|
||||
thing-type.weatherunderground.weather.group.forecastDay3.label = Météo dans 3 jours
|
||||
thing-type.weatherunderground.weather.group.forecastDay3.description = La météo prévue dans 3 jours.
|
||||
thing-type.weatherunderground.weather.group.forecastDay4.label = Météo dans 4 jours
|
||||
thing-type.weatherunderground.weather.group.forecastDay4.description = La météo prévue dans 4 jours.
|
||||
thing-type.weatherunderground.weather.group.forecastDay5.label = Météo dans 5 jours
|
||||
thing-type.weatherunderground.weather.group.forecastDay5.description = La météo prévue dans 5 jours.
|
||||
thing-type.weatherunderground.weather.group.forecastDay6.label = Météo dans 6 jours
|
||||
thing-type.weatherunderground.weather.group.forecastDay6.description = La météo prévue dans 6 jours.
|
||||
thing-type.weatherunderground.weather.group.forecastDay7.label = Météo dans 7 jours
|
||||
thing-type.weatherunderground.weather.group.forecastDay7.description = La météo prévue dans 7 jours.
|
||||
thing-type.weatherunderground.weather.group.forecastDay8.label = Météo dans 8 jours
|
||||
thing-type.weatherunderground.weather.group.forecastDay8.description = La météo prévue dans 8 jours.
|
||||
thing-type.weatherunderground.weather.group.forecastDay9.label = Météo dans 9 jours
|
||||
thing-type.weatherunderground.weather.group.forecastDay9.description = La météo prévue dans 9 jours.
|
||||
|
||||
# channel types
|
||||
channel-type.weatherunderground.location.label = Emplacement d'observation
|
||||
channel-type.weatherunderground.location.description = L'emplacement d'observation de la météo.
|
||||
channel-type.weatherunderground.stationId.label = ID station
|
||||
channel-type.weatherunderground.stationId.description = L'identifiant de la station météo.
|
||||
channel-type.weatherunderground.observationTime.label = Heure d'observation
|
||||
channel-type.weatherunderground.observationTime.description = La date et l'heure d'observation de la météo.
|
||||
channel-type.weatherunderground.forecastTime.label = Heure des prévisions
|
||||
channel-type.weatherunderground.forecastTime.description = La date et l'heure des prévisions météo.
|
||||
channel-type.weatherunderground.currentConditions.label = Conditions actuelles
|
||||
channel-type.weatherunderground.currentConditions.description = Les conditions météo actuelles.
|
||||
channel-type.weatherunderground.forecastConditions.label = Conditions prévues
|
||||
channel-type.weatherunderground.forecastConditions.description = Les conditions météo prévues.
|
||||
channel-type.weatherunderground.minTemperature.label = Température minimale
|
||||
channel-type.weatherunderground.minTemperature.description = La température minimale.
|
||||
channel-type.weatherunderground.maxTemperature.label = Température maximale
|
||||
channel-type.weatherunderground.maxTemperature.description = La température maximale.
|
||||
channel-type.weatherunderground.dewPoint.label = Température du point de rosée
|
||||
channel-type.weatherunderground.dewPoint.description = La température du point de rosée.
|
||||
channel-type.weatherunderground.heatIndex.label = Indice de chaleur
|
||||
channel-type.weatherunderground.heatIndex.description = L'indice de chaleur.
|
||||
channel-type.weatherunderground.windChill.label = Température de refroidissement du vent
|
||||
channel-type.weatherunderground.windChill.description = La température de refroidissement du vent.
|
||||
channel-type.weatherunderground.feelingTemperature.label = Température ressentie
|
||||
channel-type.weatherunderground.feelingTemperature.description = La température ressentie.
|
||||
channel-type.weatherunderground.relativeHumidity.label = Humidité relative
|
||||
channel-type.weatherunderground.relativeHumidity.description = l'humidité relative prévue.
|
||||
channel-type.weatherunderground.windDirection.label = Direction du vent
|
||||
channel-type.weatherunderground.windDirection.description = La direction du vent.
|
||||
channel-type.weatherunderground.maxWindDirection.label = Direction du vent max
|
||||
channel-type.weatherunderground.maxWindDirection.description = La direction du vent le plus fort.
|
||||
channel-type.weatherunderground.averageWindDirection.label = Direction moyenne du vent
|
||||
channel-type.weatherunderground.averageWindDirection.description = La direction moyenne du vent.
|
||||
channel-type.weatherunderground.maxWindDirection-degrees.label = Direction du vent max (angle)
|
||||
channel-type.weatherunderground.maxWindDirection-degrees.description = La direction du vent le plus fort sous la forme d'un angle.
|
||||
channel-type.weatherunderground.averageWindDirection-degrees.label = Direction moyenne du vent (angle)
|
||||
channel-type.weatherunderground.averageWindDirection-degrees.description = La direction moyenne du vent sous la forme d'un angle.
|
||||
channel-type.weatherunderground.maxWindSpeed.label = Vitesse max du vent
|
||||
channel-type.weatherunderground.maxWindSpeed.description = La vitesse maximum du vent.
|
||||
channel-type.weatherunderground.averageWindSpeed.label = Vitesse moyenne du vent
|
||||
channel-type.weatherunderground.averageWindSpeed.description = La vitesse moyenne du vent.
|
||||
channel-type.weatherunderground.windGust.label = Vitesse du vent en rafale
|
||||
channel-type.weatherunderground.windGust.description = La vitesse du vent en rafale.
|
||||
channel-type.weatherunderground.pressureTrend.label = Tendance pression atmosphérique
|
||||
channel-type.weatherunderground.pressureTrend.description = Tendance d'évolution de la pression atmosphérique
|
||||
channel-type.weatherunderground.pressureTrend.state.option.up = A la hausse
|
||||
channel-type.weatherunderground.pressureTrend.state.option.stable = Stable
|
||||
channel-type.weatherunderground.pressureTrend.state.option.down = A la baisse
|
||||
channel-type.weatherunderground.visibility.label = Distance de visibilité
|
||||
channel-type.weatherunderground.visibility.description = La distance de visibilité.
|
||||
channel-type.weatherunderground.solarRadiation.label = Rayonnement solaire
|
||||
channel-type.weatherunderground.solarRadiation.description = L'énergie rayonnante émise par le soleil.
|
||||
channel-type.weatherunderground.UVIndex.label = Indice UV
|
||||
channel-type.weatherunderground.UVIndex.description = L'indice UV.
|
||||
channel-type.weatherunderground.rainDay.label = Chutes de pluie dans la journée
|
||||
channel-type.weatherunderground.rainDay.description = Les chutes de pluie sur toute la journée.
|
||||
channel-type.weatherunderground.rainHour.label = Chutes de pluie dans l'heure
|
||||
channel-type.weatherunderground.rainHour.description = Les chutes de pluie sur la dernière heure.
|
||||
channel-type.weatherunderground.snow.label = Chutes de neige dans la journée
|
||||
channel-type.weatherunderground.snow.description = Les chutes de neige sur toute la journée.
|
||||
channel-type.weatherunderground.probaPrecipitation.label = Probabilité de précipitation
|
||||
channel-type.weatherunderground.probaPrecipitation.description = La probabilité de précipitation.
|
||||
channel-type.weatherunderground.icon.label = Icône météo
|
||||
channel-type.weatherunderground.icon.description = L'icône représentant les conditions météo.
|
||||
channel-type.weatherunderground.iconKey.label = Clé de l'icône météo
|
||||
channel-type.weatherunderground.iconKey.description = La clé utilisée dans l'URL de l'icône météo.
|
||||
|
||||
# Thing status description
|
||||
offline.comm-error-invalid-api-key = La clé d''accès est invalide.
|
||||
offline.comm-error-running-request = Une erreur est survenue lors de l''exécution de la requête Weather Underground.
|
||||
offline.comm-error-parsing-response = Une erreur est survenue lors de l''analyse de la réponse à la requête Weather Underground.
|
||||
offline.comm-error-response = Une erreur a été détectée dans la réponse à la requête Weather Underground; merci de consulter les logs pour plus de détails.
|
||||
offline.conf-error-missing-apikey = Le paramètre "clé d''accès" doit être configuré.
|
||||
offline.conf-error-missing-location = Le paramètre "emplacement des données météo" doit être configuré.
|
||||
offline.conf-error-syntax-language = La valeur du paramètre "Langue" doit contenir 2 lettres.
|
||||
offline.conf-error-min-refresh = La valeur minimale du paramètre "fréquence de rafraîchissement" doit être de 5 minutes.
|
||||
offline.uri-error = Echec création de l'URI de récupération des données; merci de vérifier la syntaxe du paramètre "emplacement des données météo".
|
||||
|
||||
# Discovery result
|
||||
discovery.weatherunderground.weather.local.label = Météo locale
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="weatherunderground"
|
||||
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">
|
||||
<!-- Weatherunderground Bridge -->
|
||||
<bridge-type id="bridge">
|
||||
<label>Weatherunderground Bridge</label>
|
||||
<description>The Weather Underground bridge represents a connection to the Weather Underground service</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="apikey" type="text" required="true">
|
||||
<context>password</context>
|
||||
<label>API Key</label>
|
||||
<description>API key to access the Weather Underground service</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,459 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="weatherunderground"
|
||||
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">
|
||||
|
||||
<!-- WeatherUnderground Binding -->
|
||||
<thing-type id="weather">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Weather Information</label>
|
||||
<description>Provides various weather data from the Weather Underground service</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="current" typeId="current"/>
|
||||
<channel-group id="forecastToday" typeId="forecast">
|
||||
<label>Weather Forecast Today</label>
|
||||
<description>This is the weather forecast for today</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastTomorrow" typeId="forecast">
|
||||
<label>Weather Forecast Tomorrow</label>
|
||||
<description>This is the weather forecast for tomorrow</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay2" typeId="forecast">
|
||||
<label>Weather Forecast Day 2</label>
|
||||
<description>This is the weather forecast in two days</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay3" typeId="forecast">
|
||||
<label>Weather Forecast Day 3</label>
|
||||
<description>This is the weather forecast in three days</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay4" typeId="forecast">
|
||||
<label>Weather Forecast Day 4</label>
|
||||
<description>This is the weather forecast in four days</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay5" typeId="forecast">
|
||||
<label>Weather Forecast Day 5</label>
|
||||
<description>This is the weather forecast in five days</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay6" typeId="forecast">
|
||||
<label>Weather Forecast Day 6</label>
|
||||
<description>This is the weather forecast in six days</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay7" typeId="forecast">
|
||||
<label>Weather Forecast Day 7</label>
|
||||
<description>This is the weather forecast in seven days</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay8" typeId="forecast">
|
||||
<label>Weather Forecast Day 8</label>
|
||||
<description>This is the weather forecast in eight days</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay9" typeId="forecast">
|
||||
<label>Weather Forecast Day 9</label>
|
||||
<description>This is the weather forecast in nine days</description>
|
||||
</channel-group>
|
||||
</channel-groups>
|
||||
|
||||
<config-description>
|
||||
<parameter name="location" type="text" required="true">
|
||||
<label>Location of Weather Information</label>
|
||||
<description>Multiple syntaxes are supported. Please read the binding documentation for more information</description>
|
||||
</parameter>
|
||||
<parameter name="language" type="text" required="false">
|
||||
<label>Language</label>
|
||||
<description>Language to be used by the Weather Underground service</description>
|
||||
<options>
|
||||
<option value="AF">Afrikaans</option>
|
||||
<option value="AL">Albanian</option>
|
||||
<option value="AR">Arabic</option>
|
||||
<option value="HY">Armenian</option>
|
||||
<option value="AZ">Azerbaijan</option>
|
||||
<option value="EU">Basque</option>
|
||||
<option value="BY">Belarusian</option>
|
||||
<option value="BU">Bulgarian</option>
|
||||
<option value="LI">British English</option>
|
||||
<option value="MY">Burmese</option>
|
||||
<option value="CA">Catalan</option>
|
||||
<option value="CN">Chinese - Simplified</option>
|
||||
<option value="TW">Chinese - Traditional</option>
|
||||
<option value="CR">Croatian</option>
|
||||
<option value="CZ">Czech</option>
|
||||
<option value="DK">Danish</option>
|
||||
<option value="DV">Dhivehi</option>
|
||||
<option value="NL">Dutch</option>
|
||||
<option value="EN">English</option>
|
||||
<option value="EO">Esperanto</option>
|
||||
<option value="ET">Estonian</option>
|
||||
<option value="FA">Farsi</option>
|
||||
<option value="FI">Finnish</option>
|
||||
<option value="FR">French</option>
|
||||
<option value="FC">French Canadian</option>
|
||||
<option value="GZ">Galician</option>
|
||||
<option value="DL">German</option>
|
||||
<option value="KA">Georgian</option>
|
||||
<option value="GR">Greek</option>
|
||||
<option value="GU">Gujarati</option>
|
||||
<option value="HT">Haitian Creole</option>
|
||||
<option value="IL">Hebrew</option>
|
||||
<option value="HI">Hindi</option>
|
||||
<option value="HU">Hungarian</option>
|
||||
<option value="IS">Icelandic</option>
|
||||
<option value="IO">Ido</option>
|
||||
<option value="ID">Indonesian</option>
|
||||
<option value="IR">Irish Gaelic</option>
|
||||
<option value="IT">Italian</option>
|
||||
<option value="JP">Japanese</option>
|
||||
<option value="JW">Javanese</option>
|
||||
<option value="KM">Khmer</option>
|
||||
<option value="KR">Korean</option>
|
||||
<option value="KU">Kurdish</option>
|
||||
<option value="LA">Latin</option>
|
||||
<option value="LV">Latvian</option>
|
||||
<option value="LT">Lithuanian</option>
|
||||
<option value="ND">Low German</option>
|
||||
<option value="MK">Macedonian</option>
|
||||
<option value="MT">Maltese</option>
|
||||
<option value="GM">Mandinka</option>
|
||||
<option value="MI">Maori</option>
|
||||
<option value="MR">Marathi</option>
|
||||
<option value="MN">Mongolian</option>
|
||||
<option value="NO">Norwegian</option>
|
||||
<option value="OC">Occitan</option>
|
||||
<option value="PS">Pashto</option>
|
||||
<option value="GN">Plautdietsch</option>
|
||||
<option value="PL">Polish</option>
|
||||
<option value="BR">Portuguese</option>
|
||||
<option value="PA">Punjabi</option>
|
||||
<option value="RO">Romanian</option>
|
||||
<option value="RU">Russian</option>
|
||||
<option value="SR">Serbian</option>
|
||||
<option value="SK">Slovak</option>
|
||||
<option value="SL">Slovenian</option>
|
||||
<option value="SP">Spanish</option>
|
||||
<option value="SI">Swahili</option>
|
||||
<option value="SW">Swedish</option>
|
||||
<option value="CH">Swiss</option>
|
||||
<option value="TL">Tagalog</option>
|
||||
<option value="TT">Tatarish</option>
|
||||
<option value="TH">Thai</option>
|
||||
<option value="TR">Turkish</option>
|
||||
<option value="TK">Turkmen</option>
|
||||
<option value="UA">Ukrainian</option>
|
||||
<option value="UZ">Uzbek</option>
|
||||
<option value="VU">Vietnamese</option>
|
||||
<option value="CY">Welsh</option>
|
||||
<option value="SN">Wolof</option>
|
||||
<option value="JI">Yiddish - transliterated</option>
|
||||
<option value="YI">Yiddish - unicode</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" min="5" required="false" unit="min">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Specifies the refresh interval in minutes.</description>
|
||||
<default>30</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-group-type id="current">
|
||||
<label>Current Weather</label>
|
||||
<description>This is the current weather</description>
|
||||
<channels>
|
||||
<channel id="location" typeId="location"/>
|
||||
<channel id="stationId" typeId="stationId"/>
|
||||
<channel id="observationTime" typeId="observationTime"/>
|
||||
<channel id="conditions" typeId="currentConditions"/>
|
||||
<channel id="temperature" typeId="system.outdoor-temperature"/>
|
||||
<channel id="relativeHumidity" typeId="system.atmospheric-humidity"/>
|
||||
<channel id="windDirection" typeId="windDirection"/>
|
||||
<channel id="windDirectionDegrees" typeId="system.wind-direction"/>
|
||||
<channel id="windSpeed" typeId="system.wind-speed"/>
|
||||
<channel id="windGust" typeId="windGust"/>
|
||||
<channel id="pressure" typeId="system.barometric-pressure"/>
|
||||
<channel id="pressureTrend" typeId="pressureTrend"/>
|
||||
<channel id="dewPoint" typeId="dewPoint"/>
|
||||
<channel id="heatIndex" typeId="heatIndex"/>
|
||||
<channel id="windChill" typeId="windChill"/>
|
||||
<channel id="feelingTemperature" typeId="feelingTemperature"/>
|
||||
<channel id="visibility" typeId="visibility"/>
|
||||
<channel id="solarRadiation" typeId="solarRadiation"/>
|
||||
<channel id="UVIndex" typeId="UVIndex"/>
|
||||
<channel id="precipitationDay" typeId="rainDay"/>
|
||||
<channel id="precipitationHour" typeId="rainHour"/>
|
||||
<channel id="icon" typeId="icon"/>
|
||||
<channel id="iconKey" typeId="iconKey"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="forecast">
|
||||
<label>Weather Forecast</label>
|
||||
<description>This is the weather forecast</description>
|
||||
<channels>
|
||||
<channel id="forecastTime" typeId="forecastTime"/>
|
||||
<channel id="conditions" typeId="forecastConditions"/>
|
||||
<channel id="minTemperature" typeId="minTemperature"/>
|
||||
<channel id="maxTemperature" typeId="maxTemperature"/>
|
||||
<channel id="relativeHumidity" typeId="relativeHumidity"/>
|
||||
<channel id="probaPrecipitation" typeId="probaPrecipitation"/>
|
||||
<channel id="precipitationDay" typeId="rainDay"/>
|
||||
<channel id="snow" typeId="snow"/>
|
||||
<channel id="maxWindDirection" typeId="maxWindDirection"/>
|
||||
<channel id="maxWindDirectionDegrees" typeId="maxWindDirection-degrees"/>
|
||||
<channel id="maxWindSpeed" typeId="maxWindSpeed"/>
|
||||
<channel id="averageWindDirection" typeId="averageWindDirection"/>
|
||||
<channel id="averageWindDirectionDegrees" typeId="averageWindDirection-degrees"/>
|
||||
<channel id="averageWindSpeed" typeId="averageWindSpeed"/>
|
||||
<channel id="icon" typeId="icon"/>
|
||||
<channel id="iconKey" typeId="iconKey"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-type id="location" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Observation Location</label>
|
||||
<description>Weather observation location</description>
|
||||
<state readOnly="true" pattern="%s"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="stationId" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Station Id</label>
|
||||
<description>Weather station identifier</description>
|
||||
<state readOnly="true" pattern="%s"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="observationTime" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Observation Time</label>
|
||||
<description>Observation date and time</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecastTime" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Forecast Time</label>
|
||||
<description>Forecast date and time</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="currentConditions">
|
||||
<item-type>String</item-type>
|
||||
<label>Current Conditions</label>
|
||||
<description>Weather current conditions</description>
|
||||
<state readOnly="true" pattern="%s"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecastConditions">
|
||||
<item-type>String</item-type>
|
||||
<label>Forecast Conditions</label>
|
||||
<description>Weather forecast conditions</description>
|
||||
<state readOnly="true" pattern="%s"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minTemperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Minimum Temperature</label>
|
||||
<description>Minimum temperature</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxTemperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Maximum Temperature</label>
|
||||
<description>Maximum temperature</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dewPoint" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Dew Point Temperature</label>
|
||||
<description>Dew Point temperature</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="heatIndex" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Heat Index</label>
|
||||
<description>Heat index</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="windChill" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Wind Chill Temperature</label>
|
||||
<description>Wind chill temperature</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="feelingTemperature" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Feeling Temperature</label>
|
||||
<description>Feeling temperature</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="relativeHumidity">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Relative Humidity</label>
|
||||
<description>Forecast relative humidity</description>
|
||||
<category>Humidity</category>
|
||||
<state readOnly="true" min="0" max="100" pattern="%d %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="windDirection">
|
||||
<item-type>String</item-type>
|
||||
<label>Wind Direction</label>
|
||||
<description>Wind direction</description>
|
||||
<state readOnly="true" pattern="%s">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxWindDirection" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Maximum Wind Direction</label>
|
||||
<description>Maximum wind direction</description>
|
||||
<state readOnly="true" pattern="%s">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="averageWindDirection" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Average Wind Direction</label>
|
||||
<description>Average wind direction</description>
|
||||
<state readOnly="true" pattern="%s">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxWindDirection-degrees" advanced="true">
|
||||
<item-type>Number:Angle</item-type>
|
||||
<label>Maximum Wind Direction (angle)</label>
|
||||
<description>Maximum wind direction as an angle</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" min="0" max="360" pattern="%.0f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="averageWindDirection-degrees" advanced="true">
|
||||
<item-type>Number:Angle</item-type>
|
||||
<label>Average Wind Direction (angle)</label>
|
||||
<description>Average wind direction as an angle</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" min="0" max="360" pattern="%.0f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxWindSpeed">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Maximum Wind Speed</label>
|
||||
<description>Maximum wind speed</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="averageWindSpeed">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Average Wind Speed</label>
|
||||
<description>Average wind speed</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="windGust">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Wind Gust</label>
|
||||
<description>Wind gust</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pressureTrend" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Pressure Trend</label>
|
||||
<description>Pressure evolution trend (up, down, stable)</description>
|
||||
<category>Pressure</category>
|
||||
<state readOnly="true" pattern="%s">
|
||||
<options>
|
||||
<option value="up">up</option>
|
||||
<option value="stable">stable</option>
|
||||
<option value="down">down</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="visibility" advanced="true">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Visibility</label>
|
||||
<description>Visibility</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="solarRadiation" advanced="true">
|
||||
<item-type>Number:Intensity</item-type>
|
||||
<label>Solar Radiation</label>
|
||||
<description>Solar radiation</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="UVIndex" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>UV Index</label>
|
||||
<description>UV Index</description>
|
||||
<state readOnly="true" pattern="%.1f">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rainDay">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rain Fall Day</label>
|
||||
<description>Rain fall during the day</description>
|
||||
<category>Rain</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rainHour">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rain Fall Hour</label>
|
||||
<description>Rain fall during the last hour</description>
|
||||
<category>Rain</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="snow">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Snow Fall</label>
|
||||
<description>Snow fall</description>
|
||||
<category>Rain</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="probaPrecipitation">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Precipitation Probability</label>
|
||||
<description>Probability of precipitation</description>
|
||||
<state readOnly="true" min="0" max="100" pattern="%d %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="icon" advanced="true">
|
||||
<item-type>Image</item-type>
|
||||
<label>Weather Icon</label>
|
||||
<description>Icon representing the weather conditions</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="iconKey" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Weather Icon Key</label>
|
||||
<description>Key used in the icon URL</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user