/* * JeLamp_C3_Slave.ino * Board 2: XIAO ESP32-C3 — I2C slave + NeoPixel (basic version) * * Wiring: * I2C SDA(D4) GPIO6, SCL(D5) GPIO7 (from ESP32-S3 master) * NeoPixel DIN -> D3, 8 LEDs * * SenseCraft class targets: * target=0 -> Paper (bu) -> blue wave * target=1 -> Stone (shitou) -> red pulse * * Companion: JeLamp_S3_Master.ino on ESP32-S3 Sense * Advanced version with web UI: JeLamp_C3_Slave_Web.ino + types.h */ #include #include #include #define NEOPIXEL_PIN D3 #define NUM_PIXELS 8 #define I2C_SLAVE_ADDR 0x08 Adafruit_NeoPixel strip(NUM_PIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); #define MSG_BOX 0x01 #define MSG_CLASS 0x02 #define MSG_POINT 0x03 #define MSG_PERF 0xFF #define TARGET_PAPER 0 #define TARGET_STONE 1 #define RX_BUF_SIZE 32 volatile uint8_t rxBuf[RX_BUF_SIZE]; volatile uint8_t rxLen = 0; volatile bool newData = false; volatile int8_t currentTarget = -1; volatile int16_t currentScore = 0; volatile uint8_t currentMsgType = 0; unsigned long lastMsgTime = 0; #define IDLE_TIMEOUT 2000 void onReceive(int numBytes) { rxLen = 0; while (Wire.available() && rxLen < RX_BUF_SIZE) { rxBuf[rxLen++] = Wire.read(); } if (rxLen < 1) return; currentMsgType = rxBuf[0]; switch (currentMsgType) { case MSG_BOX: if (rxLen >= 12) { currentScore = (int16_t)((rxBuf[9] << 8) | rxBuf[10]); currentTarget = (int8_t)rxBuf[11]; newData = true; lastMsgTime = millis(); } break; case MSG_CLASS: if (rxLen >= 4) { currentScore = (int16_t)((rxBuf[1] << 8) | rxBuf[2]); currentTarget = (int8_t)rxBuf[3]; newData = true; lastMsgTime = millis(); } break; case MSG_POINT: if (rxLen >= 10) { currentScore = (int16_t)((rxBuf[7] << 8) | rxBuf[8]); currentTarget = (int8_t)rxBuf[9]; newData = true; lastMsgTime = millis(); } break; default: break; } } void effectIdle() { static uint8_t val = 0; static int8_t dir = 1; val += dir * 1; if (val >= 60) dir = -1; if (val <= 3) dir = 1; for (int i = 0; i < NUM_PIXELS; i++) { strip.setPixelColor(i, strip.Color(val, val, val / 2)); } strip.show(); } void effectPaper(int16_t score) { static float phase = 0; float brightness = constrain(score, 30, 100) / 100.0f; for (int i = 0; i < NUM_PIXELS; i++) { float wave = (sin(phase + i * 0.8f) + 1.0f) / 2.0f; wave = wave * 0.7f + 0.3f; uint8_t r = (uint8_t)(0 * wave * brightness); uint8_t g = (uint8_t)(120 * wave * brightness); uint8_t b = (uint8_t)(255 * wave * brightness); strip.setPixelColor(i, strip.Color(r, g, b)); } strip.show(); phase += 0.15f; } void effectStone(int16_t score) { static uint8_t pulseVal = 0; static int8_t pulseDir = 5; static uint8_t flashCount = 0; static bool inFlash = true; float brightness = constrain(score, 30, 100) / 100.0f; if (inFlash) { pulseVal += pulseDir; if (pulseVal >= 250) { pulseDir = -8; } if (pulseVal <= 10) { pulseDir = 8; flashCount++; if (flashCount >= 3) { inFlash = false; flashCount = 0; } } } else { pulseVal = 200; static unsigned long stableStart = 0; if (stableStart == 0) stableStart = millis(); if (millis() - stableStart > 800) { inFlash = true; stableStart = 0; } } uint8_t r = (uint8_t)(pulseVal * brightness); uint8_t g = (uint8_t)((pulseVal / 6) * brightness); uint8_t b = 0; for (int i = 0; i < NUM_PIXELS; i++) { strip.setPixelColor(i, strip.Color(r, g, b)); } strip.show(); } bool playTransition = false; uint8_t transStep = 0; int8_t transTarget = -1; void startTransition(int8_t target) { playTransition = true; transStep = 0; transTarget = target; } bool effectTransition() { if (!playTransition) return false; uint8_t r, g, b; if (transTarget == TARGET_PAPER) { r = 0; g = 120; b = 255; } else { r = 255; g = 40; b = 0; } strip.clear(); int center1 = 3, center2 = 4; int spread = transStep / 3; for (int i = 0; i < NUM_PIXELS; i++) { int dist1 = abs(i - center1); int dist2 = abs(i - center2); int dist = min(dist1, dist2); if (dist <= spread) { strip.setPixelColor(i, strip.Color(r, g, b)); } } strip.show(); transStep++; if (transStep >= 20) { playTransition = false; return false; } return true; } void setup() { Serial.begin(115200); delay(500); Serial.println("=== Rock Paper Scissors ==="); Serial.println("ESP32-C3 NeoPixel Slave"); strip.begin(); strip.setBrightness(80); strip.clear(); strip.show(); for (int round = 0; round < 2; round++) { for (int i = 0; i < NUM_PIXELS; i++) { strip.clear(); if (round == 0) { strip.setPixelColor(i, strip.Color(0, 100, 255)); } else { strip.setPixelColor(NUM_PIXELS - 1 - i, strip.Color(255, 30, 0)); } strip.show(); delay(60); } } strip.clear(); strip.show(); delay(200); Wire.begin(I2C_SLAVE_ADDR); Wire.onReceive(onReceive); Serial.println("Ready! Waiting for hand gesture..."); Serial.println(" Paper (bu) -> Blue wave"); Serial.println(" Stone (shitou)-> Red pulse"); } int8_t prevTarget = -1; void loop() { if (millis() - lastMsgTime > IDLE_TIMEOUT) { if (currentTarget != -1) { Serial.println(">> No detection, IDLE"); currentTarget = -1; prevTarget = -1; } } if (newData) { newData = false; if (currentTarget == TARGET_PAPER) { Serial.printf("PAPER score=%d%%\n", currentScore); } else if (currentTarget == TARGET_STONE) { Serial.printf("STONE score=%d%%\n", currentScore); } else { Serial.printf("?? target=%d score=%d%%\n", currentTarget, currentScore); } if (currentTarget != prevTarget && currentTarget >= 0) { startTransition(currentTarget); prevTarget = currentTarget; } } if (effectTransition()) { delay(25); return; } if (currentTarget == TARGET_PAPER) { effectPaper(currentScore); delay(30); } else if (currentTarget == TARGET_STONE) { effectStone(currentScore); delay(20); } else { effectIdle(); delay(40); } }