Files
c_pompes/main.cpp
2025-12-07 16:48:16 +01:00

1074 lines
26 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <unistd.h>
#include <ncurses.h>
#include <math.h>
#include <locale.h>
#include <array>
#include "main.hpp"
#include "AutomForArduino.cpp"
#include <prometheus/counter.h>
#include <prometheus/gauge.h>
#include <prometheus/histogram.h>
#include <prometheus/registry.h>
#include <prometheus/exposer.h>
#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>
using namespace std::chrono_literals;
using json = nlohmann::json;
// Constantes de fonctionnement
#define LEVEL_MIN 2
#define FLOW_PER_PUMP 150
/* 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;
WINDOW *window;
int etape = 0; // Étape du grafcet : début Automatique
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;
TemporisationRetardMontee tempo1(1500);
TemporisationRetardMontee tempo2(3000);
TemporisationRetardMontee tempo3(4000);
TemporisationRetardMontee tempo4(6000);
// Prometheus
// ************************************************************
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;
Histogram::BucketBoundaries buckets = {
2, 5, 6, 7, 8, 9, 9.5
};
Histogram *tank_histogram = nullptr;
// ************************************************************
/* 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);
}
int main()
{
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;
}
/* 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
if (ch == 27 || _digital[OUT_END].ivalue) {
break;
}
// **** Beep
if (_digital[OUT_BEEP].ivalue)
{
beep();
_digital[OUT_BEEP].ivalue = false;
}
Process();
LireClavier(ch);
LireEntree();
EvolutionGrafcet();
Actions();
ProcessPrometheus();
ProcessMQTT(&client);
ProcessException();
usleep(100000);
}
endwin(); // Termine ncurses et rétablit le terminal
/* 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";
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
*/
double ProcessMoteur(int i)
{
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()
{
if (t_elapsed > 60) {
_digital[OUT_PUMP_1].mode = 0;
digitalWrite(OUT_PUMP_1, 0);
} else if (t_elapsed > 15) {
//_digital[IN_SENSOR_LOW].mode = 0;
}
}
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)
{
_digital[IN_FLOW_OUT].dvalue = SimulConsoSinusoidale(t);
//_digital[IN_FLOW_OUT].dvalue = SimulConsoBrown(_digital[IN_FLOW_OUT].dvalue);
}
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)) {
digitalWrite(p, 0);
}
}
}
// **** 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;
}
Affichage();
t_backup = t;
}
double SimulConsoSinusoidale(long t)
{
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;
}
// dt : Intervalle de temps
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]
float rand_std_normal = ((double)rand() / RAND_MAX) * 2.0 - 1.0;
// 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
*/
/**
* Initialisation, affichage des parties statiques
*/
void AffichageWindow()
{
window = subwin(stdscr, 19, 62, 0, 0);
box(window, 0, 0);
// Titre
mvwprintw(window, 1, 2, "Château d'eau (11/2024)");
// I/O
// Ligne du haut
mvwaddch(window, 2, 0, ACS_LTEE);
mvwhline(window, 2, 1, 0, 60);
mvwaddch(window, 2, 61, ACS_RTEE);
// 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");
// Graphe
// Ligne du haut
mvwaddch(window, 11, 0, ACS_LTEE);
mvwhline(window, 11, 1, 0, 60);
mvwaddch(window, 11, 61, ACS_RTEE);
// 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);
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
mvwprintw(window, 17, 2, "Mode");
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);
}
void Affichage()
{
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++)
{
mvwaddwstr(window, y, x + i, L""); // U+2588
}
int frac = (int)((value - entier) * 4);
if (frac > 3) // 0.75 -> 0.99
{
mvwaddwstr(window, y, x + i, L""); // U+258A
entier += 1;
}
if (frac > 2) // 0.5 -> 0.99
{
mvwaddwstr(window, y, x + i, L""); // U+258C
entier += 1;
}
else if (frac > 1) // 0.25 -> 0.49
{
mvwaddwstr(window, y, x + i, L""); //U+258E
entier += 1;
}
for (int i = entier; i < 59; i++)
{
mvwprintw(window, y, x + i, " ");
}
}
/**
* Prometheus
*/
void InitPrometheus()
{
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);
tank_gauge = &gauge_volume.Add({});
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);
}
void ProcessMQTT(mqtt::async_client* client)
{
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();
auto msg = mqtt::make_message("geii/telemetry", payload);
msg->set_qos(1);
client->publish(msg);
}
void ProcessPrometheus()
{
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);
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);
}