Skip to content
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

[SW-893] odometry velocities reported in the wrong frame #416

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
7 changes: 5 additions & 2 deletions spot_driver/include/spot_driver/conversions/robot_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,14 @@ std::optional<tf2_msgs::msg::TFMessage> getTf(const ::bosdyn::api::FrameTreeSnap
*
* @param robot_state Robot state message from Spot.
* @param clock_skew The clock skew reported by Spot at the timepoint when the robot state was created.
* @param is_using_vision Set to 'true' if Spot's preferred base frame is "vision". Otherwise, the preferred base frame
* will be "odom".
* @return If the robot state message contains the velocity of the Spot's body relative to the odometry frame in its
* kinematic state, return a TwistWithCovarianceStamped containing this data. Otherwise, return nullopt.
*/
std::optional<geometry_msgs::msg::TwistWithCovarianceStamped> getOdomTwist(
const ::bosdyn::api::RobotState& robot_state, const google::protobuf::Duration& clock_skew);
std::optional<geometry_msgs::msg::TwistWithCovarianceStamped> getOdomTwist(const ::bosdyn::api::RobotState& robot_state,
const google::protobuf::Duration& clock_skew,
const bool is_using_vision);

/**
* @brief Create an Odometry ROS message representing Spot's pose and velocity relative to a fixed world frame by
Expand Down
38 changes: 27 additions & 11 deletions spot_driver/src/conversions/robot_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,30 +163,46 @@ std::optional<tf2_msgs::msg::TFMessage> getTf(const ::bosdyn::api::FrameTreeSnap
return tf_msg;
}

std::optional<geometry_msgs::msg::TwistWithCovarianceStamped> getOdomTwist(
const ::bosdyn::api::RobotState& robot_state, const google::protobuf::Duration& clock_skew) {
if (!robot_state.has_kinematic_state() || !robot_state.kinematic_state().has_velocity_of_body_in_odom()) {
std::optional<geometry_msgs::msg::TwistWithCovarianceStamped> getOdomTwist(const ::bosdyn::api::RobotState& robot_state,
const google::protobuf::Duration& clock_skew,
const bool is_using_vision) {
if (!robot_state.has_kinematic_state() || !robot_state.kinematic_state().has_transforms_snapshot()) {
return std::nullopt;
}
const auto& kinematic_state = robot_state.kinematic_state();
if (is_using_vision && !kinematic_state.has_velocity_of_body_in_vision()) {
return std::nullopt;
} else if (!is_using_vision && !kinematic_state.has_velocity_of_body_in_odom()) {
return std::nullopt;
}

geometry_msgs::msg::TwistWithCovarianceStamped odom_twist_msg;
// TODO(schornakj): need to add the frame ID here?
odom_twist_msg.header.stamp =
spot_ros2::robotTimeToLocalTime(robot_state.kinematic_state().acquisition_timestamp(), clock_skew);
convertToRos(robot_state.kinematic_state().velocity_of_body_in_odom(), odom_twist_msg.twist.twist);
const bosdyn::api::SE3Velocity& velocity_of_body_in_world =
is_using_vision ? kinematic_state.velocity_of_body_in_vision() : kinematic_state.velocity_of_body_in_odom();
// This now needs to be converted to velocity of body in body frame in order to follow ROS conventions.
::bosdyn::api::SE3Velocity velocity_of_body_in_body;
const std::string world_frame_name = is_using_vision ? "vision" : "odom";
const std::string body_frame_name = "body";
const bool success =
::bosdyn::api::ExpressVelocityInNewFrame(kinematic_state.transforms_snapshot(), world_frame_name, body_frame_name,
velocity_of_body_in_world, &velocity_of_body_in_body);
if (!success) {
return std::nullopt;
}
convertToRos(velocity_of_body_in_body, odom_twist_msg.twist.twist);
odom_twist_msg.header.stamp = spot_ros2::robotTimeToLocalTime(kinematic_state.acquisition_timestamp(), clock_skew);
odom_twist_msg.header.frame_id = body_frame_name;
return odom_twist_msg;
}

std::optional<nav_msgs::msg::Odometry> getOdom(const ::bosdyn::api::RobotState& robot_state,
const google::protobuf::Duration& clock_skew, const std::string& prefix,
bool is_using_vision) {
if (!robot_state.has_kinematic_state() || !robot_state.kinematic_state().has_acquisition_timestamp() ||
!robot_state.kinematic_state().has_transforms_snapshot() ||
!robot_state.kinematic_state().has_velocity_of_body_in_odom()) {
!robot_state.kinematic_state().has_transforms_snapshot()) {
return std::nullopt;
}

const auto odom_twist = getOdomTwist(robot_state, clock_skew);
const auto odom_twist = getOdomTwist(robot_state, clock_skew, is_using_vision);
if (!odom_twist) {
return std::nullopt;
}
Expand Down
2 changes: 1 addition & 1 deletion spot_driver/src/robot_state/state_publisher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ void StatePublisher::timerCallback() {
getEstopStates(robot_state, clock_skew),
getJointStates(robot_state, clock_skew, frame_prefix_),
getTf(robot_state, clock_skew, frame_prefix_, full_odom_frame_id_),
getOdomTwist(robot_state, clock_skew),
getOdomTwist(robot_state, clock_skew, is_using_vision_),
getOdom(robot_state, clock_skew, frame_prefix_, is_using_vision_),
getPowerState(robot_state, clock_skew),
getSystemFaultState(robot_state, clock_skew),
Expand Down
12 changes: 12 additions & 0 deletions spot_driver/test/include/spot_driver/robot_state_test_tools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@ inline void addBodyVelocityOdom(::bosdyn::api::KinematicState* mutable_kinematic
velocity_angular->set_z(rz);
}

inline void addBodyVelocityVision(::bosdyn::api::KinematicState* mutable_kinematic_state, double x, double y, double z,
double rx, double ry, double rz) {
auto* velocity_linear = mutable_kinematic_state->mutable_velocity_of_body_in_vision()->mutable_linear();
velocity_linear->set_x(x);
velocity_linear->set_y(y);
velocity_linear->set_z(z);
auto* velocity_angular = mutable_kinematic_state->mutable_velocity_of_body_in_vision()->mutable_angular();
velocity_angular->set_x(rx);
velocity_angular->set_y(ry);
velocity_angular->set_z(rz);
}

inline void addAcquisitionTimestamp(::bosdyn::api::KinematicState* mutable_kinematic_state,
const google::protobuf::Timestamp& timestamp) {
mutable_kinematic_state->mutable_acquisition_timestamp()->CopyFrom(timestamp);
Expand Down
123 changes: 116 additions & 7 deletions spot_driver/test/src/conversions/test_robot_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ TEST(RobotStateConversions, TestGetTfInverted) {
EXPECT_THAT(transform.transform, GeometryMsgsTransformEq(-1.0, -2.0, -3.0, 1.0, 0.0, 0.0, 0.0));
}

TEST(RobotStateConversions, TestGetOdomTwist) {
TEST(RobotStateConversions, TestGetOdomTwistNoTransformsSnapshot) {
// GIVEN a RobotState that contains info about the velocity of the body in the odom frame
::bosdyn::api::RobotState robot_state;
google::protobuf::Timestamp timestamp;
Expand All @@ -357,16 +357,122 @@ TEST(RobotStateConversions, TestGetOdomTwist) {
clock_skew.set_seconds(1);

// WHEN we create a TwistWithCovarianceStamped ROS message
const auto out = getOdomTwist(robot_state, clock_skew);
const bool using_vision = false;
const auto out = getOdomTwist(robot_state, clock_skew, using_vision);

// THEN this fails as there is no TransformsSnapshot
ASSERT_THAT(out.has_value(), IsFalse());
}

TEST(RobotStateConversions, TestGetOdomTwistBodyAtOdom) {
// GIVEN a RobotState that contains info about the velocity of the body in the odom frame
::bosdyn::api::RobotState robot_state;
google::protobuf::Timestamp timestamp;
timestamp.set_seconds(99);
timestamp.set_nanos(0);
addAcquisitionTimestamp(robot_state.mutable_kinematic_state(), timestamp);

// GIVEN the odom frame is the root of the frame tree
addRootFrame(robot_state.mutable_kinematic_state()->mutable_transforms_snapshot(), "odom");
// GIVEN the body frame is at the odom frame
addTransform(robot_state.mutable_kinematic_state()->mutable_transforms_snapshot(), "body", "odom", 0.0, 0.0, 0.0, 1.0,
0.0, 0.0, 0.0);

auto* velocity_linear = robot_state.mutable_kinematic_state()->mutable_velocity_of_body_in_odom()->mutable_linear();
velocity_linear->set_x(1.0);
velocity_linear->set_y(2.0);
velocity_linear->set_z(3.0);
auto* velocity_angular = robot_state.mutable_kinematic_state()->mutable_velocity_of_body_in_odom()->mutable_angular();
velocity_angular->set_x(1.0);
velocity_angular->set_y(2.0);
velocity_angular->set_z(3.0);

// GIVEN some nominal clock skew
google::protobuf::Duration clock_skew;
clock_skew.set_seconds(1);

// WHEN we create a TwistWithCovarianceStamped ROS message
const bool using_vision = false;
const auto out = getOdomTwist(robot_state, clock_skew, using_vision);

// THEN this succeeds
ASSERT_THAT(out.has_value(), IsTrue());

// THEN the output twist matches the velocity in the robot state
// THEN the output twist matches the velocity in the robot state.
EXPECT_THAT(out->twist.twist, GeometryMsgsTwistEq(1.0, 2.0, 3.0, 1.0, 2.0, 3.0));
EXPECT_THAT(out->header, ClockSkewIsAppliedToHeader(timestamp, clock_skew));
}

TEST(RobotStateConversions, TestGetOdomTwistLinearTransformationLinearVelocity) {
// GIVEN a RobotState that contains info about the velocity of the body in the odom frame
::bosdyn::api::RobotState robot_state;
google::protobuf::Timestamp timestamp;
timestamp.set_seconds(99);
timestamp.set_nanos(0);
addAcquisitionTimestamp(robot_state.mutable_kinematic_state(), timestamp);

// GIVEN the odom frame is the root of the frame tree
addRootFrame(robot_state.mutable_kinematic_state()->mutable_transforms_snapshot(), "odom");
// GIVEN the body frame is linearly translated 2m forward in the x direction
addTransform(robot_state.mutable_kinematic_state()->mutable_transforms_snapshot(), "body", "odom", 2.0, 0.0, 0.0, 1.0,
0.0, 0.0, 0.0);

auto* velocity_linear = robot_state.mutable_kinematic_state()->mutable_velocity_of_body_in_odom()->mutable_linear();
velocity_linear->set_x(1.0);
velocity_linear->set_y(0.0);
velocity_linear->set_z(0.0);

// GIVEN some nominal clock skew
google::protobuf::Duration clock_skew;
clock_skew.set_seconds(1);

// WHEN we create a TwistWithCovarianceStamped ROS message
const bool using_vision = false;
const auto out = getOdomTwist(robot_state, clock_skew, using_vision);

// THEN this succeeds
ASSERT_THAT(out.has_value(), IsTrue());

// THEN the output twist matches the velocity in the robot state.
EXPECT_THAT(out->twist.twist, GeometryMsgsTwistEq(1.0, 0.0, 0.0, 0.0, 0.0, 0.0));
EXPECT_THAT(out->header, ClockSkewIsAppliedToHeader(timestamp, clock_skew));
}

TEST(RobotStateConversions, TestGetOdomTwistAngularTransformationLinearVelocity) {
// GIVEN a RobotState that contains info about the velocity of the body in the odom frame
::bosdyn::api::RobotState robot_state;
google::protobuf::Timestamp timestamp;
timestamp.set_seconds(99);
timestamp.set_nanos(0);
addAcquisitionTimestamp(robot_state.mutable_kinematic_state(), timestamp);

// GIVEN the odom frame is the root of the frame tree
addRootFrame(robot_state.mutable_kinematic_state()->mutable_transforms_snapshot(), "odom");
// GIVEN the body frame is rotated by 180 degrees from the odom frame (quaternion 0 0 0 1)
addTransform(robot_state.mutable_kinematic_state()->mutable_transforms_snapshot(), "body", "odom", 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 1.0);

auto* velocity_linear = robot_state.mutable_kinematic_state()->mutable_velocity_of_body_in_odom()->mutable_linear();
velocity_linear->set_x(1.0);
velocity_linear->set_y(0.0);
velocity_linear->set_z(0.0);

// GIVEN some nominal clock skew
google::protobuf::Duration clock_skew;
clock_skew.set_seconds(1);

// WHEN we create a TwistWithCovarianceStamped ROS message
const bool using_vision = false;
const auto out = getOdomTwist(robot_state, clock_skew, using_vision);

// THEN this succeeds
ASSERT_THAT(out.has_value(), IsTrue());

// THEN the output twist matches the velocity in the robot state.
EXPECT_THAT(out->twist.twist, GeometryMsgsTwistEq(-1.0, 0.0, 0.0, 0.0, 0.0, 0.0));
EXPECT_THAT(out->header, ClockSkewIsAppliedToHeader(timestamp, clock_skew));
}

TEST(RobotStateConversions, TestGetOdomTwistNoBodyVelocityInRobotState) {
// GIVEN a RobotState where there is some kinematic state info but no info about the velocity of the body in the odom
// frame
Expand All @@ -377,12 +483,14 @@ TEST(RobotStateConversions, TestGetOdomTwistNoBodyVelocityInRobotState) {
clock_skew.set_seconds(1);

// WHEN we attempt to create a TwistWithCovarianceStamped ROS message
const auto out = getOdomTwist(robot_state, clock_skew);
const bool using_vision = false;
const auto out = getOdomTwist(robot_state, clock_skew, using_vision);

// THEN no message is output
ASSERT_THAT(out.has_value(), IsFalse());
}

// TODO(khughes): Fix this test
TEST(RobotStateConversions, TestGetOdomInOdomFrame) {
// GIVEN some nominal clock skew
google::protobuf::Duration clock_skew;
Expand Down Expand Up @@ -423,6 +531,7 @@ TEST(RobotStateConversions, TestGetOdomInOdomFrame) {
EXPECT_THAT(out->twist.twist, GeometryMsgsTwistEq(1.0, 2.0, 3.0, 4.0, 5.0, 6.0));
}

// TODO(khughes): Fix this test
TEST(RobotStateConversions, TestGetOdomInVisionFrame) {
// GIVEN some nominal clock skew
google::protobuf::Duration clock_skew;
Expand All @@ -435,7 +544,7 @@ TEST(RobotStateConversions, TestGetOdomInVisionFrame) {
timestamp.set_seconds(99);
timestamp.set_nanos(0);
addAcquisitionTimestamp(robot_state.mutable_kinematic_state(), timestamp);
addBodyVelocityOdom(robot_state.mutable_kinematic_state(), 1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
addBodyVelocityVision(robot_state.mutable_kinematic_state(), 1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
addRootFrame(robot_state.mutable_kinematic_state()->mutable_transforms_snapshot(), "vision");
addTransform(robot_state.mutable_kinematic_state()->mutable_transforms_snapshot(), "body", "vision", 1.0, 2.0, 3.0,
1.0, 0.0, 0.0, 0.0);
Expand Down Expand Up @@ -497,7 +606,7 @@ TEST(RobotStateConversions, TestGetOdomMissingBodyVelocityOdom) {
Eq(::bosdyn::api::ValidateFrameTreeSnapshotStatus::VALID));

// WHEN we try to create an Odometry message from the RobotState
const auto out = getOdom(robot_state, clock_skew, "prefix/", true);
const auto out = getOdom(robot_state, clock_skew, "prefix/", false);

// THEN this does not succeed
ASSERT_THAT(out.has_value(), IsFalse());
Expand Down Expand Up @@ -533,7 +642,7 @@ TEST(RobotStateConversions, TestGetOdomInvalidTransformSnapshot) {
timestamp.set_seconds(99);
timestamp.set_nanos(0);
addAcquisitionTimestamp(robot_state.mutable_kinematic_state(), timestamp);
addBodyVelocityOdom(robot_state.mutable_kinematic_state(), 1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
addBodyVelocityVision(robot_state.mutable_kinematic_state(), 1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
addTransform(robot_state.mutable_kinematic_state()->mutable_transforms_snapshot(), "body", "vision", 1.0, 2.0, 3.0,
1.0, 0.0, 0.0, 0.0);
ASSERT_THAT(bosdyn::api::ValidateFrameTreeSnapshot(robot_state.kinematic_state().transforms_snapshot()),
Expand Down
Loading