| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677 |
- #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);
- }
|