Skip to main content

Drivers and Arduino Sketches for ERV Fans Control Testing

 



The Hookup

For proof of concept seems to run reliably in the lab. I don't want to be up and down ladders to make update I intend keeping the control system in Hubitat so I can tinker to my Hearts content.



Boost fan PWM control as well as start relays.

The valve position is a separate driver and device in Hubitat, This will use one of the PWM





Four boost fans in the ducts for each room inlet.

One fan in the two way turbo exhaust valve.


The valve position is a separate driver and device in Hubitat, This will use one of the PWM pins on the Wemos and its analogue input for the position pot


Hubitat Valve Position Driver


import groovy.json.JsonSlurper

metadata {
	definition(name: "Greenway ERV Turbo Valve", namespace: "Greenway", author: "Nick Goodey") {
		capability "Initialize"
		capability "Switch"
		capability "Switch Level"

		command "on"
		command "off"

	}

	preferences {

	  section("Device") {
		input("DeviceIndex", "number", title: "Index for device relay or fan", required: true, defaultValue: 0)
	   }
	
		section("URIs") {
			input "URI", "text", title: "Device URI", required: false, defaultValue: "http://192.168.1.17"
   
		}
		
		input("logEnable", "bool", title: "Enable logging", required: true, defaultValue: true)

	}

}


def installed() {
	log.info "installed..."
}



def updated() {
	if (logEnable) log.info "Updated..."
	initialize()
}

def uninstalled() {
	if (logEnable) log.info "Disconnecting from mqtt"
}


def initialize() {

	if (logEnable) runIn(900, logsOff)

}


def logsOff() {
	log.warn "Debug logging disabled."
	device.updateSetting("logEnable", [value: "false", type: "bool"])
}

			
def on() {
  
	try {
		url = "$settings.URI/?RELAY=$settings.DeviceIndex%201"
		if (logEnable) log.debug url
		
		httpGet(url) { resp ->
			 if (resp.success) {
	 
				sendEvent(name: "switch", value: "on", isStateChange: true)

			}
			if (logEnable)
				if (resp.data) log.debug "${resp.data}"
		}
	} catch (Exception e) {
		log.warn "Call to on failed: ${e.message}"
	}
}

def off() {
	

	try {
		url = "$settings.URI/?RELAY=$settings.DeviceIndex%200"
		if (logEnable) log.debug url
		
		httpGet(url) { resp ->
			if (resp.success) {
			   
				sendEvent(name: "switch", value: "off", isStateChange: true)

			}
			if (logEnable)
				if (resp.data) log.debug "${resp.data}"
		}
	} catch (Exception e) {
		log.warn "Call to off failed: ${e.message}"
	}
}       
			

def setLevel(value, rate = null) {

	if(value < 0) value = 0;
	if(value > 100) value = 100;
	
	try {
		url = "$settings.URI/?VALVE=$settings.DeviceIndex%20$value"
		if (logEnable) log.debug url
		
		httpGet(url) { resp ->
			if (resp.success) {
								
				sendEvent(name: "level", value: value)
			}
			if (logEnable){
				log.debug "$bits"
				if (resp.data) log.debug "${resp.data}"
			}
		}
	} catch (Exception e) {
		log.warn "Call to off failed: ${e.message}"
	}

	if (value == 0) {
		sendEvent(name: "switch", value: "off")
	}

}




Arduino Wemos D1 R2 



#include <Arduino_JSON.h>
#include <ESP8266WiFi.h>
#include <MQTT.h>
#include <ESP8266WebServer.h>

#define LED_PIN LED_BUILTIN //LED_BUILTIN is built in LED

String inputString = "";         // a String to hold incoming data
bool stringComplete = false;  // whether the string is complete

WiFiClient net;
MQTTClient MQTTclient; 
ESP8266WebServer server(80);

//WiFi
const char ssid[] = "????";
const char pass[] = "???";
IPAddress local_IP(192, 168, 1, 17);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0); 

// the IP address for the MQTT server
char MQTTip[] = "192.168.1.71";   

unsigned long UpdateCount = 6;
unsigned long TickCount = UpdateCount-1;
unsigned long PacketCount = 0;
unsigned long lastMillis = 0;

