(AirQuality) { channel="airquality:aqi:home:humidity" }
+```
+
+airquality.sitemap:
+
+```
+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
+ }
+
+ 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:
+
+```
+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"
+ }
+
+ sendCommand(Lamp_Color, hsb)
+end
+```
diff --git a/addons/binding/org.openhab.binding.airquality/about.html b/addons/binding/org.openhab.binding.airquality/about.html
new file mode 100644
index 0000000000000..eff01772105b9
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/about.html
@@ -0,0 +1,51 @@
+
+
+
+
+About
+
+
+About This Content
+
+<February 15, 2017>
+License
+
+The openHAB community makes available all content in this plug-in ("Content"). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available
+at http://www.eclipse.org/legal/epl-v10.html.
+For purposes of the EPL, "Program" will mean the Content.
+
+If you did not receive this Content directly from the openHAB community, the Content is
+being redistributed by another party ("Redistributor") and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was
+provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at openhab.org.
+
+
+ Third Party Content
+ The Content includes items that have been sourced from third parties as set out below. If you
+ did not receive this Content directly from the openHAB community, the following is provided
+ for informational purposes only, and you should look to the Redistributor's license for
+ terms and conditions of use.
+
+ AQIcn.org API
+ The World Air Quality Index project is an Internet service
+ offered by US Embassies in China that provides weather information for any location worldwide.
+ The usage of the API are subjected to a "acceptable usage" policy:
+
+
+ - All the API are provided for free.
+ - A valid key must be used for accessing the API.
+ - All the API are subjected to quota.
+ - The data from the API can not be sold and included in sold packages.
+ - The data from the API can not be used in paid application or service.
+ - Attribution to the World Air Quality Index Project as well as originating EPA is mandatory.
+ - Public usage by for-profit corporations requires explict agreement wit the World Air Quality Index team.
+ - Public usage by non-profit organization requires prior notification (by email) to the World Air Quality Index team.
+
+
+
+
diff --git a/addons/binding/org.openhab.binding.airquality/build.properties b/addons/binding/org.openhab.binding.airquality/build.properties
new file mode 100644
index 0000000000000..66e21b90751a7
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/build.properties
@@ -0,0 +1,6 @@
+source.. = src/main/java/
+output.. = target/classes
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/,\
+ ESH-INF/
\ No newline at end of file
diff --git a/addons/binding/org.openhab.binding.airquality/pom.xml b/addons/binding/org.openhab.binding.airquality/pom.xml
new file mode 100644
index 0000000000000..cd1bc4f39e2d1
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.binding
+ pom
+ 2.1.0-SNAPSHOT
+
+
+ org.openhab.binding.airquality
+
+ AirQuality Binding
+ eclipse-plugin
+
+
diff --git a/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/AirQualityBindingConstants.java b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/AirQualityBindingConstants.java
new file mode 100644
index 0000000000000..9656f2933e56d
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/AirQualityBindingConstants.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2014-2016 by the respective copyright holders.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.airquality;
+
+import java.util.Set;
+
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * The {@link AirQualityBinding} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Kuba Wolanin - Initial contribution
+ * @author Łukasz Dywicki
+ */
+public class AirQualityBindingConstants {
+
+ public static final String BINDING_ID = "airquality";
+
+ // List of all Thing Type UIDs
+ public final static ThingTypeUID THING_TYPE_AQI = new ThingTypeUID(BINDING_ID, "aqi");
+
+ // List of all Channel id's
+ public final static String AQI = "aqiLevel";
+ public final static String AQIDESCRIPTION = "aqiDescription";
+ public final static String PM25 = "pm25";
+ public final static String PM10 = "pm10";
+ public final static String O3 = "o3";
+ public final static String NO2 = "no2";
+ public final static String CO = "co";
+ public final static String LOCATIONNAME = "locationName";
+ public final static String STATIONLOCATION = "stationLocation";
+ public final static String STATIONID = "stationId";
+ public final static String OBSERVATIONTIME = "observationTime";
+ public final static String TEMPERATURE = "temperature";
+ public final static String PRESSURE = "pressure";
+ public final static String HUMIDITY = "humidity";
+
+ public final static Set SUPPORTED_THING_TYPES_UIDS = ImmutableSet.of(THING_TYPE_AQI);
+ public final static Set SUPPORTED_CHANNEL_IDS = ImmutableSet.of(AQI, AQIDESCRIPTION, PM25, PM10, O3, NO2,
+ CO, LOCATIONNAME, STATIONLOCATION, STATIONID, OBSERVATIONTIME, TEMPERATURE, PRESSURE, HUMIDITY);
+
+}
diff --git a/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/handler/AirQualityHandler.java b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/handler/AirQualityHandler.java
new file mode 100644
index 0000000000000..5f30fe35f843a
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/handler/AirQualityHandler.java
@@ -0,0 +1,322 @@
+/**
+ * Copyright (c) 2014-2016 by the respective copyright holders.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.airquality.handler;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Calendar;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.eclipse.smarthome.core.library.types.DateTimeType;
+import org.eclipse.smarthome.core.library.types.DecimalType;
+import org.eclipse.smarthome.core.library.types.StringType;
+import org.eclipse.smarthome.core.thing.Channel;
+import org.eclipse.smarthome.core.thing.ChannelUID;
+import org.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingStatus;
+import org.eclipse.smarthome.core.thing.ThingStatusDetail;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
+import org.eclipse.smarthome.core.types.Command;
+import org.eclipse.smarthome.core.types.RefreshType;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.UnDefType;
+import org.openhab.binding.airquality.AirQualityBindingConstants;
+import org.openhab.binding.airquality.internal.AirQualityConfiguration;
+import org.openhab.binding.airquality.internal.json.AirQualityJsonResponse;
+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
+ */
+public class AirQualityHandler extends BaseThingHandler {
+
+ private Logger logger = LoggerFactory.getLogger(AirQualityHandler.class);
+
+ private static final String URL = "http://api.waqi.info/feed/%QUERY%/?token=%apikey%";
+
+ private static final int DEFAULT_REFRESH_PERIOD = 30;
+
+ private ScheduledFuture> refreshJob;
+
+ AirQualityJsonResponse aqiResponse;
+
+ private Gson gson;
+
+ public AirQualityHandler(Thing thing) {
+ super(thing);
+ gson = new Gson();
+ }
+
+ @Override
+ public void initialize() {
+ logger.debug("Initializing Air Quality handler.");
+ super.initialize();
+
+ 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);
+
+ boolean validConfig = true;
+ String errorMsg = null;
+
+ if (StringUtils.trimToNull(config.apikey) == null) {
+ errorMsg = "Parameter 'apikey' is mandatory and must be configured";
+ validConfig = false;
+ }
+ if (StringUtils.trimToNull(config.location) == null && config.stationId == null) {
+ errorMsg = "Parameter 'location' or 'stationId' is mandatory and must be configured";
+ validConfig = false;
+ }
+ if (config.refresh != null && config.refresh < 5) {
+ errorMsg = "Parameter 'refresh' must be at least 5 minutes";
+ validConfig = false;
+ }
+
+ if (validConfig) {
+ startAutomaticRefresh();
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMsg);
+ }
+ }
+
+ /**
+ * Start the job refreshing the Air Quality data
+ */
+ private void startAutomaticRefresh() {
+ if (refreshJob == null || refreshJob.isCancelled()) {
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // Request new air quality data to the aqicn.org service
+ aqiResponse = updateAirQualityData();
+
+ // Update all channels from the updated AQI data
+ for (Channel channel : getThing().getChannels()) {
+ updateChannel(channel.getUID().getId(), aqiResponse);
+ }
+ } catch (Exception e) {
+ logger.error("Exception occurred during execution: {}", e.getMessage(), e);
+ }
+ }
+ };
+
+ AirQualityConfiguration config = getConfigAs(AirQualityConfiguration.class);
+ int delay = (config.refresh != null) ? config.refresh.intValue() : DEFAULT_REFRESH_PERIOD;
+ refreshJob = scheduler.scheduleWithFixedDelay(runnable, 0, delay, TimeUnit.MINUTES);
+ }
+ }
+
+ @Override
+ public void dispose() {
+ logger.debug("Disposing the Air Quality handler.");
+
+ if (refreshJob != null && !refreshJob.isCancelled()) {
+ refreshJob.cancel(true);
+ refreshJob = null;
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ if (command instanceof RefreshType) {
+ updateChannel(channelUID.getId(), aqiResponse);
+ } else {
+ logger.debug("The Air Quality binding is read-only and can not handle command {}", command);
+ }
+ }
+
+ /**
+ * Update the channel from the last Air Quality data retrieved
+ *
+ * @param channelId the id identifying the channel to be updated
+ */
+ private void updateChannel(String channelId, AirQualityJsonResponse aqiResponse) {
+ if (isLinked(channelId)) {
+ Object value;
+ try {
+ value = getValue(channelId, aqiResponse);
+ } catch (Exception e) {
+ logger.debug("Station doesn't provide {} measurement", channelId.toUpperCase());
+ return;
+ }
+
+ State state = null;
+ if (value == null) {
+ state = UnDefType.UNDEF;
+ } else if (value instanceof Calendar) {
+ state = new DateTimeType((Calendar) value);
+ } else if (value instanceof BigDecimal) {
+ state = new DecimalType((BigDecimal) value);
+ } else if (value instanceof Integer) {
+ state = new DecimalType(BigDecimal.valueOf(((Integer) value).longValue()));
+ } else if (value instanceof String) {
+ state = new StringType(value.toString());
+ } else {
+ logger.warn("Update channel {}: Unsupported value type {}", channelId,
+ value.getClass().getSimpleName());
+ }
+ logger.debug("Update channel {} with state {} ({})", channelId, (state == null) ? "null" : state.toString(),
+ (value == null) ? "null" : value.getClass().getSimpleName());
+
+ // Update the channel
+ if (state != null) {
+ updateState(channelId, state);
+ }
+ }
+ }
+
+ /**
+ * Get new data from aqicn.org service
+ *
+ * @return {AirQualityJsonResponse}
+ */
+ private AirQualityJsonResponse updateAirQualityData() {
+ AirQualityConfiguration config = getConfigAs(AirQualityConfiguration.class);
+ return getAirQualityData(StringUtils.trimToEmpty(config.location), config.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 AirQualityJsonResponse getAirQualityData(String location, Integer stationId) {
+ AirQualityJsonResponse result = null;
+ boolean resultOk = false;
+ String errorMsg = null;
+
+ try {
+
+ // Build a valid URL for the aqicn.org service
+ AirQualityConfiguration config = getConfigAs(AirQualityConfiguration.class);
+
+ String geoStr = "geo:";
+ geoStr += location.replace(" ", "").replace(",", ";").replace("\"", "").replace("'", "").trim();
+
+ String urlStr = URL.replace("%apikey%", StringUtils.trimToEmpty(config.apikey));
+
+ if (stationId == null) {
+ urlStr = urlStr.replace("%QUERY%", geoStr);
+ } else {
+ urlStr = urlStr.replace("%QUERY%", "@" + stationId);
+ }
+
+ logger.debug("URL = {}", urlStr);
+
+ // Run the HTTP request and get the JSON response from aqicn.org
+ URL url = new URL(urlStr);
+ URLConnection connection = url.openConnection();
+
+ try {
+ String response = IOUtils.toString(connection.getInputStream());
+ logger.debug("aqiResponse = {}", response);
+
+ // Map the JSON response to an object
+ result = gson.fromJson(response, AirQualityJsonResponse.class);
+ } finally {
+ IOUtils.closeQuietly(connection.getInputStream());
+ }
+
+ if (result == null) {
+ errorMsg = "no data returned";
+ } else if (result.getData() != null && result.getStatus() != "error") {
+ resultOk = true;
+ } else {
+ errorMsg = "missing data sub-object";
+ }
+
+ if (!resultOk) {
+ logger.warn("Error in aqicn.org (Air Quality) response: {}", errorMsg);
+ }
+
+ } catch (MalformedURLException e) {
+ errorMsg = e.getMessage();
+ logger.warn("Constructed url is not valid: {}", errorMsg);
+ } catch (JsonSyntaxException e) {
+ errorMsg = "Configuration is incorrect";
+ logger.warn("Error running aqicn.org (Air Quality) request: {}", errorMsg);
+ } catch (IOException | IllegalStateException e) {
+ errorMsg = e.getMessage();
+ }
+
+ // Update the thing status
+ if (resultOk && result != null) {
+ String attributions = result.getData().getAttributions();
+ updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, attributions);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, errorMsg);
+ }
+
+ return resultOk ? result : null;
+ }
+
+ public static Object getValue(String channelId, AirQualityJsonResponse data) throws Exception {
+ String[] fields = StringUtils.split(channelId, "#");
+
+ if (data == null) {
+ return null;
+ }
+
+ String fieldName = fields[0];
+
+ switch (fieldName) {
+ case AirQualityBindingConstants.AQI:
+ return data.getData().getAqi();
+ case AirQualityBindingConstants.AQIDESCRIPTION:
+ return data.getData().getAqiDescription();
+ case AirQualityBindingConstants.PM25:
+ return data.getData().getIaqi().getPm25();
+ case AirQualityBindingConstants.PM10:
+ return data.getData().getIaqi().getPm10();
+ case AirQualityBindingConstants.O3:
+ return data.getData().getIaqi().getO3();
+ case AirQualityBindingConstants.NO2:
+ return data.getData().getIaqi().getNo2();
+ case AirQualityBindingConstants.CO:
+ return data.getData().getIaqi().getCo();
+ case AirQualityBindingConstants.LOCATIONNAME:
+ return data.getData().getCity().getName();
+ case AirQualityBindingConstants.STATIONID:
+ return data.getData().getStationId();
+ case AirQualityBindingConstants.STATIONLOCATION:
+ return data.getData().getCity().getGeo();
+ case AirQualityBindingConstants.OBSERVATIONTIME:
+ return data.getData().getTime().getDateString();
+ case AirQualityBindingConstants.TEMPERATURE:
+ return data.getData().getIaqi().getT();
+ case AirQualityBindingConstants.PRESSURE:
+ return data.getData().getIaqi().getP();
+ case AirQualityBindingConstants.HUMIDITY:
+ return data.getData().getIaqi().getH();
+ }
+
+ return null;
+ }
+
+}
diff --git a/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/AirQualityConfiguration.java b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/AirQualityConfiguration.java
new file mode 100644
index 0000000000000..4d20cfdacb20c
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/AirQualityConfiguration.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2014-2016 by the respective copyright holders.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.airquality.internal;
+
+/**
+ * The {@link AirQualityConfiguration} is the class used to match the
+ * thing configuration.
+ *
+ * @author Kuba Wolanin - Initial contribution
+ */
+public class AirQualityConfiguration {
+
+ public String apikey;
+
+ public String location;
+
+ public Integer stationId;
+
+ public Integer refresh;
+
+}
\ No newline at end of file
diff --git a/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/AirQualityHandlerFactory.java b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/AirQualityHandlerFactory.java
new file mode 100644
index 0000000000000..e6b2c5866ecf7
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/AirQualityHandlerFactory.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2014-2016 by the respective copyright holders.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.airquality.internal;
+
+import static org.openhab.binding.airquality.AirQualityBindingConstants.THING_TYPE_AQI;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory;
+import org.eclipse.smarthome.core.thing.binding.ThingHandler;
+import org.openhab.binding.airquality.handler.AirQualityHandler;
+
+/**
+ * The {@link AirQualityHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Kuba Wolanin - Initial contribution
+ */
+public class AirQualityHandlerFactory extends BaseThingHandlerFactory {
+
+ private final static Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_AQI);
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected ThingHandler createHandler(Thing thing) {
+
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (thingTypeUID.equals(THING_TYPE_AQI)) {
+ return new AirQualityHandler(thing);
+ }
+
+ return null;
+ }
+
+}
diff --git a/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonCity.java b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonCity.java
new file mode 100644
index 0000000000000..fed3657d01bf2
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonCity.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2014-2016 by the respective copyright holders.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.airquality.internal.json;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The {@link AirQualityJsonCity} is responsible for storing
+ * the "city" node from the waqi.org JSON response
+ *
+ * @author Kuba Wolanin - Initial contribution
+ */
+public class AirQualityJsonCity {
+
+ private String name;
+ private String url;
+ private List geo;
+
+ public AirQualityJsonCity() {
+
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getGeo() {
+ List list = new ArrayList();
+ for (int i = 0; i < geo.size(); i++) {
+ list.add(geo.get(i).toString());
+ }
+ return String.join(",", list);
+ }
+
+}
diff --git a/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonData.java b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonData.java
new file mode 100644
index 0000000000000..7ec0b3c17185f
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonData.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright (c) 2014-2016 by the respective copyright holders.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.airquality.internal.json;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The {@link AirQualityJsonData} is responsible for storing
+ * the "data" node from the waqi.org JSON response
+ *
+ * @author Kuba Wolanin - Initial contribution
+ */
+public class AirQualityJsonData {
+
+ private int aqi;
+ private int idx;
+
+ private final static String GOOD = "GOOD";
+ private final static String MODERATE = "MODERATE";
+ private final static String UNHEALTHY_FOR_SENSITIVE = "UNHEALTHY_FOR_SENSITIVE";
+ private final static String UNHEALTHY = "UNHEALTHY";
+ private final static String VERY_UNHEALTHY = "VERY_UNHEALTHY";
+ private final static String HAZARDOUS = "HAZARDOUS";
+ private final static String NO_DATA = "NO_DATA";
+
+ private AirQualityJsonTime time;
+ private AirQualityJsonCity city;
+ private List attributions;
+ private AirQualityJsonIaqi iaqi;
+
+ public AirQualityJsonData() {
+ }
+
+ /**
+ * 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 list = new ArrayList();
+ for (int i = 0; i < attributions.size(); i++) {
+ list.add(attributions.get(i).getName());
+ }
+ return "Attributions: " + String.join(", ", list);
+ }
+
+ /**
+ * Receives "iaqi" node from the "data" object in JSON response
+ *
+ * @return {AirQualityJsonIaqi}
+ */
+ public AirQualityJsonIaqi getIaqi() {
+ return iaqi;
+ }
+
+ /**
+ * Interprets the current aqi value within the ranges;
+ * Returns AQI in a human readable format
+ *
+ * @return
+ */
+ public String getAqiDescription() {
+ if (aqi > 0 && aqi <= 50) {
+ return GOOD;
+ } else if (aqi >= 51 && aqi <= 100) {
+ return MODERATE;
+ } else if (aqi >= 101 && aqi <= 150) {
+ return UNHEALTHY_FOR_SENSITIVE;
+ } else if (aqi >= 151 && aqi <= 200) {
+ return UNHEALTHY;
+ } else if (aqi >= 201 && aqi < 300) {
+ return VERY_UNHEALTHY;
+ } else if (aqi >= 300) {
+ return HAZARDOUS;
+ }
+
+ return NO_DATA;
+ }
+
+}
diff --git a/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonIaqi.java b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonIaqi.java
new file mode 100644
index 0000000000000..0ba713afb4505
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonIaqi.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2014-2016 by the respective copyright holders.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.airquality.internal.json;
+
+import java.math.BigDecimal;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link AirQualityJsonIaqi} is responsible for storing
+ * "iaqi" node from the waqi.org JSON response
+ * It contains information on air pollution particles
+ * as well as some basic weather metrics.
+ *
+ * @author Kuba Wolanin - Initial contribution
+ * @author Łukasz Dywicki
+ */
+public class AirQualityJsonIaqi {
+
+ private AirQualityValue pm25;
+ private AirQualityValue pm10;
+ private AirQualityValue o3;
+ private AirQualityValue no2;
+ private AirQualityValue co;
+ private AirQualityValue t;
+
+ @SerializedName("p")
+ private AirQualityValue pressure;
+ private AirQualityValue h;
+
+ public AirQualityJsonIaqi() {
+
+ }
+
+ public BigDecimal getPm25() {
+ return pm25.getValue();
+ }
+
+ public BigDecimal getPm10() {
+ return pm10.getValue();
+ }
+
+ public BigDecimal getO3() {
+ return o3.getValue();
+ }
+
+ public BigDecimal getNo2() {
+ return no2.getValue();
+ }
+
+ public BigDecimal getCo() {
+ return co.getValue();
+ }
+
+ public BigDecimal getT() {
+ return t.getValue();
+ }
+
+ public BigDecimal getP() {
+ return pressure.getValue();
+ }
+
+ public BigDecimal getH() {
+ return h.getValue();
+ }
+
+}
diff --git a/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonResponse.java b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonResponse.java
new file mode 100644
index 0000000000000..947d23dc26309
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonResponse.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2014-2016 by the respective copyright holders.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.airquality.internal.json;
+
+/**
+ * The {@link AirQualityJsonResponse} is the Java class used to map the JSON
+ * response to the aqicn.org request.
+ *
+ * @author Kuba Wolanin - Initial contribution
+ */
+public class AirQualityJsonResponse {
+
+ private String status;
+
+ private AirQualityJsonData data;
+
+ public AirQualityJsonResponse() {
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public AirQualityJsonData getData() {
+ return data;
+ }
+
+}
diff --git a/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonTime.java b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonTime.java
new file mode 100644
index 0000000000000..4eea94a7dde9e
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityJsonTime.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2014-2016 by the respective copyright holders.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.airquality.internal.json;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+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
+ */
+public class AirQualityJsonTime {
+
+ @SerializedName("s")
+ private String dateString;
+
+ @SerializedName("tz")
+ private String timeZone;
+
+ private final static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ");
+
+ /**
+ * Get Time zone from the JSON Response
+ * in following format: "+0100"
+ *
+ * @return {String}
+ */
+ public String getTimeZone() {
+ return timeZone;
+ }
+
+ /**
+ * Get observation time
+ *
+ * @return {Calendar}
+ * @throws Exception
+ */
+ public Calendar getDateString() throws Exception {
+ Calendar calendar = Calendar.getInstance();
+ Date date = SDF.parse(dateString + timeZone.replace(":", ""));
+ calendar.setTime(date);
+ return calendar;
+ }
+
+}
diff --git a/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityValue.java b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityValue.java
new file mode 100644
index 0000000000000..5c71dfb213827
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/AirQualityValue.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2014-2016 by the respective copyright holders.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.airquality.internal.json;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Wrapper type around values reported by aqicn index values.
+ *
+ * @author Łukasz Dywicki
+ */
+public class AirQualityValue {
+
+ @SerializedName("v")
+ private T value;
+
+ public void setValue(T value) {
+ this.value = value;
+ }
+
+ public T getValue() {
+ return value;
+ }
+}
diff --git a/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/Attribute.java b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/Attribute.java
new file mode 100644
index 0000000000000..80d26844e09ad
--- /dev/null
+++ b/addons/binding/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/json/Attribute.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2014-2016 by the respective copyright holders.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.airquality.internal.json;
+
+/**
+ * Attribute representation.
+ *
+ * @author Łukasz Dywicki
+ */
+public class Attribute {
+
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+}
diff --git a/addons/binding/pom.xml b/addons/binding/pom.xml
index fb60e32d698fc..4e984997dc1b1 100644
--- a/addons/binding/pom.xml
+++ b/addons/binding/pom.xml
@@ -16,6 +16,7 @@
pom
+ org.openhab.binding.airquality
org.openhab.binding.allplay
org.openhab.binding.amazondashbutton
org.openhab.binding.astro
diff --git a/features/openhab-addons/src/main/feature/feature.xml b/features/openhab-addons/src/main/feature/feature.xml
index f42fac05b0db0..ff6c437ea64bb 100644
--- a/features/openhab-addons/src/main/feature/feature.xml
+++ b/features/openhab-addons/src/main/feature/feature.xml
@@ -13,6 +13,11 @@
+
+ openhab-runtime-base
+ mvn:org.openhab.binding/org.openhab.binding.airquality/${project.version}
+
+
openhab-runtime-base
mvn:org.openhab.binding/org.openhab.binding.allplay/${project.version}