Skip to content

Kabirk/add arm wasd#734

Open
kabirkedia wants to merge 24 commits intobdaiinstitute:mainfrom
strapsai:kabirk/add-arm-wasd
Open

Kabirk/add arm wasd#734
kabirkedia wants to merge 24 commits intobdaiinstitute:mainfrom
strapsai:kabirk/add-arm-wasd

Conversation

@kabirkedia
Copy link
Contributor

Change Overview

Adds better arm control to the driver.

Testing Done

Please create a checklist of tests you plan to do and check off the ones that have been completed successfully. Ensure that ROS 2 tests use domain_coordinator to prevent port conflicts. Further guidance for testing can be found on the ros utilities wiki.

@tcappellari-bdai
Copy link
Collaborator

When this is ready for review, I'll find some time to test it on our robot. Thanks!

@tcappellari-bdai
Copy link
Collaborator

When this is ready for review, I'll find some time to test it on our robot. Thanks!

ah, I just saw the related spot_wrapper PR. I'll find some time to review and test them next week

@kabirkedia
Copy link
Contributor Author

Hey @tcappellari-bdai,This should be ready for review. I just need your opinion onon bdaiinstitute/spot_wrapper#179, specifically regarding the arm velocity control. Do you think the end time should be set by the user or handled internally by the spot_ros2 driver?
If we go with the driver handling it, we could either introduce a custom message for arm velocity or reuse Twist.

@khughes-bdai
Copy link
Collaborator

Left some other comments on bdaiinstitute/spot_wrapper#179, but I think this example will be super useful to have in soon. Thanks @kabirkedia!

@kabirkedia
Copy link
Contributor Author

@khughes-bdai this is ready to merge
Let me know if any changes required

@khughes-bdai
Copy link
Collaborator

khughes-bdai commented Oct 20, 2025

Hi @kabirkedia! Just tested this out on robot, sorry for the super long delay. The demo is working great! I also tried out just publishing to cmd_vel in the terminal and verified that was working the same as before. Two things I ran into:

  1. when teleoperating the arm with arm_wasd I kept getting this error spammed in the terminal, not sure what is causing it. The robot arm was still moving fine despite this printout. Will look into it on the side but let me know if you have any ideas or if you have also ran into this
[spot_ros2-1] [ERROR] [1760995020.400281894] [spot_ros2]: Failed to execute arm velocity command: An error occured while trying to move arm
[spot_ros2-1]  Exception: bosdyn.api.RobotCommandResponse (InvalidRequestError): Received request with unset command.
[spot_ros2-1] [ERROR] [1760995020.771800650] [spot_ros2]: Failed to execute arm velocity command: An error occured while trying to move arm
[spot_ros2-1]  Exception: bosdyn.api.RobotCommandResponse (InvalidRequestError): Received request with unset command.
...
  1. When in arm teleoperation mode, and then switching to walking mode or sitting, the arm tries to stay at the same place in the world frame. This left the arm in a weird spot when I shut down the example. I think it would be useful on shut down to stow the arm, and then possibly when switching between arm + body mode to lock the arm in body instead of in world. What do you think?

Also, can you run pre-commit install && pre-commit run --all and re-push, I think there are a few small linting errors. Can you also revert the changes to spot_wrapper on this PR? Your associated PR in spot_wrapper has already been brought into spot_ros2 with dependabot.
Thanks!!

Comment on lines +226 to +230
# self.logger.info("Standing robot up")
# result = self.robot.command("stand")
# if not result.success:
# self.logger.error("Robot did not stand message was " + result.message)
# return False
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be deleted/uncommented?

while time.time() - start_time < ARM_VELOCITY_CMD_DURATION:
self.pub_arm_vel.publish(arm_cmd)
time.sleep(0.01)
self.pub_arm_vel.publish(ArmVelocityCommandRequest())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be the cause of the error I was seeing in the terminal:

[spot_ros2-1] [ERROR] [1760995022.039689202] [spot_ros2]: Failed to execute arm velocity command: An error occured while trying to move arm
[spot_ros2-1]  Exception: bosdyn.api.RobotCommandResponse (InvalidRequestError): Received request with unset command.

This is because ArmVelocityRequest() instead of being a zero command is read as an uninitialized command. I think you will have to explicitly define this as a proto first, then convert to the ros message form. This is just a little more robust than filling in the ros message manually and ensures all of the appropriate fields are captured. I think it would be useful to do this both for the command from the user input as well as the zero command at the end

from bosdyn.api import arm_command_pb2
arm_cmd_request = arm_command_pb2.ArmVelocityCommand.Request()
# then fill in the attributes the same way you are doing above
arm_cmd_request.angular_velocity_of_hand_rt_odom_in_hand.x = end_effector_x
arm_cmd_request.angular_velocity_of_hand_rt_odom_in_hand.y = end_effector_y
...
arm_cmd_request_ros = ArmVelocityCommandRequest()
convert(arm_cmd_request, arm_cmd_request_ros)
# then publish arm_cmd_request_ros

