Visualisation en live de température (DS18B20) sur Processing

Bonjour à tous !

Je me permet de partager la première étape de ma réalisation.
Toujours dans l'optique de développer une domotique fait maison, je cherchais à voir en temps réel la température de mes DS18B20 sur mon PC. Je voulais me servir de ces courbes pour régler un chauffage via PID et pouvoir apprécier l'ammortissement etc etc .

J'ai choisi Processing pour son IDE trés basique qui rappelle beaucoup celui de l'arduino . Il y a surement mieux mais pour l'instant il fait le job :wink: !

Comment ça se passe ? c'est assez simple !

Première partie : L'Arduino

A l'aide d'un Uno, je récupère la température d'une pièce de ma maison et je l'envoie toutes les minutes sur le port série. J'utilise aussi un LCD sur lequel j'affiche la température , l'heure via un DS1307 et le maximum/minimum en alternance :wink: !

Un petit aperçu de l'affichage :

Deuxième partie : Processing

Le soft récupère les données sur le port série et trace la partie graphique ( repères , légendes , courbes ) et gère aussi la partie sauvegarde des données via un .txt .

Aperçu :

Plus de détails ...

Concernant l'Arduino, rien de spécial, je me sers du DS1307 pour plusieurs choses :

  • stocker la date de démarrage du module qui me sert à nommer le .txt sous Processing avec une suite de chiffres qui correspondent à l'année , au mois , au jour , à l'heure , aux minutes et aux secondes
    ( ex : 20160413115932.txt) pour le 13 avril 2016 à 11:59:32
  • je fais une petite salade pour compter le nombre de minutes écoulées, cette donnée me sert d'abscisse pour tracer mes courbes sur Processing

Pour Processing, à la première réception, je crée le .txt , et ensuite je récupère la température et le nombre de secondes écoulées, ça me fait à chaque acquisition un point (x,y) et je trace donc une nouvelle ligne en me servant de ces coordonées et des précédents .

Concernant le graphique, pour l'instant , c'est un "peu" dynamique mais je n'ai pas encore finaliser le truc .
En gros pour avoir une courbe propre et bien affichée, il faut veiller à avoir au moins un pixel égal à la résolution de la sonde car j'utilise un facteur pour mettre à l'échelle ma température et si on ne respecte pas cette règle , ça fait un décalage ..
Ici pour une plage de 50°C j'ai 400 pixels , ce qui donne une résolution de 0,125°C/pixel et j'ai donc choisi cette résolution pour mes DS18B20 :wink: !

J'ai mis une petite vidéo sur ma page (j'arrive pas à intégrer de vidéos ici ) pour voir comment ça marche . Sur cette vidéo, j'envoie des données toutes les secondes pour que ça aille plus vite !

C'est une première version , il y a plusieurs choses qui sont à peaufiner :wink:

Je poste les codes dans les posts suivants !

Le code Arduino

#include <Wire.h>  
#include <LiquidCrystal_I2C.h>    // lib pour le LCD
#include <OneWire.h>
#include <DallasTemperature.h>  // lib pour les DS18B20
#include "RTClib.h" // lib pour le RTC
#include <DataTimeStrings.h>
#include <SPI.h>
 
 
 
 
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Décla du LCD
#define ONE_WIRE_BUS_PIN 12  // Broche pour les DS
OneWire oneWire(ONE_WIRE_BUS_PIN); // décla broche utilisée
DallasTemperature sensors(&oneWire); // décla sensor
int DSPOWER = 13;  // +5V pour DS18B20
DeviceAddress TempX = {  
  0x28, 0xC7, 0xDF, 0x51, 0x07, 0x00, 0x00, 0x18 }; // adresse de mon DS18B20
RTC_DS1307 rtc; // horloge
float TX; // valeur de la température
float MIN = 100; // valeur initiale du minimum
float MAX = 0; // valeur initiale du maximum
boolean BLINK = false; // flag pour le cligotement max/min
boolean DATAS = false; //flag pour l'envoi sur le port série
 
// Variables utilisées pour le "chronométrage"
int minRun = 0 ;
int avant ;
int maintenant;
int tps;
int chrono;
char buff2[30]=""; // sert de nom de fichier pour la sauvegarde des données sur Processing
//Le format est :annéemoisjourheureminuteseconde
 
