基于ESP32的蓝牙红外遥控与继电器智能家居改造方案

📅 2026/8/19 2:31:24
基于ESP32的蓝牙红外遥控与继电器智能家居改造方案
1. 项目缘起当智能家居遇上“老古董”几年前我还在为家里的空调、风扇和电视遥控器头疼。客厅里堆着五六个遥控器每次想开个空调都得先“寻宝”。更别提那些用了十几年的老式空调压根不支持Wi-Fi想接入智能家居系统简直是天方夜谭。当时市面上主流的智能家居方案要么是Wi-Fi插座要么是Zigbee网关但对于红外遥控设备要么需要购买昂贵的万能红外网关要么就得自己动手改造门槛不低。直到我开始接触ESP32这颗“神U”。它集成了Wi-Fi和蓝牙双模无线价格却只有一杯咖啡钱。一个想法冒了出来能不能用ESP32做一个“万能翻译官”让它通过蓝牙接收手机App的指令然后通过红外发射管“冒充”成原来的遥控器去控制那些老电器更进一步既然ESP32的GPIO引脚驱动能力不错何不直接集成一个继电器模块用来控制电灯、插座这类非红外设备这样一个设备就同时解决了红外遥控和物理开关的智能化问题。这就是“ESP32蓝牙家居自动化与红外遥控继电器”项目的初衷。它不追求大而全的智能家居生态而是瞄准了一个非常具体的痛点用极低的成本和一定的动手能力让家里那些“老古董”电器也能跟上智能化的步伐。整个系统的核心逻辑非常清晰手机蓝牙 - ESP32控制中枢 - 红外发射管控制空调/电视等或继电器控制灯/插座等。下面我就把这个项目的完整实现过程、踩过的坑以及一些优化思路分享给大家。2. 核心硬件选型与电路设计解析硬件是项目的骨架选型决定了项目的稳定性、成本和扩展性。这里我们追求的是在可靠的前提下实现最高的性价比和最简单的接线。2.1 主控芯片为什么是ESP32ESP32系列芯片是此项目的不二之选原因有三点双模无线集成ESP32原生支持蓝牙包括经典蓝牙和低功耗蓝牙BLE和Wi-Fi。本项目使用蓝牙作为控制通道避免了复杂的Wi-Fi配网过程手机App可以像连接蓝牙耳机一样直接连接对用户更友好。同时保留的Wi-Fi能力为未来升级如接入局域网留出了可能。强大的处理能力与丰富外设双核处理器、充足的SRAM和Flash足以流畅运行红外编解码逻辑和蓝牙协议栈。更重要的是它拥有多个GPIO、硬件脉冲计数器、以及灵活的定时器这些对于精确生成38kHz红外载波信号至关重要。极低的成本与活跃的社区一片ESP32开发板如ESP32-DevKitC价格仅在20-40元人民币但功能远超普通的Arduino。其背后庞大的开源社区Arduino core for ESP32, ESP-IDF提供了海量的库和教程开发中遇到的绝大多数问题都能找到答案。注意ESP32型号众多推荐选择带有外部天线接口的型号如ESP32-WROOM-32E信号更稳定。如果空间有限也可以选择ESP32-S系列等模组但需要自行设计PCB或使用转接板。2.2 红外发射部分从协议到硬件红外遥控的核心是协议。常见的协议有NEC、RC5、Sony SIRC等。我们需要一个库来帮助我们编码和发射这些信号。软件库选择IRremoteESP8266库是首选。虽然名字里有8266但它对ESP32的支持非常完善。它支持数十种红外协议并且提供了学习接收并解析和发射功能。硬件电路红外发射管IR LED普通5mm红外发射管即可。关键参数是波长通常为940nm这与绝大多数家电遥控器一致。驱动电路ESP32的GPIO引脚输出电流有限通常最大40mA而为了让红外信号有足够的发射距离和角度需要让IR LED工作在更大的瞬时电流下可达100mA以上。因此必须使用三极管如常见的8050 NPN三极管进行驱动。限流电阻为了防止电流过大烧毁LED或三极管需要在电路中串联一个限流电阻。阻值需要计算假设电源电压为3.3V红外LED正向压降约为1.2V期望电流为100mA。那么电阻R (3.3V - 1.2V) / 0.1A 21欧姆。我们可以选择一个接近的标称电阻如22欧姆或33欧姆。电阻功率 P I²R (0.1)² * 22 0.22W因此选用1/4W0.25W的电阻足够。参考电路图ESP32 GPIO (如 GPIO4) - 电阻 (220Ω-1kΩ基极限流) - NPN三极管基极(B) 三极管发射极(E) - GND 三极管集电极(C) - 红外LED阳极 红外LED阴极 - 限流电阻 (22Ω) - 3.3V这里用了两个电阻。连接GPIO的基极限流电阻R1用于控制基极电流通常1kΩ即可。集电极回路中的电阻R2是上面计算出的22Ω限流电阻。2.3 继电器控制部分安全隔离驱动继电器用于控制220V市电安全是第一要务。必须实现控制端ESP323.3V与被控端市电220V的电气隔离。继电器模块选型强烈建议购买现成的光耦隔离继电器模块如单路SRD-05VDC-SL-C。这种模块通常具有以下特点输入侧为低电平触发或高电平可跳线选择兼容3.3V/5V逻辑。内置光耦和晶体管驱动电路只需一个GPIO即可控制。输出侧为常开NO、常闭NC、公共端COM接口额定电流10A足以控制大多数灯具和小功率电器。连接方式非常简单。ESP32的某个GPIO如GPIO5连接继电器模块的“IN”引脚。继电器模块的VCC和GND分别连接ESP32的3.3V和GND。被控电器的火线切断后一端接COM一端接NO。重要安全警告操作220V电路部分时务必确保总闸关闭并使用绝缘工具。如果不熟悉强电操作请务必在有经验的人员指导下进行或仅用继电器控制低压直流电路进行功能测试。继电器的输出端绝不能接触ESP32或其他低压部分的任何引脚。2.4 电源设计稳定压倒一切整个系统的电源需求需要计算ESP32工作电流约80-150mA峰值可能更高。继电器吸合瞬间线圈电流较大约70-100mA。红外发射管瞬时电流约100mA。即使继电器和红外不同时工作总峰值电流也可能超过300mA。因此一个能提供5V/1A或5V/2A的USB电源适配器是可靠的基础。我们需要一个降压模块将5V转为3.3V给ESP32供电。方案一简单使用AMS1117-3.3等线性稳压模块。优点是电路简单、便宜。缺点是当输入输出压差大时5V-3.3V效率低、发热大如果总电流大需要加散热片。方案二推荐使用DC-DC降压模块如MP1584EN、LM2596。效率高通常90%发热小能提供更稳定的电流。这是更稳妥的选择。最终供电链路220V市电 - 5V/2A USB适配器 - DC-DC降压模块输出3.3V- ESP32的3.3V引脚。继电器模块可以直接接在5V电源上注意其输入电压规格常见为5V。3. 软件架构与核心代码实现软件部分需要实现蓝牙通信、协议解析、红外发射和继电器控制。我们将使用Arduino框架进行开发因为它库丰富、生态好。3.1 开发环境搭建与库安装安装Arduino IDE或VS Code with PlatformIO。在开发板管理器中添加ESP32支持。URL一般为https://espressif.github.io/arduino-esp32/package_esp32_index.json安装必备库IRremoteESP8266by Sebastien Warin (用于红外)ESP32 BLE Arduinoby nkolban (用于蓝牙低功耗本例使用BLE)3.2 蓝牙通信协议设计我们采用BLE低功耗蓝牙而非经典蓝牙因为BLE更省电连接和通信协议也更适合物联网设备。我们需要定义一个简单的自定义服务Service和特征值Characteristic来收发数据。设计思路服务UUID定义一个唯一的UUID例如4fafc201-1fb5-459e-8fcc-c5c9c331914b。控制特征值 (TX)设备接收手机指令的特征。属性为WRITE或WRITE_NR。手机向这个特征写入指令。状态特征值 (RX)设备向手机反馈状态的特征。属性为NOTIFY。当继电器状态改变或需要回复时设备通过此特征通知手机。指令格式设计示例可自定义 为了简单我们设计一个字符串指令。例如IR:NEC:0xFF00FF- 发送NEC协议的红外码数据为0xFF00FF。REL:1:ON- 控制第1路继电器打开。REL:1:OFF- 控制第1路继电器关闭。STATE?- 手机查询当前所有继电器状态。3.3 核心代码片段剖析以下是核心功能代码的整合与讲解。#include BLEDevice.h #include BLEServer.h #include BLE2902.h #include IRremoteESP8266.h #include IRsend.h // 硬件引脚定义 #define IR_LED_PIN 4 // 红外发射管连接的GPIO #define RELAY1_PIN 5 // 继电器1控制引脚 #define RELAY2_PIN 18 // 继电器2控制引脚如果有多路 // 蓝牙UUID定义 #define SERVICE_UUID 4fafc201-1fb5-459e-8fcc-c5c9c331914b #define CHARACTERISTIC_UUID_RX beb5483e-36e1-4688-b7f5-ea07361b26a8 // 手机写设备收 #define CHARACTERISTIC_UUID_TX beb5483e-36e1-4688-b7f5-ea07361b26a9 // 设备通知手机读 // 全局对象 IRsend irsend(IR_LED_PIN); BLEServer *pServer; BLEService *pService; BLECharacteristic *pTxCharacteristic; BLECharacteristic *pRxCharacteristic; bool deviceConnected false; bool oldDeviceConnected false; // 继电器状态 bool relay1State false; bool relay2State false; // 蓝牙回调类处理手机写入的数据 class MyCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { std::string rxValue pCharacteristic-getValue(); if (rxValue.length() 0) { Serial.print(Received: ); Serial.println(rxValue.c_str()); // 解析并执行指令 parseAndExecuteCommand(rxValue); } } }; // 蓝牙连接状态回调 class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected true; Serial.println(Device Connected); }; void onDisconnect(BLEServer* pServer) { deviceConnected false; Serial.println(Device Disconnected); // 蓝牙断开后可以重启广播以便重新连接 pServer-getAdvertising()-start(); } }; void setup() { Serial.begin(115200); pinMode(RELAY1_PIN, OUTPUT); digitalWrite(RELAY1_PIN, HIGH); // 假设继电器模块高电平断开 pinMode(RELAY2_PIN, OUTPUT); digitalWrite(RELAY2_PIN, HIGH); irsend.begin(); // 初始化BLE BLEDevice::init(ESP32-Smart-Relay); // 蓝牙设备名称 pServer BLEDevice::createServer(); pServer-setCallbacks(new MyServerCallbacks()); pService pServer-createService(SERVICE_UUID); // 创建TX特征通知 pTxCharacteristic pService-createCharacteristic( CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY ); pTxCharacteristic-addDescriptor(new BLE2902()); // 创建RX特征写 pRxCharacteristic pService-createCharacteristic( CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE ); pRxCharacteristic-setCallbacks(new MyCallbacks()); pService-start(); pServer-getAdvertising()-start(); Serial.println(Waiting for a client connection...); } void loop() { // 处理连接状态变化 if (!deviceConnected oldDeviceConnected) { delay(500); // 给蓝牙栈一个缓冲时间 oldDeviceConnected deviceConnected; } if (deviceConnected !oldDeviceConnected) { oldDeviceConnected deviceConnected; } // 这里可以添加其他需要循环执行的任务 delay(10); } // 指令解析与执行函数核心逻辑 void parseAndExecuteCommand(std::string cmd) { // 简单分割指令实际应用建议使用更健壮的解析方法 if (cmd.find(IR:) 0) { // 红外指令格式 IR:PROTOCOL:DATA int firstColon cmd.find(:); int secondColon cmd.find(:, firstColon 1); std::string protocol cmd.substr(firstColon 1, secondColon - firstColon - 1); std::string dataStr cmd.substr(secondColon 1); uint64_t data strtoull(dataStr.c_str(), NULL, 16); // 16进制字符串转数字 if (protocol NEC) { irsend.sendNEC(data, 32); // 发送NEC码32位 Serial.printf(Sent NEC IR code: 0x%llX\n, data); } else if (protocol RAW) { // 处理原始红外信号通过学习得到这里需要解析更复杂的数据结构 // 例如IR:RAW:len,freq,data1,data2,... } // 可以添加更多协议支持... } else if (cmd.find(REL:) 0) { // 继电器指令格式 REL:CHANNEL:ACTION int firstColon cmd.find(:); int secondColon cmd.find(:, firstColon 1); int channel atoi(cmd.substr(firstColon 1, 1).c_str()); // 通道号 std::string action cmd.substr(secondColon 1); int pin; bool *state; if (channel 1) { pin RELAY1_PIN; state relay1State; } else if (channel 2) { pin RELAY2_PIN; state relay2State; } else { return; } if (action ON) { digitalWrite(pin, LOW); // 假设低电平触发继电器吸合 *state true; Serial.printf(Relay %d ON\n, channel); notifyState(); // 通知手机状态已更新 } else if (action OFF) { digitalWrite(pin, HIGH); *state false; Serial.printf(Relay %d OFF\n, channel); notifyState(); } else if (action TOGGLE) { *state !(*state); digitalWrite(pin, *state ? LOW : HIGH); Serial.printf(Relay %d TOGGLED to %s\n, channel, *state ? ON : OFF); notifyState(); } } else if (cmd STATE?) { notifyState(); // 主动查询状态 } } // 通过BLE通知手机当前状态 void notifyState() { if (!deviceConnected) return; char stateMsg[50]; sprintf(stateMsg, STATE:1:%s,2:%s, relay1State ? ON : OFF, relay2State ? ON : OFF); pTxCharacteristic-setValue(stateMsg); pTxCharacteristic-notify(); Serial.printf(Notified: %s\n, stateMsg); }代码关键点解析蓝牙初始化创建了带有自定义UUID的服务和两个特征值。一个用于接收RX一个用于发送通知TX。指令解析parseAndExecuteCommand函数是大脑。它根据指令前缀IR:、REL:调用不同的函数。这里使用了简单的字符串查找和分割对于复杂指令建议使用sscanf或封装成JSON格式。红外发射调用irsend.sendNEC()等函数。需要提前知道被控设备的红外协议和码值。如何获取这就需要用到红外“学习”功能。状态同步notifyState()函数将当前继电器状态格式化为字符串并通过BLE的notify机制主动推送给已连接的手机App保证了手机端状态显示的实时性。4. 红外码学习与设备配对的实战流程让ESP32学会控制你家空调的关键是获取正确的红外协议和码值。我们需要给ESP32增加一个“红外接收头”VS1838B等让它能“听懂”原装遥控器发出的信号。4.1 增加红外接收电路硬件上在ESP32上找一个空闲的GPIO如GPIO14连接一个红外接收头。接收头有三只脚VCC接3.3V、GND、OUT接GPIO14。注意有些接收头需要5V供电请查阅数据手册。4.2 编写红外学习代码利用IRremoteESP8266库的接收示例我们可以很容易地获取红外原始数据。#include IRrecv.h #include IRremoteESP8266.h #include IRutils.h #define RECV_PIN 14 IRrecv irrecv(RECV_PIN); decode_results results; void setup() { Serial.begin(115200); irrecv.enableIRIn(); Serial.println(Ready to receive IR signal...); } void loop() { if (irrecv.decode(results)) { // 将解码结果以多种格式打印出来 serialPrintUint64(results.value, HEX); Serial.print( (); Serial.print(results.bits, DEC); Serial.println( bits)); Serial.print(Protocol: ); Serial.println(typeToString(results.decode_type).c_str()); Serial.print(Raw data: ); Serial.println(resultToHexidecimal(results).c_str()); Serial.println(); // 空行 irrecv.resume(); // 准备接收下一个信号 } delay(100); }将这段代码烧录到ESP32打开串口监视器。用原装空调遥控器对准接收头按下按钮你会在串口看到类似这样的输出FF00FF (32 bits) Protocol: NEC Raw data: 0xFF00FF这里NEC就是协议0xFF00FF就是该按键的码值。把每个按键的协议和码值都记录下来。4.3 创建设备控制配置文件不建议把每个设备的码值硬编码在主程序里。更好的做法是创建一个配置文件例如一个头文件或JSON字符串方便管理多个设备。// devices.h #ifndef DEVICES_H #define DEVICES_H #include Arduino.h struct IRCommand { const char* name; // 如 ac_power const char* protocol; // 如 NEC uint64_t code; // 如 0xFF00FF uint16_t bits; // 如 32 }; const IRCommand livingRoomAC[] { {power, NEC, 0xFF00FF, 32}, {temp_up, NEC, 0xFE01FE, 32}, {temp_down, NEC, 0xFD02FD, 32}, {mode_cool, NEC, 0xFB04FB, 32}, // ... 更多按键 }; const int livingRoomACSize sizeof(livingRoomAC) / sizeof(livingRoomAC[0]); #endif然后在主程序中手机App可以发送更高级的指令如DEVICE:AC_CMD:powerESP32收到后在livingRoomAC数组中查找名为power的命令获取其协议和码值并发送。这样手机App的界面就可以做得非常友好直接显示“开关”、“升温”、“降温”等按钮。5. 手机App端开发思路与连接逻辑ESP32端准备好后我们需要一个手机App作为控制终端。对于个人项目使用现成的BLE调试App如nRF Connect可以测试基本功能但体验不佳。要获得好体验需要自己开发一个简单的App。5.1 跨平台方案选择Flutter一套代码可编译成Android和iOS应用开发效率高。有成熟的BLE插件如flutter_blue_plus。React Native同样跨平台使用JavaScript。有react-native-ble-plx等库。原生开发Android使用BluetoothGATTAPIiOS使用CoreBluetooth框架。性能最佳但需要维护两套代码。对于爱好者我推荐Flutter学习曲线相对平缓生态活跃。5.2 App核心功能与逻辑一个最基本的App需要实现以下流程扫描与连接扫描周围名为ESP32-Smart-Relay的BLE设备发起连接。服务与特征值发现连接成功后发现我们自定义的服务UUID和特征值UUID。指令发送用户点击“开灯”按钮App向CHARACTERISTIC_UUID_RX特征写入字符串REL:1:ON。状态接收与显示订阅CHARACTERISTIC_UUID_TX特征的通知。当ESP32状态改变如继电器动作或响应查询时会发送通知App收到后解析STATE:1:ON,2:OFF这样的字符串并更新UI上的开关状态。红外控制界面可以做一个虚拟遥控器界面按钮对应不同的红外指令。点击“空调开关”按钮发送IR:NEC:0xFF00FF。5.3 状态同步的难点与解决一个常见的坑是状态不同步。比如有人手动按了物理开关灯亮了但App上显示还是关闭状态。解决方案硬件反馈最佳但复杂在继电器输出端并联一个电压检测电路或使用光耦将实际负载状态反馈给ESP32的另一个GPIO。ESP32可以定时或状态变化时读取这个GPIO并主动通过BLE通知App。软件同步实用每次App从后台切换到前台或连接建立时自动发送一次STATE?查询指令。ESP32返回当前所有继电器状态。虽然不能实时但保证了操作前状态的相对准确。用户手动同步在App界面提供一个“刷新”按钮。在资源有限的情况下方案2是性价比最高的选择。6. 系统集成、调试与进阶优化当硬件焊接完毕代码分别烧录到ESP32和手机App初步开发完成后就进入了集成调试阶段。6.1 上电与初步测试安全第一首次上电先不要连接220V强电。用一个小台灯低压直流或者LED接在继电器输出端进行测试。蓝牙连接测试给ESP32上电打开手机蓝牙和开发好的App或nRF Connect搜索设备应该能看到ESP32-Smart-Relay尝试连接并发现服务/特征值。继电器控制测试在App或调试工具中向RX特征写入REL:1:ON应听到继电器清晰的“咔哒”吸合声同时连接的台灯点亮。写入OFF则熄灭。红外学习测试使用4.2节的代码确认ESP32能正确接收并解码遥控器信号记录下所有需要的码值。红外发射测试将主控代码烧录回去发送IR:NEC:0xFF00FF指令用手机摄像头普通摄像头能看到红外光对准红外发射管按下发送指令时应能看到发射管发出微弱的紫白色光。最好能用原设备空调测试是否有效。6.2 常见问题与排查蓝牙连接不稳定或无法连接检查供电电源功率不足会导致ESP32在射频工作时崩溃。确保使用5V/2A以上的电源且DC-DC模块输出稳定。检查天线如果使用板载PCB天线确保周围没有金属物体遮挡。尝试调整设备方向。代码排查检查蓝牙名称、UUID是否与App端匹配。确保没有在loop()中执行长时间阻塞的操作影响蓝牙栈运行。红外控制无效发射管方向与距离红外发射管有方向性需对准设备的红外接收窗。初始测试距离建议在1米内。驱动电流不足检查三极管驱动电路测量红外LED两端在发射时的电压和电流。确保限流电阻阻值合适电流足够50mA。协议或码值错误用手机摄像头观察发射管如果发送时亮光很微弱可能是电流问题如果亮光正常但设备没反应极大概率是协议或码值不对。用学习代码再次确认。环境光干扰强烈的日光或白炽灯可能包含红外光谱干扰信号。尝试在稍暗的环境测试。继电器状态反馈异常逻辑电平反了确认继电器模块是高电平触发还是低电平触发。修改代码中digitalWrite的HIGH/LOW。App解析错误检查ESP32发送的状态字符串格式与App端解析逻辑是否完全一致。6.3 进阶优化思路一个基础版本完成后可以考虑以下优化让项目更实用、更强大Wi-Fi双模与Web控制利用ESP32的Wi-Fi功能让设备接入家庭局域网。可以创建一个简单的Web服务器使用AsyncWebServer库通过浏览器进行控制。甚至可以集成MQTT接入Home Assistant等开源智能家居平台实现语音控制和自动化场景。离线定时与自动化在ESP32端实现简单的RTC定时功能。例如可以编程让它在晚上10点自动关闭客厅灯。这需要处理ESP32的深度睡眠与定时唤醒或使用外部RTC模块。多设备管理与场景扩展协议支持一个App控制多个ESP32设备。并可以定义“场景”如发送一条“电影模式”指令让ESP32依次执行“关大灯”、“开氛围灯”、“打开投影仪红外”等操作。功耗优化如果使用电池供电需要大幅优化。控制继电器后让ESP32进入深度睡眠通过外部中断如干簧管、按钮唤醒。蓝牙广播也可以设置为低功耗间隔模式。外壳与安全为整个系统设计一个3D打印或购买一个塑料外壳将高压220V部分完全封闭确保使用安全。外壳上留出红外发射管的透明窗口和状态指示灯孔。这个项目从想法到实现涉及了嵌入式硬件、电路设计、单片机编程、无线通信和移动开发多个领域是一个非常好的全栈式物联网入门实践。它最大的魅力在于你用很低的成本创造了一个实实在在能解决生活问题的智能设备。当你第一次用手机成功打开那台老空调时那种成就感是无与伦比的。希望这份详细的指南能帮你少走弯路顺利打造属于自己的智能家居小装置。