Objectif : (tenter de) bricoler un pèse personne bon marché et le transformer en wifi afin de suivre l’évolution de mon poids. Comme je n’ai pas de réseau dans la salle de bain, le plus simple et le plus économique est d’utiliser un esp8266.
Je me suis très largement inspiré de ce que j’ai trouvé sur le net, notamment sur SquixTechBlog. Je suis parti du modèle de balance le moins cher trouvé ce jour là chez Conforama, un « Homex BAL 5000 MP » qui doit exister sous d’innombrable désignations.
Pour lire le poids, deux alternatives : soit je tente de suivre l’électronique jusqu’à ce que j’arrive après l’amplification du signal des capteurs de pression, soit je pars des capteurs de pression que je relie à un convertisseur du type HX711 comme on trouve sur ebay. Ensuite le signal sera traité par un micro contrôleur et affiché. Il faut bien sur prévoir une phase de calibrage pour être le plus juste possible.
Qu’y a-t-il dans cette balance ?
Pas grand chose en fait : Quatre capteurs reliés à un PCB et son affichage LCD. Les fils de ceux du bas passant dans le tube central. Le circuit est marqué HB2319 rev I avec une date au 3 janvier 2008. Le LCD est juste posé sur le PCB…
Je remarque que ce sont les câbles rouges qui sont notés E+ E- S+ S-, je m’en servirai pour le signal. Première étape, on dessoude les câbles du PCB qu’on n’utilisera pas, et on les assemble sur le module HX711 qui est un amplificateur de signal.
Les quatre capteurs seront numérotés arbitrairement 1, 2, 3, 4.
Les fils blancs sont joints deux à deux : 1+2 et 3+4
Les fils noirs sont joints deux à deux : 1+4 et 2+3Les fils rouges sont connectés au HX711 dans l’ordre suivant : 1>A+ 2>E+ 3>A- 4>E-
Le HX711 est connecté à l’ESP8266-12 comme suit : DT>GPIO12 et SCK>GPIO14
Chargement de la bibliothèque HX711 pour arduino que je vais utiliser ici :
1 2 3 4 5 |
cd /tmp wget https://github.com/bogde/HX711/archive/master.zip unzip master # mv HX711-master ~/Arduino/libraries/ # En général mv HX711-master ~/Documents/programmation/libraries/ # Dans mon cas |
Premier test sous l’environement Arduino :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#include <Wire.h> #include "HX711.h" #define NB_ECHANTILLON 10 uint32 index_moyenne=0; HX711 scale(12, 14); // HX711.DOUT = GPIO12 / HX711.PD_SCK = GPIO14 float resultat[NB_ECHANTILLON]; void setup() { Serial.begin(115200); Serial.println("Test HX711"); scale.read(); yield(); // Redonne la main à l'ESP8266 /* * Le nombre ci dessous permet de calibrer la balance. Pour l'obtenir, voir le fichier README de la bibliothèque HX711 */ scale.set_scale(20108.1f); scale.tare(); yield(); } void loop() { float poids,total; uint8 n; poids=scale.get_units(10); resultat[index_moyenne++]=poids; index_moyenne %= NB_ECHANTILLON; total=0; for(n=0;n<NB_ECHANTILLON;n++){ total+=resultat[n]; } Serial.print(poids); Serial.print("\t moyenne des 10 derniers poids = "); Serial.println(total/NB_ECHANTILLON, 2); delay(100); } |
Ça à l’air de fonctionner et j’obtiens des valeurs cohérentes, bien que fluctuant un peu.
Réalisation
Le principe de fonctionnement de mon pèse personne est :
Initialisation
Initialisation de l’écran. Ici un écran OLED que j’utilise avec la bibliothèque SSD131x. À noter que cet écran est en codepage IBM850…
Initialisation du capteur HX711
Initialisation du Wifi
Lecture du poids
Lecture du poids et boucle jusqu’à qu’on ait un poids stable > 0 kg.
Choix de la personne
Je passe le poids lu par MQTT à node-red qui interroge la base de donnée. La requête retourne la liste des utilisateurs (préalablement rentrés sous mysql) triés par la différence entre le poids lu et le poids archivé pour chacun. À priori, la personne en train de se peser est celle qui a l’écart le plus petit .
On soumet la liste des personnes à l’utilisateur qui peut la faire défiler avec un appui bref sur le bouton, et en choisir une avec un appui long. Un message MQTT est alors généré avec le n° de la personne et le poids, ce qui permet à node-red d’archiver le poids dans la base de données.
Finition
Il me reste à alimenter le tout par une pile (actuellement avec du 5v USB convertis en 3.3v) et à faire un support « propre » pour le boîtier.
Code
Voici le code. Il est codé avec les pieds donc très largement perfectible !
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
#include <Wire.h> #include <HX711.h> #include <SSD131x.h> // LCD en codepage IBM850 ! #include <ESP8266WiFi.h> #include <PubSubClient.h> const char *ssid = "MonPointDAcces"; const char *pass = "MonMotDePasse"; IPAddress server(192, 168, 2, 103); WiFiClient wclient; PubSubClient clientMqtt(wclient, server, 1883); #define _DEBUG 1 #define BOUTON 16 #define PIN_SDA 5 #define PIN_SCL 4 SSD131x oled; HX711 scale(12, 14); // HX711.DOUT = GPIO12 / HX711.PD_SCK = GPIO14 #define MAX_PERSONNES 10 struct Personne { uint8 no; char nom[18]; }; Personne Personnes[MAX_PERSONNES]; uint8 Fin_liste; char tampon[40]; char Poids[10]; bool WifiInitialise=false; // Indique si le wifi+mqtt sont initialisés uint8 echantillon_en_cours=0; // Partie Wifi+MQTT #define BUFFER_SIZE 100 bool Archive; // Indique si le poids a été archivé void mqtt_dde_poids(void); int bouton(void) { // Attend un appuis sur le bouton et retourne le temps d'appui int chrono; do { do { yield(); } while(digitalRead(BOUTON) == LOW); delay(50); } while(digitalRead(BOUTON) == LOW); // On s'assure qu'on a bien appuyé le bouton chrono=millis(); do { yield(); } while(digitalRead(BOUTON)== HIGH and ((millis()-chrono)<500)); // On s'assure qu'on a bien relaché return millis()-chrono; } void callback(const MQTT::Publish& pub) { // Réception d'un message char t[30*MAX_PERSONNES]; uint8 car; if(pub.topic()=="balance/liste"){ // On a reçu une liste de personnes (après avoir communiqué le poids au serveur) char * ptr; uint16 l; uint8 pointeur; pointeur=0; Fin_liste=0; // Conversion string en char* l=snprintf(t, 30*MAX_PERSONNES, "%s", pub.payload_string().c_str()); // On coupe au | ptr=strtok(t, "|"); while(ptr!=NULL && Fin_liste<MAX_PERSONNES) { // Lecture du no et du nom Personnes[Fin_liste].no=atoi(ptr); ptr=strtok(NULL, "|"); snprintf(Personnes[Fin_liste++].nom, 17, "%s ? ", ptr); ptr=strtok(NULL, "|"); } // On rajoute un nouvel utilisateur au cas ou Personnes[Fin_liste].no=255; sprintf(Personnes[Fin_liste++].nom,"Nouveau ? "); // Liste constituée, on la passe en revue pour choisir la personne correspondant au poids //oled.clear(); pointeur=Fin_liste-1; do { pointeur++; pointeur %= Fin_liste; oled.sendString(Personnes[pointeur].nom,0,0); l=bouton(); yield(); } while(l<499); // Tant qu'on a pas choisi une personne, on boucle ! if(mqtt_envoie_poids(Personnes[pointeur].no)){ oled.clear(); oled.sendString("Poids",6,0); oled.sendString("archiv\x82",5,1); for(l=300;l;l--) { if (clientMqtt.connected()) clientMqtt.loop(); else connect_mqtt(); delay(10); } oled.clear(); oled.sendString("Descendre de la",1,0); oled.sendString("balance.",5,1); while(lis_poids()>0.2){ yield(); } oled.clear(); } Archive=true; } yield(); } bool connect_mqtt(void) { // Test si mqtt connecté, et le cas échéant le connecte uint8 essai=5; if(clientMqtt.connected()) { return true; } else { clientMqtt.disconnect(); if (WiFi.status() == WL_CONNECTED) { while (!clientMqtt.connected() && essai--) { //oled.sendString("Connexion Mqtt ",0,0); clientMqtt.connect("balance"); delay(500); } if (clientMqtt.connected()) { clientMqtt.subscribe("balance/liste"); return true; } } } return false; } void mqtt_dde_poids(void) { // Envoi du poids pour demande de personne correspondante connect_mqtt(); if (clientMqtt.connected()) { clientMqtt.publish(MQTT::Publish("balance/dde_personne",Poids).set_qos(2)); clientMqtt.loop(); } else { oled.sendString("Pb client Mqtt ",0,0); delay(3000); } } bool mqtt_envoie_poids(int no) { // Envoi du poids de la personne "no" connect_mqtt(); if (clientMqtt.connected()) { sprintf(tampon, "%d|%s", no, Poids); clientMqtt.publish(MQTT::Publish("balance/poids",tampon).set_qos(2)); clientMqtt.loop(); return true; } else { oled.sendString("Pb client Mqtt ",0,0); delay(3000); return false; } } // Partie Balance void init_poids(void) { oled.clear(); oled.sendString("Lecture",5,0); oled.sendString("0.0 kg",6,1); yield(); } float lis_poids(void) { float poids; poids=scale.get_units(5); yield(); poids=float(int((poids)*10))/10; // Arrondi aux 100g return poids; } bool test_wifi(void) { if (WiFi.status() == WL_CONNECTED) { return connect_mqtt(); // Testons la connexion mqtt } return false; } // --------------------------------------- void setup() { pinMode(BOUTON, INPUT); oled.init(PIN_SDA , PIN_SCL); oled.sendString("Bonjour",5,0); yield(); oled.sendString("Init Balance ",0,1); scale.read(); yield(); // Redonne la main à l'ESP8266 /* * Le nombre ci dessous permet de calibrer la balance. Pour l'obtenir, voir le fichier README de la bibliothèque HX711 */ scale.set_scale(20458.37f); scale.tare(); oled.sendString("Init Wifi ",0,1); WiFi.begin(ssid, pass); yield(); init_poids(); // RAZ poids archive et affichage // Fonction réception de message MQTT connect_mqtt(); clientMqtt.set_callback(callback); } void loop() { float poids_archive, poids; uint8 n,m; poids=0; // Mesure du poids do { // Actualise status wifi if(!WifiInitialise) WifiInitialise=test_wifi(); else { oled.sendString("*",15,1); if (clientMqtt.connected()) clientMqtt.loop(); else connect_mqtt(); yield(); } // Lecture du poids poids_archive=poids; poids=lis_poids(); yield(); if(poids<0.2) poids=0; dtostrf(poids, 5,1 ,Poids); // conversion float en string, 5 car de long, 1 décimale oled.sendString(Poids,4,1); if (clientMqtt.connected()) clientMqtt.loop(); delay(10); } while((poids!=poids_archive) || (poids==0)); // Boucle tant qu'on a pas un poids stable oled.clear(); oled.sendString("R\x82sultat 0.0 kg",0,1); oled.sendString(Poids,9,1); n=true; while(!WifiInitialise){ WifiInitialise=test_wifi(); oled.sendString("Attente wifi ",0,0); if(n) oled.sendString(" ",15,0); else oled.sendString("*",15,0); n=!n; delay(500); } connect_mqtt(); Archive=false; mqtt_dde_poids(); // Procédure d'identification et d'archivage du poids while(!Archive){ if (clientMqtt.connected()) clientMqtt.loop(); else connect_mqtt(); yield(); } // On repart pour un tour init_poids(); connect_mqtt(); } |
Node-red
Deux actions sont gérées par Node-Red, l’interrogation de la base de données pour trouver la personne la plus probable reliée au poids passé, et la mise à jour de la base de données.
Base de données
Elle est constituée (entre autre!) de deux tables :
1 2 3 4 5 6 7 8 9 10 11 12 |
CREATE TABLE `personnes` ( `no_personne` int(11) NOT NULL DEFAULT '0', `id` varchar(255) DEFAULT NULL, `nom` varchar(100) DEFAULT NULL, PRIMARY KEY (`no_personne`) ) ENGINE=InnoDB; CREATE TABLE `poids` ( `heure` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `no_personne` tinyint(1) unsigned DEFAULT NULL, `poids` decimal(5,2) DEFAULT NULL ) ENGINE=InnoDB; |
Interrogation de la base de données
On reçoit un message mqtt « balance/dde_personne », avec le poids qui a été lu sur la balance, et que l’on passe à la création de la requète SQL, puis qu’on injecte à mySql
1 2 3 4 5 6 |
msg.topic="SELECT personnes.no_personne,personnes.nom, ABS(poids-"+msg.payload+") AS difference"; msg.topic+=" FROM personnes"; msg.topic+=" LEFT JOIN poids ON poids.no_personne=personnes.no_personne AND poids.heure>DATE_SUB(NOW(),INTERVAL 7 DAY)"; msg.topic+=" GROUP BY personnes.no_personne, nom, difference" msg.topic+=" ORDER BY -difference DESC, personnes.nom" return msg; |
Mise en forme des résultats (j’utilise le caractère « | » comme séparateur)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function code850(txt){ rpl=[["é",'e'],["ç","c"]] for (var i=0; i<rpl.length; i++) { txt=txt.replace(rpl[i][0],rpl[i][1]); } return txt; } ret=""; var n; for(n=0;n<msg.payload.length;n++){ if(ret!=="") { ret += "|" } ret = ret+msg.payload[n]["no_personne"]+"|"+code850(msg.payload[n]["nom"]); } msg.payload=ret; return msg; |
Émission d’un message mqtt « balance/liste » contenant les personnes triées dans l’ordre de probabilité de correspondance
Mise à jour de la base de données
1 2 3 4 5 6 7 8 |
res=msg.payload.split("|"); if(res[0]=="255"){ // Un nouvel utilisateur, on lui donne un n° au hasard res[0]=Math.floor(Math.random() * 50) + 200; } //msg.payload={"no":res[0],"nom":res[1]}; msg.topic="INSERT INTO poids SET no_personne="+parseInt(res[0])+", poids="+parseFloat(res[1]); return msg; |
Mise à jour:
Le circuit est désormais alimenté sur 2 piles 1.5v AA grâce à un TPS61200 qui permet d’avoir du 3.3v à partir d’une tension de 0.3v à 5.5v.
Et enfin, voici une vidéo de la bête en fonction sur son support définitif
balanceBalance Wifi (esp8266) from fheredero on Vimeo.
c’est toujours amusant de voir que quelqu’un a déjà fait ce qu’on veut faire. merci de publier 🙂
Si ça peut servir c’est cool.
Ola Francois,
Tout d’abord, merci pour cet excellent article!
S’il vous plaît, je voudrais savoir comment les paramètres node-red et sql seraient?
Merci
J’ai mis à jour l’article dans la partie Node-Red. Vous pouvez me dire si c’est suffisant pour vous
Oui, oui, cela a beaucoup aidé.
juste une chose de plus .. essayez de créer DB et ça ne marche pas très bien 🙂
merci mec
J’ai rajouté la définition des tables de la base de données, si ça peut servir.
Bonjour,
Super projet. J’utilise également le HX711 pour un projet différent. SI j’ai bien compris, tu l’alimentes en 3.3V ? As tu observé une dérive dans le temps si tu laisses un poids constamment sur la bascule ?
Cordialement,
Aurélien
Oui, c’est bien le cas.
Ici je boucle tant que je n’ai pas 2 mesures identiques, puis je conserve ce poids. Mais effectivement, dans mes tests préalables, j’ai constaté cette dérive dans le temps. Et tout le circuit est en 3.3v.