void setup()   /*----( SETUP: RUNS ONCE )----*/
{
  Serial.begin(115200);
  // Décla de mon "alim" pour le DS
  pinMode(DSPOWER,OUTPUT);
  digitalWrite(DSPOWER,HIGH);
  delay(100);
 
  sensors.begin(); //initialisation du DS
  delay(100);
  lcd.begin(12,2);   //initialisation du LCD
  sensors.setResolution(TempX, 10); //initialisation de la résolution de la sonde
  delay(100);
  rtc.begin(); //initialisation de l'horloge
  delay(100);
 
  // Je prends l'heure pour me permettre de faire un chrono à partir de ce temps là
  DateTime TOP = rtc.now();
  delay(50);
  avant = TOP.minute();
 
  // nom du fichier de sauvegarde
  sprintf(buff2,"%02d%02d%02d%02d%02d%02d",TOP.year(),TOP.month(),TOP.day(),TOP.hour(),TOP.minute(),TOP.second());
 
}
 
 
void loop()  
{
  // on regarde l'heure actuelle
  DateTime now = rtc.now();  
  delay(50);  
  char buff[50]="";  
  // préparation pour l'affichage de l'heure sur le LCD
  sprintf(buff, "%02d:%02d", now.hour(), now.minute());
 
  //flag qui change tous les 2 secondes .. ou plutôt selon les secondes paires ou impaires
  BLINK = false;
  if(now.second()%2 == 0)  {  
    BLINK = true ;
  }
 
  //Acquisition de la température
  sensors.requestTemperatures();    
  delay(50);
  TX = sensors.getTempC(TempX);    
  delay(50);
 
  //Détermination du maximum/minimum
  if(TX > MAX) {
    MAX = TX ;
  }
  if(TX < MIN) {
    MIN = TX ;
  }
 
  //Affichage sur le LCD
 
  //lcd.setCursor(colonne,ligne)
  lcd.setCursor(0,0);
  lcd.print("Temp:");
  lcd.setCursor(0,1);
  lcd.print(TX,1);
  lcd.setCursor(4,1);
  lcd.print("\337C");
 
  // Affichage de la valeur max ou min selon que les secondes soient paires ou impaires
  if(BLINK == false)
  {    
    lcd.setCursor(8,0);
    lcd.print("-:");
    lcd.setCursor(10,0);
    lcd.print(MIN,1);
    lcd.setCursor(14,0);
    lcd.print("\337C");
  }
 
  if(BLINK == true)
  {
    lcd.setCursor(8,0);
    lcd.print("+:");
    lcd.setCursor(10,0);
    lcd.print(MAX,1);
    lcd.setCursor(14,0);
    lcd.print("\337C");
  }
 
  //Mis en page
  lcd.setCursor(7,0);
  lcd.print("|");
  lcd.setCursor(7,1);
  lcd.print("*");
 
 
  lcd.setCursor(8,1);
  lcd.print("*");
  lcd.setCursor(9,1);
  lcd.print(buff);
  lcd.setCursor(14,1);
  lcd.print("*");
  lcd.setCursor(15,1);
  lcd.print("*");
 
 
  // j'envoie les données sur le port série dès que je détecte le changement de minute
  if(now.minute()!= minRun)             {
    DATAS = true;
  }
  if(now.minute()%1 == 0 && DATAS == true)
  {
    int maintenant = now.minute();
    tps = maintenant - avant ; // je calcule la différence entre la précédente mesure et l'actuelle
 
 
    if(tps<0){
      tps = 60 - abs(tps);
    }
 
    Serial.print(TX);
    Serial.print(" ");
    Serial.print(chrono);
    Serial.print(" ");
    Serial.print(buff2);
    Serial.println(" ");
 
    DATAS = false ;
    minRun = now.minute();
 
    chrono = chrono + tps ;  // j'ajoute la différence avec les précédentes
    avant = maintenant ;
 
  }
 
  delay(50); // J'aime attendre
}

Le code Processing :

import processing.serial.*;
Serial myPort;

// variables pour les températures
float f_tempX = 0;
float f_tempX_MAX = 0;
float f_tempX_MIN = 100;
float f_tempY =0;    
float f_tempY_MAX = 0;
float f_tempY_MIN = 100;
float f_tempZ =0;    
float f_tempZ_MAX = 0;
float f_tempZ_MIN = 100;

//variables pour le temps qui passe
float delai;
int i_delai;
int i_t_stockage;

// canal pour la sauvegarde des données
PrintWriter FILE;
String fichier;

// variables pour l'affichage des données
int xdata = 55;
int ydata = 50;
int offsetdata = 20;

