跳转到内容

roborock(石头扫地机)

石头扫地机走局域网直连(AES-256-GCM 加密的私有协议),运行期完全不接触石头云。 接一台要两样东西:IP 和 local_key

[[device]]
name = "robot"
adapter = "roborock"
ip = "192.168.52.66"
local_key = "${ROBOROCK_LOCAL_KEY}"
consumable_every = 40

rha 不做石头云登录,crate 里连 HTTP 客户端都没有。这不是偷懒:那条路会因为「用户 协议版本」这类服务端状态整个卡死(实测先撞 2031 需要二次验证,再撞 3006 用户协议), 把它焊进守护进程等于引入一个不可控的外部故障源。

取法任选其一,都只做一次:

Terminal window
pipx run --spec python-roborock roborock login --email <你的邮箱>
pipx run --spec python-roborock roborock list_devices # 取目标设备的 localKey

或者从已在用的 Home Assistant Roborock 集成的配置项里读同一个值。登录被 3006 拒是 石头服务端要求重新接受用户协议——先在石头 App 里点一次确认再重试。

设备不重新配网,这个 key 就不会变。

key 错了的症状是「握手成功但所有命令失败」:握手载荷是空的、不加密,所以错的 key 也能连上。日志里是 payload decryption failed (wrong local_key?)——别误诊成网络问题。

这台机器不发 UDP 发现广播(Mac 与 PVE 上各监听约 5 分钟,一条未收到),所以没有 miot 那种「按 device_id 自动重定位」的兜底。建议在路由器上给它做 DHCP 保留,否则租约 一漂就得改配置。

主刷、边刷、滤网寿命是小时级变化的量,没必要跟状态一起每 15 秒问一遍。所以耗材单独 分频:每 consumable_every 轮状态轮询才拉一次,缺省 40 轮 ≈ 10 分钟。

0 会在 check 阶段直接报 consumable_every must be >= 1——不拦的话它会变成「无声地 永不拉耗材」,那些实体永远没有值。

describe() 必须离线(rha check 不允许探测设备),所以实体表写死在代码里,不像 miot 那样从 spec.d 读:

  • 传感器state(字符串状态名)、batteryerrorclean_areaclean_timemain_brush_lifeside_brush_lifefilter_life
  • 可写action"clean" / "pause" / "stop" / "dock")、fan_powerwater_box_modeclean_room

action 的值是命令词字符串,不是布尔:

[[rule]]
name = "clean_at_9am"
trigger = { cron = "0 9 * * *" }
action = { entity = "robot.action", set = "clean" }

未知的命令词一律报错,绝不静默忽略——静默忽略会让规则引擎收到「成功」却什么都没发生。

这台机器上只有 battery 有跨生态的语义类:action / fan_power / water_box_mode / clean_room 虽然可写,但值是命令词或档位整数而不是布尔,任何 class 都表达不了,因此 它们导不进 HomeKit

字段类型默认说明
consumable_everyinteger40

每多少轮状态轮询才拉一次耗材。耗材是小时级变化的量。

ip*string

扫地机在局域网里的地址, 必须是字面 IP: 主机名/mDNS 名过不了 rha check (报 bad ip)。整条链路是直连设备的 58867 端口, 不经过任何云服务。

与 miot 不同, 这里没有"配了 device_id 就自动重新定位"那一层(见 rha_adapter_miotdevice_id): DHCP 把地址换掉之后, 表现就是一直连不上、 supervise 退避重试到你改配置为止。所以在路由器上给它留个固定地址。

local_key*string
Secret

敏感值。建议写成 ${ENV} 由环境变量插值, 不要把明文提交进配置文件。该值绝不进日志与 trace。

max_timeoutsinteger3

连续多少轮轮询超时后放弃(默认 3), 交给 supervise 标 unavailable + 指数退避重启。

只数超时(连着但不回话)。传输层断开不计入 —— 设备本来就会隔几分钟主动断一次, 那条路径是内部透明重连, 属常态而非故障。任何一轮成功都会把计数清零。

因此从设备真的失联到实体变 unavailable, 大约要 max_timeouts 轮(默认约半分钟); 想更快发现就一起调小 poll_ms, 而不是只把这里改成 1 —— 那样一次偶发丢包就会 触发一次重启。

poll_msinteger15000

状态轮询间隔, 默认 15 秒。一"轮"就是一次 get_status: 除三个耗材实体(它们走 consumable_every 分频)和只用于下发的 action/clean_room 外, 其余实体的值 都由它刷新。

它同时是 consumable_every计时单位: 耗材的真实间隔是 poll_ms × consumable_every(默认 15 秒 × 40 = 10 分钟), 改这里会连带把耗材 节奏一起改掉。

往大调是安全的: 连接保活是独立的 10 秒 PING(设备约 4.5 分钟收不到包就会主动 断连), 不靠状态轮询撑着。

poll_timeout_msinteger5000

每一次 RPC 等响应的上限(毫秒), 默认 5000 —— 不只是状态轮询: 拉耗材、保活 PING、下发命令, 以及分区清扫前那次 get_room_mapping 校验都按它计时。

调小了先坏的往往是命令: 设备执行慢一点就会以 Timeout 回执告诉规则引擎, 而机器 其实动了。它与 max_timeouts 一起决定多久判定设备失联。