Building and automating terrarium with ESP32's

Hello hello.
My name is Roel and I have become an autodidact in automating terrarium systems with the use of micro-controllers. During the day I have a completely different profession. Currently I'm in the proces of building a new terrarium enclosure. This is my 4th project. And this time I decided I wanted to give back to this amazing community by posting my progression (concept, design, building, testing and most of all: the mistakes!).
My build is a full glass terrarium, which when finished, will hopefully be able to do the following:

  • Display on OLED 128*64: Temps, Humidity %, system status and water reservoir %;
  • Regulate humidity by measuring and misting and/or activating fans accordingly;
  • Control Lighting either fixed or based on local sunrise/set;
  • Control small functions like cooling/heating fans, status LEDs, and drip wall pump mechanics.

I've drawn up a global schematic of the electronic involved in Affinity Designer (OSX). All of the components are in my stock. My attitude is 50% planning and 50% learning along the way. I love the proces learning while fiddling with materials and code lines. My next step is to globally setup the first sketch designs in Arduino IDE.

EDIT: Controller1 (the left) should probably only control Mister via Relay switches... the rest of Relay should be managed by Controller2, since those are 'daily tasks'

I have automated my house plants maintenace. You may find this library helpful, SolarCalculator library for Arduino.

1 Like

Hi,

since ESP32 has WiFi and Bluetooth capabilities, you can add a primary (secondary) display, which would also enable remote control and monitoring. You can give GUI-O application (Android based) a try; you control all aspects of the GUI from the ESP32 side... There are a lot of examples for ESP32 to get started: GUI-O application: Create high-end GUI for any device - EXAMPLES.

I really don't want this to sound like a marketing effort, but I can see advantages of using GUI-O in your terrarium setup (maybe only during SW development / debugging).

Good luck with your project.

Best regards,
kl3m3n

1 Like

Hi,
it's not an area I know very well, so I have doubts if these MOSFET modules are ideal for your project.
I don't know if they are logic level MOSFETs.

1 Like

I have been using the FQP30N06L MOSFETS for ESP32's, 2V5 threshold. Make sure that the part number has that "L".

1 Like

I think you might be on to something… I read that 4 pin fans could be controlled by just 1 PWM wire. And some PWM 4-wire fans do not have a 0% PWM = OFF mode. They turn into full speed at 0% PWM. You need to resolve that with separate relays, because I will need the functionality to fully shut down my fans.
The fans in my concept, which I have in stock, are fans with 2 wires: + & - .

Could you maybe elaborate on the “L” part? I tried to look up meaning of model types or characters, but I didn’t find them.

FQP30N06L works with a 3V3 MCU.

FQP30N06 does not work with a 3V3 MCU.

A brief start with the first sketch for Controller 1 I made today. I'm currently trying to find a logical structure in the setup, loop & functions. As for disabling the mister, I need to write a time variable(?). Because the mister needs to pause before its starts spraying again immediately. I probably also need to be aware that functions are not competing with one another... like the ones controlling the Fans.

//===Main Program for ESP32 DOIT Devkit V1 - Controller1
//===Controller1: Monitoring sensors & Controlling humidity / Fans;
//===Controller2: Controling Daily cycles such as Lighting, dripwall and water pump;

//===Structure as by: https://forum.arduino.cc/t/planning-and-implementing-an-arduino-program/252364/3 
//===User: Robin2


//===#includes placeholder


