#include <TM1637.h> // TM1637_RT (Rob Tillaart)
#include <dht.h> // DHTlib (Rob Tillaart)
/* ================== PIN MAP (เดิม) ================== */
const uint8_t PIN_LED_Y = 7; // LED1 Yellow : Auto indicator
const uint8_t PIN_LED_B = 6; // LED2 Blue : Grow light (LDR)
const uint8_t PIN_LED_G = 5; // LED3 Green : Fan (PWM)
const uint8_t PIN_LED_R = 4; // LED4 Red : Safety alert
const uint8_t PIN_BUZZ = 3; // Buzzer
const uint8_t PIN_TM_CLK = 10; // TM1637 CLK
const uint8_t PIN_TM_DIO = 11; // TM1637 DIO
const uint8_t PIN_BTN_K1 = 9; // Toggle Auto/Manual (INPUT_PULLUP)
const uint8_t PIN_BTN_K2 = 8; // Cycle Data View (INPUT_PULLUP)
const uint8_t PIN_LDR = A2; // LDR
const uint8_t PIN_POT = A0; // Potentiometer
const uint8_t PIN_DHT = 12; // DHT11
/* =============== DEVICES/OBJECTS ============== */
TM1637 tm;
dht DHT;
/* ================== CONSTANTS ================= */
// --- Greenhouse spec ---
const float CRIT_TEMP_C = 30.0; // Safety limit
const float CRIT_TEMP_HYST = 1.0; // Hysteresis เมื่อปลด safety
// --- DHT & timing ---
const uint32_t DHT_MIN_INTERVAL_MS = 2200; // DHT11 >= 1s แต่กันชัวร์ๆ 2.2s
const uint32_t PRINT_INTERVAL_MS = 1000;
const uint32_t DISPLAY_REFRESH_MS = 250; // กันจอกระพริบถี่เกิน
// --- LDR ---
const uint32_t LDR_CAL_TIME_MS = 1500;
const int LDR_DARK_MARGIN = 120; // มืด = >= baseline + margin (ทิศนี้ใช้ได้กับวงจรยอดนิยม)
const uint32_t LDR_MIN_HOLD_MS = 100; // ต้องมืดค้าง >= 100 ms
// --- Auto fan (humidity threshold by POT) ---
const int HUM_THR_MIN = 30; // %RH
const int HUM_THR_MAX = 90; // %RH
// --- Beep / Safety pulse ---
const uint32_t BEEP_MS = 80;
const uint16_t BEEP_FREQ_HZ = 2500;
const uint32_t SAFETY_ON_MS = 300;
const uint32_t SAFETY_OFF_MS = 700;
// --- LEDs blink usedเดิม ---
const uint32_t BLINK_MS = 200;
/* ==================== STATE =================== */
// โหมดหลัก 2 แบบ + safety override แยกเป็นแฟลก
enum RunMode { MODE_AUTO=0, MODE_MANUAL };
RunMode mode = MODE_AUTO;
enum ViewMode { VIEW_TEMP=0, VIEW_HUM, VIEW_LDR };
ViewMode view = VIEW_TEMP;
bool safetyActive = false; // true เมื่อ Temp >= CRIT_TEMP_C (มีฮิสเทอรีซิส)
bool growLightOn = false; // LED2 สถานะไฟปลูก
int fanPWM = 0; // 0..255 (LED3)
int humThreshold = 60; // %RH (อัปเดตจาก POT ใน Auto)
// สำหรับ LDR detection แบบ hold-time
int ldrBaseline = 0;
int lastLDRRaw = -1;
uint32_t ldrOverStart = 0;
/* =================== RUNTIME ================== */
uint32_t tLastDHTRead=0, tLastPrint=0, tLastDisp=0, tLastBlink=0;
uint32_t tBeepEnd=0, tSafetyPulse=0;
uint32_t ignoreButtonsUntil = 0;
float lastTempC = NAN, lastOkTempC = 0;
int lastRH = 0; // DHT11 ความชื้นเป็นจำนวนเต็ม
/* ================== HELPERS =================== */
bool readButtonHeld(uint8_t pin, uint16_t hold_ms = 80) {
if (millis() < ignoreButtonsUntil) return false;
static uint32_t tPressStart[32] = {0};
bool pressed = (digitalRead(pin) == LOW); // INPUT_PULLUP: LOW = กด
if (pressed) {
if (tPressStart[pin] == 0) tPressStart[pin] = millis();
return (millis() - tPressStart[pin]) >= hold_ms;
} else {
tPressStart[pin] = 0;
return false;
}
}
void beepClick() { // beep สั้นยืนยัน K2
tone(PIN_BUZZ, BEEP_FREQ_HZ);
tBeepEnd = millis() + BEEP_MS;
}
void beepTask() { // ปิด beep เมื่อครบเวลา
if (tBeepEnd && (int32_t)(millis() - tBeepEnd) >= 0) {
noTone(PIN_BUZZ);
tBeepEnd = 0;
}
}
/* ---- TM1637 helpers (ใช้รูปแบบเดิม) ---- */
void showTempC_int(int tC){
char b[5]=" ";
if (tC>=100){ snprintf(b,sizeof(b),"%03d",tC); b[3]='C'; }
else if (tC>=10){ snprintf(b,sizeof(b)," %2d",tC); b[3]='C'; }
else if (tC>=0){ snprintf(b,sizeof(b)," %d",tC); b[3]='C'; }
else { int v=-tC, d=v%10; b[0]='-'; b[1]='0'+d; b[2]=' '; b[3]='C'; }
tm.displayPChar(b);
}
void showHum_int(int rh){ // แสดง RH เป็นตัวเลข + 'H'
if (rh<0) rh=0; if (rh>100) rh=100;
char b[5]=" ";
if (rh==100) { snprintf(b,sizeof(b),"%03d",rh); b[3]='H'; }
else if (rh>=10){ snprintf(b,sizeof(b)," %2d",rh); b[3]='H'; }
else { snprintf(b,sizeof(b)," %d",rh); b[3]='H'; }
tm.displayPChar(b);
}
void showLDR_int(int v){ // 0..1023 ตัดที่ 9999
if (v<0) v=0; if (v>9999) v=9999;
char b[5]; snprintf(b,sizeof(b), "%4d", v);
tm.displayPChar(b);
}
/* ---- DHT ---- */
const char* dhtStatusText(int rc){
if (rc==DHTLIB_OK) return "OK";
#ifdef DHTLIB_ERROR_CHECKSUM
if (rc==DHTLIB_ERROR_CHECKSUM) return "CHECKSUM";
#endif
#ifdef DHTLIB_ERROR_TIMEOUT
if (rc==DHTLIB_ERROR_TIMEOUT) return "TIMEOUT";
#endif
static char buf[16]; snprintf(buf,sizeof(buf),"ERR(%d)",rc); return buf;
}
bool readDHTIfDue(){
uint32_t now=millis();
if (now - tLastDHTRead < DHT_MIN_INTERVAL_MS) return false;
tLastDHTRead = now;
int rc = DHT.read22(PIN_DHT);
if (rc==DHTLIB_OK){
lastTempC = DHT.temperature;
lastRH = (int)DHT.humidity;
if (!isnan(lastTempC) && lastTempC>-10 && lastTempC<80) lastOkTempC=lastTempC;
} else {
Serial.print(F("[DHT] status: ")); Serial.println(dhtStatusText(rc));
}
return true;
}
/* ---- LDR: มืด = ค่ามากกว่า baseline + margin (พร้อม hold-time) ---- */
bool isDarkNow(){
lastLDRRaw = analogRead(PIN_LDR);
bool over = (lastLDRRaw >= (ldrBaseline + LDR_DARK_MARGIN));
if (over) {
if (ldrOverStart == 0) ldrOverStart = millis();
return (millis() - ldrOverStart) >= LDR_MIN_HOLD_MS;
} else {
ldrOverStart = 0;
return false;
}
}
/* ---- Safety task (override) ---- */
void safetyTask() {
// อัปเดตแฟลก safety ตามฮิสเทอรีซิส
if (!safetyActive && lastOkTempC >= CRIT_TEMP_C) safetyActive = true;
if (safetyActive && lastOkTempC <= (CRIT_TEMP_C - CRIT_TEMP_HYST)) safetyActive = false;
if (safetyActive) {
// LED4 ค้าง / Fan เต็ม / Buzzer pulse ช้า
digitalWrite(PIN_LED_R, HIGH);
analogWrite(PIN_LED_G, 255); // fan full
// pulse buzzer
uint32_t now = millis();
if (tSafetyPulse==0) tSafetyPulse = now;
uint32_t phase = (now - tSafetyPulse) % (SAFETY_ON_MS + SAFETY_OFF_MS);
if (phase < SAFETY_ON_MS) tone(PIN_BUZZ, 1200);
else noTone(PIN_BUZZ);
} else {
// ปิดเอาต์พุต safety
digitalWrite(PIN_LED_R, LOW);
// buzzer ปล่อยให้ beepTask() จัดการเคสอื่น
if (tSafetyPulse) { noTone(PIN_BUZZ); tSafetyPulse=0; }
}
}
/* ---- Display updater ---- */
void updateDisplayIfDue(){
if (millis() - tLastDisp < DISPLAY_REFRESH_MS) return;
tLastDisp = millis();
switch(view){
case VIEW_TEMP: showTempC_int((int)(lastOkTempC + 0.5f)); break;
case VIEW_HUM: showHum_int(lastRH); break;
case VIEW_LDR: showLDR_int(lastLDRRaw); break;
}
}
/* =================== SETUP =================== */
void setup(){
pinMode(PIN_LED_Y,OUTPUT); pinMode(PIN_LED_B,OUTPUT);
pinMode(PIN_LED_G,OUTPUT); pinMode(PIN_LED_R,OUTPUT);
pinMode(PIN_BUZZ,OUTPUT);
pinMode(PIN_BTN_K1,INPUT_PULLUP); pinMode(PIN_BTN_K2,INPUT_PULLUP);
tm.begin(PIN_TM_CLK, PIN_TM_DIO, 4);
tm.setBrightness(7); tm.displayClear();
Serial.begin(9600);
Serial.println(F("Dynamic Greenhouse Started"));
// คาลิเบรต LDR baseline
uint32_t t0=millis(); long acc=0; uint16_t n=0;
while (millis()-t0 < LDR_CAL_TIME_MS){ acc += analogRead(PIN_LDR); n++; delay(5); }
if (n==0) n=1; ldrBaseline = acc/(int)n;
Serial.print(F("[LDR] baseline: ")); Serial.println(ldrBaseline);
// อ่าน DHT รอบแรก
delay(1500); readDHTIfDue();
Serial.print(F("[DHT] first T=")); Serial.print(lastOkTempC,1);
Serial.print(F(" C, RH=")); Serial.println(lastRH);
// เริ่มที่โหมด Auto
mode = MODE_AUTO;
digitalWrite(PIN_LED_Y, HIGH); // Auto indicator on
view = VIEW_TEMP;
ignoreButtonsUntil = millis() + 500; // กันกลิตช์ช่วงบูตเล็กน้อย
}
/* ==================== LOOP ==================== */
void loop(){
// 1) อ่านเซ็นเซอร์
readDHTIfDue();
bool darkNow = isDarkNow(); // LDR -> grow light
updateDisplayIfDue();
beepTask();
// 2) ปุ่ม K1: toggle Auto/Manual
if (readButtonHeld(PIN_BTN_K1, 80)) {
mode = (mode==MODE_AUTO) ? MODE_MANUAL : MODE_AUTO;
Serial.print(F("[MODE] ")); Serial.println(mode==MODE_AUTO ? F("AUTO") : F("MANUAL"));
// ปรับไฟบอกโหมด
digitalWrite(PIN_LED_Y, mode==MODE_AUTO ? HIGH : LOW);
beepClick();
}
// 3) ปุ่ม K2: cycle data view (Temp → Hum → LDR)
if (readButtonHeld(PIN_BTN_K2, 80)) {
view = (ViewMode)((view + 1) % 3);
Serial.print(F("[VIEW] "));
if (view==VIEW_TEMP) Serial.println(F("TEMP"));
else if (view==VIEW_HUM) Serial.println(F("HUM"));
else Serial.println(F("LDR"));
beepClick();
}
// 4) ตัดสินใจเอาต์พุตพื้นฐาน (ยังไม่รวม safety override)
// 4.1 grow light (LED2): มืด -> เปิด
growLightOn = darkNow;
digitalWrite(PIN_LED_B, growLightOn ? HIGH : LOW);
// 4.2 fan (LED3 Green)
int pot = analogRead(PIN_POT);
if (mode == MODE_AUTO) {
// แปลง POT -> humidity threshold
humThreshold = map(pot, 0, 1023, HUM_THR_MIN, HUM_THR_MAX);
fanPWM = (lastRH > humThreshold) ? 255 : 0; // เกิน threshold -> เปิดเต็ม (ตรงโจทย์)
} else {
// Manual: POT -> PWM โดยตรง
fanPWM = map(pot, 0, 1023, 0, 255);
}
analogWrite(PIN_LED_G, fanPWM);
// 5) Safety override (ทำท้ายสุดเพื่อครอบทับเอาต์พุตหากจำเป็น)
safetyTask();
// 6) Serial telemetry ทุก 1 วินาที
if (millis() - tLastPrint >= PRINT_INTERVAL_MS){
tLastPrint = millis();
Serial.print(F("MODE=")); Serial.print(mode==MODE_AUTO ? F("AUTO") : F("MANUAL"));
Serial.print(F(" | VIEW="));
if (view==VIEW_TEMP) Serial.print(F("TEMP"));
else if (view==VIEW_HUM) Serial.print(F("HUM"));
else Serial.print(F("LDR"));
Serial.print(F(" | Safety=")); Serial.print(safetyActive ? 1 : 0);
Serial.print(F(" | T=")); Serial.print(lastOkTempC,1); Serial.print(F("C"));
Serial.print(F(" | RH=")); Serial.print(lastRH); Serial.print(F("%"));
Serial.print(F(" | LDR=")); Serial.print(lastLDRRaw);
Serial.print(F(" (thr=")); Serial.print(ldrBaseline + LDR_DARK_MARGIN);
Serial.print(F(", dark=")); Serial.print(growLightOn?1:0); Serial.print(F(")"));
if (mode == MODE_AUTO) {
Serial.print(F(" | POT=")); Serial.print(pot);
Serial.print(F(" -> HUM_THR=")); Serial.print(humThreshold); Serial.print(F("%"));
Serial.print(F(" | FAN=")); Serial.print(fanPWM>0?F("ON"):F("OFF"));
} else {
Serial.print(F(" | POT=")); Serial.print(pot);
Serial.print(F(" -> FAN_PWM=")); Serial.print(fanPWM);
}
Serial.println();
}
}
Disarm
Panic