Automated Weaver Ant Terrarium

Hi all,

First time poster here, please let me know if this is the wrong place for this topic!

The aim is to create an automated planted terrarium for a colony of tropical weaver ants . I would like the system to perform the following operations:

  • Turn some LED strip lights on/off at 9am/9pm
  • Run a fan for 5 minutes every hour, with a servo opening/closing a hatch at the beginning and end
  • Control a heater to maintain temperatures in the range of 25-30C
  • Display temperature and humidity on an LCD screen

I am very apprehensive of dealing with mains voltage (which the heater runs on). I have it triple insulated, but still want my code and circuited looked at by more experienced folks before I plug it in. I've invariably made mistakes as I am still learning- Any thoughts or assistance would be greatly appreciated.


LCD is wired as per example sketches

#include "Wire.h"
#define DS3231_I2C_ADDRESS 0x68
#include "DHT.h"
#include <Servo.h>
#include "RTClib.h"
#include <LiquidCrystal.h>
Servo servo;  // create servo object to control a servo
DHT dht;
RTC_DS1307 rtc;
unsigned long previousMillis = 0;
int fanState = LOW;

// --------CONSTANTS (won't change)---------------

const int fanPin = 7;
const int servoPin = 9;
const int heaterPin = 8;
const int lightPin = 10;
const int fanDuration = 300000; // number of millisecs that fan is on
const int interval = 3300000; // number of milisecs when the fan is off
#define maxtemp 29.0
#define mintemp 24.0

//------------ VARIABLES (will change)---------------------

byte targetfanState = HIGH; // this variable is necesary to help arduino monitor the status of LED
                            //the initial condition (t=0) can be either HIGH or LOW
unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()
unsigned long previousfanMillis = 0;   // will store last time the LED was updated
int pos = 0;    // variable to store the servo position

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
//========== THE SETUP ==============================

void setup() {
// put your setup code here, to run once:
  Wire.begin();
  pinMode(fanPin, OUTPUT);
  pinMode(servoPin, OUTPUT);
  pinMode(heaterPin, OUTPUT);
  pinMode(lightPin, OUTPUT);
   
  digitalWrite(fanPin, LOW);
  digitalWrite(servoPin, LOW);
  digitalWrite(heaterPin, LOW);
  digitalWrite(lightPin, LOW);
  
  servo.attach(3);  // attaches the servo on pin 9 to the servo object
  dht.setup(6);
  Serial.begin(9600);

  lcd.begin(16,2);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Temp: ");
  lcd.setCursor(0,1);
  lcd.print("Hum: ");
}

//========== THE LOOP ==============================

void loop() {
  // put your main code here, to run repeatedly:

  DateTime now = rtc.now();
  if ((now.hour() >= 21) || (now.hour() < 9)){
    digitalWrite(lightPin, LOW);
  }
  else digitalWrite (lightPin, HIGH);

  float h = dht.getHumidity();
  float t = dht.getTemperature();

  //error check
  if (isnan(h) || isnan(t)) {
    lcd.setCursor(0,0);
    lcd.print("sensorfail");
    return;
  }
  if (t < mintemp){
    digitalWrite(heaterPin, HIGH);
  }
  if (t > maxtemp){
    digitalWrite(heaterPin, LOW);
  }
   lcd.setCursor(6,0);
 lcd.print(t,2);
 lcd.print(" *C ");
  lcd.setCursor(5,1);
lcd.print(h,2);
lcd.println(" % ");
  
  
  currentMillis = millis();   // capture the latest value of millis()
                              // this is equivalent to noting the time from a clock
void updateTargetfan() {

  if (targetfanState == LOW) { //if the fan is OFF
     Serial.println("LOW");
    if ((unsigned long) currentMillis - previousfanMillis >= interval) { 
      servo.write(180);  //change depending on original position of servo
      delay(2000);
      targetfanState = HIGH;
      digitalWrite(fanPin, targetfanState);
      // and save the time when we made the change
      previousfanMillis += interval;
      // NOTE: The previous line could alternatively be
      //        previousLedMillis = currentMillis
      //        Adding on the interval is a better way to ensure that succesive periods are identical
    }
  }
  else {  // i.e. if onBoardLedState is HIGH (if the LED is ON)
    Serial.println("HIGH");
    if ((unsigned long) currentMillis - previousfanMillis >= fanDuration) {
      targetfanState = LOW;
      digitalWrite(fanPin, targetfanState);// and save the time when we made the change
      delay(1000);
      servo.write(0);  //change depending on original position of servo
      delay(2000);
      
      previousfanMillis += fanDuration;
      // NOTE: The previous line could alternatively be
      //        previousLedMillis = currentMillis
      //        Adding on the duration is a better way to ensure that succesive periods are identical
    }
    if (fanPin = HIGH){
      lcd.setCursor(12,1);
      lcd.print("F");}
      if (heaterPin = HIGH){
        lcd.setCursor(12,0);
      lcd.print("H");}
    }
   delay(10000); }
}

