#include <WiFi.h>
#include <PubSubClient.h>
const int PIN_PH = 34;
const int PIN_TEMP = 35;
const int PIN_TURB = 32;
const int PIN_FLOAT_SWITCH = 4;
const int PIN_BTN_ESTOP = 21; // ปุ่มกด E-STOP บนบอร์ด (Pin 21)
const int PIN_PUMP_FILL = 2; // LED เขียว: ปั๊มเติมน้ำ
const int PIN_PUMP_CHEM = 15; // LED ส้ม: ปั๊มโดสเคมีปรับ pH (กรด/ด่าง)
const int PIN_FAN_COOLING = 13; // LED น้ำเงิน: พัดลมคูลลิ่ง
const int PIN_LED_ESTOP = 12; // LED แดง: ไฟเตือน Emergency
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "broker.hivemq.com";
const int mqtt_port = 1883;
const char* topic_telemetry = "mechatronics/2026/sectionA/TEAM_Jitti_99/telemetry";
const char* topic_control = "mechatronics/2026/sectionA/TEAM_Jitti_99/control";
WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastMsgTime = 0;
bool emergencyStop = false;
int lastBtnState = HIGH;
unsigned long lastDebounceTime = 0;
// ตัวแปรควบคุมโหมด
bool isManualMode = false;
bool manualPumpFill = false;
bool manualPumpAcid = false;
bool manualPumpBase = false;
bool manualCoolingFan = false;
void callback(char* topic, byte* payload, unsigned int length) {
char msg[120];
if (length >= sizeof(msg)) length = sizeof(msg) - 1;
memcpy(msg, payload, length);
msg[length] = '\0';
Serial.print("\n📥 [WEB COMMAND] ได้รับคำสั่งจาก Dashboard: ");
Serial.println(msg);
if (strstr(msg, "\"EMERGENCY_STOP\"") != NULL) {
emergencyStop = true;
Serial.println("🚨 [E-STOP ACTIVE] สั่งหยุดการทำงานของระบบฉุกเฉิน 100%!");
} else if (strstr(msg, "\"EMERGENCY_RESET\"") != NULL) {
emergencyStop = false;
Serial.println("✅ [E-STOP RESET] ปลดล็อกระบบฉุกเฉิน คืนสู่การทำงานปกติ");
}
if (strstr(msg, "\"system_mode\":\"AUTO\"") != NULL) {
isManualMode = false;
manualPumpFill = false;
manualPumpAcid = false;
manualPumpBase = false;
manualCoolingFan = false;
lastMsgTime = 0;
Serial.println("🤖 [MODE] สลับเป็นโหมด AUTO: ตรวจจับเซนเซอร์และสั่งการอัตโนมัติ");
} else if (strstr(msg, "\"system_mode\":\"MANUAL\"") != NULL) {
isManualMode = true;
Serial.println("🕹️ [MODE] สลับเป็นโหมด MANUAL: ควบคุมผ่านปุ่มกดหน้าเว็บ");
}
if (strstr(msg, "\"pump_fill\":\"ON\"") != NULL) { isManualMode = true; manualPumpFill = true; Serial.println("⚙️ [MANUAL] สั่งเปิดปั๊มเติมน้ำ"); }
else if (strstr(msg, "\"pump_fill\":\"OFF\"") != NULL) { isManualMode = true; manualPumpFill = false; Serial.println("⚙️ [MANUAL] สั่งปิดปั๊มเติมน้ำ"); }
if (strstr(msg, "\"pump_acid\":\"ON\"") != NULL) { isManualMode = true; manualPumpAcid = true; manualPumpBase = false; Serial.println("🧪 [MANUAL] สั่งจ่ายกรด (pH Down)"); }
else if (strstr(msg, "\"pump_base\":\"ON\"") != NULL) { isManualMode = true; manualPumpBase = true; manualPumpAcid = false; Serial.println("🧪 [MANUAL] สั่งจ่ายด่าง (pH Up)"); }
else if (strstr(msg, "\"pump_dose\":\"OFF\"") != NULL || strstr(msg, "\"pump_acid\":\"OFF\"") != NULL) { isManualMode = true; manualPumpAcid = false; manualPumpBase = false; Serial.println("🧪 [MANUAL] สั่งหยุดจ่ายสารเคมี"); }
if (strstr(msg, "\"cooling_fan\":\"ON\"") != NULL) { isManualMode = true; manualCoolingFan = true; Serial.println("❄️ [MANUAL] สั่งเปิดพัดลมคูลลิ่ง"); }
else if (strstr(msg, "\"cooling_fan\":\"OFF\"") != NULL) { isManualMode = true; manualCoolingFan = false; Serial.println("❄️ [MANUAL] สั่งปิดพัดลมคูลลิ่ง"); }
}
void reconnect() {
while (!client.connected()) {
Serial.print("กำลังเชื่อมต่อ MQTT Broker (HiveMQ)... ");
String clientId = "ESP32Pool-" + String(random(0xffff), HEX);
if (client.connect(clientId.c_str())) {
Serial.println("เชื่อมต่อสำเร็จ!");
client.subscribe(topic_control);
} else {
Serial.print("ไม่สำเร็จ (rc=");
Serial.print(client.state());
Serial.println(") ลองใหม่ใน 2 วินาที...");
delay(2000);
}
}
}
void setup() {
Serial.begin(115200);
delay(200);
Serial.println("\n\n========================================================");
Serial.println(" ระบบสระน้ำอัจฉริยะ ESP32 IoT (TEAM_Jitti_99) พร้อมทำงาน ");
Serial.println("========================================================");
pinMode(PIN_FLOAT_SWITCH, INPUT_PULLUP);
pinMode(PIN_BTN_ESTOP, INPUT_PULLUP);
pinMode(PIN_PUMP_FILL, OUTPUT);
pinMode(PIN_PUMP_CHEM, OUTPUT);
pinMode(PIN_FAN_COOLING, OUTPUT);
pinMode(PIN_LED_ESTOP, OUTPUT);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("กำลังเชื่อมต่อ WiFi Wokwi-GUEST");
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
}
Serial.println("\n WiFi เชื่อมต่อสำเร็จ!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
client.setServer(mqtt_server, mqtt_port);
client.setBufferSize(512);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
// ปุ่มกด E-STOP บนบอร์ด Wokwi
int reading = digitalRead(PIN_BTN_ESTOP);
if (reading == LOW && lastBtnState == HIGH && (millis() - lastDebounceTime > 250)) {
lastDebounceTime = millis();
emergencyStop = !emergencyStop;
Serial.println(emergencyStop ? "\n🚨 [HARDWARE BUTTON] กด E-STOP ตัดไฟฉุกเฉินทันที!" : "\n✅ [HARDWARE BUTTON] ปลดล็อก E-STOP คืนสู่ระบบปกติ");
}
lastBtnState = reading;
int rawPH = analogRead(PIN_PH);
float ph = (rawPH / 4095.0) * 14.0;
int rawTemp = analogRead(PIN_TEMP);
float temperature = 20.0 + ((rawTemp / 4095.0) * 25.0);
int rawTurb = analogRead(PIN_TURB);
float turbidity = (rawTurb / 4095.0) * 20.0;
bool isWaterFull = (digitalRead(PIN_FLOAT_SWITCH) == HIGH);
bool pumpFill = false;
bool pumpAcid = false;
bool pumpBase = false;
bool coolingFan = false;
if (emergencyStop) {
pumpFill = false;
pumpAcid = false;
pumpBase = false;
coolingFan = false;
digitalWrite(PIN_LED_ESTOP, HIGH);
} else {
digitalWrite(PIN_LED_ESTOP, LOW);
if (isManualMode) {
pumpFill = manualPumpFill;
pumpAcid = manualPumpAcid;
pumpBase = manualPumpBase;
coolingFan = manualCoolingFan;
} else {
pumpFill = !isWaterFull;
if (ph > 7.6) {
pumpAcid = true;
pumpBase = false;
} else if (ph < 7.2) {
pumpAcid = false;
pumpBase = true;
} else {
pumpAcid = false;
pumpBase = false;
}
coolingFan = (temperature > 31.0);
}
}
digitalWrite(PIN_PUMP_FILL, pumpFill ? HIGH : LOW);
digitalWrite(PIN_PUMP_CHEM, (pumpAcid || pumpBase) ? HIGH : LOW);
digitalWrite(PIN_FAN_COOLING, coolingFan ? HIGH : LOW);
// ส่งข้อมูล MQTT และพิมพ์ข้อความแจ้งเตือนสดขึ้นหน้าจอ Serial Monitor ทุก 800ms
unsigned long now = millis();
if (now - lastMsgTime > 800) {
lastMsgTime = now;
char buffer[360];
snprintf(buffer, sizeof(buffer),
"{\"device_id\":\"esp32_pool\",\"ph\":%.2f,\"temperature\":%.1f,\"turbidity\":%.1f,\"water_level\":\"%s\",\"pump_fill\":\"%s\",\"pump_acid\":\"%s\",\"pump_base\":\"%s\",\"cooling_fan\":\"%s\",\"emergency\":%s,\"system_mode\":\"%s\"}",
ph, temperature, turbidity,
isWaterFull ? "FULL" : "LOW",
pumpFill ? "ON" : "OFF",
pumpAcid ? "ON" : "OFF",
pumpBase ? "ON" : "OFF",
coolingFan ? "ON" : "OFF",
emergencyStop ? "true" : "false",
isManualMode ? "MANUAL" : "AUTO"
);
client.publish(topic_telemetry, buffer);
// ================= ข้อความแจ้งเตือนใน Serial Monitor =================
Serial.println("----------------------------------------------------------------");
Serial.printf("📊 [TELEMETRY] pH: %.2f | Temp: %.1f°C | Turb: %.1f NTU | Level: %s | โหมด: %s\n",
ph, temperature, turbidity, isWaterFull ? "เต็ม (FULL)" : "น้ำลด (LOW)", isManualMode ? "MANUAL" : "AUTO");
if (emergencyStop) {
Serial.println("🚨 [EMERGENCY ACTIVE] สภาวะฉุกเฉิน! ปั๊มและพัดลมทุกตัวถูกตัดไฟ 100%");
} else {
if (pumpFill) Serial.println("🚰 [STATUS] ปั๊มเติมน้ำกำลังทำงาน (Pump Fill: ON)");
if (pumpAcid) Serial.println("🧪 [STATUS] ค่าน้ำเป็นด่าง (pH > 7.6) -> จ่ายกรดลด pH (Pump Acid: ON)");
if (pumpBase) Serial.println("🧪 [STATUS] ค่าน้ำเป็นกรด (pH < 7.2) -> จ่ายด่างเพิ่ม pH (Pump Base: ON)");
if (!pumpAcid && !pumpBase) Serial.println("✨ [STATUS] ค่า pH สมดุลดี (7.2 - 7.6) -> ปั๊มเคมีสแตนด์บาย");
if (coolingFan) Serial.println("❄️ [STATUS] น้ำร้อน (> 31°C) -> เปิดพัดลมระบายความร้อน (Cooling Fan: ON)");
if (turbidity > 5.0) Serial.println("⚠️ [ALARM] น้ำขุ่นเกินเกณฑ์ (> 5.0 NTU) -> ส่งสัญญาณเตือนให้ล้างบ่อกรอง!");
}
}
}