your zero command at the end would be

zero_arm_cmd_request = arm_command_pb2.ArmVelocityCommand.Request()
zero_arm_cmd_request_ros = ArmVelocityCommandRequest()
convert(zero_arm_cmd_request, zero_arm_cmd_request_ros)

Comment on lines +581 to +585
# try:
os.environ.setdefault("ESCDELAY", "0")
curses.wrapper(wasd_interface.drive)
# except Exception as e:
# wasd_interface.logger.error("WASD has thrown an error:" + str(e))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can these comments be removed?

@kabirkedia
Copy link
Contributor Author

Hey @khughes-bdai ,
Sorry for the delay from my side but I did not get time to work with the spot recently due to shift in project changes. I just made all the necessary changes
Please look into them and let me know!

@kabirkedia
Copy link
Contributor Author

kabirkedia commented Nov 12, 2025

Hi @kabirkedia! Just tested this out on robot, sorry for the super long delay. The demo is working great! I also tried out just publishing to cmd_vel in the terminal and verified that was working the same as before. Two things I ran into:

  1. when teleoperating the arm with arm_wasd I kept getting this error spammed in the terminal, not sure what is causing it. The robot arm was still moving fine despite this printout. Will look into it on the side but let me know if you have any ideas or if you have also ran into this
[spot_ros2-1] [ERROR] [1760995020.400281894] [spot_ros2]: Failed to execute arm velocity command: An error occured while trying to move arm
[spot_ros2-1]  Exception: bosdyn.api.RobotCommandResponse (InvalidRequestError): Received request with unset command.
[spot_ros2-1] [ERROR] [1760995020.771800650] [spot_ros2]: Failed to execute arm velocity command: An error occured while trying to move arm
[spot_ros2-1]  Exception: bosdyn.api.RobotCommandResponse (InvalidRequestError): Received request with unset command.
...
  1. When in arm teleoperation mode, and then switching to walking mode or sitting, the arm tries to stay at the same place in the world frame. This left the arm in a weird spot when I shut down the example. I think it would be useful on shut down to stow the arm, and then possibly when switching between arm + body mode to lock the arm in body instead of in world. What do you think?

Also, can you run pre-commit install && pre-commit run --all and re-push, I think there are a few small linting errors. Can you also revert the changes to spot_wrapper on this PR? Your associated PR in spot_wrapper has already been brought into spot_ros2 with dependabot. Thanks!!

Yes the arm locking in the world frame is a weird thing but I implemented a lock arm in place just for that. It should now lock the arm in place using joint commands.

def _velocity_cmd_helper(self, desc: str = "", v_x: float = 0.0, v_y: float = 0.0, v_rot: float = 0.0) -> None:
        if self.service_call_in_progress:
            self.add_message("Service call in progress, cannot send velocity command")
            return
        if self.is_arm_unstowed:
            if not self.is_arm_locked_in_position:
                self.service_call_in_progress = True
                result = self.lock_arm_in_position()
                self.service_call_in_progress = False
                if not result.success:
                    self.add_message("Failed to lock arm in position, cannot send velocity command")
                    self.is_arm_locked_in_position = False
                    return
                else:
                    self.is_arm_locked_in_position = True
        else:
            self.is_arm_locked_in_position = False
        twist = Twist()
        twist.linear.x = v_x
        twist.linear.y = v_y
        twist.angular.z = v_rot
        start_time = time.time()
        while time.time() - start_time < VELOCITY_CMD_DURATION:
            self.pub_cmd_vel.publish(twist)
            time.sleep(0.01)
        self.pub_cmd_vel.publish(Twist())

@khughes-bdai
Copy link
Collaborator

khughes-bdai commented Dec 1, 2025

Hi @kabirkedia , sorry for the delay on my end! I just re tested this on robot. There were a few things I had to tweak manually to get it running. I attached the full git diff here (diff.txt) -- if you could apply this to your branch it was working really great and we should be good to merge! Edit: The changes I had to make are in the next comment!
Overall summary of things I changed -

  1. I think the change from manually filling out the ros message -> filling out the proto and then converting to a ros message introduced a few bugs just with field names
  2. I removed the empty arm velocity command at the end of the arm_velocity_cmd_helper function as it was still giving me errors in the terminal, and removing it got rid of the spew without seeming to affect the robot's behavior. I believe this is because the main arm velocity commands that are sent to the robot have a short time to live, and once they expire the robot doesn't move anymore.
  3. i moved the call to lock_arm_in_place out of the velocity_cmd function and into the toggle_control function. With the initial location I think the code encounters some race conditions where sometimes the lock arm happens before the robot is moved and sometimes it happens after, leading to some unstable behavior. In my opinion having this happen on the control switch simplifies this logic.

I am also still seeing merge conflicts from the git submodules on this PR. Could you untrack them from your branch? Thanks!

Copy link
Collaborator

