Exception Handling¶
Exception Class Overview¶
All exceptions inherit from OpenArmXError for unified handling.
OpenArmXError
├── CANError
│ ├── CANInitializationError
│ ├── CANTimeoutError
│ └── CANTransmissionError
├── MotorError
│ ├── MotorNotEnabledError
│ ├── MotorFaultError
│ ├── MotorTimeoutError
│ └── MotorCalibrationError
├── ConfigurationError
│ ├── InvalidMotorIDError
│ ├── InvalidModeError
│ ├── InvalidParameterError
│ └── ConfigFileError
├── LimitExceededError
│ ├── PositionLimitError
│ ├── VelocityLimitError
│ ├── TorqueLimitError
│ ├── KpLimitError
│ └── KdLimitError
└── ConnectionError
└── ConnectionLostError
CAN Exceptions¶
CANInitializationError¶
Raised when CAN initialization fails.
Common causes:
- Interface missing
- Insufficient permission
- Driver not loaded
- Hardware failure
CANTimeoutError(motor_id=None, timeout=None)¶
Raised when CAN communication times out.
CANTransmissionError¶
Raised when CAN frame transmission fails.
Motor Exceptions¶
MotorNotEnabledError(motor_id)¶
Raised when motion command is sent before enabling motor.
MotorFaultError(motor_id, fault_code=None, fault_description=None)¶
Raised for motor fault states.
Typical fault bits:
- bit0 undervoltage
- bit1 drive fault
- bit2 overtemperature
- bit3 encoder fault
- bit4 stall/overload
- bit5 uncalibrated
MotorTimeoutError(motor_id, command=None, timeout=None)¶
Raised when motor command response times out.
MotorCalibrationError(motor_id)¶
Raised when motor calibration is required but missing.
Configuration Exceptions¶
InvalidMotorIDError(motor_id, valid_range=(1, 8))¶
Raised when motor ID is outside valid range.
InvalidModeError(mode, valid_modes=[...])¶
Raised when an unsupported mode is requested.
InvalidParameterError(param_name, param_value, reason=None)¶
Raised for invalid parameter values.
ConfigFileError¶
Raised for config-file parsing/loading issues.
Limit Exceptions¶
PositionLimitErrorVelocityLimitErrorTorqueLimitErrorKpLimitErrorKdLimitError
Raised when command values exceed configured safety limits.
Connection Exceptions¶
ConnectionError¶
Generic connection-layer error.
ConnectionLostError¶
Raised when an established connection is lost.
Exception Handling Example¶
from openarmx_arm_driver import Arm
from openarmx_arm_driver.exceptions import (
CANInitializationError,
CANTimeoutError,
MotorNotEnabledError,
InvalidParameterError,
OpenArmXError,
)
try:
arm = Arm('can0', side='right')
arm.enable_all()
arm.set_mode('mit')
arm.move_joint_mit(5, position=1.0, kp=10, kd=1)
except CANInitializationError as e:
print('CAN init failed:', e)
except CANTimeoutError as e:
print('CAN timeout:', e)
except MotorNotEnabledError as e:
print('Motor not enabled:', e)
except InvalidParameterError as e:
print('Parameter error:', e)
except OpenArmXError as e:
print('OpenArmX error:', e)
finally:
try:
arm.disable_all()
arm.close()
except Exception:
pass
Practical Recommendations¶
- Catch specific exceptions first, generic base class last.
- In motion loops, always add timeout and retry logic.
- Treat limit exceptions as safety events and stop immediately.
- Ensure cleanup (
disable_all,close) infinallyblocks. - Log key metadata: timestamp, arm side, motor ID, command type, fault code.
Complete Source Appendix (Chinese)¶
The full original Chinese content is included below to ensure no sections are missing in the English edition.
异常处理¶
异常类总览¶
所有异常继承自 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 |
连接断开 | 重新初始化连接 |