You should provide details of the relays you are using particularly relay 4. If it is an opto-isolated relay designed for switching 230V AC it should be safe. I am not sure where the triple insulation comes in, perhaps you could supply a photograph of how you have wired it up.

Hi ardly, thanks for your reply.

I used this relay, it is rated for mains voltage. I have it set up inside a 3d printed box which will be housed with all the other electronics inside an MDF enclosure.

A few things on your schematic:
The 12V fan and 12V LED strip I would switch using MOSFET transistors instead of using a relay. It's just easier.

You have a 12V to 5V converter, why not use that same 5V to power the Arduino, instead of feeding it 12V and hope it's puny little regulator is not going to overheat? That's 7V to drop there, a lot of power wasted, lots of heat in one little spot.

Also there are much better humidity/temperature sensors than the DHT22. In my experience they're fine for temperature, but not for humidity (mine tend to get stuck at 99.9% even though humidity is normally more like 80-90%, reaching >95% only when it rains and at night when temperature drops). Your tropical ants will also have quite high a humidity in their enclosure, I would assume.

First I don't know much about electronics, second I may be over cautious.

It looks to me like you will be connecting 230V to the screw terminals on one of the relays. Those screws are recessed and so not too easy to touch. At the rear of the board though all the terminals are soldered through (see image) and therefore live.

Since everything is in a plastic box that may be fine. However maybe it would be an idea to use spacers and have a perspex sheet at the back of the board. That way if the box is opened without disconnecting the mains there is much less chance of fingers touching anything live. There is also less chance of other conductors coming in contact with the mains. If the fan and heater have metal casings make sure they are earthed.

solder.JPG

solder.JPG

There are four screw holes in that PCB. Use them. That way you also can't touch the back, and the board will stay where it should.
Loose parts is always a very bad idea when working with electronics, even at low voltages.

Something else others can probably advise on;

I think the Arduino can output a maximum of 40mA per output pin and 200mA in total.
The Songle relays you are using are electromagnetic relays and each relay seems to use more than 70mA so, to me, it looks to be over limits both individually and in total if you have 4 of them.

Data sheet here;
http://datasheetcafe.databank.netdna-cdn.com/wp-content/uploads/2015/10/SRD-05VDC-SL-C-Datasheet.pdf

As suggested by @wvmarle you could just screw the board to the box rather than using a perspex backing.

That 40 mA is the absolute maximum rating; for continuous draw it's best limited to 20 mA per pin.

All is not lost. You could still use the relays you have but you will require additional electronics to help supply the current. Here is a tutorial;
http://web.archive.org/web/20160824133553/http://www.ecs.umass.edu/ece/m5/tutorials/relay_tutorial.html

The relays you have contain electromagnets and it is powering those that draw the current. Also as an electromechanical device exactly what happens if it fails is probably not clear e.g. you might get a short between inputs and outputs.

