Sistema Avanzado de Control de Calefacción Multizona para Hogares Inteligentes
Componentes y materiales
| Arduino UNO (usa Mega si necesitas >5 zonas) | ×1 |
| Placa relé 8 canales 5V (Keyes u similar) | ×1 |
| Actuador termoeléctrico Honeywell MT8-230-NC (230V AC) | ×1 |
Sobre el proyecto
Construí este dispositivo porque mi cocina estaba siempre demasiado caliente o fría con un solo termostato en el salón. Los controladores multizona comerciales (como EvoHome) son muy caros. Este programa captura la inteligencia de esos sistemas en un simple Arduino Uno. Resolvió mi problema completamente.
Características destacadas
- Solo configuras pines y número de zonas.
- Arduino UNO controla hasta 5 zonas de suelo radiante.
- Con Arduino Mega, zonas casi ilimitadas.
El programa controla:
- Bomba del suelo radiante.
- Agrega todas las zonas como un solo termostato para la caldera.
- Válvulas de apertura/cierre por zona.
- Watchdog timer para operación robusta.
Calefacción individual por zona:
- Por zona: un termostato (entrada) que detecta demanda de calor.
- Por zona: un relé (salida) que controla una o más válvulas de ese grupo de bucles.
- Una estancia con varios grupos de bucles = una zona (cablear válvulas en paralelo al relé de zona).
- Más cómodo y ahorra energía: las estancias no se sobrecalientan.
Control de la bomba de suelo radiante:
- Solo funciona cuando hay demanda real de calefacción. Ahorra 100-200€/año en electricidad (bomba 80W = 2 kWh/día = 0.50€/día si funciona 24/7).
- Activación de mantenimiento: enciende bomba al menos 8 min cada 36 h si no hubo demanda (verano), para evitar gripaje.
- Evita arrancar bomba sin abrir válvulas antes (tardan 3-5 min en abrir).
Opcional: Control del resto de la casa (estancias sin suelo radiante)
- Suelen tener cabezales termostáticos en radiadores; solo calientan las estancias frías.
- Añade termostatos en las estancias a controlar. Cabléalos en paralelo a la entrada
No_Zone.
Notas finales
- No hace falta controlar todas las zonas; solo las que se pasan de temperatura o se quedan frías (las otras usan llaves manuales en el colector).
- Decidí no conectar a Internet:
- Aumentaría riesgo de fallos (debe ser "a prueba de balas").
- Termostatos inteligentes ya permiten control remoto; este controlador no añade valor ahí.
Código
ProjectCV.ino (C/C++)
/* Controlador calefacción suelo radiante multizona v1.0
* Copyright: GPL-3.0 Eric Kreuwels, 2017
* Créditos: Peter Kreuwels (casos de uso)
* ADVERTENCIA: FAST_MODE solo para test (bucle 50x más rápido).
* No usar FAST_MODE con bomba real: válvulas necesitan 3+ min para abrir.
*/
#include <avr/wdt.h> // Watchdog
// #define FAST_MODE // Descomenta solo para test/debug
#define VALVE_TIME 3000L // 5 min apertura/cierre válvula (seguridad)
#ifdef FAST_MODE
#define PUMP_MAINTENANCE_TIME 108000L // Test: cada 4 min (simula 36 h)
#else
#define PUMP_MAINTENANCE_TIME 1300000L // Real: cada 36 h
#endif
#define PUMP_ACTIVATION_TIME 5000L // Bomba activa ~8 min (10 seg test)
#define COOLDOWN_TIME 18000L // Post-calefacción: 30 min circulando (40 seg test)
#include "./Devices.h" // Clases Valve, Pump, Thermostat, LED, Manipulator
struct Zone {
String name;
Valve valve;
Thermostat thermostat;
};
//////////////////////////////////////////////////////
// BLOQUE DE CONFIGURACIÓN - ADAPTA A TU CABLEADO
#define HEATER_PIN 4 // Relé → entrada termostato caldera
#define FU_PUMP_PIN 5 // Relé → bomba suelo radiante
#define LIVING_VALVE 7 // Zona 1: relé válvula(s)
#define KITCHEN_VALVE 6 // Zona 2
#define DINING_VALVE 3 // Zona 3
#define LIVING_THERMO 8 // Zona 1: entrada termostato
#define KITCHEN_THERMO 9 // Zona 2
#define DINING_THERMO 11 // Zona 3
#define NO_ZONE_THERMO 10 // Opcional: termostatos estancias sin suelo
#define HEATING_LED 12 // LED: ON=calefacción, parpadea=enfriamiento
#define INDICATION_LED 13 // LED heartbeat (se puede quitar para liberar pin)
#define NR_ZONES 3
Zone Zones[NR_ZONES] = {
{"Salon", Valve(LIVING_VALVE, "Valvula Salon"), Thermostat(LIVING_THERMO, "Termostato Salon")},
{"Cocina", Valve(KITCHEN_VALVE, "Valvula Cocina"), Thermostat(KITCHEN_THERMO, "Termostato Cocina")},
{"Comedor", Valve(DINING_VALVE, "Valvula Comedor"), Thermostat(DINING_THERMO, "Termostato Comedor")}
};
// FIN BLOQUE CONFIGURACIÓN
LED iLED(INDICATION_LED, "LED Indicador");
LED hLED(HEATING_LED, "LED Calefaccion");
Manipulator CV(HEATER_PIN, "Caldera");
Pump FUPump(FU_PUMP_PIN, "Bomba Suelo");
Thermostat ZonelessThermo(NO_ZONE_THERMO, "Termostato Sin Zona");
State CVState; // Máquina de estados global
void setup() {
Serial.begin(115200);
printTimeStamp(); Serial.println(": Iniciando...");
#ifdef FAST_MODE
Serial.println("MODO TEST: tiempo 50x, mantenimiento bomba cada 3h");
#else
Serial.println("Modo normal. Formato timestamps (dd:hh:mm:ss)");
#endif
printConfiguration();
wdt_enable(WDTO_1S); // Watchdog: reset si no "pat the dog" en 1s
}
void loop() {
#ifdef FAST_MODE delay(2); else delay(100); #endif
iLED.Alternate(); // Heartbeat visual
FUPump.Update();
for (int i=0; i [");
_State = newState;
printState(_State); Serial.println("]");
// Acciones de TRANSICIÓN
switch(_State) {
case idle: hLED.Off(); CV.Off(); FUPump.Off(); allValvesOff(); break;
case on: hLED.On(); CV.On(); /* bomba espera válvulas */ break;
case cooldown: CV.Off(); allValvesOn(); /* bomba espera válvulas */ break;
}
}
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 cooldownCount>0; }
bool checkCoolDownNeeded() { return cooldownCount>0; }
void printState(states s) { if(s==idle)Serial.print("idle"); else if(s==on)Serial.print("on"); else Serial.print("cooldown"); }
};
// --- LÓGICA POR ESTADO ---
void onProcessing() {
if (ProcessThermostats()) { // ¿alguna zona pide calor?
if (FloorPumpingAllowed()) FUPump.On(); else FUPump.Off();
} else if (CVState.checkCoolDownNeeded()) {
CVState.set(State::cooldown);
} else {
CVState.set(State::idle);
}
}
void coolDownProcessing() {
hLED.Alternate();
if (HeatingRequested()) CVState.set(State::on);
else {
if (CVState.whileCoolDownNeeded()) {
if (FloorPumpingAllowed()) FUPump.On(); else FUPump.Off();
} else CVState.set(State::idle);
}
}
void idleProcessing() {
if (HeatingRequested()) CVState.set(State::on);
else {
// Mantenimiento bomba: 8 min cada 36 h
if (FUPump.doMaintenanceRun()) {
if (FUPump.IsOff()) {
if (!allValvesOpen()) { printTimeStamp(); Serial.println(": Mantenimiento bomba: abriendo válvulas"); allValvesOn(); }
if (FloorPumpingAllowed()) { printTimeStamp(); Serial.println(": Mantenimiento bomba: arrancando"); FUPump.On(); }
}
} else if (FUPump.IsOn()) {
printTimeStamp(); Serial.println(": Fin mantenimiento: parando bomba y cerrando válvulas");
FUPump.Off(); allValvesOff();
}
}
}
// --- FUNCIONES AUXILIARES ---
void allValvesOff() { for(int i=0;i
Devices.h (Clases de dispositivos)
// Clases base para dispositivos I/O
extern void printTimeStamp();
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(" pin("); Serial.print(_Pin); Serial.print(") = "); Serial.println(_IsOn?"On":"Off"); }
};
class Thermostat : public IODevice {
int _Counter;
public:
Thermostat(int pin, String name) : IODevice(pin,name), _Counter(0) { pinMode(_Pin, INPUT_PULLUP); }
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;
}
};
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(); }
bool IsOn() { return _IsOn; }
};
class Valve : public Manipulator {
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(transitionCount0) transitionCount--; } }
};
class Pump : public Manipulator {
long counter; bool doMaintenance;
public:
Pump(int pin, String name) : Manipulator(pin,name), counter(0), doMaintenance(false) {}
bool doMaintenanceRun() { return doMaintenance; }
void onSwitch() { Manipulator::onSwitch(); counter=0; }
void Update() {
if(IsOn()) {
if(counter250) #else if(++counter>5) #endif { counter=0; if(IsOn()) Off(); else On(); }
}
};
Esquemáticos


Log real del monitor serie: logexample_fTczkAa0tf.txt (muestra delay 5 min entre abrir válvulas y arrancar bomba).
Proceso de manufactura
- Sistemas de calefacción solar: de soluciones pasivas a activas para calefacción sostenible del hogar
- Monitorización de caldera de calefacción central con Raspberry Pi – Hardware, software y guía de configuración
- Sistema de Iluminación DMX Controlado por Web – Controlador Maestro Arduino
- Asistente de juego automatizado: Dispositivo inteligente de automatización de tareas para gamers
- Mando USB para juegos DIY con Arduino – Construye tu propio gamepad de alto rendimiento
- Unopad: Controlador MIDI Arduino para Ableton Live sin complicaciones
- Kit controlador para display fluorescente al vacío (VFD) basado en Arduino
- Integra tu calefacción central con Arduino para control inteligente
- Controlador doméstico Arduino con Alexa: Automatización inteligente simplificada
- Controlador de Potencia PWM: Construye un Driver de Lámpara con Modulación por Ancho de Pulso
- Guía Esencial para Mantener su Fregadora de Suelos en Óptimas Condiciones