其他深度

Odoo IoT 动作为什么最怕“点了两次”:事件会话、幂等防重与结果回流讲透

之前很多人关注 Odoo IoT 的配对和设备发现,但真正容易在现场出事故的,是动作执行和事件回流:同一个指令会不会重复触发、前端到底等的是哪台设备、结果是回 controller 还是 WebRTC。沿着 iot_drivers 的 event_manager.py 与 driver.py 看,官方其实在解决一条“会话路由 + 幂等执行 + 多通道回流”的现场控制链。

其他
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

Odoo IoT 真正难的地方,常常不是“设备能不能被发现”,而是设备动作发出去以后,系统怎么保证:

  • 不会因为重试而执行两遍
  • 正确结果回到正确的前端会话
  • 同一事件既能给 controller,也能给其他实时通道

/home/ubuntu/odoo-temp/addons/iot_drivers/event_manager.pydriver.py 看,官方真正建立的是一条:

设备监听会话 → 动作执行 → 幂等防重 → 事件回流

这条链一旦没理解清楚,现场最容易出现的不是“完全不能用”,而是更烦人的半故障:重复打印、状态串台、前端一直等不到正确结果。

第一层:为什么 EventManager 不只是一个事件列表

EventManager 里虽然有 events,但更关键的是 sessions

每个 session 会记录:

  • session_id
  • 关注的 devices
  • 一个线程事件对象 Event()
  • result
  • time_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_idsaction_unique_id

逻辑非常明确:

  • 如果这次动作携带唯一 ID
  • 且这个 ID 最近已经出现过
  • 就直接 warning 并忽略

这是 IoT 现场控制里非常关键的一层保险。

因为真实世界里动作重复发生的概率远高于普通后台系统:

  • 网络抖动导致前端重发
  • 用户觉得没反应又点了一次
  • 网关或浏览器做了重试

如果不做幂等保护,最常见的事故就是:

  • 一张小票打两次
  • 某个开门动作执行两次
  • 一条外设指令重复写入

把防重做在 driver 层的位置也很合理,因为这里最贴近设备最终执行。

第六层:为什么异常也要变成统一返回结构

Driver.action() 捕获异常后,不是直接炸掉线程,而是也组织成统一 response:

  • status: error
  • result: 异常文本
  • session_id
  • action_args

这代表官方不希望设备动作的失败变成“前端无声超时”,而是希望失败也能沿同一回流链路被前端接住。

这对现场排障很关键。

因为最糟糕的不是报错,而是没有任何结果。只要失败能被统一回流,前端至少还能:

  • 给操作员明确提示
  • 记录日志
  • 决定要不要重试

第七层:为什么打印机和支付设备被排除在通用回流之外

Driver.action() 里还有一个显眼判断:

  • printer
  • payment

这两类设备不走通用 event_manager.device_changed() 回流。

这说明 Odoo 并没有为了统一架构而忽略设备差异。

官方显然知道这两类设备的交互节奏通常更复杂,比如:

  • 打印机会有低纸、卡纸、队列状态
  • 支付设备会有等待刷卡、授权、取消、超时等专门流程

所以它们保留自己的事件处理路径,反而更成熟。

最容易误解的三个点

误区一:设备动作就是一次普通 RPC

不是。 它后面还涉及会话匹配、幂等保护和多通道回流。

误区二:只要设备被发现,动作就一定稳定

未必。 真正容易出事故的是执行和回流阶段,而不是发现阶段。

误区三:前端等不到结果,多半只是网络慢

不一定。 也可能是 session 过期、监听目标不匹配,或动作被幂等逻辑拦掉。

实战排障时怎么查最稳

如果你在现场排 Odoo IoT 动作异常,我建议按这个顺序查:

  1. 前端是不是创建了正确的 session,监听的设备粒度对不对
  2. 动作有没有携带 action_unique_id
  3. 同一个唯一 ID 是否被重复发送后被防重逻辑拦下
  4. driver 执行报错后,错误 response 有没有成功回流
  5. 当前设备类型是不是本来就走特殊事件通道

最后总结

Odoo IoT 的现场可靠性,很大程度上不取决于“能不能连上设备”,而取决于三件事是否同时成立:

  • 会话路由准确
  • 动作执行幂等
  • 结果回流统一

理解这条链之后,你就会知道 Odoo IoT 真正要防的不是单纯断连,而是那些最折磨人的“偶发重复执行”和“结果回不来”的现场问题。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。