Skip to content

Commit 0a2fd40

Browse files
nbbrooksclaude
andcommitted
feat(behaviors): add pose conversion, snapshot subscribers, Bool/Empty publishers
Adds nine behaviors required by external workspaces composing pose-based teleop pipelines (e.g. lab_sim's Quest controller teleoperation Objective): Pose / vector primitives - PoseToVectors — explodes a geometry_msgs/Pose into separate translation (x,y,z) and orientation (x,y,z,w) float ports. Lets BT-XML compose poses through scalar Script math without needing a custom Behavior. - VectorsToPose — inverse of PoseToVectors; rebuilds a Pose from the seven scalar ports. Snapshot / latest subscriber variants - GetBool — streaming std_msgs/Bool subscriber; outputs the primitive bool on each tick. Uses std::optional internally so a default-constructed Bool is never written to the blackboard before the first message arrives (avoids a cold-start race against pre-seeded values). - GetBoolInstance — one-shot variant; subscribes on start, blocks until the first message, tears down the subscription and returns SUCCESS. Outputs the full std_msgs/Bool message (useful when downstream consumers want the message wrapper rather than the primitive). - GetEmptyInstance — one-shot std_msgs/Empty subscriber; useful as a fire-on-event gate in Parallel constructs. - GetOdomInstance — one-shot nav_msgs/Odometry subscriber. - GetOdomLatest — streaming Odometry subscriber with std::optional cold-start safety (analogous to GetBool above). Message publishing - PublishBool — std_msgs/Bool publisher with a primitive bool input port. - PublishEmpty — std_msgs/Empty publisher for firing one-shot event signals from a BT (heartbeats, fire-on-event gates that downstream subscribers can latch onto). Pairs with GetEmptyInstance. Build / test - CMakeLists.txt: nine new .cpp source files added; THIS_PACKAGE_INCLUDE_DEPENDS picks up nav_msgs and std_msgs. - package.xml: nav_msgs and std_msgs depends added. - test/test_behavior_plugins.cpp: PoseToVectors and VectorsToPose added to the load-plugin smoke check. - test/test_pose_vector_conversion.cpp: new gtest suite covering the decompose / compose math, including identity, round-trip, and edge cases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7b8cad3 commit 0a2fd40

24 files changed

Lines changed: 1558 additions & 1 deletion

CMakeLists.txt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ project(experimental_behaviors CXX)
44
find_package(moveit_studio_common REQUIRED)
55
moveit_studio_package()
66

7-
set(THIS_PACKAGE_INCLUDE_DEPENDS control_msgs geometry_msgs moveit_pro_behavior_interface pluginlib moveit_studio_common behaviortree_cpp tl_expected trajectory_msgs moveit_pro_base)
7+
set(THIS_PACKAGE_INCLUDE_DEPENDS control_msgs geometry_msgs moveit_pro_behavior_interface pluginlib moveit_studio_common behaviortree_cpp nav_msgs std_msgs tl_expected trajectory_msgs moveit_pro_base)
88
foreach(package IN ITEMS ${THIS_PACKAGE_INCLUDE_DEPENDS})
99
find_package(${package} REQUIRED)
1010
endforeach()
@@ -22,6 +22,15 @@ add_library(
2222
src/get_blackboard_by_key.cpp
2323
src/set_blackboard_by_key.cpp
2424
src/trajectory_to_path.cpp
25+
src/pose_to_vectors.cpp
26+
src/vectors_to_pose.cpp
27+
src/get_odom_latest.cpp
28+
src/publish_bool.cpp
29+
src/publish_empty.cpp
30+
src/get_bool.cpp
31+
src/get_bool_instance.cpp
32+
src/get_empty_instance.cpp
33+
src/get_odom_instance.cpp
2534
src/register_behaviors.cpp)
2635
target_include_directories(
2736
experimental_behaviors
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2026 PickNik Inc.
2+
// All rights reserved.
3+
//
4+
// Unauthorized copying of this code base via any medium is strictly prohibited.
5+
// Proprietary and confidential.
6+
7+
#pragma once
8+
9+
#include <optional>
10+
#include <shared_mutex>
11+
12+
#include <behaviortree_cpp/action_node.h>
13+
#include <rclcpp/rclcpp.hpp>
14+
#include <std_msgs/msg/bool.hpp>
15+
16+
#include <moveit_pro_behavior_interface/behavior_context.hpp>
17+
#include <moveit_pro_behavior_interface/shared_resources_node.hpp>
18+
19+
namespace experimental_behaviors
20+
{
21+
/**
22+
* @brief Long-lived `std_msgs/Bool` subscriber that writes the latest received value to the
23+
* blackboard every tick. Returns RUNNING forever; halted by the parent on parallel
24+
* completion.
25+
*
26+
* @details Uses `std::optional<bool>` for the cached value and skips `setOutput` until the
27+
* first real message arrives. Without this, a default-constructed `false` would be
28+
* written on the first onRunning tick, clobbering any pre-seeded blackboard value
29+
* before the publisher has delivered a real sample.
30+
*
31+
* | Data Port Name | Port Type | Object Type |
32+
* | ---------------- | --------- | ----------- |
33+
* | bool_topic_name | input | std::string |
34+
* | subscribed_bool | output | bool |
35+
*/
36+
class GetBool : public moveit_pro::behaviors::SharedResourcesNode<BT::StatefulActionNode>
37+
{
38+
public:
39+
GetBool(const std::string& name, const BT::NodeConfiguration& config,
40+
const std::shared_ptr<moveit_pro::behaviors::BehaviorContext>& shared_resources);
41+
42+
static BT::PortsList providedPorts();
43+
static BT::KeyValueVector metadata();
44+
45+
private:
46+
BT::NodeStatus onStart() override;
47+
BT::NodeStatus onRunning() override;
48+
void onHalted() override;
49+
50+
rclcpp::Subscription<std_msgs::msg::Bool>::SharedPtr bool_subscriber_;
51+
std::shared_mutex bool_mutex_;
52+
std::optional<bool> current_bool_value_;
53+
};
54+
} // namespace experimental_behaviors
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2026 PickNik Inc.
2+
// All rights reserved.
3+
//
4+
// Unauthorized copying of this code base via any medium is strictly prohibited.
5+
// Proprietary and confidential.
6+
7+
#pragma once
8+
9+
#include <optional>
10+
#include <shared_mutex>
11+
#include <string>
12+
13+
#include <behaviortree_cpp/action_node.h>
14+
#include <rclcpp/rclcpp.hpp>
15+
#include <std_msgs/msg/bool.hpp>
16+
17+
#include <moveit_pro_behavior_interface/behavior_context.hpp>
18+
#include <moveit_pro_behavior_interface/shared_resources_node.hpp>
19+
20+
namespace experimental_behaviors
21+
{
22+
/**
23+
* @brief Snapshot-style `std_msgs/Bool` subscriber. Blocks until the first message arrives,
24+
* then returns SUCCESS and tears down its subscription. Outputs the full Bool
25+
* message (not the primitive bool) — useful when downstream consumers need the
26+
* ROS message wrapper. For streaming the primitive value, use `GetBool` instead.
27+
*
28+
* | Data Port Name | Port Type | Object Type |
29+
* | ------------------------ | --------- | ------------------- |
30+
* | bool_topic_name | input | std::string |
31+
* | subscribed_bool_instance | output | std_msgs::msg::Bool |
32+
*/
33+
class GetBoolInstance : public moveit_pro::behaviors::SharedResourcesNode<BT::StatefulActionNode>
34+
{
35+
public:
36+
GetBoolInstance(const std::string& name, const BT::NodeConfiguration& config,
37+
const std::shared_ptr<moveit_pro::behaviors::BehaviorContext>& shared_resources);
38+
39+
static BT::PortsList providedPorts();
40+
static BT::KeyValueVector metadata();
41+
42+
private:
43+
BT::NodeStatus onStart() override;
44+
BT::NodeStatus onRunning() override;
45+
void onHalted() override;
46+
47+
rclcpp::Subscription<std_msgs::msg::Bool>::SharedPtr bool_subscriber_;
48+
std::shared_mutex bool_mutex_;
49+
std::optional<std_msgs::msg::Bool> current_bool_;
50+
std::string bool_topic_name_;
51+
};
52+
} // namespace experimental_behaviors
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2026 PickNik Inc.
2+
// All rights reserved.
3+
//
4+
// Unauthorized copying of this code base via any medium is strictly prohibited.
5+
// Proprietary and confidential.
6+
7+
#pragma once
8+
9+
#include <shared_mutex>
10+
11+
#include <behaviortree_cpp/action_node.h>
12+
#include <std_msgs/msg/empty.hpp>
13+
14+
#include <moveit_pro_behavior_interface/behavior_context.hpp>
15+
#include <moveit_pro_behavior_interface/shared_resources_node.hpp>
16+
17+
namespace experimental_behaviors
18+
{
19+
/**
20+
* @brief Snapshot-style `std_msgs/Empty` subscriber. Blocks until the first message arrives,
21+
* then returns SUCCESS and tears down its subscription. Use for one-shot event
22+
* triggers where downstream logic must not run until the trigger fires.
23+
*
24+
* | Data Port Name | Port Type | Object Type |
25+
* | ----------------- | --------- | ----------- |
26+
* | empty_topic_name | input | std::string |
27+
* | message_received | output | bool |
28+
* | message_count | output | uint64_t |
29+
*/
30+
class GetEmptyInstance : public moveit_pro::behaviors::SharedResourcesNode<BT::StatefulActionNode>
31+
{
32+
public:
33+
GetEmptyInstance(const std::string& name, const BT::NodeConfiguration& config,
34+
const std::shared_ptr<moveit_pro::behaviors::BehaviorContext>& shared_resources);
35+
36+
static BT::PortsList providedPorts();
37+
static BT::KeyValueVector metadata();
38+
39+
BT::NodeStatus onStart() override;
40+
BT::NodeStatus onRunning() override;
41+
void onHalted() override;
42+
43+
private:
44+
std::shared_ptr<rclcpp::Subscription<std_msgs::msg::Empty>> empty_subscriber_;
45+
std::shared_mutex empty_mutex_;
46+
uint64_t message_count_;
47+
};
48+
} // namespace experimental_behaviors
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2026 PickNik Inc.
2+
// All rights reserved.
3+
//
4+
// Unauthorized copying of this code base via any medium is strictly prohibited.
5+
// Proprietary and confidential.
6+
7+
#pragma once
8+
9+
#include <optional>
10+
#include <shared_mutex>
11+
#include <string>
12+
13+
#include <behaviortree_cpp/action_node.h>
14+
#include <nav_msgs/msg/odometry.hpp>
15+
#include <rclcpp/rclcpp.hpp>
16+
17+
#include <moveit_pro_behavior_interface/behavior_context.hpp>
18+
#include <moveit_pro_behavior_interface/shared_resources_node.hpp>
19+
20+
namespace experimental_behaviors
21+
{
22+
/**
23+
* @brief Snapshot-style `nav_msgs/Odometry` subscriber. Subscribes on start, blocks until
24+
* the first message arrives, returns SUCCESS, and tears down its subscription. Use
25+
* for one-shot pose snapshots where downstream math must not race against
26+
* subscription cold-start.
27+
*
28+
* | Data Port Name | Port Type | Object Type |
29+
* | ------------------------ | --------- | ----------------------- |
30+
* | odom_topic_name | input | std::string |
31+
* | subscribed_odom_instance | output | nav_msgs::msg::Odometry |
32+
*/
33+
class GetOdomInstance : public moveit_pro::behaviors::SharedResourcesNode<BT::StatefulActionNode>
34+
{
35+
public:
36+
GetOdomInstance(const std::string& name, const BT::NodeConfiguration& config,
37+
const std::shared_ptr<moveit_pro::behaviors::BehaviorContext>& shared_resources);
38+
39+
static BT::PortsList providedPorts();
40+
static BT::KeyValueVector metadata();
41+
42+
private:
43+
BT::NodeStatus onStart() override;
44+
BT::NodeStatus onRunning() override;
45+
void onHalted() override;
46+
47+
rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odom_subscriber_;
48+
std::shared_mutex odom_mutex_;
49+
std::optional<nav_msgs::msg::Odometry> current_odometry_;
50+
std::string odom_topic_name_;
51+
};
52+
} // namespace experimental_behaviors
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2026 PickNik Inc.
2+
// All rights reserved.
3+
//
4+
// Unauthorized copying of this code base via any medium is strictly prohibited.
5+
// Proprietary and confidential.
6+
7+
#pragma once
8+
9+
#include <optional>
10+
#include <shared_mutex>
11+
#include <string>
12+
13+
#include <behaviortree_cpp/action_node.h>
14+
#include <nav_msgs/msg/odometry.hpp>
15+
#include <rclcpp/subscription.hpp>
16+
17+
#include <moveit_pro_behavior_interface/behavior_context.hpp>
18+
#include <moveit_pro_behavior_interface/shared_resources_node.hpp>
19+
20+
namespace experimental_behaviors
21+
{
22+
/**
23+
* @brief Long-lived odometry subscriber that publishes the latest received message every tick.
24+
*
25+
* @details Like the core `GetOdom` behavior in shape — `StatefulActionNode`, returns RUNNING
26+
* forever, halted by the parent on parallel completion — but does NOT write anything
27+
* to its output ports until at least one real message has arrived. This avoids a
28+
* cold-start race where downstream consumers (e.g. CalculatePoseOffset) see a
29+
* default-constructed Odometry with an empty `header.frame_id` on early ticks and
30+
* fail the tree.
31+
*
32+
* Intended to be used as a producer in a Parallel, with the consumer reading
33+
* `odometry_pose` from the blackboard. Pair with a seed (e.g. ConvertOdomToPoseStamped
34+
* on a fresh GetOdomInstance snapshot) so the consumer has a valid value to read on
35+
* its first tick before this behavior has received anything.
36+
*
37+
* | Data Port Name | Port Type | Object Type |
38+
* | -------------------- | --------- | ------------------------------- |
39+
* | odometry_topic_name | input | std::string |
40+
* | subscribed_odometry | output | nav_msgs::msg::Odometry |
41+
* | odometry_pose | output | geometry_msgs::msg::PoseStamped |
42+
*/
43+
class GetOdomLatest final : public moveit_pro::behaviors::SharedResourcesNode<BT::StatefulActionNode>
44+
{
45+
public:
46+
GetOdomLatest(const std::string& name, const BT::NodeConfiguration& config,
47+
const std::shared_ptr<moveit_pro::behaviors::BehaviorContext>& shared_resources);
48+
49+
static BT::PortsList providedPorts();
50+
static BT::KeyValueVector metadata();
51+
52+
BT::NodeStatus onStart() override;
53+
BT::NodeStatus onRunning() override;
54+
void onHalted() override;
55+
56+
private:
57+
std::shared_ptr<rclcpp::Subscription<nav_msgs::msg::Odometry>> odometry_subscriber_;
58+
std::shared_mutex odometry_mutex_;
59+
std::optional<nav_msgs::msg::Odometry> current_odometry_;
60+
};
61+
} // namespace experimental_behaviors
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2026 PickNik Inc.
2+
// All rights reserved.
3+
//
4+
// Unauthorized copying of this code base via any medium is strictly prohibited.
5+
// Proprietary and confidential.
6+
7+
#pragma once
8+
9+
#include <utility>
10+
#include <vector>
11+
12+
#include <behaviortree_cpp/action_node.h>
13+
#include <geometry_msgs/msg/pose.hpp>
14+
#include <moveit_pro_behavior_interface/behavior_context.hpp>
15+
#include <moveit_pro_behavior_interface/shared_resources_node.hpp>
16+
17+
namespace experimental_behaviors
18+
{
19+
namespace detail
20+
{
21+
/// @brief Decomposes a Pose into translation [x, y, z] and quaternion [x, y, z, w] vectors.
22+
/// Pure function — no I/O, no failure modes. Companion to composePose() in vectors_to_pose.hpp.
23+
[[nodiscard]] std::pair<std::vector<double>, std::vector<double>> decomposePose(const geometry_msgs::msg::Pose& pose);
24+
} // namespace detail
25+
26+
/**
27+
* @brief Decomposes a PoseStamped into translation_xyz and quaternion_xyzw vectors compatible
28+
* with the std::vector<double> ports used by core behaviors like TransformPose.
29+
*
30+
* @details Companion to VectorsToPose.
31+
*
32+
* | Data Port Name | Port Type | Object Type |
33+
* | ---------------- | --------- | ------------------------------- |
34+
* | pose_stamped | input | geometry_msgs::msg::PoseStamped |
35+
* | translation_xyz | output | std::vector<double> (size 3) |
36+
* | quaternion_xyzw | output | std::vector<double> (size 4) |
37+
*/
38+
class PoseToVectors final : public moveit_pro::behaviors::SharedResourcesNode<BT::SyncActionNode>
39+
{
40+
public:
41+
PoseToVectors(const std::string& name, const BT::NodeConfiguration& config,
42+
const std::shared_ptr<moveit_pro::behaviors::BehaviorContext>& shared_resources);
43+
44+
static BT::PortsList providedPorts();
45+
static BT::KeyValueVector metadata();
46+
47+
BT::NodeStatus tick() override;
48+
};
49+
} // namespace experimental_behaviors
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2026 PickNik Inc.
2+
// All rights reserved.
3+
//
4+
// Unauthorized copying of this code base via any medium is strictly prohibited.
5+
// Proprietary and confidential.
6+
7+
#pragma once
8+
9+
#include <memory>
10+
#include <string>
11+
12+
#include <behaviortree_cpp/action_node.h>
13+
#include <rclcpp/publisher.hpp>
14+
#include <std_msgs/msg/bool.hpp>
15+
16+
#include <moveit_pro_behavior_interface/behavior_context.hpp>
17+
#include <moveit_pro_behavior_interface/shared_resources_node.hpp>
18+
19+
namespace experimental_behaviors
20+
{
21+
/**
22+
* @brief Publishes a `std_msgs/Bool` message to the given topic.
23+
*
24+
* @details Mirror of the existing core `PublishString` and `PublishEmpty` behaviors, for
25+
* the `Bool` message type. The publisher is created lazily on the first tick that
26+
* uses a given topic, and re-created if the topic name changes between ticks.
27+
*
28+
* | Data Port Name | Port Type | Object Type |
29+
* | -------------- | --------- | ------------- |
30+
* | topic_name | input | std::string |
31+
* | value | input | bool |
32+
*/
33+
class PublishBool final : public moveit_pro::behaviors::SharedResourcesNode<BT::SyncActionNode>
34+
{
35+
public:
36+
PublishBool(const std::string& name, const BT::NodeConfiguration& config,
37+
const std::shared_ptr<moveit_pro::behaviors::BehaviorContext>& shared_resources);
38+
39+
static BT::PortsList providedPorts();
40+
static BT::KeyValueVector metadata();
41+
42+
BT::NodeStatus tick() override;
43+
44+
private:
45+
std::shared_ptr<rclcpp::Publisher<std_msgs::msg::Bool>> publisher_;
46+
std::string current_topic_name_;
47+
};
48+
} // namespace experimental_behaviors

0 commit comments

Comments
 (0)