基于ESP32的蓝牙红外智能家居中枢:自制万能遥控与电器控制方案
2026/8/19 3:09:55 网站建设 项目流程

1. 项目概述:一个融合蓝牙与红外的智能家居中枢

如果你手头有几块ESP32开发板,又对智能家居改造跃跃欲试,那么这个项目绝对值得一试。它不是一个简单的开关控制,而是一个集成了蓝牙无线控制和红外学习/发射能力的“万能遥控”中枢。核心思路是让ESP32扮演一个“翻译官”和“指挥官”的角色:一方面,通过手机蓝牙App发送指令,ESP32接收到后驱动继电器,直接控制你家里的灯具、风扇等电器;另一方面,ESP32还能学习和存储传统红外遥控器的信号,让你用手机就能遥控空调、电视、机顶盒这些老式红外设备。这样一来,你既实现了对新设备(或改造后设备)的无线控制,又统一了对旧有红外家电的管理入口,用一个低成本的自制设备,就搭建起一个覆盖有线电器和无线红外设备的混合控制中心。

这个项目的魅力在于它的实用性和可扩展性。你不需要更换家里任何一个现有的插座或电器,只需要将继电器模块串联到电器的供电线路中,就能实现手机控制开关。对于红外设备,更是做到了“无侵入式”集成。无论是科技爱好者想动手实践物联网,还是普通用户想低成本实现家居智能化,这个方案都提供了一个清晰、安全且极具成就感的实现路径。接下来,我将拆解整个项目的设计思路、硬件选型、代码实现以及那些只有实际动手才会遇到的“坑”。

2. 核心硬件选型与电路设计解析

一个稳定可靠的项目,硬件基础是关键。这里的选型不仅要考虑功能实现,更要高度重视用电安全,尤其是涉及220V市电控制的部分。

2.1 主控芯片:为什么是ESP32?

ESP32几乎是此类项目的首选,原因在于其极高的性价比和丰富的功能集成:

  • 双核处理器与充足内存:主频高达240MHz,拥有520KB SRAM和4MB Flash(常见开发板配置),足以流畅运行蓝牙协议栈、红外编解码逻辑以及网络服务(如需后续扩展Wi-Fi)。
  • 集成蓝牙与Wi-Fi:本项目主要使用其蓝牙功能(Bluetooth Classic或BLE均可),其蓝牙堆栈稳定,易于通过串口协议(SPP)与手机通信。集成的Wi-Fi为未来升级为双模控制(蓝牙+Wi-Fi/APP)留足了空间。
  • 丰富的GPIO与外设:我们需要GPIO来控制继电器,还需要一个GPIO来连接红外接收头,另一个连接红外发射管。ESP32的GPIO数量完全满足,且其内置的RMT(远程控制外设)模块是处理红外信号的“神器”,可以精准地发送和接收红外波形,比用普通GPIO模拟延时稳定得多。

注意:市面上ESP32开发板型号繁多,推荐使用像ESP32-DevKitC、NodeMCU-32S这类引脚引出完善、带有USB转串口芯片的版本,开发调试会方便很多。

2.2 继电器模块:安全控制的核心

继电器是连接弱电(ESP32的3.3V)和强电(220V市电)的桥梁,选择不当有安全隐患。

  • 类型选择:必须选择光耦隔离的继电器模块。光耦隔离意味着控制侧(ESP32)和被控侧(220V电路)在电气上是完全隔离的,ESP32的故障不会窜入市电,极大提高了安全性。模块上通常会标明“Opto-isolated”。
  • 通道与负载:根据你想控制的设备数量选择1路、2路、4路或8路继电器模块。注意查看继电器触点容量,常见的有10A 250VAC,这个参数表示每个继电器最大可以控制10安培电流、250伏交流电的负载,足以应对大多数灯具、风扇等设备。但如果是控制空调、热水器等大功率电器,务必选择更大容量(如16A、30A)的继电器或使用继电器驱动接触器。
  • 电平触发:确认模块是高电平触发还是低电平触发。大多数模块默认低电平触发(即控制引脚给低电平0V时继电器吸合)。这需要与代码中digitalWrite(pin, LOW)来触发相对应。有些模块可以通过跳线帽选择触发方式。

