ESP32 Arduino Matter 智能窗帘示例全解析:MatterWindowCovering 从配网到电机接入实战
【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32
本指南以 Arduino-ESP32 仓库中的 MatterWindowCovering 示例 为核心,系统讲解如何用 ESP32 系列 SoC 打造一个兼容 Matter 协议的智能窗帘(Window Covering)设备。你将掌握 Matter 配网(BLE/Wi-Fi/Thread 三种路径)、升降(Lift)与翻转(Tilt)双轴控制、Preferences状态持久化、按钮手动控制与恢复出厂设置,以及将示例中的"模拟电机"替换为真实电机驱动与位置反馈的完整接入方法,并最终把设备接入 Apple HomeKit、Amazon Alexa 与 Google Home。
示例概览与支持的目标芯片
MatterWindowCovering 示例演示了在 ESP32 SoC 上创建一个 Matter 兼容的窗帘设备,覆盖 Matter 配网(commissioning)、通过智能家居生态控制设备,以及使用物理按键进行手动控制三大场景。其完整工程位于 libraries/Matter/examples/MatterWindowCovering/,核心源码为 MatterWindowCovering.ino。
支持的目标平台
| SoC | Wi-Fi | Thread | BLE 配网 | 状态 |
|---|---|---|---|---|
| ESP32 | ✅ | ❌ | ❌ | 完全支持 |
| ESP32-S2 | ✅ | ❌ | ❌ | 完全支持 |
| ESP32-S3 | ✅ | ❌ | ✅ | 完全支持 |
| ESP32-C3 | ✅ | ❌ | ✅ | 完全支持 |
| ESP32-C5 | ❌ | ✅ | ✅ | 支持(仅 Thread) |
| ESP32-C6 | ✅ | ❌ | ✅ | 完全支持 |
| ESP32-H2 | ❌ | ✅ | ✅ | 支持(仅 Thread) |
配网方式的芯片差异(务必注意)
- ESP32 与 ESP32-S2 不支持通过蓝牙 LE 配网。这两款芯片必须在 sketch 代码中直接写入 Wi-Fi 凭据,使其手动连接到你的网络。
- ESP32-C6:虽然芯片本身支持 Thread,但 ESP32 Arduino Matter 库预编译版本仅启用了 Wi-Fi。若要将其配置为仅 Thread 运行,必须以 Arduino 作为 IDF 组件(Arduino as an IDF Component)方式构建工程,并禁用 Matter Wi-Fi Station 功能。
- ESP32-C5:虽然芯片支持 2.4 GHz 与 5 GHz Wi-Fi,但 ESP32 Arduino Matter 库预编译版本仅启用了 Thread。若要配置为 Wi-Fi 运行,同样需要以 Arduino 作为 ESP-IDF 组件构建,并禁用 Thread 网络、只保留 Wi-Fi Station。
从源码实现看,sketch 使用CONFIG_ENABLE_CHIPOBLE宏区分配网路径:MatterWindowCovering.ino 中,当未启用 CHIPoBLE 时才会#include <WiFi.h>并手动连接 Wi-Fi;该宏在 Matter.h 中还有配套的isWiFiStationEnabled()、isThreadEnabled()、isBLECommissioningEnabled()等能力查询接口,可在应用层确认当前固件实际启用的网络能力。
示例功能特性
- 完整的 Matter 窗帘设备协议实现
- 同时支持 Wi-Fi 与 Thread(*)连接
- 升降(Lift)位置与百分比控制(0–100%)——Lift 表示物理位置(厘米)
- 翻转(Tilt)旋转角度与百分比控制(0–100%)——Tilt 表示帘片旋转量,而非线性位移
- 多种窗帘类型支持
- 使用
Preferences库实现状态持久化 - 按钮手动调节升降与恢复出厂设置
- RGB LED 可视化:亮度映射升降(Lift)、颜色映射翻转(Tilt)位置
- 升降(cm)与翻转(绝对值)的安装限位(installed limit)配置
- 通过 QR 码或手动配对码进行 Matter 配网
- 集成 Apple HomeKit、Amazon Alexa 与 Google Home
(*)以 Arduino 作为 IDF 组件编译时方可使用 Thread。
硬件要求与引脚配置
硬件需求
- 兼容 ESP32 的开发板(参见上文支持目标表)
- 用于可视化的 RGB LED(若开发板有内置 RGB LED 则使用内置)
- 手动控制用的用户按键(默认使用 BOOT 按键)
引脚配置
- RGB LED:若定义了
RGB_BUILTIN则使用内置 RGB LED(可视化用途),否则使用引脚 2。LED 亮度代表升降位置(0% = 熄灭,100% = 全亮),颜色代表翻转角度(红色 = 0%,蓝色 = 100%)。 - 按钮:默认使用
BOOT_PIN。
在源码中,LED 引脚的选择逻辑位于 MatterWindowCovering.ino:#ifdef RGB_BUILTIN时采用RGB_BUILTIN,否则退回引脚 2 并发出#warning "Do not forget to set the RGB LED pin"编译警告;按钮引脚buttonPin = BOOT_PIN定义在 MatterWindowCovering.ino。
软件环境准备
前置条件
- 安装 Arduino IDE(推荐 2.0 或更新版本)
- 安装带 Matter 支持的 ESP32 Arduino Core
- ESP32 Arduino 库:
MatterPreferencesWi-Fi(仅 ESP32 与 ESP32-S2 需要)
配置项
上传 sketch 前需要配置以下内容:
1. Wi-Fi 凭据(未使用 BLE 配网时必须配置——ESP32 / ESP32-S2 为强制项):
const char *ssid = "your-ssid"; // 改为你的 Wi-Fi SSID const char *password = "your-password"; // 改为你的 Wi-Fi 密码注意:这段代码被#if !CONFIG_ENABLE_CHIPOBLE条件编译包裹,MatterWindowCovering.ino 中注释明确说明:启用 CHIPoBLE(BLE 配网)时使用 BLE 配网,Wi-Fi 不再使用以节省 Flash 空间。
2. RGB LED 引脚(若未使用内置 RGB LED):
const uint8_t ledPin = 2; // 在这里设置你的 RGB LED 引脚3. 按钮引脚(可选):
默认使用BOOT按键(GPIO 0)进行手动升降控制与恢复出厂设置,可按需改为其他引脚:
const uint8_t buttonPin = BOOT_PIN; // 在这里设置你的按钮引脚编译与烧录步骤
- 在 Arduino IDE 中打开
MatterWindowCovering.ino工程。 - 从Tools > Board菜单选择你的 ESP32 开发板。
- 从Tools > Partition Scheme菜单选择"Huge APP (3MB No OTA/1MB SPIFFS)"分区方案。
- 在Tools菜单中启用"Erase All Flash Before Sketch Upload"(上传前擦除全部 Flash)。
- 通过 USB 将 ESP32 开发板连接到电脑。
- 点击Upload按钮编译并烧录。
第 3 步的分区要求并非偶然:示例的 CI 配置 ci.yml 中明确使用fqbn_append: PartitionScheme=huge_app进行验证编译,并声明依赖CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y——该 Kconfig 选项控制 Matter 数据模型端点的编译,Matter 系列端点类(如MatterWindowCovering)的声明与实现均以#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL为编译前提(参见 MatterWindowCovering.h)。
预期串口输出
sketch 运行后,打开波特率为115200的串口监视器。Wi-Fi 连接信息只会在 ESP32 与 ESP32-S2 上显示;其他目标平台将通过 Matter CHIPoBLE 自动配置 IP 网络。预期输出类似:
Connecting to your-wifi-ssid ....... WiFi connected IP address: 192.168.1.100 Matter Node is not commissioned yet. Initiate the device discovery in your Matter environment. Commission it to your Matter hub with the manual pairing code or QR code Manual pairing code: 34970112332 QR code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=MT%3A6FCJ142C00KA0648G00 Matter Node not commissioned yet. Waiting for commissioning. ... Initial state: Lift=100%, Tilt=0% Matter Node is commissioned and connected to the network. Ready for use. Window Covering changed: Lift=100%, Tilt=0% Moving lift to 50% (position: 100 cm) Window Covering changed: Lift=50%, Tilt=0%对照源码可见这些输出与 MatterWindowCovering.ino 中loop()的配网等待逻辑一一对应:未配网时每隔 5 秒(timeCount++ % 50,每次delay(100))打印一次等待提示,并在配网完成后打印初始状态;Matter.getManualPairingCode()与Matter.getOnboardingQRCodeUrl()由 Matter.h 声明,配网码在Matter.begin()之后由 CommissionableDataProvider 生成。
设备使用:手动控制、状态持久化与 LED 可视化
手动控制
用户按键(默认 BOOT 按键)提供手动控制:
- 短按按键:按 20% 步进循环升降百分比。若当前位置不是 20% 的整数倍,则向上取整到下一个 20% 的倍数;否则增加 20%(0% → 20% → 40% → … → 100% → 0%)。
- 长按(超过 5 秒):恢复出厂设置(取消配网)。
源码中的实现细节:短按逻辑位于 MatterWindowCovering.ino,带 250ms 消抖(debounceTime);长按逻辑在 MatterWindowCovering.ino,当time_diff > decommissioningTimeout(5000ms)时先将电流位置置为全关(setCurrentLiftPercent100ths(10000))再调用Matter.decommission()。值得注意的是,手动控制是通过WindowBlinds.setTargetLiftPercent100ths(targetLiftPercent * 100)写入目标属性来驱动的,而不是直接改当前值——这与 Matter 的"TargetPosition 是设备应该去的位置"语义一致。
状态持久化
设备使用Preferences库保存最后一次的升降与翻转百分比。断电或重启后:
- 设备恢复到上次保存的升降与翻转百分比
- 若没有历史状态,默认状态为 100% 升降(完全打开)与 0% 翻转
- Matter 控制器会被通知恢复后的状态
- RGB LED 会反映恢复后的状态
源码实现中,matterPref以命名空间"MatterPrefs"打开(MatterWindowCovering.ino),键名为"LiftPercent"与"TiltPercent"(MatterWindowCovering.ino);每个位置回调(fullOpen()、fullClose()、goToLiftPercentage()、goToTiltPercentage())在更新完 Matter 属性后都会调用matterPref.putUChar(...)持久化当前百分比(如 MatterWindowCovering.ino)。
RGB LED 可视化
RGB LED 提供视觉反馈:
- 亮度:代表升降位置(0% = 熄灭,100% = 全亮)
- 颜色:代表翻转位置(红色 = 0% 翻转,蓝色 = 100% 翻转)
- 无 RGB LED 的开发板仅使用亮度
visualizeWindowBlinds()函数(MatterWindowCovering.ino)展示了具体算法:openness = 100 - liftPercent(越打开越亮),RGB 板调用rgbLedWrite(ledPin, red, green, blue)混合红蓝两色表示翻转角度;非 RGB 板则退化为analogWrite(ledPin, brightnessValue)纯亮度输出(因此非 RGB 板需要引脚支持 PWM)。
接入真实电机:窗帘集成实战
示例默认"瞬间完成"模拟移动,生产环境接入电动窗帘时需要替换为真实电机控制。
1. 电机控制
- 将电机驱动连接到 ESP32
- 改写回调函数(
fullOpen()、fullClose()、goToLiftPercentage()、goToTiltPercentage()、stopMotor())以控制真实电机 - 示例当前瞬间模拟移动——替换为真实电机控制代码
2. 位置反馈
- 使用编码器或限位开关提供位置反馈
- 升降:根据实际电机位置更新
currentLift(厘米) - 翻转:根据实际电机旋转更新
currentTiltPercent(旋转百分比) - 重要:在
onGoToLiftPercentage()或onGoToTiltPercentage()回调中,当物理设备实际移动时调用setLiftPercentage()与setTiltPercentage()更新CurrentPosition属性。这会触发已注册的onChange()回调。 - 移动完成时调用
setOperationalState(LIFT, STALL)或setOperationalState(TILT, STALL)表示设备已到达目标位置。 - 使用
setInstalledOpenLimitLift()、setInstalledClosedLimitLift()、setInstalledOpenLimitTilt()、setInstalledClosedLimitTilt()配置安装限位,定义窗帘的物理行程范围。
回调流程(TargetPosition → CurrentPosition 的完整链路)
Matter 命令 → TargetPosition 变化 → onGoToLiftPercentage()/onGoToTiltPercentage() 被调用 → 你的回调驱动电机 → 移动完成后调用 setLiftPercentage()/setTiltPercentage() → setLiftPercentage()/setTiltPercentage() 更新 CurrentPosition → onChange() 被调用(若已注册)这一流程在端点实现中有清晰的源码佐证。在 MatterWindowCovering.cpp 的attributeChangeCB()中,TargetPositionLiftPercent100ths变化时只触发回调、绝不直接改 CurrentPosition(注释明确:CurrentPosition应由应用在设备真正移动时更新);命令类型通过目标值判定:目标为 0 判定为UpOrOpen、目标为 10000 判定为DownOrClose、目标等于当前位置(且不在限位端点)判定为StopMotion。随后setCurrentLiftPercent100ths()(MatterWindowCovering.cpp)通过updateAttributeVal()写入属性并触发_onChangeCB回调——这就是示例中Serial.printf("Window Covering changed: Lift=%u%%, Tilt=%u%%")与 LED 更新的来源。
3. 窗帘类型(Window Covering Type)
- 将窗帘类型传入
begin()以配置正确的类型(如BLIND_LIFT_AND_TILT、ROLLERSHADE等) - 不同类型支持不同的特性(仅升降、仅翻转、或两者兼具)
- 必须在初始化时指定窗帘类型,以确保启用正确的特性
MatterWindowCovering端点类(MatterWindowCovering.h)定义了完整的窗帘类型枚举,均映射自 Matter 规范WindowCovering::Type:ROLLERSHADE、ROLLERSHADE_2_MOTOR、ROLLERSHADE_EXTERIOR、ROLLERSHADE_EXTERIOR_2_MOTOR、DRAPERY、AWNING(以上仅 LIFT);SHUTTER、BLIND_TILT_ONLY(仅 TILT);BLIND_LIFT_AND_TILT(LIFT 与 TILT 兼具);PROJECTOR_SCREEN(仅 LIFT)。在 MatterWindowCovering.cpp 的begin()中,supportsTilt只对SHUTTER、BLIND_TILT_ONLY、BLIND_LIFT_AND_TILT为真,只有这些类型才会在 feature_flags 中加入tilt::get_id() | position_aware_tilt::get_id()并创建 Tilt 相关属性。
电机校准(PositionCalibration)
示例还引入了本地电机校准结构PositionCalibration(定义于 MatterWindowCovering.h,默认open = 0、closed = 65534),用于将物理单位映射为 Matter 百分比。示例中:
// 升降限位(厘米):Matter 百分比 0 = 上限位打开,100 = 下限位关闭 const MatterWindowCovering::PositionCalibration LIFT_CALIBRATION = {.open = 0, .closed = 200}; // 翻转限位(绝对值):翻转是旋转而非线性测量 const MatterWindowCovering::PositionCalibration TILT_CALIBRATION = {.open = 0, .closed = 90};liftPercentToCm()(MatterWindowCovering.ino)基于getLiftCalibration()做线性插值:0% 对应 open 限位、100% 对应 closed 限位,并兼容 open > closed 的反向安装情形。该结构仅存于固件本地,不会作为 Matter 集群属性发布(ESP-Matter 1.5 行为),详见 ep_window_covering.rst 文档中对PositionCalibration的说明。
智能家居生态集成
使用 Matter 兼容中枢(如 Apple HomePod、Google Nest Hub 或 Amazon Echo)配网设备。
Apple Home
- 在 iOS 设备上打开"家庭"App
- 点击"+"按钮 > 添加配件
- 扫描串口监视器中显示的 QR 码,或者
- 点击"我没有或无法扫描代码",输入手动配对码
- 按提示完成设置
- 设备将以"窗帘/百叶窗"形式出现在家庭 App 中
- 可通过滑块同时控制升降与翻转位置
Amazon Alexa
- 打开 Alexa App
- 点击 更多 > 添加设备 > Matter
- 选择"扫描二维码"或"手动输入代码"
- 完成设置流程
- 窗帘将出现在 Alexa App 中
- 可通过语音命令控制位置,例如"Alexa, set blinds to 50 percent"(Alexa,把百叶窗调到百分之五十)
Google Home
- 打开 Google Home App
- 点击"+" > 设置设备 > 新设备
- 选择"Matter 设备"
- 扫描 QR 码或输入手动配对码
- 按提示完成设置
- 可通过语音命令或 App 内滑块控制位置
代码结构解析
MatterWindowCovering 示例由以下主要组件构成:
1.setup()
初始化硬件(按键、RGB LED),配置 Wi-Fi(如需),初始化Preferences库,以最后保存的状态配置 Matter 窗帘端点,注册回调函数,并启动 Matter 协议栈。初始化顺序值得参考(MatterWindowCovering.ino):
- 初始化按键 GPIO(
INPUT_PULLUP)与 LED GPIO Serial.begin(115200)#if !CONFIG_ENABLE_CHIPOBLE分支内手动WiFi.begin()并等待连接matterPref.begin("MatterPrefs", false)并读取上次保存的LiftPercent/TiltPercent(默认 0)WindowBlinds.begin(lastLiftPercent, lastTiltPercent, BLIND_LIFT_AND_TILT, &LIFT_CALIBRATION, &TILT_CALIBRATION)创建端点- 注册
onOpen/onClose/onGoToLiftPercentage/onGoToTiltPercentage/onStop/onChange六个回调 Matter.begin()启动 Matter 栈(必须在所有端点初始化之后、作为最后一步)- 若
Matter.isDeviceCommissioned()为真(重启已配网配件),直接打印初始状态并刷新 LED
2.loop()
检查 Matter 配网状态,处理按键输入(手动升降控制与恢复出厂设置),并让 Matter 协议栈处理事件。未配网时主循环会阻塞等待配网完成,配网后则持续轮询按键状态。
3. 回调(Callbacks)
目标位置回调(TargetPosition属性变化时调用,由 MatterWindowCovering.cpp 的属性变更处理逻辑驱动):
fullOpen():通过onOpen()注册,收到UpOrOpen命令时调用。移动窗帘到完全打开(100% 升降),调用setLiftPercentage()更新CurrentPosition,并将运行状态置为STALLfullClose():通过onClose()注册,收到DownOrClose命令时调用。移动窗帘到完全关闭(0% 升降),调用setLiftPercentage()更新CurrentPosition,并将运行状态置为STALLgoToLiftPercentage():通过onGoToLiftPercentage()注册,TargetPositionLiftPercent100ths变化时调用(来自命令、setTargetLiftPercent100ths()或直接属性写入)。基于安装限位计算绝对位置(cm),调用setLiftPercentage()更新CurrentPosition,移动完成时置STALLgoToTiltPercentage():通过onGoToTiltPercentage()注册,TargetPositionTiltPercent100ths变化时调用。调用setTiltPercentage()更新CurrentPosition,移动完成时置STALLstopMotor():通过onStop()注册,收到StopMotion命令时调用。停止一切进行中的移动,同时更新升降与翻转的CurrentPosition,并将两者的运行状态置为STALL
当前位置回调(CurrentPosition属性变化时调用):
onChange():通过onChange()注册,CurrentPositionLiftPercent100ths或CurrentPositionTiltPercent100ths变化时调用(在setLiftPercentage()/setTiltPercentage()之后触发)。更新 RGB LED 可视化以反映当前位置
注意:目标位置回调(fullOpen()、fullClose()、goToLiftPercentage()、goToTiltPercentage()、stopMotor())通过调用setLiftPercentage()或setTiltPercentage()更新CurrentPosition属性,这会触发onChange()回调,从而更新可视化。
onChange()的注册方式体现了该端点的灵活性——示例中直接传入 lambda 表达式(MatterWindowCovering.ino),端点类的回调类型定义可见 MatterWindowCovering.h:EndPointOpenCB、EndPointCloseCB、EndPointLiftCB、EndPointTiltCB、EndPointStopCB、EndPointCB均为std::function类型。
运行状态(Operational State)说明
端点类还封装了 Matter 的 OperationalStatus 位域操作(MatterWindowCovering.h):
OperationalState_t:STALL(静止)、MOVING_UP_OR_OPEN(正在打开)、MOVING_DOWN_OR_CLOSE(正在关闭)OperationalStatusField_t:GLOBAL(bits 0-1)、LIFT(bits 2-3)、TILT(bits 4-5)
setOperationalState(field, state)的底层实现在 MatterWindowCovering.cpp:ESP-Matter 只允许直接设置 LIFT 或 TILT 字段(直接设置 GLOBAL 会被拒绝),GLOBAL 由 LIFT 优先、其次 TILT 自动推导——当 LIFT 非STALL时 GLOBAL 跟随 LIFT,否则跟随 TILT。
故障排查(Troubleshooting)
- 配网时设备不可见:确保 Wi-Fi 或 Thread 连接配置正确
- 窗帘无响应:验证回调函数是否正确实现、电机控制是否正常
- 位置不更新:检查
setLiftPercentage()与setTiltPercentage()是否以正确数值被调用 - 状态未持久化:检查
Preferences库是否正确初始化、Flash 是否损坏 - RGB LED 不工作:RGB LED 需确认引脚支持 RGB LED 控制;非 RGB 板需确认引脚支持 PWM(
analogWrite) - 翻转不工作:确保窗帘类型支持翻转(如
BLIND_LIFT_AND_TILT、SHUTTER或BLIND_TILT_ONLY),并在begin()中指定 - 配网失败:尝试长按按键恢复出厂设置;另一个方案是通过
Arduino IDE 菜单->Tools->Erase All Flash Before Sketch Upload: "Enabled"擦除 SoC Flash,或直接执行esptool.py --port <PORT> erase_flash - 无串口输出:检查波特率(115200)与 USB 连接
延伸阅读
关于MatterWindowCovering端点的完整 API 参考(构造、begin()/end()、Lift/Tilt 位置与百分比控制、setCurrentLiftPercent100ths()/setTargetLiftPercent100ths()、限位与校准、运行状态、全部回调的签名与语义),可继续阅读仓库文档 docs/en/matter/ep_window_covering.rst;Matter 库的整体架构(ArduinoMatter管理器、MatterEndPoint基类、事件与配网 API)见 docs/en/matter/matter.rst 与 docs/en/matter/matter_ep.rst。
许可证
本示例以 Apache License 2.0 许可发布。
【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考