Les Carnets de Byfeel domotique , Objets connectés , DIY , Programmation, Nouvelles Technologies ….
horloge DIY

Horloge avec notification sous jeedom ( partie 2 )

Pour faire suite à mon precedent article , sur la fabrication d’une horloge numérique . Je vais dans cette deuxième partie illustrer comment la rendre plus dynamique et interagir avec Jeedom.

Pour quelques euros de plus ( 2€ pour le module DHT et 2€ pour le module IR ).

Installation d’un capteur de temperature / humidité :

Pour ce projet j’ai utilisé un capteur de type DHT 11.

capteur DHT 11
capteur DHT 11

Pour le branchement , il suffit de relier la sortie signal à une broche d’entrée de l’Arduino .

Dans notre exemple , j’ai choisi la broche digital numero 2 ( D2 ).

Le VCC au 5v de l’arduino , et le GCC à la broche GCC de l’arduino.

Pour utiliser le capteur , on peut utiliser la bibliothèque , mais pour des raisons de place , j’ai préféré utiliser un script minimaliste que j’ai récupéré sur le site carnet du maker .

La lecture de la temperature et de l’humidité , se fait par la’apppel de la fonction : readDHT11(BROCHE_CAPTEUR, &temperature, &humidity);

On intègre ensuite l’affichage de la temperature à la liste des notifications en cours.

Pour afficher les notifications dans l’ordre , j’ai choisi d’utiliser la fonction Switch , qui permet en fonction d’un compteur de faire defiler les différentes notifications.

  • Affichage de la date du jour
  • Affichage de la temperature / humidité

Ce qui donne le script complet suivant :

// Projet horloge avec Notif
// janvier 2018


// Chargement des librairies
// gestion affichage matrice
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
// librairie pour horloge
#include <DS3231.h>

// Parametrage matrice  ( Pin Arduino ou est branché la matrice )
#define MAX_DEVICES 4 // ( nombre de matrice )
#define CLK_PIN   10
#define DATA_PIN  12
#define CS_PIN    11


// initialisation de la matrice
MD_Parola P = MD_Parola(DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);

// parametrage horloge
DS3231 clock;
RTCDateTime dt;
// Commande a devalider pour reglage horloge premiere fois Manual (YYYY, MM, DD, HH, II, SS
//clock.setDateTime(2017, 10, 27, 16, 19, 00);

// Capteur temp / hum -- DHT 11
/** Broche "DATA" du capteur */
const byte BROCHE_CAPTEUR = 2;
/* Code d'erreur de la fonction readDHT11() */
const byte DHT_SUCCESS = 0;        // Pas d'erreur
const byte DHT_TIMEOUT_ERROR = 1;  // Temps d'attente dépassé
const byte DHT_CHECKSUM_ERROR = 2; // Données reçues erronées
float temperature, humidity;

// variable pour tempo notif 
long temps;
unsigned int tempoNotif = 40; // temps en secondes

//variable systemes
#define  BUF_SIZE  30
char Message[BUF_SIZE] ;
char charVal[BUF_SIZE] ;
char charVal2[BUF_SIZE];
int cpt=0;

void setup() {
    //init affichage
  P.begin();
    // init horloge
  clock.begin();
   /* Place la broche du capteur DHT en entrée avec pull-up */
  pinMode(BROCHE_CAPTEUR, INPUT_PULLUP);

}

