1. 这块开发板凭什么敢说“HomeKit Compatible”
先说结论:ESP32 这类 MCU 级别的开发板,确实可以做苹果 HomeKit 兼容设备,而且不需要外挂 MFi 认证芯片,纯靠软件协议就能进苹果的家庭生态。
我最初看到“ESP32 MCU Dev Kit is Apple HomeKit Compatible”这个标题时,第一反应是“又来一个营销噱头”。毕竟 HomeKit 在很多人印象里是苹果的封闭花园,想入圈得过 MFi 认证,得买苹果指定的认证芯片,交年费,送样测试,整套流程下来小公司都未必扛得住。但实际研究了一圈才发现,苹果在 iOS 11 之后放开了软件认证(Software Authentication)路径,允许配件通过软件方式完成 HomeKit 协议层的配对与通信,不再强制要求物理认证芯片。这扇门一开,ESP32 这种性价比极高的 Wi-Fi MCU 就成了 HomeKit 开发的热门载体。
这块板子能做什么?简单说,你手里有一颗 ESP32、一个 DHT11 温湿度传感器或者一个继电器模块,只要烧入对应的 HomeKit 固件,就能在 iPhone 的家庭 App 里直接添加设备、实时看温度、远程开关灯,甚至设置自动化场景。“晚上六点自动打开台灯”“温度超过 28 度启动风扇”这类玩法全部可以用苹果原生生态实现,不依赖任何第三方云平台。
适合谁看?如果你之前玩过 ESP32 但没碰过 HomeKit,或者你一直在用 MQTT、自建 App 控制智能设备,想体验一下“原生家庭 App 直接控制”的顺滑感,这篇内容就是按你的需求写的。我下面会把开发环境、库的选型、完整代码、配对流程和踩坑记录全部摊开讲,照着做就能跑通一个基础项目。
2. 为什么是 ESP32?HomeKit 兼容的底层逻辑
2.1 HomeKit 协议本质是一套“局域网内的 JSON 对话”
先别被 HomeKit 这个名字吓住。抛开苹果的营销包装,HomeKit 兼容设备的核心是跑通 HAP(HomeKit Accessory Protocol)。HAP 定义了三层东西:传输层用 TCP/IP 在局域网内通信(通常走 443 或 8080 端口),数据格式是 JSON 加 TLS 加密,逻辑层则是“配件(Accessory)→ 服务(Service)→ 特征(Characteristic)”的树形模型。
听起来很复杂,但实际开发时不需要从零手写协议栈。社区已经有成熟方案,比如 HomeSpan 库,它把 HAP 协议的配网、加密、Pairing、特征值读写全部封装成 Arduino 库,你只需要像写普通 Arduino 代码一样声明“我要一个温度传感器服务”,剩下的苹果设备发现、配对、加密通信、数据上报都由库处理。
这也是 ESP32 能成为 HomeKit 开发首选 MCU 的核心原因:HomeKit 需要 Wi-Fi 联网,需要足够的内存跑 TLS 加密,需要能生成 ECC 密钥对——ESP32 的 CPU(双核 240MHz)、320KB RAM、4MB Flash 在这个场景下刚好够用。而像 STM32F103 这类经典 MCU,算力和内存都吃紧,跑 HAP 就非常吃力;树莓派虽然性能绰绰有余,但成本和功耗又是另一回事。
2.2 MFi 认证芯片 vs 软件认证,到底差在哪
这是我在评论区经常看到有人混淆的点。早期 HomeKit 设备必须内置苹果 MFi 认证芯片,这个芯片里面预置了苹果颁发的密钥和证书,协议通信时用它做身份认证。认证芯片成本不低,而且想买芯片必须先通过 MFi 审核流程。
软件认证路径则允许设备用一个安全元件/软件实现的密钥来进行 HomeKit 配对和通信。苹果没有公开所有细节,但简单理解就是:你的设备在配对时会通过 SRP(Secure Remote Password)协议和苹果设备协商会话密钥,再配合 Ed25519 签名算法完成身份验证。这套流程不依赖物理认证芯片,普通 MCU 也能跑。
但注意:软件认证并不意味着你可以随随便便造一个 HomeKit 设备卖给消费者。苹果对商业产品的要求依然严格,MFi 会员、认证测试、生产审计这些环节一个都少不了。HomeSpan、esp-homekit 这些开源方案更适合个人项目、学习研究、产品原型验证。如果你只是想让自己的 ESP32 设备进 HomeKit 然后自家人用,这条路是完全通畅的;如果你想拿去量产,那还是得走正式的 MFi 计划。
2.3 主流方案的横向对比,我为什么推荐 HomeSpan
目前朋友圈子里做 ESP32 + HomeKit 用得最多的方案有三个:
第一个是 HomeSpan。基于 Arduino 平台,也支持 ESP-IDF,API 设计非常贴近 HomeKit 的 Accessory/Service/Characteristic 模型,文档详细,示例丰富,而且持续维护。我主力推荐它。
第二个是 esp-homekit。基于 ESP-IDF 的 C 语言方案,性能更 raw,运行效率高,但配置和编译流程对新手不够友好,文档也比较分散。适合已经有 ESP-IDF 底子的开发者在资源受限的设备上跑。
第三个是 ESP-Apple-HomeKit-ADK。苹果官方 HomeKit ADK(Accessory Development Kit)的 ESP32 移植版本,代码量巨大、结构复杂,适合深入研究 HAP 协议内部实现,不适合快速开发具体产品。
我最后选了 HomeSpan,理由很实际:开发速度快,从零写一个温湿度传感器固件半小时搞定;调试信息明确,配网失败、特征值更新失败都会在串口输出直观报错;API 稳定,我这一两个项目折腾下来没遇到库本身的大坑。
3. 开发环境搭建:十分钟跑通 HomeSpan
3.1 选板子:ESP32 型号怎么挑
先说硬件。ESP32 有 Classic 系列、S2、S3、C3 等多个型号。做 HomeKit 项目,我的建议是优先选 ESP32 Classic(ESP32-WROOM-32 模块)或 ESP32-S3,原因有两点:一是这两款 Flash 空间和 RAM 都够大,HomeKit 固件编译出来大约 1MB 左右,加上 OTA 分区和文件系统,4MB Flash 是起步;二是社区资料最丰富,踩坑的人多,搜问题容易。
ESP32-C3 是 RISC-V 内核,价格便宜,功耗低,但 I/O 数量少,HomeKit 官方 demo 在 C3 上跑需要调一些配置。我自己第一个项目用的就是 ESP32-WROOM-32 开发板,20 多元一块,性能足够,后面所有示例都基于这块板子。
3.2 Arduino IDE 搭建 ESP32 环境
HomeSpan 的安装入口在 Arduino IDE,所以先把 ESP32 开发板支持装好。
Arduino IDE 我用的是 2.x 版本,界面清爽些。安装 ESP32 支持有两种方式:一种是在“开发板管理器”里搜索 esp32 安装乐鑫官方包;另一种是下载离线安装包手动解压到 Arduino 的 hardware 目录。网络状况不理想时,离线包往往更稳。具体步骤:
- 下载 Arduino-ESP32 离线安装包(注意版本号要和你的 IDE 匹配);
- 关闭 Arduino IDE,将压缩包内容解压到
Arduino15/packages/esp32(Windows 通常在C:\Users\你的用户名\AppData\Local\Arduino15\packages\esp32,macOS 在~/Library/Arduino15/packages/esp32); - 重新打开 IDE,在“工具 → 开发板”菜单里拉到最底部,能看到 ESP32 Arduino 系列,选自己的板型即可。
这里有个我踩过的坑:离线包版本和 IDE 缓存冲突时,会出现“开发板列表里看不到 ESP32”的情况。解决办法是把Arduino15/packages/esp32目录整个删掉重新解压,同时清掉Arduino15下的package_index.json缓存文件里有关 esp32 的记录。别再问我为什么明明装了却找不到板子,先试这个操作。
3.3 安装 HomeSpan 库
在 Arduino IDE 的“库管理器”里搜索 HomeSpan,作者就是 HomeSpan 的作者,认准名字点击安装。装完以后,它在“文件 → 示例”里自带十几个示例,我第一次跑的就是HomeSpan / 01-ContactSensor,编译烧录进去就开干。
还需要注意一个依赖:HomeSpan 在 Arduino 环境下会自动处理大部分依赖,但有些板型需要额外配置分区表。用 Arduino 平台默认分区表即可,不需要手动改,除非你要同时跑 OTA 加文件系统,后面章节细说。
3.4 第一条串口日志的意义
烧录成功后打开 115200 波特率的串口监视器,如果看到类似下面的输出,说明 HomeSpan 已经跑起来了:
HomeSpan v1.9.0 HAP Span Start Accessory Created Services: Accessory Information这里会显示 HomeSpan 版本号、创建的配件信息,以及最关键的Setup Code(默认通常是 466-37-826)和配网二维码的文本表示。这个设置码就是后面家庭 App 配对时输入的 8 位数字码。HomeSpan 框架允许你自定义设置码,但默认的这套在开发阶段够用。
4. 实操:从零写一个 HomeKit 智能温湿度计
4.1 目标与整体结构
我这次做的示例是一个温湿度监测节点。硬件分别是 ESP32-WROOM-32 开发板、DHT11 温湿度传感器(用 DHT22 更准,示例代码里我用 DHT11 的简单库做演示)、3.3V 电源和杜邦线若干。
DHT11 的数据引脚接到 GPIO 4。接线表如下:
| DHT11 引脚 | ESP32 GPIO | 说明 |
|---|---|---|
| VCC | 3.3V | 供电 |
| GND | GND | 共地 |
| DATA | GPIO4 | 单总线数据,需上拉电阻 |
HomeKit 模型里,这个设备对应一个 Accessory,内部包含两个 Service:一个是 TemperatureSensor,一个是 HumiditySensor。每个 Service 又包含若干个 Characteristic,比如 TemperatureSensor 的 CurrentTemperature、StatusActive、StatusFault。
用 HomeSpan 实现,逻辑就是先new SpanAccessory()创建配件,再new SpanService()创建服务,再new SpanCharacteristic()创建特征。特征值在代码里可以随时更新,一旦更新,HomeSpan 会自动推送给已配对的苹果设备。
4.2 完整代码与逐段讲解
下面是我实际调通的代码,注释写在里面对应的地方。你可以直接复制到 Arduino IDE 里编译烧录。
#include <HomeSpan.h> #include "DHT.h" #define DHTPIN 4 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); void setup() { Serial.begin(115200); // 初始化 HomeSpan,设备名称随意 homeSpan.begin(CATEGORY_SENSOR, "ESP32 Sensor Node"); // 读取 DHT11 数据,准备首次上报 dht.begin(); // 创建一个新的 HomeKit 配件 new SpanAccessory(); // 配件信息服务,HomeKit 协议要求必须存在 new Service::AccessoryInformation(); new Characteristic::Identify(); new Characteristic::Manufacturer("MyMaker"); new Characteristic::SerialNumber("SN-2024-001"); new Characteristic::Model("ESP32-DHT11"); new Characteristic::FirmwareRevision("1.0.0"); // 温度传感器服务 new Service::TemperatureSensor(); new Characteristic::CurrentTemperature(25.0); // 初始值,单位摄氏度 new Characteristic::StatusActive(true); new Characteristic::StatusFault(0); new Characteristic::StatusLowBattery(0); // 湿度传感器服务 new Service::HumiditySensor(); new Characteristic::CurrentRelativeHumidity(50.0); // 初始值,单位百分比 new Characteristic::StatusActive(true); new Characteristic::StatusFault(0); new Characteristic::StatusLowBattery(0); } void loop() { // HomeSpan 事件循环,必须周期性调用 homeSpan.poll(); // 每 5 秒读取一次传感器并更新特征值 static uint32_t lastTime = 0; if (millis() - lastTime > 5000) { lastTime = millis(); float h = dht.readHumidity(); float t = dht.readTemperature(); if (isnan(h) || isnan(t)) { // 读取失败时把故障特征置 1,苹果家庭 App 会显示“传感器异常” SpanCharacteristic::UpdateStatusFault(1); Serial.println("DHT read failed"); return; } // 更新特征值,HomeSpan 自动处理上报 SpanCharacteristic::UpdateTemperature(t); SpanCharacteristic::UpdateHumidity(h); Serial.printf("Temp: %.1f C, Humidity: %.1f %%\n", t, h); } }这段代码里值得细说的点有几个。
homeSpan.begin()的第一个参数是配件类别。我用的是CATEGORY_SENSOR,苹果家庭 App 里会显示成传感器类图标。如果做灯,就改用CATEGORY_LIGHTBULB;做开关就用CATEGORY_SWITCH。选错类别不影响功能,但影响图标和分类方式。
Service::AccessoryInformation是 HomeKit 协议强制要求的服务,里面至少要包含Identify特征。Manufacturer、SerialNumber、Model、FirmwareRevision 这些虽然不是强制,但建议写清楚,方便在家庭 App 里辨认设备。
new Characteristic::CurrentTemperature(25.0)里的初始值必须指定。HomeKit 对特征值范围有默认定义:CurrentTemperature 范围为 0 到 100 摄氏度,步进 0.1。如果你传了超出范围的初始值,HomeSpan 会在串口打印警告并把值钳制到合法范围。
SpanCharacteristic::UpdateTemperature(t)这个函数是从 HomeSpan 1.x 版本开始提供的便捷方法,它会查找当前配件下的第一个 TemperatureSensor 服务并更新它的 CurrentTemperature 特征,然后自动推送更新给苹果设备。多设备场景下需要注意它更新的是“当前配件”,如果你的固件里只有一个传感器服务,直接调用没问题;如果有多个传感器,就得手动保存特征指针再逐个更新。
4.3 编译烧录与 iPhone 配对全流程
代码写完后,先检查一下 Arduino IDE 的“工具”菜单设置:开发板选ESP32 Dev Module,Flash Size 保持默认,Partition Scheme 保持Default 4MB with spiffs,Upload Speed 我一般用 921600,烧录更快。如果之前配过板型但没成功,重新选一次板子再试。
点击上传,等待编译烧录完成。打开串口监视器,看到 HomeSpan 打印出配件信息后,就可以开始配对了。
配对操作如下:
- 确保 iPhone 和 ESP32 在同一个 Wi-Fi 网络环境(ESP32 此时还没有连接 Wi-Fi,所以准确说是同一局域网可达区域);
- 打开 iPhone 上的“家庭”App,点击右上角“+”号,选择“添加或扫描配件”;
- 扫描 HomeSpan 在串口打印的二维码(如果你有串口工具带二维码渲染功能,或者直接用 HomeSpan Web Log 配置界面里的二维码),也可以直接点“我没有代码或无法扫描”,手动输入设置码;
- 等待配对完成。整个过程通常在十几秒内,如果长时间卡住,大概率是网络不通或者设置码错误。
配对成功后,家庭 App 里会出现“ESP32 Sensor Node”这个设备,点击进入能看到温度和湿度两个读数,并且数值会随着传感器的读取周期自动刷新。
4.4 扩展玩法:用 HomeKit 自动化控制继电器
温湿度计只是基础。把传感器换成继电器模块,加一个Service::Switch,就能实现远程控制:
new SpanAccessory(); new Service::AccessoryInformation(); new Characteristic::Identify(); new Service::Switch(); SpanCharacteristic *power = new Characteristic::On(false);然后在loop()里用power->getVal()读取开关状态,再控制 GPIO 引脚输出高低电平。继电器模块 VCC 接 5V(一般继电器需要 5V 供电,注意和 ESP32 的 3.3V 区分),IN 引脚接 ESP32 任意 GPIO,比如 GPIO 5 或 GPIO 16。代码里定时轮询power->getVal()是否变化,变化后digitalWrite(RELAY_PIN, val ? HIGH : LOW)即可。
配上苹果家庭 App 的自动化,比如“当温度高于 30 度时打开继电器”,这就是一个完整的智能风扇控制场景了。实际用的时候继电器控制风扇这样的大功率设备要注意安全,一般我会加一个光耦隔离模块,ESP32 的 GPIO 输出能力有限,直接驱动线圈不太稳妥。
5. 踩坑实录:配对失败、连不上网的排查手册
5.1 iPhone 扫不到添加码怎么办
配对时最常遇到的问题是家庭 App 扫不到设备。先确认串口日志里 HomeSpan 有没有打印Entering Pairing Mode或类似的提示。如果根本没有打印,说明你还没让 HomeSpan 进入配对模式。
HomeSpan 的配对模式默认一直开启,但如果你设置了homeSpan.setPairingButton(),可能需要长按指定的按键触发。我建议在开发阶段不要设置配对按钮,让配对模式常开,省得排错时还要考虑按键逻辑。
如果确定进入了配对模式但扫描不到,多半是 Wi-Fi 的问题。HomeSpan 在进入配对前需要通过 Wi-Fi 广播 HomeKit 的配对服务,如果 ESP32 没有连上网,苹果设备自然找不到。查看串口日志里Wi-Fi Status是不是已连接,未连接的话检查你的 AP 是 2.4GHz 还是 5GHz——ESP32 只支持 2.4GHz,路由器开了 5GHz 优先的话 ESP32 可能根本看不到网络,或者频繁掉线。我遇到过路由器开了“双频合一”,ESP32 连接后 DHCP 分配速度慢导致 HomeKit 设备发现超时,关掉双频合一、单独开 2.4GHz 网络后问题消失。
5.2 HomeKit 配对成功后设备一直显示“无响应”怎么办
设备在家庭 App 里显示“无响应”是最让人头疼的问题,因为原因不止一种。我按概率从高到低整理排查顺序:
先说最常见的原因:Wi-Fi 信号不稳定或 ESP32 休眠。检查串口日志,看 ESP32 是否还在持续上报数据。如果日志里显示 Wi-Fi 断开了或者重连了,检查路由器是否需要设置固定 IP。HomeKit 设备在局域网上需要稳定的 IP 地址,DHCP 租约时间太短或者路由器重启后 IP 被分配走,设备就会失联。我习惯在路由器后台给 ESP32 绑定一个静态 IP,或者直接在代码里设置固定 IP。
另一个高频原因是同时配对的设备太多,家庭中枢(HomePod 或 iPad)的发现列表更新不及时。这种情况不用急,等十几秒或者重启一下家庭 App 往往就恢复正常了。
最后一种比较隐蔽:ESP32 的电源供电不足。这个我在用继电器模块时遇到过,继电器吸合瞬间电流很大,如果开发板是 USB 供电且线材较长,电压跌落导致 Wi-Fi 断开。解决办法是换质量好的 USB 线,或者给继电器模块单独供电。
5.3 HomeSpan 特征值更新了但家庭 App 不刷新
这种问题多发于开发调试阶段。代码里明明执行了SpanCharacteristic::UpdateTemperature(t),串口也打印了新数值,但家庭 App 里的温度一动不动。
排查方向有两个:一是家庭 App 本身有缓存刷新延迟。iOS 对 HomeKit 特征值的更新推送是异步的,从设备上报到 App 界面刷新有 1 到 3 秒延迟,如果你用副屏或其他第三方 App 查看,刷新策略更保守。不要刚打印完日志就盯着 App 狂点,等几秒看。
二是特征值的 HAP 属性问题。CurrentTemperature特征默认是PR + EV(可读 + 可事件通知),HomeSpan 更新时会自动触发事件通知。理论上没问题,但如果你手动创建了自定义特征,忘了设置setPermissions()里的PermEvents,那苹果设备永远不会收到推送,只能靠轮询才能看到新值。自定义特征时第一时间把权限设置对,能省掉很多排错时间。
5.4 配网时设置码输错三次被锁定怎么办
HomeKit 协议对配对失败有安全锁定机制,连续输错设置码多次后,设备会进入一段时间的锁定状态,期间不允许再尝试配对。具体时长我记得是从几分钟到几十分钟不等,但实际测试中遇到过锁定的情况,等十分钟左右就会自动恢复。
如果实在等不及,可以给 ESP32 断电重启。重启后 HomeSpan 会重新初始化,配对状态也会回到可接受状态。不过这招治标不治本,重点还是输设置码时要仔细,默认的466-37-826看起来简单,但很容易把最后的826看成286,或者把中间的横杠漏掉。
如果你已经把设备配到了某个 Apple ID 下,想重新配对给另一个账号,需要在代码里清除 HomeSpan 的配对状态。HomeSpan 提供了一个串口命令S(或通过 Web Log 界面操作),在串口监视器发送后设备会取消所有配对并重置到出厂状态。这个操作在开发时很有用,我几乎每次调试新功能都会先重置一遍,避免旧配对干扰。
5.5 OTA 升级后设备丢失或设置码变化
HomeSpan 支持 OTA 升级,但开发阶段不太推荐频繁用,特别是苹果设备已经配对好的情况下。OTA 会导致设备重启,如果新固件里修改了 Accessory 的结构(比如增加了一个服务),已经配对的设备会无法识别新结构,必须删除原设备重新配对。
我的做法是:开发阶段用 USB 烧录,结构确定稳定之后才考虑 OTA。OTA 升级前先在代码里固定好设置码,避免重启后显示不同的码造成混乱。HomeSpan 允许通过homeSpan.setSetupCode()设置自定义设置码,格式为XXX-XX-XXX的 8 位数字。改完设置码后,家庭 App 里旧设备必须删除,重新扫描新码添加。
6. 关于“这真的是 HomeKit 兼容吗”的几句实在话
HomeKit 兼容这个标签,现在被很多开发板厂商当作卖点来宣传。实际拿到手你会发现,真正实现兼容的是那颗 ESP32 芯片和上面跑的那层 HAP 协议栈,而不是“某块特定开发板”。板子只是载体,同样的代码换到另一块 ESP32 开发板上一样能跑。
我在实际项目里体会最深的不是协议有多复杂,而是“苹果生态的反馈感”确实好。同样一个温度传感器,用 MQTT 接 Home Assistant,要折腾映射、配置自动化,最后还得在手机装第三方 App;用 HomeKit 方式,家庭 App 原生支持,自动化设置界面简洁,Siri 一句话就能查温度。这种体验差距是硬件开发者最容易低估的东西。
如果你打算把这个方案推进到产品阶段,我建议再去啃一下 HomeKit 官方 ADK 的文档,特别是 Accessory 状态机设计和 Pairing 流程的安全性细节,HomeSpan 帮你封装了大部分,但理解底层逻辑对排查问题很有帮助。
最后分享一个小技巧:HomeSpan 支持在 Web Log 界面里实时查看配件状态和修改一些配置,但开发机上没有浏览器时,我习惯用串口命令行交互。在串口监视器里输入?,HomeSpan 会列出所有可用命令,比如查看当前特征值、切换配对模式、清理配对记录。调试时打开串口日志和 Web Log 双通道,效率会高很多。
这套方案我已经稳定跑了大半年,ESP32 开发板 24 小时通电,温湿度数据每 5 秒上报一次,靠着苹果家庭中枢做自动化触发,从没掉过链子。如果你手里正好有吃灰的 ESP32,花一个下午把它变成 HomeKit 设备,会有一种“这叫智能家居”的感觉。