#include <WiFi.h>
#include <PubSubClient.h>
#include <WiFiClientSecure.h>
#include <Adafruit_NeoPixel.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); //เซ็ทจอ Lcd I2C 16*2
const char* ssid = "Wokwi-GUEST"; //ชื่อ WiFi
const char* password = "";//รหัสเข้า WiFi
// ลิ้งค์ mqtt broker cloud
const char* mqtt_server = "ef83793eab61492ca51bc78b401f7ff5.s2.eu.hivemq.cloud";
const char* mqtt_username = "Chutiwat"; // ชื่อผู้ใช้ในการเข้าใช้ mqtt cloud
const char* mqtt_password = "121244aa"; // รหัสผ่านในการเข้าใช้ mqtt cloud
const int mqtt_port = 8883; //port ของ mqtt
WiFiClientSecure espClient; //board esp32 เชื่อมกับ mqtt
PubSubClient client(espClient); //เตรียมสื่อสารไปยัง mqtt
unsigned long lastMsg = 0; //ตัวแปรในการใช้กับ millis
unsigned long lastMsg1 = 0; //ตัวแปรในการใช้กับ millis
#define MSG_BUFFER_SIZE (50) //ตัวแปรจำกัดข้อความไว้ 50 ตัวอักษร
#define MSG_BUFFER_SIZE1 (50)
char msg[MSG_BUFFER_SIZE];//ตัวแปรจำกัดข้อความไว้ 50 ตัวอักษร เว็ทไว้ที่ตัวแปร msg
char msg1[MSG_BUFFER_SIZE1];
int MQ2Pin = 34; // เซ็ท MQ2 Sensor ไว้ที่ขา 34
const char* MQ2Topic = "MQ2"; // ส่งค่าไปยัง Topic MQ2
int Power = 32; // เซ็ท Power ไว้ที่ขา 32
const char* PowerTopic = "Power"; // ส่งค่าไปยัง Topic Power
int powerState = 0;
const char* Backup = "Backup_Power"; // ส่งค่าไปยัง Topic Connect
const char* ConTopic = "Connect"; // ส่งค่าไปยัง Topic Connect
#define NEOPIXEL_PIN 23 //เซ็ทวงแหวน NeoPixel ไว้ที่ขา 22
#define NUM_RINGS 4 //เซ็ทไว้ใช้ 4 วง
#define NUM_PIXELS_PER_RING 16 //เซ็ทไฟจากวงแหวน 16 ดวง
#define NUM_PIXELS_TOTAL (NUM_RINGS * NUM_PIXELS_PER_RING) //รวมค่าจากตัวแปรของวงแหวน NeoPixel
Adafruit_NeoPixel strip(NUM_PIXELS_TOTAL, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);//เซ็ทตั้งค่าของวงแหวน NeoPixel
const char* Lock1Topic = "Lock1"; // ส่ง Topics Lock1 ไปยัง mqtt
const char* Lock2Topic = "Lock2"; // ส่ง Topics Lock2 ไปยัง mqtt
const char* Lock3Topic = "Lock3"; // ส่ง Topics Lock3 ไปยัง mqtt
const char* Lock4Topic = "Lock4"; // ส่ง Topics Lock4 ไปยัง mqtt
const char* LampTopic = "Lamp"; // ส่ง Topics Lamp ไปยัง mqtt
bool waitingForRelayCommand = true; // ต่อมาให้รอคำสั่งรีเลย์
bool PowerState = false; //ค่าสถานะ Powerเป็น 0 ก่อน
#define Timemil 2000 //เซ็ทตัวแปร Timemil ไว้ 2000
unsigned long time_1 = 0; //เซ็ท time_1 เป็น 0
//เซ็ทค่าสถานะรีเลย์เป็น false ก่อนเพื่อเอาไปใช้ร่วมกับฟังก์ชั่นของ controlNeopixelRing
bool LockState = false;
//เซ็ทค่าสถานะรีเลย์ในแต่ละตัวเป็น false ก่อน
bool Lock1State = false;
bool Lock2State = false;
bool Lock3State = false;
bool Lock4State = false;
bool LampState = false;
const int Lock1Pin = 19; // เซ็ทขารีเลย์เป็น 19 ของ Lock1
const int Lock2Pin = 5; // เซ็ทขารีเลย์เป็น 5 ของ Lock2
const int Lock3Pin = 17; // เซ็ทขารีเลย์เป็น 17 ของ Lock3
const int Lock4Pin = 16; // เซ็ทขารีเลย์เป็น 16 ของ Lock4
const int LampOfPower = 33; // เซ็ทขารีเลย์เป็น 17 ของ Lamp1
const int LampPin = 25; // เซ็ทขารีเลย์เป็น 16 ของ Lamp
int lockPins[] = {19, 5, 17, 16};//ขารีเลย์ใน array ไว้ใช้สำหรับ lock แต่ละอัน
int buzPin = 26; //เซ็ทลำโพงไว้ที่ขา 26
// ใบรับรองในการใช้เข้าถึงของ hivemq บน cloud
static const char *root_ca PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
)EOF";
//เซ็ทเชื่อมต่อ WiFi พร้อมแสดงข้อความที่อยู่ IP ของ WiFi บน Serial monitor
void setup_wifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("กำลังเชื่อมต่อเครือข่าย");
Serial.print("ที่อยู่ของ IP: ");
Serial.println(WiFi.localIP());
}
//function ในการเข้า mqtt จาก Hivemq บน cloud
void reconnect() {
while (!client.connected()) {
//ในขณะที่มันกำลังเชื่อมต่อกับ mqtt ให้แสดงข้อความไว้บน Serial Monitor
//พร้อมกับแสดงข้อความบนจอ lcd
Serial.print("กำลังพยายามเชื่อมต่อกับ MQTT... ");
lcd.setCursor(0, 0); // ไปที่ตัวอักษรที่ 0 แถวที่ 1
lcd.print("Connecting to");
lcd.setCursor(0, 1); // ไปที่ตัวอักษรที่ 0 แถวที่ 1
lcd.print("MQTT...");
String clientId = "ESP32Client";
if (client.connect(clientId.c_str(), mqtt_username, mqtt_password)) {
//ถ้าเชื่อมต่อ mqtt จาก hivemq บน cloud ได้แล้วให้แสดงข้อความบน Serial Monitor
//และเชื่อม Topics ไปยัง mqtt ในแต่ละอันด้วยพร้อมทั้งส่งข้อความไปยัง mqtt ว่าจริง
//เพื่อที่จะส่งค่าไปยัง LED Dashbord เป็นสีเขียวบน Node Red
//และแสดงข้อความบนจอ lcd ด้วย
Serial.println("เชื่อมต่อเสร็จสิ้น!");
lcd.clear();
lcd.setCursor(3, 0); // ไปที่ตัวอักษรที่ 0 แถวที่ 1
lcd.print("Completed!");
delay(2000);
lcd.clear();
client.subscribe(Lock1Topic);
client.subscribe(Lock2Topic);
client.subscribe(Lock3Topic);
client.subscribe(Lock4Topic);
client.subscribe(LampTopic);
client.publish(ConTopic, "true");
}else {
//แต่ถ้าเชื่อมต่อ mqtt จาก hivemq บน cloud แล้วล้มเหลวให้แสดงข้อความบน Serial Monitor
//และและทำการเชื่อมต่อใหม่อีกครั้งพร้อมทั้งส่งข้อความไปยัง mqtt ว่าไม่จริง
//เพื่อที่จะส่งค่าไปยัง LED Dashbord เป็นสีแดงบน Node Red
//และแสดงข้อความบนจอ lcd ด้วย
Serial.print("ล้มเหล้ว, rc=");
lcd.clear();
lcd.setCursor(2, 0); // ไปที่ตัวอักษรที่ 0 แถวที่ 1
lcd.print("Connect failed, rc=");
client.publish(ConTopic, "false");
Serial.print(client.state());
Serial.println(" ลองใหม่อีกครั้งใน 5 วินาที");
lcd.setCursor(5, 1); // ไปที่ตัวอักษรที่ 0 แถวที่ 1
lcd.print("Try again!");
delay(5000);
lcd.clear();
}
}
}
//function ในการรับค่าด้วย topics จาก mqtt
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("ข้อความที่ได้รับ [");
Serial.print(topic);
Serial.print("] ");
String message = "";
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println(message);
// อ่านค่าจาก Serial Monitor ที่เป็นของ Topic ในแต่ละรีเลย์เพื่อควบคุมรีเลย์
// และวงแหวนNeoPixel
if (strcmp(topic, Lock1Topic) == 0) {
if (message.equals("true")) {
Lock1State = true;
//ถ้าเป็นจริงจะให้วงแหวน NeoPixel เปลี่ยนไฟเป็นสีเขียว
//ตาม Function ของ controlNeopixelRing
LockState = true;
digitalWrite(Lock1Pin, HIGH); // เปิดใช้งานรีเลย์ที่ 1
} else if (message.equals("false")) {
Lock1State = false;
//ถ้าไม่เป็นจริงจะให้วงแหวน NeoPixel เปลี่ยนไฟเป็นสีแดง
//ตาม Function ของ controlNeopixelRing
LockState = false;
digitalWrite(Lock1Pin, LOW); // ปิดใช้งานรีเลย์ที่ 1
}
//ใช้ Function นี้รออ่านค่าจาก relayState เพื่อทำงานกับวงแหวน NeoPixel
//ตามลำดับด้วย ขารีเลย์ 4 ตัวใน Array ที่กำหนดไว้
controlNeopixelRing(0,LockState);
} else if (strcmp(topic, Lock2Topic) == 0) {
if (message.equals("true")) {
Lock2State = true;
LockState = true;
digitalWrite(Lock2Pin, HIGH); // เปิดใช้งานรีเลย์ที่ 2
} else if (message.equals("false")) {
Lock2State = false;
LockState = false;
digitalWrite(Lock2Pin, LOW); // ปิดใช้งานรีเลย์ที่ 2
}
controlNeopixelRing(1,LockState);
} else if (strcmp(topic, Lock3Topic) == 0) {
if (message.equals("true")) {
Lock3State = true;
LockState = true;
digitalWrite(Lock3Pin, HIGH); // เปิดใช้งานรีเลย์ที่ 3
} else if (message.equals("false")) {
Lock3State = false;
LockState = false;
digitalWrite(Lock3Pin, LOW); // // ปิดใช้งานรีเลย์ที่ 3
}
controlNeopixelRing(2,LockState);
} else if (strcmp(topic, Lock4Topic) == 0) {
if (message.equals("true")) {
Lock4State = true;
LockState = true;
digitalWrite(Lock4Pin, HIGH); // เปิดใช้งานรีเลย์ที่ 4
} else if (message.equals("false")) {
Lock4State = false;
LockState = false;
digitalWrite(Lock4Pin, LOW); // // ปิดใช้งานรีเลย์ที่ 4
}
controlNeopixelRing(3,LockState);
}else if (strcmp(topic, LampTopic) == 0) {
if (message.equals("true")) {
LampState = true;
digitalWrite(LampPin, HIGH); // เปิดใช้งานรีเลย์ที่ 2
} else if (message.equals("false")) {
LampState = false;
digitalWrite(LampPin, LOW); // ปิดใช้งานรีเลย์ที่ 2
}
}
}
void setup() {
Serial.begin(9600);
for (int i = 0; i < NUM_RINGS; i++) {
pinMode(lockPins[i], OUTPUT);
digitalWrite(lockPins[i], HIGH); // ตรวจสอบให้แน่ใจว่ารีเลย์ปิดอยู่ตั้งแต่แรก
}
pinMode(LampOfPower, OUTPUT);
pinMode(LampPin, OUTPUT);
digitalWrite(LampOfPower, HIGH);
digitalWrite(LampPin, HIGH);
pinMode(Power, INPUT);
pinMode(buzPin, OUTPUT);
strip.begin();
strip.show();
setup_wifi();
lcd.init(); // initialize the lcd
lcd.backlight();
lcd.setCursor(0, 0); // ไปที่ตัวอักษรที่ 0 แถวที่ 1
lcd.print("Welcome To");
lcd.setCursor(1, 1); // ไปที่ตัวอักษรที่ 7 แถวที่ 2
lcd.print("Store System");
delay(3000);
lcd.clear();
espClient.setCACert(root_ca);
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
//ถ้ายังไม่เชื่อมต่อกับ mqtt ให้ใช้ function ของ reconnect
if (!client.connected()) {
reconnect();
}
}
void loop() {
//ถ้าค่าของ PowerBoots เป็น 0 จะแสดงข้อความเป็น Off แต่ถ้าเป็น 1 ก็จะแสดงเป้นข้อความ On แทน
//จะแสดงข้อความตามค่าแบตเตอรี่ใน Serial Monitor
lcd.setCursor(1, 0); // ไปที่ตัวอักษรที่ 0 แถวที่ 1
lcd.print("Smoke:");
lcd.setCursor(1, 1); // ไปที่ตัวอักษรที่ 0 แถวที่ 1
lcd.print("Power:");
int PowerBoots = digitalRead(Power);//อ่านค่าปลั้กไฟ
// Serial.println(powerStatus);
String powerStatus;
// Serial.println(PowerBoots);
switch (PowerBoots){
case 0:
if (PowerBoots == 0 && powerState == 0) {
//ถ้าค่าปลั้กไฟเป็น 0 ให้ส่งข้อความเป็น false และยกเลิกการเชื่อม Topics ของ Lamp
lcd.setCursor(9, 1);
lcd.print(" "); // เคลียรบรรทัด
lcd.setCursor(9, 1);
lcd.print("Off"); //แสดงค่าเป็น off บนจอ lcd
digitalWrite(LampOfPower,LOW);
client.publish(PowerTopic, "false");
powerState = 1;
}
break;
case 1:
if(PowerBoots == 1 && powerState == 1){
//แต่ถ้าค่าปลั้กไฟเป็น 1 ให้ส่งข้อความเป็น true และกลับมาเชื่อมต่อ Topics ของ Lamp
//และกลับมาใช้ function ของรอสถานะคำสั่งอีกครั้ง
lcd.setCursor(9, 1);
lcd.print(" "); // เคลียรบรรทัด
lcd.setCursor(9, 1);
lcd.print("On"); //แสดงค่า on บนจอ lcd
digitalWrite(LampOfPower,HIGH);
client.publish(PowerTopic, "true");
powerState = 0;
break;
default:
// จัดการกรณีอื่นๆ หากจำเป็น
break;
}
}
client.loop();
delay(1000);
//ให้แสดงข้อความและค่าต่างๆไปในทุกๆช่วง 2 วินาที
if(millis() - time_1 > Timemil){
time_1 = millis();
snprintf(msg1, MSG_BUFFER_SIZE1, "%d", PowerBoots);
Serial.print("ค่าสถานะปลั๊ก : ");
Serial.println(msg1);
//ตั้งค่ารูปแบบของค่าจาก MQ2 Sensor เป็นรูปแบบเลขฐานสอง
int MQ2 = map(analogRead(MQ2Pin), 0, 4100, 0, 100);
snprintf(msg, MSG_BUFFER_SIZE, "%d", MQ2);
lcd.setCursor(9, 0);
lcd.print(" "); // Clear the line
lcd.setCursor(9, 0);
lcd.print(MQ2); //แสดงค่า MQ2 บนจอ lcd
lcd.setCursor(11, 0);
lcd.print("%");
Serial.print("ระดับควันไฟ : ");
Serial.println(msg); // แสดงค่าของ MQ2 Sensor ไปยัง Serial Monitor
client.publish(MQ2Topic, msg);
if (MQ2 >= 80) {
//ถ้าค่า MQ2 มากกว่าหรือเท่ากับ 80 จะแจ้งเตือนโดยข้อความที่กำหนดพร้อมส่งเสียงจากลำโพง
digitalWrite(buzPin, HIGH);
delayMicroseconds(1000);
digitalWrite(buzPin, LOW);
delayMicroseconds(1000);
} else {
//แต่ถ้าค่า MQ2 น้อยกว่า 80 จะปิดแจ้งเตือนพร้อมหยุดใช้งานลำโพง
digitalWrite(buzPin, LOW);
}
}
unsigned long now = millis();
if (now - lastMsg > 2000) {
// เพิ่ม Logic เพื่อควบคุมรีเลย์แต่ละตัวตามตัวแปรสถานะที่กำหนดไว้ดังนี้ (relay1State, relay2State, etc.)
digitalWrite(Lock1Pin, Lock1State ? HIGH : LOW);
digitalWrite(Lock2Pin, Lock2State ? HIGH : LOW);
digitalWrite(Lock3Pin, Lock3State ? HIGH : LOW);
digitalWrite(Lock4Pin, Lock4State ? HIGH : LOW);
digitalWrite(LampPin, LampState ? HIGH : LOW);
}
}
//Function ในการควบคุมวงแหวน NeoPixel
void controlNeopixelRing(int ring, int relayState) {
int startPixel = ring * NUM_PIXELS_PER_RING;
int endPixel = (ring + 1) * NUM_PIXELS_PER_RING;
//จะวนลูปสำหรับ NeoPixel ring และ Relay ตามลำดับพร้อมกัน
for (int i = startPixel; i < endPixel; i++) {
//ถ้าค่าสถานะของบิ้งค์ตัวใดตัวหนึ่งเป็น 1 relay[i] ไฟจะติด และ NeoPixel Ring[i] จะเป็นสีเขียว
if (relayState == true) {
strip.setPixelColor(i, strip.Color(0, 255, 0)); // Green
digitalWrite(lockPins[ring], HIGH); // Turn on the relay
//แต่ถ้าค่าสถานะของบิ้งค์ตัวใดตัวหนึ่งเป็น 0 relay[i] ไฟจะดับ และ NeoPixel Ring[i] จะเป็นสีน้ำเงินเหมือนเดิม
} else {
strip.setPixelColor(i, strip.Color(0, 0, 0)); // Blue
digitalWrite(lockPins[ring], LOW); // Turn off the relay
}
}
strip.show();
}