说到电梯楼层调度,我第一反应就是早年在项目现场调电梯的那段日子:明明是十几层的楼,按钮按下去,电梯要么不理你,要么直接冲过你要去的楼层再折回来,业主天天打电话催。后来我把整套逻辑用西门子PLC重写,核心调度算法全部改用SCL加For循环实现,配合触摸屏做联动演示,逻辑一下变得非常清晰。这篇文章就是想把这段实战经验完整拆开,从需求分析、数据建模、算法实现到HMI联动,一步步讲给你听。不管你是刚接触SCL的新手,还是已经用梯形图写过电梯逻辑、正想找个更优雅的写法的老手,这篇内容都能给你一套可以直接改着用的方案。
在正式开始之前,先把这篇文章解决的问题说清楚。电梯楼层优先级调度,说白了就是回答这么几个问题:电梯现在该往哪走、中间哪几层要停、停完之后要不要换向、同方向多个请求先响应谁。传统写法用梯形图做,比较指令、置位复位、跳转堆一屏,楼层一多改起来头大。SCL的好处是接近高级语言,数组、For循环、分支语句都支持,可以把“从当前层往上找最近呼叫”“判断上方是否还有顺向请求”这类逻辑写得像伪代码一样直白。看完这文,你能得到一套完整的单梯调度模型,包括DB块设计、FC/FB代码、触摸屏变量映射和演示方法。
1. 电梯楼层优先级调度,先搞清“优先级”从哪来
1.1 楼宇里最朴素的规则:顺向截梯
电梯这玩意儿,市面上绝大多数电梯用的都是“顺向截梯”这个核心原则。什么意思?就是电梯在往上走的过程中,凡是上行方向一致的楼层呼叫,只要还没到,电梯都应该顺路停;反过来,下行的时候,下行方向的呼叫优先响应。只有在当前方向上已经没有请求了,电梯才考虑换向去响应反方向的呼叫。
这是从乘客体验和能耗双重角度得出的最优解。举个例子:电梯在3楼,5楼有人按上行,8楼也有人按上行,同时2楼有人按下行。如果电梯先下去接2楼的人,5楼和8楼的人就得眼睁睁看着电梯往反方向走,体验很差;而且电梯反复来回跑,电机频繁启停,能耗和机械损耗都受不了。所以正确做法是:先往上走到5楼,再接8楼,直到上方没有上行请求了,才可以换向处理2楼的下行。
网上那些“电梯调度算法”帖子,动不动就上粒子群、遗传算法,说实话,民用建筑里的单梯根本用不着。真正实用的优先级模型就三层:内呼优先于外呼、顺向优先于反向、同向时由远及近决定换向时机。理解了这三句话,代码怎么写都有了方向。
1.2 用SCL落地的三层请求模型
要把调度规则变成PLC程序,先把“请求”这个概念结构化。电梯里的请求分三种:轿厢内呼、厅外上呼、厅外下呼。内呼优先级最高,因为乘客已经在轿厢里了,不可能让他坐过站;外呼中,和电梯当前运行方向一致的优先;方向一致的多个外呼,按楼层远近自然排序响应。
这里有个很容易被忽略的细节:请求信号是“持续电平”还是“脉冲”。实际上,乘客按按钮,按钮灯亮了,这个信号会一直保持着,直到电梯到达该楼层开门。所以PLC里不能直接把按钮输入当成瞬时脉冲处理,得自己做一个“请求锁存”和“到达复位”的流程。这也是我坚持用数组的原因:每一层楼是否有人呼叫,就是一个BOOL数组元素,置位、复位都清清楚楚。
把三个请求类型放进数组,就得到下面这样的数据模型:
- CarCall[1..N]:内呼,乘客在轿厢里按的楼层
- UpCall[2..N]:厅外上行呼,最底层没有上行按钮
- DownCall[1..N-1]:厅外下行呼,最高层没有下行按钮
调度逻辑每周期扫描时,先把三组数组“或”成一个停靠请求表StopReq[i],然后基于这个表做方向判断和楼层搜索。这样做的好处是算法只认一张表,不用每个方向各写一套复杂判断,后面加新功能也容易。
1.3 为什么这个场景用For循环最合适
很多用梯形图写过电梯的人都有一个感受:往上找附近的呼叫,梯形图真心不好写。你要做一堆比较指令,如果楼层有16层,你就得写16个比较,楼层再多点,程序容量先扛不住。而且每一次增加楼层数量,梯形图那段都得重新改。
For循环恰恰是为这种“顺序查找”场景设计的。你要找“当前楼层以上最近的一层有请求”,一行For循环从CurrentFloor+1扫到最高层,找到第一个就退出;你要找“当前方向是否还有请求”,也是For循环扫一遍,有一个就置标志位。这跟电梯调度的“按楼层顺序扫描”天然契合。
另外,SCL写出来的代码自解释性很强。你三个月后回来看代码,看见“FOR i := CurrentFloor + 1 TO MAX_FLOOR DO IF StopReq[i] THEN”这一行,马上就知道在找最近的上行停靠点。梯形图里一条条触点,说实话过几天自己都未必记得当初是干嘛的。
2. 数据块与变量设计:调度逻辑的地基
2.1 DB块应该放什么、怎么放
数据块是整个调度算法的地基,设计得好不好,直接决定代码好不好写。我习惯单独建一个电梯控制DB,把所有跟电梯状态相关的变量集中放在里面,调用FC/FB的时候直接用符号寻址访问,调试的时候监控一个DB就能看全所有状态。
我常用的DB结构大概是这样:
| 变量名 | 数据类型 | 作用 |
|---|---|---|
| CurrentFloor | INT | 当前楼层,1~N |
| Direction | INT | 运行方向:1上行,0停止,-1下行 |
| CarCall | ARRAY[1..16] OF BOOL | 内呼请求 |
| UpCall | ARRAY[2..16] OF BOOL | 外呼上行请求 |
| DownCall | ARRAY[1..15] OF BOOL | 外呼下行请求 |
| StopReq | ARRAY[1..16] OF BOOL | 合并后的停靠请求表 |
| TargetFloor | INT | 当前目标楼层 |
| DoorOpen | BOOL | 开门指令输出 |
| Running | BOOL | 运行中标志 |
变量命名我尽量用英文,方便在不同品牌PLC之间移植。有些人喜欢用中文变量名,博途也支持,但触摸屏那边映射变量名的时候经常遇到编码问题,所以我个人建议还是英文名。每个数组的上下标边界要特别注意,比如UpCall没有第1层,DownCall没有第N层,很多新手在这里出错,数组越界在博途里不一定有明确提示,但运行起来逻辑就是不对。
2.2 数组寻址与楼层映射的踩坑点
SCL里数组下标默认从0开始,也可以显式写成ARRAY[1..16]。我强烈建议你写成1开始的数组,让下标和楼层号一一对应。这样写代码时脑子里不用做“减一”的转换,避免了大量低级错误。
还有一个坑是触摸屏映射BOOL数组。威纶通、西门子HMI映射1位BOOL变量没问题,但如果你在DB里声明了一个ARRAY[0..15] OF BOOL,触摸屏上想按位对应地址,不同品牌的映射规则不一样,有的按字节偏移,有的按位偏移,很容易对不上。我的做法是:HMI上要显示的楼层状态,宁可单独声明几个单独的BOOL变量,或者在HMI里用索引变量去访问数组,别在图上一口气放16个点位,那样调试起来能疯掉。
另外,如果用的是S7-1200/1500的优化DB,变量的绝对地址不可见,触摸屏通信反而更简单,直接变量名对接。但注意优化DB的BOOL变量不能按位单独访问,如果你有旧设备走Modbus TCP之类的协议需要按位寻址,就得考虑取消优化访问,或者在DB里用BYTE外加位操作。
2.3 请求信号的边沿处理
电梯按钮信号有一个“按下、保持、电梯到达后消除”的过程。PLC扫描周期很短,人按按钮最少也有一两百毫秒,扫描周期可能只有5毫秒,所以直接用I/O点去置位请求标志,大概率没问题。但如果是来自HMI的按钮,或者是通信读进来的信号,我就一定会做边沿检测。
触发一个内呼请求:
// HMI按钮按下,产生一个上升沿 "上升沿M".CarCallEdge := "HMI_CarCall_1"; IF "上升沿M".CarCallEdge AND NOT "上升沿M".CarCallOld THEN "DB_Elevator".CarCall[1] := TRUE; END_IF; "上升沿M".CarCallOld := "上升沿M".CarCallEdge;这样写的好处是:即使HMI按钮一直按住,也不会重复触发请求。到达楼层后,用楼层检测信号统一复位对应的请求位,这样请求生命周期就完整了。
3. 核心算法实现:For循环写电梯调度
3.1 请求合并:一个For循环生成停靠表
调度逻辑每个扫描周期都从合并请求开始。合并规则很简单:任何一层有内呼、有顺向上呼或者有顺向下呼,这一层就进入停靠表。
// 请求合并:任何请求都认为该层需要停靠 FOR i := 1 TO MAX_FLOOR DO "DB_Elevator".StopReq[i] := "DB_Elevator".CarCall[i] OR "DB_Elevator".UpCall[i] OR "DB_Elevator".DownCall[i]; END_FOR;这里MAX_FLOOR是全局常量,我一般用16。需要注意的一点,这个合并结果只用于调度判断,真正到站开门之后,要单独把相应方向的请求复位,不能把整张表清空,否则其他方向同楼层的请求也被误清了。
比如电梯上行到达7楼开门,7楼同时有一个下行外呼。如果你在开门瞬间把StopReq[7]、UpCall[7]、DownCall[7]全部复位,那7楼那个下行请求就丢了。正确做法是开门时只复位CarCall[7]和当前运行方向对应的那个外呼,反方向的请求保留,等电梯换向下来时再响应。
3.2 寻找上行目标:从近到远的For扫描
当电梯处于上行状态时,目标楼层的选择逻辑是:从当前楼层的上一层开始往上扫,找到第一个有停靠请求的楼层,这个就是最近要停的楼层。注意,我找的是“最近”,不是“最远”。因为电梯是一路往上走的,途中每一层有请求都会停,真正决定电梯要不要继续往上走的,是最远那个请求;而决定“下一站停哪”的,是最远和最近共同作用的结果。
// 上行状态下:寻找最近的顺向停靠点 "DB_Elevator".TargetFloor := 0; // 0表示未找到 IF "DB_Elevator".Direction = 1 THEN FOR i := "DB_Elevator".CurrentFloor + 1 TO MAX_FLOOR DO IF "DB_Elevator".StopReq[i] THEN "DB_Elevator".TargetFloor := i; EXIT; // 找到第一个就退出 END_IF; END_FOR; END_IF;EXIT是SCL里跳出循环的关键字,只跳出当前这一层循环。这个For循环的扫描范围是从CurrentFloor+1开始,这本身就是在实现“方向限制”——下行楼层不在扫描范围内。等电梯到了TargetFloor,开门,乘客进出,然后重新扫描下一段。
这里要说明一下,实际电梯控制里,不会真的只找一个目标然后一股脑往那跑。轿厢每到一个停靠层都要重新决策一次:是继续走、还是停、还是换向。所以这段For循环其实在每一个“已停靠楼层”之后都会重新执行一遍,执行频率不需要太高,放在OB1里每周期跑完全没问题,因为SCL的运算速度对这个体量来说绰绰有余。
3.3 换向判断:反向请求怎么响应
电梯跑到最上层停完以后,怎么知道该换向下去了?答案是:上行状态下,扫描当前楼层以上的区域,如果没有发现任何请求,再扫描当前楼层以下的区域,如果下方有请求,就换向。
// 上行时检查上方是否还有请求 "DB_Elevator".HasReqAbove := FALSE; FOR i := "DB_Elevator".CurrentFloor + 1 TO MAX_FLOOR DO IF "DB_Elevator".StopReq[i] THEN "DB_Elevator".HasReqAbove := TRUE; EXIT; END_IF; END_FOR; // 如果上方没有请求,但下方有请求,换向下行 "DB_Elevator".HasReqBelow := FALSE; FOR i := 1 TO "DB_Elevator".CurrentFloor - 1 DO IF "DB_Elevator".StopReq[i] THEN "DB_Elevator".HasReqBelow := TRUE; EXIT; END_IF; END_FOR;把这个逻辑用语言翻译过来就是:电梯正在上行,如果上面还有人要停,就继续往上;如果上面没人了,下面还有人,那电梯就换向,Direction从1变成-1,开始处理下行请求。在实际程序中,这个换向判断不需要每个周期都做,可以做成“电梯停在某层、门关好、准备启动”时才判断一次,防止运行中出现逻辑抖动。
要说优先级,“上方请求存在”的判断要优先于“下方换向”的判断,顺序不能反。万一上方和下方都有请求,电梯应该继续往上走完,再考虑下来,这是把顺向优先原则落到代码里的关键一步。
3.4 运行控制输出:方向、目标、到站停车逻辑
有了目标楼层和方向,接下来让电梯动起来。方向输出很简单:Direction=1就给上行接触器通电,Direction=-1就给下行接触器通电,TargetFloor=0或者方向没确定时两个都断开。到站停车则依赖于楼层位置检测,一般用编码器或者平层开关。
简化模型里,我建议用当前楼层值和目标楼层值做比较来确定是否停车:CurrentFloor = TargetFloor,就说明电梯已经到达目标楼层,此时断开运行接触器、输出开门指令、复位对应请求。
// 到站逻辑 IF "DB_Elevator".CurrentFloor = "DB_Elevator".TargetFloor AND "DB_Elevator".TargetFloor <> 0 THEN "DB_Elevator".DoorOpen := TRUE; // 开门 "DB_Elevator".Running := FALSE; // 停止运行 // 复位当前楼层的全部请求:内呼和同向外呼 "DB_Elevator".CarCall["DB_Elevator".CurrentFloor] := FALSE; IF "DB_Elevator".Direction = 1 THEN "DB_Elevator".UpCall["DB_Elevator".CurrentFloor] := FALSE; ELSIF "DB_Elevator".Direction = -1 THEN "DB_Elevator".DownCall["DB_Elevator".CurrentFloor] := FALSE; END_IF; // 停车后清零目标,等待下一次决策 "DB_Elevator".TargetFloor := 0; END_IF;代码里有一个容易被忽视的细节:复位请求时,我复位的是CurrentFloor对应的请求,而不是TargetFloor对应的请求。这两者在“到站”那一刻是相等的,但程序执行到后面再复位时,CurrentFloor和TargetFloor可能已经不一样了。用当前楼层做索引,即使下一次扫描周期里电梯已经启动了,也不会误复位前面的楼层请求。
3.5 完整代码串讲:把这些零件拼成一台“会思考的电梯”
前面几段代码都是零件,现在把它们拼成一个完整的FB块。这个FB块名字叫FB_ElevatorDispatch,里面需要调用前面的请求合并、方向保持、目标搜索、换向判断、到站复位几个部分。建议的调用顺序是:
- 采样当前楼层,更新CurrentFloor
- 合并所有请求,生成StopReq数组
- 如果电梯处于停止状态(Direction=0),先做启动决策
- 如果电梯运行中,按当前方向寻找最近目标
- 到站则停车、开门、复位请求
- 运行方向上无请求则换向
启动决策的代码很有意思:电梯静止时,先看当前楼层是否有请求。如果有人在当前层按电梯,直接在当前层开门,不用启动。如果当前层没有,那就先往上找最近的请求,找到了就上行,找不到再往下找,找到了就下行,两个方向都没有就继续停着。
// 停止状态下选择方向 IF "DB_Elevator".Direction = 0 AND NOT "DB_Elevator".DoorOpen THEN // 优先处理当前楼层请求 IF "DB_Elevator".StopReq["DB_Elevator".CurrentFloor] THEN "DB_Elevator".DoorOpen := TRUE; "DB_Elevator".TargetFloor := "DB_Elevator".CurrentFloor; ELSE // 找上方最近请求 "DB_Elevator".TargetFloor := 0; FOR i := "DB_Elevator".CurrentFloor + 1 TO MAX_FLOOR DO IF "DB_Elevator".StopReq[i] THEN "DB_Elevator".TargetFloor := i; EXIT; END_IF; END_FOR; IF "DB_Elevator".TargetFloor <> 0 THEN "DB_Elevator".Direction := 1; ELSE // 上方没有,找下方最近请求 FOR i := "DB_Elevator".CurrentFloor - 1 TO 1 BY -1 DO IF "DB_Elevator".StopReq[i] THEN "DB_Elevator".TargetFloor := i; EXIT; END_IF; END_FOR; IF "DB_Elevator".TargetFloor <> 0 THEN "DB_Elevator".Direction := -1; END_IF; END_IF; END_IF; END_IF;注意FOR循环步长不是1的情况:从当前楼层往下找,要用“FOR i := CurrentFloor - 1 TO 1 BY -1”,这个BY -1表示步长为-1,是SCL里常见的向下循环写法。排查问题的时候,很多新手拿这个循环从上往下扫却怎么也扫不到数据,十有八九是忘了写BY -1。
4. 触摸屏联动演示:从PLC到HMI的完整数据链路
4.1 HMI变量映射与画面规划
调度算法跑通之后,必须让它“看得见”,才好给业主演示、给同事讲逻辑。触摸屏在这套系统里是两个角色:一是操作面板,二是运行监视器。推荐用西门子KTP系列或者威纶通MT系列,两者和S7-1200/1500通信都非常顺手。威纶通在中小项目里性价比高,软件EBpro上手快,而且支持标签导入,工程文件迁移成本很低。
画面布局我习惯规划成五个区域:顶层显示当前楼层和运行方向,中间16行显示每层的三色状态灯(内呼、上呼、下呼),底部放内呼数字键盘和上下行呼梯按钮,右侧放运行模式切换和故障复位。状态灯用颜色区分:红色代表有请求,绿色代表电梯当前停靠层,灰色代表无请求。
变量映射最核心就是一个原则:HMI上的按钮不直接驱动PLC的输出点,只驱动DB里的请求位或中间变量。这样做隔离性最好,万一操作员误触也只影响调用逻辑,不会直接动作接触器。我见过有人把触摸屏按钮直接映射到Q点上的,那是典型的危险设计,检修时按错一下就可能出事故。
4.2 演示场景设计与联合调试
演示要好看,关键是模拟一个“多方向抢梯”的场景。我在做演示时习惯先复位所有请求,然后按以下顺序点按钮:
- 在3楼厅外按上行呼梯,在6楼厅外按下行呼梯,在9楼厅外按上行呼梯
- 观察电梯初始位置,如果电梯在1楼,它应该先响应3楼上行,再响应9楼上行,最后才换向下来处理6楼下行
- 在轿厢里设置几个内呼,比如乘客从3楼上到8楼,然后电梯在上行途中应该优先停8楼,而不是中途跑去接反向呼叫
这个演示顺序能让观看者在一分钟内直观感受到“顺向优先”和“换向规则”。我在触摸屏画面里还会实时用指示灯显示当前扫描到哪一层,配合目标楼层数值的变化,逻辑一目了然。
联合调试时,我第一步先不做闭环,只是用HMI的按钮触发请求,然后监控PC端博途里的DB变量,确认每一位请求都能正确置位。第二步才让电机带动编码器动起来,验证到站复位逻辑。第三步再做多请求组合测试。千万不要一上来就全联动,出了问题都不知道是按钮层、逻辑层还是驱动层的问题。
4.3 没有实体触摸屏怎么跑仿真
很多朋友手上没有现成的HMI设备,那也可以演示。两个方案:第一,用博途集成的WinCC Runtime仿真,和PLCSIM配合,虚拟触摸屏直接跑;第二,用威纶通EBpro自带的离线模拟器,它能模拟触摸屏和PLC通信,但前提是你得装上Modbus TCP驱动或者S7驱动,并正确填写PLC的IP地址和机架号。
WinCC仿真我建议用这套参数:PLC按S7-1500建,IP设成192.168.0.1,HMI连接里填同样IP,通讯驱动选S7-1500,机架0,插槽1。在线模拟时打开PLCSIM并下载程序到虚拟PLC,然后WinCC点启动仿真,两边的变量就会实时同步。调试完成后如果换实体屏,改动也小,HMI里变量引用的DB结构不变,只需要重新建立连接即可。
用仿真还有一个额外好处:可以随时暂停、单周期执行,方便把电梯停在某个特定楼层来测边界情况。实体电梯可做不到你想停哪就停哪。我测边界条件几乎全是在PLCSIM里跑的,比如“电梯在1楼收到16楼下呼”“电梯在16楼收到1楼上呼”这种极端情况,实体设备上很难制造,仿真里按两个按钮就行。
5. 常见问题与排查技巧实录
5.1 问题速查表
这几年代码写多了,周围同事和朋友踩过的坑我也见过不少,专门整理一个排查表:
| 现象 | 常见原因 | 排查思路 |
|---|---|---|
| 电梯不响应任何呼叫 | 请求合并表没更新,或者方向一直为0 | 监控StopReq数组,看按钮置位是否传到数组 |
| 电梯总是过站不停 | 楼层检测信号没进入CurrentFloor,目标楼层和当前楼层对不上 | 检查编码器/平层开关信号,对比CurrentFloor和TargetFloor |
| 电梯只走一个方向 | 换向判断的BY -1写错,下方请求扫不到 | 单独监控HasReqBelow标志,重点查向下For循环 |
| 到站后请求没有消除 | 复位逻辑里用的是TargetFloor而不是CurrentFloor | 换成CurrentFloor做索引 |
| 触摸屏按钮无效果 | HMI变量映射到优化DB的位变量,访问异常 | 把需要HMI访问的变量放到非优化DB,或用字节映射 |
| 外呼按钮按了马上灭 | HMI按钮被配置成“按一下有效”的瞬时模式,而PLC没有做锁存 | HMI端按钮模式改“置位”,或者PLC里做自锁/边沿锁存 |
| 满载直驶功能失效 | 超载信号没有参与方向判断逻辑 | 在启动决策之前加一个满载直驶分支 |
5.2 优先级判断老是不对?先查这三个地方
如果你发现电梯响应顺序总是和预期不一样,别急着改算法,先用监控表查三个点。第一,StopReq数组的值对不对,有没有应该复位的请求没复位,或者不该置位的请求被置位了。第二,CurrentFloor的精度,有些编码器信号毛刺大,楼层会跳变,比如从5楼直接跳到7楼,中间6楼的请求就永远没机会响应。楼层检测建议做滤波或者用平层开关做硬定位,编码器只能做辅助。
第三,方向信号和实际运行方向是不是一致。调试初期最容易出现的情况是:程序判断Direction为1,但电机接线导致电梯实际是在往下走,这时候所有基于“当前方向=上行”的调度判断全部失效。我建议第一步不要做自动调度,手动控制电梯上下各走一遍,确认程序里的方向和实际机械方向完全一致了,再连调度逻辑。
我自己的调试习惯是用HMI加一个“手动/自动”切换开关。手动模式下,只通过HMI按钮控制上下行,先验证电机和楼层检测的基础功能;自动模式下,才让调度逻辑接管。两套逻辑互锁不能同时输出,否则手动模式下电梯也会被调度带跑。
5.3 扩展思考:从单梯调度到多梯与驱动联动
单梯调度做完以后,很多人会想往多梯群控或者和变频器联动方向扩展。这两个方向都是很好的加分项,但复杂度完全不在一个量级。先说变频器联动:热点上有人问到“西门子PLC和ABB变频器做Modbus通讯控制”,如果电梯用变频器驱动,PLC只需要发送启停、给定速度,然后读当前频率和故障状态,驱动模型基本保持前面代码不变,只是把“方向接触器输出”换成“变频器控制字”,把电流、速度反馈接进来。Modbus通讯建议用PLC自带的RS485或者Modbus TCP块,V90、ACS580这些主流变频器都有现成库,重点是做好通信超时处理,通信失败要置故障停车。
多梯群控就复杂得多,涉及“最靠近原则”的电梯分配,不能每台梯各跑各的。常见做法是加一个群控控制器,收集所有外呼,计算每台电梯到达呼叫楼层的预估时间,选最小者响应。这个计算过程在SCL里也可以实现,思路是遍历每台电梯的当前位置和方向,用For循环算每台到每个请求的“距离代价”,然后找最小值。不过多梯联动建议在单个电梯调度完全稳定之后再做,否则排查问题的时候会分不清是单梯逻辑错了还是分配逻辑错了。
最后我再提一个小经验:如果你准备把这套逻辑用在实际项目中,一定要在HMI上留一个“调度逻辑复位”按钮,一键把所有请求数组、方向、目标都清零。电梯调试和试运行阶段,这个按钮能帮你省掉大把重新置位的功夫。我在每个项目中都保留这个按钮,等系统运行稳定后才考虑是否从面向用户的操作面板上移除。我自己写电梯调度的最大体会是:代码本身并不难,难的是把”遇到什么情况怎么处理“的所有特殊场景都想全,并且让每一段循环扫描都有明确的出口条件。你用For循环把优先级逻辑一层层剥离出来以后,电梯的运行行为会变得极其可预测,这一点在实际交付时比任何花哨的功能都更有价值。