You might want to cut your loses and use solid state relays with opto-isolation that draw less than 20mA.

Sorry, I may have been talking garbage here. The relays do need 70mA each and the Arduino outputs can only supply 20mA. However the relays are on a shield so it could be that the transistors etc. needed to supply the extra current are already on the board.

It is something to check. Sorry again if I have mislead.

Hi all, thankyou for your replies. I'm learning a bunch!

After several hours studying up on transistors, diodes and solid state relays I have decided to cut my losses with the mechanical relay.

I've found this SSR which is rated for 240V 2A which is plenty for the 25W heater. Switching current is only 12.5mA which works well for the Arduino.

I've been thinking about controlling the LEDs and fan using transistors. Apparently the latter also needs a clamping diode to protect the transistor. It seems like this transistor and this zener diode might work, but I'd appreciate your thoughts.

as far as I can tell this is the circuit I need to use. The LED would be similar but without the voltage clamp.

Don't use a zener for that - those are for limiting voltage in the reverse direction. Use a regular diode, maybe a Schottky for fast switching, but a a regular 1N400x or the much faster 1N4142 will do just fine as well

Hey folks, quick update- I have made all the hardware changes as suggested, but still struggle to get the thing working. I have pulled apart my circuits four or five times now, tested each part in isolation with some simple code but when I reassemble things it still does not work.

I would love some input on the code I am using- If there are any glaring errors I would be relieved to hear about them, because I am almost at the point of giving up and throwing the whole project out the window haha

#include "Wire.h"
#define DS3231_I2C_ADDRESS 0x68
#include "DHT.h"
#include <Servo.h>
#include "RTClib.h"
#include <LiquidCrystal.h>
Servo servo;  // create servo object to control a servo
DHT dht;
RTC_DS1307 rtc;
unsigned long previousMillis = 0;
int fanState = LOW;

// --------CONSTANTS (won't change)---------------

const int fanPin = 7;
const int servoPin = 9;
const int heaterPin = 8;
const int lightPin = 10;
const int fanDuration = 10000; // number of millisecs that fan is on
const int interval = 3000; // number of milisecs when the fan is off
#define maxtemp 27.0
#define mintemp 22.0

//------------ VARIABLES (will change)---------------------

byte targetfanState = HIGH; // this variable is necesary to help arduino monitor the status of LED
                            //the initial condition (t=0) can be either HIGH or LOW
unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()
unsigned long previousfanonMillis = 0;   // will store last time the fan was turned on
unsigned long previousfanoffMillis = 0;  // will store previous timee fan was turned off
int pos = 0;    // variable to store the servo position

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
//========== THE SETUP ==============================

void setup() {
// put your setup code here, to run once:
Serial.begin(9600);

  Wire.begin();
  pinMode(fanPin, OUTPUT);
  pinMode(servoPin, OUTPUT);
  pinMode(heaterPin, OUTPUT);
  pinMode(lightPin, OUTPUT);
   
  digitalWrite(fanPin, LOW);
  digitalWrite(servoPin, LOW);
  digitalWrite(heaterPin, LOW);
  digitalWrite(lightPin, LOW);
  
  servo.attach(3);  // attaches the servo on pin 9 to the servo object
  dht.setup(6);
  Serial.begin(9600);

  lcd.begin(16,2);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Temp: ");
  lcd.setCursor(0,1);
  lcd.print("Hum: ");
}

//========== THE LOOP ==============================

