ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

无人售卖柜系统技术拆解:从MQTT通信到自动结算闭环

无人售卖柜系统技术拆解:从MQTT通信到自动结算闭环 无人售卖柜这几年铺得有多快想必大家都有目共睹。地铁站、办公楼、健身房、公寓楼道到处都能看到那种扫码开门、拿了就走、关门自动扣款的柜子。很多人觉得这不就是“大号自动售货机”嘛但真到自己动手做一套系统时才发现里面的门道远比想象中复杂。一套完整的无人售卖柜系统核心就是两条主线一条是设备端到服务端的通信链路另一条是开门、关门到自动扣款的交易闭环。这篇文章就顺着这两条主线把整个系统的技术拆解一遍从通信协议选型、主控板逻辑到结算引擎、异常处理逐个讲清楚。这篇文章适合谁看如果你是做技术选型、准备入局无人零售的决策者或者你手头正好要落地一个售卖柜项目、需要搭建软硬件架构又或者你是做IoT开发的工程师想看看这类带锁控、带支付闭环的场景到底怎么设计那这篇文章应该能给你一套完整可参考的方案。我会尽量按项目落地的顺序来讲先搭骨架再填细节最后把我在实际项目中踩过的坑一并整理出来。1. 先想清楚无人售卖柜本质上是三个系统的组合在动手之前我强烈建议你先把自己从“造柜子”的思维里拽出来。无人售卖柜不是一台硬件设备而是三个系统的组合体负责物理交互的柜体与主控板、负责数据传输的IOT通信链路、负责订单与资金的业务服务端。三个系统单独拎出来都不复杂但组合在一起真正的复杂度就来了。1.1 为什么这套系统不能用“售货机思维”来做传统自动售货机是“投币-选货-掉货”的流程交易过程中的每一步都有明确的机械动作响应掉没掉货、掉了几瓶机器自己是有感知的。但无人售卖柜完全不同它几乎没有“出货”这个动作柜门一开一关用户拿了什么、放回了什么设备本身没有视觉能力去确认它只能依赖传感器数据去“猜”。这个本质差异决定了技术架构的方向传统的售货机是程序驱动机械而无人售卖柜是事件驱动数据。开门是一个事件门磁检测到门开是第二个事件称重传感器数值变化是第三个事件关门是第四个事件每个事件都要上报、都要参与结算判断。所有的业务逻辑都建立在对这一串事件流的处理上而不是建立在一个机械动作的完成上。所以当你规划架构时必须先意识到设备端不是一个简单的执行器它是一台边缘计算节点。主控板要在本地完成门锁控制、传感器采样、数据缓存、断网重传等一系列工作而服务端则要负责所有事件的汇聚、订单状态机的推进和资金结算。两者之间不是简单的“请求-响应”而是一套长期存在的、有状态的消息交互体系。1.2 整体架构的五个逻辑层我在设计无人售卖柜系统时习惯把整体架构拆成五层每一层只做自己该做的事层与层之间通过明确定义的接口通信。这样分的好处是后面排查问题时能快速定位故障点不会被一团乱麻的耦合代码困住。层级核心职责典型技术选型设备感知层采集开门/关门状态、重力/红外传感器数据门磁开关、重力传感器、红外对射主控与执行层控制电子锁、执行本地逻辑、缓存数据STM32/ESP32、锁控板、继电器通信传输层设备与服务端之间的数据通道4G模组、MQTT/HTTP、蓝牙BLE业务服务层订单管理、库存管理、设备管理、结算Spring Boot/Go、MySQL、Redis管理应用层运营后台、用户小程序、运维告警Vue/小程序、监控大屏从这个分层可以看到设备通信和自动结算是贯穿中间三层的核心链路。设备感知层的数据要通过通信传输层送到业务服务层业务服务层计算出的结果又通过通信传输层下发给主控执行层。通信链路不只是一个管道它承载的每一次消息都可能直接触发资金变动这跟普通传感器上报数据完全是两码事。2. 设备通信柜子怎么跟服务器“保持在线”无人售卖柜的通信设计是整个系统里最容易被低估的部分。很多团队一开始用最简单的HTTP轮询来做结果柜子一多、断网一频繁问题全部暴露出来。我先说结论主柜和服务器之间我推荐走MQTT长连接 断线重连机制副柜与主柜之间走RS485串口或CAN总线用户手机与柜子之间的近场交互用蓝牙BLE。2.1 为什么MQTT比HTTP更合适无人售卖柜的业务场景有很强的实时性和不确定性。用户扫码开门的瞬间服务器要知道这个柜子是空闲的还是别人正在用这个状态必须是秒级的用户关门后的那几秒服务器要拿到传感器数据并计算订单这个判断也必须是及时的。如果用HTTP短轮询来做假设每台柜子每5秒请求一次1000台柜子就产生每秒200个请求这还只是保活占用的资源真正的业务请求还没算进去。MQTT是长连接模型基于发布/订阅模式设备和服务端之间维持一条TCP长连接服务端可以主动往设备下发指令设备也能即时上行状态。这正好契合无人售卖柜“服务端主动控锁”的需求。比如用户小程序点了“开门”服务端是通过MQTT把开锁指令下发到主控板的如果是HTTP模型服务端只能等柜子下次轮询才能动作那用户体验基本就废了。另外MQTT的QoS机制也是一大优势。QoS 0是至多一次QoS 1是至少一次QoS 2是恰好一次。我在实际项目里设备上报类消息用QoS 0比如心跳、温度这类丢了也无所谓的关键业务消息用QoS 1比如开锁指令、关门结算请求因为这类消息重复推送还可以靠消息幂等去兜底但丢失就是事故。2.2 心跳保活与断线重连的细节设计设备端连上MQTT broker之后绝对不能发完消息就断。无人售卖柜常年挂在墙角网络环境不稳定4G信号时好时坏路由器重启、运营商基站切换都很常见。所以设备必须在后台维持一条可靠的连接通道心跳保活就是这条通道的生命线。心跳间隔我推荐设置在30秒到60秒之间。太短了设备电量消耗大、服务器压力高太长了broker可能早就把连接断掉了服务端还以为设备在线。这里有一个容易踩坑的点MQTT的keepalive字段和实际发心跳的频率必须匹配。假如keepalive设的60秒但代码里却每120秒才发一次心跳包那broker会在第一次 超时后就断开连接之后你发的所有消息全部打水漂。断线重连不能做成无脑循环否则一旦设备所在网络大面积故障所有设备同时重连部署的MQTT broker会直接被打崩。我见过一个项目几百台设备同时掉线重连逻辑写得激进恢复网络后瞬间涌进来的连接请求把集群搞挂了最后只能重启大法。正确的做法是指数退避 随机抖动第一次重连等2秒第二次等4秒第三次等8秒最长不超过60秒每次重连前再加一个0到10秒的随机偏移这样就能避免“重连风暴”。2.3 设备上下线状态打通“在线标记”与“可用状态”通信链路搭建好以后服务端必须维护一张真实的设备在线表。很多团队用Redis来存设备在线状态key是设备编号value是最后心跳时间戳过期时间设成心跳间隔的三倍。每次收到心跳就刷新这个key如果key过期了就认定设备离线。这里有一个很关键的联动逻辑设备离线状态必须直接关联业务可用性。我做过的方式是当柜子的主控板离线的瞬间后台系统把该柜子标记为“不可用”用户端小程序立刻停止展示这台柜子当设备重新上线且自检通过后才恢复可售状态。千万别让用户扫了码、看到柜子了结果开门指令发不出去这时候体验损失是不可逆的。设备上线后第一件事不是报告“我活了”而是做一次全量自检和状态同步。自检内容包括门锁是否正常、传感器是否在线、本地缓存了多少条未上报的数据如果有积压数据立即补传。这一步很关键因为售卖柜在断网期间可能实际发生过买卖只是服务端不知道。3. 自动结算从扫码开门到钱款落袋的完整链路自动结算是无人售卖柜系统的核心灵魂。我的经验是结算模块的设计一定要先定义状态机再写代码。订单状态如果理不清后面加再多补偿逻辑都是亡羊补牢。3.1 一个订单的完整生命周期一次正常的购买流程是这样的用户扫码或者输入柜号服务端创建一个待支付订单同时下发开门指令用户开门拿走商品、关门后主控板把传感器数据上传服务端计算用户拿了什么生成最终账单并执行扣款扣款成功后通知用户。把这条链路抽象成状态机核心状态大概是这些CREATED已创建、OPENING开锁中、OPENED已开门、CLOSED已关门、SETTLING结算中、PAID已支付、EXCEPTION异常。每一个状态之间怎么流转、哪些条件触发流转必须在设计文档里画清楚。比如关门后如果某个重力传感器读数异常订单不能直接进入结算而是先进入异常状态等人工介入或者二次采集确认。在数据结构上订单除了基础的主订单表还需要一张订单明细表记录每个SKU的识别结果、数量、单价、小计。此外还要有一张事件流水表记录这单过程中每一次设备上报的数据包括开门时间、关门时间、传感器重量变化值等。这张流水表在排查问题的时候简直是救命稻草没有它你根本没法回答用户“我到底拿了什么”的质疑。3.2 核心交互时序两次关键的“握手”整个自动结算流程中最容易被忽视但也最容易出问题的是设备和服务器之间的两次“握手”交互。第一次握手是在开门之前。用户扫码后服务端不是直接发开锁指令而是先向设备发送一个“预开门请求”让设备回复当前锁状态和自检结果。只有设备回复“可以开门”服务端才下发正式开锁指令。这一步是为了防止设备离线但心跳还没过期导致的误操作也防止用户在柜子本来就开启的状态下重复下单。第二次握手是在关门之后。门磁检测到关门主控板先不急着上报最终数据而是把采集到的一组传感器数据通常是多帧采样缓存起来然后向服务端发送“关门通知”。服务端收到后回应一个ack主控板收到ack才把这一单的本地状态置为“结算中”。为什么需要这个ack因为如果设备直接上报完就释放本地状态但网络消息在半路丢了服务端永远不知道这柜子已经关过门了用户会收到“订单自动取消”而柜内状态已经变化后面再来一个人开门就全对不上了。这段交互中ack机制是我强烈建议保留的设计它能消除掉大部分“设备已经动作但服务器不知道”的灰色地带。3.3 商品识别主动传感器方案与AI视觉方案的取舍自动结算必须知道用户拿了什么这个“知道”有三种主流方案重力/称重传感器方案、RFID方案、AI视觉识别方案。我逐个说下我的实践经验。重力传感器方案是当前成本最低、落地最快的方案。每个货道或每个层板下装一个或多个称重传感器商品入库时把每个SKU的标准重量录入系统用户拿取商品后重量的差除以单件重量就能推断出商品数量。听起来简单但实际有两个坑必须处理好。第一称重传感器有温漂和零点漂移早中晚温度变化大同一个货道的空载重量可能差几十克所以必须做周期性清零校准。第二饮料、零食这类商品单件重量差异较大同一瓶饮料批次不同可能有5到10克差别如果货道里只剩一件、用户拿走了重量变化恰好卡在误差边缘系统就蒙圈了。对于这个问题我建议设计“重量区间匹配”而不是“精确重量匹配”给每个SKU设定一个上下浮动的百分比区间落在区间内就算命中。RFID方案在服装、箱包这类高单价商品上有优势因为它可以做到唯一识别每件商品贴一枚RFID标签关门时柜内天线读取标签变化就知道拿走了哪件。但RFID标签成本摆在那里对饮料零食这种低单价商品完全不划算。AI视觉方案是近两年的网红方案视觉摄像头配合深度学习模型在关门后通过图像识别确认用户拿走的商品品类。它的优点是无需预先录入重量信息、SKU上新也方便但落地难点在训练成本和识别准确率上。我给你的建议是如果项目初期SKU少于200个、货道不复杂重力方案足够用等规模上来以后再做视觉和传感器多模态融合不要一上来就上重方案。3.4 结算扣款与支付通道的可靠交互结算计算的最终结果就是对支付渠道发起扣款请求。这一段的可靠性要求是所有模块里最高的也是金融级的要求扣款不能多扣、不能漏扣、不能重复扣。我建议的扣款流程是分两步走先调支付渠道的“小额预授权/免密支付协议”或直接调下单接口拿到扣款凭证再执行真正的扣款确认。这里最关键的细节是服务端必须对每一次扣款产生唯一的业务流水号并且这个流水号在数据库里有唯一索引。不管支付渠道返回结果超时、服务重启、消息重复投递最终扣款时都必须检查这个流水号是否已存在。如果存在直接返回原结果不再发起第二次扣款。支付回调这块更要注意回调可能乱序、可能重复、也可能是测试环境的假回调。一定要先验签再更新订单状态且更新操作必须用乐观锁或版本号控制防止并行请求把状态改乱。我遇到过最典型的故障是用户购买后支付成功回调先进来订单状态置为“已支付”过了一秒另一个延迟的“关闭订单”请求也到了直接把这个已支付订单状态改成“已关闭”钱扣了但订单没了用户炸锅。4. 实际踩坑记录与排查实录下面这部分是我最想分享的内容因为都是在项目上线后被现实狠狠教育后才总结出的经验。我会按“现象-原因-解决”的结构来写每条都值得你保存下来。4.1 柜门关闭了但订单迟迟不结算这个现象在项目上线初期特别常见。用户关门后手机迟迟等不到扣款通知后台订单一直停在“已关门”状态。排查时我先看设备心跳记录发现设备其实在线再查主控板日志发现“关门事件”根本没有上报。最后定位到是门磁传感器安装角度问题——柜门关到位和门磁触发之间有几毫米的间隙误差有些柜门因为安装公差关是关上了但门磁没有吸合到位事件自然没触发。解决方法是给主控板增加了一个“关门边缘检测”逻辑门磁状态从“分离”变为“吸合”的那一瞬间就算关门并做连续多次采样确认状态稳定。同时在硬件上把门磁的触发距离余量调大并严格要求安装时做门缝间隙校准。4.2 设备在线却控制不了锁设备心跳正常、MQTT连接正常但服务端下发开锁指令就是没反应远程看设备日志显示“指令已收到”但锁没开。这类问题的坑在于锁控板的控制信号和主控板的IO电平不匹配。有些电子锁需要脉冲信号触发有些是电平触发指令能收到但信号参数不对硬件执行层就直接没动作。我后来总结出一个习惯设备端收到“开锁指令”后除了控制继电器还要回读锁状态传感器的反馈值并上报一条“开锁执行结果”消息。服务端如果收到开锁指令下发后3秒内没看到执行结果就自动进入告警状态并尝试重发指令。通过这条“指令下发-执行反馈”的闭环这类问题能第一时间暴露出来。4.3 网络断连恢复后数据错乱我在前面提到过断线重连和本地缓存。实际操作时缓存的数据格式如果设计不严谨恢复传输后就会出大问题。我遇到过一台柜子在离线期间用户开了三次门产生了三条本地记录但因为主控板Flash写入时序问题第二条记录覆盖了第一条恢复上传后服务器只收到两条记录库存和订单对不上最后一件一件人工核账。这个问题的根因是本地存储没有设计队列结构和写入确认机制。正确的做法是每一条待上报记录都写入独立的存储槽位带上自增序号和CRC校验上报成功且收到服务端ack后才允许将该记录标记为“已清除”。只要有任何一个槽位还未被确认就不能写入新数据覆盖它。4.4 频繁出现“用户未支付但库存已减”这个问题的本质是订单状态和库存扣减之间的事务边界没有划清楚。我见过一个团队把“扣减库存”和“创建订单”做在同一个事务里结果支付还没完成库存先锁定了用户不支付或者支付超时库存又迟迟不释放。这叫“悬空订单”问题。更合理的设计是创建订单时只做一个预占库存操作也就是把库存数量减掉但状态标记为“预占中”。订单关闭或支付超时后自动执行释放库存。真正支付成功的订单才把库存状态从“预占”变成“已售”。如果支付彻底失败释放库存的补偿任务会在几分钟内自动执行。这样虽然库存数字有一定的时间差但不会出现“卖超”或“凭空变少”的严重事故。4.5 快速排查参考表现象可能原因排查入口设备一直离线SIM卡欠费、基站信号弱、MQTT证书过期查看设备端4G模组日志、服务端在线表开门指令下发超时设备正在处理其他任务、锁控板异常查看设备端任务队列、锁控板供电状态结算金额与货道配置不符商品标准重量与实测偏差过大、货道绑定错误对比SKU重量配置、查看传感器原始读数用户已扣款但订单未关闭支付回调丢失、状态机未收到回调查支付平台回调日志、手动触发订单轮询查单本地缓存数据丢失主控板写入时断电、存储槽位被覆盖查看主控板Flash日志、检查电源管理5. 落地前的最后几张底牌选型建议与经验心得系统设计和技术实现说到这里主体框架已经完整了。最后再分享一些我在项目落地过程里沉淀下来的几个关键经验这些不属于某个单一模块但它们往往决定项目最终能否稳定跑起来。5.1 主控板与通信模组选型不要贪便宜无人售卖柜的主控板是整个大脑市面上便宜的ESP8266方案虽然只要十几块钱但它的Wi-Fi稳定性、GPIO数量、Flash空间都对大型业务捉襟见肘。我更推荐使用ESP32或者工业级STM32方案配合独立4G模组比如EC200S或Air724UG。选型时重点看工作温度范围、Flash容量、外部中断数量这几个指标。如果要做副柜联动还要确认MCU自带USART数量够不够接RS485转换芯片。5.2 一定预留远程升级和调试通道硬件设备一旦铺到线下场景你不可能每次改个bug都跑去现场刷固件。所以OTA远程升级能力必须在项目第一天就做进去。主控板固件支持通过网络下载新版本固件包校验通过后写入并重启。同时要预留一个远程调试通道比如通过MQTT下发一条指令让设备进入调试模式这种情况下设备会通过日志topic上报更详细的信息会省掉大量让你跑现场的痛苦。5.3 从小规模灰度到大规模铺量之间留足时间这是我被现实狠狠教训过的一点。我们当时在10台柜子上测了两个星期各项指标都挺好于是直接铺了200台结果各种藏在角落里的问题集中爆发不同批次柜子的门磁安装公差不一样、有些柜子所处的4G信号区域频段不同、商场里的Wi-Fi信道干扰程度远超办公室。所以我建议你至少要经历三个阶段实验室测试阶段、小规模试点阶段10-50台、批量运营阶段100台以上。每个阶段至少跑2周把稳定性、丢单率、售后率这些核心指标记录下都跑稳了再放量。5.4 基础设施层面的几条守则根据我个人经验下面这几条守则如果从第一天就遵守后期会让你少熬很多个夜所有关键操作必须写流水日志尤其是指令下发、事件上报、支付回调。没有日志排查问题等于大海捞针。所有数据库操作必须做好幂等控制同一个事件重复上报不能产生重复数据。所有金额相关字段用整数分存储不要用浮点数做金额计算否则会出现 19.99 这种精度问题。所有时间统一用服务器时间为准设备时间只做参考。设备本地时钟经常漂移必须靠服务端时间戳来做事件排序。库存一致性要做定时对账每天凌晨跑一次设备上报库存与服务端库存的差异核对有差异自动告警。这套无人售卖柜系统做下来最大的体会是技术上真正难的不是某个算法、不是某个魔术般的黑科技而是把设备、网络、业务、资金这四个不同节奏的系统彻底捏在一起并且保证每一环在异常情况下的行为均可预期。资金安全和无差错结算说到底不是某个单一技术的功劳而是靠状态机设计、幂等控制、事件流日志、以及对账机制一层一层叠出来的确定性。如果你正准备做这个项目我建议先在纸面上画出订单状态机再开始写代码。把那些异常分支想透比你多写几千行代码有价值得多。后续如果大家感兴趣我可以把主控板端的固件框架和MQTT topic设计单独再写一篇展开聊聊。
返回列表