165 lines
5.8 KiB
Python
165 lines
5.8 KiB
Python
|
|
|
|||
|
|
import csv
|
|||
|
|
|
|||
|
|
import math
|
|||
|
|
import sys
|
|||
|
|
from google.protobuf import timestamp_pb2
|
|||
|
|
|
|||
|
|
sys.path.append("../generated")
|
|||
|
|
import math
|
|||
|
|
from typing import List, Dict, Literal
|
|||
|
|
from generated.cmvr.api import humanoid_robot_command_pb2 as pb
|
|||
|
|
from generated.cmvr.api import common_pb2
|
|||
|
|
from generated.cmvr.api import humanoid_robot_service_pb2_grpc as rpc
|
|||
|
|
from clients.base_client import RobotClientBase
|
|||
|
|
|
|||
|
|
|
|||
|
|
class MoveJClient(RobotClientBase):
|
|||
|
|
"""Client to send MoveJ commands to the robot"""
|
|||
|
|
|
|||
|
|
def send(self, joint_list, vel=1.0, acc=0.5, device_id="hc01"):
|
|||
|
|
"""
|
|||
|
|
joint_list: list of dicts, e.g.,
|
|||
|
|
[
|
|||
|
|
{"joint_name": "L_SHOULDER_P", "rad": 0.0},
|
|||
|
|
{"joint_name": "L_SHOULDER_R", "rad": -1.31873},
|
|||
|
|
...
|
|||
|
|
]
|
|||
|
|
vel: velocity
|
|||
|
|
acc: acceleration
|
|||
|
|
device_id: device ID
|
|||
|
|
"""
|
|||
|
|
# Construct JointCmd list
|
|||
|
|
cmds = [pb.JointCmd(joint_name=j["joint_name"], rad=j["rad"], vel=vel) for j in joint_list]
|
|||
|
|
|
|||
|
|
# Construct request header
|
|||
|
|
header = common_pb2.CommandHeader.Request()
|
|||
|
|
header.device_id = device_id
|
|||
|
|
ts = timestamp_pb2.Timestamp()
|
|||
|
|
ts.GetCurrentTime()
|
|||
|
|
header.timestamp.CopyFrom(ts)
|
|||
|
|
|
|||
|
|
# Construct MoveJ request
|
|||
|
|
req = pb.MoveJ.Request(
|
|||
|
|
header=header,
|
|||
|
|
vel=vel,
|
|||
|
|
acc=acc,
|
|||
|
|
cmds=cmds
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Call RPC
|
|||
|
|
try:
|
|||
|
|
resp = self.stub.moveJ(req, timeout=10)
|
|||
|
|
success = getattr(resp.header, "success", None)
|
|||
|
|
error_msg = getattr(resp.header, "error_message", "")
|
|||
|
|
timestamp_sec = getattr(resp.header.timestamp, "seconds", 0)
|
|||
|
|
print("MoveJ RPC call succeeded")
|
|||
|
|
print(f"Success: {success}")
|
|||
|
|
print(f"Error message: {error_msg}")
|
|||
|
|
print(f"Timestamp: {timestamp_sec}")
|
|||
|
|
except Exception as e:
|
|||
|
|
print("MoveJ RPC call failed:", e)
|
|||
|
|
|
|||
|
|
def read_joint_from_csv(self, file_name, row_num):
|
|||
|
|
"""Read joint data from a CSV file and return joint list for the specified row."""
|
|||
|
|
joint_order = [
|
|||
|
|
'R_SHOULDER_P', 'R_SHOULDER_R', 'R_SHOULDER_Y',
|
|||
|
|
'R_ELBOW_R', 'R_WRIST_P', 'R_WRIST_Y', 'R_WRIST_R'
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
with open(file_name, newline='') as csvfile:
|
|||
|
|
reader = csv.reader(csvfile)
|
|||
|
|
# Skip header row
|
|||
|
|
next(reader)
|
|||
|
|
# Read specific row
|
|||
|
|
for i, row in enumerate(reader, start=1):
|
|||
|
|
if i == row_num:
|
|||
|
|
# Assign joint values to the list
|
|||
|
|
joint_list = [
|
|||
|
|
{'joint_name': joint_order[j], 'rad': round(float(row[j]) * math.pi / 180, 10)}
|
|||
|
|
for j in range(len(joint_order))
|
|||
|
|
]
|
|||
|
|
return joint_list
|
|||
|
|
print(f"Row {row_num} not found.")
|
|||
|
|
return []
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"Error reading CSV: {e}")
|
|||
|
|
return []
|
|||
|
|
|
|||
|
|
def apply_joint_offsets(self,
|
|||
|
|
joint_list: List[Dict[str, float]],
|
|||
|
|
on_missing: Literal["ignore", "error", "warn"] = "ignore",
|
|||
|
|
) -> List[Dict[str, float]]:
|
|||
|
|
"""
|
|||
|
|
将零偏置 joint_zero_offset 叠加到 joint_list 上,按 joint_name 对齐。
|
|||
|
|
|
|||
|
|
参数:
|
|||
|
|
joint_list: [{'joint_name': str, 'rad': float}, ...] 原始指令
|
|||
|
|
joint_zero_offset: [{'joint_name': str, 'rad': float}, ...] 零偏置
|
|||
|
|
on_missing: 当 joint_list 中出现 zero_offset 里没有的关节名时的处理:
|
|||
|
|
- "ignore": 当作偏置为 0(默认)
|
|||
|
|
- "warn": 打印警告,再当作 0
|
|||
|
|
- "error": 抛出 KeyError
|
|||
|
|
|
|||
|
|
返回:
|
|||
|
|
与 joint_list 顺序一致、已叠加偏置的新列表
|
|||
|
|
"""
|
|||
|
|
# 构造偏置表,并检查是否有重复关节名
|
|||
|
|
|
|||
|
|
deg_to_rad = math.pi / 180.0
|
|||
|
|
|
|||
|
|
joint_zero_offset = [
|
|||
|
|
{'joint_name': 'R_SHOULDER_P', 'rad': 0},
|
|||
|
|
{'joint_name': 'R_SHOULDER_R', 'rad': 0 * deg_to_rad},
|
|||
|
|
{'joint_name': 'R_SHOULDER_Y', 'rad': 0.2 * deg_to_rad},
|
|||
|
|
{'joint_name': 'R_ELBOW_R', 'rad': 0.2 * deg_to_rad},
|
|||
|
|
{'joint_name': 'R_WRIST_P', 'rad': 0.6 * deg_to_rad},
|
|||
|
|
{'joint_name': 'R_WRIST_Y', 'rad': 2.5 * deg_to_rad},
|
|||
|
|
{'joint_name': 'R_WRIST_R', 'rad': 0},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
offset_map: Dict[str, float] = {}
|
|||
|
|
for j in joint_zero_offset:
|
|||
|
|
name = j["joint_name"]
|
|||
|
|
if name in offset_map:
|
|||
|
|
raise ValueError(f"Duplicated joint in zero_offset: {name}")
|
|||
|
|
offset_map[name] = float(j["rad"])
|
|||
|
|
|
|||
|
|
joint_cmd: List[Dict[str, float]] = []
|
|||
|
|
for j in joint_list:
|
|||
|
|
name = j["joint_name"]
|
|||
|
|
base = float(j["rad"])
|
|||
|
|
if name not in offset_map:
|
|||
|
|
if on_missing == "error":
|
|||
|
|
raise KeyError(f"Missing zero offset for joint: {name}")
|
|||
|
|
elif on_missing == "warn":
|
|||
|
|
print(f"[apply_joint_offsets] WARN: Missing zero offset for {name}, using 0.0")
|
|||
|
|
off = 0.0
|
|||
|
|
else:
|
|||
|
|
off = offset_map[name]
|
|||
|
|
|
|||
|
|
joint_cmd.append({"joint_name": name, "rad": base + off})
|
|||
|
|
return joint_cmd
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
# Initialize client
|
|||
|
|
client = MoveJClient()
|
|||
|
|
|
|||
|
|
# Specify CSV file and row number to read from
|
|||
|
|
file_name = 'joint_states.csv'
|
|||
|
|
row_num = 2 # Row number you want to read (e.g., 2 for the second row)
|
|||
|
|
|
|||
|
|
# Read joint data from CSV
|
|||
|
|
joint_list = client.read_joint_from_csv(file_name, 8)
|
|||
|
|
joint_list = client.apply_joint_offsets(joint_list)
|
|||
|
|
print(joint_list)
|
|||
|
|
|
|||
|
|
if joint_list:
|
|||
|
|
|
|||
|
|
# Send MoveJ command with the read joint data
|
|||
|
|
client.send(joint_list, vel=0.8, acc=0.8)
|
|||
|
|
|
|||
|
|
# Close client
|
|||
|
|
client.close()
|