void handleRoot() {

  //http://192.168.1.17/?fan=1&speed=120
  //http://192.168.1.17/?relay=1
  /*
  String message = "RESPONSE\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  */
  
  String message = "";
  
  for (uint8_t i=0; i<server.args(); i++){
    message += " " + server.argName(i) + "=" + server.arg(i) + "\n";
  }

  String payload = server.argName(0) + " " + server.arg(0);

  Serial.println(payload);


  inputString = "";
  
  int tout = 1000;
  
  while( (!Serial.available()) && ((--tout) > 0) )
  {  
    delay(1);
    
    if(tout <= 0)
    {
      message = "IO TIMEOUT";
      break;
    }
    else//GET RESPONSE
    {
      while (Serial.available()) 
      {    
        char inChar = (char)Serial.read();
        
        if( (inChar == '\r') || (inChar == '\n') )
        { 
          message += inputString;      
        }
        else
        {
          inputString += inChar;
        }
        
      }//End of while (Serial.available()) 

      break;
    
    }//End of if(tout <= 0)
    
  }//End of while( (!Serial.available()) && ((--tout) > 0) )
  
  //message += " TOUT=" + String(tout);  
     
  server.send(200, "text/plain", message);
  
}


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

void setup() {

  pinMode(LED_PIN, OUTPUT);
    
  Serial.begin(115200);

 // Configures static IP address
  if (!WiFi.config(local_IP, gateway, subnet)) {
    Serial.println("STA Failed to configure");
  }
  
  WiFi.begin(ssid, pass);

  // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported by Arduino.
  // You need to set the IP address directly.
  MQTTclient.begin(MQTTip, net);
  MQTTclient.onMessage(messageReceived);

  WiFiconnect();
  
  MQTTclient.publish("/ervcontrol/status", "start");    

  MQTTclient.publish("/ervcontrol/status", "run");
  
  server.on("/", handleRoot);

  server.on("/status", [](){
    
    server.send(200, "text/plain", "machine status");
  });

  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("/ervcontrol/status HTTP server started");
  MQTTclient.publish("/ervcontrol/status", "HTTP server started");
}

void loop() {
  
  MQTTclient.loop();
  
  delay(10);  // <- fixes some issues with WiFi stability

  if (!MQTTclient.connected()) {
    WiFiconnect();
  }

  server.handleClient();
    
  // publish a message roughly every second.
  if (millis() - lastMillis > (UpdateCount * 1000)) 
  { 
     lastMillis = millis();
     SendMQTT();
  }
  
  delay(100);
}


void WiFiconnect() {
  
  Serial.print("checking wifi...");
  Serial.println(ssid);
  
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
    digitalWrite(LED_PIN, LOW);
    delay(500);
    digitalWrite(LED_PIN, HIGH);
  }
  
  Serial.println("\nWiFi Connected! ");
  Serial.println( WiFi.localIP());

  Serial.print("\nMQTT connecting...");
  while (!MQTTclient.connect(WiFi.localIP().toString().c_str() , "try", "try")) {
    Serial.print(".");
    delay(800);
    digitalWrite(LED_PIN, LOW);
    delay(200);
    digitalWrite(LED_PIN, HIGH);
  }

  Serial.println("\nMQTT Connected!");

  digitalWrite(LED_PIN, LOW);
  
  MQTTclient.subscribe("/ervcontrol/control");  
  MQTTclient.publish("/ervcontrol/status", "start");
  MQTTclient.publish("/ervcontrol/localip", "/ervcontrol/localip/" + WiFi.localIP().toString());

}

void SendMQTT()
{
  digitalWrite(LED_PIN, LOW);
    
  JSONVar myArray;

  
  
  //STALE
  myArray[0] = analogRead(0);
  myArray[1] = analogRead(1);
  myArray[2] = analogRead(2);
  myArray[3] = analogRead(3);
  myArray[4] = analogRead(4);
  myArray[5] = analogRead(5);

  
  //MISC
  myArray[6] = (PacketCount++);
  myArray[7] = WiFi.RSSI();
  
   
  String jsonString = JSON.stringify(myArray);
  
  MQTTclient.publish("/ervcontrol/sensors", jsonString);
  
  digitalWrite(LED_PIN, HIGH);
}

//Test MQTT mon
//Topic: /ervcontrol/control
//Payload: RELAYS 1 0 1 0 1 1 

