From 40bc21120151ebf012a7fcb4ba12a4c4a04a3ef5 Mon Sep 17 00:00:00 2001 From: Pierre Date: Fri, 14 Aug 2026 06:57:31 +0200 Subject: [PATCH] =?UTF-8?q?Endpoints=20r=C3=A9els=20domaine=20circuit=20:?= =?UTF-8?q?=20GET=20/elements/familles=20et=20GET=20/elements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.cpp | 185 ++++++++++++++++++++++++++++++++----- shipheart_api_circuits.zip | Bin 0 -> 2765 bytes 2 files changed, 164 insertions(+), 21 deletions(-) create mode 100755 shipheart_api_circuits.zip diff --git a/main.cpp b/main.cpp index 8d75fcb..2edbace 100644 --- a/main.cpp +++ b/main.cpp @@ -5,25 +5,40 @@ #include #include #include +#include #include +#include #include // ============================================================================ -// shipheart_api - SPIKE (Phase 1) +// shipheart_api - Phase 4 : première tranche verticale, domaine "circuit" // ---------------------------------------------------------------------------- -// Objectif unique de ce fichier : valider que QHttpServer + QtSql fonctionnent -// bien ensemble sur le CM5, avant d'investir dans la conception des vrais -// endpoints (cf synthèse, section 20). Pas de logique métier ici. +// Deux endpoints réels, remplaçant le spike (/ping conservé comme sonde de vie +// générique, /test-db retiré car superflu désormais) : +// - GET /elements/familles?type=circuit +// - GET /elements?type=circuit&famille= +// Logique et requêtes SQL reprises à l'identique de home.qml (init_circuits(), +// mise_a_jour_onglets_circuits(), mise_a_jour_circuits() - cf synthèse §20.4) +// et des constantes documentées dans le schéma (TB_Circuits.Etat, cf §20). // ============================================================================ +// Types de familles (cf TB_Familles.Type_source / TB_Events.Type_source) : +static const int TYPE_SOURCE_CIRCUIT = 1; + +// États circuit (cf commentaire colonne TB_Circuits.Etat dans le schéma) : +static const int ETAT_CIRCUIT_OK = 0; +static const int ETAT_CIRCUIT_LIMITATION = 1; +static const int ETAT_CIRCUIT_PROTECTION = 2; +static const int ETAT_CIRCUIT_DISJONCTE = 3; +static const int ETAT_CIRCUIT_ABSENCE_TERRE = 8; +static const int ETAT_CIRCUIT_ABSENCE_MASSE = 9; +static const int ETAT_COMMANDE_CARTE_OK = 0; + int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); // --- Connexion MariaDB locale --- - // TODO : remplacer par les vrais identifiants (mêmes que ceux utilisés par - // shipheart_qt/shipheart_appli - cf BDD_USER_NAME/BDD_USER_PWD/BDD_BASE_NAME - // dans shipheart.h du projet shipheart_qt). QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL"); db.setHostName("localhost"); db.setUserName("koriolan"); @@ -38,26 +53,154 @@ int main(int argc, char *argv[]) QHttpServer server; - // Route de test simple, sans BDD : + // Sonde de vie générique, sans BDD : server.route("/ping", []() { QJsonObject obj; obj["status"] = "ok"; - obj["message"] = "shipheart_api spike vivant"; return QHttpServerResponse(QJsonDocument(obj).toJson()); }); - // Route de test avec une vraie requête MariaDB (nombre de circuits en base) : - server.route("/test-db", []() { - QSqlQuery query("SELECT COUNT(*) FROM TB_Circuits"); - QJsonObject obj; - if (query.next()) { - obj["nb_circuits"] = query.value(0).toInt(); - obj["status"] = "ok"; - } else { - obj["status"] = "erreur"; - obj["message"] = query.lastError().text(); + // ------------------------------------------------------------------ + // GET /elements/familles?type=circuit + // Liste des familles avec état d'alarme agrégé (fusionne les deux + // requêtes onglets de mise_a_jour_onglets_circuits()). + // ------------------------------------------------------------------ + server.route("/elements/familles", [](const QHttpServerRequest &request) { + QUrlQuery query(request.query()); + const QString type = query.queryItemValue("type"); + + if (type != "circuit") { + QJsonObject err; + err["status"] = "erreur"; + err["message"] = "type non supporté (seul 'circuit' est implémenté à ce stade)"; + return QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::NotImplemented); } - return QHttpServerResponse(QJsonDocument(obj).toJson()); + + QJsonArray familles; + QSqlQuery q1(QString("SELECT Cle_primaire, Nom FROM TB_Familles WHERE Type_source=%1 ORDER BY Priorite_affichage").arg(TYPE_SOURCE_CIRCUIT)); + + while (q1.next()) { + const int idFamille = q1.value(0).toInt(); + const QString nom = q1.value(1).toString(); + int etat = 0; + + // 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_Circuits WHERE ID_Famille=:famille)"); + q2.bindValue(":type", TYPE_SOURCE_CIRCUIT); + q2.bindValue(":famille", idFamille); + q2.exec(); + if (q2.next() && q2.value(0).toInt() > 0) etat = 1; + + // État agrégé des circuits de la famille : + QSqlQuery q3; + q3.prepare("SELECT Etat, Etat_commande_carte, Etat_systeme FROM TB_Circuits " + "WHERE ID_Famille=:famille AND Priorite_affichage_accueil>0"); + q3.bindValue(":famille", idFamille); + q3.exec(); + while (q3.next()) { + const int etatCircuit = q3.value(0).toInt(); + const int etatCommandeCarte = q3.value(1).toInt(); + const int etatSysteme = q3.value(2).toInt(); + + if (etatSysteme != 0 + || etatCircuit == ETAT_CIRCUIT_LIMITATION + || etatCircuit == ETAT_CIRCUIT_PROTECTION + || etatCircuit == ETAT_CIRCUIT_DISJONCTE + || etatCircuit == ETAT_CIRCUIT_ABSENCE_TERRE + || etatCircuit == ETAT_CIRCUIT_ABSENCE_MASSE) { + etat = 2; + } else if (etat < 2 && (etatCommandeCarte != ETAT_COMMANDE_CARTE_OK || etatCircuit != ETAT_CIRCUIT_OK)) { + etat = 1; + } + } + + QJsonObject obj; + obj["id"] = idFamille; + obj["nom"] = nom; + obj["etat"] = etat; + familles.append(obj); + } + + QJsonObject reponse; + reponse["status"] = "ok"; + reponse["familles"] = familles; + return QHttpServerResponse(QJsonDocument(reponse).toJson()); + }); + + // ------------------------------------------------------------------ + // GET /elements?type=circuit&famille= + // Liste des circuits de la famille - sert chargement initial ET + // rafraîchissement périodique (même requête que init_circuits() et + // mise_a_jour_circuits(), cf §20.4 : ce n'est pas deux besoins + // différents, mais un seul, appelé deux fois). + // ------------------------------------------------------------------ + server.route("/elements", [](const QHttpServerRequest &request) { + QUrlQuery query(request.query()); + const QString type = query.queryItemValue("type"); + const QString familleStr = query.queryItemValue("famille"); + + if (type != "circuit") { + QJsonObject err; + err["status"] = "erreur"; + err["message"] = "type non supporté (seul 'circuit' est implémenté à ce stade)"; + return QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::NotImplemented); + } + + bool ok = false; + const int idFamille = familleStr.toInt(&ok); + if (!ok) { + QJsonObject err; + err["status"] = "erreur"; + err["message"] = "paramètre 'famille' manquant ou invalide"; + return QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::BadRequest); + } + + QJsonArray elements; + QSqlQuery q; + q.prepare( + "SELECT Cle_primaire, Etat_systeme, Nom, Etat_commande_carte, Mode_commande, Etat, Consigne, Old_consigne, " + "Mode_test, Tension, Intensite, Frequence, " + "(SELECT Type FROM TB_Modules_config WHERE Adresse_k_bus=TB_Circuits.Adresse_module) AS type_circuit, " + "No_circuit, " + "(SELECT COUNT(*) FROM TB_Events WHERE Validite=1 AND Visibilite=1 AND " + "(Action_circuits LIKE CONCAT(TB_Circuits.Cle_primaire,'/%') OR Action_circuits LIKE CONCAT('%;', TB_Circuits.Cle_primaire,'/%'))) AS nb_events, " + "Validation_cdes_events, " + "(SELECT COUNT(*) FROM TB_Events WHERE Validite=1 AND Type_source=:type AND Alarme_source>0 AND Etat_test=1 " + "AND ID_Source=TB_Circuits.Cle_primaire) AS Nb_alarmes " + "FROM TB_Circuits WHERE Priorite_affichage_accueil>0 AND ID_Famille=:famille " + "ORDER BY Priorite_affichage_accueil, Cle_primaire" + ); + q.bindValue(":type", TYPE_SOURCE_CIRCUIT); + q.bindValue(":famille", idFamille); + q.exec(); + + while (q.next()) { + QJsonObject obj; + obj["id"] = q.value(0).toInt(); + obj["etat_systeme"] = q.value(1).toInt(); + obj["nom"] = q.value(2).toString(); + obj["etat_commande_carte"] = q.value(3).toInt(); + obj["mode_commande"] = q.value(4).toInt(); + obj["etat"] = q.value(5).toInt(); + obj["consigne"] = q.value(6).toInt(); + obj["old_consigne"] = q.value(7).toInt(); + obj["mode_test"] = q.value(8).toInt(); + obj["tension"] = q.value(9).toDouble(); + obj["intensite"] = q.value(10).toDouble(); + obj["frequence"] = q.value(11).toDouble(); + obj["type_circuit"] = q.value(12).toInt(); + obj["no_circuit"] = q.value(13).toInt(); + obj["validation_cdes_events"] = (q.value(14).toInt() == 0) ? -1 : q.value(15).toInt(); + obj["etat_events"] = q.value(16).toInt(); + elements.append(obj); + } + + QJsonObject reponse; + reponse["status"] = "ok"; + reponse["elements"] = elements; + return QHttpServerResponse(QJsonDocument(reponse).toJson()); }); auto tcpServer = new QTcpServer(); @@ -66,7 +209,7 @@ int main(int argc, char *argv[]) delete tcpServer; return 1; } - qInfo() << "shipheart_api spike en écoute sur le port" << tcpServer->serverPort(); + qInfo() << "shipheart_api en écoute sur le port" << tcpServer->serverPort(); return app.exec(); } diff --git a/shipheart_api_circuits.zip b/shipheart_api_circuits.zip new file mode 100755 index 0000000000000000000000000000000000000000..535718d573355a19f37f31b43f5aa8f8616586bd GIT binary patch literal 2765 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 T000310RT<_003zU00000tJ5O( literal 0 HcmV?d00001