import math import threading import tkinter as tk from tkinter import messagebox, ttk from ControlPanelCore import ControlPanelCore class ControlPanelUI: def __init__(self, root): self.joystick_active = False self.Core = ControlPanelCore() self.font_style = ("Microsoft YaHei UI", 12) self.root = root self.root.title("控制台") self.setup_ui() self.target_velocity = int(self.motor_velocity_entry.get()) / 6 self.create_joystick() self.refresh_status() def refresh_status(self): self.Core.update_motor_status() self.update_ui_labels() self.schedule_next_refresh() def update_ui_labels(self): self.status_label.config(text=f"状态: {'使能' if self.Core.motor_is_running else '失能'}") self.position_label.config(text=f"位置: {self.Core.motor_position}") self.motor_angle_label.config(text=f"水平: {self.Core.motor_angle}°") self.velocity_label.config(text=f"速度: {self.Core.motor_velocity} °/s") self.mode_label.config(text=f"模式: {self.Core.motor_mode}") def schedule_next_refresh(self): self.refresh_thread = threading.Timer(3, self.refresh_status) self.refresh_thread.start() def create_joystick(self): # 创建画布用于摇杆 self.joystick_canvas = tk.Canvas(self.joystick_tab, width=200, height=200, bg='white') # 使用 grid 布局,并设置 padx, pady 将其居中对齐 self.joystick_canvas.grid(row=0, column=0, padx=10, pady=10, sticky="nsew") self.zero_angle = 255699 / self.Core.motor.resolution * 360 # 定义摇杆和圆周的属性 self.center = (100, 100) self.circle_radius = 80 self.joystick_radius = 13 # 旋转后,摇杆的初始位置位于圆周的顶部 self.joystick = self.joystick_canvas.create_oval( self.center[0] - self.joystick_radius, self.center[1] - self.circle_radius - self.joystick_radius, self.center[0] + self.joystick_radius, self.center[1] - self.circle_radius + self.joystick_radius, fill='blue' ) self.joystick_canvas.bind("", self.update_joystick_position) # 绘制摇杆的运动轨迹 self.joystick_canvas.create_oval( self.center[0] - self.circle_radius, self.center[1] - self.circle_radius, self.center[0] + self.circle_radius, self.center[1] + self.circle_radius, outline='gray' ) # 添加角度显示标签 self.angle_label = tk.Label(self.joystick_canvas, text=f"{round(self.zero_angle, 2)}°", bg='white') self.angle_label.place(x=self.center[0], y=self.center[1], anchor="center") self.joystick_canvas.bind("", self.on_joystick_release) def update_joystick_position(self, event): if self.joystick_active: # 只在激活状态下更新摇杆位置 joystick_angle = math.atan2(self.center[1] - event.y, event.x - self.center[0]) joystick_angle = math.degrees(joystick_angle) if joystick_angle < 0: joystick_angle += 360 new_x = self.center[0] + self.circle_radius * math.cos(math.radians(joystick_angle)) new_y = self.center[1] - self.circle_radius * math.sin(math.radians(joystick_angle)) self.joystick_canvas.coords(self.joystick, new_x - self.joystick_radius, new_y - self.joystick_radius, new_x + self.joystick_radius, new_y + self.joystick_radius) motor_angle = 360 - joystick_angle + self.zero_angle motor_angle = motor_angle if motor_angle <= 360 else motor_angle - 360 self.angle_label.config(text=f"{motor_angle:.2f}°") # 在圆心更新角度显示 def on_joystick_release(self, event): if self.joystick_active: # 计算并发送当前摇杆的位置 joystick_angle = math.atan2(self.center[1] - event.y, event.x - self.center[0]) joystick_angle = math.degrees(joystick_angle) if joystick_angle < 0: joystick_angle += 360 motor_angle = 360 - joystick_angle + self.zero_angle motor_angle = motor_angle if motor_angle <= 360 else motor_angle - 360 self.update_motor_position(motor_angle) def update_motor_position(self, angle): current_angle = self.Core.motor_angle relative_angle = min([angle - current_angle, current_angle - angle], key=abs) relative_position = int(relative_angle * self.Core.motor.resolution / 360) self.Core.motor.set_target_position(relative_position) self.Core.motor.enable_operation() self.Core.motor.set_relative_position() def setup_running_ui(self): control_board = tk.Frame(self.running_tab) control_board.pack(expand=True) speed_frame = tk.Frame(control_board) speed_frame.grid(column=0, row=1, padx=10, pady=10, sticky="w") # 在容器内创建文本框 speed_label = tk.Label(speed_frame, text="速度设置: ", font=self.font_style) speed_label.pack(side=tk.LEFT) self.motor_velocity_entry = ttk.Entry(speed_frame, font=self.font_style, width=8) self.motor_velocity_entry.pack(side=tk.LEFT) self.motor_velocity_entry.insert(0, "90") # 在容器内创建单位标签 unit_label = tk.Label(speed_frame, text="°/s", font=self.font_style) unit_label.pack(side=tk.LEFT) # 校准电机运动速度,消除画面偏移 calibrate_frame = tk.Frame(control_board) calibrate_frame.grid(column=0, row=3, padx=10, pady=10, sticky="w") diff_label = tk.Label(calibrate_frame, text="偏移像素: ", font=self.font_style) diff_label.pack(side=tk.LEFT) self.diff_entry = ttk.Entry(calibrate_frame, font=self.font_style, width=8) self.diff_entry.pack(side=tk.LEFT) self.diff_entry.insert(0, "-60") pitch_button_frame = tk.Frame(control_board) pitch_button_frame.grid(column=0, row=2, sticky="w", padx=10) pitch_button_label = tk.Label(pitch_button_frame, text="俯仰调节:", font=self.font_style) pitch_button_label.pack(side=tk.LEFT) # 创建增加俯仰角的按钮 increase_pitch_button = tk.Button(pitch_button_frame, text="🔼", command=self.increase_pitch_button) increase_pitch_button.pack(side=tk.LEFT) # 创建减少俯仰角的按钮 decrease_pitch_button = tk.Button(pitch_button_frame, text="🔽", command=self.decrease_pitch_button) decrease_pitch_button.pack(side=tk.LEFT) # 创建回到零度的按钮 zero_pitch_button = tk.Button(pitch_button_frame, text="0°", command=self.zero_pitch_button) zero_pitch_button.pack(side=tk.LEFT) # 创建按钮的容器 motor_button_frame = tk.Frame(control_board) motor_button_frame.grid(column=0, row=4, columnspan=2, padx=10, pady=10, sticky="ew") # 在容器内创建启动按钮 start_button = ttk.Button(motor_button_frame, text="▶ 启动", command=self.start_motor_button) start_button.grid(row=0, column=0) # 在容器内创建停止按钮 stop_button = ttk.Button(motor_button_frame, text="⏸ 暂停", command=self.stop_motor_button) stop_button.grid(row=0, column=1) # 在容器内创建转台回零按钮 motor_zero_button = ttk.Button(motor_button_frame, text="转台回零", command=self.motor_home_button) motor_zero_button.grid(row=1, column=0) # 在容器内创建俯仰回零按钮 pitch_motor_zero_button = ttk.Button(motor_button_frame, text="俯仰回零", command=self.pitch_motor_home_button) pitch_motor_zero_button.grid(row=1, column=1) def on_tab_change(self, event): # 仅在用户确认时执行激活操作 selected_tab = event.widget.tab('current')['text'] if selected_tab == '遥控': if not self.joystick_active: user_confirmation = messagebox.askokcancel("确认", "电机将切换为位置控制模式,是否继续?") if user_confirmation: self.Core.motor.shutdown() self.joystick_active = True self.Core.motor.set_position_mode() else: messagebox.showinfo("提示", "当前已处于位置控制模式。") elif selected_tab == '多段运行': user_confirmation = messagebox.askokcancel("确认", "电机将切换为多段运行模式,是否继续?") if user_confirmation: self.Core.motor.shutdown() self.Core.motor.set_multi_mode() self.joystick_active = False def center_window(self, width, height): # 获取屏幕尺寸以计算布局参数,使窗口居中 screen_width = self.root.winfo_screenwidth() screen_height = self.root.winfo_screenheight() self.root.minsize(width, height) x = (screen_width / 2) - (width / 2) y = (screen_height / 2) - (height / 2) self.root.geometry('%dx%d+%d+%d' % (width, height, x, y)) def setup_segment_ui(self): segment_frame = tk.Frame(self.rotate_tab) # 多段运动功能框架 segment_frame.pack(expand=True, pady=10) # 点动步长设置 step_frame = tk.Frame(segment_frame) step_frame.grid(column=0, row=0, padx=10, pady=10, sticky="w") step_label = tk.Label(step_frame, text="步长设置: ", font=self.font_style) step_label.pack(side=tk.LEFT) self.step_entry = ttk.Entry(step_frame, font=self.font_style, width=8) self.step_entry.pack(side=tk.LEFT) self.step_entry.insert(0, "30") # 默认步长为60° step_unit_label = tk.Label(step_frame, text="°", font=self.font_style) step_unit_label.pack(side=tk.LEFT) # 停止延迟时间设置 delay_frame = tk.Frame(segment_frame) delay_frame.grid(column=0, row=1, padx=10, pady=10, sticky="w") delay_label = tk.Label(delay_frame, text="等待时间: ", font=self.font_style) delay_label.pack(side=tk.LEFT) self.stop_delay_entry = ttk.Entry(delay_frame, font=self.font_style, width=8) self.stop_delay_entry.pack(side=tk.LEFT) self.stop_delay_entry.insert(0, "100") # 默认延迟时间为500ms delay_unit_label = tk.Label(delay_frame, text="ms", font=self.font_style) delay_unit_label.pack(side=tk.LEFT) # 速度设置 speed_frame = tk.Frame(segment_frame) speed_frame.grid(column=0, row=2, padx=10, pady=10, sticky="w") speed_label = tk.Label(speed_frame, text="轮廓速度: ", font=self.font_style) speed_label.pack(side=tk.LEFT) self.speed_entry = ttk.Entry(speed_frame, font=self.font_style, width=8) self.speed_entry.pack(side=tk.LEFT) self.speed_entry.insert(0, "50") # 默认延迟时间为500ms delay_unit_label = tk.Label(speed_frame, text="RPM", font=self.font_style) delay_unit_label.pack(side=tk.LEFT) # 加速度设置 acceleration_frame = tk.Frame(segment_frame) acceleration_frame.grid(column=0, row=3, padx=10, pady=10, sticky="w") acceleration_label = tk.Label(acceleration_frame, text=" 加速度: ", font=self.font_style) acceleration_label.pack(side=tk.LEFT) self.acceleration_entry = ttk.Entry(acceleration_frame, font=self.font_style, width=8) self.acceleration_entry.pack(side=tk.LEFT) self.acceleration_entry.insert(0, "200") # 默认延迟时间为500ms acceleration_unit_label = tk.Label(acceleration_frame, text="RPM/s", font=self.font_style) acceleration_unit_label.pack(side=tk.LEFT) # 循环复选框 continuous_frame = tk.Frame(segment_frame) continuous_frame.grid(column=0, row=4, padx=10, pady=10, sticky="w") self.continuous_var = tk.BooleanVar(value=True) self.continuous_check = tk.Checkbutton(continuous_frame, text="开启循环", variable=self.continuous_var, font=self.font_style) self.continuous_check.pack(side=tk.LEFT) # 点动按钮容器 segment_button_frame = tk.Frame(segment_frame) segment_button_frame.grid(column=0, row=5, padx=10, pady=10, sticky="w") # 在容器内创建转台回零按钮 download_button = ttk.Button(segment_button_frame, text="⏬ 下载", command=self.download_setting) download_button.grid(row=0, column=0) # 在容器内创建启动按钮 start_button = ttk.Button(segment_button_frame, text="▶ 启动", command=self.start_segment_button) start_button.grid(row=0, column=1) # 在容器内创建停止按钮 stop_button = ttk.Button(segment_button_frame, text="⏸ 暂停", command=self.stop_motor_button) stop_button.grid(row=0, column=3) def download_setting(self): velocity = int(self.step_entry.get()) acceleration = int(self.acceleration_entry.get()) wait_time = int(self.stop_delay_entry.get()) target_value = int(self.step_entry.get()) target_value = int(self.Core.motor.resolution * target_value / 360) loop = self.continuous_var.get() self.Core.download_segment(position=target_value, velocity=velocity, acceleration=acceleration, wait_time=wait_time, target_value=target_value, loop=loop) messagebox.showinfo("成功", "下载配置成功!") def start_segment_button(self): threading.Thread(target=self.Core.start_segment).start() def setup_ui(self): # 配置网格布局 self.root.columnconfigure(0, weight=1) self.root.columnconfigure(1, weight=1) self.root.columnconfigure(2, weight=1) self.center_window(300, 580) # 创建Notebook self.notebook = ttk.Notebook(self.root) self.notebook.grid(column=0, row=0, padx=10, pady=5, sticky='ew') # 创建标签页 self.joystick_tab = tk.Frame(self.notebook) self.running_tab = tk.Frame(self.notebook) self.rotate_tab = tk.Frame(self.notebook) # 新增标签页 # 添加标签页到Notebook self.notebook.add(self.running_tab, text='运行') self.notebook.add(self.joystick_tab, text='遥控') self.notebook.add(self.rotate_tab, text='多段运行') # 创建摇杆UI组件 self.create_joystick() self.setup_running_ui() self.setup_segment_ui() self.notebook.bind("<>", self.on_tab_change) self.status_frame = ttk.LabelFrame(self.root, text='监控') self.status_frame.grid(column=0, row=1, padx=10, sticky='ew') self.status_label = tk.Label(self.status_frame, text=f"状态: {'使能' if self.Core.motor_is_running else '失能'}", font=self.font_style, anchor=tk.W) self.status_label.grid(column=0, row=1, columnspan=3, padx=10, pady=5, sticky="w") self.status_label.rowconfigure(0, weight=1) self.mode_label = tk.Label(self.status_frame, text=f"模式: {self.Core.motor_mode}", font=self.font_style, anchor=tk.W) self.mode_label.grid(column=0, row=2, columnspan=3, padx=10, pady=5, sticky="w") self.velocity_label = tk.Label(self.status_frame, text=f"速度: {self.Core.motor_velocity} °/s", font=self.font_style, anchor=tk.W) self.velocity_label.grid(column=0, row=3, columnspan=3, padx=10, pady=5, sticky="w") self.position_label = tk.Label(self.status_frame, text=f"位置: {self.Core.motor_position}", font=self.font_style, anchor=tk.W) self.position_label.grid(column=0, row=4, columnspan=3, padx=10, pady=5, sticky="w") self.motor_angle_label = tk.Label(self.status_frame, text=f"水平: {self.Core.motor_angle}°", font=self.font_style, anchor=tk.W) self.motor_angle_label.grid(column=1, row=5, padx=5, pady=5, sticky="w") self.pitch_motor_angle_lable = tk.Label(self.status_frame, text=f"俯仰: {0 - self.Core.pitch_motor_angle}°", font=self.font_style, anchor=tk.W) self.pitch_motor_angle_lable.grid(column=0, row=5, padx=10, pady=5, sticky="w") def stop_motor_button(self): threading.Thread(target=self.Core.stop_motor).start() def start_motor_button(self): self.joystick_active = False try: self.target_velocity = int(self.motor_velocity_entry.get()) / 6 diff_pixels = float(self.diff_entry.get()) threading.Thread(target=self.Core.start_motor, args=(self.target_velocity, diff_pixels,)).start() except ValueError: messagebox.showerror("错误", "请输入有效的速度值") raise ValueError def motor_home_button(self): # 一个耗时操作 thread = threading.Thread(target=self.Core.motor_home) thread.start() # 成功的对话框 def pitch_motor_home_button(self): threading.Thread(target=self.Core.pitch_motor_home()).start() def increase_pitch_button(self): threading.Thread(target=self.Core.increase_pitch()).start() self.update_pitch_angle_label() self.refresh_status() def decrease_pitch_button(self): threading.Thread(target=self.Core.decrease_pitch()).start() self.update_pitch_angle_label() self.refresh_status() def zero_pitch_button(self): threading.Thread(target=self.Core.zero_pitch()).start() self.update_pitch_angle_label() self.refresh_status() def update_pitch_angle_label(self): self.Core.pitch_motor.update_angle() self.pitch_motor_angle = round(self.Core.pitch_motor.current_angle, 2) self.pitch_motor_angle_lable.config(text=f"俯仰: {0 - self.pitch_motor_angle}°") if __name__ == "__main__": root = tk.Tk() app = ControlPanelUI(root) root.mainloop()