2025-11-13 03:01:00 +08:00
|
|
|
|
import time
|
|
|
|
|
|
from google.protobuf import timestamp_pb2
|
2026-02-02 09:37:47 +08:00
|
|
|
|
import matplotlib.pyplot as plt # 新增
|
|
|
|
|
|
|
2026-02-03 13:36:52 +08:00
|
|
|
|
from clients._path_setup import ensure_paths
|
|
|
|
|
|
|
|
|
|
|
|
ensure_paths()
|
2025-11-13 03:01:00 +08:00
|
|
|
|
|
|
|
|
|
|
from clients.base_client import RobotClientBase
|
2026-06-24 15:49:54 +08:00
|
|
|
|
from cmvr.api import arm_command_pb2 as pb
|
|
|
|
|
|
from cmvr.api import arm_service_pb2_grpc as rpc
|
2025-11-13 03:01:00 +08:00
|
|
|
|
|
2026-02-02 09:37:47 +08:00
|
|
|
|
# 固定关节顺序
|
|
|
|
|
|
JOINT_ORDER = [
|
|
|
|
|
|
"L_SHOULDER_P", "L_SHOULDER_R", "L_SHOULDER_Y",
|
|
|
|
|
|
"L_ELBOW_R", "L_WRIST_P", "L_WRIST_Y", "L_WRIST_R",
|
|
|
|
|
|
"R_SHOULDER_P", "R_SHOULDER_R", "R_SHOULDER_Y",
|
|
|
|
|
|
"R_ELBOW_R", "R_WRIST_P", "R_WRIST_Y", "R_WRIST_R",
|
|
|
|
|
|
"HEAD_P", "HEAD_Y", "HEAD_R","WAIST_Y","WAIST_R"
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2025-11-13 03:01:00 +08:00
|
|
|
|
|
|
|
|
|
|
class GetJointStateClient(RobotClientBase):
|
|
|
|
|
|
"""Continuous client to fetch robot joint states"""
|
|
|
|
|
|
|
2026-02-02 09:37:47 +08:00
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
# 时间轴
|
|
|
|
|
|
self.time_history = []
|
|
|
|
|
|
self._start_time = None
|
|
|
|
|
|
# 记录每个关节的角度、速度
|
|
|
|
|
|
self.pos_history = {name: [] for name in JOINT_ORDER}
|
|
|
|
|
|
self.vel_history = {name: [] for name in JOINT_ORDER}
|
|
|
|
|
|
|
2026-06-24 15:49:54 +08:00
|
|
|
|
def send(self, interval=0.5, device_id="right_arm"):
|
2025-11-13 03:01:00 +08:00
|
|
|
|
"""Continuously fetch joint states and print in fixed order"""
|
|
|
|
|
|
try:
|
2026-02-02 09:37:47 +08:00
|
|
|
|
if self._start_time is None:
|
|
|
|
|
|
self._start_time = time.time()
|
|
|
|
|
|
|
2025-11-13 03:01:00 +08:00
|
|
|
|
while True:
|
|
|
|
|
|
# Construct request
|
|
|
|
|
|
req = pb.JointRequest()
|
|
|
|
|
|
req.header.device_id = device_id
|
|
|
|
|
|
timestamp = timestamp_pb2.Timestamp()
|
|
|
|
|
|
timestamp.GetCurrentTime()
|
|
|
|
|
|
req.header.timestamp.CopyFrom(timestamp)
|
|
|
|
|
|
|
|
|
|
|
|
# Call RPC with timeout
|
|
|
|
|
|
try:
|
|
|
|
|
|
resp = self.stub.getJointState(req, timeout=10)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"RPC call failed: {e}")
|
|
|
|
|
|
time.sleep(interval)
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
2026-02-02 09:37:47 +08:00
|
|
|
|
# ====== 1. 同时取 position 和 velocity ======
|
|
|
|
|
|
joint_pos_dict = {}
|
|
|
|
|
|
joint_vel_dict = {}
|
2026-06-24 15:49:54 +08:00
|
|
|
|
state = resp.state
|
|
|
|
|
|
for name, pos, vel in zip(state.name, state.position, state.velocity):
|
|
|
|
|
|
joint_pos_dict[name] = round(pos, 12)
|
|
|
|
|
|
joint_vel_dict[name] = round(vel, 12)
|
2025-11-13 03:01:00 +08:00
|
|
|
|
|
2026-02-02 09:37:47 +08:00
|
|
|
|
# ====== 2. 记录到历史数据里,用于之后画图 ======
|
|
|
|
|
|
t = time.time() - self._start_time
|
|
|
|
|
|
self.time_history.append(t)
|
|
|
|
|
|
for name in JOINT_ORDER:
|
|
|
|
|
|
self.pos_history[name].append(joint_pos_dict.get(name, 0.0))
|
|
|
|
|
|
self.vel_history[name].append(joint_vel_dict.get(name, 0.0))
|
|
|
|
|
|
|
|
|
|
|
|
# 保持原来的打印逻辑(打印角度)
|
|
|
|
|
|
joint_list = [{"joint_name": name, "rad": joint_pos_dict.get(name, 0.0)}
|
|
|
|
|
|
for name in JOINT_ORDER]
|
2025-11-13 03:01:00 +08:00
|
|
|
|
|
|
|
|
|
|
# Print timestamp
|
|
|
|
|
|
print(f"[{time.strftime('%H:%M:%S')}]")
|
|
|
|
|
|
# Print each joint, last one without comma
|
|
|
|
|
|
for i, j in enumerate(joint_list):
|
|
|
|
|
|
if i < len(joint_list) - 1:
|
|
|
|
|
|
print(f"{j},")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f"{j}")
|
|
|
|
|
|
|
|
|
|
|
|
time.sleep(interval)
|
|
|
|
|
|
|
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
|
print("\nStopped fetching joint states.")
|
2026-02-02 09:37:47 +08:00
|
|
|
|
# 例子:退出时画几个关节的角度 & 速度曲线
|
2026-02-03 13:36:52 +08:00
|
|
|
|
try:
|
|
|
|
|
|
# 你可以根据需要改关节列表
|
|
|
|
|
|
self.plot_pos_and_vel([ "L_SHOULDER_R"])
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"绘图失败: {e}")
|
2026-02-02 09:37:47 +08:00
|
|
|
|
|
|
|
|
|
|
# ====== 3. 绘制速度和角度(同一张图,两行子图) ======
|
|
|
|
|
|
def plot_pos_and_vel(self, joint_names):
|
|
|
|
|
|
"""
|
|
|
|
|
|
在一张图中绘制多个关节的角度和速度:
|
|
|
|
|
|
- 上 subplot:角度(rad)
|
|
|
|
|
|
- 下 subplot:速度(rad/s)
|
|
|
|
|
|
|
|
|
|
|
|
joint_names: str 或 [str, ...]
|
|
|
|
|
|
"""
|
|
|
|
|
|
if isinstance(joint_names, str):
|
|
|
|
|
|
joint_names = [joint_names]
|
|
|
|
|
|
|
|
|
|
|
|
# 检查关节名合法性
|
|
|
|
|
|
invalid = [n for n in joint_names if n not in self.pos_history]
|
|
|
|
|
|
if invalid:
|
|
|
|
|
|
print(f"未知关节名: {invalid}")
|
|
|
|
|
|
print(f"可选关节: {list(self.pos_history.keys())}")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if not self.time_history:
|
|
|
|
|
|
print("还没有采集到任何数据,无法绘图。")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
t = self.time_history
|
|
|
|
|
|
|
|
|
|
|
|
plt.figure(figsize=(10, 6))
|
|
|
|
|
|
|
|
|
|
|
|
# 上:角度
|
|
|
|
|
|
plt.subplot(2, 1, 1)
|
|
|
|
|
|
for name in joint_names:
|
|
|
|
|
|
plt.plot(t, self.pos_history[name], label=name)
|
|
|
|
|
|
plt.ylabel("Position (rad)")
|
|
|
|
|
|
plt.title("Joint Position")
|
|
|
|
|
|
plt.grid(True)
|
|
|
|
|
|
plt.legend()
|
|
|
|
|
|
|
|
|
|
|
|
# 下:速度
|
|
|
|
|
|
plt.subplot(2, 1, 2)
|
|
|
|
|
|
for name in joint_names:
|
|
|
|
|
|
plt.plot(t, self.vel_history[name], label=name)
|
|
|
|
|
|
plt.xlabel("Time (s)")
|
|
|
|
|
|
plt.ylabel("Velocity (rad/s)")
|
|
|
|
|
|
plt.title("Joint Velocity")
|
|
|
|
|
|
plt.grid(True)
|
|
|
|
|
|
plt.legend()
|
|
|
|
|
|
|
|
|
|
|
|
plt.tight_layout()
|
|
|
|
|
|
plt.show()
|
2025-11-13 03:01:00 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
client = GetJointStateClient()
|
|
|
|
|
|
try:
|
2026-02-02 09:37:47 +08:00
|
|
|
|
client.send(interval=0.005)
|
2025-11-13 03:01:00 +08:00
|
|
|
|
finally:
|
|
|
|
|
|
client.close()
|