Logo Spiria

Un minuteur de douche avec un module ESP8266

29 novembre 2016.

Les papas, eux aussi, aspirent à ressentir encore le réconfort de |’eau chaude sur leur corps lors de la douche matinale… et ce, sans devoir se lever avant les prémices de l’aube pour pouvoir en bénéficier. Ma maison étant le foyer de deux adolescentes, et la nécessité est la mère de l’invention, je me suis résolu à installer une minuterie de douche sur mon réservoir d’eau chaude afin de briser la malédiction de la douche froide.

Principe de fonctionnement

Une vanne à bille motorisée est installée sur la sortie d’eau chaude du chauffe-eau. Elle est accouplée avec un débitmètre électronique. Grâce à ce dernier, un petit dispositif à base de microcontrôleur surveille la consommation d’eau chaude et quand une certaine période d’utilisation continue est atteinte, il actionne à plusieurs reprises la fermeture et l’ouverture de l’électrovanne afin d’envoyer le message clair à la personne sous la douche que son temps autorisé est dépassé.

Matériel requis

Pour ce projet, j’ai choisi d’utiliser une carte Adafruit Huzzah ESP8266. Son prix est modeste et elle propose plein de fonctionnalités intéressantes à utiliser. En premier lieu, c’est une carte WiFi, ce qui autorise son contrôle à distance. Elle peut ainsi être initialisée comme point d’accès, permettant de s’y connecter et de la configurer pour qu’elle rejoigne le réseau de la maison. En second lieu, elle peut faire tourner un serveur Web et accepte les mises à jour distantes de son programme. Une fois que le programme (croquis/sketch) est installé, il n’est donc plus nécessaire de se connecter via son port USB pour le mettre à jour. Enfin, l’ESP8266 vient avec un régulateur de tension intégré. On peut abaisser le courant sortant de l’alimentation de la valve (5 V) à un niveau pouvant être géré par les broches GPIO.

Schéma de câblage

Wiring Diagram.

Installation

Installation.

Programme

La logique est assez simple. Une routine d’interruption surveille les impulsions générées par le débitmètre. Ces impulsions sont accumulées et une minuterie, chaque minute, appelle une routine qui stocke les impulsions accumulées dans un tableau pouvant contenir jusqu’à 15 minutes de données. Si toutes les cellules du tableau contiennent une valeur autre que 0, le contrôleur ferme puis ouvre la vanne.

Code source

// MrShower
// Created by Patrick Walsh.
//
// Hot Water monitoring system installed at the intake of a hot water tank.
// When hot water has been running for 15 minutes an electrical ball valve will cycle off and on every minute until usage ends.( Shower ) an elc
//

#include <esp8266httpupdateserver.h>
#include <esp8266mdns.h>
#include <esp8266wifi.h>
#include <ticker.h>   
#include "WiFiManager.h"

#define SKETCH_VERSION              1.0

#define HAL_SENSOR_PIN               15   // Flowmeter Hal sensor is connected to this pin 
#define MONITOR_MINS                 15   // Showers can last a maximum of 15 mins before warning kicks in
#define SHOWER_SHUTOFF_WARNING_TIME  5.0  // Duration valve will be closed to warn time is up
#define TIMER_PERIOD                 60.0 // Period required to calculate RPM of Hal effect sensor
#define VALVE_CLOSE_CTRL_PIN         12   // Relay connected to the electrical valve Open Wire     
#define VALVE_OPEN_CTRL_PIN          14   // Relay connected to the electrical valve Close Wire 
#define VALVE_TRANSIT_DELAY          2.0  // Time taken for valve to move from closed state ot open state

//
// Global variables
//
const char*              g_HostName = "MrShower";  // Duh
ESP8266WebServer         g_HttpServer(80);         // Http server we will be providing
ESP8266HTTPUpdateServer  g_HttpUpdater(false);     // A OverTheAir update service.       Http://MrShower.local/update
Ticker                   g_MonitoringTimer;        // Interupt timer used for counting minutes
unsigned                 g_Rpms[MONITOR_MINS];     // History of rpms in the last "n" minutes

// global variables accessed from ISR that need to be protected
volatile bool            g_FlashWater = false;     // True if valve needs to be closed temporarily
volatile unsigned        g_HallSensorPulses = 0;   // FlowMeter pulses that have occurred in the current minute
volatile bool            g_WaterOn = false;        // State of Valve 

//
// Forward Declarations
//
void closeWaterValve();
void openWaterValve();
void onHallEffect();
void onTimerTick();

