Tasmota Berry 固件固化(Solidification)的预处理器宏转储机制:tasmota_defines_for_berry.h 的设计与消费链路
【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota
导读
Tasmota 的 Berry 脚本固件化(solidification)会在编译期把.be脚本编译为预构建的 C 结构体并直接嵌入固件,而固化产物正确性的前提是:Berry 侧看到的USE_*功能开关、D_*语言字符串常量,必须与 C++ 编译器构建固件时看到的取值完全一致。tasmota/tasmota_defines_for_berry.h正是为此而生的构建期产物——它把配置头文件中的全部#define宏转储为扁平文本,供 Berry 固化工具有条件地固化类/模块、折叠字符串常量。读完本文,你将掌握这套宏转储机制的产生过程、输出内容结构,以及它在 Berry 固化工具链中的完整消费方式。
一、为什么需要这份宏转储文件
1.1 Berry solidification 是什么
Berry solidification 将.be脚本编译为预构建的 C 结构体,直接嵌入固件。固化工序在构建期于宿主机上运行,通过berry_port(Berry VM 的 Python 重新实现)执行。仓库中lib/libesp32/berry_tasmota/solidify_all_python.be、lib/libesp32/berry_matter/solidify_all_python.be等固化脚本即是这一环节的入口。
1.2 一致性问题:两个编译器的视角必须对齐
固化代码要保证正确,就必须知道哪些 Tasmota 功能被编译进了固件。例如,一个被固化的类若引用了USE_MATTER_DEVICE或某个D_JSON_*字符串常量,就必须看到与 C++ 编译器相同的取值。否则,固化字节码可能嵌入错误的常量,或包含死代码路径。
简单说:固化器运行在宿主机 Python 环境,而固件由交叉编译器构建,两者之间需要一个共享的"事实源"——这就是tasmota_defines_for_berry.h的角色。
二、转储文件是如何产生的
2.1 构建管线中的位置
pio-tools/dump-defines.py在 PlatformIO 构建中作为post-script运行(注册于 platformio_tasmota32.ini,位于gen-berry-defines.py与gen-berry-structures.py之前)。整个 ESP32 构建管线中与 Berry 固化相关的脚本顺序为:
post:pio-tools/dump-defines.py # 第 1 步:转储宏到 .h post:pio-tools/gen-berry-defines.py # 第 2 步:.h 转成 Berry 可导入的 .be post:pio-tools/gen-berry-structures.py # 第 3 步:运行固化 + coc 生成预编译结构 post:pio-tools/post_esp32.py2.2 核心命令
脚本在宿主机上以宏转储模式调用 C 预处理器:
xtensa-esp32-elf-g++ -E -dM -x c++ -DESP32 <build_flags -D entries> -I include/ -I tasmota/include/ -I tasmota/ -include tasmota/include/tasmota.h -include tasmota/my_user_config.h -include include/tasmota_options.h -include tasmota/include/i18n.h - < /dev/null > tasmota/tasmota_defines_for_berry.h在 dump-defines.py 中,该命令被组装为:
cmd = [cxx, "-E", "-dM", "-x", "c++"] cmd += define_flags # 从 BUILD_FLAGS 提取的 -D 条目 cmd += include_flags # -I include/ -I tasmota/include/ -I tasmota/ + stub for header in forced_includes: cmd += ["-include", str(header)] cmd.append("-") # 读取空 stdin2.3 关键设计决策
| 决策 | 原因 |
|---|---|
使用 post-script(post:) | $CXX在平台构建器运行后才会指向真正的交叉编译器,无需-m32hack |
使用交叉编译器($CXX) | 保证 int/指针宽度、字节序以及 ESP32 目标专属的内建宏都正确 |
-E -dM | 仅预处理模式;输出展开所有头文件后可见的每一个#define |
只包含配置/i18n 头文件,不包含tasmota.ino | tasmota.ino会引入 Arduino/框架库头文件(EEPROM.h、WiFiHelper.h等),在 PlatformIO 的 LDF 运行之前无法解析;这些库也不贡献固化器需要的宏 |
空sdkconfig.h桩 | tasmota_configurations_ESP32.h会#include每个 MCU 构建期才生成的sdkconfig.h;其CONFIG_*值对 Berry 固化无用,故提供空桩文件屏蔽(见 dump-defines.py) |
透传BUILD_FLAGS中的-D标志 | 保证USE_CONFIG_OVERRIDE、MY_LANGUAGE、固件变体标志等与实际构建完全一致 |
2.4 产出物与 gitignore
输出写入tasmota/tasmota_defines_for_berry.h,该文件在 .gitignore 中被忽略(连同后续生成的tasmota_defines_for_berry.be一起)——它是构建产物,每次构建都会重新生成。
三、输出内容长什么样
该文件是gcc -dM格式的扁平#define行列表。一个典型的tasmota32构建大约生成3 200 行,大致分布如下:
| 类别 | 数量 | 示例 |
|---|---|---|
USE_*功能开关 | 约 220 | #define USE_BERRY |
D_*字符串(JSON 键、命令、传感器名) | 约 2 000 | #define D_JSON_TEMPERATURE "Temperature" |
| 固件元数据 | 少量 | #define CODE_IMAGE_STR "tasmota32" |
| 语言/区域 | 少量 | #define LANGUAGE_LCID 2057 |
| 编译器内建宏等 | 其余 | #define __INT_MAX__ 2147483647 |
代表性样例:
/* Feature flags */ #define USE_BERRY #define USE_MATTER_DEVICE #define USE_RULES #define USE_TLS #define USE_WEBSERVER #define USE_IPV6 1 #define USE_ZIGBEE_ZNP /* Language / locale */ #define LANGUAGE_LCID 2057 /* en_GB */ #define CODE_IMAGE_STR "tasmota32" /* JSON key strings */ #define D_JSON_TEMPERATURE "Temperature" #define D_JSON_TEMPERATURE_UNIT "TempUnit" /* Command strings */ #define D_CMND_STATUS "Status" /* Sensor name strings */ #define D_SENSOR_SWITCH "Switch"未定义的标志不会出现在文件中。例如,如果my_user_config.h中注释掉了USE_ZIGBEE(总开关),文件中就不会有#define USE_ZIGBEE行——只剩那些无条件声明的子配置默认常量(USE_ZIGBEE_ZNP、USE_ZIGBEE_CHANNEL等)。
四、固化器如何消费这份文件
4.1 两步转换:.h → .be → 固化脚本导入
宏转储文件不能直接被 Berry 固化器读取,先由pio-tools/gen-berry-defines.py解析并转换为 Berry 脚本tasmota/tasmota_defines_for_berry.be。该脚本支持的转换规则(其余一律降级为注释):
#define NAME → preproc.define('NAME') #define NAME true/false → preproc.define('NAME', true/false) #define NAME "string" → preproc.define('NAME', "string") #define NAME 123 → preproc.define('NAME', 123) #define NAME 0x1A → preproc.define('NAME', 0x1A) #define NAME -42 → preproc.define('NAME', -42)被忽略的类型包括:以__开头的编译器内建宏,以及值为表达式、标识符、浮点数等的宏。生成的.be文件以import preproc开头,随后逐行调用preproc.define(...)。
随后,各固化脚本导入该.be文件。以 berry_tasmota 固化脚本 为例:
import "../../../tasmota/tasmota_defines_for_berry.be" as tasmota_defines固化器(gen-berry-structures.py以python3 -m berry_port -s -g solidify_all_python.be方式驱动)据此获得与固件完全一致的宏视图。
4.2 固化的两大核心用途
- 条件固化(Conditional solidification)——只有当对应的
USE_*标志存在时,某个.be类或模块才会被固化。例如berry_matter的类只在USE_MATTER_DEVICE存在时固化,避免把未被编译进固件的功能代码写入产物。 - 常量折叠(Constant folding)——
D_JSON_*与D_CMND_*字符串常量可以直接嵌入固化字节码,而不必在运行时查询语言文件,既减小固件体积又省去运行时开销。
4.3 coc 阶段的直接引用
值得注意的是,gen-berry-structures.py最后一步调用coc编译器生成预编译 Berry 结构体时,将.h文件直接作为编译输入传入(见 gen-berry-structures.py 中的-c ... "tasmota/tasmota_defines_for_berry.h"参数),与default/berry_conf.h一起参与 C 结构体的生成,进一步印证了该文件在整个固化链中的"事实源"地位。
4.4 固化缓存与失效
由于固化过程较慢,gen-berry-structures.py为每个模块维护两级缓存(存放于.pio/build/<env>/berry_solidify_cache/):
- 快速路径:比较每个输入文件的
st_mtime_ns,全部一致且输出.h存在则跳过固化; - 慢速路径:mtime 不一致时(如
git checkout后),回退到 MD5 内容哈希比较,内容未变则更新缓存 mtime,下次构建恢复快速路径。
tasmota/tasmota_defines_for_berry.be是各模块固化脚本共享的依赖文件,会被一并纳入缓存输入集合与哈希校验——这意味着改动my_user_config.h或user_config_override.h导致宏变化时,tasmota_defines_for_berry.h重新生成、mtime 变化,从而自动触发各模块的重新固化。
4.5 跳过固化
当迭代 C/C++ 代码而无需触碰 Berry 源码、或构建环境没有python3时,可在build_flags中加入-DDISABLE_BERRY_SOLIDIFY来跳过整个固化工序(见 gen-berry-structures.py)。
五、构建期完整数据流小结
my_user_config.h / user_config_override.h / tasmota_options.h / i18n.h + BUILD_FLAGS │ (每次 ESP32 构建开始时) ▼ dump-defines.py(post-script,$CXX 交叉编译器) │ -E -dM -x c++ + 强制 include + sdkconfig.h 空桩 ▼ tasmota/tasmota_defines_for_berry.h (gitignored,约 3 200 行) │ ▼ gen-berry-defines.py ▼ tasmota/tasmota_defines_for_berry.be (preproc.define(...) 序列) │ ├──► 各 solidify_all_python.be 导入(条件固化 / 常量折叠) └──► gen-berry-structures.py → coc(.h 直接作为 -c 输入)→ src/solidify/*.h 嵌入固件因为该文件在每个 esp32 构建开始时重新生成,它始终反映当前的user_config_override.h与platformio_override.ini设置——这正是其作为固化"事实源"可靠性的根本保证。
六、相关仓库资源导航
- 宏转储实现:pio-tools/dump-defines.py
- 宏转换脚本:pio-tools/gen-berry-defines.py
- 固化与缓存管理:pio-tools/gen-berry-structures.py
- 构建注册:platformio_tasmota32.ini
- 固化脚本示例:lib/libesp32/berry_tasmota/solidify_all_python.be、lib/libesp32/berry_matter/solidify_all_python.be、lib/libesp32_lvgl/lv_binding_berry/solidify_all_python.be
- 配置源头:tasmota/my_user_config.h、include/tasmota_options.h、tasmota/include/i18n.h、tasmota/user_config_override_sample.h
- gitignore 规则:.gitignore
实践提示:若你在自定义固件中新增
USE_*开关或修改MY_LANGUAGE,无需手动同步任何 Berry 固化输入——下次构建时dump-defines.py会自动重新生成宏转储,固化链随即自适应;但请记得该.h/.be均为构建产物,不要手工编辑或提交到版本库。
【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考