Files
pws2mqtt-qt/pws2mqtt.cpp

541 lines
17 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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>
#include <QImage>
#include <QTransform>
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, double> 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", {"Alerte 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);
}
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 topic;
QString deviceString = "\"device\": {\"ieeeAddress\": \"" + mqttClient->macAddress + "\", \"type\": \"" + mqttClient->type + "\", \"powerSource\": \"Battery\"";
QString notif = "";
double propertyValue = 0;
QStringList priorityList {"", "min", "low", "default", "High", "urgent"};
quint8 priority = 2;
QPair <QByteArray, QByteArray> winddir = {"", 0};
QString attachment = "";
debug(DEBUGMACRO, "looping list of query", DEBUG);
for (QPair<QString, QString> pair : queryList)
{
debug(DEBUGMACRO, pair.first + " = " + pair.second, DEBUG);
QByteArray name = pair.first.toLatin1();
QByteArray value = pair.second.toLatin1();
if (this->deviceProperties.contains(name))
{
if(deviceFlag == false)
{
deviceFlag = true;
}else
{
deviceString.append(", ");
}
deviceString += "\"" + name + "\": ";
deviceString += pair.second;
}else
{
if(propertyFlag == false)
{
propertyFlag = true;
}else
{
jsonString.append(", ");
}
jsonString.append("\"" + name + "\": ");
jsonString.append(pair.second);
if (name == "tempf")
{
static QDateTime timeTemp = QDateTime::currentDateTime().addSecs(-600);
propertyValue = round(fahrenheitToCelsius(pair.second.toFloat()));
debug (DEBUGMACRO, "timeTemp = " + timeTemp.time().toString() + " - current :" + QDateTime::currentDateTime().toString() , DEBUG);
if (compare (propertyList[name], propertyValue, 1) and timeTemp < QDateTime::currentDateTime())
{
notif += formatNotifString (propertyName[name].first, propertyName[name].second, QByteArray::number(qPow(propertyValue, 1.0)));
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
timeTemp = timeTemp.currentDateTime().addSecs(300);
propertyList[name] = propertyValue;
}
}else if (name == "indoortempf")
{
static QDateTime timeIndoorTemp = QDateTime::currentDateTime().addSecs(-600);
propertyValue = round(fahrenheitToCelsius(pair.second.toFloat()));
if (compare (propertyList[name], propertyValue, 1) and timeIndoorTemp < QDateTime::currentDateTime())
{
notif += formatNotifString (propertyName[name].first, propertyName[name].second , QByteArray::number(qPow(propertyValue, 1.0)));
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
timeIndoorTemp = timeIndoorTemp.currentDateTime().addSecs(300);
propertyList[name] = propertyValue;
}
}else if (name == "humidity")
{
propertyValue = pair.second.toFloat();
if (compare (propertyList[name], propertyValue, 3))
{
notif += formatNotifString (propertyName[name].first, propertyName[name].second , value);
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
propertyList[name] = propertyValue;
}
}else if (name == "windchillf")
{
static QDateTime timeWindchill = QDateTime::currentDateTime().addSecs(-600);
propertyValue = round(fahrenheitToCelsius(pair.second.toFloat()));
if (compare (propertyList[name], propertyValue, 1) and timeWindchill < QDateTime::currentDateTime())
{
notif += formatNotifString (propertyName[name].first, propertyName[name].second , QByteArray::number(qPow(propertyValue, 1.0)));
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
timeWindchill = timeWindchill.currentDateTime().addSecs(300);
propertyList[name] = propertyValue;
}
}else if (name == "winddir")
{
winddir.first = name;
winddir.second = value;
}else if (name == "windspeedmph")
{
propertyValue = round(mphTokmh(pair.second.toFloat()));
if (compare (propertyList[name], propertyValue, 5))
{
if (propertyValue > 10)
{
priority = setPriority (priority, 2);
notif += formatNotifString (propertyName[name].first, propertyName[name].second , QByteArray::number(propertyValue));
notif += formatNotifString (winddir.first, winddir.second);
//TODO attache fleche
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
if (propertyValue > 30)
priority = setPriority (priority, 4);
if (propertyValue > 50)
priority = setPriority (priority, 5);
propertyList[name] = propertyValue;
attachment = this->inputPath;
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
}
}
}else if (name == "windgustmph")
{
propertyValue = round(mphTokmh(pair.second.toFloat()));
if (compare (propertyList[name], propertyValue, 5) and propertyValue > 20)
{
notif += formatNotifString (propertyName[name].first, propertyName[name].second , QByteArray::number(propertyValue));
if (propertyValue > 40)
priority = setPriority (priority, 4);
if (propertyValue > 60)
priority = setPriority (priority, 5);
propertyList[name] = propertyValue;
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
}
}else if (name == "rainin")
{
static double ecart;
propertyValue = pair.second.toFloat();
if (propertyValue == 0)
{
ecart = 0.0001;
}else
{
ecart = 0.01;
}
if (compare (propertyList[name], propertyValue, ecart))
{
notif += formatNotifString (propertyName[name].first, propertyName[name].second , value);
priority = setPriority (priority, 4);
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
propertyList[name] = propertyValue;
}
}else if (name == "baromin")
{
propertyValue = tohPa(pair.second.toFloat());
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
if (compare (propertyList[name], propertyValue, 5))
{
pressureVariation(propertyValue);
notif += formatNotifString (propertyName[name].first, propertyName[name].second , QByteArray::number(propertyValue));
propertyList[name] = propertyValue;
}
}else if (name == "UV")
{
static QDateTime timeUV = QDateTime::currentDateTime().addSecs(-600);
propertyValue = pair.second.toFloat();
if (compare (propertyList[name], propertyValue, 1) and timeUV < QDateTime::currentDateTime())
{
notif += formatNotifString (propertyName[name].first, propertyName[name].second , value);
if (propertyValue == 5 )
priority = setPriority (priority, 3);
if (propertyValue >= 6 )
priority = setPriority (priority, 4);
timeUV = timeUV.currentDateTime().addSecs(300);
propertyList[name] = propertyValue;
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
}
}else if (name == "lowbat")
{
static QDateTime timeLowBat = QDateTime::currentDateTime().addSecs(-4000);
if (pair.second.toInt() == 0 and timeLowBat < QDateTime::currentDateTime())
{
notif += formatNotifString (propertyName[name].first, propertyName[name].second );
debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG);
priority = setPriority (priority, 4);
timeLowBat = timeLowBat.currentDateTime().addSecs(3600);
propertyList[name] = propertyValue;
}
}
}
}
if (!jsonString.isEmpty())
{
jsonString = jsonString +", " + deviceString + "}}";
mqttClient->send_message(jsonString);
}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;
}
}
QString pressureVariation(double 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)
{
QString priority = "default";
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)
{
priority = "high";
}else if (prevision == 5)
{
priority = "urgent";
}
return previsionList[prevision] + "," + priority;
}
return "";
}
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;
}
double tohPa (double value)
{
return value * 33.8639;
}
double mphTokmh (double value)
{
return value * 1.60934;
}
bool compare (double value, double currentValue, double 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 (QString name, QString unit ,QByteArray value)
{
QString text = "- " + name + " : " + value;
if (!unit.isEmpty())
{
text += unit;
//"- " + propertyName[name].first + " : " + value + " " + propertyName[name].second + " \n";
}
return text += " \n";
}
void notify(QString notif, QString priority, QString inputPath)
{
CURL *curl;
CURLcode res;
std::string readBuffer;
priority = "Priority: " + priority;
debug (DEBUGMACRO, "notifying at priority " + priority + " - message : " + notif, DEBUG);
// Initialise libcurl
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if (curl)
{
// 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");
curl_mime *mime;
curl_mimepart *part;
// Initialise la structure mime
mime = curl_mime_init(curl);
// Ajoute le champ "message" (ton texte de notification)
part = curl_mime_addpart(mime);
curl_mime_name(part, "message");
curl_mime_data(part, notif.toUtf8().constData(), CURL_ZERO_TERMINATED);
// Ajoute le fichier en pièce jointe
part = curl_mime_addpart(mime);
curl_mime_name(part, "attach");
curl_mime_filedata(part, inputPath.toStdString().c_str());
curl_mime_filename(part, QFileInfo(inputPath).fileName().toStdString().c_str());
// Configure la requête POST
curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:81/Meteo");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, notif.toUtf8().constData());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
// Exécute la requête
res = curl_easy_perform(curl);
// Vérifie les erreurs
if (res != CURLE_OK)
{
debug(DEBUGMACRO, "Erreur libcurl :" + QString(curl_easy_strerror(res)), DEBUG);
} else
{
debug(DEBUGMACRO, "Notification envoyée avec succès", DEBUG);
}
// Nettoie les ressources
curl_mime_free(mime);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
// Nettoie libcurl
curl_global_cleanup();
}
void rotateAndSaveImage(const QString &inputPath, const QString &outputPath, qreal angle)
{
QImage image(inputPath);
if (image.isNull())
{
debug (DEBUGMACRO, "Impossible de charger l'image : " + inputPath, WARNING);
return;
}
// Crée une transformation pour pivoter l'image
QTransform transform;
// Décale le centre de l'image à l'origine
transform.translate(-image.width() / 2.0, -image.height() / 2.0);
// Applique la rotation
transform.rotate(angle);
// Remet le centre à sa position d'origine
transform.translate(image.width() / 2.0, image.height() / 2.0);
// Applique la transformation
QImage rotatedImage = image.transformed(transform, Qt::SmoothTransformation);
// Enregistre l'image pivotée
if (!rotatedImage.save(outputPath))
{
debug (DEBUGMACRO, "Impossible d'enregistrer l'image : ", WARNING);
}
}