From c9117867a56e52f75fe089bde39defb357329cc3 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Mon, 23 Dec 2019 15:08:40 +0100 Subject: [PATCH 1/9] added AQI plug-in --- aqi/README.md | 15 +++ aqi/aqi.pro | 12 ++ aqi/devicepluginaqi.cpp | 186 +++++++++++++++++++++++++++++ aqi/devicepluginaqi.h | 59 +++++++++ aqi/devicepluginaqi.json | 156 ++++++++++++++++++++++++ debian/control | 16 +++ debian/nymea-plugin-aqi.install.in | 1 + nymea-plugins.pro | 1 + 8 files changed, 446 insertions(+) create mode 100644 aqi/README.md create mode 100644 aqi/aqi.pro create mode 100644 aqi/devicepluginaqi.cpp create mode 100644 aqi/devicepluginaqi.h create mode 100644 aqi/devicepluginaqi.json create mode 100644 debian/nymea-plugin-aqi.install.in 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 \ From 66fa7c295e5fffe2354717d270fc74f08542c64d Mon Sep 17 00:00:00 2001 From: Boernsman Date: Mon, 23 Dec 2019 15:10:29 +0100 Subject: [PATCH 2/9] added translations --- ...69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts | 234 ++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 aqi/translations/57d69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts diff --git a/aqi/translations/57d69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts b/aqi/translations/57d69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts new file mode 100644 index 00000000..740476c8 --- /dev/null +++ b/aqi/translations/57d69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts @@ -0,0 +1,234 @@ + + + + + AirQualityIndex + + + API key + The name of the ParamType (DeviceClass: airQualityIndex, Type: plugin, ID: {a3525f8a-18be-4739-b0ea-ef3af0c7f280}) + + + + + Air Quality Index + The name of the vendor ({6c8e2ded-0a33-4e77-b76c-ea02168741ec}) + + + + + + Air quality + The name of the ParamType (DeviceClass: airQualityIndex, EventType: airQuality, ID: {33a3329a-4117-4488-aa18-91c76056ed6e}) +---------- +The name of the StateType ({33a3329a-4117-4488-aa18-91c76056ed6e}) of DeviceClass airQualityIndex + + + + + Air quality changed + The name of the EventType ({33a3329a-4117-4488-aa18-91c76056ed6e}) of DeviceClass airQualityIndex + + + + + Air quality index + The name of the DeviceClass ({23ea32c9-38b0-4155-bacc-3afa8c09f6ee}) + + + + + AirQualityIndex + The name of the plugin AirQualityIndex ({57d69b76-4d2d-41ec-bef6-949a79ffbe6b}) + + + + + + Carbon monoxide level (CO) + The name of the ParamType (DeviceClass: airQualityIndex, EventType: co, ID: {54ac72f3-6444-46a8-a43d-210c2a6fbfb5}) +---------- +The name of the StateType ({54ac72f3-6444-46a8-a43d-210c2a6fbfb5}) of DeviceClass airQualityIndex + + + + + Carbon monoxide level (CO) changed + The name of the EventType ({54ac72f3-6444-46a8-a43d-210c2a6fbfb5}) of DeviceClass airQualityIndex + + + + + + Cautionary statement + The name of the ParamType (DeviceClass: airQualityIndex, EventType: cautionaryStatement, ID: {cfece671-4e88-4c49-9456-e3f8f7c79ab3}) +---------- +The name of the StateType ({cfece671-4e88-4c49-9456-e3f8f7c79ab3}) of DeviceClass airQualityIndex + + + + + Cautionary statement changed + The name of the EventType ({cfece671-4e88-4c49-9456-e3f8f7c79ab3}) of DeviceClass airQualityIndex + + + + + + Coarse dust particles pollution level (PM10) + The name of the ParamType (DeviceClass: airQualityIndex, EventType: pm10, ID: {24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6}) +---------- +The name of the StateType ({24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6}) of DeviceClass airQualityIndex + + + + + Coarse dust particles pollution level (PM10) changed + The name of the EventType ({24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6}) of DeviceClass airQualityIndex + + + + + + Connected + The name of the ParamType (DeviceClass: airQualityIndex, EventType: connected, ID: {7b9135cd-2461-4d33-b2b3-3dc600983895}) +---------- +The name of the StateType ({7b9135cd-2461-4d33-b2b3-3dc600983895}) of DeviceClass airQualityIndex + + + + + Connected changed + The name of the EventType ({7b9135cd-2461-4d33-b2b3-3dc600983895}) of DeviceClass airQualityIndex + + + + + + Fine particles pollution level (PM2.5) + The name of the ParamType (DeviceClass: airQualityIndex, EventType: pm25, ID: {bc8c4c83-d229-4be4-8732-bc4f2390f399}) +---------- +The name of the StateType ({bc8c4c83-d229-4be4-8732-bc4f2390f399}) of DeviceClass airQualityIndex + + + + + Fine particles pollution level (PM2.5) changed + The name of the EventType ({bc8c4c83-d229-4be4-8732-bc4f2390f399}) of DeviceClass airQualityIndex + + + + + + Humidity + The name of the ParamType (DeviceClass: airQualityIndex, EventType: humidity, ID: {4fc45fca-25ab-45a0-b862-817eea1f51e3}) +---------- +The name of the StateType ({4fc45fca-25ab-45a0-b862-817eea1f51e3}) of DeviceClass airQualityIndex + + + + + Humidity changed + The name of the EventType ({4fc45fca-25ab-45a0-b862-817eea1f51e3}) of DeviceClass airQualityIndex + + + + + + Nitrogen Dioxide level (NO2) + The name of the ParamType (DeviceClass: airQualityIndex, EventType: no2, ID: {6ed6c505-f36e-44c4-a982-f395b04e539b}) +---------- +The name of the StateType ({6ed6c505-f36e-44c4-a982-f395b04e539b}) of DeviceClass airQualityIndex + + + + + Nitrogen Dioxide level (NO2) changed + The name of the EventType ({6ed6c505-f36e-44c4-a982-f395b04e539b}) of DeviceClass airQualityIndex + + + + + + Ozone level (O3) + The name of the ParamType (DeviceClass: airQualityIndex, EventType: o3, ID: {4e88526d-009f-4820-9a84-09b3646d23c9}) +---------- +The name of the StateType ({4e88526d-009f-4820-9a84-09b3646d23c9}) of DeviceClass airQualityIndex + + + + + Ozone level (O3) changed + The name of the EventType ({4e88526d-009f-4820-9a84-09b3646d23c9}) of DeviceClass airQualityIndex + + + + + + Pressure + The name of the ParamType (DeviceClass: airQualityIndex, EventType: pressure, ID: {5f799040-08f8-44d1-aa0a-4cab7caad839}) +---------- +The name of the StateType ({5f799040-08f8-44d1-aa0a-4cab7caad839}) of DeviceClass airQualityIndex + + + + + Pressure changed + The name of the EventType ({5f799040-08f8-44d1-aa0a-4cab7caad839}) of DeviceClass airQualityIndex + + + + + + Station name + The name of the ParamType (DeviceClass: airQualityIndex, EventType: stationName, ID: {8385f3d5-62f7-482e-927c-b5d61a70d607}) +---------- +The name of the StateType ({8385f3d5-62f7-482e-927c-b5d61a70d607}) of DeviceClass airQualityIndex + + + + + Station name changed + The name of the EventType ({8385f3d5-62f7-482e-927c-b5d61a70d607}) of DeviceClass airQualityIndex + + + + + + Sulfur dioxide level (SO2) + The name of the ParamType (DeviceClass: airQualityIndex, EventType: so2, ID: {f3a05e65-a9b3-48fd-be43-688d4c293cc9}) +---------- +The name of the StateType ({f3a05e65-a9b3-48fd-be43-688d4c293cc9}) of DeviceClass airQualityIndex + + + + + Sulfur dioxide level (SO2) changed + The name of the EventType ({f3a05e65-a9b3-48fd-be43-688d4c293cc9}) of DeviceClass airQualityIndex + + + + + + Temperature + The name of the ParamType (DeviceClass: airQualityIndex, EventType: temperature, ID: {94219802-0a82-4761-99b3-c6b6dfc096db}) +---------- +The name of the StateType ({94219802-0a82-4761-99b3-c6b6dfc096db}) of DeviceClass airQualityIndex + + + + + Temperature changed + The name of the EventType ({94219802-0a82-4761-99b3-c6b6dfc096db}) of DeviceClass airQualityIndex + + + + + DevicePluginAqi + + + Service is already in use + + + + From cb6936261eabd6e15c17de8dc390a8b27dffb1f6 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Mon, 23 Dec 2019 15:34:40 +0100 Subject: [PATCH 3/9] added windspeed as main interface --- aqi/devicepluginaqi.cpp | 23 ++-- aqi/devicepluginaqi.json | 11 +- ...69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts | 105 ++++++++++++------ debian/control | 1 + 4 files changed, 97 insertions(+), 43 deletions(-) diff --git a/aqi/devicepluginaqi.cpp b/aqi/devicepluginaqi.cpp index 359677eb..37ab21f5 100644 --- a/aqi/devicepluginaqi.cpp +++ b/aqi/devicepluginaqi.cpp @@ -39,9 +39,11 @@ 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; + if (!myDevices().filterByDeviceClassId(info->device()->deviceClassId()).isEmpty()) { + if (!myDevices().findById(info->device()->id())) { + info->finish(Device::DeviceErrorSetupFailed, tr("Service is already in use.")); + return; + } } QUrl url; @@ -123,7 +125,6 @@ void DevicePluginAqi::getDataByIp() 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(); @@ -140,6 +141,7 @@ void DevicePluginAqi::getDataByIp() double o3 = iaqi["o3"].toMap().value("v").toDouble(); double co = iaqi["co"].toMap().value("v").toDouble(); double temperature = iaqi["t"].toMap().value("v").toDouble(); + double windSpeed = iaqi["w"].toMap().value("v").toDouble(); foreach (Device *device, myDevices().filterByDeviceClassId(airQualityIndexDeviceClassId)) { device->setStateValue(airQualityIndexStationNameStateTypeId, name); @@ -153,25 +155,26 @@ void DevicePluginAqi::getDataByIp() device->setStateValue(airQualityIndexSo2StateTypeId, so2); device->setStateValue(airQualityIndexPm10StateTypeId, pm10); device->setStateValue(airQualityIndexPm25StateTypeId, pm25); + device->setStateValue(airQualityIndexWindSpeedStateTypeId, windSpeed); if (pm25 <= 50.00) { device->setStateValue(airQualityIndexAirQualityStateTypeId, "Good"); - device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, "None"); + device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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."); + device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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."); + device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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"); + device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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."); + device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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"); + device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("Everyone should avoid all outdoor exertion")); } } }); diff --git a/aqi/devicepluginaqi.json b/aqi/devicepluginaqi.json index 4a5aa233..91a52ba7 100644 --- a/aqi/devicepluginaqi.json +++ b/aqi/devicepluginaqi.json @@ -21,7 +21,7 @@ "id": "23ea32c9-38b0-4155-bacc-3afa8c09f6ee", "name": "airQualityIndex", "displayName": "Air quality index", - "interfaces": ["humiditysensor", "pressuresensor", "temperaturesensor", "connectable"], + "interfaces": ["windspeedsensor", "humiditysensor", "pressuresensor", "temperaturesensor", "connectable"], "createMethods": ["user"], "paramTypes": [ ], @@ -147,6 +147,15 @@ "unit": "HectoPascal", "type": "double", "defaultValue": 0 + }, + { + "id": "c4366608-2511-428b-964e-2ad9e37f8f3c", + "name": "windSpeed", + "displayName": "Wind speed", + "displayNameEvent": "Wind speed changed", + "unit": "MeterPerSecond", + "type": "double", + "defaultValue": 0 } ] } diff --git a/aqi/translations/57d69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts b/aqi/translations/57d69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts index 740476c8..0278aa05 100644 --- a/aqi/translations/57d69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts +++ b/aqi/translations/57d69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts @@ -4,20 +4,20 @@ AirQualityIndex - + API key The name of the ParamType (DeviceClass: airQualityIndex, Type: plugin, ID: {a3525f8a-18be-4739-b0ea-ef3af0c7f280}) - + Air Quality Index The name of the vendor ({6c8e2ded-0a33-4e77-b76c-ea02168741ec}) - + Air quality The name of the ParamType (DeviceClass: airQualityIndex, EventType: airQuality, ID: {33a3329a-4117-4488-aa18-91c76056ed6e}) ---------- @@ -25,26 +25,26 @@ The name of the StateType ({33a3329a-4117-4488-aa18-91c76056ed6e}) of DeviceClas - + Air quality changed The name of the EventType ({33a3329a-4117-4488-aa18-91c76056ed6e}) of DeviceClass airQualityIndex - + Air quality index The name of the DeviceClass ({23ea32c9-38b0-4155-bacc-3afa8c09f6ee}) - + AirQualityIndex The name of the plugin AirQualityIndex ({57d69b76-4d2d-41ec-bef6-949a79ffbe6b}) - + Carbon monoxide level (CO) The name of the ParamType (DeviceClass: airQualityIndex, EventType: co, ID: {54ac72f3-6444-46a8-a43d-210c2a6fbfb5}) ---------- @@ -52,14 +52,14 @@ The name of the StateType ({54ac72f3-6444-46a8-a43d-210c2a6fbfb5}) of DeviceClas - + Carbon monoxide level (CO) changed The name of the EventType ({54ac72f3-6444-46a8-a43d-210c2a6fbfb5}) of DeviceClass airQualityIndex - + Cautionary statement The name of the ParamType (DeviceClass: airQualityIndex, EventType: cautionaryStatement, ID: {cfece671-4e88-4c49-9456-e3f8f7c79ab3}) ---------- @@ -67,14 +67,14 @@ The name of the StateType ({cfece671-4e88-4c49-9456-e3f8f7c79ab3}) of DeviceClas - + Cautionary statement changed The name of the EventType ({cfece671-4e88-4c49-9456-e3f8f7c79ab3}) of DeviceClass airQualityIndex - + Coarse dust particles pollution level (PM10) The name of the ParamType (DeviceClass: airQualityIndex, EventType: pm10, ID: {24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6}) ---------- @@ -82,14 +82,14 @@ The name of the StateType ({24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6}) of DeviceClas - + Coarse dust particles pollution level (PM10) changed The name of the EventType ({24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6}) of DeviceClass airQualityIndex - + Connected The name of the ParamType (DeviceClass: airQualityIndex, EventType: connected, ID: {7b9135cd-2461-4d33-b2b3-3dc600983895}) ---------- @@ -97,14 +97,14 @@ The name of the StateType ({7b9135cd-2461-4d33-b2b3-3dc600983895}) of DeviceClas - + Connected changed The name of the EventType ({7b9135cd-2461-4d33-b2b3-3dc600983895}) of DeviceClass airQualityIndex - + Fine particles pollution level (PM2.5) The name of the ParamType (DeviceClass: airQualityIndex, EventType: pm25, ID: {bc8c4c83-d229-4be4-8732-bc4f2390f399}) ---------- @@ -112,14 +112,14 @@ The name of the StateType ({bc8c4c83-d229-4be4-8732-bc4f2390f399}) of DeviceClas - + Fine particles pollution level (PM2.5) changed The name of the EventType ({bc8c4c83-d229-4be4-8732-bc4f2390f399}) of DeviceClass airQualityIndex - + Humidity The name of the ParamType (DeviceClass: airQualityIndex, EventType: humidity, ID: {4fc45fca-25ab-45a0-b862-817eea1f51e3}) ---------- @@ -127,14 +127,14 @@ The name of the StateType ({4fc45fca-25ab-45a0-b862-817eea1f51e3}) of DeviceClas - + Humidity changed The name of the EventType ({4fc45fca-25ab-45a0-b862-817eea1f51e3}) of DeviceClass airQualityIndex - + Nitrogen Dioxide level (NO2) The name of the ParamType (DeviceClass: airQualityIndex, EventType: no2, ID: {6ed6c505-f36e-44c4-a982-f395b04e539b}) ---------- @@ -142,14 +142,14 @@ The name of the StateType ({6ed6c505-f36e-44c4-a982-f395b04e539b}) of DeviceClas - + Nitrogen Dioxide level (NO2) changed The name of the EventType ({6ed6c505-f36e-44c4-a982-f395b04e539b}) of DeviceClass airQualityIndex - + Ozone level (O3) The name of the ParamType (DeviceClass: airQualityIndex, EventType: o3, ID: {4e88526d-009f-4820-9a84-09b3646d23c9}) ---------- @@ -157,14 +157,14 @@ The name of the StateType ({4e88526d-009f-4820-9a84-09b3646d23c9}) of DeviceClas - + Ozone level (O3) changed The name of the EventType ({4e88526d-009f-4820-9a84-09b3646d23c9}) of DeviceClass airQualityIndex - + Pressure The name of the ParamType (DeviceClass: airQualityIndex, EventType: pressure, ID: {5f799040-08f8-44d1-aa0a-4cab7caad839}) ---------- @@ -172,14 +172,14 @@ The name of the StateType ({5f799040-08f8-44d1-aa0a-4cab7caad839}) of DeviceClas - + Pressure changed The name of the EventType ({5f799040-08f8-44d1-aa0a-4cab7caad839}) of DeviceClass airQualityIndex - + Station name The name of the ParamType (DeviceClass: airQualityIndex, EventType: stationName, ID: {8385f3d5-62f7-482e-927c-b5d61a70d607}) ---------- @@ -187,14 +187,14 @@ The name of the StateType ({8385f3d5-62f7-482e-927c-b5d61a70d607}) of DeviceClas - + Station name changed The name of the EventType ({8385f3d5-62f7-482e-927c-b5d61a70d607}) of DeviceClass airQualityIndex - + Sulfur dioxide level (SO2) The name of the ParamType (DeviceClass: airQualityIndex, EventType: so2, ID: {f3a05e65-a9b3-48fd-be43-688d4c293cc9}) ---------- @@ -202,14 +202,14 @@ The name of the StateType ({f3a05e65-a9b3-48fd-be43-688d4c293cc9}) of DeviceClas - + Sulfur dioxide level (SO2) changed The name of the EventType ({f3a05e65-a9b3-48fd-be43-688d4c293cc9}) of DeviceClass airQualityIndex - + Temperature The name of the ParamType (DeviceClass: airQualityIndex, EventType: temperature, ID: {94219802-0a82-4761-99b3-c6b6dfc096db}) ---------- @@ -217,17 +217,58 @@ The name of the StateType ({94219802-0a82-4761-99b3-c6b6dfc096db}) of DeviceClas - + Temperature changed The name of the EventType ({94219802-0a82-4761-99b3-c6b6dfc096db}) of DeviceClass airQualityIndex + + + + Wind speed + The name of the ParamType (DeviceClass: airQualityIndex, EventType: windSpeed, ID: {c4366608-2511-428b-964e-2ad9e37f8f3c}) +---------- +The name of the StateType ({c4366608-2511-428b-964e-2ad9e37f8f3c}) of DeviceClass airQualityIndex + + + + + Wind speed changed + The name of the EventType ({c4366608-2511-428b-964e-2ad9e37f8f3c}) of DeviceClass airQualityIndex + + DevicePluginAqi - - Service is already in use + + Service is already in use. + + + + + None + + + + + + Active children and adults, and people with respiratory disease, such as asthma, should limit prolonged outdoor exertion. + + + + + 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 + + + + + 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. + + + + + Everyone should avoid all outdoor exertion diff --git a/debian/control b/debian/control index 8140a7d4..20f9b769 100644 --- a/debian/control +++ b/debian/control @@ -976,6 +976,7 @@ Package: nymea-plugins Section: libs Architecture: all Depends: nymea-plugin-anel, + nymea-plugin-aqi, nymea-plugin-awattar, nymea-plugin-bose, nymea-plugin-datetime, From 3af263c91aeccc1fc67217e17ba9272bec9c086e Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Mon, 23 Mar 2020 14:49:40 +0100 Subject: [PATCH 4/9] devices to things --- aqi/README.md | 7 + aqi/aqi.pro | 6 +- aqi/devicepluginaqi.cpp | 189 ----------------- aqi/devicepluginaqi.h | 59 ------ aqi/integrationpluginaqi.cpp | 197 ++++++++++++++++++ aqi/integrationpluginaqi.h | 67 ++++++ ...uginaqi.json => integrationpluginaqi.json} | 4 +- 7 files changed, 275 insertions(+), 254 deletions(-) delete mode 100644 aqi/devicepluginaqi.cpp delete mode 100644 aqi/devicepluginaqi.h create mode 100644 aqi/integrationpluginaqi.cpp create mode 100644 aqi/integrationpluginaqi.h rename aqi/{devicepluginaqi.json => integrationpluginaqi.json} (98%) diff --git a/aqi/README.md b/aqi/README.md index 00d1759f..9c8bd36e 100644 --- a/aqi/README.md +++ b/aqi/README.md @@ -4,6 +4,9 @@ This plug-in gets the air quality information from http://aqicn.org. Through your IP address the next nearby sensor station will be discovered. + +## Supported Things + If you encounter that a value stays to zero, it means the sensor station doesn't support that value. @@ -11,5 +14,9 @@ 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. +## Requirments + +## More + 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 index 699d884c..679f1419 100644 --- a/aqi/aqi.pro +++ b/aqi/aqi.pro @@ -1,12 +1,10 @@ include(../plugins.pri) -TARGET = $$qtLibraryTarget(nymea_devicepluginaqi) - QT+= network SOURCES += \ - devicepluginaqi.cpp \ + integrationpluginaqi.cpp \ HEADERS += \ - devicepluginaqi.h \ + integrationpluginaqi.h \ diff --git a/aqi/devicepluginaqi.cpp b/aqi/devicepluginaqi.cpp deleted file mode 100644 index 37ab21f5..00000000 --- a/aqi/devicepluginaqi.cpp +++ /dev/null @@ -1,189 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * 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()).isEmpty()) { - if (!myDevices().findById(info->device()->id())) { - 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; - } - 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(); - double windSpeed = iaqi["w"].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); - device->setStateValue(airQualityIndexWindSpeedStateTypeId, windSpeed); - - if (pm25 <= 50.00) { - device->setStateValue(airQualityIndexAirQualityStateTypeId, "Good"); - device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("None")); - } else if ((pm25 > 50.00) && (pm25 <= 100.00)) { - device->setStateValue(airQualityIndexAirQualityStateTypeId, "Moderate"); - device->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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, tr("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, tr("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, tr("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, tr("Everyone should avoid all outdoor exertion")); - } - } - }); -} - -void DevicePluginAqi::onPluginTimer() -{ - getDataByIp(); -} - - - diff --git a/aqi/devicepluginaqi.h b/aqi/devicepluginaqi.h deleted file mode 100644 index 646e2824..00000000 --- a/aqi/devicepluginaqi.h +++ /dev/null @@ -1,59 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * 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/integrationpluginaqi.cpp b/aqi/integrationpluginaqi.cpp new file mode 100644 index 00000000..6b37c3c6 --- /dev/null +++ b/aqi/integrationpluginaqi.cpp @@ -0,0 +1,197 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "integrationpluginaqi.h" +#include "plugininfo.h" + +#include +#include +#include +#include +#include +#include + +IntegrationPluginAqi::IntegrationPluginAqi() +{ + +} + +void IntegrationPluginAqi::setupThing(ThingSetupInfo *info) +{ + if (info->thing()->thingClassId() == airQualityIndexThingClassId) { + + if (!myThings().filterByThingClassId(info->thing()->thingClassId()).isEmpty()) { + if (!myThings().findById(info->thing()->id())) { + info->finish(Thing::ThingErrorSetupFailed, 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(Thing::ThingErrorSetupFailed, reply->errorString()); + } + return info->finish(Thing::ThingErrorNoError); + }); + } +} + + +void IntegrationPluginAqi::postSetupThing(Thing *thing) +{ + Q_UNUSED(thing); + getDataByIp(); + + if(!m_pluginTimer) { + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); + connect(m_pluginTimer, &PluginTimer::timeout, this, &IntegrationPluginAqi::onPluginTimer); + } +} + + +void IntegrationPluginAqi::thingRemoved(Thing *thing) +{ + Q_UNUSED(thing); + + if (myThings().empty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + m_pluginTimer = nullptr; + } +} + + +void IntegrationPluginAqi::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 (Thing *thing, myThings().filterByThingClassId(airQualityIndexThingClassId)) { + thing->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; + } + 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(); + double windSpeed = iaqi["w"].toMap().value("v").toDouble(); + + foreach (Thing *thing, myThings().filterByThingClassId(airQualityIndexThingClassId)) { + thing->setStateValue(airQualityIndexStationNameStateTypeId, name); + thing->setStateValue(airQualityIndexConnectedStateTypeId, true); + thing->setStateValue(airQualityIndexCoStateTypeId, co); + thing->setStateValue(airQualityIndexHumidityStateTypeId, humidity); + thing->setStateValue(airQualityIndexTemperatureStateTypeId, temperature); + thing->setStateValue(airQualityIndexPressureStateTypeId, pressure); + thing->setStateValue(airQualityIndexO3StateTypeId, o3); + thing->setStateValue(airQualityIndexNo2StateTypeId, no2); + thing->setStateValue(airQualityIndexSo2StateTypeId, so2); + thing->setStateValue(airQualityIndexPm10StateTypeId, pm10); + thing->setStateValue(airQualityIndexPm25StateTypeId, pm25); + thing->setStateValue(airQualityIndexWindSpeedStateTypeId, windSpeed); + + if (pm25 <= 50.00) { + thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Good"); + thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("None")); + } else if ((pm25 > 50.00) && (pm25 <= 100.00)) { + thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Moderate"); + thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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)) { + thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Unhealthy for Sensitive Groups"); + thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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)) { + thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Unhealthy"); + thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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)) { + thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Very Unhealthy"); + thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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 { + thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Hazardous"); + thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("Everyone should avoid all outdoor exertion")); + } + } + }); +} + +void IntegrationPluginAqi::onPluginTimer() +{ + getDataByIp(); +} + + + diff --git a/aqi/integrationpluginaqi.h b/aqi/integrationpluginaqi.h new file mode 100644 index 00000000..3b55eace --- /dev/null +++ b/aqi/integrationpluginaqi.h @@ -0,0 +1,67 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTEGRATIONPLUGINAQI_H +#define INTEGRATIONPLUGINAQI_H + +#include "plugintimer.h" +#include "integrations/integrationplugin.h" +#include "network/networkaccessmanager.h" + +#include +#include +#include + +class IntegrationPluginAqi : public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginaqi.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginAqi(); + + void setupThing(ThingSetupInfo *info) override; + void thingRemoved(Thing *thing) override; + void postSetupThing(Thing *thing) override; + +private: + PluginTimer *m_pluginTimer = nullptr; + QString m_baseUrl = "https://api.waqi.info"; + QString m_apiKey; + + void getDataByIp(); + +private slots: + void onPluginTimer(); +}; + +#endif // INTEGRATIONPLUGINAQI_H diff --git a/aqi/devicepluginaqi.json b/aqi/integrationpluginaqi.json similarity index 98% rename from aqi/devicepluginaqi.json rename to aqi/integrationpluginaqi.json index 91a52ba7..3f85210f 100644 --- a/aqi/devicepluginaqi.json +++ b/aqi/integrationpluginaqi.json @@ -16,7 +16,7 @@ "name": "airQualityIndex", "displayName": "Air Quality Index", "id": "6c8e2ded-0a33-4e77-b76c-ea02168741ec", - "deviceClasses": [ + "thingClasses": [ { "id": "23ea32c9-38b0-4155-bacc-3afa8c09f6ee", "name": "airQualityIndex", @@ -144,7 +144,7 @@ "name": "pressure", "displayName": "Pressure", "displayNameEvent": "Pressure changed", - "unit": "HectoPascal", + "unit": "MilliBar", "type": "double", "defaultValue": 0 }, From ebd094c57b5429f20de2c38dd20b8a1a7cb484f6 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 24 Mar 2020 10:51:52 +0100 Subject: [PATCH 5/9] added pin setup method and discovery --- aqi/airqualityindex.cpp | 231 +++++++++++++++++++++++ aqi/airqualityindex.h | 91 +++++++++ aqi/aqi.pro | 2 + aqi/integrationpluginaqi.cpp | 294 +++++++++++++++++------------ aqi/integrationpluginaqi.h | 14 +- aqi/integrationpluginaqi.json | 26 ++- debian/nymea-plugin-aqi.install.in | 2 +- 7 files changed, 528 insertions(+), 132 deletions(-) create mode 100644 aqi/airqualityindex.cpp create mode 100644 aqi/airqualityindex.h diff --git a/aqi/airqualityindex.cpp b/aqi/airqualityindex.cpp new file mode 100644 index 00000000..3bb20fc0 --- /dev/null +++ b/aqi/airqualityindex.cpp @@ -0,0 +1,231 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "airqualityindex.h" +#include "extern-plugininfo.h" + +#include +#include +#include +#include +#include + +AirQualityIndex::AirQualityIndex(NetworkAccessManager *networkAccessManager, const QString &apiKey, QObject *parent) : + QObject(parent), + m_networkAccessManager(networkAccessManager), + m_apiKey(apiKey) +{ + +} + +void AirQualityIndex::setApiKey(const QString &apiKey) +{ + m_apiKey = apiKey; +} + +QUuid AirQualityIndex::searchByName(const QString &name) +{ + if (m_apiKey.isEmpty()) + qCWarning(dcAirQualityIndex()) << "API key is not set"; + + QUuid requestId; + QUrl url; + url.setUrl(m_baseUrl); + url.setPath("/search/"); + QUrlQuery query; + query.addQueryItem("token", m_apiKey); + query.addQueryItem("keyword", name); + url.setQuery(query); + QNetworkRequest request; + request.setUrl(url); + request.setRawHeader("User-Agent", "nymea"); + + QNetworkReply *reply = m_networkAccessManager->get(request); + connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + if (status == 400) { + qCWarning(dcAirQualityIndex()) << "Request error due to exceeded request quota"; + } + requestExecuted(requestId, false); + qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString(); + return; + } + QByteArray rawData = reply->readAll(); + qCDebug(dcAirQualityIndex()) << "Search response" << rawData; + + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(rawData, &error); + if (error.error != QJsonParseError::NoError) { + emit requestExecuted(requestId, false); + qCWarning(dcAirQualityIndex()) << "Received invalide JSON object"; + return; + } + emit requestExecuted(requestId, true); + + QList stations; + QVariantList stationList = doc.toVariant().toMap().value("data").toList(); + foreach (QVariant stationVariant, stationList) { + Station station; + station.aqi = stationVariant.toMap().value("aqi").toInt(); + station.idx = stationVariant.toMap().value("idx").toInt(); + station.measurementTime = QTime::fromString(stationVariant.toMap().value("time").toMap().value("s").toString()); + station.timezone = stationVariant.toMap().value("time").toMap().value("tz").toString(); + station.name = stationVariant.toMap().value("city").toMap().value("name").toString(); + station.url = QUrl(stationVariant.toMap().value("city").toMap().value("url").toString()); + station.location.latitude = stationVariant.toMap().value("city").toMap().value("geo").toList().first().toDouble(); + station.location.longitude = stationVariant.toMap().value("city").toMap().value("geo").toList().last().toDouble(); + stations.append(station); + } + if (!stations.isEmpty()) + emit stationsReceived(requestId, stations); + }); + return requestId; +} + +QUuid AirQualityIndex::getDataByIp() +{ + if (m_apiKey.isEmpty()) + qCWarning(dcAirQualityIndex()) << "API key is not set"; + + QUuid requestId; + QUrl url; + url.setUrl(m_baseUrl); + url.setPath("/feed/here/"); + QUrlQuery query; + query.addQueryItem("token", m_apiKey); + url.setQuery(query); + QNetworkRequest request; + request.setUrl(url); + request.setRawHeader("User-Agent", "nymea"); + qCDebug(dcAirQualityIndex()) << "Get data by IP request" << url.toString(); + QNetworkReply *reply = m_networkAccessManager->get(request); + connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + if (status == 400) { + qCWarning(dcAirQualityIndex()) << "Request error due to exceeded request quota"; + } + requestExecuted(requestId, false); + qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString(); + return; + } + parseData(requestId, reply->readAll()); + }); + return requestId; +} + +QUuid AirQualityIndex::getDataByGeolocation(const QString &lat, const QString &lng) +{ + if (m_apiKey.isEmpty()) + qCWarning(dcAirQualityIndex()) << "API key is not set"; + + QUuid requestId; + QUrl url; + url.setUrl(m_baseUrl); + url.setPath("/feed/geo:"+lat+";"+lng+"/"); + QUrlQuery query; + query.addQueryItem("token", m_apiKey); + url.setQuery(query); + QNetworkRequest request; + request.setUrl(url); + request.setRawHeader("User-Agent", "nymea"); + qCDebug(dcAirQualityIndex()) << "Get data by geo location request" << url.toString(); + QNetworkReply *reply = m_networkAccessManager->get(request); + connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + if (status == 400) { + qCWarning(dcAirQualityIndex()) << "Request error due to exceeded request quota"; + } + requestExecuted(requestId, false); + qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString(); + return; + } + requestExecuted(requestId, true); + parseData(requestId, reply->readAll()); + }); + return requestId; +} + + +void AirQualityIndex::parseData(QUuid requestId, const QByteArray &data) +{ + qCDebug(dcAirQualityIndex()) << "Parsing data" << data; + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + emit requestExecuted(requestId, false); + qCWarning(dcAirQualityIndex()) << "Received invalide JSON object"; + return; + } + emit requestExecuted(requestId, true); + Station station; + station.aqi = doc.toVariant().toMap().value("data").toMap().value("aqi").toInt(); + station.idx = doc.toVariant().toMap().value("data").toMap().value("idx").toInt(); + + QVariantMap city = doc.toVariant().toMap().value("data").toMap().value("city").toMap(); + if (city["geo"].toList().length() == 2) { + station.location.latitude = city["geo"].toList().first().toDouble(); + station.location.longitude = city["geo"].toList().last().toDouble(); + } else { + qCWarning(dcAirQualityIndex()) << "Parse data: geo location data list error" << city["geo"]; + } + station.name = city["name"].toString(); + station.url = city["url"].toString(); + + QVariantMap time = doc.toVariant().toMap().value("data").toMap().value("time").toMap(); + station.timezone = time["tz"].toString(); + station.measurementTime = QTime::fromString(time["s"].toString()); + emit stationsReceived(requestId, QList() << station); + + QVariantMap iaqi = doc.toVariant().toMap().value("data").toMap().value("iaqi").toMap(); + AirQualityData aqiData; + aqiData.humidity = iaqi["h"].toMap().value("v").toDouble(); + aqiData.pressure = iaqi["p"].toMap().value("v").toDouble(); + aqiData.pm25 = iaqi["pm25"].toMap().value("v").toInt(); + aqiData.pm10 = iaqi["pm10"].toMap().value("v").toInt(); + aqiData.so2 = iaqi["so2"].toMap().value("v").toDouble(); + aqiData.no2 = iaqi["no2"].toMap().value("v").toDouble(); + aqiData.o3 = iaqi["o3"].toMap().value("v").toDouble(); + aqiData.co = iaqi["co"].toMap().value("v").toDouble(); + aqiData.temperature = iaqi["t"].toMap().value("v").toDouble(); + aqiData.windSpeed = iaqi["w"].toMap().value("v").toDouble(); + emit dataReceived(requestId, aqiData); +} diff --git a/aqi/airqualityindex.h b/aqi/airqualityindex.h new file mode 100644 index 00000000..5f15b8d1 --- /dev/null +++ b/aqi/airqualityindex.h @@ -0,0 +1,91 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef AIRQUALITYINDEX_H +#define AIRQUALITYINDEX_H + +#include "network/networkaccessmanager.h" + +#include +#include +#include + +class AirQualityIndex : public QObject +{ + Q_OBJECT +public: + struct AirQualityData { + double humidity; + double pressure; + int pm25; + int pm10; + double so2; + double no2; + double o3; + double co; + double temperature; + double windSpeed; + }; + + struct GeoData { + double latitude; + double longitude; + }; + + struct Station { + int idx; + int aqi; + QTime measurementTime; + QString timezone; + QString name; + GeoData location; + QUrl url; + }; + + explicit AirQualityIndex(NetworkAccessManager *networkAccessManager, const QString &apiKey, QObject *parent = nullptr); + void setApiKey(const QString &apiKey); + QUuid searchByName(const QString &name); + QUuid getDataByIp(); + QUuid getDataByGeolocation(const QString &lat, const QString &lng); + +private: + NetworkAccessManager *m_networkAccessManager; + QString m_baseUrl = "https://api.waqi.info"; + QString m_apiKey; + + void parseData(QUuid requestId, const QByteArray &data); + +signals: + void stationsReceived(QUuid requestId, QList stations); + void requestExecuted(QUuid requestId, bool success); + void dataReceived(QUuid requestId, const AirQualityData &data); +}; + +#endif // AIRQUALITYINDEX_H diff --git a/aqi/aqi.pro b/aqi/aqi.pro index 679f1419..0c7b9893 100644 --- a/aqi/aqi.pro +++ b/aqi/aqi.pro @@ -3,8 +3,10 @@ include(../plugins.pri) QT+= network SOURCES += \ + airqualityindex.cpp \ integrationpluginaqi.cpp \ HEADERS += \ + airqualityindex.h \ integrationpluginaqi.h \ diff --git a/aqi/integrationpluginaqi.cpp b/aqi/integrationpluginaqi.cpp index 6b37c3c6..4e340505 100644 --- a/aqi/integrationpluginaqi.cpp +++ b/aqi/integrationpluginaqi.cpp @@ -32,59 +32,106 @@ #include "plugininfo.h" #include -#include -#include -#include -#include -#include IntegrationPluginAqi::IntegrationPluginAqi() { } +void IntegrationPluginAqi::startPairing(ThingPairingInfo *info) +{ + info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter your API token for Air Quality Index")); +} + +void IntegrationPluginAqi::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) +{ + Q_UNUSED(username) + + QNetworkRequest request(QUrl("https://api.waqi.info/feed/here/?token="+secret)); + QNetworkReply *reply = hardwareManager()->networkManager()->get(request); + connect(reply, &QNetworkReply::finished, info, [this, reply, info, secret](){ + reply->deleteLater(); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // check HTTP status code + if (status != 200) { + //: Error setting up device with invalid token + info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("This token is not valid.")); + return; + } + + pluginStorage()->beginGroup(info->thingId().toString()); + pluginStorage()->setValue("apiKey", secret); + pluginStorage()->endGroup(); + info->finish(Thing::ThingErrorNoError); + }); +} + +void IntegrationPluginAqi::discoverThings(ThingDiscoveryInfo *info) +{ + if (!m_aqiConnection) { + QString apiKey = "74d31bb5ad9bcdeaed48097418b55188cb56d450"; //temporary key for discovery + m_aqiConnection = new AirQualityIndex(hardwareManager()->networkManager(), apiKey, this); + connect(m_aqiConnection, &AirQualityIndex::requestExecuted, this, &IntegrationPluginAqi::onRequestExecuted); + connect(m_aqiConnection, &AirQualityIndex::dataReceived, this, &IntegrationPluginAqi::onAirQualityDataReceived); + connect(m_aqiConnection, &AirQualityIndex::stationsReceived, this, &IntegrationPluginAqi::onAirQualityStationsReceived); + + connect(info, &ThingDiscoveryInfo::aborted, [this] { + m_aqiConnection->deleteLater(); + m_aqiConnection = nullptr; + }); + } else { + qCDebug(dcAirQualityIndex()) << "AQI connection alread created"; + } + QUuid requestId = m_aqiConnection->getDataByIp(); + m_asyncDiscovery.insert(requestId, info); +} + void IntegrationPluginAqi::setupThing(ThingSetupInfo *info) { if (info->thing()->thingClassId() == airQualityIndexThingClassId) { + if (!m_aqiConnection) { + pluginStorage()->beginGroup(info->thing()->id().toString()); + QString apiKey = pluginStorage()->value("apiKey").toString(); + pluginStorage()->endGroup(); + m_aqiConnection = new AirQualityIndex(hardwareManager()->networkManager(), apiKey, this); + connect(m_aqiConnection, &AirQualityIndex::requestExecuted, this, &IntegrationPluginAqi::onRequestExecuted); + connect(m_aqiConnection, &AirQualityIndex::dataReceived, this, &IntegrationPluginAqi::onAirQualityDataReceived); + connect(m_aqiConnection, &AirQualityIndex::stationsReceived, this, &IntegrationPluginAqi::onAirQualityStationsReceived); - if (!myThings().filterByThingClassId(info->thing()->thingClassId()).isEmpty()) { - if (!myThings().findById(info->thing()->id())) { - info->finish(Thing::ThingErrorSetupFailed, tr("Service is already in use.")); - return; - } + QString longitude = info->thing()->paramValue(airQualityIndexThingLongitudeParamTypeId).toString(); + QString latitude = info->thing()->paramValue(airQualityIndexThingLatitudeParamTypeId).toString(); + QUuid requestId = m_aqiConnection->getDataByGeolocation(latitude, longitude); + m_asyncSetups.insert(requestId, info); + + connect(info, &ThingSetupInfo::aborted, [requestId, this] { + m_asyncSetups.remove(requestId); + //m_aqiConnection->deleteLater(); + //m_aqiConnection = nullptr; + }); + } else { + info->finish(Thing::ThingErrorNoError); } - - 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(Thing::ThingErrorSetupFailed, reply->errorString()); - } - return info->finish(Thing::ThingErrorNoError); - }); + } else { + qCWarning(dcAirQualityIndex()) << "setupThing - thing class id not found" << info->thing()->thingClassId(); + info->finish(Thing::ThingErrorSetupFailed); } } void IntegrationPluginAqi::postSetupThing(Thing *thing) { - Q_UNUSED(thing); - getDataByIp(); + if (thing->thingClassId() == airQualityIndexThingClassId) { + + if (!m_aqiConnection) + return; + + QString longitude = thing->paramValue(airQualityIndexThingLongitudeParamTypeId).toString(); + QString latitude = thing->paramValue(airQualityIndexThingLatitudeParamTypeId).toString(); + QUuid requestId = m_aqiConnection->getDataByGeolocation(latitude, longitude); + m_asyncRequests.insert(requestId, thing->id()); + } if(!m_pluginTimer) { m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); @@ -95,103 +142,114 @@ void IntegrationPluginAqi::postSetupThing(Thing *thing) void IntegrationPluginAqi::thingRemoved(Thing *thing) { - Q_UNUSED(thing); + if (thing->thingClassId() == airQualityIndexThingClassId) { + + } if (myThings().empty()) { - hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); - m_pluginTimer = nullptr; + if (!m_pluginTimer) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + m_pluginTimer = nullptr; + } + if (!m_aqiConnection) { + m_aqiConnection->deleteLater(); + m_aqiConnection = nullptr; + } } } -void IntegrationPluginAqi::getDataByIp() +void IntegrationPluginAqi::onAirQualityDataReceived(QUuid requestId, AirQualityIndex::AirQualityData data) { - 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"); + if (m_asyncSetups.contains(requestId)) { + ThingSetupInfo *info = m_asyncSetups.value(requestId); + return info->finish(Thing::ThingErrorNoError); + } - 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 (Thing *thing, myThings().filterByThingClassId(airQualityIndexThingClassId)) { - thing->setStateValue(airQualityIndexConnectedStateTypeId, true); - } - qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString(); + if (m_asyncRequests.contains(requestId)) { + Thing * thing = myThings().findById(m_asyncRequests.take(requestId)); + if (!thing) return; + + //thing->setStateValue(airQualityIndexStationNameStateTypeId, data); + thing->setStateValue(airQualityIndexConnectedStateTypeId, true); + thing->setStateValue(airQualityIndexCoStateTypeId, data.co); + thing->setStateValue(airQualityIndexHumidityStateTypeId, data.humidity); + thing->setStateValue(airQualityIndexTemperatureStateTypeId, data.temperature); + thing->setStateValue(airQualityIndexPressureStateTypeId, data.pressure); + thing->setStateValue(airQualityIndexO3StateTypeId, data.o3); + thing->setStateValue(airQualityIndexNo2StateTypeId, data.no2); + thing->setStateValue(airQualityIndexSo2StateTypeId, data.so2); + thing->setStateValue(airQualityIndexPm10StateTypeId, data.pm10); + thing->setStateValue(airQualityIndexPm25StateTypeId, data.pm25); + thing->setStateValue(airQualityIndexWindSpeedStateTypeId, data.windSpeed); + + if (data.pm25 <= 50.00) { + thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Good"); + thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("None")); + } else if ((data.pm25 > 50.00) && (data.pm25 <= 100.00)) { + thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Moderate"); + thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("Active children and adults, and people with respiratory disease, such as asthma, should limit prolonged outdoor exertion.")); + } else if ((data.pm25 > 100.00) && (data.pm25 <= 150.00)) { + thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Unhealthy for Sensitive Groups"); + thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("Active children and adults, and people with respiratory disease, such as asthma, should limit prolonged outdoor exertion.")); + } else if ((data.pm25 > 150.00) && (data.pm25 <= 200.00)) { + thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Unhealthy"); + thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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 ((data.pm25 > 200.00) && (data.pm25 <= 300.00)) { + thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Very Unhealthy"); + thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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 { + thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Hazardous"); + thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("Everyone should avoid all outdoor exertion")); } - QJsonParseError error; - QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error); - if (error.error != QJsonParseError::NoError) { - qDebug(dcAirQualityIndex()) << "Received invalide JSON object"; + } +} + +void IntegrationPluginAqi::onAirQualityStationsReceived(QUuid requestId, QList stations) +{ + if (m_asyncDiscovery.contains(requestId)) { + ThingDiscoveryInfo *info = m_asyncDiscovery.take(requestId); + foreach(AirQualityIndex::Station station, stations) { + ThingDescriptor descriptor(airQualityIndexThingClassId, station.name, "Air Quality Index Station"); + ParamList params; + params << Param(airQualityIndexThingLatitudeParamTypeId, station.location.latitude); + params << Param(airQualityIndexThingLongitudeParamTypeId, station.location.longitude); + descriptor.setParams(params); + info->addThingDescriptor(descriptor); + } + info->finish(Thing::ThingErrorNoError); + } + + if (m_asyncRequests.contains(requestId)) { + Thing *thing = myThings().findById(m_asyncRequests.take(requestId)); + if (!thing) return; - } - 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(); - double windSpeed = iaqi["w"].toMap().value("v").toDouble(); - - foreach (Thing *thing, myThings().filterByThingClassId(airQualityIndexThingClassId)) { - thing->setStateValue(airQualityIndexStationNameStateTypeId, name); - thing->setStateValue(airQualityIndexConnectedStateTypeId, true); - thing->setStateValue(airQualityIndexCoStateTypeId, co); - thing->setStateValue(airQualityIndexHumidityStateTypeId, humidity); - thing->setStateValue(airQualityIndexTemperatureStateTypeId, temperature); - thing->setStateValue(airQualityIndexPressureStateTypeId, pressure); - thing->setStateValue(airQualityIndexO3StateTypeId, o3); - thing->setStateValue(airQualityIndexNo2StateTypeId, no2); - thing->setStateValue(airQualityIndexSo2StateTypeId, so2); - thing->setStateValue(airQualityIndexPm10StateTypeId, pm10); - thing->setStateValue(airQualityIndexPm25StateTypeId, pm25); - thing->setStateValue(airQualityIndexWindSpeedStateTypeId, windSpeed); - - if (pm25 <= 50.00) { - thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Good"); - thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("None")); - } else if ((pm25 > 50.00) && (pm25 <= 100.00)) { - thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Moderate"); - thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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)) { - thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Unhealthy for Sensitive Groups"); - thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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)) { - thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Unhealthy"); - thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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)) { - thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Very Unhealthy"); - thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("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 { - thing->setStateValue(airQualityIndexAirQualityStateTypeId, "Hazardous"); - thing->setStateValue(airQualityIndexCautionaryStatementStateTypeId, tr("Everyone should avoid all outdoor exertion")); - } - } - }); + thing->setStateValue(airQualityIndexConnectedStateTypeId, true); + } } void IntegrationPluginAqi::onPluginTimer() { - getDataByIp(); + if (!m_aqiConnection) + return; + + foreach (Thing *thing, myThings().filterByThingClassId(airQualityIndexThingClassId)) { + + QString longitude = thing->paramValue(airQualityIndexThingLongitudeParamTypeId).toString(); + QString latitude = thing->paramValue(airQualityIndexThingLatitudeParamTypeId).toString(); + QUuid requestId = m_aqiConnection->getDataByGeolocation(latitude, longitude); + m_asyncRequests.insert(requestId, thing->id()); + } } +void IntegrationPluginAqi::onRequestExecuted(QUuid requestId, bool success) +{ + if (m_asyncRequests.contains(requestId)) { - + Thing *thing = myThings().findById(m_asyncRequests.value(requestId)); + thing->setStateValue(airQualityIndexConnectedStateTypeId, success); + if (!success) + m_asyncRequests.remove(requestId); + } +} diff --git a/aqi/integrationpluginaqi.h b/aqi/integrationpluginaqi.h index 3b55eace..14808bfd 100644 --- a/aqi/integrationpluginaqi.h +++ b/aqi/integrationpluginaqi.h @@ -34,6 +34,7 @@ #include "plugintimer.h" #include "integrations/integrationplugin.h" #include "network/networkaccessmanager.h" +#include "airqualityindex.h" #include #include @@ -49,19 +50,26 @@ class IntegrationPluginAqi : public IntegrationPlugin public: explicit IntegrationPluginAqi(); + void startPairing(ThingPairingInfo *info) override; + void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override; + void discoverThings(ThingDiscoveryInfo *info) override; void setupThing(ThingSetupInfo *info) override; void thingRemoved(Thing *thing) override; void postSetupThing(Thing *thing) override; private: PluginTimer *m_pluginTimer = nullptr; - QString m_baseUrl = "https://api.waqi.info"; - QString m_apiKey; + AirQualityIndex *m_aqiConnection = nullptr; - void getDataByIp(); + QHash m_asyncDiscovery; + QHash m_asyncSetups; + QHash m_asyncRequests; private slots: void onPluginTimer(); + void onRequestExecuted(QUuid requestId, bool success); + void onAirQualityDataReceived(QUuid requestId, AirQualityIndex::AirQualityData data); + void onAirQualityStationsReceived(QUuid requestId, QList stations); }; #endif // INTEGRATIONPLUGINAQI_H diff --git a/aqi/integrationpluginaqi.json b/aqi/integrationpluginaqi.json index 3f85210f..85d2c9e9 100644 --- a/aqi/integrationpluginaqi.json +++ b/aqi/integrationpluginaqi.json @@ -2,15 +2,6 @@ "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", @@ -22,8 +13,23 @@ "name": "airQualityIndex", "displayName": "Air quality index", "interfaces": ["windspeedsensor", "humiditysensor", "pressuresensor", "temperaturesensor", "connectable"], - "createMethods": ["user"], + "createMethods": ["discovery", "user"], + "setupMethod": "displaypin", "paramTypes": [ + { + "id": "afd5803b-6c98-44d7-9f4a-45e91cfb062e", + "name": "latitude", + "displayName": "Latitude", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "4800d78e-a367-41f7-9bf6-7c81d40ce19a", + "name": "longitude", + "displayName": "Longitude", + "type": "QString", + "inputType": "TextLine" + } ], "stateTypes": [ { diff --git a/debian/nymea-plugin-aqi.install.in b/debian/nymea-plugin-aqi.install.in index 69ba2fda..e2bd73fd 100644 --- a/debian/nymea-plugin-aqi.install.in +++ b/debian/nymea-plugin-aqi.install.in @@ -1 +1 @@ -usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginaqi.so +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginaqi.so From 66bce1b1ab39b9aa98e0c3cc91f36d3ffa0e69a6 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 24 Mar 2020 12:07:01 +0100 Subject: [PATCH 6/9] fixed uuid bug --- aqi/README.md | 28 +++++++++++++++--- aqi/airqualityindex.cpp | 6 ++-- aqi/integrationpluginaqi.cpp | 57 ++++++++++++++++++++---------------- 3 files changed, 59 insertions(+), 32 deletions(-) diff --git a/aqi/README.md b/aqi/README.md index 9c8bd36e..2af6239c 100644 --- a/aqi/README.md +++ b/aqi/README.md @@ -1,13 +1,29 @@ # 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. - +This plug-in gets air quality information from http://aqicn.org. +Through the WAN IP address the next nearby sensor station will be discovered. +The geo location can also be set manually. ## Supported Things -If you encounter that a value stays to zero, it means the sensor station +* Air Quality Index + * Location discovery + * Manually location set + * Air Quality + * Cautionary statement + * PM2.5 pollution level + * PM10 pollution lebel + * Ozone level + * Nitrogen dioxide level + * Carbon monoxide level + * Sulfur dioxide level + * Temperature + * Humidity + * Pressure + * Wind speed + +NOTE: 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. @@ -16,6 +32,10 @@ inform you what precautions should be taken. ## Requirments +* Valid "Air Quality Index" API Key +* The package "nymea-plugin-airqualityindex" must be installed +* Intenet connection + ## More More about the different Air Quality Levels: https://www.airnow.gov/index.cfm?action=aqibasics.aqi diff --git a/aqi/airqualityindex.cpp b/aqi/airqualityindex.cpp index 3bb20fc0..7dd86351 100644 --- a/aqi/airqualityindex.cpp +++ b/aqi/airqualityindex.cpp @@ -55,7 +55,7 @@ QUuid AirQualityIndex::searchByName(const QString &name) if (m_apiKey.isEmpty()) qCWarning(dcAirQualityIndex()) << "API key is not set"; - QUuid requestId; + QUuid requestId = QUuid::createUuid();; QUrl url; url.setUrl(m_baseUrl); url.setPath("/search/"); @@ -118,7 +118,7 @@ QUuid AirQualityIndex::getDataByIp() if (m_apiKey.isEmpty()) qCWarning(dcAirQualityIndex()) << "API key is not set"; - QUuid requestId; + QUuid requestId = QUuid::createUuid();; QUrl url; url.setUrl(m_baseUrl); url.setPath("/feed/here/"); @@ -153,7 +153,7 @@ QUuid AirQualityIndex::getDataByGeolocation(const QString &lat, const QString &l if (m_apiKey.isEmpty()) qCWarning(dcAirQualityIndex()) << "API key is not set"; - QUuid requestId; + QUuid requestId = QUuid::createUuid(); QUrl url; url.setUrl(m_baseUrl); url.setPath("/feed/geo:"+lat+";"+lng+"/"); diff --git a/aqi/integrationpluginaqi.cpp b/aqi/integrationpluginaqi.cpp index 4e340505..29887096 100644 --- a/aqi/integrationpluginaqi.cpp +++ b/aqi/integrationpluginaqi.cpp @@ -45,27 +45,27 @@ void IntegrationPluginAqi::startPairing(ThingPairingInfo *info) void IntegrationPluginAqi::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) { - Q_UNUSED(username) + Q_UNUSED(username) - QNetworkRequest request(QUrl("https://api.waqi.info/feed/here/?token="+secret)); - QNetworkReply *reply = hardwareManager()->networkManager()->get(request); - connect(reply, &QNetworkReply::finished, info, [this, reply, info, secret](){ - reply->deleteLater(); + QNetworkRequest request(QUrl("https://api.waqi.info/feed/here/?token="+secret)); + QNetworkReply *reply = hardwareManager()->networkManager()->get(request); + connect(reply, &QNetworkReply::finished, info, [this, reply, info, secret](){ + reply->deleteLater(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - // check HTTP status code - if (status != 200) { - //: Error setting up device with invalid token - info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("This token is not valid.")); - return; - } + // check HTTP status code + if (status != 200) { + //: Error setting up device with invalid token + info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("This token is not valid.")); + return; + } - pluginStorage()->beginGroup(info->thingId().toString()); - pluginStorage()->setValue("apiKey", secret); - pluginStorage()->endGroup(); - info->finish(Thing::ThingErrorNoError); - }); + pluginStorage()->beginGroup(info->thingId().toString()); + pluginStorage()->setValue("apiKey", secret); + pluginStorage()->endGroup(); + info->finish(Thing::ThingErrorNoError); + }); } void IntegrationPluginAqi::discoverThings(ThingDiscoveryInfo *info) @@ -78,8 +78,10 @@ void IntegrationPluginAqi::discoverThings(ThingDiscoveryInfo *info) connect(m_aqiConnection, &AirQualityIndex::stationsReceived, this, &IntegrationPluginAqi::onAirQualityStationsReceived); connect(info, &ThingDiscoveryInfo::aborted, [this] { - m_aqiConnection->deleteLater(); - m_aqiConnection = nullptr; + if (myThings().filterByThingClassId(airQualityIndexThingClassId).isEmpty()) { + m_aqiConnection->deleteLater(); + m_aqiConnection = nullptr; + } }); } else { qCDebug(dcAirQualityIndex()) << "AQI connection alread created"; @@ -107,8 +109,10 @@ void IntegrationPluginAqi::setupThing(ThingSetupInfo *info) connect(info, &ThingSetupInfo::aborted, [requestId, this] { m_asyncSetups.remove(requestId); - //m_aqiConnection->deleteLater(); - //m_aqiConnection = nullptr; + if (myThings().filterByThingClassId(airQualityIndexThingClassId).isEmpty()) { + m_aqiConnection->deleteLater(); + m_aqiConnection = nullptr; + } }); } else { info->finish(Thing::ThingErrorNoError); @@ -120,6 +124,7 @@ void IntegrationPluginAqi::setupThing(ThingSetupInfo *info) } + void IntegrationPluginAqi::postSetupThing(Thing *thing) { if (thing->thingClassId() == airQualityIndexThingClassId) { @@ -162,7 +167,7 @@ void IntegrationPluginAqi::thingRemoved(Thing *thing) void IntegrationPluginAqi::onAirQualityDataReceived(QUuid requestId, AirQualityIndex::AirQualityData data) { if (m_asyncSetups.contains(requestId)) { - ThingSetupInfo *info = m_asyncSetups.value(requestId); + ThingSetupInfo *info = m_asyncSetups.take(requestId); return info->finish(Thing::ThingErrorNoError); } @@ -171,7 +176,6 @@ void IntegrationPluginAqi::onAirQualityDataReceived(QUuid requestId, AirQualityI if (!thing) return; - //thing->setStateValue(airQualityIndexStationNameStateTypeId, data); thing->setStateValue(airQualityIndexConnectedStateTypeId, true); thing->setStateValue(airQualityIndexCoStateTypeId, data.co); thing->setStateValue(airQualityIndexHumidityStateTypeId, data.humidity); @@ -221,11 +225,14 @@ void IntegrationPluginAqi::onAirQualityStationsReceived(QUuid requestId, QListfinish(Thing::ThingErrorNoError); } + if (m_asyncRequests.contains(requestId)) { - Thing *thing = myThings().findById(m_asyncRequests.take(requestId)); + Thing * thing = myThings().findById(m_asyncRequests.take(requestId)); if (!thing) return; - thing->setStateValue(airQualityIndexConnectedStateTypeId, true); + if (stations.length() != 0) { + thing->setStateValue(airQualityIndexStationNameStateTypeId, stations.first().name); + } } } From d0b85fe7de8ddd037cf06f608e67c792318da81d Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 24 Mar 2020 12:09:25 +0100 Subject: [PATCH 7/9] updated translation file --- ...69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts | 218 +++++++++--------- 1 file changed, 115 insertions(+), 103 deletions(-) diff --git a/aqi/translations/57d69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts b/aqi/translations/57d69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts index 0278aa05..a7e01783 100644 --- a/aqi/translations/57d69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts +++ b/aqi/translations/57d69b76-4d2d-41ec-bef6-949a79ffbe6b-en_US.ts @@ -4,270 +4,282 @@ AirQualityIndex - - API key - The name of the ParamType (DeviceClass: airQualityIndex, Type: plugin, ID: {a3525f8a-18be-4739-b0ea-ef3af0c7f280}) - - - - + Air Quality Index The name of the vendor ({6c8e2ded-0a33-4e77-b76c-ea02168741ec}) - - + + Air quality - The name of the ParamType (DeviceClass: airQualityIndex, EventType: airQuality, ID: {33a3329a-4117-4488-aa18-91c76056ed6e}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: airQuality, ID: {33a3329a-4117-4488-aa18-91c76056ed6e}) ---------- -The name of the StateType ({33a3329a-4117-4488-aa18-91c76056ed6e}) of DeviceClass airQualityIndex +The name of the StateType ({33a3329a-4117-4488-aa18-91c76056ed6e}) of ThingClass airQualityIndex - + Air quality changed - The name of the EventType ({33a3329a-4117-4488-aa18-91c76056ed6e}) of DeviceClass airQualityIndex + The name of the EventType ({33a3329a-4117-4488-aa18-91c76056ed6e}) of ThingClass airQualityIndex - + Air quality index - The name of the DeviceClass ({23ea32c9-38b0-4155-bacc-3afa8c09f6ee}) + The name of the ThingClass ({23ea32c9-38b0-4155-bacc-3afa8c09f6ee}) - + AirQualityIndex The name of the plugin AirQualityIndex ({57d69b76-4d2d-41ec-bef6-949a79ffbe6b}) - - + + Carbon monoxide level (CO) - The name of the ParamType (DeviceClass: airQualityIndex, EventType: co, ID: {54ac72f3-6444-46a8-a43d-210c2a6fbfb5}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: co, ID: {54ac72f3-6444-46a8-a43d-210c2a6fbfb5}) ---------- -The name of the StateType ({54ac72f3-6444-46a8-a43d-210c2a6fbfb5}) of DeviceClass airQualityIndex +The name of the StateType ({54ac72f3-6444-46a8-a43d-210c2a6fbfb5}) of ThingClass airQualityIndex - + Carbon monoxide level (CO) changed - The name of the EventType ({54ac72f3-6444-46a8-a43d-210c2a6fbfb5}) of DeviceClass airQualityIndex + The name of the EventType ({54ac72f3-6444-46a8-a43d-210c2a6fbfb5}) of ThingClass airQualityIndex - - + + Cautionary statement - The name of the ParamType (DeviceClass: airQualityIndex, EventType: cautionaryStatement, ID: {cfece671-4e88-4c49-9456-e3f8f7c79ab3}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: cautionaryStatement, ID: {cfece671-4e88-4c49-9456-e3f8f7c79ab3}) ---------- -The name of the StateType ({cfece671-4e88-4c49-9456-e3f8f7c79ab3}) of DeviceClass airQualityIndex +The name of the StateType ({cfece671-4e88-4c49-9456-e3f8f7c79ab3}) of ThingClass airQualityIndex - + Cautionary statement changed - The name of the EventType ({cfece671-4e88-4c49-9456-e3f8f7c79ab3}) of DeviceClass airQualityIndex + The name of the EventType ({cfece671-4e88-4c49-9456-e3f8f7c79ab3}) of ThingClass airQualityIndex - - + + Coarse dust particles pollution level (PM10) - The name of the ParamType (DeviceClass: airQualityIndex, EventType: pm10, ID: {24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: pm10, ID: {24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6}) ---------- -The name of the StateType ({24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6}) of DeviceClass airQualityIndex +The name of the StateType ({24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6}) of ThingClass airQualityIndex - + Coarse dust particles pollution level (PM10) changed - The name of the EventType ({24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6}) of DeviceClass airQualityIndex + The name of the EventType ({24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6}) of ThingClass airQualityIndex - - + + Connected - The name of the ParamType (DeviceClass: airQualityIndex, EventType: connected, ID: {7b9135cd-2461-4d33-b2b3-3dc600983895}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: connected, ID: {7b9135cd-2461-4d33-b2b3-3dc600983895}) ---------- -The name of the StateType ({7b9135cd-2461-4d33-b2b3-3dc600983895}) of DeviceClass airQualityIndex +The name of the StateType ({7b9135cd-2461-4d33-b2b3-3dc600983895}) of ThingClass airQualityIndex - + Connected changed - The name of the EventType ({7b9135cd-2461-4d33-b2b3-3dc600983895}) of DeviceClass airQualityIndex + The name of the EventType ({7b9135cd-2461-4d33-b2b3-3dc600983895}) of ThingClass airQualityIndex - - + + Fine particles pollution level (PM2.5) - The name of the ParamType (DeviceClass: airQualityIndex, EventType: pm25, ID: {bc8c4c83-d229-4be4-8732-bc4f2390f399}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: pm25, ID: {bc8c4c83-d229-4be4-8732-bc4f2390f399}) ---------- -The name of the StateType ({bc8c4c83-d229-4be4-8732-bc4f2390f399}) of DeviceClass airQualityIndex +The name of the StateType ({bc8c4c83-d229-4be4-8732-bc4f2390f399}) of ThingClass airQualityIndex - + Fine particles pollution level (PM2.5) changed - The name of the EventType ({bc8c4c83-d229-4be4-8732-bc4f2390f399}) of DeviceClass airQualityIndex + The name of the EventType ({bc8c4c83-d229-4be4-8732-bc4f2390f399}) of ThingClass airQualityIndex - - + + Humidity - The name of the ParamType (DeviceClass: airQualityIndex, EventType: humidity, ID: {4fc45fca-25ab-45a0-b862-817eea1f51e3}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: humidity, ID: {4fc45fca-25ab-45a0-b862-817eea1f51e3}) ---------- -The name of the StateType ({4fc45fca-25ab-45a0-b862-817eea1f51e3}) of DeviceClass airQualityIndex +The name of the StateType ({4fc45fca-25ab-45a0-b862-817eea1f51e3}) of ThingClass airQualityIndex - + Humidity changed - The name of the EventType ({4fc45fca-25ab-45a0-b862-817eea1f51e3}) of DeviceClass airQualityIndex + The name of the EventType ({4fc45fca-25ab-45a0-b862-817eea1f51e3}) of ThingClass airQualityIndex - - + + Latitude + The name of the ParamType (ThingClass: airQualityIndex, Type: thing, ID: {afd5803b-6c98-44d7-9f4a-45e91cfb062e}) + + + + + Longitude + The name of the ParamType (ThingClass: airQualityIndex, Type: thing, ID: {4800d78e-a367-41f7-9bf6-7c81d40ce19a}) + + + + + Nitrogen Dioxide level (NO2) - The name of the ParamType (DeviceClass: airQualityIndex, EventType: no2, ID: {6ed6c505-f36e-44c4-a982-f395b04e539b}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: no2, ID: {6ed6c505-f36e-44c4-a982-f395b04e539b}) ---------- -The name of the StateType ({6ed6c505-f36e-44c4-a982-f395b04e539b}) of DeviceClass airQualityIndex +The name of the StateType ({6ed6c505-f36e-44c4-a982-f395b04e539b}) of ThingClass airQualityIndex - + Nitrogen Dioxide level (NO2) changed - The name of the EventType ({6ed6c505-f36e-44c4-a982-f395b04e539b}) of DeviceClass airQualityIndex + The name of the EventType ({6ed6c505-f36e-44c4-a982-f395b04e539b}) of ThingClass airQualityIndex - - + + Ozone level (O3) - The name of the ParamType (DeviceClass: airQualityIndex, EventType: o3, ID: {4e88526d-009f-4820-9a84-09b3646d23c9}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: o3, ID: {4e88526d-009f-4820-9a84-09b3646d23c9}) ---------- -The name of the StateType ({4e88526d-009f-4820-9a84-09b3646d23c9}) of DeviceClass airQualityIndex +The name of the StateType ({4e88526d-009f-4820-9a84-09b3646d23c9}) of ThingClass airQualityIndex - + Ozone level (O3) changed - The name of the EventType ({4e88526d-009f-4820-9a84-09b3646d23c9}) of DeviceClass airQualityIndex + The name of the EventType ({4e88526d-009f-4820-9a84-09b3646d23c9}) of ThingClass airQualityIndex - - + + Pressure - The name of the ParamType (DeviceClass: airQualityIndex, EventType: pressure, ID: {5f799040-08f8-44d1-aa0a-4cab7caad839}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: pressure, ID: {5f799040-08f8-44d1-aa0a-4cab7caad839}) ---------- -The name of the StateType ({5f799040-08f8-44d1-aa0a-4cab7caad839}) of DeviceClass airQualityIndex +The name of the StateType ({5f799040-08f8-44d1-aa0a-4cab7caad839}) of ThingClass airQualityIndex - + Pressure changed - The name of the EventType ({5f799040-08f8-44d1-aa0a-4cab7caad839}) of DeviceClass airQualityIndex + The name of the EventType ({5f799040-08f8-44d1-aa0a-4cab7caad839}) of ThingClass airQualityIndex - - + + Station name - The name of the ParamType (DeviceClass: airQualityIndex, EventType: stationName, ID: {8385f3d5-62f7-482e-927c-b5d61a70d607}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: stationName, ID: {8385f3d5-62f7-482e-927c-b5d61a70d607}) ---------- -The name of the StateType ({8385f3d5-62f7-482e-927c-b5d61a70d607}) of DeviceClass airQualityIndex +The name of the StateType ({8385f3d5-62f7-482e-927c-b5d61a70d607}) of ThingClass airQualityIndex - + Station name changed - The name of the EventType ({8385f3d5-62f7-482e-927c-b5d61a70d607}) of DeviceClass airQualityIndex + The name of the EventType ({8385f3d5-62f7-482e-927c-b5d61a70d607}) of ThingClass airQualityIndex - - + + Sulfur dioxide level (SO2) - The name of the ParamType (DeviceClass: airQualityIndex, EventType: so2, ID: {f3a05e65-a9b3-48fd-be43-688d4c293cc9}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: so2, ID: {f3a05e65-a9b3-48fd-be43-688d4c293cc9}) ---------- -The name of the StateType ({f3a05e65-a9b3-48fd-be43-688d4c293cc9}) of DeviceClass airQualityIndex +The name of the StateType ({f3a05e65-a9b3-48fd-be43-688d4c293cc9}) of ThingClass airQualityIndex - + Sulfur dioxide level (SO2) changed - The name of the EventType ({f3a05e65-a9b3-48fd-be43-688d4c293cc9}) of DeviceClass airQualityIndex + The name of the EventType ({f3a05e65-a9b3-48fd-be43-688d4c293cc9}) of ThingClass airQualityIndex - - + + Temperature - The name of the ParamType (DeviceClass: airQualityIndex, EventType: temperature, ID: {94219802-0a82-4761-99b3-c6b6dfc096db}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: temperature, ID: {94219802-0a82-4761-99b3-c6b6dfc096db}) ---------- -The name of the StateType ({94219802-0a82-4761-99b3-c6b6dfc096db}) of DeviceClass airQualityIndex +The name of the StateType ({94219802-0a82-4761-99b3-c6b6dfc096db}) of ThingClass airQualityIndex - + Temperature changed - The name of the EventType ({94219802-0a82-4761-99b3-c6b6dfc096db}) of DeviceClass airQualityIndex + The name of the EventType ({94219802-0a82-4761-99b3-c6b6dfc096db}) of ThingClass airQualityIndex - - + + Wind speed - The name of the ParamType (DeviceClass: airQualityIndex, EventType: windSpeed, ID: {c4366608-2511-428b-964e-2ad9e37f8f3c}) + The name of the ParamType (ThingClass: airQualityIndex, EventType: windSpeed, ID: {c4366608-2511-428b-964e-2ad9e37f8f3c}) ---------- -The name of the StateType ({c4366608-2511-428b-964e-2ad9e37f8f3c}) of DeviceClass airQualityIndex +The name of the StateType ({c4366608-2511-428b-964e-2ad9e37f8f3c}) of ThingClass airQualityIndex - + Wind speed changed - The name of the EventType ({c4366608-2511-428b-964e-2ad9e37f8f3c}) of DeviceClass airQualityIndex + The name of the EventType ({c4366608-2511-428b-964e-2ad9e37f8f3c}) of ThingClass airQualityIndex - DevicePluginAqi + IntegrationPluginAqi - - Service is already in use. + + Please enter your API token for Air Quality Index - + + This token is not valid. + Error setting up device with invalid token + + + + None - - + + Active children and adults, and people with respiratory disease, such as asthma, should limit prolonged outdoor exertion. - + 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 - + 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. - + Everyone should avoid all outdoor exertion From 1ba818c69fe4c6629a0e57a09249939336892566 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 24 Mar 2020 14:33:51 +0100 Subject: [PATCH 8/9] changes requested by reviewer --- aqi/README.md | 2 +- aqi/integrationpluginaqi.cpp | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/aqi/README.md b/aqi/README.md index 2af6239c..6e18d23f 100644 --- a/aqi/README.md +++ b/aqi/README.md @@ -34,7 +34,7 @@ inform you what precautions should be taken. * Valid "Air Quality Index" API Key * The package "nymea-plugin-airqualityindex" must be installed -* Intenet connection +* Internet connection ## More diff --git a/aqi/integrationpluginaqi.cpp b/aqi/integrationpluginaqi.cpp index 29887096..99225f32 100644 --- a/aqi/integrationpluginaqi.cpp +++ b/aqi/integrationpluginaqi.cpp @@ -88,6 +88,7 @@ void IntegrationPluginAqi::discoverThings(ThingDiscoveryInfo *info) } QUuid requestId = m_aqiConnection->getDataByIp(); m_asyncDiscovery.insert(requestId, info); + connect(info, &ThingDiscoveryInfo::aborted, [=] {m_asyncDiscovery.remove(requestId);}); } void IntegrationPluginAqi::setupThing(ThingSetupInfo *info) @@ -123,8 +124,6 @@ void IntegrationPluginAqi::setupThing(ThingSetupInfo *info) } } - - void IntegrationPluginAqi::postSetupThing(Thing *thing) { if (thing->thingClassId() == airQualityIndexThingClassId) { @@ -144,13 +143,9 @@ void IntegrationPluginAqi::postSetupThing(Thing *thing) } } - void IntegrationPluginAqi::thingRemoved(Thing *thing) { - if (thing->thingClassId() == airQualityIndexThingClassId) { - - } - + Q_UNUSED(thing) if (myThings().empty()) { if (!m_pluginTimer) { hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); @@ -163,7 +158,6 @@ void IntegrationPluginAqi::thingRemoved(Thing *thing) } } - void IntegrationPluginAqi::onAirQualityDataReceived(QUuid requestId, AirQualityIndex::AirQualityData data) { if (m_asyncSetups.contains(requestId)) { From 44407f36a2fe38e2fc22a7ee2ef7a1cdd29253e5 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Fri, 10 Apr 2020 16:57:42 +0200 Subject: [PATCH 9/9] fixed missing requestId issue --- aqi/airqualityindex.cpp | 17 +++++++------ aqi/airqualityindex.h | 2 +- aqi/integrationpluginaqi.cpp | 48 +++++++++++++++++++++++++++--------- 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/aqi/airqualityindex.cpp b/aqi/airqualityindex.cpp index 7dd86351..9373a2c8 100644 --- a/aqi/airqualityindex.cpp +++ b/aqi/airqualityindex.cpp @@ -91,7 +91,6 @@ QUuid AirQualityIndex::searchByName(const QString &name) qCWarning(dcAirQualityIndex()) << "Received invalide JSON object"; return; } - emit requestExecuted(requestId, true); QList stations; QVariantList stationList = doc.toVariant().toMap().value("data").toList(); @@ -109,6 +108,8 @@ QUuid AirQualityIndex::searchByName(const QString &name) } if (!stations.isEmpty()) emit stationsReceived(requestId, stations); + + requestExecuted(requestId, true); }); return requestId; } @@ -143,7 +144,9 @@ QUuid AirQualityIndex::getDataByIp() qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString(); return; } - parseData(requestId, reply->readAll()); + if (!parseData(requestId, reply->readAll())) + requestExecuted(requestId, false); + requestExecuted(requestId, true); }); return requestId; } @@ -178,24 +181,23 @@ QUuid AirQualityIndex::getDataByGeolocation(const QString &lat, const QString &l qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString(); return; } + if (!parseData(requestId, reply->readAll())) + requestExecuted(requestId, false); requestExecuted(requestId, true); - parseData(requestId, reply->readAll()); }); return requestId; } -void AirQualityIndex::parseData(QUuid requestId, const QByteArray &data) +bool AirQualityIndex::parseData(QUuid requestId, const QByteArray &data) { qCDebug(dcAirQualityIndex()) << "Parsing data" << data; QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { - emit requestExecuted(requestId, false); qCWarning(dcAirQualityIndex()) << "Received invalide JSON object"; - return; + return false; } - emit requestExecuted(requestId, true); Station station; station.aqi = doc.toVariant().toMap().value("data").toMap().value("aqi").toInt(); station.idx = doc.toVariant().toMap().value("data").toMap().value("idx").toInt(); @@ -228,4 +230,5 @@ void AirQualityIndex::parseData(QUuid requestId, const QByteArray &data) aqiData.temperature = iaqi["t"].toMap().value("v").toDouble(); aqiData.windSpeed = iaqi["w"].toMap().value("v").toDouble(); emit dataReceived(requestId, aqiData); + return true; } diff --git a/aqi/airqualityindex.h b/aqi/airqualityindex.h index 5f15b8d1..fd332c42 100644 --- a/aqi/airqualityindex.h +++ b/aqi/airqualityindex.h @@ -80,7 +80,7 @@ private: QString m_baseUrl = "https://api.waqi.info"; QString m_apiKey; - void parseData(QUuid requestId, const QByteArray &data); + bool parseData(QUuid requestId, const QByteArray &data); signals: void stationsReceived(QUuid requestId, QList stations); diff --git a/aqi/integrationpluginaqi.cpp b/aqi/integrationpluginaqi.cpp index 99225f32..f6a21a77 100644 --- a/aqi/integrationpluginaqi.cpp +++ b/aqi/integrationpluginaqi.cpp @@ -40,7 +40,17 @@ IntegrationPluginAqi::IntegrationPluginAqi() void IntegrationPluginAqi::startPairing(ThingPairingInfo *info) { - info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter your API token for Air Quality Index")); + NetworkAccessManager *network = hardwareManager()->networkManager(); + QNetworkReply *reply = network->get(QNetworkRequest(QUrl("https://api.waqi.info"))); + connect(reply, &QNetworkReply::finished, this, [reply, info] { + reply->deleteLater(); + + if (reply->error() == QNetworkReply::NetworkError::HostNotFoundError) { + info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Air quality index server is not reachable.")); + } else { + info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter your API token for Air Quality Index")); + } + }); } void IntegrationPluginAqi::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) @@ -116,6 +126,13 @@ void IntegrationPluginAqi::setupThing(ThingSetupInfo *info) } }); } else { + // An AQI connection might be setup because of an discovery request + // or because there is already another thing using the connection + // In any case the API key is being updated to avoid using the discovery key. + pluginStorage()->beginGroup(info->thing()->id().toString()); + QString apiKey = pluginStorage()->value("apiKey").toString(); + pluginStorage()->endGroup(); + m_aqiConnection->setApiKey(apiKey); info->finish(Thing::ThingErrorNoError); } } else { @@ -128,8 +145,10 @@ void IntegrationPluginAqi::postSetupThing(Thing *thing) { if (thing->thingClassId() == airQualityIndexThingClassId) { - if (!m_aqiConnection) + if (!m_aqiConnection) { + qCWarning(dcAirQualityIndex()) << "Air quality connection not initialized"; return; + } QString longitude = thing->paramValue(airQualityIndexThingLongitudeParamTypeId).toString(); QString latitude = thing->paramValue(airQualityIndexThingLatitudeParamTypeId).toString(); @@ -147,11 +166,10 @@ void IntegrationPluginAqi::thingRemoved(Thing *thing) { Q_UNUSED(thing) if (myThings().empty()) { - if (!m_pluginTimer) { + if (m_pluginTimer) { hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); - m_pluginTimer = nullptr; - } - if (!m_aqiConnection) { + m_pluginTimer = nullptr; } + if (m_aqiConnection) { m_aqiConnection->deleteLater(); m_aqiConnection = nullptr; } @@ -160,8 +178,10 @@ void IntegrationPluginAqi::thingRemoved(Thing *thing) void IntegrationPluginAqi::onAirQualityDataReceived(QUuid requestId, AirQualityIndex::AirQualityData data) { + qCDebug(dcAirQualityIndex()) << "Air Quality data received, request id:" << requestId << "is an async request:" << m_asyncRequests.contains(requestId); + if (m_asyncSetups.contains(requestId)) { - ThingSetupInfo *info = m_asyncSetups.take(requestId); + ThingSetupInfo *info = m_asyncSetups.value(requestId); return info->finish(Thing::ThingErrorNoError); } @@ -206,6 +226,7 @@ void IntegrationPluginAqi::onAirQualityDataReceived(QUuid requestId, AirQualityI void IntegrationPluginAqi::onAirQualityStationsReceived(QUuid requestId, QList stations) { + qCDebug(dcAirQualityIndex()) << "Air Quality Stations received, request id:" << requestId << "is an async request:" << m_asyncRequests.contains(requestId); if (m_asyncDiscovery.contains(requestId)) { ThingDiscoveryInfo *info = m_asyncDiscovery.take(requestId); foreach(AirQualityIndex::Station station, stations) { @@ -221,9 +242,11 @@ void IntegrationPluginAqi::onAirQualityStationsReceived(QUuid requestId, QListsetStateValue(airQualityIndexStationNameStateTypeId, stations.first().name); } @@ -246,11 +269,14 @@ void IntegrationPluginAqi::onPluginTimer() void IntegrationPluginAqi::onRequestExecuted(QUuid requestId, bool success) { + qCDebug(dcAirQualityIndex()) << "Request executd, requestId:" << requestId << "Success:" << success << "is an async request:" << m_asyncRequests.contains(requestId); if (m_asyncRequests.contains(requestId)) { Thing *thing = myThings().findById(m_asyncRequests.value(requestId)); thing->setStateValue(airQualityIndexConnectedStateTypeId, success); - if (!success) - m_asyncRequests.remove(requestId); + if (!success) { + qCWarning(dcAirQualityIndex()) << "Request failed, removing request from async request list"; + } + m_asyncRequests.remove(requestId); } }