Skip to content

Commit

Permalink
[AirQuality] Tagging channels, prepared for Crowdin, dynamic channels (
Browse files Browse the repository at this point in the history
…openhab#11192)

* Adding semantic tags
Adding alert level and code refined.

Signed-off-by: Gaël L'hopital <gael@lhopital.org>

* Commiting intermediate work

Signed-off-by: Gaël L'hopital <gael@lhopital.org>

* Complete review of the binding.
Added measures for each pollutant.
Added extensible channel for pollutant sensibility.

Signed-off-by: clinique <gael@lhopital.org>

* Finishing translation

Signed-off-by: Gaël L'hopital <gael@lhopital.org>

* One last code cleansing to be ready to push

Signed-off-by: Gaël L'hopital <gael@lhopital.org>

* Documentation updates

Signed-off-by: Gaël L'hopital <gael@lhopital.org>

* Spotless apply

Signed-off-by: Gaël L'hopital <gael@lhopital.org>

* Correcting conflicting file

Signed-off-by: Gaël L'hopital <gael@lhopital.org>

* Code review correction

Signed-off-by: Gael L'hopital <gael@lhopital.org>

* Introducing a bridge to Api

Signed-off-by: clinique <gael@lhopital.org>

* Code review correction

Signed-off-by: clinique <gael@lhopital.org>
  • Loading branch information
clinique authored and jpg0 committed Nov 10, 2021
1 parent ce254d5 commit 77d7aed
Show file tree
Hide file tree
Showing 31 changed files with 1,391 additions and 715 deletions.
143 changes: 78 additions & 65 deletions bundles/org.openhab.binding.airquality/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,32 @@ To use this binding, you first need to [register and get your API token](https:/

## 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.
Bridge: The binding supports a bridge to connect to the [AQIcn.org service](https://aqicn.org). A bridge uses the thing ID "api".

Station: Represents the air quality information for an observation location.

Of course, you can add multiple Stations, 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.
You will created a Bridge with your apiKey.

## Binding Configuration
## Bridge Configuration

The bridge configuration only holds the api key :

| Parameter | Description |
|-----------|-------------------------------------------------------------------------|
| apiKey | Data-platform token to access the AQIcn.org service. Mandatory. |

The binding has no configuration options, all configuration is done at Thing level.

## Thing Configuration

The thing has a few configuration parameters:
The 'Station' 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. |
Expand All @@ -45,84 +51,95 @@ For the location parameter, the following syntax is allowed (comma separated lat
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.
https://api.waqi.info/search/?token=TOKEN&keyword=NAME, replacing TOKEN by your apiKey and NAME by the station you are looking for.

### Thing properties

Once created, at first execution, the station's properties will be filled with informations gathered from the web service :

- Nearest measuring station location
- Measuring station ID
- Latitude/longitude of measuring station


## Channels

The AirQuality information that is retrieved is available as these channels:
The AirQuality information that is retrieved for a given is available as these channels:

### AQI Channels Group - Global Results

| Channel ID | Item Type | Description |
|-----------------|----------------------|----------------------------------------------|
| alert-level | Number | Alert level (*) associated to AQI Index. |
| index | Number | Air Quality Index |
| timestamp | DateTime | Observation date and time |
| dominent | String | Dominent Pollutant |
| icon | Image | Pictogram associated to alert-level |
| color | Color | Color associated to alert level. |

### Weather Channels Group

| 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 |
| dew-point | Number:Temperature | Dew point temperature |
| wind-speed | Number:Speed | Wind speed |

### Pollutants Channels Group

For each pollutant (PM25, PM10, O3, NO2, CO, SO2) , depending upon availability of the station,
you will be provided with the following informations

| Channel ID | Item Type | Description |
|-----------------|----------------------|----------------------------------------------|
| value | Number:Density | Measured density of the pollutant |
| index | Number | AQI Index of the single pollutant |
| alert-level | Number | Alert level associate to the index |

`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.*
(*) The alert level is described by a color :

| Code | Color | Description |
|------|--------|--------------------------------|
| 0 | Green | Good |
| 1 | Yellow | Moderate |
| 2 | Orange | Unhealthy for Sensitive Groups |
| 3 | Red | Unhealthy |
| 4 | Purple | Very Unhealthy |
| 5 | Maroon | Hazardous |


## 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 ]
Bridge airquality:api:main "Bridge" [apiKey="xxxyyyzzz"] {
station MyHouse "Krakow"[location="50.06465,19.94498", refresh=60]
}
```

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" }
Number Aqi_Level "Air Quality Index" <flow> (AirQuality) { channel="airquality:station:local:aqi#index" }
Number Aqi_Pm25 "PM\u2082\u2085 Level" <line> (AirQuality) { channel="airquality:station:local:pm25#value" }
Number Aqi_Pm10 "PM\u2081\u2080 Level" <line> (AirQuality) { channel="airquality:station:local:pm10#value" }
Number Aqi_O3 "O\u2083 Level" <line> (AirQuality) { channel="airquality:station:local:o3#value" }
Number Aqi_No2 "NO\u2082 Level" <line> (AirQuality) { channel="airquality:station:local:no2#value" }
Number Aqi_Co "CO Level" <line> (AirQuality) { channel="airquality:station:local:co#value" }
Number Aqi_So2 "SO\u2082 Level" <line> (AirQuality) { channel="airquality:station:local:so2#value" }

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" }
DateTime Aqi_ObservationTime "Time of observation [%1$tH:%1$tM]" <clock> (AirQuality) { channel="airquality:station:local:aqi#timestamp" }

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" }
Number:Temperature Aqi_Temperature "Temperature" <temperature> (AirQuality) { channel="airquality:station:local:weather#temperature" }
Number:Pressure Aqi_Pressure "Pressure" <pressure> (AirQuality) { channel="airquality:station:local:weather#pressure" }
Number:Dimensionless Aqi_Humidity "Humidity" <humidity> (AirQuality) { channel="airquality:station:localweather#humidity" }
```

airquality.sitemap:
Expand All @@ -143,7 +160,7 @@ sitemap airquality label="Air Quality" {
Aqi_Description=="HAZARDOUS"="#7e0023",
=="VERY_UNHEALTHY"="#660099",
=="UNHEALTHY"="#cc0033",
=="UNHEALTHY_FOR_SENSITIVE"="#ff9933",
=="UNHEALTHY_FSG"="#ff9933",
=="MODERATE"="#ffde33",
=="GOOD"="#009966"
]
Expand All @@ -159,16 +176,12 @@ sitemap airquality label="Air Quality" {
}

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

```
Expand All @@ -189,7 +202,7 @@ then
hsb = "280,100,60"
case "UNHEALTHY":
hsb = "345,100,80"
case "UNHEALTHY_FOR_SENSITIVE":
case "UNHEALTHY_FSG":
hsb = "30,80,100"
case "MODERATE":
hsb = "50,80,100"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,74 +12,44 @@
*/
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.Units;
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
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class AirQualityBindingConstants {

public static final String BINDING_ID = "airquality";
private 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 thing properties
public static final String ATTRIBUTIONS = "Attributions";
public static final String DISTANCE = "Distance";

// List of all Channel groups id's
public static final String AQI = "aqi";
public static final String SENSITIVE = "sensitive-group";

// 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 INDEX = "index";
public static final String VALUE = "value";
public static final String ALERT_LEVEL = "alert-level";
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 = Units.PERCENT;
public static final Unit<Pressure> API_PRESSURE_UNIT = HECTO(SIUnits.PASCAL);
public static final String DEW_POINT = "dew-point";
public static final String WIND_SPEED = "wind-speed";
public static final String TIMESTAMP = "timestamp";
public static final String DOMINENT = "dominent";
public static final String ICON = "icon";
public static final String COLOR = "color";

// Thing Type UIDs
public static final ThingTypeUID THING_TYPE_STATION = new ThingTypeUID(BINDING_ID, "station");
public static final ThingTypeUID BRIDGE_TYPE_API = new ThingTypeUID(BINDING_ID, "api");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2021 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;

/**
* An exception that occurred while operating the binding
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class AirQualityException extends Exception {
private static final long serialVersionUID = -3398100220952729815L;
private int statusCode = -1;

public AirQualityException(String message, Exception e) {
super(message, e);
}

public AirQualityException(String message) {
super(message);
}

public int getStatusCode() {
return statusCode;
}

@Override
public @Nullable String getMessage() {
String message = super.getMessage();
return message == null ? null
: String.format("Rest call failed: statusCode=%d, message=%s", statusCode, message);
}
}
Loading

0 comments on commit 77d7aed

Please sign in to comment.