You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

217 lines
8.8 KiB

  1. #!/usr/bin/env python3
  2. """Interactive console for technicians testing the PLSR firmware."""
  3. from __future__ import annotations
  4. import time
  5. import plsr_test
  6. def ask_int(prompt: str, default: int, minimum: int, maximum: int) -> int:
  7. while True:
  8. raw = input(f"{prompt} [{default}]: ").strip()
  9. if not raw:
  10. return default
  11. try:
  12. value = int(raw)
  13. except ValueError:
  14. print("请输入整数。")
  15. continue
  16. if minimum <= value <= maximum:
  17. return value
  18. print(f"允许范围:{minimum}..{maximum}")
  19. def ask_yes(prompt: str) -> bool:
  20. return input(f"{prompt} [y/N]: ").strip().lower() in ("y", "yes")
  21. def choose_port() -> str:
  22. if plsr_test.list_ports is None:
  23. raise plsr_test.PlsrError("缺少 pyserial,请重新运行启动批处理。")
  24. ports = sorted(plsr_test.list_ports.comports(), key=lambda item: item.device)
  25. if ports:
  26. print("\n检测到的串口:")
  27. for index, item in enumerate(ports, 1):
  28. print(f" {index}. {item.device:<8} {item.description}")
  29. devices = [item.device for item in ports]
  30. default = "COM9" if "COM9" in devices else (devices[0] if devices else "COM9")
  31. selected = input(f"请输入测试串口 [{default}]: ").strip().upper()
  32. return selected or default
  33. def confirm_motion() -> bool:
  34. print("\n安全确认:断开机械/伺服负载,仅连接测试灯或示波器。")
  35. return ask_yes("已满足上述条件,允许输出脉冲吗?")
  36. def show_menu(port: str, protocol: str) -> None:
  37. print(
  38. f"""
  39. ================ PLSR Python 测试工具 ================
  40. 当前串口:{port} 参数:115200 / 8E1 / 站号1 / 映射:{protocol}
  41. 1. 读取一次状态
  42. 2. 持续监视状态
  43. 3. 配置并保存脉冲点/方向点
  44. 4. 正向低速灯测
  45. 5. 反向低速灯测
  46. 6. 正反向自动测试
  47. 7. 受控停止
  48. 8. 立即停止
  49. 9. 复位
  50. 10. 清零累计脉冲
  51. 11. 显示原始通信帧开关
  52. 12. 更换串口
  53. 0. 退出
  54. ======================================================
  55. """
  56. )
  57. def run_motion(client: plsr_test.ModbusRtuClient, direction: int) -> None:
  58. if not confirm_motion():
  59. print("已取消。")
  60. return
  61. if client.protocol == plsr_test.MAP_SPEC:
  62. pulses = ask_int("脉冲数(可负,0 不允许)", 10, -2147483648, 2147483647)
  63. while pulses == 0:
  64. print("脉冲数不能为 0。")
  65. pulses = ask_int("脉冲数(可负,0 不允许)", 10, -2147483648, 2147483647)
  66. frequency = ask_int("频率 Hz(1..100000,肉眼观察建议 1..5)", 2, 1, 100000)
  67. else:
  68. pulses = ask_int("脉冲数", 10, 1, 0xFFFFFFFF)
  69. frequency = ask_int("频率 Hz(肉眼观察建议 1..5)", 2, 1, 1000)
  70. client.start_motion(pulses, frequency, direction)
  71. result = plsr_test.wait_for_terminal(client, max(10.0, pulses / frequency + 5.0))
  72. if result.state == 5:
  73. print("PASS:运动正常完成。")
  74. else:
  75. print(f"FAIL:运动结束状态为 {result.state_name}。")
  76. def auto_test(client: plsr_test.ModbusRtuClient) -> None:
  77. if not confirm_motion():
  78. print("已取消。")
  79. return
  80. if client.protocol == plsr_test.MAP_SPEC:
  81. pulse_output = ask_int("脉冲输出 Y0..Y3", 0, 0, 3)
  82. direction_output = ask_int("方向输出 Y12..Y15(填 0..3)", 0, 0, 3)
  83. else:
  84. pulse_output = ask_int("脉冲输出 Q 点", 4, 0, 7)
  85. direction_output = ask_int("方向输出 Q 点", 1, 0, 7)
  86. if pulse_output == direction_output:
  87. print("脉冲点和方向点不能相同。")
  88. return
  89. pulses = ask_int("每个方向的脉冲数", 6, 1, 10000)
  90. frequency = ask_int("频率 Hz", 2, 1, 100000 if client.protocol == plsr_test.MAP_SPEC else 1000)
  91. save = False if client.protocol == plsr_test.MAP_SPEC else ask_yes("将 Q 点配置保存到 Flash 吗?")
  92. plsr_test.print_snapshot(client.configure_outputs(pulse_output, direction_output, save))
  93. client.command(plsr_test.COMMAND_CLEAR_COUNT)
  94. print("\n开始正向测试,请观察脉冲灯。")
  95. client.start_motion(pulses, frequency, 0)
  96. forward = plsr_test.wait_for_terminal(client, max(10.0, pulses / frequency + 5.0))
  97. if forward.state != 5 or forward.total_pulses != pulses:
  98. raise plsr_test.PlsrError(
  99. f"正向失败:state={forward.state_name}, total={forward.total_pulses}, expected={pulses}"
  100. )
  101. print("\n开始反向测试,脉冲灯应继续闪烁,方向点电平应改变。")
  102. client.start_motion(pulses, frequency, 1)
  103. reverse = plsr_test.wait_for_terminal(client, max(10.0, pulses / frequency + 5.0))
  104. expected = pulses * 2
  105. if reverse.state != 5 or reverse.total_pulses != expected:
  106. raise plsr_test.PlsrError(
  107. f"反向失败:state={reverse.state_name}, total={reverse.total_pulses}, expected={expected}"
  108. )
  109. print(f"PASS:正反向自动测试完成,累计脉冲={reverse.total_pulses}。")
  110. def run_menu(port: str, protocol: str) -> str | None:
  111. verbose = False
  112. with plsr_test.ModbusRtuClient(port, timeout=1.0, retries=2, verbose=verbose, protocol=protocol) as client:
  113. print("\n串口连接成功。")
  114. try:
  115. plsr_test.print_snapshot(client.read_snapshot())
  116. except plsr_test.PlsrError as exc:
  117. print(f"首次状态读取失败:{exc}")
  118. while True:
  119. show_menu(port, protocol)
  120. choice = input("请选择操作: ").strip()
  121. try:
  122. if choice == "0":
  123. return None
  124. if choice == "1":
  125. plsr_test.print_snapshot(client.read_snapshot())
  126. elif choice == "2":
  127. print("持续监视中,按 Ctrl+C 返回菜单。")
  128. try:
  129. while True:
  130. plsr_test.print_snapshot(client.read_snapshot())
  131. time.sleep(0.5)
  132. except KeyboardInterrupt:
  133. print("\n已停止监视。")
  134. elif choice == "3":
  135. if protocol == plsr_test.MAP_SPEC:
  136. pulse = ask_int("脉冲输出 Y0..Y3", 0, 0, 3)
  137. direction = ask_int("方向输出 Y12..Y15(填 0..3)", 0, 0, 3)
  138. else:
  139. pulse = ask_int("脉冲输出 Q 点", 4, 0, 7)
  140. direction = ask_int("方向输出 Q 点", 1, 0, 7)
  141. result = client.configure_outputs(pulse, direction, save=False)
  142. plsr_test.print_snapshot(result)
  143. print("配置已写入设备。需求版是否掉电保存由固件保存策略决定。")
  144. elif choice == "4":
  145. run_motion(client, 0)
  146. elif choice == "5":
  147. run_motion(client, 1)
  148. elif choice == "6":
  149. auto_test(client)
  150. elif choice == "7":
  151. client.command(plsr_test.COMMAND_STOP)
  152. plsr_test.print_snapshot(client.read_snapshot())
  153. elif choice == "8":
  154. client.command(plsr_test.COMMAND_IMMEDIATE_STOP)
  155. plsr_test.print_snapshot(client.read_snapshot())
  156. elif choice == "9":
  157. client.command(plsr_test.COMMAND_RESET)
  158. plsr_test.print_snapshot(client.read_snapshot())
  159. elif choice == "10":
  160. client.command(plsr_test.COMMAND_CLEAR_COUNT)
  161. plsr_test.print_snapshot(client.read_snapshot())
  162. elif choice == "11":
  163. verbose = not verbose
  164. client.verbose = verbose
  165. print(f"原始帧显示:{'开启' if verbose else '关闭'}")
  166. elif choice == "12":
  167. return choose_port()
  168. else:
  169. print("无效选项。")
  170. except (OSError, ValueError, plsr_test.PlsrError) as exc:
  171. print(f"操作失败:{exc}")
  172. def main() -> int:
  173. print("PLSR Python 测试工具")
  174. protocol = input("协议映射 spec(参数表-2026)/ legacy(旧固件) [spec]: ").strip().lower() or plsr_test.MAP_SPEC
  175. if protocol not in (plsr_test.MAP_SPEC, plsr_test.MAP_LEGACY):
  176. print("无效映射,使用 spec。")
  177. protocol = plsr_test.MAP_SPEC
  178. port = choose_port()
  179. while port:
  180. try:
  181. port = run_menu(port, protocol)
  182. except (OSError, plsr_test.PlsrError) as exc:
  183. print(f"\n无法使用 {port}:{exc}")
  184. if not ask_yes("重新选择串口吗?"):
  185. return 1
  186. port = choose_port()
  187. print("测试工具已退出。")
  188. return 0
  189. if __name__ == "__main__":
  190. raise SystemExit(main())