Passerelle.Infini 2025 : MOUMOUNAN
Sommaire
[masquer]résumé du projet
MOUMOUNA - dérivé du mot câliner "moumounan" en breton - est un objet interactif distributeur de câlins à distance. A l'échelle de notre prototype celui-ci peut être déployé dans l'espace familial ou à taille humaine disposé dans l'espace public.
Le principe : Enlacer cet objet textile afin de faire baisser son anxiété ou trouver du réconfort. La pression contre son corps active Moumouna qui émet une douce vibration et s'illumine. Cet étreinte envoie un signal au deuxième "moumouna" disposé dans un autre espace ou une autre ville, qui s'illumine en attendant un câlin en réponse. Les vibrations annoncent que deux personnes s'enlacent par Moumouna interposé.
Un QR code est imprimé sur les deux objets qui vous renvoie vers un bande sonore (bruits de vagues) à télécharger pour renforcer l'expérience du câlin.
Le bouton pression sur le moumouna A envoie un signal lumineux au moumouna B, qui si il est enlacé (pression déclencheur) renvoie un signal qui déclenche une vibration! Les vibrations simultanées indiquent un câlin en résonance!
membres du projet
Aude Mouillot - Catherine Huve - Mélissa Rosingana
Bibliographie et webographie sur le projet
Mettre ici des exemples trouvés sur web de projets qui ressemblent et des technologies qui s'en rapprochent ou qui servent d'inspiration.
- calinothérapie
https://www.24heures.ch/les-bienfaits-de-la-calinotherapie-expliques-par-une-experte-803631900339
pistes explorée, abandonnées ou fertiles
Inspiration Oeuvre de Catherine Huve " LE GRAND HUG " textiles, mixtes - Dimensions: 40x70cm, 2024
prototype qu'on souhaite réaliser
PROTOTYPE EN MOUSSE DES DEUX MOUMOUNA
PROTOTYPE TISSUS
COUTURE DU PROTOTYPE FINAL
REMPLISSAGE AVEC COPEAUX DE BOIS ET COTON
BOITE NUMERIQUE
![]()
Bande LED
Vibreur
![]()
ASSEMBLAGE DE NOS PROTOTYPES
PROTOTYPE FINAL
code
1
2 /////////////////////////////
3 // Moumouna //
4 /////////////////////////////
5 /*
6 * Un projet imaginé et réalisé au Hackathon Passerelle.Infini organisé avec les petits débrouillards au centre d'art Passerelle à Brest les 20 et 21 mars 2025.
7 * moumouna : https://wiki.lesfabriquesduponant.net/index.php?title=Passerelle.Infini_2025_:_MOUMOUNAN
8 *
9 * Ce programme s'apuie sur le programme POCL Poke (imaginé et réalisé au Hackathon POCL à l'édulab Rennes 2 les 9 et 10 décembre 2021).
10 * POCL Poke : https://wiki.lesfabriquesduponant.net/index.php?title=POCL_:_Poke
11 *
12 * Configurez le logiciel Arduino pour gérer la carte D1 mini (ESP8266).
13 * Intégrer les biobliothèues requises par le projet :
14 ** #include <MQTT.h> -> pour gérer le protocole de communication, c'est la bibliothèque MQTT de Joël Gähwiler : https://github.com/256dpi/arduino-mqtt
15 ** #include <Adafruit_NeoPixel.h> -> pour gérer les rubans de led
16 ** #include <WiFiManager.h> -> Bibliothèque WiFiManager pour configurer automatiquement le réseau wifi et le mot de passe.
17 *
18 * BROCHAGE
19 _________________
20 / D1 mini \
21 - |[ ]RST TX[ ]| -
22 - |[ ]A0 -GPIO RX[ ]| -
23 |[ ]D0-16 5-D1[ ]| -
24 |[ ]D5-14 4-D2[ ]| -
25 Bouton - |[X]D6-12 0-D3[X]| - ruban de leds
26 - |[ ]D7-13 2-D4[ ]| LED_BUILTIN
27 - |[ ]D8-15 GND[X]| - GND (Boutons, ruban de leds)
28 - |[ ]3V3 . 5V[X]| - ruban de Led
29 | +---+ |
30 |_______|USB|_______|
31
32 Matériel :
33 - des fils dupont.
34 - un ruban de led RGB WS28B12
35 - D1 mini (Wemos, LOLIN,...)
36 - bouton poussoir
37 - une alimentation 5V
38 */
39 /*
40 * Un travail d'équipe de :
41 * Alma Oskouei
42 * Gaëlle Bescond
43 * Tony Vanpoucke
44 * Wing-Anh Luy
45 * Antony Le Goïc-Auffret
46 ___
47 / ___ \
48 |_| | |
49 /_/
50 _ ___ _
51 |_| |___|_| |_
52 ___|_ _|
53 |___| |_|
54 Les petits Débrouillards - mars 2025 CC-By-Sa http://creativecommons.org/licenses/by-nc-sa/3.0/
55 // Programme nourri de https://randomnerdtutorials.com/esp32-esp8266-rgb-led-strip-web-server/
56 // Programme inspiré de celui par Joël Gähwiler
57 // https://github.com/256dpi/arduino-mqtt
58
59 Améliorations :
60 reboot en cas de déconnexion MQTT
61
62 */
63 #include <ESP8266WiFi.h>
64 #include <MQTT.h> // Bibliothèque MQTT par Joël Gaehwiler
65 #include <Adafruit_NeoPixel.h> // Bibliothèque NeoPixel d'Adafruit
66 #include <WiFiManager.h> // Bibliothèque WiFiManager pour configurer automatiquement le réseau wifi et le mot de passe.
67 #include <ESP_EEPROM.h>
68
69 WiFiClient net; //on crée l'objet WiFiClient "Net"
70 WiFiServer serveurPOCL(80);
71 MQTTClient clientMQTT;
72 #define BROKER_IP "debrouillards.ddns.net" //IP du serveur sur lequel est installé le Broker MQTT
73
74 unsigned long lastMillis = 0;
75
76 char nomPOCL[25]; // variable de stockage du nom unique du POCL
77
78
79 //==============Vibreur======================
80 const int vib = D2; // broche vibreur
81 //=============Eléments pour le ruban de led et le bouton===============
82
83 // Broche de connexion du ruban de LED
84 #define PIN D3 // Il faudra changer en D2 (broche plus adpatée).
85
86 int brocheBouton = 12; //GPIO de la broche D6
87 int rouge = 0;
88 int bleu = 255;
89 int vert = 0;
90
91 // Nombre de Led RDGB dans votre ruban
92 #define NUMPIXELS 6
93
94 // on configurer un riban nommé "pixels"
95 Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
96
97 #define T 1 // temps pour gérer le fade du ruban
98 //==============================================================
99
100 //===============Eléments pour le choix de la couleur du Ruban===============
101 // Pour décoder les valeurs de HTTP GET
102 String redString = "0";
103 String greenString = "0";
104 String blueString = "0";
105 int pos1 = 0;
106 int pos2 = 0;
107 int pos3 = 0;
108 int pos4 = 0;
109
110 // Variable to store the HTTP req uest
111 String header;
112
113 // Setting PWM bit resolution
114 // const int resolution = 256;
115
116 // Current time
117 unsigned long currentTime = millis();
118 // Previous time
119 unsigned long previousTime = 0;
120 // Define timeout time in milliseconds (example: 2000ms = 2s)
121 const long timeoutTime = 2000;
122
123 //==============================================================================
124 void connect() {
125 Serial.print("Vérification de la connexion Wifi...");
126 while (WiFi.status() != WL_CONNECTED) {
127 Serial.print(".");
128 delay(1000);
129 }
130 Serial.println("\nPOCL Connecté au Wifi :-) ");
131 Serial.print("\nconnexion au serveur MQTT en cours ");
132 //connection au serveur MQTT : identifiant, User, mot de passe
133 while (!clientMQTT.connect(nomPOCL, "poclpokou", "pokou")) {
134 Serial.println(".");
135 delay(300);
136 }
137
138 Serial.println("\nconnecté à MQTT !");
139
140 // on s'abonne au sujet (topic) "/mou"
141 clientMQTT.subscribe("/mou"); // Attention à la casse !! La casse c'est maj ou minuscule
142 Serial.println("Abonné à /mou");
143 // client.unsubscribe("/mou");// Pour se désinscrire
144 }
145
146 void messageReceived(String &topic, String &payload) {
147 digitalWrite(LED_BUILTIN, HIGH);
148 fade();
149 Serial.println("incoming: " + topic + " - " + payload);
150 if (payload == "calin"){
151 Serial.println("clic !");
152 digitalWrite(LED_BUILTIN, LOW);
153 delay(100);
154 digitalWrite(LED_BUILTIN, HIGH);
155 }
156 }
157
158 void fade() {
159 EEPROM.get(0, rouge); Serial.print("Valeur de rouge EEPROM = "); Serial.println(rouge);
160 EEPROM.get(4, vert); Serial.print("Valeur de vert EEPROM = "); Serial.println(vert);
161 EEPROM.get(8, bleu); Serial.print("Valeur de bleu EEPROM = "); Serial.println(bleu);
162
163 for (int sig=0; sig<3; sig++){
164 for (int b=0; b<255; b++){
165 pixels.setBrightness(b);
166
167 for(int i=0; i<NUMPIXELS; i++) {
168 pixels.setPixelColor(i, pixels.Color(rouge, vert, bleu));
169 }
170 pixels.show(); // On allume les rubans
171 delay(T);
172 }
173
174 for (int b=255; b>1; b--){ //boucle de gestion de la brillance
175 pixels.setBrightness(b);
176
177 // n'oubliez pas que le premier pixel porte le Numéro 0
178 for(int i=0; i<NUMPIXELS; i++) { // Pour chaque pixel...
179
180 // pixels.Color() prends les valeurs RGB de 0,0,0 jusqu'à 255,255,255
181 pixels.setPixelColor(i, pixels.Color(rouge, vert, bleu));
182 }
183 pixels.show(); // on affiche les pixels
184 delay(T);
185 }
186 }
187 }
188
189 void testEEPROM(){ // On test si la mémoire de l'ESP contient déjà des infos d'une configuration précédente
190 if(EEPROM.percentUsed()>=0) { // Si il y en a on ne fait rien
191 Serial.println("Il y a des infos dans la mémoire");
192 Serial.print(EEPROM.percentUsed());
193 Serial.println("% de l'espace mémoire flash de l'ESP flash actuellement utilisé.");
194 EEPROM.get(0, rouge); Serial.print("Valeur de rouge EEPROM = "); Serial.println(rouge);
195 EEPROM.get(4, vert); Serial.print("Valeur de vert EEPROM = "); Serial.println(vert);
196 EEPROM.get(8, bleu); Serial.print("Valeur de bleu EEPROM = "); Serial.println(bleu);
197 } else { // Si il n'y en a pas on stocke la couleur pas défaut
198 EEPROM.put(0, rouge);
199 EEPROM.put(4, vert);
200 EEPROM.put(8, bleu);
201 boolean ok = EEPROM.commit();
202 Serial.println((ok) ? "Commit initialisation couleur OK" : "Commit initialisation couleur raté");
203 }
204 }
205 void choixCouleur(){
206 WiFiClient client = serveurPOCL.available(); // On écoute si il y a des connexions sur le site web du POCL (des clients)
207
208 if (client) { // Si il y a une nouvelle connexion,
209 currentTime = millis();
210 previousTime = currentTime;
211 Serial.println("Nouveau client connecté"); // print a message out in the serial port
212 String currentLine = ""; // créer une chaîne pour contenir les données entrantes du client
213 while (client.connected() && currentTime - previousTime <= timeoutTime) { // Boucle "while", tant que le client est connecté
214 currentTime = millis();
215 if (client.available()) { // if there's bytes to read from the client,
216 char c = client.read(); // read a byte, then
217 Serial.write(c); // print it out the serial monitor
218 header += c;
219 if (c == '\n') { // if the byte is a newline character
220 // if the current line is blank, you got two newline characters in a row.
221 // that's the end of the client HTTP request, so send a response:
222 if (currentLine.length() == 0) {
223 // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
224 // and a content-type so the client knows what's coming, then a blank line:
225 client.println("HTTP/1.1 200 OK");
226 client.println("Content-type:text/html");
227 client.println("Connection: close");
228 client.println();
229
230 // Affiche la page web
231 client.println("<!DOCTYPE html><html>");
232 client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
233 client.println("<link rel=\"icon\" href=\"data:,\">");
234 client.println("<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css\">");
235 client.println("<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.0.4/jscolor.min.js\"></script>");
236 client.println("</head><body><div class=\"container\"><div class=\"row\"><h1>Choisi la couleur de ton POCL</h1></div>");
237 client.println("<a class=\"btn btn-primary btn-lg\" href=\"#\" id=\"change_color\" role=\"button\">Change de Couleur</a> ");
238 client.println("<input class=\"jscolor {onFineChange:'update(this)'}\" id=\"rgb\"></div>");
239 client.println("<script>function update(picker) {document.getElementById('rgb').innerHTML = Math.round(picker.rgb[0]) + ', ' + Math.round(picker.rgb[1]) + ', ' + Math.round(picker.rgb[2]);");
240 client.println("document.getElementById(\"change_color\").href=\"?r\" + Math.round(picker.rgb[0]) + \"g\" + Math.round(picker.rgb[1]) + \"b\" + Math.round(picker.rgb[2]) + \"&\";}</script></body></html>");
241 // The HTTP response ends with another blank line
242 client.println();
243
244 // Request sample: /?r201g32b255&
245 // Red = 201 | Green = 32 | Blue = 255
246 if(header.indexOf("GET /?r") >= 0) {
247 pos1 = header.indexOf('r'); // pos1 est la position du caractère r dans la chaine de caractères.
248 pos2 = header.indexOf('g'); // idem
249 pos3 = header.indexOf('b');
250 pos4 = header.indexOf('&');
251 redString = header.substring(pos1+1, pos2); // la chaine de caractère juste après pos1 et jusqu'à pos2 est placée dans la variable redString.
252 greenString = header.substring(pos2+1, pos3); // ...
253 blueString = header.substring(pos3+1, pos4);
254 Serial.println(redString.toInt()); // je publie sur le moniteur série
255 Serial.println(greenString.toInt());
256 Serial.println(blueString.toInt());
257 rouge = redString.toInt(); // je transforme la chaine de caractère en nombre entier que je met dans la variable rouge
258 vert = greenString.toInt();
259 bleu = blueString.toInt();
260 // Je stocke la configuration dans la mémoire du D1 mini
261 EEPROM.put(0, rouge);
262 EEPROM.put(4, vert);
263 EEPROM.put(8, bleu);
264 boolean ok1 = EEPROM.commit();
265 Serial.println((ok1) ? "Commit couleur OK" : "Commit couleur raté");
266 }
267 // On sort ("break") de la boucle "while"
268 break;
269 } else { // if you got a newline, then clear currentLine
270 currentLine = "";
271 }
272 } else if (c != '\r') { // if you got anything else but a carriage return character,
273 currentLine += c; // add it to the end of the currentLine
274 }
275 }
276 }
277 // Clear the header variable
278 header = "";
279 fade();
280 // Close the connection
281 client.stop();
282 Serial.println("Client disconnected.");
283 Serial.println("");
284 }
285 }
286
287 void setup() {
288 Serial.begin(115200); // Ouverture de communication série
289
290 uint32_t chipid=ESP.getChipId();
291 snprintf(nomPOCL,25,"MouMouNa - %08X",chipid);
292 Serial.print("Je suis le MouMouNa ");
293 Serial.println(nomPOCL);
294
295 EEPROM.begin(16);
296 testEEPROM();
297
298 WiFi.mode(WIFI_STA); // la carte D1 mini est mise en mode STATION
299 WiFiManager MonReseauWifi; // on crée l'objet "MonReseauWifi"
300
301 bool res;
302 res = MonReseauWifi.autoConnect("MouMouNa"); // le POCL diffuse un réseau wifi en accès libre nommé "MouMouNa"
303
304 if(!res) {
305 Serial.println("La connexion n'a pas fonctionnée...");
306 }
307 else {
308 Serial.println("Vous êtes connecté au Wifi... :-)");
309
310 // Print local IP address and start web server
311 Serial.println("");
312 Serial.println("WiFi connected.");
313 Serial.println("IP address : ");
314 Serial.println(WiFi.localIP());
315 serveurPOCL.begin(); //démarrage du serveur Web du POCL
316 }
317
318 pixels.begin(); //on initialise le ruban "pixels"
319 pinMode(brocheBouton,INPUT_PULLUP);
320 pinMode(PIN,OUTPUT);
321
322 pinMode(LED_BUILTIN, OUTPUT);
323 digitalWrite(LED_BUILTIN, HIGH);
324
325 // préparation de la connexion au serveur MQTT
326 clientMQTT.begin(BROKER_IP, 1883, net); // (IP, port, Client Wifi défini plus haut)
327 clientMQTT.onMessage(messageReceived); // Si on reçoit un message MQTT, la fonction "messageReceived" est appelée.
328
329 connect(); // On se connecte
330 Serial.println("Programme MouMouNa V1 en date du 24 mars 2025");
331 }
332
333 void loop() {
334 pixels.clear(); // éteint tout les pixels
335 pixels.show(); // affiches pixels
336
337 clientMQTT.loop();
338 delay(10); // <- fixes some issues with WiFi stability
339
340 if (!clientMQTT.connected()) {
341 Serial.print("MouMouNa ");
342 Serial.print(nomPOCL);
343 Serial.println(" est déconnecté du serveur MQTT");
344 connect();
345 }
346
347 // Si on appuie sur le bouton, on envoie un messagge MQTT "calin", sur le topic "/mou"
348 if ( digitalRead(brocheBouton) == LOW ){
349 Serial.println("Appuis Bouton");
350 clientMQTT.publish("/mou", "calin");
351 delay(250); // Filtre anti-rebond du bouton
352 }
353
354 choixCouleur(); // appel de la fonction qui génère la page web de configuration de la couler du POCL
355 }