#!/usr/bin/env python3 """Interactive console for technicians testing the PLSR firmware.""" from __future__ import annotations import time import plsr_test def ask_int(prompt: str, default: int, minimum: int, maximum: int) -> int: while True: raw = input(f"{prompt} [{default}]: ").strip() if not raw: return default try: value = int(raw) except ValueError: print("请输入整数。") continue if minimum <= value <= maximum: return value print(f"允许范围:{minimum}..{maximum}") def ask_yes(prompt: str) -> bool: return input(f"{prompt} [y/N]: ").strip().lower() in ("y", "yes") def choose_port() -> str: if plsr_test.list_ports is None: raise plsr_test.PlsrError("缺少 pyserial,请重新运行启动批处理。") ports = sorted(plsr_test.list_ports.comports(), key=lambda item: item.device) if ports: print("\n检测到的串口:") for index, item in enumerate(ports, 1): print(f" {index}. {item.device:<8} {item.description}") devices = [item.device for item in ports] default = "COM9" if "COM9" in devices else (devices[0] if devices else "COM9") selected = input(f"请输入测试串口 [{default}]: ").strip().upper() return selected or default def confirm_motion() -> bool: print("\n安全确认:断开机械/伺服负载,仅连接测试灯或示波器。") return ask_yes("已满足上述条件,允许输出脉冲吗?") def show_menu(port: str, protocol: str) -> None: print( f""" ================ PLSR Python 测试工具 ================ 当前串口:{port} 参数:115200 / 8E1 / 站号1 / 映射:{protocol} 1. 读取一次状态 2. 持续监视状态 3. 配置并保存脉冲点/方向点 4. 正向低速灯测 5. 反向低速灯测 6. 正反向自动测试 7. 受控停止 8. 立即停止 9. 复位 10. 清零累计脉冲 11. 显示原始通信帧开关 12. 更换串口 0. 退出 ====================================================== """ ) def run_motion(client: plsr_test.ModbusRtuClient, direction: int) -> None: if not confirm_motion(): print("已取消。") return if client.protocol == plsr_test.MAP_SPEC: pulses = ask_int("脉冲数(可负,0 不允许)", 10, -2147483648, 2147483647) while pulses == 0: print("脉冲数不能为 0。") pulses = ask_int("脉冲数(可负,0 不允许)", 10, -2147483648, 2147483647) frequency = ask_int("频率 Hz(1..100000,肉眼观察建议 1..5)", 2, 1, 100000) else: pulses = ask_int("脉冲数", 10, 1, 0xFFFFFFFF) frequency = ask_int("频率 Hz(肉眼观察建议 1..5)", 2, 1, 1000) client.start_motion(pulses, frequency, direction) result = plsr_test.wait_for_terminal(client, max(10.0, pulses / frequency + 5.0)) if result.state == 5: print("PASS:运动正常完成。") else: print(f"FAIL:运动结束状态为 {result.state_name}。") def auto_test(client: plsr_test.ModbusRtuClient) -> None: if not confirm_motion(): print("已取消。") return if client.protocol == plsr_test.MAP_SPEC: pulse_output = ask_int("脉冲输出 Y0..Y3", 0, 0, 3) direction_output = ask_int("方向输出 Y12..Y15(填 0..3)", 0, 0, 3) else: pulse_output = ask_int("脉冲输出 Q 点", 4, 0, 7) direction_output = ask_int("方向输出 Q 点", 1, 0, 7) if pulse_output == direction_output: print("脉冲点和方向点不能相同。") return pulses = ask_int("每个方向的脉冲数", 6, 1, 10000) frequency = ask_int("频率 Hz", 2, 1, 100000 if client.protocol == plsr_test.MAP_SPEC else 1000) save = False if client.protocol == plsr_test.MAP_SPEC else ask_yes("将 Q 点配置保存到 Flash 吗?") plsr_test.print_snapshot(client.configure_outputs(pulse_output, direction_output, save)) client.command(plsr_test.COMMAND_CLEAR_COUNT) print("\n开始正向测试,请观察脉冲灯。") client.start_motion(pulses, frequency, 0) forward = plsr_test.wait_for_terminal(client, max(10.0, pulses / frequency + 5.0)) if forward.state != 5 or forward.total_pulses != pulses: raise plsr_test.PlsrError( f"正向失败:state={forward.state_name}, total={forward.total_pulses}, expected={pulses}" ) print("\n开始反向测试,脉冲灯应继续闪烁,方向点电平应改变。") client.start_motion(pulses, frequency, 1) reverse = plsr_test.wait_for_terminal(client, max(10.0, pulses / frequency + 5.0)) expected = pulses * 2 if reverse.state != 5 or reverse.total_pulses != expected: raise plsr_test.PlsrError( f"反向失败:state={reverse.state_name}, total={reverse.total_pulses}, expected={expected}" ) print(f"PASS:正反向自动测试完成,累计脉冲={reverse.total_pulses}。") def run_menu(port: str, protocol: str) -> str | None: verbose = False with plsr_test.ModbusRtuClient(port, timeout=1.0, retries=2, verbose=verbose, protocol=protocol) as client: print("\n串口连接成功。") try: plsr_test.print_snapshot(client.read_snapshot()) except plsr_test.PlsrError as exc: print(f"首次状态读取失败:{exc}") while True: show_menu(port, protocol) choice = input("请选择操作: ").strip() try: if choice == "0": return None if choice == "1": plsr_test.print_snapshot(client.read_snapshot()) elif choice == "2": print("持续监视中,按 Ctrl+C 返回菜单。") try: while True: plsr_test.print_snapshot(client.read_snapshot()) time.sleep(0.5) except KeyboardInterrupt: print("\n已停止监视。") elif choice == "3": if protocol == plsr_test.MAP_SPEC: pulse = ask_int("脉冲输出 Y0..Y3", 0, 0, 3) direction = ask_int("方向输出 Y12..Y15(填 0..3)", 0, 0, 3) else: pulse = ask_int("脉冲输出 Q 点", 4, 0, 7) direction = ask_int("方向输出 Q 点", 1, 0, 7) result = client.configure_outputs(pulse, direction, save=False) plsr_test.print_snapshot(result) print("配置已写入设备。需求版是否掉电保存由固件保存策略决定。") elif choice == "4": run_motion(client, 0) elif choice == "5": run_motion(client, 1) elif choice == "6": auto_test(client) elif choice == "7": client.command(plsr_test.COMMAND_STOP) plsr_test.print_snapshot(client.read_snapshot()) elif choice == "8": client.command(plsr_test.COMMAND_IMMEDIATE_STOP) plsr_test.print_snapshot(client.read_snapshot()) elif choice == "9": client.command(plsr_test.COMMAND_RESET) plsr_test.print_snapshot(client.read_snapshot()) elif choice == "10": client.command(plsr_test.COMMAND_CLEAR_COUNT) plsr_test.print_snapshot(client.read_snapshot()) elif choice == "11": verbose = not verbose client.verbose = verbose print(f"原始帧显示:{'开启' if verbose else '关闭'}") elif choice == "12": return choose_port() else: print("无效选项。") except (OSError, ValueError, plsr_test.PlsrError) as exc: print(f"操作失败:{exc}") def main() -> int: print("PLSR Python 测试工具") protocol = input("协议映射 spec(参数表-2026)/ legacy(旧固件) [spec]: ").strip().lower() or plsr_test.MAP_SPEC if protocol not in (plsr_test.MAP_SPEC, plsr_test.MAP_LEGACY): print("无效映射,使用 spec。") protocol = plsr_test.MAP_SPEC port = choose_port() while port: try: port = run_menu(port, protocol) except (OSError, plsr_test.PlsrError) as exc: print(f"\n无法使用 {port}:{exc}") if not ask_yes("重新选择串口吗?"): return 1 port = choose_port() print("测试工具已退出。") return 0 if __name__ == "__main__": raise SystemExit(main())