Add new-update
This commit is contained in:
parent
4a0eb4fc6b
commit
f453e308fc
894
new-update
Normal file
894
new-update
Normal file
@ -0,0 +1,894 @@
|
||||
Perfect! Let's update the project to **TAKEONE Reflex** and add advanced color management for different game scenarios.
|
||||
|
||||
## ✅ Updated Code with Advanced Color Features
|
||||
|
||||
```cpp
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
#include <BLE2902.h>
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
#include <Preferences.h>
|
||||
|
||||
// ==========================================
|
||||
// ⚙️ USER CONFIGURATION (MODULAR)
|
||||
// ==========================================
|
||||
#define DEFAULT_DEVICE_NAME "TAKEONE_Reflex"
|
||||
#define PROJECT_NAME "TAKEONE Reflex"
|
||||
#define FIRMWARE_VERSION "1.0.0"
|
||||
|
||||
// --- Feature Toggles (Comment to disable) ---
|
||||
#define FEATURE_PIEZO
|
||||
#define FEATURE_BUTTON
|
||||
#define FEATURE_ACCEL
|
||||
|
||||
// --- Pin Assignments (ESP32-C3 Super Mini) ---
|
||||
#define LED_PIN 3
|
||||
|
||||
#ifdef FEATURE_PIEZO
|
||||
#define PIEZO_PIN 4
|
||||
#define PIEZO_THRESHOLD 1500
|
||||
#endif
|
||||
|
||||
#ifdef FEATURE_BUTTON
|
||||
#define BUTTON_PIN 5
|
||||
#define BUTTON_THRESHOLD 100
|
||||
#endif
|
||||
|
||||
#ifdef FEATURE_ACCEL
|
||||
#define ACCEL_X_PIN 6
|
||||
#define ACCEL_Y_PIN 7
|
||||
#define ACCEL_Z_PIN 0
|
||||
#define ACCEL_THRESHOLD 150
|
||||
#endif
|
||||
|
||||
#define NUM_PIXELS 12
|
||||
#define BRIGHTNESS 50
|
||||
#define BREATHE_MAX 40
|
||||
#define MAX_PRESETS 8 // Number of color presets
|
||||
#define MAX_SEQUENCE_STEPS 16 // Max colors in sequence
|
||||
// ==========================================
|
||||
|
||||
// --- BLE UUIDs ---
|
||||
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
|
||||
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
|
||||
#define DEVICE_INFO_SERVICE_UUID "180A"
|
||||
#define MODEL_NUMBER_CHAR_UUID "2A24"
|
||||
#define SERIAL_NUMBER_CHAR_UUID "2A25"
|
||||
#define FIRMWARE_REV_CHAR_UUID "2A26"
|
||||
#define MANUFACTURER_CHAR_UUID "2A29"
|
||||
|
||||
// --- Pod States ---
|
||||
enum PodState {
|
||||
STATE_BOOT,
|
||||
STATE_WAITING,
|
||||
STATE_CONNECTED,
|
||||
STATE_ACTIVE,
|
||||
STATE_SUCCESS,
|
||||
STATE_SEQUENCE // For playing color sequences
|
||||
};
|
||||
|
||||
// --- Color Structure ---
|
||||
struct LEDColor {
|
||||
uint8_t r, g, b;
|
||||
};
|
||||
|
||||
// --- Default Colors ---
|
||||
LEDColor colorWait = {0, 0, 255}; // Blue (breathing)
|
||||
LEDColor colorConn = {0, 255, 255}; // Cyan
|
||||
LEDColor colorActive = {255, 0, 0}; // Red
|
||||
LEDColor colorSuccess = {0, 255, 0}; // Green
|
||||
|
||||
// --- Color Presets (saved in flash) ---
|
||||
LEDColor colorPresets[MAX_PRESETS];
|
||||
|
||||
// --- Color Sequence ---
|
||||
LEDColor colorSequence[MAX_SEQUENCE_STEPS];
|
||||
uint8_t sequenceLength = 0;
|
||||
uint8_t currentStep = 0;
|
||||
unsigned long lastStepTime = 0;
|
||||
unsigned long stepDuration = 500; // ms per step
|
||||
|
||||
// --- Globals ---
|
||||
Preferences preferences;
|
||||
char deviceName[16] = DEFAULT_DEVICE_NAME;
|
||||
|
||||
Adafruit_NeoPixel strip(NUM_PIXELS, LED_PIN, NEO_GRB + NEO_KHZ800);
|
||||
BLEServer *pServer = NULL;
|
||||
BLECharacteristic *pCharacteristic = NULL;
|
||||
bool deviceConnected = false;
|
||||
bool oldDeviceConnected = false;
|
||||
|
||||
PodState currentState = STATE_BOOT;
|
||||
unsigned long stateStartTime = 0;
|
||||
unsigned long lastAnimTime = 0;
|
||||
int animStep = 0;
|
||||
|
||||
unsigned long startTime = 0;
|
||||
unsigned long reactionTime = 0;
|
||||
bool gameActive = false;
|
||||
bool isTapped = false;
|
||||
|
||||
int baseX = 0, baseY = 0, baseZ = 0;
|
||||
bool accelCalibrated = false;
|
||||
|
||||
// --- Helper: Create Color ---
|
||||
uint32_t makeColor(LEDColor c) {
|
||||
return strip.Color(c.r, c.g, c.b);
|
||||
}
|
||||
|
||||
// --- Helper: Set All LEDs ---
|
||||
void fillStrip(uint32_t color) {
|
||||
for (int i = 0; i < NUM_PIXELS; i++) {
|
||||
strip.setPixelColor(i, color);
|
||||
}
|
||||
strip.show();
|
||||
}
|
||||
|
||||
// --- Helper: Parse Color from String "R,G,B" ---
|
||||
LEDColor parseColor(String str) {
|
||||
LEDColor c = {0, 0, 0};
|
||||
int comma1 = str.indexOf(',');
|
||||
int comma2 = str.lastIndexOf(',');
|
||||
if (comma1 > 0 && comma2 > comma1) {
|
||||
c.r = str.substring(0, comma1).toInt();
|
||||
c.g = str.substring(comma1 + 1, comma2).toInt();
|
||||
c.b = str.substring(comma2 + 1).toInt();
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// --- Helper: Format Color to String ---
|
||||
String formatColor(LEDColor c) {
|
||||
return String(c.r) + "," + String(c.g) + "," + String(c.b);
|
||||
}
|
||||
|
||||
// --- Save/Load Functions ---
|
||||
void saveDeviceName(const char* name) {
|
||||
preferences.begin("takeone", false);
|
||||
preferences.putString("deviceName", name);
|
||||
preferences.end();
|
||||
strncpy(deviceName, name, 15);
|
||||
deviceName[15] = '\0';
|
||||
}
|
||||
|
||||
void loadDeviceName() {
|
||||
preferences.begin("takeone", false);
|
||||
String savedName = preferences.getString("deviceName", DEFAULT_DEVICE_NAME);
|
||||
preferences.end();
|
||||
strncpy(deviceName, savedName.c_str(), 15);
|
||||
deviceName[15] = '\0';
|
||||
}
|
||||
|
||||
void saveColorPreset(uint8_t index, LEDColor color) {
|
||||
preferences.begin("takeone", false);
|
||||
String key = "preset" + String(index);
|
||||
String value = formatColor(color);
|
||||
preferences.putString(key.c_str(), value);
|
||||
preferences.end();
|
||||
colorPresets[index] = color;
|
||||
}
|
||||
|
||||
void loadColorPresets() {
|
||||
preferences.begin("takeone", false);
|
||||
for (int i = 0; i < MAX_PRESETS; i++) {
|
||||
String key = "preset" + String(i);
|
||||
String saved = preferences.getString(key.c_str(), "");
|
||||
if (saved.length() > 0) {
|
||||
colorPresets[i] = parseColor(saved);
|
||||
} else {
|
||||
// Default presets
|
||||
switch(i) {
|
||||
case 0: colorPresets[i] = {255, 0, 0}; break; // Red
|
||||
case 1: colorPresets[i] = {0, 255, 0}; break; // Green
|
||||
case 2: colorPresets[i] = {0, 0, 255}; break; // Blue
|
||||
case 3: colorPresets[i] = {255, 255, 0}; break; // Yellow
|
||||
case 4: colorPresets[i] = {255, 0, 255}; break; // Magenta
|
||||
case 5: colorPresets[i] = {0, 255, 255}; break; // Cyan
|
||||
case 6: colorPresets[i] = {255, 128, 0}; break; // Orange
|
||||
case 7: colorPresets[i] = {128, 0, 255}; break; // Purple
|
||||
}
|
||||
}
|
||||
}
|
||||
preferences.end();
|
||||
}
|
||||
|
||||
void saveSequence() {
|
||||
preferences.begin("takeone", false);
|
||||
String seqStr = "";
|
||||
for (int i = 0; i < sequenceLength; i++) {
|
||||
if (i > 0) seqStr += ";";
|
||||
seqStr += formatColor(colorSequence[i]);
|
||||
}
|
||||
preferences.putString("sequence", seqStr);
|
||||
preferences.putUInt("seqDuration", stepDuration);
|
||||
preferences.end();
|
||||
}
|
||||
|
||||
void loadSequence() {
|
||||
preferences.begin("takeone", false);
|
||||
String saved = preferences.getString("sequence", "");
|
||||
stepDuration = preferences.getUInt("seqDuration", 500);
|
||||
|
||||
sequenceLength = 0;
|
||||
if (saved.length() > 0) {
|
||||
int startIndex = 0;
|
||||
while (startIndex < saved.length() && sequenceLength < MAX_SEQUENCE_STEPS) {
|
||||
int endIndex = saved.indexOf(';', startIndex);
|
||||
if (endIndex == -1) endIndex = saved.length();
|
||||
|
||||
String colorStr = saved.substring(startIndex, endIndex);
|
||||
colorSequence[sequenceLength++] = parseColor(colorStr);
|
||||
startIndex = endIndex + 1;
|
||||
}
|
||||
}
|
||||
preferences.end();
|
||||
}
|
||||
|
||||
// --- Play Next Sequence Step ---
|
||||
void playNextSequenceStep() {
|
||||
if (sequenceLength == 0) return;
|
||||
|
||||
fillStrip(makeColor(colorSequence[currentStep]));
|
||||
currentStep = (currentStep + 1) % sequenceLength;
|
||||
lastStepTime = millis();
|
||||
}
|
||||
|
||||
// --- BLE Callbacks ---
|
||||
class MyServerCallbacks: public BLEServerCallbacks {
|
||||
void onConnect(BLEServer* pServer) {
|
||||
deviceConnected = true;
|
||||
currentState = STATE_CONNECTED;
|
||||
stateStartTime = millis();
|
||||
Serial.println("Device Connected");
|
||||
}
|
||||
void onDisconnect(BLEServer* pServer) {
|
||||
deviceConnected = false;
|
||||
currentState = STATE_WAITING;
|
||||
stateStartTime = millis();
|
||||
Serial.println("Device Disconnected");
|
||||
BLEDevice::startAdvertising();
|
||||
}
|
||||
};
|
||||
|
||||
class MyCallbacks: public BLECharacteristicCallbacks {
|
||||
void onWrite(BLECharacteristic *pCharacteristic) {
|
||||
String value = pCharacteristic->getValue();
|
||||
value.trim();
|
||||
|
||||
Serial.print("Received: [");
|
||||
Serial.print(value);
|
||||
Serial.println("]");
|
||||
|
||||
// --- Device Name Commands ---
|
||||
if (value.startsWith("SET_DEVICE_NAME:")) {
|
||||
String newName = value.substring(16);
|
||||
newName.trim();
|
||||
|
||||
if (newName.length() > 0 && newName.length() <= 15) {
|
||||
saveDeviceName(newName.c_str());
|
||||
BLEDevice::deinit(true);
|
||||
delay(100);
|
||||
BLEDevice::init(deviceName);
|
||||
|
||||
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
|
||||
pAdvertising->addServiceUUID(SERVICE_UUID);
|
||||
pAdvertising->setScanResponse(true);
|
||||
BLEDevice::startAdvertising();
|
||||
|
||||
pCharacteristic->setValue("OK:DEVICE_NAME:" + String(deviceName));
|
||||
pCharacteristic->notify();
|
||||
} else {
|
||||
pCharacteristic->setValue("ERROR:NAME_INVALID");
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
}
|
||||
else if (value == "GET_DEVICE_NAME") {
|
||||
pCharacteristic->setValue("DEVICE_NAME:" + String(deviceName));
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
// --- Color Preset Commands ---
|
||||
else if (value.startsWith("SAVE_PRESET:")) {
|
||||
// Format: SAVE_PRESET:n:R,G,B
|
||||
int firstColon = value.indexOf(':');
|
||||
int secondColon = value.indexOf(':', firstColon + 1);
|
||||
if (firstColon > 0 && secondColon > firstColon) {
|
||||
uint8_t index = value.substring(firstColon + 1, secondColon).toInt();
|
||||
if (index < MAX_PRESETS) {
|
||||
LEDColor c = parseColor(value.substring(secondColon + 1));
|
||||
saveColorPreset(index, c);
|
||||
pCharacteristic->setValue("OK:PRESET_SAVED:" + String(index));
|
||||
pCharacteristic->notify();
|
||||
} else {
|
||||
pCharacteristic->setValue("ERROR:PRESET_INDEX");
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (value.startsWith("LOAD_PRESET:")) {
|
||||
// Format: LOAD_PRESET:n
|
||||
int colon = value.indexOf(':');
|
||||
if (colon > 0) {
|
||||
uint8_t index = value.substring(colon + 1).toInt();
|
||||
if (index < MAX_PRESETS) {
|
||||
fillStrip(makeColor(colorPresets[index]));
|
||||
pCharacteristic->setValue("OK:PRESET_LOADED:" + String(index));
|
||||
pCharacteristic->notify();
|
||||
} else {
|
||||
pCharacteristic->setValue("ERROR:PRESET_INDEX");
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (value == "GET_PRESETS") {
|
||||
String response = "PRESETS|";
|
||||
for (int i = 0; i < MAX_PRESETS; i++) {
|
||||
if (i > 0) response += "|";
|
||||
response += String(i) + ":" + formatColor(colorPresets[i]);
|
||||
}
|
||||
pCharacteristic->setValue(response);
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
// --- Color Sequence Commands ---
|
||||
else if (value.startsWith("SET_SEQUENCE:")) {
|
||||
// Format: SET_SEQUENCE:R1,G1,B1;R2,G2,B2;R3,G3,B3
|
||||
String seqData = value.substring(13);
|
||||
sequenceLength = 0;
|
||||
|
||||
int startIndex = 0;
|
||||
while (startIndex < seqData.length() && sequenceLength < MAX_SEQUENCE_STEPS) {
|
||||
int endIndex = seqData.indexOf(';', startIndex);
|
||||
if (endIndex == -1) endIndex = seqData.length();
|
||||
|
||||
String colorStr = seqData.substring(startIndex, endIndex);
|
||||
colorSequence[sequenceLength++] = parseColor(colorStr);
|
||||
startIndex = endIndex + 1;
|
||||
}
|
||||
|
||||
saveSequence();
|
||||
pCharacteristic->setValue("OK:SEQUENCE_SET:" + String(sequenceLength));
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
else if (value.startsWith("SET_STEP_DURATION:")) {
|
||||
// Format: SET_STEP_DURATION:500 (milliseconds)
|
||||
int colon = value.indexOf(':');
|
||||
if (colon > 0) {
|
||||
stepDuration = value.substring(colon + 1).toInt();
|
||||
saveSequence();
|
||||
pCharacteristic->setValue("OK:DURATION:" + String(stepDuration));
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
}
|
||||
else if (value == "PLAY_SEQUENCE") {
|
||||
if (sequenceLength > 0) {
|
||||
currentState = STATE_SEQUENCE;
|
||||
currentStep = 0;
|
||||
playNextSequenceStep();
|
||||
pCharacteristic->setValue("OK:SEQUENCE_PLAYING");
|
||||
pCharacteristic->notify();
|
||||
} else {
|
||||
pCharacteristic->setValue("ERROR:NO_SEQUENCE");
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
}
|
||||
else if (value == "STOP_SEQUENCE") {
|
||||
currentState = STATE_CONNECTED;
|
||||
fillStrip(makeColor(colorConn));
|
||||
pCharacteristic->setValue("OK:SEQUENCE_STOPPED");
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
// --- Standard Color Commands ---
|
||||
else if (value.startsWith("COLOR_WAIT:")) {
|
||||
colorWait = parseColor(value.substring(11));
|
||||
pCharacteristic->setValue("OK:COLOR_WAIT:" + formatColor(colorWait));
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
else if (value.startsWith("COLOR_CONN:")) {
|
||||
colorConn = parseColor(value.substring(11));
|
||||
pCharacteristic->setValue("OK:COLOR_CONN:" + formatColor(colorConn));
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
else if (value.startsWith("COLOR_ACTIVE:")) {
|
||||
colorActive = parseColor(value.substring(13));
|
||||
pCharacteristic->setValue("OK:COLOR_ACTIVE:" + formatColor(colorActive));
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
else if (value.startsWith("COLOR_SUCCESS:")) {
|
||||
colorSuccess = parseColor(value.substring(14));
|
||||
pCharacteristic->setValue("OK:COLOR_SUCCESS:" + formatColor(colorSuccess));
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
else if (value == "GET_COLORS") {
|
||||
String response = "COLORS|WAIT:" + formatColor(colorWait) +
|
||||
"|CONN:" + formatColor(colorConn) +
|
||||
"|ACTIVE:" + formatColor(colorActive) +
|
||||
"|SUCCESS:" + formatColor(colorSuccess);
|
||||
pCharacteristic->setValue(response);
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
else if (value == "RESET_COLORS") {
|
||||
colorWait = {0, 0, 255};
|
||||
colorConn = {0, 255, 255};
|
||||
colorActive = {255, 0, 0};
|
||||
colorSuccess = {0, 255, 0};
|
||||
pCharacteristic->setValue("OK:COLORS_RESET");
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
// --- Game Commands ---
|
||||
else if (value == "START") {
|
||||
currentState = STATE_ACTIVE;
|
||||
fillStrip(makeColor(colorActive));
|
||||
startTime = micros();
|
||||
gameActive = true;
|
||||
isTapped = false;
|
||||
accelCalibrated = false;
|
||||
pCharacteristic->setValue("OK:STARTED");
|
||||
pCharacteristic->notify();
|
||||
Serial.println("Game Started");
|
||||
}
|
||||
else if (value == "RESET") {
|
||||
currentState = STATE_CONNECTED;
|
||||
fillStrip(makeColor(colorConn));
|
||||
gameActive = false;
|
||||
pCharacteristic->setValue("OK:RESET");
|
||||
pCharacteristic->notify();
|
||||
Serial.println("Game Reset");
|
||||
}
|
||||
// --- Device Info ---
|
||||
else if (value == "GET_DEVICE_INFO") {
|
||||
String response = "INFO|NAME:" + String(deviceName) +
|
||||
"|MODEL:TAKEONE-Reflex" +
|
||||
"|FW:" + String(FIRMWARE_VERSION) +
|
||||
"|MANUFACTURER:DIY" +
|
||||
"|SENSORS:" +
|
||||
String(defined(FEATURE_PIEZO) ? "P" : "-") +
|
||||
String(defined(FEATURE_BUTTON) ? "B" : "-") +
|
||||
String(defined(FEATURE_ACCEL) ? "A" : "-");
|
||||
pCharacteristic->setValue(response);
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
else if (value == "FACTORY_RESET") {
|
||||
preferences.begin("takeone", false);
|
||||
preferences.clear();
|
||||
preferences.end();
|
||||
strncpy(deviceName, DEFAULT_DEVICE_NAME, 15);
|
||||
loadColorPresets();
|
||||
|
||||
BLEDevice::deinit(true);
|
||||
delay(100);
|
||||
BLEDevice::init(deviceName);
|
||||
|
||||
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
|
||||
pAdvertising->addServiceUUID(SERVICE_UUID);
|
||||
pAdvertising->setScanResponse(true);
|
||||
BLEDevice::startAdvertising();
|
||||
|
||||
pCharacteristic->setValue("OK:FACTORY_RESET");
|
||||
pCharacteristic->notify();
|
||||
Serial.println("Factory reset complete");
|
||||
}
|
||||
else {
|
||||
pCharacteristic->setValue("ERROR:UNKNOWN_COMMAND");
|
||||
pCharacteristic->notify();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// --- Calibrate Accelerometer ---
|
||||
void calibrateAccel() {
|
||||
#ifdef FEATURE_ACCEL
|
||||
if (!accelCalibrated) {
|
||||
baseX = analogRead(ACCEL_X_PIN);
|
||||
baseY = analogRead(ACCEL_Y_PIN);
|
||||
baseZ = analogRead(ACCEL_Z_PIN);
|
||||
accelCalibrated = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// --- Check All Enabled Triggers ---
|
||||
void checkTriggers() {
|
||||
if (!gameActive || !deviceConnected || isTapped) return;
|
||||
|
||||
bool triggered = false;
|
||||
|
||||
#ifdef FEATURE_PIEZO
|
||||
int piezoValue = analogRead(PIEZO_PIN);
|
||||
if (piezoValue > PIEZO_THRESHOLD) {
|
||||
triggered = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef FEATURE_BUTTON
|
||||
#if !defined(FEATURE_PIEZO) || (BUTTON_PIN != PIEZO_PIN)
|
||||
if (digitalRead(BUTTON_PIN) == LOW) {
|
||||
triggered = true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef FEATURE_ACCEL
|
||||
if (accelCalibrated && !triggered) {
|
||||
int curX = analogRead(ACCEL_X_PIN);
|
||||
int curY = analogRead(ACCEL_Y_PIN);
|
||||
int curZ = analogRead(ACCEL_Z_PIN);
|
||||
|
||||
int diffX = abs(curX - baseX);
|
||||
int diffY = abs(curY - baseY);
|
||||
int diffZ = abs(curZ - baseZ);
|
||||
|
||||
if (diffX > ACCEL_THRESHOLD || diffY > ACCEL_THRESHOLD || diffZ > ACCEL_THRESHOLD) {
|
||||
triggered = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (triggered) {
|
||||
isTapped = true;
|
||||
unsigned long endTime = micros();
|
||||
reactionTime = (endTime - startTime) / 1000;
|
||||
|
||||
String result = "RESULT:" + String(reactionTime) + "ms";
|
||||
pCharacteristic->setValue(result);
|
||||
pCharacteristic->notify();
|
||||
|
||||
currentState = STATE_SUCCESS;
|
||||
stateStartTime = millis();
|
||||
fillStrip(makeColor(colorSuccess));
|
||||
|
||||
gameActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Reset Trigger After Settle ---
|
||||
void resetTrigger() {
|
||||
if (isTapped) {
|
||||
bool settled = false;
|
||||
|
||||
#ifdef FEATURE_PIEZO
|
||||
int piezoValue = analogRead(PIEZO_PIN);
|
||||
if (piezoValue < (PIEZO_THRESHOLD / 2)) settled = true;
|
||||
#elif defined(FEATURE_BUTTON)
|
||||
if (digitalRead(BUTTON_PIN) == HIGH) settled = true;
|
||||
#else
|
||||
if (millis() - stateStartTime > 2000) settled = true;
|
||||
#endif
|
||||
|
||||
if (settled) {
|
||||
isTapped = false;
|
||||
accelCalibrated = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Smart Animation Engine ---
|
||||
void handleAnimations() {
|
||||
unsigned long now = millis();
|
||||
|
||||
if (currentState == STATE_BOOT) {
|
||||
if (now - stateStartTime < 2000) {
|
||||
if (now - lastAnimTime > 50) {
|
||||
for (int i = 0; i < NUM_PIXELS; i++) {
|
||||
uint32_t color = strip.ColorHSV(i * 255 / NUM_PIXELS + animStep * 10, 255, BRIGHTNESS);
|
||||
strip.setPixelColor(i, color);
|
||||
}
|
||||
strip.show();
|
||||
animStep++;
|
||||
lastAnimTime = now;
|
||||
}
|
||||
} else {
|
||||
currentState = STATE_WAITING;
|
||||
stateStartTime = now;
|
||||
fillStrip(0);
|
||||
}
|
||||
}
|
||||
|
||||
else if (currentState == STATE_WAITING) {
|
||||
if (now - lastAnimTime > 10) {
|
||||
float breath = sin(now / 800.0);
|
||||
float normalized = (breath + 1.0) / 2.0;
|
||||
float gammaCorrected = pow(normalized, 2.2);
|
||||
int brightness = (int)(gammaCorrected * BREATHE_MAX);
|
||||
|
||||
uint32_t color = strip.Color(colorWait.r * brightness / BREATHE_MAX,
|
||||
colorWait.g * brightness / BREATHE_MAX,
|
||||
colorWait.b * brightness / BREATHE_MAX);
|
||||
fillStrip(color);
|
||||
lastAnimTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
else if (currentState == STATE_CONNECTED) {
|
||||
if (now - lastAnimTime > 500) {
|
||||
fillStrip(makeColor(colorConn));
|
||||
lastAnimTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
else if (currentState == STATE_ACTIVE) {
|
||||
if (now - lastAnimTime > 500) {
|
||||
fillStrip(makeColor(colorActive));
|
||||
lastAnimTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
else if (currentState == STATE_SEQUENCE) {
|
||||
if (now - lastStepTime >= stepDuration) {
|
||||
playNextSequenceStep();
|
||||
}
|
||||
}
|
||||
|
||||
else if (currentState == STATE_SUCCESS) {
|
||||
if (now - stateStartTime > 2000) {
|
||||
currentState = STATE_CONNECTED;
|
||||
fillStrip(makeColor(colorConn));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
while (!Serial);
|
||||
|
||||
loadDeviceName();
|
||||
loadColorPresets();
|
||||
loadSequence();
|
||||
|
||||
Serial.println("================================");
|
||||
Serial.println(" " + String(PROJECT_NAME));
|
||||
Serial.println(" Firmware v" + String(FIRMWARE_VERSION));
|
||||
Serial.println("================================");
|
||||
Serial.print("Device Name: ");
|
||||
Serial.println(deviceName);
|
||||
Serial.print("Piezo: "); Serial.println(defined(FEATURE_PIEZO) ? "ON" : "OFF");
|
||||
Serial.print("Button: "); Serial.println(defined(FEATURE_BUTTON) ? "ON" : "OFF");
|
||||
Serial.print("Accel: "); Serial.println(defined(FEATURE_ACCEL) ? "ON" : "OFF");
|
||||
Serial.println("================================");
|
||||
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
|
||||
#ifdef FEATURE_PIEZO
|
||||
pinMode(PIEZO_PIN, INPUT);
|
||||
#endif
|
||||
|
||||
#ifdef FEATURE_BUTTON
|
||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||
#endif
|
||||
|
||||
#ifdef FEATURE_ACCEL
|
||||
pinMode(ACCEL_X_PIN, INPUT);
|
||||
pinMode(ACCEL_Y_PIN, INPUT);
|
||||
pinMode(ACCEL_Z_PIN, INPUT);
|
||||
#endif
|
||||
|
||||
analogReadResolution(12);
|
||||
|
||||
strip.begin();
|
||||
strip.setBrightness(BRIGHTNESS);
|
||||
strip.show();
|
||||
fillStrip(0);
|
||||
|
||||
currentState = STATE_BOOT;
|
||||
stateStartTime = millis();
|
||||
|
||||
BLEDevice::init(deviceName);
|
||||
pServer = BLEDevice::createServer();
|
||||
pServer->setCallbacks(new MyServerCallbacks());
|
||||
|
||||
BLEService *pService = pServer->createService(SERVICE_UUID);
|
||||
pCharacteristic = pService->createCharacteristic(
|
||||
CHARACTERISTIC_UUID,
|
||||
BLECharacteristic::PROPERTY_READ |
|
||||
BLECharacteristic::PROPERTY_WRITE |
|
||||
BLECharacteristic::PROPERTY_NOTIFY
|
||||
);
|
||||
|
||||
pCharacteristic->setCallbacks(new MyCallbacks());
|
||||
pCharacteristic->addDescriptor(new BLE2902());
|
||||
pService->start();
|
||||
|
||||
BLEService *pDeviceInfoService = pServer->createService(DEVICE_INFO_SERVICE_UUID);
|
||||
|
||||
BLECharacteristic *pModelNumberChar = pDeviceInfoService->createCharacteristic(
|
||||
MODEL_NUMBER_CHAR_UUID, BLECharacteristic::PROPERTY_READ);
|
||||
pModelNumberChar->setValue("TAKEONE-Reflex");
|
||||
|
||||
BLECharacteristic *pSerialNumberChar = pDeviceInfoService->createCharacteristic(
|
||||
SERIAL_NUMBER_CHAR_UUID, BLECharacteristic::PROPERTY_READ);
|
||||
uint8_t baseMac[6];
|
||||
esp_read_mac(baseMac, ESP_MAC_WIFI_STA);
|
||||
char macStr[18];
|
||||
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||
baseMac[0], baseMac[1], baseMac[2], baseMac[3], baseMac[4], baseMac[5]);
|
||||
pSerialNumberChar->setValue(macStr);
|
||||
|
||||
BLECharacteristic *pFirmwareRevChar = pDeviceInfoService->createCharacteristic(
|
||||
FIRMWARE_REV_CHAR_UUID, BLECharacteristic::PROPERTY_READ);
|
||||
pFirmwareRevChar->setValue(FIRMWARE_VERSION);
|
||||
|
||||
BLECharacteristic *pManufacturerChar = pDeviceInfoService->createCharacteristic(
|
||||
MANUFACTURER_CHAR_UUID, BLECharacteristic::PROPERTY_READ);
|
||||
pManufacturerChar->setValue("DIY Reflex Pod");
|
||||
|
||||
pDeviceInfoService->start();
|
||||
|
||||
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
|
||||
pAdvertising->addServiceUUID(SERVICE_UUID);
|
||||
pAdvertising->addServiceUUID(DEVICE_INFO_SERVICE_UUID);
|
||||
pAdvertising->setScanResponse(true);
|
||||
pAdvertising->setMinPreferred(0x06);
|
||||
pAdvertising->setMinPreferred(0x12);
|
||||
BLEDevice::startAdvertising();
|
||||
|
||||
Serial.println("System Ready - Waiting for connection...");
|
||||
Serial.print("Advertising as: ");
|
||||
Serial.println(deviceName);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (!deviceConnected && oldDeviceConnected) {
|
||||
delay(500);
|
||||
pServer->startAdvertising();
|
||||
oldDeviceConnected = deviceConnected;
|
||||
}
|
||||
if (deviceConnected && !oldDeviceConnected) {
|
||||
oldDeviceConnected = deviceConnected;
|
||||
}
|
||||
|
||||
handleAnimations();
|
||||
|
||||
if (gameActive && !accelCalibrated) {
|
||||
calibrateAccel();
|
||||
}
|
||||
|
||||
checkTriggers();
|
||||
resetTrigger();
|
||||
|
||||
delay(10);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 Complete BLE Command Reference
|
||||
|
||||
### **Device Management**
|
||||
| Command | Description | Response |
|
||||
| :--- | :--- | :--- |
|
||||
| `SET_DEVICE_NAME:NewName` | Change device name (max 15 chars) | `OK:DEVICE_NAME:NewName` |
|
||||
| `GET_DEVICE_NAME` | Get current name | `DEVICE_NAME:TAKEONE_Reflex` |
|
||||
| `GET_DEVICE_INFO` | Get full device info | `INFO\|NAME:...\|MODEL:...\|...` |
|
||||
| `FACTORY_RESET` | Reset all settings | `OK:FACTORY_RESET` |
|
||||
|
||||
### **Standard Colors**
|
||||
| Command | Description | Example |
|
||||
| :--- | :--- | :--- |
|
||||
| `COLOR_WAIT:R,G,B` | Waiting/breathing color | `COLOR_WAIT:0,0,255` |
|
||||
| `COLOR_CONN:R,G,B` | Connected state color | `COLOR_CONN:0,255,255` |
|
||||
| `COLOR_ACTIVE:R,G,B` | Game active color | `COLOR_ACTIVE:255,0,0` |
|
||||
| `COLOR_SUCCESS:R,G,B` | Success/triggered color | `COLOR_SUCCESS:0,255,0` |
|
||||
| `GET_COLORS` | Get all colors | `COLORS\|WAIT:...\|CONN:...\|...` |
|
||||
| `RESET_COLORS` | Reset to defaults | `OK:COLORS_RESET` |
|
||||
|
||||
### **Color Presets (NEW!)**
|
||||
Save and recall up to **8 color presets** for different game scenarios:
|
||||
|
||||
| Command | Description | Example |
|
||||
| :--- | :--- | :--- |
|
||||
| `SAVE_PRESET:n:R,G,B` | Save color to preset slot n (0-7) | `SAVE_PRESET:3:255,128,0` |
|
||||
| `LOAD_PRESET:n` | Load and apply preset n | `LOAD_PRESET:3` |
|
||||
| `GET_PRESETS` | Get all saved presets | `PRESETS\|0:255,0,0\|1:0,255,0\|...` |
|
||||
|
||||
**Use Case**:
|
||||
- Preset 0: Red (stop/slap)
|
||||
- Preset 1: Green (go/jump)
|
||||
- Preset 2: Blue (squat)
|
||||
- Preset 3: Yellow (push-up)
|
||||
- etc.
|
||||
|
||||
### **Color Sequences (NEW!)**
|
||||
Create automatic color patterns for reaction drills:
|
||||
|
||||
| Command | Description | Example |
|
||||
| :--- | :--- | :--- |
|
||||
| `SET_SEQUENCE:R1,G1,B1;R2,G2,B2;...` | Set sequence (up to 16 colors) | `SET_SEQUENCE:255,0,0;0,255,0;0,0,255` |
|
||||
| `SET_STEP_DURATION:ms` | Set duration per step | `SET_STEP_DURATION:1000` |
|
||||
| `PLAY_SEQUENCE` | Start playing sequence | `OK:SEQUENCE_PLAYING` |
|
||||
| `STOP_SEQUENCE` | Stop sequence | `OK:SEQUENCE_STOPPED` |
|
||||
|
||||
**Use Case**:
|
||||
- Red → Green → Blue cycle for "follow the light" drills
|
||||
- Random color patterns for cognitive training
|
||||
- Timed sequences for interval training
|
||||
|
||||
### **Game Control**
|
||||
| Command | Description | Response |
|
||||
| :--- | :--- | :--- |
|
||||
| `START` | Start reaction test | `OK:STARTED` |
|
||||
| `RESET` | Stop game | `OK:RESET` |
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Example Training Scenarios
|
||||
|
||||
### **Scenario 1: Color-Coded Actions**
|
||||
```
|
||||
# Setup different colors for different actions
|
||||
SAVE_PRESET:0:255,0,0 # Red = Slap
|
||||
SAVE_PRESET:1:0,255,0 # Green = Jump
|
||||
SAVE_PRESET:2:0,0,255 # Blue = Squat
|
||||
SAVE_PRESET:3:255,255,0 # Yellow = Push-up
|
||||
|
||||
# During training, coach sends:
|
||||
LOAD_PRESET:0 # Athlete sees RED → Slaps pod
|
||||
LOAD_PRESET:1 # Athlete sees GREEN → Jumps
|
||||
LOAD_PRESET:2 # Athlete sees BLUE → Squats
|
||||
```
|
||||
|
||||
### **Scenario 2: Pattern Recognition**
|
||||
```
|
||||
# Create a sequence
|
||||
SET_SEQUENCE:255,0,0;0,255,0;0,0,255;255,255,0
|
||||
SET_STEP_DURATION:800
|
||||
PLAY_SEQUENCE
|
||||
|
||||
# Pod cycles: Red → Green → Blue → Yellow → Red...
|
||||
# Athlete must perform action matching each color
|
||||
```
|
||||
|
||||
### **Scenario 3: Reaction Drill**
|
||||
```
|
||||
# Set custom active color (purple)
|
||||
COLOR_ACTIVE:255,0,255
|
||||
|
||||
# Start game
|
||||
START
|
||||
# Pod turns PURPLE
|
||||
# Athlete reacts
|
||||
# Result: RESULT:245ms
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Default Color Presets
|
||||
|
||||
| Slot | Color | RGB Value | Use Case |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 0 | Red | 255,0,0 | Stop/Slap |
|
||||
| 1 | Green | 0,255,0 | Go/Jump |
|
||||
| 2 | Blue | 0,0,255 | Squat/Hold |
|
||||
| 3 | Yellow | 255,255,0 | Push-up |
|
||||
| 4 | Magenta | 255,0,255 | Burpee |
|
||||
| 5 | Cyan | 0,255,255 | Rest |
|
||||
| 6 | Orange | 255,128,0 | Lunge |
|
||||
| 7 | Purple | 128,0,255 | Plank |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Quick Start Guide
|
||||
|
||||
1. **Connect** to `TAKEONE_Reflex`
|
||||
2. **Enable notifications** on characteristic
|
||||
3. **Test basic function**:
|
||||
```
|
||||
START
|
||||
[Press button]
|
||||
→ RESULT:XXXms
|
||||
```
|
||||
4. **Set custom colors**:
|
||||
```
|
||||
SAVE_PRESET:0:255,0,0
|
||||
SAVE_PRESET:1:0,255,0
|
||||
LOAD_PRESET:0 → Red lights up
|
||||
LOAD_PRESET:1 → Green lights up
|
||||
```
|
||||
5. **Create sequence**:
|
||||
```
|
||||
SET_SEQUENCE:255,0,0;0,255,0;0,0,255
|
||||
SET_STEP_DURATION:1000
|
||||
PLAY_SEQUENCE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This makes **TAKEONE Reflex** a professional-grade training tool with unlimited color combinations for different exercises! 🎨💪
|
||||
Loading…
x
Reference in New Issue
Block a user