diff --git a/aqi/README.md b/aqi/README.md new file mode 100644 index 00000000..00d1759f --- /dev/null +++ b/aqi/README.md @@ -0,0 +1,15 @@ + +# Air Quality Index + +This plug-in gets the air quality information from http://aqicn.org. +Through your IP address the next nearby sensor station will be discovered. + +If you encounter that a value stays to zero, it means the sensor station +doesn't support that value. + +Besides the air pollution level the plug-in also states a cautionary statement. +Both states can be used to let nymea notify you about the pollution level and +inform you what precautions should be taken. + +More about the different Air Quality Levels: https://www.airnow.gov/index.cfm?action=aqibasics.aqi + diff --git a/aqi/aqi.pro b/aqi/aqi.pro new file mode 100644 index 00000000..699d884c --- /dev/null +++ b/aqi/aqi.pro @@ -0,0 +1,12 @@ +include(../plugins.pri) + +TARGET = $$qtLibraryTarget(nymea_devicepluginaqi) + +QT+= network + +SOURCES += \ + devicepluginaqi.cpp \ + +HEADERS += \ + devicepluginaqi.h \ + diff --git a/aqi/devicepluginaqi.cpp b/aqi/devicepluginaqi.cpp new file mode 100644 index 00000000..359677eb --- /dev/null +++ b/aqi/devicepluginaqi.cpp @@ -0,0 +1,186 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2020 Bernhard Trinnes * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "devicepluginaqi.h" +#include "plugininfo.h" + +#include +#include +#include +#include +#include +#include + +DevicePluginAqi::DevicePluginAqi() +{ + +} + +void DevicePluginAqi::setupDevice(DeviceSetupInfo *info) +{ + if (info->device()->deviceClassId() == airQualityIndexDeviceClassId) { + + if (myDevices().filterByDeviceClassId(info->device()->deviceClassId()).count() > 1) { + info->finish(Device::DeviceErrorSetupFailed, tr("Service is already in use")); + return; + } + + QUrl url; + url.setUrl(m_baseUrl); + url.setPath("/feed/here/"); + url.setQuery("token="+configValue(airQualityIndexPluginApiKeyParamTypeId).toString()); + QNetworkRequest request; + request.setUrl(url); + request.setRawHeader("User-Agent", "nymea 1.0"); + + QNetworkReply *reply = hardwareManager()->networkManager()->get(request); + connect(reply, &QNetworkReply::finished, this, [reply, info, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + if (status == 400 || status == 401) { + + } + qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString(); + return info->finish(Device::DeviceErrorSetupFailed, reply->errorString()); + } + return info->finish(Device::DeviceErrorNoError); + }); + } +} + + +void DevicePluginAqi::postSetupDevice(Device *device) +{ + Q_UNUSED(device); + getDataByIp(); + + if(!m_pluginTimer) { + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); + connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginAqi::onPluginTimer); + } +} + + +void DevicePluginAqi::deviceRemoved(Device *device) +{ + Q_UNUSED(device); + + if (myDevices().empty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + m_pluginTimer = nullptr; + } +} + + +void DevicePluginAqi::getDataByIp() +{ + QUrl url; + url.setUrl(m_baseUrl); + url.setPath("/feed/here/"); + url.setQuery("token="+configValue(airQualityIndexPluginApiKeyParamTypeId).toString()); + QNetworkRequest request; + request.setUrl(url); + request.setRawHeader("User-Agent", "nymea"); + + QNetworkReply *reply = hardwareManager()->networkManager()->get(request); + connect(reply, &QNetworkReply::finished, this, [reply, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + foreach (Device *device, myDevices().filterByDeviceClassId(airQualityIndexDeviceClassId)) { + device->setStateValue(airQualityIndexConnectedStateTypeId, true); + } + qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString(); + return; + } + QJsonParseError error; + QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error); + if (error.error != QJsonParseError::NoError) { + qDebug(dcAirQualityIndex()) << "Received invalide JSON object"; + return; + } + qCDebug(dcAirQualityIndex()) << data.toJson(); + QVariantMap city = data.toVariant().toMap().value("data").toMap().value("city").toMap(); + //double lat = city["geo"].toList().takeAt(0).toDouble(); + //double lng = city["geo"].toList().takeAt(1).toDouble(); + QString name = city["name"].toString(); + QString url = city["url"].toString(); + + QVariantMap iaqi = data.toVariant().toMap().value("data").toMap().value("iaqi").toMap(); + double humidity = iaqi["h"].toMap().value("v").toDouble(); + double pressure = iaqi["p"].toMap().value("v").toDouble(); + int pm25 = iaqi["pm25"].toMap().value("v").toInt(); + int pm10 = iaqi["pm10"].toMap().value("v").toInt(); + double so2 = iaqi["so2"].toMap().value("v").toDouble(); + double no2 = iaqi["no2"].toMap().value("v").toDouble(); + double o3 = iaqi["o3"].toMap().value("v").toDouble(); + double co = iaqi["co"].toMap().value("v").toDouble(); + double temperature = iaqi["t"].toMap().value("v").toDouble(); + + foreach (Device *device, myDevices().filterByDeviceClassId(airQualityIndexDeviceClassId)) { + device->setStateValue(airQualityIndexStationNameStateTypeId, name); + device->setStateValue(airQualityIndexConnectedStateTypeId, true); + device->setStateValue(airQualityIndexCoStateTypeId, co); + device->setStateValue(airQualityIndexHumidityStateTypeId, humidity); + device->setStateValue(airQualityIndexTemperatureStateTypeId, temperature); + device->setStateValue(airQualityIndexPressureStateTypeId, pressure); + device->setStateValue(airQualityIndexO3StateTypeId, o3); + device->setStateValue(airQualityIndexNo2StateTypeId, no2); + device->setStateValue(airQualityIndexSo2StateTypeId, so2); + device->setStateValue(airQualityIndexPm10StateTypeId, pm10); + device->setStateValue(airQualityIndexPm25StateTypeId, pm25); + + if (pm25 <= 50.00) { + device->setStateValue(airQualityIndexAirQualityStateTypeId, "Good"); + device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, "None"); + } else if ((pm25 > 50.00) && (pm25 <= 100.00)) { + device->setStateValue(airQualityIndexAirQualityStateTypeId, "Moderate"); + device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, "Active children and adults, and people with respiratory disease, such as asthma, should limit prolonged outdoor exertion."); + } else if ((pm25 > 100.00) && (pm25 <= 150.00)) { + device->setStateValue(airQualityIndexAirQualityStateTypeId, "Unhealthy for Sensitive Groups"); + device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, "Active children and adults, and people with respiratory disease, such as asthma, should limit prolonged outdoor exertion."); + } else if ((pm25 > 150.00) && (pm25 <= 200.00)) { + device->setStateValue(airQualityIndexAirQualityStateTypeId, "Unhealthy"); + device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, "Active children and adults, and people with respiratory disease, such as asthma, should avoid prolonged outdoor exertion; everyone else, especially children, should limit prolonged outdoor exertion"); + } else if ((pm25 > 200.00) && (pm25 <= 300.00)) { + device->setStateValue(airQualityIndexAirQualityStateTypeId, "Very Unhealthy"); + device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, "Active children and adults, and people with respiratory disease, such as asthma, should avoid all outdoor exertion; everyone else, especially children, should limit outdoor exertion."); + } else { + device->setStateValue(airQualityIndexAirQualityStateTypeId, "Hazardous"); + device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, "Everyone should avoid all outdoor exertion"); + } + } + }); +} + +void DevicePluginAqi::onPluginTimer() +{ + getDataByIp(); +} + + + diff --git a/aqi/devicepluginaqi.h b/aqi/devicepluginaqi.h new file mode 100644 index 00000000..646e2824 --- /dev/null +++ b/aqi/devicepluginaqi.h @@ -0,0 +1,59 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2020 Bernhard Trinnes * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICEPLUGINAQI_H +#define DEVICEPLUGINAQI_H + +#include "plugintimer.h" +#include "devices/deviceplugin.h" +#include "network/networkaccessmanager.h" + +#include +#include +#include + +class DevicePluginAqi : public DevicePlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginaqi.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginAqi(); + + void setupDevice(DeviceSetupInfo *info) override; + void deviceRemoved(Device *device) override; + void postSetupDevice(Device *device) override; + +private: + PluginTimer *m_pluginTimer = nullptr; + QString m_baseUrl = "https://api.waqi.info"; + QString m_apiKey; + + void getDataByIp(); + +private slots: + void onPluginTimer(); +}; + +#endif // DEVICEPLUGINAQI_H diff --git a/aqi/devicepluginaqi.json b/aqi/devicepluginaqi.json new file mode 100644 index 00000000..4a5aa233 --- /dev/null +++ b/aqi/devicepluginaqi.json @@ -0,0 +1,156 @@ +{ + "name": "AirQualityIndex", + "displayName": "AirQualityIndex", + "id": "57d69b76-4d2d-41ec-bef6-949a79ffbe6b", + "paramTypes": [ + { + "id": "a3525f8a-18be-4739-b0ea-ef3af0c7f280", + "name": "apiKey", + "displayName": "API key", + "type": "QString", + "defaultValue": "74d31bb5ad9bcdeaed48097418b55188cb56d450" + } + ], + "vendors": [ + { + "name": "airQualityIndex", + "displayName": "Air Quality Index", + "id": "6c8e2ded-0a33-4e77-b76c-ea02168741ec", + "deviceClasses": [ + { + "id": "23ea32c9-38b0-4155-bacc-3afa8c09f6ee", + "name": "airQualityIndex", + "displayName": "Air quality index", + "interfaces": ["humiditysensor", "pressuresensor", "temperaturesensor", "connectable"], + "createMethods": ["user"], + "paramTypes": [ + ], + "stateTypes": [ + { + "id": "7b9135cd-2461-4d33-b2b3-3dc600983895", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "33a3329a-4117-4488-aa18-91c76056ed6e", + "name": "airQuality", + "displayName": "Air quality", + "displayNameEvent": "Air quality changed", + "type": "QString", + "possibleValues": [ + "Good", + "Moderate", + "Unhealthy for Sensitive Groups", + "Unhealthy", + "Very unhealthy", + "Hazardous" + ], + "defaultValue": "Good" + }, + { + "id": "cfece671-4e88-4c49-9456-e3f8f7c79ab3", + "name": "cautionaryStatement", + "displayName": "Cautionary statement", + "displayNameEvent": "Cautionary statement changed", + "type": "QString", + "defaultValue": "-" + }, + { + "id": "8385f3d5-62f7-482e-927c-b5d61a70d607", + "name": "stationName", + "displayName": "Station name", + "displayNameEvent": "Station name changed", + "type": "QString", + "defaultValue": "Undefined" + }, + { + "id": "bc8c4c83-d229-4be4-8732-bc4f2390f399", + "name": "pm25", + "displayName": "Fine particles pollution level (PM2.5)", + "displayNameEvent": "Fine particles pollution level (PM2.5) changed", + "type": "int", + "defaultValue": 0 + }, + { + "id": "24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6", + "name": "pm10", + "displayName": "Coarse dust particles pollution level (PM10)", + "displayNameEvent": "Coarse dust particles pollution level (PM10) changed", + "type": "int", + "defaultValue": 0 + }, + { + "id": "4e88526d-009f-4820-9a84-09b3646d23c9", + "name": "o3", + "displayName": "Ozone level (O3)", + "displayNameEvent": "Ozone level (O3) changed", + "unit": "", + "type": "double", + "defaultValue": 0 + }, + { + "id": "6ed6c505-f36e-44c4-a982-f395b04e539b", + "name": "no2", + "displayName": "Nitrogen Dioxide level (NO2)", + "displayNameEvent": "Nitrogen Dioxide level (NO2) changed", + "unit": "", + "type": "double", + "defaultValue": 0 + }, + { + "id": "54ac72f3-6444-46a8-a43d-210c2a6fbfb5", + "name": "co", + "displayName": "Carbon monoxide level (CO)", + "displayNameEvent": "Carbon monoxide level (CO) changed", + "unit": "", + "type": "double", + "defaultValue": 0 + }, + { + "id": "f3a05e65-a9b3-48fd-be43-688d4c293cc9", + "name": "so2", + "displayName": "Sulfur dioxide level (SO2)", + "displayNameEvent": "Sulfur dioxide level (SO2) changed", + "unit": "", + "type": "double", + "defaultValue": 0 + }, + { + "id": "94219802-0a82-4761-99b3-c6b6dfc096db", + "name": "temperature", + "displayName": "Temperature", + "displayNameEvent": "Temperature changed", + "unit": "DegreeCelsius", + "type": "double", + "defaultValue": 0 + }, + { + "id": "4fc45fca-25ab-45a0-b862-817eea1f51e3", + "name": "humidity", + "displayName": "Humidity", + "displayNameEvent": "Humidity changed", + "unit": "Percentage", + "type": "double", + "maxValue": 100, + "minValue": 0, + "defaultValue": 0 + }, + { + "id": "5f799040-08f8-44d1-aa0a-4cab7caad839", + "name": "pressure", + "displayName": "Pressure", + "displayNameEvent": "Pressure changed", + "unit": "HectoPascal", + "type": "double", + "defaultValue": 0 + } + ] + } + ] + } + ] +} diff --git a/debian/control b/debian/control index 6efef76e..8140a7d4 100644 --- a/debian/control +++ b/debian/control @@ -39,6 +39,22 @@ Description: nymea.io plugin for ANEL Elektronik NET-PwrCtrl power sockets network controlled power sockets. +Package: nymea-plugin-aqi +Architecture: any +Section: libs +Depends: ${shlibs:Depends}, + ${misc:Depends}, + nymea-plugins-translations, +Description: nymea.io plugin to fetch the air quaility index from http://aqicn.org + The nymea daemon is a plugin based IoT (Internet of Things) server. The + server works like a translator for devices, things and services and + allows them to interact. + With the powerful rule engine you are able to connect any device available + in the system and create individual scenes and behaviors for your environment. + . + This package will install the nymea.io plugin for the air quality index + + Package: nymea-plugin-avahimonitor Architecture: any Section: libs diff --git a/debian/nymea-plugin-aqi.install.in b/debian/nymea-plugin-aqi.install.in new file mode 100644 index 00000000..69ba2fda --- /dev/null +++ b/debian/nymea-plugin-aqi.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginaqi.so diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 23d78cdf..6ea6aacf 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -2,6 +2,7 @@ TEMPLATE = subdirs PLUGIN_DIRS = \ anel \ + aqi \ avahimonitor \ awattar \ boblight \