void setup() {
  // put your setup code here, to run once:

}
// this code repeats itself
void loop() {

// CONCEPT PROGRAM [semi-structured ;)] 
// ==A. Every 10 seconds, Measure Humidity of enclosure and take following action:
// if humidity < set_value: activate mister and disable the mister for upcomming 15 minutes
// disable FansOut & FansIn for 15 minutes when mister is activated
// if humidity > set_value: activate FansOut to 100% & FansIn to PWM 50%

// ==B.Every 10 seconds, Measure Temperature of 3 sensors (Top,Middle,Bottom) and control fans
// if temperature < set_value, and time = daytime: adjust PWM FansIn to (50-100%, dependend on gap from set_value)

// ==C.Synchronize Fans with lights on/off
// if light 3 is off (signal from GPIO controller2)
// then turn off FansIn, and turn on FansOut on PWM 50%

// ==D. Display status on OLED
// Scroll OLED display every 10 seconds:
// Temperature 1,2,3 + average temperature
// Humidity + last time mister was acitvated
// Fans Status In/Out
// Time of day


//=available functions
  // GetCurrentTime();
  // MeasureTempTop();
  // MeasureTempMiddle();
  // MeasureTempBottom();
  // MeasureHumidity();
  // ActivateMister();
  // PauseMisterFunction();
  // ActivateFansOut();
  // ActivateFansIn();
  // DeactivateFansOut();
  // DeactivateFansIn();
  // DisplayOled();

}


//===FUNCTIONS
void GetCurrentTime(){

}
void MeasureTempTop(){

}
void MeasureTempMiddle(){

}
void  MeasureTempBottom(){

}
void  MeasureHumidity (){

}
void  ActivateMister(){
  
}
void PauseMisterFunction(){
  
}
void ActivateFansIn(){
  
}
void ActivateFansOut(){
  
}
void DeactivateFansIn(){
  
}
void DeactivateFansOut(){
  
}
void DisplayOled(){

}


I'm looking for a solution for the following objective:
I want to measure and display the humidity every 10 seconds. And when the measurement reaches a certain threshold, to activate either mister or fans. But I would like a pause of about 15 (or X) minutes after this activation. To prevent constant misting or fanning... So like a 'cooldown'. Meanwhile, during that cooldown keep measuring and displaying the humidity.

Do I create something like a 'timestamp' when I activate the mister or fans? And then add the 15 minutes to that timestamp into a temporary variable?

Pretty much that's it.

Look at File|Examples|02.Digital|BlinkWithoutDelay to use millis() for timing instead of delay().

https://majenko.co.uk/blog/our-blog-1/the-finite-state-machine-26

1 Like

Thank you. I will thoroughly look at this!

This was a great tip!

1 Like

What I got so far. Maybe I would be able to test this in serial output mode?

typedef enum state_t {
    S_MISTER_IDLE, //  0
    S_MISTER_ON,  //   1
    S_MISTER_SETSTART // 2 
    S_MISTER_ACTIVE, // 3
    S_MISTER_OFF, //   4
    S_MISTER_PAUSE //  5
};

static state_t state = S_MISTER_IDLE;
bool requestMist = false;
const long misterDuration = 15000; // 15 sec, duration mister is active in millis
const long misterDisabled = 900000; // 15 min, duration of disabled mister in millis
unsigned long misterOnTime = 0;  //time in millis when mister was activated
unsigned long misterPauseTime = 0; //time in millis when mister was paused
int misterPin = x; //set GPIO mister

void setup() {
pinMode(misterPin,OUTPUT); // set misterpin output 

}

void processMisterFSM {
  switch (state) {
    case S_MISTER_IDLE: // mister waiting for activation
      digitalWrite(misterPin, LOW); // safety GPIO state
      if(requestMist) {
        state = S_MISTER_ON;
        requestMist = false;
      }
      break;
    case S_MISTER_ON: // turn on mister GPIO
      digitalWrite(misterPin, HIGH);
      state = S_MISTER_ACTIVE;
      break;
    case S_MISTER_SETSTART: // set start misting time 
      misterOnTime = currentMillis();
      break;
    case S_MISTER_ACTIVE: // counting time while mister is active
      if (currentMillis() - misterOnTime >= misterDuration) {
        state = S_MISTER_OFF;
      }
      break;
    case S_MISTER_OFF: // turn off mister GPIO
      digitalWrite(misterPin, LOW);
      misterPauseTime = currentMillis();
      break;
    case S_MISTER_PAUSE: // counting time while mister is disabled
      if (currentMillis() - misterPauseTime >= misterDisabled) {
        state = S_MISTER_IDLE;
      }
      break;
    default:
      state = S_MISTER_IDLE;
  }
}

void loop() {
  unsigned long currentMillis = millis();
  if(true) { //measuring humidity level threshold example: < 80 %H
    requestMist = true;
  }
}

The code is working now. You can check it in serial.
6 states:

  • S_MISTER_IDLE, //  0
    
  • S_MISTER_ON,  //   1 turns GPIO ON 
    
  • S_MISTER_SETSTART, // 2 set starting time 
    
  • S_MISTER_ACTIVE, // 3  starts counting the time when ON
    
  • S_MISTER_OFF, //   4 turns GPIO OFF
    
  • S_MISTER_PAUSE //  5 starts counting the time to disable/pause mister. 
    

I got a little help from a friend. I was stuck counting the Millis, because I was not executing the count in the main loop.

typedef enum {
    S_MISTER_IDLE, //  0
    S_MISTER_ON,  //   1
    S_MISTER_SETSTART, // 2 
    S_MISTER_ACTIVE, // 3
    S_MISTER_OFF, //   4
    S_MISTER_PAUSE //  5
}  misterState_t;

static misterState_t state = S_MISTER_IDLE;
bool requestMist = false;
const long misterDuration = 5000; // 15 sec, duration mister is active in millis
const long misterDisabled = 5000; // 15 min, duration of disabled mister in millis
unsigned long misterOnTime = 0;  //time in millis when mister was activated
unsigned long misterPauseTime = 0; //time in millis when mister was paused
int misterPin = 13; //set GPIO mister
unsigned long currentMillis = 0;
// script om tijd op te vragen? en die dan gebruiken om 
// OLED display te laten zien wanneer voor het laatste gemist is. 
void setup() {
  Serial.begin(115200);
  pinMode(misterPin,OUTPUT); // set misterpin output 
  Serial.println("SETUP COMPLETED");
}

void processMisterFSM() {
  //unsigned long currentMillis = millis();
  switch (state) {
    case S_MISTER_IDLE: // mister waiting for activation
      digitalWrite(misterPin, LOW); // safety GPIO state
      if(requestMist) {
        requestMist = false;
        Serial.println("IN STATE 1 - S_MISTER_IDLE");
        state = S_MISTER_ON;
      }
      break;
    case S_MISTER_ON: // turn on mister GPIO
      digitalWrite(misterPin, HIGH);
      Serial.println("IN STATE 1 - S_MISTER_ON - MISTER ON!");
      state = S_MISTER_SETSTART;
      break;
    case S_MISTER_SETSTART: // set start misting time 
      misterOnTime = currentMillis;
      Serial.println(misterOnTime);
      Serial.println("IN STATE 1 - S_MISTER_SETSTART - MISTER ON!");
      state = S_MISTER_ACTIVE;
      break;
    case S_MISTER_ACTIVE: // counting time while mister is active
      Serial.println("IN STATE 1 - S_MISTER_ACTIVE - MISTER ON!");
      if (currentMillis - misterOnTime >= misterDuration) {
        Serial.println("SWITCHING OFF - to S_MISTER_OFF");
        state = S_MISTER_OFF;
      }
      break;
    case S_MISTER_OFF: // turn off mister GPIO
      digitalWrite(misterPin, LOW);
      misterPauseTime = currentMillis;
      Serial.println("IN STATE 1 - S_MISTER_OFF");
      state = S_MISTER_PAUSE;
      break;
    case S_MISTER_PAUSE: // counting time while mister is disabled
      Serial.println("IN STATE 1 - S_MISTER_PAUSE");
      if (currentMillis - misterPauseTime >= misterDisabled) {
        //millis can pass through 0, it's the difference that counts for misterDisabled (=safe)
        Serial.println("GOING TO IDLE STATE");
        state = S_MISTER_IDLE;
      }
      break;
    default:
      state = S_MISTER_IDLE;
  }
}

void loop() {
  processMisterFSM();
  if(true) { //measuring humidity level threshold example: < 80 %H
    requestMist = true;
  }
  currentMillis = millis();
  Serial.println("IN LOOP");
  Serial.println(misterOnTime);
  Serial.println(misterPauseTime);
  Serial.println(currentMillis);
  delay(100);
}


This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.