123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- 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("<B1-Motion>", 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("<ButtonRelease-1>", 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("<<NotebookTabChanged>>", 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()
|