void messageReceived(String &topic, String &payload) {

  digitalWrite(LED_PIN, LOW);
  
  if(topic == "/ervcontrol/control")
  {  
    if(payload.startsWith("read"))
    {
       SendMQTT();
       MQTTclient.publish("/ervcontrol/localip", "/ervcontrol/localip/" + WiFi.localIP().toString());
    }
    else
    {
      Serial.println(payload);
    }
  
    MQTTclient.publish("/ervcontrol/payload", topic + "/" + payload);

  }

  digitalWrite(LED_PIN, HIGH);
 
}





Arduino Uno Remote IO 


#include <Arduino_JSON.h>

const int Relay[6] = {2, 4, 7, 8, 12, 13};
const int Fan[6] = {3, 5, 6, 9, 10, 11};

String inputString = "";        
bool stringComplete = false;
String LastCommand = "";
String Status[6] = {"","","","","",""};

void setup() 
{
  // initialize serial:
  Serial.begin(115200);

  pinMode(Relay[0], OUTPUT);
  pinMode(Relay[1], OUTPUT);
  pinMode(Relay[2], OUTPUT);
  pinMode(Relay[3], OUTPUT);
  pinMode(Relay[4], OUTPUT);
  pinMode(Relay[5], OUTPUT);

  // reserve 200 bytes for the inputString:
  inputString.reserve(200);
}

void loop() {
  
  if (stringComplete) {
    
    
    
    if(inputString.substring(0,6) == "STATUS")
    {      
      JSONVar myArray;

      myArray[0] = analogRead(0);
      myArray[1] = analogRead(1);
      myArray[2] = analogRead(2);
      myArray[3] = analogRead(3);
      myArray[4] = analogRead(4);
      myArray[5] = analogRead(5);      
      myArray[6] = LastCommand;
      myArray[7] = Status;

      inputString = JSON.stringify(myArray);

    }
    else if(inputString.substring(0,6) == "RELAY ")
    {
      int key = getValue(inputString, ' ', 1, 0, 5);
      int value = getValue(inputString, ' ', 2, 0, 1);

      digitalWrite(Relay[key], value);

      Status[key] = inputString.substring(0,6) + " " + key + " " + value;
    }
    else if(inputString.substring(0,7) == "RELAYS ")
    {
      digitalWrite(Relay[0], getValue(inputString, ' ', 1, 0, 1) );
      digitalWrite(Relay[1], getValue(inputString, ' ', 2, 0, 1) );
      digitalWrite(Relay[2], getValue(inputString, ' ', 3, 0, 1) );
      digitalWrite(Relay[3], getValue(inputString, ' ', 4, 0, 1) );
      digitalWrite(Relay[4], getValue(inputString, ' ', 5, 0, 1) );
      digitalWrite(Relay[5], getValue(inputString, ' ', 6, 0, 1) );
      
      Status[0] = getValue(inputString, ' ', 1, 0, 1);
      Status[1] = getValue(inputString, ' ', 2, 0, 1);
      Status[2] = getValue(inputString, ' ', 3, 0, 1);
      Status[3] = getValue(inputString, ' ', 4, 0, 1);
      Status[4] = getValue(inputString, ' ', 5, 0, 1);
      Status[5] = getValue(inputString, ' ', 6, 0, 1);

    }
    else if(inputString.substring(0,4) == "FAN ")
    {
      int key = getValue(inputString, ' ', 1, 0, 5);
      int value = getValue(inputString, ' ', 2, 0, 255);

      analogWrite(Fan[key], value);

      Status[key] = inputString.substring(0,6) + " " + key + " " + value;

    }
    else  if(inputString.substring(0,5) == "FANS ")
    {
      analogWrite(Fan[0], getValue(inputString, ' ', 1, 0, 255) );
      analogWrite(Fan[1], getValue(inputString, ' ', 2, 0, 255) );
      analogWrite(Fan[2], getValue(inputString, ' ', 3, 0, 255) );
      analogWrite(Fan[3], getValue(inputString, ' ', 4, 0, 255) );
      analogWrite(Fan[4], getValue(inputString, ' ', 5, 0, 255) );
      analogWrite(Fan[5], getValue(inputString, ' ', 6, 0, 255) );
      
      Status[0] = getValue(inputString, ' ', 1, 0, 255);
      Status[1] = getValue(inputString, ' ', 2, 0, 255);
      Status[2] = getValue(inputString, ' ', 3, 0, 255);
      Status[3] = getValue(inputString, ' ', 4, 0, 255);
      Status[4] = getValue(inputString, ' ', 5, 0, 255);
      Status[5] = getValue(inputString, ' ', 6, 0, 255);
    }
    
    LastCommand = inputString;
    
    Serial.println(inputString);
    
    inputString = "";
    stringComplete = false;
    
  }//End of if (stringComplete)

}