void webServerHandleRoot();
void webServerHandleNotFound();
void webServerHandleOpenCmd();
void webServerHandleCloseCmd();
void webServerHandleResetCmd();
void webServerHandleCycleCmd();

///////////////////////////////////////////////////////////////////
//
// Setup is called at initialization
//
/////////////////////////////////////////////////////////////////////
void setup()
{
  // Initialize Relay control
  pinMode(VALVE_CLOSE_CTRL_PIN, OUTPUT);
  pinMode(VALVE_OPEN_CTRL_PIN, OUTPUT);
    
  // Make sure the water valve is open.
  openWaterValve();
  g_HallSensorPulses = 0;  
 
  // Setup an Access point in order to allow network setup
  WiFiManager wifiManager;
  wifiManager.autoConnect( g_HostName );

  WiFi.mode( WIFI_STA );
  WiFi.softAPdisconnect( true );

  // Establish a connection with our configured access point
  while( WiFi.waitForConnectResult() != WL_CONNECTED )
  {
    WiFi.begin();
  }

  // install web serv handlers.
  g_HttpServer.on("/",      webServerHandleRoot ); 
  g_HttpServer.onNotFound(  webServerHandleNotFound );
  g_HttpServer.on("/open",  webServerHandleOpenCmd);
  g_HttpServer.on("/close", webServerHandleCloseCmd );
  g_HttpServer.on("/reset", webServerHandleResetCmd );
  g_HttpServer.on("/cycle", webServerHandleCycleCmd );

  // Add OTA update service provided by library "/update" command
  g_HttpUpdater.setup( &g_HttpServer );
  g_HttpServer.begin( );

  // Register web services we expose with Bonjour
  MDNS.begin( g_HostName );
  MDNS.addService( "http", "tcp", 80 );
  
  // Clear the array of rpms
  memset( g_Rpms, 0, sizeof( g_Rpms ) );  

  // Attach Hall Sensor to HAL_SENSOR_PIN.
  // ISR called when voltage rises to match one 
  // revolution of the flowmeter turbine  
  attachInterrupt( HAL_SENSOR_PIN, onHallEffect, RISING );

  // Start a timer which will treat the reading of the hall sensor and calculate RPM
  g_MonitoringTimer.attach( TIMER_PERIOD, onTimerTick );
}

///////////////////////////////////////////////////////////////////
//
// Code in the loop method will be run repeatedly
//
///////////////////////////////////////////////////////////////////
void loop() 
{
  // Allow webserver to process queue of requests sent to it.
  g_HttpServer.handleClient();

  if( g_FlashWater )
  {
    // Need to cycle the hot water valve
    closeWaterValve( );
    delay( SHOWER_SHUTOFF_WARNING_TIME * 1000 );
    openWaterValve( );
    g_FlashWater = false;
  }
}

///////////////////////////////////////////////////////////////////
//
// Close water valve by controlling 2 relays connected
// to ball valve motor
//
///////////////////////////////////////////////////////////////////void closeWaterValve()
{
  digitalWrite( VALVE_OPEN_CTRL_PIN, HIGH );
  digitalWrite( VALVE_CLOSE_CTRL_PIN, LOW );
  delay( VALVE_TRANSIT_DELAY * 1000 );
  digitalWrite(VALVE_CLOSE_CTRL_PIN, HIGH ); 
  g_WaterOn = false;
}

///////////////////////////////////////////////////////////////////
//
// Open water valve by controlling 2 relays connected
// to ball valve motor
//
///////////////////////////////////////////////////////////////////
void openWaterValve()
{ 
  digitalWrite( VALVE_CLOSE_CTRL_PIN, HIGH );
  digitalWrite( VALVE_OPEN_CTRL_PIN, LOW );
  delay( VALVE_TRANSIT_DELAY * 1000 );
  digitalWrite( VALVE_OPEN_CTRL_PIN, HIGH );
  g_WaterOn = true;
}

///////////////////////////////////////////////////////////////////
//
// Interrupt routine called when FlowMeter is generating Hal Effect pulses.
//
///////////////////////////////////////////////////////////////////
void onHallEffect()
{ 
  // Increase the number of pulses detected by the HallEffect sensor
  // of the water flowmeter
  g_HallSensorPulses++;
}

///////////////////////////////////////////////////////////////////
//
// Timer-driven callback routine called every minute.
//
///////////////////////////////////////////////////////////////////

