Table des matières:

DuvelBot - Robot de service de bière ESP32-CAM : 4 étapes (avec photos)
DuvelBot - Robot de service de bière ESP32-CAM : 4 étapes (avec photos)

Vidéo: DuvelBot - Robot de service de bière ESP32-CAM : 4 étapes (avec photos)

Vidéo: DuvelBot - Robot de service de bière ESP32-CAM : 4 étapes (avec photos)
Vidéo: Ne JAMAIS partir SANS SA COPINE 🫢 #humour #blague #drole #couplegoals #internationalcouple #shorts 2024, Juin
Anonim
DuvelBot - Robot de service de bière ESP32-CAM
DuvelBot - Robot de service de bière ESP32-CAM

Après une dure journée de travail, rien ne vaut de siroter votre bière préférée sur le canapé. Dans mon cas, c'est la bière blonde belge "Duvel". Cependant, après tout, mais en s'effondrant, nous sommes confrontés à un problème des plus graves: le réfrigérateur contenant mon Duvel est un infranchissable 20 pieds éloigné dudit canapé.

Bien qu'une légère coercition de ma part puisse pousser un adolescent éboueur de réfrigérateur occasionnel à verser mon allocation hebdomadaire de Duvel, la tâche de la livrer à son géniteur presque épuisé est évidemment un pas de trop.

Il est temps de sortir le fer à souder et le clavier…

DuvelBot est une webcam de conduite sans fioritures basée sur AI-Thinker ESP32-CAM, que vous pouvez contrôler à partir de votre smartphone, navigateur ou tablette.

Il est facile d'adapter ou d'étendre cette plateforme à des usages moins alcoolisés (pensez à SpouseSpy, NeighbourWatch, KittyCam…).

J'ai construit ce robot principalement pour en apprendre un peu plus sur l'ensemble de la programmation Web et de l'IoT, dont je ne savais rien. Donc, à la fin de ce Instructable est une explication détaillée de la façon dont cela fonctionne.

De nombreuses parties de ce Instructable sont basées sur les excellentes explications trouvées dans Random Nerd Tutorials, alors s'il vous plaît allez leur rendre visite !

Fournitures

De quoi as-tu besoin:

La liste des pièces n'est pas gravée dans la pierre et de nombreuses pièces peuvent être obtenues dans une tonne de versions différentes et à partir de nombreux endroits différents. J'ai acheté la plupart d'Ali-Express. Comme disait Machete: improvisez.

Matériel:

  • Module AI Thinker ESP32-CAM. Cela pourrait probablement fonctionner avec d'autres modules ESP32-CAM mais c'est ce que j'ai utilisé
  • Carte pilote de moteur L298N,
  • Une plateforme robotique à 4 roues pas chère,
  • Un boîtier avec une grande surface plane comme le Hammond Electronics 1599KGY,
  • Convertisseur USB vers 3.3V-TTL pour la programmation.
  • Pour l'éclairage: 3 LED blanches, BC327 ou autre transistor NPN à usage général (Ic=500mA), résistance 4k7k, 3 résistances 82Ohm, perfboard, câbles (voir schéma et photos).
  • Un interrupteur à bascule marche/arrêt et un bouton poussoir normalement ouvert pour la programmation.

Optionnel:

  • Une caméra fisheye avec un flex plus long que la caméra standard OV2460 fournie avec le module ESP32-CAM,
  • Antenne WiFi avec câble suffisamment long et connecteur coaxial ultra miniature, comme celui-ci. L'ESP32-CAM a une antenne embarquée et le boîtier est en plastique, donc une antenne n'est pas vraiment nécessaire, mais j'ai trouvé ça cool, alors…
  • Papier autocollant imprimable à jet d'encre pour la conception du capot supérieur.

Les outils matériels habituels: fer à souder, perceuses, tournevis, pinces…

Étape 1: Construire la plate-forme robotique

Construire la plate-forme robotique
Construire la plate-forme robotique
Construire la plate-forme robotique
Construire la plate-forme robotique
Construire la plate-forme robotique
Construire la plate-forme robotique

Le schéma:

