Skip to content

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

  • PositionLimitError
  • VelocityLimitError
  • TorqueLimitError
  • KpLimitError
  • KdLimitError

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

  1. Catch specific exceptions first, generic base class last.
  2. In motion loops, always add timeout and retry logic.
  3. Treat limit exceptions as safety events and stop immediately.
  4. Ensure cleanup (disable_all, close) in finally blocks.
  5. 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 连接断开 重新初始化连接