Domotique : Contrôle éclairage + détection de présence

Domotique : Contrôle éclairage + détection de présence

La découverte du Raspberry a ouvert un univers sur l’automatisation de tâche. Même avec la taille d’une carte de crédit, il est compliqué de l’utiliser pour en faire un objet connecté.

Raspberry Pi 2 Modèle B
ESP 32

L’ESP32 comble ce problème. Ce microcontrôleur équipé de plusieurs connecteurs d’entrée/sortie, permet l’exécution d’un programme codé en C++. Le programme peut interagir avec l’extérieur en Wifi.

Description du projet

L’objectif est le pilotage de la lumière du mon bureau. La gestion domotique sera confiée au logiciel Domoticz. En plus d’un contrôle manuel, le système doit pouvoir éteindre la lumière si personne n’est détecté dans le bureau. Accessoirement, le dispositif donnera aussi la température.

Capteurs et module utilisés

Relais 220v 10 A KY-019
Détection de mouvement HC-SR501
Capteur de température et d’humidité DHT11
Capteurs utilisés

Les composants sont rassemblés dans un boîtier et relié au microprocesseur par un faisceau avec alimentation commune ( 5 V ), câble récupéré d’un vieux boîtier de PC.

Capteur de présence

Afin d’éviter les coupures de lumière, le mode H a été activé.
Le principe est le suivant : si une présence est détectée, le capteur l’indique au microcontrôleur par un changement d’état. Le capteur attend alors la durée indiquée par le réglage Time Delay (réglé à 1m30 environ). Au bout de 3 secondes, le capteur re-vérifie la présence, si elle est toujours la, il relance la durée d’attente. Si aucun mouvement n’est détecté dans le “Time delay” le capteur change d’état pour indiquer la fin de présence à l’ESP

Quant à la sensibilité, il a fallu la régler pour éviter que le capteur détecte les passages dans le couloir devant mon bureau .

Le petit plus !

Afin de réduire les coupures lorsque je travaille sur mon ordinateur, mon système d’exploitation (Ubuntu) active et désactive la détection de présence selon si ma session est ouverte ou non.
Le code suivant est lancé au démarrage afin d’intercepter le signal de mise en veille et de lancer la commande.

dbus-monitor --session "type='signal',interface='org.gnome.ScreenSaver'" |
  while read x; do
    case "$x" in
      *"boolean true"*) /usr/bin/mosquitto_pub -h x.x.x.x -u user -P **** -t "domoticz/in" -m '{"command": "switchlight", "idx": 24, "switchcmd": "Off" }';;
      *"boolean false"*) /usr/bin/mosquitto_pub -h x.x.x.x -u user -P ***** -t "domoticz/in" -m '{"command": "switchlight", "idx": 24, "switchcmd": "On" }';;
    esac
  done

Coût des composants

ComposantsRéférenceCoût
ESP 32ESP32 NodeMCU5,55 €
Détecteur de présenceHC-SR5012.16 €
RelaisKY-0191,40 €
capteur températureDHT113,16€
DEL RGB + 3 résistances0.50€
Impression 3D5.8 €

Le projet est revenu à un peu moins de 20 € .

Installation du dispositif

L’installation se compose de 3 boîtiers .

  • 1 pour l’alimentation électrique
  • 1 pour l’ESP et le voyant d’état
  • 1 pour le relais et les 2 capteurs.
Schéma d’intégration du dispositif à l’éclairage existant

Dispositif Domoticz

La gestion des actions est confié au logiciel libre gratuit Domotizc. Il reçoit les commandes via le protocole MQTT géré par mosquitto.

3 modules sont configurés :

  1. Pour commander le Relais
  2. Pour récupérer l’état du capteur de présence
  3. Pour activer ou non la commande de la lumière par le détecteur de présence

Pour une plus grande réactivité, c’est ESP32 qui donne d’ordre d’allumer ou éteindre la lumière en fonction de la présence.

Programme

Afin de surveiller la présence, sans avoir à faire une boucle énergivore, j’ai utilisé le système d’interruption de ESP32.

portMUX_TYPE IRMux = portMUX_INITIALIZER_UNLOCKED;

// Fonction de traitement de l'interruption 
void IRAM_ATTR PresenceDetectee()
{
  portENTER_CRITICAL_ISR(&IRMux);
  presence_send = true;
  portEXIT_CRITICAL_ISR(&IRMux);
}

// Dans la fonction setup 
attachInterrupt(PIN_PIR, PresenceDetectee, CHANGE);

Idem pour la remontée de température, l’interruption étant déclenchée par un timer

hw_timer_t *timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

