From 0b54ba2218e1596ae03a665001a9d3badd22681a Mon Sep 17 00:00:00 2001 From: Pierre Date: Fri, 14 Aug 2026 12:08:50 +0200 Subject: [PATCH] =?UTF-8?q?Endpoints=20capteurs=20:=20conversion=20d'unit?= =?UTF-8?q?=C3=A9,=20latitude/longitude,=20listes=20de=20valeurs/textes,?= =?UTF-8?q?=20obsolescence=20NMEA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.cpp | 227 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 224 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index 4d36e6e..221d265 100644 --- a/main.cpp +++ b/main.cpp @@ -25,6 +25,7 @@ // Types de familles (cf TB_Familles.Type_source / TB_Events.Type_source) : static const int TYPE_SOURCE_CIRCUIT = 1; +static const int TYPE_SOURCE_CAPTEUR = 2; static const int TYPE_SOURCE_BATTERIE = 4; // États circuit (cf commentaire colonne TB_Circuits.Etat dans le schéma) : @@ -36,6 +37,80 @@ static const int ETAT_CIRCUIT_ABSENCE_TERRE = 8; static const int ETAT_CIRCUIT_ABSENCE_MASSE = 9; static const int ETAT_COMMANDE_CARTE_OK = 0; +// Types d'entrées capteur (cf commentaire colonne TB_Capteurs.Type_entrees) : +static const int TYPE_ENTREES_AUX = 1; +static const int TYPE_ENTREES_NMEA = 7; +static const int TYPE_ENTREES_CALCULEE_BOOLEEN = 20; + +// Types de conversion d'unité (cf commentaire colonne TB_Variables.Conversion_unite, +// confirmé identique à shipheart_type_unite_* dans main.qml) : +static const int TYPE_UNITE_VOLUME = 1; +static const int TYPE_UNITE_TEMPERATURE = 2; +static const int TYPE_UNITE_LATITUDE = 3; +static const int TYPE_UNITE_LONGITUDE = 4; + +// Unités cibles (cf main.qml, shipheart_unite_volume_*/shipheart_unite_temperature_*) : +static const int UNITE_VOLUME_LITRES = 0; +static const int UNITE_VOLUME_US_GALLONS = 1; +static const int UNITE_VOLUME_IMP_GALLONS = 2; +static const int UNITE_TEMPERATURE_CELSIUS = 0; +static const int UNITE_TEMPERATURE_FAHRENHEIT = 1; + +// ---------------------------------------------------------------------------- +// Conversions - traduction fidèle de fonctions.js (conversion_volume(), +// conversion_temperature(), chaine_latitude(), chaine_longitude()) et de +// fonctions_localisation.js (symbole_unite_volume(), symbole_unite_temperature()). +// ---------------------------------------------------------------------------- +static double conversion_volume(double val, int unite) +{ + switch (unite) { + case UNITE_VOLUME_US_GALLONS: return val / 3.785411784; + case UNITE_VOLUME_IMP_GALLONS: return val / 4.546090; + case UNITE_VOLUME_LITRES: + default: return val; + } +} + +static double conversion_temperature(double val, int unite) +{ + switch (unite) { + case UNITE_TEMPERATURE_FAHRENHEIT: return (val * 1.8) + 32.0; + case UNITE_TEMPERATURE_CELSIUS: + default: return val; + } +} + +static QString symbole_unite_volume(int unite) +{ + switch (unite) { + case UNITE_VOLUME_US_GALLONS: return QStringLiteral("US Ga"); + case UNITE_VOLUME_IMP_GALLONS: return QStringLiteral("Imp. Ga"); + case UNITE_VOLUME_LITRES: + default: return QStringLiteral("lit."); + } +} + +static QString symbole_unite_temperature(int unite) +{ + return (unite == UNITE_TEMPERATURE_FAHRENHEIT) ? QStringLiteral("°F") : QStringLiteral("°C"); +} + +static QString chaine_latitude(double lat) +{ + const int deg = static_cast(lat); + const double minutes = (lat - deg) * 60.0; + const QString suffixe = (lat >= 0) ? QStringLiteral("N") : QStringLiteral("S"); + return QString("%1°%2' %3").arg(deg).arg(minutes, 0, 'f', 3).arg(suffixe); +} + +static QString chaine_longitude(double lon) +{ + const int deg = static_cast(lon); + const double minutes = (lon - deg) * 60.0; + const QString suffixe = (lon >= 0) ? QStringLiteral("E") : QStringLiteral("W"); + return QString("%1°%2' %3").arg(deg).arg(minutes, 0, 'f', 3).arg(suffixe); +} + // ---------------------------------------------------------------------------- // Ajoute l'en-tête CORS à une réponse - indispensable puisque le client WASM // (servi par nginx sur un port distinct) et cette API (port 8080) constituent @@ -96,10 +171,11 @@ int main(int argc, char *argv[]) int typeSource; if (type == "circuit") typeSource = TYPE_SOURCE_CIRCUIT; else if (type == "batterie") typeSource = TYPE_SOURCE_BATTERIE; + else if (type == "capteur") typeSource = TYPE_SOURCE_CAPTEUR; else { QJsonObject err; err["status"] = "erreur"; - err["message"] = "type non supporté (circuit, batterie)"; + err["message"] = "type non supporté (circuit, batterie, capteur)"; return withCors(QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::NotImplemented)); } @@ -165,6 +241,41 @@ int main(int argc, char *argv[]) while (q3.next()) { if (q3.value(0).toInt() != 0) etat = 2; } + } else if (type == "capteur") { + // Période d'obsolescence NMEA (TB_Configuration) : + int periodeObsolescenceNmea = 0; + { + QSqlQuery qc("SELECT Valeur FROM TB_Configuration WHERE Nom='Periode_obsolescence_capteur_nmea'"); + if (qc.next()) periodeObsolescenceNmea = qc.value(0).toInt(); + } + + // Alarmes (Events) liées à cette famille : + QSqlQuery q2; + q2.prepare("SELECT COUNT(*) FROM TB_Events WHERE Type_source=:type AND Validite=1 AND Alarme_source>0 " + "AND Etat_test=1 AND ID_Source IN (SELECT Cle_primaire FROM TB_Capteurs WHERE ID_Famille=:famille)"); + q2.bindValue(":type", typeSource); + q2.bindValue(":famille", idFamille); + q2.exec(); + if (q2.next() && q2.value(0).toInt() > 0) etat = 1; + + QSqlQuery q3; + q3.prepare( + "SELECT Etat, Etat_systeme, " + "IF(Type_entrees=:typeNmea, (SELECT (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(Date_maj)) FROM TB_Nmea2000 " + "WHERE Pgn=SUBSTRING_INDEX(Entrees, ':', 1) AND Field=SUBSTRING_INDEX(Entrees, ':', -1)), 0) AS Age " + "FROM TB_Capteurs WHERE ID_Famille=:famille AND Priorite_affichage_accueil>0" + ); + q3.bindValue(":typeNmea", TYPE_ENTREES_NMEA); + q3.bindValue(":famille", idFamille); + q3.exec(); + while (q3.next()) { + const int etatCapteur = q3.value(0).toInt(); + const int etatSysteme = q3.value(1).toInt(); + const int age = q3.value(2).toInt(); + + if (age > periodeObsolescenceNmea && etat < 1) etat = 1; + if ((etatCapteur > 0 || etatSysteme != 0) && etat < 2) etat = 2; + } } QJsonObject obj; @@ -192,10 +303,10 @@ int main(int argc, char *argv[]) const QString type = query.queryItemValue("type"); const QString familleStr = query.queryItemValue("famille"); - if (type != "circuit" && type != "batterie") { + if (type != "circuit" && type != "batterie" && type != "capteur") { QJsonObject err; err["status"] = "erreur"; - err["message"] = "type non supporté (circuit, batterie)"; + err["message"] = "type non supporté (circuit, batterie, capteur)"; return withCors(QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::NotImplemented)); } @@ -277,6 +388,116 @@ int main(int argc, char *argv[]) obj["etat"] = q.value(7).toInt(); elements.append(obj); } + } else if (type == "capteur") { + // Préférences d'unité du bateau (TB_Configuration) : + int uniteVolume = 0, uniteTemperature = 0, periodeObsolescenceNmea = 0; + { + QSqlQuery qc("SELECT Valeur FROM TB_Configuration WHERE Nom='Unite_volume'"); + if (qc.next()) uniteVolume = qc.value(0).toInt(); + } + { + QSqlQuery qc("SELECT Valeur FROM TB_Configuration WHERE Nom='Unite_temperature'"); + if (qc.next()) uniteTemperature = qc.value(0).toInt(); + } + { + QSqlQuery qc("SELECT Valeur FROM TB_Configuration WHERE Nom='Periode_obsolescence_capteur_nmea'"); + if (qc.next()) periodeObsolescenceNmea = qc.value(0).toInt(); + } + + QSqlQuery q; + q.prepare( + "SELECT Cle_primaire, Etat_systeme, Nom, Type_entrees, Entrees, ID_Variable, Etat, " + "(SELECT COUNT(*) FROM TB_Events WHERE Validite=1 AND Type_source=:typeSource AND Alarme_source>0 AND Etat_test=1 " + "AND ID_Source=TB_Capteurs.Cle_primaire) AS Nb_alarmes, " + "Valeur, Precision_affichage_accueil, Liste_valeurs_textes, " + "IF(Type_entrees=:typeNmea, (SELECT (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(Date_maj)) FROM TB_Nmea2000 " + "WHERE Pgn=SUBSTRING_INDEX(Entrees, ':', 1) AND Field=SUBSTRING_INDEX(Entrees, ':', -1)), 0) AS Age " + "FROM TB_Capteurs WHERE ID_Famille=:famille AND Priorite_affichage_accueil>0 " + "ORDER BY Priorite_affichage_accueil, Nom" + ); + q.bindValue(":typeSource", TYPE_SOURCE_CAPTEUR); + q.bindValue(":typeNmea", TYPE_ENTREES_NMEA); + q.bindValue(":famille", idFamille); + q.exec(); + + while (q.next()) { + const int id = q.value(0).toInt(); + const int etatSysteme = q.value(1).toInt(); + const QString nom = q.value(2).toString(); + const int typeEntrees = q.value(3).toInt(); + const int idVariable = q.value(5).toInt(); + const int etatCapteur = q.value(6).toInt(); + const int nbAlarmes = q.value(7).toInt(); + const double valeurBrute = q.value(8).toDouble(); + const int precision = q.value(9).toInt(); + const QString listeValeursTextes = q.value(10).toString(); + const int age = q.value(11).toInt(); + + // Résolution de l'unité/conversion via TB_Variables : + QSqlQuery qv; + qv.prepare("SELECT Unite, Conversion_unite FROM TB_Variables WHERE Cle_primaire=:id"); + qv.bindValue(":id", idVariable); + qv.exec(); + QString uniteBrute; + int typeUnite = 0; + if (qv.next()) { + uniteBrute = qv.value(0).toString(); + typeUnite = qv.value(1).toInt(); + } + + QJsonObject obj; + obj["id"] = id; + obj["etat_systeme"] = etatSysteme; + obj["nom"] = nom; + obj["etat"] = etatCapteur; + obj["etat_events"] = nbAlarmes; + + if (typeUnite == TYPE_UNITE_LATITUDE) { + obj["valeur"] = chaine_latitude(valeurBrute); + obj["valeur_texte"] = true; + obj["unite"] = QString(); + } else if (typeUnite == TYPE_UNITE_LONGITUDE) { + obj["valeur"] = chaine_longitude(valeurBrute); + obj["valeur_texte"] = true; + obj["unite"] = QString(); + } else { + // Cas des listes de valeurs/textes (entrées AUX ou booléen calculé) : + QString texteListe; + if (typeEntrees == TYPE_ENTREES_AUX || typeEntrees == TYPE_ENTREES_CALCULEE_BOOLEEN) { + const auto paires = listeValeursTextes.split(';'); + for (const auto &paire : paires) { + const auto champs = paire.split('/'); + if (champs.size() == 2 && champs[0].toDouble() == valeurBrute) { + texteListe = champs[1]; + break; + } + } + } + + if (!texteListe.isEmpty()) { + obj["valeur"] = texteListe; + obj["valeur_texte"] = true; + obj["unite"] = QString(); + } else { + double valeurConvertie = valeurBrute; + QString symboleUnite = uniteBrute; + if (typeUnite == TYPE_UNITE_VOLUME) { + valeurConvertie = conversion_volume(valeurBrute, uniteVolume); + symboleUnite = symbole_unite_volume(uniteVolume); + } else if (typeUnite == TYPE_UNITE_TEMPERATURE) { + valeurConvertie = conversion_temperature(valeurBrute, uniteTemperature); + symboleUnite = symbole_unite_temperature(uniteTemperature); + } + // Arrondi à la précision d'affichage configurée (équivalent toFixed()) : + obj["valeur"] = QString::number(valeurConvertie, 'f', precision).toDouble(); + obj["valeur_texte"] = false; + obj["unite"] = symboleUnite; + } + } + + obj["obsolete"] = (typeEntrees == TYPE_ENTREES_NMEA && age > periodeObsolescenceNmea); + elements.append(obj); + } } QJsonObject reponse;