#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// ================== SENSOR SETTINGS ==================
#define DHTPIN 2
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
// ================== LCD SETTINGS =====================
LiquidCrystal_I2C lcd(0x27, 20, 4);
// ================== OUTPUT PINS ======================
#define FAN_PIN 3
#define HEATER_PIN 4
#define HUMIDIFIER_PIN 5
// ================== CONTROL THRESHOLDS ===============
#define TEMP_HIGH 27
#define TEMP_LOW 25
#define HUMIDITY_LOW 65
#define HUMIDITY_HIGH 75
#define HUMIDITY_FAN 80
// ================== STATE FLAGS ======================
bool fanState = false;
bool heaterState = false;
bool humidifierState = false;
// ================== BANNER (row 0 fallback) ==========
char bannerMessage[] = " MANTIS CLIMATE CONTROL ";
int bannerIndex = 0;
unsigned long lastBannerScroll = 0;
const int bannerDelay = 400;
// ================== TIMERS ===========================
unsigned long lastSensorRead = 0;
const int sensorInterval = 5000;
// ================== HEARTBEAT ========================
bool heartbeatState = false;
unsigned long lastHeartbeat = 0;
// ================== UPTIME ===========================
unsigned long lastUptimeUpdate = 0;
// ================== EVENT SCROLLER (row 0) ===========
String eventMessage = "";
int eventIndex = 0;
unsigned long lastEventScroll = 0;
const int eventScrollDelay = 400;
bool eventActive = false;
// ================== STATUS SCROLLER (row 3) ==========
String statusMessage = "";
int statusIndex = 0;
unsigned long lastStatusScroll = 0;
const int statusScrollDelay = 400;
void setup() {
Serial.begin(9600);
dht.begin();
lcd.init();
lcd.backlight();
// Boot splash
lcd.setCursor(0,0); lcd.print(" MANTIS CLIMATE ");
lcd.setCursor(0,1); lcd.print(" CONTROL ");
delay(1000);
lcd.setCursor(0,3); lcd.print("Booting: ");
for (int i=0; i<10; i++) {
lcd.setCursor(9+i, 3);
lcd.write(byte(255));
delay(200);
}
lcd.setCursor(0,2);
lcd.print(" System Ready! ");
delay(2000);
lcd.clear();
pinMode(FAN_PIN, OUTPUT);
pinMode(HEATER_PIN, OUTPUT);
pinMode(HUMIDIFIER_PIN, OUTPUT);
digitalWrite(FAN_PIN, LOW);
digitalWrite(HEATER_PIN, LOW);
digitalWrite(HUMIDIFIER_PIN, LOW);
Serial.println(F("\n=== MANTIS CLIMATE CONTROL READY ==="));
}
void loop() {
unsigned long now = millis();
// ---------- Sensor Read ----------
static float humidity = NAN, temperature = NAN;
static bool prevFan = false, prevHeater = false, prevHumidifier = false;
if (now - lastSensorRead >= sensorInterval) {
lastSensorRead = now;
humidity = dht.readHumidity();
temperature = dht.readTemperature();
if (isnan(humidity) || isnan(temperature)) {
lcd.setCursor(0,1);
lcd.print("Sensor Error! ");
Serial.println(F("⚠️ Sensor read failed"));
return;
}
// Control logic
fanState = (temperature > TEMP_HIGH || humidity > HUMIDITY_FAN);
heaterState = (temperature < TEMP_LOW);
humidifierState = (humidity < HUMIDITY_LOW);
if (humidity > HUMIDITY_HIGH) humidifierState = false;
digitalWrite(FAN_PIN, fanState);
digitalWrite(HEATER_PIN, heaterState);
digitalWrite(HUMIDIFIER_PIN, humidifierState);
// Detect changes → trigger event
if (fanState != prevFan) {
eventMessage = fanState ? ">>> FAN TURNED ON <<< " : ">>> FAN TURNED OFF <<< ";
eventIndex = 0; eventActive = true;
}
if (heaterState != prevHeater) {
eventMessage = heaterState ? ">>> HEATER TURNED ON <<< " : ">>> HEATER TURNED OFF <<< ";
eventIndex = 0; eventActive = true;
}
if (humidifierState != prevHumidifier) {
eventMessage = humidifierState ? ">>> HUMIDIFIER TURNED ON <<< " : ">>> HUMIDIFIER TURNED OFF <<< ";
eventIndex = 0; eventActive = true;
}
prevFan = fanState;
prevHeater = heaterState;
prevHumidifier = humidifierState;
// Serial Debug
Serial.print("🌡 Temp: "); Serial.print(temperature,1); Serial.print("°C ");
Serial.print("💧 Humidity: "); Serial.print(humidity,0); Serial.print("% | ");
Serial.print("Fan: "); Serial.print(fanState ? "ON " : "OFF");
Serial.print(" | Heater: "); Serial.print(heaterState ? "ON " : "OFF");
Serial.print(" | Humidifier: "); Serial.println(humidifierState ? "ON" : "OFF");
// Update LCD (Temp + Humidity)
lcd.setCursor(0,1);
lcd.print("Temp: ");
lcd.print(temperature,1);
lcd.print((char)223);
lcd.print("C ");
lcd.setCursor(0,2);
lcd.print("Humidity: ");
lcd.print(humidity,0);
lcd.print("% ");
// Build status message for scrolling
statusMessage = " Fan:" + String(fanState ? "ON " : "OFF") +
" | Heat:" + String(heaterState ? "ON " : "OFF") +
" | Humid:" + String(humidifierState ? "ON " : "OFF") + " ";
statusIndex = 0;
}
// ---------- Row 0: Event OR Banner ----------
if (eventActive) {
if (now - lastEventScroll >= eventScrollDelay) {
lastEventScroll = now;
lcd.setCursor(0,0);
int len = eventMessage.length();
for (int i=0; i<20; i++) {
char c = eventMessage[(eventIndex + i) % len];
lcd.print(c);
}
eventIndex++;
if (eventIndex >= len) {
eventActive = false; // done scrolling once
}
}
} else {
if (now - lastBannerScroll >= bannerDelay) {
lastBannerScroll = now;
int len = strlen(bannerMessage);
lcd.setCursor(0,0);
for (int i=0; i<20; i++) {
lcd.print(bannerMessage[(bannerIndex + i) % len]);
}
bannerIndex = (bannerIndex + 1) % len;
}
}
// ---------- Heartbeat Blink (top-right corner of row 0) ----------
if (now - lastHeartbeat >= 1000 && !eventActive) {
lastHeartbeat = now;
heartbeatState = !heartbeatState;
lcd.setCursor(19,0);
lcd.print(heartbeatState ? "*" : " ");
}
// ---------- Uptime (always in row 2, right side) ----------
if (now - lastUptimeUpdate >= 60000) {
lastUptimeUpdate = now;
unsigned long minutes = now / 60000;
unsigned long hours = minutes / 60;
minutes = minutes % 60;
lcd.setCursor(14,2);
char buf[6];
snprintf(buf, sizeof(buf), "%02lu:%02lu", hours, minutes);
lcd.print(buf);
}
// ---------- Row 3: Always scrolling status ----------
if (now - lastStatusScroll >= statusScrollDelay && statusMessage.length() > 0) {
lastStatusScroll = now;
lcd.setCursor(0,3);
int len = statusMessage.length();
for (int i=0; i<20; i++) {
char c = statusMessage[(statusIndex + i) % len];
lcd.print(c);
}
statusIndex = (statusIndex + 1) % len;
}
}