Georg commited on
Commit ·
ec12dc0
1
Parent(s): 4f6eb67
Add scene object extraction and update state broadcasting in mujoco_server.py
Browse files- Introduced a new function `_get_scene_objects()` to extract position and orientation data for scene objects, specifically for UR5 scenes.
- Enhanced the `broadcast_state()` function to include scene object information in the state broadcast to connected clients.
- Updated `protocol_types.py` to define a new `SceneObject` type and included a list of scene objects in the `StateData` structure.
- Modified `README.md` to document the new `scene_objects` field, detailing its structure and usage.
- README.md +4 -0
- mujoco_server.py +60 -2
- protocol_types.py +8 -0
README.md
CHANGED
|
@@ -646,6 +646,10 @@ For robot arm (UR5):
|
|
| 646 |
- Locomotion robots: Use `vx`, `vy`, `vyaw` (other fields are 0)
|
| 647 |
- UR5: Use `vx`/`vy`/`vz` for Cartesian translation, `vrx`/`vry`/`vrz` for rotation, `j1`-`j6` for joint velocities, and `gripper` for gripper control
|
| 648 |
- `connected_clients`: List of connected external client IDs (e.g., `["trainer_v1", "monitor"]`)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 649 |
|
| 650 |
*Locomotion observation fields (inside `observation`):*
|
| 651 |
- `position`: Robot base position in world coordinates (x, y, z) in meters
|
|
|
|
| 646 |
- Locomotion robots: Use `vx`, `vy`, `vyaw` (other fields are 0)
|
| 647 |
- UR5: Use `vx`/`vy`/`vz` for Cartesian translation, `vrx`/`vry`/`vrz` for rotation, `j1`-`j6` for joint velocities, and `gripper` for gripper control
|
| 648 |
- `connected_clients`: List of connected external client IDs (e.g., `["trainer_v1", "monitor"]`)
|
| 649 |
+
- `scene_objects`: List of scene objects with their positions and orientations. Each object has:
|
| 650 |
+
- `name`: Object identifier (e.g., "t_object", "t_target", "box")
|
| 651 |
+
- `position`: Object position in meters (x, y, z)
|
| 652 |
+
- `orientation`: Object orientation as quaternion (w, x, y, z)
|
| 653 |
|
| 654 |
*Locomotion observation fields (inside `observation`):*
|
| 655 |
- `position`: Robot base position in world coordinates (x, y, z) in meters
|
mujoco_server.py
CHANGED
|
@@ -554,6 +554,54 @@ def switch_robot(robot_type, scene_name=None):
|
|
| 554 |
env = init_g1()
|
| 555 |
|
| 556 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 557 |
def broadcast_state():
|
| 558 |
"""Broadcast robot state to all connected WebSocket clients."""
|
| 559 |
with mujoco_lock:
|
|
@@ -577,6 +625,9 @@ def broadcast_state():
|
|
| 577 |
with external_ws_clients_lock:
|
| 578 |
connected_clients = [info.get("identity", "client") for ws, info in list(client_metadata.items()) if ws in external_ws_clients]
|
| 579 |
|
|
|
|
|
|
|
|
|
|
| 580 |
# UR5 has different state structure
|
| 581 |
if current_robot in ("ur5", "ur5_t_push"):
|
| 582 |
ee_pos = env.get_end_effector_pos()
|
|
@@ -592,8 +643,13 @@ def broadcast_state():
|
|
| 592 |
nova_client = getattr(env, '_nova_client', None)
|
| 593 |
nova_state_streaming = getattr(env, '_use_nova_state_stream', False)
|
| 594 |
nova_ik = getattr(env, '_use_nova_ik', False)
|
| 595 |
-
#
|
| 596 |
-
nova_connected =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 597 |
|
| 598 |
nova_available = False
|
| 599 |
nova_enabled_pref = False
|
|
@@ -623,6 +679,7 @@ def broadcast_state():
|
|
| 623 |
'steps': int(steps),
|
| 624 |
'reward': reward_value,
|
| 625 |
'teleop_action': teleop_snapshot,
|
|
|
|
| 626 |
'nova_api': {
|
| 627 |
'connected': nova_connected,
|
| 628 |
'state_streaming': nova_state_streaming,
|
|
@@ -650,6 +707,7 @@ def broadcast_state():
|
|
| 650 |
'steps': int(steps),
|
| 651 |
'reward': reward_value,
|
| 652 |
'teleop_action': teleop_snapshot,
|
|
|
|
| 653 |
'connected_clients': connected_clients
|
| 654 |
}
|
| 655 |
})
|
|
|
|
| 554 |
env = init_g1()
|
| 555 |
|
| 556 |
|
| 557 |
+
def _get_scene_objects():
|
| 558 |
+
"""Extract scene objects (position and orientation) from the environment."""
|
| 559 |
+
if env is None:
|
| 560 |
+
return []
|
| 561 |
+
|
| 562 |
+
scene_objects = []
|
| 563 |
+
|
| 564 |
+
# For UR5 scenes, extract T-shape objects if they exist
|
| 565 |
+
if current_robot in ("ur5", "ur5_t_push"):
|
| 566 |
+
# Get t_object body if it exists
|
| 567 |
+
t_object_id = getattr(env, 't_object_body_id', -1)
|
| 568 |
+
if t_object_id >= 0:
|
| 569 |
+
pos = env.data.xpos[t_object_id]
|
| 570 |
+
quat = env._get_body_quat(t_object_id) if hasattr(env, '_get_body_quat') else np.array([1.0, 0.0, 0.0, 0.0])
|
| 571 |
+
scene_objects.append({
|
| 572 |
+
'name': 't_object',
|
| 573 |
+
'position': {'x': float(pos[0]), 'y': float(pos[1]), 'z': float(pos[2])},
|
| 574 |
+
'orientation': {'w': float(quat[0]), 'x': float(quat[1]), 'y': float(quat[2]), 'z': float(quat[3])}
|
| 575 |
+
})
|
| 576 |
+
|
| 577 |
+
# Get t_target body if it exists
|
| 578 |
+
t_target_id = getattr(env, 't_target_body_id', -1)
|
| 579 |
+
if t_target_id >= 0:
|
| 580 |
+
pos = env.data.xpos[t_target_id]
|
| 581 |
+
quat = env._get_body_quat(t_target_id) if hasattr(env, '_get_body_quat') else np.array([1.0, 0.0, 0.0, 0.0])
|
| 582 |
+
scene_objects.append({
|
| 583 |
+
'name': 't_target',
|
| 584 |
+
'position': {'x': float(pos[0]), 'y': float(pos[1]), 'z': float(pos[2])},
|
| 585 |
+
'orientation': {'w': float(quat[0]), 'x': float(quat[1]), 'y': float(quat[2]), 'z': float(quat[3])}
|
| 586 |
+
})
|
| 587 |
+
|
| 588 |
+
# Get box body if it exists
|
| 589 |
+
try:
|
| 590 |
+
box_id = mujoco.mj_name2id(env.model, mujoco.mjtObj.mjOBJ_BODY, "box")
|
| 591 |
+
if box_id >= 0:
|
| 592 |
+
pos = env.data.xpos[box_id]
|
| 593 |
+
quat = env._get_body_quat(box_id) if hasattr(env, '_get_body_quat') else np.array([1.0, 0.0, 0.0, 0.0])
|
| 594 |
+
scene_objects.append({
|
| 595 |
+
'name': 'box',
|
| 596 |
+
'position': {'x': float(pos[0]), 'y': float(pos[1]), 'z': float(pos[2])},
|
| 597 |
+
'orientation': {'w': float(quat[0]), 'x': float(quat[1]), 'y': float(quat[2]), 'z': float(quat[3])}
|
| 598 |
+
})
|
| 599 |
+
except:
|
| 600 |
+
pass
|
| 601 |
+
|
| 602 |
+
return scene_objects
|
| 603 |
+
|
| 604 |
+
|
| 605 |
def broadcast_state():
|
| 606 |
"""Broadcast robot state to all connected WebSocket clients."""
|
| 607 |
with mujoco_lock:
|
|
|
|
| 625 |
with external_ws_clients_lock:
|
| 626 |
connected_clients = [info.get("identity", "client") for ws, info in list(client_metadata.items()) if ws in external_ws_clients]
|
| 627 |
|
| 628 |
+
# Get scene objects
|
| 629 |
+
scene_objects = _get_scene_objects()
|
| 630 |
+
|
| 631 |
# UR5 has different state structure
|
| 632 |
if current_robot in ("ur5", "ur5_t_push"):
|
| 633 |
ee_pos = env.get_end_effector_pos()
|
|
|
|
| 643 |
nova_client = getattr(env, '_nova_client', None)
|
| 644 |
nova_state_streaming = getattr(env, '_use_nova_state_stream', False)
|
| 645 |
nova_ik = getattr(env, '_use_nova_ik', False)
|
| 646 |
+
# Check if actually connected (WebSocket active and receiving data)
|
| 647 |
+
nova_connected = False
|
| 648 |
+
if nova_client is not None and hasattr(nova_client, 'is_state_stream_connected'):
|
| 649 |
+
nova_connected = nova_client.is_state_stream_connected()
|
| 650 |
+
elif nova_client is not None and not nova_state_streaming:
|
| 651 |
+
# If not using state streaming, consider connected if client exists
|
| 652 |
+
nova_connected = True
|
| 653 |
|
| 654 |
nova_available = False
|
| 655 |
nova_enabled_pref = False
|
|
|
|
| 679 |
'steps': int(steps),
|
| 680 |
'reward': reward_value,
|
| 681 |
'teleop_action': teleop_snapshot,
|
| 682 |
+
'scene_objects': scene_objects,
|
| 683 |
'nova_api': {
|
| 684 |
'connected': nova_connected,
|
| 685 |
'state_streaming': nova_state_streaming,
|
|
|
|
| 707 |
'steps': int(steps),
|
| 708 |
'reward': reward_value,
|
| 709 |
'teleop_action': teleop_snapshot,
|
| 710 |
+
'scene_objects': scene_objects,
|
| 711 |
'connected_clients': connected_clients
|
| 712 |
}
|
| 713 |
})
|
protocol_types.py
CHANGED
|
@@ -325,6 +325,13 @@ class EulerAngles(TypedDict):
|
|
| 325 |
yaw: float
|
| 326 |
|
| 327 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
class LocomotionObservation(TypedDict):
|
| 329 |
"""Observation data for locomotion robots (G1, Spot)."""
|
| 330 |
position: Position
|
|
@@ -359,6 +366,7 @@ class StateData(TypedDict, total=False):
|
|
| 359 |
reward: float
|
| 360 |
teleop_action: ActionData # Current action/velocity commands
|
| 361 |
connected_clients: List[str] # List of connected client IDs
|
|
|
|
| 362 |
|
| 363 |
# UR5-specific fields
|
| 364 |
control_mode: ControlMode
|
|
|
|
| 325 |
yaw: float
|
| 326 |
|
| 327 |
|
| 328 |
+
class SceneObject(TypedDict):
|
| 329 |
+
"""Scene object information (position and orientation)."""
|
| 330 |
+
name: str
|
| 331 |
+
position: Position
|
| 332 |
+
orientation: Quaternion
|
| 333 |
+
|
| 334 |
+
|
| 335 |
class LocomotionObservation(TypedDict):
|
| 336 |
"""Observation data for locomotion robots (G1, Spot)."""
|
| 337 |
position: Position
|
|
|
|
| 366 |
reward: float
|
| 367 |
teleop_action: ActionData # Current action/velocity commands
|
| 368 |
connected_clients: List[str] # List of connected client IDs
|
| 369 |
+
scene_objects: List[SceneObject] # Scene objects (position and orientation)
|
| 370 |
|
| 371 |
# UR5-specific fields
|
| 372 |
control_mode: ControlMode
|