Table des matières:

Base de données EAL-Industri4.0-RFID Dataopsamling Til : 10 étapes (avec images)
Base de données EAL-Industri4.0-RFID Dataopsamling Til : 10 étapes (avec images)

Vidéo: Base de données EAL-Industri4.0-RFID Dataopsamling Til : 10 étapes (avec images)

Vidéo: Base de données EAL-Industri4.0-RFID Dataopsamling Til : 10 étapes (avec images)
Vidéo: TPM 2.0 Unveiled: Empowering IT Pros with Trusted Platform Module Insights 2024, Novembre
Anonim
EAL-Industri4.0-RFID Dataopsamling Til Database
EAL-Industri4.0-RFID Dataopsamling Til Database

Dette projekt omhandler opsamling af vægtdata, enregistrement af identiteter vha. RFID, décalage des données dans la base de données MySQL vha. node-RED, samt fremvisning og behandling af de opsamlede data i et C# program i form af en Windows Form Application. Vi forestiller os følgende:

Vi har en produktionslinje som producteur leverpostej i 200g foliebakker. Alle færdigbagte leverpostejer udstyres efter afkøling med et RFID tag i plasticlåget/labelen, som indeholder et unikt ID (UID = Unique Identifier, er en 32 bits kode, 8 hexadecimale karakterer) pour l'identification entydig af hver enkelt bakke leverposte. Da færdigvægten af hver enkelt bakke leverpostej kan svinge (afhængig af råvarer, fordampning i ovn mm), og da kunderne hver har et specifikt krav færdigvægten, bruges UID tagget til at knylogert hver ifvorik de specifikt krav færdigvægten, bruges UID tagget til at knylogert hver enkelt leverpostejer til én specifik kunde. Kunderne er supermarkedskæder:

1. Irma. Vægten på Irmas luksus leverpostej skal holde sig inden for +/- 5%, altså minimum 190g og maksimum 210g.

2. Bruges. Vægten på Brugsens leverpostej skal holde sig inden for +/- 10%, altså minimum 180g og maksimum 220g.

3. Aldi. Vægten på Aldis discount leverpostej skal holde sig inden for +/- 15%, altså minimum 170g og maksimum 230g.

Der er således følgende trieur:

Range0: hors de portée

Gamme1: minimum 190g/maximum210g

Gamme2: minimum 180g/maximum220g

Gamme3: minimum 170g/maximum230g

Étape 1: Opsamling Af Data for Vægt Samt Enregistrement Af UID

Opsamling Af Data for Vægt Samt Enregistrement Af UID
Opsamling Af Data for Vægt Samt Enregistrement Af UID

Til opsamling af data for vægt, samt enregistrement af RFID tags and anvendt en Arduino MEGA2560 med en RFID-RC522 reader/writer. Da vi ikke har nogen vægt, simulerer vi data for vægten med et potmeter tilsluttet en analog indgang på Arduinoen.

Følgende opstilling er anvendt:

1 potmètre stk 25k lineært. Yder-benene er tilsluttet hhv. GND og +5V, midterbenet er tilsluttet AN0

RFID-RC522 er tilsluttet Arduino boardets SPI port pålgende måde:

SDA -> broche 53

SCK -> broche52

MOSI ->pin51

MISO->pin50

IRQ ->NC

GND ->GND

RST -> broche 5

3.3V -> 3.3V

De données opsamlede, pour hhv. UID og vægten, envoie på den serielle port som en komma-separeret tekststreng videre til node-Red som står pour den efterfølgende præsentation på et dashboard og lagring i en base de données.

Étape 2: programme Arduino

I Arduino programmet inkluderes de to biblioteker SPI.h og MFRC522.h for at kunne bruge RFID læseren. J'ai démarré un programme initialisant une variable d'anvendte. Der laves en instants af MFRC522. I Setup blokken initialiseres den serielle forbindelse, SPI porten og MFRC522. Supprimer les scannes après les tags RFID. Pour ikke at sende det samme UID afsted flere gange efter hinanden, er der lavet en stump kode som tjekker for dette. Når der er scannet et UID tag, charge un arary nyUID med det netop læste UID. Hvis array nyUID er forskellig fra oldUID er der tale om et nyt UID som kan sendes på den serielle port. Hvis nyUID og oldUID ers, er der tale om même balise UID og UID'et skal ignoreres. Hvis der er tale om et nyt UID, sendes UID'et på den serielle port sammen med en læst værdi fra den serielle port. Den analoge værdi skaleres til området 150-250. Les données sont envoyées en komma-separeret tekststreng. Som det sidste sættes oldUID = nyUID, således at koden klart til at læse and nyt RFID tag.. Den sidste funktion i programmet er den funktion som sammenligner 2 arrays. Funktionen returnerer true hvis array'ne er ens, og false hvis array'ne er forskellige.

