跳转到内容

fake(测试用)

虚拟设备。不碰任何真实硬件、不发一个包,实体的形状和值全部由你在配置里写死。用来 跑 demo、写集成测试、以及在没有设备的机器上验证规则逻辑。

它的参数是嵌套的:一个 [[device]] 下挂一张 entities 映射,键是能力名(实体 id 点号之后那半),每个能力一段自己的配置。

[[device]]
name = "thermo_demo"
adapter = "fake"
[device.entities.temperature]
kind = "sensor"
type = "float"
unit = "°C"
class = "temperature"
script = [
{ after_ms = 1000, value = 25.0 },
{ after_ms = 5000, value = 27.5 },
{ after_ms = 10000, value = 29.0 },
]
[[device]]
name = "fan_demo"
adapter = "fake"
[device.entities.power]
kind = "switch"
type = "bool"
class = "fan"
initial = false

这就是快速开始里那个「温度爬升穿过 28°C 就开风扇」的演示——温度 不是真的,是上面这段脚本走出来的。

script 是时间线,after_ms 是绝对偏移

Section titled “script 是时间线,after_ms 是绝对偏移”

after_ms 从设备启动那一刻起算,不是「距上一步多久」。 上例是第 1 秒报 25.0、 第 5 秒报 27.5、第 10 秒报 29.0,总共 10 秒,不是 16 秒。

脚本跑完就停在最后一个值上,不循环。initial 是脚本开始之前立刻上报的值,两者可以 一起用(先给一个初值,再让脚本改它)。

这两个是每个能力块里仅有的必填字段,也都是封闭词汇表——写别的值在 rha check 阶段就报错。

kind 决定这个实体是什么形态(只读的值、可写的开关、还是瞬时信号):

取值含义
sensor

switch

trigger

type 决定 initialscript 里的值该怎么写(booltruefloat25.0str 写引号串):

取值含义
bool

int

float

str

其它 adapter 的语义类要么自动推导(miot 从官方 spec 的 URN 推)、要么写死在代码的型号表 里,唯独 fake 是从配置读的——这也是它唯一一个你必须自己填对的字段。

代价是:classkind / type 对不上(比如给 sensoroutlet)会在 rha check 报错。取值表见 devices.toml 的语义类。不写 class 也完全正常,只是这个实体 导不进 HomeKit

写了会直接报 fake adapter does not support trigger kind。想拿瞬时信号实体试规则的 fired 触发器,目前只有真的 reolink 设备能产出。

fail_commands 让这台设备把收到的每条写入都判失败,ignore_commands 让它收下但什么都 不做(值不变)。用来验「规则发了命令但设备没照做」这类路径——那是真机上最难复现、也最 容易被当成 bug 的一类现象。

每个 [device.entities.<能力名>] 块:

字段类型默认说明
classstring
DeviceClass
可省略

语义类。fake 是测试/演示 adapter, 所以直接从配置读 —— 集成测试要能造出 任意 (kind, value_type, class) 组合来验校验逻辑。

一个实体承载的语义。

刻意窄 —— 封闭枚举加一项就要动 core 和所有 bridge, 宁可少。刻意不照抄 HomeKit 的 Service 列表: [DeviceClass::Power] 与 [DeviceClass::Weight] 在 HAP 里没有 标准对应, 照抄的话这两个语义根本进不来, 接 MQTT/HA 时又得重新发明。内核词汇表与 任何单一协议的对象模型必须分开。

fail_commandsbooleanfalse

让这个开关把每条命令都回成失败, 且不改值。模拟的是"设备在线但拒绝执行", 用来验规则/API 侧对失败回执的处理。

ignore_commandsbooleanfalse

让这个开关对命令完全不回话: 不改值, 也不发回执。模拟的是丢包/设备假死 —— 与 fail_commands(当场明确失败)是两条不同的路径, 这条要等 Router 的看门狗到点 补一条 Timeout(见 rha_core::router), 正是用来验那条超时路的。

两个都配时这条优先。

initialany
Value
可省略

启动瞬间先发一次的值。不配的话这个实体在第一条脚本步之前没有值, 没有脚本 就是永远没值 —— rha status 里显示成 null。

开关尤其要配 —— 它的值只有被命令改过才会变, 不配就一直空着。

kind*string
EntityKind

这个实体是只读的量、可写的开关, 还是事件 —— 它唯一决定命令收不收: 只有开关 会执行 Set、把新值回读上报再回 Ok, 其余种类一律回 Failed("entity not writable")。想让规则的 then 打到这个实体上, 就得选开关。

事件那一种 fake 不支持(它只会上报值, 从不发事件): 配了 rha check 直接报错, 而不是给你留一个永远不触发的实体。

scriptarray可省略

一条按时间轴回放的值序列: 设备任务一启动就开始跑, 放完停在最后一个值, 不循环。 每个实体各有一条时间轴, 都从同一个零点并行开始。

initial 的先后是确定的 —— initial 在第一步之前先发出去, 所以"一开始就有 值、之后再变"要两个都写。

type*string
ValueType

实体的值类型。上报的值(initial / script 里写的那些)入库前要按它转换一次, 对不上就整条丢掉, 只在日志里留一行 type mismatch, report dropped —— 表面症状是"明明配了值, rha status 却一直是 null"。

只有整数→浮点这一个方向会自动放宽, 反向不行: 声明成整数却写 25.0(TOML 里 带小数点就是浮点)会被丢掉; 声明成浮点写 25 没问题。

unitstring可省略

展示用的单位串, 原样跟着实体元数据走到 rha status / API / MCP。

引擎不换算、也不校验它与 typeclass 自不自洽(刻意的, 见 rha_core::class 的模块文档), 北向 bridge 更是完全不看它 —— 写什么就在界面上显示什么。

script 数组里每一步:

字段类型默认说明
after_ms*integer

相对设备任务启动时刻的偏移(毫秒), 不是距上一步的间隔: 100400 两步 是在第 100ms、第 400ms 各发一次, 而不是等 100ms 再等 400ms。

因此要按升序写。写小了不报错, 只会立刻发出去(等待时长按饱和减算, 不会倒着等)。

value*any
Value

这一步上报的值。类型必须与实体的 type 相符, 否则这一步会在入库时被丢掉, 表现为"脚本跑了但值没变"(细节见 type)。