背景:迁移容器后发现固件编译失败
把 ESPHome 容器从旧机器迁移到新 NAS 后,容器启动正常,但设备固件编译失败——因为配置是 ESPHome 2022.3.2 时代写的,容器现在运行的是 2026.8.1,中间经历了大版本 schema 重构。
一开始报错让人疑惑:明明只是换了台机器,为什么编译不过?实际不是功能删除了,而是配置写法需要迁移。
是什么:ESPHome 2026.x 的平台化重构
ESPHome 2026.x 把 image 组件重构为平台组件(PR #17416),导致以下顶层键在 2027.1.0 前被废弃:
- image: → 需改为 platform: file
- animation: → 需并入 image: 块改为 platform: animation
- online_image: → 需改为 platform: online_image
废弃期间(到 2027.1.0)编译时会打印 deprecation warning 并给出可直接粘贴的迁移后配置块,极其友好。
与此同时,ArduinoJson 库升级导致 publish_state(JsonVariant) 产生重载歧义,需要显式 cast。
怎么用:三个修复逐个讲解

Fix 1:image: 顶层键 → platform: file + type: binary
报错(容器内编译日志):
[platform] is an invalid option for [image]
或类似 schema 校验失败。
原因:ESPHome 2026.x 把 image 重构为平台组件。老写法(2022.x):
image:
- file: "images/washing.png"
id: washing_running
animation:
- file: "images/washing.gif"
id: washing_running_gif
新写法(2026.x):
image:
- platform: file
file: "images/washing.png"
id: washing_running
type: binary
- platform: animation
file: "images/washing.gif"
id: washing_running_gif
type: binary
关键点:
- 所有静态图加 platform: file,动画加 platform: animation
- 图片要显式声明 type: binary(黑白点阵图)
- animation: 顶层键不再独立存在,并入 image: 块
验证方法:读容器内 ESPHome 源码确认 schema:
# animation 组件只剩 deprecation shim,真 schema 在 image.py
grep -n "schema\|CONF_\|extend" /esphome/esphome/components/animation/__init__.py
# → "Legacy top-level `animation:` deprecation shim -- REMOVE this whole file after 2027.1.0"
# → "Animations are now a platform of the `image:` component (`platform: animation`)"
Fix 2:animation: 顶层键并入 image: 块
报错:
[platform] is an invalid option for [animation]
——顶层 animation: 仍走老 list 格式,但里面写 platform: file 是错的(动画要用 platform: animation)。
原因:ESPHome 2026.7 起,animation 成为 image 组件的平台,顶层 animation: 键是 deprecation shim(保留到 2027.1.0),编译时会打印可直接粘贴的迁移建议块。
修复:把动画条目从顶层 animation: 移入 image: 块,platform: animation。
注意:动画文件是 GIF(8×8),同样要 type: binary。invert_alpha: true(图标反色)对 animation 也生效——因为 animation 复用 image 的 schema 和编码器,配置项兼容。
Fix 3:ArduinoJson 类型歧义 → (const char*) cast
报错:
publish_state(...) 调用不匹配 / 歧义(编译期 no matching function 或 ambiguous overload)。
原因:x["text"] 返回的是 ArduinoJson 的 JsonVariant(MQTT on_json_message 的 JSON 解析),而 ESPHome 的 publish_state() 有两个重载:const char* 和 const std::string&。直接把 JsonVariant 传进去,编译器不知道该转成哪个,产生类型歧义。
修复:显式 cast:
// 老写法
id(DisplayStr).publish_state(x["text"]);
// 新写法
id(DisplayStr).publish_state((const char*)x["text"]);
通用性:所有 on_json_message 里取字段赋值/发布的代码,只要目标是多载函数(publish_state 等),都建议显式 cast。这是 ESPHome + ArduinoJson 7 升级后的常见坑。
踩坑:deprecation shim 会用白捡
编译时 ESPHome 会直接打印可直接粘贴的迁移后配置块,例如:
[15:42:32][W][yaml:123]: Legacy top-level `image:` key found! Please migrate to the following format:
image:
- platform: file
file: "images/washing.png"
id: washing_running
type: binary
复制粘贴即可,比手动改更安全。
另外,因为 animation 复用 image 的 schema,所以 file 平台的所有选项(file/type/resize/transparency)都适用于 animation,配置项完全兼容。
总结:升级后编译失败先读报错,再读源码,一处一编译
这次迁移让我确认了一个经验:升级后编译失败,90% 是写法迁移问题,不是重写问题。正确排查顺序(不要盲改):
1. 看完整报错——ESPHome 的 schema 校验错误通常直接告诉你哪个 key 非法
2. 读新版本源码:报错指向哪个组件,就去 esphome/components/<组件>/__init__.py 看 schema 定义
3. 利用 deprecation shim——废弃组件会在编译时打印“可直接粘贴的迁移后配置”,复制即可
4. 一处一编译:每改一处 esphome compile 验证一次,别攒一堆改动
5. 确认通过后 OTA 烧录:esphome upload --device <device-ip>
三处都改完后,一次编译通过,OTA 烧录成功,设备恢复正常工作。如果读者用 Docker 部署,除了 docker run,也可以用 docker compose 管理容器;不习惯命令行的话,ESPHome Dashboard 里同样能触发编译和烧录。