-
Notifications
You must be signed in to change notification settings - Fork 114
Kabirk/add arm wasd #734
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kabirkedia
wants to merge
29
commits into
rai-opensource:main
Choose a base branch
from
strapsai:kabirk/add-arm-wasd
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Kabirk/add arm wasd #734
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
73b8915
add arm wasd
kabirkedia b002b04
try something
kabirkedia d5cbf8e
add QoS
kabirkedia 946a9d3
pass timestamp directly from ROS
kabirkedia cf68b2c
add documentation
kabirkedia b5c4f6a
add documentation
kabirkedia 8939cb3
Merge branch 'main' into kabirk/add-arm-wasd
kabirkedia b7cee65
added twist stamped in addtion to twsit
kabirkedia 503b22f
added twist stamped in addtion to twsit
kabirkedia 610ba70
Merge branch 'main' into kabirk/add-arm-wasd
kabirkedia 9d388f8
merge to main
kabirkedia 0d40a50
Merge branch 'main' into kabirk/add-arm-wasd
kabirkedia e702da0
revert to original
kabirkedia 1155ef6
merge with main
kabirkedia 19ba230
revert to original
kabirkedia 19d7819
revert to original
kabirkedia ac537e3
revert to original
kabirkedia 0bf385e
Merge branch 'main' into kabirk/add-arm-wasd
kabirkedia dd62f7e
Update spot_examples/spot_examples/arm_wasd.py
kabirkedia e39d7e4
dependency checks
5a07347
final suggestions hopefully
0b3897e
dependency resolver
554419a
conclude merge
556e511
pre-commit checks
04d22e6
Update spot_ros2.py
kabirkedia 0b773cb
Update arm_wasd.py
kabirkedia 1c99b8f
Update arm_wasd.py
kabirkedia 9e6fbfe
Update arm_wasd.py
kabirkedia 73c2bcb
Update arm_wasd.py
kabirkedia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Submodule ros_utilities
updated
from 92458f to f2d67a
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -62,14 +62,15 @@ | |||||
| RobotCommandFeedback, | ||||||
| RobotCommandFeedbackStatusStatus, | ||||||
| ) | ||||||
| from geometry_msgs.msg import Pose, PoseStamped, TransformStamped, Twist | ||||||
| from geometry_msgs.msg import Pose, PoseStamped, TransformStamped, Twist, TwistStamped | ||||||
| from rclpy import Parameter | ||||||
| from rclpy.action import ActionServer | ||||||
| from rclpy.action.server import ServerGoalHandle | ||||||
| from rclpy.callback_groups import CallbackGroup, MutuallyExclusiveCallbackGroup | ||||||
| from rclpy.clock import Clock | ||||||
| from rclpy.impl import rcutils_logger | ||||||
| from rclpy.publisher import Publisher | ||||||
| from rclpy.qos import DurabilityPolicy, HistoryPolicy, QoSProfile, ReliabilityPolicy | ||||||
| from sensor_msgs.msg import JointState, PointCloud2, PointField | ||||||
| from std_srvs.srv import Trigger | ||||||
| from synchros2.node import Node | ||||||
|
|
@@ -616,14 +617,37 @@ def __init__(self, parameter_list: Optional[typing.List[Parameter]] = None, **kw | |||||
| self.feedback_pub: Publisher = self.create_publisher(Feedback, "status/feedback", 1) | ||||||
| self.mobility_params_pub: Publisher = self.create_publisher(MobilityParams, "status/mobility_params", 1) | ||||||
|
|
||||||
| self.create_subscription(Twist, "cmd_vel", self.cmd_velocity_callback, 1, callback_group=self.group) | ||||||
| # Recommended QoS for Joy/Teleop commands | ||||||
| JOY_TELEOP_QOS = QoSProfile( | ||||||
| # Best Effort - prioritizes low latency over guaranteed delivery | ||||||
| # Perfect for real-time control where latest command matters most | ||||||
| reliability=ReliabilityPolicy.BEST_EFFORT, | ||||||
| # Keep Last with small depth - only care about most recent commands | ||||||
| # History of 1-5 is typical, 1 is often sufficient for teleop | ||||||
| history=HistoryPolicy.KEEP_LAST, | ||||||
| depth=1, # Only keep the latest command | ||||||
| # Volatile - don't persist commands after node restart | ||||||
| # Teleop commands shouldn't be replayed from before restart | ||||||
| durability=DurabilityPolicy.VOLATILE, | ||||||
| ) | ||||||
|
|
||||||
| self.create_subscription( | ||||||
| Twist, "cmd_vel", self.cmd_velocity_callback, JOY_TELEOP_QOS, callback_group=self.group | ||||||
| ) | ||||||
| self.create_subscription( | ||||||
| TwistStamped, | ||||||
| "cmd_vel_stamped", | ||||||
| self.cmd_velocity_stamped_callback, | ||||||
| JOY_TELEOP_QOS, | ||||||
| callback_group=self.group, | ||||||
| ) | ||||||
| self.create_subscription(Pose, "body_pose", self.body_pose_callback, 1, callback_group=self.group) | ||||||
|
|
||||||
| self.create_trigger_services() | ||||||
|
|
||||||
| if self.has_arm: | ||||||
| self.create_subscription( | ||||||
| JointState, "arm_joint_commands", self.arm_joint_cmd_callback, 100, callback_group=self.group | ||||||
| JointState, "arm_joint_commands", self.arm_joint_cmd_callback, JOY_TELEOP_QOS, callback_group=self.group | ||||||
| ) | ||||||
| self.create_subscription( | ||||||
| PoseStamped, "arm_pose_commands", self.arm_pose_cmd_callback, 100, callback_group=self.group | ||||||
|
|
@@ -632,7 +656,7 @@ def __init__(self, parameter_list: Optional[typing.List[Parameter]] = None, **kw | |||||
| ArmVelocityCommandRequest, | ||||||
| "arm_velocity_commands", | ||||||
| self.arm_velocity_cmd_callback, | ||||||
| 100, | ||||||
| JOY_TELEOP_QOS, | ||||||
| callback_group=self.group, | ||||||
| ) | ||||||
|
|
||||||
|
|
@@ -2645,8 +2669,16 @@ def cmd_velocity_callback(self, data: Twist) -> None: | |||||
| if not self.spot_wrapper: | ||||||
| self.get_logger().info(f"Mock mode, received command vel {data}") | ||||||
| return | ||||||
| self.spot_wrapper.velocity_cmd(data.linear.x, data.linear.y, data.angular.z, self.cmd_duration) | ||||||
|
|
||||||
| def cmd_velocity_stamped_callback(self, data: TwistStamped) -> None: | ||||||
| """Callback for cmd_vel command""" | ||||||
| if not self.spot_wrapper: | ||||||
| self.get_logger().info(f"Mock mode, received command vel {data}") | ||||||
| return | ||||||
| timestamp = data.header.stamp.sec + data.header.stamp.nanosec * 1e-9 | ||||||
| self.spot_wrapper.velocity_cmd( | ||||||
| v_x=data.linear.x, v_y=data.linear.y, v_rot=data.angular.z, cmd_duration=self.cmd_duration | ||||||
| data.twist.linear.x, data.twist.linear.y, data.twist.angular.z, timestamp, self.cmd_duration | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment here about explicitly passing the args:
Suggested change
|
||||||
| ) | ||||||
|
|
||||||
| def body_pose_callback(self, data: Pose) -> None: | ||||||
|
|
||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,312 @@ | ||
| # WASD Robot Control Interface Documentation | ||
|
|
||
| ## Table of Contents | ||
| 1. [Running the Example](#running-the-example) | ||
| 2. [Understanding the Code](#understanding-the-code) | ||
|
|
||
| --- | ||
|
|
||
| ## Running the Example | ||
| For this example, make sure to position the robot with 2m of clear space on all sides, as you will be able to command walking in translation and rotation and arm stowing and unstowing. | ||
| ```bash | ||
| ros2 run spot_examples arm_wasd | ||
| ``` | ||
| If you launched the driver with a namespace, use the following command instead: | ||
| ```bash | ||
| ros2 run spot_examples arm_wasd --robot <spot_name> | ||
| ``` | ||
|
|
||
| #### Initial Robot Setup | ||
|
|
||
| The interface will automatically: | ||
| 1. Claim the robot | ||
| 2. Power on the robot | ||
| 3. Wait for user commands | ||
|
|
||
| #### Control Modes | ||
|
|
||
| The interface has two control modes that you can toggle between: | ||
|
|
||
| **BASE MOVEMENT MODE** (default): | ||
| - Controls the robot's body movement | ||
| - Walking, turning, standing, sitting | ||
|
|
||
| **ARM CONTROL MODE**: | ||
| - Controls the robot's arm in 6 degrees of freedom | ||
| - Arm positioning and end-effector rotation | ||
|
|
||
| ### Key Controls | ||
|
|
||
| #### Mode Switching | ||
| - **[T]**: Toggle between BASE and ARM control modes | ||
| - **[Tab]**: Quit the program | ||
| - **[ESC]**: Emergency stop | ||
|
|
||
| #### Basic Robot Controls (Available in Both Modes) | ||
| - **[p]**: Toggle power on/off | ||
| - **[f]**: Stand up | ||
| - **[v]**: Sit down | ||
| - **[r]**: Self-right (if robot is on its side) | ||
| - **[b]**: Battery change pose (rollover) | ||
|
|
||
| #### Arm Controls (Available in Both Modes) | ||
| - **[y]**: Unstow arm (deploy from stowed position) | ||
| - **[h]**: Stow arm (return to stowed position) | ||
| - **[n]**: Open gripper | ||
| - **[m]**: Close gripper | ||
|
|
||
| #### Base Movement Controls (BASE MODE only) | ||
| - **[w]**: Move forward | ||
| - **[s]**: Move backward | ||
| - **[a]**: Strafe left | ||
| - **[d]**: Strafe right | ||
| - **[q]**: Turn left | ||
| - **[e]**: Turn right | ||
|
|
||
| #### Arm Movement Controls (ARM MODE only) | ||
| - **[w]**: Move arm forward/out | ||
| - **[s]**: Move arm backward/in | ||
| - **[a]**: Rotate arm counter-clockwise | ||
| - **[d]**: Rotate arm clockwise | ||
| - **[q]**: Move arm up | ||
| - **[e]**: Move arm down | ||
|
|
||
| #### End-Effector Rotation (ARM MODE only) | ||
| - **[i/k]**: Rotate around Y-axis (+/-) | ||
| - **[u/o]**: Rotate around X-axis (+/-) | ||
| - **[j/l]**: Rotate around Z-axis (+/-) | ||
|
|
||
| ### Safety Features | ||
|
|
||
| #### Automatic Safety Measures | ||
| - **Emergency stop**: ESC key stops all movement immediately | ||
| - **Arm locking**: When arm is unstowed and base commands are issued, arm locks in position | ||
| - **Service call blocking**: Prevents conflicting commands during service calls | ||
| - **Graceful shutdown**: On exit, automatically sits robot and powers down | ||
|
|
||
| #### Best Practices | ||
| 1. **Always test in a safe environment** with adequate space | ||
| 2. **Keep emergency stop ready**: Know where the ESC key is | ||
| 3. **Monitor robot status**: Watch the power and battery indicators | ||
| 4. **Start with small movements**: Test each control before making large movements | ||
| 5. **Unstow arm carefully**: Ensure adequate clearance before deploying arm | ||
|
|
||
| --- | ||
|
|
||
| ## Understanding the Code | ||
|
|
||
| ### Architecture Overview | ||
|
|
||
| The WASD interface is built using a modular architecture with clear separation of concerns: | ||
|
|
||
| ``` | ||
| WasdInterface | ||
| ├── ROS 2 Integration (publishers, subscribers, service clients) | ||
| ├── Control Mode Management (base vs arm) | ||
| ├── Command Processing (keyboard input handling) | ||
| ├── Safety Systems (emergency stop, arm locking) | ||
| └── UI Management (curses-based display) | ||
| ``` | ||
|
|
||
| ### Core Components | ||
|
|
||
| #### 1. Class Structure | ||
|
|
||
| **`WasdInterface`** - Main controller class | ||
| - Manages robot connection and control | ||
| - Handles user input and command routing | ||
| - Provides safety mechanisms | ||
|
|
||
| **`ExitCheck`** - Signal handling utility | ||
| - Captures SIGTERM/SIGINT for graceful shutdown | ||
| - Provides clean exit mechanism | ||
|
|
||
| **`SimpleSpotCommander`** - Robot command wrapper | ||
| - Abstracts basic robot operations | ||
| - Provides simplified interface for common commands | ||
|
|
||
| ### Key Design Patterns | ||
|
|
||
| #### 1. Command Pattern | ||
| ```python | ||
| self._base_command_dictionary = { | ||
| ord("w"): self._move_forward, | ||
| ord("s"): self._move_backward, | ||
| # ... more commands | ||
| } | ||
| ``` | ||
| Each key maps to a specific command function, allowing easy extension and modification of controls. | ||
|
|
||
| #### 2. State Management | ||
| ```python | ||
| self.control_mode = "base" # or "arm" | ||
| self.is_arm_unstowed = False | ||
| self.is_arm_locked_in_position = False | ||
| ``` | ||
| Tracks robot and interface state to ensure safe operation. | ||
|
|
||
| #### 3. Publisher-Subscriber Pattern | ||
| ```python | ||
| # Publishers for sending commands | ||
| self.pub_cmd_vel = self.node.create_publisher(Twist, ...) | ||
| self.pub_arm_vel = self.node.create_publisher(ArmVelocityCommandRequest, ...) | ||
|
|
||
| # Subscribers for status updates | ||
| self.sub_status_power_state = self.node.create_subscription(PowerState, ...) | ||
| self.sub_battery_state = self.node.create_subscription(BatteryStateArray, ...) | ||
| ``` | ||
|
|
||
| ### Control Mechanisms | ||
|
|
||
| #### 1. Base Movement Control | ||
|
|
||
| **Velocity Control**: | ||
| - Uses `geometry_msgs/Twist` messages | ||
| - Continuous velocity commands with timeout | ||
| - Linear and angular velocity components | ||
|
|
||
| ```python | ||
| def _velocity_cmd_helper(self, desc: str = "", v_x: float = 0.0, v_y: float = 0.0, v_rot: float = 0.0): | ||
| twist = Twist() | ||
| twist.linear.x = v_x | ||
| twist.linear.y = v_y | ||
| twist.angular.z = v_rot | ||
| # Publish continuously for duration | ||
| start_time = time.time() | ||
| while time.time() - start_time < VELOCITY_CMD_DURATION: | ||
| self.pub_cmd_vel.publish(twist) | ||
| time.sleep(0.01) | ||
| ``` | ||
|
|
||
| #### 2. Arm Movement Control | ||
|
|
||
| **6-DOF Arm Control**: | ||
| - Uses cylindrical coordinate system (r, theta, z) | ||
| - End-effector rotation control | ||
| - Velocity-based control with acceleration limits | ||
|
|
||
| ```python | ||
| def arm_velocity_cmd_helper(self, r=0.0, theta=0.0, z=0.0, | ||
| end_effector_x=0.0, end_effector_y=0.0, end_effector_z=0.0): | ||
| arm_cmd = ArmVelocityCommandRequest() | ||
| # Set cylindrical velocities | ||
| arm_cmd.command.cylindrical_velocity.linear_velocity.r = r | ||
| arm_cmd.command.cylindrical_velocity.linear_velocity.theta = theta | ||
| arm_cmd.command.cylindrical_velocity.linear_velocity.z = z | ||
| # Set end-effector angular velocities | ||
| arm_cmd.angular_velocity_of_hand_rt_odom_in_hand.x = end_effector_x | ||
| # ... publish with duration control | ||
| ``` | ||
|
|
||
| ### Safety Systems | ||
|
|
||
| #### 1. Emergency Stop | ||
| ```python | ||
| def _stop(self) -> None: | ||
| self.cli_stop.call_async(Trigger.Request()) | ||
| ``` | ||
| Immediately stops all robot movement. | ||
|
|
||
| #### 2. Arm Position Locking | ||
| ```python | ||
| def _velocity_cmd_helper(self, ...): | ||
| if(self.is_arm_unstowed): | ||
| if(not self.is_arm_locked_in_position): | ||
| result = self.lock_arm_in_position() | ||
| # Lock arm before base movement | ||
| ``` | ||
| When arm is deployed, it's locked in position before allowing base movement. | ||
|
|
||
| #### 3. Service Call Protection | ||
| ```python | ||
| if(self.service_call_in_progress): | ||
| self.add_message("Service call in progress, cannot send velocity command") | ||
| return | ||
| ``` | ||
| Prevents conflicting commands during ongoing operations. | ||
|
|
||
| #### 4. QoS Configuration | ||
| ```python | ||
| JOY_TELEOP_QOS = QoSProfile( | ||
| reliability=ReliabilityPolicy.BEST_EFFORT, | ||
| history=HistoryPolicy.KEEP_LAST, | ||
| depth=1, | ||
| durability=DurabilityPolicy.VOLATILE, | ||
| lifespan=rclpy.duration.Duration(seconds=1.0), | ||
| ) | ||
| ``` | ||
| Optimized for real-time teleop control with low latency. | ||
|
|
||
| #### Service Clients | ||
| The interface uses multiple ROS 2 service clients for robot operations: | ||
| - Power control (`power_on`, `power_off`) | ||
| - Posture control (`sit`, `stand`, `self_right`) | ||
| - Arm control (`arm_stow`, `arm_unstow`) | ||
| - Gripper control (`open_gripper`, `close_gripper`) | ||
|
|
||
| ### User Interface (Curses) | ||
|
|
||
| #### Display Management | ||
| ```python | ||
| def _drive_draw(self, stdscr: curses.window) -> None: | ||
| stdscr.clear() | ||
| stdscr.addstr(0, 0, f"robot name: {self.robot_name}") | ||
| stdscr.addstr(1, 0, f"CONTROL MODE: {self.control_mode.upper()}") | ||
| # ... display status and help text | ||
| ``` | ||
|
|
||
| #### Input Processing | ||
| ```python | ||
| def _drive_cmd(self, key: int) -> None: | ||
| if self.control_mode == "base": | ||
| cmd_function = self._base_command_dictionary[key] | ||
| else: | ||
| cmd_function = self._arm_command_dictionary[key] | ||
| cmd_function() | ||
| ``` | ||
|
|
||
| ### Configuration Constants | ||
|
|
||
| #### Movement Parameters | ||
| ```python | ||
| VELOCITY_BASE_SPEED = 0.3 # m/s - base linear velocity | ||
| VELOCITY_BASE_ANGULAR = 0.5 # rad/sec - base angular velocity | ||
| ARM_VELOCITY_NORMALIZED = 0.4 # m/s - arm linear velocity | ||
| ARM_VELOCITY_ANGULAR_NORMALIZED = 0.3 # rad/s - arm angular velocity | ||
| ``` | ||
|
|
||
| #### Timing Parameters | ||
| ```python | ||
| VELOCITY_CMD_DURATION = 1.0 # Duration to send base velocity commands | ||
| ARM_VELOCITY_CMD_DURATION = 0.25 # Duration to send arm velocity commands | ||
| COMMAND_INPUT_RATE = 0.1 # Rate limiting for input processing | ||
| ``` | ||
|
|
||
| #### Status Monitoring | ||
| The interface continuously monitors: | ||
| - **Battery status**: Charge levels for all battery packs | ||
| - **Power state**: Motor power on/off status | ||
| - **Arm state**: Stowed/unstowed status | ||
| - **Service call status**: Prevents command conflicts | ||
|
|
||
| ### Extension Points | ||
|
|
||
| #### Adding New Commands | ||
| 1. Add key mapping to appropriate command dictionary | ||
| 2. Implement command function | ||
| 3. Update help text in `_drive_draw()` | ||
|
|
||
| #### Adding New Control Modes | ||
| 1. Extend `control_mode` state management | ||
| 2. Create new command dictionary | ||
| 3. Add mode switching logic | ||
| 4. Update UI display | ||
|
|
||
| #### Customizing Movement Parameters | ||
| Modify the constants at the top of the file to adjust: | ||
| - Movement speeds | ||
| - Command durations | ||
| - Acceleration limits | ||
| - Input rates | ||
|
|
||
| This architecture provides a robust, extensible foundation for robot teleoperation while maintaining safety and usability. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.