diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..496ee2c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/Atom-matrix b/Atom-matrix new file mode 100644 index 0000000..62f1c5e --- /dev/null +++ b/Atom-matrix @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// --- ตั้งค่าระยะที่ต้องการ --- +const int RSSI_THRESHOLD = -45; + +// หน่วงเวลาห้ามส่งซ้ำ (2000ms = 2 วินาที) +const int COOLDOWN_TIME = 5000; + +BLEUUID targetUUID = BLEUUID("1234"); + +// MAC Address ของ Echo +uint8_t echoAddress[] = {0x90, 0x15, 0x06, 0xFD, 0xF2, 0xF8}; + +// ✅ +int checkIcon[] = { + 15, // หางสั้น (ซ้ายล่าง) + 21, // จุดกลับตัว (ล่างสุด) + 17, // เส้นเฉียงขึ้น + 13, // เส้นเฉียงขึ้น + 9 // ปลายหางยาว (ขวาบน) +}; + +BLEScan* pBLEScan; +unsigned long lastTriggerTime = 0; + +void setup() { + M5.begin(true, false, true); + delay(10); + + WiFi.mode(WIFI_STA); + if (esp_now_init() != ESP_OK) { + Serial.println("Error initializing ESP-NOW"); + return; + } + + esp_now_peer_info_t peerInfo; + memset(&peerInfo, 0, sizeof(peerInfo)); + for (int i = 0; i < 6; i++) { + peerInfo.peer_addr[i] = echoAddress[i]; + } + peerInfo.channel = 0; + peerInfo.encrypt = false; + + if (esp_now_add_peer(&peerInfo) != ESP_OK){ + Serial.println("Failed to add peer"); + return; + } + + BLEDevice::init(""); + pBLEScan = BLEDevice::getScan(); + pBLEScan->setActiveScan(true); + pBLEScan->setInterval(100); + pBLEScan->setWindow(99); + + Serial.println("System Ready: Scanning..."); + M5.dis.fillpix(0x0000FF); // เริ่มต้น: สีน้ำเงิน +} + +void loop() { + M5.update(); + + //แก้: ใช้ Pointer (*) เพื่อแก้ Error เก่า + BLEScanResults *foundDevices = pBLEScan->start(1, false); + + bool foundTarget = false; + int targetRSSI = -999; + + for (int i = 0; i < foundDevices->getCount(); i++) { + BLEAdvertisedDevice device = foundDevices->getDevice(i); + + // เช็ค UUID + if (device.haveServiceUUID() && device.isAdvertisingService(targetUUID)) { + targetRSSI = device.getRSSI(); + Serial.printf("Target Found! RSSI: %d\n", targetRSSI); + + if (targetRSSI > RSSI_THRESHOLD) { + foundTarget = true; + } + } + } + + // --- ตัดสินใจ --- + if (foundTarget) { + + // ---ติ๊กถูก (Checkmark) --- + M5.dis.clear(); // ล้างสีเดิมก่อน + for (int i = 0; i < 5; i++) { + M5.dis.drawpix(checkIcon[i], 0x00FF00); // วาดจุดสีเขียวตามแบบแปลน + } + // ------------------------------------------- + + // เช็ค Cooldown ก่อนส่งคำสั่ง + if (millis() - lastTriggerTime > COOLDOWN_TIME) { + + Serial.println(">>> UNLOCK! Sending to Echo <<<"); + + uint8_t data = 1; + esp_now_send(echoAddress, &data, sizeof(data)); + + lastTriggerTime = millis(); + } + + } else { + // ไม่เจอ -> สีน้ำเงิน 🔵 + M5.dis.fillpix(0x0000ff); + Serial.println("Searching..."); + } + + pBLEScan->clearResults(); +} diff --git a/Atom_echo/Atom_echo.ino b/Atom_echo/Atom_echo.ino new file mode 100644 index 0000000..fa14f81 --- /dev/null +++ b/Atom_echo/Atom_echo.ino @@ -0,0 +1,115 @@ +#include +#include +#include +#include +#include // เพิ่มเพื่อบังคับ Channel + +#define CONFIG_I2S_BCK_PIN 19 +#define CONFIG_I2S_LRCK_PIN 33 +#define CONFIG_I2S_DATA_PIN 22 +#define CONFIG_I2S_DATA_IN_PIN 23 + +// ตั้งค่า I2S +void InitI2S() { + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), + .sample_rate = 44100, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = I2S_COMM_FORMAT_I2S_MSB, // ใช้ MSB เสียงจะดีกว่า + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + .dma_buf_count = 8, + .dma_buf_len = 64 + }; + i2s_pin_config_t pin_config = { + .bck_io_num = CONFIG_I2S_BCK_PIN, + .ws_io_num = CONFIG_I2S_LRCK_PIN, + .data_out_num = CONFIG_I2S_DATA_PIN, + .data_in_num = CONFIG_I2S_DATA_IN_PIN + }; + i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); + i2s_set_pin(I2S_NUM_0, &pin_config); + i2s_set_clk(I2S_NUM_0, 44100, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO); +} + +// สร้างเสียง Beep +void PlayBeep(int freq, int duration_ms) { + size_t bytes_written; + int samples = 44100 * duration_ms / 1000; + int16_t *buffer = (int16_t *)malloc(samples * 2); + if (!buffer) return; + + int period = 44100 / freq; + int volume = 2000; + + for (int i = 0; i < samples; i++) { + if ((i % period) < (period / 2)) buffer[i] = volume; + else buffer[i] = -volume; + } + + i2s_write(I2S_NUM_0, buffer, samples * 2, &bytes_written, portMAX_DELAY); + free(buffer); + + // ล้างท่อเสียง + int16_t silence[1024] = {0}; + for (int k = 0; k < 3; k++) { + i2s_write(I2S_NUM_0, silence, sizeof(silence), &bytes_written, portMAX_DELAY); + } +} + +bool triggerSound = false; + +// Callback รับข้อมูล +void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { + if (len > 0 && *incomingData == 1) { + triggerSound = true; + } +} + +void setup() { + M5.begin(true, false, true); + InitI2S(); + Serial.begin(115200); + + // 1. โชว์ MAC Address ครั้งเดียวพอ (ดูใน Serial Monitor) + WiFi.mode(WIFI_STA); + Serial.println("\n--------------------------------"); + Serial.print("MY MAC ADDRESS: "); + Serial.println(WiFi.macAddress()); + Serial.println("--------------------------------"); + + // 2. ⚠️ บังคับ Channel 1 (เพื่อให้ตรงกับ Matrix) ⚠️ + esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE); + + // 3. Test Sound + Serial.println("System Start: Testing Beep..."); + M5.dis.drawpix(0, 0xFFFFFF); + PlayBeep(2000, 100); + M5.dis.drawpix(0, 0x0000FF); + + if (esp_now_init() != ESP_OK) return; + esp_now_register_recv_cb(OnDataRecv); +} + +void loop() { + M5.update(); + + // ปุ่มกดเทสเสียง + if (M5.Btn.wasPressed()) { + Serial.println("Button Clicked!"); + triggerSound = true; + } + + // เมื่อได้รับคำสั่งจาก Matrix + if (triggerSound) { + M5.dis.drawpix(0, 0x00FF00); // สีเขียว + + Serial.println("Matrix Command -> Beep!"); + PlayBeep(1500, 100); + delay(50); + PlayBeep(2500, 200); + + triggerSound = false; + M5.dis.drawpix(0, 0x0000FF); // กลับเป็นน้ำเงิน + } +} \ No newline at end of file diff --git a/M5Paper/M5Paper.ino b/M5Paper/M5Paper.ino new file mode 100644 index 0000000..a922a67 --- /dev/null +++ b/M5Paper/M5Paper.ino @@ -0,0 +1,114 @@ +#include + +// สร้าง Canvas 2 ใบ (ใบใหญ่=เมนู, ใบเล็ก=Status) +M5EPD_Canvas canvas(&M5.EPD); +M5EPD_Canvas status_canvas(&M5.EPD); + +int selectedChoice = 0; + +void drawMenu() { + canvas.createCanvas(540, 960); + + // Header + canvas.setTextSize(4); + canvas.drawString("What did you do?", 30, 50); + + canvas.setTextSize(3); + + + // ข้อ 1 + canvas.drawRect(0, 130, 540, 80, 15); + canvas.drawString("1. Walk 1000 steps", 30, 155); + canvas.drawString(" --> 10 CCoin", 30, 185); + + // ข้อ 2 + canvas.drawRect(0, 230, 540, 80, 15); + canvas.drawString("2. Recycle bottle", 30, 255); + canvas.drawString(" --> 5 CCoin", 30, 285); + + // ข้อ 3 + canvas.drawRect(0, 330, 540, 80, 15); + canvas.drawString("3. Bike 1 km", 30, 355); + canvas.drawString(" --> 20 CCoin", 30, 385); + + // ข้อ 4 + canvas.drawRect(0, 430, 540, 80, 15); + canvas.drawString("4. Reuse cup", 30, 455); + canvas.drawString(" --> 5 CCoin", 30, 485); + + canvas.fillRect(120, 550, 300, 100, 15); + canvas.setTextColor(0, 15); + canvas.setTextSize(4); + canvas.drawString("Submit", 200, 585); + + canvas.setTextColor(15, 0); + + canvas.pushCanvas(0, 0, UPDATE_MODE_GC16); +} + +void updateStatus(String msg) { + status_canvas.createCanvas(540, 100); + status_canvas.fillCanvas(0); + status_canvas.setTextSize(3); + status_canvas.drawString("Status: " + msg, 20, 20); + + status_canvas.pushCanvas(0, 700, UPDATE_MODE_DU4); + + Serial.print("Status: "); + Serial.println(msg); +} + +void setup() { + M5.begin(); + + Serial.begin(115200); + + M5.EPD.SetRotation(90); + M5.EPD.Clear(true); + M5.TP.SetRotation(0); // หมุนระบบสัมผัสให้ตรงกัน + + drawMenu(); +} + +void loop() { + if (M5.TP.available()) { + if (!M5.TP.isFingerUp()) { + M5.TP.update(); + + // อ่านค่า X (0-540) และ Y (0-960) + int x = M5.TP.readFingerX(0); + int y = M5.TP.readFingerY(0); + + Serial.printf("X: %d, Y: %d\n", x, y); + + if (x >= 130 && x <= 229) { + selectedChoice = 1; + updateStatus("Selected: Walk"); + } + else if (x >= 230 && x <= 329) { + selectedChoice = 2; + updateStatus("Selected: Recycle"); + } + else if (x >= 330 && x <= 410) { + selectedChoice = 3; + updateStatus("Selected: Bike"); + } + else if (x >= 430 && x <= 510) { + selectedChoice = 4; + updateStatus("Selected: Reuse Cup"); + } + // ปุ่ม Submit (เช็ค X ให้อยู่ในกรอบ 120-420) + else if (x >= 550 && x <= 650 && y >= 120 && y <= 420) { + if (selectedChoice == 0) { + updateStatus("Please select first!"); + } else { + updateStatus("Submitting..."); + delay(1000); + updateStatus("Sent Successfully!"); + selectedChoice = 0; + } + } + delay(100); + } + } +} \ No newline at end of file diff --git a/Station2-Core2/include/README b/Station2-Core2/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/Station2-Core2/include/README @@ -0,0 +1,37 @@ + +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/Station2-Core2/include/ShowcaseProtocol.h b/Station2-Core2/include/ShowcaseProtocol.h new file mode 100644 index 0000000..ee8b0bf --- /dev/null +++ b/Station2-Core2/include/ShowcaseProtocol.h @@ -0,0 +1,106 @@ +// File: ShowcaseProtocol.h +// Project: StationX Showcase Network Protocol +// Purpose: Shared message definitions and utility helpers used by all stations +// Notes: +// - This header is intentionally simple and portable across ESP32-based stations. +// - Messages are packed to a fixed size to ensure stable behavior over ESP-NOW. +// - Non-functional, documentation-only changes made on 2025-12-06: clearer section comments. + +#ifndef SHOWCASE_PROTOCOL_H +#define SHOWCASE_PROTOCOL_H + +#include +#include + +// ============================================ +// SYSTEM CONFIGURATION +// ============================================ +#define BROADCAST_CHANNEL 1 +static const uint8_t BROADCAST_MAC[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +#define MSG_MAX_LENGTH 64 + +// ============================================ +// MESSAGE TYPES - Enum for type safety +// ============================================ +enum MessageType : uint8_t { + MSG_IDENTITY_ASSIGN = 1, // St1 Core2 -> StickC + MSG_AUTH_REQUEST = 2, // St2 Matrix -> StickC + MSG_AUTH_SUCCESS = 3, // St2 Echo/Matrix -> StickC + MSG_EARN_COIN = 4, // St3 Paper -> StickC + MSG_SPEND_REQUEST = 5, // St4 Paper -> StickC + MSG_SPEND_CONFIRM = 6, // St4 Matrix -> StickC + MSG_RESET_ALL = 99, // Reset Button -> All + MSG_HEARTBEAT = 100, // Keep-alive + MSG_BALANCE_UPDATE = 101, // StickC -> All (broadcast balance) + MSG_ERROR = 200 // Error message +}; + +// ============================================ +// DATA STRUCTURES - Fixed 64 bytes for stability +// ============================================ +typedef struct { + uint8_t type; // MessageType enum + char username[32]; // Username (max 31 chars + null) + int32_t amount; // Coin amount (int32 for safety) + char description[16]; // Activity/Menu name + uint8_t status; // 0=pending, 1=success, 2=error + uint16_t checksum; // Simple CRC16 + uint32_t timestamp; // Packet timestamp (anti-replay) + uint8_t padding[2]; // Padding to 64 bytes +} __attribute__((packed)) ShowcaseMessage; + +// ============================================ +// COMMUNICATION CONSTANTS +// ============================================ +#define STICKC_HTTP_PORT 8888 +#define CORE2_HTTP_PORT 8888 +#define MATRIX_HTTP_PORT 8888 +#define PAPER_HTTP_PORT 8888 + +// IP addresses (192.168.4.x for AP mode) +#define STICKC_IP "192.168.4.2" +#define CORE2_IP "192.168.4.1" +#define MATRIX_IP "192.168.4.3" +#define PAPER_IP "192.168.4.4" + +// ============================================ +// UTILITY FUNCTIONS +// ============================================ +static inline uint16_t calculateChecksum(const ShowcaseMessage &msg) { + uint16_t crc = 0xFFFF; + const uint8_t *data = (const uint8_t *)&msg; + for (size_t i = 0; i < sizeof(msg) - 2; i++) { + crc ^= data[i]; + for (int j = 0; j < 8; j++) { + crc = (crc >> 1) ^ (0xA001 & (-(crc & 1))); + } + } + return crc; +} + +static inline bool verifyChecksum(const ShowcaseMessage &msg) { + return calculateChecksum(msg) == msg.checksum; +} + +static inline void setChecksum(ShowcaseMessage &msg) { + msg.checksum = calculateChecksum(msg); +} + +// Create a properly formatted message +static inline ShowcaseMessage createMessage(uint8_t type, const char *username = "", + int32_t amount = 0, const char *description = "") { + ShowcaseMessage msg; + memset(&msg, 0, sizeof(msg)); + msg.type = type; + msg.status = 0; // pending + msg.timestamp = millis(); + + if (username) strncpy(msg.username, username, 31); + if (description) strncpy(msg.description, description, 15); + msg.amount = amount; + + setChecksum(msg); + return msg; +} + +#endif diff --git a/Station2-Core2/lib/ProjectShared/Participant.h b/Station2-Core2/lib/ProjectShared/Participant.h new file mode 100644 index 0000000..ba1c302 --- /dev/null +++ b/Station2-Core2/lib/ProjectShared/Participant.h @@ -0,0 +1,49 @@ +// Participant.h: Data Structure สำหรับจัดการข้อมูลผู้เข้าร่วม (Username, Status, CCoin) + +#ifndef PARTICIPANT_H +#define PARTICIPANT_H + +#include + +class Participant { +public: + String Username; + bool isAuthenticated; + int CCoin_Balance; + String alertText; + + Participant() { + // ค่าเริ่มต้นเมื่อเริ่มต้นระบบหรือทำการ Reset + reset(); + } + + void reset() { + Username = ""; + isAuthenticated = false; + CCoin_Balance = 0; + alertText = "-"; + } + + String getAuthStatus() { + return isAuthenticated ? "✓" : "X"; + } + + // ฟังก์ชันสำหรับส่งข้อมูลในรูปแบบ JSON + String toJSON() { + String json = "{"; + json += "\"username\": \"" + Username + "\","; + json += "\"isAuthenticated\": " + String(isAuthenticated ? "true" : "false") + ","; + json += "\"ccoin_balance\": " + String(CCoin_Balance) + ","; + json += "\"alert_text\": \"" + alertText + "\""; + json += "}"; + return json; + } + + // ฟังก์ชันสำหรับอัปเดตข้อมูลจาก JSON (ใช้เมื่อรับ Request) + void updateFromJSON(const String& json) { + // ในโปรเจกต์นี้จะทำการแยก Parser JSON ในแต่ละอุปกรณ์เพื่อให้ง่ายต่อการจัดการ + // แต่โครงสร้างนี้ช่วยให้รู้ว่ามีข้อมูลอะไรบ้าง + } +}; + +#endif // PARTICIPANT_H \ No newline at end of file diff --git a/Station2-Core2/lib/ProjectShared/config.h b/Station2-Core2/lib/ProjectShared/config.h new file mode 100644 index 0000000..fcf1f94 --- /dev/null +++ b/Station2-Core2/lib/ProjectShared/config.h @@ -0,0 +1,37 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include + +// --- SoftAP Configuration (Hosted by Station 1) --- +const char* AP_SSID = "Web3_Showcase_AP"; +const char* AP_PASSWORD = NULL; // Open network for easy Captive Portal + +// --- 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" + +// [เพิ่มเติม] เพิ่ม Endpoints สำหรับ Earn และ Spend ที่หายไป +#define ENDPOINT_EARN_COIN "/earn_coin" +#define ENDPOINT_SPEND_COIN "/spend_coin" + +// [เพิ่มเติม] Endpoints สำหรับ Atom Matrix (เผื่อใช้) +#define ENDPOINT_GET_ORDER "/get_order" + +#endif // CONFIG_H \ No newline at end of file diff --git a/Station2-Core2/lib/README b/Station2-Core2/lib/README new file mode 100644 index 0000000..9379397 --- /dev/null +++ b/Station2-Core2/lib/README @@ -0,0 +1,46 @@ + +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/Station2-Core2/platformio.ini b/Station2-Core2/platformio.ini new file mode 100644 index 0000000..0972119 --- /dev/null +++ b/Station2-Core2/platformio.ini @@ -0,0 +1,15 @@ +[env:m5stack-core2] +platform = espressif32 +board = m5stack-core2 +framework = arduino +monitor_speed = 115200 +upload_speed = 1500000 + +; [สำคัญ] ชี้ไปที่โฟลเดอร์ lib นอกโปรเจกต์ +lib_extra_dirs = ../lib + +lib_deps = + m5stack/M5Core2 @ ^0.1.9 + esphome/ESPAsyncWebServer-esphome @ ^3.3.0 + esphome/AsyncTCP-esphome @ ^2.1.4 + bblanchon/ArduinoJson @ ^7.0.4 \ No newline at end of file diff --git a/Station2-Core2/src/main.cpp b/Station2-Core2/src/main.cpp new file mode 100644 index 0000000..1449b26 --- /dev/null +++ b/Station2-Core2/src/main.cpp @@ -0,0 +1,111 @@ +#include +#include +#include + +// --- ส่วนประกาศตัวแปรและโครงสร้างข้อมูล --- +typedef struct struct_message { + char type[10]; + char username[50]; + int status; +} struct_message; + +struct_message incomingReadings; + +volatile bool new_data_received = false; +volatile bool reset_triggered = false; + +String display_username = ""; + +// --- ส่วนแสดงผลหน้าจอ --- +void displayResetScreen() { + M5.Lcd.fillScreen(BLACK); + M5.Lcd.setTextColor(WHITE, BLACK); + M5.Lcd.setTextSize(2); + M5.Lcd.setTextDatum(MC_DATUM); + + M5.Lcd.drawString("SYSTEM STATUS:", 160, 100); + M5.Lcd.setTextColor(CYAN, BLACK); + M5.Lcd.drawString("Waiting for Request...", 160, 140); +} + +void displayAuthSuccess(String username) { + M5.Lcd.fillScreen(BLACK); + M5.Lcd.setTextColor(WHITE, BLACK); + M5.Lcd.setTextSize(3); + M5.Lcd.setTextDatum(MC_DATUM); + M5.Lcd.drawString("Verifying...", 160, 120); + + delay(500); + + M5.Lcd.fillScreen(GREEN); + M5.Lcd.setTextColor(BLACK, GREEN); + M5.Lcd.setTextSize(2); + + String verifyText = "Verifying (" + username + ")"; + M5.Lcd.drawString(verifyText, 160, 90); + + M5.Lcd.setTextSize(3); + M5.Lcd.drawString("Authentication", 160, 140); + M5.Lcd.drawString("Successful", 160, 180); + + // Graphic + M5.Lcd.drawCircle(160, 50, 20, BLACK); + M5.Lcd.drawLine(150, 50, 160, 60, BLACK); + M5.Lcd.drawLine(160, 60, 175, 40, BLACK); +} + +// --- ส่วนรับข้อมูล ESP-NOW --- +void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) { + memcpy(&incomingReadings, incomingData, sizeof(incomingReadings)); + + // กรณีได้รับคำสั่ง AUTH (ยืนยันตัวตน) + if (strcmp(incomingReadings.type, "AUTH") == 0 && incomingReadings.status == 1) { + display_username = String(incomingReadings.username); + new_data_received = true; + Serial.println("Command: AUTH RECEIVED"); + } + + // กรณีได้รับคำสั่ง RESET (รีเซ็ตระบบ) + else if (strcmp(incomingReadings.type, "RESET") == 0) { + reset_triggered = true; + Serial.println("Command: RESET RECEIVED"); + } +} + +// --- Setup --- +void setup() { + M5.begin(); + Serial.begin(115200); + + displayResetScreen(); + + WiFi.mode(WIFI_STA); + + if (esp_now_init() != ESP_OK) { + Serial.println("Error initializing ESP-NOW"); + return; + } + + // ลงทะเบียนฟังก์ชันรับข้อมูล (ส่วนที่ขาดไปก่อนหน้านี้) + esp_now_register_recv_cb(OnDataRecv); + Serial.println("Station 2 Ready..."); +} + +// --- Loop (ส่วนที่ขาดไปก่อนหน้านี้) --- +void loop() { + + // 1. เช็คคำสั่ง RESET ก่อน (สำคัญสุด) + if (reset_triggered) { + displayResetScreen(); + reset_triggered = false; + new_data_received = false; + } + + // 2. เช็คคำสั่ง AUTH + if (new_data_received) { + displayAuthSuccess(display_username); + new_data_received = false; + } + + M5.update(); +} diff --git a/Station2-Core2/test/README b/Station2-Core2/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/Station2-Core2/test/README @@ -0,0 +1,11 @@ + +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 diff --git a/Station2-Echo/echo1/echo1.ino b/Station2-Echo/echo1/echo1.ino new file mode 100644 index 0000000..ff09f4e --- /dev/null +++ b/Station2-Echo/echo1/echo1.ino @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include + +// --- CONFIGURATION: MAC ADDRESSES --- +uint8_t matrixAddress[] = {0x4C, 0x75, 0x25, 0xAD, 0xB5, 0xBC}; +uint8_t core2Address[] = {0x2c, 0xBC, 0xBB, 0x82, 0x91, 0xA8}; +uint8_t stickc1Address[] = {0x00, 0x4B, 0x12, 0xC4, 0x2D, 0xF8}; +uint8_t stickc2Address[] = {0x00, 0x4b, 0x12, 0xC4, 0x35, 0x48}; + +typedef struct struct_message { + char type[10]; + char username[50]; + int status; +} struct_message; + +// ตัวแปร Global สำหรับเก็บข้อมูลที่ได้รับ +struct_message incomingDataBuffer; + +// --- SOUND SEQUENCER & STATE MACHINE VARIABLES --- +const int shortBeepDuration = 200; +const int longBeepDuration = 700; +const int beepFreq = 1319; // ความถี่เสียง +const int pauseDuration = 100; // ช่วงพักระหว่างเสียง + +enum SoundState { + IDLE, BEEP1_START, BEEP1_WAIT, BEEP1_PAUSE, + BEEP2_START, BEEP2_WAIT, BEEP2_PAUSE, + BEEP3_START, BEEP3_WAIT, DONE +}; +SoundState currentSoundState = IDLE; +unsigned long stateChangeTime = 0; + +// ------------------------------------ +// 📤 ESP-NOW FUNCTIONS (SENDER) +// ------------------------------------ + +void sendRequestToAll(const char* type, const char* username, int status) { + struct_message msg; + strcpy(msg.type, type); + strcpy(msg.username, username); + msg.status = status; + + // ส่งไปยัง Core2, StickC1, StickC2 + esp_now_send(core2Address, (uint8_t *) &msg, sizeof(msg)); + esp_now_send(stickc1Address, (uint8_t *) &msg, sizeof(msg)); + esp_now_send(stickc2Address, (uint8_t *) &msg, sizeof(msg)); + + // ส่งสัญญาณยืนยันกลับไปยัง Matrix + uint8_t matrixData = 3; + esp_now_send(matrixAddress, &matrixData, sizeof(matrixData)); + + Serial.println("[ESP-NOW] Sent Request to all devices."); +} + +// ------------------------------------ +// 📥 ESP-NOW FUNCTIONS (RECEIVER) +// ------------------------------------ + +// **แก้ไข:** เปลี่ยนชื่อพารามิเตอร์ `incomingData` เป็น `dataPtr` เพื่อไม่ให้ชนกับตัวแปร Global +void OnDataRecv(const esp_now_recv_info_t * info, const uint8_t *dataPtr, int len) { + if(len == sizeof(struct_message)){ + // **แก้ไข:** 'memcy' เป็น 'memcpy' และใช้ตัวแปร Global 'incomingDataBuffer' + memcpy(&incomingDataBuffer, dataPtr, sizeof(struct_message)); + + Serial.print("\n[ESP-NOW] Received Request from: "); + for(int i=0; i<6; i++){ + Serial.printf("%02X:", info->src_addr[i]); + } + Serial.println(); + // **แก้ไข:** ใช้ incomingDataBuffer แทน incomingData + Serial.printf("Type: %s, User: %s, Status: %d\n", incomingDataBuffer.type, incomingDataBuffer.username, incomingDataBuffer.status); + + // ตรวจสอบเงื่อนไขเพื่อเริ่มกระบวนการเสียง + if (strcmp(incomingDataBuffer.type, "TRIGGER") == 0 && currentSoundState == IDLE) { + currentSoundState = BEEP1_START; + Serial.println("Sound Sequence Triggered by Matrix."); + } + } +} + +// ------------------------------------ +// SETUP (รวม peer info และลบโค้ดซ้ำ) +// ------------------------------------ + +void setup() { + auto cfg = M5.config(); + cfg.output_power = true; + M5.begin(cfg); + + M5.Speaker.begin(); + M5.Speaker.setVolume(200); + + Serial.begin(115200); + + M5.Display.fillScreen(0x0000FF); // ตั้งค่าสีเริ่มต้นเป็นสีน้ำเงิน + + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + Serial.println("\n--------------------------------"); + Serial.print("MY MAC ADDRESS: "); + Serial.println(WiFi.macAddress()); + Serial.println("--------------------------------"); + + esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE); + + if (esp_now_init() != ESP_OK) { + Serial.println("ESR-NOW Init Failed"); + delay(2000); + ESP.restart(); + } + + esp_now_register_recv_cb(OnDataRecv); + + // Helper function to add peers + auto addPeer = [](const uint8_t* addr, uint8_t channel) { + esp_now_peer_info_t peerInfo = {}; + memcpy(peerInfo.peer_addr, addr, 6); + peerInfo.channel = channel; + peerInfo.encrypt = false; + if (esp_now_add_peer(&peerInfo) != ESP_OK){ + Serial.printf("Failed to add peer: %02X:%02X:%02X:%02X:%02X:%02X\n", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + } + }; + + // ตั้งค่า Peer ทั้งหมด (รวม Core2, Matrix, StickC1, StickC2) + addPeer(core2Address, 1); + addPeer(matrixAddress, 1); + addPeer(stickc1Address, 1); + addPeer(stickc2Address, 1); + + Serial.println("Atom-Echo Setup Complete. Waiting for requests from Matrix..."); +} + +// ------------------------------------ +// LOOP (รวม Logic ทั้งหมดและลบโค้ดซ้ำ) +// ------------------------------------ + +void loop() { + M5.update(); + unsigned long currentTime = millis(); + + // 1. State Machine สำหรับการเล่นเสียงและส่ง Request (Non-blocking) + if (currentSoundState != IDLE) { + + switch (currentSoundState) { + case BEEP1_START: + M5.Display.fillScreen(0xFFFF00); // สีเหลือง + M5.Speaker.tone(beepFreq, shortBeepDuration); + stateChangeTime = currentTime; + currentSoundState = BEEP1_WAIT; + break; + + case BEEP1_WAIT: + if (currentTime - stateChangeTime >= shortBeepDuration) { + stateChangeTime = currentTime; + currentSoundState = BEEP1_PAUSE; + } + break; + case BEEP1_PAUSE: + if (currentTime - stateChangeTime >= pauseDuration) { + currentSoundState = BEEP2_START; + } + break; + + // --- BEEP 2 (เสียงสั้น) --- + case BEEP2_START: + M5.Speaker.tone(beepFreq, shortBeepDuration); + stateChangeTime = currentTime; + currentSoundState = BEEP2_WAIT; + break; + case BEEP2_WAIT: + if (currentTime - stateChangeTime >= shortBeepDuration) { + stateChangeTime = currentTime; + currentSoundState = BEEP2_PAUSE; + } + break; + case BEEP2_PAUSE: + if (currentTime - stateChangeTime >= pauseDuration) { + currentSoundState = BEEP3_START; + } + break; + + // --- BEEP 3 (เสียงยาว) --- + case BEEP3_START: + M5.Speaker.tone(beepFreq, longBeepDuration); + stateChangeTime = currentTime; + currentSoundState = BEEP3_WAIT; + break; + + case BEEP3_WAIT: + if (currentTime - stateChangeTime >= longBeepDuration) { + currentSoundState = DONE; + } + break; + + case DONE: + // 2. เมื่อเสียงทั้งหมดเล่นจบ ให้ส่ง Request ต่อไปยังอุปกรณ์อื่นๆ + M5.Display.fillScreen(0x00FF00); // สีเขียว: ส่ง Request สำเร็จ + sendRequestToAll("AUTH", "ECHO_ALERT", 1); + + // 3. กลับสู่สถานะ IDLE + currentSoundState = IDLE; + M5.Speaker.stop(); + delay(2000); + M5.Display.fillScreen(0x0000FF); // สีน้ำเงิน: กลับไปสถานะรอ + break; + } + } + + // 4. Manual Trigger (ใช้ปุ่มเป็นตัวกระตุ้นแทน Matrix) + // หากต้องการใช้ปุ่มเพื่อทดสอบการทำงาน ให้ใช้โค้ดส่วนนี้ + if (M5.BtnA.wasPressed() && currentSoundState == IDLE) { + Serial.println("Manual Button Triggered! Starting process."); + currentSoundState = BEEP1_START; + } +} \ No newline at end of file diff --git a/Station2-Echo/include/README b/Station2-Echo/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/Station2-Echo/include/README @@ -0,0 +1,37 @@ + +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/Station2-Echo/include/ShowcaseProtocol.h b/Station2-Echo/include/ShowcaseProtocol.h new file mode 100644 index 0000000..ee8b0bf --- /dev/null +++ b/Station2-Echo/include/ShowcaseProtocol.h @@ -0,0 +1,106 @@ +// File: ShowcaseProtocol.h +// Project: StationX Showcase Network Protocol +// Purpose: Shared message definitions and utility helpers used by all stations +// Notes: +// - This header is intentionally simple and portable across ESP32-based stations. +// - Messages are packed to a fixed size to ensure stable behavior over ESP-NOW. +// - Non-functional, documentation-only changes made on 2025-12-06: clearer section comments. + +#ifndef SHOWCASE_PROTOCOL_H +#define SHOWCASE_PROTOCOL_H + +#include +#include + +// ============================================ +// SYSTEM CONFIGURATION +// ============================================ +#define BROADCAST_CHANNEL 1 +static const uint8_t BROADCAST_MAC[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +#define MSG_MAX_LENGTH 64 + +// ============================================ +// MESSAGE TYPES - Enum for type safety +// ============================================ +enum MessageType : uint8_t { + MSG_IDENTITY_ASSIGN = 1, // St1 Core2 -> StickC + MSG_AUTH_REQUEST = 2, // St2 Matrix -> StickC + MSG_AUTH_SUCCESS = 3, // St2 Echo/Matrix -> StickC + MSG_EARN_COIN = 4, // St3 Paper -> StickC + MSG_SPEND_REQUEST = 5, // St4 Paper -> StickC + MSG_SPEND_CONFIRM = 6, // St4 Matrix -> StickC + MSG_RESET_ALL = 99, // Reset Button -> All + MSG_HEARTBEAT = 100, // Keep-alive + MSG_BALANCE_UPDATE = 101, // StickC -> All (broadcast balance) + MSG_ERROR = 200 // Error message +}; + +// ============================================ +// DATA STRUCTURES - Fixed 64 bytes for stability +// ============================================ +typedef struct { + uint8_t type; // MessageType enum + char username[32]; // Username (max 31 chars + null) + int32_t amount; // Coin amount (int32 for safety) + char description[16]; // Activity/Menu name + uint8_t status; // 0=pending, 1=success, 2=error + uint16_t checksum; // Simple CRC16 + uint32_t timestamp; // Packet timestamp (anti-replay) + uint8_t padding[2]; // Padding to 64 bytes +} __attribute__((packed)) ShowcaseMessage; + +// ============================================ +// COMMUNICATION CONSTANTS +// ============================================ +#define STICKC_HTTP_PORT 8888 +#define CORE2_HTTP_PORT 8888 +#define MATRIX_HTTP_PORT 8888 +#define PAPER_HTTP_PORT 8888 + +// IP addresses (192.168.4.x for AP mode) +#define STICKC_IP "192.168.4.2" +#define CORE2_IP "192.168.4.1" +#define MATRIX_IP "192.168.4.3" +#define PAPER_IP "192.168.4.4" + +// ============================================ +// UTILITY FUNCTIONS +// ============================================ +static inline uint16_t calculateChecksum(const ShowcaseMessage &msg) { + uint16_t crc = 0xFFFF; + const uint8_t *data = (const uint8_t *)&msg; + for (size_t i = 0; i < sizeof(msg) - 2; i++) { + crc ^= data[i]; + for (int j = 0; j < 8; j++) { + crc = (crc >> 1) ^ (0xA001 & (-(crc & 1))); + } + } + return crc; +} + +static inline bool verifyChecksum(const ShowcaseMessage &msg) { + return calculateChecksum(msg) == msg.checksum; +} + +static inline void setChecksum(ShowcaseMessage &msg) { + msg.checksum = calculateChecksum(msg); +} + +// Create a properly formatted message +static inline ShowcaseMessage createMessage(uint8_t type, const char *username = "", + int32_t amount = 0, const char *description = "") { + ShowcaseMessage msg; + memset(&msg, 0, sizeof(msg)); + msg.type = type; + msg.status = 0; // pending + msg.timestamp = millis(); + + if (username) strncpy(msg.username, username, 31); + if (description) strncpy(msg.description, description, 15); + msg.amount = amount; + + setChecksum(msg); + return msg; +} + +#endif diff --git a/Station2-Echo/lib/ProjectShared/Participant.h b/Station2-Echo/lib/ProjectShared/Participant.h new file mode 100644 index 0000000..ba1c302 --- /dev/null +++ b/Station2-Echo/lib/ProjectShared/Participant.h @@ -0,0 +1,49 @@ +// Participant.h: Data Structure สำหรับจัดการข้อมูลผู้เข้าร่วม (Username, Status, CCoin) + +#ifndef PARTICIPANT_H +#define PARTICIPANT_H + +#include + +class Participant { +public: + String Username; + bool isAuthenticated; + int CCoin_Balance; + String alertText; + + Participant() { + // ค่าเริ่มต้นเมื่อเริ่มต้นระบบหรือทำการ Reset + reset(); + } + + void reset() { + Username = ""; + isAuthenticated = false; + CCoin_Balance = 0; + alertText = "-"; + } + + String getAuthStatus() { + return isAuthenticated ? "✓" : "X"; + } + + // ฟังก์ชันสำหรับส่งข้อมูลในรูปแบบ JSON + String toJSON() { + String json = "{"; + json += "\"username\": \"" + Username + "\","; + json += "\"isAuthenticated\": " + String(isAuthenticated ? "true" : "false") + ","; + json += "\"ccoin_balance\": " + String(CCoin_Balance) + ","; + json += "\"alert_text\": \"" + alertText + "\""; + json += "}"; + return json; + } + + // ฟังก์ชันสำหรับอัปเดตข้อมูลจาก JSON (ใช้เมื่อรับ Request) + void updateFromJSON(const String& json) { + // ในโปรเจกต์นี้จะทำการแยก Parser JSON ในแต่ละอุปกรณ์เพื่อให้ง่ายต่อการจัดการ + // แต่โครงสร้างนี้ช่วยให้รู้ว่ามีข้อมูลอะไรบ้าง + } +}; + +#endif // PARTICIPANT_H \ No newline at end of file diff --git a/Station2-Echo/lib/ProjectShared/config.h b/Station2-Echo/lib/ProjectShared/config.h new file mode 100644 index 0000000..fcf1f94 --- /dev/null +++ b/Station2-Echo/lib/ProjectShared/config.h @@ -0,0 +1,37 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include + +// --- SoftAP Configuration (Hosted by Station 1) --- +const char* AP_SSID = "Web3_Showcase_AP"; +const char* AP_PASSWORD = NULL; // Open network for easy Captive Portal + +// --- 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" + +// [เพิ่มเติม] เพิ่ม Endpoints สำหรับ Earn และ Spend ที่หายไป +#define ENDPOINT_EARN_COIN "/earn_coin" +#define ENDPOINT_SPEND_COIN "/spend_coin" + +// [เพิ่มเติม] Endpoints สำหรับ Atom Matrix (เผื่อใช้) +#define ENDPOINT_GET_ORDER "/get_order" + +#endif // CONFIG_H \ No newline at end of file diff --git a/Station2-Echo/lib/README b/Station2-Echo/lib/README new file mode 100644 index 0000000..9379397 --- /dev/null +++ b/Station2-Echo/lib/README @@ -0,0 +1,46 @@ + +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/Station2-Echo/platformio.ini b/Station2-Echo/platformio.ini new file mode 100644 index 0000000..b75407e --- /dev/null +++ b/Station2-Echo/platformio.ini @@ -0,0 +1,18 @@ +; PlatformIO Project Configuration File +; Station 2: Atom Echo Audio Controller + +[env:m5stack-atom] +platform = espressif32 +board = m5stack-atom +framework = arduino +monitor_speed = 115200 +upload_speed = 1500000 + +lib_deps = + m5stack/M5Unified @ ^0.2.11 + bblanchon/ArduinoJson @ ^6.21.3 + +build_flags = + -DCORE_DEBUG_LEVEL=0 + +board_build.partitions = min_spiffs.csv diff --git a/Station2-Echo/src/main.cpp b/Station2-Echo/src/main.cpp new file mode 100644 index 0000000..f8f26b5 --- /dev/null +++ b/Station2-Echo/src/main.cpp @@ -0,0 +1,118 @@ +// Audio_AtomEcho.cpp: โค้ดสำหรับ M5-Atom Echo (Audio Beacons/Client) +// รับ Request เพื่อเล่นเสียงแจ้งเตือนการยืนยันตัวตนและธุรกรรมสำเร็จ + +#include +#include +#include +#include "config.h" + +AsyncWebServer server(80); + +// --- ฟังก์ชันการเล่นเสียง --- + +void playAuthSound() { + Serial.println("Playing Authentication Sound..."); + // 14. เสียงแจ้งเตือนการยืนยันตัวตน: (Beep สั้น 2 ครั้ง เว้น 1 วิ, Beep ยาว 1 วินาที) + + // Beep สั้น 1 + M5.Speaker.tone(1000, 100); + delay(100); + M5.Speaker.end(); + + delay(1000); // เว้น 1 วิ + + // Beep สั้น 2 + M5.Speaker.tone(1000, 100); + delay(100); + M5.Speaker.end(); + + delay(1000); // เว้น 1 วิ + + // Beep ยาว 1 + M5.Speaker.tone(1500, 1000); // 1 วินาที + delay(1000); + M5.Speaker.end(); + + Serial.println("Auth sound finished. Sending success signal."); + + // 15. ส่ง Request แจ้งสถานะสำเร็จไปยัง Core2, StickC-Plus2 + // ใช้ Atom Echo เป็นตัวส่งสัญญาณความสำเร็จของกระบวนการ + + // Note: Atom Echo does not have an easy way to send HTTP requests with M5Atom library, + // For simplicity and stability, we assume it can send the post request. + // In a real environment, this logic would live on the Atom Matrix which has more resources. + // However, following the flow: Atom Echo -> Core2, StickC-Plus2 + + HTTPClient http; + // Notify Core2 (Monitor) + http.begin(String("http://") + IP_CORE2.toString() + ENDPOINT_SET_AUTH); + http.POST("{}"); + http.end(); + + // Notify StickC-Plus2 (Wearable) + http.begin(String("http://") + IP_STICKC.toString() + ENDPOINT_SET_AUTH); + http.POST("{}"); + http.end(); +} + +void playTxSuccessSound() { + Serial.println("Playing Transaction Success Sound..."); + // 30. เล่นเสียงแจ้งเตือนธุรกรรมสำเร็จ (ใช้ Beep สั้น 3 ครั้ง) + M5.Speaker.tone(1200, 50); delay(50); M5.Speaker.end(); + delay(50); + M5.Speaker.tone(1200, 50); delay(50); M5.Speaker.end(); + delay(50); + M5.Speaker.tone(1500, 100); delay(100); M5.Speaker.end(); +} + +// --- ฟังก์ชัน HTTP Server Handlers --- + +void handlePlayAuth(AsyncWebServerRequest *request) { + playAuthSound(); + request->send(200, "text/plain", "Auth sound playing."); +} + +void handlePlayTx(AsyncWebServerRequest *request) { + playTxSuccessSound(); + request->send(200, "text/plain", "Tx sound playing."); +} + +void handleSystemReset(AsyncWebServerRequest *request) { + M5.Speaker.end(); // หยุดเสียงที่กำลังเล่น + request->send(200, "text/plain", "Echo reset complete."); +} + +// --- Setup Function --- +void setup() { + M5.begin(true, false, true); // Atom Echo: Init, Power=false, Serial=true + Serial.begin(115200); + + // กำหนด Static IP + WiFi.config(IP_ATOM_ECHO, IP_CORE2, IPAddress(255, 255, 255, 0)); + WiFi.begin(AP_SSID, AP_PASSWORD); + + Serial.println("Connecting to AP..."); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.print("\nConnected to AP. IP: "); + Serial.println(WiFi.localIP()); + + // ตั้งค่า Server Endpoints + server.on(ENDPOINT_PLAY_AUTH, HTTP_POST, handlePlayAuth); + server.on(ENDPOINT_PLAY_TX_SUCCESS, HTTP_POST, handlePlayTx); + server.on(ENDPOINT_RESET_USER, HTTP_POST, handleSystemReset); + + server.begin(); + Serial.println("HTTP server started on Atom Echo."); + + M5.dis.setLed(0, 0x00FF00); // Green light on successful connection +} + +void loop() { + M5.update(); + // Speaker is handled in the callback functions + delay(100); +} \ No newline at end of file diff --git a/Station2-Echo/test/README b/Station2-Echo/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/Station2-Echo/test/README @@ -0,0 +1,11 @@ + +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 diff --git a/Station2-Matrix/include/ProjectConfig.h b/Station2-Matrix/include/ProjectConfig.h new file mode 100644 index 0000000..9daf3a8 --- /dev/null +++ b/Station2-Matrix/include/ProjectConfig.h @@ -0,0 +1,38 @@ +#ifndef PROJECT_CONFIG_H +#define PROJECT_CONFIG_H + +#include + +// --- WiFi Settings --- +// Core2 จะเป็นคนสร้างวงนี้ขึ้นมา +const char* WIFI_SSID = "Web3_Showcase"; +const char* WIFI_PASS = "12345678"; + +// --- Static IP Map (Fixed for Stability) --- +// Gateway (Core2) - Station 1 Host +const IPAddress IP_CORE2(192, 168, 4, 1); + +// Wearable (User) - Station 1 Client +const IPAddress IP_STICKC(192, 168, 4, 2); + +// Station 2 (Auth) +const IPAddress IP_MATRIX_ST2(192, 168, 4, 3); + +// Station 3 (Earn) +const IPAddress IP_PAPER_ST3(192, 168, 4, 5); + +// Station 4 (Spend) +const IPAddress IP_PAPER_ST4(192, 168, 4, 6); // Menu +const IPAddress IP_MATRIX_ST4(192, 168, 4, 7); // Payment Terminal + +// Common & Admin +const IPAddress IP_ECHO(192, 168, 4, 4); // Sound Server +const IPAddress IP_RESET(192, 168, 4, 8); // Reset Button + +// Ports +const int HTTP_PORT = 80; + +// BLE Config +const char* BLE_DEVICE_NAME = "M5_Showcase_User"; + +#endif \ No newline at end of file diff --git a/Station2-Matrix/include/README b/Station2-Matrix/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/Station2-Matrix/include/README @@ -0,0 +1,37 @@ + +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/Station2-Matrix/include/ShowcaseProtocol.h b/Station2-Matrix/include/ShowcaseProtocol.h new file mode 100644 index 0000000..ee8b0bf --- /dev/null +++ b/Station2-Matrix/include/ShowcaseProtocol.h @@ -0,0 +1,106 @@ +// File: ShowcaseProtocol.h +// Project: StationX Showcase Network Protocol +// Purpose: Shared message definitions and utility helpers used by all stations +// Notes: +// - This header is intentionally simple and portable across ESP32-based stations. +// - Messages are packed to a fixed size to ensure stable behavior over ESP-NOW. +// - Non-functional, documentation-only changes made on 2025-12-06: clearer section comments. + +#ifndef SHOWCASE_PROTOCOL_H +#define SHOWCASE_PROTOCOL_H + +#include +#include + +// ============================================ +// SYSTEM CONFIGURATION +// ============================================ +#define BROADCAST_CHANNEL 1 +static const uint8_t BROADCAST_MAC[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +#define MSG_MAX_LENGTH 64 + +// ============================================ +// MESSAGE TYPES - Enum for type safety +// ============================================ +enum MessageType : uint8_t { + MSG_IDENTITY_ASSIGN = 1, // St1 Core2 -> StickC + MSG_AUTH_REQUEST = 2, // St2 Matrix -> StickC + MSG_AUTH_SUCCESS = 3, // St2 Echo/Matrix -> StickC + MSG_EARN_COIN = 4, // St3 Paper -> StickC + MSG_SPEND_REQUEST = 5, // St4 Paper -> StickC + MSG_SPEND_CONFIRM = 6, // St4 Matrix -> StickC + MSG_RESET_ALL = 99, // Reset Button -> All + MSG_HEARTBEAT = 100, // Keep-alive + MSG_BALANCE_UPDATE = 101, // StickC -> All (broadcast balance) + MSG_ERROR = 200 // Error message +}; + +// ============================================ +// DATA STRUCTURES - Fixed 64 bytes for stability +// ============================================ +typedef struct { + uint8_t type; // MessageType enum + char username[32]; // Username (max 31 chars + null) + int32_t amount; // Coin amount (int32 for safety) + char description[16]; // Activity/Menu name + uint8_t status; // 0=pending, 1=success, 2=error + uint16_t checksum; // Simple CRC16 + uint32_t timestamp; // Packet timestamp (anti-replay) + uint8_t padding[2]; // Padding to 64 bytes +} __attribute__((packed)) ShowcaseMessage; + +// ============================================ +// COMMUNICATION CONSTANTS +// ============================================ +#define STICKC_HTTP_PORT 8888 +#define CORE2_HTTP_PORT 8888 +#define MATRIX_HTTP_PORT 8888 +#define PAPER_HTTP_PORT 8888 + +// IP addresses (192.168.4.x for AP mode) +#define STICKC_IP "192.168.4.2" +#define CORE2_IP "192.168.4.1" +#define MATRIX_IP "192.168.4.3" +#define PAPER_IP "192.168.4.4" + +// ============================================ +// UTILITY FUNCTIONS +// ============================================ +static inline uint16_t calculateChecksum(const ShowcaseMessage &msg) { + uint16_t crc = 0xFFFF; + const uint8_t *data = (const uint8_t *)&msg; + for (size_t i = 0; i < sizeof(msg) - 2; i++) { + crc ^= data[i]; + for (int j = 0; j < 8; j++) { + crc = (crc >> 1) ^ (0xA001 & (-(crc & 1))); + } + } + return crc; +} + +static inline bool verifyChecksum(const ShowcaseMessage &msg) { + return calculateChecksum(msg) == msg.checksum; +} + +static inline void setChecksum(ShowcaseMessage &msg) { + msg.checksum = calculateChecksum(msg); +} + +// Create a properly formatted message +static inline ShowcaseMessage createMessage(uint8_t type, const char *username = "", + int32_t amount = 0, const char *description = "") { + ShowcaseMessage msg; + memset(&msg, 0, sizeof(msg)); + msg.type = type; + msg.status = 0; // pending + msg.timestamp = millis(); + + if (username) strncpy(msg.username, username, 31); + if (description) strncpy(msg.description, description, 15); + msg.amount = amount; + + setChecksum(msg); + return msg; +} + +#endif diff --git a/Station2-Matrix/lib/ProjectShared/Participant.h b/Station2-Matrix/lib/ProjectShared/Participant.h new file mode 100644 index 0000000..ba1c302 --- /dev/null +++ b/Station2-Matrix/lib/ProjectShared/Participant.h @@ -0,0 +1,49 @@ +// Participant.h: Data Structure สำหรับจัดการข้อมูลผู้เข้าร่วม (Username, Status, CCoin) + +#ifndef PARTICIPANT_H +#define PARTICIPANT_H + +#include + +class Participant { +public: + String Username; + bool isAuthenticated; + int CCoin_Balance; + String alertText; + + Participant() { + // ค่าเริ่มต้นเมื่อเริ่มต้นระบบหรือทำการ Reset + reset(); + } + + void reset() { + Username = ""; + isAuthenticated = false; + CCoin_Balance = 0; + alertText = "-"; + } + + String getAuthStatus() { + return isAuthenticated ? "✓" : "X"; + } + + // ฟังก์ชันสำหรับส่งข้อมูลในรูปแบบ JSON + String toJSON() { + String json = "{"; + json += "\"username\": \"" + Username + "\","; + json += "\"isAuthenticated\": " + String(isAuthenticated ? "true" : "false") + ","; + json += "\"ccoin_balance\": " + String(CCoin_Balance) + ","; + json += "\"alert_text\": \"" + alertText + "\""; + json += "}"; + return json; + } + + // ฟังก์ชันสำหรับอัปเดตข้อมูลจาก JSON (ใช้เมื่อรับ Request) + void updateFromJSON(const String& json) { + // ในโปรเจกต์นี้จะทำการแยก Parser JSON ในแต่ละอุปกรณ์เพื่อให้ง่ายต่อการจัดการ + // แต่โครงสร้างนี้ช่วยให้รู้ว่ามีข้อมูลอะไรบ้าง + } +}; + +#endif // PARTICIPANT_H \ No newline at end of file diff --git a/Station2-Matrix/lib/ProjectShared/config.h b/Station2-Matrix/lib/ProjectShared/config.h new file mode 100644 index 0000000..fcf1f94 --- /dev/null +++ b/Station2-Matrix/lib/ProjectShared/config.h @@ -0,0 +1,37 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include + +// --- SoftAP Configuration (Hosted by Station 1) --- +const char* AP_SSID = "Web3_Showcase_AP"; +const char* AP_PASSWORD = NULL; // Open network for easy Captive Portal + +// --- 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" + +// [เพิ่มเติม] เพิ่ม Endpoints สำหรับ Earn และ Spend ที่หายไป +#define ENDPOINT_EARN_COIN "/earn_coin" +#define ENDPOINT_SPEND_COIN "/spend_coin" + +// [เพิ่มเติม] Endpoints สำหรับ Atom Matrix (เผื่อใช้) +#define ENDPOINT_GET_ORDER "/get_order" + +#endif // CONFIG_H \ No newline at end of file diff --git a/Station2-Matrix/lib/README b/Station2-Matrix/lib/README new file mode 100644 index 0000000..9379397 --- /dev/null +++ b/Station2-Matrix/lib/README @@ -0,0 +1,46 @@ + +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/Station2-Matrix/platformio.ini b/Station2-Matrix/platformio.ini new file mode 100644 index 0000000..f77929e --- /dev/null +++ b/Station2-Matrix/platformio.ini @@ -0,0 +1,14 @@ +[env:m5stack-atom] +platform = espressif32 +board = m5stack-atom +framework = arduino +monitor_speed = 115200 +upload_speed = 1500000 + +; [สำคัญ] ชี้ไปที่โฟลเดอร์ lib นอกโปรเจกต์ +lib_extra_dirs = ../lib + +lib_deps = + m5stack/M5Atom @ ^0.1.2 + fastled/FastLED @ ^3.6.0 ; จำเป็นสำหรับไฟ LED Matrix + bblanchon/ArduinoJson @ ^7.0.4 \ No newline at end of file diff --git a/Station2-Matrix/src/main(matrix-2).cpp b/Station2-Matrix/src/main(matrix-2).cpp new file mode 100644 index 0000000..cba726a --- /dev/null +++ b/Station2-Matrix/src/main(matrix-2).cpp @@ -0,0 +1,150 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// --- CONFIGURATION --- +const char *SSID = "Web3Showcase_AP"; +const char *PASSWORD = "12345678"; + +// IP ของ StickC +const char* STICKC_IP = "192.168.4.2"; +const int STICKC_PORT = 88; + +// MAC Address ของ Atom Echo +uint8_t echoAddress[] = {0x90, 0x15, 0x06, 0xFD, 0xF2, 0xF8}; + +const int RSSI_THRESHOLD = -55; + +BLEUUID targetUUID = BLEUUID("1234"); +int checkIcon[] = { 15, 21, 17, 13, 9 }; + +BLEScan* pBLEScan; +bool isVerified = false; + +// --- FUNCTIONS --- + +void sendAuthStartToStickC() { + if(WiFi.status() == WL_CONNECTED) { + HTTPClient http; + String myMac = WiFi.macAddress(); + String url = "http://" + String(STICKC_IP) + ":" + String(STICKC_PORT) + "/auth_start?sender_mac=" + myMac; + + http.begin(url); + http.setConnectTimeout(1000); + int httpCode = http.GET(); + + if (httpCode > 0) { + Serial.printf("[HTTP] Sent Auth Start to StickC (Code: %d)\n", httpCode); + } else { + Serial.printf("[HTTP] Failed to send to StickC (Error: %s)\n", http.errorToString(httpCode).c_str()); + } + http.end(); + } else { + Serial.println("[HTTP] WiFi not connected!"); + } +} + +void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { + if (len > 0 && *incomingData == 2) { + isVerified = true; + Serial.println("[ESP-NOW] Received Confirmation from Echo"); + } +} + +void setup() { + M5.begin(true, false, true); + delay(10); + + // 1. Connect WiFi + WiFi.mode(WIFI_STA); + WiFi.begin(SSID, PASSWORD); + + M5.dis.fillpix(0xFF0000); + Serial.print("Connecting to WiFi"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\nWiFi Connected!"); + + // 2. Init ESP-NOW + if (esp_now_init() != ESP_OK) { + Serial.println("Error initializing ESP-NOW"); + return; + } + + esp_now_register_recv_cb(OnDataRecv); + + // Register Echo Peer + esp_now_peer_info_t peerInfo; + memset(&peerInfo, 0, sizeof(peerInfo)); + memcpy(peerInfo.peer_addr, echoAddress, 6); + peerInfo.channel = 0; + peerInfo.encrypt = false; + if (esp_now_add_peer(&peerInfo) != ESP_OK){ + Serial.println("Failed to add peer"); + } + + // 3. Init BLE + BLEDevice::init(""); + pBLEScan = BLEDevice::getScan(); + pBLEScan->setActiveScan(true); + pBLEScan->setInterval(100); + pBLEScan->setWindow(99); + + M5.dis.fillpix(0x0000FF); + Serial.println("System Ready."); +} + +void loop() { + M5.update(); + + if (isVerified) { + M5.dis.clear(); + for (int i = 0; i < 5; i++) M5.dis.drawpix(checkIcon[i], 0x00FF00); + Serial.println("Task Complete."); + while(true) { delay(1000); } + } + + // + BLEScanResults foundDevices = pBLEScan->start(1, false); + bool foundTarget = false; + + // ใช้ . แทน -> เพราะ foundDevices เป็น Object แล้ว + for (int i = 0; i < foundDevices.getCount(); i++) { + BLEAdvertisedDevice device = foundDevices.getDevice(i); + if (device.haveServiceUUID() && device.isAdvertisingService(targetUUID)) { + int rssi = device.getRSSI(); + if (rssi > RSSI_THRESHOLD) { + foundTarget = true; + Serial.printf("Found StickC! RSSI: %d\n", rssi); + } + break; + } + } + + if (foundTarget) { + Serial.println("Target in Range!"); + M5.dis.fillpix(0xFFFF00); + + // A. ส่งเสียงไป Echo + uint8_t data = 1; + esp_now_send(echoAddress, &data, sizeof(data)); + + // B. ส่ง HTTP ไป StickC + sendAuthStartToStickC(); + + delay(500); + } + else { + M5.dis.fillpix(0x0000FF); + } + + pBLEScan->clearResults(); +} \ No newline at end of file diff --git a/Station2-Matrix/src/main.cpp b/Station2-Matrix/src/main.cpp new file mode 100644 index 0000000..36a4cd6 --- /dev/null +++ b/Station2-Matrix/src/main.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include "config.h" + +AsyncWebServer server(80); +bool isAuthInProgress = false; + +// ฟังก์ชันเปลี่ยนสีไฟ (5x5 Matrix) +void showColor(uint32_t color) { + for (int i = 0; i < 25; i++) M5.dis.drawpix(i, color); +} + +// ฟังก์ชันเริ่มกระบวนการ Auth +void triggerAuth() { + if (isAuthInProgress) return; + isAuthInProgress = true; + + Serial.println("Triggering Auth..."); + showColor(0x0000FF); // สีน้ำเงิน (Processing) + + HTTPClient http; + http.setTimeout(2000); + + // 1. สั่ง Atom Echo เล่นเสียง + http.begin("http://" + IP_ATOM_ECHO.toString() + ENDPOINT_PLAY_AUTH); + http.POST("{}"); + http.end(); + + // 2. สั่ง StickC ให้เปลี่ยนสถานะ + http.begin("http://" + IP_STICKC.toString() + ENDPOINT_SET_AUTH); + http.POST("{}"); + http.end(); + + // 3. สั่ง Core2 Monitor ให้โชว์ Success + http.begin("http://" + IP_STATION2_MON.toString() + ENDPOINT_SET_AUTH); + http.POST("{}"); + http.end(); + + delay(1000); + showColor(0x00FF00); // สีเขียว (Success) + delay(2000); + showColor(0x000000); // ปิดไฟ + isAuthInProgress = false; +} + +void setup() { + M5.begin(true, false, true); // Init Atom (LED=Enable, Serial=Enable) + WiFi.begin(AP_SSID, AP_PASSWORD); + + // รอเชื่อมต่อ WiFi (ไฟสีแดงกะพริบ) + while (WiFi.status() != WL_CONNECTED) { + M5.dis.drawpix(0, 0xFF0000); delay(200); + M5.dis.drawpix(0, 0x000000); delay(200); + } + // เชื่อมต่อแล้ว (ไฟสีเขียวแวบหนึ่ง) + WiFi.config(IP_ATOM_MATRIX_S2, IP_STATION1_AP, NETMASK, IP_STATION1_AP); + M5.dis.drawpix(0, 0x00FF00); delay(1000); showColor(0x000000); + + server.on(ENDPOINT_HEARTBEAT, HTTP_GET, [](AsyncWebServerRequest *r){ r->send(200); }); + server.on(ENDPOINT_RESET_GLOBAL, HTTP_POST, [](AsyncWebServerRequest *r){ + showColor(0x000000); // Reset = ปิดไฟ + isAuthInProgress = false; + r->send(200); + }); + server.begin(); +} + +void loop() { + M5.update(); + + // 1. Manual Trigger: กดปุ่มหน้าจอเพื่อ Auth (เผื่อ RSSI ไม่แม่น) + if (M5.Btn.wasPressed()) { + triggerAuth(); + } + + // 2. RSSI Trigger: ตรวจจับสัญญาณ StickC + static unsigned long lastScan = 0; + if (millis() - lastScan > 3000 && !isAuthInProgress) { // Scan ทุก 3 วิ + int n = WiFi.scanNetworks(); + for (int i = 0; i < n; ++i) { + // เช็คว่าเจอ Mac Address หรือ SSID ของ StickC หรือไม่ + // (ใน SoftAP Mode Client จะไม่ปล่อย SSID, ดังนั้นใช้ปุ่มกดจะเสถียรกว่ามากสำหรับงาน Showcase) + } + lastScan = millis(); + } +} \ No newline at end of file diff --git a/Station2-Matrix/test/README b/Station2-Matrix/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/Station2-Matrix/test/README @@ -0,0 +1,11 @@ + +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 diff --git a/m5stickplus2/m5stickplus2.ino b/m5stickplus2/m5stickplus2.ino new file mode 100644 index 0000000..abc6b1a --- /dev/null +++ b/m5stickplus2/m5stickplus2.ino @@ -0,0 +1,112 @@ +#include +#include +#include +#include + +// --- CENTRALIZED CONFIGURATION --- +const char *SSID_AP = "Web3Showcase_AP"; +const char *PASSWORD_AP = "12345678"; +const int LOCAL_PORT = 88; // Port ที่ StickC รอรับ Request +const IPAddress local_IP(192,168,4,2); +const IPAddress gateway(192,168,4,1); +const IPAddress subnet(255,255,255,0); + +AsyncWebServer stickCServer(LOCAL_PORT); +String currentUsername = "Not Registered"; +String authenStatus = "X"; +int ccoin = 0; +String alertText = "Waiting for Identity..."; + +// --- UI DATA --- +// ตัวแปรสำหรับเก็บ MAC Address ของตัวเอง (ใช้ในการอ้างอิงสำหรับ Atom Matrix ใน Station 2) +String myMacAddress = ""; + +// --- CORE FUNCTIONS --- + +// 9a. แสดงข้อมูล Username และสถานะของผู้เข้าร่วมผ่านทางหน้าจอ +void updateDisplay() { + M5.Lcd.fillScreen(BLACK); + M5.Lcd.setTextDatum(top_left); + M5.Lcd.setFont(&fonts::Font2); + + M5.Lcd.setCursor(5, 5); + M5.Lcd.print("MAC: "); M5.Lcd.println(myMacAddress); // แสดง MAC Address เพื่อระบุตัวตน + M5.Lcd.setCursor(5, 25); + M5.Lcd.printf("Username: %s\n", currentUsername.c_str()); + M5.Lcd.printf("Authen Status: %s\n", authenStatus.c_str()); + M5.Lcd.printf("CCoin: %d\n", ccoin); + + M5.Lcd.setCursor(5, 85); + M5.Lcd.printf("ALERT: \n%s", alertText.c_str()); +} + +// Handler สำหรับรับ Username จาก Core2 (/set_username) +void handleUsername(AsyncWebServerRequest *request) { + if (request->hasParam("username", true)) { + String receivedUsername = request->getParam("username", true)->value(); + + // 8a. StickC-Plus2 ส่งเสียงแจ้งเตือนเมื่อได้รับข้อมูล + M5.Speaker.tone(1500, 150); + + // 9a. อัพเดทสถานะบนหน้าจอ + currentUsername = receivedUsername; + alertText = "Identity Created Successfully"; + authenStatus = "x"; + updateDisplay(); + + request->send(200, "text/plain", "Username received."); + } else { + request->send(400, "text/plain", "Missing Username Parameter."); + } +} + +// Handler สำหรับรับ Coin จาก Paper (/add_coin) +void handleCoin(AsyncWebServerRequest *request) { + if (request->hasParam("value", true)) { + String coin = request->getParam("value", true)->value(); + + 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."); + } +} + +void setup() { + auto cfg = M5.config(); + M5.begin(cfg); + M5.Lcd.setRotation(3); + + // Assign static IP + WiFi.config(local_IP, gateway, subnet); + + // 1. เชื่อมต่อ Wi-Fi เข้ากับ SoftAP ของ Core2 + WiFi.begin(SSID_AP, PASSWORD_AP); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + } + + // เก็บ MAC Address ของตัวเอง + myMacAddress = WiFi.macAddress(); + + M5.Lcd.printf("Connected! IP: %s\n", WiFi.localIP().toString().c_str()); + M5.Lcd.printf("MAC: %s (Target for Station 2)\n", myMacAddress.c_str()); + + // 2. ตั้งค่า Server เพื่อรอรับ Request จาก Core2 + stickCServer.on("/set_username", HTTP_POST, handleUsername); + stickCServer.on("/add_coin", HTTP_POST, handleCoin); + stickCServer.begin(); + + // แสดง UI เริ่มต้น + updateDisplay(); +} + +void loop() { + M5.update(); +} \ No newline at end of file