From cd7d97280899f37ac18e383540d31ed5f1565903 Mon Sep 17 00:00:00 2001 From: medina5 Date: Sun, 30 Nov 2025 09:48:43 +0100 Subject: [PATCH] Initial commit --- .devcontainer/Dockerfile | 29 ++ .devcontainer/devcontainer.json | 21 + .gitignore | 1 + .vscode/settings.json | 5 + .vscode/tasks.json | 33 ++ AutomForArduino.cpp | 424 +++++++++++++++++ main.cpp | 819 ++++++++++++++++++++++++++++++++ main.h | 82 ++++ 8 files changed, 1414 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 AutomForArduino.cpp create mode 100644 main.cpp create mode 100644 main.h diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..aa212c4 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,29 @@ +FROM debian:stable-slim + +RUN RUN set -eux; \ + apt-get update; \ + apt-get install -y \ + git \ + build-essential \ + cmake \ + gdb; + +RUN set -eux; \ + apt-get update; \ + apt-get install -y \ + libncurses-dev \ + libmicrohttpd-dev;\ + apt-get clean + +RUN set -eux; \ + git clone https://github.com/jelmd/libprom.git; \ + cd libprom; \ + make build; \ + cd prom/build; \ + make install; \ + cd ../../promhttp/build; \ + make install; + +WORKDIR /root + +CMD ["sleep infinity"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..a3c0629 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,21 @@ +{ + "name": "Developpement C", + "build": { + "dockerfile": "Dockerfile" + }, + "customizations": { + "vscode": { + "settings": { + "remote.downloadExtensionsLocally": true, + "telemetry.enableTelemetry": false, + "extensions.ignoreRecommendations": true, + "workbench.remoteIndicator.showExtensionRecommendations": false + }, + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.makefile-tools", + "danielpinto8zz6.c-cpp-compile-run" + ] + } + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..16be8f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/output/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ab56e49 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.insertFinalNewline": true, + "files.trimTrailingWhitespace": true, + "files.trimFinalNewlines": true +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..a8ee3fe --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,33 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: gcc build active file", + "command": "/usr/bin/gcc", + "args": [ + "-fdiagnostics-color=always", + "-g", + "${workspaceFolder}${pathSeparator}main.cpp", + "-lm", + "-lncursesw", + "-lprom", + "-lpromhttp", + "-lmicrohttpd", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Task generated by Debugger." + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/AutomForArduino.cpp b/AutomForArduino.cpp new file mode 100644 index 0000000..77fb935 --- /dev/null +++ b/AutomForArduino.cpp @@ -0,0 +1,424 @@ +#include +#include + +/* From Arduino.h */ + +#define HIGH 0x1 +#define LOW 0x00 + +#define IO_INPUT 0x02 +#define IO_OUTPUT 0x04 + +#define DIGITAL 0x08 +#define ANALOG 0x10 + +#define PI 3.1415926535897932384626433832795 +#define HALF_PI 1.5707963267948966192313216916398 + +#define true 1 +#define false 0 + +// Temps + +// Timestamp unix en millisecondes +// t_start : timestamp de départ +// t_backup : timestamp de la précédente itération +// t_elapsed : temps écoulé en secondes depuis le départ +// dt (delta t) : temps écoulé en secondes depuis la dernière itération +unsigned long t_start, t_backup; +double t_elapsed, dt; + +unsigned long millis() +{ + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + return ((unsigned long)now.tv_sec) * 1000 + ((unsigned long)now.tv_nsec) / 1000000; +} + +#define OP_DIGITAL_READ 0 +#define OP_DIGITAL_WRITE 1 +#define OP_ANALOG_READ 2 +#define OP_ANALOG_WRITE 3 +#define OP_PIN_MODE 4 + +typedef struct PinIO +{ + unsigned char mode; + int ivalue; + double dvalue; + int error; // % = 1 /error ex 1 / 20 = 5 % + double efficacite; // 0 - 100% + unsigned long start; + unsigned long time; + double duration; + unsigned int nb; // compteur d'activation + int memory; + unsigned char raising; + unsigned char falling; +} PinIO; + +PinIO _digital[256]; + +void pinMode(unsigned char p, unsigned char mode) +{ + _digital[p].mode = 0x01 | mode; + + _digital[p].ivalue = 0; + _digital[p].dvalue = 0.0; + + _digital[p].nb = 0; + _digital[p].time = 0.0; + _digital[p].duration = 0.0; + _digital[p].start = 0; + _digital[p].raising = 0; + _digital[p].memory = 0; +} + + + + +/* KEYBOARD */ + +typedef struct +{ + char input; + int vKey; +} KeyboardIO; + +#define NB_KEYBOARD 10 +KeyboardIO _keyboard[NB_KEYBOARD]; + +void LireClavier(int ch) +{ + for (int i = 0; i < NB_KEYBOARD; i++) + { + _digital[_keyboard[i].input].ivalue = (ch == _keyboard[i].vKey); + _digital[_keyboard[i].input].raising = _digital[_keyboard[i].input].ivalue > _digital[_keyboard[i].input].memory; + _digital[_keyboard[i].input].falling = _digital[_keyboard[i].input].ivalue < _digital[_keyboard[i].input].memory; + _digital[_keyboard[i].input].memory = _digital[_keyboard[i].input].ivalue; + } +} + +/* ******************************************************** + * TEMPORISATION RETARD A LA MONTEE * + * La sortie passe à 1 au bout de la tempo * + ******************************************************** */ + +class TemporisationRetardMontee +{ + // methodes + public : + // Contructeur qui prend la duree souhaitee de la temporisation + TemporisationRetardMontee(unsigned long duree) + { + this->duree = duree; + sortie = false; + captureTemps = false; + } + // Activation de la temporisation. Doit etre fait tout le temps de la duree de la tempo + void activation() + { + // Capture du temps de reference + if(!captureTemps) + { + debut = millis(); + captureTemps = true; + } + // Calcul du temps ecoule depuis le temps de reference + tempsEcoule = millis() - debut; + // Mise a 1 de la fin de tempo + if (tempsEcoule >= duree) + { + sortie = true; + captureTemps = false; + } + else + { + sortie = false; + } + } + // Precharge de la temporisation + void setDuree(unsigned long majduree) + { + duree = majduree; + sortie = false; + captureTemps = false; + } + // Interrogation du bit de fin de tempo + bool getSortie() + { + return(sortie); + } + // Recuperation du temps ecoule depuis le debut si necessaire + unsigned long getTempsEcoule() + { + return(tempsEcoule); + } + + // Attributs + private: + unsigned long duree; + unsigned long debut; + unsigned long tempsEcoule; + bool captureTemps; + bool sortie; +}; + +/******************************************************** +* TEMPORISATION RETARD A LA DESCENTE * +* La sortie passe à 0 au bout de la tempo * +*********************************************************/ +class TemporisationRetardDescente +{ + public : + // Contructeur qui prend la duree souhaitee de la temporisation + TemporisationRetardDescente(unsigned long duree) + { + this->duree = duree; + sortie = false; + captureTemps = false; + } + // Activation de la temporisation. Doit etre fait tout le temps de la duree de la tempo + void activation() + { + // Capture du temps de reference + if(!captureTemps) + { + debut = millis(); + captureTemps = true; + sortie = true; + } + // Calcul du temps ecoule depuis le temps de reference + tempsEcoule = millis() - debut; + // Mise a 0 de la fin de tempo + if (tempsEcoule >= duree) + { + sortie = false; + captureTemps = false; + } + } + // Precharge de la temporisation + void setDuree(unsigned long majduree) + { + duree = majduree; + sortie = false; + captureTemps = false; + } + // Interrogration du bit de fin de tempo + bool getSortie() + { + return(sortie); + } + // Recuperation du temps ecoule depuis le debut si necessaire + unsigned long getTempsEcoule() + { + return(tempsEcoule); + } + + private: + unsigned long duree; + unsigned long debut; + unsigned long tempsEcoule; + bool captureTemps; + bool sortie; +}; + +/******************************************************** +**** CLIGNOTEUR ************************************* +*********************************************************/ +class Clignoteur +{ + // methodes + public : + // Construteur qui prend en parametre le temps haut ou bas souhaitee + Clignoteur(int baseDeTemps) + { + this->baseDeTemps = baseDeTemps; + } + // Fonction qui renvoie true si le clignoteur est ├á l'├®tat haut et false s'il est ├á l'├®tat bas + bool statut() + { + return ((millis() / baseDeTemps) % 2 == 1); + } + + // Attributs + private: + int baseDeTemps; +}; + +/******************************************************** +**** COMPTEUR ************************************* +**** ATTENTION : Il faut gerer le front montant dans le programme +*********************************************************/ +class Compteur +{ + // methodes + public : + // Constructeur qui prend en parametre la valeur de preselection + Compteur(int valeurPreselection) + { + this->valeurPreselection = valeurPreselection; + valeur = 0; + } + // Incrementation du compteur + void incrementer() + { + valeur++; + } + // Decrementation du compteur + void decrementer() + { + valeur--; + } + // remise a zero du compteur + void remettreAZero() + { + valeur = 0; + } + // recuperation de la valeur courante + int getValeurCourante() + { + return(valeur); + } + // est-ce que la preselection est atteinte (sortie Q compteur Siemens ou Schnieder) + bool getSortie() + { + return(valeur == valeurPreselection); + } + + // Attributs + private: + int valeur; + int valeurPreselection; +}; + +/******************************************************** +**** MISE A L'ECHELLE DE VALEUR ************************ +*********************************************************/ +class MiseAEchelle +{ + public : + // Constructeur qui ne prend en parametre la plage d'entree et la plage de sortie + MiseAEchelle(float minEntree,float maxEntree,float minSortie,float maxSortie) + { + this->minEntree = minEntree; + this->maxEntree = maxEntree; + this->minSortie = minSortie; + this->maxSortie = maxSortie; + } + // fonction de conversion qui prend la valeur a convertir et renvoie la valeur convertie + float convertir(float valeurAConvertir) + { + if(valeurAConvertir >= minEntree && valeurAConvertir <= maxEntree) + { + float norm = (1 / (maxEntree - minEntree)) * (valeurAConvertir - minEntree); + float scale = (maxSortie - minSortie) * norm + minSortie; + return(scale); + } + return(-1000); + } + + // Attributs + private: + float minEntree; + float minSortie; + float maxEntree; + float maxSortie; +}; + +/* READ */ + +int digitalRead(int p) +{ + return ((_digital[p].mode & IO_INPUT) && (_digital[p].mode & DIGITAL) && (_digital[p].mode & 0x01)) ? _digital[p].ivalue : 0; +} + +double analogRead(int p) +{ + return ((_digital[p].mode & IO_INPUT) && (_digital[p].mode & ANALOG) && (_digital[p].mode & 0x01)) ? _digital[p].dvalue : 0.0; +} + +/* WRITE */ + +void digitalWrite(unsigned int p, int value) +{ + if (p < 0 || p > 255) + { + printf("Pin %d is out of Range.", p); + return; + } + + // En panne ! + if (!(_digital[p].mode & 0x01)) + { + return; + } + + if (!(_digital[p].mode & IO_OUTPUT && _digital[p].mode & DIGITAL)) + { + printf("Pin %d is not a digital input.", p); + return; + } + + unsigned long m = millis(); + + if (value != _digital[p].ivalue) + { + _digital[p].time = _digital[p].time > 5000 ? 0 : 5000 - _digital[p].time; + } + else + { + _digital[p].time += m - _digital[p].start; + } + + if (value && !_digital[p].ivalue) + { + _digital[p].start = m; + _digital[p].nb += 1; + } + else if (value) + { + _digital[p].duration += dt; + } + + _digital[p].ivalue = value; +} + +void analogWrite(unsigned int p, double value) +{ + if (p < 0 || p > 31) + { + printf("Pin %d is out of Range.", p); + return; + } + + // En panne + if (!(_digital[p].mode & 0x01)) + { + return; + } + + if (!(_digital[p].mode & IO_OUTPUT) || !(_digital[p].mode & ANALOG)) + { + printf("Pin %d is not a analog input.", p); + return; + } + + _digital[p].dvalue = value; +} + +/* ******************************************************** + * Console * + * * + ******************************************************** */ +void ConsoleInit() +{ + setlocale(LC_ALL, ""); // Activer le support des caractères Unicode + setlocale(LC_NUMERIC, "C"); + initscr(); // Initialise ncurses + raw(); // Mode brut, sans besoin de validation par Entrée + keypad(stdscr, TRUE); // Active les touches spéciales comme ESC + nodelay(stdscr, TRUE); // Mode non-bloquant pour getch() + noecho(); // Ne pas afficher les touches appuyées + curs_set(0); // Masquer le curseur +} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..79c175c --- /dev/null +++ b/main.cpp @@ -0,0 +1,819 @@ +#include +#include +#include +#include +#include +#include "main.h" +#include "AutomForArduino.cpp" + +extern "C" { + #include "libprom/prom.h" + #include "libprom/promhttp.h" +} + +// Constantes de fonctionnement +#define LEVEL_MIN 2 +#define FLOW_PER_PUMP 150 + +WINDOW *window; + +int etape = 10; // É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(500); +TemporisationRetardMontee tempo2(1000); +TemporisationRetardMontee tempo3(1500); +TemporisationRetardMontee tempo4(2000); + +// Prometheus +// ************************************************************ +struct MHD_Daemon *server; + +prom_counter_t *pm_pompe; +prom_gauge_t *pm_debit; +// ************************************************************ + +int main(int argc, char *argv[]) +{ + /* 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(); + + ProcessException(); + + usleep(100000); + } + + endwin(); // Termine ncurses et rétablit le terminal + puts("Fin du programme"); + pcr_destroy(PROM_COLLECTOR_REGISTRY); + promhttp_stop_daemon(server); + 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 > 30) { + _digital[OUT_PUMP_1].mode = 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)) { + _digital[p].ivalue = 0; + _digital[p].dvalue = 0.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); + + 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(unsigned long t) +{ + 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() +{ + int result = pcr_init(0,"geii_"); + + std::array labels = { "numero" }; + pm_pompe = prom_counter_new("pompe_on", "Mise en marche de la pompe" + , labels.size(), labels.data()); + pcr_register_metric(pm_pompe); + + pm_debit = prom_gauge_new("debit", "Débit en l/s" + , labels.size(), labels.data()); + pcr_register_metric(pm_debit); + + promhttp_set_active_collector_registry(NULL); + + // Serveur web + server = promhttp_start_daemon(MHD_USE_SELECT_INTERNALLY + , 8099, NULL, NULL); + if (server == NULL) + { + printf("Impossible de démarrer le serveur HTTP\n"); + exit(1); + } +} + +void ProcessPrometheus() +{ + // raising = 1 => front montant sur la sortie + if (_digital[OUT_PUMP_1].raising) { + std::array labels1 = { "1" }; + prom_counter_inc(pm_pompe, labels1.data()); + } + + std::array labels1 = { "sortie" }; + std::array labels2 = { "entree" }; + prom_gauge_set(pm_debit, _digital[IN_FLOW_OUT].dvalue, labels1.data()); + prom_gauge_set(pm_debit, _digital[IN_FLOW_IN].dvalue, labels2.data()); +} diff --git a/main.h b/main.h new file mode 100644 index 0000000..56df0db --- /dev/null +++ b/main.h @@ -0,0 +1,82 @@ +void ConsoleInit(); + +void LireClavier(int ch); +void LireEntree(); +void EvolutionGrafcet(); +void Actions(); +void RemiseZeroInput(); + +void ProcessInitKeyboard(); +void ProcessInitIO(); +void ProcessInitValues(); +double ProcessMoteur(int i); +void ProcessException(); +void Process(); + +void InitPrometheus(); +void ProcessPrometheus(); + +double SimulConsoSinusoidale(long t); +double SimulConsoBrown(double valeur_precedente); + +void AffichageWindow(); +void Affichage(unsigned long t); +void AffichageGraphe(int y, int x, double value); + +// KEYBOARD INPUT + +#define IN_KEYBOARD_1 0 +#define IN_KEYBOARD_2 1 +#define IN_KEYBOARD_3 2 +#define IN_KEYBOARD_4 3 +#define IN_KEYBOARD_A 4 + +#define IN_KEYBOARD_X 5 +#define IN_KEYBOARD_7 6 +#define IN_KEYBOARD_8 7 +#define IN_KEYBOARD_9 8 +#define IN_KEYBOARD_0 9 + +// DIGITAL INPUT + +#define IN_SENSOR_MIN 10 +#define IN_SENSOR_LOW 11 +#define IN_SENSOR_HIGH 12 +#define IN_SENSOR_MAX 13 + +// ANALOG INPUT + +#define IN_TANK_LEVEL 14 +#define IN_FLOW_OUT 15 +#define IN_FLOW_IN 16 +#define IN_FLOW_DIF 17 +#define IN_TANK_MIN 18 +#define IN_TANK_MAX 19 +#define IN_FLOW_CAP 20 + +#define IN_FLOW_1 21 +#define IN_FLOW_2 22 +#define IN_FLOW_3 23 +#define IN_FLOW_4 24 + +// DIGITAL OUTPUT + +#define OUT_PUMP_1 25 +#define OUT_PUMP_2 26 +#define OUT_PUMP_3 27 +#define OUT_PUMP_4 28 + +#define OUT_DISPLAY_MODE 29 +#define OUT_DISPLAY_GRAFCET 30 + +// ANALOG OUTPUT + +#define OUT_LEVEL_MIN 31 +#define OUT_LEVEL_LOW 32 +#define OUT_LEVEL_HIGH 33 +#define OUT_LEVEL_MAX 34 +#define OUT_FLOW_PER_PUMP 35 +#define OUT_FLOW_OUT_AMPLITUDE 36 + +#define OUT_BEEP 254 +#define OUT_END 255