diff --git a/src/gui/factory_calibration_tool.py b/src/gui/factory_calibration_tool.py index 7ac18e1..1e915a9 100644 --- a/src/gui/factory_calibration_tool.py +++ b/src/gui/factory_calibration_tool.py @@ -1,8 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -EZ Tool - 简化版双串口工厂舵机标定工具 -基于原始工具,只增加一个中间值校准按钮 +EZ Tool - Simplified dual-serial-port factory servo calibration tool +Based on the original tool, with just one added mid-point calibration button """ import sys @@ -95,7 +95,7 @@ def get_voltage_range(voltage): def get_servo_model_name(model_number): - """根据型号编号获取舵机型号名称,未知则返回原始编号字符串""" + """Get the servo model name from its model number; returns the raw number string if unknown""" if model_number is None: return "--" name = SERVO_MODEL_NAME_MAP.get(model_number) @@ -112,7 +112,7 @@ def get_servo_model_name(model_number): class RemoteControlWorker(QObject): - """遥控操作后台工作线程""" + """Remote-control operation background worker thread""" status_updated = Signal(str) # 状态更新信号 log_message = Signal(str) # 日志消息信号 control_started = Signal() # 遥控启动信号 @@ -127,9 +127,9 @@ def __init__(self, read_port=None, control_port=None): self.control_port = control_port def start_remote_control(self): - """启动遥控操作""" + """Start remote-control operation""" if self.running: - return False, "遥控操作已在运行" + return False, "Remote-control operation already running" try: # 使用 -m 模块方式运行,确保能找到 scservo_sdk @@ -152,21 +152,21 @@ def start_remote_control(self): ) self.running = True - self.log_message.emit("🚀 遥控操作已启动 (10ms更新间隔)") + self.log_message.emit("🚀 Remote-control operation started (10ms update interval)") self.control_started.emit() # 启动监控线程 threading.Thread(target=self._monitor_process, daemon=True).start() - return True, "遥控操作启动成功" + return True, "Remote-control operation started successfully" except Exception as e: - return False, f"启动遥控操作失败: {e}" + return False, f"Failed to start remote-control operation: {e}" def stop_remote_control(self): - """停止遥控操作""" + """Stop remote-control operation""" if not self.running: - return False, "遥控操作未运行" + return False, "Remote-control operation not running" try: if self.remote_process: @@ -181,15 +181,15 @@ def stop_remote_control(self): self.running = False self.remote_process = None - self.log_message.emit("⏹️ 遥控操作已停止") + self.log_message.emit("⏹️ Remote-control operation stopped") self.control_stopped.emit() - return True, "遥控操作停止成功" + return True, "Remote-control operation stopped successfully" except Exception as e: - return False, f"停止遥控操作失败: {e}" + return False, f"Failed to stop remote-control operation: {e}" def _monitor_process(self): - """监控遥控进程的输出""" + """Monitor remote-control process output""" if not self.remote_process: return @@ -199,28 +199,28 @@ def _monitor_process(self): if line: line = line.strip() if line: - self.log_message.emit(f"遥控: {line}") + self.log_message.emit(f"Remote: {line}") time.sleep(0.1) # 进程结束 if self.remote_process.poll() is not None: self.running = False self.remote_process = None - self.log_message.emit("🔚 遥控进程已结束") + self.log_message.emit("🔚 Remote-control process ended") self.control_stopped.emit() except Exception as e: - self.log_message.emit(f"监控遥控进程异常: {e}") + self.log_message.emit(f"Exception monitoring remote-control process: {e}") self.running = False self.control_stopped.emit() class IDChangeDialog(QDialog): - """修改舵机ID对话框 - 允许用户自定义源ID和目标ID""" + """Change servo ID dialog - lets the user set a custom source ID and target ID""" def __init__(self, current_servos, default_old_id=None, default_new_id=None, parent=None): super().__init__(parent) - self.setWindowTitle("修改舵机ID") + self.setWindowTitle("Change Servo ID") self.setMinimumWidth(280) self.setStyleSheet(""" QDialog { background-color: #f8f9fa; color: #212529; } @@ -245,12 +245,12 @@ def __init__(self, current_servos, default_old_id=None, default_new_id=None, par # 源ID(下拉选择当前在线的舵机) self.old_id_combo = QComboBox() for servo_id in sorted(current_servos): - self.old_id_combo.addItem(f"舵机 ID {servo_id}", servo_id) + self.old_id_combo.addItem(f"Servo ID {servo_id}", servo_id) if default_old_id and default_old_id in current_servos: index = self.old_id_combo.findData(default_old_id) if index >= 0: self.old_id_combo.setCurrentIndex(index) - layout.addRow("源舵机ID:", self.old_id_combo) + layout.addRow("Source Servo ID:", self.old_id_combo) # 目标ID(数字输入) self.new_id_input = QSpinBox() @@ -259,7 +259,7 @@ def __init__(self, current_servos, default_old_id=None, default_new_id=None, par self.new_id_input.setValue(default_new_id) else: self.new_id_input.setValue(1) - layout.addRow("修改ID为:", self.new_id_input) + layout.addRow("Change ID To:", self.new_id_input) # 按钮 buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) @@ -272,7 +272,7 @@ def get_values(self): class ServoWorker(QObject): - """单个舵机控制工作线程""" + """Single-servo control worker thread""" status_updated = Signal(list, bool, str) # 舵机列表, 连接状态, 端口标识 id_changed = Signal(int, int, bool, str, str) # old_id, new_id, success, message, 端口标识 log_message = Signal(str, str) # 日志消息, 端口标识 @@ -315,23 +315,23 @@ def __init__(self, port_name: str, port_id: str): self.rescan_requested = threading.Event() # 手动重新扫描请求 def request_rescan(self): - """请求立即重新扫描""" + """Request an immediate rescan""" self.rescan_requested.set() self.command_event.set() - self.log_message.emit("🔄 收到重新扫描请求", self.port_id) + self.log_message.emit("🔄 Rescan request received", self.port_id) def connect_servo(self) -> bool: - """连接舵机控制器""" + """Connect to the servo controller""" try: print(f"[DEBUG] {self.port_id}: Attempting to connect to {self.port_name}") - self.log_message.emit(f"正在连接舵机控制器: {self.port_name}", self.port_id) + self.log_message.emit(f"Connecting to servo controller: {self.port_name}", self.port_id) # 如果已有旧连接,先彻底关闭并释放(Windows 必须等句柄释放) if self.port_handler is not None: try: self.port_handler.closePort() except Exception as e: - print(f"[DEBUG] {self.port_id}: 关闭旧端口时异常: {e}") + print(f"[DEBUG] {self.port_id}: exception closing old port: {e}") self.port_handler = None self.servo_handler = None if os.name == 'nt': @@ -341,61 +341,61 @@ def connect_servo(self) -> bool: if not self.port_handler.openPort(): print(f"[DEBUG] {self.port_id}: Failed to open port {self.port_name}") - self.log_message.emit(f"❌ 无法打开串口: {self.port_name}", self.port_id) + self.log_message.emit(f"❌ Can't open serial port: {self.port_name}", self.port_id) return False if not self.port_handler.setBaudRate(self.baud_rate): print(f"[DEBUG] {self.port_id}: Failed to set baud rate {self.baud_rate}") - self.log_message.emit(f"❌ 无法设置波特率: {self.baud_rate}", self.port_id) + self.log_message.emit(f"❌ Can't set baud rate: {self.baud_rate}", self.port_id) self.port_handler.closePort() return False self.servo_handler = sms_sts(self.port_handler) self.is_connected = True print(f"[DEBUG] {self.port_id}: Successfully connected to {self.port_name}") - self.log_message.emit("✅ 舵机控制器连接成功", self.port_id) + self.log_message.emit("✅ Servo controller connected successfully", self.port_id) return True except Exception as e: print(f"[DEBUG] {self.port_id}: Connection exception: {e}") - self.log_message.emit(f"❌ 连接失败: {e}", self.port_id) + self.log_message.emit(f"❌ Connection failed: {e}", self.port_id) return False def disconnect_servo(self): - """断开舵机连接""" + """Disconnect from the servo""" try: if self.port_handler: self.port_handler.closePort() except Exception as e: - print(f"[DEBUG] {self.port_id}: 断开连接异常: {e}") + print(f"[DEBUG] {self.port_id}: exception disconnecting: {e}") finally: self.is_connected = False self.port_handler = None self.servo_handler = None - self.log_message.emit("🔌 舵机控制器已断开", self.port_id) + self.log_message.emit("🔌 Servo controller disconnected", self.port_id) def ping_servo(self, servo_id: int) -> bool: - """检测舵机是否存在""" + """Detect whether the servo is present""" try: model_number, result, error = self.servo_handler.ping(servo_id) if result == COMM_SUCCESS: - print(f"[DEBUG] {self.port_id}: 舵机 {servo_id} 型号: {model_number}") + print(f"[DEBUG] {self.port_id}: servo {servo_id} model: {model_number}") return True else: - print(f"[DEBUG] {self.port_id}: Ping 舵机 {servo_id} 失败: result={result}, error={error}") + print(f"[DEBUG] {self.port_id}: Ping servo {servo_id} failed: result={result}, error={error}") return False except Exception as e: - print(f"[DEBUG] {self.port_id}: Ping 舵机 {servo_id} 异常: {e}") + print(f"[DEBUG] {self.port_id}: Ping servo {servo_id} exception: {e}") return False def scan_servos(self) -> List[int]: - """扫描所有舵机""" + """Scan all servos""" if not self.is_connected: return [] # 热插拔检测:在 POSIX 系统上检查端口设备文件是否仍然存在 if os.name != 'nt' and not os.path.exists(self.port_name): - print(f"[DEBUG] {self.port_id}: 端口设备已消失: {self.port_name}") + print(f"[DEBUG] {self.port_id}: port device disappeared: {self.port_name}") self.is_connected = False try: self.disconnect_servo() @@ -411,15 +411,15 @@ def scan_servos(self) -> List[int]: return found_servos def change_servo_id(self, old_id: int, new_id: int) -> (bool, str): - """修改舵机ID(队列版本)""" + """Change servo ID (queued version)""" # 将请求加入队列 self.queue_id_change(old_id, new_id) - return True, "ID修改请求已加入队列" + return True, "ID change request added to queue" def queue_id_change(self, old_id: int, new_id: int): - """将ID修改请求加入队列""" - print(f"[DEBUG] {self.port_id}: ID修改请求入队: {old_id} -> {new_id}") - self.log_message.emit(f"📝 ID修改请求已排队: {old_id} -> {new_id}", self.port_id) + """Add an ID change request to the queue""" + print(f"[DEBUG] {self.port_id}: ID change request queued: {old_id} -> {new_id}") + self.log_message.emit(f"📝 ID change request queued: {old_id} -> {new_id}", self.port_id) self.id_change_queue.put((old_id, new_id, time.time())) # 启动ID修改线程(如果还没启动) @@ -427,16 +427,16 @@ def queue_id_change(self, old_id: int, new_id: int): self.start_id_change_processor() def start_id_change_processor(self): - """启动ID修改处理线程""" + """Start the ID-change processing thread""" if not self.id_change_running: self.id_change_running = True self.id_change_thread = threading.Thread(target=self.process_id_changes, daemon=True) self.id_change_thread.start() - print(f"[DEBUG] {self.port_id}: ID修改处理线程已启动") + print(f"[DEBUG] {self.port_id}: ID-change processing thread started") def process_id_changes(self): - """处理ID修改队列""" - print(f"[DEBUG] {self.port_id}: 开始处理ID修改队列") + """Process the ID-change queue""" + print(f"[DEBUG] {self.port_id}: starting to process ID-change queue") while self.id_change_running or not self.id_change_queue.empty(): try: if not self.id_change_queue.empty(): @@ -444,8 +444,8 @@ def process_id_changes(self): # 暂停扫描,避免总线冲突 self.pause_scanning = True - print(f"[DEBUG] {self.port_id}: 暂停扫描,准备执行ID修改: {old_id} -> {new_id}") - self.log_message.emit(f"⏸️ 暂停扫描,执行ID修改: {old_id} -> {new_id}", self.port_id) + print(f"[DEBUG] {self.port_id}: pausing scan, about to change ID: {old_id} -> {new_id}") + self.log_message.emit(f"⏸️ Pausing scan, changing ID: {old_id} -> {new_id}", self.port_id) # 等待一下确保扫描完全停止 time.sleep(0.5) @@ -455,8 +455,8 @@ def process_id_changes(self): # 恢复扫描 self.pause_scanning = False - print(f"[DEBUG] {self.port_id}: 恢复扫描") - self.log_message.emit(f"▶️ 恢复扫描", self.port_id) + print(f"[DEBUG] {self.port_id}: resuming scan") + self.log_message.emit(f"▶️ Resuming scan", self.port_id) # 发送结果 self.id_changed.emit(old_id, new_id, success, message, self.port_id) @@ -465,80 +465,80 @@ def process_id_changes(self): time.sleep(0.1) # 短暂休眠避免CPU占用 except Exception as e: - print(f"[DEBUG] {self.port_id}: ID修改处理异常: {e}") - self.log_message.emit(f"❌ ID修改处理异常: {e}", self.port_id) + print(f"[DEBUG] {self.port_id}: exception processing ID change: {e}") + self.log_message.emit(f"❌ Exception processing ID change: {e}", self.port_id) # 确保扫描被恢复 self.pause_scanning = False - print(f"[DEBUG] {self.port_id}: ID修改处理线程结束") + print(f"[DEBUG] {self.port_id}: ID-change processing thread ended") self.id_change_running = False self.pause_scanning = False def execute_id_change(self, old_id: int, new_id: int) -> (bool, str): - """执行实际的ID修改操作""" + """Perform the actual ID-change operation""" try: if not self.is_connected: - return False, "未连接舵机控制器" + return False, "Servo controller not connected" - self.log_message.emit(f"🔧 执行SMS_STS ID修改: {old_id} -> {new_id}", self.port_id) - print(f"[DEBUG] {self.port_id}: 执行ID修改: {old_id} -> {new_id}") + self.log_message.emit(f"🔧 Performing SMS_STS ID change: {old_id} -> {new_id}", self.port_id) + print(f"[DEBUG] {self.port_id}: performing ID change: {old_id} -> {new_id}") # 首先读取舵机信息(此时扫描已暂停,不会冲突) try: model_number, result, error = self.servo_handler.ping(old_id) if result == COMM_SUCCESS: - print(f"[DEBUG] {self.port_id}: SMS_STS 舵机型号: {model_number}") - self.log_message.emit(f"📋 舵机型号: {model_number}", self.port_id) + print(f"[DEBUG] {self.port_id}: SMS_STS servo model: {model_number}") + self.log_message.emit(f"📋 Servo model: {model_number}", self.port_id) else: - print(f"[DEBUG] {self.port_id}: 无法读取舵机信息: {error}") - return False, f"无法读取舵机信息: {error}" + print(f"[DEBUG] {self.port_id}: can't read servo info: {error}") + return False, f"Can't read servo info: {error}" except Exception as e: - return False, f"读取舵机信息异常: {e}" + return False, f"Exception reading servo info: {e}" # SMS_STS EEPROM解锁流程 - print(f"[DEBUG] {self.port_id}: SMS_STS 解锁EEPROM...") + print(f"[DEBUG] {self.port_id}: SMS_STS unlocking EEPROM...") result, error = self.servo_handler.unLockEprom(old_id) if result != COMM_SUCCESS: - print(f"[DEBUG] {self.port_id}: EEPROM解锁失败: result={result}, error={error}") - return False, f"EEPROM解锁失败: {error}" + print(f"[DEBUG] {self.port_id}: EEPROM unlock failed: result={result}, error={error}") + return False, f"EEPROM unlock failed: {error}" - print(f"[DEBUG] {self.port_id}: EEPROM解锁成功") + print(f"[DEBUG] {self.port_id}: EEPROM unlocked successfully") time.sleep(0.1) # 修改ID (使用SMS_STS_ID地址) - print(f"[DEBUG] {self.port_id}: 写入新ID: {new_id}") + print(f"[DEBUG] {self.port_id}: writing new ID: {new_id}") result, error = self.servo_handler.write1ByteTxRx(old_id, 5, new_id) # SMS_STS_ID = 5 if result != COMM_SUCCESS: - print(f"[DEBUG] {self.port_id}: ID写入失败: result={result}, error={error}") - return False, f"ID写入失败: {error}" + print(f"[DEBUG] {self.port_id}: ID write failed: result={result}, error={error}") + return False, f"ID write failed: {error}" - print(f"[DEBUG] {self.port_id}: ID写入成功") + print(f"[DEBUG] {self.port_id}: ID written successfully") time.sleep(0.3) # 验证新ID(此时扫描仍暂停,ping不会冲突) - print(f"[DEBUG] {self.port_id}: 验证新ID: {new_id}") + print(f"[DEBUG] {self.port_id}: verifying new ID: {new_id}") if not self.ping_servo(new_id): - print(f"[DEBUG] {self.port_id}: 新ID验证失败") - return False, f"验证失败,无法ping通新ID: {new_id}" + print(f"[DEBUG] {self.port_id}: new ID verification failed") + return False, f"Verification failed, can't ping new ID: {new_id}" - print(f"[DEBUG] {self.port_id}: 新ID验证成功") + print(f"[DEBUG] {self.port_id}: new ID verified successfully") # 重新锁定EEPROM - print(f"[DEBUG] {self.port_id}: 重新锁定EEPROM...") + print(f"[DEBUG] {self.port_id}: re-locking EEPROM...") result, error = self.servo_handler.LockEprom(new_id) if result != COMM_SUCCESS: - print(f"[DEBUG] {self.port_id}: 重新锁定失败: {error}") - self.log_message.emit(f"⚠️ 重新锁定EEPROM失败: {error}", self.port_id) + print(f"[DEBUG] {self.port_id}: re-lock failed: {error}") + self.log_message.emit(f"⚠️ Failed to re-lock EEPROM: {error}", self.port_id) else: - print(f"[DEBUG] {self.port_id}: 重新锁定成功") + print(f"[DEBUG] {self.port_id}: re-locked successfully") - self.log_message.emit(f"✅ SMS_STS ID修改成功: {old_id} -> {new_id}", self.port_id) - print(f"[DEBUG] {self.port_id}: ID修改完成: {old_id} -> {new_id}") + self.log_message.emit(f"✅ SMS_STS ID change successful: {old_id} -> {new_id}", self.port_id) + print(f"[DEBUG] {self.port_id}: ID change complete: {old_id} -> {new_id}") return True, "" except Exception as e: - error_msg = f"修改ID异常: {e}" - print(f"[DEBUG] {self.port_id}: 修改ID异常: {e}") + error_msg = f"Exception changing ID: {e}" + print(f"[DEBUG] {self.port_id}: exception changing ID: {e}") self.log_message.emit(f"❌ {error_msg}", self.port_id) return False, error_msg @@ -546,24 +546,24 @@ def execute_id_change(self, old_id: int, new_id: int) -> (bool, str): # 系统命令队列:寄存器读写、波特率修改、恢复出厂设置 # ------------------------------------------------------------------ def queue_system_command(self, command: dict): - """将系统命令加入队列""" - print(f"[DEBUG] {self.port_id}: 系统命令入队: {command}") + """Add a system command to the queue""" + print(f"[DEBUG] {self.port_id}: system command queued: {command}") self.system_command_queue.put(command) - self.log_message.emit(f"📝 {command.get('desc', '系统命令')} 已排队", self.port_id) + self.log_message.emit(f"📝 {command.get('desc', 'System command')} queued", self.port_id) if not self.system_command_running: self.start_system_command_processor() def start_system_command_processor(self): - """启动系统命令处理线程""" + """Start the system-command processing thread""" if not self.system_command_running: self.system_command_running = True self.system_command_thread = threading.Thread(target=self.process_system_commands, daemon=True) self.system_command_thread.start() - print(f"[DEBUG] {self.port_id}: 系统命令处理线程已启动") + print(f"[DEBUG] {self.port_id}: system-command processing thread started") def process_system_commands(self): - """处理系统命令队列""" - print(f"[DEBUG] {self.port_id}: 开始处理系统命令队列") + """Process the system-command queue""" + print(f"[DEBUG] {self.port_id}: starting to process system-command queue") while self.system_command_running or not self.system_command_queue.empty(): try: if not self.system_command_queue.empty(): @@ -572,11 +572,11 @@ def process_system_commands(self): # 暂停扫描,避免总线冲突 self.pause_scanning = True - self.log_message.emit(f"⏸️ 暂停扫描,执行: {command.get('desc', cmd_type)}", self.port_id) + self.log_message.emit(f"⏸️ Pausing scan, running: {command.get('desc', cmd_type)}", self.port_id) time.sleep(0.3) success = False - message = "未知命令" + message = "Unknown command" extra = None try: @@ -600,12 +600,12 @@ def process_system_commands(self): success, message = self.execute_clear_angle_limits(command["servo_id"]) except Exception as e: success = False - message = f"执行异常: {e}" - print(f"[DEBUG] {self.port_id}: 系统命令执行异常: {e}") + message = f"Execution exception: {e}" + print(f"[DEBUG] {self.port_id}: exception executing system command: {e}") # 恢复扫描 self.pause_scanning = False - self.log_message.emit(f"▶️ 恢复扫描", self.port_id) + self.log_message.emit(f"▶️ Resuming scan", self.port_id) # 发送结果 if cmd_type == "register_read" and extra is not None: @@ -620,20 +620,20 @@ def process_system_commands(self): time.sleep(0.1) except Exception as e: - print(f"[DEBUG] {self.port_id}: 系统命令处理异常: {e}") - self.log_message.emit(f"❌ 系统命令处理异常: {e}", self.port_id) + print(f"[DEBUG] {self.port_id}: exception processing system command: {e}") + self.log_message.emit(f"❌ Exception processing system command: {e}", self.port_id) self.pause_scanning = False - print(f"[DEBUG] {self.port_id}: 系统命令处理线程结束") + print(f"[DEBUG] {self.port_id}: system-command processing thread ended") self.system_command_running = False self.pause_scanning = False def execute_register_read(self, servo_id: int, address: int, length: int) -> (bool, str, int): - """执行寄存器读取""" + """Perform a register read""" if not self.is_connected: - return False, "未连接舵机控制器", None + return False, "Servo controller not connected", None - self.log_message.emit(f"🔍 读取 ID{servo_id} 寄存器 0x{address:02X} ({length}字节)", self.port_id) + self.log_message.emit(f"🔍 Reading ID{servo_id} register 0x{address:02X} ({length} bytes)", self.port_id) try: if length == 1: value, result, error = self.servo_handler.read1ByteTxRx(servo_id, address) @@ -642,22 +642,22 @@ def execute_register_read(self, servo_id: int, address: int, length: int) -> (bo elif length == 4: value, result, error = self.servo_handler.read4ByteTxRx(servo_id, address) else: - return False, "不支持的长度(仅支持1/2/4字节)", None + return False, "Unsupported length (only 1/2/4 bytes supported)", None if result == COMM_SUCCESS: - self.log_message.emit(f"✅ ID{servo_id} 寄存器 0x{address:02X} = {value} (0x{value:X})", self.port_id) - return True, "读取成功", value + self.log_message.emit(f"✅ ID{servo_id} register 0x{address:02X} = {value} (0x{value:X})", self.port_id) + return True, "Read successful", value else: - return False, f"读取失败: result={result}, error={error}", None + return False, f"Read failed: result={result}, error={error}", None except Exception as e: - return False, f"读取异常: {e}", None + return False, f"Read exception: {e}", None def execute_register_write(self, servo_id: int, address: int, length: int, value: int) -> (bool, str): - """执行寄存器写入""" + """Perform a register write""" if not self.is_connected: - return False, "未连接舵机控制器" + return False, "Servo controller not connected" - self.log_message.emit(f"✏️ 写入 ID{servo_id} 寄存器 0x{address:02X} = {value} ({length}字节)", self.port_id) + self.log_message.emit(f"✏️ Writing ID{servo_id} register 0x{address:02X} = {value} ({length} bytes)", self.port_id) try: if length == 1: result, error = self.servo_handler.write1ByteTxRx(servo_id, address, value) @@ -666,20 +666,20 @@ def execute_register_write(self, servo_id: int, address: int, length: int, value elif length == 4: result, error = self.servo_handler.write4ByteTxRx(servo_id, address, value) else: - return False, "不支持的长度(仅支持1/2/4字节)" + return False, "Unsupported length (only 1/2/4 bytes supported)" if result == COMM_SUCCESS: - self.log_message.emit(f"✅ ID{servo_id} 寄存器 0x{address:02X} 写入成功", self.port_id) - return True, "写入成功" + self.log_message.emit(f"✅ ID{servo_id} register 0x{address:02X} written successfully", self.port_id) + return True, "Write successful" else: - return False, f"写入失败: result={result}, error={error}" + return False, f"Write failed: result={result}, error={error}" except Exception as e: - return False, f"写入异常: {e}" + return False, f"Write exception: {e}" def execute_baud_rate_change(self, servo_id: int, new_baud_rate: int) -> (bool, str): - """执行波特率修改""" + """Perform a baud rate change""" if not self.is_connected: - return False, "未连接舵机控制器" + return False, "Servo controller not connected" # 波特率值 -> 寄存器值映射 baud_to_reg = { @@ -695,87 +695,87 @@ def execute_baud_rate_change(self, servo_id: int, new_baud_rate: int) -> (bool, reg_to_baud = {v: k for k, v in baud_to_reg.items()} if new_baud_rate not in baud_to_reg: - return False, f"不支持的波特率: {new_baud_rate}" + return False, f"Unsupported baud rate: {new_baud_rate}" reg_value = baud_to_reg[new_baud_rate] old_baud_rate = self.baud_rate self.log_message.emit( - f"🔧 修改 ID{servo_id} 波特率: {old_baud_rate} -> {new_baud_rate}", self.port_id + f"🔧 Changing ID{servo_id} baud rate: {old_baud_rate} -> {new_baud_rate}", self.port_id ) try: # 解锁 EEPROM result, error = self.servo_handler.unLockEprom(servo_id) if result != COMM_SUCCESS: - return False, f"EEPROM解锁失败: {error}" + return False, f"EEPROM unlock failed: {error}" # 写入新波特率(地址 6) result, error = self.servo_handler.write1ByteTxRx(servo_id, 6, reg_value) if result != COMM_SUCCESS: self.servo_handler.LockEprom(servo_id) - return False, f"波特率写入失败: {error}" + return False, f"Baud rate write failed: {error}" # 锁定 EEPROM self.servo_handler.LockEprom(servo_id) time.sleep(0.2) # 尝试切换到新波特率 - self.log_message.emit(f"🔄 串口切换到 {new_baud_rate} bps...", self.port_id) + self.log_message.emit(f"🔄 Switching serial port to {new_baud_rate} bps...", self.port_id) self.port_handler.setBaudRate(new_baud_rate) self.baud_rate = new_baud_rate time.sleep(0.3) # 验证通信 if self.ping_servo(servo_id): - self.log_message.emit(f"✅ 波特率修改成功,当前 {new_baud_rate} bps", self.port_id) - return True, f"波特率已修改为 {new_baud_rate} bps" + self.log_message.emit(f"✅ Baud rate changed successfully, now {new_baud_rate} bps", self.port_id) + return True, f"Baud rate changed to {new_baud_rate} bps" else: # 切换失败,尝试恢复旧波特率 - self.log_message.emit(f"⚠️ 新波特率验证失败,尝试恢复 {old_baud_rate} bps", self.port_id) + self.log_message.emit(f"⚠️ New baud rate verification failed, trying to restore {old_baud_rate} bps", self.port_id) self.port_handler.setBaudRate(old_baud_rate) self.baud_rate = old_baud_rate time.sleep(0.3) if self.ping_servo(servo_id): - return False, f"新波特率验证失败,已恢复 {old_baud_rate} bps" + return False, f"New baud rate verification failed, restored to {old_baud_rate} bps" else: - return False, f"严重:波特率修改失败且旧波特率也丢失了,请重新连接" + return False, f"Critical: baud rate change failed and the old baud rate was also lost, please reconnect" except Exception as e: - return False, f"波特率修改异常: {e}" + return False, f"Exception changing baud rate: {e}" def execute_factory_reset(self, servo_id: int) -> (bool, str): - """执行恢复出厂设置""" + """Perform a factory reset""" if not self.is_connected: - return False, "未连接舵机控制器" + return False, "Servo controller not connected" - self.log_message.emit(f"🔄 恢复 ID{servo_id} 出厂设置...", self.port_id) + self.log_message.emit(f"🔄 Restoring ID{servo_id} factory settings...", self.port_id) try: result, error = self.servo_handler.reSet(servo_id) if result == COMM_SUCCESS: self.log_message.emit( - f"✅ ID{servo_id} 已恢复出厂设置(ID 将变回 1,波特率变回 1000000)", + f"✅ ID{servo_id} restored to factory settings (ID will revert to 1, baud rate to 1000000)", self.port_id ) - return True, "恢复出厂设置成功,请重新扫描(舵机ID已变为1)" + return True, "Factory reset successful, please rescan (servo ID is now 1)" else: - return False, f"恢复出厂设置失败: result={result}, error={error}" + return False, f"Factory reset failed: result={result}, error={error}" except Exception as e: - return False, f"恢复出厂设置异常: {e}" + return False, f"Exception during factory reset: {e}" def execute_set_middle_calibration(self, servo_id: int) -> (bool, str): - """将当前位置设为指定舵机的中位值(2048)""" + """Set the given servo's current position as its mid-point value (2048)""" if not self.is_connected: - return False, "未连接舵机控制器" + return False, "Servo controller not connected" if servo_id not in self.current_servos: - return False, f"ID{servo_id} 已离线,无法设置中位" + return False, f"ID{servo_id} is offline, can't set mid-point" - self.log_message.emit(f"🔧 设置 ID{servo_id} 中位校准...", self.port_id) + self.log_message.emit(f"🔧 Setting ID{servo_id} mid-point calibration...", self.port_id) try: # 解锁 EEPROM result, error = self.servo_handler.unLockEprom(servo_id) if result != COMM_SUCCESS: - return False, f"EEPROM解锁失败: {error}" + return False, f"EEPROM unlock failed: {error}" time.sleep(0.1) # 发送中位校准命令:写 128 到地址 40 @@ -784,31 +784,31 @@ def execute_set_middle_calibration(self, servo_id: int) -> (bool, str): ) if result != COMM_SUCCESS: self.servo_handler.LockEprom(servo_id) - return False, f"中位校准命令失败: {error}" + return False, f"Mid-point calibration command failed: {error}" time.sleep(0.1) # 重新锁定 EEPROM result, error = self.servo_handler.LockEprom(servo_id) if result != COMM_SUCCESS: - self.log_message.emit(f"⚠️ ID{servo_id} EEPROM重新锁定失败: {error}", self.port_id) + self.log_message.emit(f"⚠️ ID{servo_id} failed to re-lock EEPROM: {error}", self.port_id) - return True, f"ID{servo_id} 中位校准成功(当前位置已设为 2048)" + return True, f"ID{servo_id} mid-point calibration successful (current position set to 2048)" except Exception as e: - return False, f"中位校准异常: {e}" + return False, f"Exception during mid-point calibration: {e}" def execute_clear_angle_limits(self, servo_id: int) -> (bool, str): - """清除指定舵机的最小/最大角度限制(恢复为 0 ~ 4095)""" + """Clear the given servo's min/max angle limits (restore to 0~4095)""" if not self.is_connected: - return False, "未连接舵机控制器" + return False, "Servo controller not connected" if servo_id not in self.current_servos: - return False, f"ID{servo_id} 已离线,无法清除角度限制" + return False, f"ID{servo_id} is offline, can't clear angle limits" - self.log_message.emit(f"🧹 清除 ID{servo_id} 角度限制...", self.port_id) + self.log_message.emit(f"🧹 Clearing ID{servo_id} angle limits...", self.port_id) try: # 解锁 EEPROM result, error = self.servo_handler.unLockEprom(servo_id) if result != COMM_SUCCESS: - return False, f"EEPROM解锁失败: {error}" + return False, f"EEPROM unlock failed: {error}" time.sleep(0.1) # 写入最小角度限制为 0 @@ -817,7 +817,7 @@ def execute_clear_angle_limits(self, servo_id: int) -> (bool, str): ) if result != COMM_SUCCESS: self.servo_handler.LockEprom(servo_id) - return False, f"清除最小角度限制失败: {error}" + return False, f"Failed to clear min angle limit: {error}" time.sleep(0.05) # 写入最大角度限制为 4095 @@ -826,30 +826,30 @@ def execute_clear_angle_limits(self, servo_id: int) -> (bool, str): ) if result != COMM_SUCCESS: self.servo_handler.LockEprom(servo_id) - return False, f"清除最大角度限制失败: {error}" + return False, f"Failed to clear max angle limit: {error}" time.sleep(0.05) # 重新锁定 EEPROM result, error = self.servo_handler.LockEprom(servo_id) if result != COMM_SUCCESS: - self.log_message.emit(f"⚠️ ID{servo_id} EEPROM重新锁定失败: {error}", self.port_id) + self.log_message.emit(f"⚠️ ID{servo_id} failed to re-lock EEPROM: {error}", self.port_id) - return True, f"ID{servo_id} 角度限制已清除(MIN=0, MAX=4095)" + return True, f"ID{servo_id} angle limits cleared (MIN=0, MAX=4095)" except Exception as e: - return False, f"清除角度限制异常: {e}" + return False, f"Exception clearing angle limits: {e}" def set_servo_torque(self, servo_id: int, enable: bool): - """设置单个舵机力矩(加入队列,由扫描线程串行执行)""" + """Set a single servo's torque (queued, executed serially by the scan thread)""" self.torque_queue.put((servo_id, enable)) self.command_event.set() def set_servo_position(self, servo_id: int, position: int): - """设置单个舵机目标位置(加入队列,由扫描线程串行执行)""" + """Set a single servo's target position (queued, executed serially by the scan thread)""" self.position_queue.put((servo_id, position)) self.command_event.set() def _process_commands(self): - """处理力矩和位置控制命令""" + """Process torque and position control commands""" if self.servo_handler is None or not self.is_connected: # 清空队列,避免积压 while not self.torque_queue.empty(): @@ -872,12 +872,12 @@ def _process_commands(self): result, error = self.servo_handler.write1ByteTxRx(servo_id, 40, value) if result == COMM_SUCCESS: self.log_message.emit( - f"{'⚡' if enable else '⏹️'} ID{servo_id} 力矩{'开启' if enable else '关闭'}", self.port_id + f"{'⚡' if enable else '⏹️'} ID{servo_id} torque {'On' if enable else 'Off'}", self.port_id ) else: - self.log_message.emit(f"❌ ID{servo_id} 力矩设置失败", self.port_id) + self.log_message.emit(f"❌ ID{servo_id} torque setting failed", self.port_id) except Exception as e: - self.log_message.emit(f"❌ 力矩命令异常: {e}", self.port_id) + self.log_message.emit(f"❌ Torque command exception: {e}", self.port_id) # 处理位置命令 while not self.position_queue.empty(): @@ -889,12 +889,12 @@ def _process_commands(self): if result == COMM_SUCCESS: self.log_message.emit(f"🎯 ID{servo_id} -> {position}", self.port_id) else: - self.log_message.emit(f"❌ ID{servo_id} 位置写入失败", self.port_id) + self.log_message.emit(f"❌ ID{servo_id} position write failed", self.port_id) except Exception as e: - self.log_message.emit(f"❌ 位置命令异常: {e}", self.port_id) + self.log_message.emit(f"❌ Position command exception: {e}", self.port_id) def _read_servo_info(self) -> dict: - """读取当前在线舵机的完整状态信息""" + """Read full status info for all servos currently online""" info = {} if self.servo_handler is None or not self.is_connected: return info @@ -958,7 +958,7 @@ def _read_servo_info(self) -> dict: return info def _parse_servo_status(self, status: int) -> dict: - """解析舵机状态寄存器中的保护标志""" + """Parse the protection flags in the servo status register""" return { "overload": bool(status & ERRBIT_OVERLOAD), "over_current": bool(status & ERRBIT_OVERELE), @@ -967,7 +967,7 @@ def _parse_servo_status(self, status: int) -> dict: } def _read_positions(self) -> dict: - """读取当前在线舵机的位置(兼容旧信号)""" + """Read current position of online servos (legacy-signal compatible)""" positions = {} info = self._read_servo_info() for servo_id, servo_info in info.items(): @@ -976,7 +976,7 @@ def _read_positions(self) -> dict: return positions def run_scanner(self): - """运行扫描循环""" + """Run the scan loop""" scan_count = 0 self.running = True consecutive_failures = 0 @@ -984,7 +984,7 @@ def run_scanner(self): max_failures = 3 max_empty_scans = 5 # 连续空扫描阈值,超过则强制重新连接(处理热插拔) - self.log_message.emit("🚀 扫描线程启动", self.port_id) + self.log_message.emit("🚀 Scan thread started", self.port_id) print(f"[DEBUG] {self.port_id}: Scanner thread started") # 首次连接 @@ -1005,7 +1005,7 @@ def run_scanner(self): # 如果未连接,尝试重新连接 if not self.is_connected: if consecutive_failures < max_failures: - self.log_message.emit(f"🔄 尝试重新连接... (第{consecutive_failures + 1}次)", self.port_id) + self.log_message.emit(f"🔄 Trying to reconnect... (attempt {consecutive_failures + 1})", self.port_id) time.sleep(2) # 等待2秒再重试 # stop() 期间禁止重连,否则会重新打开串口导致端口泄漏 if not self.running: @@ -1017,7 +1017,7 @@ def run_scanner(self): continue else: # 失败次数过多,延长等待时间 - self.log_message.emit(f"⚠️ 连续失败{max_failures}次,等待10秒后重试...", self.port_id) + self.log_message.emit(f"⚠️ Failed {max_failures} times in a row, retrying in 10s...", self.port_id) time.sleep(10) consecutive_failures = 0 # 重置计数 if not self.running: @@ -1026,7 +1026,7 @@ def run_scanner(self): # 检查是否暂停扫描(ID修改期间) if self.pause_scanning: - print(f"[DEBUG] {self.port_id}: 扫描已暂停(ID修改中)") + print(f"[DEBUG] {self.port_id}: scan paused (ID change in progress)") time.sleep(0.5) # 短暂休眠,减少CPU占用 continue @@ -1034,7 +1034,7 @@ def run_scanner(self): is_rescan = self.rescan_requested.is_set() if is_rescan: self.rescan_requested.clear() - self.log_message.emit("🔄 执行手动重新扫描...", self.port_id) + self.log_message.emit("🔄 Running manual rescan...", self.port_id) # 扫描舵机 new_servos = self.scan_servos() @@ -1050,7 +1050,7 @@ def run_scanner(self): consecutive_empty_scans += 1 if consecutive_empty_scans >= max_empty_scans: self.log_message.emit( - f"⚠️ 连续 {max_empty_scans} 次未扫描到舵机,判断为串口已断开,尝试重新连接...", + f"⚠️ No servos found for {max_empty_scans} scans in a row, assuming the port disconnected, trying to reconnect...", self.port_id ) self.is_connected = False @@ -1070,19 +1070,19 @@ def run_scanner(self): if new_servos: if not old_servos: - self.log_message.emit(f"📡 发现舵机: {new_servos}", self.port_id) + self.log_message.emit(f"📡 Found servos: {new_servos}", self.port_id) else: added = set(new_servos) - set(old_servos) removed = set(old_servos) - set(new_servos) changes = [] if added: - changes.append(f"新增: {list(added)}") + changes.append(f"Added: {list(added)}") if removed: - changes.append(f"移除: {list(removed)}") - self.log_message.emit(f"📡 舵机变化: {', '.join(changes)}", self.port_id) + changes.append(f"Removed: {list(removed)}") + self.log_message.emit(f"📡 Servo change: {', '.join(changes)}", self.port_id) else: if old_servos: - self.log_message.emit("📡 所有舵机已断开", self.port_id) + self.log_message.emit("📡 All servos disconnected", self.port_id) print(f"[DEBUG] {self.port_id}: Emitting status_updated: servos={new_servos}, connected={self.is_connected}") self.status_updated.emit(self.current_servos, self.is_connected, self.port_id) @@ -1104,17 +1104,17 @@ def run_scanner(self): # 每30次扫描显示一次状态(减少日志频率) if scan_count % 30 == 0: if self.current_servos: - self.log_message.emit(f"📊 当前舵机ID: {self.current_servos}", self.port_id) + self.log_message.emit(f"📊 Current servo IDs: {self.current_servos}", self.port_id) else: - self.log_message.emit("📊 当前无舵机", self.port_id) + self.log_message.emit("📊 No servos currently", self.port_id) except Exception as e: consecutive_failures += 1 - self.log_message.emit(f"❌ 扫描异常: {e} (失败次数: {consecutive_failures})", self.port_id) + self.log_message.emit(f"❌ Scan exception: {e} (failure count: {consecutive_failures})", self.port_id) # 连续异常达到阈值时,强制重新打开串口(处理底层 serial 异常未重置 is_connected 的情况) if consecutive_failures >= max_failures: self.log_message.emit( - f"⚠️ 连续扫描异常 {max_failures} 次,强制重新连接串口...", self.port_id + f"⚠️ Scan exception {max_failures} times in a row, forcing serial port reconnect...", self.port_id ) self.is_connected = False try: @@ -1126,7 +1126,7 @@ def run_scanner(self): time.sleep(1) def start(self): - """启动工作线程""" + """Start the worker thread""" # 旧扫描线程尚未退出时不重复创建,避免两个线程并发操作同一串口 if self._scanner_thread and self._scanner_thread.is_alive(): self.running = True # 让旧线程继续循环 @@ -1136,7 +1136,7 @@ def start(self): self._scanner_thread.start() def stop(self): - """停止工作线程""" + """Stop the worker thread""" self.running = False self.id_change_running = False self.system_command_running = False @@ -1162,9 +1162,9 @@ def stop(self): class ServoPanel(QWidget): - """单个舵机控制面板""" + """Single-servo control panel""" - DISABLED_PORT = "-- 禁用 --" + DISABLED_PORT = "-- Disabled --" def __init__(self, port_name: str, port_id: str): super().__init__() @@ -1177,16 +1177,16 @@ def __init__(self, port_name: str, port_id: str): self.init_connections() self.worker.start() else: - self.title_label.setText(f"🏭 {self.DISABLED_PORT} - 舵机标定") - self.connection_status.setText("⚫ 已禁用") + self.title_label.setText(f"🏭 {self.DISABLED_PORT} - Servo Calibration") + self.connection_status.setText("⚫ Disabled") def init_ui(self): - """初始化界面""" + """Initialize the UI""" layout = QVBoxLayout(self) layout.setSpacing(10) # 标题 - self.title_label = QLabel(f"🏭 {self.port_name} - 舵机标定") + self.title_label = QLabel(f"🏭 {self.port_name} - Servo Calibration") self.title_label.setAlignment(Qt.AlignCenter) self.title_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #2c3e50; margin: 5px;") layout.addWidget(self.title_label) @@ -1259,27 +1259,27 @@ def init_ui(self): self.setObjectName(self.port_id) def create_status_panel(self, layout): - """创建状态面板""" - status_group = QGroupBox("📡 系统状态") + """Create the status panel""" + status_group = QGroupBox("📡 System Status") status_layout = QHBoxLayout() status_group.setLayout(status_layout) # 连接状态 - self.connection_status = QLabel("🔴 未连接") + self.connection_status = QLabel("🔴 Not Connected") self.connection_status.setStyleSheet("font-size: 12px; font-weight: bold;") status_layout.addWidget(self.connection_status) status_layout.addStretch() # 当前舵机 - self.current_servos_label = QLabel("当前舵机: 扫描中...") + self.current_servos_label = QLabel("Current servos: scanning...") self.current_servos_label.setStyleSheet("font-size: 12px;") status_layout.addWidget(self.current_servos_label) status_layout.addSpacing(15) # 重新扫描按钮 - self.rescan_btn = QPushButton("🔄 重新扫描") + self.rescan_btn = QPushButton("🔄 Rescan") self.rescan_btn.setStyleSheet(""" QPushButton { background-color: #17a2b8; @@ -1300,15 +1300,15 @@ def create_status_panel(self, layout): background-color: #6c757d; } """) - self.rescan_btn.setToolTip("立即重新扫描舵机") + self.rescan_btn.setToolTip("Rescan servos immediately") self.rescan_btn.clicked.connect(self.request_rescan) status_layout.addWidget(self.rescan_btn) layout.addWidget(status_group) def create_servo_panel(self, layout): - """创建舵机状态面板""" - servo_group = QGroupBox("📡 舵机状态") + """Create the servo status panel""" + servo_group = QGroupBox("📡 Servo Status") servo_layout = QVBoxLayout() servo_group.setLayout(servo_layout) @@ -1318,28 +1318,28 @@ def create_servo_panel(self, layout): # 左侧:发现的舵机 found_layout = QVBoxLayout() - found_label = QLabel("✅ 发现的舵机") + found_label = QLabel("✅ Servos Found") found_label.setStyleSheet("font-weight: bold; color: #28a745; font-size: 12px;") found_layout.addWidget(found_label) self.servo_list_found = QTextEdit() self.servo_list_found.setReadOnly(True) self.servo_list_found.setMaximumHeight(150) - self.servo_list_found.setPlainText("正在扫描舵机...") + self.servo_list_found.setPlainText("Scanning servos...") found_layout.addWidget(self.servo_list_found) lists_layout.addLayout(found_layout) # 右侧:未识别ID missing_layout = QVBoxLayout() - missing_label = QLabel("⚠️ 未识别ID") + missing_label = QLabel("⚠️ Unrecognized ID") missing_label.setStyleSheet("font-weight: bold; color: #dc3545; font-size: 12px;") missing_layout.addWidget(missing_label) self.servo_list_missing = QTextEdit() self.servo_list_missing.setReadOnly(True) self.servo_list_missing.setMaximumHeight(150) - self.servo_list_missing.setPlainText("正在扫描舵机...") + self.servo_list_missing.setPlainText("Scanning servos...") missing_layout.addWidget(self.servo_list_missing) lists_layout.addLayout(missing_layout) @@ -1348,13 +1348,13 @@ def create_servo_panel(self, layout): layout.addWidget(servo_group) def create_calibration_panel(self, layout): - """创建标定面板""" - calibration_group = QGroupBox("🎯 ID标定") + """Create the calibration panel""" + calibration_group = QGroupBox("🎯 ID Calibration") calibration_layout = QVBoxLayout() calibration_group.setLayout(calibration_layout) # 说明文字 - info_label = QLabel("📋 点击目标ID执行修改\n⏸️ 自动暂停扫描确保成功") + info_label = QLabel("📋 Click a target ID to change it\n⏸️ Scanning pauses automatically to ensure success") info_label.setStyleSheet("background-color: #e3f2fd; border: 1px solid #bbdefb; padding: 8px; border-radius: 4px; color: #1565c0; font-size: 11px;") calibration_layout.addWidget(info_label) @@ -1380,20 +1380,20 @@ def create_calibration_panel(self, layout): layout.addWidget(calibration_group) def create_servo_control_widget(self): - """创建单舵机滑动条控制面板,返回一个可复用的 QWidget""" + """Create the single-servo slider control panel; returns a reusable QWidget""" container = QWidget() layout = QVBoxLayout(container) layout.setSpacing(10) layout.setContentsMargins(5, 5, 5, 5) - control_group = QGroupBox(f"🎚️ 单舵机控制 - {self.port_name}") + control_group = QGroupBox(f"🎚️ Single Servo Control - {self.port_name}") control_layout = QVBoxLayout() control_group.setLayout(control_layout) # 全局力矩按钮 global_btn_layout = QHBoxLayout() - self.enable_all_torque_btn = QPushButton("⚡ 开启所有力矩") + self.enable_all_torque_btn = QPushButton("⚡ Enable All Torque") self.enable_all_torque_btn.setStyleSheet(""" QPushButton { background-color: #28a745; @@ -1410,7 +1410,7 @@ def create_servo_control_widget(self): self.enable_all_torque_btn.clicked.connect(self.enable_all_torque) global_btn_layout.addWidget(self.enable_all_torque_btn) - self.disable_all_torque_btn = QPushButton("⏹️ 关闭所有力矩") + self.disable_all_torque_btn = QPushButton("⏹️ Disable All Torque") self.disable_all_torque_btn.setStyleSheet(""" QPushButton { background-color: #dc3545; @@ -1508,7 +1508,7 @@ def create_servo_control_widget(self): self.servo_target_labels[servo_id] = target_label row_layout.addWidget(target_label) - torque_btn = QPushButton("⚡ 力矩") + torque_btn = QPushButton("⚡ Torque") torque_btn.setCheckable(True) torque_btn.setChecked(False) torque_btn.setEnabled(False) @@ -1537,7 +1537,7 @@ def create_servo_control_widget(self): row_layout.addWidget(torque_btn) # 中位校准按钮(单独设置该舵机中位值) - middle_btn = QPushButton("🎯 中位") + middle_btn = QPushButton("🎯 Mid-Pos") middle_btn.setEnabled(False) middle_btn.setStyleSheet(""" QPushButton { @@ -1553,13 +1553,13 @@ def create_servo_control_widget(self): QPushButton:pressed { background-color: #c95d08; } QPushButton:disabled { background-color: #6c757d; } """) - middle_btn.setToolTip("将当前位置设为该舵机的中位值(2048)") + middle_btn.setToolTip("Set this servo's current position as its mid-point value (2048)") middle_btn.clicked.connect(lambda checked, sid=servo_id: self.on_set_middle_clicked(sid)) self.servo_middle_btns[servo_id] = middle_btn row_layout.addWidget(middle_btn) # 清除角度限制按钮 - clear_limit_btn = QPushButton("🧹 清限位") + clear_limit_btn = QPushButton("🧹 Clear Limits") clear_limit_btn.setEnabled(False) clear_limit_btn.setStyleSheet(""" QPushButton { @@ -1575,7 +1575,7 @@ def create_servo_control_widget(self): QPushButton:pressed { background-color: #4a2785; } QPushButton:disabled { background-color: #6c757d; } """) - clear_limit_btn.setToolTip("清除该舵机的最小/最大角度限制(恢复 0~4095)") + clear_limit_btn.setToolTip("Clear this servo's min/max angle limits (restore to 0~4095)") clear_limit_btn.clicked.connect(lambda checked, sid=servo_id: self.on_clear_limits_clicked(sid)) self.servo_clear_limit_btns[servo_id] = clear_limit_btn row_layout.addWidget(clear_limit_btn) @@ -1624,7 +1624,7 @@ def create_servo_control_widget(self): # 提示文字 tip_label = QLabel( - "💡 拖动滑块并松开后,舵机将移动到目标位置。未识别到的舵机无法操作。" + "💡 Drag the slider and release to move the servo to the target position. Unrecognized servos can't be controlled." ) tip_label.setStyleSheet( "background-color: #fff3cd; border: 1px solid #ffeeba; padding: 6px; " @@ -1637,8 +1637,8 @@ def create_servo_control_widget(self): return container def create_log_panel(self, layout): - """创建日志面板""" - log_group = QGroupBox("📋 操作日志") + """Create the log panel""" + log_group = QGroupBox("📋 Operation Log") log_layout = QVBoxLayout() log_group.setLayout(log_layout) @@ -1646,11 +1646,11 @@ def create_log_panel(self, layout): self.log_text = QTextEdit() self.log_text.setReadOnly(True) self.log_text.setMaximumHeight(120) - self.log_text.setPlainText("系统启动...") + self.log_text.setPlainText("System starting...") log_layout.addWidget(self.log_text) # 清空日志按钮 - clear_btn = QPushButton("清空日志") + clear_btn = QPushButton("Clear Log") clear_btn.setMaximumWidth(80) clear_btn.setStyleSheet("font-size: 11px;") clear_btn.clicked.connect(self.log_text.clear) @@ -1659,7 +1659,7 @@ def create_log_panel(self, layout): layout.addWidget(log_group) def init_connections(self): - """初始化信号连接""" + """Initialize signal connections""" if self.worker is None: return self.worker.status_updated.connect(self.update_status) @@ -1670,32 +1670,32 @@ def init_connections(self): self.worker.system_command_result.connect(self.on_system_command_result) # 添加初始连接日志 - self.add_log("🔄 信号连接已建立", self.port_id) - self.add_log("📡 开始扫描舵机...", self.port_id) + self.add_log("🔄 Signal connections established", self.port_id) + self.add_log("📡 Starting servo scan...", self.port_id) def on_system_command_result(self, cmd_type, success, message, port_id): - """处理系统命令执行结果(中位校准、清除限位等)""" + """Handle system command results (mid-point calibration, clear limits, etc.)""" if port_id != self.port_id: return prefix = "✅" if success else "❌" self.add_log(f"{prefix} {message}", self.port_id) def update_status(self, servos, connected, port_id): - """更新状态显示""" + """Update the status display""" if port_id != self.port_id: return print(f"[DEBUG] {port_id} update_status called: servos={servos}, connected={connected}") if connected: - self.connection_status.setText("🟢 已连接") + self.connection_status.setText("🟢 Connected") self.connection_status.setStyleSheet("color: #28a745; font-size: 12px; font-weight: bold;") else: - self.connection_status.setText("🔴 未连接") + self.connection_status.setText("🔴 Not Connected") self.connection_status.setStyleSheet("color: #dc3545; font-size: 12px; font-weight: bold;") if servos: - self.current_servos_label.setText(f"当前舵机: {', '.join(map(str, servos))}") - found_html = "
".join([f"• 舵机 ID: {servo_id}" for servo_id in servos]) + self.current_servos_label.setText(f"Current servos: {', '.join(map(str, servos))}") + found_html = "
".join([f"• Servo ID: {servo_id}" for servo_id in servos]) self.servo_list_found.setHtml(found_html) # 计算 1-6 号槽位中未识别的ID并显示 @@ -1706,10 +1706,10 @@ def update_status(self, servos, connected, port_id): missing_html = "
".join([f"• ID {servo_id}" for servo_id in missing_ids]) self.servo_list_missing.setHtml(missing_html) else: - self.servo_list_missing.setHtml("✅ 1-6号槽位全部识别") + self.servo_list_missing.setHtml("✅ All slots 1-6 recognized") else: - self.current_servos_label.setText("当前舵机: 无") - self.servo_list_found.setHtml("📡 未发现舵机

请检查:
1. 舵机控制器是否连接
2. 舵机是否通电
3. 串口配置是否正确
") + self.current_servos_label.setText("Current servos: none") + self.servo_list_found.setHtml("📡 No servos found

Please check:
1. Is the servo controller connected?
2. Is the servo powered?
3. Is the serial port configured correctly?
") self.servo_list_missing.setHtml("") # 更新按钮状态 @@ -1719,7 +1719,7 @@ def update_status(self, servos, connected, port_id): self.update_servo_control_state(servos, connected) def update_button_states(self, servos, connected): - """更新按钮状态""" + """Update button states""" has_servos = connected and len(servos) > 0 for i, btn in enumerate(self.id_buttons): @@ -1758,7 +1758,7 @@ def update_button_states(self, servos, connected): """) def update_servo_control_state(self, servos, connected): - """根据在线舵机更新滑动条和力矩按钮可用状态""" + """Update slider and torque button availability based on online servos""" has_servos = connected and len(servos) > 0 servo_set = set(servos) if servos else set() @@ -1791,10 +1791,10 @@ def update_servo_control_state(self, servos, connected): self.servo_voltage_labels[servo_id].setToolTip("") self.servo_temp_labels[servo_id].setToolTip("") torque_btn.setChecked(False) - torque_btn.setText("⚡ 力矩") + torque_btn.setText("⚡ Torque") def update_positions(self, positions: dict): - """更新各舵机当前位置显示(兼容旧信号)""" + """Update each servo's current position display (legacy-signal compatible)""" if not positions: return @@ -1803,7 +1803,7 @@ def update_positions(self, positions: dict): self.servo_pos_labels[servo_id].setText(f"Pos: {pos}") def update_servo_info(self, info: dict): - """更新各舵机完整状态显示(电压、温度、速度、负载、电流、运行状态、型号等)""" + """Update each servo's full status display (voltage, temperature, speed, load, current, moving state, model, etc.)""" if not info: return @@ -1845,13 +1845,13 @@ def update_servo_info(self, info: dict): if errors: active_errors = [] if errors.get("overload"): - active_errors.append("过载") + active_errors.append("Overload") if errors.get("over_current"): - active_errors.append("过流") + active_errors.append("Overcurrent") if errors.get("over_voltage"): - active_errors.append("过压") + active_errors.append("Overvoltage") if errors.get("over_heat"): - active_errors.append("过热") + active_errors.append("Overheat") if active_errors: status_text = "Status: " + ",".join(active_errors) @@ -1868,28 +1868,28 @@ def update_servo_info(self, info: dict): # Tooltip 显示更详细信息 tooltip_lines = [f"ID: {servo_id}"] if model is not None: - tooltip_lines.append(f"型号: {get_servo_model_name(model)}") + tooltip_lines.append(f"Model: {get_servo_model_name(model)}") if speed is not None: - tooltip_lines.append(f"速度: {speed}") + tooltip_lines.append(f"Speed: {speed}") if load is not None: - tooltip_lines.append(f"负载: {load}") + tooltip_lines.append(f"Load: {load}") if current is not None: - tooltip_lines.append(f"电流: {current_ma:.1f} mA") + tooltip_lines.append(f"Current: {current_ma:.1f} mA") if moving is not None: - tooltip_lines.append(f"运行中: {'是' if moving else '否'}") + tooltip_lines.append(f"Moving: {'Yes' if moving else 'No'}") if errors: tooltip_lines.append("") - tooltip_lines.append("保护状态:") - tooltip_lines.append(f" 过载: {'是' if errors.get('overload') else '否'}") - tooltip_lines.append(f" 过流: {'是' if errors.get('over_current') else '否'}") - tooltip_lines.append(f" 过压: {'是' if errors.get('over_voltage') else '否'}") - tooltip_lines.append(f" 过热: {'是' if errors.get('over_heat') else '否'}") + tooltip_lines.append("Protection Status:") + tooltip_lines.append(f" Overload: {'Yes' if errors.get('overload') else 'No'}") + tooltip_lines.append(f" Overcurrent: {'Yes' if errors.get('over_current') else 'No'}") + tooltip_lines.append(f" Overvoltage: {'Yes' if errors.get('over_voltage') else 'No'}") + tooltip_lines.append(f" Overheat: {'Yes' if errors.get('over_heat') else 'No'}") tooltip_lines.append("") - tooltip_lines.append("保护说明:") - tooltip_lines.append(" 过载: 堵转>80%持续2s后保护") - tooltip_lines.append(" 过流: 电流>2A持续2s后保护") - tooltip_lines.append(" 过压: 电压>8V或<4V保护") - tooltip_lines.append(" 过热: 温度>70℃关闭扭矩") + tooltip_lines.append("Protection Info:") + tooltip_lines.append(" Overload: triggers after >80% stall for 2s") + tooltip_lines.append(" Overcurrent: triggers after >2A for 2s") + tooltip_lines.append(" Overvoltage: triggers at >8V or <4V") + tooltip_lines.append(" Overheat: torque disabled above 70℃") tooltip = "\n".join(tooltip_lines) self.servo_voltage_labels[servo_id].setToolTip(tooltip) self.servo_temp_labels[servo_id].setToolTip(tooltip) @@ -1899,7 +1899,7 @@ def update_servo_info(self, info: dict): self.check_servo_health_ui(info) def check_servo_health_ui(self, info: dict): - """检查舵机健康状态并在日志/状态栏提示""" + """Check servo health and report it in the log/status bar""" warnings = [] for servo_id, servo_info in info.items(): voltage = servo_info.get("voltage") @@ -1910,22 +1910,22 @@ def check_servo_health_ui(self, info: dict): v_min, v_max = get_voltage_range(voltage) if voltage < v_min or voltage > v_max: warnings.append( - f"ID{servo_id} 电压异常: {voltage:.1f}V (安全范围 {v_min:.1f}V~{v_max:.1f}V)" + f"ID{servo_id} voltage abnormal: {voltage:.1f}V (safe range {v_min:.1f}V~{v_max:.1f}V)" ) if temperature is not None and temperature > SAFE_TEMPERATURE_MAX: warnings.append( - f"ID{servo_id} 温度过高: {temperature}°C (建议 < {SAFE_TEMPERATURE_MAX:.0f}°C)" + f"ID{servo_id} temperature too high: {temperature}°C (recommended < {SAFE_TEMPERATURE_MAX:.0f}°C)" ) # 保护状态警告 if errors.get("overload"): - warnings.append(f"ID{servo_id} 过载保护: 堵转>80%持续2s,需重新发位置指令清除") + warnings.append(f"ID{servo_id} overload protection: stall >80% for 2s, send a new position command to clear") if errors.get("over_current"): - warnings.append(f"ID{servo_id} 过流保护: 电流>2A持续2s,需重新发位置指令清除") + warnings.append(f"ID{servo_id} overcurrent protection: current >2A for 2s, send a new position command to clear") if errors.get("over_voltage"): - warnings.append(f"ID{servo_id} 过压保护: 电压>8V或<4V") + warnings.append(f"ID{servo_id} overvoltage protection: voltage >8V or <4V") if errors.get("over_heat"): - warnings.append(f"ID{servo_id} 过热保护: 温度>70℃,已关闭扭矩输出") + warnings.append(f"ID{servo_id} overheat protection: temperature >70℃, torque output disabled") if warnings: # 避免过于频繁提示:同一端口 5 秒内最多提示一次 @@ -1934,21 +1934,21 @@ def check_servo_health_ui(self, info: dict): if now - last_warn > 5: self._last_health_warning = now warning_text = " | ".join(warnings) - self.add_log(f"🚨 健康警告: {warning_text}", self.port_id) + self.add_log(f"🚨 Health warning: {warning_text}", self.port_id) # 如果有父窗口且状态栏可用,也显示在状态栏 main_window = self.window() if main_window and hasattr(main_window, "status_bar"): - main_window.status_bar.showMessage(f"🚨 {self.port_id}端口: {warning_text}", 5000) + main_window.status_bar.showMessage(f"🚨 {self.port_id} port: {warning_text}", 5000) def on_slider_value_changed(self, servo_id: int, value: int): - """滑动条数值变化时更新目标位置显示""" + """Update the target position display when the slider value changes""" if servo_id in self.servo_target_labels: self.servo_target_labels[servo_id].setText(f"T: {value}") def on_slider_released(self, servo_id: int): - """滑动条释放后发送目标位置""" + """Send the target position after the slider is released""" if self.worker is None or not self.worker.is_connected: - QMessageBox.warning(self, "警告", "当前端口未连接,无法发送位置命令") + QMessageBox.warning(self, "Warning", "Current port is not connected, can't send position command") return slider = self.servo_sliders[servo_id] @@ -1959,8 +1959,8 @@ def on_slider_released(self, servo_id: int): if not torque_btn.isChecked(): reply = QMessageBox.question( self, - "力矩未开启", - f"ID{servo_id} 力矩未开启,是否先开启力矩再移动?", + "Torque not enabled", + f"ID{servo_id} torque is not enabled, enable torque before moving?", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes ) @@ -1968,11 +1968,11 @@ def on_slider_released(self, servo_id: int): return torque_btn.setChecked(True) - self.add_log(f"🎚️ ID{servo_id} 目标位置: {target}", self.port_id) + self.add_log(f"🎚️ ID{servo_id} target position: {target}", self.port_id) self.worker.set_servo_position(servo_id, target) def on_torque_toggled(self, servo_id: int, checked: bool): - """单个舵机力矩按钮切换""" + """Toggle a single servo's torque button""" if self.worker is None or not self.worker.is_connected: return @@ -1981,19 +1981,19 @@ def on_torque_toggled(self, servo_id: int, checked: bool): self.worker.set_servo_torque(servo_id, checked) def on_set_middle_clicked(self, servo_id: int): - """设置单个舵机中位值(将当前位置设为 2048)""" + """Set a single servo's mid-point value (set current position to 2048)""" if self.worker is None or not self.worker.is_connected: - QMessageBox.warning(self, "警告", "当前端口未连接") + QMessageBox.warning(self, "Warning", "Current port is not connected") return if servo_id not in self.worker.current_servos: - QMessageBox.warning(self, "警告", f"ID{servo_id} 不在线") + QMessageBox.warning(self, "Warning", f"ID{servo_id} is offline") return reply = QMessageBox.question( self, - "确认设置中位", - f"确定要将 ID{servo_id} 的当前位置设为中位值(2048)吗?\n\n" - f"请确保舵机已处于期望的中位位置。", + "Confirm Set Mid-Point", + f"Set ID{servo_id}'s current position as its mid-point value (2048)?\n\n" + f"Make sure the servo is at the desired mid-point position first.", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) @@ -2003,23 +2003,23 @@ def on_set_middle_clicked(self, servo_id: int): self.worker.queue_system_command({ "type": "set_middle", "servo_id": servo_id, - "desc": f"设置 ID{servo_id} 中位" + "desc": f"Set ID{servo_id} Mid-Point" }) def on_clear_limits_clicked(self, servo_id: int): - """清除单个舵机的最小/最大角度限制""" + """Clear a single servo's min/max angle limits""" if self.worker is None or not self.worker.is_connected: - QMessageBox.warning(self, "警告", "当前端口未连接") + QMessageBox.warning(self, "Warning", "Current port is not connected") return if servo_id not in self.worker.current_servos: - QMessageBox.warning(self, "警告", f"ID{servo_id} 不在线") + QMessageBox.warning(self, "Warning", f"ID{servo_id} is offline") return reply = QMessageBox.question( self, - "确认清除限位", - f"确定要清除 ID{servo_id} 的角度限制吗?\n\n" - f"清除后舵机可在完整范围 0~4095 内运动。", + "Confirm Clear Limits", + f"Clear ID{servo_id}'s angle limits?\n\n" + f"After clearing, the servo can move across the full 0~4095 range.", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) @@ -2029,13 +2029,13 @@ def on_clear_limits_clicked(self, servo_id: int): self.worker.queue_system_command({ "type": "clear_angle_limits", "servo_id": servo_id, - "desc": f"清除 ID{servo_id} 角度限制" + "desc": f"Clear ID{servo_id} Angle Limits" }) def enable_all_torque(self): - """开启所有在线舵机力矩""" + """Enable torque on all online servos""" if self.worker is None or not self.worker.is_connected: - QMessageBox.warning(self, "警告", "当前端口未连接") + QMessageBox.warning(self, "Warning", "Current port is not connected") return for servo_id in self.worker.current_servos: @@ -2044,9 +2044,9 @@ def enable_all_torque(self): self.worker.set_servo_torque(servo_id, True) def disable_all_torque(self): - """关闭所有在线舵机力矩""" + """Disable torque on all online servos""" if self.worker is None or not self.worker.is_connected: - QMessageBox.warning(self, "警告", "当前端口未连接") + QMessageBox.warning(self, "Warning", "Current port is not connected") return for servo_id in self.worker.current_servos: @@ -2055,15 +2055,15 @@ def disable_all_torque(self): self.worker.set_servo_torque(servo_id, False) def change_servo_id(self, slot_index): - """修改舵机ID - 弹出对话框让用户自定义源ID和目标ID""" + """Change servo ID - opens a dialog for the user to set a custom source/target ID""" print(f"[DEBUG {self.port_id}] change_servo_id called for slot {slot_index}") if self.worker is None: print(f"[DEBUG {self.port_id}] worker is None, aborting") - QMessageBox.warning(self, "警告", "当前端口已禁用,无法修改ID") + QMessageBox.warning(self, "Warning", "Current port is disabled, can't change ID") return if not self.worker.current_servos: print(f"[DEBUG {self.port_id}] current_servos empty, aborting") - QMessageBox.warning(self, "警告", "没有可用的舵机进行ID修改") + QMessageBox.warning(self, "Warning", "No servos available for ID change") return # 槽位默认目标ID(按钮上显示的数字) @@ -2099,17 +2099,17 @@ def change_servo_id(self, slot_index): if old_id == new_id: box = QMessageBox(self) - box.setWindowTitle("提示") - box.setText("源ID和目标ID相同,无需修改") + box.setWindowTitle("Notice") + box.setText("Source ID and target ID are the same, no change needed") box.setStyleSheet(msg_box_style) box.exec() return if new_id in self.worker.current_servos and new_id != old_id: box = QMessageBox(self) - box.setWindowTitle("确认覆盖") - box.setText(f"目标ID {new_id} 已存在其他舵机,是否继续?") - box.setInformativeText("继续可能导致总线ID冲突!") + box.setWindowTitle("Confirm Overwrite") + box.setText(f"Target ID {new_id} is already used by another servo, continue anyway?") + box.setInformativeText("Continuing may cause a bus ID conflict!") box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) box.setDefaultButton(QMessageBox.No) box.setStyleSheet(msg_box_style) @@ -2118,16 +2118,16 @@ def change_servo_id(self, slot_index): # 确认对话框 box = QMessageBox(self) - box.setWindowTitle(f"确认修改ID ({self.port_name})") - box.setText(f"确定要将舵机 ID {old_id} 修改为 ID {new_id} 吗?") - box.setInformativeText("系统将自动暂停扫描确保修改成功。") + box.setWindowTitle(f"Confirm ID Change ({self.port_name})") + box.setText(f"Change servo ID {old_id} to ID {new_id}?") + box.setInformativeText("The system will automatically pause scanning to ensure the change succeeds.") box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) box.setDefaultButton(QMessageBox.No) box.setStyleSheet(msg_box_style) reply = box.exec() if reply == QMessageBox.Yes: - self.add_log(f"🎯 提交ID修改请求: {old_id} -> {new_id}", self.port_id) + self.add_log(f"🎯 Submitting ID change request: {old_id} -> {new_id}", self.port_id) # 将请求加入队列(立即返回) success, message = self.worker.change_servo_id(old_id, new_id) @@ -2144,13 +2144,13 @@ def change_servo_id(self, slot_index): self.add_log(f"❌ {message}", self.port_id) def request_rescan(self): - """请求立即重新扫描舵机""" - self.add_log("🔄 手动请求重新扫描...", self.port_id) + """Request an immediate servo rescan""" + self.add_log("🔄 Manual rescan requested...", self.port_id) if self.worker: self.worker.request_rescan() def on_id_changed(self, old_id, new_id, success, message, port_id): - """处理ID修改结果""" + """Handle the ID-change result""" if port_id != self.port_id: return @@ -2160,9 +2160,9 @@ def on_id_changed(self, old_id, new_id, success, message, port_id): return if success: - QMessageBox.information(self, f"修改成功 ({self.port_name})", f"ID修改成功!\n{old_id} -> {new_id}") + QMessageBox.information(self, f"Change Successful ({self.port_name})", f"ID changed successfully!\n{old_id} -> {new_id}") # 强制重新扫描舵机列表 - self.add_log(f"🔄 ID修改成功,重新扫描舵机...", self.port_id) + self.add_log(f"🔄 ID changed successfully, rescanning servos...", self.port_id) # 给舵机一点时间响应新ID time.sleep(0.5) # 更新内部的舵机列表 @@ -2174,13 +2174,13 @@ def on_id_changed(self, old_id, new_id, success, message, port_id): # 手动触发状态更新,刷新按钮显示 self.update_status(self.worker.current_servos, self.worker.is_connected, self.port_id) else: - QMessageBox.critical(self, f"修改失败 ({self.port_name})", f"ID修改失败!\n{message}") - self.add_log(f"❌ 队列中ID修改失败: {old_id} -> {new_id}", self.port_id) + QMessageBox.critical(self, f"Change Failed ({self.port_name})", f"ID change failed!\n{message}") + self.add_log(f"❌ Queued ID change failed: {old_id} -> {new_id}", self.port_id) # 刷新按钮状态 self.update_button_states(self.worker.current_servos, self.worker.is_connected) def add_log(self, message, port_id): - """添加日志消息""" + """Add a log message""" if port_id != self.port_id: return @@ -2205,18 +2205,18 @@ def add_log(self, message, port_id): cursor.removeSelectedText() def update_port_name(self, new_port_name: str): - """更新端口名称和标题""" + """Update the port name and title""" self.port_name = new_port_name - self.title_label.setText(f"🏭 {self.port_name} - 舵机标定") + self.title_label.setText(f"🏭 {self.port_name} - Servo Calibration") def stop(self): - """停止工作线程""" + """Stop the worker thread""" if self.worker: self.worker.stop() class AdvancedToolsPanel(QWidget): - """高级工具面板:寄存器读写、波特率修改、恢复出厂设置""" + """Advanced tools panel: register read/write, baud rate change, factory reset""" def __init__(self, servo_panel: ServoPanel): super().__init__() @@ -2261,17 +2261,17 @@ def init_ui(self): """) # 标题 - title = QLabel(f"🔧 高级工具 - {self.servo_panel.port_name}") + title = QLabel(f"🔧 Advanced Tools - {self.servo_panel.port_name}") title.setStyleSheet("font-size: 16px; font-weight: bold; color: #2c3e50;") title.setAlignment(Qt.AlignCenter) layout.addWidget(title) # 舵机选择 - servo_group = QGroupBox("🎯 目标舵机") + servo_group = QGroupBox("🎯 Target Servo") servo_layout = QHBoxLayout() servo_group.setLayout(servo_layout) - servo_label = QLabel("舵机ID:") + servo_label = QLabel("Servo ID:") servo_label.setStyleSheet("font-size: 12px;") servo_layout.addWidget(servo_label) @@ -2290,7 +2290,7 @@ def init_ui(self): refresh_btn = QPushButton("🔄") refresh_btn.setFixedSize(28, 28) - refresh_btn.setToolTip("刷新舵机列表") + refresh_btn.setToolTip("Refresh Servo List") refresh_btn.clicked.connect(self.refresh_servo_ids) servo_layout.addWidget(refresh_btn) @@ -2298,26 +2298,26 @@ def init_ui(self): layout.addWidget(servo_group) # 寄存器读取 - read_group = QGroupBox("📖 寄存器读取") + read_group = QGroupBox("📖 Register Read") read_layout = QGridLayout() read_group.setLayout(read_layout) - read_layout.addWidget(QLabel("地址:"), 0, 0) + read_layout.addWidget(QLabel("Address:"), 0, 0) self.read_addr_spin = QSpinBox() self.read_addr_spin.setRange(0, 255) self.read_addr_spin.setDisplayIntegerBase(16) self.read_addr_spin.setPrefix("0x") read_layout.addWidget(self.read_addr_spin, 0, 1) - read_layout.addWidget(QLabel("长度:"), 0, 2) + read_layout.addWidget(QLabel("Length:"), 0, 2) self.read_len_combo = QComboBox() - self.read_len_combo.addItems(["1 字节", "2 字节", "4 字节"]) + self.read_len_combo.addItems(["1 byte", "2 bytes", "4 bytes"]) self.read_len_combo.setItemData(0, 1) self.read_len_combo.setItemData(1, 2) self.read_len_combo.setItemData(2, 4) read_layout.addWidget(self.read_len_combo, 0, 3) - self.read_btn = QPushButton("🔍 读取") + self.read_btn = QPushButton("🔍 Read") self.read_btn.setStyleSheet(""" QPushButton { background-color: #17a2b8; color: white; border: none; @@ -2329,39 +2329,39 @@ def init_ui(self): self.read_btn.clicked.connect(self.on_read_register) read_layout.addWidget(self.read_btn, 1, 0, 1, 2) - self.read_result_label = QLabel("结果: --") + self.read_result_label = QLabel("Result: --") self.read_result_label.setStyleSheet("font-family: 'Consolas', monospace; font-size: 12px; color: #495057;") read_layout.addWidget(self.read_result_label, 1, 2, 1, 2) layout.addWidget(read_group) # 寄存器写入 - write_group = QGroupBox("✏️ 寄存器写入") + write_group = QGroupBox("✏️ Register Write") write_layout = QGridLayout() write_group.setLayout(write_layout) - write_layout.addWidget(QLabel("地址:"), 0, 0) + write_layout.addWidget(QLabel("Address:"), 0, 0) self.write_addr_spin = QSpinBox() self.write_addr_spin.setRange(0, 255) self.write_addr_spin.setDisplayIntegerBase(16) self.write_addr_spin.setPrefix("0x") write_layout.addWidget(self.write_addr_spin, 0, 1) - write_layout.addWidget(QLabel("长度:"), 0, 2) + write_layout.addWidget(QLabel("Length:"), 0, 2) self.write_len_combo = QComboBox() - self.write_len_combo.addItems(["1 字节", "2 字节", "4 字节"]) + self.write_len_combo.addItems(["1 byte", "2 bytes", "4 bytes"]) self.write_len_combo.setItemData(0, 1) self.write_len_combo.setItemData(1, 2) self.write_len_combo.setItemData(2, 4) write_layout.addWidget(self.write_len_combo, 0, 3) - write_layout.addWidget(QLabel("数值:"), 1, 0) + write_layout.addWidget(QLabel("Value:"), 1, 0) self.write_value_spin = QSpinBox() self.write_value_spin.setRange(0, 2147483647) self.write_value_spin.setDisplayIntegerBase(10) write_layout.addWidget(self.write_value_spin, 1, 1) - self.write_btn = QPushButton("✏️ 写入") + self.write_btn = QPushButton("✏️ Write") self.write_btn.setStyleSheet(""" QPushButton { background-color: #fd7e14; color: white; border: none; @@ -2376,17 +2376,17 @@ def init_ui(self): layout.addWidget(write_group) # 波特率修改 - baud_group = QGroupBox("🔌 波特率修改") + baud_group = QGroupBox("🔌 Baud Rate Change") baud_layout = QHBoxLayout() baud_group.setLayout(baud_layout) - baud_layout.addWidget(QLabel("新波特率:")) + baud_layout.addWidget(QLabel("New Baud Rate:")) self.baud_combo = QComboBox() for rate in [1000000, 500000, 250000, 128000, 115200, 76800, 57600, 38400]: self.baud_combo.addItem(str(rate), rate) baud_layout.addWidget(self.baud_combo) - self.baud_btn = QPushButton("🔧 修改波特率") + self.baud_btn = QPushButton("🔧 Change Baud Rate") self.baud_btn.setStyleSheet(""" QPushButton { background-color: #6f42c1; color: white; border: none; @@ -2402,16 +2402,16 @@ def init_ui(self): layout.addWidget(baud_group) # 恢复出厂设置 - reset_group = QGroupBox("🔄 恢复出厂设置") + reset_group = QGroupBox("🔄 Factory Reset") reset_layout = QHBoxLayout() reset_group.setLayout(reset_layout) - reset_info = QLabel("⚠️ 将舵机恢复为出厂状态(ID 变回 1,波特率变回 1000000)") + reset_info = QLabel("⚠️ Restore the servo to factory state (ID reverts to 1, baud rate reverts to 1000000)") reset_info.setStyleSheet("color: #856404; font-size: 11px;") reset_info.setWordWrap(True) reset_layout.addWidget(reset_info) - self.reset_btn = QPushButton("🔄 恢复出厂") + self.reset_btn = QPushButton("🔄 Factory Reset") self.reset_btn.setStyleSheet(""" QPushButton { background-color: #dc3545; color: white; border: none; @@ -2426,7 +2426,7 @@ def init_ui(self): layout.addWidget(reset_group) # 操作日志 - log_group = QGroupBox("📋 操作日志") + log_group = QGroupBox("📋 Operation Log") log_layout = QVBoxLayout() log_group.setLayout(log_layout) @@ -2442,7 +2442,7 @@ def init_ui(self): """) log_layout.addWidget(self.adv_log_text) - clear_btn = QPushButton("清空") + clear_btn = QPushButton("Clear") clear_btn.setMaximumWidth(60) clear_btn.clicked.connect(self.adv_log_text.clear) log_layout.addWidget(clear_btn) @@ -2455,7 +2455,7 @@ def init_ui(self): self.update_button_states() def init_connections(self): - """初始化信号连接""" + """Initialize signal connections""" if self.worker is None: return self.worker.register_read_result.connect(self.on_register_read_result) @@ -2463,7 +2463,7 @@ def init_connections(self): self.worker.status_updated.connect(self.on_status_updated) def refresh_servo_ids(self): - """刷新舵机ID列表""" + """Refresh the servo ID list""" self.servo_id_combo.clear() servos = [] if self.worker: @@ -2472,11 +2472,11 @@ def refresh_servo_ids(self): for sid in servos: self.servo_id_combo.addItem(f"ID{sid}", sid) else: - self.servo_id_combo.addItem("无舵机", None) + self.servo_id_combo.addItem("No Servos", None) self.update_button_states() def update_button_states(self): - """根据是否有在线舵机更新按钮状态""" + """Update button state based on whether any servo is online""" has_worker = self.worker is not None and self.worker.is_connected has_servos = has_worker and len(self.worker.current_servos) > 0 enabled = has_servos and self.servo_id_combo.currentData() is not None @@ -2487,40 +2487,40 @@ def update_button_states(self): self.reset_btn.setEnabled(enabled) def get_selected_servo_id(self): - """获取选中的舵机ID""" + """Get the selected servo ID""" return self.servo_id_combo.currentData() def add_log(self, message): - """添加日志""" + """Add a log entry""" timestamp = time.strftime("%H:%M:%S") self.adv_log_text.append(f"[{timestamp}] {message}") scrollbar = self.adv_log_text.verticalScrollBar() scrollbar.setValue(scrollbar.maximum()) def on_read_register(self): - """读取寄存器""" + """Read a register""" servo_id = self.get_selected_servo_id() if servo_id is None or self.worker is None: - QMessageBox.warning(self, "警告", "请先选择舵机") + QMessageBox.warning(self, "Warning", "Please select a servo first") return address = self.read_addr_spin.value() length = self.read_len_combo.currentData() - self.read_result_label.setText("结果: 读取中...") + self.read_result_label.setText("Result: reading...") self.worker.queue_system_command({ "type": "register_read", "servo_id": servo_id, "address": address, "length": length, - "desc": f"读取 ID{servo_id} 寄存器 0x{address:02X}", + "desc": f"Read ID{servo_id} register 0x{address:02X}", }) def on_write_register(self): - """写入寄存器""" + """Write a register""" servo_id = self.get_selected_servo_id() if servo_id is None or self.worker is None: - QMessageBox.warning(self, "警告", "请先选择舵机") + QMessageBox.warning(self, "Warning", "Please select a servo first") return address = self.write_addr_spin.value() @@ -2529,8 +2529,8 @@ def on_write_register(self): reply = QMessageBox.question( self, - "确认写入", - f"确定要写入 ID{servo_id} 寄存器 0x{address:02X} = {value} ({length}字节) 吗?", + "Confirm Write", + f"Write ID{servo_id} register 0x{address:02X} = {value} ({length} bytes)?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) @@ -2543,23 +2543,23 @@ def on_write_register(self): "address": address, "length": length, "value": value, - "desc": f"写入 ID{servo_id} 寄存器 0x{address:02X}", + "desc": f"Write ID{servo_id} register 0x{address:02X}", }) def on_change_baud_rate(self): - """修改波特率""" + """Change baud rate""" servo_id = self.get_selected_servo_id() if servo_id is None or self.worker is None: - QMessageBox.warning(self, "警告", "请先选择舵机") + QMessageBox.warning(self, "Warning", "Please select a servo first") return new_baud = self.baud_combo.currentData() reply = QMessageBox.warning( self, - "警告:修改波特率", - f"修改波特率后,串口将立即切换到 {new_baud} bps。\n" - f"如果失败,工具会尝试恢复原有波特率。\n\n" - f"确定要修改 ID{servo_id} 的波特率吗?", + "Warning: Change Baud Rate", + f"After changing the baud rate, the serial port will switch to {new_baud} bps immediately.\n" + f"If it fails, the tool will try to restore the original baud rate.\n\n" + f"Change ID{servo_id}'s baud rate?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) @@ -2570,25 +2570,25 @@ def on_change_baud_rate(self): "type": "baud_rate_change", "servo_id": servo_id, "baud_rate": new_baud, - "desc": f"修改 ID{servo_id} 波特率为 {new_baud}", + "desc": f"Change ID{servo_id} baud rate to {new_baud}", }) def on_factory_reset(self): - """恢复出厂设置""" + """Factory reset""" servo_id = self.get_selected_servo_id() if servo_id is None or self.worker is None: - QMessageBox.warning(self, "警告", "请先选择舵机") + QMessageBox.warning(self, "Warning", "Please select a servo first") return reply = QMessageBox.critical( self, - "危险:恢复出厂设置", - f"确定要恢复 ID{servo_id} 的出厂设置吗?\n\n" - f"这将导致:\n" - f"• 舵机 ID 变回 1\n" - f"• 波特率变回 1000000\n" - f"• 所有参数恢复默认值\n\n" - f"此操作不可撤销!", + "Danger: Factory Reset", + f"Restore ID{servo_id} to factory settings?\n\n" + f"This will:\n" + f"• Revert the servo ID to 1\n" + f"• Revert the baud rate to 1000000\n" + f"• Restore all parameters to defaults\n\n" + f"This action cannot be undone!", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) @@ -2598,41 +2598,41 @@ def on_factory_reset(self): self.worker.queue_system_command({ "type": "factory_reset", "servo_id": servo_id, - "desc": f"恢复 ID{servo_id} 出厂设置", + "desc": f"Restore ID{servo_id} Factory Settings", }) def on_register_read_result(self, servo_id, address, length, value, result, port_id): - """处理寄存器读取结果""" + """Handle register read result""" if port_id != self.servo_panel.port_id: return - if result == "读取成功": + if result == "Read successful": self.read_result_label.setText( - f"结果: {value} (0x{value:X})" + f"Result: {value} (0x{value:X})" ) self.add_log(f"✅ ID{servo_id} 0x{address:02X} = {value} (0x{value:X})") else: - self.read_result_label.setText(f"结果: {result}") + self.read_result_label.setText(f"Result: {result}") self.add_log(f"❌ ID{servo_id} 0x{address:02X} {result}") def on_system_command_result(self, cmd_type, success, message, port_id): - """处理系统命令结果""" + """Handle system command result""" if port_id != self.servo_panel.port_id: return prefix = "✅" if success else "❌" self.add_log(f"{prefix} {message}") if cmd_type in ("baud_rate_change", "factory_reset") and success: # 这些操作后需要重新扫描 - self.add_log("🔄 请手动点击重新扫描以更新舵机列表") + self.add_log("🔄 Please click Rescan manually to update the servo list") def on_status_updated(self, servos, connected, port_id): - """舵机列表变化时刷新ID选择""" + """Refresh ID selection when the servo list changes""" if port_id != self.servo_panel.port_id: return self.refresh_servo_ids() class EZToolUI(QMainWindow): - """EZ Tool - 简化版双串口工厂舵机标定工具""" + """EZ Tool - Simplified dual-serial-port factory servo calibration tool""" def __init__(self, left_port: str = None, right_port: str = None): # Auto-detect default ports using port_utils @@ -2673,18 +2673,18 @@ def __init__(self, left_port: str = None, right_port: str = None): self.port_refresh_timer.start(2000) # 每 2 秒刷新一次 def stop_port_refresh(self): - """停止串口自动刷新(例如校准时避免干扰)""" + """Stop automatic serial port refresh (e.g. to avoid interference during calibration)""" if hasattr(self, 'port_refresh_timer') and self.port_refresh_timer.isActive(): self.port_refresh_timer.stop() def start_port_refresh(self): - """恢复串口自动刷新""" + """Resume automatic serial port refresh""" if hasattr(self, 'port_refresh_timer') and not self.port_refresh_timer.isActive(): self.port_refresh_timer.start(2000) def init_ui(self): - """初始化界面""" - self.setWindowTitle("🏭 双串口工厂舵机标定工具") + """Initialize the UI""" + self.setWindowTitle("🏭 Dual-Serial-Port Factory Servo Calibration Tool") # 初始尺寸限制在屏幕可用区域内: # 否则在 125%/150% 缩放的 Windows 小屏上,窗口比屏幕还大, # 最大化时窗口反而缩小,面板内容高度不足导致控件相互重叠 @@ -2709,12 +2709,12 @@ def init_ui(self): header_layout = QHBoxLayout() # 左侧标题 - title_label = QLabel("🏭 双串口工厂舵机标定工具") + title_label = QLabel("🏭 Dual-Serial-Port Factory Servo Calibration Tool") title_label.setStyleSheet("font-size: 28px; font-weight: bold; color: #2c3e50;") header_layout.addWidget(title_label) # 串口选择区域 - port_selection_group = QGroupBox("串口选择") + port_selection_group = QGroupBox("Serial Port Selection") port_selection_group.setStyleSheet(""" QGroupBox { font-size: 12px; @@ -2738,7 +2738,7 @@ def init_ui(self): # 左串口选择 left_port_layout = QVBoxLayout() left_port_layout.setSpacing(2) - left_label = QLabel("串口1:") + left_label = QLabel("Port 1:") left_label.setStyleSheet("font-size: 11px; font-weight: bold; color: #495057;") left_port_layout.addWidget(left_label) @@ -2764,7 +2764,7 @@ def init_ui(self): # 右串口选择 right_port_layout = QVBoxLayout() right_port_layout.setSpacing(2) - right_label = QLabel("串口2:") + right_label = QLabel("Port 2:") right_label.setStyleSheet("font-size: 11px; font-weight: bold; color: #495057;") right_port_layout.addWidget(right_label) @@ -2810,7 +2810,7 @@ def init_ui(self): } """) refresh_btn.clicked.connect(self.refresh_ports) - refresh_btn.setToolTip("刷新串口列表") + refresh_btn.setToolTip("Refresh Port List") port_selection_layout.addWidget(refresh_btn) header_layout.addWidget(port_selection_group) @@ -2830,7 +2830,7 @@ def init_ui(self): buttons_row.setSpacing(8) # 串口1中位校准按钮 - self.left_calib_btn = QPushButton("串口1中位校准") + self.left_calib_btn = QPushButton("Port 1 Mid-Point Calibration") self.left_calib_btn.setFixedSize(100, 35) self.left_calib_btn.setStyleSheet(""" QPushButton { @@ -2855,7 +2855,7 @@ def init_ui(self): buttons_row.addWidget(self.left_calib_btn) # 串口1中位测试按钮 - self.left_test_btn = QPushButton("串口1中位测试") + self.left_test_btn = QPushButton("Port 1 Mid-Point Test") self.left_test_btn.setFixedSize(100, 35) self.left_test_btn.setStyleSheet(""" QPushButton { @@ -2880,7 +2880,7 @@ def init_ui(self): buttons_row.addWidget(self.left_test_btn) # 串口1失能电机按钮 - self.left_disable_btn = QPushButton("串口1失能电机") + self.left_disable_btn = QPushButton("Port 1 Disable Motor") self.left_disable_btn.setFixedSize(100, 35) self.left_disable_btn.setStyleSheet(""" QPushButton { @@ -2910,7 +2910,7 @@ def init_ui(self): buttons_row.addWidget(separator_label) # 串口2中位校准按钮 - self.right_calib_btn = QPushButton("串口2中位校准") + self.right_calib_btn = QPushButton("Port 2 Mid-Point Calibration") self.right_calib_btn.setFixedSize(100, 35) self.right_calib_btn.setStyleSheet(""" QPushButton { @@ -2935,7 +2935,7 @@ def init_ui(self): buttons_row.addWidget(self.right_calib_btn) # 串口2中位测试按钮 - self.right_test_btn = QPushButton("串口2中位测试") + self.right_test_btn = QPushButton("Port 2 Mid-Point Test") self.right_test_btn.setFixedSize(100, 35) self.right_test_btn.setStyleSheet(""" QPushButton { @@ -2960,7 +2960,7 @@ def init_ui(self): buttons_row.addWidget(self.right_test_btn) # 串口2失能电机按钮 - self.right_disable_btn = QPushButton("串口2失能电机") + self.right_disable_btn = QPushButton("Port 2 Disable Motor") self.right_disable_btn.setFixedSize(100, 35) self.right_disable_btn.setStyleSheet(""" QPushButton { @@ -2998,7 +2998,7 @@ def init_ui(self): # 创建状态栏(必须先创建,因为标签页初始化会使用 status_bar) self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) - self.status_bar.showMessage("双串口系统已启动 - 左右独立操作 + 中间值校准") + self.status_bar.showMessage("Dual-serial-port system started - independent left/right operation + mid-point calibration") # 创建标签页 self.tab_widget = QTabWidget() @@ -3028,7 +3028,7 @@ def init_ui(self): # 设置分割器比例 splitter.setSizes([800, 800]) - self.tab_widget.addTab(servo_tab, "🦾 舵机标定") + self.tab_widget.addTab(servo_tab, "🦾 Servo Calibration") # === Tab 2: 单舵机控制 === single_control_tab = QWidget() @@ -3042,7 +3042,7 @@ def init_ui(self): single_control_splitter.setSizes([800, 800]) single_control_layout.addWidget(single_control_splitter) - self.tab_widget.addTab(single_control_tab, "🎚️ 单舵机控制") + self.tab_widget.addTab(single_control_tab, "🎚️ Single Servo Control") # === Tab 3: 高级工具 === advanced_tab = QWidget() @@ -3058,7 +3058,7 @@ def init_ui(self): advanced_splitter.setSizes([800, 800]) advanced_tab_layout.addWidget(advanced_splitter) - self.tab_widget.addTab(advanced_tab, "🔧 高级工具") + self.tab_widget.addTab(advanced_tab, "🔧 Advanced Tools") # 设置整体样式 self.setStyleSheet(""" @@ -3069,8 +3069,8 @@ def init_ui(self): @staticmethod def _wrap_in_scroll_area(widget): - """把面板包进滚动区域:窗口高度不足时显示滚动条, - 避免布局被过度压缩导致控件(如ID标定1-6按钮)相互重叠""" + """Wrap the panel in a scroll area: shows a scrollbar when the window is too short, + preventing the layout from being compressed enough that controls (e.g. the ID calibration 1-6 buttons) overlap""" scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setFrameShape(QFrame.NoFrame) @@ -3078,12 +3078,12 @@ def _wrap_in_scroll_area(widget): return scroll def init_connections(self): - """初始化信号连接""" + """Initialize signal connections""" # 添加初始日志 - self.status_bar.showMessage("双串口系统已启动 - 左右独立操作 + 中间值校准", 3000) + self.status_bar.showMessage("Dual-serial-port system started - independent left/right operation + mid-point calibration", 3000) def toggle_remote_control(self): - """切换遥控操作""" + """Toggle remote-control operation""" if self.remote_worker is None: # 创建遥控工作线程,传递当前选择的端口 self.remote_worker = RemoteControlWorker( @@ -3102,7 +3102,7 @@ def toggle_remote_control(self): self.stop_remote_control() def start_remote_control(self): - """启动遥控操作""" + """Start remote-control operation""" # 停止现有的舵机标定操作,避免端口冲突 if self.left_panel.worker.is_connected: self.left_panel.worker.stop() @@ -3113,7 +3113,7 @@ def start_remote_control(self): success, message = self.remote_worker.start_remote_control() if success: - self.remote_btn.setText("⏹️ 停止") + self.remote_btn.setText("⏹️ Stop") self.remote_btn.setStyleSheet(""" QPushButton { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, @@ -3130,20 +3130,20 @@ def start_remote_control(self): } """) self.add_remote_log(f"✅ {message}") - self.status_bar.showMessage(f"遥控操作已启动 - {self.left_port}读取,{self.right_port}控制", 5000) + self.status_bar.showMessage(f"Remote-control operation started - reading {self.left_port}, controlling {self.right_port}", 5000) else: self.add_remote_log(f"❌ {message}") - QMessageBox.critical(self, "启动失败", f"无法启动遥控操作:\n{message}") + QMessageBox.critical(self, "Start Failed", f"Can't start remote-control operation:\n{message}") def stop_remote_control(self): - """停止遥控操作""" + """Stop remote-control operation""" if self.remote_worker is None: return False success, message = self.remote_worker.stop_remote_control() if success: - self.remote_btn.setText("🎮 遥控") + self.remote_btn.setText("🎮 Remote Control") self.remote_btn.setStyleSheet(""" QPushButton { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, @@ -3160,7 +3160,7 @@ def stop_remote_control(self): } """) self.add_remote_log(f"✅ {message}") - self.status_bar.showMessage("遥控操作已停止", 3000) + self.status_bar.showMessage("Remote-control operation stopped", 3000) # 重新启动舵机标定操作 self.left_panel.worker.start() @@ -3169,18 +3169,19 @@ def stop_remote_control(self): self.add_remote_log(f"❌ {message}") def _begin_tool_action(self, port_name: str) -> int: - """开始一个串口工具操作(校准/中位测试/失能)。 + """Start a serial port tool operation (calibration/mid-point test/disable). - 递增该端口的操作代次,并终止该端口上一个仍在运行的工具子进程, - 保证新操作一定能拿到串口(Windows 下串口独占,旧进程不释放就会 PermissionError)。 - 返回本次操作的代次号。 + Increments this port's operation generation counter, and terminates any tool subprocess + still running for this port, guaranteeing the new operation can always claim the serial + port (serial ports are exclusive on Windows -- an old process that doesn't release it causes a PermissionError). + Returns this operation's generation number. """ gen = self._tool_gen.get(port_name, 0) + 1 self._tool_gen[port_name] = gen proc = self._tool_procs.get(port_name) if proc is not None and proc.poll() is None: - self.add_remote_log(f"⏹️ 终止{port_name}上一个仍在运行的工具进程,释放串口...") + self.add_remote_log(f"⏹️ Terminating {port_name}'s previous still-running tool process, releasing serial port...") try: proc.terminate() proc.wait(timeout=3) @@ -3193,16 +3194,16 @@ def _begin_tool_action(self, port_name: str) -> int: return gen def _is_latest_tool_action(self, port_name: str, gen: int) -> bool: - """判断本次操作是否仍是该端口最新的操作(被新操作取代的旧线程不应再恢复扫描线程)""" + """Check whether this operation is still the latest for this port (an old thread superseded by a new operation should not resume the scan thread)""" return self._tool_gen.get(port_name) == gen def _register_tool_process(self, port_name: str, process): - """登记该端口当前运行的工具子进程""" + """Register the tool subprocess currently running for this port""" self._tool_procs[port_name] = process def _tool_panel_log(self, port_name: str, message: str): - """把工具执行结果写到对应面板的操作日志(经 worker 信号转发,线程安全), - 避免失败信息只在状态栏一闪而过导致用户以为"没反应\"""" + """Write the tool's execution result to the corresponding panel's operation log (forwarded via worker signal, thread-safe), + so a failure message doesn't just flash in the status bar and make the user think nothing happened""" panel = self.left_panel if port_name == self.left_port else self.right_panel try: if panel is not None and panel.worker is not None: @@ -3211,19 +3212,19 @@ def _tool_panel_log(self, port_name: str, message: str): pass def run_quick_calibration(self, port_name: str): - """快速中位校准 - 非阻塞执行""" - self.add_remote_log(f"🔧 开始{port_name}快速中位校准...") - self.status_bar.showMessage(f"正在执行{port_name}中位校准...", 5000) + """Quick mid-point calibration - non-blocking""" + self.add_remote_log(f"🔧 Starting {port_name} quick mid-point calibration...") + self.status_bar.showMessage(f"Running {port_name} mid-point calibration...", 5000) # 先终止该端口上一个仍在运行的工具进程,确保能拿到串口 gen = self._begin_tool_action(port_name) # 先停止相应端口的工作线程,避免端口冲突 if port_name == self.left_port and self.left_panel.worker.is_connected: - self.add_remote_log(f"⏸️ 已停止{port_name}扫描线程,准备校准") + self.add_remote_log(f"⏸️ Stopped {port_name} scan thread, preparing to calibrate") self.left_panel.worker.stop() elif port_name == self.right_port and self.right_panel.worker.is_connected: - self.add_remote_log(f"⏸️ 已停止{port_name}扫描线程,准备校准") + self.add_remote_log(f"⏸️ Stopped {port_name} scan thread, preparing to calibrate") self.right_panel.worker.stop() # 等待端口释放 @@ -3236,12 +3237,12 @@ def run_quick_calibration(self, port_name: str): thread.daemon = True thread.start() - self.add_remote_log(f"📝 {port_name}校准进程已启动,请等待执行完成") + self.add_remote_log(f"📝 {port_name} calibration process started, please wait for it to finish") def _execute_quick_calibration(self, port_name: str, gen: int): - """执行快速中位校准的线程函数""" + """Thread function that performs quick mid-point calibration""" try: - self.add_remote_log(f"🔍 查找校准脚本...") + self.add_remote_log(f"🔍 Looking for calibration script...") # 使用 -m 模块方式运行,确保能找到 scservo_sdk # 检查使用哪个脚本 @@ -3249,13 +3250,13 @@ def _execute_quick_calibration(self, port_name: str, gen: int): quick_script = os.path.join(tools_dir, 'servo_quick_calibration.py') if os.path.exists(quick_script): - self.add_remote_log(f"✅ 找到校准脚本: servo_quick_calibration.py") + self.add_remote_log(f"✅ Found calibration script: servo_quick_calibration.py") command = [sys.executable, '-m', 'src.tools.servo_quick_calibration', port_name] else: - self.add_remote_log(f"⚠️ 未找到servo_quick_calibration.py,使用servo_middle_calibration.py") + self.add_remote_log(f"⚠️ servo_quick_calibration.py not found, using servo_middle_calibration.py") command = [sys.executable, '-m', 'src.tools.servo_middle_calibration', port_name, "2"] # 使用自动模式 - self.add_remote_log(f"🚀 启动校准进程: {' '.join(command)}") + self.add_remote_log(f"🚀 Starting calibration process: {' '.join(command)}") process = subprocess.Popen( command, @@ -3270,7 +3271,7 @@ def _execute_quick_calibration(self, port_name: str, gen: int): self._register_tool_process(port_name, process) # 监控输出 - important_keywords = ["连接", "扫描", "校准", "完成", "失败", "错误", "成功", "发现"] + important_keywords = ["Connect", "Scan", "Calibrate", "Done", "Failed", "Error", "Success", "Found"] while process.poll() is None: try: line = process.stdout.readline() @@ -3287,34 +3288,34 @@ def _execute_quick_calibration(self, port_name: str, gen: int): return_code = process.wait() self._tool_procs.pop(port_name, None) if not self._is_latest_tool_action(port_name, gen): - self.add_remote_log(f"ℹ️ {port_name}中位校准已被新操作中断/取代") + self.add_remote_log(f"ℹ️ {port_name} mid-point calibration was interrupted/superseded by a new operation") return if return_code == 0: - self.add_remote_log(f"✅ {port_name}中位校准完成 - 进程正常退出") - self._tool_panel_log(port_name, f"✅ {port_name}中位校准完成") - self.status_bar.showMessage(f"{port_name}校准完成", 3000) + self.add_remote_log(f"✅ {port_name} mid-point calibration complete - process exited normally") + self._tool_panel_log(port_name, f"✅ {port_name} mid-point calibration complete") + self.status_bar.showMessage(f"{port_name} calibration complete", 3000) else: - self.add_remote_log(f"❌ {port_name}中位校准失败 - 退出码: {return_code}") - self._tool_panel_log(port_name, f"❌ {port_name}中位校准失败 - 退出码: {return_code}") - self.status_bar.showMessage(f"{port_name}校准失败", 3000) + self.add_remote_log(f"❌ {port_name} mid-point calibration failed - exit code: {return_code}") + self._tool_panel_log(port_name, f"❌ {port_name} mid-point calibration failed - exit code: {return_code}") + self.status_bar.showMessage(f"{port_name} calibration failed", 3000) # 重新启动相应端口的扫描线程 - self.add_remote_log(f"⏳ 等待端口释放...") + self.add_remote_log(f"⏳ Waiting for port to be released...") import time time.sleep(1.0) # 增加等待时间确保端口完全释放 if port_name == self.left_port: self.left_panel.worker.start() - self.add_remote_log(f"▶️ 已重新启动{port_name}扫描线程") + self.add_remote_log(f"▶️ Restarted {port_name} scan thread") elif port_name == self.right_port: self.right_panel.worker.start() - self.add_remote_log(f"▶️ 已重新启动{port_name}扫描线程") + self.add_remote_log(f"▶️ Restarted {port_name} scan thread") except Exception as e: self._tool_procs.pop(port_name, None) - self.add_remote_log(f"❌ {port_name}校准异常: {e}") - self._tool_panel_log(port_name, f"❌ {port_name}校准异常: {e}") - self.status_bar.showMessage(f"{port_name}校准异常: {e}", 3000) + self.add_remote_log(f"❌ {port_name} calibration exception: {e}") + self._tool_panel_log(port_name, f"❌ {port_name} calibration exception: {e}") + self.status_bar.showMessage(f"{port_name} calibration exception: {e}", 3000) # 即使出现异常也要尝试重新启动扫描线程(被新操作取代时除外) if not self._is_latest_tool_action(port_name, gen): return @@ -3323,27 +3324,27 @@ def _execute_quick_calibration(self, port_name: str, gen: int): time.sleep(1.0) if port_name == self.left_port: self.left_panel.worker.start() - self.add_remote_log(f"🔄 异常后重启{port_name}扫描线程") + self.add_remote_log(f"🔄 Restarting {port_name} scan thread after exception") elif port_name == self.right_port: self.right_panel.worker.start() - self.add_remote_log(f"🔄 异常后重启{port_name}扫描线程") + self.add_remote_log(f"🔄 Restarting {port_name} scan thread after exception") except: - self.add_remote_log(f"⚠️ 重启{port_name}扫描线程失败") + self.add_remote_log(f"⚠️ Failed to restart {port_name} scan thread") def run_quick_test(self, port_name: str): - """快速中位测试 - 非阻塞执行""" - self.add_remote_log(f"🧪 开始{port_name}中位测试...") - self.status_bar.showMessage(f"正在执行{port_name}中位测试...", 5000) + """Quick mid-point test - non-blocking""" + self.add_remote_log(f"🧪 Starting {port_name} mid-point test...") + self.status_bar.showMessage(f"Running {port_name} mid-point test...", 5000) # 先终止该端口上一个仍在运行的工具进程,确保能拿到串口 gen = self._begin_tool_action(port_name) # 先停止相应端口的工作线程,避免端口冲突 if port_name == self.left_port and self.left_panel.worker.is_connected: - self.add_remote_log(f"⏸️ 已停止{port_name}扫描线程,准备测试") + self.add_remote_log(f"⏸️ Stopped {port_name} scan thread, preparing to test") self.left_panel.worker.stop() elif port_name == self.right_port and self.right_panel.worker.is_connected: - self.add_remote_log(f"⏸️ 已停止{port_name}扫描线程,准备测试") + self.add_remote_log(f"⏸️ Stopped {port_name} scan thread, preparing to test") self.right_panel.worker.stop() # 等待端口释放 @@ -3355,10 +3356,10 @@ def run_quick_test(self, port_name: str): thread.daemon = True thread.start() - self.add_remote_log(f"📝 {port_name}测试进程已启动,请等待执行完成") + self.add_remote_log(f"📝 {port_name} test process started, please wait for it to finish") def _execute_quick_test(self, port_name: str, gen: int): - """执行快速中位测试的线程函数""" + """Thread function that performs quick mid-point test""" try: # 使用 -m 模块方式运行,确保能找到 scservo_sdk command = [sys.executable, '-m', 'src.tools.servo_center_test', port_name] @@ -3389,14 +3390,14 @@ def _execute_quick_test(self, port_name: str, gen: int): return_code = process.wait() self._tool_procs.pop(port_name, None) if not self._is_latest_tool_action(port_name, gen): - self.add_remote_log(f"ℹ️ {port_name}中位测试已被新操作中断/取代") + self.add_remote_log(f"ℹ️ {port_name} mid-point test was interrupted/superseded by a new operation") return if return_code == 0: - self.add_remote_log(f"✅ {port_name}中位测试完成") - self._tool_panel_log(port_name, f"✅ {port_name}中位测试完成(力矩保持开启,点“失能电机”可松开)") + self.add_remote_log(f"✅ {port_name} mid-point test complete") + self._tool_panel_log(port_name, f"✅ {port_name} mid-point test complete (torque stays on; click \"Disable Motor\" to release)") else: - self.add_remote_log(f"❌ {port_name}中位测试失败") - self._tool_panel_log(port_name, f"❌ {port_name}中位测试失败 - 退出码: {return_code}") + self.add_remote_log(f"❌ {port_name} mid-point test failed") + self._tool_panel_log(port_name, f"❌ {port_name} mid-point test failed - exit code: {return_code}") # 重新启动相应端口的扫描线程 import time @@ -3404,15 +3405,15 @@ def _execute_quick_test(self, port_name: str, gen: int): if port_name == self.left_port: self.left_panel.worker.start() - self.add_remote_log(f"▶️ 已重新启动{port_name}扫描线程") + self.add_remote_log(f"▶️ Restarted {port_name} scan thread") elif port_name == self.right_port: self.right_panel.worker.start() - self.add_remote_log(f"▶️ 已重新启动{port_name}扫描线程") + self.add_remote_log(f"▶️ Restarted {port_name} scan thread") except Exception as e: self._tool_procs.pop(port_name, None) - self.add_remote_log(f"❌ {port_name}测试异常: {e}") - self._tool_panel_log(port_name, f"❌ {port_name}测试异常: {e}") + self.add_remote_log(f"❌ {port_name} test exception: {e}") + self._tool_panel_log(port_name, f"❌ {port_name} test exception: {e}") # 即使出现异常也要尝试重新启动扫描线程(被新操作取代时除外) if not self._is_latest_tool_action(port_name, gen): return @@ -3427,9 +3428,9 @@ def _execute_quick_test(self, port_name: str, gen: int): pass def run_quick_disable(self, port_name: str): - """快速失能电机 - 非阻塞执行""" - self.add_remote_log(f"⏹️ 开始{port_name}失能电机...") - self.status_bar.showMessage(f"正在执行{port_name}失能电机...", 5000) + """Quick disable motor - non-blocking""" + self.add_remote_log(f"⏹️ Starting {port_name} disable motor...") + self.status_bar.showMessage(f"Running {port_name} disable motor...", 5000) # 先终止该端口上一个仍在运行的工具进程(如中位测试),确保能拿到串口。 # 否则 Windows 串口独占会让失能进程打不开端口,表现为"点失能没反应" @@ -3437,10 +3438,10 @@ def run_quick_disable(self, port_name: str): # 先停止相应端口的工作线程,避免端口冲突 if port_name == self.left_port and self.left_panel.worker and self.left_panel.worker.is_connected: - self.add_remote_log(f"⏸️ 已停止{port_name}扫描线程,准备失能") + self.add_remote_log(f"⏸️ Stopped {port_name} scan thread, preparing to disable") self.left_panel.worker.stop() elif port_name == self.right_port and self.right_panel.worker and self.right_panel.worker.is_connected: - self.add_remote_log(f"⏸️ 已停止{port_name}扫描线程,准备失能") + self.add_remote_log(f"⏸️ Stopped {port_name} scan thread, preparing to disable") self.right_panel.worker.stop() # 等待端口释放 @@ -3452,15 +3453,15 @@ def run_quick_disable(self, port_name: str): thread.daemon = True thread.start() - self.add_remote_log(f"📝 {port_name}失能进程已启动,请等待执行完成") + self.add_remote_log(f"📝 {port_name} disable process started, please wait for it to finish") def _execute_quick_disable(self, port_name: str, gen: int): - """执行快速失能电机的线程函数""" + """Thread function that performs quick disable motor""" try: # 使用 -m 模块方式运行,确保能找到 scservo_sdk command = [sys.executable, '-m', 'src.tools.servo_disable', port_name] - self.add_remote_log(f"🚀 启动失能进程: {' '.join(command)}") + self.add_remote_log(f"🚀 Starting disable process: {' '.join(command)}") print(f"[DEBUG DISABLE] Port name: {port_name}") print(f"[DEBUG DISABLE] Full command: {command}") @@ -3477,7 +3478,7 @@ def _execute_quick_disable(self, port_name: str, gen: int): self._register_tool_process(port_name, process) # 监控输出 - 显示重要信息 - important_keywords = ["连接", "扫描", "失能", "完成", "失败", "错误", "成功", "发现", "扭矩", "旋转"] + important_keywords = ["Connect", "Scan", "Disable", "Done", "Failed", "Error", "Success", "Found", "Torque", "Rotate"] while process.poll() is None: try: line = process.stdout.readline() @@ -3489,7 +3490,7 @@ def _execute_quick_disable(self, port_name: str, gen: int): if any(keyword in line for keyword in important_keywords): self.add_remote_log(f"[{port_name}] {line}") # 失败/错误类信息同时写到面板日志,避免用户看不到原因 - if "❌" in line or "无法打开" in line or "错误" in line: + if "❌" in line or "Can't Open" in line or "Error" in line: self._tool_panel_log(port_name, line) except: break @@ -3497,16 +3498,16 @@ def _execute_quick_disable(self, port_name: str, gen: int): return_code = process.wait() self._tool_procs.pop(port_name, None) if not self._is_latest_tool_action(port_name, gen): - self.add_remote_log(f"ℹ️ {port_name}失能操作已被新操作中断/取代") + self.add_remote_log(f"ℹ️ {port_name} disable operation was interrupted/superseded by a new operation") return if return_code == 0: - self.add_remote_log(f"✅ {port_name}电机已失能,可手动旋转") - self._tool_panel_log(port_name, f"✅ {port_name}电机已失能,可手动旋转") - self.status_bar.showMessage(f"{port_name}失能完成", 3000) + self.add_remote_log(f"✅ {port_name} motor disabled, can be rotated by hand") + self._tool_panel_log(port_name, f"✅ {port_name} motor disabled, can be rotated by hand") + self.status_bar.showMessage(f"{port_name} disable complete", 3000) else: - self.add_remote_log(f"❌ {port_name}失能失败 - 退出码: {return_code}") - self._tool_panel_log(port_name, f"❌ {port_name}失能失败 - 退出码: {return_code},请重试") - self.status_bar.showMessage(f"{port_name}失能失败", 3000) + self.add_remote_log(f"❌ {port_name} disable failed - exit code: {return_code}") + self._tool_panel_log(port_name, f"❌ {port_name} disable failed - exit code: {return_code}, please retry") + self.status_bar.showMessage(f"{port_name} disable failed", 3000) # 重新启动相应端口的扫描线程 import time @@ -3514,16 +3515,16 @@ def _execute_quick_disable(self, port_name: str, gen: int): if port_name == self.left_port and self.left_panel.worker: self.left_panel.worker.start() - self.add_remote_log(f"▶️ 已重新启动{port_name}扫描线程") + self.add_remote_log(f"▶️ Restarted {port_name} scan thread") elif port_name == self.right_port and self.right_panel.worker: self.right_panel.worker.start() - self.add_remote_log(f"▶️ 已重新启动{port_name}扫描线程") + self.add_remote_log(f"▶️ Restarted {port_name} scan thread") except Exception as e: self._tool_procs.pop(port_name, None) - self.add_remote_log(f"❌ {port_name}失能异常: {e}") - self._tool_panel_log(port_name, f"❌ {port_name}失能异常: {e}") - self.status_bar.showMessage(f"{port_name}失能异常: {e}", 3000) + self.add_remote_log(f"❌ {port_name} disable exception: {e}") + self._tool_panel_log(port_name, f"❌ {port_name} disable exception: {e}") + self.status_bar.showMessage(f"{port_name} disable exception: {e}", 3000) # 即使出现异常也要尝试重新启动扫描线程(被新操作取代时除外) if not self._is_latest_tool_action(port_name, gen): return @@ -3532,15 +3533,15 @@ def _execute_quick_disable(self, port_name: str, gen: int): time.sleep(0.5) if port_name == self.left_port and self.left_panel.worker: self.left_panel.worker.start() - self.add_remote_log(f"🔄 异常后重启{port_name}扫描线程") + self.add_remote_log(f"🔄 Restarting {port_name} scan thread after exception") elif port_name == self.right_port and self.right_panel.worker: self.right_panel.worker.start() - self.add_remote_log(f"🔄 异常后重启{port_name}扫描线程") + self.add_remote_log(f"🔄 Restarting {port_name} scan thread after exception") except: pass def add_remote_log(self, message): - """添加遥控日志""" + """Add a remote-control log entry""" timestamp = time.strftime("%H:%M:%S") log_entry = f"[REMOTE] {message}" try: @@ -3548,18 +3549,18 @@ def add_remote_log(self, message): except UnicodeEncodeError: # GBK 控制台下 emoji 无法编码,降级为可显示字符,避免整个按钮动作被异常打断 print(f"[REMOTE] {log_entry.encode('gbk', 'replace').decode('gbk')}") - self.status_bar.showMessage(f"遥控: {message}", 3000) + self.status_bar.showMessage(f"Remote: {message}", 3000) def on_remote_started(self): - """遥控启动回调""" + """Remote-control start callback""" pass def on_remote_stopped(self): - """遥控停止回调""" + """Remote-control stop callback""" pass def refresh_ports(self): - """刷新可用串口列表""" + """Refresh the list of available serial ports""" try: self.available_ports = get_available_ports() print(f"[DEBUG] Available ports: {self.available_ports}") @@ -3613,7 +3614,7 @@ def refresh_ports(self): ): self.right_port_combo.setCurrentIndex(0) print( - f"[DEBUG] 左右端口冲突,已禁用右端口: " + f"[DEBUG] Left/right port conflict, disabled right port: " f"{self.left_port_combo.currentText()}" ) finally: @@ -3639,14 +3640,14 @@ def refresh_ports(self): elif new_right != (self.right_port or ""): self.on_right_port_changed(new_right) - self.status_bar.showMessage(f"串口列表已刷新 - 发现 {len(self.available_ports)} 个串口", 3000) + self.status_bar.showMessage(f"Serial port list refreshed - found {len(self.available_ports)} ports", 3000) except Exception as e: print(f"[DEBUG] Refresh ports error: {e}") - self.status_bar.showMessage(f"刷新串口列表失败: {e}", 3000) + self.status_bar.showMessage(f"Failed to refresh serial port list: {e}", 3000) def on_left_port_changed(self, port_name): - """左串口选择改变""" + """Left serial port selection changed""" if port_name == self.left_port or ( not self.left_port and port_name == ServoPanel.DISABLED_PORT ): @@ -3655,8 +3656,8 @@ def on_left_port_changed(self, port_name): # 检查是否与右端口冲突 if port_name != ServoPanel.DISABLED_PORT and port_name == self.right_port: QMessageBox.warning( - self, "端口冲突", - f"串口2 已经在使用 {port_name},不能重复选择同一串口。" + self, "Port Conflict", + f"Port 2 is already using {port_name}, can't select the same port twice." ) # 恢复左下拉框到之前的状态 self.left_port_combo.blockSignals(True) @@ -3674,11 +3675,11 @@ def on_left_port_changed(self, port_name): if port_name == ServoPanel.DISABLED_PORT: self.left_port = None self.left_panel.update_port_name(ServoPanel.DISABLED_PORT) - self.left_panel.connection_status.setText("⚫ 已禁用") + self.left_panel.connection_status.setText("⚫ Disabled") self.left_panel.worker = None # 清空舵机列表显示,避免拔掉设备后仍显示旧数据 self.left_panel.update_status([], False, self.left_panel.port_id) - self.status_bar.showMessage("串口1已禁用", 3000) + self.status_bar.showMessage("Port 1 Disabled", 3000) return self.left_port = port_name @@ -3695,10 +3696,10 @@ def on_left_port_changed(self, port_name): # 启动新的工作线程 self.left_panel.worker.start() - self.status_bar.showMessage(f"串口1已切换到: {port_name}", 3000) + self.status_bar.showMessage(f"Port 1 switched to: {port_name}", 3000) def on_right_port_changed(self, port_name): - """右串口选择改变""" + """Right serial port selection changed""" if port_name == self.right_port or ( not self.right_port and port_name == ServoPanel.DISABLED_PORT ): @@ -3707,8 +3708,8 @@ def on_right_port_changed(self, port_name): # 检查是否与左端口冲突 if port_name != ServoPanel.DISABLED_PORT and port_name == self.left_port: QMessageBox.warning( - self, "端口冲突", - f"串口1 已经在使用 {port_name},不能重复选择同一串口。" + self, "Port Conflict", + f"Port 1 is already using {port_name}, can't select the same port twice." ) # 恢复右下拉框到之前的状态 self.right_port_combo.blockSignals(True) @@ -3726,11 +3727,11 @@ def on_right_port_changed(self, port_name): if port_name == ServoPanel.DISABLED_PORT: self.right_port = None self.right_panel.update_port_name(ServoPanel.DISABLED_PORT) - self.right_panel.connection_status.setText("⚫ 已禁用") + self.right_panel.connection_status.setText("⚫ Disabled") self.right_panel.worker = None # 清空舵机列表显示,避免拔掉设备后仍显示旧数据 self.right_panel.update_status([], False, self.right_panel.port_id) - self.status_bar.showMessage("串口2已禁用", 3000) + self.status_bar.showMessage("Port 2 Disabled", 3000) return self.right_port = port_name @@ -3747,34 +3748,34 @@ def on_right_port_changed(self, port_name): # 启动新的工作线程 self.right_panel.worker.start() - self.status_bar.showMessage(f"串口2已切换到: {port_name}", 3000) + self.status_bar.showMessage(f"Port 2 switched to: {port_name}", 3000) def run_quick_calibration_left(self): - """串口1快速中位校准""" + """Port 1 quick mid-point calibration""" self.run_quick_calibration(self.left_port) def run_quick_test_left(self): - """串口1快速中位测试""" + """Port 1 quick mid-point test""" self.run_quick_test(self.left_port) def run_quick_disable_left(self): - """串口1快速失能电机""" + """Port 1 quick disable motor""" self.run_quick_disable(self.left_port) def run_quick_calibration_right(self): - """串口2快速中位校准""" + """Port 2 quick mid-point calibration""" self.run_quick_calibration(self.right_port) def run_quick_test_right(self): - """串口2快速中位测试""" + """Port 2 quick mid-point test""" self.run_quick_test(self.right_port) def run_quick_disable_right(self): - """串口2快速失能电机""" + """Port 2 quick disable motor""" self.run_quick_disable(self.right_port) def closeEvent(self, event): - """关闭事件""" + """Close event""" # 停止串口自动刷新定时器 self.stop_port_refresh() @@ -3790,7 +3791,7 @@ def closeEvent(self, event): def get_available_ports(): - """获取可用串口列表 - 使用 port_utils 中已过滤的端口""" + """Get the list of available serial ports - uses the already-filtered ports from port_utils""" try: from src.port_utils import get_available_ports as get_ports ports = get_ports() @@ -3816,7 +3817,7 @@ def get_available_ports(): def main(): - """主函数""" + """Main function""" import platform import argparse @@ -3825,23 +3826,23 @@ def main(): os.environ.setdefault("PYTHONIOENCODING", "utf-8") # 解析命令行参数 - parser = argparse.ArgumentParser(description='双串口工厂舵机标定工具') - parser.add_argument('--port1', type=str, help='指定串口1 (例如: COM1 或 /dev/ttyUSB0)') - parser.add_argument('--port2', type=str, help='指定串口2 (例如: COM2 或 /dev/ttyUSB1)') - parser.add_argument('--list-ports', action='store_true', help='列出可用串口并退出') + parser = argparse.ArgumentParser(description='Dual-serial-port factory servo calibration tool') + parser.add_argument('--port1', type=str, help='Specify serial port 1 (e.g. COM1 or /dev/ttyUSB0)') + parser.add_argument('--port2', type=str, help='Specify serial port 2 (e.g. COM2 or /dev/ttyUSB1)') + parser.add_argument('--list-ports', action='store_true', help='List available serial ports and exit') args = parser.parse_args() # 如果只是列出串口 if args.list_ports: try: available_ports = get_available_ports() - print("可用串口列表:") + print("Available serial ports:") for i, port in enumerate(available_ports, 1): print(f" {i}. {port}") if not available_ports: - print(" 未发现可用串口") + print(" No available serial ports found") except Exception as e: - print(f"获取串口列表失败: {e}") + print(f"Failed to get serial port list: {e}") return app = QApplication(sys.argv) @@ -3850,7 +3851,7 @@ def main(): import signal def handle_sigint(signum, frame): - print("\n收到 Ctrl+C,正在关闭...") + print("\nReceived Ctrl+C, shutting down...") app.quit() signal.signal(signal.SIGINT, handle_sigint) @@ -3879,15 +3880,15 @@ def handle_sigint(signum, frame): left_port = args.port1 if args.port1 else default_left_port right_port = args.port2 if args.port2 else default_right_port - print(f"启动双串口工厂舵机标定工具") - print(f"系统: {system}") - print(f"串口1: {left_port}") - print(f"串口2: {right_port}") + print(f"Starting dual-serial-port factory servo calibration tool") + print(f"System: {system}") + print(f"Port 1: {left_port}") + print(f"Port 2: {right_port}") # 检查可用端口 try: available_ports = get_available_ports() - print(f"检测到的可用串口: {available_ports}") + print(f"Detected available serial ports: {available_ports}") # 如果没有指定命令行参数,自动选择最佳端口 if not args.port1 or not args.port2: @@ -3906,7 +3907,7 @@ def handle_sigint(signum, frame): # 如果找到两个首选端口,使用它们 if len(found_ports) >= 2 and not args.port1 and not args.port2: left_port, right_port = found_ports[0], found_ports[1] - print(f"使用首选端口: {left_port}, {right_port}") + print(f"Using preferred ports: {left_port}, {right_port}") # 如果只找到一个首选端口 elif len(found_ports) == 1: if not args.port1: @@ -3917,29 +3918,29 @@ def handle_sigint(signum, frame): if port != (args.port1 or found_ports[0]): right_port = port break - print(f"使用混合端口配置: {left_port}, {right_port}") + print(f"Using mixed port configuration: {left_port}, {right_port}") # 没有找到首选端口 elif not args.port1 and not args.port2: left_port, right_port = available_ports[0], available_ports[1] - print(f"使用前两个可用端口: {left_port}, {right_port}") + print(f"Using the first two available ports: {left_port}, {right_port}") elif len(available_ports) == 1: if not args.port1: left_port = available_ports[0] if not args.port2: right_port = None # 只有一个真实串口,禁用右端口避免冲突 - print(f"只有一个可用端口: {available_ports[0]}, 备用端口: {right_port if right_port else '禁用'}") + print(f"Only one port available: {available_ports[0]}, fallback port: {right_port if right_port else 'Disabled'}") else: - print("未发现可用串口,使用默认配置") + print("No available serial ports found, using default configuration") except Exception as e: - print(f"检查可用端口时出错: {e}") + print(f"Error checking available ports: {e}") # 创建并显示主窗口 window = EZToolUI(left_port, right_port) window.show() - print("UI界面已启动") + print("UI started") sys.exit(app.exec())