Timsty commited on
Commit
b6a9d87
·
verified ·
1 Parent(s): f53e03f

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +7 -0
  2. GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_ankle_pitch_link.stl +3 -0
  3. GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_hip_pitch_link.stl +3 -0
  4. GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_shoulder_pitch_link.stl +3 -0
  5. GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_shoulder_roll_link.stl +3 -0
  6. GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_shoulder_yaw_link.stl +3 -0
  7. GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/left_wrist_roll_link.stl +3 -0
  8. GR00T-WholeBodyControl/gear_sonic/data/assets/robot_description/urdf/h2/meshes/right_knee_link.stl +3 -0
  9. GR00T-WholeBodyControl/gear_sonic_deploy/policy/release/observation_config.yaml +80 -0
  10. GR00T-WholeBodyControl/gear_sonic_deploy/policy/release/observation_config_low_latency.yaml +92 -0
  11. GR00T-WholeBodyControl/gear_sonic_deploy/policy/release/observation_config_sonic_release.yaml +79 -0
  12. GR00T-WholeBodyControl/gear_sonic_deploy/policy/release/observation_config_sonic_v1_1.yaml +74 -0
  13. GR00T-WholeBodyControl/gear_sonic_deploy/policy/sonic_v1_1/observation_config.yaml +74 -0
  14. GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/gamepad.hpp +777 -0
  15. GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/gamepad_manager.hpp +1078 -0
  16. GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/input_command.hpp +108 -0
  17. GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/input_interface.hpp +505 -0
  18. GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/interface_manager.hpp +396 -0
  19. GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/keyboard_handler.hpp +701 -0
  20. GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/ros2_input_handler.hpp +1278 -0
  21. GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/streamed_motion_merger.hpp +517 -0
  22. GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/zmq_endpoint_interface.hpp +1865 -0
  23. GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/zmq_manager.hpp +1257 -0
  24. GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/input_interface/zmq_packed_message_subscriber.hpp +439 -0
  25. GR00T-WholeBodyControl/gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/include/output_interface/zmq_output_handler.hpp +423 -0
  26. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/.devcontainer/Dockerfile.devcontainer +35 -0
  27. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/.devcontainer/devcontainer.json +29 -0
  28. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/.devcontainer/docker-compose.yml +25 -0
  29. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/.gitignore +8 -0
  30. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/CMakeLists.txt +82 -0
  31. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/LICENSE +29 -0
  32. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/README.md +60 -0
  33. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/cmake/unitree_sdk2Config.cmake.in +7 -0
  34. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/cmake/unitree_sdk2Targets.cmake +96 -0
  35. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/CMakeLists.txt +12 -0
  36. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2/CMakeLists.txt +5 -0
  37. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2/b2_sport_client.cpp +24 -0
  38. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2/b2_stand_example.cpp +350 -0
  39. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2w/CMakeLists.txt +5 -0
  40. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2w/b2w_sport_client.cpp +170 -0
  41. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/b2w/b2w_stand_example.cpp +372 -0
  42. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/CMakeLists.txt +38 -0
  43. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/audio/g1_audio_client_example.cpp +174 -0
  44. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/audio/wav.hpp +232 -0
  45. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/dex3/g1_dex3_example.cpp +326 -0
  46. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/high_level/g1_arm5_sdk_dds_example.cpp +256 -0
  47. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/high_level/g1_arm7_sdk_dds_example.cpp +262 -0
  48. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/high_level/g1_arm_action_example.cpp +80 -0
  49. GR00T-WholeBodyControl/gear_sonic_deploy/thirdparty/unitree_sdk2/example/g1/high_level/g1_loco_client_example.cpp +267 -0
  50. 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