/*
  SerialEvent occurs whenever a new data comes in the hardware serial RX. This
  routine is run between each time loop() runs, so using delay inside loop can
  delay response. Multiple bytes of data may be available.
*/
void serialEvent() {
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag so the main loop can
    // do something about it:
    if( (inChar == '\r') || (inChar == '\n') ) {
      stringComplete = true;
    }
  }
}


int getValue(String data, char separator, int index, int imin, int imax)
{
    int found = 0;
    int strIndex[] = { 0, -1 };
    int maxIndex = data.length() - 1;

    for (int i = 0; i <= maxIndex && found <= index; i++) {
        if (data.charAt(i) == separator || i == maxIndex) {
            found++;
            strIndex[0] = strIndex[1] + 1;
            strIndex[1] = (i == maxIndex) ? i+1 : i;
        }
    }
    String sresp =  found > index ? data.substring(strIndex[0], strIndex[1]) : "";

    return constrain(sresp.toInt(), imin, imax);
}






Popular Posts

The Heat Exchanger

  Under construction It took around 6 hours to layer the core Based on a 60 Litre Sistema box with a 400 X 400 X 350 6 mm Coroplast core. The fan speed controller is mounted on the end so it takes a 12V 10 Amp supply from the power system and 2 analogue 0 to 5 V signals for speed demand. The pink foam is a NZ$12 exercise matt which insulates it for both sound and heat quite nicely. 60 Litre Plenum on the vent side See  https://greenwayerv.blogspot.com/2020/10/we-added-60-litre-plenum-on-vent-side.html

Home Ventilation Heat Exchanger

 

The Experiment

First things first an experiment. I had been looking around at various homemade ERV core designs and eventually decided to try a Coroplast design. The experimental core was tiny 12 X 12 X 12 cm  using 3 mm Coroplast Nice isn't it :) 20 80 mm PC cooling fans at a fixed speed four temperature probes. Here is the full scale one around 40 X 40 X 35 cm using 6 mm Coroplast A Wemos D1/R2 for instrumentation sending temperatures via MQTT to Node-Red. I'm was not totally convinced by the numbers but it did definitely exchange significant amounts of heat. I checked the calibration and it was good compared to my multi-meter TC probe. In any case it seems to work even at such a small scale. Never did understand why it seems to gain more heat that it lost from the exhaust steam, checked for leaks there were none. Any way  a hairdryer experiment showed it worked and heat was transferred too so I was happy that it would probably work at a larger scale.

Heat Exchanger Low Ambient 86% Heat Recovery

  Drop on exiting air 2.9C Heating of incoming air 2.5C So 86% of the heat recovered

The ERV Data Acquisition System

  Progress over the weekend with the ERV DAS The 999's are me testing the sensor disconnected response. I have a Pitot tube left over from an RC plane I think I might put it in the inlet air stream Pin assignment in the Arduino sketch DHT dht[ 4 ] = {DHT( 4 , DHTTYPE),DHT( 0 , DHTTYPE),DHT( 2 , DHTTYPE),DHT( 14 , DHTTYPE)}; There are 4 DHT22's inside heat exchanger lid, shown below. Sensor locations Arduino code #include < Arduino_JSON . h > #include < ESP8266WiFi . h > #include < MQTT . h > #define LED_PIN LED_BUILTIN //LED_BUILTIN is built in LED #include "DHT.h" // Uncomment whatever type you're using! #define DHTTYPE DHT11 // DHT 11 //#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321 //#define DHTTYPE DHT21 // DHT 21 (AM2301) WiFiClient net ; MQTTClient MQTTclient ; const char ssid [ ] = "Network" ; const char pass [ ] = "DF@#$%" ; // the IP address for the MQTT server char MQTTip [