新零售终端的一些实现
立泉即将离开毕业后加入的第一家公司,有些舍不得熟悉的人和物,毕竟是相伴两年的地方,已经有点家的感觉。但另一方面也长舒一口气,甩掉一个包袱,我不应该被过去束缚,有很多一直想做的事,外面的世界更加宽广。
两年里我看到一个初创企业在新零售领域所做的努力,不断产品迭代和组织结构调整,从背靠大树好乘凉的轻松到被迫独立运营的窘迫,这些真实发生的事情让我对商业的运行规则颇有兴趣。不过这篇文章不在于此,想聊一下我接触到的新零售终端设备的一些业务实现方式。它们并不复杂,远达不到“商业秘密”,不同公司具体实现细节未必相同,但架构应该大体类似。
演进
我看到的大部分新零售终端,或者叫自动售货机,结构其实很简单,1 台机器 + 1 个大屏 + 1 台 Android 工控机 + 1 张物联网卡,稍复杂的可在工控机或机器主板上通过串口外接几台附加的售卖柜。其它差异性表现为机器的货道设计和出货方式,有蛇形货道的饮料机、直通货道的饮料机、依靠弹簧转动的综合机、依靠履带传动的综合机,还有一种用机械货斗售卖易碎物品的综合机,后期也出现了咖啡机、啤酒机、现调饮料机等液体机型。
但这些都属于硬件层,我们关注的是上层 Android 工控机运行的售卖程序,它通过网络连接公司服务器、通过串口连接机器硬件、内部处理订单、指令机器动作并实时上报状态信息。简单的说,自动售货机就是一个大型 Android 手机,运行的就是普通 Android 软件,只是多一条控制机器硬件的串口连接线。
我负责维护的售卖 App 经历过两个阶段,从混沌到模块。
入职 1 年左右,公司体量较小,机型只有富士饮料机、澳柯玛饮料机和澳柯玛弹簧机三种,我和另外 2 个同事每人负责一种机型的更新迭代,零售终端依赖的售卖、补货、控制、广告等功能都集中在该机型的一个 App 里。
即使在机型较少的阶段这种模式就已经暴露出明显问题,三个人各自维护一套代码,而且历史遗留原因三份代码的结构差异非常大,所用类库和软件架构并不相同。这意味着,当售货机要增加一个功能时,比如接入新的广告系统,每个人都要在自己的代码里实现一遍。看起来很可笑,明明写一个独立的广告模块统一接口再分别集成能让代码的复用性和维护性更好,也能推动所用功能的模块化进程,但事实是直到接入的机型越来越多以至这种原始方式无法应对时才开始改变,走向模块化尝试,即第二阶段。
我们把售货机分为两种,基于货道和基于配方。对于最常见的饮料机和综合机,不管它们的出货方式多么奇特,机器硬件通过串口向上层 Android 工控显示的都是一个一个的货道,通过厂家串口通信协议可以控制每一个货道的补货和出货。对于咖啡机和现调饮料机,它们没有货道概念,只有不同口味的配方和原料存量,在软件设计上明显和基于货道的机型不同。所以需要为两种机器分别构建适用的售卖 App,以合适的方式展示前台售卖和后台补货 UI,统一处理订单和上报机器信息。把广告、支付、串口分别构建为独立 App,通过 IPC 进程间通信和售卖 App 进行数据连接。
同一机种不同机型的通用售卖上层 App 是同一个,广告 App 和工具 App 也是相同的,只有与串口通信协议相关的硬件控制部分需要根据厂家定义分别单独构建设备 App。适配新机型只需依照硬件通信协议实现统一的通信接口,以模块形式加载,其它都无需改动。各个 App 单独升级,由后台根据机器编号控制具体细节,这种模块化已经足够应对未来可能接入的繁多机型了。
订单处理
在新架构里,订单的处理逻辑,包括生成、接收、校验、上报订单,是在通用售卖上层 App 里完成的。确认收到的订单合法后,它会通过 IPC 调用设备 App,根据串口通信协议向底层机器硬件发送出货指令,硬件报告的出货结果会返回给上层售卖 App,由其记录订单出货状态报告给服务器。
代码结构使用典型的生产者 & 消费者模式,创建一个阻塞队列存储待处理的订单,包括本地生成的现金支付订单、通过 MQ 推送收到的扫码支付订单。创建一个工作线程不断从队列获取订单,因为阻塞队列的特性,没有订单时线程处于被阻塞的等待状态,有订单时则会被唤醒,执行对该订单的出货处理。
外围设备
很多人应该见过,有的自动售货机旁边会外挂一台或多台售货柜,它们没有独立屏幕,需要在主售货机屏幕上购买其中的商品。相同业务场景下,外接柜子的实现其实有两种。
一种是柜子接在售货机主板上,由售货机自己和附加柜通信,收发轮询、出货指令,对于向上层 Android 工控机的串口通信协议定义,会使用一种叫“箱号”或“柜号”的概念。比如,主售货机箱号是 0,第一个附加柜箱号是 1,第二个附加柜箱号是 2,以此类推。工控机依然只通过一根串口线连接主售货机,发出的出货指令除指定货道号外还有机器箱号,以使主售货机能定位到具体的柜子和货道,完成硬件出货动作。
另一种是柜子接在 Android 工控机的串口上,我们用的映翰通和四信工控机有多个串口,一个串口接主售货机,其它串口则可外接很多这种附加柜,根据通信协议由售卖软件处理与柜子的轮询、出货指令。
显然这两种方式对售卖软件的工作量定义是不同的,一种不需要售卖软件和附加柜通信,一种则需要。以兼容原则,售卖软件需要具备和附加柜的通信能力,对订单的定义需要扩充“箱号”概念,机器类型也要扩充“箱号柜”和“串口柜”概念。这些并非在售卖软件设计之初就能考虑到,我们先接触的都是直接和串口通信的附加柜,当后来出现接在售货机主板上的附加柜时,自然会修改内部数据结构来适应变化,软件功能就是这样一点点迭代完善,最终才能做出兼容所有机型的通用售卖上层 App。
外接刷卡器
一些特殊场景会有独特需求,比如学校里投放的售货机,尤其限制学生使用手机的中小学,仅使用现金售卖会导致机器内硬币器的找零维护工作量很大。所以有些经销商想在售货机上外接一台学校使用的刷卡POS 机来方便学生用校园卡购买。
这样的功能需求具有合理性,但真正实施却有一个非常现实的问题,几乎每一所学校的刷卡系统都不相同,其使用的 POS 机也多种多样,意味着只能根据经销商机器投放的学校去联系建造刷卡系统的厂家,确认其产品是否支持接收外部设备发来的扣款指令,一家一家逐个对接。运气好的话可能几个经销商提交的开发需求对应同一个刷卡器厂家,只需对接一次售货机就可以支持多个学校的刷卡购买,这样的事确实发生过。
刷卡功能以模块化实现,售卖软件具备连接所有已知 POS 机的能力,可根据云端配置自动加载对应的通信库。实现基于 Java 的类加载器,定义好与 POS 机通信的方法接口,对每一种 POS 机单独根据通信协议构建 JAR 包。它们被放在云端,机器需要连接哪一种 POS 机下载对应 JAR 包加载即可。
关于公司的一些想法
作为离开校园加入的第一家公司,两年时间,我看到很多、接触很多、学到很多,它们会体现在我的成长历程中。另一方面我也看到一些存在的问题,有的已经存在很久,无人过问就一直存在下去。
我没有学过大型团体的管理和运营,但身为被管理的一员可以谈一下我的个人理解。除去核心技术实力,标准化制度与清晰化流程在我看来是公司扩张时必须拥有且实行的两个基本原则。
人员增多、市场变大、需求递增,如果没有人知道如何正确、合规、严谨的连接市场和技术,不知道如何一步一步把市场需求变为实际产品,可想而知内部的沟通成本将会非常巨大。需求被曲解,与外部的对接也可能在无法确定具体承办责任人的情况下旷日持久的拖延。这些是真实发生的事情,我经历过几次,每次都在想,如果有标准化的制度和流程,接到需求从市场到技术每个人都按流程规定做自己该做的事,至少不会出现多方无法有效沟通的混乱局面。如果没有无意义的互相甩锅就不会有人自己生闷气吧,工作心情会影响开发效率,这是管理层应该看到的。