# 睿尔曼RM65机械臂Python自动化脚本开发:从零实现搬运Demo全流程
在自动化产线、实验室自动化、教育科研乃至新兴的具身智能领域,机械臂正扮演着越来越核心的角色。对于许多集成商、研发工程师和教育工作者而言,如何快速、稳定地让机械臂完成一个基础的“抓取-移动-放置”循环,往往是项目落地的第一个关键步骤。睿尔曼的RM65系列超轻量仿人机械臂,凭借其开放的Python API接口和相对友好的开发环境,为这一过程提供了极大的便利。
今天,我们就来深入探讨如何从零开始,构建一个完整的Python自动化脚本,驱动睿尔曼RM65机械臂完成一个典型的物料搬运Demo。这不仅仅是一个简单的代码拼凑,我们将从环境搭建、通信建立、点位规划、夹爪控制,一直深入到异常处理和流程优化,为你呈现一个工业级可靠性的自动化工作流开发全貌。无论你是希望将机械臂集成到现有产线的工程师,还是在实验室里探索机器人应用的研究者,这篇文章都将提供一套可直接复用、并可根据需求深度定制的实战方案。
## 1. 开发环境搭建与核心库解析
在开始编写控制逻辑之前,一个稳定、兼容的开发环境是基石。睿尔曼为RM65机械臂提供了两种主流的二次开发方式:基于动态链接库(DLL/SO)的直接调用和封装更完善的Python SDK。对于追求快速开发和更高可读性的项目,我们强烈推荐使用后者。
### 1.1 Python SDK安装与项目初始化
睿尔曼官方维护的Python SDK `Robotic_Arm` 可以通过pip直接安装,这省去了手动配置库路径的麻烦。建议在独立的虚拟环境中进行操作,以避免包依赖冲突。
```bash
# 创建并激活虚拟环境(以conda为例)
conda create -n rm65_auto python=3.9
conda activate rm65_auto
# 安装睿尔曼机械臂Python SDK
pip install Robotic_Arm
```
安装完成后,你可以通过一个简单的连接测试脚本来验证环境是否就绪。这个脚本的核心是实例化 `RoboticArm` 类并建立TCP连接。
```python
from Robotic_Arm.rm_robot_interface import *
def test_connection(robot_ip="192.168.1.18", robot_port=8080):
"""
测试与睿尔曼机械臂的TCP连接
Args:
robot_ip (str): 机械臂控制器IP地址,默认为192.168.1.18
robot_port (int): 通信端口,默认为8080
"""
try:
# 创建机械臂控制实例,使用三线程模式以保证控制、状态反馈、数据处理的并行性
robot = RoboticArm(rm_thread_mode_e.RM_TRIPLE_MODE_E)
# 建立连接,第三个参数3代表控制权限等级
handle = robot.rm_create_robot_arm(robot_ip, robot_port, 3)
if handle.id == -1:
print("连接失败,请检查:")
print("1. 机械臂电源与网络是否正常")
print("2. 电脑IP是否与机械臂在同一网段(如192.168.1.xxx)")
print("3. 防火墙是否阻止了端口通信")
return None
else:
print(f"成功连接到机械臂,句柄ID: {handle.id}")
# 获取并打印机械臂软件信息,确认型号与版本
software_info = robot.rm_get_arm_software_info()
if software_info[0] == 0:
info = software_info[1]
print(f"机械臂型号: {info['product_version']}")
print(f"算法库版本: {info['algorithm_info']['version']}")
print(f"控制层版本: {info['ctrl_info']['version']}")
return robot, handle
except Exception as e:
print(f"连接过程中发生异常: {e}")
return None
if __name__ == "__main__":
# 请将IP地址替换为你实际机械臂的IP
test_connection("192.168.1.18")
```
> **注意**:首次连接前,请务必通过睿尔曼Web示教器或机身标签确认机械臂控制器的确切IP地址。常见的出厂默认IP是 `192.168.1.18`,但如果在多设备网络中可能已被修改。
### 1.2 理解核心控制类与运动模式
成功连接后,我们需要理解 `RoboticArm` 类提供的几个核心运动控制接口。这些接口封装了底层复杂的运动学计算和轨迹规划,让我们可以专注于业务逻辑。
- **`rm_movej(joint_positions, speed, blend_radius, connect_flag, block_flag)`**: **关节空间运动**。直接指定六个关节的目标角度(单位:度)。这种方式运动效率高,路径由控制器内部优化,但不关心末端执行器的空间轨迹。适用于快速回零或到达无碰撞风险的中间点位。
- **`rm_movel(cartesian_pose, speed, blend_radius, connect_flag, block_flag)`**: **笛卡尔空间直线运动**。指定末端执行器的目标位姿(位置XYZ单位:米;姿态欧拉角RX, RY, RZ单位:弧度)。机械臂末端将沿空间直线运动到目标点,适用于需要精确直线路径的抓取、装配等场景。
- **`rm_movec(via_pose, to_pose, speed, blend_radius, loop_count, connect_flag, block_flag)`**: **笛卡尔空间圆弧运动**。通过一个途经点(`via_pose`)和一个终点(`to_pose`)来定义一段圆弧轨迹。常用于需要绕开障碍物或完成弧形涂胶、焊接等工艺。
- **`rm_movej_p(cartesian_pose, ...)`**: **关节空间位姿运动**。输入是笛卡尔位姿,但控制器会先通过逆运动学解算出关节角度,再以关节运动的方式到达。其轨迹不是末端直线,但通常比 `movel` 更快。
为了在后续开发中更清晰地使用这些接口,并方便地进行错误处理和状态管理,我们可以先构建一个简单的封装类。
```python
class RMArmController:
"""睿尔曼机械臂控制器的简易封装类"""
def __init__(self, ip, port=8080):
self.robot = RoboticArm(rm_thread_mode_e.RM_TRIPLE_MODE_E)
self.handle = self.robot.rm_create_robot_arm(ip, port, 3)
if self.handle.id == -1:
raise ConnectionError(f"无法连接到机械臂 {ip}:{port}")
self._connected = True
print(f"控制器初始化成功,连接句柄: {self.handle.id}")
def move_joint(self, joints, speed=20.0, block=True):
"""关节运动封装,增加基础错误检查"""
if not self._connected:
raise RuntimeError("机械臂未连接")
if len(joints) != 6:
raise ValueError("关节角度数组必须包含6个值")
ret = self.robot.rm_movej(joints, speed, 0, 0, 1 if block else 0)
if ret != 0:
raise RuntimeError(f"关节运动指令执行失败,错误码: {ret}")
return True
def move_linear(self, pose, speed=0.1, block=True):
"""直线运动封装,速度单位默认为m/s"""
# pose格式: [x, y, z, rx, ry, rz]
ret = self.robot.rm_movel(pose, speed, 0, 0, 1 if block else 0)
if ret != 0:
raise RuntimeError(f"直线运动指令执行失败,错误码: {ret}")
return True
def disconnect(self):
"""安全断开连接"""
if self._connected:
self.robot.rm_delete_robot_arm()
self._connected = False
print("机械臂连接已断开")
```
## 2. 搬运任务场景分析与点位标定策略
一个完整的搬运Demo,其核心是定义一套准确、安全、高效的空间点位。这些点位通常包括:**待机点(Home)**、**抓取Approach点**、**抓取点**、**放置Approach点**、**放置点**。直接通过代码输入坐标既困难又不精确,因此我们需要借助示教或视觉标定等手段。
### 2.1 使用示教器进行点位采集
最直接的方法是使用睿尔曼的Web示教器进行手动示教。操作流程如下:
1. 在浏览器中输入机械臂IP,登录Web示教器。
2. 切换到“手动操作”模式,通过拖动界面上的滑块或使用物理示教器(如果配备)轻柔地将机械臂移动到目标位置。
3. 在“点位管理”中,记录当前位姿。示教器通常会同时提供关节角和笛卡尔坐标两种表示。
4. 为每个点位起一个易于识别的名字,如 `home`, `pick_approach`, `pick`, `place_approach`, `place`。
记录下的笛卡尔坐标(单位:米和弧度)可以直接用于我们的 `movel` 指令。为了在脚本中清晰管理这些点位,我们建议使用一个配置字典或类来存储。
```python
# 点位配置示例 - 基于笛卡尔坐标(位置XYZ单位:米,姿态欧拉角单位:弧度)
# 注意:以下坐标仅为示例,实际值需通过示教获取
TASK_WAYPOINTS = {
"home": {
"pose": [0.25, 0.0, 0.35, 3.1416, 0.0, 0.0], # 安全待机位置
"description": "机械臂初始安全位置,视野开阔,无碰撞风险"
},
"pick_approach": {
"pose": [0.15, -0.22, 0.25, 3.1416, 0.0, 1.5708], # 抓取前接近点
"description": "抓取点正上方50mm处,用于垂直下降前的定位"
},
"pick": {
"pose": [0.15, -0.22, 0.20, 3.1416, 0.0, 1.5708], # 实际抓取点
"description": "夹爪中心与物体中心对齐的位置"
},
"place_approach": {
"pose": [0.15, 0.22, 0.25, 3.1416, 0.0, 1.5708], # 放置前接近点
"description": "放置点正上方50mm处"
},
"place": {
"pose": [0.15, 0.22, 0.20, 3.1416, 0.0, 1.5708], # 实际放置点
"description": "物体被放置的目标位置"
}
}
# 关节角格式的点位备份(可选,用于快速回零或特定关节空间运动)
JOINT_WAYPOINTS = {
"home_joint": [0.0, 0.0, -90.0, 0.0, -90.0, 0.0] # 单位:度
}
```
### 2.2 坐标变换与工具坐标系标定
在实际应用中,抓取点往往不是基于机械臂的基坐标系,而是基于安装在末端的**工具坐标系**。例如,夹爪的指尖中心。睿尔曼SDK支持工具坐标系(TCP)的设置与使用。
**工具坐标系标定流程(六点法)**:
1. 在机械臂末端安装好夹爪。
2. 在空间中找一个尖锐的固定点(如定位销)。
3. 在示教器中选择“工具标定”功能。
4. 用工具上的参考点(如夹爪指尖)以至少四种不同的姿态去触碰固定点,记录多个点位。
5. 控制器会自动计算出工具坐标系相对于末端法兰盘的变换矩阵。
标定完成后,在发送 `movel` 指令时,指令中的位姿就是基于这个新工具坐标系的了,这大大简化了抓取位置的计算。在代码中,我们可以通过SDK设置当前使用的工具坐标系。
```python
def set_tool_frame(self, tool_name="gripper_center"):
"""设置当前使用的工具坐标系"""
# 假设已通过示教器标定并保存了一个名为“Gripper_TCP”的工具坐标系
ret = self.robot.Change_Tool_Frame(tool_name)
if ret != 0:
print(f"警告:切换至工具坐标系 '{tool_name}' 失败,错误码 {ret}。将使用默认法兰坐标系。")
else:
print(f"已切换至工具坐标系: {tool_name}")
```
## 3. 夹爪集成与末端执行器控制
搬运任务离不开末端执行器。睿尔曼机械臂的末端通常提供数字IO或通信接口(如RS485)来控制第三方夹爪。这里我们以一款常见的电动夹爪为例,假设它通过RS485与机械臂控制器通信,并且睿尔曼SDK已提供了相应的控制函数。
### 3.1 夹爪控制函数封装
夹爪的基本操作无非是**打开**、**闭合**,以及**设置开合宽度或力度**。我们需要根据夹爪的通讯协议进行封装。
```python
class GripperController:
"""夹爪控制器封装(示例基于睿尔曼SDK的IO或串口控制功能)"""
def __init__(self, arm_controller):
self.arm = arm_controller.robot
self.handle = arm_controller.handle
# 假设夹爪控制通过机械臂的末端IO口1和2进行
self.open_io_index = 1 # 对应打开信号的IO口编号
self.close_io_index = 2 # 对应关闭信号的IO口编号
def open(self, width_mm=50):
"""打开夹爪到指定宽度(毫米)"""
# 步骤1: 发送打开指令(具体协议取决于夹爪)
# 这里示例为通过设置IO口高低电平来控制
ret1 = self.arm.Set_DO(self.handle, self.open_io_index, 1) # 置高
ret2 = self.arm.Set_DO(self.handle, self.close_io_index, 0) # 置低
time.sleep(0.5) # 等待动作执行
# 步骤2: 复位IO(如果是脉冲触发型)
self.arm.Set_DO(self.handle, self.open_io_index, 0)
if ret1 == 0 and ret2 == 0:
print(f"夹爪已打开至约{width_mm}mm宽度")
return True
else:
print("夹爪打开指令发送失败")
return False
def close(self, force_percentage=60):
"""闭合夹爪,可指定力度百分比"""
ret1 = self.arm.Set_DO(self.handle, self.close_io_index, 1)
ret2 = self.arm.Set_DO(self.handle, self.open_io_index, 0)
time.sleep(0.5)
self.arm.Set_DO(self.handle, self.close_io_index, 0)
if ret1 == 0 and ret2 == 0:
print(f"夹爪已闭合,力度约{force_percentage}%")
return True
else:
print("夹爪闭合指令发送失败")
return False
def check_object_grasped(self, sensor_io_index=3):
"""通过末端IO传感器检查是否成功抓取到物体"""
# 读取指定IO口的状态,假设夹爪内置了接触传感器
ret, state = self.arm.Get_DI(self.handle, sensor_io_index)
if ret == 0:
return state == 1 # 假设1表示检测到物体
else:
print("无法读取夹爪传感器状态")
return False
```
### 3.2 集成夹爪控制的搬运动作序列
将机械臂运动与夹爪控制组合起来,就形成了一个完整的**抓取-放置**原子动作。为了保证可靠性,每个动作后都应加入状态检查。
```python
def execute_pick_and_place(arm_controller, gripper, waypoints):
"""执行一次完整的抓取放置循环"""
try:
print("=== 开始搬运循环 ===")
# 1. 移动到抓取接近点
print(f"移动至抓取接近点...")
arm_controller.move_linear(waypoints["pick_approach"]["pose"], speed=0.15)
# 2. 直线下降至抓取点
print(f"下降至抓取点...")
arm_controller.move_linear(waypoints["pick"]["pose"], speed=0.05) # 低速接近
# 3. 闭合夹爪
print(f"闭合夹爪...")
if not gripper.close():
raise RuntimeError("夹爪闭合失败,中止任务")
time.sleep(0.8) # 确保夹紧
# 4. 检查是否抓取成功(可选)
if not gripper.check_object_grasped():
print("警告:传感器未检测到物体,可能抓取失败。")
# 这里可以加入重试或报警逻辑
# 5. 提升至抓取接近点(带着物体)
print(f"提升至抓取接近点...")
arm_controller.move_linear(waypoints["pick_approach"]["pose"], speed=0.1)
# 6. 移动至放置接近点
print(f"移动至放置接近点...")
arm_controller.move_linear(waypoints["place_approach"]["pose"], speed=0.15)
# 7. 直线下降至放置点
print(f"下降至放置点...")
arm_controller.move_linear(waypoints["place"]["pose"], speed=0.05)
# 8. 打开夹爪,释放物体
print(f"打开夹爪,释放物体...")
gripper.open()
time.sleep(0.5) # 确保完全打开
# 9. 提升至放置接近点
print(f"提升至放置接近点...")
arm_controller.move_linear(waypoints["place_approach"]["pose"], speed=0.1)
# 10. 返回安全待机点
print(f"返回安全待机点...")
arm_controller.move_linear(waypoints["home"]["pose"], speed=0.2)
print("=== 搬运循环完成 ===")
return True
except Exception as e:
print(f"搬运过程发生错误: {e}")
# 错误处理:尝试安全停止并回到待机点
emergency_stop_and_return(arm_controller, waypoints["home"]["pose"])
return False
```
## 4. 高级轨迹规划与异常中断处理
一个健壮的自动化脚本,必须能优雅地处理各种意外情况,如运动被阻挡、通信中断、超时等。
### 4.1 运动监控与超时处理
单纯发送运动指令并等待完成是不够的。我们需要为每个运动添加超时监控,防止程序因机械臂卡死而无限期等待。
```python
def safe_move_linear_with_timeout(arm_controller, target_pose, speed=0.1, timeout=10.0):
"""
带超时和状态检查的安全直线运动函数
"""
import threading
import time
move_completed = False
error_occurred = None
def _move_task():
nonlocal move_completed, error_occurred
try:
arm_controller.move_linear(target_pose, speed, block=True)
move_completed = True
except Exception as e:
error_occurred = e
# 在独立线程中执行运动
move_thread = threading.Thread(target=_move_task)
move_thread.start()
# 主线程进行超时监控
start_time = time.time()
while move_thread.is_alive():
if time.time() - start_time > timeout:
print(f"错误:运动至目标位姿超时(>{timeout}秒)")
# 这里可以尝试发送停止指令(需根据SDK提供的方法)
# arm_controller.robot.Stop_Motion()
move_thread.join(timeout=1.0)
raise TimeoutError(f"机械臂运动超时,目标位姿: {target_pose}")
time.sleep(0.1) # 避免忙等待
# 运动线程结束,检查结果
if error_occurred:
raise error_occurred
return True
```
### 4.2 异常中断与恢复策略
在自动化流程中,可能会遇到急停、外力碰撞等需要中断当前任务的情况。一个好的策略是记录中断时的状态,并在恢复后能够从中断点继续或安全回退。
```python
class TaskStateManager:
"""任务状态管理器,用于异常中断与恢复"""
def __init__(self):
self.current_step = "idle" # 记录当前执行到哪一步
self.last_safe_pose = None # 记录最后一个安全点位
self.task_paused = False
self.emergency_stop_flag = False
def signal_emergency_stop(self, arm_controller):
"""紧急停止信号处理"""
self.emergency_stop_flag = True
self.task_paused = True
print("紧急停止触发!")
# 立即停止所有运动(如果SDK支持)
# arm_controller.robot.Stop_Motion()
# 尝试缓慢移动到最近的安全点
if self.last_safe_pose:
print("正在尝试缓慢移动至最近安全点...")
try:
arm_controller.move_linear(self.last_safe_pose, speed=0.05)
except:
print("无法移动至安全点,请手动干预。")
def resume_task(self, arm_controller, gripper, waypoints):
"""从暂停点恢复任务(简化示例:回到Home重新开始)"""
if not self.task_paused:
return
print("尝试恢复任务...")
# 策略1: 回到Home点,重新开始整个循环(最安全)
arm_controller.move_linear(waypoints["home"]["pose"])
self.current_step = "idle"
self.task_paused = False
self.emergency_stop_flag = False
print("已复位至Home点,可重新启动任务。")
# 策略2: 更复杂的恢复逻辑可以根据 self.current_step 判断从哪里继续
```
### 4.3 利用UDP监听实现实时状态反馈
除了通过API主动查询,睿尔曼机械臂还支持通过UDP协议主动上报实时状态。这允许我们创建一个独立的监听线程,实时监控关节角度、力矩、错误码等,实现更及时的状态感知和预警。
```python
import socket
import json
import threading
class ArmStateMonitor(threading.Thread):
"""机械臂状态UDP监听线程"""
def __init__(self, listen_ip='0.0.0.0', listen_port=8089):
super().__init__()
self.listen_ip = listen_ip
self.listen_port = listen_port
self.sock = None
self.running = False
self.current_state = {}
self.lock = threading.Lock()
def run(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind((self.listen_ip, self.listen_port))
self.sock.settimeout(1.0) # 设置超时以便可以检查self.running
self.running = True
print(f"状态监听器启动于 {self.listen_ip}:{self.listen_port}")
while self.running:
try:
data, addr = self.sock.recvfrom(4096) # 缓冲区大小
# 假设数据为JSON格式(需参考睿尔曼UDP协议文档)
state_dict = json.loads(data.decode('utf-8'))
with self.lock:
self.current_state = state_dict
# 示例:检查错误码
if state_dict.get('arm_err', 0) != 0:
print(f"警告:机械臂错误码 {state_dict['arm_err']}")
# 可以在这里添加更多的状态分析和预警逻辑
except socket.timeout:
continue # 超时是正常的,用于循环检查running标志
except json.JSONDecodeError:
print("收到非JSON格式数据")
except Exception as e:
print(f"监听线程出错: {e}")
break
def get_current_joints(self):
"""获取当前关节角度(度)"""
with self.lock:
# 根据实际协议字段名调整,例如可能是'joint_position'
return self.current_state.get('joint_position', [0]*6)
def stop(self):
self.running = False
if self.sock:
self.sock.close()
self.join()
print("状态监听器已停止")
```
在主程序中,我们可以这样使用监控器:
```python
# 在主函数中
state_monitor = ArmStateMonitor()
state_monitor.start()
try:
# 你的主控制循环
while True:
# 在运动前或运动中,可以实时获取状态
current_joints = state_monitor.get_current_joints()
# print(f"当前关节角: {current_joints}")
# 根据状态做出决策...
time.sleep(0.1)
except KeyboardInterrupt:
print("用户中断程序")
finally:
state_monitor.stop()
# 断开机械臂连接等清理工作
```
## 5. 构建完整的、可配置的自动化脚本
将以上所有模块整合,我们最终形成一个模块化、可配置、带日志和错误处理的完整脚本。这个脚本应该易于修改点位、调整参数,并能稳定运行。
### 5.1 主程序架构与配置管理
我们使用一个配置文件(如 `config.yaml`)来管理所有参数,使脚本更具适应性。
**`config.yaml` 示例:**
```yaml
robot:
ip: "192.168.1.18"
port: 8080
gripper:
open_io: 1
close_io: 2
sensor_io: 3
waypoints:
home: [0.25, 0.0, 0.35, 3.1416, 0.0, 0.0]
pick_approach: [0.15, -0.22, 0.25, 3.1416, 0.0, 1.5708]
pick: [0.15, -0.22, 0.20, 3.1416, 0.0, 1.5708]
place_approach: [0.15, 0.22, 0.25, 3.1416, 0.0, 1.5708]
place: [0.15, 0.22, 0.20, 3.1416, 0.0, 1.5708]
motion_params:
approach_speed: 0.15 # m/s
precise_speed: 0.05 # m/s
joint_speed: 20.0 # deg/s
motion_timeout: 15.0 # seconds
task:
cycles: 10 # 循环次数,0表示无限循环
delay_between_cycles: 2.0 # 秒
```
**主程序 `main.py` 核心逻辑:**
```python
import yaml
import time
import logging
from datetime import datetime
from rm_arm_controller import RMArmController
from gripper_controller import GripperController
from state_monitor import ArmStateMonitor
from task_manager import TaskStateManager
def setup_logging():
"""配置日志系统"""
log_filename = f"arm_demo_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(log_filename),
logging.StreamHandler()
]
)
return logging.getLogger(__name__)
def main():
logger = setup_logging()
# 1. 加载配置
with open('config.yaml', 'r') as f:
config = yaml.safe_load(f)
logger.info("配置加载成功")
# 2. 初始化状态监控器(非阻塞)
state_monitor = ArmStateMonitor()
state_monitor.start()
# 3. 连接机械臂
logger.info(f"尝试连接机械臂 {config['robot']['ip']}:{config['robot']['port']}")
try:
arm = RMArmController(config['robot']['ip'], config['robot']['port'])
gripper = GripperController(arm)
task_state = TaskStateManager()
except Exception as e:
logger.error(f"初始化失败: {e}")
state_monitor.stop()
return
# 4. 移动到初始Home点
try:
logger.info("移动至初始Home点")
arm.move_linear(config['waypoints']['home'], speed=config['motion_params']['approach_speed'])
task_state.last_safe_pose = config['waypoints']['home']
except Exception as e:
logger.error(f"初始化运动失败: {e}")
arm.disconnect()
state_monitor.stop()
return
# 5. 主任务循环
cycle_count = 0
max_cycles = config['task']['cycles']
try:
while max_cycles == 0 or cycle_count < max_cycles:
cycle_count += 1
logger.info(f"开始第 {cycle_count} 次搬运循环")
# 检查紧急停止标志
if task_state.emergency_stop_flag:
logger.warning("检测到紧急停止标志,循环中止")
break
# 执行单次抓取放置
success = execute_pick_and_place(arm, gripper, config, task_state)
if not success:
logger.error(f"第 {cycle_count} 次循环失败,尝试恢复或中止...")
# 这里可以加入更复杂的错误恢复逻辑
recovery_success = task_state.resume_task(arm, gripper, config['waypoints'])
if not recovery_success:
logger.critical("恢复失败,任务终止")
break
else:
continue # 恢复成功,继续下一循环
logger.info(f"第 {cycle_count} 次循环完成")
# 循环间延时
if cycle_count < max_cycles or max_cycles == 0:
time.sleep(config['task']['delay_between_cycles'])
except KeyboardInterrupt:
logger.info("用户请求中断程序")
except Exception as e:
logger.exception(f"主循环发生未预期错误: {e}")
finally:
# 6. 清理与退出
logger.info("正在停止任务并清理资源...")
# 尝试安全返回Home点
try:
arm.move_linear(config['waypoints']['home'], speed=0.1)
except:
logger.warning("返回Home点失败")
# 断开连接
arm.disconnect()
# 停止状态监听
state_monitor.stop()
logger.info("程序安全退出")
if __name__ == "__main__":
main()
```
### 5.2 脚本的扩展性与下一步
这个Demo脚本提供了一个坚实的起点。在实际项目中,你可能还需要集成以下功能:
- **视觉引导**:使用OpenCV或深度学习库识别物体位置,动态更新 `pick` 和 `place` 坐标。
- **力传感**:如果机械臂配备六维力传感器,可以实现**力控装配**或**自适应抓取**,在 `close` 动作中根据力矩反馈调整夹持力。
- **多任务协调**:与PLC、传送带、或另一台机械臂通信,实现更复杂的协同作业。
- **图形化界面**:使用PyQt或Tkinter构建一个简单的控制面板,用于手动示教、任务启停和状态监控。
- **日志与数据分析**:将每次循环的运动数据、耗时、成功与否记录到数据库,用于分析优化节拍和稳定性。
开发过程中,最耗时的部分往往是**点位示教与精度调试**。建议先用较低速度运行,仔细观察机械臂轨迹和末端位置,使用示教器的“单步执行”模式配合脚本中的分段运动,逐步微调每个点位的坐标和姿态,直到抓放动作准确可靠。记住,好的程序结构能让调试过程事半功倍。