Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +7 -0
- GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_ankle_pitch_link.stl +3 -0
- GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_hip_pitch_link.stl +3 -0
- GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_shoulder_pitch_link.stl +3 -0
- GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_shoulder_roll_link.stl +3 -0
- GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_shoulder_yaw_link.stl +3 -0
- GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_wrist_roll_link.stl +3 -0
- GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/right_knee_link.stl +3 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/policy/release/observation_config.yaml +80 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/policy/release/observation_config_low_latency.yaml +92 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/policy/release/observation_config_sonic_release.yaml +79 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/policy/release/observation_config_sonic_v1_1.yaml +74 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/policy/sonic_v1_1/observation_config.yaml +74 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/gamepad.hpp +777 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/gamepad_manager.hpp +1078 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/input_command.hpp +108 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/input_interface.hpp +505 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/interface_manager.hpp +396 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/keyboard_handler.hpp +701 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/ros2_input_handler.hpp +1278 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/streamed_motion_merger.hpp +517 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/zmq_endpoint_interface.hpp +1865 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/zmq_manager.hpp +1257 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/zmq_packed_message_subscriber.hpp +439 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/output_interface/zmq_output_handler.hpp +423 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/.devcontainer/Dockerfile.devcontainer +35 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/.devcontainer/devcontainer.json +29 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/.devcontainer/docker-compose.yml +25 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/.gitignore +8 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/CMakeLists.txt +82 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/LICENSE +29 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/README.md +60 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/cmake/unitree_sdk2Config.cmake.in +7 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/cmake/unitree_sdk2Targets.cmake +96 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/CMakeLists.txt +12 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2/CMakeLists.txt +5 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2/b2_sport_client.cpp +24 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2/b2_stand_example.cpp +350 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2w/CMakeLists.txt +5 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2w/b2w_sport_client.cpp +170 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2w/b2w_stand_example.cpp +372 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/CMakeLists.txt +38 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/audio/g1_audio_client_example.cpp +174 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/audio/wav.hpp +232 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/dex3/g1_dex3_example.cpp +326 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/high_level/g1_arm5_sdk_dds_example.cpp +256 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/high_level/g1_arm7_sdk_dds_example.cpp +262 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/high_level/g1_arm_action_example.cpp +80 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/high_level/g1_loco_client_example.cpp +267 -0
- GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/low_level/behavior_lib/motion.seq +0 -0
.gitattributes
CHANGED
|
@@ -544,3 +544,10 @@ GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/meshes/g1/left_w
|
|
| 544 |
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/meshes/g1/pelvis_contour_link.STL filter=lfs diff=lfs merge=lfs -text
|
| 545 |
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/meshes/g1/right_hand_middle_0_link.STL filter=lfs diff=lfs merge=lfs -text
|
| 546 |
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/meshes/g1/NVBP.STL filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 544 |
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/meshes/g1/pelvis_contour_link.STL filter=lfs diff=lfs merge=lfs -text
|
| 545 |
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/meshes/g1/right_hand_middle_0_link.STL filter=lfs diff=lfs merge=lfs -text
|
| 546 |
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/meshes/g1/NVBP.STL filter=lfs diff=lfs merge=lfs -text
|
| 547 |
+
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_shoulder_roll_link.stl filter=lfs diff=lfs merge=lfs -text
|
| 548 |
+
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_ankle_pitch_link.stl filter=lfs diff=lfs merge=lfs -text
|
| 549 |
+
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/right_knee_link.stl filter=lfs diff=lfs merge=lfs -text
|
| 550 |
+
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_shoulder_yaw_link.stl filter=lfs diff=lfs merge=lfs -text
|
| 551 |
+
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_wrist_roll_link.stl filter=lfs diff=lfs merge=lfs -text
|
| 552 |
+
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_hip_pitch_link.stl filter=lfs diff=lfs merge=lfs -text
|
| 553 |
+
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_shoulder_pitch_link.stl filter=lfs diff=lfs merge=lfs -text
|
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_ankle_pitch_link.stl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:42bf18934f633451187365d240b97b0412041e06d3583ec2e93d9e84d0e51f8f
|
| 3 |
+
size 430684
|
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_hip_pitch_link.stl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ca7361b81500c3d738bc40fb40c624294b19a3147d6334e888d020ea2be6c0cb
|
| 3 |
+
size 1619884
|
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_shoulder_pitch_link.stl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0f70851b1ba071d00504fed43f213937d250bf61524bb2e6efbec4cf32052a06
|
| 3 |
+
size 9459584
|
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_shoulder_roll_link.stl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9994d592925ff408ec0590fe438d74045ea86ae83d7e2d620741811bcc002d66
|
| 3 |
+
size 749884
|
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_shoulder_yaw_link.stl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0a183a0969a0f5e8fe6364515ab8cebfef9498a267c2e0541213d7e2098b921b
|
| 3 |
+
size 1202984
|
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_wrist_roll_link.stl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:aac4f99935429b9bedc1a1df7e664885a9406c99cc692a1fc3556c59e475bb12
|
| 3 |
+
size 622484
|
GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/right_knee_link.stl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3cce66e474d1679da1ba570804ee10b2cd0adfddbb6e5bfd3d32b42ac3806d64
|
| 3 |
+
size 747184
|
GR00T-WholeBodyControl/gear_sonic_deploy/policy/release/observation_config.yaml
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# G1 Deploy - Observation Configuration
|
| 2 |
+
# ====================================
|
| 3 |
+
# Total dimension: 436 (64+12+116+116+116+12)
|
| 4 |
+
|
| 5 |
+
observations:
|
| 6 |
+
|
| 7 |
+
- name: "token_state"
|
| 8 |
+
enabled: true
|
| 9 |
+
|
| 10 |
+
- name: "his_base_angular_velocity_10frame_step1"
|
| 11 |
+
enabled: true
|
| 12 |
+
|
| 13 |
+
- name: "his_body_joint_positions_10frame_step1"
|
| 14 |
+
enabled: true
|
| 15 |
+
|
| 16 |
+
- name: "his_body_joint_velocities_10frame_step1"
|
| 17 |
+
enabled: true
|
| 18 |
+
|
| 19 |
+
- name: "his_last_actions_10frame_step1"
|
| 20 |
+
enabled: true
|
| 21 |
+
|
| 22 |
+
- name: "his_gravity_dir_10frame_step1"
|
| 23 |
+
enabled: true
|
| 24 |
+
|
| 25 |
+
encoder:
|
| 26 |
+
dimension: 64
|
| 27 |
+
use_fp16: false
|
| 28 |
+
encoder_observations:
|
| 29 |
+
- name: "encoder_mode_4"
|
| 30 |
+
enabled: true
|
| 31 |
+
- name: "motion_joint_positions_10frame_step5"
|
| 32 |
+
enabled: true
|
| 33 |
+
- name: "motion_joint_velocities_10frame_step5"
|
| 34 |
+
enabled: true
|
| 35 |
+
- name: "motion_root_z_position_10frame_step5"
|
| 36 |
+
enabled: true
|
| 37 |
+
- name: "motion_root_z_position"
|
| 38 |
+
enabled: true
|
| 39 |
+
- name: "motion_anchor_orientation"
|
| 40 |
+
enabled: true
|
| 41 |
+
- name: "motion_anchor_orientation_10frame_step5"
|
| 42 |
+
enabled: true
|
| 43 |
+
- name: "motion_joint_positions_lowerbody_10frame_step5"
|
| 44 |
+
enabled: true
|
| 45 |
+
- name: "motion_joint_velocities_lowerbody_10frame_step5"
|
| 46 |
+
enabled: true
|
| 47 |
+
- name: "vr_3point_local_target"
|
| 48 |
+
enabled: true
|
| 49 |
+
- name: "vr_3point_local_orn_target"
|
| 50 |
+
enabled: true
|
| 51 |
+
- name: "smpl_joints_10frame_step1"
|
| 52 |
+
enabled: true
|
| 53 |
+
- name: "smpl_anchor_orientation_10frame_step1"
|
| 54 |
+
enabled: true
|
| 55 |
+
- name: "motion_joint_positions_wrists_10frame_step1"
|
| 56 |
+
enabled: true
|
| 57 |
+
encoder_modes:
|
| 58 |
+
- name: "g1"
|
| 59 |
+
mode_id: 0
|
| 60 |
+
required_observations:
|
| 61 |
+
- encoder_mode_4
|
| 62 |
+
- motion_joint_positions_10frame_step5
|
| 63 |
+
- motion_joint_velocities_10frame_step5
|
| 64 |
+
- motion_anchor_orientation_10frame_step5
|
| 65 |
+
- name: "teleop"
|
| 66 |
+
mode_id: 1
|
| 67 |
+
required_observations:
|
| 68 |
+
- encoder_mode_4
|
| 69 |
+
- motion_joint_positions_lowerbody_10frame_step5
|
| 70 |
+
- motion_joint_velocities_lowerbody_10frame_step5
|
| 71 |
+
- vr_3point_local_target
|
| 72 |
+
- vr_3point_local_orn_target
|
| 73 |
+
- motion_anchor_orientation
|
| 74 |
+
- name: "smpl"
|
| 75 |
+
mode_id: 2
|
| 76 |
+
required_observations:
|
| 77 |
+
- encoder_mode_4
|
| 78 |
+
- smpl_joints_10frame_step1
|
| 79 |
+
- smpl_anchor_orientation_10frame_step1
|
| 80 |
+
- motion_joint_positions_wrists_10frame_step1
|
GR00T-WholeBodyControl/gear_sonic_deploy/policy/release/observation_config_low_latency.yaml
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SONIC Release (Low-Latency) - Observation Configuration
|
| 2 |
+
# =======================================================
|
| 3 |
+
# For ONNX models exported from the low-latency sonic_release checkpoint
|
| 4 |
+
# (smpl 4-frame lookahead; g1/teleop lookahead at step1, body-frame orientation).
|
| 5 |
+
#
|
| 6 |
+
# Encoder input dimension: 1247 Decoder/policy dimension: 994 token: 64
|
| 7 |
+
# Uses the unitoken_all_noz tokenizer config (3 encoders: g1, teleop, smpl)
|
| 8 |
+
#
|
| 9 |
+
# Requires the smpl_*_4frame_step1 entries in GetObservationRegistry()
|
| 10 |
+
# (src/g1/g1_deploy_onnx_ref/src/g1_deploy_onnx_ref.cpp) + a rebuild to load.
|
| 11 |
+
|
| 12 |
+
observations:
|
| 13 |
+
|
| 14 |
+
- name: "token_state"
|
| 15 |
+
enabled: true
|
| 16 |
+
|
| 17 |
+
- name: "his_base_angular_velocity_10frame_step1"
|
| 18 |
+
enabled: true
|
| 19 |
+
|
| 20 |
+
- name: "his_body_joint_positions_10frame_step1"
|
| 21 |
+
enabled: true
|
| 22 |
+
|
| 23 |
+
- name: "his_body_joint_velocities_10frame_step1"
|
| 24 |
+
enabled: true
|
| 25 |
+
|
| 26 |
+
- name: "his_last_actions_10frame_step1"
|
| 27 |
+
enabled: true
|
| 28 |
+
|
| 29 |
+
- name: "his_gravity_dir_10frame_step1"
|
| 30 |
+
enabled: true
|
| 31 |
+
|
| 32 |
+
encoder:
|
| 33 |
+
dimension: 64
|
| 34 |
+
use_fp16: false
|
| 35 |
+
encoder_observations:
|
| 36 |
+
# Shared
|
| 37 |
+
- name: "encoder_mode_4"
|
| 38 |
+
enabled: true
|
| 39 |
+
# g1 encoder: command_multi_future_nonflat (positions + velocities), step1
|
| 40 |
+
- name: "motion_joint_positions_10frame_step1"
|
| 41 |
+
enabled: true
|
| 42 |
+
- name: "motion_joint_velocities_10frame_step1"
|
| 43 |
+
enabled: true
|
| 44 |
+
# g1 encoder: motion_anchor_ori_b_mf_nonflat (body, multi-frame FIRST), step1
|
| 45 |
+
- name: "motion_anchor_orientation_10frame_step1"
|
| 46 |
+
enabled: true
|
| 47 |
+
# teleop encoder: motion_anchor_ori_b (body, single-frame SECOND)
|
| 48 |
+
- name: "motion_anchor_orientation"
|
| 49 |
+
enabled: true
|
| 50 |
+
# teleop encoder: command_multi_future_lower_body, step1
|
| 51 |
+
- name: "motion_joint_positions_lowerbody_10frame_step1"
|
| 52 |
+
enabled: true
|
| 53 |
+
- name: "motion_joint_velocities_lowerbody_10frame_step1"
|
| 54 |
+
enabled: true
|
| 55 |
+
# teleop encoder: vr_3point
|
| 56 |
+
- name: "vr_3point_local_target"
|
| 57 |
+
enabled: true
|
| 58 |
+
- name: "vr_3point_local_orn_target"
|
| 59 |
+
enabled: true
|
| 60 |
+
# smpl encoder: smpl_joints_multi_future_local_nonflat (4 frames, step1) <-- LOW-LATENCY
|
| 61 |
+
- name: "smpl_joints_4frame_step1"
|
| 62 |
+
enabled: true
|
| 63 |
+
# smpl encoder: smpl_root_ori_b_multi_future (body, 4 frames, step1) <-- LOW-LATENCY
|
| 64 |
+
- name: "smpl_anchor_orientation_4frame_step1"
|
| 65 |
+
enabled: true
|
| 66 |
+
# smpl encoder: joint_pos_multi_future_wrist_for_smpl (4 frames, step1) <-- LOW-LATENCY
|
| 67 |
+
- name: "motion_joint_positions_wrists_4frame_step1"
|
| 68 |
+
enabled: true
|
| 69 |
+
encoder_modes:
|
| 70 |
+
- name: "g1"
|
| 71 |
+
mode_id: 0
|
| 72 |
+
required_observations:
|
| 73 |
+
- encoder_mode_4
|
| 74 |
+
- motion_joint_positions_10frame_step1
|
| 75 |
+
- motion_joint_velocities_10frame_step1
|
| 76 |
+
- motion_anchor_orientation_10frame_step1
|
| 77 |
+
- name: "teleop"
|
| 78 |
+
mode_id: 1
|
| 79 |
+
required_observations:
|
| 80 |
+
- encoder_mode_4
|
| 81 |
+
- motion_joint_positions_lowerbody_10frame_step1
|
| 82 |
+
- motion_joint_velocities_lowerbody_10frame_step1
|
| 83 |
+
- vr_3point_local_target
|
| 84 |
+
- vr_3point_local_orn_target
|
| 85 |
+
- motion_anchor_orientation
|
| 86 |
+
- name: "smpl"
|
| 87 |
+
mode_id: 2
|
| 88 |
+
required_observations:
|
| 89 |
+
- encoder_mode_4
|
| 90 |
+
- smpl_joints_4frame_step1
|
| 91 |
+
- smpl_anchor_orientation_4frame_step1
|
| 92 |
+
- motion_joint_positions_wrists_4frame_step1
|
GR00T-WholeBodyControl/gear_sonic_deploy/policy/release/observation_config_sonic_release.yaml
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SONIC Release - Observation Configuration
|
| 2 |
+
# ==========================================
|
| 3 |
+
# For ONNX models exported from sonic_release checkpoint via:
|
| 4 |
+
# python gear_sonic/eval_agent_trl.py +checkpoint=sonic_release/last.pt \
|
| 5 |
+
# +headless=True ++num_envs=1 +export_onnx_only=true
|
| 6 |
+
#
|
| 7 |
+
# Encoder input dimension: 1751 (1 encoder_index + 1750 tokenizer obs)
|
| 8 |
+
# Uses the unitoken_all_noz tokenizer config (3 encoders: g1, teleop, smpl)
|
| 9 |
+
|
| 10 |
+
observations:
|
| 11 |
+
|
| 12 |
+
- name: "token_state"
|
| 13 |
+
enabled: true
|
| 14 |
+
|
| 15 |
+
- name: "his_base_angular_velocity_10frame_step1"
|
| 16 |
+
enabled: true
|
| 17 |
+
|
| 18 |
+
- name: "his_body_joint_positions_10frame_step1"
|
| 19 |
+
enabled: true
|
| 20 |
+
|
| 21 |
+
- name: "his_body_joint_velocities_10frame_step1"
|
| 22 |
+
enabled: true
|
| 23 |
+
|
| 24 |
+
- name: "his_last_actions_10frame_step1"
|
| 25 |
+
enabled: true
|
| 26 |
+
|
| 27 |
+
- name: "his_gravity_dir_10frame_step1"
|
| 28 |
+
enabled: true
|
| 29 |
+
|
| 30 |
+
encoder:
|
| 31 |
+
dimension: 64
|
| 32 |
+
use_fp16: false
|
| 33 |
+
encoder_observations:
|
| 34 |
+
# Used by all encoder modes
|
| 35 |
+
- name: "encoder_index"
|
| 36 |
+
enabled: true
|
| 37 |
+
# G1 mode
|
| 38 |
+
- name: "command_multi_future_nonflat"
|
| 39 |
+
enabled: true
|
| 40 |
+
- name: "motion_anchor_ori_b_mf_nonflat"
|
| 41 |
+
enabled: true
|
| 42 |
+
# Teleop mode
|
| 43 |
+
- name: "command_multi_future_lower_body"
|
| 44 |
+
enabled: true
|
| 45 |
+
- name: "vr_3point_local_target"
|
| 46 |
+
enabled: true
|
| 47 |
+
- name: "vr_3point_local_orn_target"
|
| 48 |
+
enabled: true
|
| 49 |
+
- name: "motion_anchor_ori_b"
|
| 50 |
+
enabled: true
|
| 51 |
+
# SMPL mode
|
| 52 |
+
- name: "smpl_joints_multi_future_local_nonflat"
|
| 53 |
+
enabled: true
|
| 54 |
+
- name: "smpl_root_ori_b_multi_future"
|
| 55 |
+
enabled: true
|
| 56 |
+
- name: "joint_pos_multi_future_wrist_for_smpl"
|
| 57 |
+
enabled: true
|
| 58 |
+
encoder_modes:
|
| 59 |
+
- name: "g1"
|
| 60 |
+
mode_id: 0
|
| 61 |
+
required_observations:
|
| 62 |
+
- encoder_index
|
| 63 |
+
- command_multi_future_nonflat
|
| 64 |
+
- motion_anchor_ori_b_mf_nonflat
|
| 65 |
+
- name: "teleop"
|
| 66 |
+
mode_id: 1
|
| 67 |
+
required_observations:
|
| 68 |
+
- encoder_index
|
| 69 |
+
- command_multi_future_lower_body
|
| 70 |
+
- vr_3point_local_target
|
| 71 |
+
- vr_3point_local_orn_target
|
| 72 |
+
- motion_anchor_ori_b
|
| 73 |
+
- name: "smpl"
|
| 74 |
+
mode_id: 2
|
| 75 |
+
required_observations:
|
| 76 |
+
- encoder_index
|
| 77 |
+
- smpl_joints_multi_future_local_nonflat
|
| 78 |
+
- smpl_root_ori_b_multi_future
|
| 79 |
+
- joint_pos_multi_future_wrist_for_smpl
|
GR00T-WholeBodyControl/gear_sonic_deploy/policy/release/observation_config_sonic_v1_1.yaml
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SONIC v1.1 - Observation Configuration
|
| 2 |
+
# =================================================
|
| 3 |
+
# Matches sonic_v1_1/model_encoder.onnx and model_decoder.onnx.
|
| 4 |
+
#
|
| 5 |
+
# Encoder input: 1751 Decoder input: 994 Token: 64
|
| 6 |
+
# G1/teleop references use 10 frames at step5. SMPL and wrist references use
|
| 7 |
+
# 10 frames at step1. Root orientations are normalized by robot heading.
|
| 8 |
+
|
| 9 |
+
observations:
|
| 10 |
+
- name: "token_state"
|
| 11 |
+
enabled: true
|
| 12 |
+
- name: "his_base_angular_velocity_10frame_step1"
|
| 13 |
+
enabled: true
|
| 14 |
+
- name: "his_body_joint_positions_10frame_step1"
|
| 15 |
+
enabled: true
|
| 16 |
+
- name: "his_body_joint_velocities_10frame_step1"
|
| 17 |
+
enabled: true
|
| 18 |
+
- name: "his_last_actions_10frame_step1"
|
| 19 |
+
enabled: true
|
| 20 |
+
- name: "his_gravity_dir_10frame_step1"
|
| 21 |
+
enabled: true
|
| 22 |
+
|
| 23 |
+
encoder:
|
| 24 |
+
dimension: 64
|
| 25 |
+
use_fp16: false
|
| 26 |
+
encoder_observations:
|
| 27 |
+
- name: "encoder_mode_4"
|
| 28 |
+
enabled: true
|
| 29 |
+
- name: "motion_joint_positions_10frame_step5"
|
| 30 |
+
enabled: true
|
| 31 |
+
- name: "motion_joint_velocities_10frame_step5"
|
| 32 |
+
enabled: true
|
| 33 |
+
- name: "motion_anchor_orientation_heading_10frame_step5"
|
| 34 |
+
enabled: true
|
| 35 |
+
- name: "motion_anchor_orientation_heading"
|
| 36 |
+
enabled: true
|
| 37 |
+
- name: "motion_joint_positions_lowerbody_10frame_step5"
|
| 38 |
+
enabled: true
|
| 39 |
+
- name: "motion_joint_velocities_lowerbody_10frame_step5"
|
| 40 |
+
enabled: true
|
| 41 |
+
- name: "vr_3point_local_target"
|
| 42 |
+
enabled: true
|
| 43 |
+
- name: "vr_3point_local_orn_target"
|
| 44 |
+
enabled: true
|
| 45 |
+
- name: "smpl_joints_10frame_step1"
|
| 46 |
+
enabled: true
|
| 47 |
+
- name: "smpl_anchor_orientation_heading_10frame_step1"
|
| 48 |
+
enabled: true
|
| 49 |
+
- name: "motion_joint_positions_wrists_10frame_step1"
|
| 50 |
+
enabled: true
|
| 51 |
+
encoder_modes:
|
| 52 |
+
- name: "g1"
|
| 53 |
+
mode_id: 0
|
| 54 |
+
required_observations:
|
| 55 |
+
- encoder_mode_4
|
| 56 |
+
- motion_joint_positions_10frame_step5
|
| 57 |
+
- motion_joint_velocities_10frame_step5
|
| 58 |
+
- motion_anchor_orientation_heading_10frame_step5
|
| 59 |
+
- name: "teleop"
|
| 60 |
+
mode_id: 1
|
| 61 |
+
required_observations:
|
| 62 |
+
- encoder_mode_4
|
| 63 |
+
- motion_joint_positions_lowerbody_10frame_step5
|
| 64 |
+
- motion_joint_velocities_lowerbody_10frame_step5
|
| 65 |
+
- vr_3point_local_target
|
| 66 |
+
- vr_3point_local_orn_target
|
| 67 |
+
- motion_anchor_orientation_heading
|
| 68 |
+
- name: "smpl"
|
| 69 |
+
mode_id: 2
|
| 70 |
+
required_observations:
|
| 71 |
+
- encoder_mode_4
|
| 72 |
+
- smpl_joints_10frame_step1
|
| 73 |
+
- smpl_anchor_orientation_heading_10frame_step1
|
| 74 |
+
- motion_joint_positions_wrists_10frame_step1
|
GR00T-WholeBodyControl/gear_sonic_deploy/policy/sonic_v1_1/observation_config.yaml
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SONIC v1.1 - Observation Configuration
|
| 2 |
+
# =================================================
|
| 3 |
+
# Matches sonic_v1_1/model_encoder.onnx and model_decoder.onnx.
|
| 4 |
+
#
|
| 5 |
+
# Encoder input: 1751 Decoder input: 994 Token: 64
|
| 6 |
+
# G1/teleop references use 10 frames at step5. SMPL and wrist references use
|
| 7 |
+
# 10 frames at step1. Root orientations are normalized by robot heading.
|
| 8 |
+
|
| 9 |
+
observations:
|
| 10 |
+
- name: "token_state"
|
| 11 |
+
enabled: true
|
| 12 |
+
- name: "his_base_angular_velocity_10frame_step1"
|
| 13 |
+
enabled: true
|
| 14 |
+
- name: "his_body_joint_positions_10frame_step1"
|
| 15 |
+
enabled: true
|
| 16 |
+
- name: "his_body_joint_velocities_10frame_step1"
|
| 17 |
+
enabled: true
|
| 18 |
+
- name: "his_last_actions_10frame_step1"
|
| 19 |
+
enabled: true
|
| 20 |
+
- name: "his_gravity_dir_10frame_step1"
|
| 21 |
+
enabled: true
|
| 22 |
+
|
| 23 |
+
encoder:
|
| 24 |
+
dimension: 64
|
| 25 |
+
use_fp16: false
|
| 26 |
+
encoder_observations:
|
| 27 |
+
- name: "encoder_mode_4"
|
| 28 |
+
enabled: true
|
| 29 |
+
- name: "motion_joint_positions_10frame_step5"
|
| 30 |
+
enabled: true
|
| 31 |
+
- name: "motion_joint_velocities_10frame_step5"
|
| 32 |
+
enabled: true
|
| 33 |
+
- name: "motion_anchor_orientation_heading_10frame_step5"
|
| 34 |
+
enabled: true
|
| 35 |
+
- name: "motion_anchor_orientation_heading"
|
| 36 |
+
enabled: true
|
| 37 |
+
- name: "motion_joint_positions_lowerbody_10frame_step5"
|
| 38 |
+
enabled: true
|
| 39 |
+
- name: "motion_joint_velocities_lowerbody_10frame_step5"
|
| 40 |
+
enabled: true
|
| 41 |
+
- name: "vr_3point_local_target"
|
| 42 |
+
enabled: true
|
| 43 |
+
- name: "vr_3point_local_orn_target"
|
| 44 |
+
enabled: true
|
| 45 |
+
- name: "smpl_joints_10frame_step1"
|
| 46 |
+
enabled: true
|
| 47 |
+
- name: "smpl_anchor_orientation_heading_10frame_step1"
|
| 48 |
+
enabled: true
|
| 49 |
+
- name: "motion_joint_positions_wrists_10frame_step1"
|
| 50 |
+
enabled: true
|
| 51 |
+
encoder_modes:
|
| 52 |
+
- name: "g1"
|
| 53 |
+
mode_id: 0
|
| 54 |
+
required_observations:
|
| 55 |
+
- encoder_mode_4
|
| 56 |
+
- motion_joint_positions_10frame_step5
|
| 57 |
+
- motion_joint_velocities_10frame_step5
|
| 58 |
+
- motion_anchor_orientation_heading_10frame_step5
|
| 59 |
+
- name: "teleop"
|
| 60 |
+
mode_id: 1
|
| 61 |
+
required_observations:
|
| 62 |
+
- encoder_mode_4
|
| 63 |
+
- motion_joint_positions_lowerbody_10frame_step5
|
| 64 |
+
- motion_joint_velocities_lowerbody_10frame_step5
|
| 65 |
+
- vr_3point_local_target
|
| 66 |
+
- vr_3point_local_orn_target
|
| 67 |
+
- motion_anchor_orientation_heading
|
| 68 |
+
- name: "smpl"
|
| 69 |
+
mode_id: 2
|
| 70 |
+
required_observations:
|
| 71 |
+
- encoder_mode_4
|
| 72 |
+
- smpl_joints_10frame_step1
|
| 73 |
+
- smpl_anchor_orientation_heading_10frame_step1
|
| 74 |
+
- motion_joint_positions_wrists_10frame_step1
|
GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/gamepad.hpp
ADDED
|
@@ -0,0 +1,777 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file gamepad.hpp
|
| 3 |
+
* @brief Unitree wireless gamepad driver and InputInterface implementation.
|
| 4 |
+
*
|
| 5 |
+
* This file contains:
|
| 6 |
+
* 1. Low-level data structures that map directly onto the raw 40-byte
|
| 7 |
+
* wireless-remote packet received from the Unitree SDK
|
| 8 |
+
* (xKeySwitchUnion, xRockerBtnDataStruct, REMOTE_DATA_RX).
|
| 9 |
+
* 2. A simple edge-detecting Button helper class.
|
| 10 |
+
* 3. The Gamepad class – a full InputInterface that translates raw button
|
| 11 |
+
* and analog-stick data into motion / planner commands.
|
| 12 |
+
*
|
| 13 |
+
* The Gamepad supports two operational modes:
|
| 14 |
+
* - **Non-planner mode** – uses the face buttons and bumpers to cycle through
|
| 15 |
+
* pre-loaded reference motions and play/pause/restart them.
|
| 16 |
+
* - **Planner mode** (toggled with F1) – the left stick controls movement
|
| 17 |
+
* direction, the right stick controls facing direction, and the bumpers
|
| 18 |
+
* switch between locomotion modes (idle / slow walk / walk / run / squat /
|
| 19 |
+
* kneel). L2/R2 adjust speed or height depending on the current mode.
|
| 20 |
+
*
|
| 21 |
+
* See the button-mapping comment block inside the class for the complete mapping.
|
| 22 |
+
*/
|
| 23 |
+
|
| 24 |
+
#ifndef GAMEPAD_HPP
|
| 25 |
+
#define GAMEPAD_HPP
|
| 26 |
+
|
| 27 |
+
#include <cmath>
|
| 28 |
+
#include <iostream>
|
| 29 |
+
#include <array>
|
| 30 |
+
#include <thread>
|
| 31 |
+
#include <chrono>
|
| 32 |
+
#include <cstdlib>
|
| 33 |
+
#include "input_interface.hpp"
|
| 34 |
+
|
| 35 |
+
#ifndef M_PI
|
| 36 |
+
#define M_PI 3.14159265358979323846
|
| 37 |
+
#endif
|
| 38 |
+
|
| 39 |
+
namespace unitree::common {
|
| 40 |
+
|
| 41 |
+
// =========================================================================
|
| 42 |
+
// Raw wireless-remote data structures (match the Unitree SDK wire format)
|
| 43 |
+
// =========================================================================
|
| 44 |
+
|
| 45 |
+
/**
|
| 46 |
+
* @brief Bit-field union for the 16-bit button state word.
|
| 47 |
+
*
|
| 48 |
+
* Each member is a single-bit flag corresponding to one physical button.
|
| 49 |
+
* The `value` member gives access to all 16 bits as a single uint16_t.
|
| 50 |
+
*/
|
| 51 |
+
typedef union {
|
| 52 |
+
struct {
|
| 53 |
+
uint8_t R1 : 1; ///< Right bumper
|
| 54 |
+
uint8_t L1 : 1; ///< Left bumper
|
| 55 |
+
uint8_t start : 1; ///< Start button
|
| 56 |
+
uint8_t select : 1; ///< Select / back button
|
| 57 |
+
uint8_t R2 : 1; ///< Right trigger (digital)
|
| 58 |
+
uint8_t L2 : 1; ///< Left trigger (digital)
|
| 59 |
+
uint8_t F1 : 1; ///< Function 1 button
|
| 60 |
+
uint8_t F2 : 1; ///< Function 2 button
|
| 61 |
+
uint8_t A : 1; ///< A face button
|
| 62 |
+
uint8_t B : 1; ///< B face button
|
| 63 |
+
uint8_t X : 1; ///< X face button
|
| 64 |
+
uint8_t Y : 1; ///< Y face button
|
| 65 |
+
uint8_t up : 1; ///< D-pad up
|
| 66 |
+
uint8_t right : 1; ///< D-pad right
|
| 67 |
+
uint8_t down : 1; ///< D-pad down
|
| 68 |
+
uint8_t left : 1; ///< D-pad left
|
| 69 |
+
} components;
|
| 70 |
+
|
| 71 |
+
uint16_t value; ///< Raw 16-bit button word.
|
| 72 |
+
} xKeySwitchUnion;
|
| 73 |
+
|
| 74 |
+
/**
|
| 75 |
+
* @brief 40-byte raw joystick packet (24 bytes used, 16 idle/reserved).
|
| 76 |
+
*
|
| 77 |
+
* Layout matches the Unitree wireless remote receiver format.
|
| 78 |
+
*/
|
| 79 |
+
typedef struct {
|
| 80 |
+
uint8_t head[2]; ///< 2-byte packet header
|
| 81 |
+
xKeySwitchUnion btn; ///< 16-bit button state
|
| 82 |
+
float lx; ///< Left stick horizontal (−1..+1)
|
| 83 |
+
float rx; ///< Right stick horizontal (−1..+1)
|
| 84 |
+
float ry; ///< Right stick vertical (−1..+1)
|
| 85 |
+
float L2; ///< Left trigger analog (0..+1)
|
| 86 |
+
float ly; ///< Left stick vertical (−1..+1)
|
| 87 |
+
|
| 88 |
+
uint8_t idle[16]; ///< Reserved / unused bytes
|
| 89 |
+
} xRockerBtnDataStruct;
|
| 90 |
+
|
| 91 |
+
/**
|
| 92 |
+
* @brief Union overlay giving byte-array access to the full 40-byte packet.
|
| 93 |
+
*
|
| 94 |
+
* `RF_RX` provides structured access; `buff` gives raw byte-level access
|
| 95 |
+
* (useful for memcpy from the SDK callback).
|
| 96 |
+
*/
|
| 97 |
+
typedef union {
|
| 98 |
+
xRockerBtnDataStruct RF_RX;
|
| 99 |
+
uint8_t buff[40] = {0};
|
| 100 |
+
} REMOTE_DATA_RX;
|
| 101 |
+
|
| 102 |
+
/**
|
| 103 |
+
* @brief Simple edge-detecting button helper.
|
| 104 |
+
*
|
| 105 |
+
* Call update() once per frame with the current digital state.
|
| 106 |
+
* After the call:
|
| 107 |
+
* - `pressed` – true while the button is held down.
|
| 108 |
+
* - `on_press` – true only on the frame the button transitions from released → pressed.
|
| 109 |
+
* - `on_release` – true only on the frame the button transitions from pressed → released.
|
| 110 |
+
*/
|
| 111 |
+
class Button {
|
| 112 |
+
public:
|
| 113 |
+
Button() {}
|
| 114 |
+
|
| 115 |
+
/// Update the button state for this frame.
|
| 116 |
+
void update(bool state) {
|
| 117 |
+
on_press = state ? state != pressed : false;
|
| 118 |
+
on_release = state ? false : state != pressed;
|
| 119 |
+
pressed = state;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
bool pressed = false; ///< Currently held down.
|
| 123 |
+
bool on_press = false; ///< Rising edge this frame.
|
| 124 |
+
bool on_release = false; ///< Falling edge this frame.
|
| 125 |
+
};
|
| 126 |
+
|
| 127 |
+
/**
|
| 128 |
+
* @class Gamepad
|
| 129 |
+
* @brief InputInterface implementation for the Unitree wireless gamepad.
|
| 130 |
+
*
|
| 131 |
+
* Reads raw joystick packets via `gamepad_data` (populated externally by
|
| 132 |
+
* InterfaceManager::UpdateGamepadRemoteData) and translates button presses /
|
| 133 |
+
* stick deflections into per-frame action flags.
|
| 134 |
+
*
|
| 135 |
+
* Supports two modes:
|
| 136 |
+
* - Reference-motion mode (default): L1/R1 cycle motions, A plays, B restarts.
|
| 137 |
+
* - Planner mode (toggled by F1): sticks control direction / facing,
|
| 138 |
+
* L1/R1 change locomotion mode, L2/R2 adjust speed/height.
|
| 139 |
+
*/
|
| 140 |
+
class Gamepad : public InputInterface {
|
| 141 |
+
public:
|
| 142 |
+
/// Compile-time toggle for debug log output. Set to false and rebuild to suppress.
|
| 143 |
+
static constexpr bool DEBUG_LOGGING = false;
|
| 144 |
+
|
| 145 |
+
// ------------------------------------------------------------------
|
| 146 |
+
// Per-frame action flags (reset at the start of every update() call)
|
| 147 |
+
// ------------------------------------------------------------------
|
| 148 |
+
bool motion_prev = false; ///< Switch to previous pre-loaded motion.
|
| 149 |
+
bool motion_next = false; ///< Switch to next pre-loaded motion.
|
| 150 |
+
bool play_motion = false; ///< Start / resume motion playback.
|
| 151 |
+
bool motion_restart = false; ///< Reset current motion to frame 0 (paused).
|
| 152 |
+
|
| 153 |
+
bool start_control = false; ///< Request control-system start.
|
| 154 |
+
bool stop_control = false; ///< Request emergency stop.
|
| 155 |
+
|
| 156 |
+
bool delta_left = false; ///< Nudge heading left (+0.1 rad).
|
| 157 |
+
bool delta_right = false; ///< Nudge heading right (−0.1 rad).
|
| 158 |
+
|
| 159 |
+
bool reinitialize = false; ///< Recapture the IMU base quaternion.
|
| 160 |
+
|
| 161 |
+
bool use_planner = false; ///< True when planner mode is active (toggle via F1).
|
| 162 |
+
|
| 163 |
+
// ------------------------------------------------------------------
|
| 164 |
+
// Planner-mode state (persists across frames)
|
| 165 |
+
// ------------------------------------------------------------------
|
| 166 |
+
int planner_use_movement_mode = 1; ///< Locomotion mode index (1=slow walk, 2=walk, 3=run, …).
|
| 167 |
+
double planner_use_movement_speed = -1; ///< Desired speed (−1 = mode default).
|
| 168 |
+
double planner_use_height = -1; ///< Desired body height (−1 = mode default).
|
| 169 |
+
bool planner_emergency_stop = false; ///< Immediate halt flag (B button in planner mode).
|
| 170 |
+
double planner_facing_angle = 0.0; ///< Accumulated facing angle from right stick (radians).
|
| 171 |
+
double planner_moving_direction = 0.0; ///< Current movement direction from left stick (radians).
|
| 172 |
+
|
| 173 |
+
/// Raw wireless-remote data buffer. Written externally (e.g. by InterfaceManager).
|
| 174 |
+
REMOTE_DATA_RX gamepad_data;
|
| 175 |
+
|
| 176 |
+
explicit Gamepad() : InputInterface() {
|
| 177 |
+
type_ = InputType::GAMEPAD;
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
/*
|
| 181 |
+
* Gamepad Button Mappings:
|
| 182 |
+
*
|
| 183 |
+
* Common (both modes):
|
| 184 |
+
* - A Button - Play motion
|
| 185 |
+
* - X/Y Buttons - Reinitialize base quaternion and delta heading
|
| 186 |
+
* - Start Button - Start control
|
| 187 |
+
* - Select Button - Emergency Stop (kills all motion)
|
| 188 |
+
* - F1 Button - Toggle planner on/off
|
| 189 |
+
* - D-pad L/R - Delta heading left/right (+/-0.1 rad)
|
| 190 |
+
*
|
| 191 |
+
* Non-Planner Mode:
|
| 192 |
+
* - B Button - Restart current motion and pause (emergency stop)
|
| 193 |
+
* - L1/R1 Buttons - Previous/Next motion
|
| 194 |
+
*
|
| 195 |
+
* Planner Mode:
|
| 196 |
+
* - B Button - Emergency stop
|
| 197 |
+
* - L1/R1 Buttons - Change movement mode (0-4: idle, slow walk, walk, run, boxing)
|
| 198 |
+
* - L2/R2 Buttons - Decrease/Increase movement speed
|
| 199 |
+
* - Left Stick - Movement direction (lx, ly)
|
| 200 |
+
* - Right Stick - Facing direction (rx, ry)
|
| 201 |
+
*/
|
| 202 |
+
|
| 203 |
+
// Flag to trigger safety reset in handle_input
|
| 204 |
+
bool trigger_safety_reset = false;
|
| 205 |
+
|
| 206 |
+
// Override the update function from InputInterface
|
| 207 |
+
void update() override {
|
| 208 |
+
// Check for safety reset trigger from manager
|
| 209 |
+
if (CheckAndClearSafetyReset()) {
|
| 210 |
+
use_planner = false;
|
| 211 |
+
trigger_safety_reset = true;
|
| 212 |
+
std::cout << "[Gamepad] Safety reset triggered: will disable planner and return to reference motion" << std::endl;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
// Reset input flags each frame
|
| 216 |
+
start_control = false;
|
| 217 |
+
stop_control = false;
|
| 218 |
+
motion_prev = false;
|
| 219 |
+
motion_next = false;
|
| 220 |
+
play_motion = false;
|
| 221 |
+
motion_restart = false;
|
| 222 |
+
delta_left = false;
|
| 223 |
+
delta_right = false;
|
| 224 |
+
reinitialize = false;
|
| 225 |
+
planner_emergency_stop = false;
|
| 226 |
+
|
| 227 |
+
// Process gamepad input and set flags based on current button states
|
| 228 |
+
update_gamepad_data(gamepad_data.RF_RX);
|
| 229 |
+
|
| 230 |
+
// Debug: Log analog stick values if they're above dead zone
|
| 231 |
+
if constexpr (DEBUG_LOGGING) {
|
| 232 |
+
if (std::abs(lx) > dead_zone || std::abs(ly) > dead_zone) {
|
| 233 |
+
std::cout << "[GAMEPAD DEBUG] Left stick: lx=" << lx << ", ly=" << ly << std::endl;
|
| 234 |
+
}
|
| 235 |
+
if (std::abs(rx) > dead_zone || std::abs(ry) > dead_zone) {
|
| 236 |
+
std::cout << "[GAMEPAD DEBUG] Right stick: rx=" << rx << ", ry=" << ry << std::endl;
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
// Button mappings:
|
| 241 |
+
|
| 242 |
+
if (!use_planner) {
|
| 243 |
+
// Y - Reinitialize
|
| 244 |
+
if (Y.on_press || X.on_press) {
|
| 245 |
+
reinitialize = true;
|
| 246 |
+
if constexpr (DEBUG_LOGGING) {
|
| 247 |
+
std::cout << "[GAMEPAD DEBUG] X/Y pressed - Reinitialize" << std::endl;
|
| 248 |
+
}
|
| 249 |
+
}
|
| 250 |
+
// B - Reset motion
|
| 251 |
+
if (B.on_press) {
|
| 252 |
+
motion_restart = true;
|
| 253 |
+
if constexpr (DEBUG_LOGGING) {
|
| 254 |
+
std::cout << "[GAMEPAD DEBUG] B pressed - Motion restart" << std::endl;
|
| 255 |
+
}
|
| 256 |
+
}
|
| 257 |
+
// A - Play/Pause motion
|
| 258 |
+
if (A.on_press) {
|
| 259 |
+
play_motion = true;
|
| 260 |
+
if constexpr (DEBUG_LOGGING) {
|
| 261 |
+
std::cout << "[GAMEPAD DEBUG] A pressed - Play/Pause motion" << std::endl;
|
| 262 |
+
}
|
| 263 |
+
}
|
| 264 |
+
// L1/R1 -Motion prev/next
|
| 265 |
+
if (L1.on_press) {
|
| 266 |
+
motion_prev = true;
|
| 267 |
+
if constexpr (DEBUG_LOGGING) {
|
| 268 |
+
std::cout << "[GAMEPAD DEBUG] L1 pressed - Previous motion" << std::endl;
|
| 269 |
+
}
|
| 270 |
+
}
|
| 271 |
+
if (R1.on_press) {
|
| 272 |
+
motion_next = true;
|
| 273 |
+
if constexpr (DEBUG_LOGGING) {
|
| 274 |
+
std::cout << "[GAMEPAD DEBUG] R1 pressed - Next motion" << std::endl;
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
}
|
| 278 |
+
else {
|
| 279 |
+
// Y - Reinitialize
|
| 280 |
+
if (Y.on_press || X.on_press) {
|
| 281 |
+
reinitialize = true;
|
| 282 |
+
if constexpr (DEBUG_LOGGING) {
|
| 283 |
+
std::cout << "[GAMEPAD DEBUG] X/Y pressed - Reinitialize" << std::endl;
|
| 284 |
+
}
|
| 285 |
+
}
|
| 286 |
+
// A - Play/Pause motion
|
| 287 |
+
if (A.on_press) {
|
| 288 |
+
play_motion = true;
|
| 289 |
+
if constexpr (DEBUG_LOGGING) {
|
| 290 |
+
std::cout << "[GAMEPAD DEBUG] A pressed - Play/Pause motion" << std::endl;
|
| 291 |
+
}
|
| 292 |
+
}
|
| 293 |
+
// B - Reset motion
|
| 294 |
+
if (B.on_press) {
|
| 295 |
+
planner_emergency_stop = true;
|
| 296 |
+
if constexpr (DEBUG_LOGGING) {
|
| 297 |
+
std::cout << "[GAMEPAD DEBUG] B pressed - Planner emergency stop" << std::endl;
|
| 298 |
+
}
|
| 299 |
+
}
|
| 300 |
+
// L1/R1 - change movement mode
|
| 301 |
+
if (L1.on_press) {
|
| 302 |
+
planner_use_movement_mode -= 1;
|
| 303 |
+
if (planner_use_movement_mode < 0) { planner_use_movement_mode = 6; }
|
| 304 |
+
// If movement mode is 6, i.e. switch from idle to kneel, set height to 0.8
|
| 305 |
+
if (planner_use_movement_mode == 6) { planner_use_height = 0.8;}
|
| 306 |
+
if constexpr (DEBUG_LOGGING) {
|
| 307 |
+
std::cout << "[GAMEPAD DEBUG] L1 pressed - Movement mode changed to: " << planner_use_movement_mode << std::endl;
|
| 308 |
+
}
|
| 309 |
+
}
|
| 310 |
+
if (R1.on_press) {
|
| 311 |
+
planner_use_movement_mode += 1;
|
| 312 |
+
if (planner_use_movement_mode > 6) { planner_use_movement_mode = 0; }
|
| 313 |
+
// If movement mode is 4, i.e. switch from run to squat, set height to 0.8
|
| 314 |
+
if (planner_use_movement_mode == 4) { planner_use_height = 0.8;}
|
| 315 |
+
if constexpr (DEBUG_LOGGING) {
|
| 316 |
+
std::cout << "[GAMEPAD DEBUG] R1 pressed - Movement mode changed to: " << planner_use_movement_mode << std::endl;
|
| 317 |
+
}
|
| 318 |
+
}
|
| 319 |
+
// L2/R2 - change movement speed
|
| 320 |
+
if (R2.pressed) {
|
| 321 |
+
if (planner_use_movement_mode < 4) {
|
| 322 |
+
planner_use_movement_speed += 0.02;
|
| 323 |
+
if constexpr (DEBUG_LOGGING) {
|
| 324 |
+
if (R2.pressed) std::cout << "[GAMEPAD DEBUG] R2 pressed - Speed increasing" << "Current speed: " << planner_use_movement_speed << std::endl;
|
| 325 |
+
}
|
| 326 |
+
} else {
|
| 327 |
+
planner_use_height += 0.02;
|
| 328 |
+
if constexpr (DEBUG_LOGGING) {
|
| 329 |
+
if (R2.pressed) std::cout << "[GAMEPAD DEBUG] R2 pressed - Height increasing" << "Current height: " << planner_use_height << std::endl;
|
| 330 |
+
}
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
}
|
| 334 |
+
if (L2.pressed) {
|
| 335 |
+
if (planner_use_movement_mode < 4) {
|
| 336 |
+
planner_use_movement_speed -= 0.02;
|
| 337 |
+
if constexpr (DEBUG_LOGGING) {
|
| 338 |
+
if (L2.pressed) std::cout << "[GAMEPAD DEBUG] L2 pressed - Speed decreasing" << "Current speed: " << planner_use_movement_speed << std::endl;
|
| 339 |
+
}
|
| 340 |
+
} else {
|
| 341 |
+
planner_use_height -= 0.02;
|
| 342 |
+
if constexpr (DEBUG_LOGGING) {
|
| 343 |
+
if (L2.pressed) std::cout << "[GAMEPAD DEBUG] L2 pressed - Height decreasing" << "Current height: " << planner_use_height << std::endl;
|
| 344 |
+
}
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
}
|
| 348 |
+
// Limit movement speed and height to the range of the movement mode
|
| 349 |
+
switch (planner_use_movement_mode) {
|
| 350 |
+
case 0:
|
| 351 |
+
planner_use_movement_speed = -1.0;
|
| 352 |
+
planner_use_height = -1.0;
|
| 353 |
+
break;
|
| 354 |
+
case 1: // slow walk: 0.1 - 0.8
|
| 355 |
+
planner_use_movement_speed = std::max(planner_use_movement_speed, 0.2);
|
| 356 |
+
planner_use_movement_speed = std::min(planner_use_movement_speed, 0.8);
|
| 357 |
+
planner_use_height = -1.0;
|
| 358 |
+
break;
|
| 359 |
+
case 2: // walk: 0.8 - 2.5
|
| 360 |
+
planner_use_movement_speed = std::max(planner_use_movement_speed, 0.8);
|
| 361 |
+
planner_use_movement_speed = std::min(planner_use_movement_speed, 1.5);
|
| 362 |
+
planner_use_height = -1.0;
|
| 363 |
+
break;
|
| 364 |
+
case 3: // run: 2.5 - 7.5
|
| 365 |
+
planner_use_movement_speed = std::max(planner_use_movement_speed, 1.5);
|
| 366 |
+
planner_use_movement_speed = std::min(planner_use_movement_speed, 3.0);
|
| 367 |
+
planner_use_height = -1.0;
|
| 368 |
+
break;
|
| 369 |
+
case 4: // squat:
|
| 370 |
+
planner_use_movement_speed = -1.0;
|
| 371 |
+
planner_use_height = std::max(planner_use_height, 0.1);
|
| 372 |
+
planner_use_height = std::min(planner_use_height, 0.8);
|
| 373 |
+
break;
|
| 374 |
+
case 5: // kneel two legs:
|
| 375 |
+
planner_use_movement_speed = -1.0;
|
| 376 |
+
planner_use_height = std::max(planner_use_height, 0.1);
|
| 377 |
+
planner_use_height = std::min(planner_use_height, 0.8);
|
| 378 |
+
break;
|
| 379 |
+
case 6: // kneel:
|
| 380 |
+
planner_use_movement_speed = -1.0;
|
| 381 |
+
planner_use_height = std::max(planner_use_height, 0.1);
|
| 382 |
+
planner_use_height = std::min(planner_use_height, 0.8);
|
| 383 |
+
break;
|
| 384 |
+
}
|
| 385 |
+
// Analog sticks - change movement and facing direction (with dead zone)
|
| 386 |
+
|
| 387 |
+
if (std::abs(rx) > dead_zone || std::abs(ry) > dead_zone) {
|
| 388 |
+
planner_facing_angle = planner_facing_angle - 0.02 * rx;
|
| 389 |
+
if constexpr (DEBUG_LOGGING) {
|
| 390 |
+
{
|
| 391 |
+
std::cout << "[GAMEPAD DEBUG] Right stick - Facing angle: " << planner_facing_angle << " rad ("
|
| 392 |
+
<< (planner_facing_angle * 180.0 / M_PI) << " deg)" << std::endl;
|
| 393 |
+
}
|
| 394 |
+
}
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
if (std::abs(lx) > dead_zone || std::abs(ly) > dead_zone) {
|
| 398 |
+
planner_moving_direction = atan2(ly, lx) - M_PI/2 + planner_facing_angle;
|
| 399 |
+
if constexpr (DEBUG_LOGGING) {
|
| 400 |
+
{
|
| 401 |
+
std::cout << "[GAMEPAD DEBUG] Left stick - Moving direction: " << planner_moving_direction << " rad ("
|
| 402 |
+
<< (planner_moving_direction * 180.0 / M_PI) << " deg)" << std::endl;
|
| 403 |
+
}
|
| 404 |
+
}
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
// Log speed changes after limit processing
|
| 408 |
+
if constexpr (DEBUG_LOGGING) {
|
| 409 |
+
static double prev_speed = planner_use_movement_speed;
|
| 410 |
+
if (std::abs(planner_use_movement_speed - prev_speed) > 0.01) {
|
| 411 |
+
std::cout << "[GAMEPAD DEBUG] Movement speed: " << planner_use_movement_speed << std::endl;
|
| 412 |
+
prev_speed = planner_use_movement_speed;
|
| 413 |
+
}
|
| 414 |
+
}
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
// F1 - Toggle planner
|
| 419 |
+
if (F1.on_press) {
|
| 420 |
+
use_planner = !use_planner;
|
| 421 |
+
if constexpr (DEBUG_LOGGING) {
|
| 422 |
+
std::cout << "[GAMEPAD DEBUG] F1 pressed - Planner toggled to: " << (use_planner ? "ON" : "OFF") << std::endl;
|
| 423 |
+
}
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
// start - Start control
|
| 427 |
+
if (start.on_press) {
|
| 428 |
+
start_control = true;
|
| 429 |
+
if constexpr (DEBUG_LOGGING) {
|
| 430 |
+
std::cout << "[GAMEPAD DEBUG] Start pressed - Start control" << std::endl;
|
| 431 |
+
}
|
| 432 |
+
}
|
| 433 |
+
// select - Emergency stop
|
| 434 |
+
if (select.on_press) {
|
| 435 |
+
stop_control = true;
|
| 436 |
+
if constexpr (DEBUG_LOGGING) {
|
| 437 |
+
std::cout << "[GAMEPAD DEBUG] Select pressed - Emergency Stop" << std::endl;
|
| 438 |
+
}
|
| 439 |
+
}
|
| 440 |
+
// left/right - Delta heading left/right
|
| 441 |
+
if (left.on_press) {
|
| 442 |
+
delta_left = true;
|
| 443 |
+
if constexpr (DEBUG_LOGGING) {
|
| 444 |
+
std::cout << "[GAMEPAD DEBUG] D-pad Left pressed - Delta heading left" << std::endl;
|
| 445 |
+
}
|
| 446 |
+
}
|
| 447 |
+
if (right.on_press) {
|
| 448 |
+
delta_right = true;
|
| 449 |
+
if constexpr (DEBUG_LOGGING) {
|
| 450 |
+
std::cout << "[GAMEPAD DEBUG] D-pad Right pressed - Delta heading right" << std::endl;
|
| 451 |
+
}
|
| 452 |
+
}
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
/**
|
| 456 |
+
* @brief Decode raw joystick packet into smoothed analog values and Button states.
|
| 457 |
+
*
|
| 458 |
+
* Analog values are smoothed with an exponential moving average (controlled
|
| 459 |
+
* by `smooth`) and dead-zone filtered. Each Button is updated with edge
|
| 460 |
+
* detection so callers can distinguish press / release / held.
|
| 461 |
+
*/
|
| 462 |
+
void update_gamepad_data(xRockerBtnDataStruct& key_data) {
|
| 463 |
+
lx = lx * (1 - smooth) + (std::fabs(key_data.lx) < dead_zone ? 0.0 : key_data.lx) * smooth;
|
| 464 |
+
rx = rx * (1 - smooth) + (std::fabs(key_data.rx) < dead_zone ? 0.0 : key_data.rx) * smooth;
|
| 465 |
+
ry = ry * (1 - smooth) + (std::fabs(key_data.ry) < dead_zone ? 0.0 : key_data.ry) * smooth;
|
| 466 |
+
l2 = l2 * (1 - smooth) + (std::fabs(key_data.L2) < dead_zone ? 0.0 : key_data.L2) * smooth;
|
| 467 |
+
ly = ly * (1 - smooth) + (std::fabs(key_data.ly) < dead_zone ? 0.0 : key_data.ly) * smooth;
|
| 468 |
+
|
| 469 |
+
R1.update(key_data.btn.components.R1);
|
| 470 |
+
L1.update(key_data.btn.components.L1);
|
| 471 |
+
start.update(key_data.btn.components.start);
|
| 472 |
+
select.update(key_data.btn.components.select);
|
| 473 |
+
R2.update(key_data.btn.components.R2);
|
| 474 |
+
L2.update(key_data.btn.components.L2);
|
| 475 |
+
F1.update(key_data.btn.components.F1);
|
| 476 |
+
F2.update(key_data.btn.components.F2);
|
| 477 |
+
A.update(key_data.btn.components.A);
|
| 478 |
+
B.update(key_data.btn.components.B);
|
| 479 |
+
X.update(key_data.btn.components.X);
|
| 480 |
+
Y.update(key_data.btn.components.Y);
|
| 481 |
+
up.update(key_data.btn.components.up);
|
| 482 |
+
right.update(key_data.btn.components.right);
|
| 483 |
+
down.update(key_data.btn.components.down);
|
| 484 |
+
left.update(key_data.btn.components.left);
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
// Override the handle_input function from InputInterface
|
| 488 |
+
// This processes the gamepad input flags and performs actions using the provided parameters
|
| 489 |
+
void handle_input(MotionDataReader& motion_reader,
|
| 490 |
+
std::shared_ptr<const MotionSequence>& current_motion,
|
| 491 |
+
int& current_frame,
|
| 492 |
+
OperatorState& operator_state,
|
| 493 |
+
bool& reinitialize_heading,
|
| 494 |
+
DataBuffer<HeadingState>& heading_state_buffer,
|
| 495 |
+
bool has_planner,
|
| 496 |
+
PlannerState& planner_state,
|
| 497 |
+
DataBuffer<MovementState>& movement_state_buffer,
|
| 498 |
+
std::mutex& current_motion_mutex,
|
| 499 |
+
bool& report_temperature) override {
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
// Handle safety reset from interface manager
|
| 503 |
+
if (trigger_safety_reset) {
|
| 504 |
+
trigger_safety_reset = false;
|
| 505 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE), {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 506 |
+
{
|
| 507 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 508 |
+
operator_state.play = false;
|
| 509 |
+
reinitialize_heading = true;
|
| 510 |
+
auto temp_motion = std::make_shared<MotionSequence>(*current_motion);
|
| 511 |
+
temp_motion->name = "temporary_motion";
|
| 512 |
+
current_motion = temp_motion;
|
| 513 |
+
if (has_planner && planner_state.enabled) {
|
| 514 |
+
planner_state.enabled = false;
|
| 515 |
+
planner_state.initialized = false;
|
| 516 |
+
std::cout << "Safety reset: Planner disabled" << std::endl;
|
| 517 |
+
}
|
| 518 |
+
}
|
| 519 |
+
std::cout << "Safety reset: Returned to reference motion at frame 0" << std::endl;
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
// Handle motion control commands
|
| 523 |
+
if (this->motion_prev && !motion_reader.motions.empty()) {
|
| 524 |
+
motion_reader.current_motion_index_ =
|
| 525 |
+
(motion_reader.current_motion_index_ - 1 + motion_reader.motions.size()) % motion_reader.motions.size();
|
| 526 |
+
std::string motion_name;
|
| 527 |
+
{
|
| 528 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 529 |
+
operator_state.play = false;
|
| 530 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_); // Update current motion directly
|
| 531 |
+
current_frame = 0;
|
| 532 |
+
motion_name = current_motion->name;
|
| 533 |
+
reinitialize_heading = true;
|
| 534 |
+
}
|
| 535 |
+
std::cout << "Switched to motion " << motion_reader.current_motion_index_ << ": " << motion_name
|
| 536 |
+
<< " (paused at frame 0)" << std::endl;
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
if (this->motion_next && !motion_reader.motions.empty()) {
|
| 540 |
+
motion_reader.current_motion_index_ = (motion_reader.current_motion_index_ + 1) % motion_reader.motions.size();
|
| 541 |
+
std::string motion_name;
|
| 542 |
+
{
|
| 543 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 544 |
+
operator_state.play = false;
|
| 545 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_); // Update current motion directly
|
| 546 |
+
current_frame = 0;
|
| 547 |
+
motion_name = current_motion->name;
|
| 548 |
+
reinitialize_heading = true;
|
| 549 |
+
}
|
| 550 |
+
std::cout << "Switched to motion " << motion_reader.current_motion_index_ << ": " << motion_name
|
| 551 |
+
<< " (paused at frame 0)" << std::endl;
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
if (this->play_motion) {
|
| 555 |
+
if (!operator_state.play) {
|
| 556 |
+
int frame_copy;
|
| 557 |
+
size_t timesteps_copy;
|
| 558 |
+
{
|
| 559 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 560 |
+
operator_state.play = true;
|
| 561 |
+
frame_copy = current_frame;
|
| 562 |
+
timesteps_copy = current_motion ? current_motion->timesteps : 0;
|
| 563 |
+
}
|
| 564 |
+
std::cout << "Playing motion " << motion_reader.current_motion_index_ << " from frame " << frame_copy << " to end ("
|
| 565 |
+
<< timesteps_copy << " total frames)" << std::endl;
|
| 566 |
+
}
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
if (this->motion_restart) {
|
| 570 |
+
{
|
| 571 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 572 |
+
operator_state.play = false;
|
| 573 |
+
current_frame = 0;
|
| 574 |
+
reinitialize_heading = true;
|
| 575 |
+
}
|
| 576 |
+
std::cout << "Reset motion " << motion_reader.current_motion_index_ << " to frame 0 (paused)" << std::endl;
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
if (this->stop_control) { operator_state.stop = true; }
|
| 580 |
+
|
| 581 |
+
if (this->start_control) { operator_state.start = true; }
|
| 582 |
+
|
| 583 |
+
// Handle delta heading controls
|
| 584 |
+
if (this->delta_left) {
|
| 585 |
+
auto current_heading_state = heading_state_buffer.GetDataWithTime().data;
|
| 586 |
+
HeadingState current_state = current_heading_state ? *current_heading_state : HeadingState();
|
| 587 |
+
double new_delta = current_state.delta_heading + 0.1;
|
| 588 |
+
heading_state_buffer.SetData(HeadingState(current_state.init_base_quat, new_delta));
|
| 589 |
+
std::cout << "Delta heading left: " << new_delta << " rad" << std::endl;
|
| 590 |
+
}
|
| 591 |
+
|
| 592 |
+
if (this->delta_right) {
|
| 593 |
+
auto current_heading_state = heading_state_buffer.GetDataWithTime().data;
|
| 594 |
+
HeadingState current_state = current_heading_state ? *current_heading_state : HeadingState();
|
| 595 |
+
double new_delta = current_state.delta_heading - 0.1;
|
| 596 |
+
heading_state_buffer.SetData(HeadingState(current_state.init_base_quat, new_delta));
|
| 597 |
+
std::cout << "Delta heading right: " << new_delta << " rad" << std::endl;
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
// Handle reinitialize command
|
| 601 |
+
if (this->reinitialize) {
|
| 602 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 603 |
+
reinitialize_heading = true;
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
// Handle planner control - copy the toggle state from gamepad
|
| 607 |
+
if (this->use_planner && !has_planner) {
|
| 608 |
+
std::cout << "Planner not loaded - cannot enable" << std::endl;
|
| 609 |
+
this->use_planner = false;
|
| 610 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE), {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 611 |
+
{
|
| 612 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 613 |
+
operator_state.play = false;
|
| 614 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_); // Update current motion directly
|
| 615 |
+
current_frame = 0;
|
| 616 |
+
}
|
| 617 |
+
} else if (has_planner && planner_state.enabled != this->use_planner) {
|
| 618 |
+
planner_state.enabled = this->use_planner;
|
| 619 |
+
if (planner_state.enabled) {
|
| 620 |
+
std::cout << "Planner enabled" << std::endl;
|
| 621 |
+
planner_facing_angle = 0.0;
|
| 622 |
+
{
|
| 623 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 624 |
+
operator_state.play = false;
|
| 625 |
+
}
|
| 626 |
+
// Wait for planner to be initialized with timeout (5 seconds)
|
| 627 |
+
auto wait_start = std::chrono::steady_clock::now();
|
| 628 |
+
constexpr auto PLANNER_INIT_TIMEOUT = std::chrono::seconds(5);
|
| 629 |
+
while (planner_state.enabled) {
|
| 630 |
+
{
|
| 631 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 632 |
+
if (current_motion->name == "planner_motion") {
|
| 633 |
+
std::cout << "[Gamepad] motion name is planner_motion" << std::endl;
|
| 634 |
+
break;
|
| 635 |
+
}
|
| 636 |
+
}
|
| 637 |
+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
| 638 |
+
auto elapsed = std::chrono::steady_clock::now() - wait_start;
|
| 639 |
+
if (elapsed > PLANNER_INIT_TIMEOUT) {
|
| 640 |
+
std::cerr << "[Gamepad] Planner initialization timeout after 5 seconds" << std::endl;
|
| 641 |
+
this->use_planner = false;
|
| 642 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE), {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 643 |
+
{
|
| 644 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 645 |
+
operator_state.play = false;
|
| 646 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_); // Update current motion directly
|
| 647 |
+
current_frame = 0;
|
| 648 |
+
}
|
| 649 |
+
break;
|
| 650 |
+
}
|
| 651 |
+
std::cout << "[Gamepad] Waiting for planner to be initialized" << std::endl;
|
| 652 |
+
}
|
| 653 |
+
// Check if planner is enabled and initialized
|
| 654 |
+
if (!planner_state.enabled || !planner_state.initialized) {
|
| 655 |
+
std::cout << "[Gamepad] Planner failed to initialize." << std::endl;
|
| 656 |
+
this->use_planner = false;
|
| 657 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE), {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 658 |
+
{
|
| 659 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 660 |
+
operator_state.play = false;
|
| 661 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_); // Update current motion directly
|
| 662 |
+
current_frame = 0;
|
| 663 |
+
}
|
| 664 |
+
} else {
|
| 665 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 666 |
+
operator_state.play = true;
|
| 667 |
+
}
|
| 668 |
+
} else {
|
| 669 |
+
std::cout << "Planner disabled" << std::endl;
|
| 670 |
+
planner_state.initialized = false; // Reset planner initialization when disabled
|
| 671 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE), {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 672 |
+
{
|
| 673 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 674 |
+
operator_state.play = false;
|
| 675 |
+
reinitialize_heading = true;
|
| 676 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_); // Update current motion directly
|
| 677 |
+
current_frame = 0;
|
| 678 |
+
}
|
| 679 |
+
}
|
| 680 |
+
}
|
| 681 |
+
|
| 682 |
+
if (has_planner && planner_state.enabled && planner_state.initialized) {
|
| 683 |
+
|
| 684 |
+
// Set final movement values based on momentum
|
| 685 |
+
int final_mode = this->planner_use_movement_mode;
|
| 686 |
+
std::array<double, 3> final_movement = {double(cos(planner_moving_direction)), double(sin(planner_moving_direction)), 0.0};
|
| 687 |
+
std::array<double, 3> final_facing_direction = {double(cos(planner_facing_angle)), double(sin(planner_facing_angle)), 0.0};
|
| 688 |
+
double final_speed = this->planner_use_movement_speed;
|
| 689 |
+
double final_height = this->planner_use_height;
|
| 690 |
+
|
| 691 |
+
// If left sticks are in the dead zone, idle mode
|
| 692 |
+
if (std::abs(lx) < dead_zone && std::abs(ly) < dead_zone) {
|
| 693 |
+
if constexpr (DEBUG_LOGGING) {
|
| 694 |
+
std::cout << "Both left sticks in the dead zone - Idle mode" << std::endl;
|
| 695 |
+
std::cout << "[GAMEPAD DEBUG] Left stick: lx=" << lx << ", ly=" << ly << std::endl;
|
| 696 |
+
}
|
| 697 |
+
if (is_standing_motion_mode(LocomotionMode(final_mode))) {
|
| 698 |
+
final_mode = static_cast<int>(LocomotionMode::IDLE);
|
| 699 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 700 |
+
final_speed = -1.0f;
|
| 701 |
+
final_height = -1.0f;
|
| 702 |
+
} else {
|
| 703 |
+
final_mode = final_mode;
|
| 704 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 705 |
+
final_speed = 0;
|
| 706 |
+
final_height = final_height;
|
| 707 |
+
}
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
// Handle emergency stop (immediate momentum reset)
|
| 711 |
+
if (this->planner_emergency_stop) {
|
| 712 |
+
planner_use_movement_mode = static_cast<int>(LocomotionMode::IDLE);
|
| 713 |
+
final_mode = static_cast<int>(LocomotionMode::IDLE);
|
| 714 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 715 |
+
final_speed = -1.0f;
|
| 716 |
+
final_height = -1.0f;
|
| 717 |
+
if constexpr (DEBUG_LOGGING) {
|
| 718 |
+
std::cout << "Emergency Stop! Movement momentum reset." << std::endl;
|
| 719 |
+
}
|
| 720 |
+
}
|
| 721 |
+
|
| 722 |
+
// Debug: Log final computed values being sent to planner
|
| 723 |
+
if constexpr (DEBUG_LOGGING) {
|
| 724 |
+
static int debug_counter = 0;
|
| 725 |
+
debug_counter++;
|
| 726 |
+
if (debug_counter % 50 == 0) { // Log every 50 calls to avoid spam
|
| 727 |
+
std::cout << "[GAMEPAD DEBUG] Final planner values:" << std::endl;
|
| 728 |
+
std::cout << " Mode: " << final_mode << " (0=idle, 1=slow, 2=walk, 3=run, 4=box)" << std::endl;
|
| 729 |
+
std::cout << " Speed: " << final_speed << std::endl;
|
| 730 |
+
std::cout << " Height: " << final_height << std::endl;
|
| 731 |
+
std::cout << " Movement direction: [" << final_movement[0] << ", " << final_movement[1] << ", " << final_movement[2] << "]" << std::endl;
|
| 732 |
+
std::cout << " Facing direction: [" << final_facing_direction[0] << ", " << final_facing_direction[1] << ", " << final_facing_direction[2] << "]" << std::endl;
|
| 733 |
+
}
|
| 734 |
+
}
|
| 735 |
+
|
| 736 |
+
// Update thread-safe buffer (single source of truth for planner thread)
|
| 737 |
+
MovementState mode_state(final_mode, final_movement, final_facing_direction, final_speed, final_height);
|
| 738 |
+
movement_state_buffer.SetData(mode_state);
|
| 739 |
+
}
|
| 740 |
+
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
// ------------------------------------------------------------------
|
| 744 |
+
// Smoothed analog-stick values (exponential moving average)
|
| 745 |
+
// ------------------------------------------------------------------
|
| 746 |
+
float lx = 0.f; ///< Left stick horizontal (smoothed, dead-zone filtered)
|
| 747 |
+
float rx = 0.f; ///< Right stick horizontal (smoothed)
|
| 748 |
+
float ry = 0.f; ///< Right stick vertical (smoothed)
|
| 749 |
+
float l2 = 0.f; ///< Left trigger analog (smoothed)
|
| 750 |
+
float ly = 0.f; ///< Left stick vertical (smoothed)
|
| 751 |
+
|
| 752 |
+
float smooth = 0.3f; ///< EMA smoothing factor (0 = no update, 1 = no smoothing).
|
| 753 |
+
float dead_zone = 0.05f; ///< Analog values below this are zeroed.
|
| 754 |
+
|
| 755 |
+
// ------------------------------------------------------------------
|
| 756 |
+
// Edge-detecting button states
|
| 757 |
+
// ------------------------------------------------------------------
|
| 758 |
+
Button R1; ///< Right bumper
|
| 759 |
+
Button L1; ///< Left bumper
|
| 760 |
+
Button start; ///< Start button
|
| 761 |
+
Button select; ///< Select / back button
|
| 762 |
+
Button R2; ///< Right trigger (digital)
|
| 763 |
+
Button L2; ///< Left trigger (digital)
|
| 764 |
+
Button F1; ///< Function 1 (planner toggle)
|
| 765 |
+
Button F2; ///< Function 2
|
| 766 |
+
Button A; ///< A face button (play motion / play in planner)
|
| 767 |
+
Button B; ///< B face button (restart / emergency stop in planner)
|
| 768 |
+
Button X; ///< X face button (reinitialize)
|
| 769 |
+
Button Y; ///< Y face button (reinitialize)
|
| 770 |
+
Button up; ///< D-pad up
|
| 771 |
+
Button right; ///< D-pad right (delta heading)
|
| 772 |
+
Button down; ///< D-pad down
|
| 773 |
+
Button left; ///< D-pad left (delta heading)
|
| 774 |
+
};
|
| 775 |
+
} // namespace unitree::common
|
| 776 |
+
|
| 777 |
+
#endif
|
GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/gamepad_manager.hpp
ADDED
|
@@ -0,0 +1,1078 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file gamepad_manager.hpp
|
| 3 |
+
* @brief Planner-centric gamepad manager with ZMQ delegate and D-pad motion-set selection.
|
| 4 |
+
*
|
| 5 |
+
* GamepadManager is a specialised InputInterface that:
|
| 6 |
+
* - Reads the Unitree wireless gamepad **directly** (no nested Gamepad class).
|
| 7 |
+
* - Operates **exclusively in planner mode** – the planner must always be loaded.
|
| 8 |
+
* - Can delegate to ZMQEndpointInterface via F1 button toggle.
|
| 9 |
+
* - D-pad selects one of 4 motion sets; face buttons cycle within the set.
|
| 10 |
+
* - Boxing set uses direct key selection instead of cycling.
|
| 11 |
+
*
|
| 12 |
+
* ## Gamepad Button Mapping (Planner Mode)
|
| 13 |
+
*
|
| 14 |
+
* Button | Action
|
| 15 |
+
* --------|-------
|
| 16 |
+
* Start | Start control (enable planner, wait for init, auto-play)
|
| 17 |
+
* Select | Emergency stop
|
| 18 |
+
* A | Emergency stop (immediate halt)
|
| 19 |
+
* D-Up | Select Standing motion set (SLOW_WALK default)
|
| 20 |
+
* D-Down | Select Squat/Crawl motion set (IDEL_SQUAT default)
|
| 21 |
+
* D-Left | Select Boxing motion set (IDEL_BOXING default)
|
| 22 |
+
* D-Right | Select Styled Walking motion set (LEDGE_WALKING default)
|
| 23 |
+
* F1 | Toggle ZMQ streaming
|
| 24 |
+
*
|
| 25 |
+
* --- Standing / Squat / Styled sets (loop cycling) ---
|
| 26 |
+
* X | Next mode in current set (wraps)
|
| 27 |
+
* Y | Previous mode in current set (wraps)
|
| 28 |
+
* B | Reset to set's default mode
|
| 29 |
+
* L1/R1 | Facing angle ±π/4
|
| 30 |
+
* L2/R2 | Height ±0.1 (Squat set only; disabled otherwise)
|
| 31 |
+
*
|
| 32 |
+
* --- Boxing set (direct selection) ---
|
| 33 |
+
* X | WALK_BOXING
|
| 34 |
+
* Y | RANDOM_PUNCH
|
| 35 |
+
* B | IDEL_BOXING (reset)
|
| 36 |
+
* L1 | LEFT_PUNCH
|
| 37 |
+
* R1 | RIGHT_PUNCH
|
| 38 |
+
* L2 | LEFT_HOOK
|
| 39 |
+
* R2 | RIGHT_HOOK
|
| 40 |
+
*
|
| 41 |
+
* L stick | Movement direction (binned to nearest 30° increment)
|
| 42 |
+
* R stick | Facing direction (continuous)
|
| 43 |
+
*/
|
| 44 |
+
|
| 45 |
+
#ifndef GAMEPAD_MANAGER_HPP
|
| 46 |
+
#define GAMEPAD_MANAGER_HPP
|
| 47 |
+
|
| 48 |
+
#include <memory>
|
| 49 |
+
#include <vector>
|
| 50 |
+
#include <iostream>
|
| 51 |
+
#include <cstring>
|
| 52 |
+
#include <cmath>
|
| 53 |
+
#include <array>
|
| 54 |
+
#include <thread>
|
| 55 |
+
#include <chrono>
|
| 56 |
+
|
| 57 |
+
#include "input_interface.hpp"
|
| 58 |
+
#include "zmq_endpoint_interface.hpp"
|
| 59 |
+
#include "gamepad.hpp"
|
| 60 |
+
#include "../localmotion_kplanner.hpp" // For LocomotionMode enum
|
| 61 |
+
|
| 62 |
+
#ifndef M_PI
|
| 63 |
+
#define M_PI 3.14159265358979323846
|
| 64 |
+
#endif
|
| 65 |
+
|
| 66 |
+
/**
|
| 67 |
+
* @class GamepadManager
|
| 68 |
+
* @brief Planner-only gamepad controller with ZMQ delegate and D-pad motion-set selection.
|
| 69 |
+
*
|
| 70 |
+
* When in GAMEPAD mode, button presses and stick positions are translated
|
| 71 |
+
* directly into MovementState commands for the locomotion planner.
|
| 72 |
+
* D-pad selects one of 4 motion sets; face buttons cycle within the set
|
| 73 |
+
* (except Boxing which uses direct key selection).
|
| 74 |
+
* When in ZMQ mode (toggled via F1), all calls are forwarded to ZMQ.
|
| 75 |
+
*/
|
| 76 |
+
class GamepadManager : public InputInterface {
|
| 77 |
+
public:
|
| 78 |
+
// ========================================
|
| 79 |
+
// DEBUG CONTROL FLAG
|
| 80 |
+
// ========================================
|
| 81 |
+
static constexpr bool DEBUG_LOGGING = true;
|
| 82 |
+
|
| 83 |
+
enum class ManagedType {
|
| 84 |
+
GAMEPAD = 0,
|
| 85 |
+
ZMQ = 1
|
| 86 |
+
};
|
| 87 |
+
|
| 88 |
+
GamepadManager(
|
| 89 |
+
const std::string& zmq_host,
|
| 90 |
+
int zmq_port,
|
| 91 |
+
const std::string& zmq_topic,
|
| 92 |
+
bool zmq_conflate,
|
| 93 |
+
bool zmq_verbose
|
| 94 |
+
) : InputInterface(), zmq_host_(zmq_host), zmq_port_(zmq_port), zmq_topic_(zmq_topic),
|
| 95 |
+
zmq_conflate_(zmq_conflate), zmq_verbose_(zmq_verbose) {
|
| 96 |
+
type_ = InputType::GAMEPAD; // Default to gamepad mode
|
| 97 |
+
buildInterfaces();
|
| 98 |
+
active_ = ManagedType::GAMEPAD;
|
| 99 |
+
current_ = nullptr; // Gamepad mode doesn't use delegate
|
| 100 |
+
|
| 101 |
+
// Initialize with Standing motion set, SLOW_WALK as default
|
| 102 |
+
motion_set_index_ = 0;
|
| 103 |
+
current_motion_set_ = get_motion_set(motion_set_index_);
|
| 104 |
+
mode_index_in_set_ = 0;
|
| 105 |
+
applyModeFromSet();
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
void update() override {
|
| 109 |
+
// Reset per-frame flags
|
| 110 |
+
emergency_stop_ = false;
|
| 111 |
+
report_temperature_flag_ = false;
|
| 112 |
+
start_control_ = false;
|
| 113 |
+
stop_control_ = false;
|
| 114 |
+
reinitialize_ = false;
|
| 115 |
+
planner_emergency_stop_ = false;
|
| 116 |
+
|
| 117 |
+
// Handle stdin shortcuts for switching and emergency stop
|
| 118 |
+
char ch;
|
| 119 |
+
while (ReadStdinChar(ch)) {
|
| 120 |
+
bool is_manager_key = false;
|
| 121 |
+
switch (ch) {
|
| 122 |
+
case '@':
|
| 123 |
+
SetActiveInterface(ManagedType::GAMEPAD);
|
| 124 |
+
is_manager_key = true;
|
| 125 |
+
break;
|
| 126 |
+
case '#':
|
| 127 |
+
SetActiveInterface(ManagedType::ZMQ);
|
| 128 |
+
is_manager_key = true;
|
| 129 |
+
break;
|
| 130 |
+
case 'o':
|
| 131 |
+
case 'O':
|
| 132 |
+
emergency_stop_ = true;
|
| 133 |
+
is_manager_key = true;
|
| 134 |
+
std::cout << "[GamepadManager] EMERGENCY STOP triggered (O/o key pressed)" << std::endl;
|
| 135 |
+
break;
|
| 136 |
+
case 'f':
|
| 137 |
+
case 'F':
|
| 138 |
+
report_temperature_flag_ = true;
|
| 139 |
+
is_manager_key = true;
|
| 140 |
+
break;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
if (!is_manager_key && current_) {
|
| 144 |
+
current_->PushStdinChar(ch);
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
// Update gamepad data and buttons
|
| 149 |
+
update_gamepad_data(gamepad_data_.RF_RX);
|
| 150 |
+
|
| 151 |
+
// F1 - Toggle ZMQ streaming
|
| 152 |
+
bool trigger_ZMQ_toggle = false;
|
| 153 |
+
if (F1_.on_press) {
|
| 154 |
+
if (active_ != ManagedType::ZMQ) {
|
| 155 |
+
SetActiveInterface(ManagedType::ZMQ);
|
| 156 |
+
}
|
| 157 |
+
trigger_ZMQ_toggle = true;
|
| 158 |
+
if constexpr (DEBUG_LOGGING) {
|
| 159 |
+
std::cout << "[GamepadManager DEBUG] F1 pressed - ZMQ toggle" << std::endl;
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
// D-pad - Motion set selection (switches back to GAMEPAD if in ZMQ)
|
| 164 |
+
if (up_.on_press) {
|
| 165 |
+
if (active_ != ManagedType::GAMEPAD) { SetActiveInterface(ManagedType::GAMEPAD); }
|
| 166 |
+
selectMotionSet(0); // Standing
|
| 167 |
+
}
|
| 168 |
+
if (down_.on_press) {
|
| 169 |
+
if (active_ != ManagedType::GAMEPAD) { SetActiveInterface(ManagedType::GAMEPAD); }
|
| 170 |
+
selectMotionSet(1); // Squat/Crawl
|
| 171 |
+
}
|
| 172 |
+
if (left_.on_press) {
|
| 173 |
+
if (active_ != ManagedType::GAMEPAD) { SetActiveInterface(ManagedType::GAMEPAD); }
|
| 174 |
+
selectMotionSet(2); // Boxing
|
| 175 |
+
}
|
| 176 |
+
if (right_.on_press) {
|
| 177 |
+
if (active_ != ManagedType::GAMEPAD) { SetActiveInterface(ManagedType::GAMEPAD); }
|
| 178 |
+
selectMotionSet(3); // Styled Walking
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
// Select - Emergency Stop
|
| 182 |
+
if (select_.on_press) {
|
| 183 |
+
stop_control_ = true;
|
| 184 |
+
if constexpr (DEBUG_LOGGING) {
|
| 185 |
+
std::cout << "[GamepadManager DEBUG] Select pressed - Emergency Stop" << std::endl;
|
| 186 |
+
}
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
// If not in gamepad mode, update the active interface
|
| 190 |
+
if (active_ != ManagedType::GAMEPAD && current_) {
|
| 191 |
+
current_->update();
|
| 192 |
+
if (trigger_ZMQ_toggle && zmq_) {
|
| 193 |
+
zmq_->TriggerZMQToggle();
|
| 194 |
+
}
|
| 195 |
+
} else {
|
| 196 |
+
processGamepadPlannerControls();
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
void handle_input(MotionDataReader& motion_reader,
|
| 201 |
+
std::shared_ptr<const MotionSequence>& current_motion,
|
| 202 |
+
int& current_frame,
|
| 203 |
+
OperatorState& operator_state,
|
| 204 |
+
bool& reinitialize_heading,
|
| 205 |
+
DataBuffer<HeadingState>& heading_state_buffer,
|
| 206 |
+
bool has_planner,
|
| 207 |
+
PlannerState& planner_state,
|
| 208 |
+
DataBuffer<MovementState>& movement_state_buffer,
|
| 209 |
+
std::mutex& current_motion_mutex,
|
| 210 |
+
bool& report_temperature) override {
|
| 211 |
+
// Check if planner is loaded (required for GamepadManager)
|
| 212 |
+
if (!has_planner) {
|
| 213 |
+
std::cerr << "[GamepadManager ERROR] Planner not loaded - GamepadManager requires planner. Stopping control." << std::endl;
|
| 214 |
+
operator_state.stop = true;
|
| 215 |
+
return;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
// NOTE: Encoder mode safety check removed - encoder mode is now a property of the motion
|
| 219 |
+
// If a motion has an incompatible encoder mode for gamepad mode, the user should
|
| 220 |
+
// switch to a different motion with a compatible encoder mode
|
| 221 |
+
|
| 222 |
+
// Global emergency stop
|
| 223 |
+
if (emergency_stop_) {
|
| 224 |
+
operator_state.stop = true;
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
// Global temperature report (F key)
|
| 228 |
+
if (report_temperature_flag_) {
|
| 229 |
+
report_temperature = true;
|
| 230 |
+
report_temperature_flag_ = false;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
// Handle stop control
|
| 234 |
+
if (stop_control_) { operator_state.stop = true; }
|
| 235 |
+
|
| 236 |
+
// If in gamepad mode, handle planner-only controls
|
| 237 |
+
if (active_ == ManagedType::GAMEPAD) {
|
| 238 |
+
handleGamepadPlannerInput(motion_reader, current_motion, current_frame,
|
| 239 |
+
operator_state, reinitialize_heading, heading_state_buffer,
|
| 240 |
+
planner_state, movement_state_buffer, current_motion_mutex);
|
| 241 |
+
} else {
|
| 242 |
+
// Delegate to ZMQ
|
| 243 |
+
if (current_) {
|
| 244 |
+
current_->handle_input(motion_reader, current_motion, current_frame, operator_state,
|
| 245 |
+
reinitialize_heading, heading_state_buffer, has_planner,
|
| 246 |
+
planner_state, movement_state_buffer, current_motion_mutex, report_temperature);
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
bool HasVR3PointControl() const override {
|
| 252 |
+
if (active_ != ManagedType::GAMEPAD && current_) {
|
| 253 |
+
return current_->HasVR3PointControl();
|
| 254 |
+
}
|
| 255 |
+
return has_vr_3point_control_;
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
bool HasHandJoints() const override {
|
| 259 |
+
if (active_ != ManagedType::GAMEPAD && current_) {
|
| 260 |
+
return current_->HasHandJoints();
|
| 261 |
+
}
|
| 262 |
+
return has_hand_joints_;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
bool HasExternalTokenState() const override {
|
| 266 |
+
if (active_ != ManagedType::GAMEPAD && current_) {
|
| 267 |
+
return current_->HasExternalTokenState();
|
| 268 |
+
}
|
| 269 |
+
return has_external_token_state_;
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
std::pair<bool, std::array<double, 9>> GetVR3PointPosition() const override {
|
| 273 |
+
if (active_ != ManagedType::GAMEPAD && current_) {
|
| 274 |
+
return current_->GetVR3PointPosition();
|
| 275 |
+
}
|
| 276 |
+
return InputInterface::GetVR3PointPosition();
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
std::pair<bool, std::array<double, 12>> GetVR3PointOrientation() const override {
|
| 280 |
+
if (active_ != ManagedType::GAMEPAD && current_) {
|
| 281 |
+
return current_->GetVR3PointOrientation();
|
| 282 |
+
}
|
| 283 |
+
return InputInterface::GetVR3PointOrientation();
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
std::array<double, 3> GetVR3PointCompliance() const override {
|
| 287 |
+
if (active_ != ManagedType::GAMEPAD && current_) {
|
| 288 |
+
return current_->GetVR3PointCompliance();
|
| 289 |
+
}
|
| 290 |
+
return InputInterface::GetVR3PointCompliance();
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
std::pair<bool, std::array<double, 7>> GetHandPose(bool is_left) const override {
|
| 294 |
+
if (active_ != ManagedType::GAMEPAD && current_) {
|
| 295 |
+
return current_->GetHandPose(is_left);
|
| 296 |
+
}
|
| 297 |
+
return InputInterface::GetHandPose(is_left);
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
std::pair<bool, std::vector<double>> GetExternalTokenState() const override {
|
| 301 |
+
if (active_ != ManagedType::GAMEPAD && current_) {
|
| 302 |
+
return current_->GetExternalTokenState();
|
| 303 |
+
}
|
| 304 |
+
return InputInterface::GetExternalTokenState();
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
// Receive raw wireless remote data for gamepad
|
| 308 |
+
void UpdateGamepadRemoteData(const uint8_t* buff, size_t size) {
|
| 309 |
+
if (buff == nullptr || size == 0) { return; }
|
| 310 |
+
size_t copy_size = std::min<size_t>(size, sizeof(gamepad_data_.buff));
|
| 311 |
+
std::memcpy(gamepad_data_.buff, buff, copy_size);
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
void SetActiveInterface(ManagedType t) {
|
| 315 |
+
for (size_t i = 0; i < order_.size(); ++i) {
|
| 316 |
+
if (order_[i] == t) {
|
| 317 |
+
setActiveIndex(static_cast<int>(i));
|
| 318 |
+
return;
|
| 319 |
+
}
|
| 320 |
+
}
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
ManagedType GetActiveInterface() const { return active_; }
|
| 324 |
+
|
| 325 |
+
private:
|
| 326 |
+
void buildInterfaces() {
|
| 327 |
+
order_.push_back(ManagedType::GAMEPAD);
|
| 328 |
+
|
| 329 |
+
zmq_ = std::make_unique<ZMQEndpointInterface>(
|
| 330 |
+
zmq_host_, zmq_port_, zmq_topic_, zmq_conflate_, zmq_verbose_
|
| 331 |
+
);
|
| 332 |
+
order_.push_back(ManagedType::ZMQ);
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
void setActiveIndex(int idx) {
|
| 336 |
+
if (order_.empty()) { return; }
|
| 337 |
+
if (idx < 0) { idx = static_cast<int>(order_.size()) - 1; }
|
| 338 |
+
if (idx >= static_cast<int>(order_.size())) { idx = 0; }
|
| 339 |
+
|
| 340 |
+
// Trigger safety reset on all managed interfaces when switching
|
| 341 |
+
TriggerSafetyReset(); // Self (for gamepad mode)
|
| 342 |
+
if (zmq_) zmq_->TriggerSafetyReset();
|
| 343 |
+
|
| 344 |
+
active_index_ = idx;
|
| 345 |
+
active_ = order_[static_cast<size_t>(active_index_)];
|
| 346 |
+
|
| 347 |
+
switch (active_) {
|
| 348 |
+
case ManagedType::GAMEPAD:
|
| 349 |
+
current_ = nullptr; // Gamepad mode is handled directly
|
| 350 |
+
type_ = InputType::GAMEPAD;
|
| 351 |
+
std::cout << "[GamepadManager] Switched to: GAMEPAD (safety reset triggered)" << std::endl;
|
| 352 |
+
break;
|
| 353 |
+
case ManagedType::ZMQ:
|
| 354 |
+
current_ = zmq_.get();
|
| 355 |
+
type_ = InputType::NETWORK;
|
| 356 |
+
std::cout << "[GamepadManager] Switched to: ZMQ (safety reset triggered)" << std::endl;
|
| 357 |
+
break;
|
| 358 |
+
}
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
// Update gamepad analog and button data
|
| 362 |
+
void update_gamepad_data(unitree::common::xRockerBtnDataStruct& key_data) {
|
| 363 |
+
lx_ = lx_ * (1 - smooth_) + (std::fabs(key_data.lx) < dead_zone_ ? 0.0f : key_data.lx) * smooth_;
|
| 364 |
+
rx_ = rx_ * (1 - smooth_) + (std::fabs(key_data.rx) < dead_zone_ ? 0.0f : key_data.rx) * smooth_;
|
| 365 |
+
ry_ = ry_ * (1 - smooth_) + (std::fabs(key_data.ry) < dead_zone_ ? 0.0f : key_data.ry) * smooth_;
|
| 366 |
+
l2_ = l2_ * (1 - smooth_) + (std::fabs(key_data.L2) < dead_zone_ ? 0.0f : key_data.L2) * smooth_;
|
| 367 |
+
ly_ = ly_ * (1 - smooth_) + (std::fabs(key_data.ly) < dead_zone_ ? 0.0f : key_data.ly) * smooth_;
|
| 368 |
+
|
| 369 |
+
R1_.update(key_data.btn.components.R1);
|
| 370 |
+
L1_.update(key_data.btn.components.L1);
|
| 371 |
+
start_.update(key_data.btn.components.start);
|
| 372 |
+
select_.update(key_data.btn.components.select);
|
| 373 |
+
R2_.update(key_data.btn.components.R2);
|
| 374 |
+
L2_.update(key_data.btn.components.L2);
|
| 375 |
+
F1_.update(key_data.btn.components.F1);
|
| 376 |
+
F2_.update(key_data.btn.components.F2);
|
| 377 |
+
A_.update(key_data.btn.components.A);
|
| 378 |
+
B_.update(key_data.btn.components.B);
|
| 379 |
+
X_.update(key_data.btn.components.X);
|
| 380 |
+
Y_.update(key_data.btn.components.Y);
|
| 381 |
+
up_.update(key_data.btn.components.up);
|
| 382 |
+
right_.update(key_data.btn.components.right);
|
| 383 |
+
down_.update(key_data.btn.components.down);
|
| 384 |
+
left_.update(key_data.btn.components.left);
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
// Select a motion set by index and reset to its default mode
|
| 388 |
+
void selectMotionSet(int set_index) {
|
| 389 |
+
bool was_crawling = isInCrawlingMode();
|
| 390 |
+
|
| 391 |
+
motion_set_index_ = set_index;
|
| 392 |
+
current_motion_set_ = get_motion_set(motion_set_index_);
|
| 393 |
+
mode_index_in_set_ = 0;
|
| 394 |
+
boxing_revert_time_ = {}; // Clear boxing timer on set switch
|
| 395 |
+
|
| 396 |
+
if (was_crawling) {
|
| 397 |
+
// Soft exit: crawl → kneel → target (staged)
|
| 398 |
+
LocomotionMode target = current_motion_set_[0];
|
| 399 |
+
beginExitCrawlingTransition(target);
|
| 400 |
+
} else {
|
| 401 |
+
// Clear any pending transition and apply directly
|
| 402 |
+
transition_target_mode_ = LocomotionMode::IDLE;
|
| 403 |
+
transition_final_mode_ = LocomotionMode::IDLE;
|
| 404 |
+
transition_start_time_ = {};
|
| 405 |
+
applyModeFromSet();
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
// Boxing set enters with IDEL_BOXING → auto-revert to WALK_BOXING after 1s
|
| 409 |
+
if (motion_set_index_ == 2) {
|
| 410 |
+
boxing_revert_time_ = std::chrono::steady_clock::now();
|
| 411 |
+
}
|
| 412 |
+
if constexpr (DEBUG_LOGGING) {
|
| 413 |
+
std::cout << "[GamepadManager DEBUG] D-pad - Motion set " << motion_set_index_
|
| 414 |
+
<< ", mode: " << planner_use_movement_mode_ << std::endl;
|
| 415 |
+
}
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
// Apply mode/speed/height from the current mode_index_in_set_
|
| 419 |
+
void applyModeFromSet() {
|
| 420 |
+
if (current_motion_set_.empty()) return;
|
| 421 |
+
LocomotionMode mode = current_motion_set_[mode_index_in_set_];
|
| 422 |
+
planner_use_movement_mode_ = static_cast<int>(mode);
|
| 423 |
+
applySpeedAndHeight(mode);
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
// Set fixed speed and height for a given mode
|
| 427 |
+
void applySpeedAndHeight(LocomotionMode mode) {
|
| 428 |
+
switch (mode) {
|
| 429 |
+
case LocomotionMode::SLOW_WALK:
|
| 430 |
+
planner_use_movement_speed_ = 0.4;
|
| 431 |
+
planner_use_height_ = -1.0;
|
| 432 |
+
break;
|
| 433 |
+
case LocomotionMode::RUN:
|
| 434 |
+
planner_use_movement_speed_ = 1.5;
|
| 435 |
+
planner_use_height_ = -1.0;
|
| 436 |
+
break;
|
| 437 |
+
case LocomotionMode::CRAWLING:
|
| 438 |
+
planner_use_movement_speed_ = 0.7;
|
| 439 |
+
planner_use_height_ = 0.4;
|
| 440 |
+
break;
|
| 441 |
+
case LocomotionMode::ELBOW_CRAWLING:
|
| 442 |
+
planner_use_movement_speed_ = 0.7;
|
| 443 |
+
planner_use_height_ = 0.3;
|
| 444 |
+
break;
|
| 445 |
+
case LocomotionMode::IDEL_SQUAT:
|
| 446 |
+
case LocomotionMode::IDEL_KNEEL_TWO_LEGS:
|
| 447 |
+
case LocomotionMode::IDEL_KNEEL:
|
| 448 |
+
planner_use_movement_speed_ = -1.0;
|
| 449 |
+
planner_use_height_ = 0.4;
|
| 450 |
+
break;
|
| 451 |
+
case LocomotionMode::IDEL_BOXING:
|
| 452 |
+
case LocomotionMode::WALK_BOXING:
|
| 453 |
+
case LocomotionMode::LEFT_PUNCH:
|
| 454 |
+
case LocomotionMode::RIGHT_PUNCH:
|
| 455 |
+
case LocomotionMode::RANDOM_PUNCH:
|
| 456 |
+
case LocomotionMode::LEFT_HOOK:
|
| 457 |
+
case LocomotionMode::RIGHT_HOOK:
|
| 458 |
+
planner_use_movement_speed_ = 0.7;
|
| 459 |
+
planner_use_height_ = -1.0;
|
| 460 |
+
break;
|
| 461 |
+
default:
|
| 462 |
+
// WALK, styled walking, and other modes use defaults
|
| 463 |
+
planner_use_movement_speed_ = -1.0;
|
| 464 |
+
planner_use_height_ = -1.0;
|
| 465 |
+
break;
|
| 466 |
+
}
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
// Begin a staged crawling transition to target_mode, skipping completed stages
|
| 470 |
+
void beginCrawlingTransition(LocomotionMode target_mode) {
|
| 471 |
+
LocomotionMode current = static_cast<LocomotionMode>(planner_use_movement_mode_);
|
| 472 |
+
|
| 473 |
+
if (target_mode == LocomotionMode::CRAWLING) {
|
| 474 |
+
if (current == LocomotionMode::CRAWLING) return; // Already there
|
| 475 |
+
if (current == LocomotionMode::IDEL_KNEEL_TWO_LEGS ||
|
| 476 |
+
current == LocomotionMode::IDEL_KNEEL) {
|
| 477 |
+
// Already kneeling, transition directly to crawling after delay
|
| 478 |
+
transition_target_mode_ = LocomotionMode::CRAWLING;
|
| 479 |
+
transition_start_time_ = std::chrono::steady_clock::now();
|
| 480 |
+
} else {
|
| 481 |
+
// Need to kneel first
|
| 482 |
+
planner_use_movement_mode_ = static_cast<int>(LocomotionMode::IDEL_KNEEL_TWO_LEGS);
|
| 483 |
+
applySpeedAndHeight(LocomotionMode::IDEL_KNEEL_TWO_LEGS);
|
| 484 |
+
transition_target_mode_ = LocomotionMode::CRAWLING;
|
| 485 |
+
transition_start_time_ = std::chrono::steady_clock::now();
|
| 486 |
+
}
|
| 487 |
+
} else if (target_mode == LocomotionMode::ELBOW_CRAWLING) {
|
| 488 |
+
if (current == LocomotionMode::ELBOW_CRAWLING) return; // Already there
|
| 489 |
+
if (current == LocomotionMode::CRAWLING) {
|
| 490 |
+
// Already crawling, transition to elbow crawling after delay
|
| 491 |
+
transition_target_mode_ = LocomotionMode::ELBOW_CRAWLING;
|
| 492 |
+
transition_start_time_ = std::chrono::steady_clock::now();
|
| 493 |
+
} else if (current == LocomotionMode::IDEL_KNEEL_TWO_LEGS ||
|
| 494 |
+
current == LocomotionMode::IDEL_KNEEL) {
|
| 495 |
+
// Already kneeling, go to crawling first then elbow
|
| 496 |
+
transition_target_mode_ = LocomotionMode::CRAWLING;
|
| 497 |
+
transition_final_mode_ = LocomotionMode::ELBOW_CRAWLING;
|
| 498 |
+
transition_start_time_ = std::chrono::steady_clock::now();
|
| 499 |
+
} else {
|
| 500 |
+
// Need to kneel first, then crawl, then elbow
|
| 501 |
+
planner_use_movement_mode_ = static_cast<int>(LocomotionMode::IDEL_KNEEL_TWO_LEGS);
|
| 502 |
+
applySpeedAndHeight(LocomotionMode::IDEL_KNEEL_TWO_LEGS);
|
| 503 |
+
transition_target_mode_ = LocomotionMode::CRAWLING;
|
| 504 |
+
transition_final_mode_ = LocomotionMode::ELBOW_CRAWLING;
|
| 505 |
+
transition_start_time_ = std::chrono::steady_clock::now();
|
| 506 |
+
}
|
| 507 |
+
}
|
| 508 |
+
if constexpr (DEBUG_LOGGING) {
|
| 509 |
+
std::cout << "[GamepadManager DEBUG] Crawling transition started -> target: "
|
| 510 |
+
<< static_cast<int>(target_mode) << std::endl;
|
| 511 |
+
}
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
// Begin a staged exit from crawling/elbow crawling to target_mode (reverse of enter)
|
| 515 |
+
// ELBOW_CRAWLING → (immediate CRAWLING) → (2s) KNEEL → (2s) target
|
| 516 |
+
// CRAWLING → (immediate KNEEL) → (2s) target
|
| 517 |
+
void beginExitCrawlingTransition(LocomotionMode target_mode) {
|
| 518 |
+
LocomotionMode current = static_cast<LocomotionMode>(planner_use_movement_mode_);
|
| 519 |
+
|
| 520 |
+
if (current == LocomotionMode::ELBOW_CRAWLING) {
|
| 521 |
+
// Step down to CRAWLING immediately, then KNEEL after 2s, then target after 2s
|
| 522 |
+
planner_use_movement_mode_ = static_cast<int>(LocomotionMode::CRAWLING);
|
| 523 |
+
applySpeedAndHeight(LocomotionMode::CRAWLING);
|
| 524 |
+
transition_target_mode_ = LocomotionMode::IDEL_KNEEL_TWO_LEGS;
|
| 525 |
+
transition_final_mode_ = target_mode;
|
| 526 |
+
transition_start_time_ = std::chrono::steady_clock::now();
|
| 527 |
+
} else if (current == LocomotionMode::CRAWLING) {
|
| 528 |
+
// Step down to KNEEL immediately, then target after 2s
|
| 529 |
+
planner_use_movement_mode_ = static_cast<int>(LocomotionMode::IDEL_KNEEL_TWO_LEGS);
|
| 530 |
+
applySpeedAndHeight(LocomotionMode::IDEL_KNEEL_TWO_LEGS);
|
| 531 |
+
transition_target_mode_ = target_mode;
|
| 532 |
+
transition_start_time_ = std::chrono::steady_clock::now();
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
if constexpr (DEBUG_LOGGING) {
|
| 536 |
+
std::cout << "[GamepadManager DEBUG] Exit crawling transition -> target: "
|
| 537 |
+
<< static_cast<int>(target_mode) << std::endl;
|
| 538 |
+
}
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
// Check if current mode is crawling or elbow crawling
|
| 542 |
+
bool isInCrawlingMode() const {
|
| 543 |
+
return planner_use_movement_mode_ == static_cast<int>(LocomotionMode::CRAWLING) ||
|
| 544 |
+
planner_use_movement_mode_ == static_cast<int>(LocomotionMode::ELBOW_CRAWLING);
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
// Process timed crawling transitions
|
| 548 |
+
void processCrawlingTransitions() {
|
| 549 |
+
if (transition_start_time_.time_since_epoch().count() == 0) return;
|
| 550 |
+
|
| 551 |
+
auto elapsed = std::chrono::steady_clock::now() - transition_start_time_;
|
| 552 |
+
if (elapsed < std::chrono::seconds(2)) return;
|
| 553 |
+
|
| 554 |
+
// Time to advance to the next stage
|
| 555 |
+
LocomotionMode next = transition_target_mode_;
|
| 556 |
+
planner_use_movement_mode_ = static_cast<int>(next);
|
| 557 |
+
applySpeedAndHeight(next);
|
| 558 |
+
|
| 559 |
+
if constexpr (DEBUG_LOGGING) {
|
| 560 |
+
std::cout << "[GamepadManager DEBUG] Transition -> " << static_cast<int>(next) << std::endl;
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
// Check if there's a further stage (elbow crawling after crawling)
|
| 564 |
+
if (transition_final_mode_ != LocomotionMode::IDLE && next != transition_final_mode_) {
|
| 565 |
+
transition_target_mode_ = transition_final_mode_;
|
| 566 |
+
transition_final_mode_ = LocomotionMode::IDLE;
|
| 567 |
+
transition_start_time_ = std::chrono::steady_clock::now();
|
| 568 |
+
} else {
|
| 569 |
+
// Done
|
| 570 |
+
transition_target_mode_ = LocomotionMode::IDLE;
|
| 571 |
+
transition_final_mode_ = LocomotionMode::IDLE;
|
| 572 |
+
transition_start_time_ = {};
|
| 573 |
+
}
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
// Process gamepad inputs for planner controls (called from update())
|
| 577 |
+
void processGamepadPlannerControls() {
|
| 578 |
+
// Start button
|
| 579 |
+
if (start_.on_press) {
|
| 580 |
+
start_control_ = true;
|
| 581 |
+
if constexpr (DEBUG_LOGGING) {
|
| 582 |
+
std::cout << "[GamepadManager DEBUG] Start pressed" << std::endl;
|
| 583 |
+
}
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
// A - Emergency Stop (always available)
|
| 587 |
+
if (A_.on_press) {
|
| 588 |
+
planner_emergency_stop_ = true;
|
| 589 |
+
if constexpr (DEBUG_LOGGING) {
|
| 590 |
+
std::cout << "[GamepadManager DEBUG] A pressed - Emergency Stop" << std::endl;
|
| 591 |
+
}
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
// ---- Boxing set: direct key selection (no cycling) ----
|
| 595 |
+
if (motion_set_index_ == 2) {
|
| 596 |
+
if (X_.on_press) {
|
| 597 |
+
planner_use_movement_mode_ = static_cast<int>(LocomotionMode::WALK_BOXING);
|
| 598 |
+
applySpeedAndHeight(LocomotionMode::WALK_BOXING);
|
| 599 |
+
boxing_revert_time_ = {}; // WALK_BOXING is the default, no revert needed
|
| 600 |
+
if constexpr (DEBUG_LOGGING) { std::cout << "[GamepadManager DEBUG] X - WALK_BOXING" << std::endl; }
|
| 601 |
+
}
|
| 602 |
+
if (Y_.on_press) {
|
| 603 |
+
planner_use_movement_mode_ = static_cast<int>(LocomotionMode::RANDOM_PUNCH);
|
| 604 |
+
applySpeedAndHeight(LocomotionMode::RANDOM_PUNCH);
|
| 605 |
+
boxing_revert_time_ = {}; // RANDOM_PUNCH stays until user changes
|
| 606 |
+
if constexpr (DEBUG_LOGGING) { std::cout << "[GamepadManager DEBUG] Y - RANDOM_PUNCH" << std::endl; }
|
| 607 |
+
}
|
| 608 |
+
// B - IDEL_BOXING (auto-reverts to WALK_BOXING after 1s)
|
| 609 |
+
if (B_.on_press) {
|
| 610 |
+
planner_use_movement_mode_ = static_cast<int>(LocomotionMode::IDEL_BOXING);
|
| 611 |
+
applySpeedAndHeight(LocomotionMode::IDEL_BOXING);
|
| 612 |
+
boxing_revert_time_ = std::chrono::steady_clock::now();
|
| 613 |
+
if constexpr (DEBUG_LOGGING) { std::cout << "[GamepadManager DEBUG] B - IDEL_BOXING (auto-revert 1s)" << std::endl; }
|
| 614 |
+
}
|
| 615 |
+
// L1/R1/L2/R2 - Punches/hooks (auto-revert to WALK_BOXING after 1s)
|
| 616 |
+
if (L1_.on_press) {
|
| 617 |
+
planner_use_movement_mode_ = static_cast<int>(LocomotionMode::LEFT_PUNCH);
|
| 618 |
+
applySpeedAndHeight(LocomotionMode::LEFT_PUNCH);
|
| 619 |
+
boxing_revert_time_ = std::chrono::steady_clock::now();
|
| 620 |
+
if constexpr (DEBUG_LOGGING) { std::cout << "[GamepadManager DEBUG] L1 - LEFT_PUNCH (auto-revert 1s)" << std::endl; }
|
| 621 |
+
}
|
| 622 |
+
if (R1_.on_press) {
|
| 623 |
+
planner_use_movement_mode_ = static_cast<int>(LocomotionMode::RIGHT_PUNCH);
|
| 624 |
+
applySpeedAndHeight(LocomotionMode::RIGHT_PUNCH);
|
| 625 |
+
boxing_revert_time_ = std::chrono::steady_clock::now();
|
| 626 |
+
if constexpr (DEBUG_LOGGING) { std::cout << "[GamepadManager DEBUG] R1 - RIGHT_PUNCH (auto-revert 1s)" << std::endl; }
|
| 627 |
+
}
|
| 628 |
+
if (L2_.on_press) {
|
| 629 |
+
planner_use_movement_mode_ = static_cast<int>(LocomotionMode::LEFT_HOOK);
|
| 630 |
+
applySpeedAndHeight(LocomotionMode::LEFT_HOOK);
|
| 631 |
+
boxing_revert_time_ = std::chrono::steady_clock::now();
|
| 632 |
+
if constexpr (DEBUG_LOGGING) { std::cout << "[GamepadManager DEBUG] L2 - LEFT_HOOK (auto-revert 1s)" << std::endl; }
|
| 633 |
+
}
|
| 634 |
+
if (R2_.on_press) {
|
| 635 |
+
planner_use_movement_mode_ = static_cast<int>(LocomotionMode::RIGHT_HOOK);
|
| 636 |
+
applySpeedAndHeight(LocomotionMode::RIGHT_HOOK);
|
| 637 |
+
boxing_revert_time_ = std::chrono::steady_clock::now();
|
| 638 |
+
if constexpr (DEBUG_LOGGING) { std::cout << "[GamepadManager DEBUG] R2 - RIGHT_HOOK (auto-revert 1s)" << std::endl; }
|
| 639 |
+
}
|
| 640 |
+
|
| 641 |
+
// Auto-revert to WALK_BOXING after 1s if no new mode key pressed
|
| 642 |
+
if (boxing_revert_time_.time_since_epoch().count() != 0) {
|
| 643 |
+
auto elapsed = std::chrono::steady_clock::now() - boxing_revert_time_;
|
| 644 |
+
if (elapsed > std::chrono::milliseconds(500)) {
|
| 645 |
+
planner_use_movement_mode_ = static_cast<int>(LocomotionMode::WALK_BOXING);
|
| 646 |
+
applySpeedAndHeight(LocomotionMode::WALK_BOXING);
|
| 647 |
+
boxing_revert_time_ = {};
|
| 648 |
+
if constexpr (DEBUG_LOGGING) {
|
| 649 |
+
std::cout << "[GamepadManager DEBUG] Boxing auto-revert -> WALK_BOXING" << std::endl;
|
| 650 |
+
}
|
| 651 |
+
}
|
| 652 |
+
}
|
| 653 |
+
} else {
|
| 654 |
+
// ---- Standing / Squat / Styled sets: loop cycling ----
|
| 655 |
+
if (X_.on_press) {
|
| 656 |
+
// Next mode (wraps)
|
| 657 |
+
bool was_crawling = isInCrawlingMode();
|
| 658 |
+
mode_index_in_set_ = (mode_index_in_set_ + 1) % static_cast<int>(current_motion_set_.size());
|
| 659 |
+
LocomotionMode target = current_motion_set_[mode_index_in_set_];
|
| 660 |
+
if (target == LocomotionMode::CRAWLING || target == LocomotionMode::ELBOW_CRAWLING) {
|
| 661 |
+
beginCrawlingTransition(target);
|
| 662 |
+
} else if (was_crawling) {
|
| 663 |
+
beginExitCrawlingTransition(target);
|
| 664 |
+
} else {
|
| 665 |
+
transition_start_time_ = {}; // Cancel any pending transition
|
| 666 |
+
transition_target_mode_ = LocomotionMode::IDLE;
|
| 667 |
+
transition_final_mode_ = LocomotionMode::IDLE;
|
| 668 |
+
applyModeFromSet();
|
| 669 |
+
}
|
| 670 |
+
if constexpr (DEBUG_LOGGING) {
|
| 671 |
+
std::cout << "[GamepadManager DEBUG] X - Next mode [" << mode_index_in_set_
|
| 672 |
+
<< "]: " << planner_use_movement_mode_ << std::endl;
|
| 673 |
+
}
|
| 674 |
+
}
|
| 675 |
+
if (Y_.on_press) {
|
| 676 |
+
// Previous mode (wraps)
|
| 677 |
+
bool was_crawling = isInCrawlingMode();
|
| 678 |
+
mode_index_in_set_ = (mode_index_in_set_ - 1 + static_cast<int>(current_motion_set_.size()))
|
| 679 |
+
% static_cast<int>(current_motion_set_.size());
|
| 680 |
+
LocomotionMode target = current_motion_set_[mode_index_in_set_];
|
| 681 |
+
if (target == LocomotionMode::CRAWLING || target == LocomotionMode::ELBOW_CRAWLING) {
|
| 682 |
+
beginCrawlingTransition(target);
|
| 683 |
+
} else if (was_crawling) {
|
| 684 |
+
beginExitCrawlingTransition(target);
|
| 685 |
+
} else {
|
| 686 |
+
transition_start_time_ = {};
|
| 687 |
+
transition_target_mode_ = LocomotionMode::IDLE;
|
| 688 |
+
transition_final_mode_ = LocomotionMode::IDLE;
|
| 689 |
+
applyModeFromSet();
|
| 690 |
+
}
|
| 691 |
+
if constexpr (DEBUG_LOGGING) {
|
| 692 |
+
std::cout << "[GamepadManager DEBUG] Y - Prev mode [" << mode_index_in_set_
|
| 693 |
+
<< "]: " << planner_use_movement_mode_ << std::endl;
|
| 694 |
+
}
|
| 695 |
+
}
|
| 696 |
+
if (B_.on_press) {
|
| 697 |
+
// Reset to set's default (index 0)
|
| 698 |
+
bool was_crawling = isInCrawlingMode();
|
| 699 |
+
mode_index_in_set_ = 0;
|
| 700 |
+
LocomotionMode target = current_motion_set_[0];
|
| 701 |
+
if (was_crawling && target != LocomotionMode::CRAWLING && target != LocomotionMode::ELBOW_CRAWLING) {
|
| 702 |
+
beginExitCrawlingTransition(target);
|
| 703 |
+
} else {
|
| 704 |
+
transition_start_time_ = {};
|
| 705 |
+
transition_target_mode_ = LocomotionMode::IDLE;
|
| 706 |
+
transition_final_mode_ = LocomotionMode::IDLE;
|
| 707 |
+
applyModeFromSet();
|
| 708 |
+
}
|
| 709 |
+
if constexpr (DEBUG_LOGGING) {
|
| 710 |
+
std::cout << "[GamepadManager DEBUG] B - Reset to default mode: "
|
| 711 |
+
<< planner_use_movement_mode_ << std::endl;
|
| 712 |
+
}
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
// L1/R1 - Facing angle (not in boxing set)
|
| 716 |
+
if (L1_.on_press) {
|
| 717 |
+
planner_facing_angle_ += M_PI / 4;
|
| 718 |
+
if constexpr (DEBUG_LOGGING) {
|
| 719 |
+
std::cout << "[GamepadManager DEBUG] L1 - Facing angle: " << planner_facing_angle_ << " rad" << std::endl;
|
| 720 |
+
}
|
| 721 |
+
}
|
| 722 |
+
if (R1_.on_press) {
|
| 723 |
+
planner_facing_angle_ -= M_PI / 4;
|
| 724 |
+
if constexpr (DEBUG_LOGGING) {
|
| 725 |
+
std::cout << "[GamepadManager DEBUG] R1 - Facing angle: " << planner_facing_angle_ << " rad" << std::endl;
|
| 726 |
+
}
|
| 727 |
+
}
|
| 728 |
+
|
| 729 |
+
// L2/R2 - Height control (squat set only)
|
| 730 |
+
if (motion_set_index_ == 1) {
|
| 731 |
+
if (L2_.on_press) {
|
| 732 |
+
planner_use_height_ -= 0.1;
|
| 733 |
+
planner_use_height_ = std::max(planner_use_height_, 0.2);
|
| 734 |
+
if constexpr (DEBUG_LOGGING) {
|
| 735 |
+
std::cout << "[GamepadManager DEBUG] L2 - Height: " << planner_use_height_ << std::endl;
|
| 736 |
+
}
|
| 737 |
+
}
|
| 738 |
+
if (R2_.on_press) {
|
| 739 |
+
planner_use_height_ += 0.1;
|
| 740 |
+
planner_use_height_ = std::min(planner_use_height_, 0.8);
|
| 741 |
+
if constexpr (DEBUG_LOGGING) {
|
| 742 |
+
std::cout << "[GamepadManager DEBUG] R2 - Height: " << planner_use_height_ << std::endl;
|
| 743 |
+
}
|
| 744 |
+
}
|
| 745 |
+
}
|
| 746 |
+
}
|
| 747 |
+
|
| 748 |
+
// Process timed crawling transitions (kneel → crawl → elbow)
|
| 749 |
+
processCrawlingTransitions();
|
| 750 |
+
|
| 751 |
+
// Analog sticks - facing and movement direction
|
| 752 |
+
if (std::abs(rx_) > dead_zone_ || std::abs(ry_) > dead_zone_) {
|
| 753 |
+
planner_facing_angle_ = planner_facing_angle_ - 0.02 * rx_;
|
| 754 |
+
if constexpr (DEBUG_LOGGING) {
|
| 755 |
+
std::cout << "[GamepadManager DEBUG] Right stick - Facing angle: " << planner_facing_angle_ << " rad" << std::endl;
|
| 756 |
+
}
|
| 757 |
+
}
|
| 758 |
+
|
| 759 |
+
if (std::abs(lx_) > dead_zone_ || std::abs(ly_) > dead_zone_) {
|
| 760 |
+
double raw_angle = atan2(ly_, lx_);
|
| 761 |
+
double bin_size = M_PI / 4.0; // 8 directions (45° bins)
|
| 762 |
+
double binned_angle = std::round(raw_angle / bin_size) * bin_size;
|
| 763 |
+
planner_moving_direction_ = binned_angle - M_PI / 2 + planner_facing_angle_;
|
| 764 |
+
if constexpr (DEBUG_LOGGING) {
|
| 765 |
+
std::cout << "[GamepadManager DEBUG] Left stick - Raw: " << raw_angle
|
| 766 |
+
<< ", Binned: " << binned_angle
|
| 767 |
+
<< ", Moving: " << planner_moving_direction_ << " rad" << std::endl;
|
| 768 |
+
}
|
| 769 |
+
}
|
| 770 |
+
}
|
| 771 |
+
|
| 772 |
+
// Handle gamepad planner input (called from handle_input())
|
| 773 |
+
void handleGamepadPlannerInput(MotionDataReader& motion_reader,
|
| 774 |
+
std::shared_ptr<const MotionSequence>& current_motion,
|
| 775 |
+
int& current_frame,
|
| 776 |
+
OperatorState& operator_state,
|
| 777 |
+
bool& reinitialize_heading,
|
| 778 |
+
DataBuffer<HeadingState>& heading_state_buffer,
|
| 779 |
+
PlannerState& planner_state,
|
| 780 |
+
DataBuffer<MovementState>& movement_state_buffer,
|
| 781 |
+
std::mutex& current_motion_mutex) {
|
| 782 |
+
// Handle safety reset from interface manager
|
| 783 |
+
if (CheckAndClearSafetyReset()) {
|
| 784 |
+
{
|
| 785 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 786 |
+
operator_state.play = false;
|
| 787 |
+
}
|
| 788 |
+
if (operator_state.start) {
|
| 789 |
+
if (planner_state.enabled && planner_state.initialized) {
|
| 790 |
+
// Planner is already on, keep it as is (don't touch initialized flag)
|
| 791 |
+
{
|
| 792 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 793 |
+
if (current_motion->GetEncodeMode() >= 0) {
|
| 794 |
+
current_motion->SetEncodeMode(0);
|
| 795 |
+
}
|
| 796 |
+
operator_state.play = true;
|
| 797 |
+
}
|
| 798 |
+
auto current_facing = movement_state_buffer.GetDataWithTime().data->facing_direction;
|
| 799 |
+
planner_facing_angle_ = std::atan2(current_facing[1], current_facing[0]);
|
| 800 |
+
std::cout << "[GamepadManager] Safety reset: Planner kept enabled with current state" << std::endl;
|
| 801 |
+
} else {
|
| 802 |
+
// Planner was disabled, set initial movement state
|
| 803 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE),
|
| 804 |
+
{0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 805 |
+
|
| 806 |
+
// Now enable planner
|
| 807 |
+
planner_state.enabled = true;
|
| 808 |
+
planner_facing_angle_ = 0.0;
|
| 809 |
+
std::cout << "[GamepadManager] Planner enabled" << std::endl;
|
| 810 |
+
|
| 811 |
+
// Wait for planner to be initialized with timeout (5 seconds)
|
| 812 |
+
auto wait_start = std::chrono::steady_clock::now();
|
| 813 |
+
constexpr auto PLANNER_INIT_TIMEOUT = std::chrono::seconds(5);
|
| 814 |
+
while (planner_state.enabled) {
|
| 815 |
+
{
|
| 816 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 817 |
+
if (current_motion->name == "planner_motion") {
|
| 818 |
+
std::cout << "[GamepadManager] motion name is planner_motion" << std::endl;
|
| 819 |
+
break;
|
| 820 |
+
}
|
| 821 |
+
}
|
| 822 |
+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
| 823 |
+
auto elapsed = std::chrono::steady_clock::now() - wait_start;
|
| 824 |
+
if (elapsed > PLANNER_INIT_TIMEOUT) {
|
| 825 |
+
std::cerr << "[GamepadManager ERROR] Planner initialization timeout after 5 seconds" << std::endl;
|
| 826 |
+
operator_state.stop = true;
|
| 827 |
+
return;
|
| 828 |
+
}
|
| 829 |
+
std::cout << "[GamepadManager] Waiting for planner to be initialized" << std::endl;
|
| 830 |
+
}
|
| 831 |
+
|
| 832 |
+
// Check if planner is enabled and initialized
|
| 833 |
+
if (!planner_state.enabled || !planner_state.initialized) {
|
| 834 |
+
std::cerr << "[GamepadManager ERROR] Planner failed to initialize. Stopping control." << std::endl;
|
| 835 |
+
operator_state.stop = true;
|
| 836 |
+
return;
|
| 837 |
+
}
|
| 838 |
+
|
| 839 |
+
// Play motion
|
| 840 |
+
{
|
| 841 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 842 |
+
operator_state.play = true;
|
| 843 |
+
}
|
| 844 |
+
}
|
| 845 |
+
}
|
| 846 |
+
return;
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
// Handle control start (Start button)
|
| 850 |
+
if (start_control_) {
|
| 851 |
+
// Start control
|
| 852 |
+
operator_state.start = true;
|
| 853 |
+
{
|
| 854 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 855 |
+
operator_state.play = false;
|
| 856 |
+
// Reinitialize base quaternion and reset delta heading
|
| 857 |
+
reinitialize_heading = true;
|
| 858 |
+
}
|
| 859 |
+
|
| 860 |
+
// Ensure planner is enabled (always required in GamepadManager mode)
|
| 861 |
+
if (!planner_state.enabled) {
|
| 862 |
+
planner_state.enabled = true;
|
| 863 |
+
planner_facing_angle_ = 0.0;
|
| 864 |
+
std::cout << "[GamepadManager] Planner enabled" << std::endl;
|
| 865 |
+
}
|
| 866 |
+
|
| 867 |
+
// Wait for planner to be initialized with timeout (5 seconds)
|
| 868 |
+
auto wait_start = std::chrono::steady_clock::now();
|
| 869 |
+
constexpr auto PLANNER_INIT_TIMEOUT = std::chrono::seconds(5);
|
| 870 |
+
while (planner_state.enabled) {
|
| 871 |
+
{
|
| 872 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 873 |
+
if (current_motion->name == "planner_motion") {
|
| 874 |
+
std::cout << "[GamepadManager] motion name is planner_motion" << std::endl;
|
| 875 |
+
break;
|
| 876 |
+
}
|
| 877 |
+
}
|
| 878 |
+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
| 879 |
+
auto elapsed = std::chrono::steady_clock::now() - wait_start;
|
| 880 |
+
if (elapsed > PLANNER_INIT_TIMEOUT) {
|
| 881 |
+
std::cerr << "[GamepadManager ERROR] Planner initialization timeout after 5 seconds" << std::endl;
|
| 882 |
+
operator_state.stop = true;
|
| 883 |
+
return;
|
| 884 |
+
}
|
| 885 |
+
std::cout << "[GamepadManager] Waiting for planner to be initialized" << std::endl;
|
| 886 |
+
}
|
| 887 |
+
|
| 888 |
+
// Check if planner is enabled and initialized
|
| 889 |
+
if (!planner_state.enabled || !planner_state.initialized) {
|
| 890 |
+
std::cerr << "[GamepadManager ERROR] Planner failed to initialize. Stopping control." << std::endl;
|
| 891 |
+
operator_state.stop = true;
|
| 892 |
+
return;
|
| 893 |
+
}
|
| 894 |
+
|
| 895 |
+
// Play motion
|
| 896 |
+
{
|
| 897 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 898 |
+
operator_state.play = true;
|
| 899 |
+
}
|
| 900 |
+
}
|
| 901 |
+
|
| 902 |
+
// Handle reinitialize command
|
| 903 |
+
if (reinitialize_) {
|
| 904 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 905 |
+
reinitialize_heading = true;
|
| 906 |
+
if constexpr (DEBUG_LOGGING) {
|
| 907 |
+
std::cout << "[GamepadManager DEBUG] Reinitialized base quaternion and facing angle" << std::endl;
|
| 908 |
+
}
|
| 909 |
+
}
|
| 910 |
+
|
| 911 |
+
// If planner is enabled and initialized, send movement commands
|
| 912 |
+
if (planner_state.enabled && planner_state.initialized) {
|
| 913 |
+
int final_mode = planner_use_movement_mode_;
|
| 914 |
+
std::array<double, 3> final_movement = {double(cos(planner_moving_direction_)),
|
| 915 |
+
double(sin(planner_moving_direction_)), 0.0};
|
| 916 |
+
std::array<double, 3> final_facing_direction = {double(cos(planner_facing_angle_)),
|
| 917 |
+
double(sin(planner_facing_angle_)), 0.0};
|
| 918 |
+
double final_speed = planner_use_movement_speed_;
|
| 919 |
+
double final_height = planner_use_height_;
|
| 920 |
+
|
| 921 |
+
// If left sticks in dead zone — idle logic depends on motion set
|
| 922 |
+
if (std::abs(lx_) < dead_zone_ && std::abs(ly_) < dead_zone_) {
|
| 923 |
+
if (motion_set_index_ == 0) {
|
| 924 |
+
// Standing set → go to IDLE
|
| 925 |
+
final_mode = static_cast<int>(LocomotionMode::IDLE);
|
| 926 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 927 |
+
final_speed = -1.0f;
|
| 928 |
+
final_height = -1.0f;
|
| 929 |
+
} else if (motion_set_index_ == 1) {
|
| 930 |
+
// Squat set → stay in mode, zero movement
|
| 931 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 932 |
+
LocomotionMode cur = static_cast<LocomotionMode>(planner_use_movement_mode_);
|
| 933 |
+
if (cur == LocomotionMode::CRAWLING || cur == LocomotionMode::ELBOW_CRAWLING) {
|
| 934 |
+
final_speed = 0.0f;
|
| 935 |
+
} else {
|
| 936 |
+
final_speed = -1.0f;
|
| 937 |
+
}
|
| 938 |
+
final_height = planner_use_height_;
|
| 939 |
+
} else if (motion_set_index_ == 2) {
|
| 940 |
+
// Boxing set → stay in mode, zero movement
|
| 941 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 942 |
+
LocomotionMode cur = static_cast<LocomotionMode>(planner_use_movement_mode_);
|
| 943 |
+
if (cur == LocomotionMode::LEFT_PUNCH || cur == LocomotionMode::RIGHT_PUNCH ||
|
| 944 |
+
cur == LocomotionMode::LEFT_HOOK || cur == LocomotionMode::RIGHT_HOOK) {
|
| 945 |
+
// Punches use facing direction as movement
|
| 946 |
+
final_movement = final_facing_direction;
|
| 947 |
+
}
|
| 948 |
+
final_speed = 0.0f;
|
| 949 |
+
final_height = -1.0f;
|
| 950 |
+
} else if (motion_set_index_ == 3) {
|
| 951 |
+
// Styled walking → stay in mode, zero movement
|
| 952 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 953 |
+
final_speed = 0.0f;
|
| 954 |
+
final_height = -1.0f;
|
| 955 |
+
}
|
| 956 |
+
}
|
| 957 |
+
|
| 958 |
+
// Emergency stop
|
| 959 |
+
if (planner_emergency_stop_) {
|
| 960 |
+
if (motion_set_index_ == 0) {
|
| 961 |
+
final_mode = static_cast<int>(LocomotionMode::IDLE);
|
| 962 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 963 |
+
final_speed = -1.0f;
|
| 964 |
+
final_height = -1.0f;
|
| 965 |
+
} else {
|
| 966 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 967 |
+
final_speed = 0.0f;
|
| 968 |
+
final_height = (motion_set_index_ == 1) ? planner_use_height_ : -1.0f;
|
| 969 |
+
}
|
| 970 |
+
if constexpr (DEBUG_LOGGING) {
|
| 971 |
+
std::cout << "[GamepadManager DEBUG] Emergency stop - movement reset" << std::endl;
|
| 972 |
+
}
|
| 973 |
+
}
|
| 974 |
+
|
| 975 |
+
// Static kneel modes always zero movement
|
| 976 |
+
if (planner_use_movement_mode_ == static_cast<int>(LocomotionMode::IDEL_KNEEL_TWO_LEGS) ||
|
| 977 |
+
planner_use_movement_mode_ == static_cast<int>(LocomotionMode::IDEL_KNEEL) ||
|
| 978 |
+
planner_use_movement_mode_ == static_cast<int>(LocomotionMode::IDEL_SQUAT)) {
|
| 979 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 980 |
+
final_speed = 0.0f;
|
| 981 |
+
final_height = planner_use_height_;
|
| 982 |
+
}
|
| 983 |
+
|
| 984 |
+
MovementState mode_state(final_mode, final_movement, final_facing_direction, final_speed, final_height);
|
| 985 |
+
movement_state_buffer.SetData(mode_state);
|
| 986 |
+
}
|
| 987 |
+
}
|
| 988 |
+
|
| 989 |
+
private:
|
| 990 |
+
// ------------------------------------------------------------------
|
| 991 |
+
// Owned delegate interfaces (gamepad mode is handled inline, not delegated)
|
| 992 |
+
// ------------------------------------------------------------------
|
| 993 |
+
std::unique_ptr<ZMQEndpointInterface> zmq_; ///< ZMQ streaming handler.
|
| 994 |
+
|
| 995 |
+
InputInterface* current_ = nullptr; ///< Non-owning pointer to active delegate (nullptr in gamepad mode).
|
| 996 |
+
|
| 997 |
+
// ------------------------------------------------------------------
|
| 998 |
+
// Active-selection bookkeeping
|
| 999 |
+
// ------------------------------------------------------------------
|
| 1000 |
+
std::vector<ManagedType> order_; ///< Insertion-order of available modes.
|
| 1001 |
+
int active_index_ = 0; ///< Index into order_.
|
| 1002 |
+
ManagedType active_ = ManagedType::GAMEPAD; ///< Currently-active mode tag.
|
| 1003 |
+
|
| 1004 |
+
// ZMQ configuration (stored for deferred construction)
|
| 1005 |
+
std::string zmq_host_;
|
| 1006 |
+
int zmq_port_;
|
| 1007 |
+
std::string zmq_topic_;
|
| 1008 |
+
bool zmq_conflate_ = false;
|
| 1009 |
+
bool zmq_verbose_ = false;
|
| 1010 |
+
|
| 1011 |
+
/// Global emergency-stop flag, set by 'O'/'o' keyboard shortcut.
|
| 1012 |
+
bool emergency_stop_ = false;
|
| 1013 |
+
/// Global temperature report flag, set by 'F'/'f' keyboard shortcut.
|
| 1014 |
+
bool report_temperature_flag_ = false;
|
| 1015 |
+
|
| 1016 |
+
// ------------------------------------------------------------------
|
| 1017 |
+
// Per-frame gamepad action flags (reset at the start of update())
|
| 1018 |
+
// ------------------------------------------------------------------
|
| 1019 |
+
bool start_control_ = false; ///< Start-button pressed this frame.
|
| 1020 |
+
bool stop_control_ = false; ///< Select-button pressed this frame.
|
| 1021 |
+
bool reinitialize_ = false; ///< X/Y-button reinitialize heading.
|
| 1022 |
+
bool planner_emergency_stop_ = false; ///< A-button emergency stop.
|
| 1023 |
+
|
| 1024 |
+
// ------------------------------------------------------------------
|
| 1025 |
+
// Gamepad hardware state
|
| 1026 |
+
// ------------------------------------------------------------------
|
| 1027 |
+
unitree::common::REMOTE_DATA_RX gamepad_data_ = unitree::common::REMOTE_DATA_RX(); ///< Raw 40-byte packet.
|
| 1028 |
+
|
| 1029 |
+
// Smoothed analog stick values
|
| 1030 |
+
float lx_ = 0.0f; ///< Left stick horizontal (smoothed).
|
| 1031 |
+
float rx_ = 0.0f; ///< Right stick horizontal (smoothed).
|
| 1032 |
+
float ry_ = 0.0f; ///< Right stick vertical (smoothed).
|
| 1033 |
+
float l2_ = 0.0f; ///< Left trigger analog (smoothed).
|
| 1034 |
+
float ly_ = 0.0f; ///< Left stick vertical (smoothed).
|
| 1035 |
+
float smooth_ = 0.3f; ///< EMA smoothing factor.
|
| 1036 |
+
float dead_zone_ = 0.05f; ///< Analog dead-zone threshold.
|
| 1037 |
+
|
| 1038 |
+
// Edge-detecting buttons
|
| 1039 |
+
unitree::common::Button R1_, L1_, start_, select_, R2_, L2_;
|
| 1040 |
+
unitree::common::Button F1_, F2_, A_, B_, X_, Y_;
|
| 1041 |
+
unitree::common::Button up_, right_, down_, left_;
|
| 1042 |
+
|
| 1043 |
+
// ------------------------------------------------------------------
|
| 1044 |
+
// Motion set state
|
| 1045 |
+
// ------------------------------------------------------------------
|
| 1046 |
+
int motion_set_index_ = 0; ///< Active motion set (0=Standing, 1=Squat, 2=Boxing, 3=Styled).
|
| 1047 |
+
std::vector<LocomotionMode> current_motion_set_; ///< Modes in the active set.
|
| 1048 |
+
int mode_index_in_set_ = 0; ///< Current mode index within the set.
|
| 1049 |
+
|
| 1050 |
+
// ------------------------------------------------------------------
|
| 1051 |
+
// Planner control state (persists across frames)
|
| 1052 |
+
// ------------------------------------------------------------------
|
| 1053 |
+
int planner_use_movement_mode_ = static_cast<int>(LocomotionMode::SLOW_WALK); ///< Current locomotion mode.
|
| 1054 |
+
double planner_use_movement_speed_ = 0.4; ///< Desired speed (fixed per mode).
|
| 1055 |
+
double planner_use_height_ = -1.0; ///< Desired body height (−1 = mode default).
|
| 1056 |
+
double planner_facing_angle_ = 0.0; ///< Accumulated facing direction (radians).
|
| 1057 |
+
double planner_moving_direction_ = 0.0; ///< Current movement direction (radians).
|
| 1058 |
+
|
| 1059 |
+
// ------------------------------------------------------------------
|
| 1060 |
+
// Staged crawling transition state
|
| 1061 |
+
// ------------------------------------------------------------------
|
| 1062 |
+
/// Target mode for the current transition stage (e.g., CRAWLING after kneeling).
|
| 1063 |
+
LocomotionMode transition_target_mode_ = LocomotionMode::IDLE;
|
| 1064 |
+
/// Final mode for multi-stage transitions (e.g., ELBOW_CRAWLING via kneel→crawl→elbow).
|
| 1065 |
+
LocomotionMode transition_final_mode_ = LocomotionMode::IDLE;
|
| 1066 |
+
/// Timestamp when the current transition stage began.
|
| 1067 |
+
std::chrono::time_point<std::chrono::steady_clock> transition_start_time_{};
|
| 1068 |
+
|
| 1069 |
+
// ------------------------------------------------------------------
|
| 1070 |
+
// Boxing auto-revert state
|
| 1071 |
+
// ------------------------------------------------------------------
|
| 1072 |
+
/// Timestamp when a punch/hook/idle was triggered; reverts to WALK_BOXING after 1s.
|
| 1073 |
+
std::chrono::time_point<std::chrono::steady_clock> boxing_revert_time_{};
|
| 1074 |
+
};
|
| 1075 |
+
|
| 1076 |
+
#endif // GAMEPAD_MANAGER_HPP
|
| 1077 |
+
|
| 1078 |
+
|
GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/input_command.hpp
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file input_command.hpp
|
| 3 |
+
* @brief Lightweight message structs used for inter-component communication
|
| 4 |
+
* between ZMQ subscribers and the input managers.
|
| 5 |
+
*
|
| 6 |
+
* Two message types are defined:
|
| 7 |
+
* - CommandMessage – carries high-level control signals (start / stop /
|
| 8 |
+
* planner-mode toggle) received on the ZMQ "command" topic.
|
| 9 |
+
* - PlannerMessage – carries per-frame locomotion commands (mode, movement
|
| 10 |
+
* direction, facing direction, speed, height, and optional
|
| 11 |
+
* upper-body / hand data) received on the ZMQ "planner" topic.
|
| 12 |
+
*
|
| 13 |
+
* Both structs are plain-old-data (POD-like) value types designed to be written
|
| 14 |
+
* under a mutex by a background ZMQ thread and read by the main control loop.
|
| 15 |
+
*/
|
| 16 |
+
|
| 17 |
+
#pragma once
|
| 18 |
+
|
| 19 |
+
#include <array>
|
| 20 |
+
#include <chrono>
|
| 21 |
+
#include <optional>
|
| 22 |
+
|
| 23 |
+
#include "../localmotion_kplanner.hpp" // For LocomotionMode enum
|
| 24 |
+
|
| 25 |
+
// ---------------------------------------------------------------------------
|
| 26 |
+
// CommandMessage
|
| 27 |
+
// ---------------------------------------------------------------------------
|
| 28 |
+
/**
|
| 29 |
+
* @brief Wire format for the ZMQ "command" topic.
|
| 30 |
+
*
|
| 31 |
+
* Packed binary layout sent by the remote controller:
|
| 32 |
+
* { start: bool, stop: bool, planner: bool, delta_heading?: f32/f64 }
|
| 33 |
+
*
|
| 34 |
+
* Multiple messages between two update() calls are accumulated using OR logic
|
| 35 |
+
* for start/stop (so a transient pulse is never lost), while the planner flag
|
| 36 |
+
* is overwritten with the latest value.
|
| 37 |
+
*/
|
| 38 |
+
struct CommandMessage {
|
| 39 |
+
bool start = false; ///< When true, request the control system to start.
|
| 40 |
+
bool stop = false; ///< When true, request an emergency / graceful stop.
|
| 41 |
+
bool planner = false; ///< true → planner mode (use planner topic for locomotion)
|
| 42 |
+
///< false → streamed-motion mode (use pose topic)
|
| 43 |
+
/// Optional absolute heading override (radians). When set, the value is
|
| 44 |
+
/// written directly into HeadingState.delta_heading.
|
| 45 |
+
std::optional<double> delta_heading;
|
| 46 |
+
bool valid = false; ///< Set to true once a message has been decoded successfully.
|
| 47 |
+
};
|
| 48 |
+
|
| 49 |
+
// ---------------------------------------------------------------------------
|
| 50 |
+
// PlannerMessage
|
| 51 |
+
// ---------------------------------------------------------------------------
|
| 52 |
+
/**
|
| 53 |
+
* @brief Wire format for the ZMQ "planner" topic.
|
| 54 |
+
*
|
| 55 |
+
* Required fields (must be present in every message):
|
| 56 |
+
* - mode : int32 – LocomotionMode enum cast (IDLE, WALK, RUN, …)
|
| 57 |
+
* - movement : float[3] – desired movement direction unit vector (x, y, z)
|
| 58 |
+
* - facing : float[3] – desired facing direction unit vector (x, y, z)
|
| 59 |
+
*
|
| 60 |
+
* Optional fields (may or may not be present):
|
| 61 |
+
* - speed : float – desired locomotion speed (-1.0 = use default)
|
| 62 |
+
* - height : float – desired body height (-1.0 = use default)
|
| 63 |
+
* - upper_body_position: float[17] – target upper-body joint positions (radians)
|
| 64 |
+
* - upper_body_velocity: float[17] – target upper-body joint velocities (rad/s)
|
| 65 |
+
* - left_hand_joints : float[7] – Dex3 left-hand joint positions
|
| 66 |
+
* - right_hand_joints : float[7] – Dex3 right-hand joint positions
|
| 67 |
+
*
|
| 68 |
+
* The `timestamp` field is set locally on receipt and used for timeout
|
| 69 |
+
* detection (planner messages older than ~1 s are considered stale).
|
| 70 |
+
*/
|
| 71 |
+
struct PlannerMessage {
|
| 72 |
+
bool valid = false; ///< True once this struct contains a successfully decoded message.
|
| 73 |
+
|
| 74 |
+
/// Locomotion mode (cast of LocomotionMode enum). Defaults to IDLE.
|
| 75 |
+
int mode = static_cast<int>(LocomotionMode::IDLE);
|
| 76 |
+
|
| 77 |
+
/// Desired movement direction as a 3D unit vector [x, y, z].
|
| 78 |
+
/// Zeroed when the robot should stand still.
|
| 79 |
+
std::array<double, 3> movement = {0.0, 0.0, 0.0};
|
| 80 |
+
|
| 81 |
+
/// Desired facing direction as a 3D unit vector [x, y, z].
|
| 82 |
+
/// Defaults to facing forward along the +X axis.
|
| 83 |
+
std::array<double, 3> facing = {1.0, 0.0, 0.0};
|
| 84 |
+
|
| 85 |
+
/// Optional upper-body joint target positions (17 DOF, radians).
|
| 86 |
+
/// Present when the remote controller provides whole-body commands.
|
| 87 |
+
std::optional<std::array<double, 17>> upper_body_position;
|
| 88 |
+
|
| 89 |
+
/// Optional upper-body joint target velocities (17 DOF, rad/s).
|
| 90 |
+
std::optional<std::array<double, 17>> upper_body_velocity;
|
| 91 |
+
|
| 92 |
+
/// Optional left-hand Dex3 joint positions (7 DOF).
|
| 93 |
+
std::optional<std::array<double, 7>> left_hand_joints;
|
| 94 |
+
|
| 95 |
+
/// Optional right-hand Dex3 joint positions (7 DOF).
|
| 96 |
+
std::optional<std::array<double, 7>> right_hand_joints;
|
| 97 |
+
|
| 98 |
+
/// Desired locomotion speed. -1.0 means "use the default for the current mode".
|
| 99 |
+
double speed = -1.0;
|
| 100 |
+
|
| 101 |
+
/// Desired body height. -1.0 means "use the default for the current mode".
|
| 102 |
+
double height = -1.0;
|
| 103 |
+
|
| 104 |
+
/// Local steady-clock timestamp recorded when the message was received.
|
| 105 |
+
/// Used to detect planner timeouts (stale data → fallback to IDLE).
|
| 106 |
+
std::chrono::steady_clock::time_point timestamp{};
|
| 107 |
+
};
|
| 108 |
+
|
GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/input_interface.hpp
ADDED
|
@@ -0,0 +1,505 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file input_interface.hpp
|
| 3 |
+
* @brief Abstract base class for all input sources that can drive the G1 robot.
|
| 4 |
+
*
|
| 5 |
+
* InputInterface defines the polymorphic contract that every concrete input
|
| 6 |
+
* handler (keyboard, gamepad, ZMQ, ROS2, …) must implement. Two pure-virtual
|
| 7 |
+
* methods form the core of the interface:
|
| 8 |
+
*
|
| 9 |
+
* 1. update() – Poll the hardware / network for new data and latch it
|
| 10 |
+
* into internal flags (called once per control-loop tick).
|
| 11 |
+
* 2. handle_input() – Translate the latched flags into system-state changes
|
| 12 |
+
* (motion switching, planner commands, heading adjustments,
|
| 13 |
+
* operator start / stop, etc.).
|
| 14 |
+
*
|
| 15 |
+
* The base class also provides:
|
| 16 |
+
* - Thread-safe DataBuffer members for VR 3-point tracking, upper-body
|
| 17 |
+
* joint targets, hand joint targets, and external token state.
|
| 18 |
+
* - Shared stdin buffering so the InterfaceManager can pre-read keys and
|
| 19 |
+
* dispatch them to the correct active interface.
|
| 20 |
+
* - A safety-reset mechanism used by managers when switching active
|
| 21 |
+
* interfaces to prevent stale commands from carrying over.
|
| 22 |
+
* - Keyboard-controlled compliance and hand-close-ratio helpers that are
|
| 23 |
+
* shared across all input modes (g/h/b/v and x/c keys).
|
| 24 |
+
*
|
| 25 |
+
* @note The class is non-copyable and non-movable by design. Instances are
|
| 26 |
+
* always managed via std::unique_ptr by the owning manager.
|
| 27 |
+
*/
|
| 28 |
+
|
| 29 |
+
#ifndef INPUT_INTERFACE_HPP
|
| 30 |
+
#define INPUT_INTERFACE_HPP
|
| 31 |
+
|
| 32 |
+
#include <unistd.h>
|
| 33 |
+
#include <atomic>
|
| 34 |
+
#include <queue>
|
| 35 |
+
#include <memory>
|
| 36 |
+
#include <optional>
|
| 37 |
+
#include "../utils.hpp" // For DataBuffer
|
| 38 |
+
#include "../robot_parameters.hpp" // For HeadingState, OperatorState
|
| 39 |
+
#include "../motion_data_reader.hpp" // For MotionDataReader, MotionSequence
|
| 40 |
+
#include "../math_utils.hpp" // For float_to_double
|
| 41 |
+
#include "../localmotion_kplanner.hpp" // For PlannerState, MovementState
|
| 42 |
+
|
| 43 |
+
/**
|
| 44 |
+
* @class InputInterface
|
| 45 |
+
* @brief Abstract base class for all input sources (keyboard, gamepad, ZMQ, ROS2).
|
| 46 |
+
*
|
| 47 |
+
* Concrete sub-classes must implement update() and handle_input().
|
| 48 |
+
* The base class owns shared state (VR buffers, compliance values, stdin queue)
|
| 49 |
+
* that all implementations can use.
|
| 50 |
+
*/
|
| 51 |
+
class InputInterface {
|
| 52 |
+
public:
|
| 53 |
+
|
| 54 |
+
/// Identifies the physical / logical source of this input interface.
|
| 55 |
+
enum class InputType {
|
| 56 |
+
KEYBOARD, ///< Terminal stdin keyboard
|
| 57 |
+
GAMEPAD, ///< Unitree wireless gamepad (raw joystick data)
|
| 58 |
+
ROS2, ///< ROS 2 topics (msgpack-serialised ControlGoalMsg)
|
| 59 |
+
NETWORK, ///< ZMQ packed-message protocol (pose / planner topics)
|
| 60 |
+
UNKNOWN ///< Default / uninitialised
|
| 61 |
+
};
|
| 62 |
+
|
| 63 |
+
/// Virtual destructor ensures correct cleanup of derived classes.
|
| 64 |
+
virtual ~InputInterface() = default;
|
| 65 |
+
|
| 66 |
+
// ------------------------------------------------------------------
|
| 67 |
+
// Pure-virtual interface
|
| 68 |
+
// ------------------------------------------------------------------
|
| 69 |
+
|
| 70 |
+
/**
|
| 71 |
+
* @brief Poll the input source for new data and latch it internally.
|
| 72 |
+
*
|
| 73 |
+
* Called once per control-loop tick **before** handle_input().
|
| 74 |
+
* Implementations should:
|
| 75 |
+
* - Read all available bytes / messages from their source.
|
| 76 |
+
* - Update per-frame boolean flags (e.g. start_control, delta_left).
|
| 77 |
+
* - NOT modify shared system state – that is done in handle_input().
|
| 78 |
+
*/
|
| 79 |
+
virtual void update() = 0;
|
| 80 |
+
|
| 81 |
+
/**
|
| 82 |
+
* @brief Apply latched input flags to the system state.
|
| 83 |
+
*
|
| 84 |
+
* Called once per control-loop tick **after** update().
|
| 85 |
+
*
|
| 86 |
+
* @param motion_reader Reference to the pre-loaded motion library.
|
| 87 |
+
* @param current_motion Currently-active MotionSequence (may be swapped).
|
| 88 |
+
* @param current_frame Playback cursor within current_motion.
|
| 89 |
+
* @param operator_state High-level operator signals (start/stop/play).
|
| 90 |
+
* @param reinitialize_heading Set to true to recapture the IMU heading.
|
| 91 |
+
* @param heading_state_buffer Thread-safe heading buffer (delta heading, init quat).
|
| 92 |
+
* @param has_planner Whether a locomotion planner is loaded.
|
| 93 |
+
* @param planner_state Planner enable / initialise state.
|
| 94 |
+
* @param movement_state_buffer Thread-safe buffer for locomotion commands.
|
| 95 |
+
* @param current_motion_mutex Guards concurrent access to current_motion/frame.
|
| 96 |
+
*/
|
| 97 |
+
virtual void handle_input(MotionDataReader& motion_reader,
|
| 98 |
+
std::shared_ptr<const MotionSequence>& current_motion,
|
| 99 |
+
int& current_frame,
|
| 100 |
+
OperatorState& operator_state,
|
| 101 |
+
bool& reinitialize_heading,
|
| 102 |
+
DataBuffer<HeadingState>& heading_state_buffer,
|
| 103 |
+
bool has_planner,
|
| 104 |
+
PlannerState& planner_state,
|
| 105 |
+
DataBuffer<MovementState>& movement_state_buffer,
|
| 106 |
+
std::mutex& current_motion_mutex,
|
| 107 |
+
bool& report_temperature) = 0;
|
| 108 |
+
|
| 109 |
+
// ------------------------------------------------------------------
|
| 110 |
+
// Capability queries – overridden by sub-classes as needed
|
| 111 |
+
// ------------------------------------------------------------------
|
| 112 |
+
|
| 113 |
+
/// @return The InputType tag for this concrete implementation.
|
| 114 |
+
virtual InputType GetType() {
|
| 115 |
+
return type_;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
/// @return True if this interface provides upper-body joint targets (17 DOF).
|
| 119 |
+
virtual bool HasUpperBodyControl() const {
|
| 120 |
+
return has_upper_body_control_;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
/// @return True if this interface provides VR 3-point tracking data
|
| 124 |
+
/// (left wrist, right wrist, head – 9 position + 12 orientation values).
|
| 125 |
+
virtual bool HasVR3PointControl() const {
|
| 126 |
+
return has_vr_3point_control_;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
/// @return True if this interface provides VR 5-point tracking data
|
| 130 |
+
/// (3-point + 2 extra trackers – 15 position + 20 orientation values).
|
| 131 |
+
virtual bool HasVR5PointControl() const {
|
| 132 |
+
return has_vr_5point_control_;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
/// @return True if this interface provides Dex3 hand joint targets (7 DOF per hand).
|
| 136 |
+
virtual bool HasHandJoints() const {
|
| 137 |
+
return has_hand_joints_;
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
/// @return True if an external token-state vector is available (e.g. from ROS2/ZMQ).
|
| 141 |
+
virtual bool HasExternalTokenState() const {
|
| 142 |
+
return has_external_token_state_;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
/**
|
| 146 |
+
* @brief Retrieve the latest external token-state vector (if any).
|
| 147 |
+
* @return A pair: {true, vector} if data is available; {false, {}} otherwise.
|
| 148 |
+
*/
|
| 149 |
+
virtual std::pair<bool, std::vector<double>> GetExternalTokenState() const {
|
| 150 |
+
if (!has_external_token_state_) {
|
| 151 |
+
return {false, {}};
|
| 152 |
+
}
|
| 153 |
+
auto buffered_data = external_token_state_.GetDataWithTime();
|
| 154 |
+
if (buffered_data.data) {
|
| 155 |
+
return {true, *buffered_data.data};
|
| 156 |
+
}
|
| 157 |
+
return {false, {}};
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
// ------------------------------------------------------------------
|
| 161 |
+
// VR 3-point tracking data accessors
|
| 162 |
+
// Layout: [left_wrist(xyz), right_wrist(xyz), head(xyz)]
|
| 163 |
+
// [left_quat(wxyz), right_quat(wxyz), head_quat(wxyz)]
|
| 164 |
+
// ------------------------------------------------------------------
|
| 165 |
+
|
| 166 |
+
/// Write new VR 3-point position data into the thread-safe buffer.
|
| 167 |
+
virtual void SetVR3PointPosition(const std::array<double, 9>& position) {
|
| 168 |
+
vr_3point_position_.SetData(position);
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
/// Write new VR 3-point orientation data into the thread-safe buffer.
|
| 172 |
+
virtual void SetVR3PointOrientation(const std::array<double, 12>& orientation) {
|
| 173 |
+
vr_3point_orientation_.SetData(orientation);
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
/**
|
| 177 |
+
* @brief Get the latest VR 3-point position data.
|
| 178 |
+
* @return {true, positions} if VR tracking is active and data is available;
|
| 179 |
+
* {false, default_positions} otherwise.
|
| 180 |
+
*
|
| 181 |
+
* Default positions (metres, in body frame):
|
| 182 |
+
* left wrist = ( 0.0903, 0.1615, -0.2411)
|
| 183 |
+
* right wrist = ( 0.1280, -0.1522, -0.2461)
|
| 184 |
+
* head = ( 0.0241, -0.0081, 0.4028)
|
| 185 |
+
*/
|
| 186 |
+
virtual std::pair<bool, std::array<double, 9>> GetVR3PointPosition() const {
|
| 187 |
+
if(!has_vr_3point_control_) {
|
| 188 |
+
return {false, {0.0903, 0.1615, -0.2411,
|
| 189 |
+
0.1280, -0.1522, -0.2461,
|
| 190 |
+
0.0241, -0.0081, 0.4028}};
|
| 191 |
+
}
|
| 192 |
+
auto buffered_data = vr_3point_position_.GetDataWithTime();
|
| 193 |
+
if (buffered_data.data) {
|
| 194 |
+
return {true, *buffered_data.data};
|
| 195 |
+
}
|
| 196 |
+
return {false, {0.0903, 0.1615, -0.2411,
|
| 197 |
+
0.1280, -0.1522, -0.2461,
|
| 198 |
+
0.0241, -0.0081, 0.4028}};
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
/**
|
| 202 |
+
* @brief Get the latest VR 3-point orientation data (quaternions w,x,y,z).
|
| 203 |
+
* @return {true, orientations} if VR tracking is active and data is available;
|
| 204 |
+
* {false, default_orientations} otherwise.
|
| 205 |
+
*/
|
| 206 |
+
virtual std::pair<bool, std::array<double, 12>> GetVR3PointOrientation() const {
|
| 207 |
+
if(!has_vr_3point_control_) {
|
| 208 |
+
return {false, {0.7295, 0.3145, 0.5533, -0.2506,
|
| 209 |
+
0.7320, -0.2639, 0.5395, 0.3217,
|
| 210 |
+
0.9991, 0.011, 0.0402, -0.0002}};
|
| 211 |
+
}
|
| 212 |
+
auto buffered_data = vr_3point_orientation_.GetDataWithTime();
|
| 213 |
+
if (buffered_data.data) {
|
| 214 |
+
return {true, *buffered_data.data};
|
| 215 |
+
}
|
| 216 |
+
return {false, {0.7295, 0.3145, 0.5533, -0.2506,
|
| 217 |
+
0.7320, -0.2639, 0.5395, 0.3217,
|
| 218 |
+
0.9991, 0.011, 0.0402, -0.0002}};
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
/**
|
| 222 |
+
* @brief Get VR 3-point compliance values (keyboard-controlled).
|
| 223 |
+
*
|
| 224 |
+
* Compliance is always controlled via keyboard (g/h keys for left, b/v for right)
|
| 225 |
+
* and is intentionally **not** overwritten by external sources (ROS2 / ZMQ).
|
| 226 |
+
* @return [left_arm_compliance, right_arm_compliance, head_compliance] in [0, 0.5].
|
| 227 |
+
*/
|
| 228 |
+
virtual std::array<double, 3> GetVR3PointCompliance() const {
|
| 229 |
+
auto buffered_data = vr_3point_compliance_.GetDataWithTime();
|
| 230 |
+
if (buffered_data.data) {
|
| 231 |
+
return *buffered_data.data;
|
| 232 |
+
}
|
| 233 |
+
return {0.5, 0.5, 0.0};
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
/// Set initial VR 3-point compliance values (called at startup from command-line args).
|
| 237 |
+
virtual void SetVR3PointCompliance(const std::array<double, 3>& compliance) {
|
| 238 |
+
vr_3point_compliance_.SetData(compliance);
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
/// Adjust left-arm (index 0) compliance by @p delta, clamped to [0.0, 0.5].
|
| 242 |
+
/// Only takes effect if the active policy observes 'vr_3point_compliance'.
|
| 243 |
+
virtual void AdjustLeftHandCompliance(double delta) {
|
| 244 |
+
auto compliance = GetVR3PointCompliance();
|
| 245 |
+
compliance[0] = std::clamp(compliance[0] + delta, 0.0, 0.5);
|
| 246 |
+
SetVR3PointCompliance(compliance);
|
| 247 |
+
std::cout << "[Compliance] Left hand: " << GetVR3PointCompliance()[0] << std::endl;
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
/// Adjust right-arm (index 1) compliance by @p delta, clamped to [0.0, 0.5].
|
| 251 |
+
/// Only takes effect if the active policy observes 'vr_3point_compliance'.
|
| 252 |
+
virtual void AdjustRightHandCompliance(double delta) {
|
| 253 |
+
auto compliance = GetVR3PointCompliance();
|
| 254 |
+
compliance[1] = std::clamp(compliance[1] + delta, 0.0, 0.5);
|
| 255 |
+
SetVR3PointCompliance(compliance);
|
| 256 |
+
std::cout << "[Compliance] Right hand: " << GetVR3PointCompliance()[1] << std::endl;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
// =========================================================================
|
| 260 |
+
// Hand max close ratio control (keyboard-controlled via X/C keys)
|
| 261 |
+
// Controls how much the Dex3 hands can close (0.2 = 80% open, 1.0 = fully closed)
|
| 262 |
+
// =========================================================================
|
| 263 |
+
|
| 264 |
+
// Get the current max close ratio (keyboard-controlled)
|
| 265 |
+
virtual double GetMaxCloseRatio() const {
|
| 266 |
+
return max_close_ratio_;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
// Set initial max close ratio (from command line or initialization)
|
| 270 |
+
virtual void SetMaxCloseRatio(double ratio) {
|
| 271 |
+
max_close_ratio_.store(std::clamp(ratio, 0.2, 1.0), std::memory_order_relaxed);
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
// Adjust max close ratio by delta (X = +0.1, C = -0.1), clipped to [0.2, 1.0]
|
| 275 |
+
virtual void AdjustMaxCloseRatio(double delta) {
|
| 276 |
+
double new_val = std::clamp(max_close_ratio_.load(std::memory_order_relaxed) + delta, 0.2, 1.0);
|
| 277 |
+
max_close_ratio_.store(new_val, std::memory_order_relaxed);
|
| 278 |
+
std::cout << "[InputInterface] Max close ratio adjusted to: " << new_val
|
| 279 |
+
<< " (range: 0.2-1.0, higher = more closed)" << std::endl;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
// ------------------------------------------------------------------
|
| 283 |
+
// VR 5-point tracking (3-point + 2 extra body trackers)
|
| 284 |
+
// Layout: [left_wrist, right_wrist, head, tracker1, tracker2] × xyz
|
| 285 |
+
// ------------------------------------------------------------------
|
| 286 |
+
|
| 287 |
+
/**
|
| 288 |
+
* @brief Get VR 5-point position data (15 doubles).
|
| 289 |
+
* @return {true, positions} if 5-point tracking is active; {false, defaults} otherwise.
|
| 290 |
+
*/
|
| 291 |
+
virtual std::pair<bool, std::array<double, 15>> GetVR5PointPosition() const {
|
| 292 |
+
if(!has_vr_5point_control_) {
|
| 293 |
+
return {false, {0.0903, 0.1615, -0.2411,
|
| 294 |
+
0.1280, -0.1522, -0.2461,
|
| 295 |
+
0.0, 0.0, 0.0,
|
| 296 |
+
0.0, 0.0, 0.0,
|
| 297 |
+
0.0, 0.0, 0.0}};
|
| 298 |
+
}
|
| 299 |
+
auto buffered_data = vr_5point_position_.GetDataWithTime();
|
| 300 |
+
if (buffered_data.data) {
|
| 301 |
+
return {true, *buffered_data.data};
|
| 302 |
+
}
|
| 303 |
+
return {false, {0.0903, 0.1615, -0.2411,
|
| 304 |
+
0.1280, -0.1522, -0.2461,
|
| 305 |
+
0.0, 0.0, 0.0,
|
| 306 |
+
0.0, 0.0, 0.0,
|
| 307 |
+
0.0, 0.0, 0.0}};
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
/// @brief Get VR 5-point orientation data (20 doubles = 5 quaternions × wxyz).
|
| 311 |
+
virtual std::pair<bool, std::array<double, 20>> GetVR5PointOrientation() const {
|
| 312 |
+
if(!has_vr_5point_control_) {
|
| 313 |
+
return {false, {0.7295, 0.3145, 0.5533, -0.2506,
|
| 314 |
+
0.7320, -0.2639, 0.5395, 0.3217,
|
| 315 |
+
0.9991, 0.011, 0.0402, -0.0002,
|
| 316 |
+
1.0, 0.0, 0.0, 0.0,
|
| 317 |
+
1.0, 0.0, 0.0, 0.0}};
|
| 318 |
+
}
|
| 319 |
+
auto buffered_data = vr_5point_orientation_.GetDataWithTime();
|
| 320 |
+
if (buffered_data.data) {
|
| 321 |
+
return {true, *buffered_data.data};
|
| 322 |
+
}
|
| 323 |
+
return {false, {0.7295, 0.3145, 0.5533, -0.2506,
|
| 324 |
+
0.7320, -0.2639, 0.5395, 0.3217,
|
| 325 |
+
0.9991, 0.011, 0.0402, -0.0002,
|
| 326 |
+
1.0, 0.0, 0.0, 0.0,
|
| 327 |
+
1.0, 0.0, 0.0, 0.0}};
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
// ------------------------------------------------------------------
|
| 331 |
+
// Hand / upper-body joint accessors
|
| 332 |
+
// ------------------------------------------------------------------
|
| 333 |
+
|
| 334 |
+
/**
|
| 335 |
+
* @brief Get 7-DOF Dex3 hand joint positions.
|
| 336 |
+
* @param is_left true → left hand, false → right hand.
|
| 337 |
+
* @return {true, joints} if hand joint data is available; {false, defaults} otherwise.
|
| 338 |
+
* Default left = {0, 0, 1.75, -1.57, -1.75, -1.57, -1.75}
|
| 339 |
+
* Default right = {0, 0, -1.75, 1.57, 1.75, 1.57, 1.75}
|
| 340 |
+
*/
|
| 341 |
+
virtual std::pair<bool, std::array<double, 7>> GetHandPose(bool is_left) const {
|
| 342 |
+
if(!has_hand_joints_) {
|
| 343 |
+
if(is_left) {
|
| 344 |
+
return {false, {0, 0, 1.75, -1.57, -1.75, -1.57, -1.75 }};
|
| 345 |
+
} else {
|
| 346 |
+
return {false, {0, 0, -1.75, 1.57, 1.75, 1.57, 1.75 }};
|
| 347 |
+
}
|
| 348 |
+
}
|
| 349 |
+
if(is_left) {
|
| 350 |
+
auto buffered_data = left_hand_joint_.GetDataWithTime();
|
| 351 |
+
if (buffered_data.data) {
|
| 352 |
+
return {true, *buffered_data.data};
|
| 353 |
+
}
|
| 354 |
+
else {
|
| 355 |
+
return {false, {0, 0, 1.75, -1.57, -1.75, -1.57, -1.75 }};
|
| 356 |
+
}
|
| 357 |
+
} else {
|
| 358 |
+
auto buffered_data = right_hand_joint_.GetDataWithTime();
|
| 359 |
+
if (buffered_data.data) {
|
| 360 |
+
return {true, *buffered_data.data};
|
| 361 |
+
} else {
|
| 362 |
+
return {false, {0, 0, -1.75, 1.57, 1.75, 1.57, 1.75 }};
|
| 363 |
+
}
|
| 364 |
+
}
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
/// @brief Get upper-body joint target positions (17 DOF, radians).
|
| 368 |
+
/// @return {true, positions} if upper-body data is available; {false, zeros} otherwise.
|
| 369 |
+
virtual std::pair<bool, std::array<double, 17>> GetUpperBodyJointPositions() const {
|
| 370 |
+
if(!has_upper_body_control_) {
|
| 371 |
+
return {false, {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}};
|
| 372 |
+
}
|
| 373 |
+
auto buffered_data = upper_body_joint_positions_.GetDataWithTime();
|
| 374 |
+
if (buffered_data.data) {
|
| 375 |
+
return {true, *buffered_data.data};
|
| 376 |
+
}
|
| 377 |
+
return {false, {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}};
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
/// @brief Get upper-body joint target velocities (17 DOF, rad/s).
|
| 381 |
+
/// @return {true, velocities} if upper-body data is available; {false, zeros} otherwise.
|
| 382 |
+
virtual std::pair<bool, std::array<double, 17>> GetUpperBodyJointVelocities() const {
|
| 383 |
+
if(!has_upper_body_control_) {
|
| 384 |
+
return {false, {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}};
|
| 385 |
+
}
|
| 386 |
+
auto buffered_data = upper_body_joint_velocities_.GetDataWithTime();
|
| 387 |
+
if (buffered_data.data) {
|
| 388 |
+
return {true, *buffered_data.data};
|
| 389 |
+
}
|
| 390 |
+
return {false, {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}};
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
/// @brief Get the last update time of the input interface.
|
| 394 |
+
virtual std::optional<std::chrono::steady_clock::time_point> GetLastUpdateTime() const {
|
| 395 |
+
// Return empty optional as a default, implementers should override this if they have a timestamp
|
| 396 |
+
return {};
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
// ------------------------------------------------------------------
|
| 400 |
+
// Shared stdin buffering
|
| 401 |
+
// ------------------------------------------------------------------
|
| 402 |
+
/**
|
| 403 |
+
* @brief Read one character, trying the manager-pushed buffer first, then raw stdin.
|
| 404 |
+
*
|
| 405 |
+
* When an InterfaceManager is active, it pre-reads stdin and dispatches
|
| 406 |
+
* keys: manager-level shortcuts are consumed, everything else is queued
|
| 407 |
+
* via PushStdinChar() for the active interface to read later.
|
| 408 |
+
*
|
| 409 |
+
* @param[out] ch The character that was read.
|
| 410 |
+
* @return True if a character was available, false otherwise.
|
| 411 |
+
*/
|
| 412 |
+
bool ReadStdinChar(char& ch) {
|
| 413 |
+
// First check if we have buffered keys from manager
|
| 414 |
+
if (!stdin_buffer_.empty()) {
|
| 415 |
+
ch = stdin_buffer_.front();
|
| 416 |
+
stdin_buffer_.pop();
|
| 417 |
+
return true;
|
| 418 |
+
}
|
| 419 |
+
// Otherwise read directly from stdin (non-blocking)
|
| 420 |
+
return read(STDIN_FILENO, &ch, 1) > 0;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
/// Queue a key into this interface's stdin buffer (used by managers).
|
| 424 |
+
void PushStdinChar(char ch) {
|
| 425 |
+
stdin_buffer_.push(ch);
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
// ------------------------------------------------------------------
|
| 429 |
+
// Safety-reset mechanism (used when switching active interfaces)
|
| 430 |
+
// ------------------------------------------------------------------
|
| 431 |
+
|
| 432 |
+
/// Flag that tells the interface to abandon its current mode and return
|
| 433 |
+
/// to a safe default (disable planner, stop streaming, etc.).
|
| 434 |
+
std::atomic<bool> reset_to_safe_state_{false};
|
| 435 |
+
|
| 436 |
+
/// Called by the manager to request a safety reset on this interface.
|
| 437 |
+
void TriggerSafetyReset() {
|
| 438 |
+
reset_to_safe_state_.store(true, std::memory_order_release);
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
/// Atomically check and clear the safety-reset flag.
|
| 442 |
+
/// @return True if a reset was requested since the last call.
|
| 443 |
+
bool CheckAndClearSafetyReset() {
|
| 444 |
+
return reset_to_safe_state_.exchange(false, std::memory_order_acq_rel);
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
protected:
|
| 448 |
+
/// Protected default constructor – only concrete sub-classes may instantiate.
|
| 449 |
+
InputInterface() = default;
|
| 450 |
+
|
| 451 |
+
// Non-copyable, non-movable (instances are managed via unique_ptr).
|
| 452 |
+
InputInterface(const InputInterface&) = delete;
|
| 453 |
+
InputInterface& operator=(const InputInterface&) = delete;
|
| 454 |
+
InputInterface(InputInterface&&) = delete;
|
| 455 |
+
InputInterface& operator=(InputInterface&&) = delete;
|
| 456 |
+
|
| 457 |
+
// ------------------------------------------------------------------
|
| 458 |
+
// Capability flags – set by sub-class constructors / update methods
|
| 459 |
+
// ------------------------------------------------------------------
|
| 460 |
+
InputType type_ = InputType::UNKNOWN; ///< Concrete input source tag.
|
| 461 |
+
std::atomic<bool> has_vr_3point_control_{false}; ///< VR 3-point tracking available.
|
| 462 |
+
std::atomic<bool> has_vr_5point_control_{false}; ///< VR 5-point tracking available.
|
| 463 |
+
std::atomic<bool> has_hand_joints_{false}; ///< Dex3 hand joint data available.
|
| 464 |
+
std::atomic<bool> has_external_token_state_{false}; ///< External token-state vector available.
|
| 465 |
+
std::atomic<bool> has_upper_body_control_{false}; ///< Upper-body 17-DOF targets available.
|
| 466 |
+
|
| 467 |
+
// ------------------------------------------------------------------
|
| 468 |
+
// Thread-safe data buffers (written by input threads, read by control loop)
|
| 469 |
+
// ------------------------------------------------------------------
|
| 470 |
+
|
| 471 |
+
/// VR 3-point positions [left_wrist xyz, right_wrist xyz, head xyz].
|
| 472 |
+
DataBuffer<std::array<double, 9>> vr_3point_position_;
|
| 473 |
+
/// VR 3-point orientations [left quat wxyz, right quat wxyz, head quat wxyz].
|
| 474 |
+
DataBuffer<std::array<double, 12>> vr_3point_orientation_;
|
| 475 |
+
/// VR 3-point compliance [left_arm, right_arm, head] – keyboard-controlled.
|
| 476 |
+
DataBuffer<std::array<double, 3>> vr_3point_compliance_;
|
| 477 |
+
/// VR 5-point positions (3-point + 2 extra trackers × xyz = 15 values).
|
| 478 |
+
DataBuffer<std::array<double, 15>> vr_5point_position_;
|
| 479 |
+
/// VR 5-point orientations (5 quaternions × wxyz = 20 values).
|
| 480 |
+
DataBuffer<std::array<double, 20>> vr_5point_orientation_;
|
| 481 |
+
|
| 482 |
+
/// Upper-body target joint positions (17 DOF, radians).
|
| 483 |
+
DataBuffer<std::array<double, 17>> upper_body_joint_positions_;
|
| 484 |
+
/// Upper-body target joint velocities (17 DOF, rad/s).
|
| 485 |
+
DataBuffer<std::array<double, 17>> upper_body_joint_velocities_;
|
| 486 |
+
|
| 487 |
+
/// Left-hand Dex3 joint positions (7 DOF).
|
| 488 |
+
DataBuffer<std::array<double, 7>> left_hand_joint_;
|
| 489 |
+
/// Right-hand Dex3 joint positions (7 DOF).
|
| 490 |
+
DataBuffer<std::array<double, 7>> right_hand_joint_;
|
| 491 |
+
|
| 492 |
+
/// Arbitrary external token-state vector (e.g. latent codes from a remote model).
|
| 493 |
+
DataBuffer<std::vector<double>> external_token_state_;
|
| 494 |
+
|
| 495 |
+
/// Keyboard-controlled max close ratio for Dex3 hands.
|
| 496 |
+
/// Adjusted via keyboard (X = +0.1, C = −0.1), clamped to [0.2, 1.0].
|
| 497 |
+
/// 1.0 = fully closed allowed (default); use --max-close-ratio CLI arg to limit.
|
| 498 |
+
std::atomic<double> max_close_ratio_{1.0};
|
| 499 |
+
|
| 500 |
+
/// Shared stdin buffer – the InterfaceManager pushes non-manager keys here
|
| 501 |
+
/// for the currently-active interface to consume via ReadStdinChar().
|
| 502 |
+
std::queue<char> stdin_buffer_;
|
| 503 |
+
};
|
| 504 |
+
|
| 505 |
+
#endif // INPUT_INTERFACE_HPP
|
GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/interface_manager.hpp
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file interface_manager.hpp
|
| 3 |
+
* @brief Multiplexer that owns several concrete InputInterface instances and
|
| 4 |
+
* delegates all calls to the currently-active one.
|
| 5 |
+
*
|
| 6 |
+
* InterfaceManager is itself an InputInterface so it can be used transparently
|
| 7 |
+
* by the main control loop. It creates and owns:
|
| 8 |
+
* - SimpleKeyboard (index 0, default)
|
| 9 |
+
* - Gamepad (index 1)
|
| 10 |
+
* - ZMQEndpointInterface (index 2)
|
| 11 |
+
* - ROS2InputHandler (index 3, only when compiled with HAS_ROS2)
|
| 12 |
+
*
|
| 13 |
+
* Switching is done via **keyboard shortcuts** typed into the terminal:
|
| 14 |
+
* '!' → Keyboard | '@' → Gamepad | '#' → ZMQ | '$' → ROS2
|
| 15 |
+
*
|
| 16 |
+
* The manager also handles several **global** controls that work regardless of
|
| 17 |
+
* which interface is active:
|
| 18 |
+
* - 'O'/'o' → emergency stop
|
| 19 |
+
* - g/G, h/H → left-hand compliance ±0.1
|
| 20 |
+
* - b/B, v/V → right-hand compliance ±0.1
|
| 21 |
+
* - x/X, c/C → hand max-close ratio ±0.1
|
| 22 |
+
*
|
| 23 |
+
* When switching interfaces, a **safety reset** is triggered on *all* managed
|
| 24 |
+
* interfaces to prevent stale planner / streaming state from carrying over.
|
| 25 |
+
*/
|
| 26 |
+
|
| 27 |
+
#ifndef INTERFACE_MANAGER_HPP
|
| 28 |
+
#define INTERFACE_MANAGER_HPP
|
| 29 |
+
|
| 30 |
+
#include <memory>
|
| 31 |
+
#include <vector>
|
| 32 |
+
#include <iostream>
|
| 33 |
+
#include <cstring>
|
| 34 |
+
#include <cstdlib>
|
| 35 |
+
|
| 36 |
+
#include "input_interface.hpp"
|
| 37 |
+
#include "keyboard_handler.hpp"
|
| 38 |
+
#include "gamepad.hpp"
|
| 39 |
+
#include "zmq_endpoint_interface.hpp"
|
| 40 |
+
|
| 41 |
+
#if HAS_ROS2
|
| 42 |
+
#include "ros2_input_handler.hpp"
|
| 43 |
+
#endif
|
| 44 |
+
|
| 45 |
+
/**
|
| 46 |
+
* @class InterfaceManager
|
| 47 |
+
* @brief Composite InputInterface that delegates to one of several concrete
|
| 48 |
+
* implementations, switchable at run-time via keyboard shortcuts.
|
| 49 |
+
*
|
| 50 |
+
* All getter methods (HasVR3PointControl, GetHandPose, etc.) are forwarded to
|
| 51 |
+
* the currently-active delegate so the control loop always sees live values.
|
| 52 |
+
*/
|
| 53 |
+
class InterfaceManager : public InputInterface {
|
| 54 |
+
public:
|
| 55 |
+
/// Identifies which concrete interface is currently active.
|
| 56 |
+
enum class ManagedType {
|
| 57 |
+
KEYBOARD = 0, ///< SimpleKeyboard (stdin)
|
| 58 |
+
GAMEPAD = 1, ///< Unitree wireless gamepad
|
| 59 |
+
ZMQ = 2, ///< ZMQ packed-message streaming
|
| 60 |
+
ROS2 = 3 ///< ROS 2 teleop (requires HAS_ROS2)
|
| 61 |
+
};
|
| 62 |
+
|
| 63 |
+
/**
|
| 64 |
+
* @brief Construct the manager, creating all sub-interfaces.
|
| 65 |
+
* @param zmq_host ZMQ server hostname (passed to ZMQEndpointInterface).
|
| 66 |
+
* @param zmq_port ZMQ server port.
|
| 67 |
+
* @param zmq_topic ZMQ subscription topic.
|
| 68 |
+
* @param zmq_conflate Whether to enable ZMQ conflate (latest-only) mode.
|
| 69 |
+
* @param zmq_verbose Enable verbose ZMQ logging.
|
| 70 |
+
*/
|
| 71 |
+
InterfaceManager(
|
| 72 |
+
const std::string& zmq_host,
|
| 73 |
+
int zmq_port,
|
| 74 |
+
const std::string& zmq_topic,
|
| 75 |
+
bool zmq_conflate,
|
| 76 |
+
bool zmq_verbose
|
| 77 |
+
) : InputInterface(), zmq_host_(zmq_host), zmq_port_(zmq_port), zmq_topic_(zmq_topic),
|
| 78 |
+
zmq_conflate_(zmq_conflate), zmq_verbose_(zmq_verbose) {
|
| 79 |
+
type_ = InputType::UNKNOWN;
|
| 80 |
+
buildInterfaces();
|
| 81 |
+
setActiveIndex(0); // default to keyboard (index 0)
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
void update() override {
|
| 85 |
+
// Reset per-frame flags
|
| 86 |
+
emergency_stop_ = false;
|
| 87 |
+
report_temperature_flag_ = false;
|
| 88 |
+
|
| 89 |
+
// Read stdin using shared buffering mechanism, check for manager keys
|
| 90 |
+
char ch;
|
| 91 |
+
while (ReadStdinChar(ch)) {
|
| 92 |
+
bool is_manager_key = false;
|
| 93 |
+
switch (ch) {
|
| 94 |
+
case '!':
|
| 95 |
+
SetActiveInterface(ManagedType::KEYBOARD);
|
| 96 |
+
is_manager_key = true;
|
| 97 |
+
break;
|
| 98 |
+
case '@':
|
| 99 |
+
SetActiveInterface(ManagedType::GAMEPAD);
|
| 100 |
+
is_manager_key = true;
|
| 101 |
+
break;
|
| 102 |
+
case '#':
|
| 103 |
+
SetActiveInterface(ManagedType::ZMQ);
|
| 104 |
+
is_manager_key = true;
|
| 105 |
+
break;
|
| 106 |
+
case '$':
|
| 107 |
+
SetActiveInterface(ManagedType::ROS2);
|
| 108 |
+
is_manager_key = true;
|
| 109 |
+
break;
|
| 110 |
+
case 'o':
|
| 111 |
+
case 'O':
|
| 112 |
+
// Global emergency stop - works for all interfaces (especially gamepad)
|
| 113 |
+
emergency_stop_ = true;
|
| 114 |
+
is_manager_key = true;
|
| 115 |
+
std::cout << "[InterfaceManager] EMERGENCY STOP triggered (O/o key pressed)" << std::endl;
|
| 116 |
+
break;
|
| 117 |
+
// Global compliance controls - work across ALL interfaces (keyboard, planner, ROS2, ZMQ)
|
| 118 |
+
case 'g':
|
| 119 |
+
case 'G':
|
| 120 |
+
// Increase left hand compliance by 0.1
|
| 121 |
+
AdjustLeftHandCompliance(0.1);
|
| 122 |
+
is_manager_key = true;
|
| 123 |
+
break;
|
| 124 |
+
case 'h':
|
| 125 |
+
case 'H':
|
| 126 |
+
// Decrease left hand compliance by 0.1
|
| 127 |
+
AdjustLeftHandCompliance(-0.1);
|
| 128 |
+
is_manager_key = true;
|
| 129 |
+
break;
|
| 130 |
+
case 'b':
|
| 131 |
+
case 'B':
|
| 132 |
+
// Increase right hand compliance by 0.1
|
| 133 |
+
AdjustRightHandCompliance(0.1);
|
| 134 |
+
is_manager_key = true;
|
| 135 |
+
break;
|
| 136 |
+
case 'v':
|
| 137 |
+
case 'V':
|
| 138 |
+
// Decrease right hand compliance by 0.1
|
| 139 |
+
AdjustRightHandCompliance(-0.1);
|
| 140 |
+
is_manager_key = true;
|
| 141 |
+
break;
|
| 142 |
+
// Global hand max close ratio controls - work across ALL interfaces (x/c keys)
|
| 143 |
+
case 'x':
|
| 144 |
+
case 'X':
|
| 145 |
+
// Increase max close ratio by 0.1 (allow hands to close more)
|
| 146 |
+
AdjustMaxCloseRatio(0.1);
|
| 147 |
+
is_manager_key = true;
|
| 148 |
+
break;
|
| 149 |
+
case 'c':
|
| 150 |
+
case 'C':
|
| 151 |
+
// Decrease max close ratio by 0.1 (keep hands more open)
|
| 152 |
+
AdjustMaxCloseRatio(-0.1);
|
| 153 |
+
is_manager_key = true;
|
| 154 |
+
break;
|
| 155 |
+
case 'f':
|
| 156 |
+
case 'F':
|
| 157 |
+
// Global temperature report
|
| 158 |
+
report_temperature_flag_ = true;
|
| 159 |
+
is_manager_key = true;
|
| 160 |
+
break;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
// Buffer non-manager keys for the active interface to read
|
| 164 |
+
// Note: 'O'/'o' is NOT passed through since it's handled globally
|
| 165 |
+
if (!is_manager_key) {
|
| 166 |
+
current_->PushStdinChar(ch);
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
// Run the actual active interface update (it will read buffered keys)
|
| 171 |
+
current_->update();
|
| 172 |
+
|
| 173 |
+
// Note: We don't cache data here anymore - just forward calls to active interface
|
| 174 |
+
// This ensures we always get the most current values
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
void handle_input(MotionDataReader& motion_reader,
|
| 178 |
+
std::shared_ptr<const MotionSequence>& current_motion,
|
| 179 |
+
int& current_frame,
|
| 180 |
+
OperatorState& operator_state,
|
| 181 |
+
bool& reinitialize_heading,
|
| 182 |
+
DataBuffer<HeadingState>& heading_state_buffer,
|
| 183 |
+
bool has_planner,
|
| 184 |
+
PlannerState& planner_state,
|
| 185 |
+
DataBuffer<MovementState>& movement_state_buffer,
|
| 186 |
+
std::mutex& current_motion_mutex,
|
| 187 |
+
bool& report_temperature) override {
|
| 188 |
+
|
| 189 |
+
// Handle global emergency stop (works for all interfaces, especially gamepad)
|
| 190 |
+
if (emergency_stop_) {
|
| 191 |
+
operator_state.stop = true;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
// Handle global temperature report (F key)
|
| 195 |
+
if (report_temperature_flag_) {
|
| 196 |
+
report_temperature = true;
|
| 197 |
+
report_temperature_flag_ = false;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
// Check if ROS2 is active but planner is not available - switch to keyboard
|
| 201 |
+
if (active_ == ManagedType::ROS2 && !has_planner) {
|
| 202 |
+
std::cout << "[InterfaceManager] ROS2 requires planner but planner not loaded. Switching to KEYBOARD" << std::endl;
|
| 203 |
+
SetActiveInterface(ManagedType::KEYBOARD);
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
// Delegate to the active interface
|
| 207 |
+
current_->handle_input(motion_reader, current_motion, current_frame, operator_state,
|
| 208 |
+
reinitialize_heading, heading_state_buffer, has_planner, planner_state,
|
| 209 |
+
movement_state_buffer, current_motion_mutex, report_temperature);
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
// Override all getters to forward directly to the active interface
|
| 213 |
+
// This makes the manager a transparent proxy that always returns live values
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
bool HasVR3PointControl() const override {
|
| 217 |
+
if (current_) {
|
| 218 |
+
return current_->HasVR3PointControl();
|
| 219 |
+
}
|
| 220 |
+
return has_vr_3point_control_;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
bool HasHandJoints() const override {
|
| 224 |
+
if (current_) {
|
| 225 |
+
return current_->HasHandJoints();
|
| 226 |
+
}
|
| 227 |
+
return has_hand_joints_;
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
bool HasExternalTokenState() const override {
|
| 231 |
+
if (current_) {
|
| 232 |
+
return current_->HasExternalTokenState();
|
| 233 |
+
}
|
| 234 |
+
return has_external_token_state_;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
std::pair<bool, std::array<double, 9>> GetVR3PointPosition() const override {
|
| 238 |
+
if (current_) {
|
| 239 |
+
return current_->GetVR3PointPosition();
|
| 240 |
+
}
|
| 241 |
+
return InputInterface::GetVR3PointPosition(); // Fallback to base class
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
std::pair<bool, std::array<double, 12>> GetVR3PointOrientation() const override {
|
| 245 |
+
if (current_) {
|
| 246 |
+
return current_->GetVR3PointOrientation();
|
| 247 |
+
}
|
| 248 |
+
return InputInterface::GetVR3PointOrientation(); // Fallback to base class
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
std::pair<bool, std::array<double, 7>> GetHandPose(bool is_left) const override {
|
| 252 |
+
if (current_) {
|
| 253 |
+
return current_->GetHandPose(is_left);
|
| 254 |
+
}
|
| 255 |
+
return InputInterface::GetHandPose(is_left); // Fallback to base class
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
std::pair<bool, std::vector<double>> GetExternalTokenState() const override {
|
| 259 |
+
if (current_) {
|
| 260 |
+
return current_->GetExternalTokenState();
|
| 261 |
+
}
|
| 262 |
+
return InputInterface::GetExternalTokenState(); // Fallback to base class
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
/// Forward raw wireless-remote byte buffer to the internal Gamepad instance.
|
| 266 |
+
/// Called by the Unitree SDK callback whenever new joystick data arrives.
|
| 267 |
+
void UpdateGamepadRemoteData(const uint8_t* buff, size_t size) {
|
| 268 |
+
if (!gamepad_ || buff == nullptr || size == 0) { return; }
|
| 269 |
+
size_t copy_size = std::min<size_t>(size, sizeof(gamepad_->gamepad_data.buff));
|
| 270 |
+
std::memcpy(gamepad_->gamepad_data.buff, buff, copy_size);
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
/// Programmatically switch to a specific interface type.
|
| 274 |
+
/// Triggers safety reset on all interfaces and prints a log message.
|
| 275 |
+
void SetActiveInterface(ManagedType t) {
|
| 276 |
+
for (size_t i = 0; i < order_.size(); ++i) {
|
| 277 |
+
if (order_[i] == t) {
|
| 278 |
+
setActiveIndex(static_cast<int>(i));
|
| 279 |
+
return;
|
| 280 |
+
}
|
| 281 |
+
}
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
ManagedType GetActiveInterface() const { return active_; }
|
| 285 |
+
|
| 286 |
+
private:
|
| 287 |
+
/// Instantiate all concrete interfaces and register them in order_.
|
| 288 |
+
void buildInterfaces() {
|
| 289 |
+
keyboard_ = std::make_unique<SimpleKeyboard>();
|
| 290 |
+
order_.push_back(ManagedType::KEYBOARD);
|
| 291 |
+
|
| 292 |
+
gamepad_ = std::make_unique<unitree::common::Gamepad>();
|
| 293 |
+
order_.push_back(ManagedType::GAMEPAD);
|
| 294 |
+
|
| 295 |
+
zmq_ = std::make_unique<ZMQEndpointInterface>(
|
| 296 |
+
zmq_host_, zmq_port_, zmq_topic_, zmq_conflate_, zmq_verbose_
|
| 297 |
+
);
|
| 298 |
+
order_.push_back(ManagedType::ZMQ);
|
| 299 |
+
|
| 300 |
+
#if HAS_ROS2
|
| 301 |
+
ros2_ = std::make_unique<ROS2InputHandler>(true, "g1_deploy_ros2_handler");
|
| 302 |
+
order_.push_back(ManagedType::ROS2);
|
| 303 |
+
#endif
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
/// Set the active interface by numeric index (wraps around).
|
| 307 |
+
/// Triggers safety reset on ALL interfaces to prevent stale state.
|
| 308 |
+
void setActiveIndex(int idx) {
|
| 309 |
+
if (order_.empty()) { return; }
|
| 310 |
+
if (idx < 0) { idx = static_cast<int>(order_.size()) - 1; }
|
| 311 |
+
if (idx >= static_cast<int>(order_.size())) { idx = 0; }
|
| 312 |
+
|
| 313 |
+
// Trigger safety reset on ALL interfaces when switching
|
| 314 |
+
keyboard_->TriggerSafetyReset();
|
| 315 |
+
gamepad_->TriggerSafetyReset();
|
| 316 |
+
zmq_->TriggerSafetyReset();
|
| 317 |
+
#if HAS_ROS2
|
| 318 |
+
if (ros2_) ros2_->TriggerSafetyReset();
|
| 319 |
+
#endif
|
| 320 |
+
|
| 321 |
+
active_index_ = idx;
|
| 322 |
+
active_ = order_[static_cast<size_t>(active_index_)];
|
| 323 |
+
|
| 324 |
+
switch (active_) {
|
| 325 |
+
case ManagedType::KEYBOARD:
|
| 326 |
+
current_ = keyboard_.get();
|
| 327 |
+
type_ = InputType::KEYBOARD;
|
| 328 |
+
std::cout << "[InterfaceManager] Switched to: KEYBOARD (safety reset triggered)" << std::endl;
|
| 329 |
+
break;
|
| 330 |
+
case ManagedType::GAMEPAD:
|
| 331 |
+
current_ = gamepad_.get();
|
| 332 |
+
type_ = InputType::GAMEPAD;
|
| 333 |
+
std::cout << "[InterfaceManager] Switched to: GAMEPAD (safety reset triggered)" << std::endl;
|
| 334 |
+
break;
|
| 335 |
+
case ManagedType::ZMQ:
|
| 336 |
+
current_ = zmq_.get();
|
| 337 |
+
type_ = InputType::NETWORK;
|
| 338 |
+
std::cout << "[InterfaceManager] Switched to: ZMQ (safety reset triggered)" << std::endl;
|
| 339 |
+
break;
|
| 340 |
+
case ManagedType::ROS2:
|
| 341 |
+
#if HAS_ROS2
|
| 342 |
+
current_ = ros2_.get();
|
| 343 |
+
type_ = InputType::ROS2;
|
| 344 |
+
std::cout << "[InterfaceManager] Switched to: ROS2 (safety reset triggered)" << std::endl;
|
| 345 |
+
break;
|
| 346 |
+
#else
|
| 347 |
+
// Should never happen when ROS2 disabled; fall back to keyboard
|
| 348 |
+
current_ = keyboard_.get();
|
| 349 |
+
type_ = InputType::KEYBOARD;
|
| 350 |
+
active_ = ManagedType::KEYBOARD;
|
| 351 |
+
std::cout << "[InterfaceManager] ROS2 not available. Falling back to KEYBOARD (safety reset triggered)" << std::endl;
|
| 352 |
+
break;
|
| 353 |
+
#endif
|
| 354 |
+
}
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
void nextInterface() { setActiveIndex(active_index_ + 1); } ///< Cycle forward.
|
| 358 |
+
void prevInterface() { setActiveIndex(active_index_ - 1); } ///< Cycle backward.
|
| 359 |
+
|
| 360 |
+
private:
|
| 361 |
+
// ------------------------------------------------------------------
|
| 362 |
+
// Owned concrete delegates (kept alive to preserve state across switches)
|
| 363 |
+
// ------------------------------------------------------------------
|
| 364 |
+
std::unique_ptr<SimpleKeyboard> keyboard_; ///< Keyboard handler.
|
| 365 |
+
std::unique_ptr<unitree::common::Gamepad> gamepad_; ///< Gamepad handler.
|
| 366 |
+
std::unique_ptr<ZMQEndpointInterface> zmq_; ///< ZMQ streaming handler.
|
| 367 |
+
#if HAS_ROS2
|
| 368 |
+
std::unique_ptr<ROS2InputHandler> ros2_; ///< ROS 2 teleop handler.
|
| 369 |
+
#endif
|
| 370 |
+
|
| 371 |
+
InputInterface* current_ = nullptr; ///< Non-owning pointer to the active delegate.
|
| 372 |
+
|
| 373 |
+
// ------------------------------------------------------------------
|
| 374 |
+
// Active-selection bookkeeping
|
| 375 |
+
// ------------------------------------------------------------------
|
| 376 |
+
std::vector<ManagedType> order_; ///< Insertion-order of managed types.
|
| 377 |
+
int active_index_ = 0; ///< Index into order_.
|
| 378 |
+
ManagedType active_ = ManagedType::KEYBOARD; ///< Currently-active type tag.
|
| 379 |
+
|
| 380 |
+
// ZMQ configuration (stored for deferred construction)
|
| 381 |
+
std::string zmq_host_;
|
| 382 |
+
int zmq_port_;
|
| 383 |
+
std::string zmq_topic_;
|
| 384 |
+
bool zmq_conflate_ = false;
|
| 385 |
+
bool zmq_verbose_ = false;
|
| 386 |
+
|
| 387 |
+
/// Global emergency-stop flag, set by 'O'/'o' key.
|
| 388 |
+
/// Applies to ALL interfaces (especially useful when gamepad is active
|
| 389 |
+
/// and has no physical stop button readily accessible).
|
| 390 |
+
bool emergency_stop_ = false;
|
| 391 |
+
bool report_temperature_flag_ = false;
|
| 392 |
+
};
|
| 393 |
+
|
| 394 |
+
#endif // INTERFACE_MANAGER_HPP
|
| 395 |
+
|
| 396 |
+
|
GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/keyboard_handler.hpp
ADDED
|
@@ -0,0 +1,701 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file keyboard_handler.hpp
|
| 3 |
+
* @brief Terminal-based keyboard input handler for the G1 robot.
|
| 4 |
+
*
|
| 5 |
+
* SimpleKeyboard reads single characters from stdin (set to non-blocking,
|
| 6 |
+
* non-canonical mode) and translates them into per-frame action flags.
|
| 7 |
+
*
|
| 8 |
+
* ## Two Operational Modes
|
| 9 |
+
*
|
| 10 |
+
* ### Reference-motion mode (default)
|
| 11 |
+
* Key | Action
|
| 12 |
+
* ----|-------
|
| 13 |
+
* P/p | Previous motion
|
| 14 |
+
* N/n | Next motion
|
| 15 |
+
* T/t | Play / resume playback
|
| 16 |
+
* R/r | Restart motion (frame 0, paused)
|
| 17 |
+
* ] | Start control system
|
| 18 |
+
* O/o | Emergency stop
|
| 19 |
+
* Q/q | Delta heading left (−π/12)
|
| 20 |
+
* E/e | Delta heading right (+π/12)
|
| 21 |
+
* I/i | Reinitialise heading (recapture IMU)
|
| 22 |
+
* Enter | Toggle planner mode
|
| 23 |
+
* Z/z | Toggle encoder mode
|
| 24 |
+
*
|
| 25 |
+
* ### Planner mode (Enter to toggle)
|
| 26 |
+
* Key | Action
|
| 27 |
+
* ----|-------
|
| 28 |
+
* W/S | Move forward / backward
|
| 29 |
+
* A/D | Adjust-left / adjust-right (slight turn + forward)
|
| 30 |
+
* ,/. | Strafe left / right
|
| 31 |
+
* Q/E | Heading left / right (±π/6)
|
| 32 |
+
* 1-8 | Select locomotion mode from current motion set
|
| 33 |
+
* N/P | Next / previous motion set
|
| 34 |
+
* 9/0 | Decrease / increase speed
|
| 35 |
+
* -/= | Decrease / increase height
|
| 36 |
+
* R/` | Emergency stop (reset momentum)
|
| 37 |
+
* T/t | Play motion
|
| 38 |
+
* Z/z | Toggle encoder mode
|
| 39 |
+
*
|
| 40 |
+
* Movement uses a momentum system: pressing a direction key sets momentum to
|
| 41 |
+
* 1.0; each frame without input decays it by `momentum_decay_rate`. Below
|
| 42 |
+
* `momentum_threshold` the robot transitions to IDLE (or stays in the current
|
| 43 |
+
* static pose for squat / boxing sets).
|
| 44 |
+
*/
|
| 45 |
+
|
| 46 |
+
#ifndef KEYBOARD_HANDLER_HPP
|
| 47 |
+
#define KEYBOARD_HANDLER_HPP
|
| 48 |
+
|
| 49 |
+
#include <termios.h>
|
| 50 |
+
#include <fcntl.h>
|
| 51 |
+
#include <unistd.h>
|
| 52 |
+
#include <iostream>
|
| 53 |
+
#include <array>
|
| 54 |
+
#include <cmath>
|
| 55 |
+
#include <thread>
|
| 56 |
+
#include <chrono>
|
| 57 |
+
#include <cstdlib>
|
| 58 |
+
#include "input_interface.hpp"
|
| 59 |
+
|
| 60 |
+
/**
|
| 61 |
+
* @class SimpleKeyboard
|
| 62 |
+
* @brief stdin-based InputInterface for interactive control from a terminal.
|
| 63 |
+
*/
|
| 64 |
+
class SimpleKeyboard : public InputInterface {
|
| 65 |
+
public:
|
| 66 |
+
// ------------------------------------------------------------------
|
| 67 |
+
// Per-frame action flags (reset at the start of every update() call)
|
| 68 |
+
// ------------------------------------------------------------------
|
| 69 |
+
bool motion_prev = false; ///< Switch to previous pre-loaded motion.
|
| 70 |
+
bool motion_next = false; ///< Switch to next pre-loaded motion.
|
| 71 |
+
bool play_motion = false; ///< Start / resume motion playback.
|
| 72 |
+
bool motion_restart = false; ///< Reset current motion to frame 0 (paused).
|
| 73 |
+
|
| 74 |
+
bool start_control = false; ///< Request control-system start.
|
| 75 |
+
bool stop_control = false; ///< Request emergency stop.
|
| 76 |
+
|
| 77 |
+
bool delta_left = false; ///< Nudge heading left (−π/12 rad per press).
|
| 78 |
+
bool delta_right = false; ///< Nudge heading right (+π/12 rad per press).
|
| 79 |
+
|
| 80 |
+
bool reinitialize = false; ///< Recapture the IMU base quaternion.
|
| 81 |
+
|
| 82 |
+
// ------------------------------------------------------------------
|
| 83 |
+
// Planner-mode state
|
| 84 |
+
// ------------------------------------------------------------------
|
| 85 |
+
bool use_planner = false; ///< True while planner mode is active.
|
| 86 |
+
|
| 87 |
+
/// Index into the predefined motion-set table
|
| 88 |
+
/// (0 = standing, 1 = squat, 2 = boxing, 3 = styled walk).
|
| 89 |
+
int motion_set_index = 0;
|
| 90 |
+
/// Locomotion modes available in the current motion set.
|
| 91 |
+
std::vector<LocomotionMode> current_motion_set = get_motion_set(motion_set_index);
|
| 92 |
+
|
| 93 |
+
// Directional movement flags (per-frame, planner mode only)
|
| 94 |
+
bool planner_move_forward = false; ///< W key pressed this frame.
|
| 95 |
+
bool planner_move_backward = false; ///< S key pressed this frame.
|
| 96 |
+
bool planner_move_adj_left = false; ///< A key – slight left turn + forward.
|
| 97 |
+
bool planner_move_adj_right = false; ///< D key – slight right turn + forward.
|
| 98 |
+
bool planner_move_left = false; ///< ',' key – strafe left.
|
| 99 |
+
bool planner_move_right = false; ///< '.' key – strafe right.
|
| 100 |
+
bool planner_heading_left = false; ///< E key – heading left (−π/6).
|
| 101 |
+
bool planner_heading_right = false; ///< Q key – heading right (+π/6).
|
| 102 |
+
|
| 103 |
+
LocomotionMode planner_use_movement_mode = LocomotionMode::IDLE; ///< Current locomotion mode.
|
| 104 |
+
double planner_use_movement_speed = -1; ///< Desired speed (−1 = mode default).
|
| 105 |
+
double planner_use_height = -1; ///< Desired body height (−1 = mode default).
|
| 106 |
+
bool planner_emergency_stop = false; ///< Immediate halt flag (R / ` key).
|
| 107 |
+
|
| 108 |
+
bool encoder_mode_toggle = false; ///< Toggle encoder-mode (Z key) this frame.
|
| 109 |
+
bool report_temperature = false; ///< Print motor temperatures (F key) this frame.
|
| 110 |
+
|
| 111 |
+
// Persistent planner state
|
| 112 |
+
double planner_facing_angle = 0.0; ///< Accumulated facing direction (radians).
|
| 113 |
+
|
| 114 |
+
/// Movement momentum (0 = stopped, 1 = full speed).
|
| 115 |
+
/// Decays each frame by `momentum_decay_rate` when no movement key is held.
|
| 116 |
+
double movement_momentum = 0.0;
|
| 117 |
+
const double momentum_decay_rate = 0.999; ///< Per-frame multiplicative decay.
|
| 118 |
+
const double momentum_threshold = 0.1; ///< Below this, transition to IDLE.
|
| 119 |
+
|
| 120 |
+
/**
|
| 121 |
+
* @brief Construct the keyboard handler.
|
| 122 |
+
*
|
| 123 |
+
* Puts stdin into non-canonical, non-echo, non-blocking mode so that
|
| 124 |
+
* individual key-presses can be read without waiting for Enter.
|
| 125 |
+
* The original terminal settings are restored in the destructor.
|
| 126 |
+
*/
|
| 127 |
+
explicit SimpleKeyboard() : InputInterface() {
|
| 128 |
+
tcgetattr(STDIN_FILENO, &old_termios_);
|
| 129 |
+
struct termios new_termios = old_termios_;
|
| 130 |
+
new_termios.c_lflag &= ~(ICANON | ECHO); // Disable line buffering and echo
|
| 131 |
+
tcsetattr(STDIN_FILENO, TCSANOW, &new_termios);
|
| 132 |
+
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); // Non-blocking reads
|
| 133 |
+
type_ = InputType::KEYBOARD;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
/// Restore original terminal settings on destruction.
|
| 137 |
+
~SimpleKeyboard() {
|
| 138 |
+
tcsetattr(STDIN_FILENO, TCSANOW, &old_termios_);
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
// Flag to trigger safety reset in handle_input
|
| 142 |
+
bool trigger_safety_reset = false;
|
| 143 |
+
|
| 144 |
+
// Override the update function from InputInterface
|
| 145 |
+
void update() override {
|
| 146 |
+
// Check for safety reset trigger from manager
|
| 147 |
+
if (CheckAndClearSafetyReset()) {
|
| 148 |
+
use_planner = false;
|
| 149 |
+
trigger_safety_reset = true;
|
| 150 |
+
std::cout << "[SimpleKeyboard] Safety reset triggered: will disable planner and return to reference motion" << std::endl;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
// Reset input flags each frame
|
| 154 |
+
start_control = false;
|
| 155 |
+
stop_control = false;
|
| 156 |
+
motion_prev = false;
|
| 157 |
+
motion_next = false;
|
| 158 |
+
play_motion = false;
|
| 159 |
+
motion_restart = false;
|
| 160 |
+
delta_left = false;
|
| 161 |
+
delta_right = false;
|
| 162 |
+
reinitialize = false;
|
| 163 |
+
planner_move_forward = false;
|
| 164 |
+
planner_move_backward = false;
|
| 165 |
+
planner_move_adj_left = false;
|
| 166 |
+
planner_move_adj_right = false;
|
| 167 |
+
planner_move_left = false;
|
| 168 |
+
planner_move_right = false;
|
| 169 |
+
planner_heading_left = false;
|
| 170 |
+
planner_heading_right = false;
|
| 171 |
+
planner_emergency_stop = false;
|
| 172 |
+
encoder_mode_toggle = false;
|
| 173 |
+
report_temperature = false;
|
| 174 |
+
|
| 175 |
+
// Read keyboard input (using shared buffered reading)
|
| 176 |
+
char ch;
|
| 177 |
+
while (ReadStdinChar(ch)) {
|
| 178 |
+
if (use_planner) {
|
| 179 |
+
switch (ch) {
|
| 180 |
+
case 'r':
|
| 181 |
+
case 'R':
|
| 182 |
+
case '`':
|
| 183 |
+
case '~': planner_emergency_stop = true; break; // Emergency stop - immediate halt
|
| 184 |
+
case '1':
|
| 185 |
+
case '!': if (current_motion_set.size() > 0) planner_use_movement_mode = current_motion_set[0]; break; // Use standing motion set
|
| 186 |
+
case '2':
|
| 187 |
+
case '@': if (current_motion_set.size() > 1) planner_use_movement_mode = current_motion_set[1]; break; // Use squat motion set
|
| 188 |
+
case '3':
|
| 189 |
+
case '#': if (current_motion_set.size() > 2) planner_use_movement_mode = current_motion_set[2]; break; // Use boxing motion set
|
| 190 |
+
case '4':
|
| 191 |
+
case '$': if (current_motion_set.size() > 3) planner_use_movement_mode = current_motion_set[3]; break; // Use boxing or idele squat mode
|
| 192 |
+
case '5':
|
| 193 |
+
case '%': if (current_motion_set.size() > 4) planner_use_movement_mode = current_motion_set[4]; break; // Use kneel two legs mode
|
| 194 |
+
case '6':
|
| 195 |
+
case '^': if (current_motion_set.size() > 5) planner_use_movement_mode = current_motion_set[5]; break; // Use kneel mode
|
| 196 |
+
case '7':
|
| 197 |
+
case '&': if (current_motion_set.size() > 6) planner_use_movement_mode = current_motion_set[6]; break; // Use lying face down mode
|
| 198 |
+
case '8':
|
| 199 |
+
case '*': if (current_motion_set.size() > 7) planner_use_movement_mode = current_motion_set[7]; break; // Use crawling mode
|
| 200 |
+
case '-':
|
| 201 |
+
case '_': if(planner_use_height != -1) { planner_use_height = planner_use_height - 0.1; } break; // Delta height down
|
| 202 |
+
case '=':
|
| 203 |
+
case '+': if(planner_use_height != -1) { planner_use_height = planner_use_height + 0.1; } break; // Delta height up
|
| 204 |
+
case '9':
|
| 205 |
+
case '(': if(planner_use_movement_speed != -1) { planner_use_movement_speed = planner_use_movement_speed - 0.1; } break; // Delta speed up
|
| 206 |
+
case '0':
|
| 207 |
+
case ')': if(planner_use_movement_speed != -1) { planner_use_movement_speed = planner_use_movement_speed + 0.1; } break; // Delta speed down
|
| 208 |
+
case 'n':
|
| 209 |
+
case 'N': motion_set_index = (motion_set_index + 1) % 4;
|
| 210 |
+
current_motion_set = get_motion_set(motion_set_index);
|
| 211 |
+
planner_use_movement_mode = current_motion_set[0];
|
| 212 |
+
std::cout << "Motion set: " << motion_set_index << std::endl;
|
| 213 |
+
if (motion_set_index == 1) {
|
| 214 |
+
planner_use_height = 0.8;
|
| 215 |
+
}
|
| 216 |
+
break; // Next motion set
|
| 217 |
+
case 'p':
|
| 218 |
+
case 'P': motion_set_index = (motion_set_index - 1 + 4) % 4;
|
| 219 |
+
current_motion_set = get_motion_set(motion_set_index);
|
| 220 |
+
planner_use_movement_mode = current_motion_set[0];
|
| 221 |
+
std::cout << "Motion set: " << motion_set_index << std::endl;
|
| 222 |
+
if (motion_set_index == 1) {
|
| 223 |
+
planner_use_height = 0.8;
|
| 224 |
+
}
|
| 225 |
+
break; // Previous motion set
|
| 226 |
+
case 'j':
|
| 227 |
+
case 'J': delta_left = true; break; // Delta heading left
|
| 228 |
+
case 'l':
|
| 229 |
+
case 'L': delta_right = true; break; // Delta heading right
|
| 230 |
+
case 'w':
|
| 231 |
+
case 'W': planner_move_forward = true; break; // Move forward
|
| 232 |
+
case 's':
|
| 233 |
+
case 'S': planner_move_backward = true; break; // Move backward
|
| 234 |
+
case ',':
|
| 235 |
+
case '<': planner_move_left = true; break; // Move left
|
| 236 |
+
case '.':
|
| 237 |
+
case '>': planner_move_right = true; break; // Move right
|
| 238 |
+
case 'a':
|
| 239 |
+
case 'A': planner_move_adj_left = true; break; // Move adj left
|
| 240 |
+
case 'd':
|
| 241 |
+
case 'D': planner_move_adj_right = true; break; // Move adj right
|
| 242 |
+
case 'e':
|
| 243 |
+
case 'E': planner_heading_left = true; break; // Delta heading left (-0.1)
|
| 244 |
+
case 'q':
|
| 245 |
+
case 'Q': planner_heading_right = true; break; // Delta heading right (+0.1)
|
| 246 |
+
case ']': start_control = true; break; // Start control system
|
| 247 |
+
case 'o':
|
| 248 |
+
case 'O': stop_control = true; break; // Stop/Exit
|
| 249 |
+
case '\n': use_planner = !use_planner; break; // Use planner
|
| 250 |
+
case 'i':
|
| 251 |
+
case 'I': reinitialize = true; break; // Reinitialize base quaternion and delta heading
|
| 252 |
+
case 't':
|
| 253 |
+
case 'T': play_motion = true; break; // Play motion to end
|
| 254 |
+
case 'z':
|
| 255 |
+
case 'Z': encoder_mode_toggle = true; break; // Toggle encoder mode
|
| 256 |
+
case 'f':
|
| 257 |
+
case 'F': report_temperature = true; break; // Report motor temperatures
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
// Limit movement speed and height to the range of the movement mode
|
| 261 |
+
if (is_standing_motion_mode(planner_use_movement_mode)) {
|
| 262 |
+
planner_use_height = -1.0;
|
| 263 |
+
} else {
|
| 264 |
+
planner_use_height = std::max(planner_use_height, 0.2);
|
| 265 |
+
planner_use_height = std::min(planner_use_height, 0.8);
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
if (is_static_motion_mode(planner_use_movement_mode)) {
|
| 269 |
+
planner_use_movement_speed = -1.0;
|
| 270 |
+
} else {
|
| 271 |
+
if (planner_use_movement_mode == LocomotionMode::SLOW_WALK) {
|
| 272 |
+
planner_use_movement_speed = std::max(planner_use_movement_speed, 0.2);
|
| 273 |
+
planner_use_movement_speed = std::min(planner_use_movement_speed, 0.8);
|
| 274 |
+
} else if (planner_use_movement_mode == LocomotionMode::RUN) {
|
| 275 |
+
planner_use_movement_speed = std::max(planner_use_movement_speed, 1.5);
|
| 276 |
+
planner_use_movement_speed = std::min(planner_use_movement_speed, 3.0);
|
| 277 |
+
} else if (planner_use_movement_mode == LocomotionMode::CRAWLING) {
|
| 278 |
+
planner_use_movement_speed = std::max(planner_use_movement_speed, 0.4);
|
| 279 |
+
planner_use_movement_speed = std::min(planner_use_movement_speed, 1.0);
|
| 280 |
+
} else if (planner_use_movement_mode == LocomotionMode::ELBOW_CRAWLING) {
|
| 281 |
+
planner_use_movement_speed = std::max(planner_use_movement_speed, 0.7);
|
| 282 |
+
planner_use_movement_speed = std::min(planner_use_movement_speed, 1.0);
|
| 283 |
+
} else if (planner_use_movement_mode == LocomotionMode::WALK_BOXING ||
|
| 284 |
+
planner_use_movement_mode == LocomotionMode::LEFT_PUNCH ||
|
| 285 |
+
planner_use_movement_mode == LocomotionMode::RIGHT_PUNCH ||
|
| 286 |
+
planner_use_movement_mode == LocomotionMode::RANDOM_PUNCH ||
|
| 287 |
+
planner_use_movement_mode == LocomotionMode::LEFT_HOOK ||
|
| 288 |
+
planner_use_movement_mode == LocomotionMode::RIGHT_HOOK) {
|
| 289 |
+
planner_use_movement_speed = std::max(planner_use_movement_speed, 0.7);
|
| 290 |
+
planner_use_movement_speed = std::min(planner_use_movement_speed, 1.5);
|
| 291 |
+
}
|
| 292 |
+
else {
|
| 293 |
+
planner_use_movement_speed = -1;
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
+
} else {
|
| 297 |
+
switch (ch) {
|
| 298 |
+
case 'p':
|
| 299 |
+
case 'P': motion_prev = true; break; // Previous motion
|
| 300 |
+
case 'n':
|
| 301 |
+
case 'N': motion_next = true; break; // Next motion
|
| 302 |
+
case 't':
|
| 303 |
+
case 'T': play_motion = true; break; // Play motion to end
|
| 304 |
+
case 'r':
|
| 305 |
+
case 'R': motion_restart = true; break; // Restart motion
|
| 306 |
+
case ']': start_control = true; break; // Start control system
|
| 307 |
+
case 'o':
|
| 308 |
+
case 'O': stop_control = true; break; // Stop/Exit
|
| 309 |
+
case 'q':
|
| 310 |
+
case 'Q': delta_left = true; break; // Delta heading left (-0.1)
|
| 311 |
+
case 'e':
|
| 312 |
+
case 'E': delta_right = true; break; // Delta heading right (+0.1)
|
| 313 |
+
case 'i':
|
| 314 |
+
case 'I': reinitialize = true; break; // Reinitialize base quaternion and delta heading
|
| 315 |
+
case '\n': use_planner = !use_planner; break; // Use planner
|
| 316 |
+
case 'z':
|
| 317 |
+
case 'Z': encoder_mode_toggle = true; break; // Toggle encoder mode
|
| 318 |
+
case 'h':
|
| 319 |
+
case 'H': report_temperature = true; break; // Report motor temperatures
|
| 320 |
+
}
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
}
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
// Override the handle_input function from InputInterface
|
| 327 |
+
// This processes the keyboard input flags and performs actions using the provided parameters
|
| 328 |
+
void handle_input(MotionDataReader& motion_reader,
|
| 329 |
+
std::shared_ptr<const MotionSequence>& current_motion,
|
| 330 |
+
int& current_frame,
|
| 331 |
+
OperatorState& operator_state,
|
| 332 |
+
bool& reinitialize_heading,
|
| 333 |
+
DataBuffer<HeadingState>& heading_state_buffer,
|
| 334 |
+
bool has_planner,
|
| 335 |
+
PlannerState& planner_state,
|
| 336 |
+
DataBuffer<MovementState>& movement_state_buffer,
|
| 337 |
+
std::mutex& current_motion_mutex,
|
| 338 |
+
bool& report_temperature) override {
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
// Handle safety reset from interface manager
|
| 343 |
+
if (trigger_safety_reset) {
|
| 344 |
+
trigger_safety_reset = false;
|
| 345 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE), {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 346 |
+
{
|
| 347 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 348 |
+
operator_state.play = false;
|
| 349 |
+
reinitialize_heading = true;
|
| 350 |
+
auto temp_motion = std::make_shared<MotionSequence>(*current_motion);
|
| 351 |
+
temp_motion->name = "temporary_motion";
|
| 352 |
+
current_motion = temp_motion;
|
| 353 |
+
if (has_planner && planner_state.enabled) {
|
| 354 |
+
planner_state.enabled = false;
|
| 355 |
+
planner_state.initialized = false;
|
| 356 |
+
std::cout << "Safety reset: Planner disabled" << std::endl;
|
| 357 |
+
}
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
std::cout << "Safety reset: Returned to reference motion at frame 0" << std::endl;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
// Handle encoder mode toggle
|
| 364 |
+
if (encoder_mode_toggle) {
|
| 365 |
+
int current_encoder_mode = current_motion->GetEncodeMode();
|
| 366 |
+
if (current_encoder_mode == -2) {
|
| 367 |
+
std::cout << "⚠ No encoder configured - cannot toggle encoder mode" << std::endl;
|
| 368 |
+
} else if (current_encoder_mode == -1) {
|
| 369 |
+
std::cout << "⚠ No encoder loaded - cannot toggle encoder mode" << std::endl;
|
| 370 |
+
} else {
|
| 371 |
+
int new_encoder_mode = (current_encoder_mode == 0) ? 1 : 0; // Toggle between mode 0 and 1
|
| 372 |
+
std::cout << "Encoder mode: " << (new_encoder_mode == 0 ? "mode 0" : "mode 1") << std::endl;
|
| 373 |
+
{
|
| 374 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 375 |
+
current_motion->SetEncodeMode(new_encoder_mode);
|
| 376 |
+
}
|
| 377 |
+
}
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
// Handle motion control commands
|
| 381 |
+
if (this->motion_prev && !motion_reader.motions.empty()) {
|
| 382 |
+
motion_reader.current_motion_index_ =
|
| 383 |
+
(motion_reader.current_motion_index_ - 1 + motion_reader.motions.size()) % motion_reader.motions.size();
|
| 384 |
+
std::string motion_name;
|
| 385 |
+
{
|
| 386 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 387 |
+
operator_state.play = false;
|
| 388 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_); // Update current motion directly
|
| 389 |
+
current_frame = 0;
|
| 390 |
+
motion_name = current_motion->name;
|
| 391 |
+
reinitialize_heading = true;
|
| 392 |
+
}
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
if (this->motion_next && !motion_reader.motions.empty()) {
|
| 396 |
+
motion_reader.current_motion_index_ = (motion_reader.current_motion_index_ + 1) % motion_reader.motions.size();
|
| 397 |
+
std::string motion_name;
|
| 398 |
+
{
|
| 399 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 400 |
+
operator_state.play = false;
|
| 401 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_); // Update current motion directly
|
| 402 |
+
current_frame = 0;
|
| 403 |
+
motion_name = current_motion->name;
|
| 404 |
+
reinitialize_heading = true;
|
| 405 |
+
}
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
if (this->play_motion) {
|
| 409 |
+
if (!operator_state.play) {
|
| 410 |
+
int frame_copy;
|
| 411 |
+
size_t timesteps_copy;
|
| 412 |
+
{
|
| 413 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 414 |
+
operator_state.play = true;
|
| 415 |
+
frame_copy = current_frame;
|
| 416 |
+
timesteps_copy = current_motion ? current_motion->timesteps : 0;
|
| 417 |
+
}
|
| 418 |
+
std::cout << "Playing motion " << motion_reader.current_motion_index_ << " from frame " << frame_copy << " to end ("
|
| 419 |
+
<< timesteps_copy << " total frames)" << std::endl;
|
| 420 |
+
}
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
if (this->motion_restart) {
|
| 424 |
+
{
|
| 425 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 426 |
+
operator_state.play = false;
|
| 427 |
+
current_frame = 0;
|
| 428 |
+
reinitialize_heading = true;
|
| 429 |
+
}
|
| 430 |
+
std::cout << "Reset motion " << motion_reader.current_motion_index_ << " to frame 0 (paused)" << std::endl;
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
if (this->stop_control) { operator_state.stop = true; }
|
| 434 |
+
|
| 435 |
+
if (this->report_temperature) { report_temperature = true; }
|
| 436 |
+
|
| 437 |
+
if (this->start_control) { operator_state.start = true; }
|
| 438 |
+
|
| 439 |
+
// Handle delta heading controls
|
| 440 |
+
if (this->delta_left) {
|
| 441 |
+
auto current_heading_state = heading_state_buffer.GetDataWithTime().data;
|
| 442 |
+
HeadingState current_state = current_heading_state ? *current_heading_state : HeadingState();
|
| 443 |
+
double new_delta = current_state.delta_heading + M_PI / 12;
|
| 444 |
+
heading_state_buffer.SetData(HeadingState(current_state.init_base_quat, new_delta));
|
| 445 |
+
std::cout << "Delta heading left: " << new_delta << " rad" << std::endl;
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
if (this->delta_right) {
|
| 449 |
+
auto current_heading_state = heading_state_buffer.GetDataWithTime().data;
|
| 450 |
+
HeadingState current_state = current_heading_state ? *current_heading_state : HeadingState();
|
| 451 |
+
double new_delta = current_state.delta_heading - M_PI / 12;
|
| 452 |
+
heading_state_buffer.SetData(HeadingState(current_state.init_base_quat, new_delta));
|
| 453 |
+
std::cout << "Delta heading right: " << new_delta << " rad" << std::endl;
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
// Handle reinitialize command
|
| 457 |
+
if (this->reinitialize) {
|
| 458 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 459 |
+
reinitialize_heading = true;
|
| 460 |
+
}
|
| 461 |
+
|
| 462 |
+
// Handle planner control - copy the toggle state from keyboard
|
| 463 |
+
if (this->use_planner && !has_planner) {
|
| 464 |
+
std::cout << "Planner not loaded - cannot enable" << std::endl;
|
| 465 |
+
this->use_planner = false;
|
| 466 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE), {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 467 |
+
{
|
| 468 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 469 |
+
operator_state.play = false;
|
| 470 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_); // Update current motion directly
|
| 471 |
+
current_frame = 0;
|
| 472 |
+
}
|
| 473 |
+
} else if (has_planner && planner_state.enabled != this->use_planner) {
|
| 474 |
+
planner_state.enabled = this->use_planner;
|
| 475 |
+
if (planner_state.enabled) {
|
| 476 |
+
std::cout << "Planner enabled" << std::endl;
|
| 477 |
+
planner_facing_angle = 0.0;
|
| 478 |
+
{
|
| 479 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 480 |
+
operator_state.play = false;
|
| 481 |
+
}
|
| 482 |
+
// Wait for planner to be initialized with timeout (5 seconds)
|
| 483 |
+
auto wait_start = std::chrono::steady_clock::now();
|
| 484 |
+
constexpr auto PLANNER_INIT_TIMEOUT = std::chrono::seconds(5);
|
| 485 |
+
while (planner_state.enabled) {
|
| 486 |
+
{
|
| 487 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 488 |
+
if (current_motion->name == "planner_motion") {
|
| 489 |
+
std::cout << "[Keyboard] motion name is planner_motion" << std::endl;
|
| 490 |
+
break;
|
| 491 |
+
}
|
| 492 |
+
}
|
| 493 |
+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
| 494 |
+
auto elapsed = std::chrono::steady_clock::now() - wait_start;
|
| 495 |
+
if (elapsed > PLANNER_INIT_TIMEOUT) {
|
| 496 |
+
std::cerr << "[Keyboard] Planner initialization timeout after 5 seconds" << std::endl;
|
| 497 |
+
this->use_planner = false;
|
| 498 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE), {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 499 |
+
{
|
| 500 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 501 |
+
operator_state.play = false;
|
| 502 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_); // Update current motion directly
|
| 503 |
+
current_frame = 0;
|
| 504 |
+
}
|
| 505 |
+
break;
|
| 506 |
+
}
|
| 507 |
+
std::cout << "[Keyboard] Waiting for planner to be initialized" << std::endl;
|
| 508 |
+
}
|
| 509 |
+
// Check if planner is enabled and initialized
|
| 510 |
+
if (!planner_state.enabled || !planner_state.initialized) {
|
| 511 |
+
std::cout << "[Keyboard] Planner failed to initialize." << std::endl;
|
| 512 |
+
this->use_planner = false;
|
| 513 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE), {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 514 |
+
{
|
| 515 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 516 |
+
operator_state.play = false;
|
| 517 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_); // Update current motion directly
|
| 518 |
+
current_frame = 0;
|
| 519 |
+
}
|
| 520 |
+
} else {
|
| 521 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 522 |
+
operator_state.play = true;
|
| 523 |
+
}
|
| 524 |
+
} else {
|
| 525 |
+
std::cout << "Planner disabled" << std::endl;
|
| 526 |
+
planner_state.initialized = false; // Reset planner initialization when disabled
|
| 527 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE), {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 528 |
+
movement_momentum = 0.0;
|
| 529 |
+
{
|
| 530 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 531 |
+
operator_state.play = false;
|
| 532 |
+
reinitialize_heading = true;
|
| 533 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_); // Update current motion directly
|
| 534 |
+
current_frame = 0;
|
| 535 |
+
}
|
| 536 |
+
}
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
if (has_planner && planner_state.enabled && planner_state.initialized) {
|
| 540 |
+
// Handle emergency stop (immediate momentum reset)
|
| 541 |
+
if (this->planner_emergency_stop) {
|
| 542 |
+
movement_momentum = 0.0;
|
| 543 |
+
std::cout << "Emergency Stop! Movement momentum reset." << std::endl;
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
// Get current state from buffer (single source of truth)
|
| 547 |
+
auto current_mode_data = movement_state_buffer.GetDataWithTime();
|
| 548 |
+
int current_mode = current_mode_data.data ? current_mode_data.data->locomotion_mode : static_cast<int>(LocomotionMode::IDLE);
|
| 549 |
+
std::array<double, 3> current_movement = current_mode_data.data ? current_mode_data.data->movement_direction : std::array<double, 3>{0.0, 0.0, 0.0};
|
| 550 |
+
std::array<double, 3> current_facing = current_mode_data.data ? current_mode_data.data->facing_direction : std::array<double, 3>{1.0, 0.0, 0.0};
|
| 551 |
+
|
| 552 |
+
// Use local variables for all processing - avoid updating globals until the end
|
| 553 |
+
std::array<double, 3> local_target_movement = current_movement;
|
| 554 |
+
std::array<double, 3> local_facing_direction = current_facing;
|
| 555 |
+
|
| 556 |
+
// Handle heading controls (independent of movement momentum)
|
| 557 |
+
if (this->planner_heading_left) {
|
| 558 |
+
planner_facing_angle -= M_PI / 6;
|
| 559 |
+
local_facing_direction[0] = cos(planner_facing_angle);
|
| 560 |
+
local_facing_direction[1] = sin(planner_facing_angle);
|
| 561 |
+
}
|
| 562 |
+
if (this->planner_heading_right) {
|
| 563 |
+
planner_facing_angle += M_PI / 6;
|
| 564 |
+
local_facing_direction[0] = cos(planner_facing_angle);
|
| 565 |
+
local_facing_direction[1] = sin(planner_facing_angle);
|
| 566 |
+
}
|
| 567 |
+
bool movement_key_pressed = false;
|
| 568 |
+
|
| 569 |
+
if (this->planner_move_forward && !is_static_motion_mode(planner_use_movement_mode)) {
|
| 570 |
+
// Just indicate we want to move - actual mode will be determined later based on momentum
|
| 571 |
+
local_target_movement[0] = local_facing_direction[0];
|
| 572 |
+
local_target_movement[1] = local_facing_direction[1];
|
| 573 |
+
local_target_movement[2] = 0.0f;
|
| 574 |
+
movement_momentum = 1.0; // Full momentum
|
| 575 |
+
movement_key_pressed = true;
|
| 576 |
+
}
|
| 577 |
+
if (this->planner_move_backward && !is_static_motion_mode(planner_use_movement_mode)) {
|
| 578 |
+
local_target_movement[0] = -local_facing_direction[0];
|
| 579 |
+
local_target_movement[1] = -local_facing_direction[1];
|
| 580 |
+
local_target_movement[2] = 0.0f;
|
| 581 |
+
movement_momentum = 1.0; // Full momentum
|
| 582 |
+
movement_key_pressed = true;
|
| 583 |
+
}
|
| 584 |
+
if (this->planner_move_adj_left && !is_static_motion_mode(planner_use_movement_mode)) {
|
| 585 |
+
planner_facing_angle += 0.1;
|
| 586 |
+
local_facing_direction[0] = cos(planner_facing_angle);
|
| 587 |
+
local_facing_direction[1] = sin(planner_facing_angle);
|
| 588 |
+
local_facing_direction[2] = 0.0f;
|
| 589 |
+
local_target_movement[0] = local_facing_direction[0];
|
| 590 |
+
local_target_movement[1] = local_facing_direction[1];
|
| 591 |
+
local_target_movement[2] = 0.0f;
|
| 592 |
+
movement_momentum = 1.0; // Full momentum
|
| 593 |
+
movement_key_pressed = true;
|
| 594 |
+
}
|
| 595 |
+
if (this->planner_move_adj_right && !is_static_motion_mode(planner_use_movement_mode)) {
|
| 596 |
+
planner_facing_angle -= 0.1;
|
| 597 |
+
local_facing_direction[0] = cos(planner_facing_angle);
|
| 598 |
+
local_facing_direction[1] = sin(planner_facing_angle);
|
| 599 |
+
local_facing_direction[2] = 0.0f;
|
| 600 |
+
local_target_movement[0] = local_facing_direction[0];
|
| 601 |
+
local_target_movement[1] = local_facing_direction[1];
|
| 602 |
+
local_target_movement[2] = 0.0f;
|
| 603 |
+
movement_momentum = 1.0; // Full momentum
|
| 604 |
+
movement_key_pressed = true;
|
| 605 |
+
}
|
| 606 |
+
if (this->planner_move_left && !is_static_motion_mode(planner_use_movement_mode)) {
|
| 607 |
+
local_target_movement[0] = -sin(planner_facing_angle);
|
| 608 |
+
local_target_movement[1] = cos(planner_facing_angle);
|
| 609 |
+
local_target_movement[2] = 0.0f;
|
| 610 |
+
movement_momentum = 1.0; // Full momentum
|
| 611 |
+
movement_key_pressed = true;
|
| 612 |
+
}
|
| 613 |
+
if (this->planner_move_right && !is_static_motion_mode(planner_use_movement_mode)) {
|
| 614 |
+
local_target_movement[0] = sin(planner_facing_angle);
|
| 615 |
+
local_target_movement[1] = -cos(planner_facing_angle);
|
| 616 |
+
local_target_movement[2] = 0.0f;
|
| 617 |
+
movement_momentum = 1.0; // Full momentum
|
| 618 |
+
movement_key_pressed = true;
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
// Handle mode changes - just update the mode, keep momentum
|
| 622 |
+
// No need to reset momentum when switching between walk/run modes
|
| 623 |
+
|
| 624 |
+
// Apply momentum decay if no movement key is pressed
|
| 625 |
+
if (!movement_key_pressed) {
|
| 626 |
+
movement_momentum *= momentum_decay_rate;
|
| 627 |
+
}
|
| 628 |
+
|
| 629 |
+
// Set final movement values based on momentum
|
| 630 |
+
int final_mode;
|
| 631 |
+
std::array<double, 3> final_movement;
|
| 632 |
+
double final_speed;
|
| 633 |
+
double final_height;
|
| 634 |
+
if (movement_momentum > momentum_threshold) {
|
| 635 |
+
// Determine final mode based on keyboard setting only when we have momentum
|
| 636 |
+
final_mode = static_cast<int>(this->planner_use_movement_mode);
|
| 637 |
+
final_movement = local_target_movement;
|
| 638 |
+
final_speed = this->planner_use_movement_speed;
|
| 639 |
+
final_height = this->planner_use_height;
|
| 640 |
+
} else {
|
| 641 |
+
if (motion_set_index == 1) {
|
| 642 |
+
// Below threshold - maintain current squat mode
|
| 643 |
+
final_mode = static_cast<int>(this->planner_use_movement_mode);
|
| 644 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 645 |
+
final_speed = -1.0f;
|
| 646 |
+
final_height = this->planner_use_height;
|
| 647 |
+
if (this->planner_use_movement_mode == LocomotionMode::CRAWLING) {
|
| 648 |
+
final_speed = 0;
|
| 649 |
+
} else if (this->planner_use_movement_mode == LocomotionMode::ELBOW_CRAWLING) {
|
| 650 |
+
final_speed = 0;
|
| 651 |
+
}
|
| 652 |
+
} else if (motion_set_index == 2) {
|
| 653 |
+
// Below threshold - maintain current boxing mode
|
| 654 |
+
final_mode = static_cast<int>(this->planner_use_movement_mode);
|
| 655 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 656 |
+
final_speed = -1.0f;
|
| 657 |
+
final_height = -1.0f;
|
| 658 |
+
if (this->planner_use_movement_mode == LocomotionMode::WALK_BOXING) {
|
| 659 |
+
final_speed = 0;
|
| 660 |
+
} else if (this->planner_use_movement_mode == LocomotionMode::LEFT_PUNCH) {
|
| 661 |
+
final_speed = 0;
|
| 662 |
+
final_movement = local_facing_direction;
|
| 663 |
+
} else if (this->planner_use_movement_mode == LocomotionMode::RIGHT_PUNCH) {
|
| 664 |
+
final_speed = 0;
|
| 665 |
+
final_movement = local_facing_direction;
|
| 666 |
+
} else if (this->planner_use_movement_mode == LocomotionMode::RANDOM_PUNCH) {
|
| 667 |
+
final_speed = 0;
|
| 668 |
+
} else if (this->planner_use_movement_mode == LocomotionMode::LEFT_HOOK) {
|
| 669 |
+
final_speed = 0;
|
| 670 |
+
final_movement = local_facing_direction;
|
| 671 |
+
} else if (this->planner_use_movement_mode == LocomotionMode::RIGHT_HOOK) {
|
| 672 |
+
final_speed = 0;
|
| 673 |
+
final_movement = local_facing_direction;
|
| 674 |
+
}
|
| 675 |
+
} else if (motion_set_index == 3) {
|
| 676 |
+
// Below threshold - maintain current styled walking mode
|
| 677 |
+
final_mode = static_cast<int>(this->planner_use_movement_mode);
|
| 678 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 679 |
+
final_speed = 0.0f;
|
| 680 |
+
final_height = -1.0f;
|
| 681 |
+
} else {
|
| 682 |
+
// Below threshold - switch to IDLE
|
| 683 |
+
final_mode = static_cast<int>(LocomotionMode::IDLE);
|
| 684 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 685 |
+
final_speed = -1.0f;
|
| 686 |
+
final_height = -1.0f;
|
| 687 |
+
}
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
// Update thread-safe buffer (single source of truth for planner thread)
|
| 691 |
+
MovementState mode_state(final_mode, final_movement, local_facing_direction, final_speed, final_height);
|
| 692 |
+
movement_state_buffer.SetData(mode_state);
|
| 693 |
+
}
|
| 694 |
+
}
|
| 695 |
+
|
| 696 |
+
|
| 697 |
+
private:
|
| 698 |
+
struct termios old_termios_;
|
| 699 |
+
};
|
| 700 |
+
|
| 701 |
+
#endif // KEYBOARD_HANDLER_HPP
|
GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/ros2_input_handler.hpp
ADDED
|
@@ -0,0 +1,1278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file ros2_input_handler.hpp
|
| 3 |
+
* @brief ROS 2 input handler for receiving teleop commands over DDS.
|
| 4 |
+
*
|
| 5 |
+
* ROS2InputHandler creates a lightweight ROS 2 node and subscribes to a single
|
| 6 |
+
* topic (`ControlPolicy/upper_body_pose`) that carries msgpack-serialised
|
| 7 |
+
* ControlGoalMsg payloads inside a `std_msgs/ByteMultiArray`.
|
| 8 |
+
*
|
| 9 |
+
* ## Data Flow
|
| 10 |
+
*
|
| 11 |
+
* Python teleop script ──(msgpack/ROS2)──► ROS2InputHandler
|
| 12 |
+
* ControlGoalMsg ↓
|
| 13 |
+
* update() (spin + buffer → local state)
|
| 14 |
+
* handle_input() (local state → system state)
|
| 15 |
+
*
|
| 16 |
+
* ## Operational Requirements
|
| 17 |
+
*
|
| 18 |
+
* - A locomotion **planner must be loaded** – ROS2 mode always operates through
|
| 19 |
+
* the planner (no reference-motion playback).
|
| 20 |
+
* - VR 3-point tracking is always enabled; the handler populates position and
|
| 21 |
+
* orientation buffers from the received wrist / head data.
|
| 22 |
+
* - Supports two IK modes controlled at construction time:
|
| 23 |
+
* - `use_ik_mode = true` – uses IK-processed transformation matrices
|
| 24 |
+
* (left_wrist_after_ik, right_wrist_after_ik, head_after_ik) with
|
| 25 |
+
* configurable wrist offsets.
|
| 26 |
+
* - `use_ik_mode = false` – uses raw wrist matrices (left_wrist, right_wrist).
|
| 27 |
+
*
|
| 28 |
+
* ## Locomotion Modes
|
| 29 |
+
*
|
| 30 |
+
* base_height_command | Mode
|
| 31 |
+
* --------------------|------
|
| 32 |
+
* 0.72 – 0.88 | WALK (or SLOW_WALK depending on locomotion_mode flag)
|
| 33 |
+
* 0.50 – 0.72 | SQUAT (static)
|
| 34 |
+
* 0.10 – 0.50 | KNEEL (static)
|
| 35 |
+
*
|
| 36 |
+
* ## Edge-Triggered Commands
|
| 37 |
+
*
|
| 38 |
+
* `toggle_policy_action` is accumulated with OR logic in the callback so that
|
| 39 |
+
* a transient toggle pulse is never lost between update() cycles.
|
| 40 |
+
*
|
| 41 |
+
* ## Thread Safety
|
| 42 |
+
*
|
| 43 |
+
* The subscriber callback runs on the ROS 2 executor thread. Data is copied
|
| 44 |
+
* into `control_goal_buffer_` under `control_goal_mutex_` and consumed by
|
| 45 |
+
* update() on the main thread.
|
| 46 |
+
*/
|
| 47 |
+
|
| 48 |
+
#ifndef ROS2_INPUT_HANDLER_HPP
|
| 49 |
+
#define ROS2_INPUT_HANDLER_HPP
|
| 50 |
+
|
| 51 |
+
#if HAS_ROS2
|
| 52 |
+
#include <rclcpp/rclcpp.hpp>
|
| 53 |
+
#include <rclcpp/exceptions/exceptions.hpp>
|
| 54 |
+
#include <std_msgs/msg/byte_multi_array.hpp> // For msgpack-serialized messages
|
| 55 |
+
#include <array>
|
| 56 |
+
#include <memory>
|
| 57 |
+
#include <atomic>
|
| 58 |
+
#include <mutex>
|
| 59 |
+
#include <iostream>
|
| 60 |
+
#include <thread>
|
| 61 |
+
#include <chrono>
|
| 62 |
+
#include <map>
|
| 63 |
+
#include <vector>
|
| 64 |
+
#include <variant>
|
| 65 |
+
#include <cstdlib>
|
| 66 |
+
|
| 67 |
+
#include <termios.h>
|
| 68 |
+
#include <fcntl.h>
|
| 69 |
+
#include <unistd.h>
|
| 70 |
+
|
| 71 |
+
#include <msgpack.hpp>
|
| 72 |
+
|
| 73 |
+
#include "input_interface.hpp"
|
| 74 |
+
#include "../math_utils.hpp"
|
| 75 |
+
#include "../policy_parameters.hpp" // For isaaclab_to_mujoco and default_angles
|
| 76 |
+
|
| 77 |
+
/**
|
| 78 |
+
* @brief Deserialized control-goal message received from the Python teleop script.
|
| 79 |
+
*
|
| 80 |
+
* Fields are populated by parse_msgpack_control_goal() and stored in the
|
| 81 |
+
* receiving buffer for consumption by update().
|
| 82 |
+
*/
|
| 83 |
+
struct ControlGoalMsg {
|
| 84 |
+
/// Navigation velocity command [lin_vel_x, lin_vel_y, ang_vel_z] (m/s, m/s, rad/s).
|
| 85 |
+
std::array<double, 3> navigate_cmd = {0.0f, 0.0f, 0.0f};
|
| 86 |
+
/// Wrist pose in [x,y,z, qw,qx,qy,qz] × 2 (left then right).
|
| 87 |
+
std::array<double, 14> wrist_pose = {0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0,
|
| 88 |
+
0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0};
|
| 89 |
+
|
| 90 |
+
// IK-processed wrist poses (4×4 transformation matrices, row-major flattened)
|
| 91 |
+
std::array<double, 16> left_wrist_after_ik = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1}; ///< Left wrist after IK.
|
| 92 |
+
std::array<double, 16> right_wrist_after_ik = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1}; ///< Right wrist after IK.
|
| 93 |
+
std::array<double, 16> head_after_ik = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1}; ///< Head after IK.
|
| 94 |
+
bool has_ik_data = false; ///< True if the IK-processed matrices are present.
|
| 95 |
+
|
| 96 |
+
// Non-IK raw wrist matrices (4×4 transformation matrices, row-major flattened)
|
| 97 |
+
std::array<double, 16> left_wrist = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1}; ///< Raw left wrist transform.
|
| 98 |
+
std::array<double, 16> right_wrist = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1}; ///< Raw right wrist transform.
|
| 99 |
+
bool has_wrist_matrices = false; ///< True if the raw wrist matrices are present.
|
| 100 |
+
|
| 101 |
+
double base_height_command = 0.78; ///< Desired base height (metres, valid range 0.1–0.88).
|
| 102 |
+
bool toggle_policy_action = false; ///< Edge-triggered toggle: maps to start/stop control.
|
| 103 |
+
int locomotion_mode = 0; ///< 0 = slow walk (custom speed), 1 = fast walk (default speed).
|
| 104 |
+
|
| 105 |
+
/// Dex3 hand joint positions (7 DOF per hand).
|
| 106 |
+
std::array<double, 7> left_hand_joint = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
| 107 |
+
std::array<double, 7> right_hand_joint = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
| 108 |
+
bool has_hand_joints = false; ///< True if hand joint data is present.
|
| 109 |
+
|
| 110 |
+
double ros_timestamp = 0.0; ///< ROS time in seconds (for synchronisation with other components).
|
| 111 |
+
bool valid = false; ///< True once the message has been successfully parsed.
|
| 112 |
+
};
|
| 113 |
+
|
| 114 |
+
/**
|
| 115 |
+
* @class ROS2InputHandler
|
| 116 |
+
* @brief InputInterface driven by ROS 2 DDS messages (msgpack-serialised ControlGoalMsg).
|
| 117 |
+
*
|
| 118 |
+
* Architecture:
|
| 119 |
+
* 1. **ROS 2 callback** (`control_goal_callback`) receives ByteMultiArray messages,
|
| 120 |
+
* deserialises them via msgpack, and stores the result in `control_goal_buffer_`
|
| 121 |
+
* under `control_goal_mutex_`.
|
| 122 |
+
* 2. **update()** spins the ROS 2 node, reads the buffer, and converts the data
|
| 123 |
+
* into local state (VR buffers, navigate_cmd, control flags).
|
| 124 |
+
* 3. **handle_input()** translates local state into system-state changes
|
| 125 |
+
* (planner commands, operator start/stop, movement buffer updates).
|
| 126 |
+
*
|
| 127 |
+
* A 1-second timeout (`CONTROL_GOAL_TIMEOUT`) resets the handler if messages
|
| 128 |
+
* stop arriving, and any ROS 2 errors trigger an immediate emergency stop.
|
| 129 |
+
*/
|
| 130 |
+
class ROS2InputHandler : public InputInterface {
|
| 131 |
+
public:
|
| 132 |
+
// ========================================
|
| 133 |
+
// DEBUG CONTROL FLAG
|
| 134 |
+
// ========================================
|
| 135 |
+
static constexpr bool DEBUG_LOGGING = true; // Set to false to disable debug logs
|
| 136 |
+
|
| 137 |
+
// Constructor - initializes ROS2 node and subscribers
|
| 138 |
+
explicit ROS2InputHandler(bool use_ik_mode = true, const std::string& node_name = "g1_input_handler")
|
| 139 |
+
: InputInterface() {
|
| 140 |
+
// Set terminal to non-blocking mode for keyboard input (emergency stop)
|
| 141 |
+
tcgetattr(STDIN_FILENO, &old_termios_);
|
| 142 |
+
struct termios new_termios = old_termios_;
|
| 143 |
+
new_termios.c_lflag &= ~(ICANON | ECHO);
|
| 144 |
+
tcsetattr(STDIN_FILENO, TCSANOW, &new_termios);
|
| 145 |
+
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
|
| 146 |
+
|
| 147 |
+
// Initialize ROS2 - this is the only ROS2 component in the system
|
| 148 |
+
if (!rclcpp::ok()) {
|
| 149 |
+
if constexpr (DEBUG_LOGGING) {
|
| 150 |
+
std::cout << "[ROS2 DEBUG] Initializing ROS2" << std::endl;
|
| 151 |
+
}
|
| 152 |
+
rclcpp::init(0, nullptr); // Initialize with no command line arguments
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
try {
|
| 156 |
+
// Initialize ROS2 node
|
| 157 |
+
node_ = rclcpp::Node::make_shared(node_name);
|
| 158 |
+
|
| 159 |
+
if constexpr (DEBUG_LOGGING) {
|
| 160 |
+
std::cout << "[ROS2 DEBUG] ROS2InputHandler node '" << node_name << "' initialized" << std::endl;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
// Setup subscriber for control goal topic
|
| 164 |
+
setup_subscribers();
|
| 165 |
+
|
| 166 |
+
if constexpr (DEBUG_LOGGING) {
|
| 167 |
+
std::cout << "[ROS2 DEBUG] Subscribed to topic: ControlPolicy/upper_body_pose" << std::endl;
|
| 168 |
+
}
|
| 169 |
+
} catch (const std::exception& e) {
|
| 170 |
+
std::cerr << "[ROS2 ERROR] Failed to initialize ROS2InputHandler: " << e.what() << std::endl;
|
| 171 |
+
throw;
|
| 172 |
+
}
|
| 173 |
+
type_ = InputType::ROS2;
|
| 174 |
+
has_vr_3point_control_ = true;
|
| 175 |
+
use_ik_mode_ = use_ik_mode;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
// Destructor
|
| 179 |
+
~ROS2InputHandler() {
|
| 180 |
+
if constexpr (DEBUG_LOGGING) {
|
| 181 |
+
std::cout << "[ROS2 DEBUG] ROS2InputHandler destructor called" << std::endl;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
// Restore terminal before ROS2 cleanup
|
| 185 |
+
tcsetattr(STDIN_FILENO, TCSANOW, &old_termios_);
|
| 186 |
+
|
| 187 |
+
// CRITICAL: Proper shutdown order to avoid EntityDelegate assertion
|
| 188 |
+
try {
|
| 189 |
+
// Step 1: Stop spinning to prevent new callbacks
|
| 190 |
+
if (node_) {
|
| 191 |
+
// Ensure no more spin operations happen
|
| 192 |
+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
// Step 2: Reset subscribers BEFORE resetting node (proper DDS entity order)
|
| 196 |
+
if (control_goal_sub_) {
|
| 197 |
+
if constexpr (DEBUG_LOGGING) {
|
| 198 |
+
std::cout << "[ROS2 DEBUG] Resetting control goal subscriber" << std::endl;
|
| 199 |
+
}
|
| 200 |
+
control_goal_sub_.reset();
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
// Step 3: Allow DDS cleanup time (critical for preventing assertion)
|
| 204 |
+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
| 205 |
+
|
| 206 |
+
// Step 4: Reset node AFTER subscribers are cleaned up
|
| 207 |
+
if (node_) {
|
| 208 |
+
if constexpr (DEBUG_LOGGING) {
|
| 209 |
+
std::cout << "[ROS2 DEBUG] Resetting ROS2 node" << std::endl;
|
| 210 |
+
}
|
| 211 |
+
node_.reset();
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
// Step 5: Only shutdown if we're the last ROS2 component
|
| 215 |
+
// Note: Be careful about calling shutdown() if other ROS2 components exist
|
| 216 |
+
if (rclcpp::ok()) {
|
| 217 |
+
if constexpr (DEBUG_LOGGING) {
|
| 218 |
+
std::cout << "[ROS2 DEBUG] Shutting down ROS2 context" << std::endl;
|
| 219 |
+
}
|
| 220 |
+
rclcpp::shutdown();
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
} catch (const std::exception& e) {
|
| 224 |
+
if constexpr (DEBUG_LOGGING) {
|
| 225 |
+
std::cout << "[ROS2 ERROR] Exception during cleanup: " << e.what() << std::endl;
|
| 226 |
+
}
|
| 227 |
+
// Don't rethrow in destructor
|
| 228 |
+
} catch (...) {
|
| 229 |
+
if constexpr (DEBUG_LOGGING) {
|
| 230 |
+
std::cout << "[ROS2 ERROR] Unknown exception during cleanup" << std::endl;
|
| 231 |
+
}
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
// Flag to trigger safety reset in handle_input
|
| 236 |
+
bool trigger_safety_reset = false;
|
| 237 |
+
|
| 238 |
+
// Flag to trigger emergency stop (set internally on ROS2 errors/timeout)
|
| 239 |
+
bool emergency_stop_ = false;
|
| 240 |
+
|
| 241 |
+
// Override the update function from InputInterface
|
| 242 |
+
// Reads from control goal buffer (updated by callback) and updates local state
|
| 243 |
+
//
|
| 244 |
+
// EDGE-TRIGGERED BEHAVIOR:
|
| 245 |
+
// Toggle commands like toggle_policy_action are accumulated in the callback using OR logic.
|
| 246 |
+
// Once a command arrives as 'true', it stays 'true' in the buffer until processed here.
|
| 247 |
+
// After reading, these commands are cleared from the buffer to prevent re-execution.
|
| 248 |
+
void update() override {
|
| 249 |
+
// Reset emergency stop flag each frame
|
| 250 |
+
emergency_stop_ = false;
|
| 251 |
+
|
| 252 |
+
// Check for safety reset trigger from manager
|
| 253 |
+
if (CheckAndClearSafetyReset()) {
|
| 254 |
+
trigger_safety_reset = true;
|
| 255 |
+
std::cout << "[ROS2InputHandler] Safety reset triggered: will disable planner and return to reference motion" << std::endl;
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
// Check if ROS2 is still healthy
|
| 259 |
+
if (!rclcpp::ok()) {
|
| 260 |
+
if constexpr (DEBUG_LOGGING) {
|
| 261 |
+
std::cout << "[ROS2 ERROR] ROS2 context is not OK - triggering emergency stop" << std::endl;
|
| 262 |
+
}
|
| 263 |
+
emergency_stop_ = true;
|
| 264 |
+
return; // Skip further processing
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
// First, spin ROS2 node to process any pending messages and trigger callbacks
|
| 268 |
+
if (node_) {
|
| 269 |
+
try {
|
| 270 |
+
rclcpp::spin_some(node_);
|
| 271 |
+
} catch (const rclcpp::exceptions::RCLError& e) {
|
| 272 |
+
std::cerr << "[ROS2 ERROR] RCL error during spin: " << e.what() << " - triggering emergency stop" << std::endl;
|
| 273 |
+
emergency_stop_ = true;
|
| 274 |
+
return; // Skip further processing
|
| 275 |
+
} catch (const std::exception& e) {
|
| 276 |
+
std::cerr << "[ROS2 ERROR] Failed to spin node: " << e.what() << " - triggering emergency stop" << std::endl;
|
| 277 |
+
emergency_stop_ = true;
|
| 278 |
+
return; // Skip further processing
|
| 279 |
+
}
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
// Reset input flags each frame
|
| 283 |
+
start_control_ = false;
|
| 284 |
+
stop_control_ = false;
|
| 285 |
+
report_temperature_flag_ = false;
|
| 286 |
+
|
| 287 |
+
// Read keyboard input for emergency stop ('O'/'o' key)
|
| 288 |
+
// This works in standalone mode; when managed by InterfaceManager, it's also handled there
|
| 289 |
+
char ch;
|
| 290 |
+
while (ReadStdinChar(ch)) {
|
| 291 |
+
switch (ch) {
|
| 292 |
+
case 'o':
|
| 293 |
+
case 'O':
|
| 294 |
+
stop_control_ = true;
|
| 295 |
+
std::cout << "[ROS2] Emergency stop triggered (O/o key pressed)" << std::endl;
|
| 296 |
+
break;
|
| 297 |
+
case 'f':
|
| 298 |
+
case 'F':
|
| 299 |
+
report_temperature_flag_ = true;
|
| 300 |
+
break;
|
| 301 |
+
}
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
// Check for control goal timeout (using steady_clock for monotonic timing)
|
| 305 |
+
if (received_control_goal_.load()) {
|
| 306 |
+
int64_t current_time_ns = std::chrono::steady_clock::now().time_since_epoch().count();
|
| 307 |
+
int64_t last_msg_time_ns = last_control_goal_time_ns_.load();
|
| 308 |
+
double time_since_last_msg = (current_time_ns - last_msg_time_ns) / 1e9; // Convert ns to seconds
|
| 309 |
+
if (time_since_last_msg > CONTROL_GOAL_TIMEOUT) {
|
| 310 |
+
if constexpr (DEBUG_LOGGING) {
|
| 311 |
+
std::cout << "[ROS2 WARNING] Control goal timeout (" << time_since_last_msg
|
| 312 |
+
<< "s since last message). Resetting flags." << std::endl;
|
| 313 |
+
}
|
| 314 |
+
reset_data_flags(); // Reset both flag and timestamp consistently
|
| 315 |
+
use_teleop_navigate_cmd_ = false;
|
| 316 |
+
planner_facing_angle_ = 0.0; // Reset facing angle
|
| 317 |
+
}
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
// Read from control goal buffer (teleop commands from Python) - thread-safe
|
| 321 |
+
if (received_control_goal_.load()) {
|
| 322 |
+
std::lock_guard<std::mutex> lock(control_goal_mutex_);
|
| 323 |
+
// Update navigate_cmd and base_height_command from control goal
|
| 324 |
+
navigate_cmd_from_teleop_ = control_goal_buffer_.navigate_cmd;
|
| 325 |
+
base_height_command_ = control_goal_buffer_.base_height_command;
|
| 326 |
+
use_teleop_navigate_cmd_ = true;
|
| 327 |
+
|
| 328 |
+
// Handle toggle_policy_action (edge-triggered toggle between start/stop)
|
| 329 |
+
if (control_goal_buffer_.toggle_policy_action) {
|
| 330 |
+
// Toggle the control state
|
| 331 |
+
control_is_active_ = !control_is_active_;
|
| 332 |
+
|
| 333 |
+
if (control_is_active_) {
|
| 334 |
+
start_control_ = true;
|
| 335 |
+
if constexpr (DEBUG_LOGGING) {
|
| 336 |
+
std::cout << "[ROS2 DEBUG] toggle_policy_action: START control" << std::endl;
|
| 337 |
+
}
|
| 338 |
+
} else {
|
| 339 |
+
stop_control_ = true;
|
| 340 |
+
if constexpr (DEBUG_LOGGING) {
|
| 341 |
+
std::cout << "[ROS2 DEBUG] toggle_policy_action: STOP control" << std::endl;
|
| 342 |
+
}
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
// Clear the trigger after reading (edge-triggered behavior)
|
| 346 |
+
control_goal_buffer_.toggle_policy_action = false;
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
// Handle locomotion_mode (direct state: 0 = slow walk, 1 = fast walk)
|
| 350 |
+
locomotion_mode_is_fast_ = (control_goal_buffer_.locomotion_mode == 1);
|
| 351 |
+
|
| 352 |
+
if constexpr (DEBUG_LOGGING) {
|
| 353 |
+
static int prev_locomotion_mode = -1;
|
| 354 |
+
if (prev_locomotion_mode != control_goal_buffer_.locomotion_mode) {
|
| 355 |
+
if (locomotion_mode_is_fast_) {
|
| 356 |
+
std::cout << "[ROS2 DEBUG] locomotion_mode: FAST WALK (default speed, mode 2)" << std::endl;
|
| 357 |
+
} else {
|
| 358 |
+
std::cout << "[ROS2 DEBUG] locomotion_mode: SLOW WALK (custom speed, mode 1)" << std::endl;
|
| 359 |
+
}
|
| 360 |
+
prev_locomotion_mode = control_goal_buffer_.locomotion_mode;
|
| 361 |
+
}
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
// Update hand poses from teleop
|
| 365 |
+
if (control_goal_buffer_.has_hand_joints) {
|
| 366 |
+
left_hand_joint_.SetData(control_goal_buffer_.left_hand_joint);
|
| 367 |
+
right_hand_joint_.SetData(control_goal_buffer_.right_hand_joint);
|
| 368 |
+
has_hand_joints_ = true;
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
// Update VR 3-point control data based on use_ik_mode_ flag
|
| 372 |
+
// Build arrays first, then call SetData() on buffers
|
| 373 |
+
std::array<double, 9> vr_position;
|
| 374 |
+
std::array<double, 12> vr_orientation;
|
| 375 |
+
|
| 376 |
+
if (use_ik_mode_ && control_goal_buffer_.has_ik_data) {
|
| 377 |
+
// IK mode: Use IK-processed transformation matrices
|
| 378 |
+
// Offsets from local_vr_tracking_bm.py (lines 39-41)
|
| 379 |
+
// NOTE the left and right hand offsets are opposite in sign for the y-axis
|
| 380 |
+
constexpr std::array<double, 3> LEFT_HAND_OFFSET = {0.18, -0.025, 0.0};
|
| 381 |
+
constexpr std::array<double, 3> RIGHT_HAND_OFFSET = {0.18, +0.025, 0.0};
|
| 382 |
+
constexpr std::array<double, 3> HEAD_OFFSET = {0.0, 0.0, 0.35};
|
| 383 |
+
|
| 384 |
+
// Extract left wrist
|
| 385 |
+
auto left_rot = extract_rotation_from_transform(control_goal_buffer_.left_wrist_after_ik);
|
| 386 |
+
auto left_pos = extract_position_from_transform(control_goal_buffer_.left_wrist_after_ik);
|
| 387 |
+
auto left_offset_rotated = apply_rotation_to_offset(left_rot, LEFT_HAND_OFFSET);
|
| 388 |
+
vr_position[0] = left_pos[0] + left_offset_rotated[0];
|
| 389 |
+
vr_position[1] = left_pos[1] + left_offset_rotated[1];
|
| 390 |
+
vr_position[2] = left_pos[2] + left_offset_rotated[2];
|
| 391 |
+
|
| 392 |
+
// Extract right wrist
|
| 393 |
+
auto right_rot = extract_rotation_from_transform(control_goal_buffer_.right_wrist_after_ik);
|
| 394 |
+
auto right_pos = extract_position_from_transform(control_goal_buffer_.right_wrist_after_ik);
|
| 395 |
+
auto right_offset_rotated = apply_rotation_to_offset(right_rot, RIGHT_HAND_OFFSET);
|
| 396 |
+
vr_position[3] = right_pos[0] + right_offset_rotated[0];
|
| 397 |
+
vr_position[4] = right_pos[1] + right_offset_rotated[1];
|
| 398 |
+
vr_position[5] = right_pos[2] + right_offset_rotated[2];
|
| 399 |
+
|
| 400 |
+
// Extract head
|
| 401 |
+
auto head_rot = extract_rotation_from_transform(control_goal_buffer_.head_after_ik);
|
| 402 |
+
auto head_pos = extract_position_from_transform(control_goal_buffer_.head_after_ik);
|
| 403 |
+
auto head_offset_rotated = apply_rotation_to_offset(head_rot, HEAD_OFFSET);
|
| 404 |
+
vr_position[6] = head_pos[0] + head_offset_rotated[0];
|
| 405 |
+
vr_position[7] = head_pos[1] + head_offset_rotated[1];
|
| 406 |
+
vr_position[8] = head_pos[2] + head_offset_rotated[2];
|
| 407 |
+
|
| 408 |
+
// Convert rotation matrices to quaternions using math_utils.hpp
|
| 409 |
+
auto left_quat = rotation_matrix_to_quat_d(left_rot);
|
| 410 |
+
auto right_quat = rotation_matrix_to_quat_d(right_rot);
|
| 411 |
+
auto head_quat = rotation_matrix_to_quat_d(head_rot);
|
| 412 |
+
|
| 413 |
+
// Store quaternions (w, x, y, z format)
|
| 414 |
+
vr_orientation[0] = left_quat[0]; // left qw
|
| 415 |
+
vr_orientation[1] = left_quat[1]; // left qx
|
| 416 |
+
vr_orientation[2] = left_quat[2]; // left qy
|
| 417 |
+
vr_orientation[3] = left_quat[3]; // left qz
|
| 418 |
+
vr_orientation[4] = right_quat[0]; // right qw
|
| 419 |
+
vr_orientation[5] = right_quat[1]; // right qx
|
| 420 |
+
vr_orientation[6] = right_quat[2]; // right qy
|
| 421 |
+
vr_orientation[7] = right_quat[3]; // right qz
|
| 422 |
+
vr_orientation[8] = head_quat[0]; // head qw
|
| 423 |
+
vr_orientation[9] = head_quat[1]; // head qx
|
| 424 |
+
vr_orientation[10] = head_quat[2]; // head qy
|
| 425 |
+
vr_orientation[11] = head_quat[3]; // head qz
|
| 426 |
+
|
| 427 |
+
// Update buffers
|
| 428 |
+
vr_3point_position_.SetData(vr_position);
|
| 429 |
+
vr_3point_orientation_.SetData(vr_orientation);
|
| 430 |
+
} else if (!use_ik_mode_ && control_goal_buffer_.has_wrist_matrices) {
|
| 431 |
+
// Non-IK mode with wrist matrices: Use left_wrist and right_wrist matrices
|
| 432 |
+
// Extract left wrist (matches Python: left_wrist_matrix[:3, 3:] for position)
|
| 433 |
+
auto left_rot = extract_rotation_from_transform(control_goal_buffer_.left_wrist);
|
| 434 |
+
auto left_pos = extract_position_from_transform(control_goal_buffer_.left_wrist);
|
| 435 |
+
vr_position[0] = left_pos[0];
|
| 436 |
+
vr_position[1] = left_pos[1];
|
| 437 |
+
vr_position[2] = left_pos[2];
|
| 438 |
+
|
| 439 |
+
// Extract right wrist
|
| 440 |
+
auto right_rot = extract_rotation_from_transform(control_goal_buffer_.right_wrist);
|
| 441 |
+
auto right_pos = extract_position_from_transform(control_goal_buffer_.right_wrist);
|
| 442 |
+
vr_position[3] = right_pos[0];
|
| 443 |
+
vr_position[4] = right_pos[1];
|
| 444 |
+
vr_position[5] = right_pos[2];
|
| 445 |
+
// Head position uses defaults
|
| 446 |
+
vr_position[6] = 0.0241;
|
| 447 |
+
vr_position[7] = -0.0081;
|
| 448 |
+
vr_position[8] = 0.4028;
|
| 449 |
+
|
| 450 |
+
// Convert rotation matrices to quaternions using math_utils.hpp
|
| 451 |
+
auto left_quat = rotation_matrix_to_quat_d(left_rot);
|
| 452 |
+
auto right_quat = rotation_matrix_to_quat_d(right_rot);
|
| 453 |
+
|
| 454 |
+
// Store quaternions (w, x, y, z format)
|
| 455 |
+
vr_orientation[0] = left_quat[0]; // left qw
|
| 456 |
+
vr_orientation[1] = left_quat[1]; // left qx
|
| 457 |
+
vr_orientation[2] = left_quat[2]; // left qy
|
| 458 |
+
vr_orientation[3] = left_quat[3]; // left qz
|
| 459 |
+
vr_orientation[4] = right_quat[0]; // right qw
|
| 460 |
+
vr_orientation[5] = right_quat[1]; // right qx
|
| 461 |
+
vr_orientation[6] = right_quat[2]; // right qy
|
| 462 |
+
vr_orientation[7] = right_quat[3]; // right qz
|
| 463 |
+
// Head orientation uses defaults
|
| 464 |
+
vr_orientation[8] = 0.9991;
|
| 465 |
+
vr_orientation[9] = 0.011;
|
| 466 |
+
vr_orientation[10] = 0.0402;
|
| 467 |
+
vr_orientation[11] = -0.0002;
|
| 468 |
+
|
| 469 |
+
// Update buffers
|
| 470 |
+
vr_3point_position_.SetData(vr_position);
|
| 471 |
+
vr_3point_orientation_.SetData(vr_orientation);
|
| 472 |
+
} else {
|
| 473 |
+
// Fallback: Use standard wrist_pose format (14 doubles)
|
| 474 |
+
vr_position[0] = control_goal_buffer_.wrist_pose[0]; // left wrist x
|
| 475 |
+
vr_position[1] = control_goal_buffer_.wrist_pose[1]; // left wrist y
|
| 476 |
+
vr_position[2] = control_goal_buffer_.wrist_pose[2]; // left wrist z
|
| 477 |
+
vr_position[3] = control_goal_buffer_.wrist_pose[7]; // right wrist x
|
| 478 |
+
vr_position[4] = control_goal_buffer_.wrist_pose[8]; // right wrist y
|
| 479 |
+
vr_position[5] = control_goal_buffer_.wrist_pose[9]; // right wrist z
|
| 480 |
+
// Head position uses defaults
|
| 481 |
+
vr_position[6] = 0.0241;
|
| 482 |
+
vr_position[7] = -0.0081;
|
| 483 |
+
vr_position[8] = 0.4028;
|
| 484 |
+
|
| 485 |
+
// Update VR 3-point orientation data (wrist quaternions)
|
| 486 |
+
vr_orientation[0] = control_goal_buffer_.wrist_pose[3]; // left wrist qw
|
| 487 |
+
vr_orientation[1] = control_goal_buffer_.wrist_pose[4]; // left wrist qx
|
| 488 |
+
vr_orientation[2] = control_goal_buffer_.wrist_pose[5]; // left wrist qy
|
| 489 |
+
vr_orientation[3] = control_goal_buffer_.wrist_pose[6]; // left wrist qz
|
| 490 |
+
vr_orientation[4] = control_goal_buffer_.wrist_pose[10]; // right wrist qw
|
| 491 |
+
vr_orientation[5] = control_goal_buffer_.wrist_pose[11]; // right wrist qx
|
| 492 |
+
vr_orientation[6] = control_goal_buffer_.wrist_pose[12]; // right wrist qy
|
| 493 |
+
vr_orientation[7] = control_goal_buffer_.wrist_pose[13]; // right wrist qz
|
| 494 |
+
// Head orientation uses defaults
|
| 495 |
+
vr_orientation[8] = 0.9991;
|
| 496 |
+
vr_orientation[9] = 0.011;
|
| 497 |
+
vr_orientation[10] = 0.0402;
|
| 498 |
+
vr_orientation[11] = -0.0002;
|
| 499 |
+
|
| 500 |
+
// Update buffers
|
| 501 |
+
vr_3point_position_.SetData(vr_position);
|
| 502 |
+
vr_3point_orientation_.SetData(vr_orientation);
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
+
if constexpr (DEBUG_LOGGING) {
|
| 506 |
+
static int goal_debug_counter = 0;
|
| 507 |
+
goal_debug_counter++;
|
| 508 |
+
if (goal_debug_counter % 50 == 0) { // Log every 50 calls to avoid spam
|
| 509 |
+
std::cout << "[ROS2 DEBUG] Control goal update:" << std::endl;
|
| 510 |
+
std::cout << " Navigate cmd: [" << navigate_cmd_from_teleop_[0] << ", "
|
| 511 |
+
<< navigate_cmd_from_teleop_[1] << ", " << navigate_cmd_from_teleop_[2] << "]" << std::endl;
|
| 512 |
+
std::cout << " Base height: " << control_goal_buffer_.base_height_command << std::endl;
|
| 513 |
+
std::cout << " Toggle policy action: " << (control_goal_buffer_.toggle_policy_action ? "true" : "false") << std::endl;
|
| 514 |
+
|
| 515 |
+
// Data availability flags
|
| 516 |
+
std::cout << " Data flags: has_ik_data=" << (control_goal_buffer_.has_ik_data ? "true" : "false")
|
| 517 |
+
<< ", has_wrist_matrices=" << (control_goal_buffer_.has_wrist_matrices ? "true" : "false")
|
| 518 |
+
<< ", has_hand_joints=" << (has_hand_joints_ ? "true" : "false")
|
| 519 |
+
<< ", use_ik_mode=" << (use_ik_mode_ ? "true" : "false") << std::endl;
|
| 520 |
+
|
| 521 |
+
// VR 3-point positions (left wrist, right wrist, head)
|
| 522 |
+
std::cout << " VR 3-point positions:" << std::endl;
|
| 523 |
+
std::cout << " Left wrist: [" << vr_position[0] << ", " << vr_position[1] << ", " << vr_position[2] << "]" << std::endl;
|
| 524 |
+
std::cout << " Right wrist: [" << vr_position[3] << ", " << vr_position[4] << ", " << vr_position[5] << "]" << std::endl;
|
| 525 |
+
std::cout << " Head: [" << vr_position[6] << ", " << vr_position[7] << ", " << vr_position[8] << "]" << std::endl;
|
| 526 |
+
|
| 527 |
+
// VR 3-point orientations (quaternions: w, x, y, z)
|
| 528 |
+
std::cout << " VR 3-point orientations:" << std::endl;
|
| 529 |
+
std::cout << " Left wrist: [" << vr_orientation[0] << ", " << vr_orientation[1] << ", "
|
| 530 |
+
<< vr_orientation[2] << ", " << vr_orientation[3] << "]" << std::endl;
|
| 531 |
+
std::cout << " Right wrist: [" << vr_orientation[4] << ", " << vr_orientation[5] << ", "
|
| 532 |
+
<< vr_orientation[6] << ", " << vr_orientation[7] << "]" << std::endl;
|
| 533 |
+
std::cout << " Head: [" << vr_orientation[8] << ", " << vr_orientation[9] << ", "
|
| 534 |
+
<< vr_orientation[10] << ", " << vr_orientation[11] << "]" << std::endl;
|
| 535 |
+
|
| 536 |
+
if (has_hand_joints_) {
|
| 537 |
+
auto [has_left, left_hand] = GetHandPose(true);
|
| 538 |
+
auto [has_right, right_hand] = GetHandPose(false);
|
| 539 |
+
std::cout << " Left hand pose: [";
|
| 540 |
+
for (size_t i = 0; i < 7; ++i) {
|
| 541 |
+
std::cout << left_hand[i];
|
| 542 |
+
if (i < 6) std::cout << ", ";
|
| 543 |
+
}
|
| 544 |
+
std::cout << "]" << std::endl;
|
| 545 |
+
std::cout << " Right hand pose: [";
|
| 546 |
+
for (size_t i = 0; i < 7; ++i) {
|
| 547 |
+
std::cout << right_hand[i];
|
| 548 |
+
if (i < 6) std::cout << ", ";
|
| 549 |
+
}
|
| 550 |
+
std::cout << "]" << std::endl;
|
| 551 |
+
}
|
| 552 |
+
}
|
| 553 |
+
}
|
| 554 |
+
} else {
|
| 555 |
+
// No control goal data available
|
| 556 |
+
use_teleop_navigate_cmd_ = false;
|
| 557 |
+
}
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
// Override the handle_input function from InputInterface
|
| 561 |
+
// Uses local boolean flags (set by update()) to perform actions on system state
|
| 562 |
+
void handle_input(MotionDataReader& motion_reader,
|
| 563 |
+
std::shared_ptr<const MotionSequence>& current_motion,
|
| 564 |
+
int& current_frame,
|
| 565 |
+
OperatorState& operator_state,
|
| 566 |
+
bool& reinitialize_heading,
|
| 567 |
+
DataBuffer<HeadingState>& heading_state_buffer,
|
| 568 |
+
bool has_planner,
|
| 569 |
+
PlannerState& planner_state,
|
| 570 |
+
DataBuffer<MovementState>& movement_state_buffer,
|
| 571 |
+
std::mutex& current_motion_mutex,
|
| 572 |
+
bool& report_temperature) override {
|
| 573 |
+
|
| 574 |
+
// Handle emergency stop (triggered by ROS2 errors/timeout)
|
| 575 |
+
if (emergency_stop_) {
|
| 576 |
+
std::cout << "[ROS2 EMERGENCY STOP] Triggering emergency stop due to ROS2 failure" << std::endl;
|
| 577 |
+
operator_state.stop = true;
|
| 578 |
+
return; // Skip all other processing
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
// Handle safety reset from interface manager
|
| 582 |
+
// ROS2 requires planner to be enabled
|
| 583 |
+
if (trigger_safety_reset) {
|
| 584 |
+
trigger_safety_reset = false;
|
| 585 |
+
{
|
| 586 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 587 |
+
operator_state.play = false;
|
| 588 |
+
}
|
| 589 |
+
if (has_planner && operator_state.start) {
|
| 590 |
+
control_is_active_ = true;
|
| 591 |
+
if (planner_state.enabled && planner_state.initialized) {
|
| 592 |
+
// Planner is already on, keep it as is (don't touch initialized flag)
|
| 593 |
+
{
|
| 594 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 595 |
+
if (current_motion->GetEncodeMode() >= 0) {
|
| 596 |
+
current_motion->SetEncodeMode(1);
|
| 597 |
+
}
|
| 598 |
+
operator_state.play = true;
|
| 599 |
+
}
|
| 600 |
+
auto current_facing = movement_state_buffer.GetDataWithTime().data->facing_direction;
|
| 601 |
+
planner_facing_angle_ = std::atan2(current_facing[1], current_facing[0]);
|
| 602 |
+
std::cout << "Safety reset: Planner kept enabled with current state" << std::endl;
|
| 603 |
+
} else {
|
| 604 |
+
// Planner was disabled, go to first motion and set initial heading and movement state
|
| 605 |
+
// Set initial heading and movement state
|
| 606 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE), {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 607 |
+
// Set current motion and frame to reference motion (lock mutex)
|
| 608 |
+
{
|
| 609 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 610 |
+
auto temp_motion = std::make_shared<MotionSequence>(*current_motion);
|
| 611 |
+
temp_motion->name = "temporary_motion";
|
| 612 |
+
current_motion = temp_motion;
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
// Now it is safe to enable planner
|
| 616 |
+
// Ensure planner is enabled (always required in ROS2 mode)
|
| 617 |
+
planner_state.enabled = true;
|
| 618 |
+
planner_facing_angle_ = 0.0;
|
| 619 |
+
std::cout << "[ROS2] Planner enabled" << std::endl;
|
| 620 |
+
// Wait for planner to be initialized with timeout (5 seconds)
|
| 621 |
+
auto wait_start = std::chrono::steady_clock::now();
|
| 622 |
+
constexpr auto PLANNER_INIT_TIMEOUT = std::chrono::seconds(5);
|
| 623 |
+
while (planner_state.enabled) {
|
| 624 |
+
{
|
| 625 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 626 |
+
if (current_motion->name == "planner_motion") {
|
| 627 |
+
std::cout << "[ROS2] motion name is planner_motion" << std::endl;
|
| 628 |
+
break;
|
| 629 |
+
}
|
| 630 |
+
}
|
| 631 |
+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
| 632 |
+
auto elapsed = std::chrono::steady_clock::now() - wait_start;
|
| 633 |
+
if (elapsed > PLANNER_INIT_TIMEOUT) {
|
| 634 |
+
std::cerr << "[ROS2 ERROR] Planner initialization timeout after 5 seconds" << std::endl;
|
| 635 |
+
operator_state.stop = true;
|
| 636 |
+
return;
|
| 637 |
+
}
|
| 638 |
+
std::cout << "[ROS2] Waiting for planner to be initialized" << std::endl;
|
| 639 |
+
}
|
| 640 |
+
// Check if planner is enabled and initialized
|
| 641 |
+
if (!planner_state.enabled || !planner_state.initialized) {
|
| 642 |
+
std::cerr << "[ROS2 ERROR] Planner failed to initialize - ROS2 mode requires planner. Stopping control." << std::endl;
|
| 643 |
+
operator_state.stop = true;
|
| 644 |
+
return;
|
| 645 |
+
}
|
| 646 |
+
// Play motion
|
| 647 |
+
{
|
| 648 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 649 |
+
if (current_motion->GetEncodeMode() == 0) {
|
| 650 |
+
current_motion->SetEncodeMode(1);
|
| 651 |
+
}
|
| 652 |
+
operator_state.play = true;
|
| 653 |
+
}
|
| 654 |
+
}
|
| 655 |
+
}
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
// Check if planner is loaded (required for ROS2 mode)
|
| 659 |
+
if (!has_planner) {
|
| 660 |
+
std::cerr << "[ROS2 ERROR] Planner not loaded - ROS2 mode requires planner. Stopping control." << std::endl;
|
| 661 |
+
operator_state.stop = true;
|
| 662 |
+
return;
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
// Handle control start/stop
|
| 666 |
+
if (this->stop_control_) { operator_state.stop = true; }
|
| 667 |
+
if (this->report_temperature_flag_) { report_temperature = true; }
|
| 668 |
+
|
| 669 |
+
// Handle control start
|
| 670 |
+
if (this->start_control_) {
|
| 671 |
+
// Start control
|
| 672 |
+
operator_state.start = true;
|
| 673 |
+
{
|
| 674 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 675 |
+
operator_state.play = false;
|
| 676 |
+
reinitialize_heading = true;
|
| 677 |
+
}
|
| 678 |
+
// Ensure planner is enabled (always required in ROS2 mode)
|
| 679 |
+
if (!planner_state.enabled) {
|
| 680 |
+
planner_state.enabled = true;
|
| 681 |
+
planner_facing_angle_ = 0.0;
|
| 682 |
+
std::cout << "[ROS2] Planner enabled" << std::endl;
|
| 683 |
+
}
|
| 684 |
+
// Wait for planner to be initialized with timeout (5 seconds)
|
| 685 |
+
auto wait_start = std::chrono::steady_clock::now();
|
| 686 |
+
constexpr auto PLANNER_INIT_TIMEOUT = std::chrono::seconds(5);
|
| 687 |
+
while (planner_state.enabled) {
|
| 688 |
+
{
|
| 689 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 690 |
+
if (current_motion->name == "planner_motion") {
|
| 691 |
+
std::cout << "[ROS2] motion name is planner_motion" << std::endl;
|
| 692 |
+
break;
|
| 693 |
+
}
|
| 694 |
+
}
|
| 695 |
+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
| 696 |
+
auto elapsed = std::chrono::steady_clock::now() - wait_start;
|
| 697 |
+
if (elapsed > PLANNER_INIT_TIMEOUT) {
|
| 698 |
+
std::cerr << "[ROS2 ERROR] Planner initialization timeout after 5 seconds" << std::endl;
|
| 699 |
+
operator_state.stop = true;
|
| 700 |
+
return;
|
| 701 |
+
}
|
| 702 |
+
std::cout << "[ROS2] Waiting for planner to be initialized" << std::endl;
|
| 703 |
+
}
|
| 704 |
+
// Check if planner is enabled and initialized
|
| 705 |
+
if (!planner_state.enabled || !planner_state.initialized) {
|
| 706 |
+
std::cerr << "[ROS2 ERROR] Planner failed to initialize - ROS2 mode requires planner. Stopping control." << std::endl;
|
| 707 |
+
operator_state.stop = true;
|
| 708 |
+
return;
|
| 709 |
+
}
|
| 710 |
+
// Play motion
|
| 711 |
+
{
|
| 712 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 713 |
+
operator_state.play = true;
|
| 714 |
+
}
|
| 715 |
+
}
|
| 716 |
+
|
| 717 |
+
if (planner_state.enabled && planner_state.initialized) {
|
| 718 |
+
|
| 719 |
+
// Set final movement values from navigate_cmd
|
| 720 |
+
int final_mode = static_cast<int>(LocomotionMode::IDLE);
|
| 721 |
+
std::array<double, 3> final_movement = {0.0, 0.0, 0.0};
|
| 722 |
+
std::array<double, 3> final_facing_direction = {1.0, 0.0, 0.0};
|
| 723 |
+
double final_speed = 0.0;
|
| 724 |
+
double final_height = -1.0;
|
| 725 |
+
|
| 726 |
+
// Get and process base_height_command (thread-safe copy)
|
| 727 |
+
double base_height = base_height_command_;
|
| 728 |
+
// Clip to valid range [0.1, 0.88]
|
| 729 |
+
base_height = std::clamp(base_height, 0.1, 0.88);
|
| 730 |
+
|
| 731 |
+
// Convert navigate_cmd to movement direction and mode
|
| 732 |
+
if (use_teleop_navigate_cmd_) {
|
| 733 |
+
// navigate_cmd format: [lin_vel_x, lin_vel_y, ang_vel_z]
|
| 734 |
+
// Convert to movement_direction and mode
|
| 735 |
+
double lin_vel_x = navigate_cmd_from_teleop_[0];
|
| 736 |
+
double lin_vel_y = navigate_cmd_from_teleop_[1];
|
| 737 |
+
double ang_vel_z = navigate_cmd_from_teleop_[2];
|
| 738 |
+
|
| 739 |
+
// Update facing angle based on angular velocity (similar to gamepad)
|
| 740 |
+
if (std::abs(ang_vel_z) > 0.01f) {
|
| 741 |
+
// Negative sign matches gamepad behavior (positive ang_vel_z = turn left/CCW)
|
| 742 |
+
planner_facing_angle_ += ang_vel_z * 0.02;
|
| 743 |
+
}
|
| 744 |
+
|
| 745 |
+
// Always compute facing direction from maintained angle (not from navigation topic)
|
| 746 |
+
final_facing_direction[0] = std::cos(planner_facing_angle_);
|
| 747 |
+
final_facing_direction[1] = std::sin(planner_facing_angle_);
|
| 748 |
+
final_facing_direction[2] = 0.0f;
|
| 749 |
+
|
| 750 |
+
// Calculate movement magnitude
|
| 751 |
+
double movement_mag = std::sqrt(lin_vel_x * lin_vel_x + lin_vel_y * lin_vel_y);
|
| 752 |
+
|
| 753 |
+
double planner_moving_direction = planner_facing_angle_;
|
| 754 |
+
|
| 755 |
+
// Determine locomotion mode based on base_height_command first
|
| 756 |
+
if (base_height >= 0.72f) {
|
| 757 |
+
// Height 0.72-0.88: Normal walking modes
|
| 758 |
+
if (movement_mag > 0.01f) {
|
| 759 |
+
// Moving: use walk modes
|
| 760 |
+
// Compute moving direction (same as gamepad logic)
|
| 761 |
+
planner_moving_direction = std::atan2(lin_vel_y, lin_vel_x) + planner_moving_direction;
|
| 762 |
+
|
| 763 |
+
// Bin the moving direction to 8 evenly spaced directions and get corresponding speed
|
| 764 |
+
auto [binned_angle, direction_speed] = bin_angle_to_8_directions(planner_moving_direction);
|
| 765 |
+
planner_moving_direction = binned_angle;
|
| 766 |
+
|
| 767 |
+
// Compute normalized movement direction from binned angle
|
| 768 |
+
final_movement[0] = std::cos(planner_moving_direction);
|
| 769 |
+
final_movement[1] = std::sin(planner_moving_direction);
|
| 770 |
+
final_movement[2] = 0.0f;
|
| 771 |
+
|
| 772 |
+
if (locomotion_mode_is_fast_) {
|
| 773 |
+
// Normal walk mode: default speed (-1)
|
| 774 |
+
final_mode = static_cast<int>(LocomotionMode::WALK);
|
| 775 |
+
final_speed = -1.0f;
|
| 776 |
+
} else {
|
| 777 |
+
// Slow walk mode: speed varies by direction (faster forward/lateral, slower backward)
|
| 778 |
+
final_mode = static_cast<int>(LocomotionMode::SLOW_WALK);
|
| 779 |
+
final_speed = direction_speed;
|
| 780 |
+
}
|
| 781 |
+
} else {
|
| 782 |
+
// No movement: idle
|
| 783 |
+
final_mode = static_cast<int>(LocomotionMode::IDLE);
|
| 784 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 785 |
+
final_speed = -1.0f;
|
| 786 |
+
}
|
| 787 |
+
final_height = -1.0f; // Use default height for walking
|
| 788 |
+
|
| 789 |
+
} else if (base_height >= 0.5f) {
|
| 790 |
+
// Height 0.5-0.72: Squat mode (static pose, no movement)
|
| 791 |
+
final_mode = static_cast<int>(LocomotionMode::IDEL_SQUAT);
|
| 792 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 793 |
+
final_speed = -1.0f; // Use default speed (no walking while squatting)
|
| 794 |
+
final_height = base_height; // Pass actual height command
|
| 795 |
+
|
| 796 |
+
} else {
|
| 797 |
+
// Height 0.1-0.5: Kneel mode (static pose, no movement)
|
| 798 |
+
final_mode = static_cast<int>(LocomotionMode::IDEL_KNEEL);
|
| 799 |
+
final_movement = {0.0f, 0.0f, 0.0f};
|
| 800 |
+
final_speed = -1.0f; // Use default speed (no walking while kneeling)
|
| 801 |
+
final_height = base_height; // Pass actual height command
|
| 802 |
+
}
|
| 803 |
+
}
|
| 804 |
+
|
| 805 |
+
// Debug: Log final computed values being sent to planner
|
| 806 |
+
if constexpr (DEBUG_LOGGING) {
|
| 807 |
+
static int debug_counter = 0;
|
| 808 |
+
debug_counter++;
|
| 809 |
+
if (debug_counter % 50 == 0) { // Log every 50 calls to avoid spam
|
| 810 |
+
std::cout << "[ROS2 DEBUG] Planner control values:" << std::endl;
|
| 811 |
+
if (use_teleop_navigate_cmd_) {
|
| 812 |
+
std::cout << " Input navigate_cmd: [" << navigate_cmd_from_teleop_[0] << ", "
|
| 813 |
+
<< navigate_cmd_from_teleop_[1] << ", " << navigate_cmd_from_teleop_[2] << "]" << std::endl;
|
| 814 |
+
std::cout << " Facing angle: " << planner_facing_angle_ << " rad ("
|
| 815 |
+
<< (planner_facing_angle_ * 180.0 / M_PI) << " deg)" << std::endl;
|
| 816 |
+
}
|
| 817 |
+
std::cout << " Base height command: " << base_height_command_
|
| 818 |
+
<< " (clamped: " << std::clamp(base_height_command_, 0.1, 0.88) << ")" << std::endl;
|
| 819 |
+
std::cout << " Final mode: " << final_mode << " (0=idle, 1=slow, 2=walk, 3=run, 4=squat, 6=kneel)" << std::endl;
|
| 820 |
+
std::cout << " Final speed: " << final_speed << std::endl;
|
| 821 |
+
std::cout << " Final height: " << final_height << std::endl;
|
| 822 |
+
std::cout << " Movement direction: [" << final_movement[0] << ", " << final_movement[1] << ", " << final_movement[2] << "]" << std::endl;
|
| 823 |
+
std::cout << " Facing direction: [" << final_facing_direction[0] << ", " << final_facing_direction[1] << ", " << final_facing_direction[2] << "]" << std::endl;
|
| 824 |
+
}
|
| 825 |
+
}
|
| 826 |
+
|
| 827 |
+
// Update thread-safe buffer (single source of truth for planner thread)
|
| 828 |
+
MovementState mode_state(final_mode, final_movement, final_facing_direction, final_speed, final_height);
|
| 829 |
+
movement_state_buffer.SetData(mode_state);
|
| 830 |
+
}
|
| 831 |
+
}
|
| 832 |
+
|
| 833 |
+
// Get ROS2 node for external use (e.g., spinning)
|
| 834 |
+
std::shared_ptr<rclcpp::Node> get_node() const { return node_; }
|
| 835 |
+
|
| 836 |
+
// Check if ROS2 node is healthy and receiving data
|
| 837 |
+
bool is_receiving_control_goal_data() const { return received_control_goal_.load(); }
|
| 838 |
+
bool is_ros2_ok() const { return rclcpp::ok() && node_ != nullptr; }
|
| 839 |
+
|
| 840 |
+
// Reset data received flags (useful for debugging)
|
| 841 |
+
void reset_data_flags() {
|
| 842 |
+
received_control_goal_.store(false);
|
| 843 |
+
last_control_goal_time_ns_.store(0);
|
| 844 |
+
}
|
| 845 |
+
|
| 846 |
+
// Get ROS timestamp in seconds (for state logging)
|
| 847 |
+
// This is ROS2-specific and not part of the InputInterface base class
|
| 848 |
+
double GetROSTimestamp() const {
|
| 849 |
+
if (node_) {
|
| 850 |
+
return node_->get_clock()->now().nanoseconds() / 1e9;
|
| 851 |
+
}
|
| 852 |
+
return 0.0;
|
| 853 |
+
}
|
| 854 |
+
|
| 855 |
+
private:
|
| 856 |
+
// ------------------------------------------------------------------
|
| 857 |
+
// ROS 2 infrastructure
|
| 858 |
+
// ------------------------------------------------------------------
|
| 859 |
+
std::shared_ptr<rclcpp::Node> node_; ///< Lightweight ROS 2 node for this handler.
|
| 860 |
+
|
| 861 |
+
/// Subscription to `ControlPolicy/upper_body_pose` (ByteMultiArray, msgpack).
|
| 862 |
+
rclcpp::Subscription<std_msgs::msg::ByteMultiArray>::SharedPtr control_goal_sub_;
|
| 863 |
+
|
| 864 |
+
// ------------------------------------------------------------------
|
| 865 |
+
// Thread-safe receiving buffer (written by callback, read by update())
|
| 866 |
+
// ------------------------------------------------------------------
|
| 867 |
+
ControlGoalMsg control_goal_buffer_; ///< Latest deserialized message.
|
| 868 |
+
std::mutex control_goal_mutex_; ///< Guards control_goal_buffer_.
|
| 869 |
+
std::atomic<bool> received_control_goal_{false}; ///< True once at least one message arrived.
|
| 870 |
+
std::atomic<int64_t> last_control_goal_time_ns_{0}; ///< Monotonic timestamp of last message (ns).
|
| 871 |
+
static constexpr double CONTROL_GOAL_TIMEOUT = 1.0; ///< Seconds before a timeout reset.
|
| 872 |
+
|
| 873 |
+
/// When true, use IK-processed transformation matrices for VR position;
|
| 874 |
+
/// when false, use raw left_wrist / right_wrist matrices.
|
| 875 |
+
bool use_ik_mode_ = false;
|
| 876 |
+
|
| 877 |
+
// ------------------------------------------------------------------
|
| 878 |
+
// Control-toggle state
|
| 879 |
+
// ------------------------------------------------------------------
|
| 880 |
+
bool control_is_active_ = false; ///< Tracks the toggle state for toggle_policy_action.
|
| 881 |
+
bool locomotion_mode_is_fast_ = false; ///< false = SLOW_WALK (custom speed), true = WALK (default speed).
|
| 882 |
+
|
| 883 |
+
// ------------------------------------------------------------------
|
| 884 |
+
// Per-frame control flags (reset in update())
|
| 885 |
+
// ------------------------------------------------------------------
|
| 886 |
+
bool start_control_ = false; ///< Start control this frame.
|
| 887 |
+
bool stop_control_ = false; ///< Stop control this frame.
|
| 888 |
+
bool report_temperature_flag_ = false; ///< Report temperature this frame (F key).
|
| 889 |
+
|
| 890 |
+
// ------------------------------------------------------------------
|
| 891 |
+
// Teleop state (updated from control_goal_buffer_ in update())
|
| 892 |
+
// ------------------------------------------------------------------
|
| 893 |
+
std::array<double, 3> navigate_cmd_from_teleop_ = {0.0, 0.0, 0.0}; ///< [lin_x, lin_y, ang_z].
|
| 894 |
+
bool use_teleop_navigate_cmd_ = false; ///< True while navigate_cmd is valid.
|
| 895 |
+
double base_height_command_ = 0.78; ///< Thread-safe copy of base_height_command.
|
| 896 |
+
|
| 897 |
+
/// Accumulated facing angle (radians), integrated from ang_vel_z each frame.
|
| 898 |
+
double planner_facing_angle_ = 0.0;
|
| 899 |
+
|
| 900 |
+
struct termios old_termios_; ///< Saved terminal state for restoration on destruction.
|
| 901 |
+
|
| 902 |
+
/**
|
| 903 |
+
* @brief Deserialize a msgpack-encoded control-goal payload.
|
| 904 |
+
* @param data Raw bytes from the ByteMultiArray message.
|
| 905 |
+
* @return Parsed ControlGoalMsg (valid == true on success).
|
| 906 |
+
*
|
| 907 |
+
* Expected keys: navigate_cmd, wrist_pose, left_wrist_after_ik,
|
| 908 |
+
* right_wrist_after_ik, head_after_ik, left_wrist, right_wrist,
|
| 909 |
+
* base_height_command, toggle_policy_action, locomotion_mode,
|
| 910 |
+
* left_hand_joint, right_hand_joint, ros_timestamp.
|
| 911 |
+
*/
|
| 912 |
+
ControlGoalMsg parse_msgpack_control_goal(const std::vector<uint8_t>& data) {
|
| 913 |
+
ControlGoalMsg msg;
|
| 914 |
+
msg.valid = false;
|
| 915 |
+
|
| 916 |
+
try {
|
| 917 |
+
// Use msgpack-c library for clean, efficient parsing
|
| 918 |
+
msgpack::object_handle oh = msgpack::unpack(reinterpret_cast<const char*>(data.data()), data.size());
|
| 919 |
+
msgpack::object deserialized = oh.get();
|
| 920 |
+
|
| 921 |
+
// Convert to map
|
| 922 |
+
if (deserialized.type != msgpack::type::MAP) {
|
| 923 |
+
return msg;
|
| 924 |
+
}
|
| 925 |
+
|
| 926 |
+
std::map<std::string, msgpack::object> map_data;
|
| 927 |
+
deserialized.convert(map_data);
|
| 928 |
+
|
| 929 |
+
// Extract navigate_cmd (array of 3 doubles)
|
| 930 |
+
if (map_data.count("navigate_cmd") && map_data["navigate_cmd"].type == msgpack::type::ARRAY) {
|
| 931 |
+
auto nav_arr = map_data["navigate_cmd"].as<std::vector<double>>();
|
| 932 |
+
if (nav_arr.size() >= 3) {
|
| 933 |
+
msg.navigate_cmd[0] = nav_arr[0];
|
| 934 |
+
msg.navigate_cmd[1] = nav_arr[1];
|
| 935 |
+
msg.navigate_cmd[2] = nav_arr[2];
|
| 936 |
+
}
|
| 937 |
+
}
|
| 938 |
+
|
| 939 |
+
// Extract wrist_pose (array of 14 doubles)
|
| 940 |
+
if (map_data.count("wrist_pose") && map_data["wrist_pose"].type == msgpack::type::ARRAY) {
|
| 941 |
+
auto wrist_arr = map_data["wrist_pose"].as<std::vector<double>>();
|
| 942 |
+
if (wrist_arr.size() >= 14) {
|
| 943 |
+
std::copy_n(wrist_arr.begin(), 14, msg.wrist_pose.begin());
|
| 944 |
+
}
|
| 945 |
+
}
|
| 946 |
+
|
| 947 |
+
// Extract IK-processed matrices (16 doubles each)
|
| 948 |
+
if (map_data.count("left_wrist_after_ik") && map_data["left_wrist_after_ik"].type == msgpack::type::ARRAY) {
|
| 949 |
+
try {
|
| 950 |
+
auto nested_arr = map_data["left_wrist_after_ik"].as<std::vector<std::vector<double>>>();
|
| 951 |
+
if (nested_arr.size() == 4 && nested_arr[0].size() == 4) {
|
| 952 |
+
// Flatten 4x4 matrix to 1D array (row-major)
|
| 953 |
+
size_t idx = 0;
|
| 954 |
+
for (const auto& row : nested_arr) {
|
| 955 |
+
for (double val : row) {
|
| 956 |
+
msg.left_wrist_after_ik[idx++] = val;
|
| 957 |
+
}
|
| 958 |
+
}
|
| 959 |
+
msg.has_ik_data = true;
|
| 960 |
+
}
|
| 961 |
+
} catch (const std::exception& e) {
|
| 962 |
+
if constexpr (DEBUG_LOGGING) {
|
| 963 |
+
std::cerr << "[ROS2 ERROR] Failed to parse left_wrist_after_ik: " << e.what() << std::endl;
|
| 964 |
+
}
|
| 965 |
+
}
|
| 966 |
+
}
|
| 967 |
+
|
| 968 |
+
if (map_data.count("right_wrist_after_ik") && map_data["right_wrist_after_ik"].type == msgpack::type::ARRAY) {
|
| 969 |
+
try {
|
| 970 |
+
auto nested_arr = map_data["right_wrist_after_ik"].as<std::vector<std::vector<double>>>();
|
| 971 |
+
if (nested_arr.size() == 4 && nested_arr[0].size() == 4) {
|
| 972 |
+
// Flatten 4x4 matrix to 1D array (row-major)
|
| 973 |
+
size_t idx = 0;
|
| 974 |
+
for (const auto& row : nested_arr) {
|
| 975 |
+
for (double val : row) {
|
| 976 |
+
msg.right_wrist_after_ik[idx++] = val;
|
| 977 |
+
}
|
| 978 |
+
}
|
| 979 |
+
msg.has_ik_data = true;
|
| 980 |
+
}
|
| 981 |
+
} catch (const std::exception& e) {
|
| 982 |
+
if constexpr (DEBUG_LOGGING) {
|
| 983 |
+
std::cerr << "[ROS2 ERROR] Failed to parse right_wrist_after_ik: " << e.what() << std::endl;
|
| 984 |
+
}
|
| 985 |
+
}
|
| 986 |
+
}
|
| 987 |
+
|
| 988 |
+
if (map_data.count("head_after_ik") && map_data["head_after_ik"].type == msgpack::type::ARRAY) {
|
| 989 |
+
try {
|
| 990 |
+
auto nested_arr = map_data["head_after_ik"].as<std::vector<std::vector<double>>>();
|
| 991 |
+
if (nested_arr.size() == 4 && nested_arr[0].size() == 4) {
|
| 992 |
+
// Flatten 4x4 matrix to 1D array (row-major)
|
| 993 |
+
size_t idx = 0;
|
| 994 |
+
for (const auto& row : nested_arr) {
|
| 995 |
+
for (double val : row) {
|
| 996 |
+
msg.head_after_ik[idx++] = val;
|
| 997 |
+
}
|
| 998 |
+
}
|
| 999 |
+
msg.has_ik_data = true;
|
| 1000 |
+
}
|
| 1001 |
+
} catch (const std::exception& e) {
|
| 1002 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1003 |
+
std::cerr << "[ROS2 ERROR] Failed to parse head_after_ik: " << e.what() << std::endl;
|
| 1004 |
+
}
|
| 1005 |
+
}
|
| 1006 |
+
}
|
| 1007 |
+
|
| 1008 |
+
// Extract non-IK wrist matrices (16 doubles each)
|
| 1009 |
+
if (map_data.count("left_wrist") && map_data["left_wrist"].type == msgpack::type::ARRAY) {
|
| 1010 |
+
try {
|
| 1011 |
+
auto nested_arr = map_data["left_wrist"].as<std::vector<std::vector<double>>>();
|
| 1012 |
+
if (nested_arr.size() == 4 && nested_arr[0].size() == 4) {
|
| 1013 |
+
// Flatten 4x4 matrix to 1D array (row-major)
|
| 1014 |
+
size_t idx = 0;
|
| 1015 |
+
for (const auto& row : nested_arr) {
|
| 1016 |
+
for (double val : row) {
|
| 1017 |
+
msg.left_wrist[idx++] = val;
|
| 1018 |
+
}
|
| 1019 |
+
}
|
| 1020 |
+
msg.has_wrist_matrices = true;
|
| 1021 |
+
}
|
| 1022 |
+
} catch (const std::exception& e) {
|
| 1023 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1024 |
+
std::cerr << "[ROS2 ERROR] Failed to parse left_wrist: " << e.what() << std::endl;
|
| 1025 |
+
}
|
| 1026 |
+
}
|
| 1027 |
+
}
|
| 1028 |
+
|
| 1029 |
+
if (map_data.count("right_wrist") && map_data["right_wrist"].type == msgpack::type::ARRAY) {
|
| 1030 |
+
try {
|
| 1031 |
+
auto nested_arr = map_data["right_wrist"].as<std::vector<std::vector<double>>>();
|
| 1032 |
+
if (nested_arr.size() == 4 && nested_arr[0].size() == 4) {
|
| 1033 |
+
// Flatten 4x4 matrix to 1D array (row-major)
|
| 1034 |
+
size_t idx = 0;
|
| 1035 |
+
for (const auto& row : nested_arr) {
|
| 1036 |
+
for (double val : row) {
|
| 1037 |
+
msg.right_wrist[idx++] = val;
|
| 1038 |
+
}
|
| 1039 |
+
}
|
| 1040 |
+
msg.has_wrist_matrices = true;
|
| 1041 |
+
}
|
| 1042 |
+
} catch (const std::exception& e) {
|
| 1043 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1044 |
+
std::cerr << "[ROS2 ERROR] Failed to parse right_wrist: " << e.what() << std::endl;
|
| 1045 |
+
}
|
| 1046 |
+
}
|
| 1047 |
+
}
|
| 1048 |
+
|
| 1049 |
+
// Extract base_height_command (double)
|
| 1050 |
+
if (map_data.count("base_height_command")) {
|
| 1051 |
+
msg.base_height_command = map_data["base_height_command"].as<double>();
|
| 1052 |
+
}
|
| 1053 |
+
|
| 1054 |
+
// Extract toggle_policy_action (bool)
|
| 1055 |
+
if (map_data.count("toggle_policy_action")) {
|
| 1056 |
+
msg.toggle_policy_action = map_data["toggle_policy_action"].as<bool>();
|
| 1057 |
+
}
|
| 1058 |
+
|
| 1059 |
+
// Extract locomotion_mode (int: 0 = slow walk, 1 = fast walk)
|
| 1060 |
+
if (map_data.count("locomotion_mode")) {
|
| 1061 |
+
msg.locomotion_mode = map_data["locomotion_mode"].as<int>();
|
| 1062 |
+
}
|
| 1063 |
+
|
| 1064 |
+
// Extract left_hand_joint (7 doubles - joint positions)
|
| 1065 |
+
if (map_data.count("left_hand_joint") && map_data["left_hand_joint"].type == msgpack::type::ARRAY) {
|
| 1066 |
+
auto left_hand_arr = map_data["left_hand_joint"].as<std::vector<double>>();
|
| 1067 |
+
if (left_hand_arr.size() >= 7) {
|
| 1068 |
+
std::copy_n(left_hand_arr.begin(), 7, msg.left_hand_joint.begin());
|
| 1069 |
+
msg.has_hand_joints = true;
|
| 1070 |
+
}
|
| 1071 |
+
}
|
| 1072 |
+
|
| 1073 |
+
// Extract right_hand_joint (7 doubles - joint positions)
|
| 1074 |
+
if (map_data.count("right_hand_joint") && map_data["right_hand_joint"].type == msgpack::type::ARRAY) {
|
| 1075 |
+
auto right_hand_arr = map_data["right_hand_joint"].as<std::vector<double>>();
|
| 1076 |
+
if (right_hand_arr.size() >= 7) {
|
| 1077 |
+
std::copy_n(right_hand_arr.begin(), 7, msg.right_hand_joint.begin());
|
| 1078 |
+
msg.has_hand_joints = true;
|
| 1079 |
+
}
|
| 1080 |
+
}
|
| 1081 |
+
|
| 1082 |
+
// Extract ros_timestamp (double - ROS time in seconds)
|
| 1083 |
+
if (map_data.count("ros_timestamp")) {
|
| 1084 |
+
msg.ros_timestamp = map_data["ros_timestamp"].as<double>();
|
| 1085 |
+
}
|
| 1086 |
+
|
| 1087 |
+
msg.valid = true;
|
| 1088 |
+
|
| 1089 |
+
} catch (const std::exception& e) {
|
| 1090 |
+
std::cerr << "[ROS2 ERROR] msgpack parsing failed: " << e.what() << std::endl;
|
| 1091 |
+
msg.valid = false;
|
| 1092 |
+
}
|
| 1093 |
+
|
| 1094 |
+
return msg;
|
| 1095 |
+
}
|
| 1096 |
+
|
| 1097 |
+
/**
|
| 1098 |
+
* @brief Extract the 3×3 rotation matrix from a flattened row-major 4×4 transform.
|
| 1099 |
+
* @return 3×3 rotation matrix compatible with math_utils::rotation_matrix_to_quat_d().
|
| 1100 |
+
*/
|
| 1101 |
+
std::array<std::array<double, 3>, 3> extract_rotation_from_transform(const std::array<double, 16>& transform) {
|
| 1102 |
+
// 4x4 matrix in row-major: [R00,R01,R02,tx, R10,R11,R12,ty, R20,R21,R22,tz, 0,0,0,1]
|
| 1103 |
+
return {{
|
| 1104 |
+
{transform[0], transform[1], transform[2]}, // Row 0
|
| 1105 |
+
{transform[4], transform[5], transform[6]}, // Row 1
|
| 1106 |
+
{transform[8], transform[9], transform[10]} // Row 2
|
| 1107 |
+
}};
|
| 1108 |
+
}
|
| 1109 |
+
|
| 1110 |
+
/// Extract the translation vector [x, y, z] from a flattened row-major 4×4 transform.
|
| 1111 |
+
std::array<double, 3> extract_position_from_transform(const std::array<double, 16>& transform) {
|
| 1112 |
+
// 4x4 matrix in row-major: position is at [3, 7, 11]
|
| 1113 |
+
return {transform[3], transform[7], transform[11]};
|
| 1114 |
+
}
|
| 1115 |
+
|
| 1116 |
+
/// Multiply a 3×3 rotation matrix by a 3D offset vector (R × v).
|
| 1117 |
+
std::array<double, 3> apply_rotation_to_offset(
|
| 1118 |
+
const std::array<std::array<double, 3>, 3>& rot_mat,
|
| 1119 |
+
const std::array<double, 3>& offset) {
|
| 1120 |
+
return {
|
| 1121 |
+
rot_mat[0][0] * offset[0] + rot_mat[0][1] * offset[1] + rot_mat[0][2] * offset[2], // Row 0 * offset
|
| 1122 |
+
rot_mat[1][0] * offset[0] + rot_mat[1][1] * offset[1] + rot_mat[1][2] * offset[2], // Row 1 * offset
|
| 1123 |
+
rot_mat[2][0] * offset[0] + rot_mat[2][1] * offset[1] + rot_mat[2][2] * offset[2] // Row 2 * offset
|
| 1124 |
+
};
|
| 1125 |
+
}
|
| 1126 |
+
|
| 1127 |
+
/**
|
| 1128 |
+
* @brief Quantise an angle to the nearest 45° bin and return a direction-dependent speed.
|
| 1129 |
+
*
|
| 1130 |
+
* The 8 bins: 0° (forward), ±45° (forward-diagonal), ±90° (lateral),
|
| 1131 |
+
* ±135° (backward-diagonal), 180° (backward).
|
| 1132 |
+
*
|
| 1133 |
+
* @param angle Input angle in radians (will be normalised to [−π, π]).
|
| 1134 |
+
* @return {binned_angle, slow_walk_speed} – angle snapped to nearest bin,
|
| 1135 |
+
* and the corresponding speed for SLOW_WALK mode (faster forward,
|
| 1136 |
+
* slower backward).
|
| 1137 |
+
*/
|
| 1138 |
+
std::pair<double, double> bin_angle_to_8_directions(double angle) {
|
| 1139 |
+
constexpr double BIN_SIZE = M_PI / 4.0; // 45 degrees in radians
|
| 1140 |
+
constexpr int NUM_BINS = 8;
|
| 1141 |
+
|
| 1142 |
+
// Normalize angle to [-π, π]
|
| 1143 |
+
while (angle > M_PI) angle -= 2.0 * M_PI;
|
| 1144 |
+
while (angle < -M_PI) angle += 2.0 * M_PI;
|
| 1145 |
+
|
| 1146 |
+
// Find nearest bin
|
| 1147 |
+
int bin_index = static_cast<int>(std::round(angle / BIN_SIZE));
|
| 1148 |
+
|
| 1149 |
+
// Handle wrap-around (bin_index can be -4 to 4)
|
| 1150 |
+
if (bin_index > 4) bin_index -= NUM_BINS;
|
| 1151 |
+
if (bin_index < -4) bin_index += NUM_BINS;
|
| 1152 |
+
|
| 1153 |
+
// Convert back to angle
|
| 1154 |
+
double binned_angle = bin_index * BIN_SIZE;
|
| 1155 |
+
|
| 1156 |
+
// Determine speed based on direction bin for slow walk mode
|
| 1157 |
+
// Faster forward/lateral, slower backward
|
| 1158 |
+
double slow_walk_speed;
|
| 1159 |
+
switch (bin_index) {
|
| 1160 |
+
case 0: // Forward (0°)
|
| 1161 |
+
case 1: // Forward-right (45°)
|
| 1162 |
+
case -1: // Forward-left (-45°)
|
| 1163 |
+
slow_walk_speed = 0.3f;
|
| 1164 |
+
break;
|
| 1165 |
+
case 2: // Right (90°)
|
| 1166 |
+
case -2: // Left (-90°)
|
| 1167 |
+
slow_walk_speed = 0.35f;
|
| 1168 |
+
break;
|
| 1169 |
+
case 3: // Back-right (135°)
|
| 1170 |
+
case -3: // Back-left (-135°)
|
| 1171 |
+
slow_walk_speed = 0.25f;
|
| 1172 |
+
break;
|
| 1173 |
+
case 4: // Backward (180°)
|
| 1174 |
+
case -4: // Backward (-180°)
|
| 1175 |
+
slow_walk_speed = 0.2f;
|
| 1176 |
+
break;
|
| 1177 |
+
default:
|
| 1178 |
+
slow_walk_speed = 0.2f; // Fallback
|
| 1179 |
+
break;
|
| 1180 |
+
}
|
| 1181 |
+
|
| 1182 |
+
return {binned_angle, slow_walk_speed};
|
| 1183 |
+
}
|
| 1184 |
+
|
| 1185 |
+
/**
|
| 1186 |
+
* @brief ROS 2 subscriber callback (runs on the executor thread).
|
| 1187 |
+
*
|
| 1188 |
+
* Deserialises the ByteMultiArray payload via msgpack, stores the result
|
| 1189 |
+
* in `control_goal_buffer_` under `control_goal_mutex_`, and sets the
|
| 1190 |
+
* `received_control_goal_` flag. Edge-triggered commands (toggle_policy_action)
|
| 1191 |
+
* are accumulated with OR logic to prevent lost pulses.
|
| 1192 |
+
*/
|
| 1193 |
+
void control_goal_callback(std::shared_ptr<const std_msgs::msg::ByteMultiArray> msg) {
|
| 1194 |
+
try {
|
| 1195 |
+
// Convert ByteMultiArray to vector<uint8_t>
|
| 1196 |
+
// msg->data is already std::vector<uint8_t>, so just copy it
|
| 1197 |
+
std::vector<uint8_t> data(msg->data.begin(), msg->data.end());
|
| 1198 |
+
|
| 1199 |
+
// Parse msgpack data
|
| 1200 |
+
ControlGoalMsg goal_msg = parse_msgpack_control_goal(data);
|
| 1201 |
+
|
| 1202 |
+
if (goal_msg.valid) {
|
| 1203 |
+
// Store in receiving buffer (thread-safe)
|
| 1204 |
+
{
|
| 1205 |
+
std::lock_guard<std::mutex> lock(control_goal_mutex_);
|
| 1206 |
+
// Edge-triggered commands: accumulate with OR (like mode_control)
|
| 1207 |
+
// This prevents losing a toggle if it arrives between update() cycles
|
| 1208 |
+
bool prev_toggle_policy = control_goal_buffer_.toggle_policy_action;
|
| 1209 |
+
control_goal_buffer_ = goal_msg;
|
| 1210 |
+
control_goal_buffer_.toggle_policy_action = prev_toggle_policy || goal_msg.toggle_policy_action;
|
| 1211 |
+
// locomotion_mode is a direct state value (0 or 1), not accumulated
|
| 1212 |
+
}
|
| 1213 |
+
received_control_goal_.store(true);
|
| 1214 |
+
// Update timestamp for timeout tracking (using steady_clock for monotonic timing)
|
| 1215 |
+
last_control_goal_time_ns_.store(std::chrono::steady_clock::now().time_since_epoch().count());
|
| 1216 |
+
|
| 1217 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1218 |
+
static int goal_counter = 0;
|
| 1219 |
+
goal_counter++;
|
| 1220 |
+
if (goal_counter % 50 == 0) { // Log every 50 messages to avoid spam
|
| 1221 |
+
std::cout << "[ROS2 DEBUG] Control goal message received:" << std::endl;
|
| 1222 |
+
std::cout << " navigate_cmd: [" << goal_msg.navigate_cmd[0] << ", "
|
| 1223 |
+
<< goal_msg.navigate_cmd[1] << ", " << goal_msg.navigate_cmd[2] << "]" << std::endl;
|
| 1224 |
+
std::cout << " base_height: " << goal_msg.base_height_command << std::endl;
|
| 1225 |
+
}
|
| 1226 |
+
}
|
| 1227 |
+
} else {
|
| 1228 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1229 |
+
std::cout << "[ROS2 DEBUG] Invalid control goal message received" << std::endl;
|
| 1230 |
+
}
|
| 1231 |
+
}
|
| 1232 |
+
} catch (const std::exception& e) {
|
| 1233 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1234 |
+
std::cout << "[ROS2 ERROR] Failed to process control goal message: " << e.what() << std::endl;
|
| 1235 |
+
}
|
| 1236 |
+
}
|
| 1237 |
+
}
|
| 1238 |
+
|
| 1239 |
+
// Helper method to initialize ROS2 subscriber
|
| 1240 |
+
void setup_subscribers() {
|
| 1241 |
+
// Create subscriber for control goal topic (teleop commands from Python)
|
| 1242 |
+
control_goal_sub_ = node_->create_subscription<std_msgs::msg::ByteMultiArray>(
|
| 1243 |
+
"ControlPolicy/upper_body_pose",
|
| 1244 |
+
1, // QoS depth = 1 for some buffering
|
| 1245 |
+
[this](std::shared_ptr<const std_msgs::msg::ByteMultiArray> msg) {
|
| 1246 |
+
this->control_goal_callback(msg);
|
| 1247 |
+
}
|
| 1248 |
+
);
|
| 1249 |
+
|
| 1250 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1251 |
+
std::cout << "[ROS2 DEBUG] Control goal subscriber created for topic: ControlPolicy/upper_body_pose" << std::endl;
|
| 1252 |
+
}
|
| 1253 |
+
}
|
| 1254 |
+
|
| 1255 |
+
/*
|
| 1256 |
+
* ROS2 Topic Structure:
|
| 1257 |
+
*
|
| 1258 |
+
* ControlPolicy/upper_body_pose (std_msgs/ByteMultiArray) - msgpack-serialized ControlGoalMsg:
|
| 1259 |
+
* - navigate_cmd: double[3] (navigation velocities [lin_vel_x, lin_vel_y, ang_vel_z])
|
| 1260 |
+
* - wrist_pose: double[14] (left + right wrist poses: [x,y,z,qw,qx,qy,qz] * 2)
|
| 1261 |
+
* - left_wrist_after_ik: double[16] (4x4 transformation matrix, flattened row-major)
|
| 1262 |
+
* - right_wrist_after_ik: double[16] (4x4 transformation matrix, flattened row-major)
|
| 1263 |
+
* - head_after_ik: double[16] (4x4 transformation matrix, flattened row-major)
|
| 1264 |
+
* - left_wrist: double[16] (4x4 transformation matrix, flattened row-major, non-IK)
|
| 1265 |
+
* - right_wrist: double[16] (4x4 transformation matrix, flattened row-major, non-IK)
|
| 1266 |
+
* - left_hand_joint: double[7] (7 DOF joint positions for left hand)
|
| 1267 |
+
* - right_hand_joint: double[7] (7 DOF joint positions for right hand)
|
| 1268 |
+
* - base_height_command: double (desired base height)
|
| 1269 |
+
* - toggle_policy_action: bool (toggle between start/stop control)
|
| 1270 |
+
* - locomotion_mode: int (0 = slow walk with custom speed, 1 = fast walk with default speed)
|
| 1271 |
+
* - ros_timestamp: double (ROS time in seconds for synchronization)
|
| 1272 |
+
* - valid: bool (message validity flag)
|
| 1273 |
+
*/
|
| 1274 |
+
};
|
| 1275 |
+
|
| 1276 |
+
#endif // HAS_ROS2
|
| 1277 |
+
|
| 1278 |
+
#endif // ROS2_INPUT_HANDLER_HPP
|
GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/streamed_motion_merger.hpp
ADDED
|
@@ -0,0 +1,517 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file streamed_motion_merger.hpp
|
| 3 |
+
* @brief Reusable sliding-window merger for streamed motion data.
|
| 4 |
+
*
|
| 5 |
+
* StreamedMotionMerger receives chunks of motion frames (joint positions /
|
| 6 |
+
* velocities, body quaternions, SMPL data) from any streaming source (ZMQ,
|
| 7 |
+
* ROS2, etc.) and merges them into a single growing MotionSequence using a
|
| 8 |
+
* sliding-window approach.
|
| 9 |
+
*
|
| 10 |
+
* ## Key Concepts
|
| 11 |
+
*
|
| 12 |
+
* - **Frame indices**: Each incoming chunk carries a vector of monotonically
|
| 13 |
+
* increasing integer indices that identify frames in a global timeline.
|
| 14 |
+
* The merger uses these to align new data with the existing window.
|
| 15 |
+
*
|
| 16 |
+
* - **Frame step**: The stride between consecutive frame indices (e.g. 2 if
|
| 17 |
+
* the sender runs at 60 Hz and the consumer at 30 Hz). Detected
|
| 18 |
+
* automatically from the first two indices of each chunk.
|
| 19 |
+
*
|
| 20 |
+
* - **Sliding window**: The merger maintains a window of frames centred
|
| 21 |
+
* around the current playback position. It keeps `HISTORY_FRAMES` past
|
| 22 |
+
* frames for smooth interpolation and appends new frames from the
|
| 23 |
+
* incoming chunk. Old frames that fall behind the window are discarded.
|
| 24 |
+
*
|
| 25 |
+
* - **Catch-up reset**: If the gap between the playback position and the
|
| 26 |
+
* incoming data exceeds `MAX_GAP_FRAMES`, the merger resets the window
|
| 27 |
+
* to the start of the incoming chunk and signals the caller to reset
|
| 28 |
+
* the playback cursor to frame 0. This prevents unbounded buffering
|
| 29 |
+
* when the network falls behind.
|
| 30 |
+
*
|
| 31 |
+
* - **Protocol versions**: The merger itself is version-agnostic – it
|
| 32 |
+
* merges whatever data fields are present in IncomingData. Protocol-
|
| 33 |
+
* version validation (rejecting changes mid-session, etc.) is left to
|
| 34 |
+
* the caller (e.g. ZMQEndpointInterface).
|
| 35 |
+
*
|
| 36 |
+
* ## Thread Safety
|
| 37 |
+
*
|
| 38 |
+
* The merger is **not** thread-safe. All calls must be serialised by the
|
| 39 |
+
* caller (typically by holding the data_mutex_ in ZMQEndpointInterface).
|
| 40 |
+
*/
|
| 41 |
+
|
| 42 |
+
#ifndef STREAMED_MOTION_MERGER_HPP
|
| 43 |
+
#define STREAMED_MOTION_MERGER_HPP
|
| 44 |
+
|
| 45 |
+
#include <memory>
|
| 46 |
+
#include <vector>
|
| 47 |
+
#include <array>
|
| 48 |
+
#include <algorithm>
|
| 49 |
+
#include <limits>
|
| 50 |
+
#include <iostream>
|
| 51 |
+
#include <iomanip>
|
| 52 |
+
#include <cstring>
|
| 53 |
+
|
| 54 |
+
// Forward declaration – MotionSequence is defined in motion_data_reader.hpp.
|
| 55 |
+
struct MotionSequence;
|
| 56 |
+
|
| 57 |
+
/**
|
| 58 |
+
* @class StreamedMotionMerger
|
| 59 |
+
* @brief Merges incoming motion-frame chunks into a sliding-window
|
| 60 |
+
* MotionSequence for real-time playback.
|
| 61 |
+
*/
|
| 62 |
+
class StreamedMotionMerger {
|
| 63 |
+
public:
|
| 64 |
+
/// Compile-time toggle for debug log output.
|
| 65 |
+
static constexpr bool DEBUG_LOGGING = true;
|
| 66 |
+
/// Number of already-consumed frames to retain before the playback cursor
|
| 67 |
+
/// (provides look-back for interpolation / blending).
|
| 68 |
+
static constexpr int HISTORY_FRAMES = 5;
|
| 69 |
+
/// Maximum tolerated gap (in current-rate frames) before a catch-up reset.
|
| 70 |
+
static constexpr int MAX_GAP_FRAMES = 200;
|
| 71 |
+
|
| 72 |
+
/// Returned by MergeIncomingData() to communicate what happened.
|
| 73 |
+
struct MergeResult {
|
| 74 |
+
std::shared_ptr<MotionSequence> motion; ///< Merged motion (nullptr on failure).
|
| 75 |
+
int window_start = 0; ///< Global frame index of motion[0].
|
| 76 |
+
int frame_offset_adjustment = 0; ///< Subtract from current_frame to compensate for window shift.
|
| 77 |
+
bool did_catchup_reset = false; ///< True → caller should reset playback to frame 0.
|
| 78 |
+
int frame_step = 1; ///< Detected stride between consecutive frame indices.
|
| 79 |
+
int protocol_version = 0; ///< Protocol version of the incoming data (1, 2, or 3).
|
| 80 |
+
};
|
| 81 |
+
|
| 82 |
+
/// All the data needed for one merge operation, decoded by the caller.
|
| 83 |
+
struct IncomingData {
|
| 84 |
+
// -- Joint data (required in v1 & v3, optional in v2) --
|
| 85 |
+
std::vector<std::vector<double>> joint_pos; ///< [frame][joint] positions (radians).
|
| 86 |
+
std::vector<std::vector<double>> joint_vel; ///< [frame][joint] velocities (rad/s).
|
| 87 |
+
|
| 88 |
+
// -- Body quaternions (required for all versions) --
|
| 89 |
+
std::vector<std::vector<std::array<double, 4>>> body_quat; ///< [frame][body][w,x,y,z].
|
| 90 |
+
|
| 91 |
+
// -- SMPL data (required in v2 & v3, optional in v1) --
|
| 92 |
+
std::vector<std::vector<std::array<double, 3>>> smpl_joints; ///< [frame][joint][x,y,z].
|
| 93 |
+
std::vector<std::vector<std::array<double, 3>>> smpl_pose; ///< [frame][pose][axis-angle x,y,z].
|
| 94 |
+
|
| 95 |
+
std::vector<int64_t> frame_indices; ///< Monotonic global frame indices (required).
|
| 96 |
+
|
| 97 |
+
int protocol_version = 1; ///< Protocol version (1, 2, or 3).
|
| 98 |
+
bool catch_up_enabled = true; ///< true → use MAX_GAP_FRAMES; false → allow infinite delay.
|
| 99 |
+
|
| 100 |
+
// Derived dimensions (must match the vector sizes above)
|
| 101 |
+
int num_frames = 0; ///< Number of frames in this chunk.
|
| 102 |
+
int num_joints = 0; ///< Joints per frame (joint_pos / joint_vel width).
|
| 103 |
+
int num_quat_bodies = 0; ///< Number of rigid bodies per frame (body_quat width).
|
| 104 |
+
int num_smpl_joints = 0; ///< SMPL joints per frame.
|
| 105 |
+
int num_smpl_poses = 0; ///< SMPL pose parameters per frame.
|
| 106 |
+
};
|
| 107 |
+
|
| 108 |
+
StreamedMotionMerger() {
|
| 109 |
+
Reset();
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
// Reset the merger state (clear all buffered data)
|
| 113 |
+
void Reset() {
|
| 114 |
+
streamed_motion_ = std::make_shared<MotionSequence>();
|
| 115 |
+
streamed_motion_->name = "streamed";
|
| 116 |
+
streamed_motion_->ReserveCapacity(15000, 29, 1, 1, 0, 0);
|
| 117 |
+
stream_window_start_ = 0;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
// Main merging method: merge incoming data with existing buffered data
|
| 121 |
+
// Returns MergeResult containing the merged motion and playback adjustments
|
| 122 |
+
//
|
| 123 |
+
// Note: Protocol version validation should be done by the caller before calling this method.
|
| 124 |
+
// The merger doesn't care about protocol versions - it just merges the data.
|
| 125 |
+
MergeResult MergeIncomingData(const IncomingData& data, int current_playback_frame) {
|
| 126 |
+
MergeResult result;
|
| 127 |
+
|
| 128 |
+
// Validate incoming data
|
| 129 |
+
if (!ValidateIncomingData(data)) {
|
| 130 |
+
std::cerr << "[StreamedMotionMerger] Invalid incoming data" << std::endl;
|
| 131 |
+
return result;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
// Extract frame step and validate
|
| 135 |
+
int frame_step = CalculateFrameStep(data.frame_indices);
|
| 136 |
+
int incoming_frame_start = static_cast<int>(data.frame_indices[0]);
|
| 137 |
+
int incoming_frame_end = static_cast<int>(data.frame_indices[data.num_frames - 1]);
|
| 138 |
+
|
| 139 |
+
if constexpr (DEBUG_LOGGING) {
|
| 140 |
+
std::cout << "[StreamedMotionMerger] Processing " << data.num_frames << " frames, "
|
| 141 |
+
<< "incoming_frame_start=" << incoming_frame_start
|
| 142 |
+
<< ", frame_step=" << frame_step << std::endl;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
// Calculate sliding window parameters
|
| 146 |
+
int global_playback_frame = stream_window_start_ + frame_step * std::max(0, current_playback_frame - HISTORY_FRAMES);
|
| 147 |
+
int new_window_start = stream_window_start_;
|
| 148 |
+
int merge_dst_frame = 0;
|
| 149 |
+
bool did_catchup = false;
|
| 150 |
+
|
| 151 |
+
CalculateSlidingWindow(
|
| 152 |
+
incoming_frame_start,
|
| 153 |
+
incoming_frame_end,
|
| 154 |
+
frame_step,
|
| 155 |
+
current_playback_frame,
|
| 156 |
+
global_playback_frame,
|
| 157 |
+
data.catch_up_enabled,
|
| 158 |
+
new_window_start,
|
| 159 |
+
merge_dst_frame,
|
| 160 |
+
did_catchup
|
| 161 |
+
);
|
| 162 |
+
|
| 163 |
+
// Create new motion sequence
|
| 164 |
+
auto new_motion = CreateNewMotion(data);
|
| 165 |
+
|
| 166 |
+
// Copy old data to fill gap before incoming data
|
| 167 |
+
if (merge_dst_frame > 0) {
|
| 168 |
+
CopyOldDataToNewMotion(
|
| 169 |
+
streamed_motion_,
|
| 170 |
+
stream_window_start_,
|
| 171 |
+
new_motion,
|
| 172 |
+
new_window_start,
|
| 173 |
+
incoming_frame_start,
|
| 174 |
+
frame_step,
|
| 175 |
+
data
|
| 176 |
+
);
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
// Copy incoming data to new motion
|
| 180 |
+
CopyIncomingDataToMotion(data, new_motion, merge_dst_frame);
|
| 181 |
+
|
| 182 |
+
// Update total timesteps
|
| 183 |
+
new_motion->timesteps = merge_dst_frame + data.num_frames;
|
| 184 |
+
|
| 185 |
+
if constexpr (DEBUG_LOGGING) {
|
| 186 |
+
std::cout << "[StreamedMotionMerger] Merged motion: " << new_motion->timesteps
|
| 187 |
+
<< " frames (copied: " << merge_dst_frame << " + incoming: " << data.num_frames << ")" << std::endl;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
// Calculate frame offset adjustment BEFORE updating state
|
| 191 |
+
int old_window_start = stream_window_start_;
|
| 192 |
+
int window_shift_ticks = new_window_start - old_window_start;
|
| 193 |
+
int window_shift = (frame_step > 0) ? (window_shift_ticks / frame_step) : 0;
|
| 194 |
+
|
| 195 |
+
// Update state
|
| 196 |
+
streamed_motion_ = new_motion;
|
| 197 |
+
stream_window_start_ = new_window_start;
|
| 198 |
+
|
| 199 |
+
// Build result
|
| 200 |
+
result.motion = new_motion;
|
| 201 |
+
result.window_start = new_window_start;
|
| 202 |
+
result.frame_offset_adjustment = did_catchup ? 0 : window_shift;
|
| 203 |
+
result.did_catchup_reset = did_catchup;
|
| 204 |
+
result.frame_step = frame_step;
|
| 205 |
+
result.protocol_version = data.protocol_version;
|
| 206 |
+
|
| 207 |
+
return result;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
private:
|
| 211 |
+
std::shared_ptr<MotionSequence> streamed_motion_;
|
| 212 |
+
int stream_window_start_ = 0;
|
| 213 |
+
|
| 214 |
+
// Validate incoming data structure
|
| 215 |
+
bool ValidateIncomingData(const IncomingData& data) const {
|
| 216 |
+
// Check required fields
|
| 217 |
+
if (data.body_quat.empty() || data.frame_indices.empty()) {
|
| 218 |
+
std::cerr << "[StreamedMotionMerger] Missing required fields (body_quat or frame_indices)" << std::endl;
|
| 219 |
+
return false;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
// Validate protocol-specific requirements
|
| 223 |
+
if (data.protocol_version == 3) {
|
| 224 |
+
// Version 3: requires both SMPL data AND joint data
|
| 225 |
+
if (data.smpl_joints.empty() || data.smpl_pose.empty()) {
|
| 226 |
+
std::cerr << "[StreamedMotionMerger] Protocol v3 missing smpl_joints or smpl_pose" << std::endl;
|
| 227 |
+
return false;
|
| 228 |
+
}
|
| 229 |
+
if (data.joint_pos.empty() || data.joint_vel.empty()) {
|
| 230 |
+
std::cerr << "[StreamedMotionMerger] Protocol v3 missing joint_pos or joint_vel" << std::endl;
|
| 231 |
+
return false;
|
| 232 |
+
}
|
| 233 |
+
} else if (data.protocol_version == 2) {
|
| 234 |
+
// Version 2: requires SMPL data (joint data optional)
|
| 235 |
+
if (data.smpl_joints.empty() || data.smpl_pose.empty()) {
|
| 236 |
+
std::cerr << "[StreamedMotionMerger] Protocol v2 missing smpl_joints or smpl_pose" << std::endl;
|
| 237 |
+
return false;
|
| 238 |
+
}
|
| 239 |
+
} else if (data.protocol_version == 1) {
|
| 240 |
+
// Version 1: requires joint data (SMPL data optional)
|
| 241 |
+
if (data.joint_pos.empty() || data.joint_vel.empty()) {
|
| 242 |
+
std::cerr << "[StreamedMotionMerger] Protocol v1 missing joint_pos or joint_vel" << std::endl;
|
| 243 |
+
return false;
|
| 244 |
+
}
|
| 245 |
+
} else {
|
| 246 |
+
std::cerr << "[StreamedMotionMerger] Unsupported protocol version: " << data.protocol_version << std::endl;
|
| 247 |
+
return false;
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
return true;
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
// Calculate frame step from frame indices
|
| 254 |
+
int CalculateFrameStep(const std::vector<int64_t>& frame_indices) const {
|
| 255 |
+
if (frame_indices.size() < 2) {
|
| 256 |
+
return 1;
|
| 257 |
+
}
|
| 258 |
+
int64_t step = std::abs(frame_indices[1] - frame_indices[0]);
|
| 259 |
+
return step > 0 ? static_cast<int>(step) : 1;
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
// Calculate sliding window parameters
|
| 263 |
+
void CalculateSlidingWindow(
|
| 264 |
+
int incoming_frame_start,
|
| 265 |
+
int incoming_frame_end,
|
| 266 |
+
int frame_step,
|
| 267 |
+
int current_playback_frame,
|
| 268 |
+
int global_playback_frame,
|
| 269 |
+
bool catch_up_enabled,
|
| 270 |
+
int& new_window_start,
|
| 271 |
+
int& merge_dst_frame,
|
| 272 |
+
bool& did_catchup
|
| 273 |
+
) {
|
| 274 |
+
// Special case: first packet
|
| 275 |
+
if (!streamed_motion_ || streamed_motion_->timesteps <= 0) {
|
| 276 |
+
new_window_start = incoming_frame_start;
|
| 277 |
+
merge_dst_frame = 0;
|
| 278 |
+
did_catchup = true;
|
| 279 |
+
return;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
// Calculate max gap based on catch_up flag
|
| 283 |
+
int max_gap_frames = catch_up_enabled
|
| 284 |
+
? (MAX_GAP_FRAMES + HISTORY_FRAMES)
|
| 285 |
+
: std::numeric_limits<int>::max();
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
int stream_window_end = stream_window_start_ + frame_step * (streamed_motion_->timesteps - 1);
|
| 289 |
+
|
| 290 |
+
if (DEBUG_LOGGING) {
|
| 291 |
+
std::cout << "[StreamedMotionMerger] incoming_frame_start: " << incoming_frame_start
|
| 292 |
+
<< ", incoming_frame_end: " << incoming_frame_end
|
| 293 |
+
<< ", stream_window_start_: " << stream_window_start_
|
| 294 |
+
<< ", stream_window_end: " << stream_window_end
|
| 295 |
+
<< ", frame_step: " << frame_step
|
| 296 |
+
<< ", global_playback_frame: " << global_playback_frame
|
| 297 |
+
<< ", streamed_motion_->timesteps: " << streamed_motion_->timesteps
|
| 298 |
+
<< std::endl;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
// Check for incoming data older than current window
|
| 302 |
+
if (incoming_frame_start <= stream_window_start_) {
|
| 303 |
+
if constexpr (DEBUG_LOGGING) {
|
| 304 |
+
std::cout << "[StreamedMotionMerger] WARNING: incoming_frame_start (" << incoming_frame_start
|
| 305 |
+
<< ") < stream_window_start_ (" << stream_window_start_ << ") - forcing catch-up" << std::endl;
|
| 306 |
+
}
|
| 307 |
+
new_window_start = incoming_frame_start;
|
| 308 |
+
merge_dst_frame = 0;
|
| 309 |
+
did_catchup = true;
|
| 310 |
+
return;
|
| 311 |
+
} else if (incoming_frame_end <= stream_window_end) {
|
| 312 |
+
if constexpr (DEBUG_LOGGING) {
|
| 313 |
+
std::cout << "[StreamedMotionMerger] WARNING: incoming_frame_end (" << incoming_frame_end
|
| 314 |
+
<< ") <= stream_window_end (" << stream_window_end << ") - forcing catch-up" << std::endl;
|
| 315 |
+
}
|
| 316 |
+
new_window_start = incoming_frame_start;
|
| 317 |
+
merge_dst_frame = 0;
|
| 318 |
+
did_catchup = true;
|
| 319 |
+
return;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
// Tentative window aligned to playback
|
| 323 |
+
int desired_window_start = global_playback_frame;
|
| 324 |
+
int tentative_window_start = std::min(desired_window_start, incoming_frame_start);
|
| 325 |
+
int delta_to_incoming = incoming_frame_start - tentative_window_start;
|
| 326 |
+
int tentative_merge_dst = (frame_step > 0) ? (delta_to_incoming / frame_step) : 0;
|
| 327 |
+
|
| 328 |
+
// Check for large gap
|
| 329 |
+
bool large_gap_from_old = incoming_frame_start > stream_window_end + frame_step;
|
| 330 |
+
|
| 331 |
+
if (tentative_merge_dst > max_gap_frames || large_gap_from_old) {
|
| 332 |
+
// Catch-up: reset window to incoming frame
|
| 333 |
+
new_window_start = incoming_frame_start;
|
| 334 |
+
merge_dst_frame = 0;
|
| 335 |
+
did_catchup = true;
|
| 336 |
+
|
| 337 |
+
if constexpr (DEBUG_LOGGING) {
|
| 338 |
+
std::cout << "[StreamedMotionMerger] CATCH-UP: gap too large or old data expired" << std::endl;
|
| 339 |
+
}
|
| 340 |
+
} else {
|
| 341 |
+
// Normal merge
|
| 342 |
+
new_window_start = tentative_window_start;
|
| 343 |
+
merge_dst_frame = tentative_merge_dst;
|
| 344 |
+
}
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
// Create new motion sequence with appropriate capacity
|
| 348 |
+
std::shared_ptr<MotionSequence> CreateNewMotion(const IncomingData& data) const {
|
| 349 |
+
auto new_motion = std::make_shared<MotionSequence>();
|
| 350 |
+
new_motion->name = "streamed";
|
| 351 |
+
|
| 352 |
+
int joints_to_reserve = data.num_joints;
|
| 353 |
+
int bodies_to_reserve = 1;
|
| 354 |
+
int body_quaternions_to_reserve = data.num_quat_bodies;
|
| 355 |
+
int smpl_joints_to_reserve = data.num_smpl_joints;
|
| 356 |
+
int smpl_poses_to_reserve = data.num_smpl_poses;
|
| 357 |
+
|
| 358 |
+
new_motion->ReserveCapacity(
|
| 359 |
+
15000,
|
| 360 |
+
joints_to_reserve,
|
| 361 |
+
bodies_to_reserve,
|
| 362 |
+
body_quaternions_to_reserve,
|
| 363 |
+
smpl_joints_to_reserve,
|
| 364 |
+
smpl_poses_to_reserve
|
| 365 |
+
);
|
| 366 |
+
|
| 367 |
+
// Initialize body_part_indexes (typically just root for streaming)
|
| 368 |
+
new_motion->SetBodyPartIndexes({0});
|
| 369 |
+
|
| 370 |
+
return new_motion;
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
// Copy old data to new motion to fill gap before incoming data
|
| 374 |
+
void CopyOldDataToNewMotion(
|
| 375 |
+
std::shared_ptr<MotionSequence> old_motion,
|
| 376 |
+
int old_window_start,
|
| 377 |
+
std::shared_ptr<MotionSequence> new_motion,
|
| 378 |
+
int new_window_start,
|
| 379 |
+
int incoming_frame_start,
|
| 380 |
+
int frame_step,
|
| 381 |
+
const IncomingData& data
|
| 382 |
+
) {
|
| 383 |
+
if (!old_motion || old_motion->timesteps <= 0) {
|
| 384 |
+
return;
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
int old_window_end = old_window_start + frame_step * old_motion->timesteps;
|
| 388 |
+
|
| 389 |
+
// Find overlap between old data and needed range
|
| 390 |
+
int need_start_global = new_window_start;
|
| 391 |
+
int need_end_global = incoming_frame_start;
|
| 392 |
+
int overlap_start_global = std::max(need_start_global, old_window_start);
|
| 393 |
+
int overlap_end_global = std::min(need_end_global, old_window_end);
|
| 394 |
+
|
| 395 |
+
if (overlap_start_global >= overlap_end_global) {
|
| 396 |
+
return; // No overlap
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
// Calculate copy parameters
|
| 400 |
+
int start_offset_old = overlap_start_global - old_window_start;
|
| 401 |
+
int start_offset_new = overlap_start_global - new_window_start;
|
| 402 |
+
int overlap_span = overlap_end_global - overlap_start_global;
|
| 403 |
+
int copy_src_idx = (frame_step > 0) ? (start_offset_old / frame_step) : 0;
|
| 404 |
+
int copy_dst_idx = (frame_step > 0) ? (start_offset_new / frame_step) : 0;
|
| 405 |
+
int copy_count = (frame_step > 0) ? (overlap_span / frame_step) : 0;
|
| 406 |
+
|
| 407 |
+
if constexpr (DEBUG_LOGGING) {
|
| 408 |
+
std::cout << "[StreamedMotionMerger] Copying old data: "
|
| 409 |
+
<< "global [" << overlap_start_global << ".." << (overlap_end_global-1) << "] → "
|
| 410 |
+
<< "new_motion[" << copy_dst_idx << ".." << (copy_dst_idx + copy_count - 1) << "]" << std::endl;
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
// Copy joint data if present
|
| 414 |
+
if (data.num_joints > 0 && old_motion->GetNumJoints() > 0) {
|
| 415 |
+
int joints_to_copy = std::min(data.num_joints, old_motion->GetNumJoints());
|
| 416 |
+
for (int i = 0; i < copy_count; ++i) {
|
| 417 |
+
for (int joint = 0; joint < joints_to_copy; ++joint) {
|
| 418 |
+
new_motion->JointPositions(copy_dst_idx + i)[joint] =
|
| 419 |
+
old_motion->JointPositions(copy_src_idx + i)[joint];
|
| 420 |
+
new_motion->JointVelocities(copy_dst_idx + i)[joint] =
|
| 421 |
+
old_motion->JointVelocities(copy_src_idx + i)[joint];
|
| 422 |
+
}
|
| 423 |
+
}
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
// Copy body quaternions
|
| 427 |
+
int old_quat_bodies = old_motion->GetNumBodyQuaternions();
|
| 428 |
+
int quat_bodies_to_copy = std::min(data.num_quat_bodies, old_quat_bodies);
|
| 429 |
+
for (int i = 0; i < copy_count; ++i) {
|
| 430 |
+
for (int b = 0; b < quat_bodies_to_copy; ++b) {
|
| 431 |
+
for (int q = 0; q < 4; ++q) {
|
| 432 |
+
new_motion->BodyQuaternions(copy_dst_idx + i)[b][q] =
|
| 433 |
+
old_motion->BodyQuaternions(copy_src_idx + i)[b][q];
|
| 434 |
+
}
|
| 435 |
+
}
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
// Copy SMPL data if present
|
| 439 |
+
if (data.num_smpl_joints > 0 && old_motion->GetNumSmplJoints() > 0) {
|
| 440 |
+
int smpl_joints_to_copy = std::min(data.num_smpl_joints, old_motion->GetNumSmplJoints());
|
| 441 |
+
for (int i = 0; i < copy_count; ++i) {
|
| 442 |
+
for (int joint = 0; joint < smpl_joints_to_copy; ++joint) {
|
| 443 |
+
for (int xyz = 0; xyz < 3; ++xyz) {
|
| 444 |
+
new_motion->SmplJoints(copy_dst_idx + i)[joint][xyz] =
|
| 445 |
+
old_motion->SmplJoints(copy_src_idx + i)[joint][xyz];
|
| 446 |
+
}
|
| 447 |
+
}
|
| 448 |
+
}
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
if (data.num_smpl_poses > 0 && old_motion->GetNumSmplPoses() > 0) {
|
| 452 |
+
int smpl_poses_to_copy = std::min(data.num_smpl_poses, old_motion->GetNumSmplPoses());
|
| 453 |
+
for (int i = 0; i < copy_count; ++i) {
|
| 454 |
+
for (int p = 0; p < smpl_poses_to_copy; ++p) {
|
| 455 |
+
for (int xyz = 0; xyz < 3; ++xyz) {
|
| 456 |
+
new_motion->SmplPoses(copy_dst_idx + i)[p][xyz] =
|
| 457 |
+
old_motion->SmplPoses(copy_src_idx + i)[p][xyz];
|
| 458 |
+
}
|
| 459 |
+
}
|
| 460 |
+
}
|
| 461 |
+
}
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
// Copy incoming data to motion sequence
|
| 465 |
+
void CopyIncomingDataToMotion(
|
| 466 |
+
const IncomingData& data,
|
| 467 |
+
std::shared_ptr<MotionSequence> motion,
|
| 468 |
+
int dst_frame_offset
|
| 469 |
+
) {
|
| 470 |
+
// Copy joint data if present
|
| 471 |
+
if (!data.joint_pos.empty() && !data.joint_vel.empty()) {
|
| 472 |
+
for (int frame = 0; frame < data.num_frames; ++frame) {
|
| 473 |
+
for (int joint = 0; joint < data.num_joints; ++joint) {
|
| 474 |
+
motion->JointPositions(dst_frame_offset + frame)[joint] = data.joint_pos[frame][joint];
|
| 475 |
+
motion->JointVelocities(dst_frame_offset + frame)[joint] = data.joint_vel[frame][joint];
|
| 476 |
+
}
|
| 477 |
+
}
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
// Copy body quaternions (always present)
|
| 481 |
+
for (int frame = 0; frame < data.num_frames; ++frame) {
|
| 482 |
+
for (int body = 0; body < data.num_quat_bodies; ++body) {
|
| 483 |
+
for (int q = 0; q < 4; ++q) {
|
| 484 |
+
motion->BodyQuaternions(dst_frame_offset + frame)[body][q] =
|
| 485 |
+
data.body_quat[frame][body][q];
|
| 486 |
+
}
|
| 487 |
+
}
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
// Copy SMPL joints if present
|
| 491 |
+
if (!data.smpl_joints.empty()) {
|
| 492 |
+
for (int frame = 0; frame < data.num_frames; ++frame) {
|
| 493 |
+
for (int joint = 0; joint < data.num_smpl_joints; ++joint) {
|
| 494 |
+
for (int xyz = 0; xyz < 3; ++xyz) {
|
| 495 |
+
motion->SmplJoints(dst_frame_offset + frame)[joint][xyz] =
|
| 496 |
+
data.smpl_joints[frame][joint][xyz];
|
| 497 |
+
}
|
| 498 |
+
}
|
| 499 |
+
}
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
// Copy SMPL poses if present
|
| 503 |
+
if (!data.smpl_pose.empty()) {
|
| 504 |
+
for (int frame = 0; frame < data.num_frames; ++frame) {
|
| 505 |
+
for (int pose = 0; pose < data.num_smpl_poses; ++pose) {
|
| 506 |
+
for (int xyz = 0; xyz < 3; ++xyz) {
|
| 507 |
+
motion->SmplPoses(dst_frame_offset + frame)[pose][xyz] =
|
| 508 |
+
data.smpl_pose[frame][pose][xyz];
|
| 509 |
+
}
|
| 510 |
+
}
|
| 511 |
+
}
|
| 512 |
+
}
|
| 513 |
+
}
|
| 514 |
+
};
|
| 515 |
+
|
| 516 |
+
#endif // STREAMED_MOTION_MERGER_HPP
|
| 517 |
+
|
GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/zmq_endpoint_interface.hpp
ADDED
|
@@ -0,0 +1,1865 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file zmq_endpoint_interface.hpp
|
| 3 |
+
* @brief ZMQ-based input interface for receiving streamed pose / motion data.
|
| 4 |
+
*
|
| 5 |
+
* ZMQEndpointInterface combines SimpleKeyboard-style local controls with
|
| 6 |
+
* network-streamed motion data received via the ZMQ packed-message protocol.
|
| 7 |
+
* Pressing **Enter** toggles between pre-loaded reference motions and live
|
| 8 |
+
* ZMQ streaming.
|
| 9 |
+
*
|
| 10 |
+
* ## Keyboard Controls (when this interface is active)
|
| 11 |
+
*
|
| 12 |
+
* Key | Action
|
| 13 |
+
* -------|-------
|
| 14 |
+
* Enter | Toggle ZMQ streaming on/off
|
| 15 |
+
* P/p | Previous motion (non-streaming mode)
|
| 16 |
+
* N/n | Next motion
|
| 17 |
+
* T/t | Play / resume
|
| 18 |
+
* R/r | Restart (frame 0, paused)
|
| 19 |
+
* ] | Start control
|
| 20 |
+
* O/o | Emergency stop
|
| 21 |
+
* Q/q | Delta heading left
|
| 22 |
+
* E/e | Delta heading right
|
| 23 |
+
* I/i | Reinitialise heading
|
| 24 |
+
*
|
| 25 |
+
* ## Protocol Versions
|
| 26 |
+
*
|
| 27 |
+
* All versions carry `body_quat` and `frame_index` as required fields.
|
| 28 |
+
* Additionally:
|
| 29 |
+
*
|
| 30 |
+
* Version | Required | Optional
|
| 31 |
+
* --------|----------------------------------|---------------------------
|
| 32 |
+
* 1 | joint_pos, joint_vel | smpl_joints, smpl_pose
|
| 33 |
+
* 2 | smpl_joints, smpl_pose | joint_pos, joint_vel
|
| 34 |
+
* 3 | joint_pos, joint_vel, smpl_joints, smpl_pose | —
|
| 35 |
+
*
|
| 36 |
+
* ## Optional Fields (all versions)
|
| 37 |
+
*
|
| 38 |
+
* - `left_hand_joints`, `right_hand_joints` – 7-DOF Dex3 joint values.
|
| 39 |
+
* - `vr_position` (9 doubles) – enables VR 3-point tracking mode.
|
| 40 |
+
* - `vr_orientation` (12 doubles) – defaults used if absent.
|
| 41 |
+
* - `vr_compliance` (3 doubles) – **IGNORED** (compliance is keyboard-controlled).
|
| 42 |
+
* - `catch_up` (bool, default true) – controls gap tolerance for motion sync.
|
| 43 |
+
* - `heading_increment` (scalar) – incremental heading adjustment per message.
|
| 44 |
+
*
|
| 45 |
+
* ## Streaming Architecture
|
| 46 |
+
*
|
| 47 |
+
* 1. A background ZMQPackedMessageSubscriber thread receives messages and
|
| 48 |
+
* copies them into `buffered_header_` / `buffered_buffers_` under `data_mutex_`.
|
| 49 |
+
* 2. update() reads keyboard input and resets per-frame flags.
|
| 50 |
+
* 3. handle_input() checks `has_new_data_`, decodes the buffered message via
|
| 51 |
+
* DecodeIntoMotionSequence() (which delegates to StreamedMotionMerger for
|
| 52 |
+
* sliding-window logic), and swaps the current_motion pointer.
|
| 53 |
+
*/
|
| 54 |
+
|
| 55 |
+
#ifndef ZMQ_ENDPOINT_INTERFACE_HPP
|
| 56 |
+
#define ZMQ_ENDPOINT_INTERFACE_HPP
|
| 57 |
+
|
| 58 |
+
#include <termios.h>
|
| 59 |
+
#include <fcntl.h>
|
| 60 |
+
#include <unistd.h>
|
| 61 |
+
#include <iostream>
|
| 62 |
+
#include <iomanip>
|
| 63 |
+
#include <memory>
|
| 64 |
+
#include <mutex>
|
| 65 |
+
#include <string>
|
| 66 |
+
#include <algorithm>
|
| 67 |
+
#include <cstdlib>
|
| 68 |
+
#include <limits>
|
| 69 |
+
|
| 70 |
+
#include "input_interface.hpp"
|
| 71 |
+
#include "zmq_packed_message_subscriber.hpp"
|
| 72 |
+
#include "streamed_motion_merger.hpp"
|
| 73 |
+
|
| 74 |
+
/**
|
| 75 |
+
* @class ZMQEndpointInterface
|
| 76 |
+
* @brief InputInterface that streams pose / motion data over ZMQ and merges
|
| 77 |
+
* it into a MotionSequence for real-time playback.
|
| 78 |
+
*
|
| 79 |
+
* Can operate standalone (keyboard + network) or as a delegate inside
|
| 80 |
+
* InterfaceManager / GamepadManager / ZMQManager.
|
| 81 |
+
*
|
| 82 |
+
* Protocol Version 4 additionally supports token-only streaming:
|
| 83 |
+
* REQUIRED: token_state (motion token array)
|
| 84 |
+
* OPTIONAL: frame_index, left_hand_joints, right_hand_joints, body_quat_w
|
| 85 |
+
* STORES: token_state → external_token_state_ (for policy input)
|
| 86 |
+
* hand joints → left_hand_joint_/right_hand_joint_ (for robot control)
|
| 87 |
+
*/
|
| 88 |
+
class ZMQEndpointInterface : public InputInterface {
|
| 89 |
+
public:
|
| 90 |
+
/// Compile-time toggle for debug log output.
|
| 91 |
+
static constexpr bool DEBUG_LOGGING = true;
|
| 92 |
+
|
| 93 |
+
// ------------------------------------------------------------------
|
| 94 |
+
// Per-frame action flags (reset at the start of every update() call)
|
| 95 |
+
// ------------------------------------------------------------------
|
| 96 |
+
bool motion_prev = false; ///< Previous pre-loaded motion.
|
| 97 |
+
bool motion_next = false; ///< Next pre-loaded motion.
|
| 98 |
+
bool play_motion = false; ///< Play / resume.
|
| 99 |
+
bool motion_restart = false; ///< Restart (frame 0, paused).
|
| 100 |
+
bool start_control = false; ///< Start control system.
|
| 101 |
+
bool stop_control = false; ///< Emergency stop.
|
| 102 |
+
bool delta_left = false; ///< Heading nudge left.
|
| 103 |
+
bool delta_right = false; ///< Heading nudge right.
|
| 104 |
+
bool reinitialize = false; ///< Recapture IMU heading.
|
| 105 |
+
bool toggle_zmq_mode = false; ///< Toggle ZMQ streaming on/off (Enter key).
|
| 106 |
+
bool report_temperature = false; ///< Report motor temperatures (F key).
|
| 107 |
+
|
| 108 |
+
/// When true, handle_input() reads from the ZMQ stream instead of
|
| 109 |
+
/// pre-loaded reference motions.
|
| 110 |
+
bool use_zmq_stream = false;
|
| 111 |
+
|
| 112 |
+
/// Reusable sliding-window merger that handles frame alignment, gap
|
| 113 |
+
/// detection, and catch-up logic for streamed motion data.
|
| 114 |
+
StreamedMotionMerger motion_merger_;
|
| 115 |
+
|
| 116 |
+
/// Protocol version established by the first received ZMQ message.
|
| 117 |
+
/// −1 = not yet established. Changing mid-session is an error.
|
| 118 |
+
int active_protocol_version_ = -1;
|
| 119 |
+
|
| 120 |
+
/// Shared pointer to the latest merged motion sequence from ZMQ data.
|
| 121 |
+
std::shared_ptr<MotionSequence> streamed_motion_;
|
| 122 |
+
/// Global frame index corresponding to streamed_motion_[0].
|
| 123 |
+
int stream_window_start_ = 0;
|
| 124 |
+
|
| 125 |
+
static constexpr std::string_view LOCALHOST = "localhost";
|
| 126 |
+
|
| 127 |
+
ZMQEndpointInterface(
|
| 128 |
+
const std::string& host = std::string(LOCALHOST),
|
| 129 |
+
int port = 5556,
|
| 130 |
+
const std::string& topic = "pose",
|
| 131 |
+
bool use_conflate = false,
|
| 132 |
+
bool verbose = false
|
| 133 |
+
) : InputInterface(), host_(host), port_(port), topic_(topic), verbose_(verbose), is_localhost_(host == LOCALHOST) {
|
| 134 |
+
type_ = InputType::NETWORK;
|
| 135 |
+
|
| 136 |
+
// Set terminal to non-blocking mode (same as SimpleKeyboard)
|
| 137 |
+
tcgetattr(STDIN_FILENO, &old_termios_);
|
| 138 |
+
struct termios new_termios = old_termios_;
|
| 139 |
+
new_termios.c_lflag &= ~(ICANON | ECHO);
|
| 140 |
+
tcsetattr(STDIN_FILENO, TCSANOW, &new_termios);
|
| 141 |
+
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
|
| 142 |
+
|
| 143 |
+
// Create ZMQ subscriber
|
| 144 |
+
subscriber_ = std::make_unique<ZMQPackedMessageSubscriber>(
|
| 145 |
+
host, port, topic,
|
| 146 |
+
/*timeout_ms=*/100,
|
| 147 |
+
verbose,
|
| 148 |
+
use_conflate,
|
| 149 |
+
/*rcv_hwm=*/ use_conflate ? 1 : 3
|
| 150 |
+
);
|
| 151 |
+
|
| 152 |
+
// Setup callback to receive and buffer pose data
|
| 153 |
+
subscriber_->SetOnDecodedMessage(
|
| 154 |
+
[this](const std::string& topic,
|
| 155 |
+
const ZMQPackedMessageSubscriber::DecodedHeader& hdr,
|
| 156 |
+
const std::vector<ZMQPackedMessageSubscriber::BufferView>& bufs) {
|
| 157 |
+
this->OnPoseDataReceived(topic, hdr, bufs);
|
| 158 |
+
}
|
| 159 |
+
);
|
| 160 |
+
|
| 161 |
+
// Start background receiving thread
|
| 162 |
+
subscriber_->Start();
|
| 163 |
+
|
| 164 |
+
// Initialize streamed motion buffer (reserve large capacity for streaming)
|
| 165 |
+
ResetStreamedMotion();
|
| 166 |
+
|
| 167 |
+
std::cout << "[ZMQEndpointInterface] Connected to " << host << ":" << port
|
| 168 |
+
<< " topic='" << topic << "'" << std::endl;
|
| 169 |
+
std::cout << "[ZMQEndpointInterface] Press ENTER to toggle between loaded motions and ZMQ stream" << std::endl;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
~ZMQEndpointInterface() {
|
| 173 |
+
if (subscriber_) {
|
| 174 |
+
subscriber_->Stop();
|
| 175 |
+
}
|
| 176 |
+
// Restore terminal
|
| 177 |
+
tcsetattr(STDIN_FILENO, TCSANOW, &old_termios_);
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
// Flag to trigger safety reset in handle_input
|
| 181 |
+
bool trigger_safety_reset = false;
|
| 182 |
+
|
| 183 |
+
// Update is called each frame - read keyboard and check for network data
|
| 184 |
+
void update() override {
|
| 185 |
+
// Check for safety reset trigger from manager
|
| 186 |
+
if (CheckAndClearSafetyReset()) {
|
| 187 |
+
use_zmq_stream = false;
|
| 188 |
+
trigger_safety_reset = true;
|
| 189 |
+
std::cout << "[ZMQEndpointInterface] Safety reset triggered: will disable ZMQ streaming and return to reference motion" << std::endl;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
// Reset input flags each frame
|
| 193 |
+
start_control = false;
|
| 194 |
+
stop_control = false;
|
| 195 |
+
motion_prev = false;
|
| 196 |
+
motion_next = false;
|
| 197 |
+
play_motion = false;
|
| 198 |
+
motion_restart = false;
|
| 199 |
+
delta_left = false;
|
| 200 |
+
delta_right = false;
|
| 201 |
+
reinitialize = false;
|
| 202 |
+
toggle_zmq_mode = false;
|
| 203 |
+
report_temperature = false;
|
| 204 |
+
|
| 205 |
+
// Read keyboard input (same as SimpleKeyboard, but without planner keys)
|
| 206 |
+
// Using shared buffered reading
|
| 207 |
+
char ch;
|
| 208 |
+
while (ReadStdinChar(ch)) {
|
| 209 |
+
switch (ch) {
|
| 210 |
+
case 'p':
|
| 211 |
+
case 'P': motion_prev = true; break;
|
| 212 |
+
case 'n':
|
| 213 |
+
case 'N': motion_next = true; break;
|
| 214 |
+
case 't':
|
| 215 |
+
case 'T': play_motion = true; break;
|
| 216 |
+
case 'r':
|
| 217 |
+
case 'R': motion_restart = true; break;
|
| 218 |
+
case ']': start_control = true; break;
|
| 219 |
+
case 'o':
|
| 220 |
+
case 'O': stop_control = true; break;
|
| 221 |
+
case 'f':
|
| 222 |
+
case 'F': report_temperature = true; break;
|
| 223 |
+
case 'q':
|
| 224 |
+
case 'Q': delta_left = true; break;
|
| 225 |
+
case 'e':
|
| 226 |
+
case 'E': delta_right = true; break;
|
| 227 |
+
case 'i':
|
| 228 |
+
case 'I': reinitialize = true; break;
|
| 229 |
+
case '\n': toggle_zmq_mode = true; break; // Toggle ZMQ streaming
|
| 230 |
+
}
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
/// Disable ZMQ streaming, reset to reference motion, and clear external token state.
|
| 236 |
+
/// Called when an unrecoverable protocol error is detected during ZMQ processing.
|
| 237 |
+
void DisableZmqAndReset(
|
| 238 |
+
MotionDataReader& motion_reader,
|
| 239 |
+
std::shared_ptr<const MotionSequence>& current_motion,
|
| 240 |
+
int& current_frame,
|
| 241 |
+
OperatorState& operator_state,
|
| 242 |
+
bool& reinitialize_heading,
|
| 243 |
+
std::mutex& current_motion_mutex,
|
| 244 |
+
const std::string& reason)
|
| 245 |
+
{
|
| 246 |
+
std::cerr << "✗✗✗ ERROR: " << reason << std::endl;
|
| 247 |
+
std::cerr << "✗✗✗ This is not allowed. Exiting ZMQ streaming mode for safety." << std::endl;
|
| 248 |
+
|
| 249 |
+
use_zmq_stream = false;
|
| 250 |
+
|
| 251 |
+
{
|
| 252 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 253 |
+
has_external_token_state_ = false;
|
| 254 |
+
external_token_state_.SetData({});
|
| 255 |
+
operator_state.play = false;
|
| 256 |
+
reinitialize_heading = true;
|
| 257 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_);
|
| 258 |
+
current_frame = 0;
|
| 259 |
+
if (current_motion->GetEncodeMode() >= 0) {
|
| 260 |
+
current_motion->SetEncodeMode(0);
|
| 261 |
+
}
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
std::cout << "=====================================" << std::endl;
|
| 265 |
+
std::cout << "ZMQ STREAMING MODE: FORCE DISABLED" << std::endl;
|
| 266 |
+
std::cout << "=====================================" << std::endl;
|
| 267 |
+
std::cout << "Returned to reference motion. Re-enable ZMQ mode to continue." << std::endl;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
// Handle input and update motion data
|
| 271 |
+
void handle_input(MotionDataReader& motion_reader,
|
| 272 |
+
std::shared_ptr<const MotionSequence>& current_motion,
|
| 273 |
+
int& current_frame,
|
| 274 |
+
OperatorState& operator_state,
|
| 275 |
+
bool& reinitialize_heading,
|
| 276 |
+
DataBuffer<HeadingState>& heading_state_buffer,
|
| 277 |
+
bool has_planner,
|
| 278 |
+
PlannerState& planner_state,
|
| 279 |
+
DataBuffer<MovementState>& movement_state_buffer,
|
| 280 |
+
std::mutex& current_motion_mutex,
|
| 281 |
+
bool& report_temperature) override {
|
| 282 |
+
|
| 283 |
+
// Handle safety reset from interface manager
|
| 284 |
+
if (trigger_safety_reset) {
|
| 285 |
+
trigger_safety_reset = false;
|
| 286 |
+
|
| 287 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE), {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 288 |
+
{
|
| 289 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 290 |
+
// Encoder mode will be read from the motion's encode_mode
|
| 291 |
+
has_external_token_state_ = false;
|
| 292 |
+
external_token_state_.SetData({});
|
| 293 |
+
operator_state.play = false;
|
| 294 |
+
reinitialize_heading = true;
|
| 295 |
+
auto temp_motion = std::make_shared<MotionSequence>(*current_motion);
|
| 296 |
+
temp_motion->name = "temporary_motion";
|
| 297 |
+
current_motion = temp_motion;
|
| 298 |
+
if (has_planner && planner_state.enabled) {
|
| 299 |
+
planner_state.enabled = false;
|
| 300 |
+
planner_state.initialized = false;
|
| 301 |
+
std::cout << "Safety reset: Planner disabled" << std::endl;
|
| 302 |
+
}
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
// Disable ZMQ streaming and return to reference motion
|
| 306 |
+
use_zmq_stream = false;
|
| 307 |
+
ResetStreamedMotion(); // Reset motion merger and protocol version
|
| 308 |
+
|
| 309 |
+
std::cout << "Safety reset: ZMQ streaming disabled, returned to reference motion at frame 0" << std::endl;
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
// Handle ZMQ mode toggle
|
| 313 |
+
if (toggle_zmq_mode) {
|
| 314 |
+
use_zmq_stream = !use_zmq_stream;
|
| 315 |
+
if (use_zmq_stream) {
|
| 316 |
+
std::cout << "=====================================" << std::endl;
|
| 317 |
+
std::cout << "ZMQ STREAMING MODE: ENABLED" << std::endl;
|
| 318 |
+
std::cout << "=====================================" << std::endl;
|
| 319 |
+
std::cout << "Using pose data from " << host_ << ":" << port_ << std::endl;
|
| 320 |
+
std::cout << "Press ENTER again to return to loaded motions" << std::endl;
|
| 321 |
+
// reset the heading state
|
| 322 |
+
{
|
| 323 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 324 |
+
operator_state.play = false;
|
| 325 |
+
reinitialize_heading = true; // reset the heading state
|
| 326 |
+
}
|
| 327 |
+
// reset streaming buffers when enabling to avoid mixing with stale data
|
| 328 |
+
ResetStreamedMotion(); // This also resets protocol version in the merger
|
| 329 |
+
has_new_data_ = false;
|
| 330 |
+
} else {
|
| 331 |
+
std::cout << "=====================================" << std::endl;
|
| 332 |
+
std::cout << "ZMQ STREAMING MODE: DISABLED" << std::endl;
|
| 333 |
+
std::cout << "=====================================" << std::endl;
|
| 334 |
+
std::cout << "Using pre-loaded motion data" << std::endl;
|
| 335 |
+
|
| 336 |
+
// Encoder mode will be read from the motion's encode_mode
|
| 337 |
+
|
| 338 |
+
// reset the current motion and frame
|
| 339 |
+
{
|
| 340 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 341 |
+
has_external_token_state_ = false;
|
| 342 |
+
external_token_state_.SetData({});
|
| 343 |
+
operator_state.play = false;
|
| 344 |
+
reinitialize_heading = true;
|
| 345 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_); // current motion is the pre-loaded motion
|
| 346 |
+
current_frame = 0; // current frame is 0
|
| 347 |
+
if (current_motion->GetEncodeMode() >= 0) {
|
| 348 |
+
current_motion->SetEncodeMode(0);
|
| 349 |
+
}
|
| 350 |
+
}
|
| 351 |
+
// reset the streamed motion (also resets protocol version)
|
| 352 |
+
ResetStreamedMotion();
|
| 353 |
+
has_new_data_ = false;
|
| 354 |
+
}
|
| 355 |
+
}
|
| 356 |
+
if (stop_control) { operator_state.stop = true; }
|
| 357 |
+
if (this->report_temperature) { report_temperature = true; }
|
| 358 |
+
if (start_control) { operator_state.start = true; }
|
| 359 |
+
|
| 360 |
+
// Handle delta heading controls
|
| 361 |
+
if (delta_left) {
|
| 362 |
+
auto current_heading_state = heading_state_buffer.GetDataWithTime().data;
|
| 363 |
+
HeadingState current_state = current_heading_state ? *current_heading_state : HeadingState();
|
| 364 |
+
double new_delta = current_state.delta_heading + 0.1;
|
| 365 |
+
heading_state_buffer.SetData(HeadingState(current_state.init_base_quat, new_delta));
|
| 366 |
+
std::cout << "Delta heading left: " << new_delta << " rad" << std::endl;
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
if (delta_right) {
|
| 370 |
+
auto current_heading_state = heading_state_buffer.GetDataWithTime().data;
|
| 371 |
+
HeadingState current_state = current_heading_state ? *current_heading_state : HeadingState();
|
| 372 |
+
double new_delta = current_state.delta_heading - 0.1;
|
| 373 |
+
heading_state_buffer.SetData(HeadingState(current_state.init_base_quat, new_delta));
|
| 374 |
+
std::cout << "Delta heading right: " << new_delta << " rad" << std::endl;
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
// If ZMQ mode is active, use streamed motion data
|
| 378 |
+
if (use_zmq_stream) {
|
| 379 |
+
// Check and decode new network data if available
|
| 380 |
+
std::shared_ptr<MotionSequence> new_motion;
|
| 381 |
+
int frame_offset_adjustment = 0;
|
| 382 |
+
bool did_catchup = false;
|
| 383 |
+
int protocol_version_for_mode_update = -1;
|
| 384 |
+
{
|
| 385 |
+
std::lock_guard<std::mutex> lock(data_mutex_);
|
| 386 |
+
if (has_new_data_) {
|
| 387 |
+
has_new_data_ = false; // consumed
|
| 388 |
+
if constexpr (DEBUG_LOGGING) {
|
| 389 |
+
std::cout << "[ZMQEndpointInterface] *** Starting ZMQ processing ***" << std::endl;
|
| 390 |
+
}
|
| 391 |
+
// Decode into a new MotionSequence with current playback position
|
| 392 |
+
auto result = DecodeIntoMotionSequence(current_frame, streamed_motion_, stream_window_start_, heading_state_buffer);
|
| 393 |
+
|
| 394 |
+
// Handle Protocol v4 (token-only) - no motion, just tokens
|
| 395 |
+
if (result.protocol_version == 4) {
|
| 396 |
+
if (result.motion) {
|
| 397 |
+
DisableZmqAndReset(motion_reader, current_motion, current_frame,
|
| 398 |
+
operator_state, reinitialize_heading, current_motion_mutex,
|
| 399 |
+
"Protocol version 4 with motion data is impossible!");
|
| 400 |
+
return;
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
if (result.token_data.empty()) {
|
| 404 |
+
DisableZmqAndReset(motion_reader, current_motion, current_frame,
|
| 405 |
+
operator_state, reinitialize_heading, current_motion_mutex,
|
| 406 |
+
"Protocol version 4 with empty token data!");
|
| 407 |
+
return;
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
// Keep robot active
|
| 411 |
+
{
|
| 412 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 413 |
+
external_token_state_.SetData(result.token_data);
|
| 414 |
+
has_external_token_state_ = true;
|
| 415 |
+
operator_state.play = true; // this should be redundant because the robot never read reference motion
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
// Skip motion handling and keyboard controls
|
| 419 |
+
return;
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
// Check if protocol version change was detected (error case)
|
| 423 |
+
if (!result.motion && result.protocol_version != 0) {
|
| 424 |
+
DisableZmqAndReset(motion_reader, current_motion, current_frame,
|
| 425 |
+
operator_state, reinitialize_heading, current_motion_mutex,
|
| 426 |
+
"Protocol version changed from " + std::to_string(active_protocol_version_)
|
| 427 |
+
+ " to " + std::to_string(result.protocol_version)
|
| 428 |
+
+ " during active ZMQ session!");
|
| 429 |
+
return;
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
if (result.motion) {
|
| 433 |
+
// Determine encode_mode based on protocol version (only once when first established)
|
| 434 |
+
// Version 1: Use encoder mode 0 (joint-based)
|
| 435 |
+
// Version 2/3: Use encoder mode 2 (SMPL-based)
|
| 436 |
+
if constexpr (DEBUG_LOGGING) {
|
| 437 |
+
std::cout << "[ZMQEndpointInterface] active_protocol_version_=" << active_protocol_version_ << std::endl;
|
| 438 |
+
std::cout << "[ZMQEndpointInterface] result.motion->GetEncodeMode()=" << result.motion->GetEncodeMode() << std::endl;
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
new_motion = result.motion;
|
| 443 |
+
std::cout << "[ZMQEndpointInterface] motion name: " << new_motion->name << std::endl;
|
| 444 |
+
stream_window_start_ = result.window_start;
|
| 445 |
+
frame_offset_adjustment = result.frame_offset_adjustment;
|
| 446 |
+
did_catchup = result.did_catchup_reset;
|
| 447 |
+
|
| 448 |
+
if constexpr (DEBUG_LOGGING) {
|
| 449 |
+
int window_end_msg_idx = stream_window_start_ + result.frame_step * (new_motion->timesteps - 1);
|
| 450 |
+
std::cout << "[ZMQEndpointInterface] Merged streamed data: "
|
| 451 |
+
<< new_motion->timesteps << " current-rate frames, "
|
| 452 |
+
<< "window [" << stream_window_start_ << ".." << window_end_msg_idx << "] (message-index)"
|
| 453 |
+
<< ", frame_step=" << result.frame_step
|
| 454 |
+
<< ", frame_offset_adjustment=" << frame_offset_adjustment
|
| 455 |
+
<< ", did_catchup=" << did_catchup << std::endl;
|
| 456 |
+
}
|
| 457 |
+
}
|
| 458 |
+
if constexpr (DEBUG_LOGGING) {
|
| 459 |
+
std::cout << "[ZMQEndpointInterface] *** End of ZMQ decoding processing ***" << std::endl;
|
| 460 |
+
}
|
| 461 |
+
}
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
// update streamed_motion_ and current_frame if we have new data
|
| 465 |
+
if (new_motion) {
|
| 466 |
+
streamed_motion_ = new_motion;
|
| 467 |
+
|
| 468 |
+
// Handle catch-up reset: when window was reset due to large gap, start from beginning
|
| 469 |
+
if (did_catchup) {
|
| 470 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 471 |
+
current_frame = 0;
|
| 472 |
+
current_motion = streamed_motion_; // Assign shared_ptr directly for thread safety
|
| 473 |
+
operator_state.play = true; // Auto-play when entering ZMQ mode
|
| 474 |
+
reinitialize_heading = true;
|
| 475 |
+
|
| 476 |
+
if constexpr (DEBUG_LOGGING) {
|
| 477 |
+
std::cout << "[ZMQEndpointInterface] Catch-up: Reset to frame 0 at global frame "
|
| 478 |
+
<< stream_window_start_ << std::endl;
|
| 479 |
+
}
|
| 480 |
+
} else {
|
| 481 |
+
// Normal case: Adjust current_frame to maintain global playback position after window shift
|
| 482 |
+
// current_frame represents "the next frame to be read" (not yet consumed)
|
| 483 |
+
int adjusted_frame = current_frame - frame_offset_adjustment;
|
| 484 |
+
|
| 485 |
+
// Validate the adjustment doesn't cause discontinuities due to clamping
|
| 486 |
+
if (adjusted_frame < 0) {
|
| 487 |
+
if constexpr (DEBUG_LOGGING) {
|
| 488 |
+
std::cout << "[ZMQEndpointInterface] WARNING: Window shifted past playback position. "
|
| 489 |
+
<< "Skipping from global frame " << (stream_window_start_ - frame_offset_adjustment + current_frame)
|
| 490 |
+
<< " to " << stream_window_start_ << std::endl;
|
| 491 |
+
}
|
| 492 |
+
adjusted_frame = 0; // Start from beginning of new window
|
| 493 |
+
} else if (adjusted_frame >= streamed_motion_->timesteps) {
|
| 494 |
+
if constexpr (DEBUG_LOGGING) {
|
| 495 |
+
std::cout << "[ZMQEndpointInterface] WARNING: Playback position beyond new window. "
|
| 496 |
+
<< "Clamping to last frame." << std::endl;
|
| 497 |
+
}
|
| 498 |
+
// Safety: ensure we don't set negative frame index if timesteps is 0
|
| 499 |
+
adjusted_frame = (streamed_motion_->timesteps > 0) ? (streamed_motion_->timesteps - 1) : 0;
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 503 |
+
current_frame = adjusted_frame;
|
| 504 |
+
current_motion = streamed_motion_; // Assign shared_ptr directly for thread safety
|
| 505 |
+
operator_state.play = true; // Auto-play when entering ZMQ mode
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
}
|
| 509 |
+
return; // Skip keyboard motion controls when in ZMQ mode
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
// Standard keyboard controls (same as SimpleKeyboard, without planner)
|
| 513 |
+
if (motion_prev && !motion_reader.motions.empty()) {
|
| 514 |
+
motion_reader.current_motion_index_ =
|
| 515 |
+
(motion_reader.current_motion_index_ - 1 + motion_reader.motions.size()) % motion_reader.motions.size();
|
| 516 |
+
std::string motion_name;
|
| 517 |
+
{
|
| 518 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 519 |
+
operator_state.play = false;
|
| 520 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_);
|
| 521 |
+
current_frame = 0;
|
| 522 |
+
motion_name = current_motion->name;
|
| 523 |
+
reinitialize_heading = true;
|
| 524 |
+
}
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
if (motion_next && !motion_reader.motions.empty()) {
|
| 528 |
+
motion_reader.current_motion_index_ = (motion_reader.current_motion_index_ + 1) % motion_reader.motions.size();
|
| 529 |
+
std::string motion_name;
|
| 530 |
+
{
|
| 531 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 532 |
+
operator_state.play = false;
|
| 533 |
+
current_motion = motion_reader.GetMotionShared(motion_reader.current_motion_index_);
|
| 534 |
+
current_frame = 0;
|
| 535 |
+
motion_name = current_motion->name;
|
| 536 |
+
reinitialize_heading = true;
|
| 537 |
+
}
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
if (play_motion) {
|
| 541 |
+
if (!operator_state.play) {
|
| 542 |
+
int frame_copy;
|
| 543 |
+
size_t timesteps_copy;
|
| 544 |
+
{
|
| 545 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 546 |
+
operator_state.play = true;
|
| 547 |
+
frame_copy = current_frame;
|
| 548 |
+
timesteps_copy = current_motion ? current_motion->timesteps : 0;
|
| 549 |
+
}
|
| 550 |
+
std::cout << "Playing motion " << motion_reader.current_motion_index_ << " from frame " << frame_copy << " to end ("
|
| 551 |
+
<< timesteps_copy << " total frames)" << std::endl;
|
| 552 |
+
}
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
if (motion_restart) {
|
| 556 |
+
{
|
| 557 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 558 |
+
operator_state.play = false;
|
| 559 |
+
current_frame = 0;
|
| 560 |
+
reinitialize_heading = true;
|
| 561 |
+
}
|
| 562 |
+
std::cout << "Reset motion " << motion_reader.current_motion_index_ << " to frame 0 (paused)" << std::endl;
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
// Handle reinitialize command
|
| 566 |
+
if (reinitialize) {
|
| 567 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 568 |
+
reinitialize_heading = true;
|
| 569 |
+
std::cout << "Reinitialized base quaternion and reset delta heading to 0" << std::endl;
|
| 570 |
+
}
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
// Public method to trigger ZMQ mode toggle (for programmatic control from GamepadManager)
|
| 574 |
+
void TriggerZMQToggle() {
|
| 575 |
+
toggle_zmq_mode = true;
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
std::optional<std::chrono::steady_clock::time_point> GetLastUpdateTime() const override {
|
| 579 |
+
if (is_localhost_) {
|
| 580 |
+
return data_timestamp_;
|
| 581 |
+
}
|
| 582 |
+
return last_receive_time_;
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
private:
|
| 586 |
+
/// Reset the streamed motion buffer, merger state, and protocol version.
|
| 587 |
+
/// Called on construction, when toggling ZMQ mode, and on safety reset.
|
| 588 |
+
void ResetStreamedMotion() {
|
| 589 |
+
motion_merger_.Reset();
|
| 590 |
+
active_protocol_version_ = -1; // Reset protocol version tracking
|
| 591 |
+
// Update legacy fields for backward compatibility
|
| 592 |
+
streamed_motion_ = std::make_shared<MotionSequence>();
|
| 593 |
+
streamed_motion_->name = "streamed";
|
| 594 |
+
streamed_motion_->ReserveCapacity(15000, 29, 1, 1, 0, 0); // max 15k frames, 29 joints, 1 body, 1 quat
|
| 595 |
+
stream_window_start_ = 0;
|
| 596 |
+
data_timestamp_.reset();
|
| 597 |
+
last_receive_time_.reset();
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
/// Outcome of DecodeIntoMotionSequence().
|
| 601 |
+
struct DecodeResult {
|
| 602 |
+
std::shared_ptr<MotionSequence> motion; ///< Merged motion (nullptr on failure / version change).
|
| 603 |
+
int window_start = 0; ///< Global frame index of motion[0].
|
| 604 |
+
int frame_offset_adjustment = 0; ///< Subtract from current_frame for window shift.
|
| 605 |
+
bool did_catchup_reset = false; ///< True → caller should reset playback to frame 0.
|
| 606 |
+
int frame_step = 1; ///< Detected stride between frame indices.
|
| 607 |
+
int protocol_version = 0; ///< Protocol version from the message (1, 2, or 3).
|
| 608 |
+
std::vector<double> token_data; ///< Token data from the message.
|
| 609 |
+
};
|
| 610 |
+
|
| 611 |
+
/**
|
| 612 |
+
* @brief Decode buffered network data into a new MotionSequence.
|
| 613 |
+
*
|
| 614 |
+
* Called from handle_input() (with data_mutex_ held) whenever `has_new_data_`
|
| 615 |
+
* is true. This method:
|
| 616 |
+
* 1. Parses the buffered JSON header to determine field indices and dtypes.
|
| 617 |
+
* 2. Validates required fields for the detected protocol version.
|
| 618 |
+
* 3. Decodes binary buffers into typed C++ containers (joint_pos, body_quat, …).
|
| 619 |
+
* 4. Delegates to StreamedMotionMerger::MergeIncomingData() for sliding-window logic.
|
| 620 |
+
* 5. Sets encoder mode on the resulting motion based on protocol version.
|
| 621 |
+
* 6. Updates VR / hand-joint buffers if the corresponding optional fields are present.
|
| 622 |
+
*
|
| 623 |
+
* @param current_playback_frame Current playback cursor in the old motion.
|
| 624 |
+
* @param old_motion Previous streamed motion (for window overlap).
|
| 625 |
+
* @param old_window_start Global frame index of old_motion[0].
|
| 626 |
+
* @param heading_state_buffer Heading buffer (for heading_increment field).
|
| 627 |
+
* @return DecodeResult describing the merged motion and playback adjustments.
|
| 628 |
+
*/
|
| 629 |
+
DecodeResult DecodeIntoMotionSequence(int current_playback_frame,
|
| 630 |
+
std::shared_ptr<MotionSequence> old_motion,
|
| 631 |
+
int old_window_start,
|
| 632 |
+
DataBuffer<HeadingState>& heading_state_buffer) {
|
| 633 |
+
DecodeResult result;
|
| 634 |
+
if (buffered_buffers_.empty()) {
|
| 635 |
+
std::cerr << "[ZMQEndpointInterface] No buffered buffers" << std::endl;
|
| 636 |
+
return result;
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
// Track timing between decode calls
|
| 640 |
+
uint64_t decode_start_time = std::chrono::steady_clock::now().time_since_epoch().count() / 1000000; // milliseconds
|
| 641 |
+
|
| 642 |
+
// Check protocol version
|
| 643 |
+
int protocol_version = buffered_header_.version;
|
| 644 |
+
if constexpr (DEBUG_LOGGING) {
|
| 645 |
+
std::cout << "[ZMQEndpointInterface] Protocol version: " << protocol_version << std::endl;
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
// Find expected fields by name (including frame_index for alignment)
|
| 649 |
+
int joint_pos_idx = -1, joint_vel_idx = -1, body_quat_idx = -1, frame_index_idx = -1, smpl_joints_idx = -1, smpl_pose_idx = -1;
|
| 650 |
+
int left_hand_joints_idx = -1, right_hand_joints_idx = -1, catch_up_idx = -1;
|
| 651 |
+
int token_state_idx = -1; // Protocol v4: token-only streaming
|
| 652 |
+
int heading_increment_idx = -1;
|
| 653 |
+
int timestamp_monotonic_idx = -1;
|
| 654 |
+
// VR 3-point tracking fields (optional)
|
| 655 |
+
int vr_position_idx = -1, vr_orientation_idx = -1, vr_compliance_idx = -1;
|
| 656 |
+
|
| 657 |
+
for (size_t i = 0; i < buffered_header_.fields.size(); ++i) {
|
| 658 |
+
const auto& f = buffered_header_.fields[i];
|
| 659 |
+
if (f.name == "joint_pos") joint_pos_idx = static_cast<int>(i);
|
| 660 |
+
else if (f.name == "joint_vel") joint_vel_idx = static_cast<int>(i);
|
| 661 |
+
else if (f.name == "body_quat_w" || f.name == "body_quat") body_quat_idx = static_cast<int>(i);
|
| 662 |
+
else if (f.name == "frame_index" || f.name == "last_smpl_global_frames") frame_index_idx = static_cast<int>(i);
|
| 663 |
+
else if (f.name == "smpl_joints") smpl_joints_idx = static_cast<int>(i);
|
| 664 |
+
else if (f.name == "smpl_pose") smpl_pose_idx = static_cast<int>(i);
|
| 665 |
+
else if (f.name == "left_hand_joints") left_hand_joints_idx = static_cast<int>(i);
|
| 666 |
+
else if (f.name == "right_hand_joints") right_hand_joints_idx = static_cast<int>(i);
|
| 667 |
+
else if (f.name == "catch_up") catch_up_idx = static_cast<int>(i);
|
| 668 |
+
else if (f.name == "token_state") token_state_idx = static_cast<int>(i);
|
| 669 |
+
else if (f.name == "heading_increment") heading_increment_idx = static_cast<int>(i);
|
| 670 |
+
else if (f.name == "timestamp_monotonic") timestamp_monotonic_idx = static_cast<int>(i);
|
| 671 |
+
// VR 3-point tracking fields
|
| 672 |
+
else if (f.name == "vr_position") vr_position_idx = static_cast<int>(i);
|
| 673 |
+
else if (f.name == "vr_orientation") vr_orientation_idx = static_cast<int>(i);
|
| 674 |
+
else if (f.name == "vr_compliance") vr_compliance_idx = static_cast<int>(i);
|
| 675 |
+
}
|
| 676 |
+
|
| 677 |
+
// ===== PROTOCOL VERSION 4: Token-Only Streaming (check first, has different requirements) =====
|
| 678 |
+
if (protocol_version == 4) {
|
| 679 |
+
// Token-only mode - no motion data, just tokens for the policy
|
| 680 |
+
if (token_state_idx < 0) {
|
| 681 |
+
std::cerr << "[ZMQEndpointInterface] Version 4 missing required field 'token_state'" << std::endl;
|
| 682 |
+
return result;
|
| 683 |
+
}
|
| 684 |
+
|
| 685 |
+
// Check protocol version before decoding token_state
|
| 686 |
+
if (active_protocol_version_ == -1) {
|
| 687 |
+
// First message - establish protocol version
|
| 688 |
+
active_protocol_version_ = protocol_version;
|
| 689 |
+
if constexpr (DEBUG_LOGGING) {
|
| 690 |
+
std::cout << "[ZMQEndpointInterface] Protocol version " << active_protocol_version_ << " established" << std::endl;
|
| 691 |
+
}
|
| 692 |
+
} else if (active_protocol_version_ != protocol_version) {
|
| 693 |
+
// Protocol version changed - this is an error
|
| 694 |
+
std::cerr << "[ZMQEndpointInterface] ERROR: Protocol version changed from "
|
| 695 |
+
<< active_protocol_version_ << " to " << protocol_version << std::endl;
|
| 696 |
+
result.protocol_version = protocol_version; // Signal the change to caller
|
| 697 |
+
return result;
|
| 698 |
+
}
|
| 699 |
+
|
| 700 |
+
// Decode token_state field
|
| 701 |
+
const auto& token_field = buffered_header_.fields[static_cast<size_t>(token_state_idx)];
|
| 702 |
+
const auto& token_buf = buffered_buffers_[static_cast<size_t>(token_state_idx)];
|
| 703 |
+
|
| 704 |
+
// Calculate token dimension from shape
|
| 705 |
+
size_t token_dim = 1;
|
| 706 |
+
for (size_t d : token_field.shape) {
|
| 707 |
+
token_dim *= d;
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
std::vector<double> token_data(token_dim);
|
| 711 |
+
bool needs_swap = buffered_header_.NeedsByteSwap();
|
| 712 |
+
|
| 713 |
+
if (token_field.dtype == "f32") {
|
| 714 |
+
for (size_t i = 0; i < token_dim; ++i) {
|
| 715 |
+
float val;
|
| 716 |
+
std::memcpy(&val, token_buf.data() + i * sizeof(float), sizeof(float));
|
| 717 |
+
if (needs_swap) val = byte_swap(val);
|
| 718 |
+
token_data[i] = static_cast<double>(val);
|
| 719 |
+
}
|
| 720 |
+
} else if (token_field.dtype == "f64") {
|
| 721 |
+
for (size_t i = 0; i < token_dim; ++i) {
|
| 722 |
+
double val;
|
| 723 |
+
std::memcpy(&val, token_buf.data() + i * sizeof(double), sizeof(double));
|
| 724 |
+
if (needs_swap) val = byte_swap(val);
|
| 725 |
+
token_data[i] = val;
|
| 726 |
+
}
|
| 727 |
+
} else {
|
| 728 |
+
std::cerr << "[ZMQEndpointInterface] Version 4: unsupported dtype '" << token_field.dtype << "' for token_state" << std::endl;
|
| 729 |
+
return result;
|
| 730 |
+
}
|
| 731 |
+
|
| 732 |
+
// Log for debugging (show first token value and frame info if available)
|
| 733 |
+
std::string frame_info = "";
|
| 734 |
+
if (frame_index_idx >= 0) {
|
| 735 |
+
const auto& frame_idx_field = buffered_header_.fields[static_cast<size_t>(frame_index_idx)];
|
| 736 |
+
const auto& frame_idx_buf = buffered_buffers_[static_cast<size_t>(frame_index_idx)];
|
| 737 |
+
if (frame_idx_field.dtype == "i64" && frame_idx_buf.size() >= sizeof(int64_t)) {
|
| 738 |
+
int64_t frame_val;
|
| 739 |
+
std::memcpy(&frame_val, frame_idx_buf.data(), sizeof(int64_t));
|
| 740 |
+
if (needs_swap) frame_val = byte_swap(frame_val);
|
| 741 |
+
frame_info = ", frame_index: " + std::to_string(frame_val);
|
| 742 |
+
} else if (frame_idx_field.dtype == "i64" && frame_idx_buf.size() > sizeof(int64_t)) {
|
| 743 |
+
// Chunk mode: show range
|
| 744 |
+
int num_frames = frame_idx_buf.size() / sizeof(int64_t);
|
| 745 |
+
int64_t first_frame, last_frame;
|
| 746 |
+
std::memcpy(&first_frame, frame_idx_buf.data(), sizeof(int64_t));
|
| 747 |
+
std::memcpy(&last_frame, frame_idx_buf.data() + (num_frames - 1) * sizeof(int64_t), sizeof(int64_t));
|
| 748 |
+
if (needs_swap) {
|
| 749 |
+
first_frame = byte_swap(first_frame);
|
| 750 |
+
last_frame = byte_swap(last_frame);
|
| 751 |
+
}
|
| 752 |
+
frame_info = ", frames: " + std::to_string(first_frame) + " to " + std::to_string(last_frame)
|
| 753 |
+
+ " (chunk_size: " + std::to_string(num_frames) + ")";
|
| 754 |
+
}
|
| 755 |
+
}
|
| 756 |
+
std::cout << "[ZMQEndpointInterface] Protocol v4: Received " << token_dim
|
| 757 |
+
<< "D token (latent action), tokens[0]=" << token_data[0] << frame_info << std::endl;
|
| 758 |
+
|
| 759 |
+
// Store tokens in the external token state buffer (inherited from InputInterface)
|
| 760 |
+
result.token_data = std::move(token_data);
|
| 761 |
+
|
| 762 |
+
// Decode hand joint positions if present (7 DOF joint values) - same as protocol v2/v3
|
| 763 |
+
bool has_left_hand_joints = (left_hand_joints_idx >= 0);
|
| 764 |
+
bool has_right_hand_joints = (right_hand_joints_idx >= 0);
|
| 765 |
+
auto [has_left_hand_v4, left_hand_joint_values] = GetHandPose(true);
|
| 766 |
+
auto [has_right_hand_v4, right_hand_joint_values] = GetHandPose(false);
|
| 767 |
+
|
| 768 |
+
if (has_left_hand_joints) {
|
| 769 |
+
const auto& left_hand_field = buffered_header_.fields[left_hand_joints_idx];
|
| 770 |
+
const auto& left_hand_buf = buffered_buffers_[left_hand_joints_idx];
|
| 771 |
+
|
| 772 |
+
// Validate shape: expect [7] or [N, 7] (for chunks, use first frame)
|
| 773 |
+
int num_hand_joints = 0;
|
| 774 |
+
if (left_hand_field.shape.size() == 1 && left_hand_field.shape[0] == 7) {
|
| 775 |
+
num_hand_joints = 7;
|
| 776 |
+
} else if (left_hand_field.shape.size() == 2 && left_hand_field.shape[1] == 7) {
|
| 777 |
+
num_hand_joints = 7;
|
| 778 |
+
}
|
| 779 |
+
|
| 780 |
+
if (num_hand_joints == 7) {
|
| 781 |
+
// Decode 7 joint values (from first frame if chunked [N, 7])
|
| 782 |
+
if (left_hand_field.dtype == "f32") {
|
| 783 |
+
for (int j = 0; j < 7; ++j) {
|
| 784 |
+
float val;
|
| 785 |
+
std::memcpy(&val, left_hand_buf.data() + j * sizeof(float), sizeof(float));
|
| 786 |
+
if (needs_swap) val = byte_swap(val);
|
| 787 |
+
left_hand_joint_values[j] = static_cast<double>(val);
|
| 788 |
+
}
|
| 789 |
+
} else if (left_hand_field.dtype == "f64") {
|
| 790 |
+
for (int j = 0; j < 7; ++j) {
|
| 791 |
+
double val;
|
| 792 |
+
std::memcpy(&val, left_hand_buf.data() + j * sizeof(double), sizeof(double));
|
| 793 |
+
if (needs_swap) val = byte_swap(val);
|
| 794 |
+
left_hand_joint_values[j] = val;
|
| 795 |
+
}
|
| 796 |
+
}
|
| 797 |
+
} else {
|
| 798 |
+
std::cerr << "[ZMQEndpointInterface] Protocol v4: Invalid left_hand_joints shape" << std::endl;
|
| 799 |
+
has_left_hand_joints = false;
|
| 800 |
+
}
|
| 801 |
+
}
|
| 802 |
+
|
| 803 |
+
if (has_right_hand_joints) {
|
| 804 |
+
const auto& right_hand_field = buffered_header_.fields[right_hand_joints_idx];
|
| 805 |
+
const auto& right_hand_buf = buffered_buffers_[right_hand_joints_idx];
|
| 806 |
+
|
| 807 |
+
// Validate shape: expect [7] or [N, 7] (for chunks, use first frame)
|
| 808 |
+
int num_hand_joints = 0;
|
| 809 |
+
if (right_hand_field.shape.size() == 1 && right_hand_field.shape[0] == 7) {
|
| 810 |
+
num_hand_joints = 7;
|
| 811 |
+
} else if (right_hand_field.shape.size() == 2 && right_hand_field.shape[1] == 7) {
|
| 812 |
+
num_hand_joints = 7;
|
| 813 |
+
}
|
| 814 |
+
|
| 815 |
+
if (num_hand_joints == 7) {
|
| 816 |
+
// Decode 7 joint values (from first frame if chunked [N, 7])
|
| 817 |
+
if (right_hand_field.dtype == "f32") {
|
| 818 |
+
for (int j = 0; j < 7; ++j) {
|
| 819 |
+
float val;
|
| 820 |
+
std::memcpy(&val, right_hand_buf.data() + j * sizeof(float), sizeof(float));
|
| 821 |
+
if (needs_swap) val = byte_swap(val);
|
| 822 |
+
right_hand_joint_values[j] = static_cast<double>(val);
|
| 823 |
+
}
|
| 824 |
+
} else if (right_hand_field.dtype == "f64") {
|
| 825 |
+
for (int j = 0; j < 7; ++j) {
|
| 826 |
+
double val;
|
| 827 |
+
std::memcpy(&val, right_hand_buf.data() + j * sizeof(double), sizeof(double));
|
| 828 |
+
if (needs_swap) val = byte_swap(val);
|
| 829 |
+
right_hand_joint_values[j] = val;
|
| 830 |
+
}
|
| 831 |
+
}
|
| 832 |
+
} else {
|
| 833 |
+
std::cerr << "[ZMQEndpointInterface] Protocol v4: Invalid right_hand_joints shape" << std::endl;
|
| 834 |
+
has_right_hand_joints = false;
|
| 835 |
+
}
|
| 836 |
+
}
|
| 837 |
+
|
| 838 |
+
// Set hand joints if present
|
| 839 |
+
if (has_left_hand_joints || has_right_hand_joints) {
|
| 840 |
+
has_hand_joints_ = true;
|
| 841 |
+
|
| 842 |
+
if (has_left_hand_joints) {
|
| 843 |
+
left_hand_joint_.SetData(left_hand_joint_values);
|
| 844 |
+
if constexpr (DEBUG_LOGGING) {
|
| 845 |
+
std::cout << "[ZMQEndpointInterface] Protocol v4: Left hand joints set: [";
|
| 846 |
+
for (int j = 0; j < 7; ++j) {
|
| 847 |
+
if (j > 0) std::cout << ", ";
|
| 848 |
+
std::cout << std::fixed << std::setprecision(4) << left_hand_joint_values[j];
|
| 849 |
+
}
|
| 850 |
+
std::cout << "]" << std::endl;
|
| 851 |
+
}
|
| 852 |
+
}
|
| 853 |
+
|
| 854 |
+
if (has_right_hand_joints) {
|
| 855 |
+
right_hand_joint_.SetData(right_hand_joint_values);
|
| 856 |
+
if constexpr (DEBUG_LOGGING) {
|
| 857 |
+
std::cout << "[ZMQEndpointInterface] Protocol v4: Right hand joints set: [";
|
| 858 |
+
for (int j = 0; j < 7; ++j) {
|
| 859 |
+
if (j > 0) std::cout << ", ";
|
| 860 |
+
std::cout << std::fixed << std::setprecision(4) << right_hand_joint_values[j];
|
| 861 |
+
}
|
| 862 |
+
std::cout << "]" << std::endl;
|
| 863 |
+
}
|
| 864 |
+
}
|
| 865 |
+
}
|
| 866 |
+
|
| 867 |
+
// Return success with protocol version but no motion (token-only)
|
| 868 |
+
result.protocol_version = 4;
|
| 869 |
+
return result;
|
| 870 |
+
}
|
| 871 |
+
|
| 872 |
+
// Validate required fields based on protocol version (for motion protocols v1/v2/v3)
|
| 873 |
+
// body_quat and frame_index are required for motion protocols
|
| 874 |
+
if (body_quat_idx < 0) {
|
| 875 |
+
std::cerr << "[ZMQEndpointInterface] Missing required field 'body_quat' (or 'body_quat_w')" << std::endl;
|
| 876 |
+
return result;
|
| 877 |
+
}
|
| 878 |
+
|
| 879 |
+
if (frame_index_idx < 0) {
|
| 880 |
+
std::cerr << "[ZMQEndpointInterface] Missing required field 'frame_index' (or 'last_smpl_global_frames')" << std::endl;
|
| 881 |
+
return result;
|
| 882 |
+
}
|
| 883 |
+
|
| 884 |
+
if (protocol_version == 2 || protocol_version == 3) {
|
| 885 |
+
// Version 2/3: require smpl_joints, smpl_pose (joint_pos/joint_vel optional for v2, required for v3)
|
| 886 |
+
if (smpl_joints_idx < 0) {
|
| 887 |
+
std::cerr << "[ZMQEndpointInterface] Version " << protocol_version
|
| 888 |
+
<< " missing required field 'smpl_joints' " << std::endl;
|
| 889 |
+
return result;
|
| 890 |
+
}
|
| 891 |
+
if (smpl_pose_idx < 0) {
|
| 892 |
+
std::cerr << "[ZMQEndpointInterface] Version " << protocol_version
|
| 893 |
+
<< " missing required field 'smpl_pose'" << std::endl;
|
| 894 |
+
return result;
|
| 895 |
+
}
|
| 896 |
+
if (protocol_version == 3) {
|
| 897 |
+
// Version 3 additionally requires joint_pos and joint_vel
|
| 898 |
+
if (joint_pos_idx < 0 ) {
|
| 899 |
+
std::cerr << "[ZMQEndpointInterface] Version 3 missing required field 'joint_pos'" << std::endl;
|
| 900 |
+
return result;
|
| 901 |
+
}
|
| 902 |
+
if (joint_vel_idx < 0) {
|
| 903 |
+
std::cerr << "[ZMQEndpointInterface] Version 3 missing required field 'joint_vel'" << std::endl;
|
| 904 |
+
return result;
|
| 905 |
+
}
|
| 906 |
+
}
|
| 907 |
+
} else if (protocol_version == 1) {
|
| 908 |
+
// Version 1: requires joint_pos and joint_vel (smpl_joints optional)
|
| 909 |
+
if (joint_pos_idx < 0 || joint_vel_idx < 0) {
|
| 910 |
+
std::cerr << "[ZMQEndpointInterface] Version 1 missing required fields (joint_pos, joint_vel)" << std::endl;
|
| 911 |
+
return result;
|
| 912 |
+
}
|
| 913 |
+
} else {
|
| 914 |
+
// Protocol v4 is handled above, before body_quat/frame_index validation
|
| 915 |
+
std::cerr << "[ZMQEndpointInterface] Unsupported protocol version: " << protocol_version << std::endl;
|
| 916 |
+
return result;
|
| 917 |
+
}
|
| 918 |
+
|
| 919 |
+
// Determine num_frames and num_joints from available fields
|
| 920 |
+
int num_frames = 0;
|
| 921 |
+
int num_joints = 0;
|
| 922 |
+
|
| 923 |
+
// Get num_frames from the primary required field for each version
|
| 924 |
+
if (protocol_version == 2 || protocol_version == 3) {
|
| 925 |
+
// Version 2/3: Get num_frames from smpl_joints (required)
|
| 926 |
+
const auto& smpl_field = buffered_header_.fields[smpl_joints_idx];
|
| 927 |
+
if (smpl_field.shape.size() < 2) {
|
| 928 |
+
std::cerr << "[ZMQEndpointInterface] Invalid smpl_joints shape" << std::endl;
|
| 929 |
+
return result;
|
| 930 |
+
}
|
| 931 |
+
int num_frames_smpl = static_cast<int>(smpl_field.shape[0]);
|
| 932 |
+
if (num_frames_smpl <= 0) {
|
| 933 |
+
std::cerr << "[ZMQEndpointInterface] Invalid number of frames from smpl_joints: " << num_frames_smpl << std::endl;
|
| 934 |
+
return result;
|
| 935 |
+
}
|
| 936 |
+
|
| 937 |
+
// For version 3, also validate that joint_pos has consistent frame count
|
| 938 |
+
if (protocol_version == 3) {
|
| 939 |
+
const auto& joint_pos_field = buffered_header_.fields[joint_pos_idx];
|
| 940 |
+
if (joint_pos_field.shape.size() != 2) {
|
| 941 |
+
std::cerr << "[ZMQEndpointInterface] Version 3 has invalid joint_pos shape (expected [N, num_joints])" << std::endl;
|
| 942 |
+
return result;
|
| 943 |
+
}
|
| 944 |
+
const auto& joint_vel_field = buffered_header_.fields[joint_vel_idx];
|
| 945 |
+
if (joint_vel_field.shape.size() != 2) {
|
| 946 |
+
std::cerr << "[ZMQEndpointInterface] Version 3 has invalid joint_vel shape (expected [N, num_joints])" << std::endl;
|
| 947 |
+
return result;
|
| 948 |
+
}
|
| 949 |
+
int num_frames_joint = static_cast<int>(joint_pos_field.shape[0]);
|
| 950 |
+
if (num_frames_joint != num_frames_smpl) {
|
| 951 |
+
std::cerr << "[ZMQEndpointInterface] Version 3 frame count mismatch between smpl_joints ("
|
| 952 |
+
<< num_frames_smpl << ") and joint_pos (" << num_frames_joint << ")" << std::endl;
|
| 953 |
+
return result;
|
| 954 |
+
}
|
| 955 |
+
int num_frames_joint_vel = static_cast<int>(joint_vel_field.shape[0]);
|
| 956 |
+
if (num_frames_joint_vel != num_frames_smpl) {
|
| 957 |
+
std::cerr << "[ZMQEndpointInterface] Version 3 frame count mismatch between smpl_joints ("
|
| 958 |
+
<< num_frames_smpl << ") and joint_vel (" << num_frames_joint_vel << ")" << std::endl;
|
| 959 |
+
return result;
|
| 960 |
+
}
|
| 961 |
+
}
|
| 962 |
+
num_frames = num_frames_smpl;
|
| 963 |
+
} else if (protocol_version == 1) {
|
| 964 |
+
// Version 1: Get num_frames from joint_pos (required)
|
| 965 |
+
const auto& joint_pos_field = buffered_header_.fields[joint_pos_idx];
|
| 966 |
+
if (joint_pos_field.shape.size() != 2) {
|
| 967 |
+
std::cerr << "[ZMQEndpointInterface] Invalid joint_pos shape" << std::endl;
|
| 968 |
+
return result;
|
| 969 |
+
}
|
| 970 |
+
const auto& joint_vel_field = buffered_header_.fields[joint_vel_idx];
|
| 971 |
+
if (joint_vel_field.shape.size() != 2) {
|
| 972 |
+
std::cerr << "[ZMQEndpointInterface] Invalid joint_vel shape" << std::endl;
|
| 973 |
+
return result;
|
| 974 |
+
}
|
| 975 |
+
num_frames = static_cast<int>(joint_pos_field.shape[0]);
|
| 976 |
+
if (num_frames != static_cast<int>(joint_vel_field.shape[0])) {
|
| 977 |
+
std::cerr << "[ZMQEndpointInterface] Frame count mismatch between joint_pos and joint_vel" << std::endl;
|
| 978 |
+
return result;
|
| 979 |
+
}
|
| 980 |
+
}
|
| 981 |
+
|
| 982 |
+
if (num_frames <= 0) {
|
| 983 |
+
std::cerr << "[ZMQEndpointInterface] Invalid number of frames: " << num_frames << std::endl;
|
| 984 |
+
return result;
|
| 985 |
+
}
|
| 986 |
+
|
| 987 |
+
// Get num_joints if joint data is present
|
| 988 |
+
if (joint_pos_idx >= 0 && joint_vel_idx >= 0) {
|
| 989 |
+
const auto& joint_pos_field = buffered_header_.fields[joint_pos_idx];
|
| 990 |
+
const auto& joint_vel_field = buffered_header_.fields[joint_vel_idx];
|
| 991 |
+
|
| 992 |
+
// Validate shapes: expect [N, num_joints]
|
| 993 |
+
if (joint_pos_field.shape.size() == 2 && joint_vel_field.shape.size() == 2) {
|
| 994 |
+
num_joints = static_cast<int>(joint_pos_field.shape[1]);
|
| 995 |
+
if (num_joints <= 0) {
|
| 996 |
+
std::cerr << "[ZMQEndpointInterface] Invalid number of joints: " << num_joints << std::endl;
|
| 997 |
+
return result;
|
| 998 |
+
}
|
| 999 |
+
}
|
| 1000 |
+
}
|
| 1001 |
+
|
| 1002 |
+
bool needs_swap = buffered_header_.NeedsByteSwap();
|
| 1003 |
+
|
| 1004 |
+
// ===== STEP 1: Decode all incoming data into temporary buffers =====
|
| 1005 |
+
|
| 1006 |
+
// Decode joint positions and velocities if present
|
| 1007 |
+
std::vector<std::vector<double>> decoded_joint_pos;
|
| 1008 |
+
std::vector<std::vector<double>> decoded_joint_vel;
|
| 1009 |
+
bool has_joint_data = (joint_pos_idx >= 0 && joint_vel_idx >= 0 && num_joints > 0);
|
| 1010 |
+
|
| 1011 |
+
if (has_joint_data) {
|
| 1012 |
+
// Decode joint positions
|
| 1013 |
+
decoded_joint_pos.resize(num_frames, std::vector<double>(num_joints));
|
| 1014 |
+
const auto& joint_pos_field = buffered_header_.fields[joint_pos_idx];
|
| 1015 |
+
const auto& pos_buf = buffered_buffers_[joint_pos_idx];
|
| 1016 |
+
if (joint_pos_field.dtype == "f32") {
|
| 1017 |
+
for (int frame = 0; frame < num_frames; ++frame) {
|
| 1018 |
+
for (int joint = 0; joint < num_joints; ++joint) {
|
| 1019 |
+
float val;
|
| 1020 |
+
std::memcpy(&val, pos_buf.data() + (frame * num_joints + joint) * sizeof(float), sizeof(float));
|
| 1021 |
+
if (needs_swap) val = byte_swap(val);
|
| 1022 |
+
decoded_joint_pos[frame][joint] = static_cast<double>(val);
|
| 1023 |
+
}
|
| 1024 |
+
}
|
| 1025 |
+
} else if (joint_pos_field.dtype == "f64") {
|
| 1026 |
+
for (int frame = 0; frame < num_frames; ++frame) {
|
| 1027 |
+
for (int joint = 0; joint < num_joints; ++joint) {
|
| 1028 |
+
double val;
|
| 1029 |
+
std::memcpy(&val, pos_buf.data() + (frame * num_joints + joint) * sizeof(double), sizeof(double));
|
| 1030 |
+
if (needs_swap) val = byte_swap(val);
|
| 1031 |
+
decoded_joint_pos[frame][joint] = val;
|
| 1032 |
+
}
|
| 1033 |
+
}
|
| 1034 |
+
}
|
| 1035 |
+
|
| 1036 |
+
// Decode joint velocities
|
| 1037 |
+
decoded_joint_vel.resize(num_frames, std::vector<double>(num_joints));
|
| 1038 |
+
const auto& joint_vel_field = buffered_header_.fields[joint_vel_idx];
|
| 1039 |
+
const auto& vel_buf = buffered_buffers_[joint_vel_idx];
|
| 1040 |
+
if (joint_vel_field.dtype == "f32") {
|
| 1041 |
+
for (int frame = 0; frame < num_frames; ++frame) {
|
| 1042 |
+
for (int joint = 0; joint < num_joints; ++joint) {
|
| 1043 |
+
float val;
|
| 1044 |
+
std::memcpy(&val, vel_buf.data() + (frame * num_joints + joint) * sizeof(float), sizeof(float));
|
| 1045 |
+
if (needs_swap) val = byte_swap(val);
|
| 1046 |
+
decoded_joint_vel[frame][joint] = static_cast<double>(val);
|
| 1047 |
+
}
|
| 1048 |
+
}
|
| 1049 |
+
} else if (joint_vel_field.dtype == "f64") {
|
| 1050 |
+
for (int frame = 0; frame < num_frames; ++frame) {
|
| 1051 |
+
for (int joint = 0; joint < num_joints; ++joint) {
|
| 1052 |
+
double val;
|
| 1053 |
+
std::memcpy(&val, vel_buf.data() + (frame * num_joints + joint) * sizeof(double), sizeof(double));
|
| 1054 |
+
if (needs_swap) val = byte_swap(val);
|
| 1055 |
+
decoded_joint_vel[frame][joint] = val;
|
| 1056 |
+
}
|
| 1057 |
+
}
|
| 1058 |
+
}
|
| 1059 |
+
}
|
| 1060 |
+
|
| 1061 |
+
// Decode body quaternions (required for both versions)
|
| 1062 |
+
// Support shapes: [N, num_quat_bodies, 4] or [N, 4] for single body
|
| 1063 |
+
const auto& quat_field = buffered_header_.fields[body_quat_idx];
|
| 1064 |
+
const auto& quat_buf = buffered_buffers_[body_quat_idx];
|
| 1065 |
+
|
| 1066 |
+
// Determine number of quaternion bodies from shape
|
| 1067 |
+
int num_quat_bodies = 1;
|
| 1068 |
+
if (quat_field.shape.size() == 3) {
|
| 1069 |
+
num_quat_bodies = static_cast<int>(quat_field.shape[1]);
|
| 1070 |
+
} else if (quat_field.shape.size() == 2) {
|
| 1071 |
+
num_quat_bodies = 1;
|
| 1072 |
+
}
|
| 1073 |
+
|
| 1074 |
+
// Decode quaternions: [frame][body][xyzw]
|
| 1075 |
+
std::vector<std::vector<std::array<double, 4>>> decoded_body_quat(num_frames);
|
| 1076 |
+
for (int frame = 0; frame < num_frames; ++frame) {
|
| 1077 |
+
decoded_body_quat[frame].resize(num_quat_bodies, {1.0, 0.0, 0.0, 0.0});
|
| 1078 |
+
}
|
| 1079 |
+
|
| 1080 |
+
int quat_stride = num_quat_bodies * 4;
|
| 1081 |
+
|
| 1082 |
+
if (quat_field.dtype == "f32") {
|
| 1083 |
+
for (int frame = 0; frame < num_frames; ++frame) {
|
| 1084 |
+
for (int body = 0; body < num_quat_bodies; ++body) {
|
| 1085 |
+
for (int q = 0; q < 4; ++q) {
|
| 1086 |
+
float val;
|
| 1087 |
+
std::memcpy(&val, quat_buf.data() + (frame * quat_stride + body * 4 + q) * sizeof(float), sizeof(float));
|
| 1088 |
+
if (needs_swap) val = byte_swap(val);
|
| 1089 |
+
decoded_body_quat[frame][body][q] = static_cast<double>(val);
|
| 1090 |
+
}
|
| 1091 |
+
}
|
| 1092 |
+
}
|
| 1093 |
+
} else if (quat_field.dtype == "f64") {
|
| 1094 |
+
for (int frame = 0; frame < num_frames; ++frame) {
|
| 1095 |
+
for (int body = 0; body < num_quat_bodies; ++body) {
|
| 1096 |
+
for (int q = 0; q < 4; ++q) {
|
| 1097 |
+
double val;
|
| 1098 |
+
std::memcpy(&val, quat_buf.data() + (frame * quat_stride + body * 4 + q) * sizeof(double), sizeof(double));
|
| 1099 |
+
if (needs_swap) val = byte_swap(val);
|
| 1100 |
+
decoded_body_quat[frame][body][q] = val;
|
| 1101 |
+
}
|
| 1102 |
+
}
|
| 1103 |
+
}
|
| 1104 |
+
}
|
| 1105 |
+
|
| 1106 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1107 |
+
std::cout << "[ZMQEndpointInterface] Decoded body quaternions: " << num_quat_bodies << " bodies per frame" << std::endl;
|
| 1108 |
+
}
|
| 1109 |
+
|
| 1110 |
+
// Decode SMPL joints if present
|
| 1111 |
+
// Expected shape: [N, num_smpl_joints, 3] or [N, 3] for single joint
|
| 1112 |
+
std::vector<std::vector<std::array<double, 3>>> decoded_smpl_joints; // [frame][joint][xyz]
|
| 1113 |
+
int num_smpl_joints = 0;
|
| 1114 |
+
bool has_smpl_joints = (smpl_joints_idx >= 0);
|
| 1115 |
+
|
| 1116 |
+
if (has_smpl_joints) {
|
| 1117 |
+
const auto& smpl_field = buffered_header_.fields[smpl_joints_idx];
|
| 1118 |
+
const auto& smpl_buf = buffered_buffers_[smpl_joints_idx];
|
| 1119 |
+
|
| 1120 |
+
// Determine shape: [N, num_smpl_joints, 3] or [N, 3]
|
| 1121 |
+
if (smpl_field.shape.size() == 3) {
|
| 1122 |
+
num_smpl_joints = static_cast<int>(smpl_field.shape[1]);
|
| 1123 |
+
} else if (smpl_field.shape.size() == 2) {
|
| 1124 |
+
num_smpl_joints = 1;
|
| 1125 |
+
} else {
|
| 1126 |
+
std::cerr << "[ZMQEndpointInterface] Invalid smpl_joints shape dimensions: "
|
| 1127 |
+
<< smpl_field.shape.size() << std::endl;
|
| 1128 |
+
has_smpl_joints = false; // Invalid shape, skip decoding
|
| 1129 |
+
}
|
| 1130 |
+
|
| 1131 |
+
if (has_smpl_joints && num_smpl_joints > 0) {
|
| 1132 |
+
decoded_smpl_joints.resize(num_frames);
|
| 1133 |
+
|
| 1134 |
+
int stride = num_smpl_joints * 3;
|
| 1135 |
+
|
| 1136 |
+
if (smpl_field.dtype == "f32") {
|
| 1137 |
+
for (int frame = 0; frame < num_frames; ++frame) {
|
| 1138 |
+
decoded_smpl_joints[frame].resize(num_smpl_joints);
|
| 1139 |
+
for (int joint = 0; joint < num_smpl_joints; ++joint) {
|
| 1140 |
+
for (int xyz = 0; xyz < 3; ++xyz) {
|
| 1141 |
+
float val;
|
| 1142 |
+
std::memcpy(&val, smpl_buf.data() + (frame * stride + joint * 3 + xyz) * sizeof(float), sizeof(float));
|
| 1143 |
+
if (needs_swap) val = byte_swap(val);
|
| 1144 |
+
decoded_smpl_joints[frame][joint][xyz] = static_cast<double>(val);
|
| 1145 |
+
}
|
| 1146 |
+
}
|
| 1147 |
+
}
|
| 1148 |
+
} else if (smpl_field.dtype == "f64") {
|
| 1149 |
+
for (int frame = 0; frame < num_frames; ++frame) {
|
| 1150 |
+
decoded_smpl_joints[frame].resize(num_smpl_joints);
|
| 1151 |
+
for (int joint = 0; joint < num_smpl_joints; ++joint) {
|
| 1152 |
+
for (int xyz = 0; xyz < 3; ++xyz) {
|
| 1153 |
+
double val;
|
| 1154 |
+
std::memcpy(&val, smpl_buf.data() + (frame * stride + joint * 3 + xyz) * sizeof(double), sizeof(double));
|
| 1155 |
+
if (needs_swap) val = byte_swap(val);
|
| 1156 |
+
decoded_smpl_joints[frame][joint][xyz] = val;
|
| 1157 |
+
}
|
| 1158 |
+
}
|
| 1159 |
+
}
|
| 1160 |
+
}
|
| 1161 |
+
|
| 1162 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1163 |
+
std::cout << "[ZMQEndpointInterface] Decoded smpl_joints: " << num_frames
|
| 1164 |
+
<< " frames, " << num_smpl_joints << " joints" << std::endl;
|
| 1165 |
+
}
|
| 1166 |
+
}
|
| 1167 |
+
}
|
| 1168 |
+
|
| 1169 |
+
// Decode SMPL poses if present
|
| 1170 |
+
// Expected shape: [N, num_poses, 3] or [N, 3] for single pose
|
| 1171 |
+
std::vector<std::vector<std::array<double, 3>>> decoded_smpl_pose; // [frame][pose][xyz]
|
| 1172 |
+
int num_smpl_poses = 0;
|
| 1173 |
+
bool has_smpl_pose = (smpl_pose_idx >= 0);
|
| 1174 |
+
|
| 1175 |
+
if (has_smpl_pose) {
|
| 1176 |
+
const auto& smpl_pose_field = buffered_header_.fields[smpl_pose_idx];
|
| 1177 |
+
const auto& smpl_pose_buf = buffered_buffers_[smpl_pose_idx];
|
| 1178 |
+
|
| 1179 |
+
// Determine shape: [N, num_poses, 3] or [N, 3]
|
| 1180 |
+
if (smpl_pose_field.shape.size() == 3) {
|
| 1181 |
+
num_smpl_poses = static_cast<int>(smpl_pose_field.shape[1]);
|
| 1182 |
+
} else if (smpl_pose_field.shape.size() == 2) {
|
| 1183 |
+
num_smpl_poses = 1;
|
| 1184 |
+
} else {
|
| 1185 |
+
std::cerr << "[ZMQEndpointInterface] Invalid smpl_pose shape dimensions: "
|
| 1186 |
+
<< smpl_pose_field.shape.size() << std::endl;
|
| 1187 |
+
has_smpl_pose = false; // Invalid shape, skip decoding
|
| 1188 |
+
}
|
| 1189 |
+
|
| 1190 |
+
if (has_smpl_pose && num_smpl_poses > 0) {
|
| 1191 |
+
decoded_smpl_pose.resize(num_frames);
|
| 1192 |
+
|
| 1193 |
+
int stride = num_smpl_poses * 3;
|
| 1194 |
+
|
| 1195 |
+
if (smpl_pose_field.dtype == "f32") {
|
| 1196 |
+
for (int frame = 0; frame < num_frames; ++frame) {
|
| 1197 |
+
decoded_smpl_pose[frame].resize(num_smpl_poses);
|
| 1198 |
+
for (int pose = 0; pose < num_smpl_poses; ++pose) {
|
| 1199 |
+
for (int xyz = 0; xyz < 3; ++xyz) {
|
| 1200 |
+
float val;
|
| 1201 |
+
std::memcpy(&val, smpl_pose_buf.data() + (frame * stride + pose * 3 + xyz) * sizeof(float), sizeof(float));
|
| 1202 |
+
if (needs_swap) val = byte_swap(val);
|
| 1203 |
+
decoded_smpl_pose[frame][pose][xyz] = static_cast<double>(val);
|
| 1204 |
+
}
|
| 1205 |
+
}
|
| 1206 |
+
}
|
| 1207 |
+
} else if (smpl_pose_field.dtype == "f64") {
|
| 1208 |
+
for (int frame = 0; frame < num_frames; ++frame) {
|
| 1209 |
+
decoded_smpl_pose[frame].resize(num_smpl_poses);
|
| 1210 |
+
for (int pose = 0; pose < num_smpl_poses; ++pose) {
|
| 1211 |
+
for (int xyz = 0; xyz < 3; ++xyz) {
|
| 1212 |
+
double val;
|
| 1213 |
+
std::memcpy(&val, smpl_pose_buf.data() + (frame * stride + pose * 3 + xyz) * sizeof(double), sizeof(double));
|
| 1214 |
+
if (needs_swap) val = byte_swap(val);
|
| 1215 |
+
decoded_smpl_pose[frame][pose][xyz] = val;
|
| 1216 |
+
}
|
| 1217 |
+
}
|
| 1218 |
+
}
|
| 1219 |
+
}
|
| 1220 |
+
|
| 1221 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1222 |
+
std::cout << "[ZMQEndpointInterface] Decoded smpl_pose: " << num_frames
|
| 1223 |
+
<< " frames, " << num_smpl_poses << " poses" << std::endl;
|
| 1224 |
+
}
|
| 1225 |
+
}
|
| 1226 |
+
}
|
| 1227 |
+
|
| 1228 |
+
// Decode hand joint positions if present (7 DOF joint values)
|
| 1229 |
+
bool has_left_hand_joints = (left_hand_joints_idx >= 0);
|
| 1230 |
+
bool has_right_hand_joints = (right_hand_joints_idx >= 0);
|
| 1231 |
+
auto [has_left_hand, left_hand_joint_values] = GetHandPose(true);
|
| 1232 |
+
auto [has_right_hand, right_hand_joint_values] = GetHandPose(false);
|
| 1233 |
+
|
| 1234 |
+
if (has_left_hand_joints) {
|
| 1235 |
+
const auto& left_hand_field = buffered_header_.fields[left_hand_joints_idx];
|
| 1236 |
+
const auto& left_hand_buf = buffered_buffers_[left_hand_joints_idx];
|
| 1237 |
+
|
| 1238 |
+
// Validate shape: expect [7] or [1, 7]
|
| 1239 |
+
int num_hand_joints = 0;
|
| 1240 |
+
if (left_hand_field.shape.size() == 1 && left_hand_field.shape[0] == 7) {
|
| 1241 |
+
num_hand_joints = 7;
|
| 1242 |
+
} else if (left_hand_field.shape.size() == 2 && left_hand_field.shape[1] == 7) {
|
| 1243 |
+
num_hand_joints = 7;
|
| 1244 |
+
}
|
| 1245 |
+
|
| 1246 |
+
if (num_hand_joints == 7) {
|
| 1247 |
+
// Decode 7 joint values
|
| 1248 |
+
if (left_hand_field.dtype == "f32") {
|
| 1249 |
+
for (int j = 0; j < 7; ++j) {
|
| 1250 |
+
float val;
|
| 1251 |
+
std::memcpy(&val, left_hand_buf.data() + j * sizeof(float), sizeof(float));
|
| 1252 |
+
if (needs_swap) val = byte_swap(val);
|
| 1253 |
+
left_hand_joint_values[j] = static_cast<double>(val);
|
| 1254 |
+
}
|
| 1255 |
+
} else if (left_hand_field.dtype == "f64") {
|
| 1256 |
+
for (int j = 0; j < 7; ++j) {
|
| 1257 |
+
double val;
|
| 1258 |
+
std::memcpy(&val, left_hand_buf.data() + j * sizeof(double), sizeof(double));
|
| 1259 |
+
if (needs_swap) val = byte_swap(val);
|
| 1260 |
+
left_hand_joint_values[j] = val;
|
| 1261 |
+
}
|
| 1262 |
+
}
|
| 1263 |
+
|
| 1264 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1265 |
+
std::cout << "[ZMQEndpointInterface] Decoded left_hand_joints: [";
|
| 1266 |
+
for (int j = 0; j < 7; ++j) {
|
| 1267 |
+
if (j > 0) std::cout << ", ";
|
| 1268 |
+
std::cout << std::fixed << std::setprecision(4) << left_hand_joint_values[j];
|
| 1269 |
+
}
|
| 1270 |
+
std::cout << "]" << std::endl;
|
| 1271 |
+
}
|
| 1272 |
+
} else {
|
| 1273 |
+
std::cerr << "[ZMQEndpointInterface] Invalid left_hand_joints shape" << std::endl;
|
| 1274 |
+
has_left_hand_joints = false;
|
| 1275 |
+
}
|
| 1276 |
+
}
|
| 1277 |
+
|
| 1278 |
+
if (has_right_hand_joints) {
|
| 1279 |
+
const auto& right_hand_field = buffered_header_.fields[right_hand_joints_idx];
|
| 1280 |
+
const auto& right_hand_buf = buffered_buffers_[right_hand_joints_idx];
|
| 1281 |
+
|
| 1282 |
+
// Validate shape: expect [7] or [1, 7]
|
| 1283 |
+
int num_hand_joints = 0;
|
| 1284 |
+
if (right_hand_field.shape.size() == 1 && right_hand_field.shape[0] == 7) {
|
| 1285 |
+
num_hand_joints = 7;
|
| 1286 |
+
} else if (right_hand_field.shape.size() == 2 && right_hand_field.shape[1] == 7) {
|
| 1287 |
+
num_hand_joints = 7;
|
| 1288 |
+
}
|
| 1289 |
+
|
| 1290 |
+
if (num_hand_joints == 7) {
|
| 1291 |
+
// Decode 7 joint values
|
| 1292 |
+
if (right_hand_field.dtype == "f32") {
|
| 1293 |
+
for (int j = 0; j < 7; ++j) {
|
| 1294 |
+
float val;
|
| 1295 |
+
std::memcpy(&val, right_hand_buf.data() + j * sizeof(float), sizeof(float));
|
| 1296 |
+
if (needs_swap) val = byte_swap(val);
|
| 1297 |
+
right_hand_joint_values[j] = static_cast<double>(val);
|
| 1298 |
+
}
|
| 1299 |
+
} else if (right_hand_field.dtype == "f64") {
|
| 1300 |
+
for (int j = 0; j < 7; ++j) {
|
| 1301 |
+
double val;
|
| 1302 |
+
std::memcpy(&val, right_hand_buf.data() + j * sizeof(double), sizeof(double));
|
| 1303 |
+
if (needs_swap) val = byte_swap(val);
|
| 1304 |
+
right_hand_joint_values[j] = val;
|
| 1305 |
+
}
|
| 1306 |
+
}
|
| 1307 |
+
|
| 1308 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1309 |
+
std::cout << "[ZMQEndpointInterface] Decoded right_hand_joints: [";
|
| 1310 |
+
for (int j = 0; j < 7; ++j) {
|
| 1311 |
+
if (j > 0) std::cout << ", ";
|
| 1312 |
+
std::cout << std::fixed << std::setprecision(4) << right_hand_joint_values[j];
|
| 1313 |
+
}
|
| 1314 |
+
std::cout << "]" << std::endl;
|
| 1315 |
+
}
|
| 1316 |
+
} else {
|
| 1317 |
+
std::cerr << "[ZMQEndpointInterface] Invalid right_hand_joints shape" << std::endl;
|
| 1318 |
+
has_right_hand_joints = false;
|
| 1319 |
+
}
|
| 1320 |
+
}
|
| 1321 |
+
|
| 1322 |
+
// ===== Decode VR 3-point tracking data if present =====
|
| 1323 |
+
// VR 3-point format:
|
| 1324 |
+
// vr_position: 9 doubles (left wrist xyz, right wrist xyz, head xyz) - REQUIRED for VR mode
|
| 1325 |
+
// vr_orientation: 12 doubles (left quat wxyz, right quat wxyz, head quat wxyz) - optional
|
| 1326 |
+
// vr_compliance: 3 doubles (left_arm, right_arm, head compliance) - optional
|
| 1327 |
+
bool has_vr_position = (vr_position_idx >= 0);
|
| 1328 |
+
bool has_vr_orientation = (vr_orientation_idx >= 0);
|
| 1329 |
+
bool has_vr_compliance = (vr_compliance_idx >= 0);
|
| 1330 |
+
|
| 1331 |
+
// Default values for VR 3-point (from InputInterface defaults)
|
| 1332 |
+
std::array<double, 9> vr_position_values = {
|
| 1333 |
+
0.0903, 0.1615, -0.2411, // left wrist xyz
|
| 1334 |
+
0.1280, -0.1522, -0.2461, // right wrist xyz
|
| 1335 |
+
0.0241, -0.0081, 0.4028 // head xyz
|
| 1336 |
+
};
|
| 1337 |
+
std::array<double, 12> vr_orientation_values = {
|
| 1338 |
+
0.7295, 0.3145, 0.5533, -0.2506, // left quat (w,x,y,z)
|
| 1339 |
+
0.7320, -0.2639, 0.5395, 0.3217, // right quat (w,x,y,z)
|
| 1340 |
+
0.9991, 0.011, 0.0402, -0.0002 // head quat (w,x,y,z)
|
| 1341 |
+
};
|
| 1342 |
+
std::array<double, 3> vr_compliance_values = GetVR3PointCompliance(); // Use keyboard-controlled compliance
|
| 1343 |
+
|
| 1344 |
+
if (has_vr_position) {
|
| 1345 |
+
const auto& vr_pos_field = buffered_header_.fields[vr_position_idx];
|
| 1346 |
+
const auto& vr_pos_buf = buffered_buffers_[vr_position_idx];
|
| 1347 |
+
|
| 1348 |
+
// Validate shape: expect [9] or [1, 9] or [3, 3]
|
| 1349 |
+
size_t total_elements = 1;
|
| 1350 |
+
for (auto dim : vr_pos_field.shape) total_elements *= dim;
|
| 1351 |
+
|
| 1352 |
+
if (total_elements == 9) {
|
| 1353 |
+
if (vr_pos_field.dtype == "f32") {
|
| 1354 |
+
for (int j = 0; j < 9; ++j) {
|
| 1355 |
+
float val;
|
| 1356 |
+
std::memcpy(&val, vr_pos_buf.data() + j * sizeof(float), sizeof(float));
|
| 1357 |
+
if (needs_swap) val = byte_swap(val);
|
| 1358 |
+
vr_position_values[j] = static_cast<double>(val);
|
| 1359 |
+
}
|
| 1360 |
+
} else if (vr_pos_field.dtype == "f64") {
|
| 1361 |
+
for (int j = 0; j < 9; ++j) {
|
| 1362 |
+
double val;
|
| 1363 |
+
std::memcpy(&val, vr_pos_buf.data() + j * sizeof(double), sizeof(double));
|
| 1364 |
+
if (needs_swap) val = byte_swap(val);
|
| 1365 |
+
vr_position_values[j] = val;
|
| 1366 |
+
}
|
| 1367 |
+
}
|
| 1368 |
+
|
| 1369 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1370 |
+
std::cout << "[ZMQEndpointInterface] Decoded vr_position: [";
|
| 1371 |
+
for (int j = 0; j < 9; ++j) {
|
| 1372 |
+
if (j > 0) std::cout << ", ";
|
| 1373 |
+
if (j == 3 || j == 6) std::cout << " | ";
|
| 1374 |
+
std::cout << std::fixed << std::setprecision(4) << vr_position_values[j];
|
| 1375 |
+
}
|
| 1376 |
+
std::cout << "]" << std::endl;
|
| 1377 |
+
}
|
| 1378 |
+
} else {
|
| 1379 |
+
std::cerr << "[ZMQEndpointInterface] Invalid vr_position shape (expected 9 elements, got "
|
| 1380 |
+
<< total_elements << ")" << std::endl;
|
| 1381 |
+
has_vr_position = false;
|
| 1382 |
+
}
|
| 1383 |
+
}
|
| 1384 |
+
|
| 1385 |
+
if (has_vr_orientation) {
|
| 1386 |
+
const auto& vr_orient_field = buffered_header_.fields[vr_orientation_idx];
|
| 1387 |
+
const auto& vr_orient_buf = buffered_buffers_[vr_orientation_idx];
|
| 1388 |
+
|
| 1389 |
+
// Validate shape: expect [12] or [1, 12] or [3, 4]
|
| 1390 |
+
size_t total_elements = 1;
|
| 1391 |
+
for (auto dim : vr_orient_field.shape) total_elements *= dim;
|
| 1392 |
+
|
| 1393 |
+
if (total_elements == 12) {
|
| 1394 |
+
if (vr_orient_field.dtype == "f32") {
|
| 1395 |
+
for (int j = 0; j < 12; ++j) {
|
| 1396 |
+
float val;
|
| 1397 |
+
std::memcpy(&val, vr_orient_buf.data() + j * sizeof(float), sizeof(float));
|
| 1398 |
+
if (needs_swap) val = byte_swap(val);
|
| 1399 |
+
vr_orientation_values[j] = static_cast<double>(val);
|
| 1400 |
+
}
|
| 1401 |
+
} else if (vr_orient_field.dtype == "f64") {
|
| 1402 |
+
for (int j = 0; j < 12; ++j) {
|
| 1403 |
+
double val;
|
| 1404 |
+
std::memcpy(&val, vr_orient_buf.data() + j * sizeof(double), sizeof(double));
|
| 1405 |
+
if (needs_swap) val = byte_swap(val);
|
| 1406 |
+
vr_orientation_values[j] = val;
|
| 1407 |
+
}
|
| 1408 |
+
}
|
| 1409 |
+
|
| 1410 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1411 |
+
std::cout << "[ZMQEndpointInterface] Decoded vr_orientation: [";
|
| 1412 |
+
for (int j = 0; j < 12; ++j) {
|
| 1413 |
+
if (j > 0) std::cout << ", ";
|
| 1414 |
+
if (j == 4 || j == 8) std::cout << " | ";
|
| 1415 |
+
std::cout << std::fixed << std::setprecision(4) << vr_orientation_values[j];
|
| 1416 |
+
}
|
| 1417 |
+
std::cout << "]" << std::endl;
|
| 1418 |
+
}
|
| 1419 |
+
} else {
|
| 1420 |
+
std::cerr << "[ZMQEndpointInterface] Invalid vr_orientation shape (expected 12 elements, got "
|
| 1421 |
+
<< total_elements << ")" << std::endl;
|
| 1422 |
+
has_vr_orientation = false;
|
| 1423 |
+
}
|
| 1424 |
+
}
|
| 1425 |
+
|
| 1426 |
+
// Note: vr_compliance from ZMQ is intentionally IGNORED
|
| 1427 |
+
// We always use the keyboard-controlled compliance values (g/h/b/v keys)
|
| 1428 |
+
// This keeps compliance control consistent across all input modes
|
| 1429 |
+
if (has_vr_compliance) {
|
| 1430 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1431 |
+
std::cout << "[ZMQEndpointInterface] vr_compliance field present but IGNORED (using keyboard-controlled values instead)" << std::endl;
|
| 1432 |
+
}
|
| 1433 |
+
}
|
| 1434 |
+
|
| 1435 |
+
// ===== STEP 2: Decode frame indices =====
|
| 1436 |
+
// Note: The merger will calculate frame_step and incoming_frame_start internally
|
| 1437 |
+
std::vector<int64_t> frame_indices;
|
| 1438 |
+
|
| 1439 |
+
if (frame_index_idx >= 0) {
|
| 1440 |
+
const auto& frame_idx_field = buffered_header_.fields[frame_index_idx];
|
| 1441 |
+
const auto& frame_idx_buf = buffered_buffers_[frame_index_idx];
|
| 1442 |
+
|
| 1443 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1444 |
+
std::cout << "[ZMQEndpointInterface] Raw message field '" << frame_idx_field.name
|
| 1445 |
+
<< "' (dtype=" << frame_idx_field.dtype << ", size=" << frame_idx_buf.size() << " bytes)" << std::endl;
|
| 1446 |
+
}
|
| 1447 |
+
|
| 1448 |
+
if (frame_idx_field.dtype == "i32") {
|
| 1449 |
+
int num_indices = frame_idx_buf.size() / sizeof(int32_t);
|
| 1450 |
+
frame_indices.resize(num_indices);
|
| 1451 |
+
|
| 1452 |
+
for (int i = 0; i < num_indices; ++i) {
|
| 1453 |
+
int32_t val;
|
| 1454 |
+
std::memcpy(&val, frame_idx_buf.data() + i * sizeof(int32_t), sizeof(int32_t));
|
| 1455 |
+
if (needs_swap) val = byte_swap(val);
|
| 1456 |
+
frame_indices[i] = val;
|
| 1457 |
+
}
|
| 1458 |
+
} else if (frame_idx_field.dtype == "i64") {
|
| 1459 |
+
int num_indices = frame_idx_buf.size() / sizeof(int64_t);
|
| 1460 |
+
frame_indices.resize(num_indices);
|
| 1461 |
+
|
| 1462 |
+
for (int i = 0; i < num_indices; ++i) {
|
| 1463 |
+
int64_t val;
|
| 1464 |
+
std::memcpy(&val, frame_idx_buf.data() + i * sizeof(int64_t), sizeof(int64_t));
|
| 1465 |
+
if (needs_swap) val = byte_swap(val);
|
| 1466 |
+
frame_indices[i] = val;
|
| 1467 |
+
}
|
| 1468 |
+
}
|
| 1469 |
+
|
| 1470 |
+
// Print frame indices for protocol v3 (SMPL actions)
|
| 1471 |
+
if (protocol_version == 3 && !frame_indices.empty()) {
|
| 1472 |
+
if (frame_indices.size() == 1) {
|
| 1473 |
+
std::cout << "[ZMQEndpointInterface] Protocol v3: Received SMPL action (single) - frame_index: "
|
| 1474 |
+
<< frame_indices[0] << std::endl;
|
| 1475 |
+
} else {
|
| 1476 |
+
std::cout << "[ZMQEndpointInterface] Protocol v3: Received SMPL action (chunk) - frames: "
|
| 1477 |
+
<< frame_indices[0] << " to " << frame_indices.back()
|
| 1478 |
+
<< ", chunk_size: " << frame_indices.size() << std::endl;
|
| 1479 |
+
}
|
| 1480 |
+
}
|
| 1481 |
+
}
|
| 1482 |
+
|
| 1483 |
+
// Optional: decode heading_increment (single scalar, f32 or f64)
|
| 1484 |
+
if (heading_increment_idx >= 0) {
|
| 1485 |
+
double heading_increment = 0.0;
|
| 1486 |
+
const auto& dh_buf = buffered_buffers_[heading_increment_idx];
|
| 1487 |
+
const auto& dh_field = buffered_header_.fields[heading_increment_idx];
|
| 1488 |
+
if (dh_field.dtype == "f32") {
|
| 1489 |
+
float val = 0.0f;
|
| 1490 |
+
if (dh_buf.size() >= sizeof(float)) {
|
| 1491 |
+
std::memcpy(&val, dh_buf.data(), sizeof(float));
|
| 1492 |
+
if (needs_swap) val = byte_swap(val);
|
| 1493 |
+
heading_increment = static_cast<double>(val);
|
| 1494 |
+
}
|
| 1495 |
+
} else { // f64 or default
|
| 1496 |
+
double val = 0.0;
|
| 1497 |
+
if (dh_buf.size() >= sizeof(double)) {
|
| 1498 |
+
std::memcpy(&val, dh_buf.data(), sizeof(double));
|
| 1499 |
+
if (needs_swap) val = byte_swap(val);
|
| 1500 |
+
heading_increment = val;
|
| 1501 |
+
}
|
| 1502 |
+
}
|
| 1503 |
+
|
| 1504 |
+
auto current_heading_state = heading_state_buffer.GetDataWithTime().data;
|
| 1505 |
+
HeadingState current_state =
|
| 1506 |
+
current_heading_state ? *current_heading_state : HeadingState();
|
| 1507 |
+
|
| 1508 |
+
// Add increment to current heading
|
| 1509 |
+
heading_state_buffer.SetData(
|
| 1510 |
+
HeadingState(
|
| 1511 |
+
current_state.init_base_quat,
|
| 1512 |
+
current_state.delta_heading + heading_increment));
|
| 1513 |
+
}
|
| 1514 |
+
|
| 1515 |
+
// Optional: decode monotonic timestamp (single scalar, f64)
|
| 1516 |
+
if (timestamp_monotonic_idx >= 0) {
|
| 1517 |
+
double timestamp_monotonic = 0.0;
|
| 1518 |
+
const auto& ts_buf = buffered_buffers_[timestamp_monotonic_idx];
|
| 1519 |
+
const auto& ts_field = buffered_header_.fields[timestamp_monotonic_idx];
|
| 1520 |
+
if (ts_field.dtype == "f64") {
|
| 1521 |
+
double val = 0.0;
|
| 1522 |
+
if (ts_buf.size() >= sizeof(double)) {
|
| 1523 |
+
std::memcpy(&val, ts_buf.data(), sizeof(double));
|
| 1524 |
+
if (needs_swap) val = byte_swap(val);
|
| 1525 |
+
timestamp_monotonic = val;
|
| 1526 |
+
}
|
| 1527 |
+
}
|
| 1528 |
+
if (is_localhost_)
|
| 1529 |
+
{
|
| 1530 |
+
auto duration_monotonic = std::chrono::duration<double>(timestamp_monotonic);
|
| 1531 |
+
auto time_point_monotonic = std::chrono::steady_clock::time_point(
|
| 1532 |
+
std::chrono::duration_cast<std::chrono::steady_clock::duration>(duration_monotonic));
|
| 1533 |
+
data_timestamp_ = time_point_monotonic;
|
| 1534 |
+
}
|
| 1535 |
+
}
|
| 1536 |
+
|
| 1537 |
+
// ===== Decode catch_up field if present =====
|
| 1538 |
+
// Default: catch_up = true (use MAX_GAP_FRAMES)
|
| 1539 |
+
// If catch_up = false: allow infinite delays (set max_gap_frames to very large value)
|
| 1540 |
+
bool catch_up_enabled = true; // Default to true if field not present
|
| 1541 |
+
if (catch_up_idx >= 0) {
|
| 1542 |
+
const auto& catch_up_field = buffered_header_.fields[catch_up_idx];
|
| 1543 |
+
const auto& catch_up_buf = buffered_buffers_[catch_up_idx];
|
| 1544 |
+
|
| 1545 |
+
// Decode boolean value (support bool, i32, i64, u8)
|
| 1546 |
+
if (catch_up_field.dtype == "bool" || catch_up_field.dtype == "u8") {
|
| 1547 |
+
uint8_t val = 0;
|
| 1548 |
+
if (catch_up_buf.size() >= sizeof(uint8_t)) {
|
| 1549 |
+
std::memcpy(&val, catch_up_buf.data(), sizeof(uint8_t));
|
| 1550 |
+
catch_up_enabled = (val != 0);
|
| 1551 |
+
}
|
| 1552 |
+
} else if (catch_up_field.dtype == "i32") {
|
| 1553 |
+
int32_t val = 0;
|
| 1554 |
+
if (catch_up_buf.size() >= sizeof(int32_t)) {
|
| 1555 |
+
std::memcpy(&val, catch_up_buf.data(), sizeof(int32_t));
|
| 1556 |
+
if (needs_swap) val = byte_swap(val);
|
| 1557 |
+
catch_up_enabled = (val != 0);
|
| 1558 |
+
}
|
| 1559 |
+
} else if (catch_up_field.dtype == "i64") {
|
| 1560 |
+
int64_t val = 0;
|
| 1561 |
+
if (catch_up_buf.size() >= sizeof(int64_t)) {
|
| 1562 |
+
std::memcpy(&val, catch_up_buf.data(), sizeof(int64_t));
|
| 1563 |
+
if (needs_swap) val = byte_swap(val);
|
| 1564 |
+
catch_up_enabled = (val != 0);
|
| 1565 |
+
}
|
| 1566 |
+
}
|
| 1567 |
+
|
| 1568 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1569 |
+
std::cout << "[ZMQEndpointInterface] catch_up field: " << (catch_up_enabled ? "true" : "false") << std::endl;
|
| 1570 |
+
}
|
| 1571 |
+
} else {
|
| 1572 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1573 |
+
std::cout << "[ZMQEndpointInterface] catch_up field not present, using default: true" << std::endl;
|
| 1574 |
+
}
|
| 1575 |
+
}
|
| 1576 |
+
|
| 1577 |
+
// ===== DEBUG: Print merged frame indices and decoded data =====
|
| 1578 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1579 |
+
// Print first 20 frames (or all frames if fewer than 20)
|
| 1580 |
+
int print_frames = std::min(20, num_frames);
|
| 1581 |
+
|
| 1582 |
+
std::cout << "[ZMQEndpointInterface] Decoded data (Version " << protocol_version << ", first " << print_frames << " frames";
|
| 1583 |
+
if (has_joint_data) std::cout << ", " << num_joints << " joints";
|
| 1584 |
+
if (has_smpl_joints) std::cout << ", " << num_smpl_joints << " smpl_joints";
|
| 1585 |
+
if (has_smpl_pose) std::cout << ", " << num_smpl_poses << " smpl_pose";
|
| 1586 |
+
std::cout << "):" << std::endl;
|
| 1587 |
+
|
| 1588 |
+
for (int frame = 0; frame < print_frames; ++frame) {
|
| 1589 |
+
// Print frame index if available
|
| 1590 |
+
std::cout << " Frame[" << frame << "]";
|
| 1591 |
+
if (frame < static_cast<int>(frame_indices.size())) {
|
| 1592 |
+
std::cout << " (idx=" << frame_indices[frame] << ")";
|
| 1593 |
+
}
|
| 1594 |
+
|
| 1595 |
+
// Print joint_pos and joint_vel if present
|
| 1596 |
+
if (has_joint_data && !decoded_joint_pos.empty() && !decoded_joint_vel.empty()) {
|
| 1597 |
+
int print_joints = std::min(2, num_joints);
|
| 1598 |
+
std::cout << " joint_pos: [";
|
| 1599 |
+
for (int j = 0; j < print_joints; ++j) {
|
| 1600 |
+
if (j > 0) std::cout << ", ";
|
| 1601 |
+
std::cout << std::fixed << std::setprecision(6) << decoded_joint_pos[frame][j];
|
| 1602 |
+
}
|
| 1603 |
+
std::cout << "], joint_vel: [";
|
| 1604 |
+
for (int j = 0; j < print_joints; ++j) {
|
| 1605 |
+
if (j > 0) std::cout << ", ";
|
| 1606 |
+
std::cout << std::fixed << std::setprecision(6) << decoded_joint_vel[frame][j];
|
| 1607 |
+
}
|
| 1608 |
+
std::cout << "]";
|
| 1609 |
+
}
|
| 1610 |
+
|
| 1611 |
+
// Print body_quat (always present)
|
| 1612 |
+
std::cout << ", body_quat: [";
|
| 1613 |
+
int print_quat_bodies = std::min(2, static_cast<int>(decoded_body_quat[frame].size()));
|
| 1614 |
+
for (int b = 0; b < print_quat_bodies; ++b) {
|
| 1615 |
+
if (b > 0) std::cout << "; ";
|
| 1616 |
+
std::cout << "(";
|
| 1617 |
+
for (int q = 0; q < 4; ++q) {
|
| 1618 |
+
if (q > 0) std::cout << ", ";
|
| 1619 |
+
std::cout << std::fixed << std::setprecision(6) << decoded_body_quat[frame][b][q];
|
| 1620 |
+
}
|
| 1621 |
+
std::cout << ")";
|
| 1622 |
+
}
|
| 1623 |
+
std::cout << "]";
|
| 1624 |
+
|
| 1625 |
+
// Print smpl_joints if present
|
| 1626 |
+
if (has_smpl_joints && frame < static_cast<int>(decoded_smpl_joints.size())) {
|
| 1627 |
+
std::cout << ", smpl_joints: [";
|
| 1628 |
+
int print_bodies = std::min(1, static_cast<int>(decoded_smpl_joints[frame].size()));
|
| 1629 |
+
for (int b = 0; b < print_bodies; ++b) {
|
| 1630 |
+
if (b > 0) std::cout << "; ";
|
| 1631 |
+
std::cout << "(";
|
| 1632 |
+
for (int xyz = 0; xyz < 3; ++xyz) {
|
| 1633 |
+
if (xyz > 0) std::cout << ", ";
|
| 1634 |
+
std::cout << std::fixed << std::setprecision(6) << decoded_smpl_joints[frame][b][xyz];
|
| 1635 |
+
}
|
| 1636 |
+
std::cout << ")";
|
| 1637 |
+
}
|
| 1638 |
+
std::cout << "]";
|
| 1639 |
+
}
|
| 1640 |
+
|
| 1641 |
+
// Print smpl_pose if present
|
| 1642 |
+
if (has_smpl_pose && frame < static_cast<int>(decoded_smpl_pose.size())) {
|
| 1643 |
+
std::cout << ", smpl_pose: [";
|
| 1644 |
+
int print_poses = std::min(1, static_cast<int>(decoded_smpl_pose[frame].size()));
|
| 1645 |
+
for (int p = 0; p < print_poses; ++p) {
|
| 1646 |
+
if (p > 0) std::cout << "; ";
|
| 1647 |
+
std::cout << "(";
|
| 1648 |
+
for (int xyz = 0; xyz < 3; ++xyz) {
|
| 1649 |
+
if (xyz > 0) std::cout << ", ";
|
| 1650 |
+
std::cout << std::fixed << std::setprecision(6) << decoded_smpl_pose[frame][p][xyz];
|
| 1651 |
+
}
|
| 1652 |
+
std::cout << ")";
|
| 1653 |
+
}
|
| 1654 |
+
std::cout << "]";
|
| 1655 |
+
}
|
| 1656 |
+
|
| 1657 |
+
std::cout << std::endl;
|
| 1658 |
+
}
|
| 1659 |
+
}
|
| 1660 |
+
|
| 1661 |
+
// ===== STEP 3: Validate protocol version (application-specific) =====
|
| 1662 |
+
|
| 1663 |
+
// Check protocol version before merging
|
| 1664 |
+
if (active_protocol_version_ == -1) {
|
| 1665 |
+
// First message - establish protocol version
|
| 1666 |
+
active_protocol_version_ = protocol_version;
|
| 1667 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1668 |
+
std::cout << "[ZMQEndpointInterface] Protocol version " << active_protocol_version_ << " established" << std::endl;
|
| 1669 |
+
}
|
| 1670 |
+
} else if (active_protocol_version_ != protocol_version) {
|
| 1671 |
+
// Protocol version changed - this is an error
|
| 1672 |
+
std::cerr << "[ZMQEndpointInterface] ERROR: Protocol version changed from "
|
| 1673 |
+
<< active_protocol_version_ << " to " << protocol_version << std::endl;
|
| 1674 |
+
result.protocol_version = protocol_version; // Signal the change to caller
|
| 1675 |
+
return result;
|
| 1676 |
+
}
|
| 1677 |
+
|
| 1678 |
+
// ===== STEP 4: Package decoded data and call StreamedMotionMerger =====
|
| 1679 |
+
|
| 1680 |
+
// Prepare IncomingData structure for the merger
|
| 1681 |
+
StreamedMotionMerger::IncomingData incoming_data;
|
| 1682 |
+
incoming_data.joint_pos = std::move(decoded_joint_pos);
|
| 1683 |
+
incoming_data.joint_vel = std::move(decoded_joint_vel);
|
| 1684 |
+
incoming_data.body_quat = std::move(decoded_body_quat);
|
| 1685 |
+
incoming_data.smpl_joints = std::move(decoded_smpl_joints);
|
| 1686 |
+
incoming_data.smpl_pose = std::move(decoded_smpl_pose);
|
| 1687 |
+
incoming_data.frame_indices = std::move(frame_indices);
|
| 1688 |
+
incoming_data.protocol_version = protocol_version;
|
| 1689 |
+
incoming_data.catch_up_enabled = catch_up_enabled;
|
| 1690 |
+
incoming_data.num_frames = num_frames;
|
| 1691 |
+
incoming_data.num_joints = num_joints;
|
| 1692 |
+
incoming_data.num_quat_bodies = num_quat_bodies;
|
| 1693 |
+
incoming_data.num_smpl_joints = num_smpl_joints;
|
| 1694 |
+
incoming_data.num_smpl_poses = num_smpl_poses;
|
| 1695 |
+
|
| 1696 |
+
// Call the reusable merger to handle sliding window logic
|
| 1697 |
+
auto merge_result = motion_merger_.MergeIncomingData(incoming_data, current_playback_frame);
|
| 1698 |
+
|
| 1699 |
+
// Check for merge failure
|
| 1700 |
+
if (!merge_result.motion) {
|
| 1701 |
+
std::cerr << "[ZMQEndpointInterface] Failed to merge incoming data" << std::endl;
|
| 1702 |
+
return result;
|
| 1703 |
+
}
|
| 1704 |
+
|
| 1705 |
+
// Convert MergeResult to DecodeResult
|
| 1706 |
+
if (active_protocol_version_ == 1) {
|
| 1707 |
+
merge_result.motion->SetEncodeMode(0); // Protocol 1: joint-based
|
| 1708 |
+
} else if (active_protocol_version_ == 2 || active_protocol_version_ == 3) {
|
| 1709 |
+
// Protocol versions 2 and 3 both use encoder mode 2 (SMPL-based)
|
| 1710 |
+
merge_result.motion->SetEncodeMode(2);
|
| 1711 |
+
}
|
| 1712 |
+
result.motion = merge_result.motion;
|
| 1713 |
+
result.window_start = merge_result.window_start;
|
| 1714 |
+
result.frame_offset_adjustment = merge_result.frame_offset_adjustment;
|
| 1715 |
+
result.did_catchup_reset = merge_result.did_catchup_reset;
|
| 1716 |
+
result.frame_step = merge_result.frame_step;
|
| 1717 |
+
result.protocol_version = merge_result.protocol_version;
|
| 1718 |
+
|
| 1719 |
+
// Handle hand joints: set hand joint values directly from decoded data
|
| 1720 |
+
if (has_left_hand_joints || has_right_hand_joints) {
|
| 1721 |
+
has_hand_joints_ = true;
|
| 1722 |
+
|
| 1723 |
+
if (has_left_hand_joints) {
|
| 1724 |
+
left_hand_joint_.SetData(left_hand_joint_values);
|
| 1725 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1726 |
+
std::cout << "[ZMQEndpointInterface] Left hand joints set: [";
|
| 1727 |
+
for (int j = 0; j < 7; ++j) {
|
| 1728 |
+
if (j > 0) std::cout << ", ";
|
| 1729 |
+
std::cout << std::fixed << std::setprecision(4) << left_hand_joint_values[j];
|
| 1730 |
+
}
|
| 1731 |
+
std::cout << "]" << std::endl;
|
| 1732 |
+
}
|
| 1733 |
+
}
|
| 1734 |
+
|
| 1735 |
+
if (has_right_hand_joints) {
|
| 1736 |
+
right_hand_joint_.SetData(right_hand_joint_values);
|
| 1737 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1738 |
+
std::cout << "[ZMQEndpointInterface] Right hand joints set: [";
|
| 1739 |
+
for (int j = 0; j < 7; ++j) {
|
| 1740 |
+
if (j > 0) std::cout << ", ";
|
| 1741 |
+
std::cout << std::fixed << std::setprecision(4) << right_hand_joint_values[j];
|
| 1742 |
+
}
|
| 1743 |
+
std::cout << "]" << std::endl;
|
| 1744 |
+
}
|
| 1745 |
+
}
|
| 1746 |
+
}
|
| 1747 |
+
|
| 1748 |
+
// Handle VR 3-point tracking: set buffers when vr_position is present
|
| 1749 |
+
// vr_position is required to enable VR mode; orientation uses default if not provided
|
| 1750 |
+
// compliance is ALWAYS from keyboard-controlled values (ignoring ZMQ data)
|
| 1751 |
+
if (has_vr_position) {
|
| 1752 |
+
vr_3point_position_.SetData(vr_position_values);
|
| 1753 |
+
vr_3point_orientation_.SetData(vr_orientation_values);
|
| 1754 |
+
if (has_vr_compliance) SetVR3PointCompliance(vr_compliance_values);
|
| 1755 |
+
has_vr_3point_control_ = true;
|
| 1756 |
+
|
| 1757 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1758 |
+
std::cout << "[ZMQEndpointInterface] VR 3-point tracking ENABLED:" << std::endl;
|
| 1759 |
+
std::cout << " Position [L|R|H]: [";
|
| 1760 |
+
for (int j = 0; j < 9; ++j) {
|
| 1761 |
+
if (j > 0) std::cout << ", ";
|
| 1762 |
+
if (j == 3 || j == 6) std::cout << "| ";
|
| 1763 |
+
std::cout << std::fixed << std::setprecision(4) << vr_position_values[j];
|
| 1764 |
+
}
|
| 1765 |
+
std::cout << "]" << std::endl;
|
| 1766 |
+
std::cout << " Orientation [L|R|H]: [";
|
| 1767 |
+
for (int j = 0; j < 12; ++j) {
|
| 1768 |
+
if (j > 0) std::cout << ", ";
|
| 1769 |
+
if (j == 4 || j == 8) std::cout << "| ";
|
| 1770 |
+
std::cout << std::fixed << std::setprecision(4) << vr_orientation_values[j];
|
| 1771 |
+
}
|
| 1772 |
+
std::cout << "]" << (has_vr_orientation ? "" : " (default)") << std::endl;
|
| 1773 |
+
if (has_vr_compliance) {
|
| 1774 |
+
std::cout << " Compliance [L,R,H]: [";
|
| 1775 |
+
for (int j = 0; j < 3; ++j) {
|
| 1776 |
+
if (j > 0) std::cout << ", ";
|
| 1777 |
+
std::cout << std::fixed << std::setprecision(2) << vr_compliance_values[j];
|
| 1778 |
+
}
|
| 1779 |
+
std::cout << "] (keyboard-controlled)" << std::endl;
|
| 1780 |
+
}
|
| 1781 |
+
}
|
| 1782 |
+
}
|
| 1783 |
+
|
| 1784 |
+
// log the decode interval and decode time
|
| 1785 |
+
uint64_t decode_end_time = std::chrono::steady_clock::now().time_since_epoch().count() / 1000000; // milliseconds
|
| 1786 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1787 |
+
if (last_decode_time_ > 0) {
|
| 1788 |
+
uint64_t decode_time = decode_end_time - decode_start_time;
|
| 1789 |
+
uint64_t time_delta = decode_end_time - last_decode_time_;
|
| 1790 |
+
std::cout << "[ZMQEndpointInterface] Decode interval: " << time_delta << " ms, decode time: " << decode_time << " ms" << std::endl;
|
| 1791 |
+
}
|
| 1792 |
+
}
|
| 1793 |
+
last_decode_time_ = decode_end_time;
|
| 1794 |
+
|
| 1795 |
+
return result;
|
| 1796 |
+
}
|
| 1797 |
+
|
| 1798 |
+
/**
|
| 1799 |
+
* @brief ZMQ subscriber callback – invoked on the **background thread**.
|
| 1800 |
+
*
|
| 1801 |
+
* Copies the received header and buffer data into `buffered_header_` /
|
| 1802 |
+
* `buffered_buffers_` under `data_mutex_` and sets `has_new_data_` = true.
|
| 1803 |
+
* The actual decoding happens later on the main thread in handle_input().
|
| 1804 |
+
*/
|
| 1805 |
+
void OnPoseDataReceived(
|
| 1806 |
+
const std::string& topic,
|
| 1807 |
+
const ZMQPackedMessageSubscriber::DecodedHeader& hdr,
|
| 1808 |
+
const std::vector<ZMQPackedMessageSubscriber::BufferView>& bufs) {
|
| 1809 |
+
|
| 1810 |
+
std::lock_guard<std::mutex> lock(data_mutex_);
|
| 1811 |
+
|
| 1812 |
+
// Print message received info
|
| 1813 |
+
std::cout << "[ZMQEndpointInterface] Received ZMQ message - topic: '" << topic
|
| 1814 |
+
<< "', protocol_version: " << hdr.version
|
| 1815 |
+
<< ", num_fields: " << hdr.fields.size()
|
| 1816 |
+
<< ", total_size: " << bufs.size() << " buffers" << std::endl;
|
| 1817 |
+
|
| 1818 |
+
// Buffer the received data for processing in handle_input (main thread)
|
| 1819 |
+
buffered_header_ = hdr;
|
| 1820 |
+
buffered_buffers_.clear();
|
| 1821 |
+
for (const auto& buf : bufs) {
|
| 1822 |
+
// Copy buffer data (BufferView is only valid during callback)
|
| 1823 |
+
std::vector<uint8_t> copied(static_cast<const uint8_t*>(buf.data),
|
| 1824 |
+
static_cast<const uint8_t*>(buf.data) + buf.size);
|
| 1825 |
+
buffered_buffers_.push_back(std::move(copied));
|
| 1826 |
+
}
|
| 1827 |
+
|
| 1828 |
+
has_new_data_ = true;
|
| 1829 |
+
last_receive_time_ = std::chrono::steady_clock::now();
|
| 1830 |
+
receive_count_++;
|
| 1831 |
+
}
|
| 1832 |
+
|
| 1833 |
+
// ------------------------------------------------------------------
|
| 1834 |
+
// Configuration
|
| 1835 |
+
// ------------------------------------------------------------------
|
| 1836 |
+
std::string host_; ///< ZMQ server hostname.
|
| 1837 |
+
int port_; ///< ZMQ server port.
|
| 1838 |
+
std::string topic_; ///< ZMQ subscription topic.
|
| 1839 |
+
bool verbose_; ///< Verbose logging flag.
|
| 1840 |
+
|
| 1841 |
+
/// Background subscriber for the pose / motion topic.
|
| 1842 |
+
std::unique_ptr<ZMQPackedMessageSubscriber> subscriber_;
|
| 1843 |
+
|
| 1844 |
+
struct termios old_termios_; ///< Saved terminal state for restoration on destruction.
|
| 1845 |
+
|
| 1846 |
+
// ------------------------------------------------------------------
|
| 1847 |
+
// Thread-safe data buffering (written by ZMQ subscriber thread, read by input thread)
|
| 1848 |
+
// ------------------------------------------------------------------
|
| 1849 |
+
mutable std::mutex data_mutex_; ///< Guards the fields below.
|
| 1850 |
+
bool has_new_data_ = false; ///< True when a new message is waiting to be decoded.
|
| 1851 |
+
ZMQPackedMessageSubscriber::DecodedHeader buffered_header_; ///< Latest JSON header.
|
| 1852 |
+
std::vector<std::vector<uint8_t>> buffered_buffers_; ///< Copied binary field data.
|
| 1853 |
+
|
| 1854 |
+
// ------------------------------------------------------------------
|
| 1855 |
+
// Timing / diagnostics
|
| 1856 |
+
// ------------------------------------------------------------------
|
| 1857 |
+
bool is_localhost_ = true; ///< True if host_ is localhost (for directly comparing timestamps)
|
| 1858 |
+
std::optional<std::chrono::steady_clock::time_point> data_timestamp_{}; ///< Timestamp of last received message from XR source
|
| 1859 |
+
std::optional<std::chrono::steady_clock::time_point> last_receive_time_{}; ///< Timestamp of last OnPoseDataReceived (ms, monotonic).
|
| 1860 |
+
uint64_t receive_count_ = 0; ///< Total number of messages received.
|
| 1861 |
+
uint64_t last_decode_time_ = 0; ///< Timestamp of last DecodeIntoMotionSequence call (ms).
|
| 1862 |
+
|
| 1863 |
+
};
|
| 1864 |
+
|
| 1865 |
+
#endif // ZMQ_ENDPOINT_INTERFACE_HPP
|
GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/zmq_manager.hpp
ADDED
|
@@ -0,0 +1,1257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file zmq_manager.hpp
|
| 3 |
+
* @brief Network-only input manager that switches between planner mode and
|
| 4 |
+
* streamed-motion mode, both driven by ZMQ topics.
|
| 5 |
+
*
|
| 6 |
+
* ZMQManager subscribes to **three** ZMQ topics on the same host:port:
|
| 7 |
+
*
|
| 8 |
+
* Topic | Purpose
|
| 9 |
+
* -----------|--------
|
| 10 |
+
* command | High-level control (start / stop / mode switch).
|
| 11 |
+
* | Wire format: `{ start: bool, stop: bool, planner: bool, delta_heading?: f32 }`
|
| 12 |
+
* planner | Per-frame locomotion commands (mode, movement, facing, speed, height,
|
| 13 |
+
* | optional upper-body / hand / VR data). Active in PLANNER mode.
|
| 14 |
+
* pose | Streamed motion frames (joint_pos, joint_vel, body_quat, …).
|
| 15 |
+
* | Active in STREAMED_MOTION mode, handled by an internal ZMQEndpointInterface.
|
| 16 |
+
*
|
| 17 |
+
* ## Mode Switching
|
| 18 |
+
*
|
| 19 |
+
* The `planner` field in the command message selects the mode:
|
| 20 |
+
* - `planner = true` → PLANNER mode (movement commands from the planner topic).
|
| 21 |
+
* - `planner = false` → STREAMED_MOTION mode (pose data from the pose topic).
|
| 22 |
+
*
|
| 23 |
+
* On each mode switch, safety resets are triggered and the planner buffer is
|
| 24 |
+
* cleared to prevent stale commands from leaking across modes.
|
| 25 |
+
*
|
| 26 |
+
* ## Planner Timeout
|
| 27 |
+
*
|
| 28 |
+
* If no planner message arrives within 1 second (PLANNER_TIMEOUT), the manager
|
| 29 |
+
* automatically resets the locomotion to IDLE and clears upper-body / hand-joint
|
| 30 |
+
* control flags.
|
| 31 |
+
*
|
| 32 |
+
* ## Keyboard Shortcuts (via stdin)
|
| 33 |
+
*
|
| 34 |
+
* Key | Action
|
| 35 |
+
* -----|-------
|
| 36 |
+
* O/o | Emergency stop
|
| 37 |
+
* g/G, h/H | Left-hand compliance ±0.1
|
| 38 |
+
* b/B, v/V | Right-hand compliance ±0.1
|
| 39 |
+
* x/X, c/C | Hand max-close ratio ±0.1
|
| 40 |
+
*/
|
| 41 |
+
|
| 42 |
+
#ifndef ZMQ_MANAGER_HPP
|
| 43 |
+
#define ZMQ_MANAGER_HPP
|
| 44 |
+
|
| 45 |
+
#include <memory>
|
| 46 |
+
#include <vector>
|
| 47 |
+
#include <iostream>
|
| 48 |
+
#include <cstring>
|
| 49 |
+
#include <cmath>
|
| 50 |
+
#include <array>
|
| 51 |
+
#include <thread>
|
| 52 |
+
#include <chrono>
|
| 53 |
+
#include <mutex>
|
| 54 |
+
|
| 55 |
+
#include "input_interface.hpp"
|
| 56 |
+
#include "input_command.hpp"
|
| 57 |
+
#include "zmq_endpoint_interface.hpp"
|
| 58 |
+
#include "zmq_packed_message_subscriber.hpp"
|
| 59 |
+
#include "../localmotion_kplanner.hpp" // For LocomotionMode enum
|
| 60 |
+
#include "../math_utils.hpp" // For normalize_vector
|
| 61 |
+
|
| 62 |
+
#ifndef M_PI
|
| 63 |
+
#define M_PI 3.14159265358979323846
|
| 64 |
+
#endif
|
| 65 |
+
|
| 66 |
+
/**
|
| 67 |
+
* @class ZMQManager
|
| 68 |
+
* @brief InputInterface that manages two ZMQ-driven modes:
|
| 69 |
+
* PLANNER (locomotion commands) and STREAMED_MOTION (pose data).
|
| 70 |
+
*
|
| 71 |
+
* Internally owns a ZMQEndpointInterface for streamed-motion mode and two
|
| 72 |
+
* ZMQPackedMessageSubscriber instances for the command and planner topics.
|
| 73 |
+
*/
|
| 74 |
+
class ZMQManager : public InputInterface {
|
| 75 |
+
public:
|
| 76 |
+
static constexpr bool DEBUG_LOGGING = false;
|
| 77 |
+
|
| 78 |
+
enum class ManagedMode {
|
| 79 |
+
PLANNER = 0, // Planner-only mode (self-managed planner topic)
|
| 80 |
+
STREAMED_MOTION = 1 // ZMQ streamed motion mode (pose topic via ZMQEndpointInterface)
|
| 81 |
+
};
|
| 82 |
+
|
| 83 |
+
ZMQManager(
|
| 84 |
+
const std::string& zmq_host,
|
| 85 |
+
int zmq_port,
|
| 86 |
+
const std::string& pose_topic = "pose",
|
| 87 |
+
const std::string& command_topic = "command",
|
| 88 |
+
const std::string& planner_topic = "planner",
|
| 89 |
+
bool zmq_conflate = false,
|
| 90 |
+
bool zmq_verbose = false
|
| 91 |
+
) : InputInterface(),
|
| 92 |
+
zmq_host_(zmq_host),
|
| 93 |
+
zmq_port_(zmq_port),
|
| 94 |
+
pose_topic_(pose_topic),
|
| 95 |
+
command_topic_(command_topic),
|
| 96 |
+
planner_topic_(planner_topic),
|
| 97 |
+
zmq_conflate_(zmq_conflate),
|
| 98 |
+
zmq_verbose_(zmq_verbose) {
|
| 99 |
+
|
| 100 |
+
type_ = InputType::NETWORK;
|
| 101 |
+
active_mode_ = ManagedMode::PLANNER; // Default to planner mode
|
| 102 |
+
|
| 103 |
+
// Create pose interface (for streamed motion mode)
|
| 104 |
+
pose_interface_ = std::make_unique<ZMQEndpointInterface>(
|
| 105 |
+
zmq_host_, zmq_port_, pose_topic_, zmq_conflate_, zmq_verbose_
|
| 106 |
+
);
|
| 107 |
+
|
| 108 |
+
// Create command subscriber
|
| 109 |
+
command_subscriber_ = std::make_unique<ZMQPackedMessageSubscriber>(
|
| 110 |
+
zmq_host_, zmq_port_, command_topic_,
|
| 111 |
+
/*timeout_ms=*/100,
|
| 112 |
+
zmq_verbose_,
|
| 113 |
+
/*use_conflate=*/false,
|
| 114 |
+
/*rcv_hwm=*/3
|
| 115 |
+
);
|
| 116 |
+
|
| 117 |
+
command_subscriber_->SetOnDecodedMessage(
|
| 118 |
+
[this](const std::string& topic,
|
| 119 |
+
const ZMQPackedMessageSubscriber::DecodedHeader& hdr,
|
| 120 |
+
const std::vector<ZMQPackedMessageSubscriber::BufferView>& bufs) {
|
| 121 |
+
this->OnCommandReceived(topic, hdr, bufs);
|
| 122 |
+
}
|
| 123 |
+
);
|
| 124 |
+
|
| 125 |
+
command_subscriber_->Start();
|
| 126 |
+
|
| 127 |
+
// Create planner subscriber
|
| 128 |
+
planner_subscriber_ = std::make_unique<ZMQPackedMessageSubscriber>(
|
| 129 |
+
zmq_host_, zmq_port_, planner_topic_,
|
| 130 |
+
/*timeout_ms=*/100,
|
| 131 |
+
zmq_verbose_,
|
| 132 |
+
/*use_conflate=*/false,
|
| 133 |
+
/*rcv_hwm=*/3
|
| 134 |
+
);
|
| 135 |
+
|
| 136 |
+
planner_subscriber_->SetOnDecodedMessage(
|
| 137 |
+
[this](const std::string& topic,
|
| 138 |
+
const ZMQPackedMessageSubscriber::DecodedHeader& hdr,
|
| 139 |
+
const std::vector<ZMQPackedMessageSubscriber::BufferView>& bufs) {
|
| 140 |
+
this->OnPlannerReceived(topic, hdr, bufs);
|
| 141 |
+
}
|
| 142 |
+
);
|
| 143 |
+
|
| 144 |
+
planner_subscriber_->Start();
|
| 145 |
+
|
| 146 |
+
std::cout << "[ZMQManager] Initialized (default: PLANNER mode)" << std::endl;
|
| 147 |
+
std::cout << " - Host: " << zmq_host_ << ":" << zmq_port_ << std::endl;
|
| 148 |
+
std::cout << " - Command topic: '" << command_topic_ << "' (start/stop/mode)" << std::endl;
|
| 149 |
+
std::cout << " Format: { start: bool, stop: bool, planner: bool }" << std::endl;
|
| 150 |
+
std::cout << " - Planner topic: '" << planner_topic_ << "' (movement)" << std::endl;
|
| 151 |
+
std::cout << " - Pose topic: '" << pose_topic_ << "' (streamed motion)" << std::endl;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
~ZMQManager() {
|
| 155 |
+
if (command_subscriber_) command_subscriber_->Stop();
|
| 156 |
+
if (planner_subscriber_) planner_subscriber_->Stop();
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
void update() override {
|
| 160 |
+
// Reset per-frame flags
|
| 161 |
+
emergency_stop_ = false;
|
| 162 |
+
report_temperature_flag_ = false;
|
| 163 |
+
start_control_ = false;
|
| 164 |
+
stop_control_ = false;
|
| 165 |
+
|
| 166 |
+
// Handle stdin shortcuts
|
| 167 |
+
char ch;
|
| 168 |
+
while (ReadStdinChar(ch)) {
|
| 169 |
+
bool is_manager_key = false;
|
| 170 |
+
switch (ch) {
|
| 171 |
+
case 'o':
|
| 172 |
+
case 'O':
|
| 173 |
+
emergency_stop_ = true;
|
| 174 |
+
is_manager_key = true;
|
| 175 |
+
std::cout << "[ZMQManager] EMERGENCY STOP (O/o)" << std::endl;
|
| 176 |
+
break;
|
| 177 |
+
case 'f':
|
| 178 |
+
case 'F':
|
| 179 |
+
report_temperature_flag_ = true;
|
| 180 |
+
is_manager_key = true;
|
| 181 |
+
break;
|
| 182 |
+
// Global compliance controls - work across ALL modes
|
| 183 |
+
case 'g':
|
| 184 |
+
case 'G':
|
| 185 |
+
// Increase left hand compliance by 0.1
|
| 186 |
+
AdjustLeftHandCompliance(0.1);
|
| 187 |
+
is_manager_key = true;
|
| 188 |
+
break;
|
| 189 |
+
case 'h':
|
| 190 |
+
case 'H':
|
| 191 |
+
// Decrease left hand compliance by 0.1
|
| 192 |
+
AdjustLeftHandCompliance(-0.1);
|
| 193 |
+
is_manager_key = true;
|
| 194 |
+
break;
|
| 195 |
+
case 'b':
|
| 196 |
+
case 'B':
|
| 197 |
+
// Increase right hand compliance by 0.1
|
| 198 |
+
AdjustRightHandCompliance(0.1);
|
| 199 |
+
is_manager_key = true;
|
| 200 |
+
break;
|
| 201 |
+
case 'v':
|
| 202 |
+
case 'V':
|
| 203 |
+
// Decrease right hand compliance by 0.1
|
| 204 |
+
AdjustRightHandCompliance(-0.1);
|
| 205 |
+
is_manager_key = true;
|
| 206 |
+
break;
|
| 207 |
+
// Global hand max close ratio controls (x/c keys)
|
| 208 |
+
case 'x':
|
| 209 |
+
case 'X':
|
| 210 |
+
// Increase max close ratio by 0.1 (allow hands to close more)
|
| 211 |
+
AdjustMaxCloseRatio(0.1);
|
| 212 |
+
is_manager_key = true;
|
| 213 |
+
break;
|
| 214 |
+
case 'c':
|
| 215 |
+
case 'C':
|
| 216 |
+
// Decrease max close ratio by 0.1 (keep hands more open)
|
| 217 |
+
AdjustMaxCloseRatio(-0.1);
|
| 218 |
+
is_manager_key = true;
|
| 219 |
+
break;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
// Pass other keys to pose interface (only in streamed motion mode)
|
| 223 |
+
if (!is_manager_key && active_mode_ == ManagedMode::STREAMED_MOTION && pose_interface_) {
|
| 224 |
+
pose_interface_->PushStdinChar(ch);
|
| 225 |
+
}
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
// Translate received command to control flags and handle mode switching
|
| 229 |
+
bool trigger_zmq_toggle = false;
|
| 230 |
+
{
|
| 231 |
+
std::lock_guard<std::mutex> lock(command_mutex_);
|
| 232 |
+
if (latest_command_.valid) {
|
| 233 |
+
// Set control flags (already accumulated in callback)
|
| 234 |
+
if (latest_command_.start) {
|
| 235 |
+
start_control_ = true;
|
| 236 |
+
}
|
| 237 |
+
if (latest_command_.stop) {
|
| 238 |
+
stop_control_ = true;
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
// Handle mode switching
|
| 242 |
+
ManagedMode new_mode = latest_command_.planner ? ManagedMode::PLANNER : ManagedMode::STREAMED_MOTION;
|
| 243 |
+
|
| 244 |
+
if (new_mode != active_mode_) {
|
| 245 |
+
// Trigger safety reset on mode switch
|
| 246 |
+
TriggerSafetyReset();
|
| 247 |
+
if (pose_interface_) {
|
| 248 |
+
pose_interface_->TriggerSafetyReset();
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
if (new_mode == ManagedMode::PLANNER) {
|
| 252 |
+
std::cout << "[ZMQManager] Switched to: PLANNER mode (safety reset)" << std::endl;
|
| 253 |
+
if (latest_planner_message_.valid) {
|
| 254 |
+
constexpr auto PLANNER_MESSAGE_TIMEOUT = std::chrono::milliseconds(100);
|
| 255 |
+
auto time_since_last_planner = std::chrono::steady_clock::now() - latest_planner_message_.timestamp;
|
| 256 |
+
if (time_since_last_planner < PLANNER_MESSAGE_TIMEOUT) {
|
| 257 |
+
// Valid planner message within timeout - use it
|
| 258 |
+
// Update upper body control state based on this message
|
| 259 |
+
has_upper_body_control_ = latest_planner_message_.upper_body_position.has_value();
|
| 260 |
+
|
| 261 |
+
// Update hand joints control state based on this message
|
| 262 |
+
has_hand_joints_ = latest_planner_message_.left_hand_joints.has_value() ||
|
| 263 |
+
latest_planner_message_.right_hand_joints.has_value();
|
| 264 |
+
}
|
| 265 |
+
}
|
| 266 |
+
} else if (new_mode == ManagedMode::STREAMED_MOTION) {
|
| 267 |
+
std::cout << "[ZMQManager] Switched to: STREAMED MOTION mode (safety reset)" << std::endl;
|
| 268 |
+
trigger_zmq_toggle = true;
|
| 269 |
+
|
| 270 |
+
// Clear planner buffer when switching away from planner mode
|
| 271 |
+
{
|
| 272 |
+
std::lock_guard<std::mutex> lock(planner_mutex_);
|
| 273 |
+
latest_planner_message_.valid = false;
|
| 274 |
+
latest_planner_message_.timestamp = {};
|
| 275 |
+
is_planner_ready_ = false;
|
| 276 |
+
switch_from_teleop_to_planner_ = true;
|
| 277 |
+
}
|
| 278 |
+
std::cout << "[ZMQManager] Cleared planner buffer" << std::endl;
|
| 279 |
+
}
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
// Clear valid flag - next callback will start fresh accumulation
|
| 283 |
+
active_mode_ = new_mode;
|
| 284 |
+
latest_command_.valid = false;
|
| 285 |
+
}
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
// Update active interface based on mode
|
| 289 |
+
if (active_mode_ == ManagedMode::STREAMED_MOTION && pose_interface_) {
|
| 290 |
+
// In streamed motion mode: update pose interface
|
| 291 |
+
pose_interface_->update();
|
| 292 |
+
if (trigger_zmq_toggle) {
|
| 293 |
+
pose_interface_->TriggerZMQToggle();
|
| 294 |
+
std::cout << "[ZMQManager] ZMQ streaming enabled" << std::endl;
|
| 295 |
+
}
|
| 296 |
+
}
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
void handle_input(MotionDataReader& motion_reader,
|
| 300 |
+
std::shared_ptr<const MotionSequence>& current_motion,
|
| 301 |
+
int& current_frame,
|
| 302 |
+
OperatorState& operator_state,
|
| 303 |
+
bool& reinitialize_heading,
|
| 304 |
+
DataBuffer<HeadingState>& heading_state_buffer,
|
| 305 |
+
bool has_planner,
|
| 306 |
+
PlannerState& planner_state,
|
| 307 |
+
DataBuffer<MovementState>& movement_state_buffer,
|
| 308 |
+
std::mutex& current_motion_mutex,
|
| 309 |
+
bool& report_temperature) override {
|
| 310 |
+
if (!has_planner) {
|
| 311 |
+
std::cerr << "[ZMQCommandManager ERROR] Planner not available in planner mode" << std::endl;
|
| 312 |
+
operator_state.stop = true;
|
| 313 |
+
return;
|
| 314 |
+
}
|
| 315 |
+
// Emergency stop
|
| 316 |
+
if (report_temperature_flag_) {
|
| 317 |
+
report_temperature = true;
|
| 318 |
+
report_temperature_flag_ = false;
|
| 319 |
+
}
|
| 320 |
+
if (emergency_stop_) {
|
| 321 |
+
operator_state.stop = true;
|
| 322 |
+
if (planner_state.enabled) {
|
| 323 |
+
planner_state.enabled = false;
|
| 324 |
+
planner_state.initialized = false;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
// Clear planner buffer on emergency stop
|
| 328 |
+
{
|
| 329 |
+
std::lock_guard<std::mutex> lock(planner_mutex_);
|
| 330 |
+
latest_planner_message_.valid = false;
|
| 331 |
+
latest_planner_message_.timestamp = {};
|
| 332 |
+
}
|
| 333 |
+
// Clear upper body control state
|
| 334 |
+
has_upper_body_control_ = false;
|
| 335 |
+
|
| 336 |
+
// Clear hand joints control state
|
| 337 |
+
has_hand_joints_ = false;
|
| 338 |
+
|
| 339 |
+
return;
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
// Handle stop control
|
| 343 |
+
if (stop_control_) {
|
| 344 |
+
operator_state.stop = true;
|
| 345 |
+
if (planner_state.enabled) {
|
| 346 |
+
planner_state.enabled = false;
|
| 347 |
+
planner_state.initialized = false;
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
// Clear planner buffer on stop
|
| 351 |
+
{
|
| 352 |
+
std::lock_guard<std::mutex> lock(planner_mutex_);
|
| 353 |
+
latest_planner_message_.valid = false;
|
| 354 |
+
latest_planner_message_.timestamp = {};
|
| 355 |
+
}
|
| 356 |
+
// Clear upper body control state
|
| 357 |
+
has_upper_body_control_ = false;
|
| 358 |
+
|
| 359 |
+
// Clear hand joints control state
|
| 360 |
+
has_hand_joints_ = false;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
// Delegate based on current mode
|
| 364 |
+
if (active_mode_ == ManagedMode::PLANNER) {
|
| 365 |
+
// Planner mode: handle planner input ourselves
|
| 366 |
+
handlePlannerInput(motion_reader, current_motion, current_frame,
|
| 367 |
+
operator_state, reinitialize_heading,
|
| 368 |
+
heading_state_buffer,
|
| 369 |
+
has_planner, planner_state, movement_state_buffer,
|
| 370 |
+
current_motion_mutex);
|
| 371 |
+
} else {
|
| 372 |
+
// Streamed motion mode: delegate to pose interface
|
| 373 |
+
if (pose_interface_) {
|
| 374 |
+
pose_interface_->handle_input(motion_reader, current_motion, current_frame,
|
| 375 |
+
operator_state, reinitialize_heading,
|
| 376 |
+
heading_state_buffer,
|
| 377 |
+
has_planner, planner_state, movement_state_buffer,
|
| 378 |
+
current_motion_mutex, report_temperature);
|
| 379 |
+
}
|
| 380 |
+
}
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
// Forward getters to pose interface when in streamed motion mode
|
| 384 |
+
bool HasVR3PointControl() const override {
|
| 385 |
+
if ((active_mode_ == ManagedMode::STREAMED_MOTION || (!is_planner_ready_ && switch_from_teleop_to_planner_)) && pose_interface_) {
|
| 386 |
+
return pose_interface_->HasVR3PointControl();
|
| 387 |
+
}
|
| 388 |
+
return has_vr_3point_control_;
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
bool HasHandJoints() const override {
|
| 392 |
+
if ((active_mode_ == ManagedMode::STREAMED_MOTION || (!is_planner_ready_ && switch_from_teleop_to_planner_)) && pose_interface_) {
|
| 393 |
+
return pose_interface_->HasHandJoints();
|
| 394 |
+
}
|
| 395 |
+
return has_hand_joints_;
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
bool HasExternalTokenState() const override {
|
| 399 |
+
if ((active_mode_ == ManagedMode::STREAMED_MOTION || (!is_planner_ready_ && switch_from_teleop_to_planner_)) && pose_interface_) {
|
| 400 |
+
return pose_interface_->HasExternalTokenState();
|
| 401 |
+
}
|
| 402 |
+
return has_external_token_state_;
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
std::pair<bool, std::array<double, 9>> GetVR3PointPosition() const override {
|
| 406 |
+
if ((active_mode_ == ManagedMode::STREAMED_MOTION || (!is_planner_ready_ && switch_from_teleop_to_planner_)) && pose_interface_) {
|
| 407 |
+
return pose_interface_->GetVR3PointPosition();
|
| 408 |
+
}
|
| 409 |
+
return InputInterface::GetVR3PointPosition();
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
std::pair<bool, std::array<double, 12>> GetVR3PointOrientation() const override {
|
| 413 |
+
if ((active_mode_ == ManagedMode::STREAMED_MOTION || (!is_planner_ready_ && switch_from_teleop_to_planner_)) && pose_interface_) {
|
| 414 |
+
return pose_interface_->GetVR3PointOrientation();
|
| 415 |
+
}
|
| 416 |
+
return InputInterface::GetVR3PointOrientation();
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
std::array<double, 3> GetVR3PointCompliance() const override {
|
| 420 |
+
if ((active_mode_ == ManagedMode::STREAMED_MOTION || (!is_planner_ready_ && switch_from_teleop_to_planner_)) && pose_interface_) {
|
| 421 |
+
return pose_interface_->GetVR3PointCompliance();
|
| 422 |
+
}
|
| 423 |
+
return InputInterface::GetVR3PointCompliance();
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
std::pair<bool, std::array<double, 7>> GetHandPose(bool is_left) const override {
|
| 427 |
+
if ((active_mode_ == ManagedMode::STREAMED_MOTION || (!is_planner_ready_ && switch_from_teleop_to_planner_)) && pose_interface_) {
|
| 428 |
+
return pose_interface_->GetHandPose(is_left);
|
| 429 |
+
}
|
| 430 |
+
return InputInterface::GetHandPose(is_left);
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
std::pair<bool, std::vector<double>> GetExternalTokenState() const override {
|
| 434 |
+
if ((active_mode_ == ManagedMode::STREAMED_MOTION || (!is_planner_ready_ && switch_from_teleop_to_planner_)) && pose_interface_) {
|
| 435 |
+
return pose_interface_->GetExternalTokenState();
|
| 436 |
+
}
|
| 437 |
+
return InputInterface::GetExternalTokenState();
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
std::optional<std::chrono::steady_clock::time_point> GetLastUpdateTime() const override {
|
| 441 |
+
if ((active_mode_ == ManagedMode::STREAMED_MOTION) && pose_interface_) {
|
| 442 |
+
return pose_interface_->GetLastUpdateTime();
|
| 443 |
+
}
|
| 444 |
+
return InputInterface::GetLastUpdateTime();
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
private:
|
| 448 |
+
// Handle planner mode input (similar to GamepadManager::handleGamepadPlannerInput)
|
| 449 |
+
void handlePlannerInput(MotionDataReader& motion_reader,
|
| 450 |
+
std::shared_ptr<const MotionSequence>& current_motion,
|
| 451 |
+
int& current_frame,
|
| 452 |
+
OperatorState& operator_state,
|
| 453 |
+
bool& reinitialize_heading,
|
| 454 |
+
DataBuffer<HeadingState>& heading_state_buffer,
|
| 455 |
+
bool has_planner,
|
| 456 |
+
PlannerState& planner_state,
|
| 457 |
+
DataBuffer<MovementState>& movement_state_buffer,
|
| 458 |
+
std::mutex& current_motion_mutex) {
|
| 459 |
+
|
| 460 |
+
// Handle safety reset from interface manager (same as GamepadManager)
|
| 461 |
+
if (CheckAndClearSafetyReset()) {
|
| 462 |
+
{
|
| 463 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 464 |
+
operator_state.play = false;
|
| 465 |
+
}
|
| 466 |
+
if (operator_state.start) {
|
| 467 |
+
if (planner_state.enabled && planner_state.initialized) {
|
| 468 |
+
// Planner is already on, keep it as is (don't touch initialized flag)
|
| 469 |
+
{
|
| 470 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 471 |
+
if (current_motion->GetEncodeMode() == 1) {
|
| 472 |
+
current_motion->SetEncodeMode(0);
|
| 473 |
+
}
|
| 474 |
+
operator_state.play = true;
|
| 475 |
+
}
|
| 476 |
+
auto current_facing = movement_state_buffer.GetDataWithTime().data->facing_direction;
|
| 477 |
+
std::cout << "[ZMQManager] Safety reset: Planner kept enabled with current state" << std::endl;
|
| 478 |
+
} else {
|
| 479 |
+
// Planner was disabled, set initial movement state
|
| 480 |
+
movement_state_buffer.SetData(MovementState(static_cast<int>(LocomotionMode::IDLE),
|
| 481 |
+
{0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, -1.0f, -1.0f));
|
| 482 |
+
|
| 483 |
+
// Now enable planner
|
| 484 |
+
planner_state.enabled = true;
|
| 485 |
+
std::cout << "[ZMQManager] Planner enabled" << std::endl;
|
| 486 |
+
|
| 487 |
+
// Wait for planner to be initialized with timeout (5 seconds)
|
| 488 |
+
auto wait_start = std::chrono::steady_clock::now();
|
| 489 |
+
constexpr auto PLANNER_INIT_TIMEOUT = std::chrono::seconds(5);
|
| 490 |
+
while (planner_state.enabled) {
|
| 491 |
+
{
|
| 492 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 493 |
+
if (current_motion->name == "planner_motion") {
|
| 494 |
+
break;
|
| 495 |
+
}
|
| 496 |
+
}
|
| 497 |
+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
| 498 |
+
auto elapsed = std::chrono::steady_clock::now() - wait_start;
|
| 499 |
+
if (elapsed > PLANNER_INIT_TIMEOUT) {
|
| 500 |
+
std::cerr << "[ZMQCommandManager ERROR] Planner initialization timeout after 5 seconds" << std::endl;
|
| 501 |
+
operator_state.stop = true;
|
| 502 |
+
return;
|
| 503 |
+
}
|
| 504 |
+
std::cout << "[ZMQManager] Waiting for planner to be initialized" << std::endl;
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
// Check if planner is enabled and initialized
|
| 508 |
+
if (!planner_state.enabled || !planner_state.initialized) {
|
| 509 |
+
std::cerr << "[ZMQCommandManager ERROR] Planner failed to initialize. Stopping control." << std::endl;
|
| 510 |
+
operator_state.stop = true;
|
| 511 |
+
return;
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
is_planner_ready_ = true;
|
| 515 |
+
|
| 516 |
+
// Play motion
|
| 517 |
+
{
|
| 518 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 519 |
+
operator_state.play = true;
|
| 520 |
+
}
|
| 521 |
+
}
|
| 522 |
+
}
|
| 523 |
+
return;
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
// Handle start control
|
| 527 |
+
if (start_control_ && !operator_state.start) {
|
| 528 |
+
operator_state.start = true;
|
| 529 |
+
{
|
| 530 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 531 |
+
operator_state.play = false;
|
| 532 |
+
reinitialize_heading = true;
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
// Ensure planner is enabled
|
| 536 |
+
if (!planner_state.enabled) {
|
| 537 |
+
planner_state.enabled = true;
|
| 538 |
+
std::cout << "[ZMQManager] Planner enabled" << std::endl;
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
// Wait for initialization
|
| 542 |
+
auto wait_start = std::chrono::steady_clock::now();
|
| 543 |
+
constexpr auto PLANNER_INIT_TIMEOUT = std::chrono::seconds(5);
|
| 544 |
+
while (planner_state.enabled) {
|
| 545 |
+
{
|
| 546 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 547 |
+
if (current_motion->name == "planner_motion") {
|
| 548 |
+
std::cout << "[ZMQManager] motion name is planner_motion" << std::endl;
|
| 549 |
+
break;
|
| 550 |
+
}
|
| 551 |
+
}
|
| 552 |
+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
| 553 |
+
auto elapsed = std::chrono::steady_clock::now() - wait_start;
|
| 554 |
+
if (elapsed > PLANNER_INIT_TIMEOUT) {
|
| 555 |
+
std::cerr << "[ZMQCommandManager ERROR] Planner initialization timeout" << std::endl;
|
| 556 |
+
operator_state.stop = true;
|
| 557 |
+
return;
|
| 558 |
+
}
|
| 559 |
+
std::cout << "[ZMQManager] Waiting for planner to be initialized" << std::endl;
|
| 560 |
+
}
|
| 561 |
+
|
| 562 |
+
// Check if planner is enabled and initialized
|
| 563 |
+
if (!planner_state.enabled || !planner_state.initialized) {
|
| 564 |
+
std::cerr << "[ZMQCommandManager ERROR] Planner failed to initialize. Stopping control." << std::endl;
|
| 565 |
+
operator_state.stop = true;
|
| 566 |
+
return;
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
is_planner_ready_ = true;
|
| 570 |
+
|
| 571 |
+
{
|
| 572 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 573 |
+
operator_state.play = true;
|
| 574 |
+
}
|
| 575 |
+
}
|
| 576 |
+
|
| 577 |
+
// Apply planner commands if planner is ready
|
| 578 |
+
if (planner_state.enabled && planner_state.initialized) {
|
| 579 |
+
std::lock_guard<std::mutex> lock(planner_mutex_);
|
| 580 |
+
|
| 581 |
+
// Check for planner timeout (1 second)
|
| 582 |
+
constexpr auto PLANNER_TIMEOUT = std::chrono::milliseconds(1000);
|
| 583 |
+
auto time_since_last_planner = std::chrono::steady_clock::now() - latest_planner_message_.timestamp;
|
| 584 |
+
|
| 585 |
+
if (latest_planner_message_.valid) {
|
| 586 |
+
// Valid planner message within timeout - use it
|
| 587 |
+
// Update upper body control state based on this message
|
| 588 |
+
has_upper_body_control_ = latest_planner_message_.upper_body_position.has_value();
|
| 589 |
+
|
| 590 |
+
// Update hand joints control state based on this message
|
| 591 |
+
has_hand_joints_ = latest_planner_message_.left_hand_joints.has_value() ||
|
| 592 |
+
latest_planner_message_.right_hand_joints.has_value();
|
| 593 |
+
|
| 594 |
+
MovementState mode_state(
|
| 595 |
+
latest_planner_message_.mode,
|
| 596 |
+
latest_planner_message_.movement,
|
| 597 |
+
latest_planner_message_.facing,
|
| 598 |
+
latest_planner_message_.speed,
|
| 599 |
+
latest_planner_message_.height
|
| 600 |
+
);
|
| 601 |
+
|
| 602 |
+
if (is_squat_motion_mode(static_cast<LocomotionMode>(mode_state.locomotion_mode))) {
|
| 603 |
+
if (mode_state.height < 0.2) mode_state.height = 0.2;
|
| 604 |
+
}
|
| 605 |
+
if (is_static_motion_mode(static_cast<LocomotionMode>(mode_state.locomotion_mode))) {
|
| 606 |
+
mode_state.movement_speed = -1.0f;
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
// normalize facing direction and movement direction
|
| 610 |
+
mode_state.facing_direction = normalize_vector_d(mode_state.facing_direction);
|
| 611 |
+
mode_state.movement_direction = normalize_vector_d(mode_state.movement_direction);
|
| 612 |
+
|
| 613 |
+
movement_state_buffer.SetData(mode_state);
|
| 614 |
+
|
| 615 |
+
if constexpr (DEBUG_LOGGING) {
|
| 616 |
+
std::cout << "[ZMQManager] Planner command: mode=" << latest_planner_message_.mode
|
| 617 |
+
<< ", speed=" << latest_planner_message_.speed << std::endl;
|
| 618 |
+
}
|
| 619 |
+
|
| 620 |
+
// Clear planner buffer to avoid using stale data
|
| 621 |
+
latest_planner_message_.valid = false;
|
| 622 |
+
|
| 623 |
+
} else if (!latest_planner_message_.valid && time_since_last_planner >= PLANNER_TIMEOUT) {
|
| 624 |
+
// Planner timeout - reset to IDLE and clear buffer
|
| 625 |
+
has_upper_body_control_ = false;
|
| 626 |
+
|
| 627 |
+
has_hand_joints_ = false;
|
| 628 |
+
|
| 629 |
+
auto current_facing = movement_state_buffer.GetDataWithTime().data->facing_direction;
|
| 630 |
+
MovementState idle_state(
|
| 631 |
+
static_cast<int>(LocomotionMode::IDLE),
|
| 632 |
+
{0.0f, 0.0f, 0.0f},
|
| 633 |
+
current_facing,
|
| 634 |
+
-1.0f,
|
| 635 |
+
-1.0f
|
| 636 |
+
);
|
| 637 |
+
movement_state_buffer.SetData(idle_state);
|
| 638 |
+
|
| 639 |
+
if (latest_planner_message_.timestamp != std::chrono::steady_clock::time_point{}) {
|
| 640 |
+
std::cout << "[ZMQManager] Planner timeout ("
|
| 641 |
+
<< std::chrono::duration_cast<std::chrono::milliseconds>(time_since_last_planner).count()
|
| 642 |
+
<< "ms) - reset to IDLE and cleared buffer" << std::endl;
|
| 643 |
+
|
| 644 |
+
// Clear planner buffer to avoid using stale data
|
| 645 |
+
latest_planner_message_.valid = false;
|
| 646 |
+
latest_planner_message_.timestamp = {};
|
| 647 |
+
}
|
| 648 |
+
|
| 649 |
+
}
|
| 650 |
+
}
|
| 651 |
+
|
| 652 |
+
if (has_vr_3point_control_ && !last_has_vr_3point_control_) {
|
| 653 |
+
std::cout << "[ZMQManager] VR 3-point control enabled" << std::endl;
|
| 654 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 655 |
+
if (current_motion->GetEncodeMode() >= 0) {
|
| 656 |
+
current_motion->SetEncodeMode(1);
|
| 657 |
+
}
|
| 658 |
+
}
|
| 659 |
+
else if (!has_vr_3point_control_ && last_has_vr_3point_control_) {
|
| 660 |
+
std::cout << "[ZMQManager] VR 3-point control disabled" << std::endl;
|
| 661 |
+
std::lock_guard<std::mutex> lock(current_motion_mutex);
|
| 662 |
+
if (current_motion->GetEncodeMode() >= 0) {
|
| 663 |
+
current_motion->SetEncodeMode(0);
|
| 664 |
+
}
|
| 665 |
+
}
|
| 666 |
+
last_has_vr_3point_control_ = has_vr_3point_control_;
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
// Callback handlers - just update buffer, no queue
|
| 670 |
+
void OnCommandReceived(
|
| 671 |
+
const std::string& topic,
|
| 672 |
+
const ZMQPackedMessageSubscriber::DecodedHeader& hdr,
|
| 673 |
+
const std::vector<ZMQPackedMessageSubscriber::BufferView>& bufs) {
|
| 674 |
+
|
| 675 |
+
if (hdr.fields.empty() || bufs.empty()) return;
|
| 676 |
+
|
| 677 |
+
int start_idx = -1, stop_idx = -1, planner_idx = -1;
|
| 678 |
+
for (size_t i = 0; i < hdr.fields.size(); ++i) {
|
| 679 |
+
if (hdr.fields[i].name == "start") start_idx = static_cast<int>(i);
|
| 680 |
+
else if (hdr.fields[i].name == "stop") stop_idx = static_cast<int>(i);
|
| 681 |
+
else if (hdr.fields[i].name == "planner") planner_idx = static_cast<int>(i);
|
| 682 |
+
}
|
| 683 |
+
|
| 684 |
+
if (start_idx < 0 || stop_idx < 0 || planner_idx < 0) {
|
| 685 |
+
std::cerr << "[ZMQManager] Command missing fields (need: start, stop, planner)" << std::endl;
|
| 686 |
+
return;
|
| 687 |
+
}
|
| 688 |
+
|
| 689 |
+
CommandMessage cmd;
|
| 690 |
+
cmd.valid = true;
|
| 691 |
+
|
| 692 |
+
bool needs_swap = hdr.NeedsByteSwap();
|
| 693 |
+
|
| 694 |
+
// Decode start
|
| 695 |
+
const auto& start_buf = bufs[start_idx];
|
| 696 |
+
const auto& start_field = hdr.fields[start_idx];
|
| 697 |
+
if (start_field.dtype == "bool" || start_field.dtype == "u8") {
|
| 698 |
+
uint8_t val = 0;
|
| 699 |
+
if (start_buf.size >= sizeof(uint8_t)) {
|
| 700 |
+
std::memcpy(&val, start_buf.data, sizeof(uint8_t));
|
| 701 |
+
cmd.start = (val != 0);
|
| 702 |
+
}
|
| 703 |
+
} else if (start_field.dtype == "i32") {
|
| 704 |
+
int32_t val = 0;
|
| 705 |
+
if (start_buf.size >= sizeof(int32_t)) {
|
| 706 |
+
std::memcpy(&val, start_buf.data, sizeof(int32_t));
|
| 707 |
+
if (needs_swap) val = byte_swap(val);
|
| 708 |
+
cmd.start = (val != 0);
|
| 709 |
+
}
|
| 710 |
+
}
|
| 711 |
+
|
| 712 |
+
// Decode stop
|
| 713 |
+
const auto& stop_buf = bufs[stop_idx];
|
| 714 |
+
const auto& stop_field = hdr.fields[stop_idx];
|
| 715 |
+
if (stop_field.dtype == "bool" || stop_field.dtype == "u8") {
|
| 716 |
+
uint8_t val = 0;
|
| 717 |
+
if (stop_buf.size >= sizeof(uint8_t)) {
|
| 718 |
+
std::memcpy(&val, stop_buf.data, sizeof(uint8_t));
|
| 719 |
+
cmd.stop = (val != 0);
|
| 720 |
+
}
|
| 721 |
+
} else if (stop_field.dtype == "i32") {
|
| 722 |
+
int32_t val = 0;
|
| 723 |
+
if (stop_buf.size >= sizeof(int32_t)) {
|
| 724 |
+
std::memcpy(&val, stop_buf.data, sizeof(int32_t));
|
| 725 |
+
if (needs_swap) val = byte_swap(val);
|
| 726 |
+
cmd.stop = (val != 0);
|
| 727 |
+
}
|
| 728 |
+
}
|
| 729 |
+
|
| 730 |
+
// Decode planner
|
| 731 |
+
const auto& planner_buf = bufs[planner_idx];
|
| 732 |
+
const auto& planner_field = hdr.fields[planner_idx];
|
| 733 |
+
if (planner_field.dtype == "bool" || planner_field.dtype == "u8") {
|
| 734 |
+
uint8_t val = 0;
|
| 735 |
+
if (planner_buf.size >= sizeof(uint8_t)) {
|
| 736 |
+
std::memcpy(&val, planner_buf.data, sizeof(uint8_t));
|
| 737 |
+
cmd.planner = (val != 0);
|
| 738 |
+
}
|
| 739 |
+
} else if (planner_field.dtype == "i32") {
|
| 740 |
+
int32_t val = 0;
|
| 741 |
+
if (planner_buf.size >= sizeof(int32_t)) {
|
| 742 |
+
std::memcpy(&val, planner_buf.data, sizeof(int32_t));
|
| 743 |
+
if (needs_swap) val = byte_swap(val);
|
| 744 |
+
cmd.planner = (val != 0);
|
| 745 |
+
}
|
| 746 |
+
}
|
| 747 |
+
|
| 748 |
+
// Update buffer with OR logic to accumulate start/stop signals
|
| 749 |
+
std::lock_guard<std::mutex> lock(command_mutex_);
|
| 750 |
+
|
| 751 |
+
// If starting new accumulation cycle, reset start/stop
|
| 752 |
+
if (!latest_command_.valid) {
|
| 753 |
+
latest_command_.start = false;
|
| 754 |
+
latest_command_.stop = false;
|
| 755 |
+
}
|
| 756 |
+
|
| 757 |
+
// Accumulate start/stop with OR logic
|
| 758 |
+
latest_command_.start = latest_command_.start || cmd.start;
|
| 759 |
+
latest_command_.stop = latest_command_.stop || cmd.stop;
|
| 760 |
+
latest_command_.planner = cmd.planner; // Overwrite (mode should be latest)
|
| 761 |
+
latest_command_.valid = true;
|
| 762 |
+
|
| 763 |
+
if constexpr (DEBUG_LOGGING) {
|
| 764 |
+
std::cout << "[ZMQManager] Command received: start=" << cmd.start
|
| 765 |
+
<< ", stop=" << cmd.stop << ", planner=" << cmd.planner << std::endl;
|
| 766 |
+
}
|
| 767 |
+
}
|
| 768 |
+
|
| 769 |
+
void OnPlannerReceived(
|
| 770 |
+
const std::string& topic,
|
| 771 |
+
const ZMQPackedMessageSubscriber::DecodedHeader& hdr,
|
| 772 |
+
const std::vector<ZMQPackedMessageSubscriber::BufferView>& bufs) {
|
| 773 |
+
|
| 774 |
+
int mode_idx = -1, movement_idx = -1, facing_idx = -1;
|
| 775 |
+
int speed_idx = -1, height_idx = -1;
|
| 776 |
+
int upper_body_position_idx = -1, upper_body_velocity_idx = -1;
|
| 777 |
+
int left_hand_joints_idx = -1, right_hand_joints_idx = -1;
|
| 778 |
+
int vr_position_idx = -1, vr_orientation_idx = -1, vr_compliance_idx = -1;
|
| 779 |
+
|
| 780 |
+
for (size_t i = 0; i < hdr.fields.size(); ++i) {
|
| 781 |
+
const auto& f = hdr.fields[i];
|
| 782 |
+
if (f.name == "mode") mode_idx = static_cast<int>(i);
|
| 783 |
+
else if (f.name == "movement") movement_idx = static_cast<int>(i);
|
| 784 |
+
else if (f.name == "facing") facing_idx = static_cast<int>(i);
|
| 785 |
+
else if (f.name == "speed") speed_idx = static_cast<int>(i);
|
| 786 |
+
else if (f.name == "height") height_idx = static_cast<int>(i);
|
| 787 |
+
else if (f.name == "upper_body_position") upper_body_position_idx = static_cast<int>(i);
|
| 788 |
+
else if (f.name == "upper_body_velocity") upper_body_velocity_idx = static_cast<int>(i);
|
| 789 |
+
else if (f.name == "left_hand_joints") left_hand_joints_idx = static_cast<int>(i);
|
| 790 |
+
else if (f.name == "right_hand_joints") right_hand_joints_idx = static_cast<int>(i);
|
| 791 |
+
else if (f.name == "vr_position") vr_position_idx = static_cast<int>(i);
|
| 792 |
+
else if (f.name == "vr_orientation") vr_orientation_idx = static_cast<int>(i);
|
| 793 |
+
else if (f.name == "vr_compliance") vr_compliance_idx = static_cast<int>(i);
|
| 794 |
+
}
|
| 795 |
+
|
| 796 |
+
if (mode_idx < 0 || movement_idx < 0 || facing_idx < 0) {
|
| 797 |
+
std::cerr << "[ZMQManager] Planner missing required fields" << std::endl;
|
| 798 |
+
return;
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
PlannerMessage msg;
|
| 802 |
+
msg.valid = true;
|
| 803 |
+
|
| 804 |
+
bool needs_swap = hdr.NeedsByteSwap();
|
| 805 |
+
|
| 806 |
+
// Decode mode
|
| 807 |
+
const auto& mode_buf = bufs[mode_idx];
|
| 808 |
+
int32_t mode_val;
|
| 809 |
+
std::memcpy(&mode_val, mode_buf.data, sizeof(int32_t));
|
| 810 |
+
if (needs_swap) mode_val = byte_swap(mode_val);
|
| 811 |
+
msg.mode = static_cast<int>(mode_val);
|
| 812 |
+
|
| 813 |
+
// Decode movement based on dtype
|
| 814 |
+
const auto& movement_buf = bufs[movement_idx];
|
| 815 |
+
const auto& movement_field = hdr.fields[movement_idx];
|
| 816 |
+
if (movement_field.dtype == "f32") {
|
| 817 |
+
for (int i = 0; i < 3; ++i) {
|
| 818 |
+
float val;
|
| 819 |
+
std::memcpy(&val, static_cast<const uint8_t*>(movement_buf.data) + i * sizeof(float), sizeof(float));
|
| 820 |
+
if (needs_swap) val = byte_swap(val);
|
| 821 |
+
msg.movement[i] = static_cast<double>(val);
|
| 822 |
+
}
|
| 823 |
+
} else { // f64 or default
|
| 824 |
+
for (int i = 0; i < 3; ++i) {
|
| 825 |
+
double val;
|
| 826 |
+
std::memcpy(&val, static_cast<const uint8_t*>(movement_buf.data) + i * sizeof(double), sizeof(double));
|
| 827 |
+
if (needs_swap) val = byte_swap(val);
|
| 828 |
+
msg.movement[i] = val;
|
| 829 |
+
}
|
| 830 |
+
}
|
| 831 |
+
|
| 832 |
+
// Decode facing based on dtype
|
| 833 |
+
const auto& facing_buf = bufs[facing_idx];
|
| 834 |
+
const auto& facing_field = hdr.fields[facing_idx];
|
| 835 |
+
if (facing_field.dtype == "f32") {
|
| 836 |
+
for (int i = 0; i < 3; ++i) {
|
| 837 |
+
float val;
|
| 838 |
+
std::memcpy(&val, static_cast<const uint8_t*>(facing_buf.data) + i * sizeof(float), sizeof(float));
|
| 839 |
+
if (needs_swap) val = byte_swap(val);
|
| 840 |
+
msg.facing[i] = static_cast<double>(val);
|
| 841 |
+
}
|
| 842 |
+
} else { // f64 or default
|
| 843 |
+
for (int i = 0; i < 3; ++i) {
|
| 844 |
+
double val;
|
| 845 |
+
std::memcpy(&val, static_cast<const uint8_t*>(facing_buf.data) + i * sizeof(double), sizeof(double));
|
| 846 |
+
if (needs_swap) val = byte_swap(val);
|
| 847 |
+
msg.facing[i] = val;
|
| 848 |
+
}
|
| 849 |
+
}
|
| 850 |
+
|
| 851 |
+
// Optional: speed (decode based on dtype)
|
| 852 |
+
if (speed_idx >= 0) {
|
| 853 |
+
const auto& speed_buf = bufs[speed_idx];
|
| 854 |
+
const auto& speed_field = hdr.fields[speed_idx];
|
| 855 |
+
if (speed_field.dtype == "f32") {
|
| 856 |
+
float val;
|
| 857 |
+
std::memcpy(&val, speed_buf.data, sizeof(float));
|
| 858 |
+
if (needs_swap) val = byte_swap(val);
|
| 859 |
+
msg.speed = static_cast<double>(val);
|
| 860 |
+
} else { // f64 or default
|
| 861 |
+
double val;
|
| 862 |
+
std::memcpy(&val, speed_buf.data, sizeof(double));
|
| 863 |
+
if (needs_swap) val = byte_swap(val);
|
| 864 |
+
msg.speed = val;
|
| 865 |
+
}
|
| 866 |
+
}
|
| 867 |
+
|
| 868 |
+
// Optional: height (decode based on dtype)
|
| 869 |
+
if (height_idx >= 0) {
|
| 870 |
+
const auto& height_buf = bufs[height_idx];
|
| 871 |
+
const auto& height_field = hdr.fields[height_idx];
|
| 872 |
+
if (height_field.dtype == "f32") {
|
| 873 |
+
float val;
|
| 874 |
+
std::memcpy(&val, height_buf.data, sizeof(float));
|
| 875 |
+
if (needs_swap) val = byte_swap(val);
|
| 876 |
+
msg.height = static_cast<double>(val);
|
| 877 |
+
} else { // f64 or default
|
| 878 |
+
double val;
|
| 879 |
+
std::memcpy(&val, height_buf.data, sizeof(double));
|
| 880 |
+
if (needs_swap) val = byte_swap(val);
|
| 881 |
+
msg.height = val;
|
| 882 |
+
}
|
| 883 |
+
}
|
| 884 |
+
|
| 885 |
+
// Optional: upper_body_position (17 DOF, decode based on dtype)
|
| 886 |
+
if (upper_body_position_idx >= 0) {
|
| 887 |
+
const auto& ub_pos_buf = bufs[upper_body_position_idx];
|
| 888 |
+
const auto& ub_pos_field = hdr.fields[upper_body_position_idx];
|
| 889 |
+
|
| 890 |
+
std::array<double, 17> upper_body_position_data{};
|
| 891 |
+
if (ub_pos_field.dtype == "f32") {
|
| 892 |
+
for (int i = 0; i < 17; ++i) {
|
| 893 |
+
float val;
|
| 894 |
+
std::memcpy(&val,
|
| 895 |
+
static_cast<const uint8_t*>(ub_pos_buf.data) + i * sizeof(float),
|
| 896 |
+
sizeof(float));
|
| 897 |
+
if (needs_swap) val = byte_swap(val);
|
| 898 |
+
upper_body_position_data[i] = static_cast<double>(val);
|
| 899 |
+
}
|
| 900 |
+
} else { // f64 or default
|
| 901 |
+
for (int i = 0; i < 17; ++i) {
|
| 902 |
+
double val;
|
| 903 |
+
std::memcpy(&val,
|
| 904 |
+
static_cast<const uint8_t*>(ub_pos_buf.data) + i * sizeof(double),
|
| 905 |
+
sizeof(double));
|
| 906 |
+
if (needs_swap) val = byte_swap(val);
|
| 907 |
+
upper_body_position_data[i] = val;
|
| 908 |
+
}
|
| 909 |
+
}
|
| 910 |
+
msg.upper_body_position = upper_body_position_data;
|
| 911 |
+
|
| 912 |
+
// Push into upper-body position buffer
|
| 913 |
+
upper_body_joint_positions_.SetData(upper_body_position_data);
|
| 914 |
+
}
|
| 915 |
+
|
| 916 |
+
// Optional: upper_body_velocity (17 DOF, decode based on dtype)
|
| 917 |
+
if (upper_body_velocity_idx >= 0) {
|
| 918 |
+
const auto& ub_vel_buf = bufs[upper_body_velocity_idx];
|
| 919 |
+
const auto& ub_vel_field = hdr.fields[upper_body_velocity_idx];
|
| 920 |
+
|
| 921 |
+
std::array<double, 17> upper_body_velocity_data{};
|
| 922 |
+
if (ub_vel_field.dtype == "f32") {
|
| 923 |
+
for (int i = 0; i < 17; ++i) {
|
| 924 |
+
float val;
|
| 925 |
+
std::memcpy(&val,
|
| 926 |
+
static_cast<const uint8_t*>(ub_vel_buf.data) + i * sizeof(float),
|
| 927 |
+
sizeof(float));
|
| 928 |
+
if (needs_swap) val = byte_swap(val);
|
| 929 |
+
upper_body_velocity_data[i] = static_cast<double>(val);
|
| 930 |
+
}
|
| 931 |
+
} else { // f64 or default
|
| 932 |
+
for (int i = 0; i < 17; ++i) {
|
| 933 |
+
double val;
|
| 934 |
+
std::memcpy(&val,
|
| 935 |
+
static_cast<const uint8_t*>(ub_vel_buf.data) + i * sizeof(double),
|
| 936 |
+
sizeof(double));
|
| 937 |
+
if (needs_swap) val = byte_swap(val);
|
| 938 |
+
upper_body_velocity_data[i] = val;
|
| 939 |
+
}
|
| 940 |
+
}
|
| 941 |
+
msg.upper_body_velocity = upper_body_velocity_data;
|
| 942 |
+
|
| 943 |
+
// Push into upper-body velocity buffer
|
| 944 |
+
upper_body_joint_velocities_.SetData(upper_body_velocity_data);
|
| 945 |
+
}
|
| 946 |
+
|
| 947 |
+
// Optional: left_hand_joints (7 DOF, decode based on dtype)
|
| 948 |
+
if (left_hand_joints_idx >= 0) {
|
| 949 |
+
const auto& lh_buf = bufs[left_hand_joints_idx];
|
| 950 |
+
const auto& lh_field = hdr.fields[left_hand_joints_idx];
|
| 951 |
+
|
| 952 |
+
std::array<double, 7> left_hand_joints_data{};
|
| 953 |
+
if (lh_field.dtype == "f32") {
|
| 954 |
+
for (int i = 0; i < 7; ++i) {
|
| 955 |
+
float val;
|
| 956 |
+
std::memcpy(&val,
|
| 957 |
+
static_cast<const uint8_t*>(lh_buf.data) + i * sizeof(float),
|
| 958 |
+
sizeof(float));
|
| 959 |
+
if (needs_swap) val = byte_swap(val);
|
| 960 |
+
left_hand_joints_data[i] = static_cast<double>(val);
|
| 961 |
+
}
|
| 962 |
+
} else { // f64 or default
|
| 963 |
+
for (int i = 0; i < 7; ++i) {
|
| 964 |
+
double val;
|
| 965 |
+
std::memcpy(&val,
|
| 966 |
+
static_cast<const uint8_t*>(lh_buf.data) + i * sizeof(double),
|
| 967 |
+
sizeof(double));
|
| 968 |
+
if (needs_swap) val = byte_swap(val);
|
| 969 |
+
left_hand_joints_data[i] = val;
|
| 970 |
+
}
|
| 971 |
+
}
|
| 972 |
+
msg.left_hand_joints = left_hand_joints_data;
|
| 973 |
+
|
| 974 |
+
// Push into left hand joint buffer
|
| 975 |
+
left_hand_joint_.SetData(left_hand_joints_data);
|
| 976 |
+
}
|
| 977 |
+
|
| 978 |
+
// Optional: right_hand_joints (7 DOF, decode based on dtype)
|
| 979 |
+
if (right_hand_joints_idx >= 0) {
|
| 980 |
+
const auto& rh_buf = bufs[right_hand_joints_idx];
|
| 981 |
+
const auto& rh_field = hdr.fields[right_hand_joints_idx];
|
| 982 |
+
|
| 983 |
+
std::array<double, 7> right_hand_joints_data{};
|
| 984 |
+
if (rh_field.dtype == "f32") {
|
| 985 |
+
for (int i = 0; i < 7; ++i) {
|
| 986 |
+
float val;
|
| 987 |
+
std::memcpy(&val,
|
| 988 |
+
static_cast<const uint8_t*>(rh_buf.data) + i * sizeof(float),
|
| 989 |
+
sizeof(float));
|
| 990 |
+
if (needs_swap) val = byte_swap(val);
|
| 991 |
+
right_hand_joints_data[i] = static_cast<double>(val);
|
| 992 |
+
}
|
| 993 |
+
} else { // f64 or default
|
| 994 |
+
for (int i = 0; i < 7; ++i) {
|
| 995 |
+
double val;
|
| 996 |
+
std::memcpy(&val,
|
| 997 |
+
static_cast<const uint8_t*>(rh_buf.data) + i * sizeof(double),
|
| 998 |
+
sizeof(double));
|
| 999 |
+
if (needs_swap) val = byte_swap(val);
|
| 1000 |
+
right_hand_joints_data[i] = val;
|
| 1001 |
+
}
|
| 1002 |
+
}
|
| 1003 |
+
msg.right_hand_joints = right_hand_joints_data;
|
| 1004 |
+
|
| 1005 |
+
// Push into right hand joint buffer
|
| 1006 |
+
right_hand_joint_.SetData(right_hand_joints_data);
|
| 1007 |
+
}
|
| 1008 |
+
|
| 1009 |
+
// Decode VR 3-point tracking data if present (9 doubles for position, 12 doubles for orientation, 3 doubles for compliance)
|
| 1010 |
+
// Use default values from InputInterface as fallback
|
| 1011 |
+
bool has_vr_position = (vr_position_idx >= 0);
|
| 1012 |
+
bool has_vr_orientation = (vr_orientation_idx >= 0);
|
| 1013 |
+
bool has_vr_compliance = (vr_compliance_idx >= 0);
|
| 1014 |
+
// Default values from input_interface.hpp
|
| 1015 |
+
std::array<double, 9> vr_position_values = GetVR3PointPosition().second;
|
| 1016 |
+
std::array<double, 12> vr_orientation_values = GetVR3PointOrientation().second;
|
| 1017 |
+
std::array<double, 3> vr_compliance_values = GetVR3PointCompliance();
|
| 1018 |
+
|
| 1019 |
+
if (has_vr_position) {
|
| 1020 |
+
const auto& vr_pos_field = hdr.fields[vr_position_idx];
|
| 1021 |
+
const auto& vr_pos_buf = bufs[vr_position_idx];
|
| 1022 |
+
|
| 1023 |
+
// Validate shape: expect [9] or [1, 9]
|
| 1024 |
+
int num_vr_pos_values = 0;
|
| 1025 |
+
if (vr_pos_field.shape.size() == 1 && vr_pos_field.shape[0] == 9) {
|
| 1026 |
+
num_vr_pos_values = 9;
|
| 1027 |
+
} else if (vr_pos_field.shape.size() == 2 && vr_pos_field.shape[1] == 9) {
|
| 1028 |
+
num_vr_pos_values = 9;
|
| 1029 |
+
}
|
| 1030 |
+
|
| 1031 |
+
if (num_vr_pos_values == 9) {
|
| 1032 |
+
// Decode 9 position values
|
| 1033 |
+
if (vr_pos_field.dtype == "f32") {
|
| 1034 |
+
for (int j = 0; j < 9; ++j) {
|
| 1035 |
+
float val;
|
| 1036 |
+
std::memcpy(&val, static_cast<const uint8_t*>(vr_pos_buf.data) + j * sizeof(float), sizeof(float));
|
| 1037 |
+
if (needs_swap) val = byte_swap(val);
|
| 1038 |
+
vr_position_values[j] = static_cast<double>(val);
|
| 1039 |
+
}
|
| 1040 |
+
} else { // f64 or default
|
| 1041 |
+
for (int j = 0; j < 9; ++j) {
|
| 1042 |
+
double val;
|
| 1043 |
+
std::memcpy(&val, static_cast<const uint8_t*>(vr_pos_buf.data) + j * sizeof(double), sizeof(double));
|
| 1044 |
+
if (needs_swap) val = byte_swap(val);
|
| 1045 |
+
vr_position_values[j] = val;
|
| 1046 |
+
}
|
| 1047 |
+
}
|
| 1048 |
+
|
| 1049 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1050 |
+
std::cout << "[ZMQManager] Decoded vr_position: [";
|
| 1051 |
+
for (int j = 0; j < 9; ++j) {
|
| 1052 |
+
if (j > 0) std::cout << ", ";
|
| 1053 |
+
std::cout << std::fixed << std::setprecision(4) << vr_position_values[j];
|
| 1054 |
+
}
|
| 1055 |
+
std::cout << "]" << std::endl;
|
| 1056 |
+
}
|
| 1057 |
+
} else {
|
| 1058 |
+
std::cerr << "[ZMQManager] Invalid vr_position shape" << std::endl;
|
| 1059 |
+
has_vr_position = false;
|
| 1060 |
+
}
|
| 1061 |
+
}
|
| 1062 |
+
|
| 1063 |
+
if (has_vr_orientation) {
|
| 1064 |
+
const auto& vr_orient_field = hdr.fields[vr_orientation_idx];
|
| 1065 |
+
const auto& vr_orient_buf = bufs[vr_orientation_idx];
|
| 1066 |
+
|
| 1067 |
+
// Validate shape: expect [12] or [1, 12]
|
| 1068 |
+
int num_vr_orient_values = 0;
|
| 1069 |
+
if (vr_orient_field.shape.size() == 1 && vr_orient_field.shape[0] == 12) {
|
| 1070 |
+
num_vr_orient_values = 12;
|
| 1071 |
+
} else if (vr_orient_field.shape.size() == 2 && vr_orient_field.shape[1] == 12) {
|
| 1072 |
+
num_vr_orient_values = 12;
|
| 1073 |
+
}
|
| 1074 |
+
|
| 1075 |
+
if (num_vr_orient_values == 12) {
|
| 1076 |
+
// Decode 12 orientation values (quaternions)
|
| 1077 |
+
if (vr_orient_field.dtype == "f32") {
|
| 1078 |
+
for (int j = 0; j < 12; ++j) {
|
| 1079 |
+
float val;
|
| 1080 |
+
std::memcpy(&val, static_cast<const uint8_t*>(vr_orient_buf.data) + j * sizeof(float), sizeof(float));
|
| 1081 |
+
if (needs_swap) val = byte_swap(val);
|
| 1082 |
+
vr_orientation_values[j] = static_cast<double>(val);
|
| 1083 |
+
}
|
| 1084 |
+
} else { // f64 or default
|
| 1085 |
+
for (int j = 0; j < 12; ++j) {
|
| 1086 |
+
double val;
|
| 1087 |
+
std::memcpy(&val, static_cast<const uint8_t*>(vr_orient_buf.data) + j * sizeof(double), sizeof(double));
|
| 1088 |
+
if (needs_swap) val = byte_swap(val);
|
| 1089 |
+
vr_orientation_values[j] = val;
|
| 1090 |
+
}
|
| 1091 |
+
}
|
| 1092 |
+
|
| 1093 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1094 |
+
std::cout << "[ZMQManager] Decoded vr_orientation: [";
|
| 1095 |
+
for (int j = 0; j < 12; ++j) {
|
| 1096 |
+
if (j > 0) std::cout << ", ";
|
| 1097 |
+
std::cout << std::fixed << std::setprecision(4) << vr_orientation_values[j];
|
| 1098 |
+
}
|
| 1099 |
+
std::cout << "]" << std::endl;
|
| 1100 |
+
}
|
| 1101 |
+
} else {
|
| 1102 |
+
std::cerr << "[ZMQManager] Invalid vr_orientation shape" << std::endl;
|
| 1103 |
+
has_vr_orientation = false;
|
| 1104 |
+
}
|
| 1105 |
+
}
|
| 1106 |
+
|
| 1107 |
+
if (has_vr_compliance) {
|
| 1108 |
+
const auto& vr_compl_field = hdr.fields[vr_compliance_idx];
|
| 1109 |
+
const auto& vr_compl_buf = bufs[vr_compliance_idx];
|
| 1110 |
+
|
| 1111 |
+
// Validate shape: expect [3] or [1, 3]
|
| 1112 |
+
int num_vr_compl_values = 0;
|
| 1113 |
+
if (vr_compl_field.shape.size() == 1 && vr_compl_field.shape[0] == 3) {
|
| 1114 |
+
num_vr_compl_values = 3;
|
| 1115 |
+
} else if (vr_compl_field.shape.size() == 2 && vr_compl_field.shape[1] == 3) {
|
| 1116 |
+
num_vr_compl_values = 3;
|
| 1117 |
+
}
|
| 1118 |
+
|
| 1119 |
+
if (num_vr_compl_values == 3) {
|
| 1120 |
+
// Decode 3 compliance values
|
| 1121 |
+
if (vr_compl_field.dtype == "f32") {
|
| 1122 |
+
for (int j = 0; j < 3; ++j) {
|
| 1123 |
+
float val;
|
| 1124 |
+
std::memcpy(&val, static_cast<const uint8_t*>(vr_compl_buf.data) + j * sizeof(float), sizeof(float));
|
| 1125 |
+
if (needs_swap) val = byte_swap(val);
|
| 1126 |
+
vr_compliance_values[j] = static_cast<double>(val);
|
| 1127 |
+
}
|
| 1128 |
+
} else { // f64 or default
|
| 1129 |
+
for (int j = 0; j < 3; ++j) {
|
| 1130 |
+
double val;
|
| 1131 |
+
std::memcpy(&val, static_cast<const uint8_t*>(vr_compl_buf.data) + j * sizeof(double), sizeof(double));
|
| 1132 |
+
if (needs_swap) val = byte_swap(val);
|
| 1133 |
+
vr_compliance_values[j] = val;
|
| 1134 |
+
}
|
| 1135 |
+
}
|
| 1136 |
+
|
| 1137 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1138 |
+
std::cout << "[ZMQManager] Decoded vr_compliance: [";
|
| 1139 |
+
for (int j = 0; j < 3; ++j) {
|
| 1140 |
+
if (j > 0) std::cout << ", ";
|
| 1141 |
+
std::cout << std::fixed << std::setprecision(4) << vr_compliance_values[j];
|
| 1142 |
+
}
|
| 1143 |
+
std::cout << "]" << std::endl;
|
| 1144 |
+
}
|
| 1145 |
+
} else {
|
| 1146 |
+
std::cerr << "[ZMQManager] Invalid vr_compliance shape" << std::endl;
|
| 1147 |
+
has_vr_compliance = false;
|
| 1148 |
+
}
|
| 1149 |
+
}
|
| 1150 |
+
|
| 1151 |
+
// Handle VR 3-point tracking: vr_position is required, orientation and compliance are optional
|
| 1152 |
+
// If vr_position is present, set has_vr_3point_control_ = true and update all buffers
|
| 1153 |
+
if (has_vr_position) {
|
| 1154 |
+
// Always update all three buffers when VR position is present
|
| 1155 |
+
// (orientation and compliance will use defaults if not provided)
|
| 1156 |
+
vr_3point_position_.SetData(vr_position_values);
|
| 1157 |
+
vr_3point_orientation_.SetData(vr_orientation_values);
|
| 1158 |
+
if (has_vr_compliance) {
|
| 1159 |
+
SetVR3PointCompliance(vr_compliance_values);
|
| 1160 |
+
}
|
| 1161 |
+
has_vr_3point_control_ = true;
|
| 1162 |
+
|
| 1163 |
+
pose_interface_->SetVR3PointPosition(vr_position_values);
|
| 1164 |
+
pose_interface_->SetVR3PointOrientation(vr_orientation_values);
|
| 1165 |
+
pose_interface_->SetVR3PointCompliance(vr_compliance_values);
|
| 1166 |
+
|
| 1167 |
+
if constexpr (DEBUG_LOGGING) {
|
| 1168 |
+
std::cout << "[ZMQManager] VR 3-point data updated:" << std::endl;
|
| 1169 |
+
std::cout << " Position (left, right, head): [";
|
| 1170 |
+
for (int j = 0; j < 9; ++j) {
|
| 1171 |
+
if (j > 0) std::cout << ", ";
|
| 1172 |
+
std::cout << std::fixed << std::setprecision(4) << vr_position_values[j];
|
| 1173 |
+
}
|
| 1174 |
+
std::cout << "]" << (has_vr_position ? " (from message)" : " (default)") << std::endl;
|
| 1175 |
+
|
| 1176 |
+
std::cout << " Orientation (left quat, right quat, head quat): [";
|
| 1177 |
+
for (int j = 0; j < 12; ++j) {
|
| 1178 |
+
if (j > 0) std::cout << ", ";
|
| 1179 |
+
std::cout << std::fixed << std::setprecision(4) << vr_orientation_values[j];
|
| 1180 |
+
}
|
| 1181 |
+
std::cout << "]" << (has_vr_orientation ? " (from message)" : " (default)") << std::endl;
|
| 1182 |
+
if (has_vr_compliance) {
|
| 1183 |
+
std::cout << " Compliance (left, right, head): [";
|
| 1184 |
+
for (int j = 0; j < 3; ++j) {
|
| 1185 |
+
if (j > 0) std::cout << ", ";
|
| 1186 |
+
std::cout << std::fixed << std::setprecision(4) << vr_compliance_values[j];
|
| 1187 |
+
}
|
| 1188 |
+
std::cout << "]" << (has_vr_compliance ? " (from message)" : " (default)") << std::endl;
|
| 1189 |
+
}
|
| 1190 |
+
}
|
| 1191 |
+
}
|
| 1192 |
+
else {
|
| 1193 |
+
// No VR position provided - disable VR 3-point control
|
| 1194 |
+
has_vr_3point_control_ = false;
|
| 1195 |
+
}
|
| 1196 |
+
|
| 1197 |
+
// Update buffer directly (no queue) and set timestamp
|
| 1198 |
+
std::lock_guard<std::mutex> lock(planner_mutex_);
|
| 1199 |
+
latest_planner_message_ = msg;
|
| 1200 |
+
latest_planner_message_.timestamp = std::chrono::steady_clock::now();
|
| 1201 |
+
}
|
| 1202 |
+
|
| 1203 |
+
|
| 1204 |
+
private:
|
| 1205 |
+
// ------------------------------------------------------------------
|
| 1206 |
+
// Configuration (set once in constructor)
|
| 1207 |
+
// ------------------------------------------------------------------
|
| 1208 |
+
std::string zmq_host_; ///< ZMQ server hostname.
|
| 1209 |
+
int zmq_port_; ///< ZMQ server port.
|
| 1210 |
+
std::string pose_topic_; ///< Topic for streamed motion data.
|
| 1211 |
+
std::string command_topic_; ///< Topic for start / stop / mode commands.
|
| 1212 |
+
std::string planner_topic_; ///< Topic for planner movement commands.
|
| 1213 |
+
bool zmq_conflate_; ///< ZMQ conflate option for pose topic.
|
| 1214 |
+
bool zmq_verbose_; ///< Verbose logging flag.
|
| 1215 |
+
|
| 1216 |
+
// ------------------------------------------------------------------
|
| 1217 |
+
// Owned sub-components
|
| 1218 |
+
// ------------------------------------------------------------------
|
| 1219 |
+
/// Pose-streaming interface (handles STREAMED_MOTION mode internally).
|
| 1220 |
+
std::unique_ptr<ZMQEndpointInterface> pose_interface_;
|
| 1221 |
+
|
| 1222 |
+
/// Background subscriber for the command topic.
|
| 1223 |
+
std::unique_ptr<ZMQPackedMessageSubscriber> command_subscriber_;
|
| 1224 |
+
/// Background subscriber for the planner topic.
|
| 1225 |
+
std::unique_ptr<ZMQPackedMessageSubscriber> planner_subscriber_;
|
| 1226 |
+
|
| 1227 |
+
// ------------------------------------------------------------------
|
| 1228 |
+
// Mode / message state
|
| 1229 |
+
// ------------------------------------------------------------------
|
| 1230 |
+
ManagedMode active_mode_; ///< Current operational mode (PLANNER or STREAMED_MOTION).
|
| 1231 |
+
|
| 1232 |
+
std::mutex command_mutex_; ///< Guards access to latest_command_.
|
| 1233 |
+
CommandMessage latest_command_; ///< Most recent (or accumulated) command message.
|
| 1234 |
+
|
| 1235 |
+
std::mutex planner_mutex_; ///< Guards access to latest_planner_message_.
|
| 1236 |
+
PlannerMessage latest_planner_message_; ///< Most recent planner movement message.
|
| 1237 |
+
|
| 1238 |
+
// ------------------------------------------------------------------
|
| 1239 |
+
// Per-frame control flags (reset at start of update())
|
| 1240 |
+
// ------------------------------------------------------------------
|
| 1241 |
+
bool emergency_stop_ = false; ///< Set by 'O'/'o' keyboard shortcut.
|
| 1242 |
+
bool report_temperature_flag_ = false; ///< Set by 'F'/'f' keyboard shortcut.
|
| 1243 |
+
bool start_control_ = false; ///< Start request from command message.
|
| 1244 |
+
bool stop_control_ = false; ///< Stop request from command message.
|
| 1245 |
+
|
| 1246 |
+
/// True once the planner has been initialised and is generating motions.
|
| 1247 |
+
bool is_planner_ready_ = false;
|
| 1248 |
+
/// True when transitioning from streamed-motion (teleop) back to planner mode;
|
| 1249 |
+
/// used to keep forwarding VR/hand data from the pose interface until planner is ready.
|
| 1250 |
+
bool switch_from_teleop_to_planner_ = false;
|
| 1251 |
+
|
| 1252 |
+
/// Tracks the previous frame's VR-3-point state to detect enable/disable transitions
|
| 1253 |
+
/// and automatically toggle encoder mode accordingly.
|
| 1254 |
+
bool last_has_vr_3point_control_ = false;
|
| 1255 |
+
};
|
| 1256 |
+
|
| 1257 |
+
#endif // ZMQ_MANAGER_HPP
|
GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/zmq_packed_message_subscriber.hpp
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file zmq_packed_message_subscriber.hpp
|
| 3 |
+
* @brief ZeroMQ SUB client for receiving "packed" binary messages.
|
| 4 |
+
*
|
| 5 |
+
* ## Wire Format
|
| 6 |
+
*
|
| 7 |
+
* Each ZMQ message is a **single-part message** with the following layout:
|
| 8 |
+
*
|
| 9 |
+
* [topic_prefix (optional)] [1280-byte JSON header] [concatenated binary fields]
|
| 10 |
+
*
|
| 11 |
+
* - **Topic prefix**: If a non-empty topic is configured, the subscriber
|
| 12 |
+
* filters on this prefix and strips it before processing.
|
| 13 |
+
* - **JSON header** (exactly `HEADER_SIZE` = 1280 bytes, null-padded):
|
| 14 |
+
* Describes the binary payload – version, endianness, field names, dtypes,
|
| 15 |
+
* and shapes. Example:
|
| 16 |
+
* ```json
|
| 17 |
+
* { "v": 1, "endian": "le", "count": 100,
|
| 18 |
+
* "fields": [
|
| 19 |
+
* { "name": "joint_pos", "dtype": "f32", "shape": [100, 29] },
|
| 20 |
+
* { "name": "body_quat", "dtype": "f64", "shape": [100, 1, 4] }
|
| 21 |
+
* ]
|
| 22 |
+
* }
|
| 23 |
+
* ```
|
| 24 |
+
* - **Binary payload**: Fields concatenated in the order listed in the header.
|
| 25 |
+
*
|
| 26 |
+
* Because each message is a single ZMQ part, ZMQ's `conflate` option can safely
|
| 27 |
+
* be used to drop older messages and always process the latest one.
|
| 28 |
+
*
|
| 29 |
+
* ## Threading Model
|
| 30 |
+
*
|
| 31 |
+
* Start() spawns a background thread that calls PollOnce() in a tight loop.
|
| 32 |
+
* Decoded messages are dispatched to the user-supplied callback, which runs
|
| 33 |
+
* **on the background thread** – the callback must therefore be thread-safe
|
| 34 |
+
* or buffer data for later processing on the main thread.
|
| 35 |
+
*
|
| 36 |
+
* ## Endianness
|
| 37 |
+
*
|
| 38 |
+
* The header's `endian` field ("le" or "be") is compared against the native
|
| 39 |
+
* endianness at runtime. Helper functions `is_little_endian()` and
|
| 40 |
+
* `byte_swap()` are provided for callers that need to swap multi-byte values.
|
| 41 |
+
*/
|
| 42 |
+
|
| 43 |
+
#ifndef ZMQ_PACKED_MESSAGE_SUBSCRIBER_HPP
|
| 44 |
+
#define ZMQ_PACKED_MESSAGE_SUBSCRIBER_HPP
|
| 45 |
+
|
| 46 |
+
#include <atomic>
|
| 47 |
+
#include <chrono>
|
| 48 |
+
#include <cstdint>
|
| 49 |
+
#include <cstring>
|
| 50 |
+
#include <iostream>
|
| 51 |
+
#include <memory>
|
| 52 |
+
#include <string>
|
| 53 |
+
#include <thread>
|
| 54 |
+
#include <functional>
|
| 55 |
+
#include <vector>
|
| 56 |
+
#include <algorithm>
|
| 57 |
+
|
| 58 |
+
#include <zmq.hpp>
|
| 59 |
+
#include <nlohmann/json.hpp>
|
| 60 |
+
|
| 61 |
+
/// @brief Detect whether the host CPU is little-endian.
|
| 62 |
+
inline bool is_little_endian() {
|
| 63 |
+
uint32_t test = 1;
|
| 64 |
+
return *reinterpret_cast<uint8_t*>(&test) == 1;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
/**
|
| 68 |
+
* @brief Reverse the byte order of a value (up to 64 bits).
|
| 69 |
+
* @tparam T An arithmetic type of at most 8 bytes.
|
| 70 |
+
*/
|
| 71 |
+
template<typename T>
|
| 72 |
+
inline T byte_swap(T value) {
|
| 73 |
+
static_assert(sizeof(T) <= 8, "byte_swap only supports up to 64-bit types");
|
| 74 |
+
union {
|
| 75 |
+
T val;
|
| 76 |
+
uint8_t bytes[sizeof(T)];
|
| 77 |
+
} src, dst;
|
| 78 |
+
src.val = value;
|
| 79 |
+
for (size_t i = 0; i < sizeof(T); ++i) {
|
| 80 |
+
dst.bytes[i] = src.bytes[sizeof(T) - 1 - i];
|
| 81 |
+
}
|
| 82 |
+
return dst.val;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
/**
|
| 86 |
+
* @class ZMQPackedMessageSubscriber
|
| 87 |
+
* @brief ZeroMQ SUB client that receives, parses, and dispatches packed
|
| 88 |
+
* binary messages described by a JSON header.
|
| 89 |
+
*
|
| 90 |
+
* Usage:
|
| 91 |
+
* 1. Construct with host / port / topic.
|
| 92 |
+
* 2. Call SetOnDecodedMessage() to register a callback.
|
| 93 |
+
* 3. Call Start() to begin background receiving.
|
| 94 |
+
* 4. Call Stop() (or let the destructor do it) to shut down.
|
| 95 |
+
*/
|
| 96 |
+
class ZMQPackedMessageSubscriber {
|
| 97 |
+
public:
|
| 98 |
+
/// Fixed size (in bytes) of the JSON header block at the start of each packed message.
|
| 99 |
+
static constexpr size_t HEADER_SIZE = 1280;
|
| 100 |
+
|
| 101 |
+
/**
|
| 102 |
+
* @brief Construct a subscriber (does NOT connect or start yet).
|
| 103 |
+
* @param host ZMQ server hostname or IP.
|
| 104 |
+
* @param port ZMQ server port.
|
| 105 |
+
* @param topic Subscription topic prefix (empty = receive all).
|
| 106 |
+
* @param timeout_ms Receive timeout in milliseconds.
|
| 107 |
+
* @param verbose Enable verbose logging.
|
| 108 |
+
* @param conflate Enable ZMQ conflate (keep only latest message).
|
| 109 |
+
* @param rcv_hwm Receive high-water mark (−1 = ZMQ default).
|
| 110 |
+
*/
|
| 111 |
+
ZMQPackedMessageSubscriber(
|
| 112 |
+
const std::string &host = "localhost",
|
| 113 |
+
int port = 5556,
|
| 114 |
+
const std::string &topic = "pose",
|
| 115 |
+
int timeout_ms = 1000,
|
| 116 |
+
bool verbose = true,
|
| 117 |
+
bool conflate = false,
|
| 118 |
+
int rcv_hwm = -1
|
| 119 |
+
)
|
| 120 |
+
: host_(host), port_(port), topic_(topic), timeout_ms_(timeout_ms), verbose_(verbose),
|
| 121 |
+
conflate_(conflate), rcv_hwm_(rcv_hwm),
|
| 122 |
+
context_(1), running_(false) {}
|
| 123 |
+
|
| 124 |
+
// Non-copyable, non-movable
|
| 125 |
+
ZMQPackedMessageSubscriber(const ZMQPackedMessageSubscriber&) = delete;
|
| 126 |
+
ZMQPackedMessageSubscriber& operator=(const ZMQPackedMessageSubscriber&) = delete;
|
| 127 |
+
|
| 128 |
+
~ZMQPackedMessageSubscriber() { Stop(); }
|
| 129 |
+
|
| 130 |
+
/// Zero-copy view into the received message buffer.
|
| 131 |
+
/// **Only valid during the callback invocation** – do not store.
|
| 132 |
+
struct BufferView {
|
| 133 |
+
const void* data; ///< Pointer to the raw field bytes inside the ZMQ message.
|
| 134 |
+
std::size_t size; ///< Number of bytes.
|
| 135 |
+
};
|
| 136 |
+
|
| 137 |
+
/// Describes one binary field as declared in the JSON header.
|
| 138 |
+
struct FieldInfo {
|
| 139 |
+
std::string name; ///< Human-readable field name (e.g. "joint_pos").
|
| 140 |
+
std::string dtype; ///< Data-type string: "f32", "f64", "i32", "i64", "bool", etc.
|
| 141 |
+
std::vector<size_t> shape; ///< N-D shape (e.g. [100, 29] for 100 frames × 29 joints).
|
| 142 |
+
bool optional = false; ///< Whether the field may be absent from the payload.
|
| 143 |
+
|
| 144 |
+
/// @brief Return the byte size of a single element for this dtype.
|
| 145 |
+
size_t GetElementSize() const {
|
| 146 |
+
if (dtype == "f64" || dtype == "i64") return 8;
|
| 147 |
+
if (dtype == "f32" || dtype == "i32") return 4;
|
| 148 |
+
if (dtype == "i16" || dtype == "f16") return 2;
|
| 149 |
+
if (dtype == "i8" || dtype == "u8" || dtype == "bool") return 1;
|
| 150 |
+
return 4; // default
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
// Compute total byte size for this field
|
| 154 |
+
size_t ComputeByteSize() const {
|
| 155 |
+
if (shape.empty()) return 0;
|
| 156 |
+
size_t total_elements = 1;
|
| 157 |
+
for (auto dim : shape) total_elements *= dim;
|
| 158 |
+
return total_elements * GetElementSize();
|
| 159 |
+
}
|
| 160 |
+
};
|
| 161 |
+
|
| 162 |
+
/// Parsed representation of the 1280-byte JSON header.
|
| 163 |
+
struct DecodedHeader {
|
| 164 |
+
int version = 0; ///< Protocol version (e.g. 1, 2, 3).
|
| 165 |
+
std::string endian; ///< "le" or "be" (empty defaults to "le").
|
| 166 |
+
int count = -1; ///< Optional frame/element count hint.
|
| 167 |
+
std::vector<FieldInfo> fields; ///< Ordered list of binary field descriptors.
|
| 168 |
+
|
| 169 |
+
/// @return True if the payload byte order differs from the native CPU order.
|
| 170 |
+
bool NeedsByteSwap() const {
|
| 171 |
+
bool native_le = is_little_endian();
|
| 172 |
+
bool data_le = (endian == "le" || endian.empty()); // default to le
|
| 173 |
+
return native_le != data_le;
|
| 174 |
+
}
|
| 175 |
+
};
|
| 176 |
+
|
| 177 |
+
/// Register a callback to be invoked (on the background thread) for each
|
| 178 |
+
/// successfully decoded packed message. Must be called before Start().
|
| 179 |
+
void SetOnDecodedMessage(
|
| 180 |
+
std::function<void(const std::string&, const DecodedHeader&, const std::vector<BufferView>&)> cb) {
|
| 181 |
+
on_decoded_ = std::move(cb);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
/// Create the ZMQ SUB socket, set options, and connect to the endpoint.
|
| 185 |
+
/// @return True on success, false on error (logged to stderr).
|
| 186 |
+
bool Connect() {
|
| 187 |
+
if (socket_) return true;
|
| 188 |
+
|
| 189 |
+
try {
|
| 190 |
+
socket_ = std::make_unique<zmq::socket_t>(context_, zmq::socket_type::sub);
|
| 191 |
+
|
| 192 |
+
socket_->set(zmq::sockopt::rcvtimeo, timeout_ms_);
|
| 193 |
+
socket_->set(zmq::sockopt::linger, 0);
|
| 194 |
+
if (rcv_hwm_ > 0) {
|
| 195 |
+
socket_->set(zmq::sockopt::rcvhwm, rcv_hwm_);
|
| 196 |
+
}
|
| 197 |
+
if (conflate_) {
|
| 198 |
+
socket_->set(zmq::sockopt::conflate, 1);
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
const std::string endpoint = "tcp://" + host_ + ":" + std::to_string(port_);
|
| 202 |
+
socket_->connect(endpoint);
|
| 203 |
+
|
| 204 |
+
// Subscribe: if topic is empty, receive all; otherwise filter by topic prefix
|
| 205 |
+
socket_->set(zmq::sockopt::subscribe, topic_);
|
| 206 |
+
|
| 207 |
+
if (verbose_) {
|
| 208 |
+
std::cout << "[ZMQPackedMessageSubscriber] Subscribed to '" << topic_ << "' at "
|
| 209 |
+
<< endpoint << std::endl;
|
| 210 |
+
}
|
| 211 |
+
return true;
|
| 212 |
+
} catch (const zmq::error_t &e) {
|
| 213 |
+
std::cerr << "[ZMQPackedMessageSubscriber] Connect error: " << e.what() << std::endl;
|
| 214 |
+
socket_.reset();
|
| 215 |
+
return false;
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
/// Spawn the background receive thread. Requires a callback and a connection.
|
| 220 |
+
void Start() {
|
| 221 |
+
if (running_) {
|
| 222 |
+
std::cerr << "[ZMQPackedMessageSubscriber] Already running" << std::endl;
|
| 223 |
+
return;
|
| 224 |
+
}
|
| 225 |
+
if (!on_decoded_) {
|
| 226 |
+
std::cerr << "[ZMQPackedMessageSubscriber] Error: callback not set" << std::endl;
|
| 227 |
+
return;
|
| 228 |
+
}
|
| 229 |
+
if (!Connect()) {
|
| 230 |
+
std::cerr << "[ZMQPackedMessageSubscriber] Error: Connect() failed" << std::endl;
|
| 231 |
+
return;
|
| 232 |
+
}
|
| 233 |
+
running_ = true;
|
| 234 |
+
recv_thread_ = std::thread([this]() { this->RunLoop(); });
|
| 235 |
+
if (verbose_) {
|
| 236 |
+
std::cout << "[ZMQPackedMessageSubscriber] Background thread started" << std::endl;
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
/// Stop the background thread and close the socket.
|
| 241 |
+
void Stop() {
|
| 242 |
+
if (!running_) return;
|
| 243 |
+
running_ = false;
|
| 244 |
+
if (recv_thread_.joinable()) {
|
| 245 |
+
recv_thread_.join();
|
| 246 |
+
}
|
| 247 |
+
if (socket_) {
|
| 248 |
+
try { socket_->close(); } catch (...) {}
|
| 249 |
+
socket_.reset();
|
| 250 |
+
}
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
/**
|
| 254 |
+
* @brief Receive and decode one packed message (blocking up to timeout_ms).
|
| 255 |
+
* @return True if a message was successfully received and decoded.
|
| 256 |
+
*
|
| 257 |
+
* Can be called manually for single-threaded use, or is called in a loop
|
| 258 |
+
* by the background thread spawned by Start().
|
| 259 |
+
*/
|
| 260 |
+
bool PollOnce() {
|
| 261 |
+
if (!socket_ && !Connect()) return false;
|
| 262 |
+
|
| 263 |
+
try {
|
| 264 |
+
// Receive single packed message
|
| 265 |
+
zmq::message_t packed_frame;
|
| 266 |
+
zmq::recv_result_t r1 = socket_->recv(packed_frame, zmq::recv_flags::none);
|
| 267 |
+
if (!r1) return false;
|
| 268 |
+
|
| 269 |
+
size_t packed_size = packed_frame.size();
|
| 270 |
+
const unsigned char* packed_data = static_cast<const unsigned char*>(packed_frame.data());
|
| 271 |
+
|
| 272 |
+
// Strip topic prefix if configured
|
| 273 |
+
if (!topic_.empty()) {
|
| 274 |
+
if (packed_size < topic_.size()) return false; // too small
|
| 275 |
+
if (std::memcmp(packed_data, topic_.data(), topic_.size()) != 0) {
|
| 276 |
+
return false; // topic mismatch
|
| 277 |
+
}
|
| 278 |
+
packed_data += topic_.size();
|
| 279 |
+
packed_size -= topic_.size();
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
if (packed_size < HEADER_SIZE) {
|
| 283 |
+
if (verbose_) {
|
| 284 |
+
std::cerr << "[ZMQPackedMessageSubscriber] Packed frame too small: " << packed_size
|
| 285 |
+
<< " < " << HEADER_SIZE << std::endl;
|
| 286 |
+
}
|
| 287 |
+
return false;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
// Extract JSON header (first HEADER_SIZE bytes, null-terminated)
|
| 291 |
+
std::string header_json;
|
| 292 |
+
size_t json_len = strnlen(reinterpret_cast<const char*>(packed_data), HEADER_SIZE);
|
| 293 |
+
header_json.assign(reinterpret_cast<const char*>(packed_data), json_len);
|
| 294 |
+
|
| 295 |
+
// Decode header
|
| 296 |
+
DecodedHeader decoded;
|
| 297 |
+
if (!DecodeHeaderJSON(header_json, decoded)) {
|
| 298 |
+
if (verbose_) {
|
| 299 |
+
std::cerr << "[ZMQPackedMessageSubscriber] JSON parse failed" << std::endl;
|
| 300 |
+
std::cerr << " Header content: " << header_json << std::endl;
|
| 301 |
+
}
|
| 302 |
+
return false;
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
bool needs_swap = decoded.NeedsByteSwap();
|
| 306 |
+
|
| 307 |
+
if (verbose_) {
|
| 308 |
+
std::cout << "[ZMQPackedMessageSubscriber] Parsed header: v=" << decoded.version
|
| 309 |
+
<< " endian=" << decoded.endian << " count=" << decoded.count
|
| 310 |
+
<< " fields=" << decoded.fields.size()
|
| 311 |
+
<< " needs_byteswap=" << (needs_swap ? "yes" : "no") << std::endl;
|
| 312 |
+
for (size_t i = 0; i < decoded.fields.size(); ++i) {
|
| 313 |
+
const auto& f = decoded.fields[i];
|
| 314 |
+
std::cout << " Field[" << i << "]: name=" << f.name << " dtype=" << f.dtype
|
| 315 |
+
<< " shape=[";
|
| 316 |
+
for (size_t j = 0; j < f.shape.size(); ++j) {
|
| 317 |
+
std::cout << f.shape[j];
|
| 318 |
+
if (j < f.shape.size() - 1) std::cout << ",";
|
| 319 |
+
}
|
| 320 |
+
std::cout << "] bytes=" << f.ComputeByteSize() << std::endl;
|
| 321 |
+
}
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
// Note: BufferView provides raw pointers. If needs_swap=true, user must byte-swap
|
| 325 |
+
// when reading multi-byte values. We don't modify the received data in-place.
|
| 326 |
+
|
| 327 |
+
// Build BufferViews for each field from the data section
|
| 328 |
+
const unsigned char* data_start = packed_data + HEADER_SIZE;
|
| 329 |
+
const size_t data_size = packed_size - HEADER_SIZE;
|
| 330 |
+
|
| 331 |
+
std::vector<BufferView> buffers;
|
| 332 |
+
buffers.reserve(decoded.fields.size());
|
| 333 |
+
|
| 334 |
+
size_t offset = 0;
|
| 335 |
+
for (const auto& field : decoded.fields) {
|
| 336 |
+
size_t field_bytes = field.ComputeByteSize();
|
| 337 |
+
if (offset + field_bytes > data_size) {
|
| 338 |
+
if (verbose_) {
|
| 339 |
+
std::cerr << "[ZMQPackedMessageSubscriber] Field " << field.name
|
| 340 |
+
<< " exceeds data bounds" << std::endl;
|
| 341 |
+
}
|
| 342 |
+
return false;
|
| 343 |
+
}
|
| 344 |
+
buffers.push_back(BufferView{data_start + offset, field_bytes});
|
| 345 |
+
offset += field_bytes;
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
if (verbose_) {
|
| 349 |
+
std::cout << "[ZMQPackedMessageSubscriber] Received packed message: header_bytes=" << json_len
|
| 350 |
+
<< " data_bytes=" << offset << " fields=" << buffers.size() << std::endl;
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
if (on_decoded_) {
|
| 354 |
+
on_decoded_(topic_, decoded, buffers); // Use configured topic, not extracted
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
return true;
|
| 358 |
+
} catch (const zmq::error_t &e) {
|
| 359 |
+
if (verbose_) {
|
| 360 |
+
std::cerr << "[ZMQPackedMessageSubscriber] Receive error: " << e.what() << std::endl;
|
| 361 |
+
}
|
| 362 |
+
Reconnect();
|
| 363 |
+
return false;
|
| 364 |
+
}
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
private:
|
| 368 |
+
void RunLoop() {
|
| 369 |
+
if (verbose_) {
|
| 370 |
+
std::cout << "[ZMQPackedMessageSubscriber] RunLoop started" << std::endl;
|
| 371 |
+
}
|
| 372 |
+
int poll_count = 0;
|
| 373 |
+
while (running_) {
|
| 374 |
+
PollOnce();
|
| 375 |
+
poll_count++;
|
| 376 |
+
}
|
| 377 |
+
if (verbose_) {
|
| 378 |
+
std::cout << "[ZMQPackedMessageSubscriber] RunLoop finished after " << poll_count << " polls" << std::endl;
|
| 379 |
+
}
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
void Reconnect() {
|
| 383 |
+
try {
|
| 384 |
+
if (socket_) {
|
| 385 |
+
try { socket_->close(); } catch (...) {}
|
| 386 |
+
socket_.reset();
|
| 387 |
+
}
|
| 388 |
+
} catch (...) {}
|
| 389 |
+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
| 390 |
+
Connect();
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
bool DecodeHeaderJSON(const std::string& header_json, DecodedHeader& out) const {
|
| 394 |
+
try {
|
| 395 |
+
auto j = nlohmann::json::parse(header_json);
|
| 396 |
+
if (j.contains("v")) out.version = j["v"].get<int>();
|
| 397 |
+
if (j.contains("endian")) out.endian = j["endian"].get<std::string>();
|
| 398 |
+
if (j.contains("count")) out.count = j["count"].get<int>();
|
| 399 |
+
out.fields.clear();
|
| 400 |
+
if (j.contains("fields") && j["fields"].is_array()) {
|
| 401 |
+
for (const auto& f : j["fields"]) {
|
| 402 |
+
FieldInfo fi;
|
| 403 |
+
if (f.contains("name")) fi.name = f["name"].get<std::string>();
|
| 404 |
+
if (f.contains("dtype")) fi.dtype = f["dtype"].get<std::string>();
|
| 405 |
+
if (f.contains("optional")) fi.optional = f["optional"].get<bool>();
|
| 406 |
+
fi.shape.clear();
|
| 407 |
+
if (f.contains("shape") && f["shape"].is_array()) {
|
| 408 |
+
for (const auto& dim : f["shape"]) {
|
| 409 |
+
fi.shape.push_back(dim.get<size_t>());
|
| 410 |
+
}
|
| 411 |
+
}
|
| 412 |
+
out.fields.push_back(std::move(fi));
|
| 413 |
+
}
|
| 414 |
+
}
|
| 415 |
+
return true;
|
| 416 |
+
} catch (...) {
|
| 417 |
+
return false;
|
| 418 |
+
}
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
std::string host_;
|
| 422 |
+
int port_;
|
| 423 |
+
std::string topic_;
|
| 424 |
+
int timeout_ms_;
|
| 425 |
+
bool verbose_;
|
| 426 |
+
bool conflate_;
|
| 427 |
+
int rcv_hwm_;
|
| 428 |
+
|
| 429 |
+
zmq::context_t context_;
|
| 430 |
+
std::unique_ptr<zmq::socket_t> socket_;
|
| 431 |
+
|
| 432 |
+
std::atomic<bool> running_;
|
| 433 |
+
std::thread recv_thread_;
|
| 434 |
+
|
| 435 |
+
std::function<void(const std::string&, const DecodedHeader&, const std::vector<BufferView>&)> on_decoded_;
|
| 436 |
+
};
|
| 437 |
+
|
| 438 |
+
#endif // ZMQ_PACKED_MESSAGE_SUBSCRIBER_HPP
|
| 439 |
+
|
GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/output_interface/zmq_output_handler.hpp
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file zmq_output_handler.hpp
|
| 3 |
+
* @brief ZMQ PUB output handler for publishing robot state and configuration.
|
| 4 |
+
*
|
| 5 |
+
* Publishes two topic-prefixed streams over a single ZMQ PUB socket:
|
| 6 |
+
*
|
| 7 |
+
* Topic Prefix | Frequency | Description
|
| 8 |
+
* ----------------|-----------------|-------------------------------------------
|
| 9 |
+
* {user_topic} | Every tick | Combined state + visualisation (msgpack).
|
| 10 |
+
* robot_config | Every ~2 s | Robot configuration (msgpack, always re-published).
|
| 11 |
+
*
|
| 12 |
+
* Wire format (single-part ZMQ message):
|
| 13 |
+
*
|
| 14 |
+
* [topic_prefix][msgpack payload]
|
| 15 |
+
*
|
| 16 |
+
* The topic prefix is a plain string (e.g. "g1_debug") prepended to the
|
| 17 |
+
* message so that subscribers using `zmq::sockopt::subscribe` can filter by
|
| 18 |
+
* topic.
|
| 19 |
+
*
|
| 20 |
+
* ---------------------------------------------------------------------------
|
| 21 |
+
* ## `{user_topic}` (e.g. `g1_debug`) — published every tick
|
| 22 |
+
* ---------------------------------------------------------------------------
|
| 23 |
+
*
|
| 24 |
+
* A single msgpack map with up to 30 keys (28 always-present + 2 conditional).
|
| 25 |
+
* All joints are in **MuJoCo order** (remapped from IsaacLab via
|
| 26 |
+
* `isaaclab_to_mujoco`).
|
| 27 |
+
*
|
| 28 |
+
* # | Key | Type | Description
|
| 29 |
+
* ---|------------------------|--------------|---------------------------------------------
|
| 30 |
+
* | **Metadata** | |
|
| 31 |
+
* 1 | control_loop_type | string | Always "cpp".
|
| 32 |
+
* 2 | index | int | Monotonic state-logger entry index.
|
| 33 |
+
* 3 | ros_timestamp | double | ROS 2 wall-clock (s); 0.0 if no ROS 2.
|
| 34 |
+
* | | |
|
| 35 |
+
* | **Base IMU** | |
|
| 36 |
+
* 4 | base_quat | double[4] | Base IMU quaternion (w,x,y,z).
|
| 37 |
+
* 5 | base_ang_vel | double[3] | Base angular velocity.
|
| 38 |
+
* 6 | body_torso_quat | double[4] | Torso IMU quaternion.
|
| 39 |
+
* 7 | body_torso_ang_vel | double[3] | Torso angular velocity.
|
| 40 |
+
* | | |
|
| 41 |
+
* | **Body joints** | |
|
| 42 |
+
* 8 | body_q | double[29] | Joint positions (+ default offsets).
|
| 43 |
+
* 9 | body_dq | double[29] | Joint velocities.
|
| 44 |
+
* | | |
|
| 45 |
+
* | **Hand joints** | |
|
| 46 |
+
* 10 | left_hand_q | double[7] | Left-hand joint positions (from state logger).
|
| 47 |
+
* 11 | left_hand_dq | double[7] | Left-hand joint velocities.
|
| 48 |
+
* 12 | right_hand_q | double[7] | Right-hand joint positions (from state logger).
|
| 49 |
+
* 13 | right_hand_dq | double[7] | Right-hand joint velocities.
|
| 50 |
+
* | | |
|
| 51 |
+
* | **Policy actions** | |
|
| 52 |
+
* 14 | last_action | double[29] | Last body action (scaled + default offsets).
|
| 53 |
+
* 15 | last_left_hand_action | double[7] | Last left-hand action.
|
| 54 |
+
* 16 | last_right_hand_action | double[7] | Last right-hand action.
|
| 55 |
+
* | | |
|
| 56 |
+
* | **Encoder** | |
|
| 57 |
+
* 17 | token_state | double[N] | Encoder token state (empty array if N/A).
|
| 58 |
+
* | | |
|
| 59 |
+
* | **Heading** *(conditional — only when heading state is available)* |
|
| 60 |
+
* 18 | init_base_quat | double[4] | Initial base quaternion at heading init.
|
| 61 |
+
* 19 | delta_heading | double | Accumulated heading delta (rad).
|
| 62 |
+
* | | |
|
| 63 |
+
* | **Viz: targets** *(from current motion frame + heading correction)* |
|
| 64 |
+
* 20 | base_trans_target | double[3] | Target base translation.
|
| 65 |
+
* 21 | base_quat_target | double[4] | Target base quaternion.
|
| 66 |
+
* 22 | body_q_target | double[29] | Target joint positions.
|
| 67 |
+
* | | |
|
| 68 |
+
* | **Viz: measured** | |
|
| 69 |
+
* 23 | base_trans_measured | double[3] | Measured base translation (fixed default).
|
| 70 |
+
* 24 | base_quat_measured | double[4] | Measured base quaternion (= base_quat).
|
| 71 |
+
* 25 | body_q_measured | double[29] | Measured joint positions (= body_q).
|
| 72 |
+
* 26 | left_hand_q_measured | double[7] | Measured left-hand Dex3 positions.
|
| 73 |
+
* 27 | right_hand_q_measured | double[7] | Measured right-hand Dex3 positions.
|
| 74 |
+
* | | |
|
| 75 |
+
* | **Viz: VR 3-point** | |
|
| 76 |
+
* 28 | vr_3point_position | double[9] | VR positions (3×xyz, target body frame).
|
| 77 |
+
* 29 | vr_3point_orientation | double[12] | VR orientations (3×quat wxyz).
|
| 78 |
+
* 30 | vr_3point_compliance | double[3] | VR compliance (left arm, right arm, head).
|
| 79 |
+
*
|
| 80 |
+
* ---------------------------------------------------------------------------
|
| 81 |
+
* ## `robot_config` — re-published every ~2 s
|
| 82 |
+
* ---------------------------------------------------------------------------
|
| 83 |
+
*
|
| 84 |
+
* StateLogger configuration map (policy parameters, joint mappings, etc.)
|
| 85 |
+
* as a msgpack map of string → string | int | double | bool.
|
| 86 |
+
* Re-published on every `publish()` tick (throttled to ~2 s intervals) so
|
| 87 |
+
* that late-joining subscribers always receive it (ZMQ PUB has no persistence).
|
| 88 |
+
*
|
| 89 |
+
* ---------------------------------------------------------------------------
|
| 90 |
+
* ## Socket Options
|
| 91 |
+
* ---------------------------------------------------------------------------
|
| 92 |
+
*
|
| 93 |
+
* - Send HWM = 10 (old messages dropped rather than queued).
|
| 94 |
+
* - Send buffer = 32 KB.
|
| 95 |
+
* - Linger = 0 (immediate close, no pending-send wait).
|
| 96 |
+
* - Non-blocking send (dontwait) to avoid stalling the control loop.
|
| 97 |
+
*/
|
| 98 |
+
|
| 99 |
+
#ifndef ZMQ_OUTPUT_HANDLER_HPP
|
| 100 |
+
#define ZMQ_OUTPUT_HANDLER_HPP
|
| 101 |
+
|
| 102 |
+
#include <memory>
|
| 103 |
+
#include <iostream>
|
| 104 |
+
#include <chrono>
|
| 105 |
+
#include <cstring>
|
| 106 |
+
#include <map>
|
| 107 |
+
#include <vector>
|
| 108 |
+
#include <variant>
|
| 109 |
+
#include <stdexcept>
|
| 110 |
+
#include <zmq.hpp>
|
| 111 |
+
#include <msgpack.hpp>
|
| 112 |
+
|
| 113 |
+
#include "output_interface.hpp"
|
| 114 |
+
#include "../policy_parameters.hpp" // For isaaclab_to_mujoco, default_angles, g1_action_scale
|
| 115 |
+
#include "../robot_parameters.hpp" // For HeadingState
|
| 116 |
+
#include "../utils.hpp" // For DataBuffer
|
| 117 |
+
|
| 118 |
+
/**
|
| 119 |
+
* @class ZMQOutputHandler
|
| 120 |
+
* @brief OutputInterface that publishes state data over a ZMQ PUB socket.
|
| 121 |
+
*/
|
| 122 |
+
class ZMQOutputHandler : public OutputInterface {
|
| 123 |
+
public:
|
| 124 |
+
static constexpr bool DEBUG_LOGGING = true;
|
| 125 |
+
|
| 126 |
+
/**
|
| 127 |
+
* @brief Construct the handler: create a ZMQ PUB socket and bind to the given port.
|
| 128 |
+
* @param logger Reference to the shared StateLogger.
|
| 129 |
+
* @param port TCP port to bind the PUB socket to (e.g. 5557).
|
| 130 |
+
* @param topic Topic prefix prepended to each published message.
|
| 131 |
+
*/
|
| 132 |
+
explicit ZMQOutputHandler(StateLogger& logger, int port, const std::string& topic)
|
| 133 |
+
: OutputInterface(logger), realtime_debug_context_(1), topic_(topic),
|
| 134 |
+
robot_config_topic_("robot_config") {
|
| 135 |
+
|
| 136 |
+
std::cout << "Initializing realtime debug socket" << std::endl;
|
| 137 |
+
std::cout << "Binding to port: " << port << " and topic: " << topic_ << std::endl;
|
| 138 |
+
realtime_debug_socket_ = std::make_unique<zmq::socket_t>(realtime_debug_context_, ZMQ_PUB);
|
| 139 |
+
|
| 140 |
+
realtime_debug_socket_->set(zmq::sockopt::sndhwm, 10); // Drop old messages quickly
|
| 141 |
+
realtime_debug_socket_->set(zmq::sockopt::sndbuf, 32768); // 32 KB send buffer
|
| 142 |
+
realtime_debug_socket_->set(zmq::sockopt::linger, 0); // No lingering on close
|
| 143 |
+
realtime_debug_socket_->bind("tcp://*:" + std::to_string(port));
|
| 144 |
+
|
| 145 |
+
std::cout << "[INFO] Realtime debug socket bound to port: " << port << std::endl;
|
| 146 |
+
|
| 147 |
+
if constexpr (DEBUG_LOGGING) {
|
| 148 |
+
std::cout << "[ZMQ Output DEBUG] ZMQOutputHandler initialized with topics: "
|
| 149 |
+
<< "'" << topic_ << "' (combined state+viz), "
|
| 150 |
+
<< "'" << robot_config_topic_ << "' (config)" << std::endl;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
type_ = OutputType::ZMQ;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
/**
|
| 157 |
+
* @brief Send combined visualisation + state-logger data in a single ZMQ message (non-blocking, called each tick).
|
| 158 |
+
*
|
| 159 |
+
* Calls `create_output_data_map()` to compute visualisation targets, then
|
| 160 |
+
* `pack_combined_state()` to merge state-logger + viz fields into one msgpack
|
| 161 |
+
* buffer, and sends it on the user topic with `zmq::send_flags::dontwait`.
|
| 162 |
+
*/
|
| 163 |
+
void publish(
|
| 164 |
+
const std::array<double, 9>& vr_3point_position,
|
| 165 |
+
const std::array<double, 12>& vr_3point_orientation,
|
| 166 |
+
const std::array<double, 3>& vr_3point_compliance,
|
| 167 |
+
const std::array<double, 7>& left_hand_joint,
|
| 168 |
+
const std::array<double, 7>& right_hand_joint,
|
| 169 |
+
const std::array<double, 4>& init_ref_data_root_rot_array,
|
| 170 |
+
DataBuffer<HeadingState>& heading_state_buffer,
|
| 171 |
+
std::shared_ptr<const MotionSequence> current_motion,
|
| 172 |
+
int current_frame
|
| 173 |
+
) override
|
| 174 |
+
{
|
| 175 |
+
// 1. Compute visualisation data (populates output_data_map_)
|
| 176 |
+
create_output_data_map(
|
| 177 |
+
vr_3point_position,
|
| 178 |
+
vr_3point_orientation,
|
| 179 |
+
vr_3point_compliance,
|
| 180 |
+
left_hand_joint,
|
| 181 |
+
right_hand_joint,
|
| 182 |
+
init_ref_data_root_rot_array,
|
| 183 |
+
heading_state_buffer,
|
| 184 |
+
current_motion,
|
| 185 |
+
current_frame
|
| 186 |
+
);
|
| 187 |
+
|
| 188 |
+
// 2. Build a single combined message with state-logger + visualisation fields
|
| 189 |
+
pack_combined_state(heading_state_buffer);
|
| 190 |
+
|
| 191 |
+
// 3. Send once on user topic (e.g. "g1_debug")
|
| 192 |
+
if (state_data_sbuf_.size() > 0) {
|
| 193 |
+
send_zmq_message(topic_, state_data_sbuf_);
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
// 4. Re-publish robot_config periodically (ZMQ has no persistence)
|
| 197 |
+
publish_config();
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
/**
|
| 201 |
+
* @brief Publish robot_config if enough time has elapsed since the last send.
|
| 202 |
+
*
|
| 203 |
+
* On the first call the config is serialised from StateLogger and cached.
|
| 204 |
+
* Subsequent calls simply re-send the cached buffer every
|
| 205 |
+
* `CONFIG_REPUBLISH_INTERVAL_SEC` seconds. This gives ZMQ PUB the same
|
| 206 |
+
* "late-subscriber" semantics as ROS 2's `transient_local` QoS.
|
| 207 |
+
*
|
| 208 |
+
* Called once during init (from g1_deploy_onnx_ref.cpp) and then on every
|
| 209 |
+
* control-loop tick from `publish()`.
|
| 210 |
+
*/
|
| 211 |
+
void publish_config() override {
|
| 212 |
+
// Lazy-init: serialise once, reuse forever.
|
| 213 |
+
if (config_sbuf_cache_.size() == 0) {
|
| 214 |
+
auto config_opt = state_logger_.GetConfig();
|
| 215 |
+
if (!config_opt.has_value() || config_opt->empty()) {
|
| 216 |
+
throw std::runtime_error("[ZMQ Output ERROR] Cannot publish config: StateLogger config is empty");
|
| 217 |
+
}
|
| 218 |
+
pack_robot_config(config_sbuf_cache_, *config_opt);
|
| 219 |
+
std::cout << "[ZMQ Output] Robot config cached ("
|
| 220 |
+
<< config_opt->size() << " fields, "
|
| 221 |
+
<< config_sbuf_cache_.size() << " bytes)" << std::endl;
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
auto now = std::chrono::steady_clock::now();
|
| 225 |
+
double elapsed = std::chrono::duration<double>(now - config_last_publish_time_).count();
|
| 226 |
+
if (elapsed >= CONFIG_REPUBLISH_INTERVAL_SEC) {
|
| 227 |
+
send_zmq_message(robot_config_topic_, config_sbuf_cache_);
|
| 228 |
+
config_last_publish_time_ = now;
|
| 229 |
+
}
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
private:
|
| 233 |
+
zmq::context_t realtime_debug_context_; ///< ZMQ context (1 I/O thread).
|
| 234 |
+
std::unique_ptr<zmq::socket_t> realtime_debug_socket_; ///< ZMQ PUB socket.
|
| 235 |
+
|
| 236 |
+
std::string topic_; ///< User-provided topic name (e.g. "g1_debug") for combined state+viz.
|
| 237 |
+
std::string robot_config_topic_; ///< Topic for robot config messages.
|
| 238 |
+
|
| 239 |
+
msgpack::sbuffer state_data_sbuf_; ///< Reused each tick; cleared in pack_combined_state().
|
| 240 |
+
|
| 241 |
+
// -- Config re-publish (ZMQ equivalent of ROS 2 transient_local) --
|
| 242 |
+
static constexpr double CONFIG_REPUBLISH_INTERVAL_SEC = 2.0;
|
| 243 |
+
msgpack::sbuffer config_sbuf_cache_; ///< Serialised config (populated on first publish_config()).
|
| 244 |
+
std::chrono::steady_clock::time_point config_last_publish_time_;
|
| 245 |
+
|
| 246 |
+
/// Non-blocking send of [topic][msgpack payload] over the PUB socket.
|
| 247 |
+
void send_zmq_message(const std::string& topic, const msgpack::sbuffer& sbuf) {
|
| 248 |
+
zmq::message_t msg(topic.size() + sbuf.size());
|
| 249 |
+
memcpy(msg.data(), topic.c_str(), topic.size());
|
| 250 |
+
memcpy(static_cast<char*>(msg.data()) + topic.size(), sbuf.data(), sbuf.size());
|
| 251 |
+
realtime_debug_socket_->send(msg, zmq::send_flags::dontwait);
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
/**
|
| 255 |
+
* @brief Serialise both state-logger and visualisation data into state_data_sbuf_.
|
| 256 |
+
*
|
| 257 |
+
* Combines into a single msgpack map:
|
| 258 |
+
* - State-logger fields (body_q, body_dq, last_action, etc. in MuJoCo order)
|
| 259 |
+
* - Visualisation fields from output_data_map_ (targets, VR data, measured poses)
|
| 260 |
+
*
|
| 261 |
+
* This avoids sending two overlapping messages per tick.
|
| 262 |
+
*/
|
| 263 |
+
void pack_combined_state(const DataBuffer<HeadingState>& heading_state_buffer) {
|
| 264 |
+
state_data_sbuf_.clear();
|
| 265 |
+
|
| 266 |
+
if (state_logger_.size() == 0) {
|
| 267 |
+
return;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
std::vector<StateLogger::Entry> entries = state_logger_.GetLatest(1);
|
| 271 |
+
const StateLogger::Entry& state = entries[0];
|
| 272 |
+
msgpack::packer<msgpack::sbuffer> pk(&state_data_sbuf_);
|
| 273 |
+
|
| 274 |
+
HeadingState heading_state;
|
| 275 |
+
bool has_heading_state = false;
|
| 276 |
+
auto heading_state_data = heading_state_buffer.GetDataWithTime().data;
|
| 277 |
+
if (heading_state_data) {
|
| 278 |
+
heading_state = *heading_state_data;
|
| 279 |
+
has_heading_state = true;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
// State-logger fields: 18 base + 2 optional heading
|
| 283 |
+
// Visualisation fields: output_data_map_.size() (typically 11)
|
| 284 |
+
int num_state_fields = has_heading_state ? 20 : 18;
|
| 285 |
+
int num_viz_fields = static_cast<int>(output_data_map_.size());
|
| 286 |
+
pk.pack_map(num_state_fields + num_viz_fields);
|
| 287 |
+
|
| 288 |
+
// ---- State-logger fields ----
|
| 289 |
+
|
| 290 |
+
pk.pack("control_loop_type");
|
| 291 |
+
pk.pack("cpp");
|
| 292 |
+
|
| 293 |
+
pk.pack("index");
|
| 294 |
+
pk.pack(state.index);
|
| 295 |
+
|
| 296 |
+
pk.pack("ros_timestamp");
|
| 297 |
+
pk.pack(state.ros_timestamp);
|
| 298 |
+
|
| 299 |
+
pk.pack("base_quat");
|
| 300 |
+
pk.pack_array(4);
|
| 301 |
+
for (const auto& val : state.base_quat) pk.pack(val);
|
| 302 |
+
|
| 303 |
+
pk.pack("base_ang_vel");
|
| 304 |
+
pk.pack_array(3);
|
| 305 |
+
for (const auto& val : state.base_ang_vel) pk.pack(val);
|
| 306 |
+
|
| 307 |
+
pk.pack("body_torso_quat");
|
| 308 |
+
pk.pack_array(4);
|
| 309 |
+
for (const auto& val : state.body_torso_quat) pk.pack(val);
|
| 310 |
+
|
| 311 |
+
pk.pack("body_torso_ang_vel");
|
| 312 |
+
pk.pack_array(3);
|
| 313 |
+
for (const auto& val : state.body_torso_ang_vel) pk.pack(val);
|
| 314 |
+
|
| 315 |
+
// body_q: IsaacLab -> MuJoCo order, add default-angle offset
|
| 316 |
+
pk.pack("body_q");
|
| 317 |
+
pk.pack_array(state.body_q.size());
|
| 318 |
+
if (state.body_q.size() == 29) {
|
| 319 |
+
std::array<double, 29> body_q_mujoco;
|
| 320 |
+
for (size_t i = 0; i < 29; ++i)
|
| 321 |
+
body_q_mujoco[i] = state.body_q[isaaclab_to_mujoco[i]] + default_angles[i];
|
| 322 |
+
for (const auto& val : body_q_mujoco) pk.pack(val);
|
| 323 |
+
} else {
|
| 324 |
+
for (const auto& val : state.body_q) pk.pack(val);
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
// body_dq: IsaacLab -> MuJoCo order (no offset for velocities)
|
| 328 |
+
pk.pack("body_dq");
|
| 329 |
+
pk.pack_array(state.body_dq.size());
|
| 330 |
+
if (state.body_dq.size() == 29) {
|
| 331 |
+
std::array<double, 29> body_dq_mujoco;
|
| 332 |
+
for (size_t i = 0; i < 29; ++i)
|
| 333 |
+
body_dq_mujoco[i] = state.body_dq[isaaclab_to_mujoco[i]];
|
| 334 |
+
for (const auto& val : body_dq_mujoco) pk.pack(val);
|
| 335 |
+
} else {
|
| 336 |
+
for (const auto& val : state.body_dq) pk.pack(val);
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
// last_action: IsaacLab -> MuJoCo order, scale + default-angle offset
|
| 340 |
+
pk.pack("last_action");
|
| 341 |
+
pk.pack_array(state.last_action.size());
|
| 342 |
+
if (state.last_action.size() == 29) {
|
| 343 |
+
std::array<double, 29> last_action_mujoco;
|
| 344 |
+
for (size_t i = 0; i < 29; ++i)
|
| 345 |
+
last_action_mujoco[i] = state.last_action[isaaclab_to_mujoco[i]] * g1_action_scale[i] + default_angles[i];
|
| 346 |
+
for (const auto& val : last_action_mujoco) pk.pack(val);
|
| 347 |
+
} else {
|
| 348 |
+
for (const auto& val : state.last_action) pk.pack(val);
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
pk.pack("left_hand_q");
|
| 352 |
+
pk.pack_array(state.left_hand_q.size());
|
| 353 |
+
for (const auto& val : state.left_hand_q) pk.pack(val);
|
| 354 |
+
|
| 355 |
+
pk.pack("left_hand_dq");
|
| 356 |
+
pk.pack_array(state.left_hand_dq.size());
|
| 357 |
+
for (const auto& val : state.left_hand_dq) pk.pack(val);
|
| 358 |
+
|
| 359 |
+
pk.pack("right_hand_q");
|
| 360 |
+
pk.pack_array(state.right_hand_q.size());
|
| 361 |
+
for (const auto& val : state.right_hand_q) pk.pack(val);
|
| 362 |
+
|
| 363 |
+
pk.pack("right_hand_dq");
|
| 364 |
+
pk.pack_array(state.right_hand_dq.size());
|
| 365 |
+
for (const auto& val : state.right_hand_dq) pk.pack(val);
|
| 366 |
+
|
| 367 |
+
pk.pack("last_left_hand_action");
|
| 368 |
+
pk.pack_array(state.last_left_hand_action.size());
|
| 369 |
+
for (const auto& val : state.last_left_hand_action) pk.pack(val);
|
| 370 |
+
|
| 371 |
+
pk.pack("last_right_hand_action");
|
| 372 |
+
pk.pack_array(state.last_right_hand_action.size());
|
| 373 |
+
for (const auto& val : state.last_right_hand_action) pk.pack(val);
|
| 374 |
+
|
| 375 |
+
pk.pack("token_state");
|
| 376 |
+
if (state.has_post_state_data && !state.token_state.empty()) {
|
| 377 |
+
pk.pack_array(state.token_state.size());
|
| 378 |
+
for (const auto& val : state.token_state) pk.pack(val);
|
| 379 |
+
} else {
|
| 380 |
+
pk.pack_array(0);
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
// Motor temperature: hardware order, 2 values per motor (winding, driver)
|
| 384 |
+
pk.pack("motor_temperature");
|
| 385 |
+
pk.pack_array(state.motor_temperature.size());
|
| 386 |
+
for (const auto& val : state.motor_temperature) pk.pack(val);
|
| 387 |
+
|
| 388 |
+
if (has_heading_state) {
|
| 389 |
+
pk.pack("init_base_quat");
|
| 390 |
+
pk.pack_array(4);
|
| 391 |
+
for (const auto& val : heading_state.init_base_quat) pk.pack(val);
|
| 392 |
+
|
| 393 |
+
pk.pack("delta_heading");
|
| 394 |
+
pk.pack(heading_state.delta_heading);
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
// ---- Visualisation fields (from output_data_map_) ----
|
| 398 |
+
// Adds: base_trans_target, base_quat_target, body_q_target,
|
| 399 |
+
// base_trans_measured, base_quat_measured, body_q_measured,
|
| 400 |
+
// left_hand_q_measured, right_hand_q_measured,
|
| 401 |
+
// vr_3point_position, vr_3point_orientation, vr_3point_compliance
|
| 402 |
+
for (const auto& [key, values] : output_data_map_) {
|
| 403 |
+
pk.pack(key);
|
| 404 |
+
pk.pack_array(values.size());
|
| 405 |
+
for (const auto& val : values) pk.pack(val);
|
| 406 |
+
}
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
/// Serialise a config map into the given sbuffer.
|
| 410 |
+
static void pack_robot_config(msgpack::sbuffer& sbuf,
|
| 411 |
+
const std::map<std::string, std::variant<std::string, int, double, bool>>& config) {
|
| 412 |
+
sbuf.clear();
|
| 413 |
+
msgpack::packer<msgpack::sbuffer> pk(&sbuf);
|
| 414 |
+
pk.pack_map(config.size());
|
| 415 |
+
for (const auto& [key, value] : config) {
|
| 416 |
+
pk.pack(key);
|
| 417 |
+
std::visit([&pk](auto&& arg) { pk.pack(arg); }, value);
|
| 418 |
+
}
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
};
|
| 422 |
+
|
| 423 |
+
#endif // ZMQ_OUTPUT_HANDLER_HPP
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/.devcontainer/Dockerfile.devcontainer
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM ubuntu:20.04
|
| 2 |
+
|
| 3 |
+
ENV TZ=Asia/Shanghai
|
| 4 |
+
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
| 5 |
+
|
| 6 |
+
ENV DEBIAN_FRONTEND=noninteractive
|
| 7 |
+
|
| 8 |
+
USER root
|
| 9 |
+
|
| 10 |
+
# install zsh and oh-my-zsh
|
| 11 |
+
RUN apt update && apt-get install -y git wget zsh && \
|
| 12 |
+
wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh && chmod +x install.sh
|
| 13 |
+
RUN ./install.sh
|
| 14 |
+
RUN git clone https://github.com/zsh-users/zsh-autosuggestions.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions && \
|
| 15 |
+
apt install zsh-syntax-highlighting
|
| 16 |
+
# configure zsh
|
| 17 |
+
RUN echo 'export ZSH=$HOME/.oh-my-zsh' >> ~/.zshrc && \
|
| 18 |
+
echo 'ZSH_THEME="ys"' >> ~/.zshrc && \
|
| 19 |
+
echo 'plugins=(git zsh-autosuggestions)' >> ~/.zshrc && \
|
| 20 |
+
echo 'source $ZSH/oh-my-zsh.sh' >> ~/.zshrc && \
|
| 21 |
+
echo 'source /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh' >> ~/.zshrc
|
| 22 |
+
RUN chsh -s /bin/zsh
|
| 23 |
+
|
| 24 |
+
# install build tools
|
| 25 |
+
RUN apt-get install -y build-essential gcc-9 g++-9 gdb cmake ninja-build
|
| 26 |
+
|
| 27 |
+
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 \
|
| 28 |
+
&& update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 100
|
| 29 |
+
|
| 30 |
+
# install libraries
|
| 31 |
+
RUN apt-get install -y libyaml-cpp-dev libeigen3-dev
|
| 32 |
+
|
| 33 |
+
WORKDIR /workspace
|
| 34 |
+
|
| 35 |
+
CMD ["zsh"]
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/.devcontainer/devcontainer.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Existing Docker Compose (Extend)",
|
| 3 |
+
"dockerComposeFile": [
|
| 4 |
+
"docker-compose.yml"
|
| 5 |
+
],
|
| 6 |
+
"service": "devcontainer",
|
| 7 |
+
"workspaceFolder": "/workspace",
|
| 8 |
+
"customizations": {
|
| 9 |
+
"vscode": {
|
| 10 |
+
"extensions": [
|
| 11 |
+
"gitlab.gitlab-workflow",
|
| 12 |
+
"llvm-vs-code-extensions.vscode-clangd",
|
| 13 |
+
"xaver.clang-format",
|
| 14 |
+
"ms-vscode.cmake-tools",
|
| 15 |
+
"zxh404.vscode-proto3",
|
| 16 |
+
"donjayamanne.githistory",
|
| 17 |
+
"mhutchie.git-graph",
|
| 18 |
+
"matepek.vscode-catch2-test-adapter",
|
| 19 |
+
"ryanluker.vscode-coverage-gutters",
|
| 20 |
+
"donjayamanne.python-extension-pack",
|
| 21 |
+
"adam-bender.commit-message-editor",
|
| 22 |
+
"axetroy.vscode-changelog-generator",
|
| 23 |
+
"vsliveshare.vsliveshare",
|
| 24 |
+
"bierner.markdown-mermaid",
|
| 25 |
+
"vadimcn.vscode-lldb"
|
| 26 |
+
]
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
}
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/.devcontainer/docker-compose.yml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3'
|
| 2 |
+
services:
|
| 3 |
+
devcontainer:
|
| 4 |
+
volumes:
|
| 5 |
+
- ..:/workspace:cached
|
| 6 |
+
- /tmp/.X11-unix:/tmp/.X11-unix:rw
|
| 7 |
+
- /mnt/wslg:/mnt/wslg
|
| 8 |
+
- /root/.gazebo/models/:/root/.gazebo/models/
|
| 9 |
+
- /var/run/docker.sock:/var/run/docker.sock
|
| 10 |
+
- $HOME/.Xauthority:/tmp/.Xauthority:rw
|
| 11 |
+
environment:
|
| 12 |
+
- DISPLAY=${DISPLAY}
|
| 13 |
+
- XAUTHORITY=/tmp/.Xauthority
|
| 14 |
+
|
| 15 |
+
cap_add:
|
| 16 |
+
- SYS_PTRACE
|
| 17 |
+
command: /bin/sh -c "while sleep 1000; do :; done"
|
| 18 |
+
|
| 19 |
+
build:
|
| 20 |
+
context: .
|
| 21 |
+
dockerfile: Dockerfile.devcontainer
|
| 22 |
+
|
| 23 |
+
privileged: true
|
| 24 |
+
network_mode: "host"
|
| 25 |
+
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/.gitignore
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# IDE
|
| 2 |
+
.vscode
|
| 3 |
+
|
| 4 |
+
# cpp
|
| 5 |
+
build
|
| 6 |
+
|
| 7 |
+
#cland
|
| 8 |
+
.cache
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/CMakeLists.txt
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
cmake_minimum_required(VERSION 3.5)
|
| 2 |
+
project(unitree_sdk2 VERSION 2.0.0)
|
| 3 |
+
|
| 4 |
+
## Project Options
|
| 5 |
+
option(BUILD_EXAMPLES "Build examples" ON)
|
| 6 |
+
|
| 7 |
+
## Set compiler to use c++ 17 features
|
| 8 |
+
set(CMAKE_CXX_STANDARD 17)
|
| 9 |
+
set(CMAKE_CXX_EXTENSIONS OFF)
|
| 10 |
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
| 11 |
+
|
| 12 |
+
## Chosse build type
|
| 13 |
+
set(default_build_type "Release")
|
| 14 |
+
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
| 15 |
+
message(STATUS "Setting build type to '${default_build_type}' as none was specified.")
|
| 16 |
+
set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE
|
| 17 |
+
STRING "Choose the type of build." FORCE)
|
| 18 |
+
# Set the possible values of build type for cmake-gui
|
| 19 |
+
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
| 20 |
+
"Debug" "Release" "MinSizeRel" "RelWithDebInfo")
|
| 21 |
+
endif ()
|
| 22 |
+
|
| 23 |
+
## Use GNUInstallDirs to install libraries into correct locations on all platforms.
|
| 24 |
+
include(GNUInstallDirs)
|
| 25 |
+
|
| 26 |
+
## Put all binary files into /bin and libraries into /lib
|
| 27 |
+
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
|
| 28 |
+
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
|
| 29 |
+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
|
| 30 |
+
|
| 31 |
+
## Check system architecture
|
| 32 |
+
message(STATUS "Current system architecture: ${CMAKE_SYSTEM_PROCESSOR}")
|
| 33 |
+
|
| 34 |
+
## Import thirdparty libraries
|
| 35 |
+
add_subdirectory(thirdparty)
|
| 36 |
+
|
| 37 |
+
## Import Unitree SDK2 library
|
| 38 |
+
set(UNITREE_SDK_PATH ${CMAKE_CURRENT_LIST_DIR}/lib/${CMAKE_SYSTEM_PROCESSOR})
|
| 39 |
+
find_library(UNITREE_SDK_LIB unitree_sdk2 PATHS ${UNITREE_SDK_PATH} NO_DEFAULT_PATH)
|
| 40 |
+
|
| 41 |
+
if (NOT UNITREE_SDK_LIB)
|
| 42 |
+
message(FATAL_ERROR "Unitree SDK library for the architecture is not found")
|
| 43 |
+
else ()
|
| 44 |
+
message(STATUS "Unitree SDK library found at: ${UNITREE_SDK_LIB}")
|
| 45 |
+
endif ()
|
| 46 |
+
|
| 47 |
+
message(STATUS "Importing: ${UNITREE_SDK_LIB}")
|
| 48 |
+
|
| 49 |
+
find_package(Threads REQUIRED)
|
| 50 |
+
|
| 51 |
+
add_library(unitree_sdk2 STATIC IMPORTED GLOBAL)
|
| 52 |
+
set_target_properties(unitree_sdk2 PROPERTIES
|
| 53 |
+
IMPORTED_LOCATION ${UNITREE_SDK_LIB})
|
| 54 |
+
target_link_libraries(unitree_sdk2 INTERFACE ddsc ddscxx Threads::Threads)
|
| 55 |
+
target_include_directories(unitree_sdk2 INTERFACE
|
| 56 |
+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
| 57 |
+
$<INSTALL_INTERFACE:include>)
|
| 58 |
+
|
| 59 |
+
if (BUILD_EXAMPLES)
|
| 60 |
+
add_subdirectory(example)
|
| 61 |
+
endif ()
|
| 62 |
+
|
| 63 |
+
## Install the library
|
| 64 |
+
install(DIRECTORY include/
|
| 65 |
+
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
| 66 |
+
|
| 67 |
+
install(FILES ${UNITREE_SDK_LIB}
|
| 68 |
+
DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
| 69 |
+
|
| 70 |
+
install(FILES cmake/unitree_sdk2Targets.cmake
|
| 71 |
+
DESTINATION lib/cmake/unitree_sdk2)
|
| 72 |
+
|
| 73 |
+
include(CMakePackageConfigHelpers)
|
| 74 |
+
write_basic_package_version_file(
|
| 75 |
+
unitree_sdk2ConfigVersion.cmake
|
| 76 |
+
VERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH}"
|
| 77 |
+
COMPATIBILITY ExactVersion)
|
| 78 |
+
|
| 79 |
+
configure_file(cmake/unitree_sdk2Config.cmake.in unitree_sdk2Config.cmake @ONLY)
|
| 80 |
+
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/unitree_sdk2Config.cmake"
|
| 81 |
+
"${CMAKE_CURRENT_BINARY_DIR}/unitree_sdk2ConfigVersion.cmake"
|
| 82 |
+
DESTINATION lib/cmake/unitree_sdk2)
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
BSD 3-Clause License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2016-2023 HangZhou YuShu TECHNOLOGY CO.,LTD. ("Unitree Robotics")
|
| 4 |
+
All rights reserved.
|
| 5 |
+
|
| 6 |
+
Redistribution and use in source and binary forms, with or without
|
| 7 |
+
modification, are permitted provided that the following conditions are met:
|
| 8 |
+
|
| 9 |
+
1. Redistributions of source code must retain the above copyright notice, this
|
| 10 |
+
list of conditions and the following disclaimer.
|
| 11 |
+
|
| 12 |
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
| 13 |
+
this list of conditions and the following disclaimer in the documentation
|
| 14 |
+
and/or other materials provided with the distribution.
|
| 15 |
+
|
| 16 |
+
3. Neither the name of the copyright holder nor the names of its
|
| 17 |
+
contributors may be used to endorse or promote products derived from
|
| 18 |
+
this software without specific prior written permission.
|
| 19 |
+
|
| 20 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 21 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 22 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 23 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 24 |
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 25 |
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 26 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 27 |
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 28 |
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 29 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# unitree_sdk2
|
| 2 |
+
Unitree robot sdk version 2.
|
| 3 |
+
|
| 4 |
+
### Prebuild environment
|
| 5 |
+
* OS (Ubuntu 20.04 LTS)
|
| 6 |
+
* CPU (aarch64 and x86_64)
|
| 7 |
+
* Compiler (gcc version 9.4.0)
|
| 8 |
+
|
| 9 |
+
### Environment Setup
|
| 10 |
+
|
| 11 |
+
Before building or running the SDK, ensure the following dependencies are installed:
|
| 12 |
+
|
| 13 |
+
- CMake (version 3.10 or higher)
|
| 14 |
+
- GCC (version 9.4.0)
|
| 15 |
+
- Make
|
| 16 |
+
|
| 17 |
+
You can install the required packages on Ubuntu 20.04 with:
|
| 18 |
+
|
| 19 |
+
```bash
|
| 20 |
+
apt-get update
|
| 21 |
+
apt-get install -y cmake g++ build-essential libyaml-cpp-dev libeigen3-dev libboost-all-dev libspdlog-dev libfmt-dev
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
### Build examples
|
| 25 |
+
|
| 26 |
+
To build the examples inside this repository:
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
mkdir build
|
| 30 |
+
cd build
|
| 31 |
+
cmake ..
|
| 32 |
+
make
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
### Installation
|
| 36 |
+
|
| 37 |
+
To build your own application with the SDK, you can install the unitree_sdk2 to your system directory:
|
| 38 |
+
|
| 39 |
+
```bash
|
| 40 |
+
mkdir build
|
| 41 |
+
cd build
|
| 42 |
+
cmake ..
|
| 43 |
+
sudo make install
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
Or install unitree_sdk2 to a specified directory:
|
| 47 |
+
|
| 48 |
+
```bash
|
| 49 |
+
mkdir build
|
| 50 |
+
cd build
|
| 51 |
+
cmake .. -DCMAKE_INSTALL_PREFIX=/opt/unitree_robotics
|
| 52 |
+
sudo make install
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
You can refer to `example/cmake_sample` on how to import the unitree_sdk2 into your CMake project.
|
| 56 |
+
|
| 57 |
+
Note that if you install the library to other places other than `/opt/unitree_robotics`, you need to make sure the path is added to "${CMAKE_PREFIX_PATH}" so that cmake can find it with "find_package()".
|
| 58 |
+
|
| 59 |
+
### Notice
|
| 60 |
+
For more reference information, please go to [Unitree Document Center](https://support.unitree.com/home/zh/developer).
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/cmake/unitree_sdk2Config.cmake.in
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
include(CMakeFindDependencyMacro)
|
| 2 |
+
|
| 3 |
+
# Same syntax as find_package
|
| 4 |
+
find_dependency(Threads REQUIRED)
|
| 5 |
+
|
| 6 |
+
# Add the targets file
|
| 7 |
+
include("${CMAKE_CURRENT_LIST_DIR}/unitree_sdk2Targets.cmake")
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/cmake/unitree_sdk2Targets.cmake
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
if(CMAKE_VERSION VERSION_LESS 3.5.0)
|
| 2 |
+
message(FATAL_ERROR "This file relies on consumers using CMake 3.5.0 or greater.")
|
| 3 |
+
endif()
|
| 4 |
+
cmake_policy(PUSH)
|
| 5 |
+
cmake_policy(VERSION 2.6)
|
| 6 |
+
|
| 7 |
+
# Commands may need to know the format version.
|
| 8 |
+
set(CMAKE_IMPORT_FILE_VERSION 1)
|
| 9 |
+
|
| 10 |
+
# Protect against multiple inclusion, which would fail when already imported targets are added once more.
|
| 11 |
+
set(_targetsDefined)
|
| 12 |
+
set(_targetsNotDefined)
|
| 13 |
+
set(_expectedTargets)
|
| 14 |
+
foreach(_expectedTarget unitree_sdk2)
|
| 15 |
+
list(APPEND _expectedTargets ${_expectedTarget})
|
| 16 |
+
if(NOT TARGET ${_expectedTarget})
|
| 17 |
+
list(APPEND _targetsNotDefined ${_expectedTarget})
|
| 18 |
+
endif()
|
| 19 |
+
if(TARGET ${_expectedTarget})
|
| 20 |
+
list(APPEND _targetsDefined ${_expectedTarget})
|
| 21 |
+
endif()
|
| 22 |
+
endforeach()
|
| 23 |
+
if("${_targetsDefined}" STREQUAL "${_expectedTargets}")
|
| 24 |
+
unset(_targetsDefined)
|
| 25 |
+
unset(_targetsNotDefined)
|
| 26 |
+
unset(_expectedTargets)
|
| 27 |
+
set(CMAKE_IMPORT_FILE_VERSION)
|
| 28 |
+
cmake_policy(POP)
|
| 29 |
+
return()
|
| 30 |
+
endif()
|
| 31 |
+
if(NOT "${_targetsDefined}" STREQUAL "")
|
| 32 |
+
message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n")
|
| 33 |
+
endif()
|
| 34 |
+
unset(_targetsDefined)
|
| 35 |
+
unset(_targetsNotDefined)
|
| 36 |
+
unset(_expectedTargets)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
# Compute the installation prefix relative to this file.
|
| 40 |
+
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
| 41 |
+
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
| 42 |
+
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
| 43 |
+
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
| 44 |
+
if(_IMPORT_PREFIX STREQUAL "/")
|
| 45 |
+
set(_IMPORT_PREFIX "")
|
| 46 |
+
endif()
|
| 47 |
+
|
| 48 |
+
# Create imported target ddsc and ddscxx
|
| 49 |
+
add_library(ddsc SHARED IMPORTED GLOBAL)
|
| 50 |
+
set_target_properties(ddsc PROPERTIES
|
| 51 |
+
IMPORTED_LOCATION ${_IMPORT_PREFIX}/lib/libddsc.so
|
| 52 |
+
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include;${_IMPORT_PREFIX}/include"
|
| 53 |
+
INTERFACE_LINK_LIBRARIES "Threads::Threads"
|
| 54 |
+
IMPORTED_NO_SONAME TRUE)
|
| 55 |
+
|
| 56 |
+
add_library(ddscxx SHARED IMPORTED GLOBAL)
|
| 57 |
+
set_target_properties(ddscxx PROPERTIES
|
| 58 |
+
IMPORTED_LOCATION ${_IMPORT_PREFIX}/lib/libddscxx.so
|
| 59 |
+
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include;${_IMPORT_PREFIX}/include/ddscxx"
|
| 60 |
+
INTERFACE_LINK_LIBRARIES "Threads::Threads"
|
| 61 |
+
IMPORTED_NO_SONAME TRUE)
|
| 62 |
+
|
| 63 |
+
# Create imported target unitree_sdk2
|
| 64 |
+
add_library(unitree_sdk2 STATIC IMPORTED GLOBAL)
|
| 65 |
+
set_target_properties(unitree_sdk2 PROPERTIES
|
| 66 |
+
IMPORTED_LOCATION ${_IMPORT_PREFIX}/lib/libunitree_sdk2.a
|
| 67 |
+
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include;${_IMPORT_PREFIX}/include"
|
| 68 |
+
INTERFACE_LINK_LIBRARIES "ddsc;ddscxx;Threads::Threads"
|
| 69 |
+
LINKER_LANGUAGE CXX
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
# Cleanup temporary variables.
|
| 73 |
+
set(_IMPORT_PREFIX)
|
| 74 |
+
|
| 75 |
+
# Loop over all imported files and verify that they actually exist
|
| 76 |
+
foreach(target ${_IMPORT_CHECK_TARGETS} )
|
| 77 |
+
foreach(file ${_IMPORT_CHECK_FILES_FOR_${target}} )
|
| 78 |
+
if(NOT EXISTS "${file}" )
|
| 79 |
+
message(FATAL_ERROR "The imported target \"${target}\" references the file
|
| 80 |
+
\"${file}\"
|
| 81 |
+
but this file does not exist. Possible reasons include:
|
| 82 |
+
* The file was deleted, renamed, or moved to another location.
|
| 83 |
+
* An install or uninstall procedure did not complete successfully.
|
| 84 |
+
* The installation package was faulty and contained
|
| 85 |
+
\"${CMAKE_CURRENT_LIST_FILE}\"
|
| 86 |
+
but not all the files it references.
|
| 87 |
+
")
|
| 88 |
+
endif()
|
| 89 |
+
endforeach()
|
| 90 |
+
unset(_IMPORT_CHECK_FILES_FOR_${target})
|
| 91 |
+
endforeach()
|
| 92 |
+
unset(_IMPORT_CHECK_TARGETS)
|
| 93 |
+
|
| 94 |
+
# Commands beyond this point should not need to know the version.
|
| 95 |
+
set(CMAKE_IMPORT_FILE_VERSION)
|
| 96 |
+
cmake_policy(POP)
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/CMakeLists.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
add_subdirectory(helloworld)
|
| 2 |
+
add_subdirectory(wireless_controller)
|
| 3 |
+
add_subdirectory(jsonize)
|
| 4 |
+
add_subdirectory(state_machine)
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
add_subdirectory(go2)
|
| 8 |
+
add_subdirectory(b2)
|
| 9 |
+
add_subdirectory(h1)
|
| 10 |
+
add_subdirectory(g1)
|
| 11 |
+
add_subdirectory(go2w)
|
| 12 |
+
add_subdirectory(b2w)
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2/CMakeLists.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
add_executable(b2_sport_client b2_sport_client.cpp)
|
| 2 |
+
target_link_libraries(b2_sport_client unitree_sdk2)
|
| 3 |
+
|
| 4 |
+
add_executable(b2_stand_example b2_stand_example.cpp)
|
| 5 |
+
target_link_libraries(b2_stand_example unitree_sdk2)
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2/b2_sport_client.cpp
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <unitree/robot/go2/sport/sport_client.hpp>
|
| 2 |
+
|
| 3 |
+
int main()
|
| 4 |
+
{
|
| 5 |
+
/*
|
| 6 |
+
* Initilaize ChannelFactory
|
| 7 |
+
*/
|
| 8 |
+
unitree::robot::ChannelFactory::Instance()->Init(0);
|
| 9 |
+
unitree::robot::go2::SportClient sc;
|
| 10 |
+
|
| 11 |
+
sc.SetTimeout(5.0f);
|
| 12 |
+
sc.Init();
|
| 13 |
+
|
| 14 |
+
//Test Api
|
| 15 |
+
while (true)
|
| 16 |
+
{
|
| 17 |
+
int32_t ret = sc.Move(0.5, 0.0, 0.0);
|
| 18 |
+
std::cout << "Call Move ret:" << ret << std::endl;
|
| 19 |
+
|
| 20 |
+
usleep(1000);
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
return 0;
|
| 24 |
+
}
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2/b2_stand_example.cpp
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <iostream>
|
| 2 |
+
#include <stdio.h>
|
| 3 |
+
#include <stdint.h>
|
| 4 |
+
#include <math.h>
|
| 5 |
+
#include <unitree/robot/channel/channel_publisher.hpp>
|
| 6 |
+
#include <unitree/robot/channel/channel_subscriber.hpp>
|
| 7 |
+
#include <unitree/idl/go2/LowState_.hpp>
|
| 8 |
+
#include <unitree/idl/go2/LowCmd_.hpp>
|
| 9 |
+
#include <unitree/common/time/time_tool.hpp>
|
| 10 |
+
#include <unitree/common/thread/thread.hpp>
|
| 11 |
+
#include <unitree/robot/b2/motion_switcher/motion_switcher_client.hpp>
|
| 12 |
+
|
| 13 |
+
using namespace unitree::common;
|
| 14 |
+
using namespace unitree::robot;
|
| 15 |
+
using namespace unitree::robot::b2;
|
| 16 |
+
|
| 17 |
+
#define TOPIC_LOWCMD "rt/lowcmd"
|
| 18 |
+
#define TOPIC_LOWSTATE "rt/lowstate"
|
| 19 |
+
|
| 20 |
+
constexpr double PosStopF = (2.146E+9f);
|
| 21 |
+
constexpr double VelStopF = (16000.0f);
|
| 22 |
+
|
| 23 |
+
class Custom
|
| 24 |
+
{
|
| 25 |
+
public:
|
| 26 |
+
explicit Custom(){}
|
| 27 |
+
~Custom(){}
|
| 28 |
+
|
| 29 |
+
void Init();
|
| 30 |
+
void Start();
|
| 31 |
+
|
| 32 |
+
private:
|
| 33 |
+
void InitLowCmd();
|
| 34 |
+
void LowStateMessageHandler(const void* messages);
|
| 35 |
+
void LowCmdWrite();
|
| 36 |
+
int queryMotionStatus();
|
| 37 |
+
std::string queryServiceName(std::string form,std::string name);
|
| 38 |
+
|
| 39 |
+
private:
|
| 40 |
+
float Kp = 1000.0;
|
| 41 |
+
float Kd = 10.0;
|
| 42 |
+
double time_consume = 0;
|
| 43 |
+
int rate_count = 0;
|
| 44 |
+
int sin_count = 0;
|
| 45 |
+
int motiontime = 0;
|
| 46 |
+
float dt = 0.002; // 0.001~0.01
|
| 47 |
+
|
| 48 |
+
MotionSwitcherClient msc;
|
| 49 |
+
|
| 50 |
+
unitree_go::msg::dds_::LowCmd_ low_cmd{}; // default init
|
| 51 |
+
unitree_go::msg::dds_::LowState_ low_state{}; // default init
|
| 52 |
+
|
| 53 |
+
/*publisher*/
|
| 54 |
+
ChannelPublisherPtr<unitree_go::msg::dds_::LowCmd_> lowcmd_publisher;
|
| 55 |
+
/*subscriber*/
|
| 56 |
+
ChannelSubscriberPtr<unitree_go::msg::dds_::LowState_> lowstate_subscriber;
|
| 57 |
+
|
| 58 |
+
/*LowCmd write thread*/
|
| 59 |
+
ThreadPtr lowCmdWriteThreadPtr;
|
| 60 |
+
|
| 61 |
+
float _targetPos_1[12] = {0.0, 1.36, -2.65, 0.0, 1.36, -2.65,
|
| 62 |
+
0.2, 1.36, -2.65, 0.2, 1.36, -2.65};
|
| 63 |
+
|
| 64 |
+
float _targetPos_2[12] = {0.0, 0.67, -1.3, 0.0, 0.67, -1.3,
|
| 65 |
+
0.0, 0.67, -1.3, 0.0, 0.67, -1.3};
|
| 66 |
+
|
| 67 |
+
float _targetPos_3[12] = {0.0, 1.36, -2.65, 0.0, 1.36, -2.65,
|
| 68 |
+
0.0, 1.36, -2.65, 0.0, 1.36, -2.65};
|
| 69 |
+
|
| 70 |
+
float _targetPos_4[12] = {-0.5, 1.36, -2.65, 0.5, 1.36, -2.65,
|
| 71 |
+
-0.5, 1.36, -2.65, 0.5, 1.36, -2.65};
|
| 72 |
+
|
| 73 |
+
float _startPos[12];
|
| 74 |
+
float _duration_1 = 500;
|
| 75 |
+
float _duration_2 = 900;
|
| 76 |
+
float _duration_3 = 1000;
|
| 77 |
+
float _duration_4 = 1100;
|
| 78 |
+
float _duration_5 = 500;
|
| 79 |
+
float _percent_1 = 0;
|
| 80 |
+
float _percent_2 = 0;
|
| 81 |
+
float _percent_3 = 0;
|
| 82 |
+
float _percent_4 = 0;
|
| 83 |
+
float _percent_5 = 0;
|
| 84 |
+
|
| 85 |
+
bool firstRun = true;
|
| 86 |
+
bool done = false;
|
| 87 |
+
};
|
| 88 |
+
|
| 89 |
+
uint32_t crc32_core(uint32_t* ptr, uint32_t len)
|
| 90 |
+
{
|
| 91 |
+
unsigned int xbit = 0;
|
| 92 |
+
unsigned int data = 0;
|
| 93 |
+
unsigned int CRC32 = 0xFFFFFFFF;
|
| 94 |
+
const unsigned int dwPolynomial = 0x04c11db7;
|
| 95 |
+
|
| 96 |
+
for (unsigned int i = 0; i < len; i++)
|
| 97 |
+
{
|
| 98 |
+
xbit = 1 << 31;
|
| 99 |
+
data = ptr[i];
|
| 100 |
+
for (unsigned int bits = 0; bits < 32; bits++)
|
| 101 |
+
{
|
| 102 |
+
if (CRC32 & 0x80000000)
|
| 103 |
+
{
|
| 104 |
+
CRC32 <<= 1;
|
| 105 |
+
CRC32 ^= dwPolynomial;
|
| 106 |
+
}
|
| 107 |
+
else
|
| 108 |
+
{
|
| 109 |
+
CRC32 <<= 1;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
if (data & xbit)
|
| 113 |
+
CRC32 ^= dwPolynomial;
|
| 114 |
+
xbit >>= 1;
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
return CRC32;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
void Custom::Init()
|
| 122 |
+
{
|
| 123 |
+
InitLowCmd();
|
| 124 |
+
|
| 125 |
+
/*create publisher*/
|
| 126 |
+
lowcmd_publisher.reset(new ChannelPublisher<unitree_go::msg::dds_::LowCmd_>(TOPIC_LOWCMD));
|
| 127 |
+
lowcmd_publisher->InitChannel();
|
| 128 |
+
|
| 129 |
+
/*create subscriber*/
|
| 130 |
+
lowstate_subscriber.reset(new ChannelSubscriber<unitree_go::msg::dds_::LowState_>(TOPIC_LOWSTATE));
|
| 131 |
+
lowstate_subscriber->InitChannel(std::bind(&Custom::LowStateMessageHandler, this, std::placeholders::_1), 1);
|
| 132 |
+
|
| 133 |
+
/*init MotionSwitcherClient*/
|
| 134 |
+
msc.SetTimeout(10.0f);
|
| 135 |
+
msc.Init();
|
| 136 |
+
|
| 137 |
+
/*Shut down motion control-related service*/
|
| 138 |
+
while(queryMotionStatus())
|
| 139 |
+
{
|
| 140 |
+
std::cout << "Try to deactivate the motion control-related service." << std::endl;
|
| 141 |
+
int32_t ret = msc.ReleaseMode();
|
| 142 |
+
if (ret == 0) {
|
| 143 |
+
std::cout << "ReleaseMode succeeded." << std::endl;
|
| 144 |
+
} else {
|
| 145 |
+
std::cout << "ReleaseMode failed. Error code: " << ret << std::endl;
|
| 146 |
+
}
|
| 147 |
+
sleep(5);
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
void Custom::InitLowCmd()
|
| 152 |
+
{
|
| 153 |
+
low_cmd.head()[0] = 0xFE;
|
| 154 |
+
low_cmd.head()[1] = 0xEF;
|
| 155 |
+
low_cmd.level_flag() = 0xFF;
|
| 156 |
+
low_cmd.gpio() = 0;
|
| 157 |
+
|
| 158 |
+
for(int i=0; i<20; i++)
|
| 159 |
+
{
|
| 160 |
+
low_cmd.motor_cmd()[i].mode() = (0x0A); // motor switch to servo (PMSM) mode
|
| 161 |
+
low_cmd.motor_cmd()[i].q() = (PosStopF);
|
| 162 |
+
low_cmd.motor_cmd()[i].kp() = (0);
|
| 163 |
+
low_cmd.motor_cmd()[i].dq() = (VelStopF);
|
| 164 |
+
low_cmd.motor_cmd()[i].kd() = (0);
|
| 165 |
+
low_cmd.motor_cmd()[i].tau() = (0);
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
int Custom::queryMotionStatus()
|
| 170 |
+
{
|
| 171 |
+
std::string robotForm,motionName;
|
| 172 |
+
int motionStatus;
|
| 173 |
+
int32_t ret = msc.CheckMode(robotForm,motionName);
|
| 174 |
+
if (ret == 0) {
|
| 175 |
+
std::cout << "CheckMode succeeded." << std::endl;
|
| 176 |
+
} else {
|
| 177 |
+
std::cout << "CheckMode failed. Error code: " << ret << std::endl;
|
| 178 |
+
}
|
| 179 |
+
if(motionName.empty())
|
| 180 |
+
{
|
| 181 |
+
std::cout << "The motion control-related service is deactivated." << std::endl;
|
| 182 |
+
motionStatus = 0;
|
| 183 |
+
}
|
| 184 |
+
else
|
| 185 |
+
{
|
| 186 |
+
std::string serviceName = queryServiceName(robotForm,motionName);
|
| 187 |
+
std::cout << "Service: "<< serviceName<< " is activate" << std::endl;
|
| 188 |
+
motionStatus = 1;
|
| 189 |
+
}
|
| 190 |
+
return motionStatus;
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
std::string Custom::queryServiceName(std::string form,std::string name)
|
| 194 |
+
{
|
| 195 |
+
if(form == "0")
|
| 196 |
+
{
|
| 197 |
+
if(name == "normal" ) return "sport_mode";
|
| 198 |
+
if(name == "ai" ) return "ai_sport";
|
| 199 |
+
if(name == "advanced" ) return "advanced_sport";
|
| 200 |
+
}
|
| 201 |
+
else
|
| 202 |
+
{
|
| 203 |
+
if(name == "ai-w" ) return "wheeled_sport(go2W)";
|
| 204 |
+
if(name == "normal-w" ) return "wheeled_sport(b2W)";
|
| 205 |
+
}
|
| 206 |
+
return "";
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
void Custom::Start()
|
| 210 |
+
{
|
| 211 |
+
/*loop publishing thread*/
|
| 212 |
+
lowCmdWriteThreadPtr = CreateRecurrentThreadEx("writebasiccmd", UT_CPU_ID_NONE, 2000, &Custom::LowCmdWrite, this);
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
void Custom::LowStateMessageHandler(const void* message)
|
| 216 |
+
{
|
| 217 |
+
low_state = *(unitree_go::msg::dds_::LowState_*)message;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
void Custom::LowCmdWrite()
|
| 221 |
+
{
|
| 222 |
+
if(_percent_5<1)
|
| 223 |
+
{
|
| 224 |
+
std::cout<<"Read sensor data example: "<<std::endl;
|
| 225 |
+
std::cout<<"Joint 0 pos: "<<low_state.motor_state()[0].q()<<std::endl;
|
| 226 |
+
std::cout<<"Imu accelerometer : "<<"x: "<<low_state.imu_state().accelerometer()[0]<<" y: "<<low_state.imu_state().accelerometer()[1]<<" z: "<<low_state.imu_state().accelerometer()[2]<<std::endl;
|
| 227 |
+
std::cout<<"Foot force "<<low_state.foot_force()[0]<<std::endl;
|
| 228 |
+
std::cout<<std::endl;
|
| 229 |
+
}
|
| 230 |
+
if((_percent_5 == 1) && ( done == false))
|
| 231 |
+
{
|
| 232 |
+
std::cout<<"The example is done! "<<std::endl;
|
| 233 |
+
std::cout<<std::endl;
|
| 234 |
+
done = true;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
motiontime++;
|
| 238 |
+
if(motiontime>=500)
|
| 239 |
+
{
|
| 240 |
+
if(firstRun)
|
| 241 |
+
{
|
| 242 |
+
for(int i = 0; i < 12; i++)
|
| 243 |
+
{
|
| 244 |
+
_startPos[i] = low_state.motor_state()[i].q();
|
| 245 |
+
}
|
| 246 |
+
firstRun = false;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
_percent_1 += (float)1 / _duration_1;
|
| 250 |
+
_percent_1 = _percent_1 > 1 ? 1 : _percent_1;
|
| 251 |
+
if (_percent_1 < 1)
|
| 252 |
+
{
|
| 253 |
+
for (int j = 0; j < 12; j++)
|
| 254 |
+
{
|
| 255 |
+
low_cmd.motor_cmd()[j].q() = (1 - _percent_1) * _startPos[j] + _percent_1 * _targetPos_1[j];
|
| 256 |
+
low_cmd.motor_cmd()[j].dq() = 0;
|
| 257 |
+
low_cmd.motor_cmd()[j].kp() = Kp;
|
| 258 |
+
low_cmd.motor_cmd()[j].kd() = Kd;
|
| 259 |
+
low_cmd.motor_cmd()[j].tau() = 0;
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
}
|
| 263 |
+
if ((_percent_1 == 1)&&(_percent_2 < 1))
|
| 264 |
+
{
|
| 265 |
+
_percent_2 += (float)1 / _duration_2;
|
| 266 |
+
_percent_2 = _percent_2 > 1 ? 1 : _percent_2;
|
| 267 |
+
|
| 268 |
+
for (int j = 0; j < 12; j++)
|
| 269 |
+
{
|
| 270 |
+
low_cmd.motor_cmd()[j].q() = (1 - _percent_2) * _targetPos_1[j] + _percent_2 * _targetPos_2[j];
|
| 271 |
+
low_cmd.motor_cmd()[j].dq() = 0;
|
| 272 |
+
low_cmd.motor_cmd()[j].kp() = Kp;
|
| 273 |
+
low_cmd.motor_cmd()[j].kd() = Kd;
|
| 274 |
+
low_cmd.motor_cmd()[j].tau() = 0;
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
if ((_percent_1 == 1)&&(_percent_2 == 1)&&(_percent_3<1))
|
| 279 |
+
{
|
| 280 |
+
_percent_3 += (float)1 / _duration_3;
|
| 281 |
+
_percent_3 = _percent_3 > 1 ? 1 : _percent_3;
|
| 282 |
+
|
| 283 |
+
for (int j = 0; j < 12; j++)
|
| 284 |
+
{
|
| 285 |
+
low_cmd.motor_cmd()[j].q() = _targetPos_2[j];
|
| 286 |
+
low_cmd.motor_cmd()[j].dq() = 0;
|
| 287 |
+
low_cmd.motor_cmd()[j].kp() = Kp;
|
| 288 |
+
low_cmd.motor_cmd()[j].kd() = Kd;
|
| 289 |
+
low_cmd.motor_cmd()[j].tau() = 0;
|
| 290 |
+
}
|
| 291 |
+
}
|
| 292 |
+
if ((_percent_1 == 1)&&(_percent_2 == 1)&&(_percent_3==1)&&(_percent_4<1))
|
| 293 |
+
{
|
| 294 |
+
_percent_4 += (float)1 / _duration_4;
|
| 295 |
+
_percent_4 = _percent_4 > 1 ? 1 : _percent_4;
|
| 296 |
+
for (int j = 0; j < 12; j++)
|
| 297 |
+
{
|
| 298 |
+
low_cmd.motor_cmd()[j].q() = (1 - _percent_4) * _targetPos_2[j] + _percent_4 * _targetPos_3[j];
|
| 299 |
+
low_cmd.motor_cmd()[j].dq() = 0;
|
| 300 |
+
low_cmd.motor_cmd()[j].kp() = Kp;
|
| 301 |
+
low_cmd.motor_cmd()[j].kd() = Kd;
|
| 302 |
+
low_cmd.motor_cmd()[j].tau() = 0;
|
| 303 |
+
}
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
if ((_percent_1 == 1)&&(_percent_2 == 1)&&(_percent_3==1)&&(_percent_4==1)&&((_percent_5<=1)))
|
| 307 |
+
{
|
| 308 |
+
_percent_5 += (float)1 / _duration_5;
|
| 309 |
+
_percent_5 = _percent_5 > 1 ? 1 : _percent_5;
|
| 310 |
+
for (int j = 0; j < 12; j++)
|
| 311 |
+
{
|
| 312 |
+
low_cmd.motor_cmd()[j].q() = (1 - _percent_5) * _targetPos_3[j] + _percent_5 * _targetPos_4[j];
|
| 313 |
+
low_cmd.motor_cmd()[j].dq() = 0;
|
| 314 |
+
low_cmd.motor_cmd()[j].kp() = Kp;
|
| 315 |
+
low_cmd.motor_cmd()[j].kd() = Kd;
|
| 316 |
+
low_cmd.motor_cmd()[j].tau() = 0;
|
| 317 |
+
}
|
| 318 |
+
}
|
| 319 |
+
low_cmd.crc() = crc32_core((uint32_t *)&low_cmd, (sizeof(unitree_go::msg::dds_::LowCmd_)>>2)-1);
|
| 320 |
+
|
| 321 |
+
lowcmd_publisher->Write(low_cmd);
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
int main(int argc, const char** argv)
|
| 327 |
+
{
|
| 328 |
+
if (argc < 2)
|
| 329 |
+
{
|
| 330 |
+
std::cout << "Usage: " << argv[0] << " networkInterface" << std::endl;
|
| 331 |
+
exit(-1);
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
std::cout << "WARNING: Make sure the robot is lying on the ground." << std::endl
|
| 335 |
+
<< "Press Enter to continue..." << std::endl;
|
| 336 |
+
std::cin.ignore();
|
| 337 |
+
|
| 338 |
+
ChannelFactory::Instance()->Init(0, argv[1]);
|
| 339 |
+
|
| 340 |
+
Custom custom;
|
| 341 |
+
custom.Init();
|
| 342 |
+
custom.Start();
|
| 343 |
+
|
| 344 |
+
while (1)
|
| 345 |
+
{
|
| 346 |
+
sleep(10);
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
return 0;
|
| 350 |
+
}
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2w/CMakeLists.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
add_executable(b2w_sport_client b2w_sport_client.cpp)
|
| 2 |
+
target_link_libraries(b2w_sport_client unitree_sdk2)
|
| 3 |
+
|
| 4 |
+
add_executable(b2w_stand_example b2w_stand_example.cpp)
|
| 5 |
+
target_link_libraries(b2w_stand_example unitree_sdk2)
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2w/b2w_sport_client.cpp
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <iostream>
|
| 2 |
+
#include <string>
|
| 3 |
+
#include <chrono>
|
| 4 |
+
#include <mutex>
|
| 5 |
+
#include <thread>
|
| 6 |
+
#include <pthread.h>
|
| 7 |
+
#include <stdexcept>
|
| 8 |
+
#include <unitree/robot/b2/sport/sport_client.hpp>
|
| 9 |
+
|
| 10 |
+
using namespace std;
|
| 11 |
+
|
| 12 |
+
struct TestOption
|
| 13 |
+
{
|
| 14 |
+
std::string name;
|
| 15 |
+
int id;
|
| 16 |
+
};
|
| 17 |
+
|
| 18 |
+
const vector<TestOption> option_list =
|
| 19 |
+
{{"damp", 0},
|
| 20 |
+
{"stand_up", 1},
|
| 21 |
+
{"stand_down", 2},
|
| 22 |
+
{"move forward", 3},
|
| 23 |
+
{"move lateral", 4},
|
| 24 |
+
{"move rotate", 5},
|
| 25 |
+
{"stop_move", 6},
|
| 26 |
+
{"switch_gait", 7},
|
| 27 |
+
{"switch_gait", 8},
|
| 28 |
+
{"recovery", 9},
|
| 29 |
+
};
|
| 30 |
+
|
| 31 |
+
int ConvertToInt(const std::string &str)
|
| 32 |
+
{
|
| 33 |
+
try
|
| 34 |
+
{
|
| 35 |
+
std::stoi(str);
|
| 36 |
+
return std::stoi(str);
|
| 37 |
+
}
|
| 38 |
+
catch (const std::invalid_argument &)
|
| 39 |
+
{
|
| 40 |
+
return -1;
|
| 41 |
+
}
|
| 42 |
+
catch (const std::out_of_range &)
|
| 43 |
+
{
|
| 44 |
+
return -1;
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
class UserInterface
|
| 49 |
+
{
|
| 50 |
+
public:
|
| 51 |
+
UserInterface(){};
|
| 52 |
+
~UserInterface(){};
|
| 53 |
+
|
| 54 |
+
void terminalHandle()
|
| 55 |
+
{
|
| 56 |
+
std::string input;
|
| 57 |
+
std::getline(std::cin, input);
|
| 58 |
+
|
| 59 |
+
if (input.compare("list") == 0)
|
| 60 |
+
{
|
| 61 |
+
for (TestOption option : option_list)
|
| 62 |
+
{
|
| 63 |
+
std::cout << option.name << ", id: " << option.id << std::endl;
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
for (TestOption option : option_list)
|
| 68 |
+
{
|
| 69 |
+
if (input.compare(option.name) == 0 || ConvertToInt(input) == option.id)
|
| 70 |
+
{
|
| 71 |
+
test_option_->id = option.id;
|
| 72 |
+
test_option_->name = option.name;
|
| 73 |
+
std::cout << "Test: " << test_option_->name << ", test_id: " << test_option_->id << std::endl;
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
};
|
| 77 |
+
|
| 78 |
+
TestOption *test_option_;
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
+
int main(int argc, char **argv)
|
| 82 |
+
{
|
| 83 |
+
if (argc < 2)
|
| 84 |
+
{
|
| 85 |
+
std::cout << "Usage: " << argv[0] << " networkInterface" << std::endl;
|
| 86 |
+
exit(-1);
|
| 87 |
+
}
|
| 88 |
+
unitree::robot::ChannelFactory::Instance()->Init(0, argv[1]);
|
| 89 |
+
|
| 90 |
+
TestOption test_option;
|
| 91 |
+
test_option.id = 1;
|
| 92 |
+
|
| 93 |
+
unitree::robot::b2::SportClient sport_client;
|
| 94 |
+
sport_client.SetTimeout(25.0f);
|
| 95 |
+
sport_client.Init();
|
| 96 |
+
|
| 97 |
+
UserInterface user_interface;
|
| 98 |
+
user_interface.test_option_ = &test_option;
|
| 99 |
+
|
| 100 |
+
std::cout << "Input \"list \" to list all test option ..." << std::endl;
|
| 101 |
+
long res_count = 0;
|
| 102 |
+
while (1)
|
| 103 |
+
{
|
| 104 |
+
auto time_start_trick = std::chrono::high_resolution_clock::now();
|
| 105 |
+
static const constexpr auto dt = std::chrono::microseconds(20000); // 50Hz
|
| 106 |
+
|
| 107 |
+
user_interface.terminalHandle();
|
| 108 |
+
|
| 109 |
+
int res = 1;
|
| 110 |
+
if (test_option.id == 0)
|
| 111 |
+
{
|
| 112 |
+
res = sport_client.Damp();
|
| 113 |
+
}
|
| 114 |
+
else if (test_option.id == 1)
|
| 115 |
+
{
|
| 116 |
+
res = sport_client.StandUp();
|
| 117 |
+
}
|
| 118 |
+
else if (test_option.id == 2)
|
| 119 |
+
{
|
| 120 |
+
res = sport_client.StandDown();
|
| 121 |
+
}
|
| 122 |
+
else if (test_option.id == 3)
|
| 123 |
+
{
|
| 124 |
+
res = sport_client.Move(0.3, 0, 0);// the robot will move for 0.5 seconds before stopping.
|
| 125 |
+
// If the Move command is called in a loop, the robot will continue moving.
|
| 126 |
+
// If no Move request is received within 0.5 seconds, the robot will automatically stop.
|
| 127 |
+
}
|
| 128 |
+
else if (test_option.id == 4)
|
| 129 |
+
{
|
| 130 |
+
res = sport_client.Move(0, 0.3, 0);// the robot will move for 0.5 seconds before stopping.
|
| 131 |
+
// If the Move command is called in a loop, the robot will continue moving.
|
| 132 |
+
// If no Move request is received within 0.5 seconds, the robot will automatically stop.
|
| 133 |
+
}
|
| 134 |
+
else if (test_option.id == 5)
|
| 135 |
+
{
|
| 136 |
+
res = sport_client.Move(0, 0, 0.5);// the robot will move for 0.5 seconds before stopping.
|
| 137 |
+
// If the Move command is called in a loop, the robot will continue moving.
|
| 138 |
+
// If no Move request is received within 0.5 seconds, the robot will automatically stop.
|
| 139 |
+
}
|
| 140 |
+
else if (test_option.id == 6)
|
| 141 |
+
{
|
| 142 |
+
res = sport_client.StopMove();
|
| 143 |
+
}
|
| 144 |
+
else if (test_option.id == 7)
|
| 145 |
+
{
|
| 146 |
+
res = sport_client.SwitchGait(0);
|
| 147 |
+
}
|
| 148 |
+
else if (test_option.id == 8)
|
| 149 |
+
{
|
| 150 |
+
res = sport_client.SwitchGait(1);
|
| 151 |
+
}
|
| 152 |
+
else if (test_option.id == 9)
|
| 153 |
+
{
|
| 154 |
+
res = sport_client.RecoveryStand();
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
if (res < 0)
|
| 158 |
+
{
|
| 159 |
+
res_count += 1;
|
| 160 |
+
std::cout << "Request error for: " << option_list[test_option.id].name << ", code: " << res << ", count: " << res_count << std::endl;
|
| 161 |
+
}
|
| 162 |
+
else
|
| 163 |
+
{
|
| 164 |
+
res_count = 0;
|
| 165 |
+
std::cout << "Request successed: " << option_list[test_option.id].name << ", code: " << res << std::endl;
|
| 166 |
+
}
|
| 167 |
+
std::this_thread::sleep_until(time_start_trick + dt);
|
| 168 |
+
}
|
| 169 |
+
return 0;
|
| 170 |
+
}
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2w/b2w_stand_example.cpp
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <iostream>
|
| 2 |
+
#include <stdio.h>
|
| 3 |
+
#include <stdint.h>
|
| 4 |
+
#include <math.h>
|
| 5 |
+
#include <unitree/robot/channel/channel_publisher.hpp>
|
| 6 |
+
#include <unitree/robot/channel/channel_subscriber.hpp>
|
| 7 |
+
#include <unitree/idl/go2/LowState_.hpp>
|
| 8 |
+
#include <unitree/idl/go2/LowCmd_.hpp>
|
| 9 |
+
#include <unitree/common/time/time_tool.hpp>
|
| 10 |
+
#include <unitree/common/thread/thread.hpp>
|
| 11 |
+
#include <unitree/robot/b2/motion_switcher/motion_switcher_client.hpp>
|
| 12 |
+
#include <unitree/robot/b2/sport/sport_client.hpp>
|
| 13 |
+
|
| 14 |
+
using namespace unitree::common;
|
| 15 |
+
using namespace unitree::robot;
|
| 16 |
+
using namespace unitree::robot::b2;
|
| 17 |
+
|
| 18 |
+
#define TOPIC_LOWCMD "rt/lowcmd"
|
| 19 |
+
#define TOPIC_LOWSTATE "rt/lowstate"
|
| 20 |
+
|
| 21 |
+
constexpr double PosStopF = (2.146E+9f);
|
| 22 |
+
constexpr double VelStopF = (16000.0f);
|
| 23 |
+
|
| 24 |
+
class Custom
|
| 25 |
+
{
|
| 26 |
+
public:
|
| 27 |
+
explicit Custom(){}
|
| 28 |
+
~Custom(){}
|
| 29 |
+
|
| 30 |
+
void Init();
|
| 31 |
+
void Start();
|
| 32 |
+
|
| 33 |
+
private:
|
| 34 |
+
void InitLowCmd();
|
| 35 |
+
void LowStateMessageHandler(const void* messages);
|
| 36 |
+
void LowCmdWrite();
|
| 37 |
+
int queryMotionStatus();
|
| 38 |
+
std::string queryServiceName(std::string form,std::string name);
|
| 39 |
+
|
| 40 |
+
private:
|
| 41 |
+
float Kp = 1000.0;
|
| 42 |
+
float Kd = 10.0;
|
| 43 |
+
double time_consume = 0;
|
| 44 |
+
int rate_count = 0;
|
| 45 |
+
int sin_count = 0;
|
| 46 |
+
int motiontime = 0;
|
| 47 |
+
float dt = 0.002; // 0.001~0.01
|
| 48 |
+
|
| 49 |
+
MotionSwitcherClient msc;
|
| 50 |
+
SportClient sc;
|
| 51 |
+
|
| 52 |
+
unitree_go::msg::dds_::LowCmd_ low_cmd{}; // default init
|
| 53 |
+
unitree_go::msg::dds_::LowState_ low_state{}; // default init
|
| 54 |
+
|
| 55 |
+
/*publisher*/
|
| 56 |
+
ChannelPublisherPtr<unitree_go::msg::dds_::LowCmd_> lowcmd_publisher;
|
| 57 |
+
/*subscriber*/
|
| 58 |
+
ChannelSubscriberPtr<unitree_go::msg::dds_::LowState_> lowstate_subscriber;
|
| 59 |
+
|
| 60 |
+
/*LowCmd write thread*/
|
| 61 |
+
ThreadPtr lowCmdWriteThreadPtr;
|
| 62 |
+
|
| 63 |
+
float _targetPos_1[12] = {0.0, 1.36, -2.65, 0.0, 1.36, -2.65,
|
| 64 |
+
-0.2, 1.36, -2.65, 0.2, 1.36, -2.65};
|
| 65 |
+
|
| 66 |
+
float _targetPos_2[12] = {0.0, 0.67, -1.3, 0.0, 0.67, -1.3,
|
| 67 |
+
0.0, 0.67, -1.3, 0.0, 0.67, -1.3};
|
| 68 |
+
|
| 69 |
+
float _targetPos_3[12] = {-0.65, 1.36, -2.65, 0.65, 1.36, -2.65,
|
| 70 |
+
-0.65, 1.36, -2.65, 0.65, 1.36, -2.65};
|
| 71 |
+
|
| 72 |
+
float _startPos[12];
|
| 73 |
+
float _duration_1 = 800;
|
| 74 |
+
float _duration_2 = 800;
|
| 75 |
+
float _duration_3 = 2000;
|
| 76 |
+
float _duration_4 = 1500;
|
| 77 |
+
float _percent_1 = 0;
|
| 78 |
+
float _percent_2 = 0;
|
| 79 |
+
float _percent_3 = 0;
|
| 80 |
+
float _percent_4 = 0;
|
| 81 |
+
|
| 82 |
+
bool firstRun = true;
|
| 83 |
+
bool done = false;
|
| 84 |
+
};
|
| 85 |
+
|
| 86 |
+
uint32_t crc32_core(uint32_t* ptr, uint32_t len)
|
| 87 |
+
{
|
| 88 |
+
unsigned int xbit = 0;
|
| 89 |
+
unsigned int data = 0;
|
| 90 |
+
unsigned int CRC32 = 0xFFFFFFFF;
|
| 91 |
+
const unsigned int dwPolynomial = 0x04c11db7;
|
| 92 |
+
|
| 93 |
+
for (unsigned int i = 0; i < len; i++)
|
| 94 |
+
{
|
| 95 |
+
xbit = 1 << 31;
|
| 96 |
+
data = ptr[i];
|
| 97 |
+
for (unsigned int bits = 0; bits < 32; bits++)
|
| 98 |
+
{
|
| 99 |
+
if (CRC32 & 0x80000000)
|
| 100 |
+
{
|
| 101 |
+
CRC32 <<= 1;
|
| 102 |
+
CRC32 ^= dwPolynomial;
|
| 103 |
+
}
|
| 104 |
+
else
|
| 105 |
+
{
|
| 106 |
+
CRC32 <<= 1;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
if (data & xbit)
|
| 110 |
+
CRC32 ^= dwPolynomial;
|
| 111 |
+
xbit >>= 1;
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
return CRC32;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
void Custom::Init()
|
| 119 |
+
{
|
| 120 |
+
InitLowCmd();
|
| 121 |
+
|
| 122 |
+
/*create publisher*/
|
| 123 |
+
lowcmd_publisher.reset(new ChannelPublisher<unitree_go::msg::dds_::LowCmd_>(TOPIC_LOWCMD));
|
| 124 |
+
lowcmd_publisher->InitChannel();
|
| 125 |
+
|
| 126 |
+
/*create subscriber*/
|
| 127 |
+
lowstate_subscriber.reset(new ChannelSubscriber<unitree_go::msg::dds_::LowState_>(TOPIC_LOWSTATE));
|
| 128 |
+
lowstate_subscriber->InitChannel(std::bind(&Custom::LowStateMessageHandler, this, std::placeholders::_1), 1);
|
| 129 |
+
|
| 130 |
+
/*init MotionSwitcherClient*/
|
| 131 |
+
msc.SetTimeout(10.0f);
|
| 132 |
+
msc.Init();
|
| 133 |
+
|
| 134 |
+
/*Shut down motion control-related service*/
|
| 135 |
+
while(queryMotionStatus())
|
| 136 |
+
{
|
| 137 |
+
std::cout << "Try to deactivate the motion control-related service." << std::endl;
|
| 138 |
+
int32_t ret = msc.ReleaseMode();
|
| 139 |
+
if (ret == 0) {
|
| 140 |
+
std::cout << "ReleaseMode succeeded." << std::endl;
|
| 141 |
+
} else {
|
| 142 |
+
std::cout << "ReleaseMode failed. Error code: " << ret << std::endl;
|
| 143 |
+
}
|
| 144 |
+
sleep(5);
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
void Custom::InitLowCmd()
|
| 149 |
+
{
|
| 150 |
+
low_cmd.head()[0] = 0xFE;
|
| 151 |
+
low_cmd.head()[1] = 0xEF;
|
| 152 |
+
low_cmd.level_flag() = 0xFF;
|
| 153 |
+
low_cmd.gpio() = 0;
|
| 154 |
+
|
| 155 |
+
for(int i=0; i<20; i++)
|
| 156 |
+
{
|
| 157 |
+
low_cmd.motor_cmd()[i].mode() = (0x01); // motor switch to servo (PMSM) mode
|
| 158 |
+
low_cmd.motor_cmd()[i].q() = (PosStopF);
|
| 159 |
+
low_cmd.motor_cmd()[i].kp() = (0);
|
| 160 |
+
low_cmd.motor_cmd()[i].dq() = (VelStopF);
|
| 161 |
+
low_cmd.motor_cmd()[i].kd() = (0);
|
| 162 |
+
low_cmd.motor_cmd()[i].tau() = (0);
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
int Custom::queryMotionStatus()
|
| 167 |
+
{
|
| 168 |
+
std::string robotForm,motionName;
|
| 169 |
+
int motionStatus;
|
| 170 |
+
int32_t ret = msc.CheckMode(robotForm,motionName);
|
| 171 |
+
if (ret == 0) {
|
| 172 |
+
std::cout << "CheckMode succeeded." << std::endl;
|
| 173 |
+
} else {
|
| 174 |
+
std::cout << "CheckMode failed. Error code: " << ret << std::endl;
|
| 175 |
+
}
|
| 176 |
+
if(motionName.empty())
|
| 177 |
+
{
|
| 178 |
+
std::cout << "The motion control-related service is deactivated." << std::endl;
|
| 179 |
+
motionStatus = 0;
|
| 180 |
+
}
|
| 181 |
+
else
|
| 182 |
+
{
|
| 183 |
+
std::string serviceName = queryServiceName(robotForm,motionName);
|
| 184 |
+
std::cout << "Service: "<< serviceName<< " is activate" << std::endl;
|
| 185 |
+
motionStatus = 1;
|
| 186 |
+
}
|
| 187 |
+
return motionStatus;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
std::string Custom::queryServiceName(std::string form,std::string name)
|
| 191 |
+
{
|
| 192 |
+
if(form == "0")
|
| 193 |
+
{
|
| 194 |
+
if(name == "normal" ) return "sport_mode";
|
| 195 |
+
if(name == "ai" ) return "ai_sport";
|
| 196 |
+
if(name == "advanced" ) return "advanced_sport";
|
| 197 |
+
}
|
| 198 |
+
else
|
| 199 |
+
{
|
| 200 |
+
if(name == "ai-w" ) return "wheeled_sport(go2W)";
|
| 201 |
+
if(name == "normal-w" ) return "wheeled_sport(b2W)";
|
| 202 |
+
}
|
| 203 |
+
return "";
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
void Custom::Start()
|
| 207 |
+
{
|
| 208 |
+
/*loop publishing thread*/
|
| 209 |
+
lowCmdWriteThreadPtr = CreateRecurrentThreadEx("writebasiccmd", UT_CPU_ID_NONE, 2000, &Custom::LowCmdWrite, this);
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
void Custom::LowStateMessageHandler(const void* message)
|
| 213 |
+
{
|
| 214 |
+
low_state = *(unitree_go::msg::dds_::LowState_*)message;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
void Custom::LowCmdWrite()
|
| 218 |
+
{
|
| 219 |
+
if(_percent_4<1)
|
| 220 |
+
{
|
| 221 |
+
std::cout<<"Read sensor data example: "<<std::endl;
|
| 222 |
+
std::cout<<"Joint 0 pos: "<<low_state.motor_state()[0].q()<<std::endl;
|
| 223 |
+
std::cout<<"Imu accelerometer : "<<"x: "<<low_state.imu_state().accelerometer()[0]<<" y: "<<low_state.imu_state().accelerometer()[1]<<" z: "<<low_state.imu_state().accelerometer()[2]<<std::endl;
|
| 224 |
+
std::cout<<"Foot force "<<low_state.foot_force()[0]<<std::endl;
|
| 225 |
+
std::cout<<std::endl;
|
| 226 |
+
}
|
| 227 |
+
if((_percent_4 == 1) && ( done == false))
|
| 228 |
+
{
|
| 229 |
+
std::cout<<"The example is done! "<<std::endl;
|
| 230 |
+
std::cout<<std::endl;
|
| 231 |
+
done = true;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
motiontime++;
|
| 235 |
+
if(motiontime>=500)
|
| 236 |
+
{
|
| 237 |
+
if(firstRun)
|
| 238 |
+
{
|
| 239 |
+
for(int i = 0; i < 12; i++)
|
| 240 |
+
{
|
| 241 |
+
_startPos[i] = low_state.motor_state()[i].q();
|
| 242 |
+
}
|
| 243 |
+
firstRun = false;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
_percent_1 += (float)1 / _duration_1;
|
| 247 |
+
_percent_1 = _percent_1 > 1 ? 1 : _percent_1;
|
| 248 |
+
if (_percent_1 < 1)
|
| 249 |
+
{
|
| 250 |
+
for (int j = 0; j < 12; j++)
|
| 251 |
+
{
|
| 252 |
+
low_cmd.motor_cmd()[j].q() = (1 - _percent_1) * _startPos[j] + _percent_1 * _targetPos_1[j];
|
| 253 |
+
low_cmd.motor_cmd()[j].dq() = 0;
|
| 254 |
+
low_cmd.motor_cmd()[j].kp() = Kp;
|
| 255 |
+
low_cmd.motor_cmd()[j].kd() = Kd;
|
| 256 |
+
low_cmd.motor_cmd()[j].tau() = 0;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
}
|
| 260 |
+
if ((_percent_1 == 1)&&(_percent_2 < 1))
|
| 261 |
+
{
|
| 262 |
+
_percent_2 += (float)1 / _duration_2;
|
| 263 |
+
_percent_2 = _percent_2 > 1 ? 1 : _percent_2;
|
| 264 |
+
|
| 265 |
+
for (int j = 0; j < 12; j++)
|
| 266 |
+
{
|
| 267 |
+
low_cmd.motor_cmd()[j].q() = (1 - _percent_2) * _targetPos_1[j] + _percent_2 * _targetPos_2[j];
|
| 268 |
+
low_cmd.motor_cmd()[j].dq() = 0;
|
| 269 |
+
low_cmd.motor_cmd()[j].kp() = Kp;
|
| 270 |
+
low_cmd.motor_cmd()[j].kd() = Kd;
|
| 271 |
+
low_cmd.motor_cmd()[j].tau() = 0;
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
if ((_percent_1 == 1)&&(_percent_2 == 1)&&(_percent_3<1))
|
| 276 |
+
{
|
| 277 |
+
_percent_3 += (float)1 / _duration_3;
|
| 278 |
+
_percent_3 = _percent_3 > 1 ? 1 : _percent_3;
|
| 279 |
+
|
| 280 |
+
for (int j = 0; j < 12; j++)
|
| 281 |
+
{
|
| 282 |
+
low_cmd.motor_cmd()[j].q() = _targetPos_2[j];
|
| 283 |
+
low_cmd.motor_cmd()[j].dq() = 0;
|
| 284 |
+
low_cmd.motor_cmd()[j].kp() = Kp;
|
| 285 |
+
low_cmd.motor_cmd()[j].kd() = Kd;
|
| 286 |
+
low_cmd.motor_cmd()[j].tau() = 0;
|
| 287 |
+
}
|
| 288 |
+
if(_percent_3<0.4)
|
| 289 |
+
{
|
| 290 |
+
for (int j = 12; j < 16; j++)
|
| 291 |
+
{
|
| 292 |
+
low_cmd.motor_cmd()[j].q() = 0;
|
| 293 |
+
low_cmd.motor_cmd()[j].kp() = 0;
|
| 294 |
+
low_cmd.motor_cmd()[j].dq() = 3;
|
| 295 |
+
|
| 296 |
+
low_cmd.motor_cmd()[j].kd() = Kd;
|
| 297 |
+
low_cmd.motor_cmd()[j].tau() = 0;
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
}
|
| 301 |
+
else if((_percent_3>=0.4)&&(_percent_3<0.8))
|
| 302 |
+
{
|
| 303 |
+
for (int j = 12; j < 16; j++)
|
| 304 |
+
{
|
| 305 |
+
low_cmd.motor_cmd()[j].q() = 0;
|
| 306 |
+
low_cmd.motor_cmd()[j].kp() = 0;
|
| 307 |
+
low_cmd.motor_cmd()[j].dq() = -3;
|
| 308 |
+
|
| 309 |
+
low_cmd.motor_cmd()[j].kd() = Kd;
|
| 310 |
+
low_cmd.motor_cmd()[j].tau() = 0;
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
}
|
| 314 |
+
else if(_percent_3>=0.8)
|
| 315 |
+
{
|
| 316 |
+
for (int j = 12; j < 16; j++)
|
| 317 |
+
{
|
| 318 |
+
low_cmd.motor_cmd()[j].q() = 0;
|
| 319 |
+
low_cmd.motor_cmd()[j].kp() = 0;
|
| 320 |
+
low_cmd.motor_cmd()[j].dq() = 0;
|
| 321 |
+
|
| 322 |
+
low_cmd.motor_cmd()[j].kd() = Kd;
|
| 323 |
+
low_cmd.motor_cmd()[j].tau() = 0;
|
| 324 |
+
}
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
}
|
| 328 |
+
if ((_percent_1 == 1)&&(_percent_2 == 1)&&(_percent_3==1)&&((_percent_4<=1)))
|
| 329 |
+
{
|
| 330 |
+
_percent_4 += (float)1 / _duration_4;
|
| 331 |
+
_percent_4 = _percent_4 > 1 ? 1 : _percent_4;
|
| 332 |
+
for (int j = 0; j < 12; j++)
|
| 333 |
+
{
|
| 334 |
+
low_cmd.motor_cmd()[j].q() = (1 - _percent_4) * _targetPos_2[j] + _percent_4 * _targetPos_3[j];
|
| 335 |
+
low_cmd.motor_cmd()[j].dq() = 0;
|
| 336 |
+
low_cmd.motor_cmd()[j].kp() = Kp;
|
| 337 |
+
low_cmd.motor_cmd()[j].kd() = Kd;
|
| 338 |
+
low_cmd.motor_cmd()[j].tau() = 0;
|
| 339 |
+
}
|
| 340 |
+
}
|
| 341 |
+
low_cmd.crc() = crc32_core((uint32_t *)&low_cmd, (sizeof(unitree_go::msg::dds_::LowCmd_)>>2)-1);
|
| 342 |
+
|
| 343 |
+
lowcmd_publisher->Write(low_cmd);
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
int main(int argc, const char** argv)
|
| 349 |
+
{
|
| 350 |
+
if (argc < 2)
|
| 351 |
+
{
|
| 352 |
+
std::cout << "Usage: " << argv[0] << " networkInterface" << std::endl;
|
| 353 |
+
exit(-1);
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
std::cout << "WARNING: Make sure the robot is lying on the ground." << std::endl
|
| 357 |
+
<< "Press Enter to continue..." << std::endl;
|
| 358 |
+
std::cin.ignore();
|
| 359 |
+
|
| 360 |
+
ChannelFactory::Instance()->Init(0, argv[1]);
|
| 361 |
+
|
| 362 |
+
Custom custom;
|
| 363 |
+
custom.Init();
|
| 364 |
+
custom.Start();
|
| 365 |
+
|
| 366 |
+
while (1)
|
| 367 |
+
{
|
| 368 |
+
sleep(10);
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
return 0;
|
| 372 |
+
}
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/CMakeLists.txt
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
add_executable(g1_loco_client high_level/g1_loco_client_example.cpp)
|
| 2 |
+
target_link_libraries(g1_loco_client unitree_sdk2)
|
| 3 |
+
|
| 4 |
+
add_executable(g1_arm5_sdk_dds_example high_level/g1_arm5_sdk_dds_example.cpp)
|
| 5 |
+
target_link_libraries(g1_arm5_sdk_dds_example unitree_sdk2)
|
| 6 |
+
|
| 7 |
+
add_executable(g1_arm7_sdk_dds_example high_level/g1_arm7_sdk_dds_example.cpp)
|
| 8 |
+
target_link_libraries(g1_arm7_sdk_dds_example unitree_sdk2)
|
| 9 |
+
|
| 10 |
+
add_executable(g1_arm_action_example high_level/g1_arm_action_example.cpp)
|
| 11 |
+
target_link_libraries(g1_arm_action_example unitree_sdk2)
|
| 12 |
+
|
| 13 |
+
add_executable(g1_ankle_swing_example low_level/g1_ankle_swing_example.cpp)
|
| 14 |
+
target_link_libraries(g1_ankle_swing_example unitree_sdk2)
|
| 15 |
+
|
| 16 |
+
add_executable(g1_audio_client_example audio/g1_audio_client_example.cpp)
|
| 17 |
+
target_link_libraries(g1_audio_client_example unitree_sdk2)
|
| 18 |
+
|
| 19 |
+
add_executable(g1_dex3_example dex3/g1_dex3_example.cpp)
|
| 20 |
+
target_link_libraries(g1_dex3_example unitree_sdk2)
|
| 21 |
+
|
| 22 |
+
find_package(Boost COMPONENTS program_options)
|
| 23 |
+
if(Boost_FOUND)
|
| 24 |
+
add_executable(g1_termination low_level/terminations.cpp)
|
| 25 |
+
target_link_libraries(g1_termination unitree_sdk2 Boost::program_options)
|
| 26 |
+
endif()
|
| 27 |
+
|
| 28 |
+
find_package(yaml-cpp QUIET)
|
| 29 |
+
if(yaml-cpp_FOUND)
|
| 30 |
+
if (${yaml-cpp_VERSION} VERSION_GREATER_EQUAL "0.6")
|
| 31 |
+
message(STATUS "Found yaml-cpp version ${yaml-cpp_VERSION}")
|
| 32 |
+
add_executable(g1_dual_arm_example low_level/g1_dual_arm_example.cpp)
|
| 33 |
+
target_link_libraries(g1_dual_arm_example PRIVATE unitree_sdk2 yaml-cpp)
|
| 34 |
+
target_compile_definitions(g1_dual_arm_example PUBLIC BLIB_DIR="${CMAKE_CURRENT_SOURCE_DIR}/low_level/behavior_lib/")
|
| 35 |
+
else()
|
| 36 |
+
message(STATUS "yaml-cpp version ${yaml-cpp_VERSION} is too old, skipping build of g1_dual_arm_example.")
|
| 37 |
+
endif()
|
| 38 |
+
endif()
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/audio/g1_audio_client_example.cpp
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <fstream>
|
| 2 |
+
#include <iostream>
|
| 3 |
+
#include <thread>
|
| 4 |
+
#include <unitree/common/time/time_tool.hpp>
|
| 5 |
+
#include <unitree/idl/ros2/String_.hpp>
|
| 6 |
+
#include <unitree/robot/channel/channel_subscriber.hpp>
|
| 7 |
+
#include <unitree/robot/g1/audio/g1_audio_client.hpp>
|
| 8 |
+
|
| 9 |
+
#include "wav.hpp"
|
| 10 |
+
|
| 11 |
+
#define AUDIO_FILE_PATH "../example/g1/audio/test.wav"
|
| 12 |
+
#define AUDIO_SUBSCRIBE_TOPIC "rt/audio_msg"
|
| 13 |
+
#define GROUP_IP "239.168.123.161"
|
| 14 |
+
#define PORT 5555
|
| 15 |
+
|
| 16 |
+
#define WAV_SECOND 5 // record seconds
|
| 17 |
+
#define WAV_LEN (16000 * 2 * WAV_SECOND)
|
| 18 |
+
#define CHUNK_SIZE 96000 // 3 seconds
|
| 19 |
+
int sock;
|
| 20 |
+
|
| 21 |
+
void asr_handler(const void *msg) {
|
| 22 |
+
std_msgs::msg::dds_::String_ *resMsg = (std_msgs::msg::dds_::String_ *)msg;
|
| 23 |
+
std::cout << "Topic:\"rt/audio_msg\" recv: " << resMsg->data() << std::endl;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
std::string get_local_ip_for_multicast() {
|
| 27 |
+
struct ifaddrs *ifaddr, *ifa;
|
| 28 |
+
char host[NI_MAXHOST];
|
| 29 |
+
std::string result = "";
|
| 30 |
+
|
| 31 |
+
getifaddrs(&ifaddr);
|
| 32 |
+
for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
|
| 33 |
+
if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET) continue;
|
| 34 |
+
getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST,
|
| 35 |
+
NULL, 0, NI_NUMERICHOST);
|
| 36 |
+
std::string ip(host);
|
| 37 |
+
if (ip.find("192.168.123.") == 0) {
|
| 38 |
+
result = ip;
|
| 39 |
+
break;
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
freeifaddrs(ifaddr);
|
| 43 |
+
return result;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
void thread_mic(void) {
|
| 47 |
+
sock = socket(AF_INET, SOCK_DGRAM, 0);
|
| 48 |
+
sockaddr_in local_addr{};
|
| 49 |
+
local_addr.sin_family = AF_INET;
|
| 50 |
+
local_addr.sin_port = htons(PORT);
|
| 51 |
+
local_addr.sin_addr.s_addr = INADDR_ANY;
|
| 52 |
+
bind(sock, (sockaddr *)&local_addr, sizeof(local_addr));
|
| 53 |
+
|
| 54 |
+
ip_mreq mreq{};
|
| 55 |
+
inet_pton(AF_INET, GROUP_IP, &mreq.imr_multiaddr);
|
| 56 |
+
std::string local_ip = get_local_ip_for_multicast();
|
| 57 |
+
std::cout << "local ip: " << local_ip << std::endl;
|
| 58 |
+
mreq.imr_interface.s_addr = inet_addr(local_ip.c_str());
|
| 59 |
+
setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
|
| 60 |
+
|
| 61 |
+
int total_bytes = 0;
|
| 62 |
+
std::vector<int16_t> pcm_data;
|
| 63 |
+
pcm_data.reserve(WAV_LEN / 2);
|
| 64 |
+
std::cout << "start record!" << std::endl;
|
| 65 |
+
while (total_bytes < WAV_LEN) {
|
| 66 |
+
char buffer[2048];
|
| 67 |
+
ssize_t len = recvfrom(sock, buffer, sizeof(buffer), 0, nullptr, nullptr);
|
| 68 |
+
if (len > 0) {
|
| 69 |
+
size_t sample_count = len / 2;
|
| 70 |
+
const int16_t *samples = reinterpret_cast<const int16_t *>(buffer);
|
| 71 |
+
pcm_data.insert(pcm_data.end(), samples, samples + sample_count);
|
| 72 |
+
total_bytes += len;
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
WriteWave("record.wav", 16000, pcm_data.data(), pcm_data.size(), 1);
|
| 77 |
+
std::cout << "record finish! save to record.wav " << std::endl;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
int main(int argc, char const *argv[]) {
|
| 81 |
+
if (argc < 2) {
|
| 82 |
+
std::cout << "Usage: audio_client_example [NetWorkInterface(eth0)]"
|
| 83 |
+
<< std::endl;
|
| 84 |
+
exit(0);
|
| 85 |
+
}
|
| 86 |
+
int32_t ret;
|
| 87 |
+
/*
|
| 88 |
+
* Initilaize ChannelFactory
|
| 89 |
+
*/
|
| 90 |
+
unitree::robot::ChannelFactory::Instance()->Init(0, argv[1]);
|
| 91 |
+
unitree::robot::g1::AudioClient client;
|
| 92 |
+
client.Init();
|
| 93 |
+
client.SetTimeout(10.0f);
|
| 94 |
+
|
| 95 |
+
/*ASR message Example*/
|
| 96 |
+
unitree::robot::ChannelSubscriber<std_msgs::msg::dds_::String_> subscriber(
|
| 97 |
+
AUDIO_SUBSCRIBE_TOPIC);
|
| 98 |
+
subscriber.InitChannel(asr_handler);
|
| 99 |
+
|
| 100 |
+
/*Volume Example*/
|
| 101 |
+
uint8_t volume;
|
| 102 |
+
ret = client.GetVolume(volume);
|
| 103 |
+
std::cout << "GetVolume API ret:" << ret
|
| 104 |
+
<< " volume = " << std::to_string(volume) << std::endl;
|
| 105 |
+
ret = client.SetVolume(100);
|
| 106 |
+
std::cout << "SetVolume to 100% , API ret:" << ret << std::endl;
|
| 107 |
+
|
| 108 |
+
/*TTS Example*/
|
| 109 |
+
ret = client.TtsMaker("你好。我是宇树科技的机器人。例程启动成功",
|
| 110 |
+
0); // Auto play
|
| 111 |
+
std::cout << "TtsMaker API ret:" << ret << std::endl;
|
| 112 |
+
unitree::common::Sleep(5);
|
| 113 |
+
|
| 114 |
+
ret = client.TtsMaker(
|
| 115 |
+
"Hello. I'm a robot from Unitree Robotics. The example has started "
|
| 116 |
+
"successfully. ",
|
| 117 |
+
1); // Engilsh TTS
|
| 118 |
+
std::cout << "TtsMaker API ret:" << ret << std::endl;
|
| 119 |
+
unitree::common::Sleep(8);
|
| 120 |
+
|
| 121 |
+
/*Audio Play Example*/
|
| 122 |
+
int32_t sample_rate = -1;
|
| 123 |
+
int8_t num_channels = 0;
|
| 124 |
+
bool filestate = false;
|
| 125 |
+
std::vector<uint8_t> pcm =
|
| 126 |
+
ReadWave(AUDIO_FILE_PATH, &sample_rate, &num_channels, &filestate);
|
| 127 |
+
|
| 128 |
+
std::cout << "wav file sample_rate = " << sample_rate
|
| 129 |
+
<< " num_channels = " << std::to_string(num_channels)
|
| 130 |
+
<< " filestate =" << filestate << "filesize = " << pcm.size()
|
| 131 |
+
<< std::endl;
|
| 132 |
+
|
| 133 |
+
if (filestate && sample_rate == 16000 && num_channels == 1) {
|
| 134 |
+
size_t total_size = pcm.size();
|
| 135 |
+
size_t offset = 0;
|
| 136 |
+
int chunk_index = 0;
|
| 137 |
+
std::string stream_id =
|
| 138 |
+
std::to_string(unitree::common::GetCurrentTimeMillisecond());
|
| 139 |
+
|
| 140 |
+
while (offset < total_size) {
|
| 141 |
+
size_t remaining = total_size - offset;
|
| 142 |
+
size_t current_chunk_size =
|
| 143 |
+
std::min(static_cast<size_t>(CHUNK_SIZE), remaining);
|
| 144 |
+
std::vector<uint8_t> chunk(pcm.begin() + offset,
|
| 145 |
+
pcm.begin() + offset + current_chunk_size);
|
| 146 |
+
client.PlayStream("example", stream_id, chunk);
|
| 147 |
+
unitree::common::Sleep(1);
|
| 148 |
+
std::cout << "Playing size: " << offset << std::endl;
|
| 149 |
+
offset += current_chunk_size;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
ret = client.PlayStop(stream_id); // stop playback after transmission ends
|
| 153 |
+
|
| 154 |
+
} else {
|
| 155 |
+
std::cout << "audio file format error, please check!" << std::endl;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
/*LED Control Example*/
|
| 159 |
+
client.LedControl(0, 255, 0);
|
| 160 |
+
unitree::common::Sleep(1);
|
| 161 |
+
client.LedControl(0, 0, 0);
|
| 162 |
+
unitree::common::Sleep(1);
|
| 163 |
+
client.LedControl(0, 0, 255);
|
| 164 |
+
|
| 165 |
+
std::cout << "AudioClient api test finish , asr start..." << std::endl;
|
| 166 |
+
|
| 167 |
+
std::thread mic_t(thread_mic);
|
| 168 |
+
|
| 169 |
+
while (1) {
|
| 170 |
+
sleep(1); // wait for asr message
|
| 171 |
+
}
|
| 172 |
+
mic_t.join();
|
| 173 |
+
return 0;
|
| 174 |
+
}
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/audio/wav.hpp
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#ifndef __UT_ROBOT_WAV_READER_HPP__
|
| 2 |
+
#define __UT_ROBOT_WAV_READER_HPP__
|
| 3 |
+
|
| 4 |
+
struct WaveHeader {
|
| 5 |
+
void SeekToDataChunk(std::istream &is) {
|
| 6 |
+
while (is && subchunk2_id != 0x61746164) {
|
| 7 |
+
is.seekg(subchunk2_size, std::istream::cur);
|
| 8 |
+
is.read(reinterpret_cast<char *>(&subchunk2_id), sizeof(int32_t));
|
| 9 |
+
is.read(reinterpret_cast<char *>(&subchunk2_size), sizeof(int32_t));
|
| 10 |
+
}
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
int32_t chunk_id;
|
| 14 |
+
int32_t chunk_size;
|
| 15 |
+
int32_t format;
|
| 16 |
+
int32_t subchunk1_id;
|
| 17 |
+
int32_t subchunk1_size;
|
| 18 |
+
int16_t audio_format;
|
| 19 |
+
int16_t num_channels;
|
| 20 |
+
int32_t sample_rate;
|
| 21 |
+
int32_t byte_rate;
|
| 22 |
+
int16_t block_align;
|
| 23 |
+
int16_t bits_per_sample;
|
| 24 |
+
int32_t subchunk2_id; // a tag of this chunk
|
| 25 |
+
int32_t subchunk2_size; // size of subchunk2
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
static_assert(sizeof(WaveHeader) == 44);
|
| 29 |
+
|
| 30 |
+
std::vector<uint8_t> ReadWaveImpl(std::istream &is, int32_t *sampling_rate,
|
| 31 |
+
int8_t *channelCount, bool *is_ok) {
|
| 32 |
+
WaveHeader header{};
|
| 33 |
+
is.read(reinterpret_cast<char *>(&header.chunk_id), sizeof(header.chunk_id));
|
| 34 |
+
|
| 35 |
+
// F F I R
|
| 36 |
+
if (header.chunk_id != 0x46464952) {
|
| 37 |
+
printf("Expected chunk_id RIFF. Given: 0x%08x\n", header.chunk_id);
|
| 38 |
+
*is_ok = false;
|
| 39 |
+
return {};
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
is.read(reinterpret_cast<char *>(&header.chunk_size),
|
| 43 |
+
sizeof(header.chunk_size));
|
| 44 |
+
|
| 45 |
+
is.read(reinterpret_cast<char *>(&header.format), sizeof(header.format));
|
| 46 |
+
|
| 47 |
+
// E V A W
|
| 48 |
+
if (header.format != 0x45564157) {
|
| 49 |
+
printf("Expected format WAVE. Given: 0x%08x\n", header.format);
|
| 50 |
+
*is_ok = false;
|
| 51 |
+
return {};
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
is.read(reinterpret_cast<char *>(&header.subchunk1_id),
|
| 55 |
+
sizeof(header.subchunk1_id));
|
| 56 |
+
|
| 57 |
+
is.read(reinterpret_cast<char *>(&header.subchunk1_size),
|
| 58 |
+
sizeof(header.subchunk1_size));
|
| 59 |
+
|
| 60 |
+
if (header.subchunk1_id == 0x4b4e554a) {
|
| 61 |
+
// skip junk padding
|
| 62 |
+
is.seekg(header.subchunk1_size, std::istream::cur);
|
| 63 |
+
|
| 64 |
+
is.read(reinterpret_cast<char *>(&header.subchunk1_id),
|
| 65 |
+
sizeof(header.subchunk1_id));
|
| 66 |
+
|
| 67 |
+
is.read(reinterpret_cast<char *>(&header.subchunk1_size),
|
| 68 |
+
sizeof(header.subchunk1_size));
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
if (header.subchunk1_id != 0x20746d66) {
|
| 72 |
+
printf("Expected subchunk1_id 0x20746d66. Given: 0x%08x\n",
|
| 73 |
+
header.subchunk1_id);
|
| 74 |
+
*is_ok = false;
|
| 75 |
+
return {};
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
if (header.subchunk1_size != 16 &&
|
| 79 |
+
header.subchunk1_size != 18) { // 16 for PCM
|
| 80 |
+
printf("Expected subchunk1_size 16. Given: %d\n", header.subchunk1_size);
|
| 81 |
+
*is_ok = false;
|
| 82 |
+
return {};
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
is.read(reinterpret_cast<char *>(&header.audio_format),
|
| 86 |
+
sizeof(header.audio_format));
|
| 87 |
+
|
| 88 |
+
if (header.audio_format != 1) { // 1 for PCM
|
| 89 |
+
printf("Expected audio_format 1. Given: %d\n", header.audio_format);
|
| 90 |
+
*is_ok = false;
|
| 91 |
+
return {};
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
is.read(reinterpret_cast<char *>(&header.num_channels),
|
| 95 |
+
sizeof(header.num_channels));
|
| 96 |
+
|
| 97 |
+
*channelCount = (int8_t)header.num_channels;
|
| 98 |
+
|
| 99 |
+
is.read(reinterpret_cast<char *>(&header.sample_rate),
|
| 100 |
+
sizeof(header.sample_rate));
|
| 101 |
+
|
| 102 |
+
is.read(reinterpret_cast<char *>(&header.byte_rate),
|
| 103 |
+
sizeof(header.byte_rate));
|
| 104 |
+
|
| 105 |
+
is.read(reinterpret_cast<char *>(&header.block_align),
|
| 106 |
+
sizeof(header.block_align));
|
| 107 |
+
|
| 108 |
+
is.read(reinterpret_cast<char *>(&header.bits_per_sample),
|
| 109 |
+
sizeof(header.bits_per_sample));
|
| 110 |
+
|
| 111 |
+
if (header.byte_rate !=
|
| 112 |
+
(header.sample_rate * header.num_channels * header.bits_per_sample / 8)) {
|
| 113 |
+
printf("Incorrect byte rate: %d. Expected: %d", header.byte_rate,
|
| 114 |
+
(header.sample_rate * header.num_channels * header.bits_per_sample /
|
| 115 |
+
8));
|
| 116 |
+
*is_ok = false;
|
| 117 |
+
return {};
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
if (header.block_align !=
|
| 121 |
+
(header.num_channels * header.bits_per_sample / 8)) {
|
| 122 |
+
printf("Incorrect block align: %d. Expected: %d\n", header.block_align,
|
| 123 |
+
(header.num_channels * header.bits_per_sample / 8));
|
| 124 |
+
*is_ok = false;
|
| 125 |
+
return {};
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
if (header.bits_per_sample != 16) { // we support only 16 bits per sample
|
| 129 |
+
printf("Expected bits_per_sample 16. Given: %d\n", header.bits_per_sample);
|
| 130 |
+
*is_ok = false;
|
| 131 |
+
return {};
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
if (header.subchunk1_size == 18) {
|
| 135 |
+
int16_t extra_size = -1;
|
| 136 |
+
is.read(reinterpret_cast<char *>(&extra_size), sizeof(int16_t));
|
| 137 |
+
if (extra_size != 0) {
|
| 138 |
+
printf(
|
| 139 |
+
"Extra size should be 0 for wave from NAudio. Current extra size "
|
| 140 |
+
"%d\n",
|
| 141 |
+
extra_size);
|
| 142 |
+
*is_ok = false;
|
| 143 |
+
return {};
|
| 144 |
+
}
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
is.read(reinterpret_cast<char *>(&header.subchunk2_id),
|
| 148 |
+
sizeof(header.subchunk2_id));
|
| 149 |
+
|
| 150 |
+
is.read(reinterpret_cast<char *>(&header.subchunk2_size),
|
| 151 |
+
sizeof(header.subchunk2_size));
|
| 152 |
+
|
| 153 |
+
header.SeekToDataChunk(is);
|
| 154 |
+
if (!is) {
|
| 155 |
+
*is_ok = false;
|
| 156 |
+
return {};
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
*sampling_rate = header.sample_rate;
|
| 160 |
+
|
| 161 |
+
// header.subchunk2_size contains the number of bytes in the data.
|
| 162 |
+
// As we assume each sample contains two bytes, so it is divided by 2 here
|
| 163 |
+
std::vector<int16_t> samples(header.subchunk2_size / 2);
|
| 164 |
+
|
| 165 |
+
is.read(reinterpret_cast<char *>(samples.data()), header.subchunk2_size);
|
| 166 |
+
if (!is) {
|
| 167 |
+
*is_ok = false;
|
| 168 |
+
return {};
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
std::vector<uint8_t> ans(samples.size() * 2);
|
| 172 |
+
for (int32_t i = 0; i != static_cast<int32_t>(samples.size()); ++i) {
|
| 173 |
+
ans[i * 2] = samples[i] & 0xFF;
|
| 174 |
+
ans[i * 2 + 1] = (samples[i] >> 8) & 0xFF;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
*is_ok = true;
|
| 178 |
+
return ans;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
std::vector<uint8_t> ReadWave(const std::string &filename,
|
| 182 |
+
int32_t *sampling_rate, int8_t *channelCount,
|
| 183 |
+
bool *is_ok) {
|
| 184 |
+
std::ifstream is(filename, std::ifstream::binary);
|
| 185 |
+
auto samples = ReadWaveImpl(is, sampling_rate, channelCount, is_ok);
|
| 186 |
+
return samples;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
bool WriteWave(const std::string &filename, int32_t sampling_rate,
|
| 190 |
+
const int16_t *samples, int32_t n, uint8_t num_channels) {
|
| 191 |
+
WaveHeader header{};
|
| 192 |
+
header.chunk_id = 0x46464952; // FFIR
|
| 193 |
+
header.format = 0x45564157; // EVAW
|
| 194 |
+
header.subchunk1_id = 0x20746d66; // "fmt "
|
| 195 |
+
header.subchunk1_size = 16; // 16 for PCM
|
| 196 |
+
header.audio_format = 1; // PCM =1
|
| 197 |
+
|
| 198 |
+
int32_t bits_per_sample = 16; // int16_t
|
| 199 |
+
header.num_channels = num_channels;
|
| 200 |
+
header.sample_rate = sampling_rate;
|
| 201 |
+
header.byte_rate = sampling_rate * num_channels * bits_per_sample / 8;
|
| 202 |
+
header.block_align = num_channels * bits_per_sample / 8;
|
| 203 |
+
header.bits_per_sample = bits_per_sample;
|
| 204 |
+
header.subchunk2_id = 0x61746164; // atad
|
| 205 |
+
header.subchunk2_size = n * num_channels * bits_per_sample / 8;
|
| 206 |
+
|
| 207 |
+
header.chunk_size = 36 + header.subchunk2_size;
|
| 208 |
+
|
| 209 |
+
std::vector<int16_t> samples_int16(n * num_channels);
|
| 210 |
+
for (int32_t i = 0; i != n * num_channels; ++i) {
|
| 211 |
+
samples_int16[i] = samples[i];
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
std::ofstream os(filename, std::ios::binary);
|
| 215 |
+
if (!os) {
|
| 216 |
+
printf("Failed to create %s", filename.c_str());
|
| 217 |
+
return false;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
os.write(reinterpret_cast<const char *>(&header), sizeof(header));
|
| 221 |
+
os.write(reinterpret_cast<const char *>(samples_int16.data()),
|
| 222 |
+
samples_int16.size() * sizeof(int16_t));
|
| 223 |
+
|
| 224 |
+
if (!os) {
|
| 225 |
+
printf("Write %s failed", filename.c_str());
|
| 226 |
+
return false;
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
return true;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
#endif
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/dex3/g1_dex3_example.cpp
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <chrono>
|
| 2 |
+
#include <thread>
|
| 3 |
+
#include <unitree/idl/hg/HandState_.hpp> //replace your sdk path
|
| 4 |
+
#include <unitree/idl/hg/HandCmd_.hpp> //replace your sdk path
|
| 5 |
+
#include <unitree/robot/channel/channel_publisher.hpp>
|
| 6 |
+
#include <unitree/robot/channel/channel_subscriber.hpp>
|
| 7 |
+
#include <iostream>
|
| 8 |
+
#include <unistd.h>
|
| 9 |
+
#include <atomic>
|
| 10 |
+
#include <mutex>
|
| 11 |
+
#include <cmath>
|
| 12 |
+
#include <termios.h>
|
| 13 |
+
#include <unistd.h>
|
| 14 |
+
#include <eigen3/Eigen/Dense>
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
enum State {
|
| 18 |
+
INIT,
|
| 19 |
+
ROTATE,
|
| 20 |
+
GRIP,
|
| 21 |
+
STOP,
|
| 22 |
+
PRINT
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
// set URDF Limits
|
| 26 |
+
const float maxLimits_left[7]= { 1.05 , 1.05 , 1.75 , 0 , 0 , 0 , 0 }; // set max motor value
|
| 27 |
+
const float minLimits_left[7]= { -1.05 , -0.724 , 0 , -1.57 , -1.75 , -1.57 ,-1.75};
|
| 28 |
+
const float maxLimits_right[7]= { 1.05 , 0.742 , 0 , 1.57 , 1.75 , 1.57 , 1.75};
|
| 29 |
+
const float minLimits_right[7]= { -1.05 , -1.05 , -1.75, 0 , 0 , 0 ,0 };
|
| 30 |
+
|
| 31 |
+
// Initing the dds configuration
|
| 32 |
+
std::string dds_namespace = "rt/dex3/left";
|
| 33 |
+
std::string sub_namespace = "rt/dex3/left/state";
|
| 34 |
+
unitree::robot::ChannelPublisherPtr<unitree_hg::msg::dds_::HandCmd_> handcmd_publisher;
|
| 35 |
+
unitree::robot::ChannelSubscriberPtr<unitree_hg::msg::dds_::HandState_> handstate_subscriber;
|
| 36 |
+
unitree_hg::msg::dds_::HandCmd_ msg;
|
| 37 |
+
unitree_hg::msg::dds_::HandState_ state;
|
| 38 |
+
std::atomic<State> currentState(INIT);
|
| 39 |
+
std::mutex stateMutex;
|
| 40 |
+
|
| 41 |
+
#define MOTOR_MAX 7
|
| 42 |
+
#define SENSOR_MAX 9
|
| 43 |
+
uint8_t hand_id = 0;
|
| 44 |
+
|
| 45 |
+
typedef struct {
|
| 46 |
+
uint8_t id : 4;
|
| 47 |
+
uint8_t status : 3;
|
| 48 |
+
uint8_t timeout: 1;
|
| 49 |
+
} RIS_Mode_t;
|
| 50 |
+
|
| 51 |
+
// stateToString Method
|
| 52 |
+
const char* stateToString(State state) {
|
| 53 |
+
switch (state) {
|
| 54 |
+
case INIT: return "INIT";
|
| 55 |
+
case ROTATE: return "ROTATE";
|
| 56 |
+
case GRIP: return "GRIP";
|
| 57 |
+
case STOP: return "STOP";
|
| 58 |
+
case PRINT: return "PRINT";
|
| 59 |
+
default: return "UNKNOWN";
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
// Monitor user's input
|
| 64 |
+
char getNonBlockingInput() {
|
| 65 |
+
struct termios oldt, newt;
|
| 66 |
+
char ch;
|
| 67 |
+
int oldf;
|
| 68 |
+
|
| 69 |
+
tcgetattr(STDIN_FILENO, &oldt);
|
| 70 |
+
newt = oldt;
|
| 71 |
+
newt.c_lflag &= ~(ICANON | ECHO);
|
| 72 |
+
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
|
| 73 |
+
oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
|
| 74 |
+
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
|
| 75 |
+
|
| 76 |
+
ch = getchar();
|
| 77 |
+
|
| 78 |
+
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
|
| 79 |
+
fcntl(STDIN_FILENO, F_SETFL, oldf);
|
| 80 |
+
|
| 81 |
+
return ch;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
void userInputThread() {
|
| 85 |
+
while (true) {
|
| 86 |
+
char ch = getNonBlockingInput();
|
| 87 |
+
if (ch == 'q') {
|
| 88 |
+
std::cout << "Exiting..." << std::endl;
|
| 89 |
+
currentState = STOP;
|
| 90 |
+
break;
|
| 91 |
+
} else if (ch == 'r') {
|
| 92 |
+
currentState = ROTATE;
|
| 93 |
+
} else if (ch == 'g') {
|
| 94 |
+
currentState = GRIP;
|
| 95 |
+
} else if (ch == 'p') {
|
| 96 |
+
currentState = PRINT;
|
| 97 |
+
} else if (ch == 's') {
|
| 98 |
+
currentState = STOP;
|
| 99 |
+
}
|
| 100 |
+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
// this method can send kp and kd to motors
|
| 105 |
+
void rotateMotors(bool isLeftHand) {
|
| 106 |
+
static int _count = 1;
|
| 107 |
+
static int dir = 1;
|
| 108 |
+
const float* maxLimits = isLeftHand ? maxLimits_left : maxLimits_right;
|
| 109 |
+
const float* minLimits = isLeftHand ? minLimits_left : minLimits_right;
|
| 110 |
+
|
| 111 |
+
for (int i = 0; i < MOTOR_MAX; i++) {
|
| 112 |
+
RIS_Mode_t ris_mode;
|
| 113 |
+
ris_mode.id = i;
|
| 114 |
+
ris_mode.status = 0x01;
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
uint8_t mode = 0;
|
| 118 |
+
mode |= (ris_mode.id & 0x0F);
|
| 119 |
+
mode |= (ris_mode.status & 0x07) << 4;
|
| 120 |
+
mode |= (ris_mode.timeout & 0x01) << 7;
|
| 121 |
+
msg.motor_cmd()[i].mode(mode);
|
| 122 |
+
msg.motor_cmd()[i].tau(0);
|
| 123 |
+
msg.motor_cmd()[i].kp(0.5);
|
| 124 |
+
msg.motor_cmd()[i].kd(0.1);
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
float range = maxLimits[i] - minLimits[i];
|
| 128 |
+
float mid = (maxLimits[i] + minLimits[i]) / 2.0;
|
| 129 |
+
float amplitude = range / 2.0;
|
| 130 |
+
float q = mid + amplitude * sin(_count / 20000.0 * M_PI);
|
| 131 |
+
|
| 132 |
+
msg.motor_cmd()[i].q(q);
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
handcmd_publisher->Write(msg);
|
| 136 |
+
_count += dir;
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
if (_count >= 10000) {
|
| 140 |
+
dir = -1;
|
| 141 |
+
}
|
| 142 |
+
if (_count <= -10000) {
|
| 143 |
+
dir = 1;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
usleep(100);
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
// this method can send static position to motors
|
| 150 |
+
void gripHand(bool isLeftHand) {
|
| 151 |
+
|
| 152 |
+
const float* maxLimits = isLeftHand ? maxLimits_left : maxLimits_right;
|
| 153 |
+
const float* minLimits = isLeftHand ? minLimits_left : minLimits_right;
|
| 154 |
+
|
| 155 |
+
for (int i = 0; i < MOTOR_MAX; i++) {
|
| 156 |
+
RIS_Mode_t ris_mode;
|
| 157 |
+
ris_mode.id = i;
|
| 158 |
+
ris_mode.status = 0x01;
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
uint8_t mode = 0;
|
| 162 |
+
mode |= (ris_mode.id & 0x0F);
|
| 163 |
+
mode |= (ris_mode.status & 0x07) << 4;
|
| 164 |
+
mode |= (ris_mode.timeout & 0x01) << 7;
|
| 165 |
+
msg.motor_cmd()[i].mode(mode);
|
| 166 |
+
msg.motor_cmd()[i].tau(0);
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
float mid = (maxLimits[i] + minLimits[i]) / 2.0;
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
msg.motor_cmd()[i].q(mid);
|
| 173 |
+
msg.motor_cmd()[i].dq(0);
|
| 174 |
+
msg.motor_cmd()[i].kp(1.5);
|
| 175 |
+
msg.motor_cmd()[i].kd(0.1);
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
handcmd_publisher->Write(msg);
|
| 180 |
+
usleep(1000000);
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
// this method can send dynamic position to motors
|
| 184 |
+
void stopMotors() {
|
| 185 |
+
for (int i = 0; i < MOTOR_MAX; i++) {
|
| 186 |
+
RIS_Mode_t ris_mode;
|
| 187 |
+
ris_mode.id = i;
|
| 188 |
+
ris_mode.status = 0x01;
|
| 189 |
+
ris_mode.timeout = 0x01;
|
| 190 |
+
|
| 191 |
+
uint8_t mode = 0;
|
| 192 |
+
mode |= (ris_mode.id & 0x0F);
|
| 193 |
+
mode |= (ris_mode.status & 0x07) << 4;
|
| 194 |
+
mode |= (ris_mode.timeout & 0x01) << 7;
|
| 195 |
+
msg.motor_cmd()[i].mode(mode);
|
| 196 |
+
msg.motor_cmd()[i].tau(0);
|
| 197 |
+
msg.motor_cmd()[i].dq(0);
|
| 198 |
+
msg.motor_cmd()[i].kp(0);
|
| 199 |
+
msg.motor_cmd()[i].kd(0);
|
| 200 |
+
msg.motor_cmd()[i].q(0);
|
| 201 |
+
|
| 202 |
+
}
|
| 203 |
+
handcmd_publisher->Write(msg);
|
| 204 |
+
usleep(1000000);
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
// this method can subscribe dds and show the position for now
|
| 208 |
+
void printState(bool isLeftHand){
|
| 209 |
+
Eigen::Matrix<float, 7, 1> q;
|
| 210 |
+
|
| 211 |
+
const float* maxLimits = isLeftHand ? maxLimits_left : maxLimits_right;
|
| 212 |
+
const float* minLimits = isLeftHand ? minLimits_left : minLimits_right;
|
| 213 |
+
for(int i = 0; i < 7; i++)
|
| 214 |
+
{
|
| 215 |
+
q(i) = state.motor_state()[i].q();
|
| 216 |
+
|
| 217 |
+
q(i) = (q(i) - minLimits[i] ) / (maxLimits[i] - minLimits[i]);
|
| 218 |
+
q(i) = std::clamp(q(i), 0.0f, 1.0f);
|
| 219 |
+
}
|
| 220 |
+
std::cout << "\033[2J\033[H";
|
| 221 |
+
std::cout << "-- Hand State --\n";
|
| 222 |
+
std::cout << "--- Current State: " << "Test" << " ---\n";
|
| 223 |
+
std::cout << "Commands:\n";
|
| 224 |
+
std::cout << " r - Rotate\n";
|
| 225 |
+
std::cout << " g - Grip\n";
|
| 226 |
+
std::cout << " t - Test\n";
|
| 227 |
+
std::cout << " q - Quit\n";
|
| 228 |
+
if(isLeftHand){
|
| 229 |
+
std::cout << " L: " << q.transpose() << std::endl;
|
| 230 |
+
}else std::cout << " R: " << q.transpose() << std::endl;
|
| 231 |
+
usleep(0.1 * 1e6);
|
| 232 |
+
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
void StateHandler(const void *message) {
|
| 236 |
+
state = *(unitree_hg::msg::dds_::HandState_ *)message;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
int main(int argc, const char** argv)
|
| 243 |
+
{
|
| 244 |
+
std::cout << " --- Unitree Robotics --- \n";
|
| 245 |
+
std::cout << " Dex3 Hand Example \n\n";
|
| 246 |
+
std::string input;
|
| 247 |
+
std::cout << "Please input the hand id (L for left hand, R for right hand): ";
|
| 248 |
+
std::cin >> input;
|
| 249 |
+
|
| 250 |
+
if (input == "L") {
|
| 251 |
+
hand_id = 0;
|
| 252 |
+
dds_namespace = "rt/dex3/left";
|
| 253 |
+
sub_namespace = "rt/lf/dex3/left/state";
|
| 254 |
+
} else if (input == "R") {
|
| 255 |
+
hand_id = 1;
|
| 256 |
+
dds_namespace = "rt/dex3/right";
|
| 257 |
+
sub_namespace = "rt/lf/dex3/right/state";
|
| 258 |
+
} else {
|
| 259 |
+
std::cout << "Invalid hand id. Please input 'L' or 'R'." << std::endl;
|
| 260 |
+
return -1;
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
if (argc < 2)
|
| 264 |
+
{
|
| 265 |
+
std::cout << "Usage: " << argv[0] << " networkInterface" << std::endl;
|
| 266 |
+
exit(-1);
|
| 267 |
+
}
|
| 268 |
+
unitree::robot::ChannelFactory::Instance()->Init(0, argv[1]);
|
| 269 |
+
handcmd_publisher.reset(new unitree::robot::ChannelPublisher<unitree_hg::msg::dds_::HandCmd_>(dds_namespace + "/cmd"));
|
| 270 |
+
handstate_subscriber.reset(new unitree::robot::ChannelSubscriber<unitree_hg::msg::dds_::HandState_>(sub_namespace));
|
| 271 |
+
handcmd_publisher->InitChannel();
|
| 272 |
+
handstate_subscriber->InitChannel(
|
| 273 |
+
std::bind(&StateHandler, std::placeholders::_1), 1);
|
| 274 |
+
state.motor_state().resize(MOTOR_MAX);
|
| 275 |
+
state.press_sensor_state().resize(SENSOR_MAX);
|
| 276 |
+
msg.motor_cmd().resize(MOTOR_MAX);
|
| 277 |
+
|
| 278 |
+
// handcmd_publisher->msg_.motor_cmd().resize(MOTOR_MAX);
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
std::thread inputThread(userInputThread);
|
| 282 |
+
State lastState = INIT;
|
| 283 |
+
while (true) {
|
| 284 |
+
State state;
|
| 285 |
+
{
|
| 286 |
+
std::lock_guard<std::mutex> lock(stateMutex);
|
| 287 |
+
state = currentState.load();
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
if (state != lastState) {
|
| 291 |
+
std::cout << "\n--- Current State: " << stateToString(state) << " ---\n";
|
| 292 |
+
std::cout << "Commands:\n";
|
| 293 |
+
std::cout << " r - Rotate\n";
|
| 294 |
+
std::cout << " g - Grip\n";
|
| 295 |
+
std::cout << " p - Print_state\n";
|
| 296 |
+
std::cout << " q - Quit\n";
|
| 297 |
+
std::cout << " s - Stop\n";
|
| 298 |
+
lastState = state;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
switch (state) {
|
| 302 |
+
case INIT:
|
| 303 |
+
std::cout << "Initializing..." << std::endl;
|
| 304 |
+
currentState = ROTATE;
|
| 305 |
+
break;
|
| 306 |
+
case ROTATE:
|
| 307 |
+
rotateMotors(input == "L");
|
| 308 |
+
break;
|
| 309 |
+
case GRIP:
|
| 310 |
+
gripHand(input == "L");
|
| 311 |
+
break;
|
| 312 |
+
case STOP:
|
| 313 |
+
stopMotors();
|
| 314 |
+
break;
|
| 315 |
+
case PRINT:
|
| 316 |
+
printState(input == "L");
|
| 317 |
+
break;
|
| 318 |
+
default:
|
| 319 |
+
std::cout << "Invalid state!" << std::endl;
|
| 320 |
+
inputThread.join();
|
| 321 |
+
break;
|
| 322 |
+
}
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
return 0;
|
| 326 |
+
}
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/high_level/g1_arm5_sdk_dds_example.cpp
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <array>
|
| 2 |
+
#include <chrono>
|
| 3 |
+
#include <iostream>
|
| 4 |
+
#include <thread>
|
| 5 |
+
|
| 6 |
+
#include <unitree/idl/hg/LowCmd_.hpp>
|
| 7 |
+
#include <unitree/idl/hg/LowState_.hpp>
|
| 8 |
+
#include <unitree/robot/channel/channel_publisher.hpp>
|
| 9 |
+
#include <unitree/robot/channel/channel_subscriber.hpp>
|
| 10 |
+
|
| 11 |
+
static const std::string kTopicArmSDK = "rt/arm_sdk";
|
| 12 |
+
static const std::string kTopicState = "rt/lowstate";
|
| 13 |
+
constexpr float kPi = 3.141592654;
|
| 14 |
+
constexpr float kPi_2 = 1.57079632;
|
| 15 |
+
|
| 16 |
+
enum JointIndex {
|
| 17 |
+
// Left leg
|
| 18 |
+
kLeftHipPitch = 0,
|
| 19 |
+
kLeftHipRoll = 1,
|
| 20 |
+
kLeftHipYaw = 2,
|
| 21 |
+
kLeftKnee = 3,
|
| 22 |
+
kLeftAnkle = 4,
|
| 23 |
+
kLeftAnkleRoll = 5,
|
| 24 |
+
|
| 25 |
+
// Right leg
|
| 26 |
+
kRightHipPitch = 6,
|
| 27 |
+
kRightHipRoll = 7,
|
| 28 |
+
kRightHipYaw = 8,
|
| 29 |
+
kRightKnee = 9,
|
| 30 |
+
kRightAnkle = 10,
|
| 31 |
+
kRightAnkleRoll = 11,
|
| 32 |
+
|
| 33 |
+
kWaistYaw = 12,
|
| 34 |
+
kWaistRoll = 13,
|
| 35 |
+
kWaistPitch = 14,
|
| 36 |
+
|
| 37 |
+
// Left arm
|
| 38 |
+
kLeftShoulderPitch = 15,
|
| 39 |
+
kLeftShoulderRoll = 16,
|
| 40 |
+
kLeftShoulderYaw = 17,
|
| 41 |
+
kLeftElbowPitch = 18,
|
| 42 |
+
kLeftElbowRoll = 19,
|
| 43 |
+
|
| 44 |
+
// Right arm
|
| 45 |
+
kRightShoulderPitch = 22,
|
| 46 |
+
kRightShoulderRoll = 23,
|
| 47 |
+
kRightShoulderYaw = 24,
|
| 48 |
+
kRightElbowPitch = 25,
|
| 49 |
+
kRightElbowRoll = 26,
|
| 50 |
+
|
| 51 |
+
kNotUsedJoint = 29,
|
| 52 |
+
kNotUsedJoint1 = 30,
|
| 53 |
+
kNotUsedJoint2 = 31,
|
| 54 |
+
kNotUsedJoint3 = 32,
|
| 55 |
+
kNotUsedJoint4 = 33,
|
| 56 |
+
kNotUsedJoint5 = 34
|
| 57 |
+
};
|
| 58 |
+
|
| 59 |
+
int main(int argc, char const *argv[]) {
|
| 60 |
+
if (argc < 2) {
|
| 61 |
+
std::cout << "Usage: " << argv[0] << " networkInterface" << std::endl;
|
| 62 |
+
exit(-1);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
unitree::robot::ChannelFactory::Instance()->Init(0, argv[1]);
|
| 66 |
+
|
| 67 |
+
unitree::robot::ChannelPublisherPtr<unitree_hg::msg::dds_::LowCmd_>
|
| 68 |
+
arm_sdk_publisher;
|
| 69 |
+
unitree_hg::msg::dds_::LowCmd_ msg;
|
| 70 |
+
|
| 71 |
+
arm_sdk_publisher.reset(
|
| 72 |
+
new unitree::robot::ChannelPublisher<unitree_hg::msg::dds_::LowCmd_>(
|
| 73 |
+
kTopicArmSDK));
|
| 74 |
+
arm_sdk_publisher->InitChannel();
|
| 75 |
+
|
| 76 |
+
unitree::robot::ChannelSubscriberPtr<unitree_hg::msg::dds_::LowState_>
|
| 77 |
+
low_state_subscriber;
|
| 78 |
+
|
| 79 |
+
// create subscriber
|
| 80 |
+
unitree_hg::msg::dds_::LowState_ state_msg;
|
| 81 |
+
low_state_subscriber.reset(
|
| 82 |
+
new unitree::robot::ChannelSubscriber<unitree_hg::msg::dds_::LowState_>(
|
| 83 |
+
kTopicState));
|
| 84 |
+
low_state_subscriber->InitChannel([&](const void *msg) {
|
| 85 |
+
auto s = ( const unitree_hg::msg::dds_::LowState_* )msg;
|
| 86 |
+
memcpy( &state_msg, s, sizeof( unitree_hg::msg::dds_::LowState_ ) );
|
| 87 |
+
}, 1);
|
| 88 |
+
|
| 89 |
+
std::array<JointIndex, 13> arm_joints = {
|
| 90 |
+
JointIndex::kLeftShoulderPitch, JointIndex::kLeftShoulderRoll,
|
| 91 |
+
JointIndex::kLeftShoulderYaw, JointIndex::kLeftElbowPitch,
|
| 92 |
+
JointIndex::kLeftElbowRoll,
|
| 93 |
+
JointIndex::kRightShoulderPitch, JointIndex::kRightShoulderRoll,
|
| 94 |
+
JointIndex::kRightShoulderYaw, JointIndex::kRightElbowPitch,
|
| 95 |
+
JointIndex::kRightElbowRoll,
|
| 96 |
+
JointIndex::kWaistYaw,
|
| 97 |
+
JointIndex::kWaistRoll,
|
| 98 |
+
JointIndex::kWaistPitch};
|
| 99 |
+
|
| 100 |
+
float weight = 0.f;
|
| 101 |
+
float weight_rate = 0.2f;
|
| 102 |
+
|
| 103 |
+
float kp = 60.f;
|
| 104 |
+
float kd = 1.5f;
|
| 105 |
+
float dq = 0.f;
|
| 106 |
+
float tau_ff = 0.f;
|
| 107 |
+
|
| 108 |
+
float control_dt = 0.02f;
|
| 109 |
+
float max_joint_velocity = 0.5f;
|
| 110 |
+
|
| 111 |
+
float delta_weight = weight_rate * control_dt;
|
| 112 |
+
float max_joint_delta = max_joint_velocity * control_dt;
|
| 113 |
+
auto sleep_time =
|
| 114 |
+
std::chrono::milliseconds(static_cast<int>(control_dt / 0.001f));
|
| 115 |
+
|
| 116 |
+
std::array<float, 13> init_pos{0, 0, 0, 0, 0,
|
| 117 |
+
0, 0, 0, 0, 0,
|
| 118 |
+
0, 0, 0};
|
| 119 |
+
|
| 120 |
+
std::array<float, 13> target_pos = {0.f, kPi_2, 0.f, kPi_2, 0.f,
|
| 121 |
+
0.f, -kPi_2, 0.f, kPi_2, 0.f,
|
| 122 |
+
0.f, 0.f, 0.f};
|
| 123 |
+
|
| 124 |
+
// wait for init
|
| 125 |
+
std::cout << "Press ENTER to init arms ...";
|
| 126 |
+
std::cin.get();
|
| 127 |
+
|
| 128 |
+
// get current joint position
|
| 129 |
+
std::array<float, 13> current_jpos{};
|
| 130 |
+
std::cout<<"Current joint position: ";
|
| 131 |
+
for (int i = 0; i < arm_joints.size(); ++i) {
|
| 132 |
+
current_jpos.at(i) = state_msg.motor_state().at(arm_joints.at(i)).q();
|
| 133 |
+
std::cout << current_jpos.at(i) << " ";
|
| 134 |
+
}
|
| 135 |
+
std::cout << std::endl;
|
| 136 |
+
|
| 137 |
+
// set init pos
|
| 138 |
+
std::cout << "Initailizing arms ...";
|
| 139 |
+
float init_time = 2.0f;
|
| 140 |
+
int init_time_steps = static_cast<int>(init_time / control_dt);
|
| 141 |
+
|
| 142 |
+
for (int i = 0; i < init_time_steps; ++i) {
|
| 143 |
+
// set weight
|
| 144 |
+
weight = 1.0;
|
| 145 |
+
msg.motor_cmd().at(JointIndex::kNotUsedJoint).q(weight);
|
| 146 |
+
float phase = 1.0 * i / init_time_steps;
|
| 147 |
+
std::cout << "Phase: " << phase << std::endl;
|
| 148 |
+
|
| 149 |
+
// set control joints
|
| 150 |
+
for (int j = 0; j < init_pos.size(); ++j) {
|
| 151 |
+
msg.motor_cmd().at(arm_joints.at(j)).q(init_pos.at(j) * phase + current_jpos.at(j) * (1 - phase));
|
| 152 |
+
msg.motor_cmd().at(arm_joints.at(j)).dq(dq);
|
| 153 |
+
msg.motor_cmd().at(arm_joints.at(j)).kp(kp);
|
| 154 |
+
msg.motor_cmd().at(arm_joints.at(j)).kd(kd);
|
| 155 |
+
msg.motor_cmd().at(arm_joints.at(j)).tau(tau_ff);
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
// send dds msg
|
| 159 |
+
arm_sdk_publisher->Write(msg);
|
| 160 |
+
|
| 161 |
+
// sleep
|
| 162 |
+
std::this_thread::sleep_for(sleep_time);
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
std::cout << "Done!" << std::endl;
|
| 166 |
+
|
| 167 |
+
// wait for control
|
| 168 |
+
std::cout << "Press ENTER to start arm ctrl ..." << std::endl;
|
| 169 |
+
std::cin.get();
|
| 170 |
+
|
| 171 |
+
// start control
|
| 172 |
+
std::cout << "Start arm ctrl!" << std::endl;
|
| 173 |
+
float period = 5.f;
|
| 174 |
+
int num_time_steps = static_cast<int>(period / control_dt);
|
| 175 |
+
|
| 176 |
+
std::array<float, 13> current_jpos_des{};
|
| 177 |
+
|
| 178 |
+
// lift arms up
|
| 179 |
+
for (int i = 0; i < num_time_steps; ++i) {
|
| 180 |
+
// update jpos des
|
| 181 |
+
for (int j = 0; j < init_pos.size(); ++j) {
|
| 182 |
+
current_jpos_des.at(j) +=
|
| 183 |
+
std::clamp(target_pos.at(j) - current_jpos_des.at(j),
|
| 184 |
+
-max_joint_delta, max_joint_delta);
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
// set control joints
|
| 188 |
+
for (int j = 0; j < init_pos.size(); ++j) {
|
| 189 |
+
msg.motor_cmd().at(arm_joints.at(j)).q(current_jpos_des.at(j));
|
| 190 |
+
msg.motor_cmd().at(arm_joints.at(j)).dq(dq);
|
| 191 |
+
msg.motor_cmd().at(arm_joints.at(j)).kp(kp);
|
| 192 |
+
msg.motor_cmd().at(arm_joints.at(j)).kd(kd);
|
| 193 |
+
msg.motor_cmd().at(arm_joints.at(j)).tau(tau_ff);
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
// send dds msg
|
| 197 |
+
arm_sdk_publisher->Write(msg);
|
| 198 |
+
|
| 199 |
+
// sleep
|
| 200 |
+
std::this_thread::sleep_for(sleep_time);
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
// put arms down
|
| 204 |
+
for (int i = 0; i < num_time_steps; ++i) {
|
| 205 |
+
// update jpos des
|
| 206 |
+
for (int j = 0; j < init_pos.size(); ++j) {
|
| 207 |
+
current_jpos_des.at(j) +=
|
| 208 |
+
std::clamp(init_pos.at(j) - current_jpos_des.at(j), -max_joint_delta,
|
| 209 |
+
max_joint_delta);
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
// set control joints
|
| 213 |
+
for (int j = 0; j < init_pos.size(); ++j) {
|
| 214 |
+
msg.motor_cmd().at(arm_joints.at(j)).q(current_jpos_des.at(j));
|
| 215 |
+
msg.motor_cmd().at(arm_joints.at(j)).dq(dq);
|
| 216 |
+
msg.motor_cmd().at(arm_joints.at(j)).kp(kp);
|
| 217 |
+
msg.motor_cmd().at(arm_joints.at(j)).kd(kd);
|
| 218 |
+
msg.motor_cmd().at(arm_joints.at(j)).tau(tau_ff);
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
// send dds msg
|
| 222 |
+
arm_sdk_publisher->Write(msg);
|
| 223 |
+
|
| 224 |
+
// sleep
|
| 225 |
+
std::this_thread::sleep_for(sleep_time);
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
// stop control
|
| 229 |
+
std::cout << "Stoping arm ctrl ...";
|
| 230 |
+
float stop_time = 2.0f;
|
| 231 |
+
int stop_time_steps = static_cast<int>(stop_time / control_dt);
|
| 232 |
+
|
| 233 |
+
for (int i = 0; i < stop_time_steps; ++i) {
|
| 234 |
+
// increase weight
|
| 235 |
+
weight -= delta_weight;
|
| 236 |
+
weight = std::clamp(weight, 0.f, 1.f);
|
| 237 |
+
|
| 238 |
+
// set weight
|
| 239 |
+
msg.motor_cmd().at(JointIndex::kNotUsedJoint).q(weight);
|
| 240 |
+
|
| 241 |
+
// send dds msg
|
| 242 |
+
arm_sdk_publisher->Write(msg);
|
| 243 |
+
|
| 244 |
+
// sleep
|
| 245 |
+
std::this_thread::sleep_for(sleep_time);
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
// set weight
|
| 249 |
+
msg.motor_cmd().at(JointIndex::kNotUsedJoint).q(0);
|
| 250 |
+
// send dds msg
|
| 251 |
+
arm_sdk_publisher->Write(msg);
|
| 252 |
+
|
| 253 |
+
std::cout << "Done!" << std::endl;
|
| 254 |
+
|
| 255 |
+
return 0;
|
| 256 |
+
}
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/high_level/g1_arm7_sdk_dds_example.cpp
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <array>
|
| 2 |
+
#include <chrono>
|
| 3 |
+
#include <iostream>
|
| 4 |
+
#include <thread>
|
| 5 |
+
|
| 6 |
+
#include <unitree/idl/hg/LowCmd_.hpp>
|
| 7 |
+
#include <unitree/idl/hg/LowState_.hpp>
|
| 8 |
+
#include <unitree/robot/channel/channel_publisher.hpp>
|
| 9 |
+
#include <unitree/robot/channel/channel_subscriber.hpp>
|
| 10 |
+
|
| 11 |
+
static const std::string kTopicArmSDK = "rt/arm_sdk";
|
| 12 |
+
static const std::string kTopicState = "rt/lowstate";
|
| 13 |
+
constexpr float kPi = 3.141592654;
|
| 14 |
+
constexpr float kPi_2 = 1.57079632;
|
| 15 |
+
|
| 16 |
+
enum JointIndex {
|
| 17 |
+
// Left leg
|
| 18 |
+
kLeftHipPitch,
|
| 19 |
+
kLeftHipRoll,
|
| 20 |
+
kLeftHipYaw,
|
| 21 |
+
kLeftKnee,
|
| 22 |
+
kLeftAnkle,
|
| 23 |
+
kLeftAnkleRoll,
|
| 24 |
+
|
| 25 |
+
// Right leg
|
| 26 |
+
kRightHipPitch,
|
| 27 |
+
kRightHipRoll,
|
| 28 |
+
kRightHipYaw,
|
| 29 |
+
kRightKnee,
|
| 30 |
+
kRightAnkle,
|
| 31 |
+
kRightAnkleRoll,
|
| 32 |
+
|
| 33 |
+
kWaistYaw,
|
| 34 |
+
kWaistRoll,
|
| 35 |
+
kWaistPitch,
|
| 36 |
+
|
| 37 |
+
// Left arm
|
| 38 |
+
kLeftShoulderPitch,
|
| 39 |
+
kLeftShoulderRoll,
|
| 40 |
+
kLeftShoulderYaw,
|
| 41 |
+
kLeftElbow,
|
| 42 |
+
kLeftWristRoll,
|
| 43 |
+
kLeftWristPitch,
|
| 44 |
+
kLeftWristYaw,
|
| 45 |
+
// Right arm
|
| 46 |
+
kRightShoulderPitch,
|
| 47 |
+
kRightShoulderRoll,
|
| 48 |
+
kRightShoulderYaw,
|
| 49 |
+
kRightElbow,
|
| 50 |
+
kRightWristRoll,
|
| 51 |
+
kRightWristPitch,
|
| 52 |
+
kRightWristYaw,
|
| 53 |
+
|
| 54 |
+
kNotUsedJoint,
|
| 55 |
+
kNotUsedJoint1,
|
| 56 |
+
kNotUsedJoint2,
|
| 57 |
+
kNotUsedJoint3,
|
| 58 |
+
kNotUsedJoint4,
|
| 59 |
+
kNotUsedJoint5
|
| 60 |
+
};
|
| 61 |
+
|
| 62 |
+
int main(int argc, char const *argv[]) {
|
| 63 |
+
if (argc < 2) {
|
| 64 |
+
std::cout << "Usage: " << argv[0] << " networkInterface" << std::endl;
|
| 65 |
+
exit(-1);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
unitree::robot::ChannelFactory::Instance()->Init(0, argv[1]);
|
| 69 |
+
|
| 70 |
+
unitree::robot::ChannelPublisherPtr<unitree_hg::msg::dds_::LowCmd_>
|
| 71 |
+
arm_sdk_publisher;
|
| 72 |
+
unitree_hg::msg::dds_::LowCmd_ msg;
|
| 73 |
+
|
| 74 |
+
arm_sdk_publisher.reset(
|
| 75 |
+
new unitree::robot::ChannelPublisher<unitree_hg::msg::dds_::LowCmd_>(
|
| 76 |
+
kTopicArmSDK));
|
| 77 |
+
arm_sdk_publisher->InitChannel();
|
| 78 |
+
|
| 79 |
+
unitree::robot::ChannelSubscriberPtr<unitree_hg::msg::dds_::LowState_>
|
| 80 |
+
low_state_subscriber;
|
| 81 |
+
|
| 82 |
+
// create subscriber
|
| 83 |
+
unitree_hg::msg::dds_::LowState_ state_msg;
|
| 84 |
+
low_state_subscriber.reset(
|
| 85 |
+
new unitree::robot::ChannelSubscriber<unitree_hg::msg::dds_::LowState_>(
|
| 86 |
+
kTopicState));
|
| 87 |
+
low_state_subscriber->InitChannel([&](const void *msg) {
|
| 88 |
+
auto s = ( const unitree_hg::msg::dds_::LowState_* )msg;
|
| 89 |
+
memcpy( &state_msg, s, sizeof( unitree_hg::msg::dds_::LowState_ ) );
|
| 90 |
+
}, 1);
|
| 91 |
+
|
| 92 |
+
std::array<JointIndex, 17> arm_joints = {
|
| 93 |
+
JointIndex::kLeftShoulderPitch, JointIndex::kLeftShoulderRoll,
|
| 94 |
+
JointIndex::kLeftShoulderYaw, JointIndex::kLeftElbow,
|
| 95 |
+
JointIndex::kLeftWristRoll, JointIndex::kLeftWristPitch,
|
| 96 |
+
JointIndex::kLeftWristYaw,
|
| 97 |
+
JointIndex::kRightShoulderPitch, JointIndex::kRightShoulderRoll,
|
| 98 |
+
JointIndex::kRightShoulderYaw, JointIndex::kRightElbow,
|
| 99 |
+
JointIndex::kRightWristRoll, JointIndex::kRightWristPitch,
|
| 100 |
+
JointIndex::kRightWristYaw,
|
| 101 |
+
JointIndex::kWaistYaw,
|
| 102 |
+
JointIndex::kWaistRoll,
|
| 103 |
+
JointIndex::kWaistPitch};
|
| 104 |
+
|
| 105 |
+
float weight = 0.f;
|
| 106 |
+
float weight_rate = 0.2f;
|
| 107 |
+
|
| 108 |
+
float kp = 60.f;
|
| 109 |
+
float kd = 1.5f;
|
| 110 |
+
float dq = 0.f;
|
| 111 |
+
float tau_ff = 0.f;
|
| 112 |
+
|
| 113 |
+
float control_dt = 0.02f;
|
| 114 |
+
float max_joint_velocity = 0.5f;
|
| 115 |
+
|
| 116 |
+
float delta_weight = weight_rate * control_dt;
|
| 117 |
+
float max_joint_delta = max_joint_velocity * control_dt;
|
| 118 |
+
auto sleep_time =
|
| 119 |
+
std::chrono::milliseconds(static_cast<int>(control_dt / 0.001f));
|
| 120 |
+
|
| 121 |
+
std::array<float, 17> init_pos{0, 0, 0, 0, 0, 0, 0,
|
| 122 |
+
0, 0, 0, 0, 0, 0, 0,
|
| 123 |
+
0, 0, 0};
|
| 124 |
+
|
| 125 |
+
std::array<float, 17> target_pos = {0.f, kPi_2, 0.f, kPi_2, 0.f, 0.f, 0.f,
|
| 126 |
+
0.f, -kPi_2, 0.f, kPi_2, 0.f, 0.f, 0.f,
|
| 127 |
+
0, 0, 0};
|
| 128 |
+
|
| 129 |
+
// wait for init
|
| 130 |
+
std::cout << "Press ENTER to init arms ...";
|
| 131 |
+
std::cin.get();
|
| 132 |
+
|
| 133 |
+
// get current joint position
|
| 134 |
+
std::array<float, 17> current_jpos{};
|
| 135 |
+
std::cout<<"Current joint position: ";
|
| 136 |
+
for (int i = 0; i < arm_joints.size(); ++i) {
|
| 137 |
+
current_jpos.at(i) = state_msg.motor_state().at(arm_joints.at(i)).q();
|
| 138 |
+
std::cout << current_jpos.at(i) << " ";
|
| 139 |
+
}
|
| 140 |
+
std::cout << std::endl;
|
| 141 |
+
|
| 142 |
+
// set init pos
|
| 143 |
+
std::cout << "Initailizing arms ...";
|
| 144 |
+
float init_time = 2.0f;
|
| 145 |
+
int init_time_steps = static_cast<int>(init_time / control_dt);
|
| 146 |
+
|
| 147 |
+
for (int i = 0; i < init_time_steps; ++i) {
|
| 148 |
+
// increase weight
|
| 149 |
+
weight = 1.0;
|
| 150 |
+
msg.motor_cmd().at(JointIndex::kNotUsedJoint).q(weight);
|
| 151 |
+
float phase = 1.0 * i / init_time_steps;
|
| 152 |
+
std::cout << "Phase: " << phase << std::endl;
|
| 153 |
+
|
| 154 |
+
// set control joints
|
| 155 |
+
for (int j = 0; j < init_pos.size(); ++j) {
|
| 156 |
+
msg.motor_cmd().at(arm_joints.at(j)).q(init_pos.at(j) * phase + current_jpos.at(j) * (1 - phase));
|
| 157 |
+
msg.motor_cmd().at(arm_joints.at(j)).dq(dq);
|
| 158 |
+
msg.motor_cmd().at(arm_joints.at(j)).kp(kp);
|
| 159 |
+
msg.motor_cmd().at(arm_joints.at(j)).kd(kd);
|
| 160 |
+
msg.motor_cmd().at(arm_joints.at(j)).tau(tau_ff);
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
// send dds msg
|
| 164 |
+
arm_sdk_publisher->Write(msg);
|
| 165 |
+
|
| 166 |
+
// sleep
|
| 167 |
+
std::this_thread::sleep_for(sleep_time);
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
std::cout << "Done!" << std::endl;
|
| 171 |
+
|
| 172 |
+
// wait for control
|
| 173 |
+
std::cout << "Press ENTER to start arm ctrl ..." << std::endl;
|
| 174 |
+
std::cin.get();
|
| 175 |
+
|
| 176 |
+
// start control
|
| 177 |
+
std::cout << "Start arm ctrl!" << std::endl;
|
| 178 |
+
float period = 5.f;
|
| 179 |
+
int num_time_steps = static_cast<int>(period / control_dt);
|
| 180 |
+
|
| 181 |
+
std::array<float, 17> current_jpos_des{};
|
| 182 |
+
|
| 183 |
+
// lift arms up
|
| 184 |
+
for (int i = 0; i < num_time_steps; ++i) {
|
| 185 |
+
// update jpos des
|
| 186 |
+
for (int j = 0; j < init_pos.size(); ++j) {
|
| 187 |
+
current_jpos_des.at(j) +=
|
| 188 |
+
std::clamp(target_pos.at(j) - current_jpos_des.at(j),
|
| 189 |
+
-max_joint_delta, max_joint_delta);
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
// set control joints
|
| 193 |
+
for (int j = 0; j < init_pos.size(); ++j) {
|
| 194 |
+
msg.motor_cmd().at(arm_joints.at(j)).q(current_jpos_des.at(j));
|
| 195 |
+
msg.motor_cmd().at(arm_joints.at(j)).dq(dq);
|
| 196 |
+
msg.motor_cmd().at(arm_joints.at(j)).kp(kp);
|
| 197 |
+
msg.motor_cmd().at(arm_joints.at(j)).kd(kd);
|
| 198 |
+
msg.motor_cmd().at(arm_joints.at(j)).tau(tau_ff);
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
// send dds msg
|
| 202 |
+
arm_sdk_publisher->Write(msg);
|
| 203 |
+
|
| 204 |
+
// sleep
|
| 205 |
+
std::this_thread::sleep_for(sleep_time);
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
// put arms down
|
| 209 |
+
for (int i = 0; i < num_time_steps; ++i) {
|
| 210 |
+
// update jpos des
|
| 211 |
+
for (int j = 0; j < init_pos.size(); ++j) {
|
| 212 |
+
current_jpos_des.at(j) +=
|
| 213 |
+
std::clamp(init_pos.at(j) - current_jpos_des.at(j), -max_joint_delta,
|
| 214 |
+
max_joint_delta);
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
// set control joints
|
| 218 |
+
for (int j = 0; j < init_pos.size(); ++j) {
|
| 219 |
+
msg.motor_cmd().at(arm_joints.at(j)).q(current_jpos_des.at(j));
|
| 220 |
+
msg.motor_cmd().at(arm_joints.at(j)).dq(dq);
|
| 221 |
+
msg.motor_cmd().at(arm_joints.at(j)).kp(kp);
|
| 222 |
+
msg.motor_cmd().at(arm_joints.at(j)).kd(kd);
|
| 223 |
+
msg.motor_cmd().at(arm_joints.at(j)).tau(tau_ff);
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
// send dds msg
|
| 227 |
+
arm_sdk_publisher->Write(msg);
|
| 228 |
+
|
| 229 |
+
// sleep
|
| 230 |
+
std::this_thread::sleep_for(sleep_time);
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
// stop control
|
| 234 |
+
std::cout << "Stoping arm ctrl ...";
|
| 235 |
+
float stop_time = 2.0f;
|
| 236 |
+
int stop_time_steps = static_cast<int>(stop_time / control_dt);
|
| 237 |
+
|
| 238 |
+
for (int i = 0; i < stop_time_steps; ++i) {
|
| 239 |
+
// increase weight
|
| 240 |
+
weight -= delta_weight;
|
| 241 |
+
weight = std::clamp(weight, 0.f, 1.f);
|
| 242 |
+
|
| 243 |
+
// set weight
|
| 244 |
+
msg.motor_cmd().at(JointIndex::kNotUsedJoint).q(weight);
|
| 245 |
+
|
| 246 |
+
// send dds msg
|
| 247 |
+
arm_sdk_publisher->Write(msg);
|
| 248 |
+
|
| 249 |
+
// sleep
|
| 250 |
+
std::this_thread::sleep_for(sleep_time);
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
// set weight
|
| 255 |
+
msg.motor_cmd().at(JointIndex::kNotUsedJoint).q(0);
|
| 256 |
+
// send dds msg
|
| 257 |
+
arm_sdk_publisher->Write(msg);
|
| 258 |
+
|
| 259 |
+
std::cout << "Done!" << std::endl;
|
| 260 |
+
|
| 261 |
+
return 0;
|
| 262 |
+
}
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/high_level/g1_arm_action_example.cpp
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file g1_arm_action_example.cpp
|
| 3 |
+
* @brief This example demonstrates how to use the G1 Arm Action Client to execute predefined arm actions.
|
| 4 |
+
*/
|
| 5 |
+
#include "unitree/robot/g1/arm/g1_arm_action_error.hpp"
|
| 6 |
+
#include "unitree/robot/g1/arm/g1_arm_action_client.hpp"
|
| 7 |
+
|
| 8 |
+
using namespace unitree::robot::g1;
|
| 9 |
+
|
| 10 |
+
int main(int argc, const char** argv)
|
| 11 |
+
{
|
| 12 |
+
std::cout << " --- Unitree Robotics --- \n";
|
| 13 |
+
std::cout << " G1 Arm Action Example \n\n";
|
| 14 |
+
|
| 15 |
+
// Unitree DDS Initialization; arg[1] is the network interface
|
| 16 |
+
unitree::robot::ChannelFactory::Instance()->Init(0, argc > 1 ? argv[1] : "");
|
| 17 |
+
|
| 18 |
+
auto client = std::make_shared<unitree::robot::g1::G1ArmActionClient>();
|
| 19 |
+
client->Init();
|
| 20 |
+
client->SetTimeout(10.f); // All actions will last less than 10 seconds.
|
| 21 |
+
|
| 22 |
+
std::cout << "Usage: \n";
|
| 23 |
+
std::cout << " - 0: print supported actions.\n";
|
| 24 |
+
std::cout << " - an id: execute an action.\n";
|
| 25 |
+
std::cout << "Attention: \n";
|
| 26 |
+
std::cout << " Some actions will not be displayed on the APP, \n";
|
| 27 |
+
std::cout << " but can be executed by the program.\n";
|
| 28 |
+
std::cout << " These actions may cause the robot to fall,\n";
|
| 29 |
+
std::cout << " so please execute them with caution.\n";
|
| 30 |
+
|
| 31 |
+
int32_t action_id = 0;
|
| 32 |
+
std::string line;
|
| 33 |
+
while (true) {
|
| 34 |
+
std::cout << "\nEnter action ID: .\n";
|
| 35 |
+
std::getline(std::cin, line);
|
| 36 |
+
try {
|
| 37 |
+
action_id = std::stoi(line);
|
| 38 |
+
} catch (const std::exception&) {
|
| 39 |
+
std::cout << "Invalid input. Please enter an integer.\n";
|
| 40 |
+
continue;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
if (action_id == 0) {
|
| 44 |
+
std::string action_list_data;
|
| 45 |
+
int32_t ret = client->GetActionList(action_list_data);
|
| 46 |
+
if (ret != 0) {
|
| 47 |
+
std::cerr << "Failed to get action list, error code: " << ret << "\n";
|
| 48 |
+
continue;
|
| 49 |
+
}
|
| 50 |
+
std::cout << "Available actions:\n" << action_list_data << std::endl;
|
| 51 |
+
} else {
|
| 52 |
+
int32_t ret = client->ExecuteAction(action_id);
|
| 53 |
+
if(ret != 0) {
|
| 54 |
+
switch (ret)
|
| 55 |
+
{
|
| 56 |
+
case UT_ROBOT_ARM_ACTION_ERR_ARMSDK:
|
| 57 |
+
std::cout << UT_ROBOT_ARM_ACTION_ERR_ARMSDK_DESC << std::endl;
|
| 58 |
+
break;
|
| 59 |
+
case UT_ROBOT_ARM_ACTION_ERR_HOLDING:
|
| 60 |
+
std::cout << UT_ROBOT_ARM_ACTION_ERR_HOLDING_DESC << std::endl;
|
| 61 |
+
break;
|
| 62 |
+
case UT_ROBOT_ARM_ACTION_ERR_INVALID_ACTION_ID:
|
| 63 |
+
std::cout << UT_ROBOT_ARM_ACTION_ERR_INVALID_ACTION_ID_DESC << std::endl;
|
| 64 |
+
break;
|
| 65 |
+
case UT_ROBOT_ARM_ACTION_ERR_INVALID_FSM_ID:
|
| 66 |
+
std::cout << "The actions are only supported in fsm id {500, 501, 801}" << std::endl;
|
| 67 |
+
std::cout << "You can subscribe the topic rt/sportmodestate to check the fsm id." << std::endl;
|
| 68 |
+
std::cout << "And in the state 801, the actions are only supported in the fsm mode {0, 3}." << std::endl;
|
| 69 |
+
std::cout << "If an error is still returned at this point, ignore this action.";
|
| 70 |
+
break;
|
| 71 |
+
default:
|
| 72 |
+
std::cerr << "Execute action failed, error code: " << ret << std::endl;
|
| 73 |
+
break;
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
return 0;
|
| 80 |
+
};
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/high_level/g1_loco_client_example.cpp
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <chrono>
|
| 2 |
+
#include <iostream>
|
| 3 |
+
#include <thread>
|
| 4 |
+
|
| 5 |
+
#include <unitree/robot/g1/loco/g1_loco_api.hpp>
|
| 6 |
+
#include <unitree/robot/g1/loco/g1_loco_client.hpp>
|
| 7 |
+
|
| 8 |
+
std::vector<float> stringToFloatVector(const std::string &str) {
|
| 9 |
+
std::vector<float> result;
|
| 10 |
+
std::stringstream ss(str);
|
| 11 |
+
float num;
|
| 12 |
+
while (ss >> num) {
|
| 13 |
+
result.push_back(num);
|
| 14 |
+
// ignore any trailing whitespace
|
| 15 |
+
ss.ignore();
|
| 16 |
+
}
|
| 17 |
+
return result;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
int main(int argc, char const *argv[]) {
|
| 21 |
+
std::map<std::string, std::string> args = {{"network_interface", "lo"}};
|
| 22 |
+
|
| 23 |
+
std::map<std::string, std::string> values;
|
| 24 |
+
for (int i = 1; i < argc; ++i) {
|
| 25 |
+
std::string arg = argv[i];
|
| 26 |
+
if (arg.substr(0, 2) == "--") {
|
| 27 |
+
size_t pos = arg.find("=");
|
| 28 |
+
std::string key, value;
|
| 29 |
+
if (pos != std::string::npos) {
|
| 30 |
+
key = arg.substr(2, pos - 2);
|
| 31 |
+
value = arg.substr(pos + 1);
|
| 32 |
+
|
| 33 |
+
if (value.front() == '"' && value.back() == '"') {
|
| 34 |
+
value = value.substr(1, value.length() - 2);
|
| 35 |
+
}
|
| 36 |
+
} else {
|
| 37 |
+
key = arg.substr(2);
|
| 38 |
+
value = "";
|
| 39 |
+
}
|
| 40 |
+
if (args.find(key) != args.end()) {
|
| 41 |
+
args[key] = value;
|
| 42 |
+
} else {
|
| 43 |
+
args.insert({{key, value}});
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
unitree::robot::ChannelFactory::Instance()->Init(0,
|
| 49 |
+
args["network_interface"]);
|
| 50 |
+
|
| 51 |
+
unitree::robot::g1::LocoClient client;
|
| 52 |
+
|
| 53 |
+
client.Init();
|
| 54 |
+
client.SetTimeout(10.f);
|
| 55 |
+
|
| 56 |
+
for (const auto &arg_pair : args) {
|
| 57 |
+
std::cout << "Processing command: [" << arg_pair.first << "] with param: ["
|
| 58 |
+
<< arg_pair.second << "] ..." << std::endl;
|
| 59 |
+
if (arg_pair.first == "network_interface") {
|
| 60 |
+
continue;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
if (arg_pair.first == "get_fsm_id") {
|
| 64 |
+
int fsm_id;
|
| 65 |
+
client.GetFsmId(fsm_id);
|
| 66 |
+
std::cout << "current fsm_id: " << fsm_id << std::endl;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
if (arg_pair.first == "get_fsm_mode") {
|
| 70 |
+
int fsm_mode;
|
| 71 |
+
client.GetFsmMode(fsm_mode);
|
| 72 |
+
std::cout << "current fsm_mode: " << fsm_mode << std::endl;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
if (arg_pair.first == "get_balance_mode") {
|
| 76 |
+
int balance_mode;
|
| 77 |
+
client.GetBalanceMode(balance_mode);
|
| 78 |
+
std::cout << "current balance_mode: " << balance_mode << std::endl;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
if (arg_pair.first == "get_swing_height") {
|
| 82 |
+
float swing_height;
|
| 83 |
+
client.GetSwingHeight(swing_height);
|
| 84 |
+
std::cout << "current swing_height: " << swing_height << std::endl;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
if (arg_pair.first == "get_stand_height") {
|
| 88 |
+
float stand_height;
|
| 89 |
+
client.GetStandHeight(stand_height);
|
| 90 |
+
std::cout << "current stand_height: " << stand_height << std::endl;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
if (arg_pair.first == "get_phase") {
|
| 94 |
+
std::vector<float> phase;
|
| 95 |
+
client.GetPhase(phase);
|
| 96 |
+
std::cout << "current phase: (";
|
| 97 |
+
for (const auto &p : phase) {
|
| 98 |
+
std::cout << p << ", ";
|
| 99 |
+
}
|
| 100 |
+
std::cout << ")" << std::endl;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
if (arg_pair.first == "set_fsm_id") {
|
| 104 |
+
int fsm_id = std::stoi(arg_pair.second);
|
| 105 |
+
client.SetFsmId(fsm_id);
|
| 106 |
+
std::cout << "set fsm_id to " << fsm_id << std::endl;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
if (arg_pair.first == "set_balance_mode") {
|
| 110 |
+
int balance_mode = std::stoi(arg_pair.second);
|
| 111 |
+
client.SetBalanceMode(balance_mode);
|
| 112 |
+
std::cout << "set balance_mode to " << balance_mode << std::endl;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
if (arg_pair.first == "set_swing_height") {
|
| 116 |
+
float swing_height = std::stof(arg_pair.second);
|
| 117 |
+
client.SetSwingHeight(swing_height);
|
| 118 |
+
std::cout << "set swing_height to " << swing_height << std::endl;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
if (arg_pair.first == "set_stand_height") {
|
| 122 |
+
float stand_height = std::stof(arg_pair.second);
|
| 123 |
+
client.SetStandHeight(stand_height);
|
| 124 |
+
std::cout << "set stand_height to " << stand_height << std::endl;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
if (arg_pair.first == "set_velocity") {
|
| 128 |
+
std::vector<float> param = stringToFloatVector(arg_pair.second);
|
| 129 |
+
auto param_size = param.size();
|
| 130 |
+
float vx, vy, omega, duration;
|
| 131 |
+
if (param_size == 3) {
|
| 132 |
+
vx = param.at(0);
|
| 133 |
+
vy = param.at(1);
|
| 134 |
+
omega = param.at(2);
|
| 135 |
+
duration = 1.f;
|
| 136 |
+
} else if (param_size == 4) {
|
| 137 |
+
vx = param.at(0);
|
| 138 |
+
vy = param.at(1);
|
| 139 |
+
omega = param.at(2);
|
| 140 |
+
duration = param.at(3);
|
| 141 |
+
} else {
|
| 142 |
+
std::cerr << "Invalid param size for method SetVelocity: " << param_size
|
| 143 |
+
<< std::endl;
|
| 144 |
+
return 1;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
client.SetVelocity(vx, vy, omega, duration);
|
| 148 |
+
std::cout << "set velocity to " << arg_pair.second << std::endl;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
if (arg_pair.first == "damp") {
|
| 152 |
+
client.Damp();
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
if (arg_pair.first == "start") {
|
| 156 |
+
client.Start();
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
if (arg_pair.first == "squat") {
|
| 160 |
+
client.Squat();
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
if (arg_pair.first == "sit") {
|
| 164 |
+
client.Sit();
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
if (arg_pair.first == "stand_up") {
|
| 168 |
+
client.StandUp();
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
if (arg_pair.first == "zero_torque") {
|
| 172 |
+
client.ZeroTorque();
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
if (arg_pair.first == "stop_move") {
|
| 176 |
+
client.StopMove();
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
if (arg_pair.first == "high_stand") {
|
| 180 |
+
client.HighStand();
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
if (arg_pair.first == "low_stand") {
|
| 184 |
+
client.LowStand();
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
if (arg_pair.first == "balance_stand") {
|
| 188 |
+
client.BalanceStand();
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
if (arg_pair.first == "continous_gait") {
|
| 192 |
+
bool flag;
|
| 193 |
+
if (arg_pair.second == "true") {
|
| 194 |
+
flag = true;
|
| 195 |
+
} else if (arg_pair.second == "false") {
|
| 196 |
+
flag = false;
|
| 197 |
+
} else {
|
| 198 |
+
std::cerr << "invalid argument: " << arg_pair.second << std::endl;
|
| 199 |
+
return 1;
|
| 200 |
+
}
|
| 201 |
+
client.ContinuousGait(flag);
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
if (arg_pair.first == "switch_move_mode") {
|
| 205 |
+
bool flag;
|
| 206 |
+
if (arg_pair.second == "true") {
|
| 207 |
+
flag = true;
|
| 208 |
+
} else if (arg_pair.second == "false") {
|
| 209 |
+
flag = false;
|
| 210 |
+
} else {
|
| 211 |
+
std::cerr << "invalid argument: " << arg_pair.second << std::endl;
|
| 212 |
+
return 1;
|
| 213 |
+
}
|
| 214 |
+
client.SwitchMoveMode(flag);
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
if (arg_pair.first == "move") {
|
| 218 |
+
std::vector<float> param = stringToFloatVector(arg_pair.second);
|
| 219 |
+
auto param_size = param.size();
|
| 220 |
+
float vx, vy, omega;
|
| 221 |
+
if (param_size == 3) {
|
| 222 |
+
vx = param.at(0);
|
| 223 |
+
vy = param.at(1);
|
| 224 |
+
omega = param.at(2);
|
| 225 |
+
} else {
|
| 226 |
+
std::cerr << "Invalid param size for method SetVelocity: " << param_size
|
| 227 |
+
<< std::endl;
|
| 228 |
+
return 1;
|
| 229 |
+
}
|
| 230 |
+
client.Move(vx, vy, omega);
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
if (arg_pair.first == "set_task_id") {
|
| 234 |
+
int task_id = std::stoi(arg_pair.second);
|
| 235 |
+
client.SetTaskId(task_id);
|
| 236 |
+
std::cout << "set task_id to " << task_id << std::endl;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
if (arg_pair.first == "shake_hand") {
|
| 240 |
+
client.ShakeHand(0);
|
| 241 |
+
std::cout << "Shake hand starts! Waiting for 10 s for ending"
|
| 242 |
+
<< std::endl;
|
| 243 |
+
std::this_thread::sleep_for(std::chrono::seconds(10));
|
| 244 |
+
std::cout << "Shake hand ends!" << std::endl;
|
| 245 |
+
client.ShakeHand(1);
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
if (arg_pair.first == "wave_hand") {
|
| 249 |
+
client.WaveHand();
|
| 250 |
+
std::cout << "wave hand" << std::endl;
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
if (arg_pair.first == "wave_hand_with_turn") {
|
| 254 |
+
client.WaveHand(true);
|
| 255 |
+
std::cout << "wave hand with turn" << std::endl;
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
if (arg_pair.first == "set_speed_mode") {
|
| 259 |
+
client.SetSpeedMode(std::stoi(arg_pair.second));
|
| 260 |
+
std::cout << "set speed mode" << std::endl;
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
std::cout << "Done!" << std::endl;
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
return 0;
|
| 267 |
+
}
|
GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/low_level/behavior_lib/motion.seq
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|