200 lines
6.8 KiB
Markdown
200 lines
6.8 KiB
Markdown
# TP3 : Pupitre de commande et dashboard
|
||
|
||
## NodeRed
|
||
|
||
Node-RED est un outil de développement visuel, basé sur des flux (flows), qui permet de créer facilement des applications connectées en assemblant des blocs logiques.
|
||
Il fonctionne dans un navigateur et utilise un système de “nœuds” que l’on relie entre eux pour traiter des données, piloter des équipements ou créer des interfaces web.
|
||
|
||
Node-RED est particulièrement utilisé pour :
|
||
|
||
- l’IoT (Internet des Objets)
|
||
- la collecte et transformation de données
|
||
- la communication avec des protocoles industriels (MQTT, Modbus, OPC-UA…)
|
||
- la création de petits dashboards ou synoptiques
|
||
|
||
```shell
|
||
docker run --detach --name nodered ^
|
||
--network tp_net ^
|
||
-p "1880:1880" ^
|
||
-v nodered:/data ^
|
||
-e "TZ=Europe/Paris" ^
|
||
nodered/node-red:4.1
|
||
```
|
||
|
||
## RabbitMQ
|
||
|
||
RabbitMQ est un serveur de messagerie qui permet à différentes applications de communiquer entre elles en s’échangeant des messages de manière fiable, asynchrone et découplée.
|
||
Il utilise principalement le protocole AMQP, gère les files d’attente, l’acheminement intelligent des messages, les accusés de réception et la persistance.
|
||
|
||
RabbitMQ est très utilisé pour :
|
||
|
||
- répartir des tâches entre plusieurs services,
|
||
- connecter des systèmes industriels ou IoT,
|
||
- absorber des flux importants sans perdre de messages,
|
||
- assurer une communication robuste entre modules d’une application
|
||
|
||
```shell
|
||
docker run --detach --name rabbitmq ^
|
||
--network tp_net ^
|
||
-p "5672:5672" -p "1883:1883" ^
|
||
-e "RABBITMQ_DEFAULT_USER=admin" ^
|
||
-e "RABBITMQ_DEFAULT_PASS=geii2025" ^
|
||
-e "RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS=-rabbitmq_mqtt tcp_listeners [1883]" ^
|
||
-v rabbitmq_data:/var/lib/rabbitmq ^
|
||
-l "caddy=rabbitmq.localhost" ^
|
||
-l "caddy.reverse_proxy={{upstreams 15672}}" ^
|
||
-l "caddy.tls=internal" ^
|
||
rabbitmq:4.1.4-management ^
|
||
sh -c "rabbitmq-plugins enable --offline rabbitmq_mqtt rabbitmq_management && rabbitmq-server"
|
||
```
|
||
|
||
Ajouter la bibliothèque Flow sDashboard 2
|
||
|
||
## Programme C
|
||
|
||
Ajouter la bibliothèque MQTT
|
||
|
||
```c
|
||
#include <curl/curl.h>
|
||
#include <string>
|
||
#include <iostream>
|
||
|
||
#include <thread>
|
||
#include <atomic>
|
||
#include <queue>
|
||
#include <mutex>
|
||
#include <condition_variable>
|
||
#include <csignal>
|
||
#include <chrono>
|
||
#include <cstring>
|
||
#undef timeout
|
||
#include "mqtt/async_client.h"
|
||
|
||
#include <nlohmann/json.hpp>
|
||
```
|
||
|
||
Déclarer les constantes
|
||
|
||
```c
|
||
/* Configuration MQTT */
|
||
const std::string ADDRESS = "tcp://rabbitmq:1883";
|
||
const std::string CLIENTID = "CppClientTP";
|
||
const std::string TOPIC = "geii/ordre/#";
|
||
const int QOS = 1;
|
||
const int CYCLE_MS = 100;
|
||
```
|
||
|
||
|
||
Écrire la fonction qui va traiter l'arrivée des messages
|
||
|
||
```c
|
||
class callback : public virtual mqtt::callback {
|
||
public:
|
||
void message_arrived(mqtt::const_message_ptr msg) override {
|
||
std::string payload = msg->to_string();
|
||
//... Effectuer les actions en conséquences
|
||
}
|
||
};
|
||
```
|
||
|
||
```c
|
||
mqtt::async_client client(ADDRESS, CLIENTID);
|
||
callback cb;
|
||
client.set_callback(cb);
|
||
|
||
mqtt::connect_options connOpts;
|
||
connOpts.set_clean_session(true);
|
||
connOpts.set_user_name("admin");
|
||
connOpts.set_password("geii2025");
|
||
try {
|
||
client.connect(connOpts)->wait();
|
||
client.start_consuming();
|
||
client.subscribe(TOPIC, QOS)->wait();
|
||
} catch (const mqtt::exception &exc) {
|
||
std::cerr << "Erreur MQTT: " << exc.what() << "\n";
|
||
return 1;
|
||
}
|
||
```
|
||
|
||
Dans la fonction ProcessMQTT créer un objet JSON avec les valeurs des différents capteurs.
|
||
|
||
```c
|
||
json obj = {
|
||
{"entree", _digital[IN_FLOW_IN].dvalue},
|
||
{"sortie", _digital[IN_FLOW_OUT].dvalue}
|
||
};
|
||
```
|
||
|
||
Envoyer le message
|
||
|
||
```c
|
||
std::string payload = obj.dump();
|
||
auto msg = mqtt::make_message("geii/telemetry", payload);
|
||
msg->set_qos(1);
|
||
client->publish(msg);
|
||
```
|
||
|
||
Après la boucle principale déconnecter MQTT.
|
||
|
||
```c
|
||
try {
|
||
client.unsubscribe(TOPIC)->wait();
|
||
client.stop_consuming();
|
||
client.disconnect()->wait();
|
||
} catch(const mqtt::exception &exc){
|
||
std::cerr << "Erreur déconnexion MQTT: " << exc.what() << "\n";
|
||
}
|
||
```
|
||
|
||
## Énoncé
|
||
|
||
Un château d'eau est une citerne située en hauteur qui alimente un certain nombre d'habitations en eau potable.
|
||
|
||
Pour garantir une pression constante et un service de qualité, la hauteur dans la citerne doit être régulière.
|
||
|
||
Notre système se compose :
|
||
|
||
* D'une citerne de 10m3,
|
||
* De 4 pompes chacune capable d'alimenter la citerne avec un débit de 60 l/s pour chaque pompe (*OUT_PUMP_1*, *OUT_PUMP_2*, *OUT_PUMP_3*, *OUT_PUMP_4*).
|
||
* De 4 capteurs disposés à différentes hauteurs (*IN_SENSOR_MIN*, *IN_SENSOR_LOW*, *IN_SENSOR_HIGH*, *IN_SENSOR_MAX*).
|
||
* D'un bouton poussoir permettant de changer le mode de fonctionnement (*IN_KEYBOARD_A*).
|
||
* De quatre boutons poussoirs permettant de démarrer les moteurs manuellement (*IN_KEYBOARD_1*, *IN_KEYBOARD_2*, *IN_KEYBOARD_3*, *IN_KEYBOARD_4*).
|
||
* Un voyant qui indique le mode de fonctionnement (*OUT_DISPLAY_MODE*)
|
||
* Enfin un avertisseur sonore qui sonne lorsque une anomalie grave survient (*OUT_BEEP*).
|
||
|
||
|
||
# Fonctionnement
|
||
|
||
Le système démarre en mode manuel (*OUT_DISPLAY_MODE* = 0). Un appui sur le bouton poussoir (*IN_KEYBOARD_A*) passe le système en mode automatique, un nouvel appui est nécessaire pour repasser en mode manuel.
|
||
|
||
La sortie (*OUT_DISPLAY_MODE* = 1) est activée en mode automatique.
|
||
|
||
* Faire le grafcet de ce fonctionnement
|
||
* Programmer ce fonctionnement
|
||
|
||
## Mode manuel
|
||
|
||
Un appui sur un bouton poussoir (*IN_KEYBOARD_4*) met en route la pompe correspondante (*OUT_PUMP_4*). Un nouvel appui arrête la pompe.
|
||
|
||
Lorsque le système passe d'un mode à l'autre (manuel/automatique) toutes les pompes sont mises à l'arrêt instantanément.
|
||
|
||
* Faire le grafcet de ce fonctionnement
|
||
* Programmer ce fonctionnement
|
||
|
||
## Mode de sécurité
|
||
|
||
Lorsque le niveau *IN_SENSOR_MAX* est atteint tous les moteurs sont arrêtés quelque soit le mode de fonctionnement.
|
||
|
||
Lorsque le niveau *IN_SENSOR_MIN* est atteint l'avertisseur sonore est déclenché. Si le système est en mode automatique il repasse en mode manuel. L'avertisseur retenti qu'une seule fois quelque soit la durée en dessous du seuil d'avertissement. Il faut que le niveau repasse au dessus du niveau *IN_SENSOR_MIN* pour réinitialiser l'avertisseur soniore.
|
||
|
||
* Faire le grafcet de ce fonctionnement
|
||
* Programmer ce fonctionnement
|
||
|
||
## Mode automatique
|
||
|
||
Lorsque le système est en dessous de *IN_SENSOR_LOW* pendant plus de 1.5s le système démarre un moteur. Si le niveau est toujours inférieur à *IN_SENSOR_LOW* = 0 au bout de 1.5s le système démarre une nouvelle pompe jusqu'à ce que les 4 pompes soient activées.
|
||
|
||
A l'inverse Lorsque le système est au dessus de *IN_SENSOR_LOW* = 1 pendant plus de 1.5s le système arrête un moteur. Si le niveau est toujours supérieur à IN_SENSOR_LOW au bout de 1.5s le système arrête une nouvelle pompe jusqu'à ce que les 4 pompes soient à l'arrêt.
|
||
|
||
* Programmer ce fonctionnement en suivant le [grafcet](grafcet.pdf) fourni
|