From 18095fc631f0a803f73bfca44ad659ec3f3d47e3 Mon Sep 17 00:00:00 2001 From: "e.medina" Date: Fri, 9 Jan 2026 09:25:37 +0100 Subject: [PATCH] Reprise --- .devcontainer/nodered/flows.json | 192 ++++++++++++++- AutomForArduino.cpp | 411 +++++++++++++++++++++++++++++++ main.cpp | 119 ++++++++- main.hpp | 48 ++++ 4 files changed, 758 insertions(+), 12 deletions(-) create mode 100644 AutomForArduino.cpp diff --git a/.devcontainer/nodered/flows.json b/.devcontainer/nodered/flows.json index 58231b4..4280b51 100644 --- a/.devcontainer/nodered/flows.json +++ b/.devcontainer/nodered/flows.json @@ -225,7 +225,7 @@ "type": "function", "z": "41526b8c80d5a5f7", "name": "format mqtt", - "func": "let marche = Number(flow.get(\"marche\") || 0);\nlet b0 = Number(flow.get(\"b0\") || 0);\nlet b1 = Number(flow.get(\"b1\") || 0);\nlet b2 = Number(flow.get(\"b2\") || 0);\n\nmsg.payload = {\n \"marche\": marche,\n \"b0\": b0,\n \"b1\": b1,\n \"b2\": b2\n};\n\nreturn msg;", + "func": "let marche = Number(flow.get(\"marche\") || 0);\nlet b0 = Number(flow.get(\"b0\") || 0);\nlet b1 = Number(flow.get(\"b1\") || 0);\nlet b2 = Number(flow.get(\"b2\") || 0);\nlet b3 = Number(flow.get(\"b3\") || 0);\n\nmsg.payload = {\n \"marche\": marche,\n \"b0\": b0,\n \"b1\": b1,\n \"b2\": b2,\n \"b3\": b3\n};\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, @@ -284,8 +284,15 @@ }, { "t": "set", - "p": "b1", - "pt": "msg", + "p": "s0", + "pt": "flow", + "to": "0", + "tot": "num" + }, + { + "t": "set", + "p": "b0", + "pt": "flow", "to": "0", "tot": "num" } @@ -420,7 +427,8 @@ "y": 580, "wires": [ [ - "31ef90b8227cd6cb" + "31ef90b8227cd6cb", + "9ef1bb67f51b254c" ] ] }, @@ -436,9 +444,163 @@ "complete": "false", "statusVal": "", "statusType": "auto", + "x": 740, + "y": 460, + "wires": [] + }, + { + "id": "309cae1a9bcc718b", + "type": "ui-text", + "z": "41526b8c80d5a5f7", + "group": "c3dd11d0778f9e67", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "s0", + "format": "{{msg.payload}}", + "layout": "row-spread", + "style": false, + "font": "", + "fontSize": 16, + "color": "#717171", + "wrapText": false, + "className": "", + "value": "payload", + "valueType": "msg", + "x": 870, + "y": 520, + "wires": [] + }, + { + "id": "6bd900b695c3a793", + "type": "ui-text", + "z": "41526b8c80d5a5f7", + "group": "c3dd11d0778f9e67", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "s1", + "format": "{{msg.payload}}", + "layout": "row-spread", + "style": false, + "font": "", + "fontSize": 16, + "color": "#717171", + "wrapText": false, + "className": "", + "value": "payload", + "valueType": "msg", + "x": 870, + "y": 560, + "wires": [] + }, + { + "id": "cdc7f0e7741ad210", + "type": "ui-text", + "z": "41526b8c80d5a5f7", + "group": "c3dd11d0778f9e67", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "s2", + "format": "{{msg.payload}}", + "layout": "row-spread", + "style": false, + "font": "", + "fontSize": 16, + "color": "#717171", + "wrapText": false, + "className": "", + "value": "payload", + "valueType": "msg", + "x": 870, + "y": 600, + "wires": [] + }, + { + "id": "9ef1bb67f51b254c", + "type": "function", + "z": "41526b8c80d5a5f7", + "name": "function 1", + "func": "flow.set(\"s0\", !Number(msg.payload.s0));\nflow.set(\"s1\", !Number(msg.payload.s1));\nflow.set(\"s2\", !Number(msg.payload.s2));\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], "x": 380, "y": 580, - "wires": [] + "wires": [ + [ + "9dad44eccec1d282", + "fc568b731fec8026", + "6179e102d7866a82" + ] + ] + }, + { + "id": "9dad44eccec1d282", + "type": "function", + "z": "41526b8c80d5a5f7", + "name": "s0", + "func": "msg.payload = Number(flow.get(\"s0\")) ? \"🟥\" : \"🟩\";\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 690, + "y": 520, + "wires": [ + [ + "309cae1a9bcc718b" + ] + ] + }, + { + "id": "fc568b731fec8026", + "type": "function", + "z": "41526b8c80d5a5f7", + "name": "s1", + "func": "msg.payload = Number(flow.get(\"s1\")) ? \"🟥\" : \"🟩\";\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 690, + "y": 560, + "wires": [ + [ + "6bd900b695c3a793" + ] + ] + }, + { + "id": "6179e102d7866a82", + "type": "function", + "z": "41526b8c80d5a5f7", + "name": "s0", + "func": "msg.payload = Number(flow.get(\"s2\")) ? \"🟥\" : \"🟩\";\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 690, + "y": 600, + "wires": [ + [ + "cdc7f0e7741ad210" + ] + ] }, { "id": "5fe915fcd26e78ae", @@ -447,7 +609,7 @@ "page": "bb436fe040268d40", "width": "2", "height": 1, - "order": 1, + "order": 2, "showTitle": true, "className": "", "visible": "true", @@ -485,6 +647,20 @@ "userProps": "", "sessionExpiry": "" }, + { + "id": "c3dd11d0778f9e67", + "type": "ui-group", + "name": "Voyants", + "page": "bb436fe040268d40", + "width": "3", + "height": 1, + "order": 1, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, { "id": "bb436fe040268d40", "type": "ui-page", @@ -561,11 +737,11 @@ } }, { - "id": "925a608e1a2d82a3", + "id": "7514f74ba5dcf202", "type": "global-config", "env": [], "modules": { - "@flowfuse/node-red-dashboard": "1.29.0" + "@flowfuse/node-red-dashboard": "1.30.1" } } ] \ No newline at end of file diff --git a/AutomForArduino.cpp b/AutomForArduino.cpp new file mode 100644 index 0000000..3d957c2 --- /dev/null +++ b/AutomForArduino.cpp @@ -0,0 +1,411 @@ +#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 +{ + int 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 > 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].raising = (_digital[p].memory < _digital[p].ivalue); + _digital[p].falling = (_digital[p].memory > _digital[p].ivalue); + _digital[p].memory = _digital[p].ivalue; + _digital[p].ivalue = value; +} + +void analogWrite(unsigned int p, double value) +{ + if (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; +} diff --git a/main.cpp b/main.cpp index 1317bd7..36070f1 100644 --- a/main.cpp +++ b/main.cpp @@ -1,16 +1,127 @@ +#include +#include + +#include +#include +#include #include #include "main.hpp" -#include "autom.cpp" +#include "AutomForArduino.cpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#undef timeout +#include "mqtt/async_client.h" + +#include +using json = nlohmann::json; + +// Constantes de fonctionnement +#define LEVEL_MIN 2 +#define FLOW_PER_PUMP 250 + +/* Configuration MQTT */ +const std::string ADDRESS = "tcp://rabbitmq:1883"; +const std::string CLIENTID = "CppClientTP"; +const std::string TOPIC = "geii/in/#"; +const int QOS = 1; +const int CYCLE_MS = 100; + +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 TankInitialValue = 4.6; + +int s0, s1, s2, s3, s4; + +// Réception des messages MQTT +// ************************************************************ +class callback : public virtual mqtt::callback { +public: + void message_arrived(mqtt::const_message_ptr msg) override { + std::string payload = msg->to_string(); + + try { + json j = json::parse(payload); + + // Ne rien faire si l'objet JSON est vide + if (j.empty()) return; + + if (j.contains("b0")) pompe1 = j["b0"].get() != 0; + if (j.contains("b1")) pompe2 = j["b1"].get() != 0; + if (j.contains("b2")) pompe3 = j["b2"].get() != 0; + if (j.contains("b3")) pompe4 = j["b3"].get() != 0; + + std::cout << "Pompes : " << pompe1 << " " << pompe2 << " " << pompe3 << " " << pompe4 << std::endl; + } + catch (const json::parse_error& e) { + std::cerr << "Erreur JSON : " << e.what() << "\n"; + } + } +}; +// ************************************************************ int main() { - open(); + 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; + } while (1) { - send(); + ProcessMQTT(&client); + + usleep(100000); } - close(); + try { + client.unsubscribe(TOPIC)->wait(); + client.stop_consuming(); + client.disconnect()->wait(); + } catch(const mqtt::exception &exc){ + std::cerr << "Erreur déconnexion MQTT: " << exc.what() << std::endl; + } + + std::cout << "Fin du programme" << std::endl; return 0; } + +void ProcessMQTT(mqtt::async_client* client) +{ + json obj = { + {"s0", s0 }, + {"s1", s1 }, + {"s2", s2 }, + {"s3", s3 }, + }; + + std::string payload = obj.dump(); + auto msg = mqtt::make_message("geii/out", payload); + msg->set_qos(1); + client->publish(msg); +} diff --git a/main.hpp b/main.hpp index e69de29..7b3a45b 100644 --- a/main.hpp +++ b/main.hpp @@ -0,0 +1,48 @@ +#undef timeout +#include "mqtt/async_client.h" + +void ProcessMQTT(mqtt::async_client* client); + +// 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