Le schéma n'a rien de spécial. L'ESP32-cam contrôle les moteurs via la carte de commande de moteur L298N, qui a deux canaux. Les moteurs des côtés gauche et droit sont placés en parallèle et chaque côté occupe un canal. Quatre petits condensateurs céramiques 10..100nF proches des broches du moteur sont comme toujours conseillés pour contrer les interférences RF. De plus, un grand capuchon électrolytique (2200…4700uF) sur l'alimentation de la carte moteur comme indiqué dans le schéma, bien qu'il ne soit pas strictement nécessaire, peut limiter un peu l'ondulation de la tension d'alimentation (si vous voulez voir un film d'horreur, alors sondez Vbat avec un oscilloscope pendant que les moteurs sont actifs).

Notez que les deux broches ENABLE des canaux du moteur sont pilotées par la même broche à modulation de largeur d'impulsion (PWM) de l'ESP32 (IO12). C'est parce que le module ESP32-CAM n'a pas une tonne de GPIO (schéma du module inclus pour référence). Les LED du robot sont pilotées par IO4, qui pilote également la LED flash intégrée, retirez donc Q1 pour empêcher la LED flash de s'allumer dans un boîtier fermé.

Le bouton de programmation, l'interrupteur marche/arrêt, le connecteur de charge et le connecteur de programmation sont accessibles sous le robot. J'aurais pu faire un bien meilleur travail pour le connecteur de programmation (jack 3,5 mm ?), mais la bière ne pouvait plus attendre. Les mises à jour en direct (OTA) seraient également intéressantes à configurer.

Pour mettre le robot en mode programmation, appuyez sur le bouton de programmation (cela fait baisser IO0) puis allumez-le.

Important: pour charger les batteries NiMH du robot, utilisez une alimentation de laboratoire (déchargée) à environ 14V et un courant limité à 250mA. La tension s'adaptera à la tension des batteries. Déconnectez-vous si le robot est chaud ou si la tension de la batterie atteint environ 12,5 V. Une amélioration évidente ici serait d'intégrer un chargeur de batterie approprié, mais cela sort du cadre de ce Instructable.

Le matériel:

Veuillez également voir les notes dans les images. Le boîtier est monté sur la base du robot à l'aide de 4 boulons M4 et d'écrous autobloquants. Notez les tubes en caoutchouc utilisés comme entretoises de distance. Espérons que cela donne également une certaine suspension à la Duvel, si le trajet s'avère cahoteux. Le module ESP32-CAM et la carte moteur L298N sont montés dans le boîtier à l'aide de pieds collants en plastique (pas sûr du bon nom en anglais), pour éviter d'avoir à percer des trous supplémentaires. De plus, l'ESP32 est monté sur sa propre carte de perforation et ses embases enfichables. Cela facilite le remplacement de l'ESP32.

N'oubliez pas: si vous optez pour une antenne WiFi externe au lieu de celle intégrée, soudez également le cavalier de sélection d'antenne sous la carte ESP32-CAM.

Imprimez le logo supérieur dans le fichier DuvelBot.svg sur du papier autocollant à jet d'encre (ou créez le vôtre), et vous êtes prêt à partir !

Étape 2: programmer le robot

Programmer le robot
Programmer le robot

Il est conseillé de programmer le robot avant de le fermer, pour s'assurer que tout fonctionne et qu'aucune fumée magique n'apparaisse.

Vous avez besoin des outils logiciels suivants:

  • L'IDE Arduino,
  • Les bibliothèques ESP32, SPIFFS (système de fichiers flash périphérique série), la bibliothèque ESPAsync Webserver.

Ce dernier peut être installé en suivant ce didacticiel randomnerd jusqu'à et y compris la section « organiser vos fichiers ». Je ne pourrais vraiment pas mieux l'expliquer.

Le code:

Mon code peut être trouvé sur:

  • Un croquis Arduino DuvelBot.ino,
  • Un sous-dossier de données qui contient les fichiers qui vont être téléchargés sur le flash ESP à l'aide de SPIFFS. Ce dossier contient la page Web que l'ESP servira (index.html), une image de logo faisant partie de la page Web (duvel.png) et une feuille de style en cascade ou un fichier CSS (style.css).

Pour programmer le robot:

  • Connectez le convertisseur USB-TTL comme indiqué sur le schéma,
  • Fichier -> Ouvrir -> aller dans le dossier où se trouve DuvelBot.ino.
  • Modifiez vos identifiants réseau dans l'esquisse:

const char* ssid = "yourNetworkSSIDHere";const char* password = "yourPasswordHere";

  • Outils -> Carte -> "AI-Thinker ESP-32 CAM" et sélectionnez le port série approprié pour votre PC (Outils -> Port -> quelque chose comme /dev/ttyUSB0 ou COM4),
  • Ouvrez le moniteur série dans l'IDE Arduino, tout en appuyant sur le bouton PROG (qui tire IO0 bas), allumez le robot,
  • Vérifiez sur le moniteur série que l'ESP32 est prêt pour le téléchargement,
  • Fermez le moniteur série (sinon le téléchargement SPIFFS échoue),
  • Outils -> "ESP32 Sketch Data Upload" et attendez qu'il se termine,
  • Eteindre et rallumer en maintenant la touche PROG pour revenir en mode programmation,
  • Appuyez sur la flèche "Upload" pour programmer le sketch et attendez qu'il se termine,
  • Ouvrez le moniteur série et réinitialisez l'ESP32 en éteignant / rallumant,
  • Une fois qu'il a démarré, notez l'adresse IP (quelque chose comme 192.168.0.121) et déconnectez le robot du convertisseur USB-TTL,
  • Ouvrez un navigateur à cette adresse IP. Vous devriez voir l'interface comme sur l'image.
  • Facultatif: définissez l'adresse mac de l'ESP32 sur une adresse IP fixe dans votre routeur (cela dépend de la façon de faire du routeur).

C'est ça! Continuez à lire si vous voulez savoir comment cela fonctionne…

Étape 3: Comment ça marche

Venons-en maintenant à la partie intéressante: comment tout cela fonctionne-t-il ensemble ?

Je vais essayer de l'expliquer étape par étape, mais gardez à l'esprit que Kajnjaps n'est pas un spécialiste de la programmation Web. En fait, apprendre un peu de programmation Web était la prémisse de la création de DuvelBot. Si je fais des erreurs évidentes, s'il vous plaît laissez un commentaire!

Ok, une fois l'ESP32 allumé, comme d'habitude dans la configuration, il initialise les GPIO, les associe à des minuteries PWM pour le contrôle du moteur et des LED. Voir ici pour en savoir plus sur le contrôle du moteur, c'est assez standard.

Ensuite, la caméra est configurée. J'ai délibérément gardé la résolution assez basse (VGA ou 640x480) pour éviter une réponse lente. Notez que la carte AI-Thinker ESP32-CAM dispose d'une puce RAM série (PSRAM) qu'elle utilise pour stocker des images de caméra de plus grande résolution:

if(psramFound()) { Serial.println("PSRAM trouvée."); config.frame_size = FRAMESIZE_VGA; config.jpg_quality = 12; config.fb_count = 2; //nombre de framebuffers voir: https://github.com/espressif/esp32-camera } else { Serial.println("aucune PSRAM trouvée."); config.frame_size = FRAMESIZE_QVGA; config.jpg_quality = 12; config.fb_count = 1; }

Ensuite, le système de fichiers flash périphérique série (SPIFFS) est initialisé:

//initialiser SPIFFS if(!SPIFFS.begin(true)) { Serial.println("Une erreur s'est produite lors du montage de SPIFFS!"); revenir; }

SPIFFS agit comme un petit système de fichiers sur l'ESP32. Ici, il est utilisé pour stocker trois fichiers: la page Web elle-même index.html, une feuille de style de fichier en cascade style.css et un logo d'image-p.webp

Ensuite, l'ESP32 se connecte à votre routeur (n'oubliez pas de définir vos informations d'identification avant de télécharger):

//modifier les informations d'identification de votre routeur hereconst char* ssid = "yourNetworkSSIDHere";const char* password = "yourPasswordHere"; … // se connecter au WiFi Serial.print("Connexion au WiFi"); WiFi.begin(ssid, mot de passe); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); retard (500); } //maintenant connecté au routeur: ESP32 a maintenant une adresse IP

Pour faire quelque chose d'utile, nous démarrons un serveur Web asynchrone:

//créer un objet AsyncWebServer sur le port 80AsyncWebServer server(80); … server.begin(); //commencer à écouter les connexions

Maintenant, si vous saisissez l'adresse IP qui a été attribuée à l'ESP32 par le routeur dans la barre d'adresse d'un navigateur, l'ESP32 reçoit une requête. Cela signifie qu'il doit répondre au client (vous ou votre navigateur) en lui fournissant quelque chose, par exemple une page Web.

L'ESP32 sait comment répondre, car dans la configuration, les réponses à toutes les demandes autorisées possibles ont été enregistrées à l'aide de server.on(). Par exemple, la page Web principale ou l'index (/) est géré comme ceci:

server.on("/", HTTP_GET, (AsyncWebServerRequest *request){ Serial.println("/requête reçue!"); request->send(SPIFFS, "/index.html", String(), false, processeur); });

Ainsi, si le client se connecte, l'ESP32 répond en envoyant le fichier index.html depuis le système de fichiers SPIFFS. Le processeur de paramètres est le nom d'une fonction qui prétraite le code HTML et remplace toutes les balises spéciales:

// Remplace les espaces réservés dans le html comme %DATA%// par les variables que vous souhaitez afficher//

Données: %DATA%

Processeur de chaîne(const String& var){ if(var == "DATA"){ //Serial.println("in processor!"); return String(dutyCycleNow); } return String();}

Maintenant, disséquons la page Web index.html elle-même. En général, il y a toujours trois parties:

  1. code html: quels éléments doivent être affichés (boutons/texte/sliders/images etc.),
  2. code de style, soit dans un fichier.css séparé, soit dans une section …: à quoi doivent ressembler les éléments,
  3. javascript a … section: comment la page Web doit agir.

Une fois que index.html est chargé dans le navigateur (qui sait qu'il s'agit de HTML à cause de la ligne DOCTYPE), il se heurte à cette ligne:

C'est une demande pour une feuille de style CSS. L'emplacement de cette feuille est donné dans href="…". Alors que fait votre navigateur ? A droite, il lance une autre requête au serveur, cette fois pour style.css. Le serveur capture cette requête, car elle a été enregistrée:

server.on("/style.css", HTTP_GET, (AsyncWebServerRequest *request){ Serial.println(" css request receive"); request->send(SPIFFS, "/style.css", "text/css "); });

Bien, hein ? Incidemment, cela aurait pu être href="/some/file/on/the/other/side/of/the/moon", pour tous vos navigateurs pris en charge. Il irait chercher ce dossier tout aussi heureusement. Je n'expliquerai pas la feuille de style car elle ne contrôle que les apparences, donc ce n'est pas vraiment intéressant ici, mais si vous voulez en savoir plus, consultez ce tutoriel.

Comment apparaît le logo DuvelBot ? Dans index.html nous avons:

auquel l'ESP32 répond par:

server.on("/duvel", HTTP_GET, (AsyncWebServerRequest *request){ Serial.println("duvel logo request receive!"); request->send(SPIFFS, "/duvel.png", "image-p.webp

..un autre fichier SPIFFS, cette fois une image complète, comme indiqué par "image/png" dans la réponse.

Venons-en maintenant à la partie vraiment intéressante: le code des boutons. Concentrons-nous sur le bouton FORWARD:

EFFRONTÉ

Le nom n'est qu'un nom pour le lier à la feuille de style pour personnaliser la taille, la couleur, etc. Les parties importantes sont onmousedown="toggleCheckbox('forward')" et onmouseup="toggleCheckbox('stop') ". Celles-ci constituent les actions du bouton (idem pour ontouchstart/ontouchend mais pour les écrans tactiles/téléphones). Ici, l'action du bouton appelle une fonction toggleCheckbox(x) dans la section javascript:

function toggleCheckbox(x){ var xhr = new XMLHttpRequest(); xhr.open("GET", "/" + x, true); xhr.send(); //pourrait faire quelque chose avec la réponse aussi une fois prêt, mais nous ne le faisons pas }

Donc, en appuyant sur le bouton avant, cela entraîne immédiatement l'appel de toggleCheckbox('forward'). Cette fonction lance alors une XMLHttpRequest "GET", de l'emplacement "/forward" qui agit comme si vous aviez tapé 192.168.0.121/forward dans la barre d'adresse de votre navigateur. Une fois cette demande arrivée à l'ESP32, elle est traitée par:

server.on("/forward", HTTP_GET, (AsyncWebServerRequest *request){ Serial.println("received /forward"); actionNow = FORWARD; request->send(200, "text/plain", "OK forward."); });

Maintenant, l'ESP32 répond simplement par un texte "OK en avant". Notez que toggleCheckBox() ne fait rien avec (ou n'attend pas) cette réponse, mais cela pourrait, comme indiqué plus loin dans le code de la caméra.

En lui-même pendant cette réponse, le programme définit uniquement une variable actionNow = FORWARD, en réponse à l'appui sur le bouton. Maintenant, dans la boucle principale du programme, cette variable est surveillée dans le but d'augmenter/diminuer le PWM des moteurs. La logique est la suivante: tant que nous avons une action qui n'est pas STOP, accélérer les moteurs dans cette direction jusqu'à ce qu'un certain nombre (dutyCycleMax) soit atteint. Maintenez ensuite cette vitesse, tant que l'actionNow n'a pas changé:

boucle vide(){ currentMillis = millis(); if (currentMillis - previousMillis >= dutyCycleStepDelay) { // enregistre la dernière fois que vous avez exécuté la boucle previousMillis = currentMillis; //mainloop est responsable de la montée/descente des moteurs if(actionNow != previousAction) { //rampe vers le bas, puis arrêt, puis changement d'action et montée en puissance dutyCycleNow = dutyCycleNow-dutyCycleStep; if (dutyCycleNow <= 0) { //si après la décélération, le courant continu est égal à 0, défini dans la nouvelle direction, démarre au cycle d'utilisation min setDir(actionNow); action précédente = action maintenant; devoirCycleNow = devoirCycleMin; } } else //actionNow == accélération de l'action précédente, sauf lorsque la direction est STOP { if (actionNow != STOP) { dutyCycleNow = dutyCycleNow+dutyCycleStep; if (dutyCycleNow > dutyCycleMax) dutyCycleNow = dutyCycleMax; } sinon devoirCycleNow = 0; } ledcWrite(pwmChannel, devoirCycleNow); // ajuste le cycle de service du moteur }}

Cela augmente lentement la vitesse des moteurs, au lieu de simplement lancer à pleine vitesse et de renverser le précieux Duvel. Une amélioration évidente serait de déplacer ce code vers une routine d'interruption de minuterie, mais cela fonctionne tel quel.

Maintenant, si nous relâchons le bouton avant, votre navigateur appelle toggleCheckbox('stop'), ce qui entraîne une demande de GET /stop. L'ESP32 règle actionNow sur STOP (et répond par "OK stop"), ce qui déclenche la boucle principale pour faire ralentir les moteurs.

Et les LED ? Même mécanisme, mais maintenant nous avons un curseur:

Dans le javascript, le réglage du curseur est surveillé, de sorte qu'à chaque changement, un appel pour obtenir "/LED/xxx" se produit, où xxx est la valeur de luminosité à laquelle les LED doivent être réglées:

var slide = document.getElementById('slide'), sliderDiv = document.getElementById("sliderAmount"); slide.onchange = function() { var xhr = new XMLHttpRequest(); xhr.open("GET", "/LED/" + this.value, true); xhr.send(); sliderDiv.innerHTML = this.value; }

Notez que nous avons utilisé document.getElementByID('slide') pour obtenir l'objet curseur lui-même, qui a été déclaré avec et que la valeur est sortie dans un élément de texte à chaque modification.

Le gestionnaire dans l'esquisse capture toutes les demandes de luminosité en utilisant "/LED/*" dans l'enregistrement du gestionnaire. Ensuite, la dernière partie (un nombre) est divisée et convertie en un entier:

server.on("/LED/*", HTTP_GET, (AsyncWebServerRequest *request){ Serial.println("led request receive!"); setLedBrightness((request->url()).substring(5).toInt ()); request->send(200, "text/plain", "OK Leds."); });

Comme décrit ci-dessus, les boutons radio contrôlent les variables qui définissent les valeurs par défaut du PWM, de sorte que DuvelBot peut vous conduire lentement avec la bière, en faisant attention à ne pas renverser cet or liquide et revenir rapidement à la cuisine pour en chercher d'autres.

…Alors, comment l'image de la caméra est-elle mise à jour sans que vous ayez à actualiser la page ? Pour cela nous utilisons une technique appelée AJAX (Asynchronous JavaScript and XML). Le problème est que normalement une connexion client-serveur suit une procédure fixe: le client (navigateur) fait la demande, le serveur (ESP32) répond, le dossier est clos. Terminé. Il ne se passe plus rien. Si seulement nous pouvions amener le navigateur à demander régulièrement des mises à jour à l'ESP32… et c'est exactement ce que nous ferons avec ce morceau de javascript:

setInterval(function(){ var xhttp = new XMLHttpRequest(); xhttp.open("GET", "/CAMERA", true); xhttp.responseType = "blob"; xhttp.timeout = 500; xhttp.ontimeout = function(){}; xhttp.onload = function(e){ if (this.readyState == 4 && this.status == 200) { //voir: https://stackoverflow.com/questions/7650587/using… // https://www.html5rocks.com/en/tutorials/file/xhr2/ var urlCreator = window. URL || window.webkitURL; var imageUrl = urlCreator.createObjectURL(this.response); //créer un objet à partir du blob document.querySelector("#camimage").src = imageUrl; urlCreator.revokeObjectURL(imageurl) } }; xhttp.send(); }, 250);

setInterval prend comme paramètre une fonction et l'exécute de temps en temps (ici une fois toutes les 250 ms résultant en 4 images/seconde). La fonction qui est exécutée fait une demande de "blob" binaire à l'adresse /CAMERA. Ceci est géré par l'ESP32-CAM dans le croquis comme (de Randomnerdtutorials):

server.on("/CAMERA", HTTP_GET, (AsyncWebServerRequest *request){ Serial.println("demande de caméra reçue!"); camera_fb_t * fb = NULL; //esp_err_t res = ESP_OK; size_t _jpg_buf_len = 0; uint8_t * _jpg_buf = NULL; //capture d'une image fb = esp_camera_fb_get(); if (!fb) {Serial.println("Le tampon de trame n'a pas pu être acquis");return;} if(fb->format != PIXFORMAT_JPEG)/ /déjà dans ce format de config{ bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); esp_camera_fb_return(fb); fb = NULL; if(!jpeg_converted){Serial.println("La compression JPEG a échoué");return; } } else{ _jpg_buf_len = fb->len; _jpg_buf = fb->buf; } //Serial.println(_jpg_buf_len); //envoyer la demande d'image formatée->send_P(200, "image/jpg", _jpg_buf, _jpg_buf_len); //nettoyage if(fb){ esp_camera_fb_return(fb); fb = NULL; _jpg_buf = NULL; } else if(_jpg_buf){ free(_jpg_buf); _jpg_buf = NULL; } });

Les parties importantes sont d'obtenir le cadre fb = esp_camera_fb_get() en le convertissant en-j.webp

La fonction javascript attend alors que cette image arrive. Ensuite, il suffit d'un peu de travail pour convertir le "blob" reçu en une URL pouvant être utilisée comme source pour mettre à jour l'image dans la page html.

ouf, c'est fini !

Étape 4: Idées et restes

Idées et restes
Idées et restes

L'objectif de ce projet pour moi était d'apprendre juste assez de programmation Web pour interfacer le matériel avec le Web. Plusieurs extensions de ce projet sont possibles. Voici quelques idées:

  • Implémentez le "vrai" streaming de caméra comme expliqué ici et ici et déplacez-le vers un 2ème serveur comme expliqué ici sur le même ESP32, mais sur l'autre cœur de processeur, puis importez le camerastream dans le html servi par le 1er serveur à l'aide d'un …. Cela devrait entraîner des mises à jour plus rapides de l'appareil photo.
  • Utilisez le mode point d'accès (AP) pour que le robot soit plus autonome, comme expliqué ici.
  • Développez avec la mesure de la tension de la batterie, les capacités de sommeil profond, etc. C'est un peu difficile pour le moment car l'AI-Thinker ESP32-CAM n'a pas beaucoup de GPIO; a besoin d'une extension via uart et par exemple un arduino esclave.
  • Convertissez-vous en un robot à la recherche de chats qui éjecte des friandises pour chats de temps en temps en appuyant sur la patte d'un gros bouton, diffusez des tonnes de belles photos de chats pendant la journée…

S'il vous plaît commenter si vous avez aimé ou avez des questions et merci d'avoir lu!

Conseillé: