CONCEPT
PROBLEM : Greenhouses overheat on sunny days and lack light on dark ones. Ventilation and lighting are usually adjusted by hand, often too late.
OUR RESPONSE : A miniature greenhouse that senses its environment and reacts on its own: the roof opens to release heat, and LEDs switch on when daylight fades.
HOW THE GREENHOUSE BEHAVES
Temperature rises → The servo opens the roof to release warm air
Temperature falls → The servo closes the roof to keep the heat in
Daylight fades → The LEDs turn on to light the plants
Daylight returns → The LEDs turn off to save energy

INPUT PROCESS OUTPUT

BILL OF MATERIALS

SCHEMATIC


CODE
// =====================================================================
// ADAPTIVE GREENHOUSE: responsive roof and lighting
// – Temperature (DHT20 sensor) → servo opens/closes the roof
// – Daylight (light sensor) → LEDs turn on/off
// – OLED display shows live data
// – Button re-measures the room temperature (baseline)
// =====================================================================
#include <Servo.h>
#include <U8x8lib.h>
#include “DHT.h”
#include “Wire.h”
// —————- Pins —————-
#define LIGHT_PIN A6 // light sensor
#define BUTTON_PIN 6 // button
#define SERVO_PIN 9 // servo signal wire (orange)
const int LED_PINS[] = { 2, 7, 8, 12 }; // 4 LEDs (pin 6 is used by the button)
const int N_LEDS = 4;
// —————- Roof angles —————-
// Swap the two values if the roof moves the wrong way
#define ROOF_CLOSED 0
#define ROOF_OPEN 90
// —————- Temperature thresholds —————-
// Relative to the room temperature measured at startup (baseline)
#define OPEN_OFFSET 0.5 // roof opens at baseline + 0.5 °C
#define CLOSE_OFFSET 0.2 // roof closes at baseline + 0.2 °C
// —————- Light thresholds —————-
// Check the values in the Serial Monitor and adjust if needed
#define LIGHT_ON_BELOW 200 // darker than this → LEDs on
#define LIGHT_OFF_ABOVE 300 // brighter than this → LEDs off
// —————- Timing (milliseconds) —————-
#define STEP_INTERVAL 20 // time per 1° of roof movement → smooth motion
#define SENSOR_INTERVAL 2000 // how often the temperature is read
#define DISPLAY_INTERVAL 200 // OLED refresh rate
#define DEBOUNCE_TIME 25 // ignore button noise shorter than this
// —————- Objects —————-
Servo roof;
U8X8_SSD1306_128X64_NONAME_HW_I2C oled(U8X8_PIN_NONE);
DHT dht(DHT20); // DHT20 temperature & humidity sensor (I2C)
// —————- State variables —————-
float temperature = 22; // current temperature
float baseline = 22; // room temperature at startup
float openTemp, closeTemp; // thresholds calculated from the baseline
bool roofOpen = false; // should the roof be open?
int currentAngle = ROOF_CLOSED;
float smoothLight = 0; // smoothed light reading
bool ledsOn = false;
bool buttonState = LOW;
bool lastReading = LOW;
unsigned long lastDebounceTime = 0;
unsigned long lastStepTime = 0;
unsigned long lastSensorTime = 0;
unsigned long lastDisplayTime = 0;
// ———————————————————————
// Measure the current room temperature and use it as the new reference
// ———————————————————————
void calibrate() {
float t = dht.readTemperature();
if (!isnan(t)) {
temperature = t;
baseline = t;
}
openTemp = baseline + OPEN_OFFSET;
closeTemp = baseline + CLOSE_OFFSET;
Serial.print(“Baseline: “);
Serial.println(baseline);
}
// ———————————————————————
// SETUP: runs once at power-up
// ———————————————————————
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT);
for (int i = 0; i < N_LEDS; i++) pinMode(LED_PINS[i], OUTPUT);
// Start with the roof closed
roof.attach(SERVO_PIN);
roof.write(currentAngle);
// Start the OLED
oled.begin();
oled.setFont(u8x8_font_chroma48medium8_r);
oled.setCursor(0, 3);
oled.print(“Calibrating…”);
// Start the temperature sensor
Wire.begin();
dht.begin();
delay(2000); // the sensor needs time after power-up
calibrate();
smoothLight = analogRead(LIGHT_PIN);
oled.clear();
}
// ———————————————————————
// LOOP: runs thousands of times per second, no delay() inside
// ———————————————————————
void loop() {
unsigned long now = millis();
// ———- INPUT: button → recalibrate ———-
bool reading = digitalRead(BUTTON_PIN);
if (reading != lastReading) lastDebounceTime = now;
if (now – lastDebounceTime > DEBOUNCE_TIME && reading != buttonState) {
buttonState = reading;
if (buttonState == HIGH) calibrate(); // new press → new baseline
}
lastReading = reading;
// ———- INPUT + PROCESS: temperature → roof ———-
if (now – lastSensorTime >= SENSOR_INTERVAL) {
lastSensorTime = now;
float t = dht.readTemperature();
if (!isnan(t)) temperature = t;
if (temperature >= openTemp) roofOpen = true; // too warm → open
else if (temperature <= closeTemp) roofOpen = false; // cooled down → close
// in between: keep the current state (hysteresis)
// Serial Plotter output
Serial.print(“Temp:”); Serial.print(temperature);
Serial.print(“,Open:”); Serial.print(openTemp);
Serial.print(“,Close:”); Serial.print(closeTemp);
Serial.print(“,Roof:”); Serial.print(roofOpen ? openTemp + 0.2 : closeTemp – 0.2);
Serial.print(“,Light:”); Serial.println((int)smoothLight);
}
// ———- INPUT + PROCESS: light → LEDs ———-
// Smoothing: 90% old value + 10% new value, so shadows don’t cause flicker
smoothLight = smoothLight * 0.9 + analogRead(LIGHT_PIN) * 0.1;
if (smoothLight < LIGHT_ON_BELOW) ledsOn = true;
else if (smoothLight > LIGHT_OFF_ABOVE) ledsOn = false;
// ———- OUTPUT: LEDs ———-
for (int i = 0; i < N_LEDS; i++) digitalWrite(LED_PINS[i], ledsOn);
// ———- OUTPUT: smooth roof movement ———-
// The servo moves 1° at a time toward its target angle
int targetAngle = roofOpen ? ROOF_OPEN : ROOF_CLOSED;
if (now – lastStepTime >= STEP_INTERVAL) {
lastStepTime = now;
if (currentAngle < targetAngle) currentAngle++;
else if (currentAngle > targetAngle) currentAngle–;
roof.write(currentAngle);
}
// ———- OUTPUT: OLED display ———-
if (now – lastDisplayTime >= DISPLAY_INTERVAL) {
lastDisplayTime = now;
// Roof state text
bool moving = (currentAngle != targetAngle);
const char* state;
if (moving) state = roofOpen ? “OPENING ” : “CLOSING “;
else state = roofOpen ? “OPEN ” : “CLOSED “;
// How many degrees the roof is open, and how long the bar should be
int openAngle = abs(currentAngle – ROOF_CLOSED);
int filled = map(openAngle, 0, abs(ROOF_OPEN – ROOF_CLOSED), 0, 14);
oled.setCursor(0, 0); oled.print(” GREENHOUSE”);
// Temperature and thresholds with one decimal place
oled.setCursor(0, 1); oled.print(“Temp: “); oled.print(temperature, 1); oled.print(” C “);
oled.setCursor(0, 2); oled.print(“Op:”); oled.print(openTemp, 1);
oled.print(” Cl:”); oled.print(closeTemp, 1); oled.print(” “);
oled.setCursor(0, 3); oled.print(“Roof: “); oled.print(state);
oled.setCursor(0, 4); oled.print(“Angle: “); oled.print(openAngle); oled.print(” deg “);
// Progress bar: [#######…….]
oled.setCursor(0, 5); oled.print(“[“);
for (int i = 0; i < 14; i++) oled.print(i < filled ? “#” : “.”);
oled.print(“]”);
oled.setCursor(0, 6); oled.print(“Light: “); oled.print((int)smoothLight); oled.print(” “);
oled.setCursor(0, 7); oled.print(“LEDs: “); oled.print(ledsOn ? “ON ” : “OFF”);
}
}
HOW THE CODE RUNS

TEMPERATURE TRESHOLDS

LIGHT TRESHOLDS

LIVE FEEDBACK ON THE OLED

PHYSICAL PROTOTYPE

RENDERS

DEMO AND NEXT STEPS
How we demonstrate it
- A halogen desk lamp acts as the sun: the greenhouse warms up and the roof opens
- A warm palm or breath on the sensor works in about 20 seconds
- Covering the light sensor turns the LEDs on
- The Serial Plotter shows temperature crossing the thresholds
Next steps
- More accurate sensor (e.g. SHT31, ±0.2 °C)
- Soil moisture sensor and a small water pump
- Ventilation driven by humidity as well
- Several roof panels opening in sequence
FUTURE VISION

ANIMATION