#include "pws2mqtt.h" #include "mqtt.h" //#include "httpserver.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern MqttClient *mqttClient; extern Pws2mqtt *pws2mqtt; extern QHttpServer *httpServer; QStringList previsionList { "Temps variable (incertain).", \ "Beau temps stable (anticyclone).", \ "Beau temps en baisse (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).", \ "Temps stable" }; // 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 propertyList; QMap propertiesValue; QMap > 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", ""}} }; QList > forceVent { // { max wind speed, name, priority} {1, "Temps calme", 1}, {5, "Très légère brise", 1}, {11, "Légère brise", 1}, {19, "Petite brise", 1}, {28, "Jolie brise", 1}, {38, "Bonne brise", 1}, {49, "Vent frais", 3}, {61, "Grand vent", 4}, {74, "Vent fort", 5}, {88, "Vent violent", 5}, {117, "Tempête", 5}, {1000, "Ouragan", 5} }; 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> 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> 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> 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; static qreal winddir = 0; QString attachment = ""; bool changed = false; debug(DEBUGMACRO, "looping list of query", DEBUG); for (QPair pair : queryList) { debug(DEBUGMACRO, pair.first + " = " + pair.second, DEBUG); QByteArray name = pair.first.toLatin1(); QByteArray value = pair.second.toLatin1(); float fValue = value.toDouble(); 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(fValue)); debug (DEBUGMACRO, "timeTemp = " + timeTemp.time().toString() + " - current :" + QDateTime::currentDateTime().toString() , DEBUG); if (compare (propertiesValue[name], propertyValue, 1) and timeTemp < QDateTime::currentDateTime()) { //notif += formatNotifString (propertyName[name].first, propertyName[name].second, QByteArray::number(qPow(propertyValue, 1.0))); //debug (DEBUGMACRO, "", DEBUG); timeTemp = timeTemp.currentDateTime().addSecs(300); propertyList[name] = propertyValue; propertiesValue[name] = propertyValue; changed = true; } /*}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 = value.toFloat(); debug (DEBUGMACRO, name + " : " + QByteArray::number(propertyValue), DEBUG); if (compare (propertiesValue[name], propertyValue, 3)) { //notif += formatNotifString (propertyName[name].first, propertyName[name].second , value); propertyList[name] = propertyValue; propertiesValue[name] = propertyValue; changed = true; } }else if (name == "windchillf") { static QDateTime timeWindchill = QDateTime::currentDateTime().addSecs(-600); propertyValue = round(fahrenheitToCelsius(value.toFloat())); debug (DEBUGMACRO, name + " : " + QByteArray::number(propertyValue), DEBUG); if (compare (propertiesValue[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; propertiesValue[name] = propertyValue; changed = true; } }else if (name == "winddir") { winddir = value.toFloat(); rotateAndSaveImage(this->inputPath, this->outputPath, (qreal)winddir); debug (DEBUGMACRO, name + " : " + QByteArray::number(propertyValue), DEBUG); }else if (name == "windspeedmph" or name == "windgustmph") { QString msg = ""; static QDateTime timeWind = QDateTime::currentDateTime().addSecs(-600); quint8 windPriority = 1; static QString msgNotif; debug (DEBUGMACRO, name + " : " + QByteArray::number(propertyValue), DEBUG); propertyValue = round(mphTokmh(value.toFloat())); for (quint8 i=0; ioutputPath; if (priority > 3) { notify(msgNotif, priorityList[priority], attachment); }else if (timeWind < QDateTime::currentDateTime() or propertyValue > 20) { timeWind = timeWind.currentDateTime().addSecs(300); changed = true; } debug (DEBUGMACRO, "Notif = *" + notif + "*", DEBUG); } notif += msgNotif; /*}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; } debug (DEBUGMACRO, name + " : " + QByteArray::number(propertyValue), DEBUG); if (compare (propertiesValue[name], propertyValue, ecart)) { //notif += formatNotifString (propertyName[name].first, propertyName[name].second , value); QString pluviosite = getPluviosite(propertyValue); priority = setPriority (priority, 4); //debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG); propertyList[name] = pluviosite + " : " + QString::number(round(propertyValue*100)/100); propertiesValue[name] = propertyValue; changed = true; } }else if (name == "dailyrainin") { static double ecart; propertyValue = pair.second.toFloat(); if (propertyValue == 0) { ecart = 0.0001; }else { ecart = 0.01; } debug (DEBUGMACRO, name + " : " + QByteArray::number(propertyValue), DEBUG); if (compare (propertiesValue[name], propertyValue, ecart)) { priority = setPriority (priority, 3); //debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG); propertyList[name] = propertyValue; propertiesValue[name] = propertyValue; changed = true; } }else if (name == "baromin") { //static QDateTime timePrevision = QDateTime::currentDateTime().addSecs(-1000); propertyValue = tohPa(pair.second.toFloat()); debug (DEBUGMACRO, "Barometre en hPa : " + QByteArray::number(propertyValue), DEBUG); if (compare (propertiesValue[name], propertyValue, 0.5)) { //notif += formatNotifString (propertyName[name].first, propertyName[name].second , QByteArray::number(propertyValue)); //debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG); propertyList[name] = propertyValue; propertiesValue[name] = propertyValue; changed = true; } static QString prevision; QString ret = pressureVariation(propertyValue, priority); if (!ret.isEmpty()) { debug (DEBUGMACRO, "baromin ret not empty : " + ret, DEBUG); QString newPrevision = ret; priority = setPriority(priority, priority); if (prevision != newPrevision) { prevision = newPrevision; propertyList["prevision"] = prevision; //debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG); //changed = true; } } notif += prevision + " \n"; }else if (name == "UV") { static QDateTime timeUV = QDateTime::currentDateTime().addSecs(-600); propertyValue = pair.second.toFloat(); debug (DEBUGMACRO, name + " : " + QByteArray::number(propertyValue), DEBUG); if (compare (propertiesValue[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; propertiesValue[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()) { debug(DEBUGMACRO, "json string : " + jsonString, DEBUG); jsonString = jsonString +", " + deviceString + "}}"; mqttClient->send_message(jsonString); }else { debug(DEBUGMACRO, "No values to send", DEBUG); } if (changed) { debug(DEBUGMACRO, "looping to fill notif", DEBUG); for (auto [name, value]: propertyList.asKeyValueRange()) { QString unit; QByteArray valueBA; debug(DEBUGMACRO, "Name = " + name + ", value = " + QString::number(value.toUInt()), DEBUG); if (! propertyName.contains(name)) { unit = ""; debug(DEBUGMACRO, "unit is empty ", DEBUG); }else { unit = propertyName[name].second; debug(DEBUGMACRO, "unit : " + unit, DEBUG); QString dataType = value.typeName(); debug(DEBUGMACRO, "datatype is : " + dataType, DEBUG); if (dataType == "double") { debug(DEBUGMACRO, "datatype is 'double'", DEBUG); valueBA = QByteArray::number(round(value.toFloat()*100)/100); }else if (dataType == "QString") { debug(DEBUGMACRO, "datatype is 'QString'" + value.toString(), DEBUG); valueBA = value.toByteArray(); }else { debug(DEBUGMACRO, "datatype is null" + QString::number(value.toUInt()), DEBUG); valueBA = value.toByteArray(); } notif += formatNotifString(propertyName[name].first, unit, valueBA); } } debug(DEBUGMACRO, "calling notify with notif = #" + notif + "#", DEBUG); notify (notif, priorityList[priority], attachment); } debug(DEBUGMACRO, "parseData: Returning", DEBUG); } QString getPluviosite(double value) { if (value < 1) { return "Pluie fine, bruine"; }else if (value < 5) { return "Pluie Modérée"; }else if (value < 15) { return "Pluie forte"; }else if (value > 30) { return "Évènement extrème"; }else if (value > 15) { return "Forte pluie"; } } QString previsionMeteo(double currentPressure, double variation3h, quint8 &priority) { QString variation = ""; priority = 3; debug (DEBUGMACRO, "begin", DEBUG); if (variation3h == 0.0) { variation = " - temps stable"; }else if (variation3h > 0.2) { variation = " en amélioration rapide"; }else if (variation3h < -2.0) { variation = "en dégradation rapide"; }else if (variation3h > 0) { variation = " en amélioration"; }else if (variation3h <0) { variation = " en dégradation"; } if (currentPressure > 1020.0) { return "Beau temps (anticyclone)" + variation; } else if (currentPressure > 1010.0 && currentPressure <= 1020.0) { return "Éclaircies" + variation; } else if (currentPressure > 1000.0 && currentPressure <= 1010.0) { priority = 4; return "Temps variable, risque de pluie légère" + variation; } else if (currentPressure >=990 && currentPressure <= 1000.0) { priority = 5; return "Pluie/vent/orage" + variation; } else if (currentPressure < 990.0) { priority = 5; return "Tempête ou dépression forte (vigilance)" + variation; } return variation; } QString pressureVariation(double currentPressure, quint8 &priority) { const int NB_MESURES_3H = 360; // 3h × 120 mesures/heure (1 mesure toutes les 30s) static QVector historiquePressions; debug(DEBUGMACRO, "PressureVariation : current pressure = " + QByteArray::number(currentPressure), DEBUG); historiquePressions.append(currentPressure); // Si on a assez de mesures pour couvrir 3h if (historiquePressions.size() > NB_MESURES_3H) { debug(DEBUGMACRO, "historiquePressions > " + QByteArray::number(NB_MESURES_3H), DEBUG); historiquePressions.erase(historiquePressions.begin()); } debug(DEBUGMACRO, "taille de historiquePression : " + QByteArray::number(historiquePressions.size()), DEBUG); // 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 QString prevision = previsionMeteo(pressionFinale, variation, priority); debug(DEBUGMACRO, "Prévision : " + prevision, DEBUG); // Exemple : Envoi d'une alerte si nécessaire debug(DEBUGMACRO, "ending pressureVariation with result", DEBUG); return prevision; }else { static QTime time = QTime::currentTime().addSecs(NB_MESURES_3H * 30); return "Première prévision à " + time.toString(); } debug(DEBUGMACRO, "ending pressureVariation no result", DEBUG); 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 round(value * 33.8639 * 100) / 100; } 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"; } static size_t ReadFile(void *ptr, size_t size, size_t nmemb, void *stream) { FILE *f = (FILE *)stream; size_t n = fread(ptr, size, nmemb, f); return 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); */ // 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(); if (!inputPath.isEmpty()) { FILE *fd = fopen(inputPath.toStdString().c_str(), "rb"); if (!fd) { perror("Erreur lors de l'ouverture du fichier"); return; } // 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, "Config: /etc/ntfy.client.yml"); headers = curl_slist_append(headers, "Firebase: no"); headers = curl_slist_append(headers, "Content-Type: image/png"); headers = curl_slist_append(headers, "X-Original-Size: true"); headers = curl_slist_append(headers, "X-Filename: fleche.png"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); // Configurer l'URL curl_easy_setopt(curl, CURLOPT_URL, "localhost:81/Meteo"); // Utiliser la méthode PUT (comme `curl -T`) curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); // Lire le fichier et l'envoyer en tant que body curl_easy_setopt(curl, CURLOPT_READFUNCTION, ReadFile); curl_easy_setopt(curl, CURLOPT_READDATA, fd); // Définir la taille du fichier (optionnel mais recommandé) struct stat file_info; fstat(fileno(fd), &file_info); curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size); // Exécuter 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); } // Nettoyer fclose(fd); 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); } }