/* * ESP32-S3 万能红外遥控器 * 功能:学习并存储多帧红外信号,发送时依次发送协议和RAW * 支持断电保存、多帧处理、RMT硬件时序 */ #define BLINKER_PRINT Serial #define BLINKER_WIFI #include #include #include #include #include #include #include #include #include #include #include #include #include // ==================== 引脚定义 ==================== const uint16_t IR_RX = 4; const uint16_t IR_TX = 5; #define SDA_PIN 8 #define SCL_PIN 9 // ==================== 红外对象 ==================== IRrecv irrecv(IR_RX, 1024, 50, true); IRsend irsend(IR_TX); IRac ac(IR_TX); // ==================== SHT31 ==================== Adafruit_SHT31 sht31 = Adafruit_SHT31(); // ==================== Blinker ==================== char auth[] = "xxxxxxxx"; BlinkerNumber HUMI("num_humi"); BlinkerNumber TEMP("num_temp"); BlinkerButton btnLearn("btn-learn"); BlinkerButton btnSend("btn-send"); // 发送学习到的命令 BlinkerButton btnReset("btn-reset"); WiFiManager wm; Preferences preferences; // ==================== 数据结构 ==================== #define MAX_RAW_LEN 512 // 单帧RAW最大长度 struct IRFrame { decode_type_t protocol; // 协议类型 uint64_t value; // 解码值 uint16_t rawLen; // RAW数据长度 uint16_t rawBuf[MAX_RAW_LEN]; // RAW数据 }; struct IRCommand { std::vector frames; // 多帧 }; IRCommand learnedCmd; bool isLearning = false; unsigned long learnStartTime = 0; const unsigned long LEARN_TIMEOUT = 30000; // ==================== 存储函数 ==================== void saveCommand(const IRCommand &cmd) { preferences.begin("ir", false); preferences.putUInt("frameCount", cmd.frames.size()); for (size_t i = 0; i < cmd.frames.size(); i++) { String keyBase = "f" + String(i); preferences.putChar((keyBase + "p").c_str(), (char)cmd.frames[i].protocol); preferences.putUInt((keyBase + "v").c_str(), (uint32_t)cmd.frames[i].value); preferences.putUShort((keyBase + "l").c_str(), cmd.frames[i].rawLen); if (cmd.frames[i].rawLen > 0) { preferences.putBytes((keyBase + "b").c_str(), cmd.frames[i].rawBuf, sizeof(uint16_t) * cmd.frames[i].rawLen); } } preferences.end(); Serial.println("✅ 红外指令已保存到Preferences"); } bool loadCommand(IRCommand &cmd) { preferences.begin("ir", true); uint32_t count = preferences.getUInt("frameCount", 0); if (count == 0) { preferences.end(); return false; } cmd.frames.clear(); for (uint32_t i = 0; i < count; i++) { String keyBase = "f" + String(i); IRFrame frame; frame.protocol = (decode_type_t)preferences.getChar((keyBase + "p").c_str(), 0); frame.value = preferences.getUInt((keyBase + "v").c_str(), 0); frame.rawLen = preferences.getUShort((keyBase + "l").c_str(), 0); if (frame.rawLen > 0 && frame.rawLen <= MAX_RAW_LEN) { preferences.getBytes((keyBase + "b").c_str(), frame.rawBuf, sizeof(uint16_t) * frame.rawLen); } cmd.frames.push_back(frame); } preferences.end(); Serial.printf("✅ 已加载 %d 帧红外数据\n", cmd.frames.size()); return true; } // ==================== 学习函数 ==================== void startLearning() { isLearning = true; learnedCmd.frames.clear(); irrecv.enableIRIn(); learnStartTime = millis(); Serial.println("📖 进入学习模式,请按遥控器按键(支持多帧)"); Blinker.notify("进入学习模式,请按键"); } void stopLearning() { isLearning = false; irrecv.disableIRIn(); if (learnedCmd.frames.empty()) { Serial.println("⚠️ 未捕获任何信号"); Blinker.notify("学习失败:无信号"); return; } saveCommand(learnedCmd); Serial.printf("✅ 学习完成,共捕获 %d 帧\n", learnedCmd.frames.size()); Blinker.notify("学习成功"); } void captureIR() { if (!isLearning) return; // 连续捕获200ms内的所有帧 unsigned long start = millis(); while (millis() - start < 200) { decode_results results; if (irrecv.decode(&results)) { IRFrame frame; frame.protocol = results.decode_type; frame.value = results.value; frame.rawLen = results.rawlen; if (frame.rawLen > MAX_RAW_LEN) frame.rawLen = MAX_RAW_LEN; memcpy(frame.rawBuf, results.rawbuf, frame.rawLen * sizeof(uint16_t)); learnedCmd.frames.push_back(frame); Serial.printf("📌 捕获帧:协议=%d, 值=0x%08llX, 长度=%d\n", frame.protocol, frame.value, frame.rawLen); irrecv.resume(); } delay(10); } } // ==================== 发送函数(双保险:协议 + RAW) ==================== void sendCommand(const IRCommand &cmd) { if (cmd.frames.empty()) { Serial.println("❌ 没有可发送的命令"); Blinker.notify("无命令"); return; } for (size_t i = 0; i < cmd.frames.size(); i++) { const IRFrame &frame = cmd.frames[i]; Serial.printf("📤 发送帧 %d:协议=%d, 值=0x%08llX, RAW长度=%d\n", i, frame.protocol, frame.value, frame.rawLen); // 1. 先尝试用协议发送 bool sentProtocol = false; if (frame.protocol != UNKNOWN && frame.protocol != -1) { // 使用 IRac 或直接发送 stdAc::state_t state; state.protocol = frame.protocol; state.power = true; state.mode = stdAc::opmode_t::kCool; state.degrees = 24; state.fanspeed = stdAc::fanspeed_t::kMedium; // 注意:这里只能发固定参数,若要精确控制温度等,需额外学习 // 更通用的做法:直接调用 send* 函数 // 以下尝试多种方式 switch (frame.protocol) { case NEC: case NEC_LIKE: irsend.sendNEC(frame.value, 32); sentProtocol = true; break; case COOLIX: irsend.sendCOOLIX(frame.value); sentProtocol = true; break; case BOSCH144: irsend.sendBosch144(frame.rawBuf, frame.rawLen, 0); sentProtocol = true; break; // 可根据需要添加更多协议 default: // 未知协议,跳过 break; } if (sentProtocol) Serial.println(" ✅ 协议发送成功"); } delay(200); // 间隔200ms,避免冲突 // 2. 发送 RAW 波形(无论协议是否成功都发) if (frame.rawLen > 0) { irsend.sendRaw(frame.rawBuf, frame.rawLen, 38); Serial.println(" ✅ RAW发送完成"); } delay(200); // 帧间延迟 } Blinker.notify("发送完成"); } // ==================== Blinker 回调 ==================== void btnLearn_callback(const String &state) { if (state == "tap") { if (!isLearning) startLearning(); else { stopLearning(); btnLearn.color("#FF0000"); btnLearn.text("学习"); btnLearn.print(); } } } void btnSend_callback(const String &state) { if (state == "tap") { IRCommand cmd; if (loadCommand(cmd)) { sendCommand(cmd); } else { Blinker.notify("未学习任何指令"); } } } void btnReset_callback(const String &state) { if (state == "tap") { preferences.begin("ir", false); preferences.clear(); preferences.end(); Blinker.notify("已清除所有红外数据"); Serial.println("🗑️ 已清除红外数据"); } } // ==================== SHT31 ==================== void heartbeat() { float temp = sht31.readTemperature(); float humi = sht31.readHumidity(); if (!isnan(temp) && !isnan(humi)) { TEMP.print(temp); HUMI.print(humi); } } // ==================== Setup ==================== void setup() { Serial.begin(115200); delay(100); Serial.println("\n=== ESP32-S3 万能红外遥控器 (双保险发送) ==="); // WiFiManager wm.setConfigPortalTimeout(120); String apName = "ESP_AP_" + String((uint32_t)ESP.getEfuseMac(), HEX); if (!wm.autoConnect(apName.c_str(), "12345678")) { ESP.restart(); } Serial.println("WiFi IP: " + WiFi.localIP().toString()); // 红外 irsend.begin(); irrecv.enableIRIn(); // SHT31 Wire.begin(SDA_PIN, SCL_PIN); if (!sht31.begin(0x44)) sht31.begin(0x45); // Blinker btnLearn.attach(btnLearn_callback); btnSend.attach(btnSend_callback); btnReset.attach(btnReset_callback); btnLearn.color("#FF0000"); btnLearn.text("学习"); btnSend.color("#00FF00"); btnSend.text("发送"); btnReset.color("#FFA500"); btnReset.text("清除"); Blinker.begin(auth, WiFi.SSID().c_str(), WiFi.psk().c_str()); Blinker.attachHeartbeat(heartbeat); // 加载已有指令(用于验证) IRCommand temp; if (loadCommand(temp)) { Serial.printf("📂 已加载 %d 帧数据\n", temp.frames.size()); } Serial.println("✅ 系统就绪"); Serial.println("操作:点击“学习”进入学习模式,按遥控器键,再点击“学习”结束"); Serial.println("点击“发送”将按顺序发送协议+RAW"); } // ==================== Loop ==================== void loop() { Blinker.run(); if (isLearning) { captureIR(); if (millis() - learnStartTime > LEARN_TIMEOUT) { Serial.println("⏰ 学习超时,自动退出"); stopLearning(); btnLearn.color("#FF0000"); btnLearn.text("学习"); btnLearn.print(); } } // 检查Blinker连接状态(简化) static unsigned long lastCheck = 0; if (millis() - lastCheck > 10000) { lastCheck = millis(); if (!Blinker.connected()) { Serial.println("⚠️ Blinker离线"); } } delay(10); }