ControlPanelUI.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. import math
  2. import threading
  3. import tkinter as tk
  4. from tkinter import messagebox, ttk
  5. from ControlPanelCore import ControlPanelCore
  6. class ControlPanelUI:
  7. def __init__(self, root):
  8. self.joystick_active = False
  9. self.Core = ControlPanelCore()
  10. self.font_style = ("Microsoft YaHei UI", 12)
  11. self.root = root
  12. self.root.title("控制台")
  13. self.setup_ui()
  14. self.target_velocity = int(self.motor_velocity_entry.get()) / 6
  15. self.create_joystick()
  16. self.refresh_status()
  17. def refresh_status(self):
  18. self.Core.update_motor_status()
  19. self.update_ui_labels()
  20. self.schedule_next_refresh()
  21. def update_ui_labels(self):
  22. self.status_label.config(text=f"状态: {'使能' if self.Core.motor_is_running else '失能'}")
  23. self.position_label.config(text=f"位置: {self.Core.motor_position}")
  24. self.motor_angle_label.config(text=f"水平: {self.Core.motor_angle}°")
  25. self.velocity_label.config(text=f"速度: {self.Core.motor_velocity} °/s")
  26. self.mode_label.config(text=f"模式: {self.Core.motor_mode}")
  27. def schedule_next_refresh(self):
  28. self.refresh_thread = threading.Timer(3, self.refresh_status)
  29. self.refresh_thread.start()
  30. def create_joystick(self):
  31. # 创建画布用于摇杆
  32. self.joystick_canvas = tk.Canvas(self.joystick_tab, width=200, height=200, bg='white')
  33. # 使用 grid 布局,并设置 padx, pady 将其居中对齐
  34. self.joystick_canvas.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
  35. self.zero_angle = 255699 / self.Core.motor.resolution * 360
  36. # 定义摇杆和圆周的属性
  37. self.center = (100, 100)
  38. self.circle_radius = 80
  39. self.joystick_radius = 13
  40. # 旋转后,摇杆的初始位置位于圆周的顶部
  41. self.joystick = self.joystick_canvas.create_oval(
  42. self.center[0] - self.joystick_radius, self.center[1] - self.circle_radius - self.joystick_radius,
  43. self.center[0] + self.joystick_radius, self.center[1] - self.circle_radius + self.joystick_radius,
  44. fill='blue'
  45. )
  46. self.joystick_canvas.bind("<B1-Motion>", self.update_joystick_position)
  47. # 绘制摇杆的运动轨迹
  48. self.joystick_canvas.create_oval(
  49. self.center[0] - self.circle_radius, self.center[1] - self.circle_radius,
  50. self.center[0] + self.circle_radius, self.center[1] + self.circle_radius,
  51. outline='gray'
  52. )
  53. # 添加角度显示标签
  54. self.angle_label = tk.Label(self.joystick_canvas, text=f"{round(self.zero_angle, 2)}°", bg='white')
  55. self.angle_label.place(x=self.center[0], y=self.center[1], anchor="center")
  56. self.joystick_canvas.bind("<ButtonRelease-1>", self.on_joystick_release)
  57. def update_joystick_position(self, event):
  58. if self.joystick_active: # 只在激活状态下更新摇杆位置
  59. joystick_angle = math.atan2(self.center[1] - event.y, event.x - self.center[0])
  60. joystick_angle = math.degrees(joystick_angle)
  61. if joystick_angle < 0:
  62. joystick_angle += 360
  63. new_x = self.center[0] + self.circle_radius * math.cos(math.radians(joystick_angle))
  64. new_y = self.center[1] - self.circle_radius * math.sin(math.radians(joystick_angle))
  65. self.joystick_canvas.coords(self.joystick, new_x - self.joystick_radius, new_y - self.joystick_radius,
  66. new_x + self.joystick_radius, new_y + self.joystick_radius)
  67. motor_angle = 360 - joystick_angle + self.zero_angle
  68. motor_angle = motor_angle if motor_angle <= 360 else motor_angle - 360
  69. self.angle_label.config(text=f"{motor_angle:.2f}°") # 在圆心更新角度显示
  70. def on_joystick_release(self, event):
  71. if self.joystick_active:
  72. # 计算并发送当前摇杆的位置
  73. joystick_angle = math.atan2(self.center[1] - event.y, event.x - self.center[0])
  74. joystick_angle = math.degrees(joystick_angle)
  75. if joystick_angle < 0:
  76. joystick_angle += 360
  77. motor_angle = 360 - joystick_angle + self.zero_angle
  78. motor_angle = motor_angle if motor_angle <= 360 else motor_angle - 360
  79. self.update_motor_position(motor_angle)
  80. def update_motor_position(self, angle):
  81. current_angle = self.Core.motor_angle
  82. relative_angle = min([angle - current_angle, current_angle - angle], key=abs)
  83. relative_position = int(relative_angle * self.Core.motor.resolution / 360)
  84. self.Core.motor.set_target_position(relative_position)
  85. self.Core.motor.enable_operation()
  86. self.Core.motor.set_relative_position()
  87. def setup_running_ui(self):
  88. control_board = tk.Frame(self.running_tab)
  89. control_board.pack(expand=True)
  90. speed_frame = tk.Frame(control_board)
  91. speed_frame.grid(column=0, row=1, padx=10, pady=10, sticky="w")
  92. # 在容器内创建文本框
  93. speed_label = tk.Label(speed_frame, text="速度设置: ", font=self.font_style)
  94. speed_label.pack(side=tk.LEFT)
  95. self.motor_velocity_entry = ttk.Entry(speed_frame, font=self.font_style, width=8)
  96. self.motor_velocity_entry.pack(side=tk.LEFT)
  97. self.motor_velocity_entry.insert(0, "90")
  98. # 在容器内创建单位标签
  99. unit_label = tk.Label(speed_frame, text="°/s", font=self.font_style)
  100. unit_label.pack(side=tk.LEFT)
  101. # 校准电机运动速度,消除画面偏移
  102. calibrate_frame = tk.Frame(control_board)
  103. calibrate_frame.grid(column=0, row=3, padx=10, pady=10, sticky="w")
  104. diff_label = tk.Label(calibrate_frame, text="偏移像素: ", font=self.font_style)
  105. diff_label.pack(side=tk.LEFT)
  106. self.diff_entry = ttk.Entry(calibrate_frame, font=self.font_style, width=8)
  107. self.diff_entry.pack(side=tk.LEFT)
  108. self.diff_entry.insert(0, "-60")
  109. pitch_button_frame = tk.Frame(control_board)
  110. pitch_button_frame.grid(column=0, row=2, sticky="w", padx=10)
  111. pitch_button_label = tk.Label(pitch_button_frame, text="俯仰调节:", font=self.font_style)
  112. pitch_button_label.pack(side=tk.LEFT)
  113. # 创建增加俯仰角的按钮
  114. increase_pitch_button = tk.Button(pitch_button_frame, text="🔼", command=self.increase_pitch_button)
  115. increase_pitch_button.pack(side=tk.LEFT)
  116. # 创建减少俯仰角的按钮
  117. decrease_pitch_button = tk.Button(pitch_button_frame, text="🔽", command=self.decrease_pitch_button)
  118. decrease_pitch_button.pack(side=tk.LEFT)
  119. # 创建回到零度的按钮
  120. zero_pitch_button = tk.Button(pitch_button_frame, text="0°", command=self.zero_pitch_button)
  121. zero_pitch_button.pack(side=tk.LEFT)
  122. # 创建按钮的容器
  123. motor_button_frame = tk.Frame(control_board)
  124. motor_button_frame.grid(column=0, row=4, columnspan=2, padx=10, pady=10, sticky="ew")
  125. # 在容器内创建启动按钮
  126. start_button = ttk.Button(motor_button_frame, text="▶ 启动", command=self.start_motor_button)
  127. start_button.grid(row=0, column=0)
  128. # 在容器内创建停止按钮
  129. stop_button = ttk.Button(motor_button_frame, text="⏸ 暂停", command=self.stop_motor_button)
  130. stop_button.grid(row=0, column=1)
  131. # 在容器内创建转台回零按钮
  132. motor_zero_button = ttk.Button(motor_button_frame, text="转台回零", command=self.motor_home_button)
  133. motor_zero_button.grid(row=1, column=0)
  134. # 在容器内创建俯仰回零按钮
  135. pitch_motor_zero_button = ttk.Button(motor_button_frame, text="俯仰回零", command=self.pitch_motor_home_button)
  136. pitch_motor_zero_button.grid(row=1, column=1)
  137. def on_tab_change(self, event):
  138. # 仅在用户确认时执行激活操作
  139. selected_tab = event.widget.tab('current')['text']
  140. if selected_tab == '遥控':
  141. if not self.joystick_active:
  142. user_confirmation = messagebox.askokcancel("确认", "电机将切换为位置控制模式,是否继续?")
  143. if user_confirmation:
  144. self.Core.motor.shutdown()
  145. self.joystick_active = True
  146. self.Core.motor.set_position_mode()
  147. else:
  148. messagebox.showinfo("提示", "当前已处于位置控制模式。")
  149. elif selected_tab == '多段运行':
  150. user_confirmation = messagebox.askokcancel("确认", "电机将切换为多段运行模式,是否继续?")
  151. if user_confirmation:
  152. self.Core.motor.shutdown()
  153. self.Core.motor.set_multi_mode()
  154. self.joystick_active = False
  155. def center_window(self, width, height):
  156. # 获取屏幕尺寸以计算布局参数,使窗口居中
  157. screen_width = self.root.winfo_screenwidth()
  158. screen_height = self.root.winfo_screenheight()
  159. self.root.minsize(width, height)
  160. x = (screen_width / 2) - (width / 2)
  161. y = (screen_height / 2) - (height / 2)
  162. self.root.geometry('%dx%d+%d+%d' % (width, height, x, y))
  163. def setup_segment_ui(self):
  164. segment_frame = tk.Frame(self.rotate_tab) # 多段运动功能框架
  165. segment_frame.pack(expand=True, pady=10)
  166. # 点动步长设置
  167. step_frame = tk.Frame(segment_frame)
  168. step_frame.grid(column=0, row=0, padx=10, pady=10, sticky="w")
  169. step_label = tk.Label(step_frame, text="步长设置: ", font=self.font_style)
  170. step_label.pack(side=tk.LEFT)
  171. self.step_entry = ttk.Entry(step_frame, font=self.font_style, width=8)
  172. self.step_entry.pack(side=tk.LEFT)
  173. self.step_entry.insert(0, "30") # 默认步长为60°
  174. step_unit_label = tk.Label(step_frame, text="°", font=self.font_style)
  175. step_unit_label.pack(side=tk.LEFT)
  176. # 停止延迟时间设置
  177. delay_frame = tk.Frame(segment_frame)
  178. delay_frame.grid(column=0, row=1, padx=10, pady=10, sticky="w")
  179. delay_label = tk.Label(delay_frame, text="等待时间: ", font=self.font_style)
  180. delay_label.pack(side=tk.LEFT)
  181. self.stop_delay_entry = ttk.Entry(delay_frame, font=self.font_style, width=8)
  182. self.stop_delay_entry.pack(side=tk.LEFT)
  183. self.stop_delay_entry.insert(0, "100") # 默认延迟时间为500ms
  184. delay_unit_label = tk.Label(delay_frame, text="ms", font=self.font_style)
  185. delay_unit_label.pack(side=tk.LEFT)
  186. # 速度设置
  187. speed_frame = tk.Frame(segment_frame)
  188. speed_frame.grid(column=0, row=2, padx=10, pady=10, sticky="w")
  189. speed_label = tk.Label(speed_frame, text="轮廓速度: ", font=self.font_style)
  190. speed_label.pack(side=tk.LEFT)
  191. self.speed_entry = ttk.Entry(speed_frame, font=self.font_style, width=8)
  192. self.speed_entry.pack(side=tk.LEFT)
  193. self.speed_entry.insert(0, "50") # 默认延迟时间为500ms
  194. delay_unit_label = tk.Label(speed_frame, text="RPM", font=self.font_style)
  195. delay_unit_label.pack(side=tk.LEFT)
  196. # 加速度设置
  197. acceleration_frame = tk.Frame(segment_frame)
  198. acceleration_frame.grid(column=0, row=3, padx=10, pady=10, sticky="w")
  199. acceleration_label = tk.Label(acceleration_frame, text=" 加速度: ", font=self.font_style)
  200. acceleration_label.pack(side=tk.LEFT)
  201. self.acceleration_entry = ttk.Entry(acceleration_frame, font=self.font_style, width=8)
  202. self.acceleration_entry.pack(side=tk.LEFT)
  203. self.acceleration_entry.insert(0, "200") # 默认延迟时间为500ms
  204. acceleration_unit_label = tk.Label(acceleration_frame, text="RPM/s", font=self.font_style)
  205. acceleration_unit_label.pack(side=tk.LEFT)
  206. # 循环复选框
  207. continuous_frame = tk.Frame(segment_frame)
  208. continuous_frame.grid(column=0, row=4, padx=10, pady=10, sticky="w")
  209. self.continuous_var = tk.BooleanVar(value=True)
  210. self.continuous_check = tk.Checkbutton(continuous_frame, text="开启循环",
  211. variable=self.continuous_var, font=self.font_style)
  212. self.continuous_check.pack(side=tk.LEFT)
  213. # 点动按钮容器
  214. segment_button_frame = tk.Frame(segment_frame)
  215. segment_button_frame.grid(column=0, row=5, padx=10, pady=10, sticky="w")
  216. # 在容器内创建转台回零按钮
  217. download_button = ttk.Button(segment_button_frame, text="⏬ 下载", command=self.download_setting)
  218. download_button.grid(row=0, column=0)
  219. # 在容器内创建启动按钮
  220. start_button = ttk.Button(segment_button_frame, text="▶ 启动", command=self.start_segment_button)
  221. start_button.grid(row=0, column=1)
  222. # 在容器内创建停止按钮
  223. stop_button = ttk.Button(segment_button_frame, text="⏸ 暂停", command=self.stop_motor_button)
  224. stop_button.grid(row=0, column=3)
  225. def download_setting(self):
  226. velocity = int(self.step_entry.get())
  227. acceleration = int(self.acceleration_entry.get())
  228. wait_time = int(self.stop_delay_entry.get())
  229. target_value = int(self.step_entry.get())
  230. target_value = int(self.Core.motor.resolution * target_value / 360)
  231. loop = self.continuous_var.get()
  232. self.Core.download_segment(position=target_value, velocity=velocity, acceleration=acceleration,
  233. wait_time=wait_time, target_value=target_value, loop=loop)
  234. messagebox.showinfo("成功", "下载配置成功!")
  235. def start_segment_button(self):
  236. threading.Thread(target=self.Core.start_segment).start()
  237. def setup_ui(self):
  238. # 配置网格布局
  239. self.root.columnconfigure(0, weight=1)
  240. self.root.columnconfigure(1, weight=1)
  241. self.root.columnconfigure(2, weight=1)
  242. self.center_window(300, 580)
  243. # 创建Notebook
  244. self.notebook = ttk.Notebook(self.root)
  245. self.notebook.grid(column=0, row=0, padx=10, pady=5, sticky='ew')
  246. # 创建标签页
  247. self.joystick_tab = tk.Frame(self.notebook)
  248. self.running_tab = tk.Frame(self.notebook)
  249. self.rotate_tab = tk.Frame(self.notebook) # 新增标签页
  250. # 添加标签页到Notebook
  251. self.notebook.add(self.running_tab, text='运行')
  252. self.notebook.add(self.joystick_tab, text='遥控')
  253. self.notebook.add(self.rotate_tab, text='多段运行')
  254. # 创建摇杆UI组件
  255. self.create_joystick()
  256. self.setup_running_ui()
  257. self.setup_segment_ui()
  258. self.notebook.bind("<<NotebookTabChanged>>", self.on_tab_change)
  259. self.status_frame = ttk.LabelFrame(self.root, text='监控')
  260. self.status_frame.grid(column=0, row=1, padx=10, sticky='ew')
  261. self.status_label = tk.Label(self.status_frame,
  262. text=f"状态: {'使能' if self.Core.motor_is_running else '失能'}",
  263. font=self.font_style,
  264. anchor=tk.W)
  265. self.status_label.grid(column=0, row=1, columnspan=3, padx=10, pady=5, sticky="w")
  266. self.status_label.rowconfigure(0, weight=1)
  267. self.mode_label = tk.Label(self.status_frame,
  268. text=f"模式: {self.Core.motor_mode}",
  269. font=self.font_style,
  270. anchor=tk.W)
  271. self.mode_label.grid(column=0, row=2, columnspan=3, padx=10, pady=5, sticky="w")
  272. self.velocity_label = tk.Label(self.status_frame,
  273. text=f"速度: {self.Core.motor_velocity} °/s",
  274. font=self.font_style,
  275. anchor=tk.W)
  276. self.velocity_label.grid(column=0, row=3, columnspan=3, padx=10, pady=5, sticky="w")
  277. self.position_label = tk.Label(self.status_frame,
  278. text=f"位置: {self.Core.motor_position}",
  279. font=self.font_style,
  280. anchor=tk.W)
  281. self.position_label.grid(column=0, row=4, columnspan=3, padx=10, pady=5, sticky="w")
  282. self.motor_angle_label = tk.Label(self.status_frame,
  283. text=f"水平: {self.Core.motor_angle}°",
  284. font=self.font_style,
  285. anchor=tk.W)
  286. self.motor_angle_label.grid(column=1, row=5, padx=5, pady=5, sticky="w")
  287. self.pitch_motor_angle_lable = tk.Label(self.status_frame,
  288. text=f"俯仰: {0 - self.Core.pitch_motor_angle}°",
  289. font=self.font_style,
  290. anchor=tk.W)
  291. self.pitch_motor_angle_lable.grid(column=0, row=5, padx=10, pady=5, sticky="w")
  292. def stop_motor_button(self):
  293. threading.Thread(target=self.Core.stop_motor).start()
  294. def start_motor_button(self):
  295. self.joystick_active = False
  296. try:
  297. self.target_velocity = int(self.motor_velocity_entry.get()) / 6
  298. diff_pixels = float(self.diff_entry.get())
  299. threading.Thread(target=self.Core.start_motor, args=(self.target_velocity, diff_pixels,)).start()
  300. except ValueError:
  301. messagebox.showerror("错误", "请输入有效的速度值")
  302. raise ValueError
  303. def motor_home_button(self):
  304. # 一个耗时操作
  305. thread = threading.Thread(target=self.Core.motor_home)
  306. thread.start()
  307. # 成功的对话框
  308. def pitch_motor_home_button(self):
  309. threading.Thread(target=self.Core.pitch_motor_home()).start()
  310. def increase_pitch_button(self):
  311. threading.Thread(target=self.Core.increase_pitch()).start()
  312. self.update_pitch_angle_label()
  313. self.refresh_status()
  314. def decrease_pitch_button(self):
  315. threading.Thread(target=self.Core.decrease_pitch()).start()
  316. self.update_pitch_angle_label()
  317. self.refresh_status()
  318. def zero_pitch_button(self):
  319. threading.Thread(target=self.Core.zero_pitch()).start()
  320. self.update_pitch_angle_label()
  321. self.refresh_status()
  322. def update_pitch_angle_label(self):
  323. self.Core.pitch_motor.update_angle()
  324. self.pitch_motor_angle = round(self.Core.pitch_motor.current_angle, 2)
  325. self.pitch_motor_angle_lable.config(text=f"俯仰: {0 - self.pitch_motor_angle}°")
  326. if __name__ == "__main__":
  327. root = tk.Tk()
  328. app = ControlPanelUI(root)
  329. root.mainloop()