2.3 红外部分硬件:看不见的通信

红外部分负责学习和发射,需要两个关键元件:

  • 红外接收头:如VS1838B、HS0038。它们的作用是将38kHz载波的红外信号解调,输出原始的数字波形给ESP32。注意接收头有三个引脚:VCC(接3.3V)、GND、OUT(信号输出,接ESP32的某个GPIO)。
  • 红外发射管:普通940nm红外发射二极管即可。发射管需要串联一个限流电阻(通常100-200欧姆)后再连接到ESP32的GPIO,防止电流过大烧毁GPIO或发射管。一个GPIO的驱动能力可能有限,如果需要增强发射距离和角度,可以考虑使用三极管(如8050)构成简单放大电路来驱动发射管。

2.4 电路连接图与安全要点

下面是一个控制一路继电器和一个红外设备的简化接线示意(务必在断电情况下操作):

ESP32 GPIO连接目标说明
GPIO16继电器模块IN1控制继电器1,具体引脚可自定义
GPIO17红外接收头OUT接收红外信号
GPIO18红外发射管(经限流电阻)发射红外信号
3.3V继电器模块VCC, 红外接收头VCC为模块供电
GND继电器模块GND, 红外接收头GND, 红外发射管GND共地

安全警告与实操心得

  1. 强弱电分离:在焊接或连接导线时,确保继电器模块的高压端子(COM, NO, NC)部分与ESP32的低压电路部分在物理空间上隔离开,不要有任何导线交叉或触碰的风险。可以使用绝缘胶带或热缩管包裹所有高压连接点。
  2. 先接弱电,调试逻辑:永远先只连接ESP32、继电器模块(仅低压侧)、红外部分的电路,用手机蓝牙发送指令,测试继电器是否正常动作、红外学习发射是否正常。确认所有逻辑功能完全正确后,再断开电源,进行最后的强电连接
  3. 使用带绝缘外壳的插座:如果需要将整个系统装入一个盒子,务必使用绝缘良好的塑料外壳,并将继电器模块的高压端子部分用绝缘罩盖好。
  4. 断电操作:任何涉及220V线路的连接或改动,必须确保总闸关闭,并用电笔确认无电后再操作。

3. 软件架构与核心代码实现

我们将使用Arduino IDE进行开发,因为它对ESP32和常用库的支持非常友好。首先需要在Arduino IDE的开发板管理器中安装ESP32支持。

3.1 蓝牙通信协议设计

蓝牙部分我们采用经典的SPP(串口协议),这相当于在手机和ESP32之间虚拟了一条串口线,数据透传,简单可靠。在手机上,我们可以使用像“Serial Bluetooth Terminal”这样的App进行连接和发送指令。

我们需要设计一个简单高效的指令协议,例如:

  • RLY1_ON-> 打开继电器1
  • RLY1_OFF-> 关闭继电器1
  • IR_LEARN-> 进入红外学习模式,等待接收红外信号
  • IR_SEND,0-> 发射存储在位置0的红外编码

在代码中,我们需要监听蓝牙串口的数据,并解析这些指令。

