diff --git a/barometertrend.h b/barometertrend.h new file mode 100644 index 0000000..a8c9849 --- /dev/null +++ b/barometertrend.h @@ -0,0 +1,252 @@ +#pragma once +#include +#include +#include +#include + +struct ForecastResult +{ + QString message; + int urgency; // 0-5 + QString colorCode; // "vert", "jaune", ... + bool snowRisk; + bool rainRisk; + bool stormRisk; + bool blackIce; +}; + +class BarometerTrend : public QObject +{ + Q_OBJECT +public: + explicit BarometerTrend(QObject *parent = nullptr) + : QObject(parent) + {} + + void addPressure(double pressure_hPa) + { + // Enregistrement de la nouvelle mesure + m_values.append(pressure_hPa); + m_timestamps.append(QDateTime::currentDateTime()); + + // Limiter la taille de l’historique à 3 heures (360 mesures à 30s d’intervalle) + const int maxSamples = 360; + while (m_values.size() > maxSamples) + { + m_values.removeFirst(); + m_timestamps.removeFirst(); + } + } + + double currentPressure() const + { + return m_values.isEmpty() ? 0.0 : m_values.last(); + } + + // Moyenne glissante sur les dernières N mesures (par ex. 5 minutes) + double smoothedPressure(int samples = 10) const + { + if (m_values.isEmpty()) + { + return 0.0; + } + int n = qMin(samples, m_values.size()); + double sum = 0.0; + for (int i = m_values.size() - n; i < m_values.size(); ++i) + sum += m_values[i]; + return sum / n; + } + + // Variation de pression sur les dernières 3h + double deltaPressure3h() const + { + if (m_values.size() < 2) + { + return 0.0; + } + QDateTime now = QDateTime::currentDateTime(); + QDateTime target = now.addSecs(-3 * 3600); + + // Cherche la valeur la plus proche d’il y a 3 heures + int idx = -1; + for (int i = 0; i < m_timestamps.size(); ++i) + { + if (m_timestamps[i] >= target) + { + idx = i; + break; + } + } + + if (idx == -1) + { + idx = 0; // Pas assez vieux, on prend la plus ancienne + } + double p_now = smoothedPressure(); + double p_past = m_values[idx]; + return p_now - p_past; + } + + ForecastResult forecast(double temperatureC, double humidityPercent) const + { + ForecastResult r; + + double p = currentPressure(); + double d = deltaPressure3h(); + + r.snowRisk = false; + r.rainRisk = false; + r.stormRisk = false; + r.blackIce = false; + + + // Logique combinée pression / tendance + if (p >= 1020 && d >= +3) + { + r.message = "Très beau temps durable"; + r.urgency = 0; + r.colorCode = "sunny"; + }else if (p >= 1020 && d >= +1) + { + r.message = "Beau temps durable"; + r.urgency = 0; + r.colorCode = "sunny"; + }else if (p >= 1020 && d < -3) + { + r.message = "Beau, mais rapide dégradation"; + r.urgency = 4; + r.colorCode = "sun_behind_large_cloud"; + }else if (p >= 1020 && d < -1) + { + r.message = "Beau, mais possible dégradation"; + r.urgency = 1; + r.colorCode = "sun_behind_small_cloud"; + }else if (p >= 1020 && d < 0.5) + { + r.message = "Beau temps stable"; + r.urgency = 0; + r.colorCode = "sunny"; + }else if (p >= 1010 && d >= +3) + { + r.message = "Rapide amélioration du temps"; + r.urgency = 4; + r.colorCode = "sun_behind_small_cloud"; + }else if (p >= 1010 && d >= +1) + { + r.message = "Amélioration du temps"; + r.urgency = 0; + r.colorCode = "sun_behind_large_cloud"; + }else if (p >= 1010 && d <= -3) + { + r.message = "Fort risque de pluie ou vent"; + r.urgency = 4; + r.colorCode = "sun_behind_rain_cloud"; + }else if (p >= 1010 && d <= -1) + { + r.message = "Risque de pluie ou vent"; + r.urgency = 2; + r.colorCode = "sun_behind_rain_cloud"; + }else if (p >= 1010 && d <= 0.5) + { + r.message = "Nuageux avec des éclaircies"; + r.urgency = 2; + r.colorCode = "sun_behind_large_cloud"; + }else if (p >= 1000 && d >= +3) + { + r.message = "Variable, amélioration rapide"; + r.urgency = 4; + r.colorCode = "sun_behind_large_cloud"; + }else if (p >= 1000 && d >= +1) + { + r.message = "Variable, tendance au beau"; + r.urgency = 0; + r.colorCode = "sun_behind_large_cloud"; + }else if (p >= 1000 && d <= -3) + { + r.message = "Pluie ou perturbation, dégradation rapide"; + r.urgency = 4; + r.colorCode = "cloud_with_rain"; + }else if (p >= 1000 && d <= -1) + { + r.message = "Pluie ou perturbation"; + r.urgency = 2; + r.colorCode = "cloud_with_rain"; + }else if (p < 990 && d <= -3) + { + r.message = "Tempête ou orage"; + r.urgency = 5; + r.colorCode = "cloud_with_lightning"; + }else if (p < 1000 && d >= +3) + { + r.message = "Accalmie temporaire, amélioration rapide"; + r.urgency = 1; + r.colorCode = "sun_behind_rain_cloud"; + }else if (p < 1000 && d >= +1) + { + r.message = "Accalmie temporaire"; + r.urgency = 1; + r.colorCode = "sun_behind_rain_cloud"; + }else if (p < 1000 && d <= -1) + { + r.message = "Mauvais temps durable"; + r.urgency = 4; + r.colorCode = "cloud_with_rain"; + } + + if (qAbs(d) < 0.5) + { + r.message = "Stable, pas de changement significatif"; + r.urgency = 0; + } + + // Risque de neige + if (p < 1000 && d < -1 && temperatureC <= 1.0 && humidityPercent > 80.0) + { + r.snowRisk = true; + r.message += " — Risque de neige"; + r.urgency = qMax(r.urgency, 3); + r.colorCode += "snowflake,"; + } + + // Risque de pluie + else if (p < 1010 && d < -1 && temperatureC > 1.0 && humidityPercent > 70.0) + { + r.rainRisk = true; + r.message += " — Risque de pluie"; + r.urgency = qMax(r.urgency, 2); + r.colorCode += "cloud_with_rain,"; + } + + // Risque d’orage + if (p < 995 && d < -3 && temperatureC > 20.0 && humidityPercent > 70.0) + { + r.stormRisk = true; + r.message += " ⚡ Risque d’orage"; + r.urgency = qMax(r.urgency, 4); + r.colorCode += "cloud_with_lightning,"; + } + + if (temperatureC <= 0.5 && humidityPercent >= 85.0) { + // conditions classiques de gel + if (d >= -1 && d <= +1) + { + r.blackIce = true; // temps stable = ciel clair = refroidissement + }else if (d < -1 && p > 995) + { + r.blackIce = true; // arrivée d'air humide froid + } + } + + if (r.blackIce) + { + r.message += "Risque de verglas"; + r.colorCode += "ice_cube,"; + r.urgency = qMax(r.urgency, 4); + } + return r; + } + +private: + QVector m_values; + QVector m_timestamps; +}; diff --git a/pws2mqtt-qt.pro b/pws2mqtt-qt.pro index dc261e9..b87c25f 100644 --- a/pws2mqtt-qt.pro +++ b/pws2mqtt-qt.pro @@ -25,6 +25,7 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target HEADERS += \ + barometertrend.h \ httpserver.h \ mqtt.h \ pws2mqtt.h \ diff --git a/src/pws2mqtt.cpp b/src/pws2mqtt.cpp index 7617013..1e72718 100644 --- a/src/pws2mqtt.cpp +++ b/src/pws2mqtt.cpp @@ -1,6 +1,8 @@ -#include "pws2mqtt.h" +#include "pws2mqtt.h" +#include "barometertrend.h" #include "mqtt.h" //#include "httpserver.h" +#include #include #include #include @@ -21,6 +23,9 @@ extern MqttClient *mqttClient; extern Pws2mqtt *pws2mqtt; extern QHttpServer *httpServer; UtciCalculator calc; +BarometerTrend baro; + +using namespace std; QStringList previsionList { @@ -153,6 +158,7 @@ void Pws2mqtt::parseData(QList> queryList) QString notif = ""; QStringList priorityList {"", "min", "low", "default", "High", "urgent"}; QString attachment = ""; + QString tag = ""; quint8 priority = 2; double propertyValue = 0; bool propertyFlag = false; @@ -216,19 +222,6 @@ void Pws2mqtt::parseData(QList> queryList) propertyList[name] = formatNotifString(propertyName[name].first, propertyName[name].second, QByteArray::number(propertyValue)); propertiesValue[name] = propertyValue; } - /*}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] = formatNotifString(propertyName[name].first, propertyName[name].second, QByteArray::number(propertyValue)); - propertiesValue[name] = propertyValue; - }*/ }else if (name == "winddir") { propertyValue = value.toFloat(); @@ -270,7 +263,8 @@ void Pws2mqtt::parseData(QList> queryList) attachment = this->outputPath; } - }else if (name == "rainin") + debug (DEBUGMACRO, "priority = " + QString::number(priority), DEBUG); + }else if (name == "rainin") { static double ecart; quint8 raininPriority = 1; @@ -291,7 +285,8 @@ void Pws2mqtt::parseData(QList> queryList) propertyList[name] = formatNotifString(pluviosite + " : ", propertyName[name].second, QByteArray::number(propertyValue)); propertiesValue[name] = propertyValue; } - }else if (name == "dailyrainin") + debug (DEBUGMACRO, "priority = " + QString::number(priority), DEBUG); + }else if (name == "dailyrainin") { static double ecart; propertyValue = round(pair.second.toFloat()*100)/100; @@ -310,38 +305,33 @@ void Pws2mqtt::parseData(QList> queryList) propertyList[name] = formatNotifString(propertyName[name].first, propertyName[name].second, QByteArray::number(propertyValue)); propertiesValue[name] = propertyValue; } - }else if (name == "baromin") + debug (DEBUGMACRO, "priority = " + QString::number(priority), DEBUG); + }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] = formatNotifString(propertyName[name].first, "", QByteArray::number(propertyValue)); - propertiesValue[name] = propertyValue; - } + propertyValue = round(tohPa(pair.second.toFloat()) * 100) / 100; + debug (DEBUGMACRO, "Barometre en hPa : " + QByteArray::number(propertyValue), DEBUG); + baro.addPressure(propertyValue); + propertyList[name] = formatNotifString(propertyName[name].first, "", QByteArray::number(propertyValue)); + propertiesValue[name] = propertyValue; static QString prevision; - quint8 prevPriority = 0; - QString ret = pressureVariation(propertyValue, prevPriority); - if (!ret.isEmpty()) - { - debug (DEBUGMACRO, "baromin ret not empty : " + ret, DEBUG); - QString newPrevision = ret; - priority = setPriority(priority, prevPriority); - if (prevision != newPrevision) - { - prevision = newPrevision; - propertyList["prevision"] = "- " + prevision + " \n"; - } - } - }else if (name == "UV") + auto ret = baro.forecast(propertiesValue["tempf"], propertiesValue["humidity"]); + + QString newPrevision = ret.message; + if (prevision != newPrevision) + { + prevision = newPrevision; + propertyList["prevision"] = "- " + prevision + " \n"; + priority = setPriority(priority, ret.urgency); + tag += ret.colorCode; + } + debug (DEBUGMACRO, "priority = " + QString::number(priority), DEBUG); + }else if (name == "UV") { static QDateTime timeUV = QDateTime::currentDateTime().addSecs(-600); - propertyValue = pair.second.toFloat(); + propertyValue = pair.second.toUInt(); debug (DEBUGMACRO, name + " : " + QByteArray::number(propertyValue), DEBUG); if (compare (propertiesValue[name], propertyValue, 1) and timeUV < QDateTime::currentDateTime()) @@ -356,6 +346,8 @@ void Pws2mqtt::parseData(QList> queryList) propertiesValue[name] = propertyValue; debug (DEBUGMACRO, "Notif = #" + notif + "#", DEBUG); } + debug (DEBUGMACRO, "priority = " + QString::number(priority), DEBUG); + }else if (name == "solarradiation") { propertiesValue[name] = round(pair.second.toFloat()*100/100); @@ -364,8 +356,8 @@ void Pws2mqtt::parseData(QList> queryList) } propertiesValue["windchill"] = calc.computeUTCI(propertiesValue["tempf"], propertiesValue["vent"], propertiesValue["solarradiation"], propertiesValue["humidity"]); propertyList["windchill"] = formatNotifString(propertyName["windchill"].first, propertyName["windchill"].second, QByteArray::number(propertiesValue["windchill"])); - windChill(propertiesValue["tempf"], propertiesValue["vent"]); - calculerHumidex(propertiesValue["tempf"], propertiesValue["humidity"]); + //windChill(propertiesValue["tempf"], propertiesValue["vent"]); + //calculerHumidex(propertiesValue["tempf"], propertiesValue["humidity"]); if (!jsonString.isEmpty()) { @@ -406,7 +398,7 @@ void Pws2mqtt::parseData(QList> queryList) debug(DEBUGMACRO, "calling notify with notif = #" + notif + "#", DEBUG); if (changed) { - notify (notif, priorityList[priority], attachment); + notify (notif, priorityList[priority], attachment, tag); precPropertyList = propertyList; } } @@ -434,7 +426,7 @@ double calculerUTCI(double temperature, double humiditeRelative, double vitesseV return round(utci * 100) / 100; } -double windChill(double airT, double vent) +/*double windChill(double airT, double vent) { double w = 0; @@ -447,9 +439,9 @@ double windChill(double airT, double vent) debug(DEBUGMACRO, "windChill = " + QString::number(w), DEBUG); return w; } -} +}*/ -double calculerHumidex(double temperature, double humiditeRelative) +/*double calculerHumidex(double temperature, double humiditeRelative) { // Calcul de la température du point de rosée (T_rosée) double T_rosee = (243.04 * (log(humiditeRelative / 100.0) + (17.625 * temperature) / (243.04 + temperature))) / (17.625 - (log(humiditeRelative / 100.0) + (17.625 * temperature) / (243.04 + temperature))); @@ -459,7 +451,7 @@ double calculerHumidex(double temperature, double humiditeRelative) debug(DEBUGMACRO, "humidex = " + QString::number(H), DEBUG); return H; -} +}*/ QString getPluviosite(double value, quint8 &priority) { @@ -514,9 +506,9 @@ QString previsionMeteo(double currentPressure, double variation3h, quint8 &prior } else if (currentPressure > 1010.0 && currentPressure <= 1020.0) { return "Éclaircies" + variation; - } else if (currentPressure > 1000.0 && currentPressure <= 1010.0) + } else if (currentPressure > 1000.0 && currentPressure < 1009.0) { - priority = 4; + priority = 3; return "Temps variable, risque de pluie légère" + variation; } else if (currentPressure >=990 && currentPressure <= 1000.0) { @@ -530,7 +522,7 @@ QString previsionMeteo(double currentPressure, double variation3h, quint8 &prior return variation; } -QString pressureVariation(double currentPressure, quint8 &priority) +/*QString pressureVariation(double currentPressure, quint8 &priority) { const int NB_MESURES_3H = 360; // 3h × 120 mesures/heure (1 mesure toutes les 30s) static QVector historiquePressions; @@ -570,7 +562,7 @@ QString pressureVariation(double currentPressure, quint8 &priority) } debug(DEBUGMACRO, "ending pressureVariation no result", DEBUG); return ""; -} +}*/ quint8 setPriority (quint8 currentPriority, quint8 newPriority) { @@ -631,7 +623,7 @@ static size_t ReadFile(void *ptr, size_t size, size_t nmemb, void *stream) { return n; } -void notify(QString notif, QString priority, QString inputPath) +void notify(QString notif, QString priority, QString inputPath, QString tag) { CURL *curl; CURLcode res; @@ -643,11 +635,12 @@ void notify(QString notif, QString priority, QString inputPath) // Initialise libcurl curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); - if (curl) + 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, ("Tags: " + tag).toStdString().c_str()); + 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"); diff --git a/src/pws2mqtt.h b/src/pws2mqtt.h index 8f15cbc..13116cd 100644 --- a/src/pws2mqtt.h +++ b/src/pws2mqtt.h @@ -56,7 +56,7 @@ double tohPa(double value); bool compare (double value=0, double testValue=0, double ecart = 0.5); QString formatNotifString (QString name, QString unit, QByteArray value=""); double mphTokmh (double value); -void notify (QString notif, QString priority = "low", QString inputPath = ""); +void notify (QString notif, QString priority = "low", QString inputPath = "", QString tag = ""); quint8 setPriority (quint8 currentPriority, quint8 newPriority); QString previsionMeteo(double currentPressure, double variation3h, quint8 &priority); QString pressureVariation(double currentPressure, quint8 &priority);