Radiator booster (heating)


  • Hero Member

    Winter is cold and energy consumption going up. Time to get out of my cave and start a new project ;-).
    Low temperature heating systems use special radiators which can be benficial also for other heating systems.
    With a few PC vents I built a radiator booster to increase yield of my existing radiators. Still in research stage but results are promising. There are a few commercial options but these are either sub-optimal, expensive and not MySensorized:-1:
    0_1486224457799_upload-32148448-19bf-4fa3-bc2b-9e5390a6cb29 0_1486224551817_upload-e0df9470-6973-4978-910b-e172fce78276 0_1486224672608_upload-e6901de8-736a-43dd-bf72-c228abbdd8b5

    The workbench example built around a compact MySensors node jModule three Dallas temp sensors and PWM circuitry. The whole thing lives in or under the radiator.
    0_1486223384333_upload-15ca957a-7c92-457e-b732-46a1ec6671a1

    0_1486223717517_upload-c5ba7173-eb26-459b-99b9-978284cad0bc

    Working principle:

    • Measure temperatures for water in/ out and environment.
    • Vents start spinning slowly as the "In" temperature reaches a threshold (30 Celcius for now)
    • Vent spinning increase in time for a few minutes and then slowly spin down until constant level.
    • Vents stop spinning when temperature falls below threshold.
    • etc.

    Extra options:

    • Manual mode, started automatically when (MySensors) controller takes over.
    • Fan speed control for tuning

    I am curious if anybody has some hands-on experience on how to best tune the system. And will publish details (sketch) if anyone is interested.

    0_1486224230174_upload-d2dbb63e-1cfc-4a7a-8e14-5c10b6e057090_1486224762708_upload-40a2fd75-5190-4c40-8b63-42160b059d8d



  • @AWI Hi, Very interesting project, please if you can publish sketch?
    Thanks.


  • Hero Member

    @Andrej_AB I think this sketch is doing the job. There are some things to take into account:

    • You can comment out the line #define SPEED_CONTROL_PIN A0 // connect to potentiometer for speed control if you don't want to have manual speed control.
    • If you use a PMW fan (4 wire) you need to adjust the PWM frequency to something near 25kHz. ~32 kHz works for me (see sketch) but some fan's are more sensitive.
    • You can control a non-PWM fan (2 or 3 three wire) with an external MOSFET. In this case you need to lower the PWM frequency to keep a brushless motor in control. ~31 Hz (see sketch) worked for me with the circuit below (sorry for the quality of the drawing ;-). MOSFET is IRLZ44 or almost any other.
      0_1486326818435_upload-4c35d27c-2091-4252-aeed-c8098914afc4
    • Be aware that I changed the transmission channel / set a fixed node/ set a fixed parent for my own network
    /*
     PROJECT: MySensors / Ventilator control
     PROGRAMMER: AWI
     DATE: february 2, 2017/ last update: february 5, 2017
     FILE: AWI_Radiator_Booster.ino
     LICENSE: Public domain
    
     Hardware: MySensors 2.0, MOSFET dimmer, Dallas temperature
    		
     Special:
    	
     SUMMARY:
    
    	Full independent with manual mode
    	Controls ventilator speed with PWM
    	Measure temperature and change speed accordingly
    	
     Remarks:
    	Fixed node-id
    	
     Change log:
     20170202 - Created from Ventilator control
     20170205 - Clean code and change manual mode 
    */
    
    // Enable debug prints to serial monitor
    #define MY_DEBUG 
    #define MY_PARENT_NODE_ID 0
    #define MY_NODE_ID 91											// fixed node number
    #define NODE_TXT "Radiator Boost 91"							// Text to add to sensor name
    #define MY_BAUD_RATE 19200
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    #define MY_RF24_CHANNEL 83										// 83, radio channel, default = 76
    
    #undef MY_REGISTRATION_FEATURE									// sketch moves on if no registration
    
    #include <SPI.h>
    #include <MySensors.h> 
    #include <OneWire.h>
    #include <DallasTemperature.h>
    
    #define ONE_WIRE_BUS 4 											// Pin where dallas sensor is connected 
    #define MAX_ATTACHED_DS18B20 3
    #define TEMPERATURE_PRECISION TEMP_12_BIT
    #define IN_THERM 0 												// indexes of in thermometer, adapt to you situation
    #define OUT_THERM 2
    #define ENV_THERM 1
    
    #define VENT_PIN1 3      										// Arduino PWM pin attached to MOSFET Gate pin -or- vent PWM pin (adjust PWM-Frequency)
    #define VENT_MIN_LEVEL 20										// lowest pwm level for vent
    #define VENT_MAX_LEVEL 255										// highest pwm level for vent
    
    // #define SPEED_CONTROL_PIN A0									// connect to potentiometer for speed control, if commented out no speed control
    
    #define IN_TEMP_CHILD	0										// measure incoming water temp
    #define OUT_TEMP_CHILD	1										// measure outgoing water temp
    #define ENV_TEMP_CHILD	3										// environment
    #define VENT_CHILD	2											// ventilator
    
    // Helper for Debug:  1 = Serial debug output ; 2 = V_TEXT remote output ; else no debug
    // Use Formats described in fprint() : http://www.cplusplus.com/reference/cstdio/printf/  
    // Example: 	Printf("Temp %2d Hum %2d\n", temperature, humidity);
    // warning: max print size < 24 ; float = NOT supported in Arduino, you need to convert it yourself, ie. dtostrf(Temperature, 5, 2, tempBuf)
    #define _DEBUG 1												// 0 = no output ; 1 = Serial debug output ; 2 = V_TEXT remote output 
    #if _DEBUG == 1													// Serial output
    	char printBuf[24] ;											// need temporary buffer
    	#define Printf(...) { sprintf(printBuf, __VA_ARGS__) ; Serial.print(printBuf);}	// macro to substitute Printf()
    #elif _DEBUG == 2												// if remote debug you need to define a child and present it to the controller
    	#define DEBUG_CHILD_ID 10									// Child id of V_TEXT
    	MyMessage debugMsg(DEBUG_CHILD_ID, V_TEXT);					// get the debug message ready
    	char printBuf[24] ;											// need temporary buffer
    	#define Printf(...) { sprintf(printBuf, __VA_ARGS__) ; send(debugMsg.set(printBuf)); }	// macro to substitute Printf()						
    #else															// No debug wanted
    	#define Printf(...)										
    #endif
    
    const unsigned long heartbeatInterval = 1 * 3600UL * 1000UL ;	// heartbeat, let the controller know its alive
    unsigned long heartbeatCounter = 0 ;
    
    const unsigned long updateInterval = 10 * 1000UL ;				// controller update
    unsigned long lastUpdate = 0 ;
    
    const unsigned long manualTime = 60 * 60 * 1000UL ;				// manual timer exit to automatic if not reset by controller command reception 
    unsigned long manualTimer = 0 ;
    boolean manualFlag = false ;									// flag for boost machine to indicate manual operation
    boolean manualStatus = false ;									// actual status of automatic/ manual
    
    const unsigned long boostTime = 30 * 60 * 1000UL ;				// boost timer: period boost is active 
    unsigned long boostTimer = 0 ;
    boolean boostStatus = false ;
    const uint8_t boostTemperature = 30 ;							// temperature on which boost becomes active in Celcius
    const uint8_t boostThreshold = 2 ;								// threshold for temperature in Celcius	
    
    const uint8_t maxVentLevel = 0xFF ;								// level in max boost
    const uint8_t minVentLevel = 20 ;								// level in min boost	
    const uint8_t runVentLevel = 50 ;								// level in running state (rest)	
    
    const unsigned long dimLevelInterval = 10UL ;					// dimlevelinterval
    unsigned long lastDimLevelUpdate = 0 ;
    
    uint8_t lastDimLevel = 0x0 , newDimLevel = 0x0	;				// Current or last dim level...
    
    // flags to indicate if transmission is needed, heartbeat and/or changes > treshold
    boolean txTemperature = true ;									// flags to indicate if transmit is needed (time / change driven)
    const float tempThreshold = 0.01 ; 								// send only if change > treshold (Celcius)
    
    // arrays to hold device addresses and last temp
    DeviceAddress dThermometer[MAX_ATTACHED_DS18B20];				// hold addresses
    float lastTemperature[MAX_ATTACHED_DS18B20];
    
    OneWire oneWire(ONE_WIRE_BUS); 									// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
    DallasTemperature sensors(&oneWire); 							// Pass the oneWire reference to Dallas Temperature. 
    
    MyMessage dimmerMsg(0, V_PERCENTAGE);
    MyMessage lightMsg(0, V_LIGHT);
    MyMessage temperatureMsg(0, V_TEMP);							// Temp 
    
    /***
     * Dimmable LED initialization method
     */
    void setup(){ 
    	// Set PWM for vents, PWM 4 wire vents needs 25kHz, PWM 3 wire (voltage controlled) need low frequency PWM < 100 Hz
    	// comment out frequency here. (https://arduino-info.wikispaces.com/Arduino-PWM-Frequency)
    	pinMode( VENT_PIN1, OUTPUT) ;
    	TCCR2B = TCCR2B & B11111000 | B00000001;    				// set timer 2 divisor to     1 for PWM frequency of 31372.55 Hz	
    	//TCCR2B = TCCR2B & B11111000 | B00000110;    				// pin3, set timer 2 divisor to   256 for PWM frequency of   122.55 Hz
    	//TCCR2B = TCCR2B & B11111000 | B00000111;    				// set timer 2 divisor to  1024 for PWM frequency of    30.64 Hz
    
    
    	// Startup up the OneWire library
    	sensors.begin();
    	sensors.setResolution(TEMPERATURE_PRECISION);
    	for (int i = 0; i < MAX_ATTACHED_DS18B20; i++ ){
    		if (!sensors.getAddress(dThermometer[i], i)){
    			Printf("no address for Device \n") ;
    		}
    	}
    	// requestTemperatures() will not block current thread
    	sensors.setWaitForConversion(false);
    }
    
    void presentation() {
    	// Send the sketch version information to the gateway and Controller
    	sendSketchInfo("AWI " NODE_TXT, "1.0");
    	
    	present(VENT_CHILD, S_DIMMER, "Speed " NODE_TXT);
    	
    	present(IN_TEMP_CHILD, S_TEMP, "T in " NODE_TXT);
    	present(OUT_TEMP_CHILD, S_TEMP, "T out " NODE_TXT);	
    	present(ENV_TEMP_CHILD, S_TEMP, "T env " NODE_TXT);	
    }
    
    /***
     *  Dimmable LED main processing loop 
     */
    void loop(){
    	unsigned long now = millis();
    	if ( now > heartbeatCounter + heartbeatInterval) {				// alive signal to controller
    	    sendHeartbeat();
    		heartbeatCounter = now ; 
    	}
    	if ( now > lastUpdate + updateInterval) {						// update all sensors
    	    readTempHum();
    		lastUpdate = now ;
    		boostMachine() ;
    	}
    	fadeToLevel();													// dimmer fade update
    }
    
    void receive(const MyMessage &message) {
    	if (message.type == V_STATUS){
    		manualStatus = (message.getInt() != 0)?true:false ;
    		manualFlag = true ;											// override automatic operation
    	} else if (message.type == V_DIMMER){
        //  Retrieve the power or dim level from the incoming request message
    		newDimLevel = map(message.getLong(), 0, 100, 0, 255);
    		lastDimLevel = newDimLevel ;
    		manualStatus = true ;
    		manualFlag = true ;											// override automatic operation
    		Printf("Level %3d from %3d\n", newDimLevel, lastDimLevel);
    	}
    }
    
    /* 	Boost state machine
    	States:
    	- Idle:  	nothing waiting armed, vent off
    	- Armed:	active waiting for start
    	- Boost:  	starting fast start-up curve, 
    	- Running:	in action, slow vent 
    	- Manual: 	commands received from controller, exit after manual timer
    */ 
    void boostMachine() {
    	unsigned long now = millis(); 									// loop timer reference
    	enum state_t { idle_s, armed_s, boost_s, running_s, manual_s } ;
    	static state_t State = idle_s ; 								// start in idle
    	if (manualFlag){												// check if manual here is not the best solution, but works
    		State = manual_s ; 											// manualflag needs to be reset within time frame
    		manualTimer = now ;
    		manualFlag = false ;
    	}
    	switch (State) {
    		case idle_s:
    			if(( boostTemperature - lastTemperature[0] ) > boostThreshold ){	// arm when in_temp below boost temperature
    				State = armed_s ;
    				newDimLevel = 0 ;
    				Printf("Armed started\n") ;
    			}
    			break ;
    		case armed_s:												// default state, browse through patterns
    			if (( lastTemperature[0] - boostTemperature) > boostThreshold){		// when temp rises above threshold start boost curve
    				State = boost_s ;
    				boostTimer = now ;									// boost lasts for boost time from now
    				Printf("Boost started\n") ;
    			}
    			break ;
    		case boost_s:												// boost state with vent curve, wait for completion
    			if ( now > boostTimer + boostTime) {					// check if boost time expired
    				newDimLevel = runVentLevel ;						// set vent to running (rest) level
    				Printf("Boost ended on time, to running\n") ;
    				State = running_s ;
    			} else {												// boost curve (can be extended now triangle)
    				int boostProgress = map((now - boostTimer)/1000, 0, boostTime/ 1000, 0, 0xFF ) ; // progress in 0xFF steps
    				const uint8_t maxSpeed = 0x20 ;						//  split point in boost period
    				if (boostProgress < maxSpeed){						// speed increase to max for first period
    					newDimLevel = map(boostProgress, 0, maxSpeed, 0, 0xFF) ;
    				} else {											// slow speed decrease to min for second period
    					newDimLevel = 0xFF - map(boostProgress, maxSpeed, 0xFF, 0, 0xFF) ;
    				} 
    				Printf("Prg %d, Spd %d\n", boostProgress, newDimLevel) ;
    			}
    			break ;
    		case running_s:												// steady running waiting for end condition
    			if (( boostTemperature - lastTemperature[IN_THERM] ) > boostThreshold ){
    				State = idle_s ;
    				Printf("Idle\n") ;
    				newDimLevel = 0 ;
    			}
    			break ;
    		case manual_s:												// reacting on actions from controller. Exit on timer (or controller command)
    			if ( (now > manualTimer + manualTime) || !manualStatus ){
    				State = idle_s ;
    				Printf("Idle\n") ;
    				manualStatus = false ;
    				newDimLevel = 0 ;
    			}
    			break ;
    
    		default :
    			State = idle_s ;
    			break ;
    	}
    }
    
    void readTempHum(void){
    	sensors.requestTemperatures();					// Fetch temperatures from Dallas sensors
    	// query conversion time and sleep until conversion completed
    	int16_t conversionTime = sensors.millisToWaitForConversion(sensors.getResolution());
    	//Printf("Conversion time %4d ms \n", conversionTime);
    	wait(conversionTime);
    	
    	// Read temperatures and send them to controller if no error and change > threshold
    	float temperature ; 
    	for (int i = 0; i < MAX_ATTACHED_DS18B20; i++ ){
    		temperature	= sensors.getTempC(dThermometer[i]) ;
    		Printf("Temp %2d %2dC \n", i, (int)temperature) ;
    		if ( abs(temperature - lastTemperature[i]) >= tempThreshold && temperature != -127.00 && temperature != 85.00){ 
    			lastTemperature[i] = temperature ;
    			txTemperature = true ;
    		}
    	}
    	if (txTemperature){
    		send(temperatureMsg.setSensor(IN_TEMP_CHILD).set(lastTemperature[IN_THERM], 2));		// Send in deg C
    		send(temperatureMsg.setSensor(OUT_TEMP_CHILD).set(lastTemperature[OUT_THERM], 2));		// Send in deg C
    		send(temperatureMsg.setSensor(ENV_TEMP_CHILD).set(lastTemperature[ENV_THERM], 2));		// Send in deg C
    		txTemperature = false ;
    	}
    }
    
    /***
     *  gracefull fade to global newDimLevel
     */
    void fadeToLevel() {
      	unsigned long now = millis() ;
    	static uint8_t curDimLevel = 0 ;
    	static uint8_t lastAnalogLevel = 0 ;
    	if (now > lastDimLevelUpdate + dimLevelInterval){					// check if time for update
    		if ( newDimLevel > curDimLevel) {
    			curDimLevel++ ;
    		} else if ( newDimLevel < curDimLevel){
    			curDimLevel-- ;
    		}
    		#ifdef SPEED_CONTROL_PIN										// if speedcontrol connected
    			uint8_t speedSetting = ((long)curDimLevel * analogRead(SPEED_CONTROL_PIN))/ 1024 ;		// adjust level with speed setting
    		#else
    			int speedSetting = curDimLevel ;							// no adjustment
    		#endif
    		uint8_t setAnalogLevel = (speedSetting == 0)?0:map((int)(speedSetting), 0, 255, VENT_MIN_LEVEL, VENT_MAX_LEVEL) ;	// calculate new analog
    		if (setAnalogLevel != lastAnalogLevel){							// avoid writes to PWM (needed ?)
    			Printf("Speed setting: %d\n", speedSetting);
    			Printf("Analog: %d \n", setAnalogLevel) ;
    			analogWrite( VENT_PIN1, setAnalogLevel);
    			lastAnalogLevel = setAnalogLevel ;
    			}
    		lastDimLevelUpdate = now ;
    	}
    }
    

    I just received a load of fans to equip the rest of the radiators :chart_with_upwards_trend:
    0_1486327191487_upload-15c4a7b5-b9b5-4e87-a312-ed8efdf18bcd



  • Hi,
    very interesting project. How did you choose the fan? Did you calculate the CFM or something else (f.i. why Silent 14 and not 12)? How do you assemble the fans, will you replicate any of the commercial solutions?
    Thank you.


  • Hero Member

    @mortommy No real answer here apart from (my) common sense. Large fans are more silent. These were avaliable for a few €. The size is also dependent on the size of the radiator. As my circuit makes it possible to set the fan speed I can avoid resonance noise.

    I can place the fans 'in' most of my radiators of the "Jaga"type below:
    0_1486387630195_upload-65a6907d-4a3d-45cf-8050-e9059f67ab1c
    For other types like below I need to make a construction to attach them at the bottow (possibly wood with rubber dampers)
    0_1486387430510_upload-55c49627-a0fa-46d1-85f5-58e16e440bc8

    Dampers:
    0_1486387761399_upload-970203ee-1f00-4ad9-aa32-438682e9c40d



  • @AWI Many thanks for quick response. Will try at soon, just will order fans.



  • @AWI I've made similar project but using RS485 and modbus for communication. Now I'm porting it into mysensors. In my project I've light sensor to lower fan revs during night (because of noise). I will try to post my sketch when I finish it.


Log in to reply
 

Looks like your connection to MySensors Forum was lost, please wait while we try to reconnect.