#include "BluetoothSerial.h" BluetoothSerial SerialBT; const char* device_name = "ESP32_Smart_Relay"; // 蓝牙设备名称 void setup() { Serial.begin(115200); SerialBT.begin(device_name); // 初始化蓝牙 Serial.println("蓝牙设备已启动,等待连接..."); // ... 初始化GPIO等 } void loop() { // 检查蓝牙是否有数据可读 if (SerialBT.available()) { String command = SerialBT.readStringUntil('\n'); // 假设以换行符结束一条指令 command.trim(); // 去除首尾空白字符 Serial.println("收到指令: " + command); if (command == "RLY1_ON") { digitalWrite(RELAY_PIN_1, LOW); // 假设低电平触发 SerialBT.println("Relay 1 ON"); } else if (command == "RLY1_OFF") { digitalWrite(RELAY_PIN_1, HIGH); SerialBT.println("Relay 1 OFF"); } else if (command == "IR_LEARN") { enterIrLearnMode(); SerialBT.println("进入红外学习模式,请对准设备按下遥控器..."); } else if (command.startsWith("IR_SEND,")) { int index = command.substring(8).toInt(); // 提取索引号 sendIrSignal(index); SerialBT.println("红外信号已发射"); } } // ... 其他逻辑 }

3.2 红外信号的学习与存储

红外信号的学习是项目的难点之一。我们需要捕获红外接收头传来的原始波形,这个波形由一系列高低电平的持续时间组成(对应遥控器编码中的“0”和“1”)。ESP32的RMT外设可以完美地记录这些高精度时序。

我们将使用IRremoteESP8266库(它也支持ESP32),它封装了对RMT的操作,极大简化了流程。

#include <IRrecv.h> #include <IRremoteESP8266.h> #include <IRutils.h> const uint16_t kRecvPin = 17; // 红外接收头连接的GPIO IRrecv irrecv(kRecvPin); decode_results results; // 用于存储解码结果 // 用于存储学习到的原始数据 uint16_t rawData[RAWBUF]; // RAWBUF需要足够大,例如200 uint16_t rawLen = 0; void enterIrLearnMode() { irrecv.enableIRIn(); // 启动接收 Serial.println("等待红外信号..."); // 清空之前的缓冲区 rawLen = 0; // 循环等待,直到收到信号或超时 unsigned long startTime = millis(); while (millis() - startTime < 10000) { // 10秒超时 if (irrecv.decode(&results)) { Serial.println("信号已捕获!"); // 将原始时序数据复制到我们的数组中 rawLen = results.rawlen - 1; for (uint16_t i = 1; i < results.rawlen; i++) { // 注意:results.rawbuf[]存储的是以50us为单位的计数值 rawData[i-1] = results.rawbuf[i] * kRawTick; } irrecv.resume(); // 准备接收下一个信号 // 这里可以将rawData和rawLen存储到EEPROM或文件系统中,索引为0 saveIrDataToStorage(0, rawData, rawLen); break; } delay(50); } if (rawLen == 0) { Serial.println("学习超时,未收到信号。"); } }

实操心得:不同设备的红外协议(如NEC、RC5、Sony等)编码方式不同。IRremoteESP8266库虽然能自动解码很多协议,但有些小众或自定义协议可能解码失败。我们这里采用“原始数据学习法”,即不关心具体协议,只记录下高低电平的微秒时间长度。这种方法通用性最强,可以克隆绝大多数遥控器,但存储的数据量会稍大。

3.3 红外信号的精确发射

发射是学习的逆过程。我们需要将存储的原始时序数据,通过红外发射管,以38kHz的载波频率重新播放出来。RMT外设同样能精确地生成这些波形。

#include <IRsend.h> const uint16_t kIrLedPin = 18; // 红外发射管连接的GPIO IRsend irsend(kIrLedPin); void sendIrSignal(int index) { // 从存储中读取索引为index的原始数据 uint16_t *storedData; uint16_t storedLen; loadIrDataFromStorage(index, &storedData, &storedLen); if (storedLen > 0) { // 使用RMT发送原始数据 irsend.sendRaw(storedData, storedLen, 38); // 38代表38kHz载波频率 Serial.println("红外原始信号已发送"); delete[] storedData; // 释放内存 } else { Serial.println("错误:未找到该索引的红外数据"); } } void setup() { // ... irsend.begin(); // ... }

注意事项sendRaw函数要求传入的数组数据单位是微秒。我们在学习时从results.rawbuf提取的数据已经乘以kRawTick(通常为50)转换成了微秒,所以可以直接使用。确保学习和发射使用的载波频率一致(通常是38kHz)。

