智能马桶逆向记录:从小程序误标的“脚感”抓出真实入座状态

2026年10月13日2 次阅读0 人喜欢
智能家居Home Assistant智能马桶蓝牙微信小程序Node-RED自动化
所属合集

最近在改浴室的自动化。浴室顶上装了人体存在传感器,走进去自动开灯,没人了延时关灯。

但有个很烦的问题:人在马桶上坐久了,动作幅度一小,人体传感器很容易判定成“无人”,把灯给灭了。摸黑上厕所这种体验经历过一次就不想有第二次。

最好的解决办法自然是在关灯逻辑里加一条:如果马桶上有人坐着,就不许关灯。

这台马桶走的是蓝牙(VCX-Knob 方案),之前为了接进 Home Assistant,我反编译过它配套的微信小程序(skj-toilet),把冲水、翻盖、座温这套做成了 HA 集成(ha-vcx-knob)。

但当时翻遍了小程序源码,根本没找到人体入座的传感器。

小程序只解析了 01 到 06 这六类状态帧。在解析位图里,我只看到一个 virtual_seat_enabled(虚拟座座状态)。测了一下发现那只是个设置开关,根本不是实时的入座检测。小程序的界面上也完全没有展示“当前是否有人坐着”。

但这里有个很明显的物理线索:
每次屁股坐上座圈,马桶内部都会清脆地“滴”一声;站起来之后,又会伴随相应的蜂鸣反馈。

蜂鸣器响了,说明主控板必然收到了座圈微动/重力开关的电平变化。既然蓝牙本来就在周期性上报设备状态,硬件主控没道理把这个状态死死藏在本地。小程序没展示,大概率只是前端没做,或者协议字段写漏、标偏了。

靠猜字段名不行,最稳妥的办法就是抓包对照。

我写了个只监听 BLE 通知帧的脚本连上马桶,不去发任何控制命令:

  1. 先跑脚本,记录马桶空闲无人时的广播状态帧;
  2. 肉身坐上去,听到“滴”的一声,保持坐姿抓几秒;
  3. 起身离开座圈,再抓几秒。

马桶的状态帧结构很规整:
AA 08 88 <type> <data1> <data2> <data3> <checksum>

把坐上去前后的数据一对比,在 Type 01(功能状态位图)这一帧里,变化极其明显:

  • 无人时,data1 的某个 bit 是 0;
  • 人坐上去听到“滴”的一声,data1 的 Bit 1(即 0x02)瞬间变成 1;
  • 人一起身,这个 bit 又稳稳地变回 0。

来回坐起试了三次,完全一致。

蓝牙状态帧对比示意图

这时候我再回过头去看微信小程序的反编译代码:

python 复制代码
chongshui_status = bool(data1 & 0x80)  # 冲水状态
zhuozuo_status = bool(data1 & 0x40)    # 座座状态
paopao_status = bool(data1 & 0x20)     # 气泡状态
kongqi_status = bool(data1 & 0x10)     # 空气状态
leida_status = bool(data1 & 0x08)      # 雷达状态
yuyin_status = bool(data1 & 0x04)      # 语音状态
jiaogan_status = bool(data1 & 0x02)    # 传感器状态 (小程序里标为脚感)
jieneng_status = bool(data1 & 0x01)    # 节能状态

小程序开发者当初把 data1 & 0x02 标成了 jiaogan_status(脚感)。实际上这台马桶根本没有脚感开盖,硬件主控把这个位对应的是座圈微动信号。小程序代码标偏了,界面又没做展示,才让人误以为马桶底层不报入座状态。

既然找到了确切位置,改起来就很快了。

在 ha-vcx-knob 的 protocol.py 里加上字段:

python 复制代码
seat_occupancy = bool(data1 & 0x02)  # 有人入座状态

暴露成二进制传感器 binary_sensor.you_ren_ru_zuo,推了集成 v1.0.4。

最后在 Node-RED 浴室自动关灯流程里加了个拦截判断:人体传感器超时准备关灯时,读一下全局变量,如果有人在马桶上就直接拦住关灯指令;起身后才恢复延时关灯。

浴室自动关灯拦截逻辑流图

折腾完总算闭环了,再也不用担心上厕所突然两眼一抹黑。

加载评论中...