nymea-plugins/aqi/airqualityindex.cpp

264 lines
10 KiB
C++

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, 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, not sending request";
return QUuid();
}
QUuid requestId = QUuid::createUuid();;
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";
emit 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;
}
QList<Station> stations;
QVariantList stationList = doc.toVariant().toMap().value("data").toList();
foreach (const 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);
emit requestExecuted(requestId, true);
});
return requestId;
}
QUuid AirQualityIndex::getDataByIp()
{
if (m_apiKey.isEmpty()) {
qCWarning(dcAirQualityIndex()) << "API key is not set, not sending request";
return QUuid();
}
QUuid requestId = QUuid::createUuid();
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";
emit requestExecuted(requestId, false);
qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString();
return;
}
if (!parseData(requestId, reply->readAll()))
emit requestExecuted(requestId, false);
emit requestExecuted(requestId, true);
});
return requestId;
}
QUuid AirQualityIndex::getDataByGeolocation(double lat, double lng)
{
if (m_apiKey.isEmpty()) {
qCWarning(dcAirQualityIndex()) << "API key is not set, not sending request";
return QUuid();
}
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setUrl(m_baseUrl);
url.setPath(QString("/feed/geo:%1;%2/").arg(lat).arg(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";
emit requestExecuted(requestId, false);
qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString();
return;
}
if (!parseData(requestId, reply->readAll()))
emit requestExecuted(requestId, false);
emit requestExecuted(requestId, true);
});
return requestId;
}
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) {
qCWarning(dcAirQualityIndex()) << "Received invalide JSON object";
return false;
}
if (doc.toVariant().toMap().contains("status")) {
if (doc.toVariant().toMap().value("status") == "error") {
qCWarning(dcAirQualityIndex()) << "Server responded with error:" << doc.toVariant().toMap().value("data").toString();
return false;
}
}
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);
return true;
}