spec.d/*.toml
miot 设备能暴露哪些实体,由配置目录下的 spec.d/*.toml 决定——不是编译进二进制的
表。接一个新型号只要重新生成这份文件,不用改代码、不用重新编译。
它是配置目录里的第五类东西:rha.toml / devices.toml / bridges.toml /
automations/ 那四份都由 config-schema.json 描述,站点的字段表全部由它渲染;
spec.d/ 不在其中,所以本页整页手写,没有自动生成的字段表。
/etc/rha/spec.d/ 10-generated.toml ← spec-gen 独占, 每次整个覆盖重写 50-manual.toml ← 你的, 工具永不触碰配了 miot 设备却没有对应型号时,rha check 会点名报错并给出修复命令:
error: devices.toml: 型号 "cuco.plug.v3" 不在 spec.d 里。跑 `rha spec-gen` 生成, 或在 spec.d/ 里手写一条spec-gen
Section titled “spec-gen”rha --config /etc/rha spec-gen # 按 devices.toml 从官方 miot-spec 生成rha --config /etc/rha spec-gen --check # 只比对不写盘, 有差异非零退出它是运维时命令(与 rha miot-token 同类):联网、产出配置材料。运行期(serve)
绝不联网,只读生成好的文件。
它也只解析 devices.toml 里真出现过的型号——官方目录有 45,000+ 个型号,没必要为你
没有的设备付代价。
spec-gen --check 适合挂在定时任务里:它让「上游把 spec 升版改了属性」变成一件能主动
发现的事,而不是等设备行为异常了才回头查。
为什么是目录,不是一个文件
Section titled “为什么是目录,不是一个文件”因为 TOML 序列化不保留注释。 混在一个文件里的话,spec-gen 每次重写都得读进来、
保留手写部分、再写回去,而往返一次就会把你写的「这台固件实测 piid=6 不响应」这类注释
静默抹掉。
拆开之后 spec-gen 只 truncate 自己那个文件,根本不读别人的——文件归属就是「谁拥有
这条」的标记。
加载顺序:字典序,同型号整体替换
Section titled “加载顺序:字典序,同型号整体替换”目录下所有 *.toml 按文件名字典序加载(与 automations/ 一致),所以
10-generated.toml 在 50-manual.toml 之前。
同一个型号名后加载的整体替换先加载的——不做实体级合并。规则一句话说完、没有 合并歧义:手写条目要么完整接管一个型号,要么完全不管。
目录不存在不算错,退化成空表(一份没有 miot 设备的配置不该被强制要求 spec.d);但
单个文件里有一条坏条目就是硬错误,整份配置加载失败——静默跳过一条坏条目等于凭空少一个
实体,那正是这套设计要根除的失败模式。
什么时候需要手写覆盖
Section titled “什么时候需要手写覆盖”官方 spec 与真机固件对不上时。现成的例子是空调伴侣 lumi.acpartner.mcn02:官方把它列为
正常 MIoT 型号,spec-gen 会照着生成 MIoT 方言条目,但这台真机只认老式 get_prop,
生成的条目是错的,必须用手写的 legacy 条目整体盖掉。
version = 1
[[model]]name = "lumi.acpartner.mcn02"dialect = "legacy" # 按属性名寻址, 没有 siid/piid; 官方 spec 里没有这种形态
[[model.entity]] cap = "power" prop = "power" # 设备侧属性名, 写命令是 set_power type = "bool" settable = true两种方言的必填字段不同:dialect = "miot" 要 siid + piid,dialect = "legacy" 要
prop。
生成出来的条目长这样,可以照着手写:
version = 1
[[model]]name = "cuco.plug.v3"urn = "urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:2"dialect = "miot"
[[model.entity]] cap = "switch_s2_on_p1" siid = 2 piid = 1 stype = "switch" ptype = "on" type = "bool" settable = true
[[model.entity]] cap = "power_consumption_s11_electric_power_p2" siid = 11 piid = 2 stype = "power-consumption" ptype = "electric-power" type = "float" settable = false unit = "watt" range = [0, 10000]urn 带版本号,spec-gen --check 靠它把「上游升到 v2 改了属性」报成 diff。手写条目
可以省略。
能力名为什么这么长
Section titled “能力名为什么这么长”格式是 <service>_s<siid>_<property>_p<piid>,例如
fan_circulator.fan_s2_horizontal_swing_p4。
必须带 siid / piid,因为同名属性是常态:一台插座上 on 出现 6 次(开关、指示灯、
充电保护、快捷倒计时、功率上限、超用电量告警各一个),cuco.plug.v3 的 indicator-light
甚至同时存在于 siid=3 与 siid=13;循环扇的 fan-level 在同一个 siid=2 下出现两次
(piid=2 是 1-4 档位枚举,piid=6 是 1-100 无级)。
能力名只能是 [a-z][a-z0-9_]*,且在一个型号内唯一。
stype / ptype 与语义类
Section titled “stype / ptype 与语义类”这两个是官方 URN 里的服务 / 属性类型片段。miot 的语义类不用手标,由它们自动推导—— 一张表覆盖官方目录里全部型号,比逐型号手标可靠得多。
它们是可选字段:v0.4.0 之前生成的老 spec.d 照常加载,只是所有 miot 实体的 class
都是空(降级而非崩溃)。要用上就重新生成一次:
rha --config /etc/rha spec-genrha --config /etc/rha checksystemctl restart rha顺序反过来不会失败,只是这一轮 class 全空。
推导结果不满意时,在 devices.toml 里逐实体覆盖,不要改型号表——见
devices.toml 的语义类。