Bathroom fan controller

I was looking at a controller for my fan in my bathroom, but found none I liked. ($$$$ :P)

What it should have was

  • manual control
  • humidity controlled
  • autonomous air cycle (run for x mins every x hours)

What I did instead, was to order a 328, the stuff needed to run it, a humidity/temperature sensor, a relay brick, and a 230V>USB power supply.

The code will be updated later, when I got my piezo to beep out what it is doing depending on the button.

Everything is controllable from a button

  • 500-2500ms = +30 mins running (up to 4 hours)
  • 5000-10000ms = Start/stop without timer
  • 10000ms = soft reset

#define relayPin 13        //Relay on pin 8 (pin 14)
#define btnPin 3       //Button on pin 5 (pin 11)
#define maxTime 14400000 //How long the fan max can be on
#define triggerHumidity 40 //Start fan when humitidy is over this percentage


#define DHT11_PIN 0      // ADC0

byte read_dht11_dat()
{
  byte i = 0;
  byte result=0;
  for(i=0; i< 8; i++)
  {
    while(!(PINC & _BV(DHT11_PIN)));  // wait for 50us
    delayMicroseconds(30);

    if(PINC & _BV(DHT11_PIN))
    {
      result |=(1<<(7-i));
    }

    while((PINC & _BV(DHT11_PIN)));  // wait '1' finish
  }
  return result;
}


unsigned long btnTime = 0;

unsigned long  startTime = 0;
unsigned long  humidityTrigger = 0;
long  runTime = 0;


//For humidity smoothing
const int numReadings = 10;

int readings[numReadings];      // the readings from the analog input
int index = 0;                  // the index of the current reading
int total = 0;                  // the running total
int average = 0;                // the average

void setup()
{
  for (int thisReading = 0; thisReading < numReadings; thisReading++)
  {
    readings[thisReading] = 0; 
  }

  DDRC |= _BV(DHT11_PIN);
  PORTC |= _BV(DHT11_PIN);


  Serial.begin(9600);

  pinMode(relayPin, OUTPUT);
  pinMode(btnPin, INPUT);

  attachInterrupt(1, btnDN, FALLING); //Digital pin 3 (pin 5)
}

void loop()                     
{
  if (millis() >= (4294967295 - maxTime)-60000) software_Reset();
  //Check and reset if the fan can run for 4 hours without millis() rolling over
  //leave 1 minute to make sure the loop successfully software resetting

  int humidity = 0;

  byte dht11_dat[5];
  byte dht11_in;
  byte i;
  // start condition
  // 1. pull-down i/o pin from 18ms
  PORTC &= ~_BV(DHT11_PIN);
  delay(18);
  PORTC |= _BV(DHT11_PIN);
  delayMicroseconds(40);

  DDRC &= ~_BV(DHT11_PIN);
  delayMicroseconds(40);

  dht11_in = PINC & _BV(DHT11_PIN);

  if(dht11_in)
  {
    Serial.println("dht11 start condition 1 not met");
    return;
  }
  delayMicroseconds(80);

  dht11_in = PINC & _BV(DHT11_PIN);

  if(!dht11_in)
  {
    Serial.println("dht11 start condition 2 not met");
    return;
  }

  delayMicroseconds(80);

  // now ready for data reception
  for (i=0; i<5; i++)
  {
    dht11_dat[i] = read_dht11_dat();
  }

  DDRC |= _BV(DHT11_PIN);
  PORTC |= _BV(DHT11_PIN);

  byte dht11_check_sum = dht11_dat[0]+dht11_dat[1]+dht11_dat[2]+dht11_dat[3];

  // check check_sum
  if(dht11_dat[4]!= dht11_check_sum)
  {
    Serial.print("Checksum error");
    delay(1000);
  }
  else
  {
    // subtract the last reading:
    total= total - readings[index];         
    // read from the sensor:  
    readings[index] = (dht11_dat[0], DEC);
    // add the reading to the total:
    total= total + readings[index];       
    // advance to the next position in the array:  
    index = index + 1;                    

    // if we're at the end of the array...
    if (index >= numReadings)
    {
      index = 0; // ...wrap around to the beginning
    }

    // calculate the average:
    humidity = dht11_dat[0];
    
    if (humidity > triggerHumidity && millis() > 10000)
    {
      humidityTrigger = millis();
    }

    Serial.print("Humdity: ");
    Serial.print(humidity);
    Serial.print(" - Temperature: "); 
    Serial.println(dht11_dat[2], DEC);
  }

  long holdtime = 0;

  if (btnTime != 0 && digitalRead(btnPin) == HIGH)
  {
    holdtime = millis() - btnTime;
    btnTime = 0;
  }

  if (holdtime != 0)
  {
    if (holdtime >= 500 && holdtime <= 2500) //Short press, start fan for 30 mins, or add 30 mins, up to 4 hours
    {
      if (startTime == 0) startTime = millis();

      Serial.println("30 mins added");
      runTime += 1800000;
      if (runTime > maxTime) runTime = maxTime; //Check and set max time
    }

    else if (holdtime >= 5000 && holdtime <= 10000) //Long press, manually on/off
    {
      if (startTime == 0) startTime = millis();

      if (runTime == -1 || runTime > 0)
      {
        runTime = 0;
        Serial.println("Switched off");
      }
      else
      {
        runTime = -1; //Continuous on
        Serial.println("On until manually switched off");
      }
    }

    else if (holdtime >= 10000) //Very long press
    {
      Serial.println("Very long");
      software_Reset();
    }

    holdtime = 0;
  }

  //Check if the fan should be on or off
  if ((millis() - startTime <= runTime && startTime != 0) || runTime == -1 || (millis() - humidityTrigger <= 300000 && humidityTrigger != 0 ))
  {
    //Serial.println("on");
    digitalWrite(relayPin, HIGH);
  }
  else
  {
    //Serial.println("off");
    digitalWrite(relayPin, LOW);
  }

  delay(500); //Delay in for not spamming serial debugging
}