@khughes-bdai khughes-bdai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the changes I had to make locally to review format to make it easier to parse!

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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to catch this earlier, but this will currently break because of the order of the args from this spot_wrapper function: https://github.com/bdaiinstitute/spot_wrapper/blob/8e6ff4e55817952f5201fe510204324d023825ae/spot_wrapper/wrapper.py#L1288

I would suggest explicitly passing the arg names here to make sure that this is parsed correctly

Suggested change
self.spot_wrapper.velocity_cmd(data.linear.x, data.linear.y, data.angular.z, self.cmd_duration)
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)

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here about explicitly passing the args:

Suggested change
data.twist.linear.x, data.twist.linear.y, data.twist.angular.z, timestamp, self.cmd_duration
v_x=data.linear.x, v_y=data.linear.y, v_rot=data.angular.z, timestamp=timestamp, cmd_duration=self.cmd_duration

Comment on lines +529 to +534
arm_cmd_request.maximum_acceleration.data = ARM_MAXIMUM_ACCELERATION
arm_cmd_request.command.command_choice = arm_cmd_request.command.COMMAND_CYLINDRICAL_VELOCITY_SET
arm_cmd_request.command.cylindrical_velocity.linear_velocity.r = r
arm_cmd_request.command.cylindrical_velocity.linear_velocity.theta = theta
arm_cmd_request.command.cylindrical_velocity.linear_velocity.z = z
arm_cmd_request.command.cylindrical_velocity.max_linear_velocity.data = ARM_MAX_LINEAR_VELOCITY
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think this was left over from switching from ros msg -> proto

Suggested change
arm_cmd_request.maximum_acceleration.data = ARM_MAXIMUM_ACCELERATION
arm_cmd_request.command.command_choice = arm_cmd_request.command.COMMAND_CYLINDRICAL_VELOCITY_SET
arm_cmd_request.command.cylindrical_velocity.linear_velocity.r = r
arm_cmd_request.command.cylindrical_velocity.linear_velocity.theta = theta
arm_cmd_request.command.cylindrical_velocity.linear_velocity.z = z
arm_cmd_request.command.cylindrical_velocity.max_linear_velocity.data = ARM_MAX_LINEAR_VELOCITY
arm_cmd_request.maximum_acceleration.value = ARM_MAXIMUM_ACCELERATION
arm_cmd_request.cylindrical_velocity.linear_velocity.r = r
arm_cmd_request.cylindrical_velocity.linear_velocity.theta = theta
arm_cmd_request.cylindrical_velocity.linear_velocity.z = z
arm_cmd_request.cylindrical_velocity.max_linear_velocity.value = ARM_MAX_LINEAR_VELOCITY

self.add_message("Switched to ARM CONTROL mode")
else:
self.control_mode = "base"
self.add_message("Switched to BASE MOVEMENT mode")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is more robust to lock the arm in place when you switch modes:

Suggested change
self.add_message("Switched to BASE MOVEMENT mode")
self.add_message("Switched to BASE MOVEMENT mode")
self.lock_arm_in_position()

Comment on lines +542 to +546
zero_arm_cmd_request = arm_command_pb2.ArmVelocityCommand.Request()
zero_arm_cmd_request_ros = ArmVelocityCommandRequest()
convert(zero_arm_cmd_request, zero_arm_cmd_request_ros)

self.pub_arm_vel.publish(zero_arm_cmd_request_ros)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this part can be removed. It gets rid of terminal spew without affecting the performance. I would also reccomend setting the arm_is_unstowed bool to true here as this moves the arm

Suggested change
zero_arm_cmd_request = arm_command_pb2.ArmVelocityCommand.Request()
zero_arm_cmd_request_ros = ArmVelocityCommandRequest()
convert(zero_arm_cmd_request, zero_arm_cmd_request_ros)
self.pub_arm_vel.publish(zero_arm_cmd_request_ros)
self.is_arm_unstowed = True

Comment on lines +437 to +452
if self.service_call_in_progress:
self.add_message("Service call in progress, cannot send velocity command")
return
if self.is_arm_unstowed:
if not self.is_arm_locked_in_position:
self.service_call_in_progress = True
result = self.lock_arm_in_position()
self.service_call_in_progress = False
if not result:
self.add_message("Failed to lock arm in position, cannot send velocity command")
self.is_arm_locked_in_position = False
return
else:
self.is_arm_locked_in_position = True
else:
self.is_arm_locked_in_position = False
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we handle locking the arm when we switch between arm/base mode, this whole block can be deleted

Suggested change
if self.service_call_in_progress:
self.add_message("Service call in progress, cannot send velocity command")
return
if self.is_arm_unstowed:
if not self.is_arm_locked_in_position:
self.service_call_in_progress = True
result = self.lock_arm_in_position()
self.service_call_in_progress = False
if not result:
self.add_message("Failed to lock arm in position, cannot send velocity command")
self.is_arm_locked_in_position = False
return
else:
self.is_arm_locked_in_position = True
else:
self.is_arm_locked_in_position = False

Comment on lines +549 to +550
"""Lock arm in current position - placeholder for actual implementation"""
# This would need to be implemented based on your robot's API
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update/remove this comment?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants