added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.airquality/.classpath
Normal file
32
bundles/org.openhab.binding.airquality/.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.airquality/.project
Normal file
23
bundles/org.openhab.binding.airquality/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.airquality</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>
|
||||
13
bundles/org.openhab.binding.airquality/NOTICE
Normal file
13
bundles/org.openhab.binding.airquality/NOTICE
Normal file
@@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
202
bundles/org.openhab.binding.airquality/README.md
Normal file
202
bundles/org.openhab.binding.airquality/README.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# Air Quality Binding
|
||||
|
||||
This binding uses the [AQIcn.org service](https://aqicn.org) for providing air quality information for any location worldwide.
|
||||
|
||||
The World Air Quality Index project is a social enterprise project started in 2007.
|
||||
Its mission is to promote Air Pollution awareness and provide a unified Air Quality information for the whole world.
|
||||
|
||||
The project is proving a transparent Air Quality information for more than 70 countries, covering more than 9000 stations in 600 major cities, via those two websites: [aqicn.org](https://aqicn.org) and [waqi.info](https://waqi.info).
|
||||
|
||||
To use this binding, you first need to [register and get your API token](https://aqicn.org/data-platform/token/).
|
||||
|
||||
## Supported Things
|
||||
|
||||
There is exactly one supported thing type, which represents the air quality information for an observation location.
|
||||
It has the `aqi` id.
|
||||
Of course, you can add multiple Things, e.g. for measuring AQI for different locations.
|
||||
|
||||
## Discovery
|
||||
|
||||
Local Air Quality can be autodiscovered based on system location.
|
||||
You will have complete default configuration with your apiKey.
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
The binding has no configuration options, all configuration is done at Thing level.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The thing has a few configuration parameters:
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------------------------------------------------------------------|
|
||||
| apikey | Data-platform token to access the AQIcn.org service. Mandatory. |
|
||||
| location | Geo coordinates to be considered by the service. |
|
||||
| stationId | Unique ID of the measuring station. |
|
||||
| refresh | Refresh interval in minutes. Optional, the default value is 60 minutes. |
|
||||
|
||||
For the location parameter, the following syntax is allowed (comma separated latitude and longitude):
|
||||
|
||||
```java
|
||||
37.8,-122.4
|
||||
37.8255,-122.456
|
||||
```
|
||||
|
||||
If you always want to receive data from specific station and you know its unique ID, you can enter it instead of the coordinates.
|
||||
|
||||
This `stationId` can be found by using the following link:
|
||||
https://api.waqi.info/search/?token=TOKEN&keyword=NAME, replacing TOKEN by your apikey and NAME by the station you are looking for.
|
||||
|
||||
## Channels
|
||||
|
||||
The AirQuality information that is retrieved is available as these channels:
|
||||
|
||||
| Channel ID | Item Type | Description |
|
||||
|-----------------|----------------------|----------------------------------------------|
|
||||
| aqiLevel | Number | Air Quality Index |
|
||||
| aqiColor | Color | Color associated to given AQI Index. |
|
||||
| aqiDescription | String | AQI Description |
|
||||
| locationName | String | Nearest measuring station location |
|
||||
| stationId | Number | Measuring station ID |
|
||||
| stationLocation | Location | Latitude/longitude of measuring station |
|
||||
| pm25 | Number | Fine particles pollution level (PM2.5) |
|
||||
| pm10 | Number | Coarse dust particles pollution level (PM10) |
|
||||
| o3 | Number | Ozone level (O3) |
|
||||
| no2 | Number | Nitrogen Dioxide level (NO2) |
|
||||
| co | Number | Carbon monoxide level (CO) |
|
||||
| so2 | Number | Sulfur dioxide level (SO2) |
|
||||
| observationTime | DateTime | Observation date and time |
|
||||
| temperature | Number:Temperature | Temperature in Celsius degrees |
|
||||
| pressure | Number:Pressure | Pressure level |
|
||||
| humidity | Number:Dimensionless | Humidity level |
|
||||
| dominentpol | String | Dominent Polutor |
|
||||
|
||||
`AQI Description` item provides a human-readable output that can be interpreted e.g. by MAP transformation.
|
||||
|
||||
*Note that channels like* `pm25`, `pm10`, `o3`, `no2`, `co`, `so2` *can sometimes return* `UNDEF` *value due to the fact that some stations don't provide measurements for them.*
|
||||
|
||||
## Full Example
|
||||
|
||||
airquality.map:
|
||||
|
||||
```text
|
||||
-=-
|
||||
UNDEF=No data
|
||||
NULL=No data
|
||||
NO_DATA=No data
|
||||
GOOD=Good
|
||||
MODERATE=Moderate
|
||||
UNHEALTHY_FOR_SENSITIVE=Unhealthy for sensitive groups
|
||||
UNHEALTHY=Unhealthy
|
||||
VERY_UNHEALTHY=Very unhealthy
|
||||
HAZARDOUS=Hazardous
|
||||
```
|
||||
|
||||
airquality.things:
|
||||
|
||||
```java
|
||||
airquality:aqi:home "AirQuality" @ "Krakow" [ apikey="XXXXXXXXXXXX", location="50.06465,19.94498", refresh=60 ]
|
||||
airquality:aqi:warsaw "AirQuality in Warsaw" [ apikey="XXXXXXXXXXXX", location="52.22,21.01", refresh=60 ]
|
||||
airquality:aqi:brisbane "AirQuality in Brisbane" [ apikey="XXXXXXXXXXXX", stationId=5115 ]
|
||||
```
|
||||
|
||||
airquality.items:
|
||||
|
||||
```java
|
||||
Group AirQuality <flow>
|
||||
|
||||
Number Aqi_Level "Air Quality Index" <flow> (AirQuality) { channel="airquality:aqi:home:aqiLevel" }
|
||||
String Aqi_Description "AQI Level [MAP(airquality.map):%s]" <flow> (AirQuality) { channel="airquality:aqi:home:aqiDescription" }
|
||||
|
||||
Number Aqi_Pm25 "PM\u2082\u2085 Level" <line> (AirQuality) { channel="airquality:aqi:home:pm25" }
|
||||
Number Aqi_Pm10 "PM\u2081\u2080 Level" <line> (AirQuality) { channel="airquality:aqi:home:pm10" }
|
||||
Number Aqi_O3 "O\u2083 Level" <line> (AirQuality) { channel="airquality:aqi:home:o3" }
|
||||
Number Aqi_No2 "NO\u2082 Level" <line> (AirQuality) { channel="airquality:aqi:home:no2" }
|
||||
Number Aqi_Co "CO Level" <line> (AirQuality) { channel="airquality:aqi:home:co" }
|
||||
Number Aqi_So2 "SO\u2082 Level" <line> (AirQuality) { channel="airquality:aqi:home:so2" }
|
||||
|
||||
String Aqi_LocationName "Measuring Location" <settings> (AirQuality) { channel="airquality:aqi:home:locationName" }
|
||||
Location Aqi_StationGeo "Station Location" <office> (AirQuality) { channel="airquality:aqi:home:stationLocation" }
|
||||
Number Aqi_StationId "Station ID" <pie> (AirQuality) { channel="airquality:aqi:home:stationId" }
|
||||
DateTime Aqi_ObservationTime "Time of observation [%1$tH:%1$tM]" <clock> (AirQuality) { channel="airquality:aqi:home:observationTime" }
|
||||
|
||||
Number:Temperature Aqi_Temperature "Temperature" <temperature> (AirQuality) { channel="airquality:aqi:home:temperature" }
|
||||
Number:Pressure Aqi_Pressure "Pressure" <pressure> (AirQuality) { channel="airquality:aqi:home:pressure" }
|
||||
Number:DimensionLess Aqi_Humidity "Humidity" <humidity> (AirQuality) { channel="airquality:aqi:home:humidity" }
|
||||
```
|
||||
|
||||
airquality.sitemap:
|
||||
|
||||
```perl
|
||||
sitemap airquality label="Air Quality" {
|
||||
Frame {
|
||||
Text item=Aqi_Level valuecolor=[
|
||||
Aqi_Level=="-"="lightgray",
|
||||
Aqi_Level>=300="#7e0023",
|
||||
>=201="#660099",
|
||||
>=151="#cc0033",
|
||||
>=101="#ff9933",
|
||||
>=51="#ffde33",
|
||||
>=0="#009966"
|
||||
]
|
||||
Text item=Aqi_Description valuecolor=[
|
||||
Aqi_Description=="HAZARDOUS"="#7e0023",
|
||||
=="VERY_UNHEALTHY"="#660099",
|
||||
=="UNHEALTHY"="#cc0033",
|
||||
=="UNHEALTHY_FOR_SENSITIVE"="#ff9933",
|
||||
=="MODERATE"="#ffde33",
|
||||
=="GOOD"="#009966"
|
||||
]
|
||||
}
|
||||
|
||||
Frame {
|
||||
Text item=Aqi_Pm25
|
||||
Text item=Aqi_Pm10
|
||||
Text item=Aqi_O3
|
||||
Text item=Aqi_No2
|
||||
Text item=Aqi_Co
|
||||
Text item=Aqi_So2
|
||||
}
|
||||
|
||||
Frame {
|
||||
Text item=Aqi_LocationName
|
||||
Text item=Aqi_ObservationTime
|
||||
Text item=Aqi_Temperature
|
||||
Text item=Aqi_Pressure
|
||||
Text item=Aqi_Humidity
|
||||
}
|
||||
|
||||
Frame label="Station Location" {
|
||||
Mapview item=Aqi_StationGeo height=10
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
airquality.rules:
|
||||
|
||||
```java
|
||||
rule "Change lamp color to reflect Air Quality"
|
||||
when
|
||||
Item Aqi_Description changed
|
||||
then
|
||||
var String hsb
|
||||
|
||||
switch Aqi_Description.state {
|
||||
case "HAZARDOUS":
|
||||
hsb = "343,100,49"
|
||||
case "VERY_UNHEALTHY":
|
||||
hsb = "280,100,60"
|
||||
case "UNHEALTHY":
|
||||
hsb = "345,100,80"
|
||||
case "UNHEALTHY_FOR_SENSITIVE":
|
||||
hsb = "30,80,100"
|
||||
case "MODERATE":
|
||||
hsb = "50,80,100"
|
||||
case "GOOD":
|
||||
hsb = "160,100,60"
|
||||
}
|
||||
|
||||
Lamp_Color.sendCommand(hsb)
|
||||
end
|
||||
```
|
||||
17
bundles/org.openhab.binding.airquality/pom.xml
Normal file
17
bundles/org.openhab.binding.airquality/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.airquality</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Airquality Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.airquality-${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-airquality" description="Air Quality Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.airquality/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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.airquality.internal;
|
||||
|
||||
import static org.openhab.core.library.unit.MetricPrefix.HECTO;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Dimensionless;
|
||||
import javax.measure.quantity.Pressure;
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link AirQualityBinding} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Kuba Wolanin - Initial contribution
|
||||
* @author Łukasz Dywicki - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AirQualityBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "airquality";
|
||||
public static final String LOCAL = "local";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_AQI = new ThingTypeUID(BINDING_ID, "aqi");
|
||||
|
||||
// List of all Channel id's
|
||||
public static final String AQI = "aqiLevel";
|
||||
public static final String AQI_COLOR = "aqiColor";
|
||||
public static final String AQIDESCRIPTION = "aqiDescription";
|
||||
public static final String PM25 = "pm25";
|
||||
public static final String PM10 = "pm10";
|
||||
public static final String O3 = "o3";
|
||||
public static final String NO2 = "no2";
|
||||
public static final String CO = "co";
|
||||
public static final String SO2 = "so2";
|
||||
public static final String LOCATIONNAME = "locationName";
|
||||
public static final String STATIONLOCATION = "stationLocation";
|
||||
public static final String STATIONID = "stationId";
|
||||
public static final String OBSERVATIONTIME = "observationTime";
|
||||
public static final String TEMPERATURE = "temperature";
|
||||
public static final String PRESSURE = "pressure";
|
||||
public static final String HUMIDITY = "humidity";
|
||||
public static final String DOMINENTPOL = "dominentpol";
|
||||
|
||||
public static final State GOOD = new StringType("GOOD");
|
||||
public static final State MODERATE = new StringType("MODERATE");
|
||||
public static final State UNHEALTHY_FOR_SENSITIVE = new StringType("UNHEALTHY_FOR_SENSITIVE");
|
||||
public static final State UNHEALTHY = new StringType("UNHEALTHY");
|
||||
public static final State VERY_UNHEALTHY = new StringType("VERY_UNHEALTHY");
|
||||
public static final State HAZARDOUS = new StringType("HAZARDOUS");
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_AQI);
|
||||
public static final Set<String> SUPPORTED_CHANNEL_IDS = Stream.of(AQI, AQIDESCRIPTION, PM25, PM10, O3, NO2, CO, SO2,
|
||||
LOCATIONNAME, STATIONLOCATION, STATIONID, OBSERVATIONTIME, TEMPERATURE, PRESSURE, HUMIDITY)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Units of measurement of the data delivered by the API
|
||||
public static final Unit<Temperature> API_TEMPERATURE_UNIT = SIUnits.CELSIUS;
|
||||
public static final Unit<Dimensionless> API_HUMIDITY_UNIT = SmartHomeUnits.PERCENT;
|
||||
public static final Unit<Pressure> API_PRESSURE_UNIT = HECTO(SIUnits.PASCAL);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.airquality.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link AirQualityConfiguration} is the class used to match the
|
||||
* thing configuration.
|
||||
*
|
||||
* @author Kuba Wolanin - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AirQualityConfiguration {
|
||||
|
||||
public static final String LOCATION = "location";
|
||||
|
||||
public String apikey = "";
|
||||
public String location = "";
|
||||
public @Nullable Integer stationId;
|
||||
public int refresh = 60;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.airquality.internal;
|
||||
|
||||
import static org.openhab.binding.airquality.internal.AirQualityBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.airquality.internal.handler.AirQualityHandler;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* The {@link AirQualityHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Kuba Wolanin - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.airquality")
|
||||
@NonNullByDefault
|
||||
public class AirQualityHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final Gson gson = new Gson();
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
@Activate
|
||||
public AirQualityHandlerFactory(final @Reference TimeZoneProvider timeZoneProvider) {
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_AQI.equals(thingTypeUID)) {
|
||||
return new AirQualityHandler(thing, gson, timeZoneProvider);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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.airquality.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.airquality.internal.AirQualityBindingConstants.*;
|
||||
import static org.openhab.binding.airquality.internal.AirQualityConfiguration.LOCATION;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
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.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Modified;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AirQualityDiscoveryService} creates things based on the configured location.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial Contribution
|
||||
*/
|
||||
@Component(service = DiscoveryService.class, configurationPid = "discovery.airquality")
|
||||
@NonNullByDefault
|
||||
public class AirQualityDiscoveryService extends AbstractDiscoveryService {
|
||||
private final Logger logger = LoggerFactory.getLogger(AirQualityDiscoveryService.class);
|
||||
|
||||
private static final int DISCOVER_TIMEOUT_SECONDS = 10;
|
||||
private static final int LOCATION_CHANGED_CHECK_INTERVAL = 60;
|
||||
|
||||
private final LocationProvider locationProvider;
|
||||
private @Nullable ScheduledFuture<?> discoveryJob;
|
||||
private @Nullable PointType previousLocation;
|
||||
|
||||
/**
|
||||
* Creates a AirQualityDiscoveryService with enabled autostart.
|
||||
*/
|
||||
@Activate
|
||||
public AirQualityDiscoveryService(@Reference LocationProvider locationProvider) {
|
||||
super(SUPPORTED_THING_TYPES, DISCOVER_TIMEOUT_SECONDS, true);
|
||||
this.locationProvider = locationProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate(@Nullable Map<String, @Nullable Object> configProperties) {
|
||||
super.activate(configProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modified
|
||||
protected void modified(@Nullable Map<String, @Nullable Object> configProperties) {
|
||||
super.modified(configProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Starting Air Quality 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
if (discoveryJob == null) {
|
||||
discoveryJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||
PointType currentLocation = locationProvider.getLocation();
|
||||
if (currentLocation != null && !Objects.equals(currentLocation, previousLocation)) {
|
||||
logger.debug("Location has been changed from {} to {}: Creating new discovery results",
|
||||
previousLocation, currentLocation);
|
||||
createResults(currentLocation);
|
||||
previousLocation = currentLocation;
|
||||
}
|
||||
}, 0, LOCATION_CHANGED_CHECK_INTERVAL, TimeUnit.SECONDS);
|
||||
logger.debug("Scheduled Air Qualitylocation-changed job every {} seconds", LOCATION_CHANGED_CHECK_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
public void createResults(PointType location) {
|
||||
ThingUID localAirQualityThing = new ThingUID(THING_TYPE_AQI, LOCAL);
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(LOCATION, String.format("%s,%s", location.getLatitude(), location.getLongitude()));
|
||||
thingDiscovered(DiscoveryResultBuilder.create(localAirQualityThing).withLabel("Local Air Quality")
|
||||
.withProperties(properties).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
logger.debug("Stopping Air Quality background discovery");
|
||||
ScheduledFuture<?> job = this.discoveryJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
if (job.cancel(true)) {
|
||||
discoveryJob = null;
|
||||
logger.debug("Stopped Air Quality background discovery");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* 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.airquality.internal.handler;
|
||||
|
||||
import static org.openhab.binding.airquality.internal.AirQualityBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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.airquality.internal.AirQualityConfiguration;
|
||||
import org.openhab.binding.airquality.internal.json.AirQualityJsonData;
|
||||
import org.openhab.binding.airquality.internal.json.AirQualityJsonResponse;
|
||||
import org.openhab.binding.airquality.internal.json.AirQualityJsonResponse.ResponseStatus;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
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.HSBType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link AirQualityHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Kuba Wolanin - Initial contribution
|
||||
* @author Łukasz Dywicki - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AirQualityHandler extends BaseThingHandler {
|
||||
private static final String URL = "http://api.waqi.info/feed/%QUERY%/?token=%apikey%";
|
||||
private static final int REQUEST_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(30);
|
||||
private final Logger logger = LoggerFactory.getLogger(AirQualityHandler.class);
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
private int retryCounter = 0;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
public AirQualityHandler(Thing thing, Gson gson, TimeZoneProvider timeZoneProvider) {
|
||||
super(thing);
|
||||
this.gson = gson;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing Air Quality handler.");
|
||||
|
||||
AirQualityConfiguration config = getConfigAs(AirQualityConfiguration.class);
|
||||
logger.debug("config apikey = (omitted from logging)");
|
||||
logger.debug("config location = {}", config.location);
|
||||
logger.debug("config stationId = {}", config.stationId);
|
||||
logger.debug("config refresh = {}", config.refresh);
|
||||
|
||||
List<String> errorMsg = new ArrayList<>();
|
||||
|
||||
if (config.apikey.trim().isEmpty()) {
|
||||
errorMsg.add("Parameter 'apikey' is mandatory and must be configured");
|
||||
}
|
||||
if (config.location.trim().isEmpty() && config.stationId == null) {
|
||||
errorMsg.add("Parameter 'location' or 'stationId' is mandatory and must be configured");
|
||||
}
|
||||
if (config.refresh < 30) {
|
||||
errorMsg.add("Parameter 'refresh' must be at least 30 minutes");
|
||||
}
|
||||
|
||||
if (errorMsg.isEmpty()) {
|
||||
ScheduledFuture<?> job = this.refreshJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(this::updateAndPublishData, 0, config.refresh,
|
||||
TimeUnit.MINUTES);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String.join(", ", errorMsg));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAndPublishData() {
|
||||
retryCounter = 0;
|
||||
AirQualityJsonData aqiResponse = getAirQualityData();
|
||||
if (aqiResponse != null) {
|
||||
// Update all channels from the updated AQI data
|
||||
getThing().getChannels().stream().filter(channel -> isLinked(channel.getUID().getId())).forEach(channel -> {
|
||||
String channelId = channel.getUID().getId();
|
||||
State state = getValue(channelId, aqiResponse);
|
||||
updateState(channelId, state);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing the Air Quality handler.");
|
||||
ScheduledFuture<?> job = this.refreshJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
job.cancel(true);
|
||||
refreshJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateAndPublishData();
|
||||
} else {
|
||||
logger.debug("The Air Quality binding is read-only and can not handle command {}", command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build request URL from configuration data
|
||||
*
|
||||
* @return a valid URL for the aqicn.org service
|
||||
*/
|
||||
private String buildRequestURL() {
|
||||
AirQualityConfiguration config = getConfigAs(AirQualityConfiguration.class);
|
||||
|
||||
String location = config.location.trim();
|
||||
Integer stationId = config.stationId;
|
||||
|
||||
String geoStr = "geo:" + location.replace(" ", "").replace(",", ";").replace("\"", "").replace("'", "").trim();
|
||||
|
||||
String urlStr = URL.replace("%apikey%", config.apikey.trim());
|
||||
|
||||
return urlStr.replace("%QUERY%", stationId == null ? geoStr : "@" + stationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request new air quality data to the aqicn.org service
|
||||
*
|
||||
* @param location geo-coordinates from config
|
||||
* @param stationId station ID from config
|
||||
* @return the air quality data object mapping the JSON response or null in case of error
|
||||
*/
|
||||
private @Nullable AirQualityJsonData getAirQualityData() {
|
||||
String errorMsg;
|
||||
|
||||
String urlStr = buildRequestURL();
|
||||
logger.debug("URL = {}", urlStr);
|
||||
|
||||
try {
|
||||
String response = HttpUtil.executeUrl("GET", urlStr, null, null, null, REQUEST_TIMEOUT_MS);
|
||||
logger.debug("aqiResponse = {}", response);
|
||||
AirQualityJsonResponse result = gson.fromJson(response, AirQualityJsonResponse.class);
|
||||
if (result.getStatus() == ResponseStatus.OK) {
|
||||
AirQualityJsonData data = result.getData();
|
||||
String attributions = data.getAttributions();
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, attributions);
|
||||
return data;
|
||||
} else {
|
||||
retryCounter++;
|
||||
if (retryCounter == 1) {
|
||||
logger.warn("Error in aqicn.org, retrying once");
|
||||
return getAirQualityData();
|
||||
}
|
||||
errorMsg = "Missing data sub-object";
|
||||
logger.warn("Error in aqicn.org response: {}", errorMsg);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
errorMsg = e.getMessage();
|
||||
} catch (JsonSyntaxException e) {
|
||||
errorMsg = "Configuration is incorrect";
|
||||
logger.warn("Error running aqicn.org request: {}", errorMsg);
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, errorMsg);
|
||||
return null;
|
||||
}
|
||||
|
||||
public State getValue(String channelId, AirQualityJsonData aqiResponse) {
|
||||
String[] fields = channelId.split("#");
|
||||
|
||||
switch (fields[0]) {
|
||||
case AQI:
|
||||
return new DecimalType(aqiResponse.getAqi());
|
||||
case AQIDESCRIPTION:
|
||||
return getAqiDescription(aqiResponse.getAqi());
|
||||
case PM25:
|
||||
case PM10:
|
||||
case O3:
|
||||
case NO2:
|
||||
case CO:
|
||||
case SO2:
|
||||
double value = aqiResponse.getIaqiValue(fields[0]);
|
||||
return value != -1 ? new DecimalType(value) : UnDefType.UNDEF;
|
||||
case TEMPERATURE:
|
||||
double temp = aqiResponse.getIaqiValue("t");
|
||||
return temp != -1 ? new QuantityType<>(temp, API_TEMPERATURE_UNIT) : UnDefType.UNDEF;
|
||||
case PRESSURE:
|
||||
double press = aqiResponse.getIaqiValue("p");
|
||||
return press != -1 ? new QuantityType<>(press, API_PRESSURE_UNIT) : UnDefType.UNDEF;
|
||||
case HUMIDITY:
|
||||
double hum = aqiResponse.getIaqiValue("h");
|
||||
return hum != -1 ? new QuantityType<>(hum, API_HUMIDITY_UNIT) : UnDefType.UNDEF;
|
||||
case LOCATIONNAME:
|
||||
return new StringType(aqiResponse.getCity().getName());
|
||||
case STATIONID:
|
||||
return new DecimalType(aqiResponse.getStationId());
|
||||
case STATIONLOCATION:
|
||||
return new PointType(aqiResponse.getCity().getGeo());
|
||||
case OBSERVATIONTIME:
|
||||
return new DateTimeType(
|
||||
aqiResponse.getTime().getObservationTime().withZoneSameLocal(timeZoneProvider.getTimeZone()));
|
||||
case DOMINENTPOL:
|
||||
return new StringType(aqiResponse.getDominentPol());
|
||||
case AQI_COLOR:
|
||||
return getAsHSB(aqiResponse.getAqi());
|
||||
default:
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interprets the current aqi value within the ranges;
|
||||
* Returns AQI in a human readable format
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public State getAqiDescription(int index) {
|
||||
if (index >= 300) {
|
||||
return HAZARDOUS;
|
||||
} else if (index >= 201) {
|
||||
return VERY_UNHEALTHY;
|
||||
} else if (index >= 151) {
|
||||
return UNHEALTHY;
|
||||
} else if (index >= 101) {
|
||||
return UNHEALTHY_FOR_SENSITIVE;
|
||||
} else if (index >= 51) {
|
||||
return MODERATE;
|
||||
} else if (index > 0) {
|
||||
return GOOD;
|
||||
}
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
private State getAsHSB(int index) {
|
||||
State state = getAqiDescription(index);
|
||||
if (state == HAZARDOUS) {
|
||||
return HSBType.fromRGB(343, 100, 49);
|
||||
} else if (state == VERY_UNHEALTHY) {
|
||||
return HSBType.fromRGB(280, 100, 60);
|
||||
} else if (state == UNHEALTHY) {
|
||||
return HSBType.fromRGB(345, 100, 80);
|
||||
} else if (state == UNHEALTHY_FOR_SENSITIVE) {
|
||||
return HSBType.fromRGB(30, 80, 100);
|
||||
} else if (state == MODERATE) {
|
||||
return HSBType.fromRGB(50, 80, 100);
|
||||
} else if (state == GOOD) {
|
||||
return HSBType.fromRGB(160, 100, 60);
|
||||
}
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.airquality.internal.json;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link AirQualityJsonCity} is responsible for storing
|
||||
* the "city" node from the waqi.org JSON response
|
||||
*
|
||||
* @author Kuba Wolanin - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AirQualityJsonCity {
|
||||
|
||||
private String name = "";
|
||||
private @Nullable String url;
|
||||
private List<Double> geo = new ArrayList<>();
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public @Nullable String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getGeo() {
|
||||
List<String> list = new ArrayList<>();
|
||||
geo.forEach(item -> list.add(item.toString()));
|
||||
return String.join(",", list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 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.airquality.internal.json;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link AirQualityJsonData} is responsible for storing
|
||||
* the "data" node from the waqi.org JSON response
|
||||
*
|
||||
* @author Kuba Wolanin - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AirQualityJsonData {
|
||||
|
||||
private int aqi;
|
||||
private int idx;
|
||||
|
||||
private @NonNullByDefault({}) AirQualityJsonTime time;
|
||||
private @NonNullByDefault({}) AirQualityJsonCity city;
|
||||
private List<Attribute> attributions = new ArrayList<>();
|
||||
private Map<String, @Nullable AirQualityValue> iaqi = new HashMap<>();
|
||||
private String dominentpol = "";
|
||||
|
||||
/**
|
||||
* Air Quality Index
|
||||
*
|
||||
* @return {Integer}
|
||||
*/
|
||||
public int getAqi() {
|
||||
return aqi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Measuring Station ID
|
||||
*
|
||||
* @return {Integer}
|
||||
*/
|
||||
public int getStationId() {
|
||||
return idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives "time" node from the "data" object in JSON response
|
||||
*
|
||||
* @return {AirQualityJsonTime}
|
||||
*/
|
||||
public AirQualityJsonTime getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives "city" node from the "data" object in JSON response
|
||||
*
|
||||
* @return {AirQualityJsonCity}
|
||||
*/
|
||||
public AirQualityJsonCity getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects a list of attributions (vendors making data available)
|
||||
* and transforms it into readable string.
|
||||
* Currently displayed in Thing Status description when ONLINE
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
public String getAttributions() {
|
||||
List<String> list = new ArrayList<>();
|
||||
attributions.forEach(item -> list.add(item.getName()));
|
||||
return "Attributions : " + String.join(", ", list);
|
||||
}
|
||||
|
||||
public String getDominentPol() {
|
||||
return dominentpol;
|
||||
}
|
||||
|
||||
public double getIaqiValue(String key) {
|
||||
AirQualityValue result = iaqi.get(key);
|
||||
if (result != null) {
|
||||
return result.getValue();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.airquality.internal.json;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link AirQualityJsonResponse} is the Java class used to map the JSON
|
||||
* response to the aqicn.org request.
|
||||
*
|
||||
* @author Kuba Wolanin - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AirQualityJsonResponse {
|
||||
|
||||
public static enum ResponseStatus {
|
||||
NONE,
|
||||
@SerializedName("error")
|
||||
ERROR,
|
||||
@SerializedName("ok")
|
||||
OK;
|
||||
}
|
||||
|
||||
private ResponseStatus status = ResponseStatus.NONE;
|
||||
|
||||
@SerializedName("data")
|
||||
private @NonNullByDefault({}) AirQualityJsonData data;
|
||||
|
||||
public ResponseStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public AirQualityJsonData getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.airquality.internal.json;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link AirQualityJsonTime} is responsible for storing
|
||||
* the "time" node from the waqi.org JSON response
|
||||
*
|
||||
* @author Kuba Wolanin - Initial contribution
|
||||
* @author Gaël L'hopital - Use ZonedDateTime instead of Calendar
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AirQualityJsonTime {
|
||||
|
||||
@SerializedName("s")
|
||||
private String dateString = "";
|
||||
|
||||
@SerializedName("tz")
|
||||
private String timeZone = "";
|
||||
|
||||
private String iso = "";
|
||||
|
||||
/**
|
||||
* Get observation time
|
||||
*
|
||||
* @return {ZonedDateTime}
|
||||
*/
|
||||
public ZonedDateTime getObservationTime() throws DateTimeParseException {
|
||||
return ZonedDateTime.parse(iso);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.airquality.internal.json;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Wrapper type around values reported by aqicn index values.
|
||||
*
|
||||
* @author Łukasz Dywicki - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AirQualityValue {
|
||||
|
||||
@SerializedName("v")
|
||||
private double value;
|
||||
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.airquality.internal.json;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Attribute representation.
|
||||
*
|
||||
* @author Łukasz Dywicki - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Attribute {
|
||||
|
||||
private @NonNullByDefault({}) String name;
|
||||
private @Nullable String url;
|
||||
private @Nullable String logo;
|
||||
|
||||
public @Nullable String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public @Nullable String getLogo() {
|
||||
return logo;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="airquality" 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>Air Quality Binding</name>
|
||||
<description>Measure Air Quality Index and details about pollution particles for a given location</description>
|
||||
<author>Kuba Wolanin</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,30 @@
|
||||
# binding
|
||||
binding.airquality.name = Extension Air Quality
|
||||
binding.airquality.description = Indice de qualité de l'air et informations sur la pollution aux particules pour un emplacement donné.
|
||||
|
||||
# thing types
|
||||
thing-type.airquality.aqi.label = Qualité de l'air
|
||||
thing-type.airquality.aqi.description = Fournit diverses données sur la qualité de l'air du World Air Quality Project. Pour recevoir les données, vous devez créer un compte sur http://aqicn.org/data-platform/token/ pour obtenir votre token API.
|
||||
|
||||
channel-type.airquality.aqiLevel.label = Indice
|
||||
channel-type.airquality.aqiDescription.label = Appréciation
|
||||
channel-type.airquality.observationTime.label = Heure d'observation
|
||||
channel-type.airquality.temperature.label = Température
|
||||
channel-type.airquality.pressure.label = Pression
|
||||
channel-type.airquality.humidity.label = Humidité
|
||||
channel-type.airquality.dominentpol.label = Polluant principal
|
||||
|
||||
|
||||
channel-type.airquality.aqiDescription.state.option.GOOD = Bonne
|
||||
channel-type.airquality.aqiDescription.state.option.MODERATE = Modérée
|
||||
channel-type.airquality.aqiDescription.state.option.UNHEALTHY_FOR_SENSITIVE = Mauvaise pour les groupes sensibles
|
||||
channel-type.airquality.aqiDescription.state.option.UNHEALTHY = Mauvaise
|
||||
channel-type.airquality.aqiDescription.state.option.VERY_UNHEALTHY = Très mauvaise
|
||||
channel-type.airquality.aqiDescription.state.option.HAZARDOUS = Dangereuse
|
||||
|
||||
channel-type.airquality.dominentPol.state.option.pm25 = Particules fines
|
||||
channel-type.airquality.dominentPol.state.option.pm10 = Particules de poussière
|
||||
channel-type.airquality.dominentPol.state.option.o3 = Ozone
|
||||
channel-type.airquality.dominentPol.state.option.no2 = Dioxyde d'azote
|
||||
channel-type.airquality.dominentPol.state.option.co = Monoxyde de carbone
|
||||
channel-type.airquality.dominentPol.state.option.so2 = Dioxyde de soufre
|
||||
@@ -0,0 +1,214 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="airquality"
|
||||
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">
|
||||
|
||||
<!-- Air Quality Thing -->
|
||||
<thing-type id="aqi">
|
||||
<label>Air Quality</label>
|
||||
<description>
|
||||
Provides various air quality data from the World Air Quality Project.
|
||||
In order to receive the data, you
|
||||
must register an account on http://aqicn.org/data-platform/token/ and get your API
|
||||
token.
|
||||
</description>
|
||||
|
||||
<channels>
|
||||
<channel id="aqiLevel" typeId="aqiLevel"/>
|
||||
<channel id="aqiColor" typeId="aqiColor"/>
|
||||
<channel id="aqiDescription" typeId="aqiDescription"/>
|
||||
<channel id="pm25" typeId="pm25"/>
|
||||
<channel id="pm10" typeId="pm10"/>
|
||||
<channel id="o3" typeId="o3"/>
|
||||
<channel id="no2" typeId="no2"/>
|
||||
<channel id="co" typeId="co"/>
|
||||
<channel id="so2" typeId="so2"/>
|
||||
<channel id="locationName" typeId="locationName"/>
|
||||
<channel id="stationLocation" typeId="stationLocation"/>
|
||||
<channel id="stationId" typeId="stationId"/>
|
||||
<channel id="observationTime" typeId="observationTime"/>
|
||||
<channel id="temperature" typeId="temperature"/>
|
||||
<channel id="pressure" typeId="pressure"/>
|
||||
<channel id="humidity" typeId="humidity"/>
|
||||
<channel id="dominentpol" typeId="dominentPol"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="apikey" type="text" required="true">
|
||||
<context>password</context>
|
||||
<label>API Key</label>
|
||||
<description>Data-platform token to access the AQIcn.org service</description>
|
||||
</parameter>
|
||||
<parameter name="location" type="text" required="false"
|
||||
pattern="^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?)[,]\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$">
|
||||
<label>Location</label>
|
||||
<description>Your geo coordinates separated with comma (e.g. "37.8,-122.4").</description>
|
||||
</parameter>
|
||||
<parameter name="stationId" type="integer" required="false">
|
||||
<label>Station ID</label>
|
||||
<description>Fill only in case you want to receive data from the specific station</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" min="30" required="false" unit="min">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Specifies the refresh interval in minutes.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>60</default>
|
||||
<unitLabel>Minutes</unitLabel>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="aqiLevel">
|
||||
<item-type>Number</item-type>
|
||||
<label>Air Quality Index</label>
|
||||
<description></description>
|
||||
<category>Air Quality Index</category>
|
||||
<state readOnly="true" pattern="%d" min="0" max="500"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="aqiDescription">
|
||||
<item-type>String</item-type>
|
||||
<label>AQI Description</label>
|
||||
<description></description>
|
||||
<category>AQI Description</category>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="GOOD">Good</option>
|
||||
<option value="MODERATE">Moderate</option>
|
||||
<option value="UNHEALTHY_FOR_SENSITIVE">Unhealthy for Sensitive Groups</option>
|
||||
<option value="UNHEALTHY">Unhealthy</option>
|
||||
<option value="VERY_UNHEALTHY">Very Unhealthy</option>
|
||||
<option value="HAZARDOUS">Hazardous</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pm25">
|
||||
<item-type>Number</item-type>
|
||||
<label>PM2.5</label>
|
||||
<description>Fine particles pollution level</description>
|
||||
<category>PM2.5</category>
|
||||
<state readOnly="true" pattern="%d" min="0" max="500"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pm10">
|
||||
<item-type>Number</item-type>
|
||||
<label>PM10</label>
|
||||
<description>Coarse dust particles pollution level</description>
|
||||
<category>PM10</category>
|
||||
<state readOnly="true" pattern="%d" min="0" max="500"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="o3">
|
||||
<item-type>Number</item-type>
|
||||
<label>O3</label>
|
||||
<description>Ozone level</description>
|
||||
<category>O3</category>
|
||||
<state readOnly="true" pattern="%.1f" min="0" max="500"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="no2">
|
||||
<item-type>Number</item-type>
|
||||
<label>NO2</label>
|
||||
<description>Nitrogen dioxide level</description>
|
||||
<category>NO2</category>
|
||||
<state readOnly="true" pattern="%.1f" min="0" max="500"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="co">
|
||||
<item-type>Number</item-type>
|
||||
<label>CO</label>
|
||||
<description>Carbon monoxide level</description>
|
||||
<category>CO</category>
|
||||
<state readOnly="true" pattern="%.1f" min="0" max="500"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="so2">
|
||||
<item-type>Number</item-type>
|
||||
<label>SO2</label>
|
||||
<description>Sulfur dioxide level</description>
|
||||
<category>SO2</category>
|
||||
<state readOnly="true" pattern="%.1f"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="locationName" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Location</label>
|
||||
<description>Nearest measuring station location</description>
|
||||
<category>Location</category>
|
||||
<state readOnly="true" pattern="%s"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="stationLocation" advanced="true">
|
||||
<item-type>Location</item-type>
|
||||
<label>Station Location</label>
|
||||
<description>Location of the measuring station</description>
|
||||
<category>Station Location</category>
|
||||
<state readOnly="true" pattern="%2$s°N,%3$s°W"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="stationId" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Station ID</label>
|
||||
<description>Unique measuring station ID</description>
|
||||
<category>Station ID</category>
|
||||
<state readOnly="true" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="observationTime" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Observation Time</label>
|
||||
<description>Observation date and time</description>
|
||||
<category>Observation time</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="temperature" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<description>Temperature</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pressure" advanced="true">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Pressure</label>
|
||||
<description>Current Pressure</description>
|
||||
<category>Pressure</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="humidity" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Humidity</label>
|
||||
<description>Current humidity</description>
|
||||
<category>Humidity</category>
|
||||
<state readOnly="true" min="0" max="100" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dominentPol">
|
||||
<item-type>String</item-type>
|
||||
<label>Dominent Polutor</label>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="pm25">Fine particles</option>
|
||||
<option value="pm10">Coarse dust particles</option>
|
||||
<option value="o3">Ozone</option>
|
||||
<option value="no2">Nitrogen Dioxide</option>
|
||||
<option value="co">Carbon Monoxide</option>
|
||||
<option value="so2">Sulfur Dioxide</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="aqiColor" advanced="true">
|
||||
<item-type>Color</item-type>
|
||||
<label>AQI Color</label>
|
||||
<description>Color associated to given AQI Index.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user