// variables pour le tracage des repères , c'est plus ou moins "dynamique" ^^ !
int x_repA = 300;      
int y_repA = 36;
int x_repB = x_repA;    
int y_repB = 436 ;
int x_repC = 1100;      
int y_repC = y_repB;
int zero_ordonnee = (y_repB - y_repA)/5 ;
int x_leg = x_repA -20;
int y_leg = 20;
int intleg = (y_repB - y_repA)/20;

// Echelle complete , ici 50°C : de -10 à 40
float f_maxEchelle = 50;
float f_facteur = (y_repB - y_repA)/f_maxEchelle ; // facteur pour caler la température sur le repère
//int x1_TX=0; int y1_TX=0; int x2_TX=0; int y2_TX=0;
//int x1_TY=0; int y1_TY=0; int x2_TY=0; int y2_TY=0;
//int x1_TZ=0; int y1_TZ=0; int x2_TZ=0; int y2_TZ=0;
float x1_TX=x_repA, y1_TX=y_repB, x2_TX=0, y2_TX=0;
float x1_TY=x_repA, y1_TY=y_repB, x2_TY=0, y2_TY=0;
float x1_TZ=x_repA, y1_TZ=y_repB, x2_TZ=0, y2_TZ=0;

int i_tps = 0;
int i_limit = 0;
int interval = 20;
int i;
int offsetaffichage=0;

// Flags
boolean tracage = false; // tracage des courbes
boolean stockage = false; // sauvegarde des données
boolean creation_fichier = false;

void setup()
{
 size(1200, 600);//largeur , hauteur)
 textAlign(LEFT);


 delay(100);
 printArray(Serial.list());  // affiche les ports dispo

 try {
   myPort = new Serial(this, Serial.list()[0], 115200); // je choisis le premier
   myPort.bufferUntil('\n');
 }
 catch(Exception e) {
   println("soucis");
 }

 background(255, 255, 255); // couleur du fond , ici blanc
}
void draw()
{
 // refresh datas
 stroke(255, 255, 255); // contour
 fill(255, 255, 255); // intérieur
 rect(0, 0, xdata*5 - 35, ydata*7);


 fill(0, 0, 0); // legende datas
 if (i_delai>5)
 {

   if (f_tempX < f_tempX_MIN) {
     f_tempX_MIN = f_tempX;
   }
   if (f_tempX > f_tempX_MAX) {
     f_tempX_MAX = f_tempX;
   }
   if (f_tempY < f_tempY_MIN) {
     f_tempY_MIN = f_tempY;
   }
   if (f_tempY > f_tempY_MAX) {
     f_tempY_MAX = f_tempY;
   }
   if (f_tempZ < f_tempZ_MIN) {
     f_tempZ_MIN = f_tempZ;
   }
   if (f_tempZ > f_tempZ_MAX) {
     f_tempZ_MAX = f_tempZ;
   }
 }

 //datas pour 1 sonde mais on peut en rajouter 2 voir plus mais faut rajouter du code
 text("Home :", xdata-40, ydata);
 //  text("YY :", xdata-40, ydata+offsetdata);
 //  text("ZZ :", xdata-40, ydata+(2*offsetdata));
 text("Tps  :", xdata-40, ydata+(3*offsetdata));

 text("Home", xdata+30, ydata+(5*offsetdata));
 //  text("YY", xdata+80, ydata+(5*offsetdata));
 //  text("ZZ", xdata+130, ydata+(5*offsetdata));
 text("MIN:", xdata-40, ydata+(6*offsetdata));
 text("MAX:", xdata-40, ydata+(7*offsetdata));

 text(f_tempX_MIN, xdata+30, ydata+(6*offsetdata));
 //  text(f_tempY_MIN, xdata+80, ydata+(6*offsetdata));
 //  text(f_tempZ_MIN, xdata+130, ydata+(6*offsetdata));

 text(f_tempX_MAX, xdata+30, ydata+(7*offsetdata));
 //  text(f_tempY_MAX, xdata+80, ydata+(7*offsetdata));
 //  text(f_tempZ_MAX, xdata+130, ydata+(7*offsetdata));

 text("°C", xdata+50, ydata);
 // text("°C", xdata+50, ydata+offsetdata);
 // text("°C", xdata+50, ydata+(2*offsetdata));
 text("min", xdata+50, ydata+(3*offsetdata));
 fill(0, 0, 255);
 text(f_tempX, xdata, ydata);  
 fill(0, 255, 0);
 //  text(f_tempY, xdata, ydata+offsetdata);    
 fill(255, 0, 0);
 //  text(f_tempZ, xdata, ydata+(2*offsetdata));
 fill(0, 0, 0);
 text(i_delai, xdata, ydata+(3*offsetdata));

 strokeWeight(2); // epaisseur courbe
 //courbe sonde 1
 stroke(0, 0, 255);

 x2_TX = x_repB + i_delai-offsetaffichage*800;
 y2_TX = y_repB - zero_ordonnee - (f_tempX * f_facteur);    
 line(x1_TX, y1_TX, x2_TX, y2_TX);    
 x1_TX = x2_TX ;
 y1_TX = y2_TX ;
 //courbe sonde 2
 /*  stroke(0, 255, 0);
  x2_TY = x_repB+i_delai-offsetaffichage*800;
  y2_TY = y_repB - zero_ordonnee - (f_tempY * f_facteur);    
  line(x1_TY, y1_TY, x2_TY, y2_TY);    
  x1_TY = x2_TY ;
  y1_TY = y2_TY ;
  //courbe sonde 3
  stroke(255, 0, 0);
  
  x2_TZ = x_repB+i_delai-offsetaffichage*800;
  y2_TZ = y_repB - zero_ordonnee - (f_tempZ * f_facteur);    
  line(x1_TZ, y1_TZ, x2_TZ, y2_TZ);    
  x1_TZ = x2_TZ ;
  y1_TZ = y2_TZ ;
  */

 stroke(0, 0, 0);
 strokeWeight(2);
 line(x_repA, y_repA-15, x_repB, y_repB+15);

 strokeWeight(1);

 if (tracage == false) {
   tracer();
 }


 //creation du txt pour la sauvegarde des données
 if (creation_fichier == false && fichier!= null)
 {
   println(fichier);
   FILE = createWriter(fichier);
   FILE.println("TempX TempY TempZ tps(sec)");

   creation_fichier = true;
 }

 // ecriture des données dans le txt
 if (stockage == true && creation_fichier == true)
 {

   FILE.print(f_tempX);
   FILE.print(" ");
   FILE.print(f_tempY);
   FILE.print(" ");
   FILE.print(f_tempZ);
   FILE.print(" ");
   FILE.println(i_delai);
   FILE.flush();

   stockage = false;

   println("sauvegarde"); // On se rassure
 }

 // Remet les choses comme il faut une fois la premiere "page" du graphe rempli

 if (i_delai >= 800*(offsetaffichage+1))
 {
   background(255, 255, 255);
   x1_TX=x1_TX - 800;
   x1_TY=x1_TY - 800;
   x1_TZ=x1_TZ - 800;
   offsetaffichage ++;
   // i_delai2 = 0;

   // refresh legende x
   stroke(255, 255, 255); // contour
   fill(255, 255, 255); // intérieur
   rect(x_leg-30, 0, 45, y_repB+25);
   // refresh legende y
   stroke(255, 255, 255); // contour
   fill(255, 255, 255); //intérieur
   rect(x_repA+10, y_repC+17, 850, 20);

   tracage = false;
 }
}

