Endpoints capteurs : conversion d'unité, latitude/longitude, listes de valeurs/textes, obsolescence NMEA

This commit is contained in:
2026-08-14 12:08:50 +02:00
parent f5ceca6abb
commit 0b54ba2218
+224 -3
View File
@@ -25,6 +25,7 @@
// Types de familles (cf TB_Familles.Type_source / TB_Events.Type_source) : // Types de familles (cf TB_Familles.Type_source / TB_Events.Type_source) :
static const int TYPE_SOURCE_CIRCUIT = 1; static const int TYPE_SOURCE_CIRCUIT = 1;
static const int TYPE_SOURCE_CAPTEUR = 2;
static const int TYPE_SOURCE_BATTERIE = 4; static const int TYPE_SOURCE_BATTERIE = 4;
// États circuit (cf commentaire colonne TB_Circuits.Etat dans le schéma) : // É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_CIRCUIT_ABSENCE_MASSE = 9;
static const int ETAT_COMMANDE_CARTE_OK = 0; 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<int>(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<int>(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 // 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 // (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; int typeSource;
if (type == "circuit") typeSource = TYPE_SOURCE_CIRCUIT; if (type == "circuit") typeSource = TYPE_SOURCE_CIRCUIT;
else if (type == "batterie") typeSource = TYPE_SOURCE_BATTERIE; else if (type == "batterie") typeSource = TYPE_SOURCE_BATTERIE;
else if (type == "capteur") typeSource = TYPE_SOURCE_CAPTEUR;
else { else {
QJsonObject err; QJsonObject err;
err["status"] = "erreur"; 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)); return withCors(QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::NotImplemented));
} }
@@ -165,6 +241,41 @@ int main(int argc, char *argv[])
while (q3.next()) { while (q3.next()) {
if (q3.value(0).toInt() != 0) etat = 2; 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; QJsonObject obj;
@@ -192,10 +303,10 @@ int main(int argc, char *argv[])
const QString type = query.queryItemValue("type"); const QString type = query.queryItemValue("type");
const QString familleStr = query.queryItemValue("famille"); const QString familleStr = query.queryItemValue("famille");
if (type != "circuit" && type != "batterie") { if (type != "circuit" && type != "batterie" && type != "capteur") {
QJsonObject err; QJsonObject err;
err["status"] = "erreur"; 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)); 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(); obj["etat"] = q.value(7).toInt();
elements.append(obj); 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; QJsonObject reponse;