void btnDN()
{
  if (btnTime == 0)
  {
    btnTime = millis();
  }
}

void software_Reset() // Restarts program from beginning but does not reset the peripherals and registers
{
  asm volatile ("  jmp 0");  
}

To-do is still to get the average stuff implemented, get the piezo stuff in, maybe control for rgb leds, and maybe some blue pulsing leds under the cap, just to make it fancy :wink:

you will like it :) i converted my fan several years ago to automatic operation using the switch from an old dehumidifier but if i would have known about the Arduino then i would have used it as i found that summer and winter have an effect on the readings so i have to make it more sensitive in the winter and less in the summer

but with an Arduino you could just throw a temp sensor in the attic or maybe even just watch for a sudden change in humidity rather then above or below a set value so it wouldn't be effected by the gradual changes from weather/seasons

but i love it being automatic and i think they should make all of them like that so you don't have to worry about the super high humidity from someone taking a long hot shower and not turning on the fan :)

I want to make it detect sudden changes too, and only respond to that, but haven't decided how to fo it yet.

Maybe collect 10 samples every minute, and if last minute was 5% lower than this minute, then start.

My problem right now is that I don't have much to base the percentages on, so it is try and learn as I go. :)

Being a bathroom fan, you may consider adding a methane sensor too! :D

methane is for whimps...

I need geiger counter...

bld, great job!

did you get your sensor from Seeed studio? I recognize the plug and the wire. I’ve no way to tell if mine is accurate or not. Wonder if you’re happy with your (if you bought from Seeed studio).

Yes, I got the humidity/temperature sensor from seeedstudio (http://www.seeedstudio.com/depot/electronic-brick-indoor-temp-humidity-sensor-p-683.html?cPath=48_52).

I got no idea how accurate it is, but it only jumps 1% up and down when close to changing, and that is close enough for what I am going to use it for.

Actually, I don't even need to know the actual humidity, only notice the change in humidity, so it is plenty for my project.

Only "problem" I got with it, is the code to read it... My problem is that I don't understand it 100% and any attempt to simplify it failed so far.

I've done some data logging in my bathroom in the morning when I get up to shower. I've seen a 15% increase in RH in about 3 minutes. You could just look for a sudden change compared a running average. Not sure how often you should sample, but I think I was sampling about every 10 seconds. I also used an RTC in my project -- DS1307. My temp/humidity sensor is a Sensirion SHT-11.