void loop() {
  // put your main code here, to run repeatedly:

  DateTime now = rtc.now();
  if ((now.hour() >= 21) || (now.hour() < 9)){
    digitalWrite(lightPin, LOW);
  }
  else digitalWrite (lightPin, HIGH);

  float h = dht.getHumidity();
  float t = dht.getTemperature();

  Serial.print(t);
  Serial.print(h);

  //error check
  if (isnan(h) || isnan(t)) {
    lcd.setCursor(0,0);
    lcd.print("sensorfail");
    return;
  }
  if (t < mintemp){
    digitalWrite(heaterPin, HIGH);
  }
  if (t > maxtemp){
    digitalWrite(heaterPin, LOW);
  }
   lcd.setCursor(6,0);
 lcd.print(t,2);
 lcd.print(" *C ");
  lcd.setCursor(5,1);
lcd.print(h,2);
lcd.println(" % ");
  
  
  currentMillis = millis();   // capture the latest value of millis()

if (fanPin == LOW){
   if ((unsigned long) currentMillis - previousfanoffMillis >= interval) { 
      servo.write(180);  //change depending on original position of servo
      delay(2000);
      digitalWrite(fanPin, HIGH);
      previousfanonMillis = millis();
}
}
else { 
    if ((unsigned long) currentMillis - previousfanonMillis >= fanDuration) {
      digitalWrite(fanPin, LOW);
      previousfanoffMillis = millis();
      delay(1000);
      servo.write(0);  //change depending on original position of servo
      delay(2000);

    }
}
    if (fanPin == HIGH){
      lcd.setCursor(12,1);
      lcd.print("F");}
      if (heaterPin == HIGH){
        lcd.setCursor(12,0);
      lcd.print("H");}
    
   delay(10000); 
}

Do explain how this "does not work". What does it do, and how does this differ from what you expect it to do.

Skimming the code a few issues:

Get rid of that delay(10000); at the end. Use millis() timing for that one just like you did elsewhere.

Most of your const values can be a byte rather than an int. Pin numbers and servo positions are all <256.

I see a line lcd.print("sensorfail"); but no lines that revert to normal temp/hum display - at least the first letters appear to remain on the display.

I see casts to unsigned long. That doesn't make sense as those variables should be (are) unsigned long already.

Your indentation is a mess. Have the IDE autocorrect that (press ctrl-T)

On the concern of the relays:

Those relays are opto-coupled: the arduino output does not act on the relay coil, but on the emmiting diode of the (opto)coupler, that sinks on the range of few mA (or even less than a mA).

Regards

Apologies, I should have been more specific.

I have split everything up into separate sub assemblies (lights + RTC1307, fan + servo, DHT22+SSrelay+heater) and tested them with their own individual sketch.

The both the light and heater sub assemblies work ok, but I am still having trouble with the fan. At this stage I have just been connecting a simple LED+resistor circuit to pin 7 and then to ground for testing purposes. I ought to see the LED flash on and off consistent with the timings I have in the sketch, but I am seeing nothing light up. I removed the casts to unsigned long in the loop function like wvmarle suggested, but no improvement thus far.

#include <Servo.h>
Servo servo;
unsigned long previousMillis = 0;
unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()
unsigned long previousfanonMillis = 0;   // will store last time the fan was turned on
unsigned long previousfanoffMillis = 0;  // will store previous timee fan was turned off
int pos = 0;    // variable to store the servo position
const int fanPin = 7;
const int servoPin = 9;
const int fanDuration = 10000; // number of millisecs that fan is on
const int interval = 5000;

void setup() {
  // put your setup code here, to run once:
  pinMode(fanPin, OUTPUT);
  pinMode(servoPin, OUTPUT);
  digitalWrite(fanPin, LOW);
  digitalWrite(servoPin, LOW);
  servo.attach(9);
}

void loop() {
  // put your main code here, to run repeatedly:
  currentMillis = millis();   // capture the latest value of millis()

  if (fanPin == LOW) {
    if (currentMillis - previousfanoffMillis >= interval) {
      servo.write(180);  //change depending on original position of servo
      delay(2000);
      digitalWrite(fanPin, HIGH);
      previousfanonMillis = millis();
    }
  }
  else {
    if (currentMillis - previousfanonMillis >= fanDuration) {
      digitalWrite(fanPin, LOW);
      previousfanoffMillis = millis();
      delay(1000);
      servo.write(0);  //change depending on original position of servo
      delay(2000);

    }
  }
}

