|
@@ -0,0 +1,677 @@
|
|
|
|
|
+#include <WiFi.h>
|
|
|
|
|
+#include <PubSubClient.h>
|
|
|
|
|
+#include <ArduinoJson.h>
|
|
|
|
|
+
|
|
|
|
|
+// =====================================================
|
|
|
|
|
+// ESP32-C3 SuperMini + 原玩具公共正极灯板:MQTT 蓝牙控制增强版 V2
|
|
|
|
|
+//
|
|
|
|
|
+// 接线方式(V2 版本:红黄互换 + 黄绿互换):
|
|
|
|
|
+// 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。
|
|
|
|
|
+// =====================================================
|
|
|
|
|
+
|
|
|
|
|
+// =====================================================
|
|
|
|
|
+// WiFi & MQTT 配置(请根据你的网络修改)
|
|
|
|
|
+// =====================================================
|
|
|
|
|
+#define WIFI_SSID "MokiBox-IoT"
|
|
|
|
|
+#define WIFI_PASSWORD "Moki@886."
|
|
|
|
|
+#define MQTT_BROKER "47.92.50.210"
|
|
|
|
|
+#define MQTT_PORT 9883
|
|
|
|
|
+#define MQTT_USERNAME "moki"
|
|
|
|
|
+#define MQTT_PASSWORD "Moki@886."
|
|
|
|
|
+#define MQTT_CLIENT_ID "AI-Light"
|
|
|
|
|
+#define MQTT_TOPIC "opencode/status"
|
|
|
|
|
+#define MQTT_STATUS_TOPIC "openCodeLight/status"
|
|
|
|
|
+
|
|
|
|
|
+// V2 版本:红黄互换 + 黄绿互换
|
|
|
|
|
+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;
|
|
|
|
|
+
|
|
|
|
|
+WiFiClient wifiClient;
|
|
|
|
|
+PubSubClient mqttClient(wifiClient);
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+// =====================================================
|
|
|
|
|
+// 基础工具函数:公共正极反相输出
|
|
|
|
|
+// =====================================================
|
|
|
|
|
+
|
|
|
|
|
+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 blinkLed(int pin, int times = 3, int intervalMs = 200) {
|
|
|
|
|
+ for (int i = 0; i < times; i++) {
|
|
|
|
|
+ writeLed(pin, 200);
|
|
|
|
|
+ delay(intervalMs);
|
|
|
|
|
+ writeLed(pin, 0);
|
|
|
|
|
+ delay(intervalMs);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+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 == "off" ||
|
|
|
|
|
+ mode == "idle"
|
|
|
|
|
+ );
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void publishStatus() {
|
|
|
|
|
+ mqttClient.publish(MQTT_STATUS_TOPIC, currentMode.c_str(), true);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+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();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ publishStatus();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+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);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 工具执行完成:绿灯快闪三次
|
|
|
|
|
+void updateCompletedFlash() {
|
|
|
|
|
+ unsigned long t = millis() - modeStart;
|
|
|
|
|
+ const int flashCount = 3;
|
|
|
|
|
+ const unsigned long flashDuration = 150; // 每次闪烁150ms
|
|
|
|
|
+ const unsigned long totalDuration = flashCount * 2 * flashDuration; // 3次闪烁总时长
|
|
|
|
|
+
|
|
|
|
|
+ if (t >= totalDuration) {
|
|
|
|
|
+ // 闪烁完成,保持灭灯
|
|
|
|
|
+ setOnly(0, 0, 0);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算当前闪烁状态
|
|
|
|
|
+ unsigned long flashPhase = t % (2 * flashDuration);
|
|
|
|
|
+ if (flashPhase < flashDuration) {
|
|
|
|
|
+ // 亮灯阶段
|
|
|
|
|
+ setOnly(0, 0, GREEN_MAX);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 灭灯阶段
|
|
|
|
|
+ setOnly(0, 0, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 会话完成:绿灯呼吸闪烁(3次后自动切换到idle)
|
|
|
|
|
+void updateBreathing() {
|
|
|
|
|
+ unsigned long t = millis() - modeStart;
|
|
|
|
|
+ const unsigned long period = 2500; // 2.5秒一个呼吸周期
|
|
|
|
|
+ const int breathCount = 3; // 呼吸3次
|
|
|
|
|
+ const unsigned long totalDuration = breathCount * period; // 总时长
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否完成3次呼吸
|
|
|
|
|
+ if (t >= totalDuration) {
|
|
|
|
|
+ // 3次呼吸完成,自动切换到idle
|
|
|
|
|
+ setMode("idle");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ unsigned long x = t % period;
|
|
|
|
|
+
|
|
|
|
|
+ int brightness;
|
|
|
|
|
+ if (x < 800) {
|
|
|
|
|
+ // 吸入阶段:0 -> 最大
|
|
|
|
|
+ brightness = map(x, 0, 800, 0, GREEN_MAX);
|
|
|
|
|
+ } else if (x < 1200) {
|
|
|
|
|
+ // 保持阶段:最大亮度
|
|
|
|
|
+ brightness = GREEN_MAX;
|
|
|
|
|
+ } else if (x < 2000) {
|
|
|
|
|
+ // 呼出阶段:最大 -> 0
|
|
|
|
|
+ brightness = map(x, 1200, 2000, GREEN_MAX, 0);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 停顿阶段:0亮度
|
|
|
|
|
+ brightness = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setOnly(0, 0, brightness);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+// =====================================================
|
|
|
|
|
+// MQTT 回调
|
|
|
|
|
+// =====================================================
|
|
|
|
|
+
|
|
|
|
|
+void mqttCallback(char* topic, byte* payload, unsigned int length) {
|
|
|
|
|
+ String message = "";
|
|
|
|
|
+ for (unsigned int i = 0; i < length; i++) {
|
|
|
|
|
+ message += (char)payload[i];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Serial.print("MQTT message on ");
|
|
|
|
|
+ Serial.print(topic);
|
|
|
|
|
+ Serial.print(": ");
|
|
|
|
|
+ Serial.println(message);
|
|
|
|
|
+
|
|
|
|
|
+ JsonDocument doc;
|
|
|
|
|
+ DeserializationError error = deserializeJson(doc, message);
|
|
|
|
|
+
|
|
|
|
|
+ if (error) {
|
|
|
|
|
+ Serial.print("JSON parse failed, treating as raw mode: ");
|
|
|
|
|
+ Serial.println(error.c_str());
|
|
|
|
|
+ setMode(message);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const char* code = doc["code"];
|
|
|
|
|
+ if (code) {
|
|
|
|
|
+ String codeStr = String(code);
|
|
|
|
|
+
|
|
|
|
|
+ // 状态码映射到灯效
|
|
|
|
|
+ if (codeStr == "idle") {
|
|
|
|
|
+ setMode("idle");
|
|
|
|
|
+ } else if (codeStr == "busy" || codeStr == "running") {
|
|
|
|
|
+ setMode("busy");
|
|
|
|
|
+ } else if (codeStr == "retry" || codeStr == "permission") {
|
|
|
|
|
+ setMode("alarm");
|
|
|
|
|
+ } else if (codeStr == "pending") {
|
|
|
|
|
+ setMode("yellow");
|
|
|
|
|
+ } else if (codeStr == "reasoning") {
|
|
|
|
|
+ setMode("thinking");
|
|
|
|
|
+ } else if (codeStr == "using_tool") {
|
|
|
|
|
+ setMode("ai");
|
|
|
|
|
+ } else if (codeStr == "error") {
|
|
|
|
|
+ setMode("error");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 直接使用原始模式名
|
|
|
|
|
+ setMode(codeStr);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Serial.println("MQTT message missing 'code' field");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+unsigned long lastWifiLedToggle = 0;
|
|
|
|
|
+bool wifiLedState = false;
|
|
|
|
|
+
|
|
|
|
|
+void updateWifiLed() {
|
|
|
|
|
+ if (WiFi.status() == WL_CONNECTED) {
|
|
|
|
|
+ digitalWrite(WIFI_LED_PIN, LOW);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (millis() - lastWifiLedToggle >= 500) {
|
|
|
|
|
+ wifiLedState = !wifiLedState;
|
|
|
|
|
+ digitalWrite(WIFI_LED_PIN, wifiLedState ? LOW : HIGH);
|
|
|
|
|
+ lastWifiLedToggle = millis();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void connectWiFi() {
|
|
|
|
|
+ Serial.print("Connecting to WiFi");
|
|
|
|
|
+ WiFi.mode(WIFI_STA);
|
|
|
|
|
+ WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
|
|
|
|
+
|
|
|
|
|
+ int maxRetries = 5;
|
|
|
|
|
+ int attemptsPerRetry = 60;
|
|
|
|
|
+
|
|
|
|
|
+ for (int retry = 1; retry <= maxRetries; retry++) {
|
|
|
|
|
+ Serial.print("\nWiFi attempt ");
|
|
|
|
|
+ Serial.print(retry);
|
|
|
|
|
+ Serial.print("/");
|
|
|
|
|
+ Serial.println(maxRetries);
|
|
|
|
|
+
|
|
|
|
|
+ int attempts = 0;
|
|
|
|
|
+ while (WiFi.status() != WL_CONNECTED && attempts < attemptsPerRetry) {
|
|
|
|
|
+ delay(5);
|
|
|
|
|
+ setOnly(100, 100, 100);
|
|
|
|
|
+ updateWifiLed();
|
|
|
|
|
+ Serial.print(".");
|
|
|
|
|
+ attempts++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (WiFi.status() == WL_CONNECTED) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Serial.println("\nWiFi connection failed, retrying...");
|
|
|
|
|
+ delay(2000);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (WiFi.status() != WL_CONNECTED) {
|
|
|
|
|
+ Serial.println("WiFi failed after 5 retries. Restarting...");
|
|
|
|
|
+ delay(1000);
|
|
|
|
|
+ ESP.restart();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Serial.println();
|
|
|
|
|
+ Serial.print("WiFi connected. IP: ");
|
|
|
|
|
+ Serial.println(WiFi.localIP());
|
|
|
|
|
+ digitalWrite(WIFI_LED_PIN, LOW);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void breathingGreen(int times) {
|
|
|
|
|
+ const unsigned long period = 2500;
|
|
|
|
|
+ for (int i = 0; i < times; i++) {
|
|
|
|
|
+ unsigned long start = millis();
|
|
|
|
|
+ while (millis() - start < period) {
|
|
|
|
|
+ unsigned long t = millis() - start;
|
|
|
|
|
+ int brightness;
|
|
|
|
|
+ if (t < 800) {
|
|
|
|
|
+ brightness = map(t, 0, 800, 0, GREEN_MAX);
|
|
|
|
|
+ } else if (t < 1200) {
|
|
|
|
|
+ brightness = GREEN_MAX;
|
|
|
|
|
+ } else if (t < 2000) {
|
|
|
|
|
+ brightness = map(t, 1200, 2000, GREEN_MAX, 0);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ brightness = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ setOnly(0, 0, brightness);
|
|
|
|
|
+ delay(5);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ allOff();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void connectMQTT() {
|
|
|
|
|
+ mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
|
|
|
|
|
+ mqttClient.setCallback(mqttCallback);
|
|
|
|
|
+
|
|
|
|
|
+ Serial.print("Connecting to MQTT broker");
|
|
|
|
|
+ int attempts = 0;
|
|
|
|
|
+ bool ledState = false;
|
|
|
|
|
+ while (!mqttClient.connected()) {
|
|
|
|
|
+ ledState = !ledState;
|
|
|
|
|
+ if (ledState) {
|
|
|
|
|
+ setOnly(RED_MAX, 0, 0);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setOnly(0, YELLOW_MAX, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (mqttClient.connect(MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD)) {
|
|
|
|
|
+ Serial.println();
|
|
|
|
|
+ Serial.println("MQTT connected.");
|
|
|
|
|
+
|
|
|
|
|
+ mqttClient.subscribe(MQTT_TOPIC);
|
|
|
|
|
+ Serial.print("Subscribed to: ");
|
|
|
|
|
+ Serial.println(MQTT_TOPIC);
|
|
|
|
|
+
|
|
|
|
|
+ allOff();
|
|
|
|
|
+ breathingGreen(3);
|
|
|
|
|
+ publishStatus();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Serial.print(".");
|
|
|
|
|
+ delay(500);
|
|
|
|
|
+ attempts++;
|
|
|
|
|
+ if (attempts > 60) {
|
|
|
|
|
+ Serial.println("\nMQTT connection failed!");
|
|
|
|
|
+ attempts = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void checkMQTTConnection() {
|
|
|
|
|
+ if (!mqttClient.connected()) {
|
|
|
|
|
+ Serial.println("MQTT disconnected. Reconnecting...");
|
|
|
|
|
+ connectMQTT();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+// =====================================================
|
|
|
|
|
+// 初始化
|
|
|
|
|
+// =====================================================
|
|
|
|
|
+
|
|
|
|
|
+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, LOW); // 初始状态关闭
|
|
|
|
|
+
|
|
|
|
|
+ allOff();
|
|
|
|
|
+
|
|
|
|
|
+ Serial.println();
|
|
|
|
|
+ Serial.println("Power on.");
|
|
|
|
|
+ Serial.println("Common anode MQTT enhanced version V2 (Red/Yellow + Yellow/Green swapped).");
|
|
|
|
|
+ Serial.print("Device name: ");
|
|
|
|
|
+ Serial.println(MQTT_CLIENT_ID);
|
|
|
|
|
+
|
|
|
|
|
+ currentMode = "init";
|
|
|
|
|
+ modeStart = millis();
|
|
|
|
|
+
|
|
|
|
|
+ connectWiFi();
|
|
|
|
|
+ connectMQTT();
|
|
|
|
|
+
|
|
|
|
|
+ setMode("traffic");
|
|
|
|
|
+
|
|
|
|
|
+ Serial.println("Supported modes:");
|
|
|
|
|
+ Serial.println("init / thinking / ai / busy / success / error / alarm / traffic / off / red / yellow / green");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+// =====================================================
|
|
|
|
|
+// 主循环
|
|
|
|
|
+// =====================================================
|
|
|
|
|
+
|
|
|
|
|
+void loop() {
|
|
|
|
|
+ updateWifiLed();
|
|
|
|
|
+ checkMQTTConnection();
|
|
|
|
|
+ mqttClient.loop();
|
|
|
|
|
+
|
|
|
|
|
+ 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);
|
|
|
|
|
+}
|