added pin setup method and discovery
This commit is contained in:
parent
3af263c91a
commit
ebd094c57b
231
aqi/airqualityindex.cpp
Normal file
231
aqi/airqualityindex.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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 <QNetworkReply>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonArray>
|
||||||
|
|
||||||
|
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<Station> 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>() << 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);
|
||||||
|
}
|
||||||
91
aqi/airqualityindex.h
Normal file
91
aqi/airqualityindex.h
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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 <QObject>
|
||||||
|
#include <QUuid>
|
||||||
|
#include <QTime>
|
||||||
|
|
||||||
|
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<Station> stations);
|
||||||
|
void requestExecuted(QUuid requestId, bool success);
|
||||||
|
void dataReceived(QUuid requestId, const AirQualityData &data);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // AIRQUALITYINDEX_H
|
||||||
@ -3,8 +3,10 @@ include(../plugins.pri)
|
|||||||
QT+= network
|
QT+= network
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
airqualityindex.cpp \
|
||||||
integrationpluginaqi.cpp \
|
integrationpluginaqi.cpp \
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
|
airqualityindex.h \
|
||||||
integrationpluginaqi.h \
|
integrationpluginaqi.h \
|
||||||
|
|
||||||
|
|||||||
@ -32,59 +32,106 @@
|
|||||||
#include "plugininfo.h"
|
#include "plugininfo.h"
|
||||||
|
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QUrlQuery>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonArray>
|
|
||||||
|
|
||||||
IntegrationPluginAqi::IntegrationPluginAqi()
|
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)
|
void IntegrationPluginAqi::setupThing(ThingSetupInfo *info)
|
||||||
{
|
{
|
||||||
if (info->thing()->thingClassId() == airQualityIndexThingClassId) {
|
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()) {
|
QString longitude = info->thing()->paramValue(airQualityIndexThingLongitudeParamTypeId).toString();
|
||||||
if (!myThings().findById(info->thing()->id())) {
|
QString latitude = info->thing()->paramValue(airQualityIndexThingLatitudeParamTypeId).toString();
|
||||||
info->finish(Thing::ThingErrorSetupFailed, tr("Service is already in use."));
|
QUuid requestId = m_aqiConnection->getDataByGeolocation(latitude, longitude);
|
||||||
return;
|
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);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
QUrl url;
|
qCWarning(dcAirQualityIndex()) << "setupThing - thing class id not found" << info->thing()->thingClassId();
|
||||||
url.setUrl(m_baseUrl);
|
info->finish(Thing::ThingErrorSetupFailed);
|
||||||
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)
|
void IntegrationPluginAqi::postSetupThing(Thing *thing)
|
||||||
{
|
{
|
||||||
Q_UNUSED(thing);
|
if (thing->thingClassId() == airQualityIndexThingClassId) {
|
||||||
getDataByIp();
|
|
||||||
|
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) {
|
if(!m_pluginTimer) {
|
||||||
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60);
|
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60);
|
||||||
@ -95,103 +142,114 @@ void IntegrationPluginAqi::postSetupThing(Thing *thing)
|
|||||||
|
|
||||||
void IntegrationPluginAqi::thingRemoved(Thing *thing)
|
void IntegrationPluginAqi::thingRemoved(Thing *thing)
|
||||||
{
|
{
|
||||||
Q_UNUSED(thing);
|
if (thing->thingClassId() == airQualityIndexThingClassId) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if (myThings().empty()) {
|
if (myThings().empty()) {
|
||||||
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
|
if (!m_pluginTimer) {
|
||||||
m_pluginTimer = nullptr;
|
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;
|
if (m_asyncSetups.contains(requestId)) {
|
||||||
url.setUrl(m_baseUrl);
|
ThingSetupInfo *info = m_asyncSetups.value(requestId);
|
||||||
url.setPath("/feed/here/");
|
return info->finish(Thing::ThingErrorNoError);
|
||||||
url.setQuery("token="+configValue(airQualityIndexPluginApiKeyParamTypeId).toString());
|
}
|
||||||
QNetworkRequest request;
|
|
||||||
request.setUrl(url);
|
|
||||||
request.setRawHeader("User-Agent", "nymea");
|
|
||||||
|
|
||||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
|
if (m_asyncRequests.contains(requestId)) {
|
||||||
connect(reply, &QNetworkReply::finished, this, [reply, this] {
|
Thing * thing = myThings().findById(m_asyncRequests.take(requestId));
|
||||||
reply->deleteLater();
|
if (!thing)
|
||||||
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;
|
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<AirQualityIndex::Station> 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;
|
return;
|
||||||
}
|
thing->setStateValue(airQualityIndexConnectedStateTypeId, true);
|
||||||
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()
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@
|
|||||||
#include "plugintimer.h"
|
#include "plugintimer.h"
|
||||||
#include "integrations/integrationplugin.h"
|
#include "integrations/integrationplugin.h"
|
||||||
#include "network/networkaccessmanager.h"
|
#include "network/networkaccessmanager.h"
|
||||||
|
#include "airqualityindex.h"
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
@ -49,19 +50,26 @@ class IntegrationPluginAqi : public IntegrationPlugin
|
|||||||
public:
|
public:
|
||||||
explicit IntegrationPluginAqi();
|
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 setupThing(ThingSetupInfo *info) override;
|
||||||
void thingRemoved(Thing *thing) override;
|
void thingRemoved(Thing *thing) override;
|
||||||
void postSetupThing(Thing *thing) override;
|
void postSetupThing(Thing *thing) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PluginTimer *m_pluginTimer = nullptr;
|
PluginTimer *m_pluginTimer = nullptr;
|
||||||
QString m_baseUrl = "https://api.waqi.info";
|
AirQualityIndex *m_aqiConnection = nullptr;
|
||||||
QString m_apiKey;
|
|
||||||
|
|
||||||
void getDataByIp();
|
QHash<QUuid, ThingDiscoveryInfo *> m_asyncDiscovery;
|
||||||
|
QHash<QUuid, ThingSetupInfo *> m_asyncSetups;
|
||||||
|
QHash<QUuid, ThingId> m_asyncRequests;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onPluginTimer();
|
void onPluginTimer();
|
||||||
|
void onRequestExecuted(QUuid requestId, bool success);
|
||||||
|
void onAirQualityDataReceived(QUuid requestId, AirQualityIndex::AirQualityData data);
|
||||||
|
void onAirQualityStationsReceived(QUuid requestId, QList<AirQualityIndex::Station> stations);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // INTEGRATIONPLUGINAQI_H
|
#endif // INTEGRATIONPLUGINAQI_H
|
||||||
|
|||||||
@ -2,15 +2,6 @@
|
|||||||
"name": "AirQualityIndex",
|
"name": "AirQualityIndex",
|
||||||
"displayName": "AirQualityIndex",
|
"displayName": "AirQualityIndex",
|
||||||
"id": "57d69b76-4d2d-41ec-bef6-949a79ffbe6b",
|
"id": "57d69b76-4d2d-41ec-bef6-949a79ffbe6b",
|
||||||
"paramTypes": [
|
|
||||||
{
|
|
||||||
"id": "a3525f8a-18be-4739-b0ea-ef3af0c7f280",
|
|
||||||
"name": "apiKey",
|
|
||||||
"displayName": "API key",
|
|
||||||
"type": "QString",
|
|
||||||
"defaultValue": "74d31bb5ad9bcdeaed48097418b55188cb56d450"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
"name": "airQualityIndex",
|
"name": "airQualityIndex",
|
||||||
@ -22,8 +13,23 @@
|
|||||||
"name": "airQualityIndex",
|
"name": "airQualityIndex",
|
||||||
"displayName": "Air quality index",
|
"displayName": "Air quality index",
|
||||||
"interfaces": ["windspeedsensor", "humiditysensor", "pressuresensor", "temperaturesensor", "connectable"],
|
"interfaces": ["windspeedsensor", "humiditysensor", "pressuresensor", "temperaturesensor", "connectable"],
|
||||||
"createMethods": ["user"],
|
"createMethods": ["discovery", "user"],
|
||||||
|
"setupMethod": "displaypin",
|
||||||
"paramTypes": [
|
"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": [
|
"stateTypes": [
|
||||||
{
|
{
|
||||||
|
|||||||
2
debian/nymea-plugin-aqi.install.in
vendored
2
debian/nymea-plugin-aqi.install.in
vendored
@ -1 +1 @@
|
|||||||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginaqi.so
|
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginaqi.so
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user