先说结论
Odoo IoT 真正难的地方,常常不是“设备能不能被发现”,而是设备动作发出去以后,系统怎么保证:
- 不会因为重试而执行两遍
- 正确结果回到正确的前端会话
- 同一事件既能给 controller,也能给其他实时通道
从 /home/ubuntu/odoo-temp/addons/iot_drivers/event_manager.py 与 driver.py 看,官方真正建立的是一条:
设备监听会话 → 动作执行 → 幂等防重 → 事件回流
这条链一旦没理解清楚,现场最容易出现的不是“完全不能用”,而是更烦人的半故障:重复打印、状态串台、前端一直等不到正确结果。
第一层:为什么 EventManager 不只是一个事件列表
EventManager 里虽然有 events,但更关键的是 sessions。
每个 session 会记录:
session_id- 关注的
devices - 一个线程事件对象
Event() resulttime_request
这说明 Odoo 的 IoT 事件模型不是“有消息就广播”,而是:
- 前端先声明自己在等什么
- 后端再把设备事件路由给匹配的监听会话
这对现场设备非常重要。
因为同一台 IoT Box 可能同时服务多个界面:
- POS 在等扫码枪结果
- 某个设置页在等硬件状态变化
- 某个业务流程在等外设动作完成
如果没有 session 级路由,事件非常容易串到错误页面。
第二层:为什么 session 要有过期清理
_delete_expired_sessions() 会清理一段时间未再请求的 session。
虽然源码里的实现细节看起来很朴素,但背后的业务意义很明确:
- 前端监听不是永久订阅
- 过期会话不应该一直占着路由表
- IoT 现场事件应尽量回给“仍然活着”的等待者
这点看似是性能细节,其实也关系到正确性。
如果旧 session 不清掉,后续同设备事件可能落到已经无人的会话上,新的页面却一直收不到。
第三层:为什么 device_changed() 同时承担“事件标准化”和“结果分发”
device_changed() 并不只是把数据 append 到列表。
它会把最终事件整理成一个统一结构,包含:
- 设备自身 data
device_identifier- 时间戳
- 动作返回数据
然后再依次:
send_to_controller()- 如果有
webrtc_client就继续发送 - 存进本地事件池
- 遍历所有 session,找出谁该收到这条结果
这说明在 Odoo 里,IoT 事件不是某个单一路径的响应包,而是一个标准化后的现场事实,可以被多个消费通道接住。
第四层:为什么 session 既可以按 device identifier,也可以按 device type 匹配
device_changed() 匹配 session 时,允许监听条件里写:
- 具体
device_identifier - 或
device_type
而且如果 session 等的是设备类型,返回事件时还会把 device_identifier 改成该类型,方便 longpolling 消费。
这说明 Odoo 接受两种不同粒度的监听需求:
一种是盯住具体设备
比如只等某一台扫码枪。
另一种是盯住某类设备
比如系统只关心“任何打印机回个状态都行”。
这让 IoT 前端整合时更灵活,不需要所有界面都提前知道精确硬件 ID。
第五层:为什么 Driver.action() 要做 action_unique_id 幂等防重
Driver.action() 里最关键的细节之一就是 _recent_action_ids 和 action_unique_id。
逻辑非常明确:
- 如果这次动作携带唯一 ID
- 且这个 ID 最近已经出现过
- 就直接 warning 并忽略
这是 IoT 现场控制里非常关键的一层保险。
因为真实世界里动作重复发生的概率远高于普通后台系统:
- 网络抖动导致前端重发
- 用户觉得没反应又点了一次
- 网关或浏览器做了重试
如果不做幂等保护,最常见的事故就是:
- 一张小票打两次
- 某个开门动作执行两次
- 一条外设指令重复写入
把防重做在 driver 层的位置也很合理,因为这里最贴近设备最终执行。
第六层:为什么异常也要变成统一返回结构
Driver.action() 捕获异常后,不是直接炸掉线程,而是也组织成统一 response:
status: errorresult: 异常文本session_idaction_args
这代表官方不希望设备动作的失败变成“前端无声超时”,而是希望失败也能沿同一回流链路被前端接住。
这对现场排障很关键。
因为最糟糕的不是报错,而是没有任何结果。只要失败能被统一回流,前端至少还能:
- 给操作员明确提示
- 记录日志
- 决定要不要重试
第七层:为什么打印机和支付设备被排除在通用回流之外
Driver.action() 里还有一个显眼判断:
printerpayment
这两类设备不走通用 event_manager.device_changed() 回流。
这说明 Odoo 并没有为了统一架构而忽略设备差异。
官方显然知道这两类设备的交互节奏通常更复杂,比如:
- 打印机会有低纸、卡纸、队列状态
- 支付设备会有等待刷卡、授权、取消、超时等专门流程
所以它们保留自己的事件处理路径,反而更成熟。
最容易误解的三个点
误区一:设备动作就是一次普通 RPC
不是。 它后面还涉及会话匹配、幂等保护和多通道回流。
误区二:只要设备被发现,动作就一定稳定
未必。 真正容易出事故的是执行和回流阶段,而不是发现阶段。
误区三:前端等不到结果,多半只是网络慢
不一定。 也可能是 session 过期、监听目标不匹配,或动作被幂等逻辑拦掉。
实战排障时怎么查最稳
如果你在现场排 Odoo IoT 动作异常,我建议按这个顺序查:
- 前端是不是创建了正确的 session,监听的设备粒度对不对
- 动作有没有携带
action_unique_id - 同一个唯一 ID 是否被重复发送后被防重逻辑拦下
- driver 执行报错后,错误 response 有没有成功回流
- 当前设备类型是不是本来就走特殊事件通道
最后总结
Odoo IoT 的现场可靠性,很大程度上不取决于“能不能连上设备”,而取决于三件事是否同时成立:
- 会话路由准确
- 动作执行幂等
- 结果回流统一
理解这条链之后,你就会知道 Odoo IoT 真正要防的不是单纯断连,而是那些最折磨人的“偶发重复执行”和“结果回不来”的现场问题。
DISCUSSION
评论区