3.4 数据存储与电源管理

学习到的红外数据需要非易失性存储。ESP32的Preferences库(类似于键值对存储)或SPIFFS/LittleFS文件系统都是不错的选择。对于少量数据(几十个遥控器),Preferences更简单。

#include <Preferences.h> Preferences preferences; void saveIrDataToStorage(int index, uint16_t* data, uint16_t len) { preferences.begin("ir-storage", false); String key = "ir_" + String(index); // 将数据转换为字符串存储(简化示例,实际需处理二进制数据) String dataStr; for (int i=0; i<len; i++) { dataStr += String(data[i]) + ","; } preferences.putString(key.c_str(), dataStr); preferences.end(); } void loadIrDataFromStorage(int index, uint16_t** data, uint16_t* len) { preferences.begin("ir-storage", true); String key = "ir_" + String(index); String dataStr = preferences.getString(key.c_str(), ""); preferences.end(); // 解析字符串,还原为数组(此处为简化逻辑,实际应用需更健壮的解析) if (dataStr.length() > 0) { // ... 解析逻辑,计算长度*len,并为*data分配内存并填充 } else { *len = 0; } }

关于电源,如果设备需要7x24小时运行,建议使用可靠的5V/2A USB电源适配器为ESP32开发板供电。继电器模块的耗电在吸合瞬间较大,确保电源总功率足够。

4. 系统集成、调试与功能优化

当硬件连接完毕,核心代码模块测试通过后,就需要将它们整合成一个稳定、易用的系统。

4.1 状态指示与用户交互

一个好的项目需要有状态反馈。我们可以添加一个RGB LED或两个普通LED(如蓝色和红色)。

  • 蓝色LED:慢闪表示系统正常运行,等待蓝牙连接;常亮表示蓝牙已连接。
  • 红色LED:进入红外学习模式时快速闪烁;红外信号学习成功时亮起1秒后熄灭。
  • 串口打印:始终通过串口监视器输出详细的运行日志,这是调试的生命线。

在蓝牙指令解析中,除了执行动作,务必给手机App返回明确的确认信息,如OK:RLY1_ONERROR:IR_LEARN_TIMEOUT,这能极大提升用户体验和调试效率。

4.2 蓝牙连接稳定性处理

在实际环境中,蓝牙连接可能会意外断开。我们需要在代码中增加重连和状态维护机制。

