ai_light_v2.ino 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. #include <WiFi.h>
  2. #include <PubSubClient.h>
  3. #include <ArduinoJson.h>
  4. // =====================================================
  5. // ESP32-C3 SuperMini + 原玩具公共正极灯板:MQTT 蓝牙控制增强版 V2
  6. //
  7. // 接线方式(V2 版本:红黄互换 + 黄绿互换):
  8. // ESP32 3.3V -> 原灯板 + / 原电池正极
  9. // ESP32 IO2 -> 220Ω -> L1 控制点 = 黄灯
  10. // ESP32 IO3 -> 220Ω -> L2 控制点 = 红灯
  11. // ESP32 IO4 -> 220Ω -> L3 控制点 = 绿灯
  12. //
  13. // 注意:
  14. // 1. 原灯板 - / 原电池负极 第一版先不要接。
  15. // 2. 公共正极:GPIO LOW = 灯亮,GPIO HIGH = 灯灭。
  16. // 3. 默认开机模式:init
  17. // 4. 除 off、traffic 外,其他模式最多运行 5 分钟,然后自动进入 traffic。
  18. // 5. traffic 最多运行 10 分钟,然后自动 off。
  19. // =====================================================
  20. // =====================================================
  21. // WiFi & MQTT 配置(请根据你的网络修改)
  22. // =====================================================
  23. #define WIFI_SSID "MokiBox-IoT"
  24. #define WIFI_PASSWORD "Moki@886."
  25. #define MQTT_BROKER "47.92.50.210"
  26. #define MQTT_PORT 9883
  27. #define MQTT_USERNAME "moki"
  28. #define MQTT_PASSWORD "Moki@886."
  29. #define MQTT_CLIENT_ID "AI-Light"
  30. #define MQTT_TOPIC "opencode/status"
  31. #define MQTT_STATUS_TOPIC "openCodeLight/status"
  32. // V2 版本:红黄互换 + 黄绿互换
  33. const int GREEN_PIN = 2; // IO2 -> L1 绿灯
  34. const int RED_PIN = 3; // IO3 -> L2 红灯
  35. const int YELLOW_PIN = 4; // IO4 -> L3 黄灯
  36. const int WIFI_LED_PIN = 8; // IO8 -> 板载LED
  37. const int PWM_FREQ = 5000;
  38. const int PWM_RESOLUTION = 8;
  39. // 红灯偏弱,所以红灯单独增强
  40. const int RED_MAX = 255;
  41. const int YELLOW_MAX = 220;
  42. const int GREEN_MAX = 220;
  43. const unsigned long NORMAL_MODE_TIMEOUT_MS = 5UL * 60UL * 1000UL; // 5 分钟
  44. const unsigned long TRAFFIC_MODE_TIMEOUT_MS = 10UL * 60UL * 1000UL; // 10 分钟
  45. String currentMode = "init";
  46. unsigned long modeStart = 0;
  47. WiFiClient wifiClient;
  48. PubSubClient mqttClient(wifiClient);
  49. // =====================================================
  50. // 基础工具函数:公共正极反相输出
  51. // =====================================================
  52. void writeLed(int pin, int value) {
  53. value = constrain(value, 0, 255);
  54. int pwmValue = 255 - value; // 公共正极反相
  55. ledcWrite(pin, pwmValue);
  56. }
  57. void allOff() {
  58. writeLed(RED_PIN, 0);
  59. writeLed(YELLOW_PIN, 0);
  60. writeLed(GREEN_PIN, 0);
  61. }
  62. void setOnly(int red, int yellow, int green) {
  63. writeLed(RED_PIN, constrain(red, 0, RED_MAX));
  64. writeLed(YELLOW_PIN, constrain(yellow, 0, YELLOW_MAX));
  65. writeLed(GREEN_PIN, constrain(green, 0, GREEN_MAX));
  66. }
  67. int triWave(unsigned long t, unsigned long period, int maxValue) {
  68. unsigned long x = t % period;
  69. if (x < period / 2) {
  70. return map(x, 0, period / 2, 0, maxValue);
  71. } else {
  72. return map(x, period / 2, period, maxValue, 0);
  73. }
  74. }
  75. int fadeInOutBrightness(
  76. unsigned long t,
  77. unsigned long fadeIn,
  78. unsigned long hold,
  79. unsigned long fadeOut,
  80. unsigned long offTime,
  81. int maxValue
  82. ) {
  83. unsigned long total = fadeIn + hold + fadeOut + offTime;
  84. unsigned long x = t % total;
  85. if (x < fadeIn) {
  86. return map(x, 0, fadeIn, 0, maxValue);
  87. }
  88. x -= fadeIn;
  89. if (x < hold) {
  90. return maxValue;
  91. }
  92. x -= hold;
  93. if (x < fadeOut) {
  94. return map(x, 0, fadeOut, maxValue, 0);
  95. }
  96. return 0;
  97. }
  98. void blinkLed(int pin, int times = 3, int intervalMs = 200) {
  99. for (int i = 0; i < times; i++) {
  100. writeLed(pin, 200);
  101. delay(intervalMs);
  102. writeLed(pin, 0);
  103. delay(intervalMs);
  104. }
  105. }
  106. void fadeToStatic(int targetRed, int targetYellow, int targetGreen, int fadeMs = 80) {
  107. allOff();
  108. int steps = 12;
  109. int delayPerStep = max(1, fadeMs / steps);
  110. for (int i = 0; i <= steps; i++) {
  111. float p = (float)i / steps;
  112. setOnly(targetRed * p, targetYellow * p, targetGreen * p);
  113. delay(delayPerStep);
  114. }
  115. }
  116. // =====================================================
  117. // 模式处理
  118. // =====================================================
  119. bool isValidMode(String mode) {
  120. return (
  121. mode == "red" ||
  122. mode == "yellow" ||
  123. mode == "green" ||
  124. mode == "busy" ||
  125. mode == "error" ||
  126. mode == "thinking" ||
  127. mode == "ai" ||
  128. mode == "success" ||
  129. mode == "traffic" ||
  130. mode == "alarm" ||
  131. mode == "init" ||
  132. mode == "off" ||
  133. mode == "idle"
  134. );
  135. }
  136. void publishStatus() {
  137. mqttClient.publish(MQTT_STATUS_TOPIC, currentMode.c_str(), true);
  138. }
  139. void setMode(String mode) {
  140. mode.trim();
  141. mode.toLowerCase();
  142. if (!isValidMode(mode)) {
  143. Serial.print("Unknown mode: ");
  144. Serial.println(mode);
  145. return;
  146. }
  147. if (mode == "idle") {
  148. mode = "traffic";
  149. }
  150. currentMode = mode;
  151. modeStart = millis();
  152. Serial.print("Mode changed to: ");
  153. Serial.println(currentMode);
  154. if (mode == "red") {
  155. fadeToStatic(RED_MAX, 0, 0, 80);
  156. } else if (mode == "yellow") {
  157. fadeToStatic(0, YELLOW_MAX, 0, 80);
  158. } else if (mode == "green") {
  159. fadeToStatic(0, 0, GREEN_MAX, 80);
  160. } else if (mode == "success") {
  161. setOnly(0, 0, GREEN_MAX);
  162. } else if (mode == "off") {
  163. allOff();
  164. }
  165. publishStatus();
  166. }
  167. void autoTimeoutCheck() {
  168. unsigned long elapsed = millis() - modeStart;
  169. if (currentMode == "off") {
  170. return;
  171. }
  172. if (currentMode == "traffic") {
  173. if (elapsed >= TRAFFIC_MODE_TIMEOUT_MS) {
  174. Serial.println("Traffic timeout -> off");
  175. setMode("off");
  176. }
  177. return;
  178. }
  179. if (elapsed >= NORMAL_MODE_TIMEOUT_MS) {
  180. Serial.println("Normal mode timeout -> traffic");
  181. setMode("traffic");
  182. }
  183. }
  184. // =====================================================
  185. // 灯效模式
  186. // =====================================================
  187. void updateBusy() {
  188. unsigned long t = millis() - modeStart;
  189. int y = fadeInOutBrightness(t, 80, 500, 120, 500, YELLOW_MAX);
  190. setOnly(0, y, 0);
  191. }
  192. void updateError() {
  193. unsigned long t = millis() - modeStart;
  194. int r = fadeInOutBrightness(t, 40, 180, 80, 180, RED_MAX);
  195. setOnly(r, 0, 0);
  196. }
  197. // thinking:连贯跑马灯,按实物从上到下:L1绿 -> L2黄 -> L3红
  198. void updateThinking() {
  199. unsigned long t = millis() - modeStart;
  200. const unsigned long period = 1050;
  201. unsigned long x = t % period;
  202. int g = 0;
  203. int y = 0;
  204. int r = 0;
  205. if (x < 350) {
  206. g = map(x, 0, 350, GREEN_MAX, 70);
  207. y = map(x, 0, 350, 20, YELLOW_MAX);
  208. r = 0;
  209. } else if (x < 700) {
  210. unsigned long p = x - 350;
  211. g = map(p, 0, 350, 70, 0);
  212. y = map(p, 0, 350, YELLOW_MAX, 70);
  213. r = map(p, 0, 350, 20, RED_MAX);
  214. } else {
  215. unsigned long p = x - 700;
  216. g = map(p, 0, 350, 20, GREEN_MAX);
  217. y = map(p, 0, 350, 70, 0);
  218. r = map(p, 0, 350, RED_MAX, 70);
  219. }
  220. setOnly(r, y, g);
  221. }
  222. // ai:柔和版跑马灯,比 thinking 更慢、更柔和、亮度低一点
  223. void updateAi() {
  224. unsigned long t = millis() - modeStart;
  225. const unsigned long period = 1800;
  226. unsigned long x = t % period;
  227. unsigned long gx = (x + 0) % period;
  228. unsigned long yx = (x + period / 3) % period;
  229. unsigned long rx = (x + 2 * period / 3) % period;
  230. int g = triWave(gx, period, 150);
  231. int y = triWave(yx, period, 140);
  232. int r = triWave(rx, period, 170);
  233. setOnly(r, y, g);
  234. }
  235. void updateSuccess() {
  236. setOnly(0, 0, GREEN_MAX);
  237. }
  238. // alarm:红黄交替警灯,带短渐变
  239. void updateAlarm() {
  240. unsigned long t = millis() - modeStart;
  241. const unsigned long phaseMs = 260;
  242. int phase = (t / phaseMs) % 2;
  243. unsigned long inside = t % phaseMs;
  244. int brightness;
  245. if (inside < 60) {
  246. brightness = map(inside, 0, 60, 0, 255);
  247. } else if (inside < 180) {
  248. brightness = 255;
  249. } else {
  250. brightness = map(inside, 180, phaseMs, 255, 0);
  251. }
  252. if (phase == 0) {
  253. setOnly(brightness, 0, 0);
  254. } else {
  255. setOnly(0, min(brightness, YELLOW_MAX), 0);
  256. }
  257. }
  258. // traffic:绿灯变黄前绿闪;黄灯;红灯变绿前红闪
  259. void updateTraffic() {
  260. unsigned long t = (millis() - modeStart) % 15000;
  261. if (t < 5000) {
  262. setOnly(0, 0, GREEN_MAX);
  263. }
  264. else if (t < 6500) {
  265. unsigned long phase = (t - 5000) % 500;
  266. int g = 0;
  267. if (phase < 60) g = map(phase, 0, 60, 0, GREEN_MAX);
  268. else if (phase < 230) g = GREEN_MAX;
  269. else if (phase < 320) g = map(phase, 230, 320, GREEN_MAX, 0);
  270. else g = 0;
  271. setOnly(0, 0, g);
  272. }
  273. else if (t < 8500) {
  274. setOnly(0, YELLOW_MAX, 0);
  275. }
  276. else if (t < 13500) {
  277. setOnly(RED_MAX, 0, 0);
  278. }
  279. else {
  280. unsigned long phase = (t - 13500) % 500;
  281. int r = 0;
  282. if (phase < 60) r = map(phase, 0, 60, 0, RED_MAX);
  283. else if (phase < 230) r = RED_MAX;
  284. else if (phase < 320) r = map(phase, 230, 320, RED_MAX, 0);
  285. else r = 0;
  286. setOnly(r, 0, 0);
  287. }
  288. }
  289. // init:三色一起呼吸
  290. void updateInit() {
  291. unsigned long t = millis() - modeStart;
  292. const unsigned long period = 2500;
  293. unsigned long x = t % period;
  294. int brightness;
  295. if (x < 800) {
  296. brightness = map(x, 0, 800, 0, 200);
  297. } else if (x < 1200) {
  298. brightness = 200;
  299. } else if (x < 2000) {
  300. brightness = map(x, 1200, 2000, 200, 0);
  301. } else {
  302. brightness = 0;
  303. }
  304. setOnly(brightness, brightness, brightness);
  305. }
  306. // 工具执行完成:绿灯快闪三次
  307. void updateCompletedFlash() {
  308. unsigned long t = millis() - modeStart;
  309. const int flashCount = 3;
  310. const unsigned long flashDuration = 150; // 每次闪烁150ms
  311. const unsigned long totalDuration = flashCount * 2 * flashDuration; // 3次闪烁总时长
  312. if (t >= totalDuration) {
  313. // 闪烁完成,保持灭灯
  314. setOnly(0, 0, 0);
  315. return;
  316. }
  317. // 计算当前闪烁状态
  318. unsigned long flashPhase = t % (2 * flashDuration);
  319. if (flashPhase < flashDuration) {
  320. // 亮灯阶段
  321. setOnly(0, 0, GREEN_MAX);
  322. } else {
  323. // 灭灯阶段
  324. setOnly(0, 0, 0);
  325. }
  326. }
  327. // 会话完成:绿灯呼吸闪烁(3次后自动切换到idle)
  328. void updateBreathing() {
  329. unsigned long t = millis() - modeStart;
  330. const unsigned long period = 2500; // 2.5秒一个呼吸周期
  331. const int breathCount = 3; // 呼吸3次
  332. const unsigned long totalDuration = breathCount * period; // 总时长
  333. // 检查是否完成3次呼吸
  334. if (t >= totalDuration) {
  335. // 3次呼吸完成,自动切换到idle
  336. setMode("idle");
  337. return;
  338. }
  339. unsigned long x = t % period;
  340. int brightness;
  341. if (x < 800) {
  342. // 吸入阶段:0 -> 最大
  343. brightness = map(x, 0, 800, 0, GREEN_MAX);
  344. } else if (x < 1200) {
  345. // 保持阶段:最大亮度
  346. brightness = GREEN_MAX;
  347. } else if (x < 2000) {
  348. // 呼出阶段:最大 -> 0
  349. brightness = map(x, 1200, 2000, GREEN_MAX, 0);
  350. } else {
  351. // 停顿阶段:0亮度
  352. brightness = 0;
  353. }
  354. setOnly(0, 0, brightness);
  355. }
  356. // =====================================================
  357. // MQTT 回调
  358. // =====================================================
  359. void mqttCallback(char* topic, byte* payload, unsigned int length) {
  360. String message = "";
  361. for (unsigned int i = 0; i < length; i++) {
  362. message += (char)payload[i];
  363. }
  364. Serial.print("MQTT message on ");
  365. Serial.print(topic);
  366. Serial.print(": ");
  367. Serial.println(message);
  368. JsonDocument doc;
  369. DeserializationError error = deserializeJson(doc, message);
  370. if (error) {
  371. Serial.print("JSON parse failed, treating as raw mode: ");
  372. Serial.println(error.c_str());
  373. setMode(message);
  374. return;
  375. }
  376. const char* code = doc["code"];
  377. if (code) {
  378. String codeStr = String(code);
  379. // 状态码映射到灯效
  380. if (codeStr == "idle") {
  381. setMode("idle");
  382. } else if (codeStr == "busy" || codeStr == "running") {
  383. setMode("busy");
  384. } else if (codeStr == "retry" || codeStr == "permission") {
  385. setMode("alarm");
  386. } else if (codeStr == "pending") {
  387. setMode("yellow");
  388. } else if (codeStr == "reasoning") {
  389. setMode("thinking");
  390. } else if (codeStr == "using_tool") {
  391. setMode("ai");
  392. } else if (codeStr == "error") {
  393. setMode("error");
  394. } else {
  395. // 直接使用原始模式名
  396. setMode(codeStr);
  397. }
  398. } else {
  399. Serial.println("MQTT message missing 'code' field");
  400. }
  401. }
  402. unsigned long lastWifiLedToggle = 0;
  403. bool wifiLedState = false;
  404. void updateWifiLed() {
  405. if (WiFi.status() == WL_CONNECTED) {
  406. digitalWrite(WIFI_LED_PIN, LOW);
  407. } else {
  408. if (millis() - lastWifiLedToggle >= 500) {
  409. wifiLedState = !wifiLedState;
  410. digitalWrite(WIFI_LED_PIN, wifiLedState ? LOW : HIGH);
  411. lastWifiLedToggle = millis();
  412. }
  413. }
  414. }
  415. void connectWiFi() {
  416. Serial.print("Connecting to WiFi");
  417. WiFi.mode(WIFI_STA);
  418. WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  419. int maxRetries = 5;
  420. int attemptsPerRetry = 60;
  421. for (int retry = 1; retry <= maxRetries; retry++) {
  422. Serial.print("\nWiFi attempt ");
  423. Serial.print(retry);
  424. Serial.print("/");
  425. Serial.println(maxRetries);
  426. int attempts = 0;
  427. while (WiFi.status() != WL_CONNECTED && attempts < attemptsPerRetry) {
  428. delay(5);
  429. setOnly(100, 100, 100);
  430. updateWifiLed();
  431. Serial.print(".");
  432. attempts++;
  433. }
  434. if (WiFi.status() == WL_CONNECTED) {
  435. break;
  436. }
  437. Serial.println("\nWiFi connection failed, retrying...");
  438. delay(2000);
  439. }
  440. if (WiFi.status() != WL_CONNECTED) {
  441. Serial.println("WiFi failed after 5 retries. Restarting...");
  442. delay(1000);
  443. ESP.restart();
  444. }
  445. Serial.println();
  446. Serial.print("WiFi connected. IP: ");
  447. Serial.println(WiFi.localIP());
  448. digitalWrite(WIFI_LED_PIN, LOW);
  449. }
  450. void breathingGreen(int times) {
  451. const unsigned long period = 2500;
  452. for (int i = 0; i < times; i++) {
  453. unsigned long start = millis();
  454. while (millis() - start < period) {
  455. unsigned long t = millis() - start;
  456. int brightness;
  457. if (t < 800) {
  458. brightness = map(t, 0, 800, 0, GREEN_MAX);
  459. } else if (t < 1200) {
  460. brightness = GREEN_MAX;
  461. } else if (t < 2000) {
  462. brightness = map(t, 1200, 2000, GREEN_MAX, 0);
  463. } else {
  464. brightness = 0;
  465. }
  466. setOnly(0, 0, brightness);
  467. delay(5);
  468. }
  469. }
  470. allOff();
  471. }
  472. void connectMQTT() {
  473. mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
  474. mqttClient.setCallback(mqttCallback);
  475. Serial.print("Connecting to MQTT broker");
  476. int attempts = 0;
  477. bool ledState = false;
  478. while (!mqttClient.connected()) {
  479. ledState = !ledState;
  480. if (ledState) {
  481. setOnly(RED_MAX, 0, 0);
  482. } else {
  483. setOnly(0, YELLOW_MAX, 0);
  484. }
  485. if (mqttClient.connect(MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD)) {
  486. Serial.println();
  487. Serial.println("MQTT connected.");
  488. mqttClient.subscribe(MQTT_TOPIC);
  489. Serial.print("Subscribed to: ");
  490. Serial.println(MQTT_TOPIC);
  491. allOff();
  492. breathingGreen(3);
  493. publishStatus();
  494. } else {
  495. Serial.print(".");
  496. delay(500);
  497. attempts++;
  498. if (attempts > 60) {
  499. Serial.println("\nMQTT connection failed!");
  500. attempts = 0;
  501. }
  502. }
  503. }
  504. }
  505. void checkMQTTConnection() {
  506. if (!mqttClient.connected()) {
  507. Serial.println("MQTT disconnected. Reconnecting...");
  508. connectMQTT();
  509. }
  510. }
  511. // =====================================================
  512. // 初始化
  513. // =====================================================
  514. void setup() {
  515. Serial.begin(115200);
  516. delay(500);
  517. ledcAttach(RED_PIN, PWM_FREQ, PWM_RESOLUTION);
  518. ledcAttach(YELLOW_PIN, PWM_FREQ, PWM_RESOLUTION);
  519. ledcAttach(GREEN_PIN, PWM_FREQ, PWM_RESOLUTION);
  520. pinMode(WIFI_LED_PIN, OUTPUT);
  521. digitalWrite(WIFI_LED_PIN, LOW); // 初始状态关闭
  522. allOff();
  523. Serial.println();
  524. Serial.println("Power on.");
  525. Serial.println("Common anode MQTT enhanced version V2 (Red/Yellow + Yellow/Green swapped).");
  526. Serial.print("Device name: ");
  527. Serial.println(MQTT_CLIENT_ID);
  528. currentMode = "init";
  529. modeStart = millis();
  530. connectWiFi();
  531. connectMQTT();
  532. setMode("traffic");
  533. Serial.println("Supported modes:");
  534. Serial.println("init / thinking / ai / busy / success / error / alarm / traffic / off / red / yellow / green");
  535. }
  536. // =====================================================
  537. // 主循环
  538. // =====================================================
  539. void loop() {
  540. updateWifiLed();
  541. checkMQTTConnection();
  542. mqttClient.loop();
  543. autoTimeoutCheck();
  544. if (currentMode == "busy") {
  545. updateBusy();
  546. } else if (currentMode == "error") {
  547. updateError();
  548. } else if (currentMode == "thinking") {
  549. updateThinking();
  550. } else if (currentMode == "ai") {
  551. updateAi();
  552. } else if (currentMode == "success") {
  553. updateSuccess();
  554. } else if (currentMode == "traffic") {
  555. updateTraffic();
  556. } else if (currentMode == "alarm") {
  557. updateAlarm();
  558. } else if (currentMode == "init") {
  559. updateInit();
  560. } else if (currentMode == "off") {
  561. allOff();
  562. }
  563. delay(5);
  564. }