Système avancé de contrôle de chauffage multi-zones pour maison intelligente
Composants et fournitures
| Composant | Quantité | Notes |
|---|---|---|
| Arduino UNO (ou Mega pour >5 zones) | 1 | Mega recommandé si >5 zones (incl. zone "reste de la maison" sans plancher chauffant) |
| Carte relais 8 canaux 5 V (Keyes) | 1 | 4 relais pour 2 zones plancher (1 pompe, 1 chaudière, 2 vannes) ; 1 relais supplémentaire par zone extra |
| Actionneur thermoélectrique Honeywell MT8-230-NC (230 V AC) | 1+ | 1 actionneur (vanne) par groupe de boucles de plancher. Plusieurs modèles possibles. |
À propos de ce projet
J'ai construit ce dispositif car ma cuisine était soit trop chaude, soit trop froide avec un seul thermostat dans le salon. Les contrôleurs multi-zones commerciaux (type EvoHome) sont très chers. Ce programme réplique l'intelligence de ces systèmes coûteux sur un simple Arduino Uno. Problème résolu !
Points forts / Fonctionnalités
- Configuration simple : brochage et nombre de zones uniquement
- Arduino Uno → jusqu'à 5 zones de plancher chauffant
- Arduino Mega → zones quasi illimitées
- Contrôle : pompe de plancher, agrégation zones comme un seul thermostat pour la chaudière, vannes d'ouverture/fermeture, watchdog pour robustesse
Chauffage individuel par zone
- Par zone : un thermostat (demande de chauffe) + un relais (commande vanne(s))
- Pièce avec plusieurs groupes de boucles = une zone (vannes en parallèle sur le relais de zone)
- Plus confortable, économie d'énergie : les pièces ne surchauffent plus
Contrôle de la pompe de plancher
- Ne fait tourner la pompe que quand nécessaire → économie 100–200 €/an électricité (vs 24/7 : 80 W = 2 kWh/j = 0,50 €/j)
- Active la pompe au moins 8 min toutes les 36 h si pas de demande (été, entretien)
- Ne démarre la pompe qu'après ouverture des vannes (3–5 min pour ouverture)
Zone "sans plancher" (optionnelle)
- Pour le reste de la maison (radiateurs avec têtes thermostatiques) : seules les pièces froides chauffent
- Ajoutez un thermostat dans la/les pièce(s) à contrôler, câblez en parallèle sur l'entrée
No_Zone
Notes finales
- Pas besoin de contrôler toutes les zones : seulement celles qui surchauffent ou restent froides (autres : têtes thermostatiques manuelles sur le collecteur)
- Choix délibéré de ne pas connecter à Internet :
- Réduit les risques de dysfonctionnement (doit être ultra-fiable)
- Les thermostats connectés existent déjà pour le pilotage à distance ; ce contrôleur n'apporte rien de plus à distance
Code
ProjectCV.ino (C/C++)
/*
* Contrôleur chauffage plancher multi-zones v1.0
* Copyright: GPL-3.0 par Eric Kreuwels, 2017
* Crédits: Peter Kreuwels pour les cas d'usage
* Attention: testez avant utilisation réelle !
*/
#include <avr/wdt.h> // Watchdog
// #define FAST_MODE // 50x plus rapide pour test (débranchez pompe/chaudière réelles !)
#define VALVE_TIME 3000L // 5 min ouverture/fermeture vanne (sécurité)
#ifdef FAST_MODE
#define PUMP_MAINTENANCE_TIME 108000L // Test: cycle entretien pompe toutes les 4 min (simule 3h)
#else
#define PUMP_MAINTENANCE_TIME 1300000L // Normal: cycle entretien pompe toutes les 36h
#endif
#define PUMP_ACTIVATION_TIME 5000L // Pompe active ~8 min (10s en test)
#define COOLDOWN_TIME 18000L // Après chauffe: circulation 30 min (40s test)
#include "./Devices.h" // Classes vannes, pompe, thermostats
struct Zone {
String name;
Valve valve;
Thermostat thermostat;
};
////////////////////////////////////////////////////
// BLOC CONFIGURATION
// Configurez/réordonnez le brochage comme vous voulez (mon câblage sur Uno):
// Note: broches 1 et 2 libres pour zone extra
#define HEATER_PIN 4 // Relais → entrée thermostat chaudière
#define FU_PUMP_PIN 5 // Relais → pompe plancher
#define LIVING_VALVE 7 // Zone 1: relais vanne(s)
#define KITCHEN_VALVE 6 // Zone 2
#define DINING_VALVE 3 // Zone 3
#define LIVING_THERMO 8 // Zone 1: entrée thermostat salon
#define KITCHEN_THERMO 9 // Zone 2: cuisine
#define DINING_THERMO 11 // Zone 3: salle à manger
#define NO_ZONE_THERMO 10 // Optionnel: thermostats pièces sans plancher
#define HEATING_LED 12 // Allumé = chauffe, clignote = refroidissement, éteint = veille
#define INDICATION_LED 13 // Clignote = carte vivante (peut être supprimé pour libérer broche)
#define NR_ZONES 3
Zone Zones[NR_ZONES] = {
{"Salon", Valve(LIVING_VALVE, "Vanne Salon"), Thermostat(LIVING_THERMO, "Thermo Salon")},
{"Cuisine", Valve(KITCHEN_VALVE, "Vanne Cuisine"), Thermostat(KITCHEN_THERMO, "Thermo Cuisine")},
{"Salle a manger", Valve(DINING_VALVE, "Vanne Salle a manger"), Thermostat(DINING_THERMO, "Thermo Salle a manger")}
};
// FIN BLOC CONFIGURATION
////////////////////////////////////////////////////
// Périphériques fixes
LED iLED(INDICATION_LED, "LED Indication");
LED hLED(HEATING_LED, "LED Chauffage");
Manipulator CV(HEATER_PIN, "Chaufferie CV");
Pump FUPump(FU_PUMP_PIN, "Pompe Plancher");
Thermostat ZonelessThermo(NO_ZONE_THERMO, "Thermo Sans Zone");
void printConfiguration() {
Serial.println("------ Configuration Carte ------");
iLED.Print(); hLED.Print(); CV.Print(); FUPump.Print(); ZonelessThermo.Print();
for (int i = 0; i < NR_ZONES; i++) {
Serial.print("Zone["); Serial.print(i+1); Serial.print("]: "); Serial.println(Zones[i].name);
Serial.print(" - "); Zones[i].valve.Print();
Serial.print(" - "); Zones[i].thermostat.Print();
}
Serial.println("---------------------------------");
}
// Machine à états : idle, on, cooldown
class State {
public:
enum states { idle, on, cooldown };
private:
states _State;
unsigned long cooldownCount;
public:
State() : _State(idle) {}
states getState() const { return _State; }
void setState(states const& newState) {
printTimeStamp();
Serial.print(": Changement d'etat [");
printState(_State);
_State = newState;
Serial.print("] vers [");
printState(_State);
Serial.println("]");
switch (_State) {
case idle: hLED.Off(); CV.Off(); FUPump.Off(); allValvesOff(); break;
case on: hLED.On(); CV.On(); break; // pompe attend ouverture vanne
case cooldown: CV.Off(); allValvesOn(); break; // pompe attend ouverture
}
}
void doProcessingActions() {
switch (_State) {
case on: onProcessing(); break;
case cooldown: coolDownProcessing(); break;
case idle: idleProcessing(); break;
}
}
void setCoolDownNeeded() { cooldownCount = COOLDOWN_TIME; }
bool whileCoolDownNeeded() {
if (cooldownCount > 0) cooldownCount--;
return checkCoolDownNeeded();
}
bool checkCoolDownNeeded() { return (cooldownCount > 0); }
void printState(states s) {
switch (s) { case idle: Serial.print("idle"); break; case on: Serial.print("on"); break; case cooldown: Serial.print("cooldown"); break; }
}
};
State CVState;
void setup() {
Serial.begin(115200);
printTimeStamp(); Serial.print(": ");
#ifdef FAST_MODE
Serial.println("Controleur zones CV demarre en Mode Test!\n - Temps carte ~50x plus vite\n - Maintenance pompe toutes les 3h au lieu de 36h");
#else
Serial.println("Controleur zones CV demarre. Horodatage (jj:hh:mm:ss)");
#endif
Serial.println(" - Format horodatage (jj:hh:mm:ss)");
printConfiguration();
wdt_enable(WDTO_1S); // Watchdog: reset si pas "caressé" dans 1s
}
void loop() {
#ifdef FAST_MODE
delay(2);
#else
delay(100); // ~10 boucles/sec
#endif
iLED.Alternate(); // vie de la carte
FUPump.Update();
for (int i = 0; i < NR_ZONES; i++) Zones[i].valve.Update();
wdt_reset(); // caresse le watchdog
CVState.doProcessingActions();
}
//////////////////////////////////
// Méthodes de traitement par état
////////////////////////////////
void onProcessing() {
if (ProcessThermostats()) { // true si au moins un thermostat demande chauffe
if (FloorPumpingAllowed()) FUPump.On(); else FUPump.Off();
} else if (CVState.checkCoolDownNeeded()) {
CVState.setState(State::cooldown);
} else {
CVState.setState(State::idle);
}
}
void coolDownProcessing() {
hLED.Alternate();
if (HeatingRequested()) {
CVState.setState(State::on);
} else {
if (CVState.whileCoolDownNeeded()) {
if (FloorPumpingAllowed()) FUPump.On(); else FUPump.Off();
} else {
CVState.setState(State::idle);
}
}
}
void idleProcessing() {
if (HeatingRequested()) {
CVState.setState(State::on);
} else {
if (FUPump.doMaintenanceRun()) {
if (FUPump.IsOff()) {
if (!allValvesOpen()) {
printTimeStamp(); Serial.println(": Debut cycle journalier pompe; ouverture vannes");
allValvesOn();
}
if (FloorPumpingAllowed()) {
printTimeStamp(); Serial.println(": Debut cycle journalier pompe; demarrage pompe");
FUPump.On();
}
}
} else if (FUPump.IsOn()) {
printTimeStamp(); Serial.println(": Fin cycle journalier pompe; arret pompe et fermeture vannes");
FUPump.Off(); allValvesOff();
}
}
}
//////////////////////////////////
// Méthodes utilitaires
////////////////////////////////
void allValvesOff() { for (int i=0;i
Devices.h (C/C++)
// Classes utilitaires pour périphériques E/S
extern void printTimeStamp(); // défini dans le .ino principal
// Classe de base
class IODevice {
protected:
bool _IsOn;
int _Pin;
String _Name;
public:
IODevice(int pin, String name) : _IsOn(false), _Pin(pin), _Name(name) {}
virtual bool IsOn() = 0;
bool IsOff() { return !IsOn(); }
void DebugPrint() { printTimeStamp(); Serial.print(": "); Print(); }
void Print() {
Serial.print(_Name); Serial.print(" sur broche("); Serial.print(_Pin);
if (_IsOn) Serial.println(") = On"); else Serial.println(") = Off");
}
};
// Thermostat : entrée numérique avec anti-rebond
class Thermostat : public IODevice {
private:
int _Counter;
public:
Thermostat(int pin, String name) : IODevice(pin, name) { _Counter = 0; pinMode(_Pin, INPUT_PULLUP); }
virtual bool IsOn() {
if (digitalRead(_Pin) == HIGH && _IsOn) { if (++_Counter > 5) { _IsOn = false; DebugPrint(); _Counter = 0; } }
else if (digitalRead(_Pin) == LOW && !_IsOn) { if (++_Counter > 5) { _IsOn = true; DebugPrint(); _Counter = 0; } }
else _Counter = 0;
return _IsOn;
}
};
// Manipulateur : sortie numérique basique
class Manipulator : public IODevice {
public:
Manipulator(int pin, String name) : IODevice(pin, name) { pinMode(_Pin, OUTPUT); digitalWrite(_Pin, HIGH); }
void On() { if (!_IsOn) { _IsOn = true; digitalWrite(_Pin, LOW); onSwitch(); } }
void Off() { if (_IsOn) { _IsOn = false; digitalWrite(_Pin, HIGH); onSwitch(); } }
virtual void onSwitch() { DebugPrint(); }
virtual bool IsOn() { return _IsOn; }
};
// Vanne : sortie avec gestion temps de transition (3-5 min)
class Valve : public Manipulator {
private:
long transitionCount;
public:
Valve(int pin, String name) : Manipulator(pin, name) { transitionCount = 0; }
bool ValveIsOpen() { return (IsOn() && transitionCount >= VALVE_TIME); }
void Update() {
if (IsOn()) { if (transitionCount < VALVE_TIME) transitionCount++; }
else { if (transitionCount > 0) transitionCount--; }
}
};
// Pompe : cycles d'entretien périodiques
class Pump : public Manipulator {
private:
long counter;
bool doMaintenance;
public:
Pump(int pin, String name) : Manipulator(pin, name) { counter = 0; doMaintenance = false; }
bool doMaintenanceRun() { return doMaintenance; }
virtual void onSwitch() { Manipulator::onSwitch(); counter = 0; }
void Update() {
if (IsOn()) {
if (counter < PUMP_ACTIVATION_TIME) counter++;
else if (doMaintenance) { printTimeStamp(); Serial.println(": Maintenance pompe terminee"); doMaintenance = false; }
} else {
if (counter < PUMP_MAINTENANCE_TIME) counter++;
else if (!doMaintenance) { printTimeStamp(); Serial.println(": Maintenance pompe necessaire"); doMaintenance = true; }
}
}
};
// LED : On/Off + alternance 1Hz
class LED : public Manipulator {
private:
long counter;
public:
LED(int pin, String name) : Manipulator(pin, name) { counter = 0; }
virtual void onSwitch() {} // pas de debug pour LEDs
void Alternate() {
#ifdef FAST_MODE
if (counter++ > 250)
#else
if (counter++ > 5)
#endif
{ counter = 0; if (IsOn()) Off(); else On(); }
}
};
Schémas
- Câblage détaillé des périphériques (Pompe, Vannes, Thermostats, LEDs) — image fournie
- Exemple de câblage de contrôleurs "en cascade" (un par plancher) — image fournie
- Extrait de logs série réels (horodatages montrent délai 5 min entre ouverture vannes et démarrage pompe) — fichier
logexample_fTczkAa0tf.txt
Processus de fabrication
- Contrôleur de puissance PWM : construction d'un driver de lampe à modulation de largeur d'impulsion
- Systèmes de chauffage solaire : des solutions passives aux actives pour un chauffage domestique durable
- Surveillance de votre chaudière centrale avec Raspberry Pi – Matériel, Logiciel et Guide de configuration
- Guide essentiel pour maintenir votre autolaveuse à performance optimale
- Système d'éclairage DMX piloté par web – Contrôleur maître Arduino
- Assistant de jeu automatisé : Dispositif intelligent d'automatisation de tâches pour gamers
- Manette de jeu USB Arduino DIY – Construisez votre propre gamepad haute performance
- Unopad : contrôleur MIDI Arduino fluide pour Ableton Live
- Kit contrôleur d'affichage VFD (afficheur fluorescent sous vide) basé sur Arduino