// Fonction de traitement de l'interruption 
void IRAM_ATTR onTime()
{
  portENTER_CRITICAL_ISR(&timerMux);
  get_temp = true;
  portEXIT_CRITICAL_ISR(&timerMux);
}

// Dans la fonction setup 
  // Timer pour l'envoi température
  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTime, true);
  timerAlarmWrite(timer, 120000000, true);
  timerAlarmEnable(timer);

Afin de ne pas bloquer l’ESP trop longtemps pendant l’interruption, les fonctions modifient seulement une valeur booléenne.
Cette valeur est ensuite analysée par la boucle loop de l’ESP32 afin de faire le traitement.

#include <Arduino.h>

#include "EspMQTTClient.h"
#include <ArduinoJson.h>
#include "DHT.h"

#define DHTPIN 14     // DHT Pin
#define DHTTYPE DHT11 // DHT 11

#define PIN_RELAIS 2

#define LED_ROUGE 25
#define LED_VERT 26
#define LED_BLEU 27

#define PIN_PIR 33

volatile bool get_temp = true;      // déclenché par le Timer
volatile bool presence_send = true; // Déclenché par le capteur IR
volatile bool DetecterLaPresence = false;

const int freq = 5000;
const int ledChannelROUGE = 0;
const int ledChannelVERT = 1;
const int ledChannelBLEU = 2;
const int resolution = 8;

hw_timer_t *timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
portMUX_TYPE IRMux = portMUX_INITIALIZER_UNLOCKED;

EspMQTTClient client(
    "<SSID>",
    "<CLé WIFI>",
    "<IP Broker>", 
    "<Utilisateur MQTT>",           
    "<Mot de passe MQTT>",      
    "BoxBureau",      
    1883              
);

DHT dht(DHTPIN, DHTTYPE);

void IRAM_ATTR PresenceDetectee()
{
  portENTER_CRITICAL_ISR(&IRMux);
  presence_send = true;
  portEXIT_CRITICAL_ISR(&IRMux);
}

void IRAM_ATTR onTime()
{
  portENTER_CRITICAL_ISR(&timerMux);
  get_temp = true;
  portEXIT_CRITICAL_ISR(&timerMux);
}

void setup()
{
  Serial.begin(115200);

  // initialisation Relais
  pinMode(PIN_RELAIS, OUTPUT);

  // Capteur de présence
  pinMode(PIN_PIR, INPUT_PULLUP);
  attachInterrupt(PIN_PIR, PresenceDetectee, CHANGE);

  // LED
  ledcSetup(ledChannelROUGE, freq, resolution);
  ledcSetup(ledChannelVERT, freq, resolution);
  ledcSetup(ledChannelBLEU, freq, resolution);

  ledcAttachPin(LED_ROUGE, ledChannelROUGE);
  ledcAttachPin(LED_VERT, ledChannelVERT);
  ledcAttachPin(LED_BLEU, ledChannelBLEU);

  ledcWrite(ledChannelROUGE, 256);
  ledcWrite(ledChannelVERT, 256);
  ledcWrite(ledChannelBLEU, 256);

  // Optionnal functionnalities of EspMQTTClient :
  // client.enableDebuggingMessages();                                         // Enable debugging messages sent to serial output
  client.enableHTTPWebUpdater();                                            // Enable the web updater. User and password default to values of MQTTUsername and MQTTPassword. These can be overrited with enableHTTPWebUpdater("user", "password").
  client.enableLastWillMessage("BoxBureau/lastwill", "I am going offline"); // You can activate the retain flag by setting the third parameter to true

  // Timer pour l'envoi température
  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTime, true);
  timerAlarmWrite(timer, 120000000, true);
  timerAlarmEnable(timer);
}

void onMessageReceived(const String &topic, const String &payload)
{
  StaticJsonDocument<300> JSONBuffer;

  auto error = deserializeJson(JSONBuffer, payload);
  if (error)
  {
    Serial.println("deserializeJson() failed with code ");
    Serial.println(error.c_str());
    return;
  }

  int idx = JSONBuffer["idx"]; //Get value of sensor measurement
  const char *switchcmd = JSONBuffer["switchcmd"];

  switch (idx)
  {
  case 18:
    if (strcmp(switchcmd, "On"))
    {
      Serial.println("DOMOTICZ : LUMIERE ON ");
      digitalWrite(PIN_RELAIS, HIGH);
    }
    else
    {
      Serial.println("DOMOTICZ : LUMIERE OFF ");
      digitalWrite(PIN_RELAIS, LOW);
    }
    break;
  }
}