void onTimerTick()
{  
  // At every clock tick, once per minute, interrupts temporarily disabled while
  // accumulated pulses generated by flowmeter sensor are read and reset.  This gives us a rpm value.
  
  cli( );                          // disable interupts
  long l_rpm = g_HallSensorPulses; // copy rpm value locally
  g_HallSensorPulses = 0;          // sensor reading done for this minute
  sei( );                          // reenable interrupts

  // shift the content of the array to the right 1
  memmove( &g_Rpms[1], &g_Rpms[0], sizeof( g_Rpms ) - sizeof( g_Rpms[0] ) );

  // Set the latest reading in array
  g_Rpms[0] = l_rpm;

  int l_numUsedTicks = 0;
  
  for( int idx = 0; idx < MONITOR_MINS; idx++ )
  {
    if( g_Rpms[idx] )
    {
      l_numUsedTicks++;
    }
  }

  // Do we need to flash hot water
  if( l_numUsedTicks >= MONITOR_MINS && !g_FlashWater )
  {
    g_FlashWater = true;

    // Set last element of array to 0 to delay next hot water valve cycle by one minute
    g_Rpms[ MONITOR_MINS - 1 ] = 0; 
  }
}

///////////////////////////////////////////////////////////////////
//
// Web Server section
//
///////////////////////////////////////////////////////////////////
void webServerHandleRoot() 
{
  String message;
  message += g_HostName;
  message += "Version ";
  message += SKETCH_VERSION;
  message += " \n";
  message += "FlowMeter revolutions this minute ";
  message += g_HallSensorPulses;
  message += " \n";
  message += g_WaterOn == true ? "Valve is Opened" : "Valve is Closed";
  message += " : ";
  for(int idx = 0; idx < MONITOR_MINS; idx++)
  {
    message +=  g_Rpms[idx];
    message += " "; 
  }

  message += " \n";
  message += "Commands : /open /close /update /cycle /reset\n";
  
  g_HttpServer.send(200, "text/plain", message );      
}

///////////////////////////////////////////////////////////////////
//
// Unknown page requested
//
///////////////////////////////////////////////////////////////////
void webServerHandleNotFound() 
{
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += g_HttpServer.uri();
  message += "\nMethod: ";
  message += (g_HttpServer.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += g_HttpServer.args();
  message += "\n";
  
  for (uint8_t i = 0; i < g_HttpServer.args(); i++) 
  {
    message += " " + g_HttpServer.argName(i) + ": " + g_HttpServer.arg(i) + "\n";
  }
  
  g_HttpServer.send(404, "text/plain", message);
}


///////////////////////////////////////////////////////////////////
//
// Open requested
//
///////////////////////////////////////////////////////////////////
void webServerHandleOpenCmd()
{
  g_HttpServer.send(200, "text/plain", "Opening Valve");
  openWaterValve();
} 

///////////////////////////////////////////////////////////////////
//
// Close requested
//
///////////////////////////////////////////////////////////////////
void webServerHandleCloseCmd()
{
  g_HttpServer.send(200, "text/plain", "Closing Valve");
  closeWaterValve();
}

///////////////////////////////////////////////////////////////////
//
// Reset requested
//
///////////////////////////////////////////////////////////////////
void webServerHandleResetCmd()
{
  memset( g_Rpms, 0, sizeof( g_Rpms ) );  
  openWaterValve();
  g_HttpServer.send(200, "text/plain", "System was reset" );
}

///////////////////////////////////////////////////////////////////
//
// Cycle the valve off and on
//
///////////////////////////////////////////////////////////////////
void webServerHandleCycleCmd()
{
  g_HttpServer.send(200, "text/plain", "Cycling water valve" );

  // Set the Flash water variable to true.
  // The loop routine, when true, will cycle the water valve,
  g_FlashWater = true;
}
</ticker.h></esp8266wifi.h></esp8266mdns.h></esp8266httpupdateserver.h>

 

Web Server.

Le saviez-vous ?
Les équipes de Spiria ont une longue expérience en développement de logiciel sur mesure et peuvent vous aider sur tout projet d’envergure.

Idées d'améliorations

  • Enregistrer la consommation d’eau chaude par mois ou par année.
  • Fournir une interface utilisateur pour faciliter la configuration (temps de douche autorisé, temporisation des cycles de la vanne).
  • Ajouter un capteur d’eau au sol pour détecter les fuites accidentelles du chauffe-eau.
  • Prise en charge d’Apple Homekit ou d’un autre réseau domotique.