年初接了一个挺拧巴的项目:某工厂的仓库和产线分布在不同楼层的厂房里,厂里为了提升搬运效率,上了几台自主移动机器人(AMR),本想让它们自己坐电梯上下楼送货。结果第一批机器人到场之后,对接测试卡了整整一个月——厂房里那三台老旧货梯,一台是纯继电器控制柜,另外两台是老式PLC控制,既没有标准通讯接口,也没有现成的API开放给外部设备。老板觉得"给电梯加个接口"是件小工程,但真正拆开控制柜之后会发现,"接口""协议""状态同步"这三件事,每一件都能让你头皮发麻。
这篇文章我就把整个改造过程摊开来讲:从老旧货梯控制系统的真实解剖,到声明式梯控架构的建模逻辑,再到协议分层的重构思路,最后是现场联调踩过的坑和最终的数据变化。如果你也正在做机器人调度、AGV开发,或者只是想把老设备拉进物联网体系里,这篇内容可以帮你省掉不少试错成本。
1. 老旧货梯的"三不通"困境:接口不通、协议不通、状态不通
1.1 真实的老电梯控制柜里到底有什么
很多人对电梯的印象停留在"按钮一按,电梯就到",但真正打开老旧货梯的控制柜,你会看到一个非常复古的世界。最常见的是继电器控制柜,里面密密麻麻排着中间继电器、时间继电器、接触器,上下行方向接触器还带机械互锁,整个逻辑靠硬接线的梯形逻辑实现。这类电梯唯一的"对外接口"就是操纵箱按钮、门区信号、平层信号这些干接点,没有任何通讯端口。
另外一批稍微"年轻"的老PLC电梯,虽然可编程控制器已经取代了继电器阵列,但通讯能力依然非常有限。我遇到的那两台老PLC,自带一个RS232调试口和一个RS485接口,但协议是厂商私有格式,资料早就找不到了,只能靠抓报文一点点去猜点表逻辑。更麻烦的是,这些电梯的控制逻辑里混杂了大量机械信号:门锁开关、限位开关、超载开关、急停回路,任何一个信号异常,整个系统就会进入保护状态。
我当时的第一反应是"换个电梯主控板,直接上标准协议",但被安全评估打回来了。货梯涉及特种设备监管,主控更换需要整体重新走验收流程,厂房又不能长时间停梯,这条路直接走死。所以最后定下来的改造原则是:不改电梯原有主回路,不改原有安全回路,在控制柜外面旁路叠加一套独立的物联网网关,用最小侵入的方式让老电梯"开口说话"。
1.2 "三不通"为何让机器人寸步难行
把问题拆开说,机器人想坐电梯,其实要解决三个层面的对接,缺一个都会出乱子。
第一个是接口不通。机器人是一种只能理解数字信号的设备,老电梯连一个标准接口都没给。干接点信号要自己接,串口协议要自己破,没有寄存器列表就只能拿万用表和示波器慢慢找。这个层面的问题是物理性的,不存在技巧,只能靠加装硬件和耐心测试来补。
第二个是协议不通。就算你从老PLC的串口读出数据,拿到的也是厂商私有报文的原始字节流。比如当时抓到的报文,一条状态帧里把楼层编码、方向状态、门状态塞在同一个字节的不同位,不研究透点表根本没法用。这个层面的核心工作不是"传输",而是"翻译"。
第三个是状态不通。这是比接口和协议更深一层的坑。电梯是一个具有强物理状态和强安全约束的设备:它在几楼,正往哪个方向走,门开到位没有,有没有人正在使用。机器人调度系统如果拿不到这些实时状态,就完全没办法判断"现在能不能进电梯""我按了呼梯之后到底要等多长时间"。老电梯没有状态广播机制,这些信息只能靠外部网关主动去采集、去推算、去维持。
三个不通叠加在一起,经典的自动化改造死结就形成了:你没法在不了解状态的前提下安全地发指令,又没法在不发指令的条件下持续了解状态。破局的关键,是先把"状态"这件事做成一个可以被声明、被查询、被仲裁的抽象层。这就引出了整个架构的核心思路——声明式梯控。
2. 声明式梯控模型:把"电梯怎么动"交给调度器,把"我要去哪"交给机器人
2.1 命令式遥控的致命弱点
一开始,我们团队内部讨论的是最朴素的方案:给机器人写一套"电梯遥控器"接口——机器人调一个pressUpButton(),再调一个waitForDoorOpen(),然后进轿厢调pressFloorButton(3)。这就是典型的命令式编程思维,把控制电梯等同于按按钮。
这个方案看着直接,实际跑起来问题非常大。最大的问题是调用者和执行者之间的时序依赖。电梯不是一台立刻响应、立刻完成动作的机器,按下呼梯按钮之后,电梯可能在2楼还开着门等工人卸货,30秒之后才上来。命令式调用里,机器人必须自己维护这段等待状态,自己处理超时,自己判断电梯来了没有。
更麻烦的是并发场景下的资源竞争。仓库里有6台机器人同时要坐电梯,每台机器人都命令式地发"按上""按下""开门""关门"的时候,电梯到底听谁的?按下的顺序靠运气定吗?先进先出就是最优调度吗?显然不是。命令式接口把"如何协调多机器人使用同一部电梯"的难题完全推给了调用方,而机器人的调度系统根本不知道电梯内部还有没有别的呼梯任务排队。
所以我们需要换一种思路:不要让机器人告诉电梯"你下一步该干嘛",而是让机器人声明"我最终想要什么状态",由梯控系统去组织所有机器人请求并执行到底。这就是声明式梯控模型的起点。
2.2 声明"目标状态"而不是调用"动作接口"
声明式梯控的核心抽象极其简单:一个机器人向梯控网关发出声明——"我想从当前楼层移动到目标楼层,使用某部电梯,期望等待时间是某个范围"。网关收到声明后,负责把这份声明拆解为具体的物理动作序列:判断当前电梯位置、决定是否发送呼梯信号、等待电梯到达、允许机器人进出、在轿厢内选择目标楼层。
这么做最大的优势在于将"期望"和"实现"彻底解耦。机器人不需要知道电梯是继电器控制还是PLC控制,不需要关心是脉冲还是干接点,它只面向一份稳定的、语义清晰的声明接口。而所有电梯控制细节、信号采集细节、安全互锁细节,全部收拢在网关内部。
举个例子,以前机器人要坐电梯上楼,代码大概是这样的伪代码:
# 命令式时代的伪代码(问题多多) robot.press_up_button() while not robot.detect_door_open(): time.sleep(1) if robot.wait_time() > 30: robot.press_up_button_again() # 超时重按 robot.reset_wait_time() robot.move_into_elevator() robot.press_floor_button(7)换成声明式之后,机器人侧的逻辑就变得非常简单了:
# 声明式时代的伪代码:只声明目标 request = ElevatorRequest( request_id="R-20231115-001", source_floor=3, target_floor=7, elevator_id="E01", strategy="fastest", priority=5, max_wait_seconds=60, ) response = elevator_gateway.submit(request) # 之后只要轮询 request 的状态,直到 success / failed / cancelled这段伪代码我相信所有做机器人调度的开发者看一眼就明白了。电梯怎么动、怎么和机械信号打交道,全部在网关内部完成,机器人侧要维护的状态机被大幅压缩。
2.3 一个完整的状态声明请求与应答
为了让这套模型真正落地,我们把接口定义成了四件套:请求(Request)、应答(Response)、状态(State)、事件(Event)。
请求是机器人提交的声明,核心字段包括request_id、source_floor、target_floor、elevator_id、priority。应答是网关对声明的受理反馈,表示"我收到了,预计等待多久,前面还有几单"。状态是网关向所有订阅方广播当前电梯的位置、方向、门状态,以及当前占用状态。事件则是关键节点的主动推送,例如"电梯已到达3层,门已完全打开,放行窗口开启"。
这里我想重点说一下priority字段的设计逻辑。刚开始我们没做优先级,所有请求按到达顺序排队,结果现场暴露了一个低效问题:一台从5楼去1楼的机器人占了电梯,但紧接着来了一台从4楼去1楼的临时加急请求,却被排在后面多等了整整一趟梯。加了优先级仲裁之后,调度器会定期重排队列,在同方向和顺路的条件下允许高优请求插队,但要保证任何被插队的请求的max_wait_seconds不被无限放大。
这套模型设计好之后,物理协议和上层接口之间的桥梁就成了整个项目里工作量最大的部分——也就是协议重构。
3. 协议重构:从干接点、私有报文到分层标准语义
3.1 底层采集:读懂干接点与老PLC
协议重构的第一步不是写代码,而是把老电梯"身上"所有能采的信号搞清楚。我们当时给三台电梯分别建立了信号台账:每台电梯有哪些输入点(门区信号、平层信号、开门到位、关门到位、上/下行方向、满载信号、故障信号),有哪些输出点(开门指令、关门指令、上/下行指令、楼层选层指令),每个点在控制柜端子排上对应的编号是什么,是常开还是常闭。
干接点信号的读取相对直观:通过光电隔离输入模块,把外部无源触点转换为内部24V逻辑电平。这里有个非常重要的细节——干接点必须由外部供电来"点亮",不能指望控制柜主动给你送电。我们用的是独立DC-DC隔离电源,给每路输入提供24V,然后通过光耦把信号读进来,这样做的好处是即便外挂网关掉电,也不会反灌影响电梯原系统。
老PLC那两台就复杂一些。我们先从PLC的RS232调试口抓帧,用示波器确认波特率、校验位,然后花了两天时间把历史报文一点一点翻译出来。翻译的过程其实就像解谜:找到某一帧数据的某一位在电梯动作时发生了翻转,再结合操作人员的实际动作去验证猜测。最后我们还原出PLC内部的一段寄存器映射表:0x0002字节的低四位是当前楼层BCD码,0x0003字节的bit7是方向信号,bit5是门开到位信号。拿到这张表,老PLC的私有协议在语义层面就算是"破译"了。
3.2 语义归一化:把物理信号翻译成事件
信号采集完成之后,紧接着就是"语义归一化"。这一步的核心工作是建立一套与硬件无关的状态模型,把所有物理信号统一映射到几个语义明确的状态字段上。
我们的状态模型只保留了6个核心字段:current_floor(当前楼层)、blocked_by_door(门状态)、moving_up、moving_down、is_idle(空闲中)、in_service(是否处于故障/检修状态)。干接点信号也好、老PLC私有报文也好,统一在网关内部经过一道"翻译层",全部汇入这6个字段。
这道翻译层最大的价值,是让上层协议彻底与硬件绝缘。后面我们在一台电梯上做了小改动,把它的平层感应从磁开关换成了光电开关,物理信号变了,但机器人侧完全无感知,因为语义层输出的current_floor依旧是稳定的整数楼层号。
不过,语义归一化也隐藏着一个大坑:物理信号本身会出错。老电梯的干接点经过十几年使用,触点氧化、弹跳、信号抖动都是家常便饭。如果直接把抖动信号翻译成楼层变化,上层调度简直是灾难。所以我们给翻译层加了滤波器——所有楼层信号必须连续3次采样一致才认为有效,方向信号必须持续500ms才算真正切换方向。用这些"笨办法"把信号错误挡在语义层之外,上层才能睡得着觉。
3.3 上层接口:机器人只认的JSON声明
协议重构的最后一步,是把归一化后的状态和声明式模型,通过标准化的上层接口开放给机器人调度系统。我们最终的选择是MQTT + JSON。机器人侧通过MQTT客户端订阅电梯状态主题、发布声明请求主题,网关负责订阅声明请求、发布状态更新。
选择MQTT而不是HTTP,主要是考虑现场网络环境。厂房里机器人会移动,Wi-Fi覆盖本身就不稳定,HTTP长连接在这种网络下非常容易频繁断开。MQTT基于TCP长连接,配合心跳保活和QoS1,即使网络瞬断也能自动恢复会话。实测下来,三台电梯的声明请求和状态广播走MQTT,平均延迟在80ms以内,完全满足机器人的决策需求。
声明接口的字段定义如下:
{ "request_id": "R-20231115-038", "source_floor": 3, "target_floor": 7, "elevator_id": "E01", "strategy": "fastest", "priority": 5, "max_wait_seconds": 60, "caller": "AMR-007" }网关的应答让机器人心里有底:
{ "request_id": "R-20231115-038", "status": "accepted", "queue_position": 1, "eta_seconds": 25, "elevator_id": "E01" }这里我说一个设计上的细节:我们没有把eta_seconds当成一个"承诺值",而是当成一个"参考值"。电梯在运行过程中可能被人工插单(比如工人临时要操作货梯),也可能因为满载信号跳停。所以网关的eta_seconds只预测当前排队状态下的预估等待时间,一旦电梯状态发生重大变化,会立刻发布新状态,让机器人重新决策。这个"状态事件驱动重算"的机制,比硬扣一个超时时间要灵活得多。
3.4 协议安全:超时、重试、取消、互锁
声明式协议在设计时容易犯的一个毛病是"只管发声明,不管过程安全"。我们当时在联调时吃过亏,差点让机器人撞上正在关闭的电梯门。所以协议层加入了一整套安全流程。
超时与重试:如果网关受理声明后,电梯在max_wait_seconds内没有到达,网关必须发布timeout事件,机器人可以选择重新提交声明或放弃本次请求。注意,这里不能由机器人无脑重试,否则会造成请求风暴。我们的做法是网关对同一caller的重复声明做去重:相同源楼层、目标楼层、电梯ID的请求在5秒内重复提交,直接沿用原request_id,不产生新任务。
取消机制:机器人取消声明时,网关需要判断电梯是否已经开始执行。如果电梯还在空闲和响应阶段,可以平稳撤销;如果电梯已经到达放行窗口,则不能撤销,必须等机器人放行或超时后再释放梯控权限。这背后是"谁占用谁释放"的一致性约束。
互锁逻辑是安全重点。电梯门完全到位是机器人进出轿厢的唯一前提。我们在网关内部有这样一个判断规则:只有同时满足"当前楼层=机器人所在楼层""门状态=open""电梯速度=0"三个条件,才向机器人发布ENTER_WINDOW事件。任何一个条件缺失,机器人就被禁止移动。这套规则写进协议之后,联调现场的意外被大大压减。
4. 控制柜硬件改造的最小侵入方案
4.1 旁路并联:不改电梯主回路
硬件改造是整个项目里最容易被低估的部分,也是风险最高的部分。我们的基本原则是"不走主回路、不触安全回路、只做信号并联叠加"。所有梯控网关的输出继电器,与原控制柜的按钮开关是并联关系——也就是说,网关给出的控制信号,相当于有人同时按下了按钮,而不是切断了原逻辑。
以呼梯为例,原操纵箱的上行按钮两端本来是一个常开触点,按下后接通。我们用网关的中间继电器触点与它并联,继电器闭合的效果等同于按钮被按下。这样做的好处特别明显:即使网关工作异常,电梯的原按钮功能完全不受影响;维修人员也可以随时用物理开关切掉整个外挂系统的电源,电梯立刻回归纯人工操作。
但这个方案有一个必须严格遵守的规矩:继电器触点的容量和寿命必须经过验证。电梯按钮回路的电流不大,但按钮频繁通断对继电器触点的机械寿命要求很高。我们最开始用的普通信号继电器,连续测试两周后就有触点接触不良的迹象,后来全部换成工业级中间继电器(带有机座和指示灯),触点容量余量放了三倍以上。
4.2 信号采集与驱动隔离
信号采集中有个常见的迷思:以为把网关的IO模块和控制柜端子直接连上就完事了。实际上,老旧电梯控制柜内部电磁环境非常恶劣,变频器(VVVF)如果就在同柜安装,高频噪声会从电源和信号线两个路径灌进来。
我们做了两重隔离:第一重是电源隔离,梯控网关使用独立的DC-DC隔离电源模块供电,与控制柜主电源完全电隔离,避免地环路噪声;第二重是信号隔离,所有干接点输入通过光耦隔离后再进入IO模块,所有驱动输出通过中间继电器隔离后再并到原按钮回路。这两道隔离防线做扎实之后,串口通讯和干接点读取的误码率才降到可接受水平。
另外,老PLC那两台电梯的RS485通讯线,我们全部换成了屏蔽双绞线,并且在PLC端的通讯口外加了磁环和信号隔离器。现场测试的时候对比过接法:不换屏蔽线之前,变频器启动瞬间报文CRC错误率能到5%左右,换了屏蔽双绞线并做单点接地之后,同样的工况下错误率直接降到0.1%以下。
4.3 故障降级:切换回人工模式
任何外挂系统都必须回答一个问题:你挂了,电梯怎么办?我们的答案是一个物理旁路开关加一套逻辑看门狗。
物理层,在网关电源入口串联了一个带锁的工业旋钮开关,旋钮打到"旁路"位置,外挂系统完全断电,所有并联触点回到断开状态,电梯彻底变成一台普通的人工货梯。逻辑层,网关内部跑了三重看门狗:软看门狗定时巡检主程序,硬件看门狗防止死机,协议看门狗监测与MQTT服务器的连接状态。
这里有一个非常关键的经验:协议看门狗断连后,绝不能自动让所有声明请求静默挂起或无条件执行。我们早期的设计是断连后网关维持当前状态等待恢复,结果有一次MQTT服务器做重启升级,网关发布不了状态,有台机器人正好在轿厢里,调度系统完全瞎了。后来改成:断连超过5秒,网关立即把已在队列中的声明全部标记为"suspended",并拒绝新声明,同时把电梯释放给人工使用。等连接恢复后,再由调度系统重新提交声明。
这套降级逻辑,就是整个梯控系统安全性的最后一道防线。
5. 联调实测与踩坑复盘
5.1 特例:平层信号抖动导致的位置漂移
进入实梯联调的第一天就遇到一个很魔幻的问题:电梯明明停在3楼,但网关上报的状态却显示成4楼。排查下来是平层信号的问题。老电梯的平层感应器位置校准得不是很好,轿厢停靠后,感应器的输出在楼层边界来回跳,干接点信号瞬间反复通断。
其实这在老电梯上非常常见,因为当初安装时就是对到"差不多能用"的标准。我们用滤波延时解决了它——连续3次采样一致才更新楼层号,才把这种抖动压住。但我知道有些同行会直接把滤波时间拉很长,这又会引入电梯已经到达但状态迟迟不更新的新问题。所以滤波参数的调参测试很有必要,最终我们定的是30ms采样间隔、3次一致确认,既滤掉了抖动,又能保证电梯到位后150ms内上报状态。
5.2 再平层逻辑与机器人放行冲突
第二个坑更有意思。三台货梯中有一台具备"再平层"功能——电梯装货之后因为有货物重量导致钢丝绳拉伸,轿厢会在门开着的情况下自动再平层几厘米。按理说这是个很贴心的功能,但对机器人来说却是个惊吓。
机器人收到ENTER_WINDOW事件之后开始移动,走到轿厢门口时,电梯突然做了一个小幅的再平层动作,机器人上的激光雷达立刻检测到"电梯在动",触发急停保护,整个运输任务卡死。我们把ENTER_WINDOW事件的触发条件改成"门开到位 + 电梯静止 + 再平层动作完成",再平层动作期间不发布放行窗口,机器人自然就不会撞上这个细小的动作。
这个坑告诉我们:声明式模型虽然抽象掉了电梯的机械细节,但安全窗口的判定必须保留对物理信号的直接引用。抽象的是控制流程,不能抽象的是安全前提。
5.3 电磁干扰引发的串口丢包
老PLC那两台的通讯故障排查是最折磨人的。联调初期,电梯只要一启动,Modbus帧的CRC错误率就飙升,报文的响应时延也忽高忽低。我们用工业现场的经典办法逐步定位:先怀疑波特率问题,把9600降到2400,无效;再怀疑RS485收发器芯片,换了一版,无效;最后怀疑线缆和地环路,把原来的普通双绞线换成屏蔽双绞线,PLC通讯口外加信号隔离器,错误率才真正降下来。
事后总结,问题核心在于变频驱动系统在启动瞬间把共模噪声耦合到了RS485总线上。屏蔽双绞线解决了电场耦合,信号隔离器解决了地环路。这也是个典型的老设备改造问题:新设备的设计通常在硬件上就考虑了EMC问题,老设备只能靠外围补偿。
5.4 多机并发的优先级仲裁
最后分享一个纯软件层面的坑。6台机器人和3部电梯接入调度系统之后,并发请求开始出现。最初网关用的是简单的FIFO队列,结果出现了一个很尴尬的场景:两台机器人分别要从3楼去1楼和从1楼去3楼,一个在上行、一个在下行,FIFO却按到达顺序把它们排成了两趟梯,实际上同一趟就能把两个请求同时完成。
后来我们在网关里加了"方向分组仲裁":收到新声明时先看当前电梯方向是否与新请求方向一致,如果顺路就合并进当前行程,如果不顺路才进待调度队列。这里的代价是调度算法复杂度上来了,但整个电梯的使用效率提升非常显著——单部电梯每小时能完成的梯次任务数,从FIFO时代的平均9次提升到了合并调度之后的14次。
6. 改造后的收益数据与后续扩展
项目收尾时,我们把改造前后的数据做了个对比。梯控响应这个维度,人工呼叫电梯平均等梯时间大约是45秒,声明式调度之后下降到18秒。机器人跨楼层运输的准时率(按任务时间窗交付计算)从82%提升到99%,这个提升主要来自两个层面:一是不需要再派人按电梯,二是声明式调度让机器人可以提前规划路径,不用在电梯口空等。
可靠性方面,改造后6个月的非计划停机一共2次,一次是老继电器触点粘连导致信号误判(已按计划更换全部老旧触点),另一次是网关与MQTT服务器之间的网络接头氧化松动造成断连(已换成带锁防松接头)。整体可靠性算是达到了工厂连续生产的要求。
项目结束后,我又把这套声明式梯控的思路做了点扩展。现在它不光可以接电梯,还可以接AGV通道的卷帘门、自动分割门、甚至楼宇内的自动门。道理是相通的:把设备和机器人的交互抽象成"声明目标状态 + 安全窗口确认 + 状态事件广播"三个层级,不管底层是干接点还是私有协议,上层都能形成一套稳定的协作接口。对正在做工业物联网改造的同行来说,这个思路可以参考——尤其是那些面对老设备、老协议、但又不愿意停产大改的项目,旁路叠加与声明式协调的组合,可能是目前性价比最高的破局方式。