From d1c4ac9998bc1454c6157849c6401a3499dabf41 Mon Sep 17 00:00:00 2001 From: Natthawut Laeme <141604850+POTAEPT@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:12:29 +0700 Subject: [PATCH 1/5] Remove BLE features and update WiFi setup Refactor M5Stick C++ code to remove BLE functionality and update WiFi and display handling. --- StickCplus2Station1/src/m5stick-c-plus.cpp | 208 ++++++++------------- 1 file changed, 77 insertions(+), 131 deletions(-) diff --git a/StickCplus2Station1/src/m5stick-c-plus.cpp b/StickCplus2Station1/src/m5stick-c-plus.cpp index 6627171..eebdcca 100644 --- a/StickCplus2Station1/src/m5stick-c-plus.cpp +++ b/StickCplus2Station1/src/m5stick-c-plus.cpp @@ -2,177 +2,123 @@ #include #include #include -#include -#include -#include -#include // เพิ่ม Library นี้สำหรับเปลี่ยนชื่อแบบ Fast Mode -// --- CONFIGURATION --- +// --- ตั้งชื่อ WiFi ที่จะปล่อย --- const char *SSID_AP = "Web3Showcase_AP"; -const char *PASSWORD_AP = "12345678"; -const int LOCAL_PORT = 88; +const char *PASSWORD_AP = NULL; // ใส่รหัสผ่านได้ถ้าต้องการ หรือ NULL เพื่อเปิดฟรี +const int LOCAL_PORT = 88; // Port 80 คือมาตรฐาน HTTP, แต่ถ้าใช้ 88 เวลาเข้าเว็บต้องพิมพ์ 192.168.4.1:88 -// --- FIX IP SETTINGS --- -IPAddress localIP(192, 168, 4, 20); // ใช้ IP .20 (ถูกต้องแล้ว) -IPAddress gateway(192, 168, 4, 1); -IPAddress subnet(255, 255, 255, 0); +// --- ตั้งค่า IP Address ของเครื่องนี้ --- +// ปกติ M5 จะเป็น 192.168.4.1 แต่ถ้าอยากฟิกเป็น .2 ก็ทำได้ +const IPAddress local_IP(192, 168, 4, 1); +const IPAddress gateway(192, 168, 4, 1); +const IPAddress subnet(255, 255, 255, 0); -// --- GLOBALS --- AsyncWebServer stickCServer(LOCAL_PORT); + String currentUsername = "Not Registered"; String authenStatus = "X"; int ccoin = 0; -String alertText = "Waiting for Identity..."; +String alertText = "Waiting..."; String myMacAddress = ""; -BLEServer *pServer = NULL; -BLEAdvertising *pAdvertising = NULL; - -// Flag ฝากงานให้ Loop ทำ -bool shouldRestartBLE = false; -String newBLEName = ""; - -// --- FUNCTIONS --- - -// 1. ฟังก์ชันเปลี่ยนชื่อ BLE (ฉบับแก้ไข: ไม่ต้องปิดระบบ Matrix เห็นไวมาก) -void startBLE(String name) { - // กรณีเพิ่งเริ่มครั้งแรก (Start) - if (pAdvertising == NULL) { - BLEDevice::init(name.c_str()); - pServer = BLEDevice::createServer(); - pAdvertising = BLEDevice::getAdvertising(); - pAdvertising->setScanResponse(true); - pAdvertising->setMinPreferred(0x06); - pAdvertising->setMinPreferred(0x12); - } else { - // กรณีเปลี่ยนชื่อ: แค่หยุดประกาศชั่วคราว (Stop) - pAdvertising->stop(); - } - - // --- ส่วนสำคัญ: เปลี่ยนชื่อระดับ Hardware โดยไม่ต้อง deinit --- - esp_ble_gap_set_device_name(name.c_str()); - // ------------------------------------------------------- - - // จัดกระเป๋าใบที่ 1 (Main): ใส่ UUID 1234 - BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); - oAdvertisementData.setFlags(0x06); - oAdvertisementData.setCompleteServices(BLEUUID("1234")); - pAdvertising->setAdvertisementData(oAdvertisementData); - - // จัดกระเป๋าใบที่ 2 (Response): ใส่ชื่อใหม่ - BLEAdvertisementData oScanResponseData = BLEAdvertisementData(); - oScanResponseData.setName(name.c_str()); - pAdvertising->setScanResponseData(oScanResponseData); - - // เริ่มประกาศอีกครั้ง (Start) - pAdvertising->start(); - - Serial.printf("[BLE] Name Updated to: %s (UUID: 1234)\n", name.c_str()); -} +volatile bool needUpdate = false; -// 2. ฟังก์ชันอัปเดตหน้าจอ void updateDisplay() { - M5.Display.fillScreen(BLACK); - M5.Display.setTextDatum(top_left); - M5.Display.setFont(&fonts::Font2); - M5.Display.setTextColor(WHITE); + M5.Lcd.fillScreen(BLACK); + M5.Lcd.setCursor(0, 0); - M5.Display.setCursor(5, 5); - M5.Display.printf("IP: %s\n", WiFi.localIP().toString().c_str()); - M5.Display.setCursor(5, 25); - M5.Display.printf("User: %s\n", currentUsername.c_str()); - M5.Display.printf("Status: %s\n", authenStatus.c_str()); - M5.Display.printf("CCoin: %d\n", ccoin); + M5.Lcd.setTextColor(CYAN); + M5.Lcd.print("MAC: "); M5.Lcd.println(myMacAddress); + M5.Lcd.print("IP: "); M5.Lcd.println(WiFi.softAPIP()); // โชว์ IP ให้รู้ - if (authenStatus == "/") M5.Display.setTextColor(GREEN); - else M5.Display.setTextColor(ORANGE); + M5.Lcd.setTextColor(WHITE); + M5.Lcd.print("User: "); M5.Lcd.println(currentUsername); - M5.Display.setCursor(5, 95); - M5.Display.printf("MSG: %s", alertText.c_str()); + M5.Lcd.setTextColor(YELLOW); + M5.Lcd.print("Coin: "); M5.Lcd.println(ccoin); + + M5.Lcd.setTextColor(RED); + M5.Lcd.println(alertText); } -// 3. Handler รับ Username จาก Core2 void handleUsername(AsyncWebServerRequest *request) { - String receivedUsername = ""; if (request->hasParam("username", true)) { - receivedUsername = request->getParam("username", true)->value(); - } else if (request->hasParam("username", false)) { - receivedUsername = request->getParam("username", false)->value(); + currentUsername = request->getParam("username", true)->value(); + alertText = "ID Created!"; + needUpdate = true; + // M5.Speaker.tone(1500, 150); + request->send(200, "text/plain", "OK"); + } else { + request->send(400, "text/plain", "No User"); } +} - if (receivedUsername != "") { - M5.Speaker.tone(1500, 150); - - Serial.printf("[HTTP] Received Username: %s\n", receivedUsername.c_str()); - - currentUsername = receivedUsername; - alertText = "Identity Confirmed!"; - authenStatus = "/"; - - // ยกธงบอก Loop ให้เปลี่ยนชื่อ - newBLEName = receivedUsername; - shouldRestartBLE = true; - - updateDisplay(); +void handleCoin(AsyncWebServerRequest *request) { + if (request->hasParam("value", true)) { + ccoin += request->getParam("value", true)->value().toInt(); + alertText = "Coin Get!"; + needUpdate = true; + // M5.Speaker.tone(2000, 100); request->send(200, "text/plain", "OK"); } else { - request->send(400, "text/plain", "Fail: Missing Username"); + request->send(400, "text/plain", "No Value"); } } void setup() { auto cfg = M5.config(); M5.begin(cfg); - M5.Display.setRotation(3); + Serial.begin(115200); + Serial.println("--- BOOT START ---"); - // --- Phase 1: Connecting WiFi --- - M5.Display.fillScreen(BLACK); - M5.Display.setFont(&fonts::Font2); - M5.Display.setTextColor(WHITE); - M5.Display.setTextDatum(middle_center); - M5.Display.drawString("Connecting WiFi...", M5.Display.width()/2, M5.Display.height()/2 - 10); + M5.Lcd.setRotation(3); + M5.Lcd.setTextSize(2); - WiFi.mode(WIFI_STA); + // --- ส่วนที่แก้ไข: ตั้งค่าเป็น Access Point --- + Serial.print("Setting up AP..."); + M5.Lcd.print("Creating WiFi..."); - WiFi.config(localIP, gateway, subnet); - WiFi.begin(SSID_AP, PASSWORD_AP); + WiFi.mode(WIFI_AP); // บังคับโหมด AP + WiFi.softAPConfig(local_IP, gateway, subnet); // ตั้งค่า IP + bool result = WiFi.softAP(SSID_AP, PASSWORD_AP); // เริ่มปล่อยสัญญาณ - Serial.println("[WiFi] Connecting..."); - - M5.Display.setTextDatum(top_left); - M5.Display.setCursor(10, M5.Display.height()/2 + 10); - while (WiFi.status() != WL_CONNECTED) { - delay(500); - M5.Display.print("."); - Serial.print("."); + if(result) { + Serial.println("Ready"); + M5.Lcd.println("Done"); + } else { + Serial.println("Failed!"); + M5.Lcd.println("Failed"); } - Serial.println("\n[WiFi] Connected!"); - - // --- Phase 2: System Ready --- - myMacAddress = WiFi.macAddress(); - + + // อ่าน MAC Address ของฝั่ง AP + myMacAddress = WiFi.softAPmacAddress(); + Serial.print("AP IP address: "); + Serial.println(WiFi.softAPIP()); + Serial.println("MAC: " + myMacAddress); + + // เริ่ม Server stickCServer.on("/set_username", HTTP_POST, handleUsername); - stickCServer.on("/set_username", HTTP_GET, handleUsername); - stickCServer.begin(); + stickCServer.on("/add_coin", HTTP_POST, handleCoin); + + // จัดการ 404 Not Found (เผื่อคนเข้าผิดหน้า) + stickCServer.onNotFound([](AsyncWebServerRequest *request){ + request->send(404, "text/plain", "Not found"); + }); - // เริ่ม BLE ครั้งแรก - startBLE("GUEST-PLAYER"); + stickCServer.begin(); + Serial.println("Server Started"); updateDisplay(); } void loop() { - // --- โซนทำงานหนัก (ปลอดภัย ไม่รีเซ็ต) --- - if (shouldRestartBLE) { - delay(100); - M5.Speaker.tone(2000, 300); - - // เรียกฟังก์ชันเปลี่ยนชื่อแบบใหม่ (ที่ไม่พัง) - startBLE(newBLEName); - - shouldRestartBLE = false; - } - M5.update(); -} \ No newline at end of file + if (needUpdate) { + Serial.println("Updating Display..."); + updateDisplay(); + needUpdate = false; + } + // ไม่ต้องมี delay ใน loop นี้เพื่อให้ AsyncWebServer ทำงานได้เต็มที่ +} From a0305462573725a41d10f95ec69441d2f0aedf4b Mon Sep 17 00:00:00 2001 From: Natthawut Laeme <141604850+POTAEPT@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:13:14 +0700 Subject: [PATCH 2/5] Update m5stack-core2.cpp --- Core2Station1/src/m5stack-core2.cpp | 298 ++++++++++++++-------------- 1 file changed, 150 insertions(+), 148 deletions(-) diff --git a/Core2Station1/src/m5stack-core2.cpp b/Core2Station1/src/m5stack-core2.cpp index ac99b74..c6a1a77 100644 --- a/Core2Station1/src/m5stack-core2.cpp +++ b/Core2Station1/src/m5stack-core2.cpp @@ -2,198 +2,200 @@ #include #include #include -#include +#include "qrcode.h" #include #include +#include // --- CONFIGURATION --- const char *SSID = "Web3Showcase_AP"; -const char *PASSWORD = "12345678"; -const char* STICKC_IP = "192.168.4.20"; -const int STICKC_PORT = 88; +const char *PASSWORD = NULL; // รหัสผ่าน -IPAddress localIP(192, 168, 4, 1); -IPAddress gateway(192, 168, 4, 1); -IPAddress subnet(255, 255, 255, 0); +// Fixed IP Config +const IPAddress localIP(192, 168, 4, 1); +const IPAddress gateway(192, 168, 4, 1); +const IPAddress subnet(255, 255, 255, 0); -// สร้างตัวแปร String สำหรับ URL ของ IP เพื่อใช้ในโค้ดใหม่ -String localIPURL = "http://192.168.4.1"; +// Target StickC +const String STICKC_URL = "http://192.168.4.2:80/set_username"; +// --- OBJECTS --- AsyncWebServer server(80); DNSServer dnsServer; -const byte DNS_PORT = 53; +QRCode qrcode; -String penningUsername = ""; -bool hasNewUserName = false; +// --- STATE --- +String registeredUser = ""; +bool isSuccessScreen = false; +unsigned long successTimer = 0; -// --- HTML (เปลี่ยนชื่อตัวแปรเป็น index_html ตามโค้ดใหม่) --- -const char* index_html = R"raw( +// --- HTML --- +const char index_html[] PROGMEM = R"rawliteral( - - - Web3 Student Club Identity - + + Web3 Student Club + -

STATION 1: Identity Creation

-
-
-
- +
+

🎯 Station 1

+

Create Identity

+ + + +
-)raw"; +)rawliteral"; -// --- CORE FUNCTIONS --- +// --- UI FUNCTIONS --- +void drawQRCode() { + isSuccessScreen = false; + M5.Display.fillScreen(BLACK); + + String qrData = "WIFI:S:" + String(SSID) + ";T:WPA;P:" + String(PASSWORD) + ";;"; + + uint8_t qrcodeData[qrcode_getBufferSize(3)]; + qrcode_initText(&qrcode, qrcodeData, 3, 0, qrData.c_str()); + + int scale = 4; + int offsetX = (320 - (qrcode.size * scale)) / 2; + int offsetY = (240 - (qrcode.size * scale)) / 2 - 20; -void sendUsernameToStickC(String username) { - HTTPClient http; - String url = "http://" + String(STICKC_IP) + ":" + String(STICKC_PORT) + "/set_username"; + M5.Display.fillRect(offsetX - 5, offsetY - 5, (qrcode.size * scale) + 10, (qrcode.size * scale) + 10, WHITE); - http.begin(url); - http.setConnectTimeout(2000); // Timeout 2 วินาที กันค้าง - http.addHeader("Content-Type", "application/x-www-form-urlencoded"); - String postData = "username=" + username; + for (uint8_t y = 0; y < qrcode.size; y++) { + for (uint8_t x = 0; x < qrcode.size; x++) { + if (qrcode_getModule(&qrcode, x, y)) { + M5.Display.fillRect(x * scale + offsetX, y * scale + offsetY, scale, scale, BLACK); + } + } + } + M5.Display.setTextSize(2); + M5.Display.setTextColor(CYAN); + M5.Display.drawCenterString("Scan to Register", 160, 215); +} - int httpResponseCode = http.POST(postData); +void drawSuccess(String user) { + isSuccessScreen = true; + successTimer = millis(); + M5.Display.fillScreen(BLACK); + M5.Display.setTextColor(GREEN); + M5.Display.setTextSize(3); + M5.Display.drawCenterString("SUCCESS!", 160, 60); + M5.Display.setTextColor(YELLOW); + M5.Display.drawCenterString(user, 160, 140); + M5.Display.setTextColor(WHITE); + M5.Display.setTextSize(2); + M5.Display.drawCenterString("Check StickC", 160, 200); +} - M5.Lcd.setCursor(5, 100); - if (httpResponseCode > 0) { - M5.Lcd.printf("Sent to StickC (Code: %d) ", httpResponseCode); - } else { - M5.Lcd.printf("StickC Request Failed (Error: %s)", http.errorToString(httpResponseCode).c_str()); - } +bool sendToStickC(String user) { + HTTPClient http; + http.begin(STICKC_URL); + http.addHeader("Content-Type", "application/x-www-form-urlencoded"); + http.setConnectTimeout(2000); + String postData = "username=" + user; + int httpCode = http.POST(postData); http.end(); + return (httpCode == 200); } -void displayQRCode(const char* data) { - M5.Lcd.fillScreen(BLACK); - M5.Lcd.setTextDatum(top_center); - M5.Lcd.setTextColor(WHITE); - M5.Lcd.setFont(&fonts::Font4); - M5.Lcd.drawString("1. Connect to Wi-Fi", M5.Lcd.width() / 2, 10); - - int size = 180; - int x = (M5.Lcd.width() - size) / 2; - int y = 50; +// --- SETUP --- +void setup() { + auto cfg = M5.config(); + M5.begin(cfg); + M5.Display.setRotation(1); + Serial.begin(115200); - M5.Lcd.qrcode(data, x, y, size); + // ---------------------------------------------------- + // 🛠️ 1. ANDROID CONNECTION FIX (ลำดับสำคัญมาก) + // ---------------------------------------------------- + WiFi.mode(WIFI_AP); - M5.Lcd.setTextDatum(top_left); - M5.Lcd.setCursor(5, 240); - M5.Lcd.setFont(&fonts::Font2); - M5.Lcd.print("2. Enter Username in Browser"); -} - -// --- SETUP SERVER FUNCTION (รวมโค้ดใหม่ที่นี่) --- -void setUpWebserver() { - // 1. Required Handlers (Windows 11 & PAD Fixes) - server.on("/connecttest.txt", [](AsyncWebServerRequest *request) { request->redirect("http://logout.net"); }); - server.on("/wpad.dat", [](AsyncWebServerRequest *request) { request->send(404); }); - - // 2. Android Specific (แก้ตรงนี้: ส่งหน้าเว็บ index_html สวนกลับไปเลย ไม่ต้อง redirect) - // Android รุ่นใหม่ - server.on("/generate_204", [](AsyncWebServerRequest *request) { - AsyncWebServerResponse *response = request->beginResponse(200, "text/html", index_html); - response->addHeader("Cache-Control", "public,max-age=31536000"); - request->send(response); - }); - // Android รุ่นเก่า - server.on("/gen_204", [](AsyncWebServerRequest *request) { - AsyncWebServerResponse *response = request->beginResponse(200, "text/html", index_html); - response->addHeader("Cache-Control", "public,max-age=31536000"); - request->send(response); + // สั่งปิด WiFi ก่อนเพื่อแก้ Config + esp_wifi_stop(); + esp_wifi_deinit(); + + // โหลด Config ใหม่และปิด AMPDU RX (ตัวปัญหาของ Android) + wifi_init_config_t my_config = WIFI_INIT_CONFIG_DEFAULT(); + my_config.ampdu_rx_enable = false; + esp_wifi_init(&my_config); + esp_wifi_start(); + delay(100); + + // เริ่มสร้าง AP *หลังจาก* แก้ Config แล้วเท่านั้น + WiFi.softAPConfig(localIP, gateway, subnet); + WiFi.softAP(SSID, PASSWORD); + + Serial.print("AP IP: "); Serial.println(WiFi.softAPIP()); + // ---------------------------------------------------- + + // 2. DNS Server (Captive Portal หัวใจหลัก) + // ดักจับทุก Domain (*) ให้ชี้มาที่ IP เรา + dnsServer.setTTL(300); + dnsServer.setErrorReplyCode(DNSReplyCode::NoError); + dnsServer.start(53, "*", localIP); + + // 3. Routes for Captive Portal (ดักทุกทาง) + // Android Check + server.on("/generate_204", HTTP_GET, [](AsyncWebServerRequest *r){ r->redirect("http://192.168.4.1/"); }); + server.on("/gen_204", HTTP_GET, [](AsyncWebServerRequest *r){ r->redirect("http://192.168.4.1/"); }); + // Windows Check + server.on("/ncsi.txt", HTTP_GET, [](AsyncWebServerRequest *r){ r->redirect("http://192.168.4.1/"); }); + server.on("/connecttest.txt", HTTP_GET, [](AsyncWebServerRequest *r){ r->redirect("http://logout.net"); }); + // Apple Check + server.on("/hotspot-detect.html", HTTP_GET, [](AsyncWebServerRequest *r){ r->redirect("http://192.168.4.1/"); }); + + // Catch-All: ถ้าหาอะไรไม่เจอ ให้เด้งเข้าหน้าแรกเราให้หมด + server.onNotFound([](AsyncWebServerRequest *request){ + request->redirect("http://192.168.4.1/"); }); - // 3. Background responses (Microsoft, Apple, Firefox) - พวกนี้ Redirect เหมือนเดิมดีแล้ว - server.on("/redirect", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // Microsoft - server.on("/hotspot-detect.html", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // Apple - server.on("/canonical.html", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // Firefox - server.on("/success.txt", [](AsyncWebServerRequest *request) { request->send(200); }); // Firefox - server.on("/ncsi.txt", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // Windows - - // 4. Favicon - server.on("/favicon.ico", [](AsyncWebServerRequest *request) { request->send(404); }); - - // 5. Serve Basic HTML Page (Root) - server.on("/", HTTP_ANY, [](AsyncWebServerRequest *request) { - AsyncWebServerResponse *response = request->beginResponse(200, "text/html", index_html); - response->addHeader("Cache-Control", "public,max-age=31536000"); - request->send(response); - Serial.println("Served Basic HTML Page"); + // Main Page + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send_P(200, "text/html", index_html); }); - // 6. Handle Form Submission - server.on("/submit", HTTP_POST, [](AsyncWebServerRequest *request){ + // Register Handler + server.on("/register", HTTP_POST, [](AsyncWebServerRequest *request){ if (request->hasParam("username", true)) { - String username = request->getParam("username", true)->value(); - penningUsername = username; - hasNewUserName = true; - M5.Lcd.fillScreen(BLACK); - M5.Lcd.setTextDatum(top_left); - M5.Lcd.setFont(&fonts::Font2); - M5.Lcd.printf("Username: %s", username.c_str()); - request->send(200, "text/plain", "Success! Check the StickC."); + String user = request->getParam("username", true)->value(); + bool success = sendToStickC(user); + + String html = ""; + if(success) { + html += "

Success!

ID: " + user + "

"; + drawSuccess(user); + } else { + html += "

⚠️

Connection Error

StickC not found

"; + } + html += ""; + request->send(200, "text/html", html); } else { request->send(400, "text/plain", "Missing Username"); } }); - // 7. Catch All (onNotFound) - ส่งหน้าเว็บสวนไปเลยเช่นกัน - server.onNotFound([](AsyncWebServerRequest *request) { - AsyncWebServerResponse *response = request->beginResponse(200, "text/html", index_html); - request->send(response); // ไม่ Redirect แล้ว ส่งหน้าเว็บใส่เลย - Serial.print("Caught: "); - Serial.println(request->url()); - }); - server.begin(); - M5.Lcd.println("\nHTTP Server Started."); -} - -void setup() { - auto cfg = M5.config(); - M5.begin(cfg); - M5.Lcd.setRotation(1); - Serial.begin(115200); // เริ่มต้น Serial เพื่อดู Debug จากโค้ดใหม่ - - // WiFi Setup - WiFi.mode(WIFI_AP); - WiFi.softAPConfig(localIP, gateway, subnet); - WiFi.softAP(SSID, PASSWORD); - - // Display Info - M5.Lcd.printf("AP IP: %s\n", WiFi.softAPIP().toString().c_str()); - M5.Lcd.printf("AP MAC: %s\n", WiFi.softAPmacAddress().c_str()); - - // QR Code - String qrData = "WIFI:S:" + String(SSID) + ";T:WPA;P:" + String(PASSWORD) + ";;"; - displayQRCode(qrData.c_str()); - - // DNS Server - dnsServer.start(DNS_PORT, "*", localIP); - - // เริ่มต้น Webserver ด้วยฟังก์ชันใหม่ - setUpWebserver(); + drawQRCode(); } void loop() { + M5.update(); + // สำคัญ: ต้องเรียกตลอดเวลาเพื่อให้มือถือรู้ว่าต้องเด้งหน้าเว็บ dnsServer.processNextRequest(); - if(hasNewUserName){ - sendUsernameToStickC(penningUsername); - hasNewUserName = false; - // หลังจากส่งเสร็จ อาจจะให้กลับมาหน้า QR Code หรือหน้า Info ก็ได้ - delay(2000); - String qrData = "WIFI:S:" + String(SSID) + ";T:WPA;P:" + String(PASSWORD) + ";;"; - displayQRCode(qrData.c_str()); + // Reset หน้าจอกลับเป็น QR Code หลังจาก 5 วินาที + if (isSuccessScreen && (millis() - successTimer > 5000)) { + drawQRCode(); } - M5.update(); -} \ No newline at end of file +} From 1c024affa5f0cc2fc190b71fd75ce36745114ea5 Mon Sep 17 00:00:00 2001 From: Natthawut Laeme <141604850+POTAEPT@users.noreply.github.com> Date: Mon, 8 Dec 2025 20:53:16 +0700 Subject: [PATCH 3/5] Delete Core2Station1 directory --- Core2Station1/include/README | 37 --- Core2Station1/lib/README | 46 ---- Core2Station1/platformio.ini | 23 -- Core2Station1/src/m5stack-core2.cpp | 341 ---------------------------- Core2Station1/test/README | 11 - 5 files changed, 458 deletions(-) delete mode 100644 Core2Station1/include/README delete mode 100644 Core2Station1/lib/README delete mode 100644 Core2Station1/platformio.ini delete mode 100644 Core2Station1/src/m5stack-core2.cpp delete mode 100644 Core2Station1/test/README diff --git a/Core2Station1/include/README b/Core2Station1/include/README deleted file mode 100644 index 49819c0..0000000 --- a/Core2Station1/include/README +++ /dev/null @@ -1,37 +0,0 @@ - -This directory is intended for project header files. - -A header file is a file containing C declarations and macro definitions -to be shared between several project source files. You request the use of a -header file in your project source file (C, C++, etc) located in `src` folder -by including it, with the C preprocessing directive `#include'. - -```src/main.c - -#include "header.h" - -int main (void) -{ - ... -} -``` - -Including a header file produces the same results as copying the header file -into each source file that needs it. Such copying would be time-consuming -and error-prone. With a header file, the related declarations appear -in only one place. If they need to be changed, they can be changed in one -place, and programs that include the header file will automatically use the -new version when next recompiled. The header file eliminates the labor of -finding and changing all the copies as well as the risk that a failure to -find one copy will result in inconsistencies within a program. - -In C, the convention is to give header files names that end with `.h'. - -Read more about using header files in official GCC documentation: - -* Include Syntax -* Include Operation -* Once-Only Headers -* Computed Includes - -https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/Core2Station1/lib/README b/Core2Station1/lib/README deleted file mode 100644 index 9379397..0000000 --- a/Core2Station1/lib/README +++ /dev/null @@ -1,46 +0,0 @@ - -This directory is intended for project specific (private) libraries. -PlatformIO will compile them to static libraries and link into the executable file. - -The source code of each library should be placed in a separate directory -("lib/your_library_name/[Code]"). - -For example, see the structure of the following example libraries `Foo` and `Bar`: - -|--lib -| | -| |--Bar -| | |--docs -| | |--examples -| | |--src -| | |- Bar.c -| | |- Bar.h -| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html -| | -| |--Foo -| | |- Foo.c -| | |- Foo.h -| | -| |- README --> THIS FILE -| -|- platformio.ini -|--src - |- main.c - -Example contents of `src/main.c` using Foo and Bar: -``` -#include -#include - -int main (void) -{ - ... -} - -``` - -The PlatformIO Library Dependency Finder will find automatically dependent -libraries by scanning project source files. - -More information about PlatformIO Library Dependency Finder -- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/Core2Station1/platformio.ini b/Core2Station1/platformio.ini deleted file mode 100644 index dba2846..0000000 --- a/Core2Station1/platformio.ini +++ /dev/null @@ -1,23 +0,0 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[env:m5stack-core2] -platform = espressif32 -board = m5stack-core2 -framework = arduino -upload_speed = 1500000 -build_src_filter = + -lib_deps = - m5stack/M5Unified - me-no-dev/ESPAsyncWebServer@3.6.0 - me-no-dev/AsyncTCP@3.3.2 - ricmoo/qrcode@^0.0.1 - HTTPClient - diff --git a/Core2Station1/src/m5stack-core2.cpp b/Core2Station1/src/m5stack-core2.cpp deleted file mode 100644 index 8d3c145..0000000 --- a/Core2Station1/src/m5stack-core2.cpp +++ /dev/null @@ -1,341 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "../include/ShowcaseProtocol.h" - -// ============================================ -// CONFIGURATION -// ============================================ -const char *SSID_AP = "Web3_Showcase"; -const char *PASS_AP = ""; -const byte DNS_PORT = 53; -IPAddress apIP(192, 168, 4, 1); - -// ============================================ -// GLOBAL STATE -// ============================================ -DNSServer dnsServer; -WebServer webServer(80); -String lastUsername = "Guest"; -unsigned long lastUIUpdate = 0; -bool espNowReady = false; - -// ============================================ -// HTML FORM (Dark Theme + Responsive UI) -// ============================================ -const char index_html[] PROGMEM = R"rawliteral( - - - - Web3 Showcase - Identity - - - - - -
-

🎯 Web3 Showcase

-

Create Your Digital Identity

- -
-

📱 Scan QR Code to Connect

-

WiFi: Web3_Showcase (Open Network)

-
- -
- - -
- -
- ✓ Check your wearable device after submission -
-
- - -)rawliteral"; - -// ============================================ -// ESP-NOW INITIALIZATION -// ============================================ -void initESPNow() { - WiFi.mode(WIFI_AP_STA); - if (esp_now_init() != ESP_OK) { - Serial.println("[ERROR] ESP-NOW Init Failed"); - return; - } - - esp_now_peer_info_t peerInfo = {}; - memcpy(peerInfo.peer_addr, BROADCAST_MAC, 6); - peerInfo.channel = BROADCAST_CHANNEL; - peerInfo.encrypt = false; - - if (esp_now_add_peer(&peerInfo) != ESP_OK) { - Serial.println("[ERROR] Failed to add broadcast peer"); - return; - } - espNowReady = true; - Serial.println("[OK] ESP-NOW initialized"); -} - -// ============================================ -// SEND USERNAME TO STICKC -// ============================================ -void sendIdentityToStickC(const String &username) { - if (!espNowReady) { - M5.Lcd.fillRect(0, 140, 320, 100, TFT_RED); - M5.Lcd.setTextColor(TFT_WHITE); - M5.Lcd.setCursor(10, 160); - M5.Lcd.setTextSize(2); - M5.Lcd.print("ESP-NOW Error"); - return; - } - - ShowcaseMessage msg = createMessage(MSG_IDENTITY_ASSIGN, username.c_str(), 0, ""); - esp_err_t result = esp_now_send(BROADCAST_MAC, (uint8_t *)&msg, sizeof(msg)); - - // Clear previous status - M5.Lcd.fillRect(0, 140, 320, 100, TFT_BLACK); - M5.Lcd.setTextSize(2); - M5.Lcd.setCursor(10, 160); - - if (result == ESP_OK) { - M5.Lcd.setTextColor(TFT_GREEN); - M5.Lcd.printf("✓ Sent: %s", username.c_str()); - lastUsername = username; - Serial.printf("[OK] Identity sent: %s\n", username.c_str()); - } else { - M5.Lcd.setTextColor(TFT_RED); - M5.Lcd.print("✗ Send Failed"); - Serial.println("[ERROR] Failed to send identity"); - } - lastUIUpdate = millis(); -} - -// ============================================ -// UI DRAWING -// ============================================ -void drawUI() { - M5.Lcd.fillScreen(TFT_BLACK); - - // Header - M5.Lcd.fillRect(0, 0, 320, 60, TFT_NAVY); - M5.Lcd.setTextColor(TFT_WHITE); - M5.Lcd.setTextSize(3); - M5.Lcd.drawString("STATION 1", 20, 15); - - // QR Code area - M5.Lcd.setTextColor(TFT_CYAN); - M5.Lcd.setTextSize(1); - M5.Lcd.drawString("QR Code:", 20, 75); - - // Generate and display QR - String qrData = "WIFI:S:" + String(SSID_AP) + ";T:nopass;;"; - M5.Lcd.qrcode(qrData.c_str(), 50, 100, 140, 5); - - // Info - M5.Lcd.setTextColor(TFT_ORANGE); - M5.Lcd.setTextSize(2); - M5.Lcd.drawString("Go to: Web3_Showcase", 20, 320); - - // Status - M5.Lcd.setTextColor(TFT_LIGHTGREY); - M5.Lcd.setTextSize(1); - M5.Lcd.drawString("Last user: " + lastUsername, 20, 350); -} - -// ============================================ -// SETUP -// ============================================ -void setup() { - M5.begin(true, true, true, true); - Serial.begin(115200); - delay(500); - - Serial.println("\n\n=== STATION 1: CORE2 STARTING ==="); - - // Initialize SPIFFS if needed for static files - if (!SPIFFS.begin(true)) { - Serial.println("[WARN] SPIFFS mount failed"); - } - - // Draw initial UI - drawUI(); - - // Setup WiFi AP - WiFi.mode(WIFI_AP_STA); - WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); - if (!WiFi.softAP(SSID_AP, PASS_AP, BROADCAST_CHANNEL, 0, 4)) { - Serial.println("[ERROR] SoftAP creation failed"); - return; - } - Serial.printf("[OK] SoftAP started: %s\n", SSID_AP); - - // Setup DNS - dnsServer.start(DNS_PORT, "*", apIP); - Serial.println("[OK] DNS server started"); - - // Setup Web Server routes - webServer.on("/", HTTP_GET, [](void) { - webServer.send(200, "text/html", index_html); - }); - - webServer.on("/submit", HTTP_POST, [](void) { - if (webServer.hasArg("username")) { - String username = webServer.arg("username"); - // Sanitize input - username.trim(); - if (username.length() > 0 && username.length() <= 12) { - // Send success page - webServer.send(200, "text/html", - "" - "