void loop() {
  dt = clock.getDateTime();
  // Affichage d'un texte simple
 if (P.displayAnimate())
 {
  P.displayText(clock.dateFormat("H:i", dt), PA_CENTER,0,0, PA_PRINT, PA_NO_EFFECT); 
 
 // notif auto
    // temp et date
    if ((millis()-temps)>( tempoNotif*1000L))
    {
  switch(cpt)
     {
        case 0 :
          {
             P.displayText(Message, PA_LEFT, 40, 1500, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
             strcpy(Message,clock.dateFormat("   --> d.m.y", dt));
             ++cpt;
             break;
          }
          case 1 :
          {
            readDHT11(BROCHE_CAPTEUR, &temperature, &humidity);
            P.displayText(Message, PA_LEFT, 40, 1500, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
          dtostrf(temperature, 3, 0, charVal);
          dtostrf(humidity, 3, 0, charVal2);
          sprintf(Message,"   --> temp.: %s C  Hum.: %s %%",charVal,charVal2);    
          //sprintf(message,"test temp");
          //strcpy(curMessage,message);
             cpt=0;
             temps=millis();
             break;
          }
     }

    } // fin notif
    

    
    }
 
 }


/**
 * Lit la température et le taux d'humidité mesuré par un capteur DHT11.
 *
 * @param pin Broche sur laquelle est câblée le capteur.
 * @param temperature Pointeur vers la variable stockant la température.
 * @param humidity Pointeur vers la variable stockant le taux d'humidité.
 * @return DHT_SUCCESS si aucune erreur, DHT_TIMEOUT_ERROR en cas de timeout, ou DHT_CHECKSUM_ERROR en cas d'erreur de checksum.
 */
byte readDHT11(byte pin, float* temperature, float* humidity) {
  
  /* Lit le capteur */
  byte data[5];
  byte ret = readDHTxx(pin, data, 18, 1000);
  
  /* Détecte et retourne les erreurs de communication */
  if (ret != DHT_SUCCESS) 
    return ret;
    
  /* Calcul la vraie valeur de la température et de l'humidité */
  *humidity = data[0];
  *temperature = data[2];

  /* Ok */
  return DHT_SUCCESS;
}

/**
 * Fonction bas niveau permettant de lire la température et le taux d'humidité (en valeurs brutes) mesuré par un capteur DHTxx.
 */
byte readDHTxx(byte pin, byte* data, unsigned long start_time, unsigned long timeout) {
  data[0] = data[1] = data[2] = data[3] = data[4] = 0;
  // start_time est en millisecondes
  // timeout est en microsecondes
 
  /* Conversion du numéro de broche Arduino en ports / masque binaire "bas niveau" */
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *ddr = portModeRegister(port);   // Registre MODE (INPUT / OUTPUT)
  volatile uint8_t *out = portOutputRegister(port); // Registre OUT (écriture)
  volatile uint8_t *in = portInputRegister(port);   // Registre IN (lecture)
  
  /* Conversion du temps de timeout en nombre de cycles processeur */
  unsigned long max_cycles = microsecondsToClockCycles(timeout);
 
  /* Evite les problèmes de pull-up */
  *out |= bit;  // PULLUP
  *ddr &= ~bit; // INPUT
  delay(100);   // Laisse le temps à la résistance de pullup de mettre la ligne de données à HIGH
 
  /* Réveil du capteur */
  *ddr |= bit;  // OUTPUT
  *out &= ~bit; // LOW
  delay(start_time); // Temps d'attente à LOW causant le réveil du capteur
  // N.B. Il est impossible d'utilise delayMicroseconds() ici car un délai
  // de plus de 16 millisecondes ne donne pas un timing assez précis.
  
  /* Portion de code critique - pas d'interruptions possibles */
  noInterrupts();
  
  /* Passage en écoute */
  *out |= bit;  // PULLUP
  delayMicroseconds(40);
  *ddr &= ~bit; // INPUT
 
  /* Attente de la réponse du capteur */
  timeout = 0;
  while(!(*in & bit)) { /* Attente d'un état LOW */
    if (++timeout == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
  }
    
  timeout = 0;
  while(*in & bit) { /* Attente d'un état HIGH */
    if (++timeout == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
  }

  /* Lecture des données du capteur (40 bits) */
  for (byte i = 0; i < 40; ++i) {
 
    /* Attente d'un état LOW */
    unsigned long cycles_low = 0;
    while(!(*in & bit)) {
      if (++cycles_low == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
    }

    /* Attente d'un état HIGH */
    unsigned long cycles_high = 0;
    while(*in & bit) {
      if (++cycles_high == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
    }
    
    /* Si le temps haut est supérieur au temps bas c'est un "1", sinon c'est un "0" */
    data[i / 8] <<= 1;
    if (cycles_high > cycles_low) {
      data[i / 8] |= 1;
    }
  }
  
  /* Fin de la portion de code critique */
  interrupts();
 
  /*
   * Format des données :
   * [1, 0] = humidité en %
   * [3, 2] = température en degrés Celsius
   * [4] = checksum (humidité + température)
   */
   
  /* Vérifie la checksum */
  byte checksum = (data[0] + data[1] + data[2] + data[3]) & 0xff;
  if (data[4] != checksum)
    return DHT_CHECKSUM_ERROR; /* Erreur de checksum */
  else
    return DHT_SUCCESS; /* Pas d'erreur */
}

La fonction dtostrf(temperature, 3, 0, charVal); permet de convertir une valeur de type float ( temperature ) , en une chaine de caractère ( charval ) autorisant 3 chiffres maximum avant la virgule et zero décimale .

[adsense]

Un peu d’interaction …

Comme expliqué plus haut , le but est de rendre cette horloge un peu plus dynamique . Je l’ai conçu dans le but de la mettre dans la salle de jeux des enfants , qui se trouve à l’étage. J’aimerais , pouvoir envoyer une notification afin de les faire descendre quand l’heure du repas ou du gouter arrive.

La salle de jeux est équipée , d’un module Orvibo , afin de pouvoir piloter la TV à distance. J’ai donc décider d’intégrer un module IR à mon horloge , afin de pouvoir déclencher à distance des messages prédéfinis.

Pour cela j’utilise le module IR suivant :

Module IR Receiver

il est equipé de trois broche ( GND , VCC et S pour Signal )

Selon les modèles , les broches sont représentés par G ( gnd ) ,R ( 5v) et Y(S) ou un moins du coté de la broche GND et un S du coté du signal.

Une fois les broches reperés , il suffit de le brancher a l’Arduino sur une Entrée Digital ( ici j’ai choisi la numero 7 ) . si le capteur est bien branché , la led clignote en rouge à chaque reception d’un code infra rouge.

Pour tester votre module IR , et ainsi repérer les différents codes à utiliser , ci dessous un sketch , juste pour contrôler les codes reçus et les afficher dans le moniteur série.

#include "IRremote.h"

int receiver = 7; // Singnal PIN du module IR

/*-----( Declare objects )-----*/
IRrecv irrecv(receiver);     // create instance of 'irrecv'
decode_results results;      // create instance of 'decode_results'

/*-----( Function )-----*/
void translateIR() // Actions en fonction des codes reçus
{

  switch(results.value)

  {
  case 0xFFA25D: Serial.println("POWER"); break;
  case 4001918335: Serial.println("NOTIF 1"); break;
  case 1255008989: Serial.println("NOTIF 2"); break;
  case 3533356899: Serial.println("NOTIF 3"); break;
  case 0xFFFFFFFF: Serial.println(" REPEAT");break;  

  default: 
    Serial.println(" Valeur bouton non reconnu   ");

  }// End Case

  delay(500); // Do not get immediate repeat


} //END translateIR
void setup()   /*----( SETUP: RUNS ONCE )----*/
{
  Serial.begin(9600);
  Serial.println("Module IR : Affichage des valeurs Boutons"); 
  irrecv.enableIRIn(); // Init. du module IR

}/*--(end setup )---*/

// Boucle principale
void loop()  
{
  if (irrecv.decode(&results)) // have we received an IR signal?

  {
    translateIR(); 
    Serial.println(results.value);  
    irrecv.resume(); // receive the next value
  }  
}

 

Comment j’ai procédé :

  • Tout d’abord , j’ai enregistré dans mon Orvibo , 4 codes d’une télécommande dont je ne me servais pas.
  • Puis j’ai installé mon Orvibo face à mon Arduino , avec ce sketch installé .
  • J’ai exécuté les 4 codes depuis l’orvibo et les 4 valeurs de ces codes ont été listés dans le Moniteur Série.
  • Afin d’être sur que le bon code soit reconnu à chaque appuie , j’ai ensuite ajouté une ligne de code à la fonction case :
    case 4001918335:  Serial.println(« NOTIF 1« ); break;
    A chaque envoie du meme code , avec l’ORVIBO , mon module reconnaissait bien ce code.
Exemple : Commande Notif enregistré dans le module ORVIBO

Une fois mes codes en mains , il ne reste plus qu’a finaliser mon programme.

Code source complet :

// Projet horloge avec Notif
// janvier 2018


// Chargement des librairies
// gestion affichage matrice
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
// librairie pour horloge
#include <DS3231.h>
// librairie infrarouge
#include "IRremote.h"

// Parametrage matrice  ( Pin Arduino ou est branché la matrice )
#define MAX_DEVICES 4 // ( nombre de matrice )
#define CLK_PIN   10
#define DATA_PIN  12
#define CS_PIN    11


// initialisation de la matrice
MD_Parola P = MD_Parola(DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
uint8_t scrollSpeed = 30;    // default frame delay value
textEffect_t scrollEffect = PA_SCROLL_LEFT;

// parametrage horloge
DS3231 clock;
RTCDateTime dt;
// Commande a devalider pour reglage horloge premiere fois Manual (YYYY, MM, DD, HH, II, SS
//clock.setDateTime(2017, 10, 27, 16, 19, 00);

// Capteur temp / hum -- DHT 11
/** Broche "DATA" du capteur */
const byte BROCHE_CAPTEUR = 2;
/* Code d'erreur de la fonction readDHT11() */
const byte DHT_SUCCESS = 0;        // Pas d'erreur
const byte DHT_TIMEOUT_ERROR = 1;  // Temps d'attente dépassé
const byte DHT_CHECKSUM_ERROR = 2; // Données reçues erronées
float temperature, humidity;

 // Signal Pin of IR receiver to Arduino Digital Pin 11
int receiver = 7;
/*-----( Declare objects )-----*/
IRrecv irrecv(receiver);     // create instance of 'irrecv'
decode_results results;

//LED
int LED = 3;

// variable pour tempo notif 
long temps;
unsigned int tempoNotif = 600; // temps en secondes

//variable systemes
#define  BUF_SIZE  30
char Notif[BUF_SIZE];
char charVal[BUF_SIZE] ;
char charVal2[BUF_SIZE];
int cpt=0;
int cpta=0;
boolean Alert=false;



void setup() {
    //init affichage
  P.begin();
    // init horloge
  clock.begin();
   /* Place la broche du capteur DHT en entrée avec pull-up */
  pinMode(BROCHE_CAPTEUR, INPUT_PULLUP);
   
 // init recepteur IR
 irrecv.enableIRIn();
 //init LED
 pinMode(LED, OUTPUT);
 
// affichage Programme chargé
 P.print("Ready ...");
delay(1000);
}

void loop() {
dt = clock.getDateTime();

   // infrarouge
     if (irrecv.decode(&results)) // have we received an IR signal?
  {
    switch(results.value)
    {
  case 4001918335:  // Notif1
    {
      Alert=true;
      sprintf(Notif,"A Table !");
     break;
    }
  case 4039382595:    // Notif 2
  {
      Alert=true;
      sprintf(Notif,"C'est l'heure de se coucher !");
     break;
    } 
  case 1110255125:   // Notif 3
   {
      Alert=true;
      sprintf(Notif,"Tout le monde descend !!");
     break;
    }  
  case 1386468383:    //notif 4
    {
     Alert=true;
    sprintf(Notif,"On Sonne au Portail !!!"); 
    break; 
    }
  case 2538093563:     break;  //Notif 5
   case 3238126971:     break;  //Notif 6
     case 0xFFA25D: {    
            Alert=true;
          sprintf(Notif,"Test Notif !!!"); 
           break;  //Notif 6
           }
    }
      irrecv.resume();
  } 
  
if (P.displayAnimate())
{
  if (Alert) {
      digitalWrite(LED, HIGH);
      P.setIntensity(MAX_INTENSITY); // intensité au max
     switch(cpta)
     {
      case 0 : 
      {
        P.displayText("ALERT", PA_LEFT, scrollSpeed, 1000, PA_OPENING_CURSOR, PA_BLINDS);
       ++cpta;
        break;
      }
      case 1 : 
      {
      P.displayText(Notif, PA_LEFT, scrollSpeed,2000, PA_SCROLL_LEFT, PA_SCROLL_LEFT); 
      //strcpy(Message,"test message");
       ++cpta;
       break;
      }
      case 2 :
      {
        P.displayText(Notif, PA_LEFT, scrollSpeed,1000, PA_SCROLL_LEFT, PA_CLOSING_CURSOR); 
        cpta=0;
       Alert=false;
       break;
      }
    }
    
  }
  else {
    digitalWrite(LED, LOW);
    if (dt.hour >6 and dt.hour<23) {
    P.setIntensity(3);
  // Affichage heure
    strcpy(Notif,clock.dateFormat("H:i", dt));
  
    P.displayText(Notif, PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
    //notif auto
    if ((millis()-temps)>(tempoNotif*1000L))
    {
     switch(cpt)
     {
        case 0 :
          {
             P.displayText(Notif, PA_LEFT, scrollSpeed, 1500, scrollEffect, scrollEffect);
             strcpy(Notif,clock.dateFormat("  l, d F Y", dt));
             ++cpt;
             break;
          }
          case 1 :
          {
            readDHT11(BROCHE_CAPTEUR, &temperature, &humidity);

              P.displayText(Notif, PA_LEFT, scrollSpeed, 1500, scrollEffect, scrollEffect);
              dtostrf(temperature, 3, 0, charVal);
              dtostrf(humidity, 3, 0, charVal2);
              sprintf(Notif,"   --> T:%s C / H:%s %%",charVal,charVal2);    
           
              cpt=0;
              temps=millis();
             break;
          }
     }
    }
    } // fin plage horaire
    else {
  P.setIntensity(1);
  P.print("");

    }
  }  // fin else

  
}
 

    

}


/**
 * Lit la température et le taux d'humidité mesuré par un capteur DHT11.
 *
 * @param pin Broche sur laquelle est câblée le capteur.
 * @param temperature Pointeur vers la variable stockant la température.
 * @param humidity Pointeur vers la variable stockant le taux d'humidité.
 * @return DHT_SUCCESS si aucune erreur, DHT_TIMEOUT_ERROR en cas de timeout, ou DHT_CHECKSUM_ERROR en cas d'erreur de checksum.
 */
byte readDHT11(byte pin, float* temperature, float* humidity) {
  
  /* Lit le capteur */
  byte data[5];
  byte ret = readDHTxx(pin, data, 18, 1000);
  
  /* Détecte et retourne les erreurs de communication */
  if (ret != DHT_SUCCESS) 
    return ret;
    
  /* Calcul la vraie valeur de la température et de l'humidité */
  *humidity = data[0];
  *temperature = data[2];

  /* Ok */
  return DHT_SUCCESS;
}

/**
 * Fonction bas niveau permettant de lire la température et le taux d'humidité (en valeurs brutes) mesuré par un capteur DHTxx.
 */
byte readDHTxx(byte pin, byte* data, unsigned long start_time, unsigned long timeout) {
  data[0] = data[1] = data[2] = data[3] = data[4] = 0;
  // start_time est en millisecondes
  // timeout est en microsecondes
 
  /* Conversion du numéro de broche Arduino en ports / masque binaire "bas niveau" */
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *ddr = portModeRegister(port);   // Registre MODE (INPUT / OUTPUT)
  volatile uint8_t *out = portOutputRegister(port); // Registre OUT (écriture)
  volatile uint8_t *in = portInputRegister(port);   // Registre IN (lecture)
  
  /* Conversion du temps de timeout en nombre de cycles processeur */
  unsigned long max_cycles = microsecondsToClockCycles(timeout);
 
  /* Evite les problèmes de pull-up */
  *out |= bit;  // PULLUP
  *ddr &= ~bit; // INPUT
  delay(100);   // Laisse le temps à la résistance de pullup de mettre la ligne de données à HIGH
 
  /* Réveil du capteur */
  *ddr |= bit;  // OUTPUT
  *out &= ~bit; // LOW
  delay(start_time); // Temps d'attente à LOW causant le réveil du capteur
  // N.B. Il est impossible d'utilise delayMicroseconds() ici car un délai
  // de plus de 16 millisecondes ne donne pas un timing assez précis.
  
  /* Portion de code critique - pas d'interruptions possibles */
  noInterrupts();
  
  /* Passage en écoute */
  *out |= bit;  // PULLUP
  delayMicroseconds(40);
  *ddr &= ~bit; // INPUT
 
  /* Attente de la réponse du capteur */
  timeout = 0;
  while(!(*in & bit)) { /* Attente d'un état LOW */
    if (++timeout == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
  }
    
  timeout = 0;
  while(*in & bit) { /* Attente d'un état HIGH */
    if (++timeout == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
  }

  /* Lecture des données du capteur (40 bits) */
  for (byte i = 0; i < 40; ++i) {
 
    /* Attente d'un état LOW */
    unsigned long cycles_low = 0;
    while(!(*in & bit)) {
      if (++cycles_low == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
    }

    /* Attente d'un état HIGH */
    unsigned long cycles_high = 0;
    while(*in & bit) {
      if (++cycles_high == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
    }
    
    /* Si le temps haut est supérieur au temps bas c'est un "1", sinon c'est un "0" */
    data[i / 8] <<= 1;
    if (cycles_high > cycles_low) {
      data[i / 8] |= 1;
    }
  }
  
  /* Fin de la portion de code critique */
  interrupts();
 
  /*
   * Format des données :
   * [1, 0] = humidité en %
   * [3, 2] = température en degrés Celsius
   * [4] = checksum (humidité + température)
   */
   
  /* Vérifie la checksum */
  byte checksum = (data[0] + data[1] + data[2] + data[3]) & 0xff;
  if (data[4] != checksum)
    return DHT_CHECKSUM_ERROR; /* Erreur de checksum */
  else
    return DHT_SUCCESS; /* Pas d'erreur */
}

J’ai opté pour afficher la date sous la forme « vendredi 3 Octobre 2017 » , la date va s’afficher en anglais , pour l’avoir en français , j’ai directement modifié le fichier DS3231.cpp dans la bibliothèque . Ce n’est pas la meilleur méthode , car lors de la mise a jour de la bibliothèque les noms en anglais vont revenir , mais mon code prenant 99,9 % de la memoire , je n’ai pas pu rajouter une fonction permettant de convertir directement depuis mon sketch.

Afin que les notifications soient plus visibles , je met l’intensité à fond , par la commande :
P.setIntensity(MAX_INTENSITY);

J’ai ajouté  , sur le dessus de l’horloge une LED Bleue ( relié à la sortie D3 ) , afin que la notification soit plus visible.
Pour allumer la led , il suffit d’entrer la commande :
digitalWrite(LED, HIGH);

Integration dans Jeedom

Une fois en place , dans la pièce choisi , il ne reste plus qu’a intégrer ses notifications dans divers scenarios.

Tout d’abord , nous allons créer un virtuel , reprenant les différentes notifications déclenché par les commandes de l’Orvibo.

Notification Hotloge

Ces notifications peuvent être déclenché manuellement , ou via l’appel de scenario.

Detail commande virtuel

 

Chaque commande est lié à une commande correspondante au module Orvibo .

 

Exemple de scenario possible  :

  • L’appuie sur un bouton dans la cuisine , déclenche la notification « A table »
  • Une sonnerie au portail , déclenche la notif « On sonne »
  • A l’heure du coucher la notification « Bonne Nuit »
  • etc …..