void onPresenceReceived(const String &topic, const String &payload)
{
  StaticJsonDocument<300> JSONBuffer;

  auto error = deserializeJson(JSONBuffer, payload);
  if (error)
  {
    Serial.println("deserializeJson() failed with code ");
    Serial.println(error.c_str());
    return;
  }

  int idx = JSONBuffer["idx"]; //Get value of sensor measurement
  const char *switchcmd = JSONBuffer["switchcmd"];

  switch (idx)
  {
  case 22:
    if (strcmp(switchcmd, "On"))
    {
      DetecterLaPresence = true;
      Serial.println("DOMOTICZ : DETECTEUR ACTIVÉ ");
      if (digitalRead(PIN_PIR)) //récupération état capteur
      {
        ledcWrite(ledChannelVERT, 250);
        ledcWrite(ledChannelROUGE, 256);
        ledcWrite(ledChannelBLEU, 256);
      }
      else
      {
        ledcWrite(ledChannelVERT, 256);
        ledcWrite(ledChannelROUGE, 250);
        ledcWrite(ledChannelBLEU, 256);
      }
    }
    else
    {
      DetecterLaPresence = false;
      Serial.println("DOMOTICZ : DETECTEUR DESACTIVÉ ");

      ledcWrite(ledChannelVERT, 256);
      ledcWrite(ledChannelROUGE, 256);
      ledcWrite(ledChannelBLEU, 250);
    }

    break;
  }
}

void onConnectionEstablished()
{
  dht.begin();

  client.subscribe("domoticz/out/RDC/Bureau/Detecteur", onPresenceReceived);
  client.subscribe("domoticz/out", onMessageReceived);
  portENTER_CRITICAL(&timerMux);
  get_temp = true;
  portEXIT_CRITICAL(&timerMux);
}

// Boucle Principal
void loop()
{
  client.loop();
  // Envoi la température si Timer demande
  if (get_temp)
  {
    portENTER_CRITICAL(&timerMux);
    get_temp = false;
    portEXIT_CRITICAL(&timerMux);
    float t = dht.readTemperature();
    float h = dht.readHumidity();

    if (isnan(t) || isnan(h))
      Serial.println("[ERROR] Please check the DHT sensor !");
    else
    {
      client.publish("domoticz/in", "{ \"idx\" : 19, \"nvalue\" : 1, \"svalue\" : \"" + String(t) + ";" + String(h) + ";1\"}");
      client.publish("topic/tempBureau", String(t) );
      Serial.println("ESP 32 : Température :" + String(t) + ", Humidité :" + String(h) + " %");
    }
  }

  // Detection de présence
  if (DetecterLaPresence) // Detection de présence activé dans Domotizc ?
  {
    
      portENTER_CRITICAL(&IRMux);
      presence_send = false;
      portEXIT_CRITICAL(&IRMux);

      if (digitalRead(PIN_PIR)) //récupération état capteur
      {
        // Informe Domotizc de la présence
        client.publish("domoticz/in", "{\"command\": \"switchlight\", \"idx\": 21, \"switchcmd\": \"On\" }");

        //Change la couleur de la LED
        ledcWrite(ledChannelVERT, 250);
        ledcWrite(ledChannelROUGE, 256);
        ledcWrite(ledChannelBLEU, 256);

        Serial.println("ESP32 : Présence détecté , Detecteur activée, j'allume la lumière");
        client.publish("domoticz/in", "{\"command\": \"switchlight\", \"idx\": 18, \"switchcmd\": \"On\" }");
      }
      else
      {
        // Informe Domotizc de la fin de présence
        client.publish("domoticz/in", "{\"command\": \"switchlight\", \"idx\": 21, \"switchcmd\": \"Off\" }");

        //Change la couleur de la LED
        ledcWrite(ledChannelVERT, 256);
        ledcWrite(ledChannelROUGE, 250);
        ledcWrite(ledChannelBLEU, 256);

        Serial.println("ESP32 : Fin de présence , Detecteur activée, j'éteind la lumière");
        client.publish("domoticz/in", "{\"command\": \"switchlight\", \"idx\": 18, \"switchcmd\": \"Off\" }");
      }
    }
  }
}

Améliorations

Le système étant aussi relié à l’interrupteur mural, l’envoi de la température est interrompu si on éteint la lumière de l’interrupteur. L’ajout d’une batterie permettrait de maintenir cette fonctionnalité.

Avec l’ajout d’un capteur de luminosité, il serait possible d’éviter de mettre la lumière au mouvement lorsqu’il fait jour.

Le design de boitier est perfectible. Il serait possible d’intégrer l’alimentation au boitier ESP.

Conclusion

Ce projet m’a permis de découvrir plusieurs concepts comme les résistances de Pull-up, les interruptions matérielles, breadboard … Cette première expérience, appelle de nombreuses autres, voir la création prochaine de ce type d’atelier dans la cadre de l’association Z-Gen .

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *