Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@

.DS_Store
42 changes: 42 additions & 0 deletions Global/config/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#ifndef CONFIG_H
#define CONFIG_H

#include <Arduino.h>

// --- SoftAP Configuration (Hosted by Station 1) ---
const char* AP_SSID = "Web3_Showcase_AP";
const char* AP_PASSWORD = NULL; // Open network for easy Captive Portal

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states "Open network for easy Captive Portal" but the AP_PASSWORD is actually set to NULL, not a comment. The comment is misleading as it describes what should happen when using an open network, but doesn't clearly state that this IS an open network configuration. Consider clarifying to "NULL = Open network for easy Captive Portal" to make it explicit.

Suggested change
const char* AP_PASSWORD = NULL; // Open network for easy Captive Portal
const char* AP_PASSWORD = NULL; // NULL = Open network for easy Captive Portal

Copilot uses AI. Check for mistakes.

// --- IP Address Mapping ---
// Gateway (Station 1) must be x.x.x.1
IPAddress IP_STATION1_AP(192, 168, 4, 1); // Core2: Identity & AP
IPAddress IP_STATION2_MON(192, 168, 4, 2); // Core2: Auth Monitor
IPAddress IP_RESET_MON(192, 168, 4, 3); // Core Basic: System Monitor & Reset

IPAddress IP_STICKC(192, 168, 4, 10); // Wearable
IPAddress IP_ATOM_MATRIX(192, 168, 4, 20); // S2/S4 Sensor
IPAddress IP_ATOM_ECHO(192, 168, 4, 30); // Sound
IPAddress IP_PAPER_S3(192, 168, 4, 40); // Earn
IPAddress IP_PAPER_S4(192, 168, 4, 50); // Spend

IPAddress NETMASK(255, 255, 255, 0);

// --- Endpoints ---
#define ENDPOINT_HEARTBEAT "/heartbeat"
#define ENDPOINT_SET_USER "/set_user"
#define ENDPOINT_SET_AUTH "/set_auth"
#define ENDPOINT_RESET_GLOBAL "/reset_global"

// Earn และ Spend
#define ENDPOINT_EARN_COIN "/earn_coin"
#define ENDPOINT_SPEND_COIN "/spend_coin"

// Station 4 Order System
#define ENDPOINT_GET_ORDER "/get_order"
#define ENDPOINT_SEND_ORDER "/send_order"

// Audio Endpoints
#define ENDPOINT_PLAY_AUTH "/play_auth"
#define ENDPOINT_PLAY_TX "/play_tx"

#endif // CONFIG_H
149 changes: 149 additions & 0 deletions Global/core2/core2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#include <M5Unified.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <qrcode.h>
#include <HTTPClient.h>

// 1. กำหนดค่า SoftAP

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent comment formatting. Lines 8, 21, 25, 50, 60, 63, 74-75, 81-82, 97, 111 and others contain Thai language comments. For better code maintainability and international collaboration, comments should use consistent language (preferably English) throughout the codebase.

Copilot uses AI. Check for mistakes.
const char *ssid = "Web3Showcase_AP";

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SSID is defined as "Web3Showcase_AP" (without underscore after "Web3") in this file, but in the config.h files for stations it is defined as "Web3_Showcase_AP" (with underscore). This inconsistency in SSID naming will prevent devices from connecting to the correct access point. All files should use the same SSID value.

Suggested change
const char *ssid = "Web3Showcase_AP";
const char *ssid = "Web3_Showcase_AP";

Copilot uses AI. Check for mistakes.
const char *password = "12345678";

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded password "12345678" is a weak password and poses a security risk. For a production system or public demonstration, this password is easily guessable and should be replaced with a stronger password. Consider using a more complex password or making it configurable.

Suggested change
const char *password = "12345678";
// TODO: For production, make the password configurable and do not hardcode it in source code.
const char *password = "w3Showcase!2024#ap";

Copilot uses AI. Check for mistakes.
const char* STICKC_IP = "192.168.4.2";

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The STICKC_IP is hardcoded as "192.168.4.2" which conflicts with the IP address mapping defined in all config.h files where IP_STICKC is defined as 192.168.4.10. This mismatch will cause the Core2 to send requests to the wrong IP address, preventing communication with the StickC device.

Suggested change
const char* STICKC_IP = "192.168.4.2";
const char* STICKC_IP = "192.168.4.10";

Copilot uses AI. Check for mistakes.
const int STICKC_PORT = 88;

IPAddress localIP(192, 168, 4, 1);
IPAddress gateway(192, 168, 4, 1);
IPAddress subnet(255, 255, 255, 0);

AsyncWebServer server(80);
String capturedUsername = "";

// Forward Declarations: ประกาศฟังก์ชันไว้ด้านบนเพื่อแก้ปัญหา Scope
void sendUsernameToStickC(String username);
void displayQRCode(const char* data);

// โค้ด HTML สำหรับ Captive Portal
const char* CAPTIVE_HTML = R"raw(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web3 Student Club</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; }
input[type=text], button { padding: 10px; margin: 5px 0; border-radius: 5px; border: 1px solid #ccc; width: 80%; max-width: 300px; }
button { background-color: #4CAF50; color: white; cursor: pointer; }
</style>
</head>
<body>
<h1>Web3 Student Club</h1>
<form action="/submit" method="POST">
<label for="username">Username</label><br>
<input type="text" id="username" name="username" placeholder="Enter Username" required><br>
<button type="submit">Submit</button>
</form>
</body>
</html>
)raw";

// ฟังก์ชันสำหรับส่ง Username ไปยัง StickC-Plus2
void sendUsernameToStickC(String username) {
if (WiFi.getMode() == WIFI_AP) {
HTTPClient http;
String url = "http://" + String(STICKC_IP) + ":" + String(STICKC_PORT) + "/set_username";

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The endpoint "/set_username" is used here but does not match any endpoint definition in the config files. According to the config files, the user endpoint is defined as ENDPOINT_SET_USER with value "/set_user". This mismatch will cause communication failures between Core2 and StickC.

Suggested change
String url = "http://" + String(STICKC_IP) + ":" + String(STICKC_PORT) + "/set_username";
String url = "http://" + String(STICKC_IP) + ":" + String(STICKC_PORT) + "/set_user";

Copilot uses AI. Check for mistakes.

http.begin(url);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");

String postData = "username=" + username;

int httpResponseCode = http.POST(postData);

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());
}

http.end();
}
}

// ฟังก์ชันสำหรับแสดง QR Code
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("Scan Here", M5.Lcd.width() / 2, 10);

int size = 180;
int x = (M5.Lcd.width() - size) / 2;
int y = 50;

QRCode qrcode;
uint8_t qrcodeData[qrcode_getBufferSize(3)];
qrcode_initText(&qrcode, qrcodeData, 3, 0, data);
M5.Lcd.qrcode(data, x, y, size);
}

void setup() {
auto cfg = M5.config();
M5.begin(cfg);
M5.Lcd.setRotation(1);

// 1. สร้าง WiFi Access Point (SoftAP)
M5.Lcd.print("Setting up SoftAP...");
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(localIP, gateway, subnet);
bool result = WiFi.softAP(ssid, password);

if (result) {
M5.Lcd.printf("\nSoftAP Ready! SSID: %s\n", ssid);
M5.Lcd.printf("IP: %s\n", WiFi.softAPIP().toString().c_str());
} else {
M5.Lcd.println("SoftAP Failed!");
return;
}

// 2. แสดง QR Code สำหรับการเชื่อมต่อ
String qrData = "WIFI:S:" + String(ssid) + ";T:WPA;P:" + String(password) + ";;";
displayQRCode(qrData.c_str());

// 3. ตั้งค่า Web Server สำหรับ Captive Portal

// A. Handle Root Path (หน้า Captive Portal หลัก)
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/html", CAPTIVE_HTML);
});

// B. Handle Submission (รับ Username)
server.on("/submit", HTTP_POST, [](AsyncWebServerRequest *request){
if (request->hasParam("username", true)) {
// ดึง Username จาก Form Data
String capturedUsername = request->getParam("username", true)->value();

M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextDatum(top_left);
M5.Lcd.setFont(&fonts::Font2);
M5.Lcd.printf("Username Received: %s", capturedUsername.c_str());

request->send(200, "text/plain", "Username '" + capturedUsername + "' submitted successfully.");

// 7a. ส่งข้อมูล Username ที่ได้รับมาไปยัง StickC-Plus2
sendUsernameToStickC(capturedUsername);

} else {
request->send(400, "text/plain", "Missing Username");
}
});

server.begin();
M5.Lcd.println("\nHTTP Server Started.");
}

void loop() {
M5.update();
}
196 changes: 196 additions & 0 deletions Global/stick/m5stick-c-plus.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#include <M5Unified.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <esp_gap_ble_api.h> // เพิ่ม Library นี้สำหรับเปลี่ยนชื่อแบบ Fast Mode

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent comment formatting. Lines 8, 31, 37, 48, 52-54, 62, 67 and others contain Thai language comments. For better code maintainability and international collaboration, comments should use consistent language (preferably English) throughout the codebase.

Copilot uses AI. Check for mistakes.

// --- CONFIGURATION ---
const char *SSID_AP = "Web3Showcase_AP";

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SSID is defined as "Web3Showcase_AP" (without underscore after "Web3") in this file, but in the config.h files for stations it is defined as "Web3_Showcase_AP" (with underscore). This inconsistency in SSID naming will prevent devices from connecting to the correct access point. All files should use the same SSID value.

Suggested change
const char *SSID_AP = "Web3Showcase_AP";
const char *SSID_AP = "Web3_Showcase_AP";

Copilot uses AI. Check for mistakes.
const char *PASSWORD_AP = "12345678";

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded password "12345678" is a weak password and poses a security risk. For a production system or public demonstration, this password is easily guessable and should be replaced with a stronger password. Consider using a more complex password or making it configurable.

Suggested change
const char *PASSWORD_AP = "12345678";
// Set WiFi AP password: Change this for production use!
#ifndef PASSWORD_AP_VALUE
#define PASSWORD_AP_VALUE "w3Showcase!2024"
#endif
const char *PASSWORD_AP = PASSWORD_AP_VALUE;

Copilot uses AI. Check for mistakes.
const int LOCAL_PORT = 88;

// --- FIX IP SETTINGS ---
IPAddress localIP(192, 168, 4, 10);
IPAddress gateway(192, 168, 4, 1);
IPAddress subnet(255, 255, 255, 0);

// --- GLOBALS ---
AsyncWebServer stickCServer(LOCAL_PORT);
String currentUsername = "Not Registered";
String authenStatus = "X";

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming inconsistency. The variable is named "authenStatus" but should be "authStatus" or "authenticationStatus" for clarity. The abbreviation "authen" is non-standard; the standard abbreviation is "auth".

Suggested change
String authenStatus = "X";
String authStatus = "X";

Copilot uses AI. Check for mistakes.
int ccoin = 0;
String alertText = "Waiting for Identity...";
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());
}

// 2. ฟังก์ชันอัปเดตหน้าจอ
void updateDisplay() {
M5.Display.fillScreen(BLACK);
M5.Display.setTextDatum(top_left);
M5.Display.setFont(&fonts::Font2);
M5.Display.setTextColor(WHITE);

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);

if (authenStatus == "/") M5.Display.setTextColor(GREEN);
else M5.Display.setTextColor(ORANGE);

M5.Display.setCursor(5, 95);
M5.Display.printf("MSG: %s", alertText.c_str());
}

// 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();
}

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();
request->send(200, "text/plain", "OK");
} else {
request->send(400, "text/plain", "Fail: Missing Username");
}
}

// Handler สำหรับรับ Coin จาก Paper (/add_coin)
void handleCoin(AsyncWebServerRequest *request) {
if (request->hasParam("value", true)) {
String coin = request->getParam("value", true)->value();
Comment on lines +125 to +126

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name is inconsistent: the endpoint expects "amount" but the handler uses "value" to extract the parameter. This will cause the coin amount to not be received correctly. Line 120 should use "amount" instead of "value" to match the POST data being sent from Station3's paper.ino at line 120.

Copilot uses AI. Check for mistakes.

M5.Speaker.tone(1500, 150);

ccoin += coin.toInt();
alertText = "Receive " + coin + " CCoin";
updateDisplay();

request->send(200, "text/plain", "Coin received.");
} else {
request->send(400, "text/plain", "Missing value Parameter.");

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message refers to "value" parameter but the actual parameter name should be "amount" based on how the endpoint is used in Station3/paper/paper.ino. This inconsistency will be confusing when debugging. The error message should say "Missing amount Parameter" to match the expected parameter name.

Copilot uses AI. Check for mistakes.
}
}

void setup() {
auto cfg = M5.config();
M5.begin(cfg);
M5.Display.setRotation(3);
Serial.begin(115200);

// --- 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);

WiFi.mode(WIFI_STA);

WiFi.config(localIP, gateway, subnet);
WiFi.begin(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(".");
}
Serial.println("\n[WiFi] Connected!");

// --- Phase 2: System Ready ---
myMacAddress = WiFi.macAddress();

stickCServer.on("/set_username", HTTP_POST, handleUsername);
stickCServer.on("/set_username", HTTP_GET, handleUsername);
Comment on lines +172 to +173

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The endpoint "/set_username" is used here but does not match any endpoint definition in the config files. According to the config files, the user endpoint is defined as ENDPOINT_SET_USER with value "/set_user". This mismatch will cause communication failures between Core2 and StickC. The endpoint should be updated to match the config definition.

Suggested change
stickCServer.on("/set_username", HTTP_POST, handleUsername);
stickCServer.on("/set_username", HTTP_GET, handleUsername);
stickCServer.on("/set_user", HTTP_POST, handleUsername);
stickCServer.on("/set_user", HTTP_GET, handleUsername);

Copilot uses AI. Check for mistakes.
stickCServer.on("/add_coin", HTTP_POST, handleCoin);

Copilot AI Dec 12, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The endpoint used here is "/add_coin" but in all config.h files, the endpoint for earning coins is defined as ENDPOINT_EARN_COIN with value "/earn_coin". This inconsistency between the endpoint definition and usage will cause HTTP requests to fail. The code should use the endpoint defined in the config files or update the endpoint name to match.

Copilot uses AI. Check for mistakes.
stickCServer.begin();

// เริ่ม BLE ครั้งแรก
startBLE("GUEST-PLAYER");

updateDisplay();
}

void loop() {
// --- โซนทำงานหนัก (ปลอดภัย ไม่รีเซ็ต) ---
if (shouldRestartBLE) {
delay(100);
M5.Speaker.tone(2000, 300);

// เรียกฟังก์ชันเปลี่ยนชื่อแบบใหม่ (ที่ไม่พัง)
startBLE(newBLEName);

shouldRestartBLE = false;
}

M5.update();
}
Loading