✓ Success!

" - "

Identity created: " + username + "

" - "

Check your wearable device...

" - "← Back" - ""); - sendIdentityToStickC(username); - } else { - webServer.send(400, "text/html", - "" - "

✗ Invalid Input

" - "

Name must be 2-12 characters

" - "← Back" - ""); - } - } else { - webServer.send(400, "text/plain", "Missing username field"); - } - }); - - webServer.onNotFound([](void) { - webServer.send(200, "text/html", index_html); - }); - - webServer.begin(); - Serial.println("[OK] Web server started on port 80"); - - // Initialize ESP-NOW - initESPNow(); - - Serial.println("=== STATION 1 READY ===\n"); -} - -// ============================================ -// MAIN LOOP -// ============================================ -void loop() { - // Handle DNS and HTTP requests - dnsServer.processNextRequest(); - webServer.handleClient(); - - // Handle button presses - M5.update(); - - // Periodic UI refresh - if (millis() - lastUIUpdate > 10000) { - drawUI(); - lastUIUpdate = millis(); - } - - delay(10); -} diff --git a/Core2Station1/test/README b/Core2Station1/test/README deleted file mode 100644 index 9b1e87b..0000000 --- a/Core2Station1/test/README +++ /dev/null @@ -1,11 +0,0 @@ - -This directory is intended for PlatformIO Test Runner and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PlatformIO Unit Testing: -- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html From 2c16f3550ccfde67ee453acdbbcf3069c1a9a2c4 Mon Sep 17 00:00:00 2001 From: Natthawut Laeme <141604850+POTAEPT@users.noreply.github.com> Date: Mon, 8 Dec 2025 20:55:31 +0700 Subject: [PATCH 4/5] Delete Station1-StickC/src directory --- Station1-StickC/src/main(stick).cpp | 107 --------------------- Station1-StickC/src/main.cpp | 143 ---------------------------- 2 files changed, 250 deletions(-) delete mode 100644 Station1-StickC/src/main(stick).cpp delete mode 100644 Station1-StickC/src/main.cpp diff --git a/Station1-StickC/src/main(stick).cpp b/Station1-StickC/src/main(stick).cpp deleted file mode 100644 index 92b6d6a..0000000 --- a/Station1-StickC/src/main(stick).cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include -#include -#include -#include -#include - -AsyncWebServer server(80); - -// Global State -String currentUsername = "Guest"; -String authStatus = "X"; -int ccoin = 0; - -void updateScreen(String alert = "") { - M5.Display.fillScreen(BLACK); - M5.Display.setCursor(5, 10); - M5.Display.setTextSize(1.5); // StickC จอเล็ก - M5.Display.printf("User: %s\n", currentUsername.c_str()); - M5.Display.printf("Auth: %s\n", authStatus.c_str()); - M5.Display.printf("Coin: %d\n", ccoin); - - if(alert != "") { - M5.Display.setTextColor(YELLOW); - M5.Display.setCursor(5, 80); - M5.Display.print(alert); - M5.Display.setTextColor(WHITE); - } -} - -void setup() { - auto cfg = M5.config(); - M5.begin(cfg); - M5.Display.setRotation(3); - M5.Display.setTextSize(2); - M5.Display.print("Connecting..."); - - // Fixed IP for StickC using ProjectConfig.h IPAddress constants - IPAddress subnet(255, 255, 255, 0); - WiFi.config(IP_STICKC, IP_CORE2, subnet); - WiFi.begin(WIFI_SSID, WIFI_PASS); - - while (WiFi.status() != WL_CONNECTED) delay(500); - updateScreen("Ready"); - - // 1. รับ Identity จาก Station 1 - server.on("/update_identity", HTTP_POST, [](AsyncWebServerRequest *request){}, NULL, - [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { - StaticJsonDocument<200> doc; deserializeJson(doc, data); - currentUsername = doc["username"].as(); - authStatus = "X"; ccoin = 0; - M5.Speaker.tone(1000, 200); - updateScreen("Registered!"); - request->send(200, "application/json", "{\"status\":\"ok\"}"); - }); - - // 2. Auth Process จาก Station 2 - server.on("/auth_start", HTTP_POST, [](AsyncWebServerRequest *request){ - updateScreen("AUTH...\nIn Progress"); - request->send(200); - }); - server.on("/auth_success", HTTP_POST, [](AsyncWebServerRequest *request){ - authStatus = "/"; - M5.Speaker.tone(2000, 200); - updateScreen("Auth Success!"); - request->send(200); - }); - - // 3. Add Coin จาก Station 3 - server.on("/add_coin", HTTP_POST, [](AsyncWebServerRequest *request){}, NULL, - [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { - StaticJsonDocument<200> doc; deserializeJson(doc, data); - int amount = doc["amount"]; - ccoin += amount; - M5.Speaker.tone(3000, 100); M5.Speaker.tone(4000, 100); - updateScreen("Got " + String(amount) + " Coins"); - request->send(200); - }); - - // 4. Spend Coin จาก Station 4 - server.on("/spend_coin", HTTP_POST, [](AsyncWebServerRequest *request){}, NULL, - [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { - StaticJsonDocument<200> doc; deserializeJson(doc, data); - int cost = doc["cost"]; - String item = doc["item"].as(); - - if(ccoin >= cost) { - ccoin -= cost; - updateScreen("Paid for\n" + item); - request->send(200); // OK - } else { - M5.Speaker.tone(200, 500); - updateScreen("Not Enough\nCoin!"); - request->send(400); // Fail - } - }); - - // 5. Reset - server.on("/reset", HTTP_POST, [](AsyncWebServerRequest *request){ - currentUsername = "Guest"; authStatus = "X"; ccoin = 0; - updateScreen("Reset Done"); - request->send(200); - }); - - server.begin(); -} - -void loop() { M5.update(); } \ No newline at end of file diff --git a/Station1-StickC/src/main.cpp b/Station1-StickC/src/main.cpp deleted file mode 100644 index 800ffcc..0000000 --- a/Station1-StickC/src/main.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include -#include -#include -#include "config.h" -#include "Participant.h" - -AsyncWebServer server(80); -Participant participant; -String alertMsg = "Ready"; - -void drawUI() { - M5.Lcd.fillScreen(BLACK); - - // Battery Status - int bat = M5.Power.getBatteryLevel(); - M5.Lcd.setTextSize(1); - - // แก้ไข: ใช้ Font มาตรฐานแทน Font ภาษาไทยที่หาไม่เจอ - M5.Lcd.setFont(&fonts::Font2); // Font ขนาดกลาง อ่านง่าย - - M5.Lcd.setCursor(180, 5); - if (bat > 20) M5.Lcd.setTextColor(GREEN, BLACK); - else M5.Lcd.setTextColor(RED, BLACK); - M5.Lcd.printf("%d%%", bat); - - // Info - M5.Lcd.setTextColor(WHITE, BLACK); - // M5.Lcd.setFont(&fonts::lgfxThai_24); // <-- บรรทัดที่มีปัญหา (คอมเมนต์ออก) - M5.Lcd.setFont(&fonts::Font4); // ใช้อันนี้แทน (ตัวใหญ่ หนา ชัดเจน) - - M5.Lcd.setCursor(5, 30); - M5.Lcd.printf("U: %s", participant.Username.c_str()); - - M5.Lcd.setCursor(5, 60); - String st = participant.isAuthenticated ? "OK" : "NO"; - if (participant.isAuthenticated) M5.Lcd.setTextColor(GREEN, BLACK); - else M5.Lcd.setTextColor(RED, BLACK); - M5.Lcd.printf("Auth: %s", st.c_str()); - - M5.Lcd.setCursor(5, 90); - M5.Lcd.setTextColor(YELLOW, BLACK); - M5.Lcd.printf("Coin: %d", participant.CCoin_Balance); - - M5.Lcd.setTextColor(WHITE, BLACK); - M5.Lcd.setCursor(5, 115); - M5.Lcd.setFont(&fonts::Font2); // กลับมาใช้ตัวเล็กสำหรับข้อความยาวๆ - M5.Lcd.printf("%s", alertMsg.c_str()); -} - -void setup() { - M5.begin(); - M5.Lcd.setRotation(1); - - // เชื่อมต่อ WiFi - M5.Lcd.print("Connecting..."); - WiFi.begin(AP_SSID, AP_PASSWORD); - - // รอจนกว่าจะเชื่อมต่อได้ - int retry = 0; - while (WiFi.status() != WL_CONNECTED && retry < 20) { - delay(500); - M5.Lcd.print("."); - retry++; - } - - // ตั้งค่า IP - if (WiFi.status() == WL_CONNECTED) { - WiFi.config(IP_STICKC, IP_STATION1_AP, NETMASK, IP_STATION1_AP); - } - - // Endpoints - server.on(ENDPOINT_HEARTBEAT, HTTP_GET, [](AsyncWebServerRequest *r){ r->send(200); }); - - server.on(ENDPOINT_SET_USER, HTTP_POST, [](AsyncWebServerRequest *r){ - if (r->hasParam("username", true)) { - participant.Username = r->getParam("username", true)->value(); - participant.reset(); - alertMsg = "ID Received"; - M5.Speaker.tone(2000, 100); - drawUI(); - r->send(200); - } else r->send(400); - }); - - // Handle Auth - server.on(ENDPOINT_SET_AUTH, HTTP_POST, [](AsyncWebServerRequest *r){ - participant.isAuthenticated = true; - alertMsg = "Auth Success"; - M5.Speaker.tone(1000, 100); // Beep - drawUI(); - r->send(200); - }); - - // Handle Earn - server.on(ENDPOINT_EARN_COIN, HTTP_POST, [](AsyncWebServerRequest *r){ - if (r->hasParam("amount", true)) { - int amt = r->getParam("amount", true)->value().toInt(); - participant.CCoin_Balance += amt; - alertMsg = "Earned " + String(amt); - M5.Speaker.tone(3000, 100); - drawUI(); - r->send(200); - } else r->send(400); - }); - - // Handle Spend - server.on(ENDPOINT_SPEND_COIN, HTTP_POST, [](AsyncWebServerRequest *r){ - if (r->hasParam("amount", true)) { - int amt = r->getParam("amount", true)->value().toInt(); - if (participant.CCoin_Balance >= amt) { - participant.CCoin_Balance -= amt; - alertMsg = "Spent " + String(amt); - M5.Speaker.tone(4000, 100); - drawUI(); - r->send(200); - } else { - alertMsg = "No Coin!"; - M5.Speaker.tone(500, 500); // Error sound - drawUI(); - r->send(402, "text/plain", "Insufficient funds"); - } - } else r->send(400); - }); - - server.on(ENDPOINT_RESET_GLOBAL, HTTP_POST, [](AsyncWebServerRequest *r){ - participant.reset(); - alertMsg = "Reset"; - drawUI(); - r->send(200); - }); - - server.begin(); - drawUI(); -} - -void loop() { - M5.update(); - static long lastBat = 0; - if (millis() - lastBat > 5000) { - drawUI(); // Refresh battery - lastBat = millis(); - } -} \ No newline at end of file From f1bbfc467ce17096f0b3b1d13f30b90b6755f462 Mon Sep 17 00:00:00 2001 From: Natthawut Laeme <141604850+POTAEPT@users.noreply.github.com> Date: Mon, 8 Dec 2025 20:58:30 +0700 Subject: [PATCH 5/5] Add files via upload --- Atom_Stick.ino | 258 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 Atom_Stick.ino diff --git a/Atom_Stick.ino b/Atom_Stick.ino new file mode 100644 index 0000000..c4e025d --- /dev/null +++ b/Atom_Stick.ino @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "Participant.h" + +AsyncWebServer server(80); +Participant participant; +String alertMsg = "Ready"; + +// [DEBUG] ตัวแปร Flag เพื่อบอกให้ Loop หลักวาดหน้าจอ +volatile bool needUpdateUI = false; + +// --- Data Structure (ต้องเหมือนกับฝั่งส่ง) --- +typedef struct struct_message { + char type[10]; // เช่น "TRIGGER", "AUTH", "USER" + char username[50]; + int status; +} struct_message; + +struct_message incomingData; + +// --- [BLE] ตัวแปร Global --- +BLEServer *pServer = NULL; +BLEAdvertising *pAdvertising = NULL; +bool shouldRestartBLE = false; +String newBLEName = ""; + +// ------------------------------------------------------ +// 📥 ESP-NOW Callback (Logic ใหม่ + Signature ที่ถูกต้อง) +// ------------------------------------------------------ +void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingDataPtr, int len) { + if (len == sizeof(struct_message)) { + memcpy(&incomingData, incomingDataPtr, sizeof(struct_message)); + + Serial.printf("[ESP-NOW] Recv Type: %s | User: %s\n", incomingData.type, incomingData.username); + + // --- Case 1: ยืนยันตัวตนสำเร็จ (จาก Atom Echo) --- + if (strcmp(incomingData.type, "AUTH") == 0) { + participant.isAuthenticated = true; + alertMsg = "Auth by Echo"; // หรือ "Auth Success" ตามชอบ + + M5.Speaker.tone(4000, 200); + needUpdateUI = true; + Serial.println(">> AUTH SUCCESS VIA ESP-NOW <<"); + } + // --- Case 2: ลงทะเบียนชื่อผู้ใช้ (จาก Core2) --- + else if (strcmp(incomingData.type, "USER") == 0) { + // 1. บันทึกชื่อ + participant.reset(); + participant.Username = String(incomingData.username); + + // 2. อัปเดตข้อความ + alertMsg = "ID Received"; + + // 3. สั่งเปลี่ยนชื่อ BLE + newBLEName = participant.Username; + shouldRestartBLE = true; + + // 4. แจ้งเตือน + M5.Speaker.tone(2000, 200); + needUpdateUI = true; + Serial.printf(">> USER REGISTERED: %s <<\n", participant.Username.c_str()); + } + } +} + +// ------------------------------------------------------ +// BLE Functions +// ------------------------------------------------------ +void startBLE(String name) { + if (pAdvertising == NULL) { + BLEDevice::init(name.c_str()); + pServer = BLEDevice::createServer(); + pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->setScanResponse(true); + pAdvertising->setMinPreferred(0x06); + pAdvertising->setMinPreferred(0x12); + } else { + pAdvertising->stop(); + } + + esp_ble_gap_set_device_name(name.c_str()); + + BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); + oAdvertisementData.setFlags(0x06); + oAdvertisementData.setCompleteServices(BLEUUID("1234")); + pAdvertising->setAdvertisementData(oAdvertisementData); + + BLEAdvertisementData oScanResponseData = BLEAdvertisementData(); + oScanResponseData.setName(name.c_str()); + pAdvertising->setScanResponseData(oScanResponseData); + + pAdvertising->start(); + Serial.printf("[BLE] Name Updated to: %s\n", name.c_str()); +} + +// ------------------------------------------------------ +// 🎨 UI Function (แบบเดิม: พื้นหลังดำ) +// ------------------------------------------------------ +void drawUI() { + M5.Lcd.fillScreen(BLACK); // กลับมาใช้สีดำ + + // Battery Status + int bat = M5.Power.getBatteryLevel(); + M5.Lcd.setTextSize(1); + M5.Lcd.setFont(&fonts::Font2); + + M5.Lcd.setCursor(180, 5); + if (bat > 20) M5.Lcd.setTextColor(GREEN, BLACK); + else M5.Lcd.setTextColor(RED, BLACK); + M5.Lcd.printf("%d%%", bat); + + // Info: Username + M5.Lcd.setTextColor(WHITE, BLACK); + M5.Lcd.setFont(&fonts::Font4); + + M5.Lcd.setCursor(5, 30); + // ถ้ายังไม่มีชื่อ ให้ขึ้น - + String displayName = (participant.Username.length() > 0) ? participant.Username : "-"; + M5.Lcd.printf("U: %s", displayName.c_str()); + + // Info: Auth Status + M5.Lcd.setCursor(5, 60); + String st = participant.isAuthenticated ? "YES" : "NO"; + if (participant.isAuthenticated) M5.Lcd.setTextColor(GREEN, BLACK); + else M5.Lcd.setTextColor(RED, BLACK); + M5.Lcd.printf("Auth: %s", st.c_str()); + + // Info: Coin + M5.Lcd.setCursor(5, 90); + M5.Lcd.setTextColor(YELLOW, BLACK); + M5.Lcd.printf("Coin: %d", participant.CCoin_Balance); + + // Info: Alert Message + M5.Lcd.setTextColor(WHITE, BLACK); + M5.Lcd.setCursor(5, 115); + M5.Lcd.setFont(&fonts::Font2); + M5.Lcd.printf("%s", alertMsg.c_str()); +} + +// ------------------------------------------------------ +// SETUP +// ------------------------------------------------------ +void setup() { + auto cfg = M5.config(); + M5.begin(cfg); + M5.Lcd.setRotation(1); // แนวนอนแบบเดิม + + Serial.begin(115200); + Serial.println("\n\n--- StickC System Booting ---"); + + // UI เริ่มต้น (Loading) + M5.Lcd.fillScreen(BLACK); + M5.Lcd.setTextColor(WHITE, BLACK); + M5.Lcd.setFont(&fonts::Font2); + M5.Lcd.setCursor(10, 50); + M5.Lcd.print("Connecting WiFi..."); + + // WiFi Setup + WiFi.mode(WIFI_AP_STA); + WiFi.begin(AP_SSID, AP_PASSWORD); + + int retry = 0; + while (WiFi.status() != WL_CONNECTED && retry < 20) { + delay(500); + retry++; + } + + if (WiFi.status() == WL_CONNECTED) { + WiFi.config(IP_STICKC, IP_STATION1_AP, NETMASK, IP_STATION1_AP); + Serial.println("\nWiFi Connected!"); + Serial.print("IP: "); Serial.println(WiFi.localIP()); + Serial.print("Channel: "); Serial.println(WiFi.channel()); + } else { + Serial.println("\n[WIFI] Connect Failed! (Offline Mode)"); + } + + // ESP-NOW Init + if (esp_now_init() != ESP_OK) { + Serial.println("[ESP-NOW] Init Failed"); + M5.Lcd.println("ESP-NOW Fail"); + } else { + esp_now_register_recv_cb(OnDataRecv); + Serial.println("[ESP-NOW] Ready"); + } + + // --- Web Server Endpoints --- + + // 1. Endpoint สำหรับ Matrix + server.on("/auth_start", HTTP_GET, [](AsyncWebServerRequest *r){ + alertMsg = "AUTH... In Progress"; + needUpdateUI = true; + r->send(200, "text/plain", "ACK"); + }); + + // 2. Endpoint ตั้งค่า User ผ่าน HTTP (Backup) + server.on(ENDPOINT_SET_USER, HTTP_POST, [](AsyncWebServerRequest *r){ + if (r->hasParam("username", true)) { + String val = r->getParam("username", true)->value(); + participant.reset(); + participant.Username = val; + alertMsg = "ID Received"; + + newBLEName = val; + shouldRestartBLE = true; + needUpdateUI = true; + r->send(200); + } else r->send(400); + }); + + // Endpoint Reset + server.on(ENDPOINT_RESET_GLOBAL, HTTP_POST, [](AsyncWebServerRequest *r){ + participant.reset(); + alertMsg = "Ready"; + newBLEName = "GUEST"; + shouldRestartBLE = true; + needUpdateUI = true; + r->send(200); + }); + + server.begin(); + startBLE("GUEST"); + drawUI(); +} + +// ------------------------------------------------------ +// LOOP +// ------------------------------------------------------ +void loop() { + M5.update(); + + if (needUpdateUI) { + drawUI(); + needUpdateUI = false; + } + + if (shouldRestartBLE) { + startBLE(newBLEName); + shouldRestartBLE = false; + } + + // Refresh แบตเตอรี่ทุก 5 วิ + static long lastBat = 0; + if (millis() - lastBat > 5000) { + // อัปเดตเฉพาะแบตเตอรี่ หรือเรียก drawUI() ก็ได้ถ้าไม่ซีเรียสเรื่องกระพริบ + // เพื่อความชัวร์ เรียก drawUI ไปเลยก็ได้ครับสำหรับ StickC + // drawUI(); + lastBat = millis(); + } +} \ No newline at end of file