Those casts won't be doing anything - shouldn't give a change, but make it more readable.

Why those delay() calls? that blocks your code and can't do anything else in the meantime. Replace that all by millis() based timing.

I can't really figure out what your code is supposed to do. please write down a list of desired timings of a sequence. It seems there's a fan and a servo doing things concurrently.

In this case I want a fan to run in a 5 mins on/ 60 mins off cycle. In the end this would be:

const int fanDuration = 300000;
const int interval = 3600000;

For testing, I've used a 10 second on/ 5 second off cycle as reflected in the sketch above.

Before the fan turns on I want a servo to open a hatch to allow air flow and close it after the fan turns off.

I have coded this as:

if (fanPin == LOW) {
** if (currentMillis - previousfanoffMillis >= interval) {**
** servo.write(180);**
** digitalWrite(fanPin, HIGH);**
** previousfanonMillis = millis();**

"if the fan is off, see if it should be turned on. If the time now minus the last time the fan was turned off is greater than or equal to the interval (length of time the fan should be off) then sweep the servo half way, turn on the fan and store the time the fan was turned on as 'previousfanonMillis"

else {
** if (currentMillis - previousfanonMillis >= fanDuration) {**
** digitalWrite(fanPin, LOW);**
** previousfanoffMillis = millis();**
** servo.write(0); //change depending on original position of servo**

"if the fan is on, see if it should be turned off. If the time now minus the last time the fan was turned on is greater than or equal to the desired fan duration then turn the fan off, store the time the fan was turned off as 'previousfanoffMillis' and sweep the servo back to the home position"

The delay() calls were just to allow the servo enough time to fully open the hatch, but you are right in saying I should change them to millis()

AidanRM:
In this case I want a fan to run in a 5 mins on/ 60 mins off cycle. In the end this would be:

const int fanDuration = 300000;
const int interval = 3600000;

I don't think so.
Try making that unsigned long instead (and do look up what values that can be stored in the various variable types).

It is more common to use a separate bool flag to indicate whether the fan is on, rather than checking the pin state. The other problem in your code is that you never actually read the current state of the pin (you compare with fanPin which contains the value 7 - so both checks fanPin == LOW and fanPin == HIGH will evaluate false).

Thanks for your suggestions wvmarle, it's all working nicely now. This is what I went with in the end:

#include <Servo.h>
Servo servo;
unsigned long previousMillis = 0;
unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()
unsigned long previousfanonMillis = 0;   // will store last time the fan was turned on
unsigned long previousfanoffMillis = 0;  // will store previous timee fan was turned off
int pos = 10;    // variable to store the servo position
const int fanPin = 7;
const int servoPin = 9;
unsigned long fanDuration = 5000; // number of millisecs that fan is on
unsigned long interval = 10000;
bool fanrunning = false;


void setup() {
  // put your setup code here, to run once:
  pinMode(fanPin, OUTPUT);
  pinMode(servoPin, OUTPUT);
  digitalWrite(fanPin, LOW);
  digitalWrite(servoPin, LOW);
  servo.attach(9);
}

void loop() {
  // put your main code here, to run repeatedly:
  currentMillis = millis();   // capture the latest value of millis()

  if (!fanrunning) {
    if ((unsigned long) currentMillis - previousfanoffMillis >= interval) {
      servo.write(90);
      digitalWrite(fanPin, HIGH);
      previousfanonMillis = millis();
      fanrunning = !fanrunning;
    }
  }
  else {
    if ((unsigned long) currentMillis - previousfanonMillis >= fanDuration) {
      digitalWrite(fanPin, LOW);
      previousfanoffMillis = millis();
      servo.write(10);  //change depending on original position of servo
      fanrunning = !fanrunning;

    }
  }
}