// récupération des données du port série

void serialEvent(Serial myPort)
{
 println("test");
 String Temperatures = myPort.readString();
 String[] Sonde = split(Temperatures, ' ');
 f_tempX = Float.parseFloat(Sonde[0]);
 // f_tempY = Float.parseFloat(Sonde[1]);
 // f_tempZ = Float.parseFloat(Sonde[2]);
 delai = Float.parseFloat(Sonde[1]);



 i_delai=int(delai);
 //i_delai = i_delai * 100;
 //   println(i_delai);
 //println(Sonde[4]);

 i_t_stockage= i_delai;
 println(i_delai);
 //  println(f_tempX);
 stockage = true;



 //creation du nom de fichier du txt
 String nomFichier ="";
 nomFichier += Sonde[2]; // récupere le buff du setup de l'arduino ;)
 nomFichier += ".txt";    
 fichier = nomFichier;


 nomFichier = "";
}

// reperes, legende ,etc
void tracer()
{
 int dec = -5;
 tracage = true;
 //legende repere x
 fill(0, 0, 0);  
 text("(°C)", x_leg-14, y_repB- (21*intleg)-dec);
 text("40", x_leg-15, y_repB- (20*intleg)-dec);
 text("37,5", x_leg-15, y_repB- (19*intleg)-dec);
 text("35", x_leg-15, y_repB- (18*intleg)-dec);
 text("32,5", x_leg-15, y_repB- (17*intleg)-dec);
 text("30", x_leg-15, y_repB- (16*intleg)-dec);
 text("27,5", x_leg-15, y_repB- (15*intleg)-dec);
 text("25", x_leg-15, y_repB- (14* intleg)-dec);
 text("22,5", x_leg-15, y_repB- (13* intleg)-dec);
 text("20", x_leg-15, y_repB- (12*intleg)-dec);
 text("17,5", x_leg-15, y_repB- (11*intleg)-dec);
 text("15", x_leg-15, y_repB- (10*intleg)-dec);
 text("12,5", x_leg-15, y_repB- (9*intleg)-dec);
 text("10", x_leg-15, y_repB- (8*intleg)-dec);
 text("7,5", x_leg-15, y_repB- (7*intleg)-dec);
 text("5", x_leg-15, y_repB- (6*intleg)-dec);
 text("2,5", x_leg-15, y_repB- (5* intleg)-dec);
 text("0", x_leg-15, y_repB- (4*intleg)-dec);
 text("-2,5", x_leg-15, y_repB- (3*intleg)-dec);
 text("-5", x_leg-15, y_repB- (2*intleg)-dec);  
 text("-7,5", x_leg-15, y_repB- (1*intleg)-dec);
 text("-10", x_leg-15, y_repB- (0*intleg)-dec);
 text("(Heure)", x_repA +800 +30, y_repB+ 30-dec);


 //repere secondaire
 stroke(200, 200, 200);
 strokeWeight(1);

 for (i=0; i<21; i++)
 {
   line(x_leg+15, y_repB- (i*intleg), x_repC +15, y_repB- (i*intleg));
 }
 for (i=1; i<14; i++)
 {
   line(x_repA+60*i, y_repB+15, x_repA+60*i, y_repA-15);
   text((i)+offsetaffichage*13, x_repA+60*i -3, y_repB+ 30);
 }

 // repere
 stroke(0, 0, 0);
 strokeWeight(2);
 line(x_repA, y_repA-15, x_repB, y_repB+15);
 //line(x_repB, y_repB, x_repC, y_repC);
 line(x_repA-5, y_repB-zero_ordonnee, x_repC+15, y_repB-zero_ordonnee);
}

