异常处理¶
异常类总览¶
所有异常继承自 OpenArmXError,便于统一捕获处理。
OpenArmXError
├── CANError(CAN通信异常)
│ ├── CANInitializationError
│ ├── CANTimeoutError
│ └── CANTransmissionError
├── MotorError(电机异常)
│ ├── MotorNotEnabledError
│ ├── MotorFaultError
│ ├── MotorTimeoutError
│ └── MotorCalibrationError
├── ConfigurationError(配置异常)
│ ├── InvalidMotorIDError
│ ├── InvalidModeError
│ ├── InvalidParameterError
│ └── ConfigFileError
├── LimitExceededError(限制异常)
│ ├── PositionLimitError
│ ├── VelocityLimitError
│ ├── TorqueLimitError
│ ├── KpLimitError
│ └── KdLimitError
└── ConnectionError(连接异常)
└── ConnectionLostError
CAN通信异常¶
CANInitializationError¶
CAN总线初始化失败。
抛出场景:
- CAN接口不存在
- 权限不足
- 驱动未加载
- 硬件故障
try:
arm = Arm('can9') # can9不存在
except CANInitializationError as e:
print(f"CAN初始化失败: {e}")
CANTimeoutError¶
CANTimeoutError(motor_id=None, timeout=None)
CAN通信超时。
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
motor_id |
int |
None |
超时的电机ID |
timeout |
float |
None |
超时时间(秒) |
抛出场景:
- 电机未连接或断电
- CAN总线故障
- 电机ID配置错误
- 网络拥堵
try:
arm.move_joint_mit(5, position=1.0)
except CANTimeoutError as e:
print(f"电机{e.motor_id}通信超时: {e.timeout}s")
CANTransmissionError¶
CAN消息发送失败。
抛出场景:
- CAN总线错误
- 总线关闭状态
- 硬件故障
电机异常¶
MotorNotEnabledError¶
MotorNotEnabledError(motor_id)
电机未使能。
| 参数名 | 类型 | 说明 |
|---|---|---|
motor_id |
int |
未使能的电机ID |
# 在未使能的情况下尝试移动电机
arm.move_joint_position(motor_id=5, position=1.0)
# 抛出 MotorNotEnabledError
MotorFaultError¶
MotorFaultError(motor_id, fault_code=None, fault_description=None)
电机故障。
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
motor_id |
int |
- | 故障的电机ID |
fault_code |
int |
None |
故障代码(16进制) |
fault_description |
str |
None |
故障描述 |
故障类型:
| 位 | 故障类型 |
|---|---|
| bit 0 | 欠压故障 |
| bit 1 | 驱动故障 |
| bit 2 | 过温 |
| bit 3 | 磁编码故障 |
| bit 4 | 堵转过载故障 |
| bit 5 | 未标定 |
MotorTimeoutError¶
MotorTimeoutError(motor_id, command=None, timeout=None)
电机响应超时。
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
motor_id |
int |
- | 超时的电机ID |
command |
str |
None |
超时的命令类型 |
timeout |
float |
None |
超时时间(秒) |
MotorCalibrationError¶
MotorCalibrationError(motor_id)
电机未标定。
| 参数名 | 类型 | 说明 |
|---|---|---|
motor_id |
int |
未标定的电机ID |
配置异常¶
InvalidMotorIDError¶
InvalidMotorIDError(motor_id, valid_range=(1, 8))
无效的电机ID。
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
motor_id |
int |
- | 无效的电机ID |
valid_range |
tuple |
(1, 8) |
有效范围 |
try:
arm.enable(motor_id=10) # ID超出范围
except InvalidMotorIDError as e:
print(f"电机ID错误: {e}")
InvalidModeError¶
InvalidModeError(mode, valid_modes=['mit', 'pp', 'speed', 'current', 'csp'])
无效的控制模式。
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
mode |
str/int |
- | 无效的模式 |
valid_modes |
list |
['mit', 'pp', 'speed', 'current', 'csp'] |
有效模式列表 |
InvalidParameterError¶
InvalidParameterError(param_name, param_value, reason=None)
无效的参数。
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
param_name |
str |
- | 参数名称 |
param_value |
any |
- | 参数值 |
reason |
str |
None |
无效原因 |
ConfigFileError¶
ConfigFileError(config_file, reason=None)
配置文件错误。
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
config_file |
str |
- | 配置文件路径 |
reason |
str |
None |
错误原因 |
限制异常¶
PositionLimitError¶
PositionLimitError(motor_id, position, min_limit, max_limit)
位置超限。
| 参数名 | 类型 | 说明 |
|---|---|---|
motor_id |
int |
电机ID |
position |
float |
设置的位置值 |
min_limit |
float |
最小限制 |
max_limit |
float |
最大限制 |
VelocityLimitError¶
VelocityLimitError(motor_id, velocity, min_limit, max_limit)
速度超限。
| 参数名 | 类型 | 说明 |
|---|---|---|
motor_id |
int |
电机ID |
velocity |
float |
设置的速度值 |
min_limit |
float |
最小限制 |
max_limit |
float |
最大限制 |
TorqueLimitError¶
TorqueLimitError(motor_id, torque, min_limit, max_limit)
扭矩超限。
| 参数名 | 类型 | 说明 |
|---|---|---|
motor_id |
int |
电机ID |
torque |
float |
设置的扭矩值 |
min_limit |
float |
最小限制 |
max_limit |
float |
最大限制 |
KpLimitError¶
KpLimitError(motor_id, kp, min_limit, max_limit)
KP增益超限。
| 参数名 | 类型 | 说明 |
|---|---|---|
motor_id |
int |
电机ID |
kp |
float |
设置的KP值 |
min_limit |
float |
最小限制 |
max_limit |
float |
最大限制 |
KdLimitError¶
KdLimitError(motor_id, kd, min_limit, max_limit)
KD增益超限。
| 参数名 | 类型 | 说明 |
|---|---|---|
motor_id |
int |
电机ID |
kd |
float |
设置的KD值 |
min_limit |
float |
最小限制 |
max_limit |
float |
最大限制 |
连接异常¶
ConnectionError¶
连接错误基类。
抛出场景:
- 与硬件的连接出现问题
ConnectionLostError¶
ConnectionLostError(device=None)
连接丢失。
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
device |
str |
None |
丢失连接的设备描述 |
异常处理示例¶
from openarmx_arm_driver import Arm
from openarmx_arm_driver.exceptions import *
try:
# 1. 初始化机械臂
arm = Arm('can0', side='right')
# 2. 使能电机
arm.enable_all()
# 3. 控制运动
arm.move_joint_mit(motor_id=5, position=1.0)
# 4. 状态查询
status = arm.get_status(5)
except CANInitializationError as e:
print(f"CAN初始化失败: {e}")
print("请检查:")
print(" 1. CAN接口是否存在")
print(" 2. 是否有sudo权限")
print(" 3. CAN驱动是否已加载")
except MotorNotEnabledError as e:
print(f"电机未使能: {e}")
print("请先调用 enable() 或 enable_all() 方法")
except InvalidMotorIDError as e:
print(f"无效的电机ID: {e}")
print(f"有效范围: {e.valid_range[0]}-{e.valid_range[1]}")
except MotorFaultError as e:
print(f"电机故障: {e}")
if e.fault_code:
print(f"故障代码: 0x{e.fault_code:02X}")
except CANTimeoutError as e:
print(f"通信超时: {e}")
print("可能的原因:")
print(" 1. 电机电源未开")
print(" 2. CAN线连接不良")
print(" 3. 网络拥堵")
except Exception as e:
print(f"未知错误: {e}")
finally:
# 确保连接关闭
if 'arm' in locals():
arm.close()
异常处理建议¶
- 从具体到一般: 先捕获具体异常,再捕获基类异常
- 资源清理: 在finally块中确保资源释放
- 错误恢复: 根据异常类型尝试恢复操作
- 日志记录: 记录异常信息便于调试
- 用户提示: 提供明确的错误解决建议
场景化异常处理示例¶
场景1:机械臂初始化¶
from openarmx_arm_driver import Arm
from openarmx_arm_driver.exceptions import CANInitializationError
def init_arm_safely(can_channel, side='right', max_retries=3):
"""安全初始化机械臂,支持重试"""
for attempt in range(max_retries):
try:
arm = Arm(can_channel, side=side)
print(f"机械臂初始化成功: {can_channel}")
return arm
except CANInitializationError as e:
print(f"初始化失败 (尝试 {attempt + 1}/{max_retries}): {e}")
if attempt < max_retries - 1:
print("检查CAN接口后重试...")
import time
time.sleep(2)
print("初始化失败,请检查:")
print(" 1. CAN接口是否存在: ip link show")
print(" 2. CAN接口是否启用: sudo ip link set can0 up type can bitrate 1000000")
print(" 3. 是否有足够权限")
return None
# 使用
arm = init_arm_safely('can0', side='right')
if arm:
# 继续操作...
pass
场景2:电机使能与故障检测¶
from openarmx_arm_driver import Arm
from openarmx_arm_driver.exceptions import (
MotorNotEnabledError,
MotorFaultError,
CANTimeoutError
)
def enable_motors_with_check(arm):
"""使能电机并检测故障"""
failed_motors = []
fault_motors = []
for motor_id in arm.motor_ids:
try:
result = arm.enable(motor_id, timeout=2.0)
if result != 0:
failed_motors.append(motor_id)
print(f"电机{motor_id}: 使能失败")
else:
# 检查电机状态
status = arm.get_status(motor_id)
if status and status.get('fault_status') != '正常':
fault_motors.append((motor_id, status.get('fault_status')))
print(f"电机{motor_id}: 存在故障 - {status.get('fault_status')}")
else:
print(f"电机{motor_id}: 使能成功 ✓")
except CANTimeoutError as e:
failed_motors.append(motor_id)
print(f"电机{motor_id}: 通信超时")
except MotorFaultError as e:
fault_motors.append((motor_id, e.fault_description))
print(f"电机{motor_id}: 故障 - {e.fault_description}")
return {
'success': len(failed_motors) == 0 and len(fault_motors) == 0,
'failed': failed_motors,
'faults': fault_motors
}
# 使用
with Arm('can0', side='right') as arm:
result = enable_motors_with_check(arm)
if not result['success']:
print(f"警告: {len(result['failed'])}个电机使能失败, {len(result['faults'])}个电机有故障")
场景3:参数限制检查¶
from openarmx_arm_driver import Arm
from openarmx_arm_driver.exceptions import (
PositionLimitError,
VelocityLimitError,
TorqueLimitError,
KpLimitError,
KdLimitError
)
def safe_move_joint(arm, motor_id, position, kp=10.0, kd=1.0):
"""安全移动关节,自动处理限制异常"""
try:
result = arm.move_joint_mit(
motor_id=motor_id,
position=position,
kp=kp,
kd=kd
)
return result == 0
except PositionLimitError as e:
print(f"位置超限: {e.position:.3f} rad")
print(f"允许范围: [{e.min_limit:.3f}, {e.max_limit:.3f}] rad")
# 自动裁剪到安全范围
safe_pos = max(e.min_limit, min(e.max_limit, position))
print(f"自动调整到: {safe_pos:.3f} rad")
return arm.move_joint_mit(motor_id, position=safe_pos, kp=kp, kd=kd) == 0
except KpLimitError as e:
print(f"KP增益超限: {e.kp:.3f}")
print(f"允许范围: [{e.min_limit:.3f}, {e.max_limit:.3f}]")
safe_kp = max(e.min_limit, min(e.max_limit, kp))
return arm.move_joint_mit(motor_id, position=position, kp=safe_kp, kd=kd) == 0
except KdLimitError as e:
print(f"KD增益超限: {e.kd:.3f}")
safe_kd = max(e.min_limit, min(e.max_limit, kd))
return arm.move_joint_mit(motor_id, position=position, kp=kp, kd=safe_kd) == 0
# 使用
with Arm('can0', side='right') as arm:
arm.enable_all()
arm.set_mode('mit')
# 即使参数超限也能安全执行
safe_move_joint(arm, motor_id=5, position=100.0, kp=10000.0) # 会自动裁剪
场景4:双臂协调控制异常处理¶
from openarmx_arm_driver import Robot
from openarmx_arm_driver.exceptions import (
CANInitializationError,
CANTimeoutError,
OpenArmXError
)
def coordinated_move(robot, left_positions, right_positions, kp=10.0, kd=1.0):
"""双臂协调运动,带异常恢复"""
try:
robot.move_joints_mit(
left_positions=left_positions,
right_positions=right_positions,
kp=kp,
kd=kd
)
return True
except CANTimeoutError as e:
print(f"通信超时: {e}")
# 尝试单独控制未超时的臂
print("尝试单独控制...")
try:
if 'left' in str(e).lower():
robot.move_joints_mit(right_positions=right_positions, kp=kp, kd=kd)
print("右臂控制成功,左臂通信异常")
else:
robot.move_joints_mit(left_positions=left_positions, kp=kp, kd=kd)
print("左臂控制成功,右臂通信异常")
except Exception:
print("双臂均无法控制")
return False
except OpenArmXError as e:
print(f"控制异常: {e}")
# 紧急停止
try:
robot.disable_all()
print("已紧急停止所有电机")
except Exception:
pass
return False
# 使用
try:
robot = Robot('can0', 'can1')
robot.enable_all()
robot.set_mode_all('mit')
coordinated_move(
robot,
left_positions=[0.1, 0.2, 0.3, 0.4, 0, 0, 0, 0],
right_positions=[0.1, 0.2, 0.3, 0.4, 0, 0, 0, 0]
)
except CANInitializationError as e:
print(f"机器人初始化失败: {e}")
finally:
if 'robot' in locals():
robot.shutdown()
场景5:异常日志记录¶
import logging
from datetime import datetime
from openarmx_arm_driver import Arm
from openarmx_arm_driver.exceptions import OpenArmXError
# 配置日志
logging.basicConfig(
filename=f'openarmx_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def logged_operation(arm, operation_name, func, *args, **kwargs):
"""带日志的操作执行器"""
logger.info(f"开始执行: {operation_name}")
try:
result = func(*args, **kwargs)
logger.info(f"执行成功: {operation_name}")
return result
except OpenArmXError as e:
logger.error(f"执行失败: {operation_name} - {type(e).__name__}: {e}")
raise
except Exception as e:
logger.exception(f"未知错误: {operation_name}")
raise
# 使用
with Arm('can0', side='right') as arm:
logged_operation(arm, "使能所有电机", arm.enable_all)
logged_operation(arm, "设置MIT模式", arm.set_mode, 'mit')
logged_operation(arm, "移动电机5", arm.move_joint_mit, 5, position=1.0, kp=10.0, kd=1.0)
场景6:重试机制¶
import time
from functools import wraps
from openarmx_arm_driver.exceptions import CANTimeoutError, OpenArmXError
def retry_on_timeout(max_retries=3, delay=0.5):
"""超时重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except CANTimeoutError as e:
last_exception = e
print(f"超时重试 ({attempt + 1}/{max_retries}): {e}")
time.sleep(delay)
raise last_exception
return wrapper
return decorator
# 使用
@retry_on_timeout(max_retries=3, delay=0.5)
def reliable_move(arm, motor_id, position):
"""可靠的移动操作"""
return arm.move_joint_mit(motor_id, position=position, kp=10.0, kd=1.0)
with Arm('can0', side='right') as arm:
arm.enable_all()
arm.set_mode('mit')
# 自动重试
reliable_move(arm, 5, 1.0)
异常速查表¶
| 异常类 | 常见原因 | 处理建议 |
|---|---|---|
CANInitializationError |
CAN接口不存在/未启用 | 检查接口配置,使用password参数自动启用 |
CANTimeoutError |
电机断电/线缆故障 | 检查电源和连接,增加timeout |
CANTransmissionError |
CAN总线错误 | 检查总线状态,重启接口 |
MotorNotEnabledError |
未调用enable | 先调用enable()或enable_all() |
MotorFaultError |
电机硬件故障 | 根据fault_code诊断,可能需要重启 |
MotorTimeoutError |
电机响应慢 | 增加timeout,检查负载 |
MotorCalibrationError |
电机未标定 | 执行标定流程 |
InvalidMotorIDError |
ID超出范围 | 检查motor_ids配置 |
InvalidModeError |
模式名错误 | 使用有效模式:mit/csp/pp/speed/current |
PositionLimitError |
位置超限 | 裁剪到有效范围 |
VelocityLimitError |
速度超限 | 降低速度设置 |
TorqueLimitError |
扭矩超限 | 降低扭矩设置 |
KpLimitError |
KP增益超限 | 使用合理的KP值 |
KdLimitError |
KD增益超限 | 使用合理的KD值 |
ConnectionLostError |
连接断开 | 重新初始化连接 |