力传感器,末端参数辨识
This commit is contained in:
parent
4bf67089fe
commit
c42a4000ab
@ -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:
|
||||||
|
181
Massage/MassageControl/tools/identifier_dobot.py
Normal file
181
Massage/MassageControl/tools/identifier_dobot.py
Normal 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)
|
Loading…
x
Reference in New Issue
Block a user