-
Notifications
You must be signed in to change notification settings - Fork 1
feat(blackboard): add GetBlackboardByKey and SetBlackboardByKey for dynamic-key access #11
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
Merged
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
cb5e064
feat(blackboard): add GetBlackboardByKey behavior for dynamic-key reads
WillYingling 1234904
fix(blackboard): use canonical setOutput and clarify docs
WillYingling 23278c3
feat(blackboard): add SetBlackboardByKey for dynamic-key writes
WillYingling 6101af1
ci(workflow): bump reusable workflow to v0.0.7
WillYingling aeaea76
docs(blackboard): enumerate all FAILURE conditions in GetBlackboardBy…
WillYingling 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
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
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
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,48 @@ | ||
| // Copyright 2026 PickNik Inc. | ||
| // All rights reserved. | ||
| // | ||
| // Unauthorized copying of this code base via any medium is strictly prohibited. | ||
| // Proprietary and confidential. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <behaviortree_cpp/action_node.h> | ||
| #include <moveit_pro_behavior_interface/behavior_context.hpp> | ||
| #include <moveit_pro_behavior_interface/shared_resources_node.hpp> | ||
|
|
||
| namespace experimental_behaviors | ||
| { | ||
| /** | ||
| * @brief Reads a blackboard entry whose key is determined dynamically at tick | ||
| * time from an input port, and copies its value to an output port. | ||
| * | ||
| * @details | ||
| * This is the read-side complement of BT.CPP's native SetBlackboard, which | ||
| * already supports a dynamic `output_key` port but has no corresponding | ||
| * read-by-dynamic-key primitive. | ||
| * | ||
| * | Data Port Name | Port Type | Object Type | | ||
| * | -------------- | --------- | -------------------- | | ||
| * | key | input | std::string | | ||
| * | value | output | any (AnyTypeAllowed) | | ||
| * | ||
| * The `key` port accepts both literal strings and root-scope references | ||
| * prefixed with `@`. Returns FAILURE if the key is missing or empty. | ||
| * | ||
| * An entry that exists but holds an empty value is also treated as a cache | ||
| * miss and reported as FAILURE. Callers that need to distinguish "declared | ||
| * but uninitialized" from "never declared" should populate the entry with | ||
| * an explicit sentinel before reading. | ||
| */ | ||
| class GetBlackboardByKey final : public moveit_pro::behaviors::SharedResourcesNode<BT::SyncActionNode> | ||
| { | ||
| public: | ||
| GetBlackboardByKey(const std::string& name, const BT::NodeConfiguration& config, | ||
| const std::shared_ptr<moveit_pro::behaviors::BehaviorContext>& shared_resources); | ||
|
|
||
| static BT::PortsList providedPorts(); | ||
| static BT::KeyValueVector metadata(); | ||
|
|
||
| BT::NodeStatus tick() override; | ||
| }; | ||
| } // namespace experimental_behaviors |
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,55 @@ | ||
| // Copyright 2026 PickNik Inc. | ||
| // All rights reserved. | ||
| // | ||
| // Unauthorized copying of this code base via any medium is strictly prohibited. | ||
| // Proprietary and confidential. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <behaviortree_cpp/action_node.h> | ||
| #include <moveit_pro_behavior_interface/behavior_context.hpp> | ||
| #include <moveit_pro_behavior_interface/shared_resources_node.hpp> | ||
|
|
||
| namespace experimental_behaviors | ||
| { | ||
| /** | ||
| * @brief Writes the value of an input port to a blackboard entry whose key is | ||
| * determined dynamically at tick time. | ||
| * | ||
| * @details | ||
| * Write-side complement to GetBlackboardByKey and a drop-in replacement for | ||
| * BT.CPP's native SetBlackboard when the source value is carried on a port | ||
| * typed as `AnyTypeAllowed` (e.g. a value produced by a Script `:=` | ||
| * assignment). | ||
| * | ||
| * BT.CPP's SetBlackboard runs a string-conversion step whenever the | ||
| * destination entry is not strictly `std::string` and the source value is a | ||
| * string. For `AnyTypeAllowed` destinations there is no registered converter, | ||
| * so the conversion produces an empty Any and silently clobbers the | ||
| * destination — see set_blackboard_node.h and basic_types.cpp::parseString. | ||
| * | ||
| * This behavior copies the source Any directly, preserving both type and | ||
| * value, which matches the semantics callers expect when caching arbitrary | ||
| * subtree outputs under runtime-computed keys. | ||
| * | ||
| * | Data Port Name | Port Type | Object Type | | ||
| * | -------------- | --------- | -------------------- | | ||
| * | key | input | std::string | | ||
| * | value | input | any (AnyTypeAllowed) | | ||
| * | ||
| * The `key` port accepts both literal strings and root-scope references | ||
| * prefixed with `@`. Returns FAILURE if `key` is missing/empty or if the | ||
| * `value` port refers to a blackboard entry that does not exist. | ||
| */ | ||
| class SetBlackboardByKey final : public moveit_pro::behaviors::SharedResourcesNode<BT::SyncActionNode> | ||
| { | ||
| public: | ||
| SetBlackboardByKey(const std::string& name, const BT::NodeConfiguration& config, | ||
| const std::shared_ptr<moveit_pro::behaviors::BehaviorContext>& shared_resources); | ||
|
|
||
| static BT::PortsList providedPorts(); | ||
| static BT::KeyValueVector metadata(); | ||
|
|
||
| BT::NodeStatus tick() override; | ||
| }; | ||
| } // namespace experimental_behaviors |
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,87 @@ | ||
| // Copyright 2026 PickNik Inc. | ||
| // All rights reserved. | ||
| // | ||
| // Unauthorized copying of this code base via any medium is strictly prohibited. | ||
| // Proprietary and confidential. | ||
|
|
||
| #include <experimental_behaviors/get_blackboard_by_key.hpp> | ||
|
|
||
| #include <moveit_pro_behavior_interface/metadata_fields.hpp> | ||
|
|
||
| namespace | ||
| { | ||
| inline constexpr auto kDescriptionGetBlackboardByKey = R"( | ||
| <p> | ||
| Reads a blackboard entry whose key is computed at runtime (typically via a Script node | ||
| building a string from other blackboard variables) and copies the entry's value to an | ||
| output port. The key may be prefixed with '@' to address the root blackboard. | ||
| </p> | ||
| <p> | ||
| Returns FAILURE if the key refers to a missing blackboard entry. This is the read-side | ||
| complement to BT.CPP's native SetBlackboard. | ||
| </p> | ||
| )"; | ||
|
|
||
| constexpr auto kPortIDKey = "key"; | ||
| constexpr auto kPortIDValue = "value"; | ||
| } // namespace | ||
|
|
||
| namespace experimental_behaviors | ||
| { | ||
| GetBlackboardByKey::GetBlackboardByKey(const std::string& name, const BT::NodeConfiguration& config, | ||
| const std::shared_ptr<moveit_pro::behaviors::BehaviorContext>& shared_resources) | ||
| : moveit_pro::behaviors::SharedResourcesNode<BT::SyncActionNode>(name, config, shared_resources) | ||
| { | ||
| } | ||
|
|
||
| BT::PortsList GetBlackboardByKey::providedPorts() | ||
| { | ||
| return { BT::InputPort<std::string>(kPortIDKey, | ||
| "Blackboard key to read from. May be constructed dynamically via Script. " | ||
| "Prefix with '@' for root-scope access."), | ||
| BT::OutputPort(kPortIDValue, "Value read from the blackboard entry referenced by `key`.") }; | ||
| } | ||
|
|
||
| BT::KeyValueVector GetBlackboardByKey::metadata() | ||
| { | ||
| return { { moveit_pro::behaviors::kSubcategoryMetadataKey, "Blackboard" }, | ||
| { moveit_pro::behaviors::kDescriptionMetadataKey, kDescriptionGetBlackboardByKey } }; | ||
| } | ||
|
|
||
| BT::NodeStatus GetBlackboardByKey::tick() | ||
| { | ||
| std::string key; | ||
| if (!getInput<std::string>(kPortIDKey, key) || key.empty()) | ||
| { | ||
| shared_resources_->logger->publishFailureMessage(name(), "Missing or empty input port [key]"); | ||
| return BT::NodeStatus::FAILURE; | ||
| } | ||
|
|
||
| auto src_entry = config().blackboard->getEntry(key); | ||
| if (!src_entry) | ||
| { | ||
| shared_resources_->logger->publishFailureMessage(name(), | ||
| "Blackboard entry '" + key + "' does not exist (cache miss)."); | ||
| return BT::NodeStatus::FAILURE; | ||
| } | ||
|
|
||
| if (src_entry->value.empty()) | ||
| { | ||
| shared_resources_->logger->publishFailureMessage(name(), | ||
| "Blackboard entry '" + key + "' exists but holds no value."); | ||
| return BT::NodeStatus::FAILURE; | ||
| } | ||
|
|
||
| // Pass the stored BT::Any through the canonical setOutput path so SubTree | ||
| // auto-remapping and blackboard-pointer validation are handled consistently | ||
| // with the rest of BT.CPP (see tree_node.h setOutput). | ||
| const auto result = setOutput<BT::Any>(kPortIDValue, src_entry->value); | ||
| if (!result) | ||
| { | ||
| shared_resources_->logger->publishFailureMessage(name(), result.error()); | ||
| return BT::NodeStatus::FAILURE; | ||
| } | ||
|
|
||
| return BT::NodeStatus::SUCCESS; | ||
| } | ||
| } // namespace experimental_behaviors | ||
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
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,122 @@ | ||
| // Copyright 2026 PickNik Inc. | ||
| // All rights reserved. | ||
| // | ||
| // Unauthorized copying of this code base via any medium is strictly prohibited. | ||
| // Proprietary and confidential. | ||
|
|
||
| #include <experimental_behaviors/set_blackboard_by_key.hpp> | ||
|
|
||
| #include <moveit_pro_behavior_interface/metadata_fields.hpp> | ||
|
|
||
| namespace | ||
| { | ||
| inline constexpr auto kDescriptionSetBlackboardByKey = R"( | ||
| <p> | ||
| Writes a source port's value to a blackboard entry whose key is computed at runtime | ||
| (typically via a Script node building a string from other blackboard variables). | ||
| The key may be prefixed with '@' to address the root blackboard. | ||
| </p> | ||
| <p> | ||
| Use this instead of BT.CPP's native SetBlackboard when the source is an Any-typed | ||
| port (e.g. produced by a Script `:=` assignment): SetBlackboard runs a | ||
| string-to-destination-type conversion that silently empties the value when the | ||
| destination type is AnyTypeAllowed and no converter is registered. This behavior | ||
| copies the source Any directly. | ||
| </p> | ||
| )"; | ||
|
|
||
| constexpr auto kPortIDKey = "key"; | ||
| constexpr auto kPortIDValue = "value"; | ||
| } // namespace | ||
|
|
||
| namespace experimental_behaviors | ||
| { | ||
| SetBlackboardByKey::SetBlackboardByKey(const std::string& name, const BT::NodeConfiguration& config, | ||
| const std::shared_ptr<moveit_pro::behaviors::BehaviorContext>& shared_resources) | ||
| : moveit_pro::behaviors::SharedResourcesNode<BT::SyncActionNode>(name, config, shared_resources) | ||
| { | ||
| } | ||
|
|
||
| BT::PortsList SetBlackboardByKey::providedPorts() | ||
| { | ||
| return { BT::InputPort<std::string>(kPortIDKey, | ||
| "Blackboard key to write to. May be constructed dynamically via Script. " | ||
| "Prefix with '@' for root-scope access."), | ||
| BT::InputPort(kPortIDValue, "Source value. Accepts a blackboard pointer ({some_port}) whose " | ||
| "Any contents are copied verbatim, or a literal string.") }; | ||
| } | ||
|
|
||
| BT::KeyValueVector SetBlackboardByKey::metadata() | ||
| { | ||
| return { { moveit_pro::behaviors::kSubcategoryMetadataKey, "Blackboard" }, | ||
| { moveit_pro::behaviors::kDescriptionMetadataKey, kDescriptionSetBlackboardByKey } }; | ||
| } | ||
|
|
||
| BT::NodeStatus SetBlackboardByKey::tick() | ||
| { | ||
| std::string key; | ||
| if (!getInput<std::string>(kPortIDKey, key) || key.empty()) | ||
| { | ||
| shared_resources_->logger->publishFailureMessage(name(), "Missing or empty input port [key]"); | ||
| return BT::NodeStatus::FAILURE; | ||
| } | ||
|
|
||
| // Read the raw port expression from config rather than going through | ||
| // getInput<std::string>, which would stringify whatever the source port | ||
| // holds. We need the untouched Any so trajectories, vectors, etc. survive | ||
| // the copy. | ||
| const auto value_it = config().input_ports.find(kPortIDValue); | ||
| if (value_it == config().input_ports.end()) | ||
| { | ||
| shared_resources_->logger->publishFailureMessage(name(), "Missing input port [value]"); | ||
| return BT::NodeStatus::FAILURE; | ||
| } | ||
| const std::string& value_expr = value_it->second; | ||
|
|
||
| BT::StringView stripped_key; | ||
| BT::Any out_value; | ||
| BT::TypeInfo src_info; | ||
| if (BT::TreeNode::isBlackboardPointer(value_expr, &stripped_key)) | ||
| { | ||
| const auto input_key = std::string(stripped_key); | ||
| auto src_entry = config().blackboard->getEntry(input_key); | ||
| if (!src_entry) | ||
| { | ||
| shared_resources_->logger->publishFailureMessage( | ||
| name(), "Source port '" + input_key + "' for [value] does not reference an existing blackboard entry."); | ||
| return BT::NodeStatus::FAILURE; | ||
| } | ||
| out_value = src_entry->value; | ||
| src_info = src_entry->info; | ||
| } | ||
| else | ||
| { | ||
| out_value = BT::Any(value_expr); | ||
| src_info = BT::TypeInfo::Create<std::string>(); | ||
| } | ||
|
|
||
| // Ensure the destination entry exists before writing. Create it with the | ||
| // source entry's TypeInfo so downstream consumers that inspect entry->info | ||
| // see a consistent type (e.g. AnyTypeAllowed vs std::string vs trajectory). | ||
| auto dst_entry = config().blackboard->getEntry(key); | ||
| if (!dst_entry) | ||
| { | ||
| config().blackboard->createEntry(key, src_info); | ||
| dst_entry = config().blackboard->getEntry(key); | ||
| if (!dst_entry) | ||
| { | ||
| shared_resources_->logger->publishFailureMessage(name(), "Failed to create blackboard entry '" + key + "'"); | ||
| return BT::NodeStatus::FAILURE; | ||
| } | ||
| } | ||
|
|
||
| { | ||
| std::scoped_lock lock(dst_entry->entry_mutex); | ||
| dst_entry->value = out_value; | ||
| dst_entry->sequence_id++; | ||
| dst_entry->stamp = std::chrono::steady_clock::now().time_since_epoch(); | ||
| } | ||
|
|
||
| return BT::NodeStatus::SUCCESS; | ||
| } | ||
| } // namespace experimental_behaviors |
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
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.