bool btConnected = false; void loop() { // 检查蓝牙连接状态(部分蓝牙库提供此接口) if (!btConnected && SerialBT.hasClient()) { btConnected = true; Serial.println("蓝牙已连接"); setStatusLed(CONNECTED); // 设置状态灯为已连接 } else if (btConnected && !SerialBT.hasClient()) { btConnected = false; Serial.println("蓝牙已断开"); setStatusLed(DISCONNECTED); // 设置状态灯为未连接 } // 原有的指令处理逻辑... }

同时,手机App端也应具备自动重连和发送心跳包(如定期发送PING,ESP32回复PONG)的功能,以保持连接活跃。

4.3 红外学习的抗干扰与优化

红外学习环境可能存在干扰(如日光灯、其他红外源)。可以采取以下措施:

  1. 多次学习取平均:在enterIrLearnMode函数中,连续学习2-3次同一个按键的信号,对比每次的原始数据长度和关键时间点(如起始引导码),如果一致才确认学习成功,存储数据。
  2. 信号滤波:对于学习到的原始数据,可以过滤掉过短或过长的脉冲(可能是干扰),但需谨慎,以免破坏有效信号。
  3. 学习环境:尽量在室内光线稳定、远离其他红外设备的环境下进行学习,将遥控器对准接收头,距离10-20厘米为宜。

4.4 创建简单的手机控制界面

虽然可以使用通用的串口蓝牙终端App,但自制一个简单的App体验更好。对于安卓用户,可以使用MIT App Inventor这类图形化工具快速拖拽一个界面,包含:

  • 连接/断开蓝牙按钮。
  • 几个继电器开关按钮。
  • 一个“学习红外”按钮和一个下拉菜单(选择存储位置)。
  • 一个“发射红外”按钮。
  • 一个文本框显示ESP32返回的状态信息。

将按钮与发送对应的指令字符串绑定即可。这比记忆和输入命令字符串要直观得多。

5. 常见问题排查与进阶玩法

即使按照步骤操作,也可能会遇到一些问题。这里记录一些典型的排查点。

5.1 问题排查速查表

现象可能原因排查步骤
蓝牙搜索不到设备1. ESP32蓝牙未初始化成功
2. 设备名称冲突
3. 手机蓝牙问题
1. 检查串口日志,确认SerialBT.begin()执行且无报错。
2. 修改代码中的device_name,重启ESP32。
3. 重启手机蓝牙,或换一部手机测试。
蓝牙连接后发送指令无反应1. 指令格式错误
2. 代码指令解析部分有误
3. GPIO引脚配置错误
1. 确认手机App发送的指令末尾有换行符(\n)。
2. 在loop()中打印收到的原始字符串,检查是否被正确接收和解析。
3. 用digitalWrite和LED单独测试继电器控制引脚是否正常。
继电器不动作1. 供电不足
2. 触发电平弄反
3. 继电器模块损坏
1. 确保继电器模块VCC接3.3V或5V(视模块而定),GND共地。
2. 尝试将代码中的LOW改为HIGH,或检查模块上的触发电平跳线帽。
3. 短接模块的IN和GND,听是否有“咔嗒”声,判断继电器好坏。
红外学习失败1. 接收头引脚接错
2. 遥控器没电或不对准
3. 环境光干扰太强
4. 库不支持该协议
1. 用手机摄像头对准遥控器发射管,按按键看是否有白光闪烁,验证遥控器。
2. 检查接收头VCC、GND、OUT是否接对。
3. 拉上窗帘,在较暗环境下学习。
4. 尝试在代码中打印results.decode_type,看库是否识别出协议。
红外发射无效1. 发射管接反或损坏
2. 限流电阻过大
3. 存储的数据格式错误
1. 用手机摄像头对准发射管,发射时看是否有微弱白光(红外光手机可见)。
2. 减小限流电阻(不低于100欧姆)。
3. 对比学习时存储的数据和发射时读取的数据是否一致。

5.2 功能扩展与进阶思路

这个项目的基础框架具有很强的可扩展性:

  1. 接入Wi-Fi与MQTT:启用ESP32的Wi-Fi功能,连接家庭路由器,通过MQTT协议接入Home Assistant、Node-RED等开源智能家居平台。这样就能实现语音控制(通过天猫精灵/小爱同学)、远程控制、自动化场景联动(如“离家模式”关闭所有继电器)。
  2. 增加物理按键:在GPIO上连接几个轻触开关,实现本地手动控制,作为蓝牙控制失效时的备用方案。
  3. 功耗优化:如果使用电池供电,可以深度利用ESP32的睡眠模式,仅当蓝牙被连接或定时唤醒时才工作,大幅延长续航。
  4. Web配置界面:ESP32连接Wi-Fi后,可以启动一个Web服务器,提供一个简单的网页,用于配置Wi-Fi密码、管理存储的红外信号名称、测试继电器等,无需再刷写固件。
  5. 多设备组网:如果需要控制多个房间的设备,可以部署多个ESP32节点,它们之间可以通过Wi-Fi或蓝牙Mesh进行通信,由一个主节点统一协调。

这个项目从简单的点对点控制出发,其内核是一个灵活的可编程物联网节点。当你成功让手机上的一个按钮点亮房间的灯,或者用手机遥控打开空调时,那种亲手创造“智能”的满足感,是购买成品设备无法比拟的。更重要的是,你完全掌握了它的所有细节,可以根据自己的需求任意定制和扩展。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询