2025-11-30 09:48:43 +01:00
# include <unistd.h>
# include <ncurses.h>
# include <math.h>
# include <locale.h>
# include <array>
2025-12-03 23:27:56 +01:00
# include "main.hpp"
2025-11-30 09:48:43 +01:00
# include "AutomForArduino.cpp"
2025-12-07 15:51:07 +01:00
# include <prometheus/counter.h>
# include <prometheus/gauge.h>
# include <prometheus/histogram.h>
# include <prometheus/registry.h>
# include <prometheus/exposer.h>
2025-12-07 15:51:48 +01:00
# 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"
2025-12-07 16:48:16 +01:00
# include <nlohmann/json.hpp>
2025-12-07 15:51:48 +01:00
using namespace std : : chrono_literals ;
2025-12-07 16:48:16 +01:00
using json = nlohmann : : json ;
2025-12-07 15:51:48 +01:00
2025-11-30 09:48:43 +01:00
// Constantes de fonctionnement
# define LEVEL_MIN 2
# define FLOW_PER_PUMP 150
2025-12-07 16:48:16 +01:00
/* 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 ;
2025-11-30 09:48:43 +01:00
WINDOW * window ;
2025-12-07 16:48:16 +01:00
int etape = 0 ; // Étape du grafcet : début Automatique
2025-11-30 09:48:43 +01:00
int bp_mode , bp_mode_fm ;
unsigned short pompe1 , pompe2 , pompe3 , pompe4 ; // bouton des pompes 0 (arrêt) / 1 (marche)
unsigned short pompe1_old , pompe2_old , pompe3_old , pompe4_old ;
unsigned short sensor_max , sensor_high , sensor_low , sensor_min ;
float TankInitalValue = 7 ;
2025-12-07 15:51:07 +01:00
TemporisationRetardMontee tempo1 ( 1500 ) ;
TemporisationRetardMontee tempo2 ( 3000 ) ;
TemporisationRetardMontee tempo3 ( 4000 ) ;
TemporisationRetardMontee tempo4 ( 6000 ) ;
2025-11-30 09:48:43 +01:00
// Prometheus
// ************************************************************
2025-12-07 15:51:07 +01:00
using namespace prometheus ;
std : : shared_ptr < Registry > registry ;
Gauge * debit_entree = nullptr ;
Gauge * debit_sortie = nullptr ;
Gauge * debit_p1 = nullptr ;
Gauge * debit_p2 = nullptr ;
Gauge * debit_p3 = nullptr ;
Gauge * debit_p4 = nullptr ;
Gauge * tank_gauge = nullptr ;
Counter * volume_p1 = nullptr ;
Counter * volume_p2 = nullptr ;
Counter * volume_p3 = nullptr ;
Counter * volume_p4 = nullptr ;
2025-11-30 09:48:43 +01:00
2025-12-07 15:51:07 +01:00
Histogram : : BucketBoundaries buckets = {
2 , 5 , 6 , 7 , 8 , 9 , 9.5
} ;
Histogram * tank_histogram = nullptr ;
2025-11-30 09:48:43 +01:00
// ************************************************************
2025-12-07 16:48:16 +01:00
/* Queue thread-safe */
std : : queue < std : : string > orders_queue ;
std : : mutex queue_mtx ;
std : : string pop_all_and_get_last ( ) {
std : : lock_guard < std : : mutex > lock ( queue_mtx ) ;
if ( orders_queue . empty ( ) ) return " " ;
std : : string last ;
while ( ! orders_queue . empty ( ) ) {
last = orders_queue . front ( ) ;
orders_queue . pop ( ) ;
}
return last ;
}
void push_order ( const std : : string & msg ) {
std : : lock_guard < std : : mutex > lock ( queue_mtx ) ;
orders_queue . push ( msg ) ;
}
/* Etats machine */
enum class MachineState { STOPPED , RUNNING , ESTOP } ;
std : : atomic < MachineState > machine_state ( MachineState : : STOPPED ) ;
std : : atomic < bool > estop_flag ( false ) ;
std : : atomic < bool > running ( true ) ;
/* Fonctions d'application */
void apply_start ( ) {
if ( machine_state ! = MachineState : : RUNNING ) {
std : : cout < < " [MACHINE] -> START \n " ;
machine_state = MachineState : : RUNNING ;
}
}
void apply_stop ( ) {
if ( machine_state ! = MachineState : : STOPPED ) {
std : : cout < < " [MACHINE] -> STOP \n " ;
machine_state = MachineState : : STOPPED ;
}
}
void apply_estop ( ) {
if ( machine_state ! = MachineState : : ESTOP ) {
std : : cout < < " [MACHINE] -> E-STOP \n " ;
machine_state = MachineState : : ESTOP ;
}
}
/* Thread machine */
void machine_thread_fn ( ) {
while ( running ) {
if ( estop_flag ) {
apply_estop ( ) ;
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( CYCLE_MS ) ) ;
continue ;
}
pompe1 = 1 ;
std : : string last = pop_all_and_get_last ( ) ;
if ( ! last . empty ( ) ) {
if ( last = = " START " ) apply_start ( ) ;
if ( last = = " P1 " ) {
pompe1 = 1 ;
} else if ( last = = " P2 " ) {
pompe2 = 1 ;
} else if ( last = = " P3 " ) {
pompe3 = 1 ;
} else if ( last = = " P4 " ) {
pompe4 = 1 ;
}
else if ( last = = " STOP " ) apply_stop ( ) ;
else std : : cout < < " [MACHINE] Commande inconnue: ' " < < last < < " ' \n " ;
}
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( CYCLE_MS ) ) ;
}
apply_stop ( ) ;
}
/* Callback MQTT */
class callback : public virtual mqtt : : callback {
public :
void message_arrived ( mqtt : : const_message_ptr msg ) override {
std : : string payload = msg - > to_string ( ) ;
if ( payload = = " E_STOP " ) {
estop_flag = true ;
std : : cout < < " [MQTT] E-STOP reçu \n " ;
} else if ( payload = = " P1 " ) {
pompe2 = 1 ;
} else {
push_order ( payload ) ;
std : : cout < < " [MQTT] Reçu: ' " < < payload < < " ' \n " ;
}
}
} ;
/* SIGINT handler */
void sigint_handler ( int ) {
std : : cout < < " [MAIN] SIGINT reçu \n " ;
running = false ;
}
void send_to_influx ( double value ) {
CURL * curl = curl_easy_init ( ) ;
if ( ! curl ) {
std : : cerr < < " Erreur CURL \n " ;
return ;
}
// Line protocol
std : : string line = " machine_cycle,machine=convoyeur1 value= " + std : : to_string ( value ) ;
// Endpoint InfluxDB 2.x
std : : string url =
" http://influxdb:8086/api/v2/write?org=geii&bucket=mesures&precision=s " ;
struct curl_slist * headers = nullptr ;
headers = curl_slist_append ( headers , " Authorization: Token MON_TOKEN " ) ;
headers = curl_slist_append ( headers , " Content-Type: text/plain " ) ;
curl_easy_setopt ( curl , CURLOPT_URL , url . c_str ( ) ) ;
curl_easy_setopt ( curl , CURLOPT_HTTPHEADER , headers ) ;
curl_easy_setopt ( curl , CURLOPT_POSTFIELDS , line . c_str ( ) ) ;
CURLcode res = curl_easy_perform ( curl ) ;
if ( res ! = CURLE_OK ) {
std : : cerr < < " Erreur CURL: " < < curl_easy_strerror ( res ) < < " \n " ;
}
curl_slist_free_all ( headers ) ;
curl_easy_cleanup ( curl ) ;
}
2025-12-03 23:27:56 +01:00
int main ( )
2025-12-03 23:20:46 +01:00
{
2025-12-07 16:48:16 +01:00
std : : signal ( SIGINT , sigint_handler ) ;
/* MQTT async client */
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 ( " ChangeMe " ) ;
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 ;
}
2025-11-30 09:48:43 +01:00
/* Initialisation */
ConsoleInit ( ) ;
AffichageWindow ( ) ;
InitPrometheus ( ) ;
ProcessInitKeyboard ( ) ;
ProcessInitIO ( ) ;
ProcessInitValues ( ) ;
while ( 1 )
{
int ch = getch ( ) ; // Lit l'entrée du clavier sans bloquer
// **** Break loop if escape key (27) is pressed
2025-12-03 23:20:46 +01:00
if ( ch = = 27 | | _digital [ OUT_END ] . ivalue ) {
2025-11-30 09:48:43 +01:00
break ;
}
2025-12-03 23:20:46 +01:00
2025-11-30 09:48:43 +01:00
// **** Beep
if ( _digital [ OUT_BEEP ] . ivalue )
{
beep ( ) ;
_digital [ OUT_BEEP ] . ivalue = false ;
}
Process ( ) ;
LireClavier ( ch ) ;
LireEntree ( ) ;
EvolutionGrafcet ( ) ;
Actions ( ) ;
ProcessPrometheus ( ) ;
2025-12-07 16:04:44 +01:00
ProcessMQTT ( & client ) ;
2025-11-30 09:48:43 +01:00
ProcessException ( ) ;
usleep ( 100000 ) ;
}
endwin ( ) ; // Termine ncurses et rétablit le terminal
2025-12-07 16:04:44 +01:00
/* Arrêt */
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 " ;
}
//th_machine.join();
std : : cout < < " [MAIN] Terminé \n " ;
2025-12-03 23:20:46 +01:00
2025-11-30 09:48:43 +01:00
return 0 ;
}
/**
* Programme
*/
void LireEntree ( )
{
int input ;
input = digitalRead ( IN_KEYBOARD_A ) ;
bp_mode_fm = ( input > bp_mode ) ;
bp_mode = input ;
sensor_min = digitalRead ( IN_SENSOR_MIN ) ;
sensor_low = digitalRead ( IN_SENSOR_LOW ) ;
sensor_high = digitalRead ( IN_SENSOR_HIGH ) ;
sensor_max = digitalRead ( IN_SENSOR_MAX ) ;
}
void EvolutionGrafcet ( )
{
int etape_futur = etape ;
if ( etape < 10 & & bp_mode_fm )
{
etape_futur = 10 ;
pompe1 = pompe2 = pompe3 = pompe4 = 0 ;
}
if ( etape < = 2 & & _digital [ IN_KEYBOARD_1 ] . raising )
{
pompe1 = ! pompe1 ;
}
if ( etape < = 2 & & _digital [ IN_KEYBOARD_2 ] . raising )
{
pompe2 = ! pompe2 ;
}
if ( etape < = 2 & & _digital [ IN_KEYBOARD_3 ] . raising )
{
pompe3 = ! pompe3 ;
}
if ( etape < = 2 & & _digital [ IN_KEYBOARD_4 ] . raising )
{
pompe4 = ! pompe4 ;
}
if ( etape = = 0 & & ! sensor_min )
{
etape_futur = 1 ;
}
if ( etape = = 1 )
{
etape_futur = 2 ;
}
if ( etape = = 2 & & sensor_min )
{
etape_futur = 0 ;
}
if ( etape > = 10 & & bp_mode_fm )
{
etape_futur = 0 ;
pompe1 = pompe2 = pompe3 = pompe4 = 0 ;
}
if ( sensor_max )
{
pompe1 = pompe2 = pompe3 = pompe4 = 0 ;
}
/* Automatique */
if ( etape = = 10 & & ! sensor_low & & ! sensor_high )
{
etape_futur = 11 ;
}
if ( etape = = 11 & & sensor_high )
{
etape_futur = 10 ;
}
if ( etape = = 11 & & tempo1 . getSortie ( ) )
{
etape_futur = 12 ;
}
if ( etape = = 12 & & sensor_high )
{
etape_futur = 13 ;
}
if ( etape = = 12 & & tempo2 . getSortie ( ) )
{
etape_futur = 14 ; // Allumer le moteur 2
}
if ( etape = = 13 & & tempo1 . getSortie ( ) )
{
etape_futur = 10 ;
}
if ( etape = = 13 & & ! sensor_low & & ! sensor_high )
{
etape_futur = 12 ;
}
if ( etape = = 14 & & sensor_high )
{
etape_futur = 15 ;
}
if ( etape = = 14 & & tempo3 . getSortie ( ) )
{
etape_futur = 16 ;
}
if ( etape = = 15 & & tempo1 . getSortie ( ) )
{
etape_futur = 13 ;
}
if ( etape = = 15 & & ! sensor_low & & ! sensor_high )
{
etape_futur = 14 ;
}
if ( etape = = 16 & & sensor_high )
{
etape_futur = 17 ;
}
if ( etape = = 16 & & tempo4 . getSortie ( ) )
{
etape_futur = 18 ;
}
if ( etape = = 17 & & tempo1 . getSortie ( ) )
{
etape_futur = 15 ;
}
if ( etape = = 17 & & ! sensor_low & & ! sensor_high )
{
etape_futur = 16 ;
}
if ( etape = = 18 & & sensor_high )
{
etape_futur = 19 ;
}
if ( etape = = 19 & & tempo1 . getSortie ( ) )
{
etape_futur = 17 ;
}
if ( etape = = 19 & & ! sensor_low & & ! sensor_high )
{
etape_futur = 18 ;
}
/* Fin de mode automatique */
if ( etape ! = etape_futur )
{
etape = etape_futur ;
}
}
void Actions ( )
{
digitalWrite ( OUT_DISPLAY_GRAFCET , etape ) ;
digitalWrite ( OUT_DISPLAY_MODE , etape > = 10 ) ;
digitalWrite ( OUT_PUMP_1 , ! sensor_max & & ( pompe1 = = 1 | | etape > = 12 ) ) ;
digitalWrite ( OUT_PUMP_2 , ! sensor_max & & ( pompe2 = = 1 | | etape > = 14 ) ) ;
digitalWrite ( OUT_PUMP_3 , ! sensor_max & & ( pompe3 = = 1 | | etape > = 16 ) ) ;
digitalWrite ( OUT_PUMP_4 , ! sensor_max & & ( pompe4 = = 1 | | etape > = 18 ) ) ;
// digitalWrite(OUT_BEEP, etape == 1);
if ( etape > = 11 )
{
tempo1 . activation ( ) ;
tempo2 . activation ( ) ;
tempo3 . activation ( ) ;
tempo4 . activation ( ) ;
}
}
/**
* Process
*/
void ProcessInitKeyboard ( )
{
_keyboard [ 0 ] . vKey = ' 1 ' ;
_keyboard [ 0 ] . input = IN_KEYBOARD_1 ;
_keyboard [ 1 ] . vKey = ' 2 ' ;
_keyboard [ 1 ] . input = IN_KEYBOARD_2 ;
_keyboard [ 2 ] . vKey = ' 3 ' ;
_keyboard [ 2 ] . input = IN_KEYBOARD_3 ;
_keyboard [ 3 ] . vKey = ' 4 ' ;
_keyboard [ 3 ] . input = IN_KEYBOARD_4 ;
_keyboard [ 4 ] . vKey = ' a ' ;
_keyboard [ 4 ] . input = IN_KEYBOARD_A ;
_keyboard [ 5 ] . vKey = ' x ' ;
_keyboard [ 5 ] . input = IN_KEYBOARD_X ;
_keyboard [ 6 ] . vKey = ' 6 ' ;
_keyboard [ 6 ] . input = IN_KEYBOARD_7 ;
_keyboard [ 7 ] . vKey = ' 7 ' ;
_keyboard [ 7 ] . input = IN_KEYBOARD_8 ;
_keyboard [ 8 ] . vKey = ' 8 ' ;
_keyboard [ 8 ] . input = IN_KEYBOARD_9 ;
_keyboard [ 9 ] . vKey = ' 9 ' ;
_keyboard [ 9 ] . input = IN_KEYBOARD_0 ;
for ( int i = 0 ; i < NB_KEYBOARD ; i + + )
{
_digital [ _keyboard [ i ] . input ] . mode = OP_DIGITAL_READ ;
}
}
void ProcessInitIO ( )
{
pinMode ( IN_KEYBOARD_1 , IO_INPUT | DIGITAL ) ;
pinMode ( IN_KEYBOARD_2 , IO_INPUT | DIGITAL ) ;
pinMode ( IN_KEYBOARD_3 , IO_INPUT | DIGITAL ) ;
pinMode ( IN_KEYBOARD_4 , IO_INPUT | DIGITAL ) ;
pinMode ( IN_KEYBOARD_A , IO_INPUT | DIGITAL ) ;
pinMode ( IN_KEYBOARD_7 , IO_INPUT | DIGITAL ) ;
pinMode ( IN_KEYBOARD_8 , IO_INPUT | DIGITAL ) ;
pinMode ( IN_KEYBOARD_9 , IO_INPUT | DIGITAL ) ;
pinMode ( IN_KEYBOARD_0 , IO_INPUT | DIGITAL ) ;
pinMode ( IN_KEYBOARD_X , IO_INPUT | DIGITAL ) ;
pinMode ( IN_SENSOR_MIN , IO_INPUT | DIGITAL ) ;
pinMode ( IN_SENSOR_LOW , IO_INPUT | DIGITAL ) ;
pinMode ( IN_SENSOR_HIGH , IO_INPUT | DIGITAL ) ;
pinMode ( IN_SENSOR_MAX , IO_INPUT | DIGITAL ) ;
pinMode ( IN_TANK_LEVEL , IO_INPUT | ANALOG ) ;
pinMode ( IN_FLOW_OUT , IO_INPUT | ANALOG ) ;
pinMode ( IN_FLOW_IN , IO_INPUT | ANALOG ) ;
pinMode ( IN_FLOW_DIF , IO_INPUT | ANALOG ) ;
pinMode ( IN_TANK_MIN , IO_INPUT | ANALOG ) ;
pinMode ( IN_TANK_MAX , IO_INPUT | ANALOG ) ;
pinMode ( IN_FLOW_CAP , IO_INPUT | ANALOG ) ;
pinMode ( IN_FLOW_1 , IO_INPUT | ANALOG ) ;
pinMode ( IN_FLOW_2 , IO_INPUT | ANALOG ) ;
pinMode ( IN_FLOW_3 , IO_INPUT | ANALOG ) ;
pinMode ( IN_FLOW_4 , IO_INPUT | ANALOG ) ;
pinMode ( OUT_PUMP_1 , IO_OUTPUT | DIGITAL ) ;
pinMode ( OUT_PUMP_2 , IO_OUTPUT | DIGITAL ) ;
pinMode ( OUT_PUMP_3 , IO_OUTPUT | DIGITAL ) ;
pinMode ( OUT_PUMP_4 , IO_OUTPUT | DIGITAL ) ;
pinMode ( OUT_DISPLAY_MODE , IO_OUTPUT | DIGITAL ) ;
pinMode ( OUT_DISPLAY_GRAFCET , IO_OUTPUT | DIGITAL ) ;
pinMode ( OUT_LEVEL_MIN , IO_OUTPUT | ANALOG ) ;
pinMode ( OUT_LEVEL_LOW , IO_OUTPUT | ANALOG ) ;
pinMode ( OUT_LEVEL_HIGH , IO_OUTPUT | ANALOG ) ;
pinMode ( OUT_LEVEL_MAX , IO_OUTPUT | ANALOG ) ;
pinMode ( OUT_FLOW_PER_PUMP , IO_OUTPUT | ANALOG ) ;
pinMode ( OUT_FLOW_OUT_AMPLITUDE , IO_OUTPUT | ANALOG ) ;
pinMode ( OUT_BEEP , IO_OUTPUT | DIGITAL ) ;
_digital [ OUT_PUMP_1 ] . error = 30 ;
_digital [ OUT_PUMP_1 ] . efficacite = 1.0 ;
//_digital[OUT_PUMP_1].time = 4294967295; //UINT_MAX
_digital [ OUT_PUMP_2 ] . error = 30 ;
_digital [ OUT_PUMP_2 ] . efficacite = 0.72 ;
//_digital[OUT_PUMP_2].time = 4294967295;
_digital [ OUT_PUMP_3 ] . error = 10 ;
_digital [ OUT_PUMP_3 ] . efficacite = 1.0 ;
//_digital[OUT_PUMP_3].time = 4294967295;
_digital [ OUT_PUMP_4 ] . error = 30 ;
_digital [ OUT_PUMP_4 ] . efficacite = 1.0 ;
//_digital[OUT_PUMP_4].time = 4294967295;
}
void ProcessInitValues ( )
{
t_start = t_backup = millis ( ) ;
srand ( time ( NULL ) ) ;
_digital [ IN_TANK_LEVEL ] . dvalue = _digital [ IN_TANK_MAX ] . dvalue = _digital [ IN_TANK_MIN ] . dvalue = TankInitalValue ;
_digital [ OUT_FLOW_PER_PUMP ] . dvalue = FLOW_PER_PUMP ;
_digital [ OUT_FLOW_OUT_AMPLITUDE ] . dvalue = 100.0 ;
_digital [ OUT_LEVEL_MIN ] . dvalue = LEVEL_MIN ;
_digital [ OUT_LEVEL_LOW ] . dvalue = 6 ;
_digital [ OUT_LEVEL_HIGH ] . dvalue = 7 ;
_digital [ OUT_LEVEL_MAX ] . dvalue = 9.5 ;
_digital [ IN_FLOW_OUT ] . dvalue = 100.0 ;
}
/**
* Fonctionnement des moteurs
*/
2025-12-03 23:20:46 +01:00
double ProcessMoteur ( int i )
2025-11-30 09:48:43 +01:00
{
double vitesse = 1.0 ;
double t = _digital [ i ] . time / 5000.0 ;
if ( _digital [ i ] . ivalue )
{
if ( _digital [ i ] . time < 2500 )
{
vitesse = 4 * pow ( t , 3.0 ) ;
}
else if ( _digital [ i ] . time < 5000 )
{
vitesse = 1.0 - pow ( 2 - 2 * t , 3 ) / 2.0 ;
}
else
{
vitesse = 1.0 + 1.0 / ( _digital [ i ] . error * 2.0 ) - rand ( ) / ( double ) RAND_MAX / _digital [ i ] . error ;
}
}
else
{
if ( _digital [ i ] . time < 2500 )
{
vitesse = 1 - 4 * pow ( t , 3.0 ) ;
}
else if ( _digital [ i ] . time < 5000 )
{
vitesse = pow ( 2 - 2 * t , 3 ) / 2.0 ;
// vitesse = 1 - pow(t, 4.0);
}
else
{
vitesse = 0.0 ;
}
}
return _digital [ OUT_FLOW_PER_PUMP ] . dvalue * _digital [ i ] . efficacite * vitesse ;
}
void ProcessException ( )
{
2025-12-07 15:51:07 +01:00
if ( t_elapsed > 60 ) {
2025-11-30 09:48:43 +01:00
_digital [ OUT_PUMP_1 ] . mode = 0 ;
2025-12-07 15:51:07 +01:00
digitalWrite ( OUT_PUMP_1 , 0 ) ;
2025-11-30 09:48:43 +01:00
} else if ( t_elapsed > 15 ) {
2025-12-07 15:51:07 +01:00
//_digital[IN_SENSOR_LOW].mode = 0;
2025-11-30 09:48:43 +01:00
}
}
void Process ( )
{
// *****
unsigned long t = millis ( ) ;
t_elapsed = ( t - t_start ) / 1000.0 ;
dt = ( t - t_backup ) / 1000.0 ;
// ***** FLOW OUT
if ( _digital [ IN_TANK_LEVEL ] . dvalue > 1.0 )
{
2025-12-07 15:51:07 +01:00
_digital [ IN_FLOW_OUT ] . dvalue = SimulConsoSinusoidale ( t ) ;
//_digital[IN_FLOW_OUT].dvalue = SimulConsoBrown(_digital[IN_FLOW_OUT].dvalue);
2025-11-30 09:48:43 +01:00
}
else
{
if ( _digital [ IN_FLOW_CAP ] . dvalue = = 0.0 ) {
_digital [ IN_FLOW_CAP ] . dvalue = _digital [ IN_FLOW_OUT ] . dvalue ;
}
_digital [ IN_FLOW_OUT ] . dvalue = _digital [ IN_FLOW_CAP ] . dvalue * _digital [ IN_TANK_LEVEL ] . dvalue ;
}
// ***** FLOW IN
_digital [ IN_FLOW_IN ] . dvalue = 0 ;
for ( int i = OUT_PUMP_1 ; i < = OUT_PUMP_4 ; i + + )
{
_digital [ i - 4 ] . dvalue = ProcessMoteur ( i ) ;
_digital [ IN_FLOW_IN ] . dvalue + = _digital [ i - 4 ] . dvalue ;
}
_digital [ IN_FLOW_DIF ] . dvalue = _digital [ IN_FLOW_IN ] . dvalue - _digital [ IN_FLOW_OUT ] . dvalue ;
// ***** TANK LEVEL
_digital [ IN_TANK_LEVEL ] . dvalue + = ( _digital [ IN_FLOW_IN ] . dvalue - _digital [ IN_FLOW_OUT ] . dvalue ) / 1000.0 * dt ;
if ( _digital [ IN_TANK_LEVEL ] . dvalue > 10.0 ) {
_digital [ IN_TANK_LEVEL ] . dvalue = 10.0 ;
}
if ( _digital [ IN_TANK_LEVEL ] . dvalue > _digital [ IN_TANK_MAX ] . dvalue ) {
_digital [ IN_TANK_MAX ] . dvalue = _digital [ IN_TANK_LEVEL ] . dvalue ;
}
if ( _digital [ IN_TANK_LEVEL ] . dvalue < _digital [ IN_TANK_MIN ] . dvalue ) {
_digital [ IN_TANK_MIN ] . dvalue = _digital [ IN_TANK_LEVEL ] . dvalue ;
}
// **** KEYBOARD
if ( _digital [ IN_KEYBOARD_X ] . raising )
{
_digital [ IN_SENSOR_LOW ] . mode = _digital [ IN_SENSOR_LOW ] . mode ^ 0x01 ;
}
for ( int i = IN_KEYBOARD_7 ; i < = IN_KEYBOARD_0 ; i + + )
{
if ( _digital [ i ] . raising )
{
unsigned char p = i + ( OUT_PUMP_1 - IN_KEYBOARD_7 ) ;
_digital [ p ] . mode ^ = 0x01 ;
if ( ! ( _digital [ p ] . mode & 0x01 ) ) {
2025-12-07 15:51:07 +01:00
digitalWrite ( p , 0 ) ;
2025-11-30 09:48:43 +01:00
}
}
}
// **** SENSOR
int test ;
test = ( _digital [ IN_TANK_LEVEL ] . dvalue > _digital [ OUT_LEVEL_MIN ] . dvalue ) ;
if ( _digital [ IN_SENSOR_MIN ] . ivalue ! = test )
{
if ( test = = 0 )
{
_digital [ IN_SENSOR_MIN ] . nb + = 1 ;
}
_digital [ IN_SENSOR_MIN ] . ivalue = test ;
}
test = _digital [ IN_TANK_LEVEL ] . dvalue > _digital [ OUT_LEVEL_LOW ] . dvalue & & _digital [ IN_SENSOR_LOW ] . mode & 0x01 ;
if ( _digital [ IN_SENSOR_LOW ] . ivalue ! = test )
{
if ( test = = 0 )
{
_digital [ IN_SENSOR_LOW ] . nb + = 1 ;
}
_digital [ IN_SENSOR_LOW ] . ivalue = test ;
}
test = _digital [ IN_TANK_LEVEL ] . dvalue > _digital [ OUT_LEVEL_MAX ] . dvalue ;
if ( _digital [ IN_SENSOR_MAX ] . ivalue ! = test )
{
if ( test = = 1 )
{
_digital [ IN_SENSOR_MAX ] . nb + = 1 ;
}
_digital [ IN_SENSOR_MAX ] . ivalue = test ;
}
test = _digital [ IN_TANK_LEVEL ] . dvalue > _digital [ OUT_LEVEL_HIGH ] . dvalue ;
if ( _digital [ IN_SENSOR_HIGH ] . ivalue ! = test )
{
if ( test = = 1 )
{
_digital [ IN_SENSOR_HIGH ] . nb + = 1 ;
}
_digital [ IN_SENSOR_HIGH ] . ivalue = test ;
}
2025-12-03 23:27:56 +01:00
Affichage ( ) ;
2025-11-30 09:48:43 +01:00
t_backup = t ;
}
2025-12-03 23:20:46 +01:00
double SimulConsoSinusoidale ( long t )
2025-11-30 09:48:43 +01:00
{
double alea = ( ( long ) ( t / 100.0 ) % 600 ) * 3 / 1800.0 * PI ;
//mvprintw(18, 0, "%ld %f", (long)(t / 100.0), alea);
return 100 + cos ( alea ) * cos ( alea ) * _digital [ OUT_FLOW_OUT_AMPLITUDE ] . dvalue ;
}
2025-12-03 23:20:46 +01:00
// dt : Intervalle de temps
2025-11-30 09:48:43 +01:00
double SimulConsoBrown ( double valeur_precedente )
{
float mu = 0.01 * - ( ( ( ( int ) t_elapsed / 30 ) % 2 ) * 2 - 1 ) ; // Taux de croissance (1%)
float sigma = 0.05 ; // Volatilité (5%)
mvprintw ( 8 , 40 , " (µ %.1f %% ; σ %.1f %%) " , mu * 100 , sigma * 100 ) ;
// Nombre aléatoire compris dans [-1 +1]
2025-12-03 23:20:46 +01:00
float rand_std_normal = ( ( double ) rand ( ) / RAND_MAX ) * 2.0 - 1.0 ;
2025-11-30 09:48:43 +01:00
// Calcule la variation logarithmique pour cette étape
float drift = ( mu - 0.5f * sigma * sigma ) * dt ;
float diffusion = sigma * sqrt ( dt ) * rand_std_normal ;
return valeur_precedente * exp ( drift + diffusion ) ;
}
/**
* Affichage dans la console
*/
/**
2025-12-03 23:20:46 +01:00
* Initialisation , affichage des parties statiques
2025-11-30 09:48:43 +01:00
*/
void AffichageWindow ( )
{
window = subwin ( stdscr , 19 , 62 , 0 , 0 ) ;
2025-12-03 23:20:46 +01:00
2025-11-30 09:48:43 +01:00
box ( window , 0 , 0 ) ;
2025-12-03 23:20:46 +01:00
2025-11-30 09:48:43 +01:00
// Titre
mvwprintw ( window , 1 , 2 , " Château d'eau (11/2024) " ) ;
2025-12-03 23:20:46 +01:00
2025-11-30 09:48:43 +01:00
// I/O
// Ligne du haut
mvwaddch ( window , 2 , 0 , ACS_LTEE ) ;
mvwhline ( window , 2 , 1 , 0 , 60 ) ;
mvwaddch ( window , 2 , 61 , ACS_RTEE ) ;
2025-12-03 23:20:46 +01:00
2025-11-30 09:48:43 +01:00
// Ligne du bas
mvwaddch ( window , 7 , 0 , ACS_LTEE ) ;
mvwhline ( window , 7 , 1 , 0 , 60 ) ;
mvwaddch ( window , 7 , 61 , ACS_RTEE ) ;
// Séparation verticale
mvwaddch ( window , 2 , 18 , ACS_TTEE ) ;
mvwvline ( window , 3 , 18 , 0 , 4 ) ;
mvwaddch ( window , 7 , 18 , ACS_BTEE ) ;
// Input : Boutons poussoirs
mvwprintw ( window , 3 , 2 , " BP 1 " ) ;
mvwprintw ( window , 4 , 2 , " BP 2 " ) ;
mvwprintw ( window , 5 , 2 , " BP 3 " ) ;
mvwprintw ( window , 6 , 2 , " BP 4 " ) ;
// Output : Moteurs pompes
mvwprintw ( window , 3 , 20 , " Pompe 1 " ) ;
mvwprintw ( window , 4 , 20 , " Pompe 2 " ) ;
mvwprintw ( window , 5 , 20 , " Pompe 3 " ) ;
mvwprintw ( window , 6 , 20 , " Pompe 4 " ) ;
// Mesures
mvwprintw ( window , 8 , 2 , " Debit en sortie " ) ;
mvwprintw ( window , 9 , 2 , " Debit en entrée " ) ;
mvwprintw ( window , 10 , 2 , " Volume dans le réservoir " ) ;
2025-12-03 23:20:46 +01:00
2025-11-30 09:48:43 +01:00
// Graphe
2025-12-03 23:20:46 +01:00
2025-11-30 09:48:43 +01:00
// Ligne du haut
mvwaddch ( window , 11 , 0 , ACS_LTEE ) ;
mvwhline ( window , 11 , 1 , 0 , 60 ) ;
mvwaddch ( window , 11 , 61 , ACS_RTEE ) ;
2025-12-03 23:20:46 +01:00
2025-11-30 09:48:43 +01:00
// Ligne du bas
mvwaddch ( window , 13 , 0 , ACS_LTEE ) ;
mvwhline ( window , 13 , 1 , 0 , 60 ) ;
mvwaddch ( window , 13 , 61 , ACS_RTEE ) ;
// Graduations
for ( int i = 1 ; i < 11 ; i + + ) {
mvwaddch ( window , 11 , 6 * i , ACS_TTEE ) ;
mvwaddch ( window , 13 , 6 * i , ACS_BTEE ) ;
}
mvwaddch ( window , 13 , 2 * 6 , ACS_PLUS ) ;
mvwaddch ( window , 13 , 6 * 6 , ACS_PLUS ) ;
mvwaddch ( window , 13 , 7 * 6 , ACS_PLUS ) ;
mvwaddch ( window , 13 , 57 , ACS_TTEE ) ; //9.5 x 6
mvwvline ( window , 12 , 6 * 10 , 0 , 1 ) ;
// Légende
mvwaddch ( window , 16 , 0 , ACS_LTEE ) ;
mvwhline ( window , 16 , 1 , 0 , 60 ) ;
mvwaddch ( window , 16 , 61 , ACS_RTEE ) ;
2025-12-03 23:20:46 +01:00
2025-11-30 09:48:43 +01:00
mvwprintw ( window , 14 , 2 * 6 - 1 , " min " ) ;
mvwprintw ( window , 14 , 6 * 6 - 2 , " low " ) ;
mvwprintw ( window , 14 , 7 * 6 , " high " ) ;
mvwprintw ( window , 14 , 56 , " max " ) ;
// Informations
2025-12-03 23:20:46 +01:00
mvwprintw ( window , 17 , 2 , " Mode " ) ;
2025-11-30 09:48:43 +01:00
mvwaddch ( window , 16 , 18 , ACS_TTEE ) ;
mvwaddch ( window , 18 , 18 , ACS_BTEE ) ;
mvwvline ( window , 17 , 18 , 0 , 1 ) ;
mvwprintw ( window , 17 , 20 , " Grafcet " ) ;
mvwaddch ( window , 16 , 31 , ACS_TTEE ) ;
mvwaddch ( window , 18 , 31 , ACS_BTEE ) ;
mvwvline ( window , 17 , 31 , 0 , 1 ) ;
mvwprintw ( window , 17 , 33 , " min " ) ;
mvwprintw ( window , 17 , 46 , " max " ) ;
wrefresh ( window ) ;
}
2025-12-03 23:27:56 +01:00
void Affichage ( )
2025-11-30 09:48:43 +01:00
{
mvwprintw ( window , 1 , 50 , " %5.1f s " , t_elapsed ) ;
for ( int i = OUT_PUMP_1 ; i < = OUT_PUMP_4 ; i + + )
{
mvwprintw ( window , ( short ) ( i - OUT_PUMP_1 + 3 ) , 30 , " %c %5.1f s %dx " , _digital [ i ] . mode & 0x01 ? _digital [ i ] . ivalue ? ' M ' : ' . ' : ' X ' , _digital [ i ] . duration , _digital [ i ] . nb ) ;
}
for ( int i = IN_KEYBOARD_1 ; i < = IN_KEYBOARD_4 ; i + + )
{
mvwprintw ( window , ( short ) ( i + 3 ) , 10 , _digital [ _keyboard [ i ] . input ] . ivalue ? " 1 " : " 0 " ) ;
}
mvwprintw ( window , 8 , 28 , " %3d l/s " , ( int ) _digital [ IN_FLOW_OUT ] . dvalue ) ;
mvwprintw ( window , 9 , 28 , " %3d l/s " , ( int ) _digital [ IN_FLOW_IN ] . dvalue ) ;
mvwprintw ( window , 10 , 29 , " %5.2lf m3 (%+d l/s) " , _digital [ IN_TANK_LEVEL ] . dvalue , ( int ) _digital [ IN_FLOW_DIF ] . dvalue ) ;
AffichageGraphe ( 12 , 1 , _digital [ IN_TANK_LEVEL ] . dvalue * 6 ) ;
/*
if ( _digital [ IN_SENSOR_LOW ] . mode & 0x01 )
{
mvwprintw ( window , 15 , 1 , " (%dx) (%dx) (%dx) (%dx) " , _digital [ IN_SENSOR_MIN ] . nb , _digital [ IN_SENSOR_LOW ] . nb , _digital [ IN_SENSOR_HIGH ] . nb , _digital [ IN_SENSOR_MAX ] . nb ) ;
}
else
{
mvwprintw ( window , 15 , 1 , " (%dx) X (%dx) (%dx) (%dx) " , _digital [ IN_SENSOR_MIN ] . nb , _digital [ IN_SENSOR_LOW ] . nb , _digital [ IN_SENSOR_HIGH ] . nb , _digital [ IN_SENSOR_MAX ] . nb ) ;
}
*/
mvwprintw ( window , 14 , 15 , " %d " , _digital [ IN_SENSOR_MIN ] . ivalue ) ;
mvwprintw ( window , 14 , 38 , " %d " , _digital [ IN_SENSOR_LOW ] . ivalue ) ;
mvwprintw ( window , 14 , 47 , " %d " , _digital [ IN_SENSOR_HIGH ] . ivalue ) ;
mvwprintw ( window , 14 , 60 , " %d " , _digital [ IN_SENSOR_MAX ] . ivalue ) ;
mvwprintw ( window , 15 , 11 , " (%dx) %s " , _digital [ IN_SENSOR_MIN ] . nb , _digital [ IN_SENSOR_MIN ] . mode & 0x01 ? " " : " D " ) ;
mvwprintw ( window , 15 , 34 , " (%dx) %s " , _digital [ IN_SENSOR_LOW ] . nb , _digital [ IN_SENSOR_LOW ] . mode & 0x01 ? " " : " D " ) ;
mvwprintw ( window , 15 , 42 , " (%dx) %s " , _digital [ IN_SENSOR_HIGH ] . nb , _digital [ IN_SENSOR_HIGH ] . mode & 0x01 ? " " : " D " ) ;
mvwprintw ( window , 15 , 56 , " (%dx) " , _digital [ IN_SENSOR_MAX ] . nb ) ;
// Informations complémentaires
mvwprintw ( window , 17 , 2 , _digital [ OUT_DISPLAY_MODE ] . ivalue ? " Automatique " : " Manuel " ) ;
mvwprintw ( window , 17 , 28 , " %d " , _digital [ OUT_DISPLAY_GRAFCET ] . ivalue ) ;
mvwprintw ( window , 17 , 38 , " %4.2lf m3 " , _digital [ IN_TANK_MIN ] . dvalue ) ;
mvwprintw ( window , 17 , 51 , " %4.2lf m3 " , _digital [ IN_TANK_MAX ] . dvalue ) ;
wrefresh ( window ) ;
}
void AffichageGraphe ( int y , int x , double value )
{
int entier = ( int ) ( value ) ;
int i ;
for ( i = 0 ; i < entier ; i + + )
{
2025-12-03 23:20:46 +01:00
mvwaddwstr ( window , y , x + i , L " █ " ) ; // U+2588
2025-11-30 09:48:43 +01:00
}
int frac = ( int ) ( ( value - entier ) * 4 ) ;
if ( frac > 3 ) // 0.75 -> 0.99
{
2025-12-03 23:20:46 +01:00
mvwaddwstr ( window , y , x + i , L " ▊ " ) ; // U+258A
2025-11-30 09:48:43 +01:00
entier + = 1 ;
}
if ( frac > 2 ) // 0.5 -> 0.99
{
2025-12-03 23:20:46 +01:00
mvwaddwstr ( window , y , x + i , L " ▌ " ) ; // U+258C
2025-11-30 09:48:43 +01:00
entier + = 1 ;
}
else if ( frac > 1 ) // 0.25 -> 0.49
{
2025-12-03 23:20:46 +01:00
mvwaddwstr ( window , y , x + i , L " ▎ " ) ; //U+258E
2025-11-30 09:48:43 +01:00
entier + = 1 ;
}
for ( int i = entier ; i < 59 ; i + + )
{
mvwprintw ( window , y , x + i , " " ) ;
2025-12-03 23:20:46 +01:00
}
2025-11-30 09:48:43 +01:00
}
/**
2025-12-03 23:20:46 +01:00
* Prometheus
2025-11-30 09:48:43 +01:00
*/
2025-12-03 23:20:46 +01:00
void InitPrometheus ( )
2025-11-30 09:48:43 +01:00
{
2025-12-07 15:51:07 +01:00
static Exposer exposer { " 0.0.0.0:8099 " } ;
// Le registre central
registry = std : : make_shared < Registry > ( ) ;
exposer . RegisterCollectable ( registry ) ;
auto & gauge_volume = BuildGauge ( )
. Name ( " geii_volume " )
. Help ( " Volume en m3 " )
. Register ( * registry ) ;
2025-12-03 23:20:46 +01:00
2025-12-07 15:51:07 +01:00
tank_gauge = & gauge_volume . Add ( { } ) ;
2025-11-30 09:48:43 +01:00
2025-12-07 15:51:07 +01:00
auto & gauge_debit = BuildGauge ( )
. Name ( " geii_debit " )
. Help ( " Débit en l/s " )
. Register ( * registry ) ;
debit_entree = & gauge_debit . Add ( { { " numero " , " entree " } } ) ;
debit_sortie = & gauge_debit . Add ( { { " numero " , " sortie " } } ) ;
debit_p1 = & gauge_debit . Add ( { { " numero " , " 1 " } } ) ;
debit_p2 = & gauge_debit . Add ( { { " numero " , " 2 " } } ) ;
debit_p3 = & gauge_debit . Add ( { { " numero " , " 3 " } } ) ;
debit_p4 = & gauge_debit . Add ( { { " numero " , " 4 " } } ) ;
auto & counter_debit = BuildCounter ( )
. Name ( " geii_litre " )
. Help ( " Volume en l " )
. Register ( * registry ) ;
volume_p1 = & counter_debit . Add ( { { " numero " , " 1 " } } ) ;
volume_p2 = & counter_debit . Add ( { { " numero " , " 2 " } } ) ;
volume_p3 = & counter_debit . Add ( { { " numero " , " 3 " } } ) ;
volume_p4 = & counter_debit . Add ( { { " numero " , " 4 " } } ) ;
auto & hist_volume = BuildHistogram ( )
. Name ( " geii_tank " )
. Help ( " volume du reservoir en m3 " )
. Register ( * registry ) ;
tank_histogram = & hist_volume . Add ( { } , buckets ) ;
2025-11-30 09:48:43 +01:00
}
2025-12-07 16:04:44 +01:00
void ProcessMQTT ( mqtt : : async_client * client )
{
2025-12-07 16:48:16 +01:00
json obj = {
{ " entree " , _digital [ IN_FLOW_IN ] . dvalue } ,
{ " sortie " , _digital [ IN_FLOW_OUT ] . dvalue } ,
{ " p1 " , _digital [ IN_FLOW_1 ] . dvalue } ,
{ " p2 " , _digital [ IN_FLOW_2 ] . dvalue } ,
{ " p3 " , _digital [ IN_FLOW_3 ] . dvalue } ,
{ " p4 " , _digital [ IN_FLOW_4 ] . dvalue } ,
{ " level " , _digital [ IN_TANK_LEVEL ] . dvalue }
} ;
std : : string payload = obj . dump ( ) ;
2025-12-07 16:04:44 +01:00
auto msg = mqtt : : make_message ( " geii/telemetry " , payload ) ;
msg - > set_qos ( 1 ) ;
client - > publish ( msg ) ;
}
2025-11-30 09:48:43 +01:00
void ProcessPrometheus ( )
{
2025-12-07 15:51:07 +01:00
tank_gauge - > Set ( _digital [ IN_TANK_LEVEL ] . dvalue ) ;
tank_histogram - > Observe ( _digital [ IN_TANK_LEVEL ] . dvalue ) ;
debit_entree - > Set ( _digital [ IN_FLOW_IN ] . dvalue ) ;
debit_sortie - > Set ( _digital [ IN_FLOW_OUT ] . dvalue ) ;
debit_p1 - > Set ( _digital [ IN_FLOW_1 ] . dvalue ) ;
debit_p2 - > Set ( _digital [ IN_FLOW_2 ] . dvalue ) ;
debit_p3 - > Set ( _digital [ IN_FLOW_3 ] . dvalue ) ;
debit_p4 - > Set ( _digital [ IN_FLOW_4 ] . dvalue ) ;
2025-12-04 13:04:23 +01:00
2025-12-07 15:51:07 +01:00
volume_p1 - > Increment ( _digital [ IN_FLOW_1 ] . dvalue * dt ) ;
volume_p2 - > Increment ( _digital [ IN_FLOW_2 ] . dvalue * dt ) ;
volume_p3 - > Increment ( _digital [ IN_FLOW_3 ] . dvalue * dt ) ;
volume_p4 - > Increment ( _digital [ IN_FLOW_4 ] . dvalue * dt ) ;
2025-11-30 09:48:43 +01:00
}