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 0000000..535718d Binary files /dev/null and b/shipheart_api_circuits.zip differ