Voilà un exemple pour ma station de surveillance de température de ma Honda :

bonjour je suis débutant en arduino et je ne connais pas processing. Je viens de le charger. Que fait on après et comment compiler le code.
Merci de ton aide.

Re bonjour je viens de te lire sur le link..http://nonoduino.blogspot.fr/. Ton projet est très intéressant car j'en ai similaire. J'ai 5 sondes DS18B20 dans une cave à vins.
UNE en ambiance.
UNE sur le condenseur pour couper la clim au delà des 55°C car celle-ci continu de tourner sans faire de froid = jusqu'à 18KwH pour ne pas faire de froid. (je suis en froid avec le fournisseur qui ne veut pas modifier sa clim ce qui m'a amené à faire ce projet).
UNE dans le grenier.
UNE dans le caisson ou est la clim car celle-ci ne doit pas geler l'hiver. ( à 1°C je mettrai en route la ventilation coté cave qui me ramèneras de l'air à 12°C dans la clim et qui remonteras la T° dans le caisson par déperdition). (pour l'instant, l'hiver c'est un radiateur de 800w avec un thermostat électronique qui régule à 0°+/- 0.4°)
UNE dans la gaine pour voir quand la clim à trop chaud que le coté froid n'évolue plus vers le froid.
Pour l'instant je gère la T° cave en tout ou rien (c'est cette sonde qui gérera le radiateur). Comme tu as pu le constater les DS18B20 sont assez sensibles et la T° affichée oscille souvent entre 2/10eme. Alors je voudrais faire du PID. J'en ai trouvé un sur le net en ToR mais je n'arrives pas à le faire fonctionner. Je suis donc intéressée par ton projet. Merci de ton aide.

Le ToR c'est pas de la PID :wink: !

Concernant mon programme Processing , normalement ça marche tout seul . Le seul soucis c'est si il y a plusieurs ports dispo , pour l'instant je prends par défaut le premier de la liste . Ca fait des modifications que je veux apporter .

Il faut aussi que tu envoies sur le port série avec ton Arduino un "message" comme celui de mon code :

    Serial.print(TX);   // ma température 
    Serial.print(" ");  // espace , trés important car c'est ce qui me permet de séparer les données sur //Processing
    Serial.print(chrono);  // temps écoulé 
    Serial.print(" ");
    Serial.print(buff2); // ne sert qu'une fois pour créer le nom du fichier .txt de sauvegarde 
    Serial.println(" ");

En gros , sur le port série , tu dois avoir un truc dans le genre :

23,2 1 20160422173432
23,2 2 20160422173432
23,3 3 20160422173432
23,3 4 20160422173432
23,3 5 20160422173432

etc etc