力传感器,末端参数辨识

This commit is contained in:
ziwei.he 2025-05-13 10:28:00 +08:00
parent 4bf67089fe
commit c42a4000ab
3 changed files with 182 additions and 30505 deletions

View File

@ -880,7 +880,7 @@ class DobotApiDashboard(DobotApi):
可选参数 可选参数
参数名 类型 说明 参数名 类型 说明
User string 格式为"user=index"index为已标定的坐标系索引 User string 格式为"user=index"index为已标定的坐标系索引
Tool string 格式为"tool=index"index为已标定的坐标系索引 Tool string 格式为"tool=index"index为已标定的工具坐标系索引
必须同时传或同时不传不传时默认为全局具坐标系 必须同时传或同时不传不传时默认为全局具坐标系
Get the Cartesian coordinates of the current posture under the specific coordinate system. Get the Cartesian coordinates of the current posture under the specific coordinate system.
Optional parameter: Optional parameter:

View File

@ -0,0 +1,181 @@
# cython: language_level=3
import time
import numpy as np
import atexit
from scipy.spatial.transform import Rotation as R
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).resolve().parent.parent))
from hardware.dobot_nova5 import dobot_nova5
from tools.yaml_operator import *
from hardware.force_sensor import XjcSensor
import os
import math
class Identifier:
"""
reference: https://blog.csdn.net/qq_34935373/article/details/106208345
"""
def __init__(self,arm:dobot_nova5,sensor:XjcSensor,config_path:str,):
self.arm = arm
self.sensor = sensor
self.config_path = config_path
# solve for xyz: M = F @ A, A=[x, y, z, k1, k2, k3]
self.M = None
self.F = None
# solve for F0F1F2: f = R @ B, B=[0, 0, -g, Fx0, Fy0, Fz0]
self.R = None
self.f = None
self.len_data = 0
def __add_data(self, wrench: np.ndarray, ee_pose: np.ndarray):
force = wrench[:3].copy()
torque = wrench[3:].copy()
# import pdb
# pdb.set_trace()
if self.M is None:
self.M = torque.reshape((3, 1))
f_skew = self.__skew_symmetric(force)
self.F = np.hstack((f_skew, np.eye(3)))
else:
self.M = np.vstack((self.M, torque.reshape((3, 1))))
f_skew = self.__skew_symmetric(force)
self.F = np.vstack((self.F, np.hstack((f_skew, np.eye(3)))))
if self.R is None:
self.R = np.hstack((ee_pose.T, np.eye(3)))
self.f = force.reshape((3, 1))
else:
self.R = np.vstack((self.R, np.hstack((ee_pose.T, np.eye(3)))))
self.f = np.vstack((self.f, force.reshape((3, 1))))
self.len_data += 1
def __solve(self):
A, residuals, rank, s = np.linalg.lstsq(self.F, self.M, rcond=None)
B, residuals, rank, s = np.linalg.lstsq(self.R, self.f, rcond=None)
x, y, z, k1, k2, k3 = A.flatten()
Gx, Gy, Gz, Fx0, Fy0, Fz0 = B.flatten()
mass_center = np.array([x, y, z])
F_0 = np.array([Fx0, Fy0, Fz0])
M_x0 = k1 - Fy0 * z + Fz0 * y
M_y0 = k2 - Fz0 * x + Fx0 * z
M_z0 = k3 - Fx0 * y + Fy0 * x
M_0 = np.array([M_x0, M_y0, M_z0])
gravity_base = np.array([Gx, Gy, Gz])
return mass_center, F_0, M_0, gravity_base
def __read_data_once(self,num):
sum = np.zeros(6)
i = 0
# 读取num次数据取平均值
while i < num:
# data = self.sensor.read()
data = self.sensor.read_data_f32()
if data is not None:
sum += data
i += 1
time.sleep(0.2)
return sum/i
def __skew_symmetric(self, v: np.ndarray):
""" 从3维向量生成对应的3x3反对称矩阵 """
if len(v) != 3:
raise ValueError("输入数组的长度必须为3!")
return np.array([[0, v[2], -v[1]],
[-v[2], 0, v[0]],
[v[1], -v[0], 0]])
def set_position(self,x,y,z,roll,pitch,yaw):
'''
x y z : mm
r p y : deg
'''
pose_command = np.concatenate([[x,y,z], [roll,pitch,yaw]])
cur = self.arm.getAngel() # 接口调用: 获取机器人的关节位置
cur_str = "{" + ",".join(map(str, cur)) + "}"
target_J = self.arm.getInverseKin(poses_list=pose_command,useJointNear=1,JointNear=cur_str)
print(target_J)
self.arm.RunPoint_P_inJoint(target_J)
def identify_param_auto(self,ready_pose,cnt,sample_num = 5):
if cnt < 15:
raise ValueError("数据量太少请重新输入至少要有15组数据")
control_pose = np.array(ready_pose)
desired_cart_pose = control_pose.copy()
# move to the initial position
self.set_position(x=desired_cart_pose[0],y=desired_cart_pose[1],z=desired_cart_pose[2],
roll=desired_cart_pose[3],pitch=desired_cart_pose[4],yaw=desired_cart_pose[5])
# self.sensor.set_zero()
time.sleep(1)
delta = 60/cnt*6
quarter = cnt // 6
for i in range(cnt):
if i % 2 == 0:
control_pose[3]+= delta * np.where((i <= quarter) | (3* quarter< i <= 5*quarter) , 1, -1)
else:
control_pose[4]+= delta * np.where((i <= quarter) | (3* quarter< i <= 5*quarter), -1, 1)
desired_cart_pose = control_pose.copy()
print("desired_cart_pose:",desired_cart_pose)
self.set_position(x=desired_cart_pose[0],
y=desired_cart_pose[1],
z=desired_cart_pose[2],
roll=desired_cart_pose[3],
pitch=desired_cart_pose[4],
yaw=desired_cart_pose[5])
print("move done")
time.sleep(1)
wrench = self.__read_data_once(sample_num)
print(f"wrench:{wrench}")
print("read done")
cur_pose = self.arm.getPose()
b_R_e = R.from_euler('xyz', cur_pose[3:], degrees=False).as_matrix()
self.__add_data(wrench, b_R_e)
print(f"=======添加了第{i+1}组数据=======")
time.sleep(0.5)
mass_center, F_0, M_0, gravity_base = self.__solve()
# output all results
print(f"mass_center_position: {mass_center}")
print(f"force_zero: {F_0}")
print(f"torque_zero: {M_0}")
print(f"gravity_base: {gravity_base}")
dict_data = {
'mass_center_position': mass_center.tolist(),
'force_zero': F_0.tolist(),
'torque_zero': M_0.tolist(),
'gravity_base': gravity_base.tolist()
}
confirmation = input("是否确认更新数据?(y/n): ").strip().lower()
if confirmation == 'y' or confirmation == 'Y' or confirmation == 'yes' or confirmation == 'YES':
# 执行更新操作
if not os.path.exists(self.config_path):
# 如果文件不存在则创建一个新的config.yaml文件
with open(self.config_path, 'w') as file:
pass
update_yaml(self.config_path, dict_data)
print("更新操作已完成。")
else:
print("更新操作已取消。")
if __name__ == '__main__':
arm = dobot_nova5()
arm.setEndEffector(i=9,tool_i="x,y,z,rx,ry,rz")
arm.chooseEndEffector(i=9)
arm.init()
sensor = XjcSensor()
sensor.connect()
sensor.disable_active_transmission()
sensor.disable_active_transmission()
sensor.disable_active_transmission()
atexit.register(sensor.disconnect)
identifier = Identifier(arm=arm,sensor=sensor,config_path="/home/jsfb/jsfb_ws/global_config/massage_head/none_playload.yaml")
ready_pose = [-1,-1,-1,-1,-1,-1]
time.sleep(1)
identifier.identify_param_auto(ready_pose,45)

30504
app.log

File diff suppressed because it is too large Load Diff