#comprendre

#include // Ce programme scanne les cartes RFID à l'aide de la carte de lecture/écriture RDIF-RC522. // L'UID est lu, une broche analogique est lue. La valeur analogique 0-1023 est mise à l'échelle à 150-250. // L'UID et la valeur analogique sont envoyés sous forme de texte séparé par des virgules sur le port série en utilisant 9600, N, 8, 1. // On a pris soin de n'envoyer chaque UID qu'une seule fois de suite, // un nouvel UID doit être présent avant que le même UID puisse être envoyé à nouveau. // Cette fonction est implémentée dans le code en comparant des tableaux: oldUIDnyUID dans la fonction array_cmp(oldUID, nyUID)

constexpr uint8_t RST_PIN = 5;

constexpr uint8_t SS_PIN = 53; int capteurPin = A0; Valeur entière = 0; String StringValue = "0000"; octet oldUID[4] = {}; octet nyUID[4] = {};

MFRC522 mfrc522(SS_PIN, RST_PIN); // Créer une instance MFRC522.

void setup()

{ Serial.begin(9600); // Initie une communication série SPI.begin(); // Lancer le bus SPI mfrc522. PCD_Init(); // Lancer le CRFM522 }

boucle vide()

{ // Recherche de nouvelles cartes if (! mfrc522. PICC_IsNewCardPresent()) { return; } // Sélectionnez une des cartes if (! mfrc522. PICC_ReadCardSerial()) { return; } //charge nyUID avec la balise UID pour (octet i = 0; i < mfrc522.uid.size; i++) { nyUID = mfrc522.uid.uidByte; } // if oldUIDnyUID if (!array_cmp(oldUID, nyUID)) { // envoyer la balise UID sur le port série pour (byte i = 0; i 1000) { Value = 1000; } Valeur = (Valeur / 10) + 150; // envoie la valeur analogique mise à l'échelle Serial.print(Value); // envoie une nouvelle ligne Serial.println(); //set oldUID = nyUID pour (octet z = 0; z < 4; z++) oldUID[z] = nyUID[z]; } // attendre 1 s de délai (1000); }

// comparer 2 tableaux…

boolean array_cmp(byte a, byte b) { bool test = true; // tester chaque élément pour qu'il soit le même. si un seul ne l'est pas, retourne false pour (byte n = 0; n < 4; n++) { if (a[n] != b[n]) test = false; // si l'octet n'est pas égal, test = false } if (test == true) renvoie true; sinon retourne faux; }

Étape 3: Node-RED, base de données Lagring Af Data I

Node-RED, Base de données Lagring Af Data I
Node-RED, Base de données Lagring Af Data I
Node-RED, Base de données Lagring Af Data I
Node-RED, Base de données Lagring Af Data I

Følgende flow er lavet i node-RED:

COM4 er den serielle forbindelse hvor data modtages fra Arduino boardet. Funktionerne "Split and Get value" og "Split and Get UID" splitter teksstrengen ved kommaet og returnere hhv vægten og UID. Vægten bruges til fremvisning på dashboardet i et linechart og en scale. UID fremvises i et tekstfelt. Funktionen test_sound advarer verbalt med sætningen "Out of range", hvis vægten er moins de 170g eller plus de 230g, dvs i range 0.

Diviser et obtenir la valeur:

var sortie = msg.payload.split(', ');

temp = {charge utile:(sortie[1])}; température de retour;

Diviser et obtenir l'UID:

var sortie = msg.payload.split(", ");

temp = {payload:output[0]}; température de retour;

test_son:

var nombre = parseInt(msg.payload);

if (nombre >230 || nombre<170) { newMsg = {payload:"Out of range"}; renvoyer newMsg; } else { newMsg = {payload:""}; renvoyer newMsg; }

Funktionen Split string ", " indsætter et timestamp, UID og vægten i en database patedb.patelog.

var sortie = msg.payload.split(", "); // diviser msg.payload par virgule dans un tableau

UIDTag = sortie[0]; //première partie en première position [0] ValueTag = output[1]; //deuxième partie en deuxième position [1]

var m = {

topic: "INSERT INTO patedb.patelog (horodatage, UID, poids) VALUES('"+new Date().toISOString()+"', '"+ UIDTag +"', '"+ValueTag+"');" }; retourner m;

patelog er en MySQL database forbindelse som er sat op med følgende paramètre:

Hôte: localhost

Port: 3306

Utilisateur: root

Base de données: patedb

Étape 4: conception de la base de données

Conception de base de données
Conception de base de données

Databasen patedb indeholder 4 tabeller

patelog er dataopsamlingstabellen, tilskrives data af node-RED og C# programt

ordertable er en tabel som indeholder data om de genemførte ordrer, tilskrives data af C# programmet

customertable er et kunderregister

rangetable er en tabel som indeholder grænseværdierne pour de i C# programmet benyttede ranges.

Étape 5: Patelog

Patelog
Patelog

Tabellen patelog indeholder folgende 6 kolonner:

pateID (int) er la clé primaire og inkrementeres automatisk.

Horodatage, UID et vægt er af typen varchar (med forskellig max længde)

rangeNr er af typen tinyint (beregnes og tilføjes af C# programmet)

orderID er af typen int (orderID tilføjes af C# programmet)

Node-RED tilføjer ikke værdier til kolonnerne rangeNr og orderID. rangeNr og orderID tillader NULL værdier, det bruges i C# programmet til at detektere de rækker som skal tilskrives værdier for rangeNr og orderID

Étape 6: Tableau de commande

Tableau de commande
Tableau de commande

tableau de commande indeholder 5 kolonner:

orderID (int) er det aktuelle commandenummer

orderQuant (mediumint) er ordens pålydende antal

quantProducted (mediumint) er antal der rent faktisk er Produceret på ordren. (Tælles af C# programmet)

comment (tinytext) er en eventuel kommentar til ordren.

customerID (int) er det aktuelle kundenummer på ordren.

Étape 7: tableau client

Tableau client
Tableau client

table client indeholder 6 kolonner:

customerID (int) er la clé primaire og auto inc.

nom, adresse, téléphone, e-mail (varchar) med forskellig max længde

rangeNr (int)

Étape 8: Tableau des plages

Tableau des plages
Tableau des plages

rangetable indeholder 3 kolonner:

rangeNr (int) er clé primaire og auto inc.

rangeMin (int)

rangeMax (entier)

Étape 9: programme C#

Programme C#
Programme C#

Når der producteurs en ordre leverpostej, er proceduren følgende:

Kundenummer, ordrenummer, ordreantal og en eventuel kommentar indtastes i C# programt (i praksis overføres det digitalt fra virksomhedens ordresystem. Produktionen startes nu ved tryk på 'start'- knappen. Når en leverpostej er færget aproducterret og lån et transportbånd) Samhørende værdier af UID og den aktuelle vægt envoie serielt til node-RED, som viser de opsamlede data på dashboard 'et. tidspunkt ikke tilskrives værdier til rangeNr og orderID vil de have værdien NULL.

Med et timerinterval undersøger C# programmet patedb.patelogtabellen for nye tilkomne rækker med NULL værdier i rangeNr kolonnen. Nr der er detekteret en række med NULL værdi, beregnes rangeNr og det tilføjes sammen med det aktuelle orderID. Når en ordre er Produceret, afsluttes ordren ved tryk på "stop" - knappen. Når ordren afsluttes, tilføjes en række til patedb.ordertable med de aktuelle ordredata. Pas d'ordre er afsluttet, kan kan de opsamlede data i patelog tabellen fremvises ved at trykke på de forskellige knapper i gruppen Update DataGridview. tableau de commande kan også vises, og der kan søges ordredata på individuelr UID'er eller kundedata på individuel ordrer.

utilisation de System;utilisation de System. Collections. Generic; en utilisant System. ComponentModel; en utilisant System. Data; en utilisant System. Drawing; en utilisant System. Linq; en utilisant System. Text; en utilisant System. Threading. Tasks; en utilisant System. Windows. Forms; en utilisant MySql. Data. MySqlClient;

espace de noms show_data_from_database

{ classe partielle publique Form1: Form { MySqlConnection connection = new MySqlConnection("datasource=localhost; username=root; password=''"); int RowNumber = 0; // Variable pour stocker la valeur pateID int RangeNumber = 0; //Variable pour stocker rangenumber int weight =0; // Variable pour stocker le poids int OrderNr = 0; // Variable pour stocker OrderNR int QuantProduced = 0; //Variable pour stocker la quantité produite int NumberOfRows = 0; //nombre de lignes avec des valeurs NULL. bool ProdRunning = false; //Variable indiquant si les boutons start & stop ont été activés intlimits = new int[6];// initialize array int CustomerID; // Variable pour stocker l'ID client public Form1() { InitializeComponent(); load_table(); // appelle load_table }

void load_table()

{ MySqlCommand command = new MySqlCommand("SELECT * FROM patedb.patelog ORDER BY timestamp DESC;", connexion); try { Adaptateur MySqlDataAdapter = new MySqlDataAdapter(); adapter. SelectCommand = commande; DataTable dbdataset = new DataTable(); adapter. Fill(dbdataset); BindingSource bsource = new BindingSource(); bsource. DataSource = dbdataset; dataGridView1. DataSource = bsource; SetRowOrder(); adapter. Update(dbdataset); } catch (Exception ex) { MessageBox. Show(ex. Message); } }

vide privé SetRowOrder()

{ dataGridView1. Columns["pateID"]. DisplayIndex = 0; // Son kan rækkefølgen af kolonner ændres dataGridView1. Columns["timestamp"]. DisplayIndex = 1; // Son kan rækkefølgen af kolonner ændres dataGridView1. Columns["UID"]. DisplayIndex = 2; // Son kan rækkefølgen af kolonner ændres dataGridView1. Columns["weight"]. DisplayIndex = 3; // Son kan rækkefølgen af kolonner ændres dataGridView1. Columns["rangeNr"]. DisplayIndex = 4; // Son kan rækkefølgen af kolonner ændres dataGridView1. Columns["orderID"]. DisplayIndex = 5; // Son kan rækkefølgen af kolonner ændres }

private void GetData_Click(object sender, EventArgs e) // Lit la table de la base de données et les commandes par horodatage

{ load_table(); }

void privé btnRefreshUID_Click (expéditeur d'objet, EventArgs e) //

{ string timeStr = "SELECT * FROM patedb.patelog ORDER BY UID;"; Commande MySqlCommand = new MySqlCommand(timeStr, connection); try { Adaptateur MySqlDataAdapter = new MySqlDataAdapter(); adapter. SelectCommand = commande; DataTable dbdataset = new DataTable(); adapter. Fill(dbdataset); BindingSource bsource = new BindingSource(); bsource. DataSource = dbdataset; dataGridView1. DataSource = bsource; SetRowOrder(); adapter. Update(dbdataset); } catch (Exception ex) { MessageBox. Show(ex. Message); } }

void privé btnRefreshValue_Click (expéditeur d'objet, EventArgs e)

{ string weightSort = "SELECT * FROM patedb.patelog ORDER BY CAST (weight AS SIGNED INTEGER);"; MySqlCommand command = new MySqlCommand(weightSort, connection); try { Adaptateur MySqlDataAdapter = new MySqlDataAdapter(); adapter. SelectCommand = commande; DataTable dbdataset = new DataTable(); adapter. Fill(dbdataset); BindingSource bsource = new BindingSource(); bsource. DataSource = dbdataset; dataGridView1. DataSource = bsource; SetRowOrder(); adapter. Update(dbdataset); } catch (Exception ex) { MessageBox. Show(ex. Message); } }

void privé ChkNullBtn_Click (expéditeur d'objet, EventArgs e)

{ if (ProdRunning) { CheckTableForNull(); load_table(); } }

vide privé CheckTableForNull()

{ //Vérifier/définir l'intervalle de temps minimum 100 ms int i; int. TryParse(textTimer1. Text, out i); if (i <100) { timer1. Stop(); je = 100; timer1. Interval = i; MessageBox. Show("Valeur minimale i 100mS"); timer1. Start(); } else { timer1. Interval = i; } textTimer1. Text = timer1. Interval. ToString(); //Vérifiez si des lignes avec null sont disponibles dans la table, renvoie le nombre de lignes dans la variable:NumberOfRows string weightStr = ""; string chkNull = "SELECT COUNT(*) FROM patedb.patelog WHERE rangeNR IS NULL ORDER BY pateID LIMIT 1;"; Commande MySqlCommand = new MySqlCommand(chkNull, connexion); essayez { connection. Open(); NumberOfRows = Convert. ToInt32(command. ExecuteScalar()); connexion. Fermer(); } catch (Exception ex) { MessageBox. Show(ex. Message); } finally { if (NumberOfRows != 0) { try { //Sélectionne le numéro de pateID le plus bas où rangeNr est NULL string readID = "SELECT pateID FROM patedb.patelog WHERE rangeNR IS NULL ORDER BY pateID ASC LIMIT 1;"; MySqlCommand cmdID = new MySqlCommand(readID, connexion); { connexion. Ouvert(); RowNumber = (int)cmdID. ExecuteScalar(); //entier!! connexion. Fermer(); } listPateID. Text = RowNumber. ToString(); // lit le numéro PateID sélectionné // Sélectionne le poids de la chaîne de numéro de ligne sélectionnée row = RowNumber. ToString(); string readweight = "SELECT poids FROM patedb.patelog WHERE pateID=" + ligne; MySqlCommand cmdweight = new MySqlCommand(readweight, connexion); { connexion. Ouvert(); weightStr = (chaîne)cmdweight. ExecuteScalar(); //Chaîne de caractères !! connexion. Fermer(); } poids = int. Parse(weightStr); // convertir en int txtWeight. Text = weight. ToString(); // print int RangeNumber = 0; if (poids>= limites[0] && poids = limites[2] && poids = limites[4] && poids <= limites[5]) { RangeNumber = 3; } } txtRange. Text = RangeNumber. ToString(); UpdateLog(); } catch (Exception ex) { MessageBox. Show(ex. Message); } QuantProduced = QuantProduced + 1; } } } private void btnStart_Click(object sender, EventArgs e) { if (ProdRunning == false) { int valtest; essayez { CustomerID = int. Parse (txtCustomerNr. Text); //read customerID } catch { MessageBox. Show("Entrez les données de production et appuyez sur le bouton 'start'."); }

string test = "SELECT COUNT(*) FROM patedb.customertable WHERE customerID ="+CustomerID;

MySqlCommand cmdtestcustomer = new MySqlCommand(test, connexion); { connexion. Ouvert(); valtest = Convert. ToInt32(cmdtestcustomer. ExecuteScalar()); // renvoie 0 si le client n'existe pas connection. Close(); } if (valtest==1) // si le client existe dans la base de données - démarrer la production { try { OrderNr = int. Parse(txtOrderNumber. Text); ProdRunning = vrai; timer1. Start(); textTimer1. Text = timer1. Interval. ToString(); ReadLimits(); } catch (Exception ex) { MessageBox. Show("Entrez les données de production et appuyez sur le bouton 'start'."); } } else MessageBox. Show("Le client n'est pas dans la base de données, réessayez"); } //LireLimites(); }

void privé ReadLimits()

{ // Lit les limites de rangetable, range 1 à 3 int counter = 0; for (int rangeNr = 1; rangeNr < 4; rangeNr++) { string readmin = "SELECT rangeMin FROM patedb.rangetable WHERE rangeNr=" + rangeNr; MySqlCommand cmdmin = new MySqlCommand(readmin, connexion); { connexion. Ouvert(); limites[compteur] = (int)cmdmin. ExecuteScalar(); compteur = compteur + 1; connexion. Fermer(); } // MessageBox. Show(counter. ToString()); string readmax = "SELECT rangeMax FROM patedb.rangetable WHERE rangeNr=" + rangeNr; MySqlCommand cmdmax = new MySqlCommand(readmax, connection); { connexion. Ouvert(); limites[compteur] = (int)cmdmax. ExecuteScalar(); compteur = compteur + 1; connexion. Fermer(); } } // fin de la boucle }

privé void UpdateLog()

{ // UPDATE rangeNR & orderID string Range = RangeNumber. ToString(); string Order = OrderNr. ToString(); string update = "UPDATE patedb.patelog SET rangeNr= "+Plage+', ' + "orderID= "+OrderNr+" WHERE pateID="+RowNumber; MySqlCommand updatecmd = new MySqlCommand (mise à jour, connexion); essayez { connection. Open(); updatecmd. ExecuteNonQuery(); connexion. Fermer(); } catch (Exception ex) { MessageBox. Show(ex. Message); } }

void privé btnStop_Click (expéditeur d'objet, EventArgs e)

{ if (ProdRunning == true) { timer1. Stop(); ProdRunning = false; UpdateOrderTable(); } else { MessageBox. Show("Aucune production n'a encore commencé. Entrez les données et appuyez sur le bouton 'start'"); } }

privé void UpdateOrderTable()

{ string insert = "INSERT INTO patedb.ordertable (orderID, orderQuant, quantProduced, comment, customerID) VALUES ('" + this.txtOrderNumber. Text + "', '" + this.txtOrderQuant. Text + "', '"+ QuantProduced. ToString()+"', '"+this.txtComment. Text+"', '"+this.txtCustomerNr. Text+"');"; MySqlCommand insertcmd = new MySqlCommand(insert, connection); essayez { connection. Open(); insertcmd. ExecuteNonQuery(); connexion. Fermer(); QuantProducted = 0; } catch (Exception ex) { MessageBox. Show(ex. Message); } }

private void timer1_Tick (expéditeur d'objet, EventArgs e)

{ CheckTableForNull(); load_table(); }

void privé btnShowOrderTable_Click (expéditeur d'objet, EventArgs e)

{ if (ProdRunning == false) { MySqlCommand command = new MySqlCommand("SELECT * FROM patedb.ordertable ORDER BY orderID DESC;", connexion); try { Adaptateur MySqlDataAdapter = new MySqlDataAdapter(); adapter. SelectCommand = commande; DataTable dbdataset = new DataTable(); adapter. Fill(dbdataset); BindingSource bsource = new BindingSource(); bsource. DataSource = dbdataset; dataGridView1. DataSource = bsource; adapter. Update(dbdataset); } catch (Exception ex) { MessageBox. Show(ex. Message); } } else { MessageBox. Show("Appuyez sur stop pour afficher le tableau des commandes"); } }

void privé btnShowOrderDetails_Click (expéditeur d'objet, EventArgs e)

{ if (ProdRunning == false) { string test = ("SELECT patedb.ordertable.orderID, orderQuant, quantProduced, comment, customerID FROM patedb.ordertable INNER JOIN patedb.patelog ON patedb.patelog.orderID= patedb.ordertable.orderID WHERE patedb.patelog. UID = '" + txtShowOrderDetails. Text + "'"); Commande MySqlCommand = new MySqlCommand (test, connexion); essayez { connection. Open(); Adaptateur MySqlDataAdapter = new MySqlDataAdapter(); adapter. SelectCommand = commande; DataTable dbdataset = new DataTable(); adapter. Fill(dbdataset); BindingSource bsource = new BindingSource(); bsource. DataSource = dbdataset; dataGridView1. DataSource = bsource; adapter. Update(dbdataset); } catch (Exception ex) { MessageBox. Show(ex. Message); } connexion. Fermer(); } else { MessageBox. Show("Appuyez sur stop pour afficher les détails de la commande"); } }

void privé btnShowCustomerDetails_Click (expéditeur d'objet, EventArgs e)

{ if (ProdRunning == false) { string test = ("SELECT patedb.customertable.customerID, nom, adresse, téléphone, email, rangeNr FROM patedb.customertable INNER JOIN patedb.ordertable ON patedb.ordertable.customerID= patedb.customertable. customerID WHERE patedb.ordertable.orderID = '" + txtShowCustomerDetails. Text + "'"); Commande MySqlCommand = new MySqlCommand (test, connexion); try { Adaptateur MySqlDataAdapter = new MySqlDataAdapter(); adapter. SelectCommand = commande; DataTable dbdataset = new DataTable(); adapter. Fill(dbdataset); BindingSource bsource = new BindingSource(); bsource. DataSource = dbdataset; dataGridView1. DataSource = bsource; adapter. Update(dbdataset); } catch (Exception ex) { MessageBox. Show(ex. Message); } } else { MessageBox. Show("Appuyez sur stop pour afficher les détails du client"); } } }

}

Conseillé: