rha 命令行
rha 没有配置界面。改配置靠编辑 TOML,看状态和排障靠这套命令行——面板是 agent 画给 人看的展示层,不是控制台。所以下面这些命令就是运维这套系统的全部入口。
先分清两类,很多困惑出在这里:
-
serve/check/doctor/miot-token/spec-gen/config-schema直接读 配置目录干活,不需要 daemon 在跑。 -
其余全部(
status/get/set/history/trace/reload/mcp/dashboard)是 HTTP API 的客户端。它们不读设备也不解析规则,只是把请求发给正在 跑的 daemon。daemon 没起来就是这一条:error: request failed: error sending request for url (http://127.0.0.1:8420/api/entities)
三个全局参数,写在哪都行
Section titled “三个全局参数,写在哪都行”--config / --url / --token 是全局参数,放在子命令前后都认:
rha --config /etc/rha statusrha status --config /etc/rha # 完全一样rha status --url http://192.168.52.40:8420 --token "$RHA_TOKEN"--config 默认 /etc/rha。--url / --token 不写时从配置目录取——地址是
[api].listen 拼成的 http://<listen>,token 是 [api].token。
环境变量 RHA_URL / RHA_TOKEN 与对应参数等价,命令行参数优先。
日常查看与控制
Section titled “日常查看与控制”status / get —— 当前值
Section titled “status / get —— 当前值”status 列全部实体,get <设备名> 只列这一台的:
$ rha statusfan_demo.power truethermo_demo.temperature 21.5°Cbroken_plug.power nullnull 是「这个实体还没有过值」——推送型设备(MQTT、BLE 广播)冷启动时正常会这样,等
下一次上报。实体不可用时行尾多一个 [unavailable]。
set —— 下发一条命令
Section titled “set —— 下发一条命令”rha set fan_demo.power onrha set light.brightness 80值的解析规则很短:on / true → 布尔真,off / false → 布尔假,能解析成整数就是
整数,能解析成小数就是小数,其余当字符串。之后再按实体声明的类型做一次转换,对不上就
拒绝:
$ rha set fan_demo.power helloerror: 400 Bad Request: value type mismatch for fan_demo.power (Bool)只有 kind = "switch" 的实体能写。 传感器读数、瞬时信号一律拒掉,而且明说是因为
kind:
$ rha set thermo_demo.temperature 25error: 400 Bad Request: entity thermo_demo.temperature is not writable (kind: Sensor)history —— 存下来的读数
Section titled “history —— 存下来的读数”需要 storage 打开,关着的话:
$ rha history fan_demo.powererror: 503 Service Unavailable: storage disabled时间窗默认是最近 24 小时(--to 缺省为现在,--from 缺省为 --to 减 24 小时),
两个都收 RFC 3339 时间串:
rha history thermo.temperature --from 2026-08-01T00:00:00Z --to 2026-08-02T00:00:00Zrha history thermo.temperature --hourly--limit 默认 500,服务端上限 5000(要更多就缩小时间窗分段拉)。
trace —— 规则为什么没跑
Section titled “trace —— 规则为什么没跑”$ rha trace fan_on_hot --limit 52026-08-03T01:15:13.936567Z throttled fan_demo.power: Some(Bool(false)) -> Bool(true)2026-08-03T01:15:13.936538Z done 1 action(s)2026-08-03T01:15:13.936512Z fired fan_demo.power: Some(Bool(true)) -> Bool(false)新→旧排列,--limit 默认 20。每一档 outcome 的含义见
automations 的四道闸。
两条限制要知道:
- 规则名必须在当前生效的规则集里,否则是 404 而不是空列表。刚
rha reload过、规则 被改名或删掉,这里就查不到了。 - trace 是内存里的环形缓冲,全部规则共用 1000 条的总容量。 一条每秒都在触发的规则会
把安静规则的历史挤出去;daemon 一重启,全部清空(storage 打开时 trace 另有一份落盘,
但
rha trace读的是内存那份)。
check —— 离线校验,能进 pre-commit
Section titled “check —— 离线校验,能进 pre-commit”$ rha --config /etc/rha checkconfig ok: 1 entities, 0 rules失败时逐条列到 stderr,最后一行给总数,退出 1:
$ rha --config /etc/rha checkerror: automations (x): trigger entity "nope.thing" does not exist1 error(s)它完全不碰网络:不连设备、不连 broker、不开蓝牙扫描、不查 DNS。这不是碰巧成立的,
是 adapter 的 describe() 契约撑起来的——每个 adapter 必须能在
离线状态下说清「我这台设备有哪些实体」。
代价和收益都在这条上:收益是 rha check 可以挂 git pre-commit、可以在 CI 里跑,也可以
在完全没有设备的开发机上验整份配置;代价是 rha 不做设备自动发现(那需要联网才知道有
什么实体),MQTT 设备得手写。
rha serve 启动时跑的是同一段校验,所以配置坏了 serve 也起不来(退出 1)。
reload —— 与 SIGHUP 等价
Section titled “reload —— 与 SIGHUP 等价”$ rha reloadreloaded: 1 rules, 0 actions这三种写法走的是同一条代码路径,效果完全相同:
rha reload # 打 POST /api/reloadkill -HUP <pid> # 直接发信号systemctl reload rha # ExecReload=/bin/kill -HUP $MAINPID先校验、全过了才切换。任何一条不过,现役配置一动不动——不存在一次 reload 把系统搞挂 的路径。
现在能热加载的是两样:
- 规则与命名动作(
automations/*.toml) - 设备的增、删、改(
devices.toml)。加一台设备不会重启 daemon,既有设备的连接、 会话、轮询节奏都不受影响;改参数(比如 IP 变了)等价于只重启那一台。判断「变没变」 用的是devices.toml里那一条的原文,所以只改label也会让这台设备重连一次——换来 的是不会出现「改了显示名 reload 说成功、实际还是旧名字」这种静默失效。
仍然要 systemctl restart 的:[api] / [storage] / [llm] / [notify] 四段,
以及 bridges.toml 和 spec.d/(型号表在启动时读进内存,跑着的
adapter 手里就是那一份)。
还有一个可见的副作用:设备集一变,全部 bridge 会重起一遍。 桥的导出集跟着设备走, 不重起就还导着旧的那批。表现是 HomeKit 断开重连大约一秒。
serve —— 前台跑 daemon
Section titled “serve —— 前台跑 daemon”rha --config /etc/rha serve前台运行,不会自己变成守护进程;rha listening on 127.0.0.1:8420 这一行走 stdout,
日志全部走 stderr(这是全局固定的,为的是 rha mcp 的 stdout 只有 JSON-RPC 帧)。
日志级别用 RUST_LOG 调,默认 info。
信号处置:
SIGTERM(systemctl stop|restart发的)与SIGINT(Ctrl-C)走同一条优雅关停路径。SIGHUP触发一次 reload,结果写日志,reload 失败不会让进程退出。
systemd 单元用 Type=notify:就绪后发 READY=1,关停时发 STOPPING=1,并按
WatchdogSec 喂狗。所以 systemctl start rha 返回时服务是真的在听了,不是「进程起来了
但还在初始化」。
doctor —— 环境自检
Section titled “doctor —— 环境自检”$ rha --config /etc/rha doctor[OK] config: 1 entities, 0 rules[WARN] miot-spec: /etc/rha/spec.d 里没有任何型号 —— 跑 `rha spec-gen` 生成[OK] adapter-registration: 1 device(s), all adapters registered[OK] storage: storage disabled[WARN] bluetooth: bluetooth check unavailable (needs a linux build with the mibeacon feature)[WARN] device-reachability: lamp (192.168.52.31:54321) unreachable (device may be off)[OK] token-format: all tokens/bindkeys are valid 32-hex逐项查什么:
| 检查项 | 查的是 | 失败等级 |
|---|---|---|
config |
与 rha check 完全同一条路径:加载四份配置 + 语义校验 |
FAIL |
miot-spec |
spec.d/ 能不能读、里面有几个型号 |
空目录 WARN,读取出错 FAIL |
adapter-registration |
每台设备的 adapter 名在不在本二进制注册的列表里 |
FAIL |
storage |
在 db 文件的父目录里建一个探测文件再删掉,验可写 | FAIL |
bluetooth |
能不能开 bluez 会话、有没有默认适配器 | 一律 WARN |
device-reachability |
只探 miot(UDP hello 包)和 reolink(TCP 80/443),各 2 秒超时 | 一律 WARN |
token-format |
miot 的 token 与 mibeacon 的 bindkey 是不是 32 位十六进制。纯格式,不联网 |
FAIL |
miot-spec 单独列一项是有原因的:这个目录缺了的话,每台 miot 设备都会报「型号不在
spec.d 里」,真正的原因(目录压根不在)会被埋在一堆派生错误里。
doctor 是 check 的超集,但它会碰网络(UDP/TCP 探测、BLE 会话)。所以能进
pre-commit 的是 check,不是 doctor。
还有一个读日志的技巧:配置压根加载不出来时,miot-spec 那一行会整个消失,其余各项
变成 skipped: config failed to load。看到一份只有 [FAIL] config 加一串 skipped 的
输出,先修 TOML 语法,别看后面。
一次性取数据
Section titled “一次性取数据”这三个都是运维时命令:联网(或读代码里的静态信息)产出配置材料,跑完就完事,不参与
serve。
miot-token —— 从米家云取设备凭据
Section titled “miot-token —— 从米家云取设备凭据”export MI_PASSWORD='...'rha miot-token --username 13800138000 --region cn输出一台设备一行,token 填进 miot 设备的 token 字段,
bindkey 填进 mibeacon 设备的 bindkey 字段(同行给出
mac,用来跟配置对上号)。
--password 能直接写在命令行上,但建议走 MI_PASSWORD 环境变量:argv 对同一台机器
上的其他用户 ps 可见,也会进 shell history。--region 默认 cn,其他区域(us /
eu / ru / in)会换成对应的子域。
两个会让人以为出了故障、其实是正常提示的地方:
- 云端没给某台设备 token(常见于网关子设备)不会静默跳过,会往 stderr 写一条
warning: <名字> (<did>) 云端未返回 token。少一行会被当成「这台设备不存在」。 - BLE 设备拿到的
bindkey不是 32 位十六进制时,行尾会跟一句<- 不是 32 hex, rha 的 MiBeacon v5 解密用不了。那是旧版设备,当场说清好过等你在rha check撞墙。
spec-gen —— 生成 miot 型号表
Section titled “spec-gen —— 生成 miot 型号表”rha --config /etc/rha spec-gen # 联网生成 spec.d/10-generated.tomlrha --config /etc/rha spec-gen --check # 只比对不写盘, 有差异非零退出它只解析 devices.toml 里真出现过的型号,--check 适合挂定时任务发现「上游把 spec
改了」。细节(文件归属、加载顺序、能力名格式)见 spec.d。
config-schema —— 输出配置项的 JSON Schema
Section titled “config-schema —— 输出配置项的 JSON Schema”本站所有字段表的数据来源。不需要配置目录也能跑(schema 描述的是配置的形状,不是某 一份具体配置)。
给 agent 用
Section titled “给 agent 用”mcp —— stdio MCP server
Section titled “mcp —— stdio MCP server”rha --config /etc/rha mcp挂到 Claude Code(~/.claude.json 的 mcpServers):
{ "rha": { "command": "rha", "args": ["--config", "/etc/rha", "mcp"] } }daemon 自己也内置一个 POST /mcp(与 API 同一个 bearer token)。rha mcp 是给只会说
stdio 的客户端准备的另一个入口,不是另一套实现。
工具共 10 个:list_entities / list_devices / get_state / history / control /
list_rules / upsert_rule / delete_rule / get_trace / upload_dashboard。
rha mcp 的 stdout 是 JSON-RPC 帧通道,所以全部日志固定写 stderr——这是全局设置,
不是只对 mcp 生效。
dashboard —— 面板的推送与回滚
Section titled “dashboard —— 面板的推送与回滚”agent 画好一个静态目录,用这组命令托管到 daemon 上:
rha dashboard push bedroom ./dist # 目录里必须有 index.htmlrha dashboard listrha dashboard rollback bedroomrha dashboard rm bedroom面板名只能是 [a-z0-9_-],最长 64 字符(大写字母会被拒:bad dashboard name "Demo")。
push 会在本机先做四件事再上传,为的是让错误当场出现而不是等服务端:
- 目录里没有
index.html就直接拒绝——面板入口必须叫这个名字。 - 跳过
node_modules/和.git/。一次误传就能撞上大小上限。 - 拒绝符号链接。 一个指向
/etc/rha/rha.toml的链接会把 API token 一起打包发走。 - 打包后超过 8 MiB 本地就拦下。服务端超限回的是 413,响应体是纯文本而不是
{ok,data,error}信封,本机先拦能给出看得懂的错误(最常见的原因是把整个项目目录而不是构建产物目录传了 进去)。
list 在没有面板时不打印一个 [],而是给一句能照着做的话:
$ rha dashboard list还没有面板。用 `rha dashboard push <名字> <目录>` 传一个只有 0 和 1 两个值。 没有 2、3 这类细分码,所以脚本里区分失败原因要看 stderr,不能 只看退出码。
| 命令 | 0 表示 | 1 表示 |
|---|---|---|
check |
配置全过 | 加载失败或校验失败;错误逐条在 stderr,末行是 N error(s) |
doctor |
没有 FAIL 项(WARN 不算) | 至少一项 FAIL |
serve |
收到 SIGTERM / SIGINT 后优雅退出 | 启动期校验失败、端口占用、绑定失败 |
reload |
校验通过并已切换 | 校验失败(此时旧配置继续跑)或连不上 daemon |
status / get |
请求成功——哪怕一行都没打印 | 连不上、认证失败、配置读不出来 |
set |
命令结果是 ok |
failed / timeout / 实体不存在 / 类型不匹配 / 不可写 |
history / trace |
请求成功 | 实体或规则不存在、storage 关着、连不上 |
spec-gen |
写盘成功;--check 时表示与官方 spec 一致 |
拉取失败、型号不在官方目录、--check 发现差异 |
miot-token |
设备表打印完毕 | 登录被拒、二次验证失败、拉设备表失败 |
dashboard * |
操作成功 | 本地校验失败或服务端拒绝 |
mcp |
客户端断开,会话正常结束 | URL 不合法、传输错误 |
config-schema |
总是 0 | —— |
要进 CI 和监控的是 check 与 doctor 这两条,它们的语义刻意分开了:
check管「配置对不对」,离线、可复现、与环境无关。放 pre-commit 和 CI。doctor管「这台机器现在能不能跑」,会碰网络。放开机自检和监控告警,而且因为 「设备关机」只是 WARN,它不会因为你出门时拔了一个插座就把你叫醒。