电梯控制器测试平台:基于Modbus、串口与语音播报的搭建实践 电梯控制器不装进电梯怎么验证逻辑到底对不对这是做默纳克控制器调试、出厂测试和售后排查时最常遇到的场景之一。过去很多工程师的做法是拿十几个按钮和指示灯搭一个临时电路靠人工摁开关、看灯亮不亮来判断控制器有没有正常工作。这样不是不能用但效率低而且非常依赖人的注意力。按钮按快了可能漏看指示灯状态变了人没盯住测试结果只能靠手写记录测试完还要再花时间对一遍。这其实就是默纳克测试平台要解决的核心问题不依赖真实轿厢和井道用一套可以重复、可以记录、可以自动判断结果的外部系统把默纳克控制器的输入输出、运行逻辑和状态反馈都测一遍。相比“能不能测出来”我判断一套测试平台值不值得投入更看重三点能不能模拟真实信号能不能自动判断结果能不能低成本落地。语音播报恰好补上了最后一个环节里的一个真实盲区——当测试人员盯着屏幕或指示灯看了几个小时状态可能已经变了但人没看到。如果平台能直接“说”出来测试这件事的可靠性和体验都会明显好很多。这篇文章会从需求拆解、架构设计、硬件准备、软件环境、核心代码到常见问题完整走一遍默纳克控制器测试平台的搭建思路。文章里的串口、Modbus、继电器控制和语音播报都能直接复用具体寄存器地址请以你手上的默纳克型号技术手册为准。1. 默纳克测试平台到底在测什么默纳克是电梯控制系统领域一个很常见的品牌产品覆盖面广从一体化控制器、变频器到门机控制器都有。不同产品型号之间通信协议和控制逻辑会存在差异但大多数控制器在测试阶段都需要做同一类事情模拟外部信号输入观察控制器输出是否正确。电梯控制器的外部信号一般包括开关信号和模拟量信号。比如轿厢按钮、外呼按钮、门区感应开关、限位开关、急停开关、消防返回信号、超载信号这些都属于开关量输入。控制器收到信号之后会通过输出继电器或晶体管输出去控制接触器、抱闸回路、门机、指示灯、蜂鸣器。这就是最基础的“输入到输出”的闭环。没有真实电梯的时候测试平台要代替外部设备主动向控制器发送这些信号然后读取控制器返回的状态数据判断控制逻辑是否符合预期。平台的核心功能可以拆成三层信号层模拟按钮按下、模拟开关闭合、模拟楼层和门区状态。通信层通过串口或 Modbus 与控制器交换数据读到控制器内部的运行状态、故障码、楼层信息。展示层用界面、日志和语音播报把测试过程和结果清楚地呈现给测试人员。语音播报在这三层里并不是业务必须但它解决的问题很真实。测试的时候人的视线不可能全程停留在屏幕上一个长时间运行的测试用例跑到第 40 分钟才等到某个故障信号结果测试员刚好去喝水了就可能漏掉关键状态。语音播报可以把重要节点变成声音信号触发“正在测试”“测试通过”“故障报警”的播报。人不用一直盯着也能在第一时间知道平台发生了什么。2. 平台整体架构与技术选型一套比较典型的默纳克测试平台可以分成四层。第一层是硬件层。核心是默纳克控制器本身其次是为控制器提供外部信号的继电器组、按钮、限位开关以及负责电平转换的串口模块。为了让平台能够模拟真实输入信号硬件层还需要带隔离的继电器板或光耦隔离模块。第二层是驱动层。控制器和电脑之间通过 RS485 转 USB 模块通信电脑通过串口发送 Modbus RTU 命令。如果控制器的输出口是继电器类型还可以通过信号采集电路把输出状态实时读回给电脑形成完整的测试闭环。第三层是业务逻辑层。这一层负责维护测试用例配置测试步骤校验控制器的输出是否符合预期。测试用例不是随便点几下就完它应该是一个可以重复执行的脚本。第四层是展示层。负责把测试结果展示出来包括页面展示、控制台日志、文件记录和语音播报。技术选型上我推荐以 Python 为开发语言。原因很直接Python 对串口、Modbus 和设备控制的第三方库比较成熟代码量相对少而且调试时可以直接进入交互环境测试一行命令不用反复编译。通信库选择 minimalmodbus它底层封装的是 Modbus RTU 协议适合通过 USB 转 RS485 读写寄存器。串口管理使用 pyserial语音播报使用 pyttsx3这是跨平台的 TTS 库在 Windows 环境下默认走 SAPI5不需要额外申请网络接口。如果你希望语音更自然也可以按需替换成 edge-tts、讯飞语音、阿里云语音等在线 TTS但离线场景下 pyttsx3 最省事。整体架构看起来并不复杂。但正因为简单才要注意边界测试平台不是真实电梯它只能验证“控制器的逻辑是否符合预期”不能替代“控制器连接真实负载后的整机验证”。这个边界提前讲清楚后面的开发才不会跑偏。3. 硬件环境与接线准备构建测试平台前先把硬件清单理清楚。一条通用的清单如下默纳克控制器一台型号以实际项目为准。一体机或控制板都行。24V 直流电源一个用于控制器 IO 供电和继电器模块供电。RS485 转 USB 模块一个用于控制器和电脑通信。常见方案是 CH340、FT232 等芯片方案。继电器模块一个通道数建议 8 路以上。用来模拟按钮和开关信号优先选择带光耦隔离的。按钮和限位开关若干准备用手动脉冲代替继电器模拟时使用。运行指示灯若干方便直接观察控制器的输出。端子排、导轨、线槽、不同颜色的 0.5 或 0.75 平方线若干。接线时最需要注意的是信号隔离和电源共地问题。RS485 通信线要接 A/B 两个端子接线顺序不要反。受扰严重时应使用双绞屏蔽线屏蔽层单端接地。继电器模块的驱动侧和输出测尽量使用隔离电源。如果控制器本身支持外接 24V 供电需要确认电源容量和极性。控制器内部有保护电路但接线错误仍然可能烧毁 IO 板。接线完成之后先用万用表量一遍确认每个端子的电压都在预期范围内再给控制器上电。实际接线示例电脑 USB - RS485转USB模块 - 控制器RS485端子(A/B/GND) 控制器24V输出 - 继电器模块供电端 继电器模块的输出触点 - 控制器输入端子(X1/X2/X3...) 控制器输出端子(Y1/Y2...) - 指示灯或信号采集模块对于测试平台来说控制器外部的强电回路不要接比如接触器、抱闸回路、主回路都必须在测试阶段维持断开状态。这既是为了保护设备也是为了保证测试人员的安全。4. 软件环境准备串口、Modbus 与语音库软件环境以 Windows 为主Python 3.9 或更高版本都是比较稳妥的选择。先在终端里确认 Python 已安装python --version pip --version然后安装依赖库pip install pyserial pip install minimalmodbus pip install pyttsx3如果是在 Windows 上使用 RS485 转 USB 模块还需要确认模块对应的驱动已经安装完成。插上设备后右键“此电脑”选择“管理”进入“设备管理器”展开“端口”可以看到类似 COM3、COM4 的串口号。记住它后面写代码时会用到。在写业务代码之前先做一次串口连通性检查。以下代码可以列出当前电脑可用的串口# scan_ports.py import serial.tools.list_ports ports serial.tools.list_ports.comports() for p in ports: print(f串口: {p.device}, 描述: {p.description})运行结果大概是串口: COM4, 描述: USB-SERIAL CH340出现串口描述说明驱动没有问题。下一步需要把串口参数和 Modbus 地址确认清楚。默纳克控制器作为 Modbus 从站通常需要设置站号、波特率、数据位、校验位、停止位。这些参数取决于控制器的参数设置常见组合是 9600 波特率、8 数据位、偶校验、1 停止位也有部分型号用无校验。不要直接照抄以实际手册为准。配置错误时通信层会返回超时或校验错误这也是测试平台最常见的问题之一。如果控制器面板或上位机软件里可以查看通信参数进入对应菜单确认一遍。如果模型比较老没有面板显示就用默认参数逐个尝试并按下面的排查顺序从波特率开始测试。5. Modbus 通信核心代码实现平台的关键路径是先读状态再写信号最后根据状态做判断。先写一个最小通信模块用来初始化串口和连接设备# modbus_device.py import time import minimalmodbus class MonarchDevice: 封装默纳克控制器的相关Modbus操作 def __init__(self, port: str, slave_id: int 1): self.instrument minimalmodbus.Instrument(port, slave_id) self.instrument.serial.baudrate 9600 self.instrument.serial.bytesize 8 self.instrument.serial.parity minimalmodbus.serial.PARITY_EVEN self.instrument.serial.stopbits 1 self.instrument.serial.timeout 0.5 def read_status(self, address: int, count: int 1): 读取保持寄存器状态 return self.instrument.read_registers(address, count, functioncode3) def read_input_bit(self, address: int): 读取输入状态 return self.instrument.read_bit(address, functioncode2) def write_output_bit(self, address: int, value: bool): 写入线圈状态模拟外部信号 return self.instrument.write_bit(address, value) def write_setting_register(self, address: int, value: int): 写入单个寄存器用于设置参数 return self.instrument.write_register(address, value)这个模块做的事情很简单初始化、读状态、写状态。关键点在于地址和功能码要跟控制器手册对应上。Modbus 功能码 3 用于读保持寄存器2 用于读输入状态5 用于写单个线圈。如果你的控制器手册要求的是功能码 4 读输入寄存器就把 functioncode 参数改成 4。在真正跑业务之前建议先做一次手动验证。写一个小脚本读取一组寄存器把原始数据打印出来# quick_read.py from modbus_device import MonarchDevice dev MonarchDevice(COM4, 1) data dev.read_status(0x0100, 10) print(寄存器原始数据:, data)这一步的作用是确认通信链路通没通也确认寄存器值的解释方式。比如有些寄存器是十进制显示有些需要按位拆分有些是补码表示这些都必须对照手册确认。不要假设所有数据都能直接读出来用。6. 信号模拟继电器控制和输入输出映射Modbus 通信通之后下一步就是模拟外部信号。控制器的输入点通常对应 X 端子输出点对应 Y 端子。测试平台要通过继电器的闭合与断开把信号送到 X 端子。比如测试“开门按钮按下”这个动作平台就闭合连接在 X1 端子的继电器。继电器控制可以由电脑串口直接驱动也可以通过单片机或 USB 继电器板实现。如果继电器板支持 Modbus RTU那么这个动作可以直接并入当前通信模块如果继电器板上只有串口指令就单独封装一个函数。以常见的继电器板为例伪代码如下# relay_controller.py import serial class RelayBoard: def __init__(self, port: str): self.ser serial.Serial(port, 9600, timeout0.5) def on(self, channel: int): 打开第 channel 路继电器 cmd bytes([0xA0, 0x01, channel, 0x01, 0x00]) self.ser.write(cmd) def off(self, channel: int): 关闭第 channel 路继电器 cmd bytes([0xA0, 0x01, channel, 0x00, 0x00]) self.ser.write(cmd)这里的字节指令只是示例不代表某一款具体继电器板的协议。接入自己的设备前把继电器的通信手册和默认指令确认清楚。输入信号模拟过程中另一个重要环节是延时。真实的电梯系统中按钮信号会有持续时间门区信号有高有低急停信号是保持型信号。模拟测试时不能一开一关瞬间结束否则控制器可能来不及处理。“按钮按下”至少保持 200 到 500 毫秒保持型信号在测试过程中应持续存在。这个延时不是随便加的它是为了让控制器的扫描周期能够检测到变化。如果平台逻辑检测不到控制器有响应优先检查信号保持时间是否太短。7. 语音播报模块让测试结果开口说话语音播报是这个平台的体验加分项也是让测试过程不那么枯燥的关键。这里选择 pyttsx3 作为离线语音方案初始化之后直接调用即可。# voice.py import pyttsx3 class VoiceReporter: def __init__(self): self.engine pyttsx3.init() # Windows下选用SAPI5Linux/macOS按实际环境调整 self.engine.setProperty(rate, 180) self.engine.setProperty(volume, 0.9) def say(self, text: str): print(f[语音] {text}) self.engine.say(text) self.engine.runAndWait() def say_test_start(self, case_name: str): self.say(f开始测试{case_name}) def say_test_pass(self, case_name: str): self.say(f{case_name}测试通过) def say_test_fail(self, case_name: str, reason: str): self.say(f{case_name}测试失败原因是{reason})调用时只需要创建 VoiceReporter 对象在测试流程的关键节点调用对应方法reporter VoiceReporter() reporter.say_test_start(门区信号测试)语音播报不是简单“把文字读出来”它还要配合测试流程的节奏。测试开始、测试通过、测试失败、故障报警、设备离线这几类状态的播报优先级是不同。故障报警要尽量打断当前流程而普通的测试开始提示则不需要反复播报。还有一个比较实际的调参问题pyttsx3 的语速默认可能偏快或偏慢需要根据测试人员的反馈调整 rate 参数。一般 160 到 200 是比较适合中文播报的区间具体以实际听感为准。如果遇到中英文混读问题可以在播报前统一转成中文。8. 完整的测试流程示例现在把通信、继电器控制和语音播报整合到一起写一个有针对性的测试示例验证“按下外呼上行按钮后控制器能正确记录上行请求”。测试步骤如下初始化串口、Modbus 设备、继电器板、语音播报器。继电器闭合模拟外呼上行按钮按下。延时 300 毫秒。继电器断开模拟按钮松开。读取控制器上行状态寄存器。判断状态是否为预期值。播报测试结果并写入日志。对应代码实现如下# demo_test.py import time import minimalmodbus import pyttsx3 from relay_controller import RelayBoard # 这个示例仅供参考实际寄存器地址来自控制器手册 UP_CALL_REGISTER 0x0201 UP_CALL_EXPECT 1 def demo_test(): relay RelayBoard(COM5) instrument minimalmodbus.Instrument(COM4, 1) instrument.serial.baudrate 9600 instrument.serial.parity minimalmodbus.serial.PARITY_EVEN instrument.serial.timeout 0.5 engine pyttsx3.init() engine.setProperty(rate, 180) def speak(text): print([语音], text) engine.say(text) engine.runAndWait() speak(开始测试外呼上行按钮测试) relay.on(1) time.sleep(0.3) relay.off(1) time.sleep(0.5) state instrument.read_registers(UP_CALL_REGISTER, 1, functioncode3)[0] if state UP_CALL_EXPECT: speak(外呼上行按钮测试通过) else: speak(外呼上行按钮测试失败状态异常) if __name__ __main__: demo_test()从这段代码可以看到真正的业务逻辑其实不强。难点集中在三件事寄存器地址对不对、继电器通道对应关系对不对、判断条件是否符合设备逻辑。为了让平台能服务更多测试场景建议把测试用例从主流程里拆出去。比如把测试用例写成 JSON 文件平台主程序负责读取并执行。这样每次新增用例不需要改代码只需要改配置。{ name: 外呼上行按钮测试, steps: [ {action: relay_on, channel: 1, label: 按下外呼上行}, {action: sleep, duration: 0.3}, {action: relay_off, channel: 1, label: 松开外呼上行}, {action: sleep, duration: 0.5}, {action: read_register, address: 512, expected: 1, label: 校验上行状态} ] }这里的 512 是十进制地址等于十六进制的 0x0200。实际地址需要从手册确认这里只是演示配置结构。9. 运行结果与效果验证运行上面的 demo_test.py如果一切正常控制台会输出如下信息[语音] 开始测试外呼上行按钮测试 [语音] 外呼上行按钮测试通过如果故障真的存在比如控制器没有记录到上行请求控制台输出[语音] 开始测试外呼上行按钮测试 [语音] 外呼上行按钮测试失败状态异常这里的验证标准不能只看语音播报是否“说”了结果还要确认设备侧确实做出了响应。比如观察对应的输出指示灯是否点亮或者通过另一个寄存器读取确认输出状态变化。语音播报只是结果呈现真正的验证来自控制器实际响应。为了让测试可追溯建议把每次测试的记录保存到日志文件。# log_test.py import logging logging.basicConfig( filenametest_platform.log, levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s, encodingutf-8 ) logging.info(外呼上行按钮测试,结果:PASS)运行结束后按时间查看日志把失败用例的寄存器快照、继电器通道、预期值和实际值都记录下来。这样出了问题不用重新跑一遍也能分析。如果程序运行后没有任何输出先从三个位置排查设备管理器里串口是否还在。Modbus 设备是否已上电。串口参数是否和控制器一致。这三个问题能解决现场 80% 以上的通信故障。10. 常见问题与排查思路问题现象可能原因排查方式解决方案串口打开失败串口号已经改变或端口被占用重新拔插设备打开设备管理器查看端口更新代码中的串口号关闭占用串口的其他程序读取寄存器超时波特率、校验位或站号配置错误读取控制器通信参数并和代码对比修改初始化参数或修改控制器参数读到的值一直是 0寄存器地址错误或数据类型解释错误先读取一段连续寄存器原始值和面板显示对比对照手册修正寄存器地址或解析方式继电器不动作通道号错误或供电不足测量继电器电源逐个通道测试修正通道映射考虑使用隔离电源语音播报无声音系统默认语音设备未配置或 rate 过高检查 Windows 语音设置单独运行 pyttsx3 测试设置默认音频设备调整语速中英文混杂播报TTS 对英文单词不友好检查播报文本中是否有英文文本归一化把英文标识转成中文控制器响应不稳定RS485 接线未加终端电阻或线缆过长受干扰检查通信线缆和接地在总线两端接终端电阻使用屏蔽双绞线测试结果和人工判断不符判断条件写反逐步打印寄存器原始值观察真实状态对照手册重新确认逻辑11. 测试平台的工程化建议测试平台很容易从“一个脚本”滑向“一盘散沙”。最开始只是为了测一两个按钮代码写在一个文件里后来越加越多最后连当时设置的地址都找不到来源。为了避免这种问题有几件事值得从一开始就做好。11.1 给控制器型号建立专属配置不同型号的默纳克控制器寄存器地址存在差异。建议一个型号一套配置文件包含通信参数、寄存器映射、IO 通道映射和测试用例。这样以后换到另一台设备只需要切换配置而不是改代码。配置文件可以用 JSON 或 YAML。以 JSON 为例{ controller: NICE-XXXX, baudrate: 9600, parity: E, slave_id: 1, floor_register: 4096, status_register: 4100, io_inputs: { up_button: 1, down_button: 2, door_open: 3 }, io_outputs: { motor_forward: 1, motor_reverse: 2 } }11.2 测试用例要可重复、可回滚测试用例不要靠人肉点击尽量写成脚本或配置描述保证同样的测试步骤可以重复执行。每个测试用例执行前记录环境状态执行后把寄存器数值和继电器通道的变化都写进日志。11.3 异常处理要向前看控制器通信偶尔会出现“一次失败再试就成功”的情况。不要在业务代码里直接让程序崩溃而是加入重试机制。# retry.py import time import minimalmodbus def read_with_retry(instrument, address, retries3): for i in range(retries): try: return instrument.read_register(address) except Exception as e: print(f第{i 1}次读取失败: {e}) time.sleep(0.2) raise RuntimeError(连续读取失败)但重试要有限度。连续失败三次以上建议直接判定为通信故障并触发语音播报而不是无限尝试。11.4 语音内容要标准化播报内容统一使用“开始测试”“测试通过”“测试失败”“通信故障”等固定句式。这样测试人员即使不盯着屏幕也能通过语音判断当前测试状态。不要每次把一长串寄存器地址都播报出来人记不住也没有意义。11.5 关键安全边界测试平台只允许在小功率、隔离回路中进行信号模拟。任何涉及强电、抱闸回路、主回路的操作都必须切断测试平台连接在真实整机验证阶段由专业人员完成。测试接线前断电改线后复查这是最基本的原则。12. 从“能跑”到“好用”的下一步第一版测试平台跑通后先别急着加功能。先问自己几个问题测试结果能不能准确记录连续跑十个用例会不会出现稳定性问题寄存器配置有没有文档可查语音播报在嘈杂环境中能不能替代人工观察如果答案都是“够用”再考虑后续优化。比较有价值的方向有三个。第一把测试报告自动导出成 HTML 或 Excel 文件。测试执行完自动生成报告发给同事就能直接查看结果省掉手工整理。第二加入测试任务队列。把多个测试用例排成一个队列批量执行中途失败自动停止并播报。这个功能能让测试平台从“半自动”变成“全自动”。第三把历史测试结果存起来做趋势分析。同一台控制器在两个时间点的测试数据如果出现差异说明设备可能已经出现了老化或参数漂移。从简单串口通信到继电器信号模拟再到语音播报结果这套平台的每个模块都不复杂但组合起来之后测试效率和可靠性会比“看灯、按按钮”高出一个量级。语音播报是其中最有体验感的一部分。开发时可以先做好通信和信号模拟这两条主干再逐步完善语音、报告和队列。测试这件事关键是结果可重复、过程可追溯。平台能把这些做到位就已经非常实用了。