| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- #include <BLEDevice.h>
- #include <BLEServer.h>
- #include <BLEUtils.h>
- #include <BLE2902.h>
- // =====================================================
- // ESP32-C3 SuperMini + 原玩具公共正极灯板:BLE 蓝牙控制增强版
- //
- // 接线方式:
- // ESP32 3.3V -> 原灯板 + / 原电池正极
- // ESP32 IO2 -> 220Ω -> L1 控制点 = 绿灯
- // ESP32 IO3 -> 220Ω -> L2 控制点 = 红灯
- // ESP32 IO4 -> 220Ω -> L3 控制点 = 黄灯
- //
- // 注意:
- // 1. 原灯板 - / 原电池负极 第一版先不要接。
- // 2. 公共正极:GPIO LOW = 灯亮,GPIO HIGH = 灯灭。
- // 3. 默认开机模式:init
- // 4. 除 off、traffic 外,其他模式最多运行 5 分钟,然后自动进入 traffic。
- // 5. traffic 最多运行 10 分钟,然后自动 off。
- // =====================================================
- const char* BLE_DEVICE_NAME = "AI-Light";
- #define SERVICE_UUID "b8b7e001-7a6b-4f4f-9a8b-11c0ffee0001"
- #define MODE_CHAR_UUID "b8b7e002-7a6b-4f4f-9a8b-11c0ffee0001"
- // 你的实测:L1=绿灯,L2=红灯,L3=黄灯
- const int GREEN_PIN = 2; // IO2 -> L1 绿灯
- const int RED_PIN = 3; // IO3 -> L2 红灯
- const int YELLOW_PIN = 4; // IO4 -> L3 黄灯
- const int WIFI_LED_PIN = 8; // IO8 -> 板载LED
- const int PWM_FREQ = 5000;
- const int PWM_RESOLUTION = 8;
- // 红灯偏弱,所以红灯单独增强
- const int RED_MAX = 255;
- const int YELLOW_MAX = 220;
- const int GREEN_MAX = 220;
- const unsigned long NORMAL_MODE_TIMEOUT_MS = 5UL * 60UL * 1000UL; // 5 分钟
- const unsigned long TRAFFIC_MODE_TIMEOUT_MS = 10UL * 60UL * 1000UL; // 10 分钟
- String currentMode = "init";
- unsigned long modeStart = 0;
- BLEServer* pServer = nullptr;
- BLECharacteristic* pModeCharacteristic = nullptr;
- bool deviceConnected = false;
- // =====================================================
- // 基础工具函数:公共正极反相输出
- // =====================================================
- void writeLed(int pin, int value) {
- value = constrain(value, 0, 255);
- int pwmValue = 255 - value; // 公共正极反相
- ledcWrite(pin, pwmValue);
- }
- void allOff() {
- writeLed(RED_PIN, 0);
- writeLed(YELLOW_PIN, 0);
- writeLed(GREEN_PIN, 0);
- }
- void setOnly(int red, int yellow, int green) {
- writeLed(RED_PIN, constrain(red, 0, RED_MAX));
- writeLed(YELLOW_PIN, constrain(yellow, 0, YELLOW_MAX));
- writeLed(GREEN_PIN, constrain(green, 0, GREEN_MAX));
- }
- int triWave(unsigned long t, unsigned long period, int maxValue) {
- unsigned long x = t % period;
- if (x < period / 2) {
- return map(x, 0, period / 2, 0, maxValue);
- } else {
- return map(x, period / 2, period, maxValue, 0);
- }
- }
- int fadeInOutBrightness(
- unsigned long t,
- unsigned long fadeIn,
- unsigned long hold,
- unsigned long fadeOut,
- unsigned long offTime,
- int maxValue
- ) {
- unsigned long total = fadeIn + hold + fadeOut + offTime;
- unsigned long x = t % total;
- if (x < fadeIn) {
- return map(x, 0, fadeIn, 0, maxValue);
- }
- x -= fadeIn;
- if (x < hold) {
- return maxValue;
- }
- x -= hold;
- if (x < fadeOut) {
- return map(x, 0, fadeOut, maxValue, 0);
- }
- return 0;
- }
- void fadeToStatic(int targetRed, int targetYellow, int targetGreen, int fadeMs = 80) {
- allOff();
- int steps = 12;
- int delayPerStep = max(1, fadeMs / steps);
- for (int i = 0; i <= steps; i++) {
- float p = (float)i / steps;
- setOnly(targetRed * p, targetYellow * p, targetGreen * p);
- delay(delayPerStep);
- }
- }
- // =====================================================
- // 模式处理
- // =====================================================
- bool isValidMode(String mode) {
- return (
- mode == "red" ||
- mode == "yellow" ||
- mode == "green" ||
- mode == "busy" ||
- mode == "error" ||
- mode == "thinking" ||
- mode == "ai" ||
- mode == "success" ||
- mode == "traffic" ||
- mode == "alarm" ||
- mode == "init" ||
- mode == "idle" ||
- mode == "off"
- );
- }
- void notifyMode() {
- if (pModeCharacteristic) {
- pModeCharacteristic->setValue(currentMode.c_str());
- if (deviceConnected) {
- pModeCharacteristic->notify();
- }
- }
- }
- void setMode(String mode) {
- mode.trim();
- mode.toLowerCase();
- if (!isValidMode(mode)) {
- Serial.print("Unknown mode: ");
- Serial.println(mode);
- return;
- }
- if (mode == "idle") {
- mode = "traffic";
- }
- currentMode = mode;
- modeStart = millis();
- Serial.print("Mode changed to: ");
- Serial.println(currentMode);
- if (mode == "red") {
- fadeToStatic(RED_MAX, 0, 0, 80);
- } else if (mode == "yellow") {
- fadeToStatic(0, YELLOW_MAX, 0, 80);
- } else if (mode == "green") {
- fadeToStatic(0, 0, GREEN_MAX, 80);
- } else if (mode == "success") {
- setOnly(0, 0, GREEN_MAX);
- } else if (mode == "off") {
- allOff();
- }
- notifyMode();
- }
- void autoTimeoutCheck() {
- unsigned long elapsed = millis() - modeStart;
- if (currentMode == "off") {
- return;
- }
- if (currentMode == "traffic") {
- if (elapsed >= TRAFFIC_MODE_TIMEOUT_MS) {
- Serial.println("Traffic timeout -> off");
- setMode("off");
- }
- return;
- }
- if (elapsed >= NORMAL_MODE_TIMEOUT_MS) {
- Serial.println("Normal mode timeout -> traffic");
- setMode("traffic");
- }
- }
- // =====================================================
- // 灯效模式
- // =====================================================
- void updateBusy() {
- unsigned long t = millis() - modeStart;
- int y = fadeInOutBrightness(t, 80, 500, 120, 500, YELLOW_MAX);
- setOnly(0, y, 0);
- }
- void updateError() {
- unsigned long t = millis() - modeStart;
- int r = fadeInOutBrightness(t, 40, 180, 80, 180, RED_MAX);
- setOnly(r, 0, 0);
- }
- // thinking:连贯跑马灯,按实物从上到下:L1绿 -> L2黄 -> L3红
- void updateThinking() {
- unsigned long t = millis() - modeStart;
- const unsigned long period = 1050;
- unsigned long x = t % period;
- int g = 0;
- int y = 0;
- int r = 0;
- if (x < 350) {
- g = map(x, 0, 350, GREEN_MAX, 70);
- y = map(x, 0, 350, 20, YELLOW_MAX);
- r = 0;
- } else if (x < 700) {
- unsigned long p = x - 350;
- g = map(p, 0, 350, 70, 0);
- y = map(p, 0, 350, YELLOW_MAX, 70);
- r = map(p, 0, 350, 20, RED_MAX);
- } else {
- unsigned long p = x - 700;
- g = map(p, 0, 350, 20, GREEN_MAX);
- y = map(p, 0, 350, 70, 0);
- r = map(p, 0, 350, RED_MAX, 70);
- }
- setOnly(r, y, g);
- }
- // ai:柔和版跑马灯,比 thinking 更慢、更柔和、亮度低一点
- void updateAi() {
- unsigned long t = millis() - modeStart;
- const unsigned long period = 1800;
- unsigned long x = t % period;
- unsigned long gx = (x + 0) % period;
- unsigned long yx = (x + period / 3) % period;
- unsigned long rx = (x + 2 * period / 3) % period;
- int g = triWave(gx, period, 150);
- int y = triWave(yx, period, 140);
- int r = triWave(rx, period, 170);
- setOnly(r, y, g);
- }
- void updateSuccess() {
- setOnly(0, 0, GREEN_MAX);
- }
- // alarm:红黄交替警灯,带短渐变
- void updateAlarm() {
- unsigned long t = millis() - modeStart;
- const unsigned long phaseMs = 260;
- int phase = (t / phaseMs) % 2;
- unsigned long inside = t % phaseMs;
- int brightness;
- if (inside < 60) {
- brightness = map(inside, 0, 60, 0, 255);
- } else if (inside < 180) {
- brightness = 255;
- } else {
- brightness = map(inside, 180, phaseMs, 255, 0);
- }
- if (phase == 0) {
- setOnly(brightness, 0, 0);
- } else {
- setOnly(0, min(brightness, YELLOW_MAX), 0);
- }
- }
- // traffic:绿灯变黄前绿闪;黄灯;红灯变绿前红闪
- void updateTraffic() {
- unsigned long t = (millis() - modeStart) % 15000;
- if (t < 5000) {
- setOnly(0, 0, GREEN_MAX);
- }
- else if (t < 6500) {
- unsigned long phase = (t - 5000) % 500;
- int g = 0;
- if (phase < 60) g = map(phase, 0, 60, 0, GREEN_MAX);
- else if (phase < 230) g = GREEN_MAX;
- else if (phase < 320) g = map(phase, 230, 320, GREEN_MAX, 0);
- else g = 0;
- setOnly(0, 0, g);
- }
- else if (t < 8500) {
- setOnly(0, YELLOW_MAX, 0);
- }
- else if (t < 13500) {
- setOnly(RED_MAX, 0, 0);
- }
- else {
- unsigned long phase = (t - 13500) % 500;
- int r = 0;
- if (phase < 60) r = map(phase, 0, 60, 0, RED_MAX);
- else if (phase < 230) r = RED_MAX;
- else if (phase < 320) r = map(phase, 230, 320, RED_MAX, 0);
- else r = 0;
- setOnly(r, 0, 0);
- }
- }
- // init:三色一起呼吸
- void updateInit() {
- unsigned long t = millis() - modeStart;
- const unsigned long period = 2500;
- unsigned long x = t % period;
-
- int brightness;
- if (x < 800) {
- brightness = map(x, 0, 800, 0, 200);
- } else if (x < 1200) {
- brightness = 200;
- } else if (x < 2000) {
- brightness = map(x, 1200, 2000, 200, 0);
- } else {
- brightness = 0;
- }
-
- setOnly(brightness, brightness, brightness);
- }
- // =====================================================
- // BLE 回调
- // =====================================================
- class ServerCallbacks : public BLEServerCallbacks {
- void onConnect(BLEServer* pServer) {
- deviceConnected = true;
- Serial.println("BLE client connected.");
- }
- void onDisconnect(BLEServer* pServer) {
- deviceConnected = false;
- Serial.println("BLE client disconnected. Restart advertising.");
- BLEDevice::startAdvertising();
- }
- };
- class ModeCharacteristicCallbacks : public BLECharacteristicCallbacks {
- void onWrite(BLECharacteristic* pCharacteristic) {
- String value = pCharacteristic->getValue();
- value.trim();
- Serial.print("BLE write: ");
- Serial.println(value);
- setMode(value);
- }
- void onRead(BLECharacteristic* pCharacteristic) {
- pCharacteristic->setValue(currentMode.c_str());
- }
- };
- // =====================================================
- // 初始化
- // =====================================================
- void setup() {
- Serial.begin(115200);
- delay(500);
- ledcAttach(RED_PIN, PWM_FREQ, PWM_RESOLUTION);
- ledcAttach(YELLOW_PIN, PWM_FREQ, PWM_RESOLUTION);
- ledcAttach(GREEN_PIN, PWM_FREQ, PWM_RESOLUTION);
- pinMode(WIFI_LED_PIN, OUTPUT);
- digitalWrite(WIFI_LED_PIN, HIGH);
- allOff();
- currentMode = "init";
- modeStart = millis();
- Serial.println();
- Serial.println("Power on. Default mode: init");
- Serial.println("Common anode BLE enhanced version.");
- Serial.println("BLE device name: AI-Light");
- BLEDevice::init(BLE_DEVICE_NAME);
- pServer = BLEDevice::createServer();
- pServer->setCallbacks(new ServerCallbacks());
- BLEService* pService = pServer->createService(SERVICE_UUID);
- pModeCharacteristic = pService->createCharacteristic(
- MODE_CHAR_UUID,
- BLECharacteristic::PROPERTY_READ |
- BLECharacteristic::PROPERTY_WRITE |
- BLECharacteristic::PROPERTY_NOTIFY
- );
- pModeCharacteristic->setCallbacks(new ModeCharacteristicCallbacks());
- pModeCharacteristic->setValue(currentMode.c_str());
- pModeCharacteristic->addDescriptor(new BLE2902());
- pService->start();
- BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
- pAdvertising->addServiceUUID(SERVICE_UUID);
- pAdvertising->setScanResponse(true);
- pAdvertising->setMinPreferred(0x06);
- pAdvertising->setMinPreferred(0x12);
- BLEDevice::startAdvertising();
- Serial.println("BLE advertising started.");
- Serial.println("Supported modes:");
- Serial.println("init / thinking / ai / busy / success / error / alarm / traffic / off / red / yellow / green");
- }
- // =====================================================
- // 主循环
- // =====================================================
- unsigned long lastBleLedToggle = 0;
- bool bleLedState = false;
- void updateBleLed() {
- if (deviceConnected) {
- digitalWrite(WIFI_LED_PIN, LOW);
- } else {
- if (millis() - lastBleLedToggle >= 500) {
- bleLedState = !bleLedState;
- digitalWrite(WIFI_LED_PIN, bleLedState ? LOW : HIGH);
- lastBleLedToggle = millis();
- }
- }
- }
- void loop() {
- updateBleLed();
- autoTimeoutCheck();
- if (currentMode == "busy") {
- updateBusy();
- } else if (currentMode == "error") {
- updateError();
- } else if (currentMode == "thinking") {
- updateThinking();
- } else if (currentMode == "ai") {
- updateAi();
- } else if (currentMode == "success") {
- updateSuccess();
- } else if (currentMode == "traffic") {
- updateTraffic();
- } else if (currentMode == "alarm") {
- updateAlarm();
- } else if (currentMode == "init") {
- updateInit();
- } else if (currentMode == "off") {
- allOff();
- }
- delay(5);
- }
|