ai_light.ino 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. #include <BLEDevice.h>
  2. #include <BLEServer.h>
  3. #include <BLEUtils.h>
  4. #include <BLE2902.h>
  5. // =====================================================
  6. // ESP32-C3 SuperMini + 原玩具公共正极灯板:BLE 蓝牙控制增强版
  7. //
  8. // 接线方式:
  9. // ESP32 3.3V -> 原灯板 + / 原电池正极
  10. // ESP32 IO2 -> 220Ω -> L1 控制点 = 绿灯
  11. // ESP32 IO3 -> 220Ω -> L2 控制点 = 红灯
  12. // ESP32 IO4 -> 220Ω -> L3 控制点 = 黄灯
  13. //
  14. // 注意:
  15. // 1. 原灯板 - / 原电池负极 第一版先不要接。
  16. // 2. 公共正极:GPIO LOW = 灯亮,GPIO HIGH = 灯灭。
  17. // 3. 默认开机模式:init
  18. // 4. 除 off、traffic 外,其他模式最多运行 5 分钟,然后自动进入 traffic。
  19. // 5. traffic 最多运行 10 分钟,然后自动 off。
  20. // =====================================================
  21. const char* BLE_DEVICE_NAME = "AI-Light";
  22. #define SERVICE_UUID "b8b7e001-7a6b-4f4f-9a8b-11c0ffee0001"
  23. #define MODE_CHAR_UUID "b8b7e002-7a6b-4f4f-9a8b-11c0ffee0001"
  24. // 你的实测:L1=绿灯,L2=红灯,L3=黄灯
  25. const int GREEN_PIN = 2; // IO2 -> L1 绿灯
  26. const int RED_PIN = 3; // IO3 -> L2 红灯
  27. const int YELLOW_PIN = 4; // IO4 -> L3 黄灯
  28. const int WIFI_LED_PIN = 8; // IO8 -> 板载LED
  29. const int PWM_FREQ = 5000;
  30. const int PWM_RESOLUTION = 8;
  31. // 红灯偏弱,所以红灯单独增强
  32. const int RED_MAX = 255;
  33. const int YELLOW_MAX = 220;
  34. const int GREEN_MAX = 220;
  35. const unsigned long NORMAL_MODE_TIMEOUT_MS = 5UL * 60UL * 1000UL; // 5 分钟
  36. const unsigned long TRAFFIC_MODE_TIMEOUT_MS = 10UL * 60UL * 1000UL; // 10 分钟
  37. String currentMode = "init";
  38. unsigned long modeStart = 0;
  39. BLEServer* pServer = nullptr;
  40. BLECharacteristic* pModeCharacteristic = nullptr;
  41. bool deviceConnected = false;
  42. // =====================================================
  43. // 基础工具函数:公共正极反相输出
  44. // =====================================================
  45. void writeLed(int pin, int value) {
  46. value = constrain(value, 0, 255);
  47. int pwmValue = 255 - value; // 公共正极反相
  48. ledcWrite(pin, pwmValue);
  49. }
  50. void allOff() {
  51. writeLed(RED_PIN, 0);
  52. writeLed(YELLOW_PIN, 0);
  53. writeLed(GREEN_PIN, 0);
  54. }
  55. void setOnly(int red, int yellow, int green) {
  56. writeLed(RED_PIN, constrain(red, 0, RED_MAX));
  57. writeLed(YELLOW_PIN, constrain(yellow, 0, YELLOW_MAX));
  58. writeLed(GREEN_PIN, constrain(green, 0, GREEN_MAX));
  59. }
  60. int triWave(unsigned long t, unsigned long period, int maxValue) {
  61. unsigned long x = t % period;
  62. if (x < period / 2) {
  63. return map(x, 0, period / 2, 0, maxValue);
  64. } else {
  65. return map(x, period / 2, period, maxValue, 0);
  66. }
  67. }
  68. int fadeInOutBrightness(
  69. unsigned long t,
  70. unsigned long fadeIn,
  71. unsigned long hold,
  72. unsigned long fadeOut,
  73. unsigned long offTime,
  74. int maxValue
  75. ) {
  76. unsigned long total = fadeIn + hold + fadeOut + offTime;
  77. unsigned long x = t % total;
  78. if (x < fadeIn) {
  79. return map(x, 0, fadeIn, 0, maxValue);
  80. }
  81. x -= fadeIn;
  82. if (x < hold) {
  83. return maxValue;
  84. }
  85. x -= hold;
  86. if (x < fadeOut) {
  87. return map(x, 0, fadeOut, maxValue, 0);
  88. }
  89. return 0;
  90. }
  91. void fadeToStatic(int targetRed, int targetYellow, int targetGreen, int fadeMs = 80) {
  92. allOff();
  93. int steps = 12;
  94. int delayPerStep = max(1, fadeMs / steps);
  95. for (int i = 0; i <= steps; i++) {
  96. float p = (float)i / steps;
  97. setOnly(targetRed * p, targetYellow * p, targetGreen * p);
  98. delay(delayPerStep);
  99. }
  100. }
  101. // =====================================================
  102. // 模式处理
  103. // =====================================================
  104. bool isValidMode(String mode) {
  105. return (
  106. mode == "red" ||
  107. mode == "yellow" ||
  108. mode == "green" ||
  109. mode == "busy" ||
  110. mode == "error" ||
  111. mode == "thinking" ||
  112. mode == "ai" ||
  113. mode == "success" ||
  114. mode == "traffic" ||
  115. mode == "alarm" ||
  116. mode == "init" ||
  117. mode == "idle" ||
  118. mode == "off"
  119. );
  120. }
  121. void notifyMode() {
  122. if (pModeCharacteristic) {
  123. pModeCharacteristic->setValue(currentMode.c_str());
  124. if (deviceConnected) {
  125. pModeCharacteristic->notify();
  126. }
  127. }
  128. }
  129. void setMode(String mode) {
  130. mode.trim();
  131. mode.toLowerCase();
  132. if (!isValidMode(mode)) {
  133. Serial.print("Unknown mode: ");
  134. Serial.println(mode);
  135. return;
  136. }
  137. if (mode == "idle") {
  138. mode = "traffic";
  139. }
  140. currentMode = mode;
  141. modeStart = millis();
  142. Serial.print("Mode changed to: ");
  143. Serial.println(currentMode);
  144. if (mode == "red") {
  145. fadeToStatic(RED_MAX, 0, 0, 80);
  146. } else if (mode == "yellow") {
  147. fadeToStatic(0, YELLOW_MAX, 0, 80);
  148. } else if (mode == "green") {
  149. fadeToStatic(0, 0, GREEN_MAX, 80);
  150. } else if (mode == "success") {
  151. setOnly(0, 0, GREEN_MAX);
  152. } else if (mode == "off") {
  153. allOff();
  154. }
  155. notifyMode();
  156. }
  157. void autoTimeoutCheck() {
  158. unsigned long elapsed = millis() - modeStart;
  159. if (currentMode == "off") {
  160. return;
  161. }
  162. if (currentMode == "traffic") {
  163. if (elapsed >= TRAFFIC_MODE_TIMEOUT_MS) {
  164. Serial.println("Traffic timeout -> off");
  165. setMode("off");
  166. }
  167. return;
  168. }
  169. if (elapsed >= NORMAL_MODE_TIMEOUT_MS) {
  170. Serial.println("Normal mode timeout -> traffic");
  171. setMode("traffic");
  172. }
  173. }
  174. // =====================================================
  175. // 灯效模式
  176. // =====================================================
  177. void updateBusy() {
  178. unsigned long t = millis() - modeStart;
  179. int y = fadeInOutBrightness(t, 80, 500, 120, 500, YELLOW_MAX);
  180. setOnly(0, y, 0);
  181. }
  182. void updateError() {
  183. unsigned long t = millis() - modeStart;
  184. int r = fadeInOutBrightness(t, 40, 180, 80, 180, RED_MAX);
  185. setOnly(r, 0, 0);
  186. }
  187. // thinking:连贯跑马灯,按实物从上到下:L1绿 -> L2黄 -> L3红
  188. void updateThinking() {
  189. unsigned long t = millis() - modeStart;
  190. const unsigned long period = 1050;
  191. unsigned long x = t % period;
  192. int g = 0;
  193. int y = 0;
  194. int r = 0;
  195. if (x < 350) {
  196. g = map(x, 0, 350, GREEN_MAX, 70);
  197. y = map(x, 0, 350, 20, YELLOW_MAX);
  198. r = 0;
  199. } else if (x < 700) {
  200. unsigned long p = x - 350;
  201. g = map(p, 0, 350, 70, 0);
  202. y = map(p, 0, 350, YELLOW_MAX, 70);
  203. r = map(p, 0, 350, 20, RED_MAX);
  204. } else {
  205. unsigned long p = x - 700;
  206. g = map(p, 0, 350, 20, GREEN_MAX);
  207. y = map(p, 0, 350, 70, 0);
  208. r = map(p, 0, 350, RED_MAX, 70);
  209. }
  210. setOnly(r, y, g);
  211. }
  212. // ai:柔和版跑马灯,比 thinking 更慢、更柔和、亮度低一点
  213. void updateAi() {
  214. unsigned long t = millis() - modeStart;
  215. const unsigned long period = 1800;
  216. unsigned long x = t % period;
  217. unsigned long gx = (x + 0) % period;
  218. unsigned long yx = (x + period / 3) % period;
  219. unsigned long rx = (x + 2 * period / 3) % period;
  220. int g = triWave(gx, period, 150);
  221. int y = triWave(yx, period, 140);
  222. int r = triWave(rx, period, 170);
  223. setOnly(r, y, g);
  224. }
  225. void updateSuccess() {
  226. setOnly(0, 0, GREEN_MAX);
  227. }
  228. // alarm:红黄交替警灯,带短渐变
  229. void updateAlarm() {
  230. unsigned long t = millis() - modeStart;
  231. const unsigned long phaseMs = 260;
  232. int phase = (t / phaseMs) % 2;
  233. unsigned long inside = t % phaseMs;
  234. int brightness;
  235. if (inside < 60) {
  236. brightness = map(inside, 0, 60, 0, 255);
  237. } else if (inside < 180) {
  238. brightness = 255;
  239. } else {
  240. brightness = map(inside, 180, phaseMs, 255, 0);
  241. }
  242. if (phase == 0) {
  243. setOnly(brightness, 0, 0);
  244. } else {
  245. setOnly(0, min(brightness, YELLOW_MAX), 0);
  246. }
  247. }
  248. // traffic:绿灯变黄前绿闪;黄灯;红灯变绿前红闪
  249. void updateTraffic() {
  250. unsigned long t = (millis() - modeStart) % 15000;
  251. if (t < 5000) {
  252. setOnly(0, 0, GREEN_MAX);
  253. }
  254. else if (t < 6500) {
  255. unsigned long phase = (t - 5000) % 500;
  256. int g = 0;
  257. if (phase < 60) g = map(phase, 0, 60, 0, GREEN_MAX);
  258. else if (phase < 230) g = GREEN_MAX;
  259. else if (phase < 320) g = map(phase, 230, 320, GREEN_MAX, 0);
  260. else g = 0;
  261. setOnly(0, 0, g);
  262. }
  263. else if (t < 8500) {
  264. setOnly(0, YELLOW_MAX, 0);
  265. }
  266. else if (t < 13500) {
  267. setOnly(RED_MAX, 0, 0);
  268. }
  269. else {
  270. unsigned long phase = (t - 13500) % 500;
  271. int r = 0;
  272. if (phase < 60) r = map(phase, 0, 60, 0, RED_MAX);
  273. else if (phase < 230) r = RED_MAX;
  274. else if (phase < 320) r = map(phase, 230, 320, RED_MAX, 0);
  275. else r = 0;
  276. setOnly(r, 0, 0);
  277. }
  278. }
  279. // init:三色一起呼吸
  280. void updateInit() {
  281. unsigned long t = millis() - modeStart;
  282. const unsigned long period = 2500;
  283. unsigned long x = t % period;
  284. int brightness;
  285. if (x < 800) {
  286. brightness = map(x, 0, 800, 0, 200);
  287. } else if (x < 1200) {
  288. brightness = 200;
  289. } else if (x < 2000) {
  290. brightness = map(x, 1200, 2000, 200, 0);
  291. } else {
  292. brightness = 0;
  293. }
  294. setOnly(brightness, brightness, brightness);
  295. }
  296. // =====================================================
  297. // BLE 回调
  298. // =====================================================
  299. class ServerCallbacks : public BLEServerCallbacks {
  300. void onConnect(BLEServer* pServer) {
  301. deviceConnected = true;
  302. Serial.println("BLE client connected.");
  303. }
  304. void onDisconnect(BLEServer* pServer) {
  305. deviceConnected = false;
  306. Serial.println("BLE client disconnected. Restart advertising.");
  307. BLEDevice::startAdvertising();
  308. }
  309. };
  310. class ModeCharacteristicCallbacks : public BLECharacteristicCallbacks {
  311. void onWrite(BLECharacteristic* pCharacteristic) {
  312. String value = pCharacteristic->getValue();
  313. value.trim();
  314. Serial.print("BLE write: ");
  315. Serial.println(value);
  316. setMode(value);
  317. }
  318. void onRead(BLECharacteristic* pCharacteristic) {
  319. pCharacteristic->setValue(currentMode.c_str());
  320. }
  321. };
  322. // =====================================================
  323. // 初始化
  324. // =====================================================
  325. void setup() {
  326. Serial.begin(115200);
  327. delay(500);
  328. ledcAttach(RED_PIN, PWM_FREQ, PWM_RESOLUTION);
  329. ledcAttach(YELLOW_PIN, PWM_FREQ, PWM_RESOLUTION);
  330. ledcAttach(GREEN_PIN, PWM_FREQ, PWM_RESOLUTION);
  331. pinMode(WIFI_LED_PIN, OUTPUT);
  332. digitalWrite(WIFI_LED_PIN, HIGH);
  333. allOff();
  334. currentMode = "init";
  335. modeStart = millis();
  336. Serial.println();
  337. Serial.println("Power on. Default mode: init");
  338. Serial.println("Common anode BLE enhanced version.");
  339. Serial.println("BLE device name: AI-Light");
  340. BLEDevice::init(BLE_DEVICE_NAME);
  341. pServer = BLEDevice::createServer();
  342. pServer->setCallbacks(new ServerCallbacks());
  343. BLEService* pService = pServer->createService(SERVICE_UUID);
  344. pModeCharacteristic = pService->createCharacteristic(
  345. MODE_CHAR_UUID,
  346. BLECharacteristic::PROPERTY_READ |
  347. BLECharacteristic::PROPERTY_WRITE |
  348. BLECharacteristic::PROPERTY_NOTIFY
  349. );
  350. pModeCharacteristic->setCallbacks(new ModeCharacteristicCallbacks());
  351. pModeCharacteristic->setValue(currentMode.c_str());
  352. pModeCharacteristic->addDescriptor(new BLE2902());
  353. pService->start();
  354. BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
  355. pAdvertising->addServiceUUID(SERVICE_UUID);
  356. pAdvertising->setScanResponse(true);
  357. pAdvertising->setMinPreferred(0x06);
  358. pAdvertising->setMinPreferred(0x12);
  359. BLEDevice::startAdvertising();
  360. Serial.println("BLE advertising started.");
  361. Serial.println("Supported modes:");
  362. Serial.println("init / thinking / ai / busy / success / error / alarm / traffic / off / red / yellow / green");
  363. }
  364. // =====================================================
  365. // 主循环
  366. // =====================================================
  367. unsigned long lastBleLedToggle = 0;
  368. bool bleLedState = false;
  369. void updateBleLed() {
  370. if (deviceConnected) {
  371. digitalWrite(WIFI_LED_PIN, LOW);
  372. } else {
  373. if (millis() - lastBleLedToggle >= 500) {
  374. bleLedState = !bleLedState;
  375. digitalWrite(WIFI_LED_PIN, bleLedState ? LOW : HIGH);
  376. lastBleLedToggle = millis();
  377. }
  378. }
  379. }
  380. void loop() {
  381. updateBleLed();
  382. autoTimeoutCheck();
  383. if (currentMode == "busy") {
  384. updateBusy();
  385. } else if (currentMode == "error") {
  386. updateError();
  387. } else if (currentMode == "thinking") {
  388. updateThinking();
  389. } else if (currentMode == "ai") {
  390. updateAi();
  391. } else if (currentMode == "success") {
  392. updateSuccess();
  393. } else if (currentMode == "traffic") {
  394. updateTraffic();
  395. } else if (currentMode == "alarm") {
  396. updateAlarm();
  397. } else if (currentMode == "init") {
  398. updateInit();
  399. } else if (currentMode == "off") {
  400. allOff();
  401. }
  402. delay(5);
  403. }