devices to things

This commit is contained in:
bernhard.trinnes 2020-03-23 14:49:40 +01:00
parent cb6936261e
commit 3af263c91a
7 changed files with 275 additions and 254 deletions

View File

@ -4,6 +4,9 @@
This plug-in gets the air quality information from http://aqicn.org. This plug-in gets the air quality information from http://aqicn.org.
Through your IP address the next nearby sensor station will be discovered. 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 If you encounter that a value stays to zero, it means the sensor station
doesn't support that value. 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 Both states can be used to let nymea notify you about the pollution level and
inform you what precautions should be taken. 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 More about the different Air Quality Levels: https://www.airnow.gov/index.cfm?action=aqibasics.aqi

View File

@ -1,12 +1,10 @@
include(../plugins.pri) include(../plugins.pri)
TARGET = $$qtLibraryTarget(nymea_devicepluginaqi)
QT+= network QT+= network
SOURCES += \ SOURCES += \
devicepluginaqi.cpp \ integrationpluginaqi.cpp \
HEADERS += \ HEADERS += \
devicepluginaqi.h \ integrationpluginaqi.h \

View File

@ -1,189 +0,0 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2020 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
* *
* 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 *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "devicepluginaqi.h"
#include "plugininfo.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QUrlQuery>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
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();
}

View File

@ -1,59 +0,0 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2020 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
* *
* 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 *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DEVICEPLUGINAQI_H
#define DEVICEPLUGINAQI_H
#include "plugintimer.h"
#include "devices/deviceplugin.h"
#include "network/networkaccessmanager.h"
#include <QTimer>
#include <QUrl>
#include <QHostAddress>
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

View File

@ -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 <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 "integrationpluginaqi.h"
#include "plugininfo.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QUrlQuery>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
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();
}

View File

@ -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 <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 INTEGRATIONPLUGINAQI_H
#define INTEGRATIONPLUGINAQI_H
#include "plugintimer.h"
#include "integrations/integrationplugin.h"
#include "network/networkaccessmanager.h"
#include <QTimer>
#include <QUrl>
#include <QHostAddress>
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

View File

@ -16,7 +16,7 @@
"name": "airQualityIndex", "name": "airQualityIndex",
"displayName": "Air Quality Index", "displayName": "Air Quality Index",
"id": "6c8e2ded-0a33-4e77-b76c-ea02168741ec", "id": "6c8e2ded-0a33-4e77-b76c-ea02168741ec",
"deviceClasses": [ "thingClasses": [
{ {
"id": "23ea32c9-38b0-4155-bacc-3afa8c09f6ee", "id": "23ea32c9-38b0-4155-bacc-3afa8c09f6ee",
"name": "airQualityIndex", "name": "airQualityIndex",
@ -144,7 +144,7 @@
"name": "pressure", "name": "pressure",
"displayName": "Pressure", "displayName": "Pressure",
"displayNameEvent": "Pressure changed", "displayNameEvent": "Pressure changed",
"unit": "HectoPascal", "unit": "MilliBar",
"type": "double", "type": "double",
"defaultValue": 0 "defaultValue": 0
}, },