450 lines
13 KiB
C++
450 lines
13 KiB
C++
#include "pws2mqtt.h"
|
||
#include "mqtt.h"
|
||
//#include "httpserver.h"
|
||
#include <unistd.h>
|
||
#include <QtGlobal>
|
||
#include <iostream>
|
||
#include <QList>
|
||
#include <sys/socket.h>
|
||
#include <netinet/in.h>
|
||
#include <QtHttpServer/QHttpServer>
|
||
#include <QtHttpServer/QHttpServerRequest>
|
||
#include <curl/curl.h>
|
||
#include <QtMath>
|
||
|
||
extern MqttClient *mqttClient;
|
||
extern Pws2mqtt *pws2mqtt;
|
||
extern QHttpServer *httpServer;
|
||
QStringList previsionList {"Temps variable (incertain).", \
|
||
"Beau temps stable (anticyclone).", \
|
||
"Amélioration progressive (éclaircies).", \
|
||
"Risque de pluie légère ou nuages.", \
|
||
"Dégradation marquée (pluie/vent/orage).", \
|
||
"Tempête ou dépression forte (vigilance)." \
|
||
};
|
||
|
||
// Callback for curl library
|
||
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
|
||
{
|
||
(void)contents; // Évite les warnings de compilation
|
||
(void)userp;
|
||
return size * nmemb; // On ne traite pas les données reçues
|
||
}
|
||
|
||
QMap <QByteArray, qfloat16> propertyList;
|
||
QMap <QByteArray, QPair<QString, QByteArray>> propertyName
|
||
{
|
||
{"tempf", {"Température extérieure", "°C"}},
|
||
{"humidity", {"Humidité extérieure", "%"}},
|
||
{"dewptf", {"Point de rosée", "°C"}},
|
||
{"windchillf", {"Température ressentie", "°C"}},
|
||
{"winddir", {"Direction du vent", "°"}},
|
||
{"windspeedmph", {"vitesse du vent", "km/h"}},
|
||
{"windgustmph", {"Rafales", "km/h"}},
|
||
{"rainin", {"Pluie", "mm/h"}},
|
||
{"dailyrainin", {"Pluie de la journée", "mm"}},
|
||
{"monthlyrainin", {"Pluie du mois", "mm"}},
|
||
{"yearlyrainin", {"Pluie de l'année", "mm"}},
|
||
{"solarradiation", {"Énergie solaire", "W/m²"}},
|
||
{"indoortempf", {"Température intérieure", "°C"}},
|
||
{"indoorhumidity", {"Humidité intérieure", "%"}},
|
||
{"baromin", {"pression atmosphérique", "hPa"}},
|
||
{"lowbatt", {"Alerte batterie faible", ""}},
|
||
{"UV", {"UV", ""}}
|
||
};
|
||
|
||
|
||
Pws2mqtt::Pws2mqtt()
|
||
{
|
||
this->init();
|
||
}
|
||
|
||
Pws2mqtt::~Pws2mqtt()
|
||
{
|
||
}
|
||
|
||
void Pws2mqtt::init()
|
||
{
|
||
debug(DEBUGMACRO, "init http server", DEBUG);
|
||
httpServer->setMissingHandler([](const QHttpServerRequest &request, QHttpServerResponder &&responder)
|
||
{
|
||
(void) responder;
|
||
debug(DEBUGMACRO, "body " + request.url().toString(), WARNING);
|
||
return QHttpServerResponse("404 - Page non trouvée. Route par défaut.", QHttpServerResponse::StatusCode::NotFound);
|
||
//responder.write(QHttpServerResponse("404 - Page non trouvée. Route par défaut.", QHttpServerResponse::StatusCode::NotFound));
|
||
});
|
||
|
||
httpServer->route("/", [](const QHttpServerRequest &request)
|
||
{
|
||
QList<std::pair<QByteArray, QByteArray>> headersList = request.headers().toList();
|
||
debug(DEBUGMACRO, "Remote address" + request.remoteAddress().toString(), WARNING);
|
||
return QHttpServerResponse("text/plain", "Failed\n");
|
||
});
|
||
httpServer->route("/query", [this](const QHttpServerRequest &request)
|
||
{
|
||
QByteArray data;
|
||
QList<std::pair<QString, QString>> queryList = request.query().queryItems();
|
||
//QTextStream result(&data);
|
||
|
||
if (queryList.isEmpty())
|
||
{
|
||
debug(DEBUGMACRO, "Request query is empty", WARNING);
|
||
}else
|
||
{
|
||
debug(DEBUGMACRO, "Request query :" + request.query().toString() , DEBUG);
|
||
this->parseData(queryList);
|
||
debug(DEBUGMACRO, "Returning 'success'", DEBUG);
|
||
}
|
||
//mqttClient.send_message(jsonString);
|
||
|
||
return QHttpServerResponse("text/plain", "Success\n");
|
||
});
|
||
}
|
||
|
||
void Pws2mqtt::listeningHttp()
|
||
{
|
||
//QByteArray data;
|
||
debug (DEBUGMACRO, "listening http requests", DEBUG);
|
||
const auto port = httpServer->listen(QHostAddress::Any, 5000);
|
||
if (!port)
|
||
{
|
||
debug(DEBUGMACRO, "Http Server failed to listen on a port.", ERROR);
|
||
//return 0;
|
||
}
|
||
debug(DEBUGMACRO, "Listening on port " + QString::number(port));
|
||
|
||
}
|
||
|
||
void Pws2mqtt::parseData(QList<std::pair<QString, QString>> queryList)
|
||
{
|
||
debug(DEBUGMACRO, "Parsing Datas", DEBUG);
|
||
|
||
QString jsonString = "{";
|
||
bool propertyFlag = false;
|
||
bool deviceFlag = false;
|
||
QString spaceFlag = "";
|
||
// QString topic;
|
||
QString deviceString = "\"device\": {\"ieeeAddress\": \"" + mqttClient->macAddress + "\", \"type\": \"" + mqttClient->type + "\", \"powerSource\": \"Battery\"";
|
||
QString notif = "";
|
||
double propertyValue;
|
||
QStringList priorityList {"", "min", "low", "default", "High", "urgent"};
|
||
quint8 priority = 2;
|
||
|
||
debug(DEBUGMACRO, "looping list of query", DEBUG);
|
||
|
||
for (QPair<QString, QString> pair : queryList)
|
||
{
|
||
debug(DEBUGMACRO, pair.first + " = " + pair.second, DEBUG);
|
||
|
||
if (this->deviceProperties.contains(pair.first))
|
||
{
|
||
if(deviceFlag == false)
|
||
{
|
||
deviceFlag = true;
|
||
}else
|
||
{
|
||
deviceString.append(", ");
|
||
}
|
||
deviceString += "\"" + pair.first + "\": ";
|
||
deviceString += pair.second;
|
||
}else
|
||
{
|
||
if(propertyFlag == false)
|
||
{
|
||
propertyFlag = true;
|
||
}else
|
||
{
|
||
jsonString.append(", ");
|
||
}
|
||
jsonString.append("\"" + pair.first + "\": ");
|
||
jsonString.append(pair.second);
|
||
//mqttClient->end_message(topic, jsonString);
|
||
if (pair.first == "tempf")
|
||
{
|
||
propertyValue = fahrenheitToCelsius(pair.second.toFloat());
|
||
if (compare (propertyList[pair.first.toLatin1()], propertyValue, 0.5))
|
||
{
|
||
notif += spaceFlag + formatNotifString (pair, QByteArray::number(qPow(propertyValue, 1.0)));
|
||
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
|
||
propertyList[pair.first.toLatin1()] = propertyValue;
|
||
}
|
||
}else if (pair.first == "humidity")
|
||
{
|
||
propertyValue = pair.second.toFloat();
|
||
if (compare (propertyList[pair.first.toLatin1()], propertyValue, 3))
|
||
{
|
||
notif += spaceFlag + formatNotifString (pair, pair.second.toLatin1());
|
||
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
|
||
}
|
||
}else if (pair.first == "windchillf")
|
||
{
|
||
propertyValue = fahrenheitToCelsius(pair.second.toFloat());
|
||
if (compare (propertyList[pair.first.toLatin1()], propertyValue, 0.2))
|
||
{
|
||
notif += spaceFlag + formatNotifString (pair, QByteArray::number(qPow(propertyValue, 1.0)));
|
||
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
|
||
}
|
||
}else if (pair.first == "winddir")
|
||
{
|
||
propertyValue = pair.second.toFloat();
|
||
if (compare (propertyList[pair.first.toLatin1()], propertyValue, 90))
|
||
{
|
||
notif += spaceFlag + formatNotifString (pair, pair.second.toLatin1());
|
||
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
|
||
}
|
||
}else if (pair.first == "windspeedmph")
|
||
{
|
||
propertyValue = mphTokmh(pair.second.toFloat());
|
||
if (compare (propertyList[pair.first.toLatin1()], propertyValue, 10))
|
||
{
|
||
notif += spaceFlag + formatNotifString (pair, QByteArray::number(round(propertyValue)));
|
||
if (propertyValue > 30)
|
||
priority = setPriority (priority, 4);
|
||
if (propertyValue > 50)
|
||
priority = setPriority (priority, 5);
|
||
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
|
||
}
|
||
}else if (pair.first == "windgustmph")
|
||
{
|
||
propertyValue = mphTokmh(pair.second.toFloat());
|
||
if (compare (propertyList[pair.first.toLatin1()], propertyValue, 5) and propertyValue > 20)
|
||
{
|
||
notif += spaceFlag + formatNotifString (pair, QByteArray::number(round(propertyValue)));
|
||
if (propertyValue > 40)
|
||
priority = setPriority (priority, 4);
|
||
if (propertyValue > 60)
|
||
priority = setPriority (priority, 5);
|
||
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
|
||
}
|
||
}else if (pair.first == "rainin")
|
||
{
|
||
propertyValue = pair.second.toFloat();
|
||
if (compare (propertyList[pair.first.toLatin1()], propertyValue, 0.01))
|
||
{
|
||
notif += spaceFlag + formatNotifString (pair, pair.second.toLatin1());
|
||
priority = setPriority (priority, 4);
|
||
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
|
||
}
|
||
}else if (pair.first == "baromin")
|
||
{
|
||
propertyValue = tohPa(pair.second.toFloat());
|
||
pressureVariation(propertyValue);
|
||
if (compare (propertyList[pair.first.toLatin1()], propertyValue, 100))
|
||
{
|
||
notif += spaceFlag + formatNotifString (pair, QByteArray::number(propertyValue));
|
||
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
|
||
}
|
||
}else if (pair.first == "UV")
|
||
{
|
||
propertyValue = pair.second.toFloat();
|
||
if (compare (propertyList[pair.first.toLatin1()], propertyValue, 1))
|
||
{
|
||
notif += spaceFlag + formatNotifString (pair, pair.second.toLatin1());
|
||
if (propertyValue > 3)
|
||
priority = setPriority (priority, 5);
|
||
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
|
||
}
|
||
}else if (pair.first == "lowbat")
|
||
{
|
||
static QTime time;
|
||
if (pair.second.toInt() == 1 and time > QTime::currentTime().addSecs(3600))
|
||
{
|
||
notif += spaceFlag + formatNotifString (pair, "");
|
||
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
|
||
priority = setPriority (priority, 4);
|
||
time = time.currentTime();
|
||
}
|
||
}
|
||
}
|
||
propertyList[pair.first.toLatin1()] = propertyValue;
|
||
if (!notif.isEmpty())
|
||
{
|
||
spaceFlag = " ";
|
||
}
|
||
}
|
||
if (!jsonString.isEmpty())
|
||
{
|
||
jsonString = jsonString +", " + deviceString + "}}";
|
||
mqttClient->send_message(jsonString);
|
||
//debug(DEBUGMACRO, "sent => " + jsonString, DEBUG);
|
||
}else
|
||
{
|
||
debug(DEBUGMACRO, "No values to send", DEBUG);
|
||
}
|
||
|
||
if (!notif.isEmpty())
|
||
{
|
||
debug(DEBUGMACRO, "calling notify with notif = #" + notif + "#", DEBUG);
|
||
notify (notif, priorityList[priority]);
|
||
}
|
||
debug(DEBUGMACRO, "parseData: Returning", DEBUG);
|
||
}
|
||
|
||
quint8 previsionMeteo(double currentPressure, double variation3h)
|
||
{
|
||
if (currentPressure > 1020.0 && variation3h >= 0.0)
|
||
{
|
||
return 0;
|
||
} else if (currentPressure > 1010.0 && currentPressure <= 1020.0 && variation3h > 0.0)
|
||
{
|
||
return 1;
|
||
} else if (currentPressure > 1000.0 && currentPressure <= 1010.0 && variation3h < 0.0)
|
||
{
|
||
return 2;
|
||
} else if (currentPressure <= 1000.0 && variation3h < -2.0)
|
||
{
|
||
return 3;
|
||
} else if (currentPressure < 990.0)
|
||
{
|
||
return 4;
|
||
} else
|
||
{
|
||
return 5;
|
||
}
|
||
}
|
||
|
||
void pressureVariation(qfloat16 currentPressure)
|
||
{
|
||
const int NB_MESURES_3H = 360; // 3h × 120 mesures/heure (1 mesure toutes les 30s)
|
||
QVector<double> historiquePressions;
|
||
|
||
debug(DEBUGMACRO, "PressureVariation", DEBUG);
|
||
|
||
historiquePressions.reserve(NB_MESURES_3H);
|
||
|
||
// Ajout de la mesure à l'historique
|
||
historiquePressions.push_back(currentPressure);
|
||
|
||
// Si on a assez de mesures pour couvrir 3h
|
||
if (historiquePressions.size() > NB_MESURES_3H)
|
||
{
|
||
historiquePressions.erase(historiquePressions.begin());
|
||
}
|
||
|
||
// Calcul de la variation sur 3h si on a assez de mesures
|
||
if (historiquePressions.size() == NB_MESURES_3H)
|
||
{
|
||
double pressionInitiale = historiquePressions.front();
|
||
double pressionFinale = historiquePressions.back();
|
||
double variation = pressionFinale - pressionInitiale;
|
||
|
||
debug(DEBUGMACRO, "Pression actuelle : " + QString::number(pressionFinale) + " hPa", DEBUG);
|
||
debug(DEBUGMACRO, "Variation sur 3h : " + QString::number(variation) + " hPa", DEBUG);
|
||
|
||
// Prévision météo
|
||
quint8 prevision = previsionMeteo(pressionFinale, variation);
|
||
debug(DEBUGMACRO, "Prévision : " + previsionList[prevision], DEBUG);
|
||
|
||
// Exemple : Envoi d'une alerte si nécessaire
|
||
if (prevision == 4)
|
||
{
|
||
notify ("ALERTE : " + previsionList[prevision], "high");
|
||
}else if (prevision == 5)
|
||
{
|
||
notify ("ALERTE : " + previsionList[prevision], "urgent");
|
||
}
|
||
}
|
||
}
|
||
|
||
quint8 setPriority (quint8 currentPriority, quint8 newPriority)
|
||
{
|
||
if (newPriority > currentPriority)
|
||
{
|
||
return newPriority;
|
||
}else
|
||
{
|
||
return currentPriority;
|
||
}
|
||
}
|
||
|
||
double fahrenheitToCelsius (double fahrenheit)
|
||
{
|
||
return (fahrenheit - 32.0) * 5.0 / 9.0;
|
||
}
|
||
|
||
qfloat16 tohPa (qfloat16 value)
|
||
{
|
||
return value * 33.8639;
|
||
}
|
||
|
||
qfloat16 mphTokmh (qfloat16 value)
|
||
{
|
||
return value * 1.60934;
|
||
}
|
||
|
||
bool compare (qfloat16 value, qfloat16 currentValue, qfloat16 ecart)
|
||
{
|
||
|
||
debug(DEBUGMACRO, "value: " + QByteArray::number(value) + "testValue: " + QByteArray::number(currentValue), DEBUG);
|
||
|
||
if (currentValue <= (value - ecart) or currentValue >= (value + ecart))
|
||
{
|
||
debug(DEBUGMACRO, "compare return true", DEBUG);
|
||
return true;
|
||
}else
|
||
{
|
||
debug(DEBUGMACRO, "compare return false", DEBUG);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
QString formatNotifString (QPair <QString, QString> pair, QByteArray value)
|
||
{
|
||
return "- " + propertyName[pair.first.toLatin1()].first + " : " + value + propertyName[pair.first.toLatin1()].second;
|
||
}
|
||
|
||
void notify(QString notif, QString priority)
|
||
{
|
||
CURL *curl;
|
||
CURLcode res;
|
||
|
||
priority = "Priority: " + priority;
|
||
|
||
// Initialise libcurl
|
||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||
curl = curl_easy_init();
|
||
|
||
if (curl)
|
||
{
|
||
// URL de ton serveur ntfy
|
||
QString url = "http://localhost:81/Meteo"; // Remplace par ton URL et topic
|
||
|
||
// Message à envoyer
|
||
QString message = notif;
|
||
|
||
// Définis les en-têtes pour le titre et les priorités
|
||
struct curl_slist *headers = NULL;
|
||
headers = curl_slist_append(headers, "Title: Météo");
|
||
headers = curl_slist_append(headers, priority.toStdString().c_str());
|
||
headers = curl_slist_append(headers, "Markdown: yes");
|
||
headers = curl_slist_append(headers, "Config: /etc/ntfy.client.yml");
|
||
headers = curl_slist_append(headers, "Firebase: no");
|
||
|
||
// Configure la requête POST
|
||
curl_easy_setopt(curl, CURLOPT_URL, url.toUtf8().constData());
|
||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, message.toUtf8().constData());
|
||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||
|
||
// Exécute la requête
|
||
res = curl_easy_perform(curl);
|
||
|
||
// Vérifie les erreurs
|
||
if (res != CURLE_OK)
|
||
{
|
||
qCritical() << "Erreur libcurl :" << curl_easy_strerror(res);
|
||
} else
|
||
{
|
||
qDebug() << "Notification envoyée avec succès !";
|
||
}
|
||
|
||
// Nettoie les ressources
|
||
curl_slist_free_all(headers);
|
||
curl_easy_cleanup(curl);
|
||
}
|
||
|
||
// Nettoie libcurl
|
||
curl_global_cleanup();
|
||
}
|
||
|