1. 从一个遥控器焦虑说起HAMote 到底想解决什么问题家里智能设备一多遥控器就成了灾难。电视一个、机顶盒一个、空调一个、风扇一个、灯带一个茶几上永远堆着一小堆塑料壳子找哪个都得翻半天。更麻烦的是这些遥控器大多走红外方向要对准、中间不能挡东西冬天窝在被窝里想关个灯还得爬起来对着设备按一下。我身边不少玩 Home Assistant 的朋友最后都走上了同一条路把红外发射模块接到 ESP 开发板上用 HA 统一接管手机当遥控器用。但手机遥控也有它的问题。解锁、找 App、等加载、点进设备卡片一套流程下来十几秒关个灯比走过去按开关还慢。而且手机屏幕是触控的没有物理按键的盲操手感晚上摸黑根本按不准。HAMote 这个项目本质上就是在解决这个最后一米的交互问题用一块带 Wi-Fi 的 ESP 开发板配上一块小尺寸 TFT 彩屏做成一个可以摆在床头、茶几、玄关的实体遥控器屏幕上显示几个常用设备的开关状态手指一点就发指令给 Home Assistant由 HA 去控制真正的设备。这个思路听起来简单但它把几个关键技术点串在了一起ESP 系列的 Wi-Fi 联网能力、TFT 彩屏的驱动与界面渲染、和 Home Assistant 的 API 通信、以及一个足够省电、足够稳定的固件架构。关键词里出现的 ESP、ESP-IDF、TFT LCD 驱动、1.8 寸 128x160 分辨率这些都是这个项目绕不开的硬骨头。它适合谁适合已经有一台跑着 Home Assistant 的设备、会一点 ESP 开发、想让家里的智能控制实体化的玩家。如果你连 HA 都还没搭起来建议先把 HA 跑通再来看这个不然会卡在通信那一环。我下面会按硬件怎么选、屏幕怎么驱动、HA 怎么对接、固件怎么写、坑怎么避这条线把 HAMote 这类项目从零到能用的完整链路拆开讲。内容里涉及的具体参数和代码是基于这类项目的常见实践补全的你实际做的时候按自己手头的板子和屏幕微调即可。2. 硬件选型ESP 板子、TFT 屏和供电的取舍逻辑2.1 为什么是 ESP 而不是别的 MCU做这种联网遥控器核心诉求有三个要有 Wi-Fi、要有足够的 GPIO 和内存驱动一块彩屏、要能用成熟的开发框架快速迭代。ESP32 系列几乎是唯一同时满足这三点的选择。ESP8266 虽然便宜但 GPIO 数量偏少驱动 TFT 时如果还要接触摸和几个按键引脚会很紧张而且内存也吃紧跑 LVGL 这类图形库会比较勉强。ESP32 有双核、几百 KB 的 RAM、几十个可用 GPIO还能跑 ESP-IDF 这套官方框架Wi-Fi 协议栈稳定蓝牙也能顺带用上比如配网时用 BLE 辅助。具体到型号ESP32-WROOM-32 是最通用的选择模组自带 4MB Flash价格便宜社区资料多。如果你想要更省电或者更小的封装ESP32-C3 也可以它是 RISC-V 内核单核但 Wi-Fi 能力不弱GPIO 也够用。ESP32-S3 则是升级选项带 USB OTG、更多 GPIO、更强的算力如果你打算在屏幕上跑比较复杂的动画或者多页面切换S3 会更从容。我个人的建议是第一次做就用 WROOM-32踩坑成本最低等跑通了再考虑换更省电的型号。2.2 1.8 寸 128x160 TFT 屏的驱动方式关键词里明确提到了1.8 寸 TFT LCD 分辨率 128x160这是这类小遥控器最常用的屏幕规格。这种屏通常是 ST7735 或 ST7789 驱动芯片SPI 接口8 根左右的引脚就能点亮。128x160 这个分辨率不算高但用来显示四到六个设备按钮、几个状态图标完全够用而且像素少意味着刷新快、内存占用小对 ESP32 来说压力不大。驱动方式上SPI 是绝对主流。接线一般是SCLK、MOSI、CS、DC、RST再加上背光 BLK 和可选的 MISO。这里有个容易忽略的点ST7735 有一堆变体红板、黑板、绿板它们的偏移量offset和颜色顺序BGR/RGB不一样如果你初始化参数写错屏幕要么显示偏移几个像素要么红蓝反色。我踩过这个坑一块屏调了半天颜色不对最后发现是ST7735_INITB和ST7735_INITR用反了。所以拿到屏第一件事是确认它的驱动芯片型号和变体别急着写业务代码。屏幕参数典型值说明尺寸1.8 寸对角线长度适合手持或桌面摆放分辨率128x160像素数决定界面布局粒度驱动芯片ST7735 / ST7789决定初始化命令序列接口SPI4 线 SPI 最常用速率可到 40MHz供电3.3V逻辑和背光通常都是 3.3V背光单 LED 或并联可加限流电阻或 PWM 调光2.3 供电与外壳的现实考量遥控器是要拿在手里或者摆在桌上的供电方案直接影响体验。用 USB 线供电最省事但线一插就不无线了。用锂电池比如 18650 或小容量锂聚合物供电就得考虑充电管理和低功耗。ESP32 在 Wi-Fi 常连状态下电流大概在 80 到 150mA 之间屏幕背光再吃掉 20 到 40mA一块 2000mAh 的电池撑不了太久。所以如果你要做电池版必须做几件事屏幕背光用 PWM 调低亮度、空闲时让 ESP32 进 light sleep、屏幕在无人操作时熄屏。这些在固件里都要专门处理不是接上电池就能用的。外壳方面3D 打印是最灵活的。屏幕开窗尺寸要按实际屏的边框留 0.5mm 左右的余量按键位置要避开屏幕排线。我见过有人直接把屏幕用热熔胶固定在塑料盒里能用但不好看也不方便返修。建议还是画个简单的壳把 ESP 板、屏幕、电池分层放屏幕在最上层板子和电池在下层走线从侧面出。3. 屏幕驱动与界面渲染让 128x160 的像素发挥最大价值3.1 从裸驱动到图形库TFT_eSPI 与 LVGL 的分工点亮屏幕只是第一步真正难的是在上面画出好看的界面。裸写 SPI 命令去画矩形、写字代码会非常啰嗦而且不好维护。实践中一般用两层底层用 TFT_eSPI 这类驱动库负责和屏幕通信上层用 LVGL 这类图形库负责控件布局和事件处理。TFT_eSPI 的好处是它针对 ESP 系列做了大量优化支持 DMA 传输刷屏速度快而且内置了多种 ST7735 变体的配置改几个宏就能适配不同屏。LVGL 则是把按钮、标签、滑块这些控件抽象出来你只需要描述这里放一个按钮文字是客厅灯点击时触发回调它帮你处理绘制和触摸。128x160 的屏幕跑 LVGL 是完全可行的但要注意别放太多控件字体也别用太大的否则内存和刷新都会吃紧。我的经验是一屏放 4 到 6 个按钮每个按钮 60x40 像素左右字体用 16 号这样布局清晰手指也点得准。3.2 中文字体与图标资源的处理英文界面好办LVGL 自带字体直接能用。但中文就麻烦了一个中文字库动辄几 MBESP32 的 Flash 虽然有 4MB但还要放固件和别的资源不可能把整个字库塞进去。解决办法是按需取字只把你界面上会用到的那些汉字提取出来生成一个小的字体文件用 LVGL 的字体转换工具转成 C 数组编译进固件。比如你界面上只有客厅灯、卧室灯、空调、风扇、开关这几个词那提取这十几个字就够了生成的字体文件可能只有几十 KB。图标也是同理。LVGL 支持把 PNG 转成 C 数组但彩色图标很占空间。对于 128x160 这种小屏建议用单色或者少量颜色的简单图标甚至直接用几何图形拼。我试过用 FontAwesome 这类图标字体效果不错一个字体文件包含几百个图标比一张张图片省空间而且可以像文字一样设置颜色和大小。3.3 界面状态同步屏幕显示和真实设备状态的一致性这是很多人做遥控器时忽略的一点屏幕上的按钮状态必须和 Home Assistant 里设备的真实状态保持一致。如果屏幕上显示灯是关的但实际灯是开的你点一下反而把它关了体验就很差。所以固件里必须有一个状态同步机制要么定时轮询 HA 的 API 拉取设备状态要么用 HA 的 WebSocket 订阅状态变化一有变化就推送到 ESP32 更新界面。轮询简单但费流量、有延迟WebSocket 实时但实现复杂一些需要处理长连接和重连。对于遥控器这种场景我建议用 WebSocket因为状态变化要即时反映到屏幕上轮询间隔太短会加重 HA 负担太长又不够实时。ESP32 上可以用arduinoWebSockets或者 ESP-IDF 自带的 WebSocket 客户端来实现连接 HA 的/api/websocket端点订阅state_changed事件过滤出你关心的那几个实体。4. 和 Home Assistant 对接API、鉴权与通信协议4.1 用 REST API 还是 WebSocketHome Assistant 对外提供两套接口REST API 和 WebSocket API。REST API 适合发指令这种一次性操作比如POST /api/services/light/toggle带上实体 ID就能切换灯的状态。WebSocket API 适合订阅状态这种持续性的需求。HAMote 这类遥控器通常是两者都用发指令走 REST收状态走 WebSocket。REST API 的调用很直接用 HTTP 客户端发一个 POST 请求Header 里带上Authorization: Bearer 长期访问令牌Body 里写服务名和实体 ID。长期访问令牌在 HA 的用户资料页里可以生成注意这个令牌权限很高别硬编码在会公开的代码里。WebSocket 则要先发一个auth消息带上令牌认证通过后再发subscribe_events订阅状态变化。4.2 长期访问令牌的安全存放令牌泄露等于把你家所有设备的管理权限交出去了所以存放要谨慎。最省事的做法是编译时写进固件但这样一旦固件被别人拿到就完了。好一点的做法是用 ESP32 的 NVS非易失存储保存令牌首次配网时通过网页或者串口写入之后从 NVS 读取。更讲究的可以用 ESP32 的 flash 加密功能但配置复杂一般玩家用 NVS 就够了。配网环节也值得说一下。ESP32 可以用 SmartConfig 或者自建 AP 热点的方式配网。自建 AP 更可控设备首次启动时开一个热点你用手机连上去打开一个网页填 Wi-Fi 密码和 HA 地址、令牌提交后设备保存并重启连网。这个方案不依赖特定 App跨平台我比较推荐。4.3 指令下发与状态回传的完整链路一条完整的控制链路是这样的你点屏幕上的客厅灯按钮LVGL 触发回调回调里构造一个 HTTP POST 请求发给 HA 的 REST APIHA 执行服务调用灯的状态改变HA 通过 WebSocket 推送state_changed事件ESP32 收到事件后更新对应按钮的显示状态。整个过程如果都在局域网内延迟通常在 100 到 300 毫秒之间手感是可以接受的。这里有个细节HA 的服务调用是异步的你发完 POST 请求后灯不一定立刻变。所以不要发完请求就马上改屏幕状态而应该等 WebSocket 推来的真实状态再更新。否则如果指令失败比如设备离线屏幕会显示错误的状态。我一开始就是发完请求直接改 UI结果偶尔出现屏幕和实际不一致后来改成完全依赖 WebSocket 回传问题就没了。// 发送控制指令的简化示例基于 ESP-IDF 的 HTTP 客户端 esp_http_client_config_t config { .url http://192.168.1.100:8123/api/services/light/toggle, .method HTTP_METHOD_POST, }; esp_http_client_handle_t client esp_http_client_init(config); esp_http_client_set_header(client, Authorization, Bearer 你的令牌); esp_http_client_set_header(client, Content-Type, application/json); const char *body {\entity_id\: \light.living_room\}; esp_http_client_set_post_field(client, body, strlen(body)); esp_http_client_perform(client); esp_http_client_cleanup(client);5. 固件架构ESP-IDF 下的任务划分与稳定性设计5.1 为什么选 ESP-IDF 而不是 ArduinoArduino 框架上手快但它的抽象层比较厚对任务调度、内存管理的控制不够细。HAMote 这种要同时处理屏幕刷新、Wi-Fi 通信、WebSocket 长连接、按键输入的项目用 FreeRTOS 的任务模型来组织会更清晰。ESP-IDF 原生就是基于 FreeRTOS 的你可以把屏幕刷新放一个任务、网络通信放一个任务、按键扫描放一个任务用队列在任务间传消息互不阻塞。当然Arduino 也能用 FreeRTOS但 ESP-IDF 的 API 更完整尤其是 Wi-Fi 事件处理、NVS 存储、OTA 升级这些官方文档和示例都很全。如果你之前只玩过 Arduino转 ESP-IDF 会有一段适应期主要是要理解app_main作为入口、组件化的项目结构、以及menuconfig配置系统。但一旦上手做复杂项目会舒服很多。5.2 任务划分与消息队列一个合理的任务划分是这样的ui_task负责 LVGL 的定时刷新和触摸事件处理优先级中等net_task负责 Wi-Fi 连接维护、WebSocket 收发、HTTP 请求优先级稍高input_task负责物理按键扫描优先级低app_main负责初始化和创建这些任务。任务之间通过 FreeRTOS 的队列或者事件组通信比如net_task收到状态更新后往ui_queue里塞一条消息ui_task取出来更新界面。这里要注意 LVGL 的线程安全。LVGL 本身不是线程安全的所有对控件的操作都必须在同一个任务里做或者用互斥锁保护。我建议把所有 UI 操作都收敛到ui_task里其他任务只往队列里发消息不直接碰控件。这样逻辑清晰也不会出现界面卡死或者花屏。5.3 断网重连与看门狗Wi-Fi 断线是家常便饭路由器重启、信号波动都会导致断连。固件必须能自动重连而且重连期间界面不能卡死。ESP-IDF 的 Wi-Fi 事件处理里收到WIFI_EVENT_STA_DISCONNECTED就触发重连同时更新屏幕上的网络状态图标。WebSocket 断开也要重连并且重连后要重新订阅状态。看门狗Watchdog是保命的。如果某个任务卡死看门狗会复位设备避免设备变成砖头一直无响应。ESP-IDF 默认开启了任务看门狗你需要在耗时较长的循环里定期调用vTaskDelay或者喂狗否则会被判定为卡死。屏幕刷新这种循环尤其要注意别写成死循环不让出 CPU。6. 实操中踩过的坑与排查思路6.1 屏幕花屏、偏移、颜色反了这是最高频的问题几乎每个做 TFT 项目的人都会遇到。花屏通常是 SPI 速率太高或者接线太长导致的把 SPI 时钟从 40MHz 降到 20MHz 试试。偏移是 ST7735 变体的 offset 设置不对红板一般是(0, 0)黑板可能是(0, 24)或(24, 0)要查你手上屏的具体资料。颜色反了是 BGR/RGB 顺序问题在初始化里改一个宏就行。排查顺序建议是先降速排除硬件问题再调 offset最后调颜色顺序。6.2 Wi-Fi 连不上或者频繁掉线先确认你用的是 2.4GHz 频段ESP32 不支持 5GHz这是新手最容易犯的错。然后看信号强度RSSI 低于 -80dBm 就容易掉线考虑换个位置或者加个中继。如果路由器开了 AP 隔离或者 MAC 过滤也会连不上。还有一种情况是 DHCP 分配不到 IP可以试试设静态 IP。排查的时候打开 ESP-IDF 的日志级别看 Wi-Fi 事件的具体报错码比瞎猜快得多。6.3 HA 令牌失效或者 API 返回 401令牌失效一般是你在 HA 里删除了那个长期访问令牌或者令牌复制的时候多了空格、少了字符。API 返回 401 就是鉴权失败先检查令牌字符串是否完整再确认 HA 的地址和端口对不对。还有一种情况是 HA 配置了反向代理请求头里的 Host 不对导致拒绝这种要在代理层配置放行。建议在固件里把 HTTP 响应码打到日志里出问题一眼就能看出是 401 还是 404。6.4 内存不足导致重启ESP32 的 RAM 有限LVGL 的缓冲区、WebSocket 的接收缓冲、HTTP 的响应缓冲都要占内存。如果同时开太多或者有内存泄漏就会触发abort重启。排查方法是打印esp_get_free_heap_size()看剩余内存如果持续下降就是泄漏。常见的泄漏点是 HTTP 请求完没cleanup、WebSocket 消息处理完没释放、LVGL 对象创建了没删除。养成谁申请谁释放的习惯能避免大部分内存问题。现象可能原因排查方向屏幕花屏SPI 速率过高、接线干扰降速、缩短排线显示偏移ST7735 变体 offset 错误查屏资料调 offset颜色反色BGR/RGB 顺序错误改初始化宏Wi-Fi 掉线5GHz、信号弱、AP 隔离换 2.4G、看 RSSIAPI 401令牌错误、代理拦截检查令牌和 Host频繁重启内存不足、看门狗打印剩余堆、查泄漏7. 从能用到好用几个提升体验的细节7.1 熄屏与唤醒策略遥控器不可能一直亮着屏那样既费电又刺眼。合理的策略是无操作 30 秒后熄屏触摸或者按键唤醒。熄屏就是把背光 PWM 调到 0屏幕内容可以保留也可以清空。唤醒时如果内容还在直接亮背光就行响应最快。如果为了省电把屏幕也断电了唤醒时要重新初始化会有几百毫秒的延迟体验差一些。我建议只关背光不断电平衡功耗和响应。7.2 物理按键的加入纯触摸屏在黑暗中不好用加一两个物理按键会舒服很多。比如一个返回/主页键一个电源键。按键用 GPIO 中断或者定时扫描都行注意做消抖否则一次按下会触发多次。按键的功能可以做成可配置的比如长按进入配网模式短按返回主页。这样即使屏幕出问题也能通过按键恢复。7.3 OTA 升级让维护变简单设备装到墙上或者壳子里之后再想插 USB 线烧录就很麻烦。OTA空中升级能让你通过 Wi-Fi 推送新固件。ESP-IDF 自带 OTA 组件你只需要在固件里加一个 HTTP 接口或者用 HA 的某个服务触发升级。OTA 要注意分区表要留出两个 app 分区升级时写入备用分区校验通过后切换启动分区失败还能回滚。这个功能一旦用上就回不去了强烈建议在项目初期就规划好。7.4 多设备与场景联动单个遥控器控制几个灯只是开始真正好玩的是场景联动。比如一个回家按钮触发 HA 里的一个脚本同时开灯、开空调、拉窗帘。这种在 HA 侧配置好脚本或者场景遥控器只需要调用对应的服务就行。屏幕上的按钮可以做成可配置的通过 HA 的 API 拉取你预设的场景列表动态生成按钮。这样遥控器就变成了一个通用的场景触发器而不是写死几个设备。8. 一些个人体会做 HAMote 这类项目最大的乐趣不在于技术多复杂而在于它真的能改善日常体验。我把它放在床头之后晚上关灯、开风扇、看时间都不用摸手机了手指在屏幕上点一下就行。屏幕虽然只有 128x160但显示几个按钮和状态图标绰绰有余反而因为简单而显得清爽。技术上的体会是ESP32 加 TFT 这套组合难点不在单个模块而在把它们稳定地整合在一起。屏幕驱动、Wi-Fi 通信、HA 对接每一块单独看都有现成的库和示例但组合起来就会遇到内存、任务调度、状态同步这些胶水问题。解决这些问题的过程其实就是在学嵌入式系统设计的思路资源有限的情况下怎么划分任务、怎么管理内存、怎么处理异常。如果你打算动手做我的建议是先跑通最小闭环一块 ESP32、一块屏、连上 Wi-Fi、能通过 HA 的 API 控制一盏灯。这个闭环跑通了剩下的都是在这个基础上加功能。别一上来就想着做多漂亮的外壳、多复杂的界面先把核心链路打通后面慢慢迭代。踩坑是必然的但每解决一个你对这套系统的理解就深一层。
