BehaviorTree/BehaviorTree.CPP

Error sending information via a blackboard port on an action node

Closed this issue · 5 comments

Hello everywhere,

I have a question. I want to send information from a node to the next node of my BT, but I can't send this information from the onStart or onRunning functions, but if I send the information at node initialization I can.

For example: This way works well.

ComputeShiftRelativeDistancesActionNode::ComputeShiftRelativeDistancesActionNode(const std::string& name, const BT::NodeConfiguration& config)
    : BT::StatefulActionNode(name, config), 
{

    need_align_base = true;
    // Set the boolean output variable on the blackboard
    config.blackboard->set("need_align_base", need_align_base);

}

BT::PortsList ComputeShiftRelativeDistancesActionNode::providedPorts()
{
    return {
        BT::OutputPort<bool>("need_align_base"),
    };
}

However, I need to set the state of this variable in other functions and I'm doing it this way, except that the variable doesn't update on the next node.

BT::NodeStatus ComputeShiftRelativeDistancesActionNode::onRunning() 
    {

            need_align_base = true;
            // vision_tag action failed, need to align base
            config().blackboard->set("need_align_base", need_align_base);
}

What could I be doing wrong? I'm working in ROS.

Thanks in advance.

What you are doing wrong is following the antipattern that we saw in Nav2 and ignore all the official tutorials I wrote 🥲

Pinging @SteveMacenski : this is the problem I mentioned a few times, people bypassing the way things should be done and then being surprised when something doesn't work.

@RodrigoCodeBernardo :

  1. are you using Subtrees? That may explain the issue.
  2. Use setInput and setOutput as explained in the tutorials. Never use config().blackboard unless it is for unit testing or debugging.
  3. Send me a FULL example I can run, please, based on what you are showing here, I can not know what you the problem is.

Hi, thanks for your reply,

I work with ROS and not ROS2, I've read some Nav2 documentation but not much because I don't work with the framework, maybe soon.

What I do is create my behavior tree based on the output of a pddl or, for example, an optimization algorithm (e.g. distribution route in an internal logistics environment).

The point here is that when I form the behavior tree, I pass the parameters, for example from the PDDL, such as the preconditions and effects etc. And everything works fine here. The point is that I then want to send data from one node to another, for example. I think the problem is because the whole tree is already running.

The example config().blackboard->set("need_align_base", need_align_base);, it was just a test.

` /********************************************
* *
* Register the action nodes *
* *
********************************************/

factory.registerNodeType<DockActionNode>("Dock");
factory.registerNodeType<UndockActionNode>("Undock");
factory.registerNodeType<MoveBaseActionNode>("MoveBase");
factory.registerNodeType<WaitLoadingActionNode>("WaitLoading");
factory.registerNodeType<WaitUnloadActionNode>("WaitUnload");


/********************************************
*                                           *
*      Register the condition nodes         *
*             Dont change this              *
*                                           *
********************************************/

factory.registerNodeType<SimpleCondition>("Condition");

/********************************************
*                                           *
*          Initialize BehaviorTree          *
*                                           *
********************************************/


// Create the ReactiveSequence in order to put a condition to check the validity of the plan
BT::ReactiveSequence* reactiveSeq = new BT::ReactiveSequence("reactive_sequence");

BT::TreeNode* valid_plan = new SimpleCondition("Condition");  
reactiveSeq->addChild(valid_plan);

// Create the sequence node where the actions that the agent must execute sequentially as defined in the pddl will be added
BT::SequenceNode* sequenceNode = new BT::SequenceNode("sequence");


/********************************************
*                                           *
*   Construct BehaviorTree based on pddl    *
*      and actions of robotic agent         *
********************************************/


BT::NodeConfiguration nodeConfig;
nodeConfig.blackboard = BT::Blackboard::create();
    
ActionsParser::Action action;

 // Print the actions in the original order
  for (const auto& action : actionList) {

    std::cout << "Action: " << action.action << std::endl;

    // Create the corresponding action node based on the action name
    if (action.action == "dock")
    {

        nodeConfig.blackboard->set("msec", 1000);
        nodeConfig.blackboard->set("init_location", action.parameters[0]);
        nodeConfig.blackboard->set("agent_", action.parameters[1]);
        BT::TreeNode* Dock = new DockActionNode("Dock", nodeConfig);
        sequenceNode->addChild(Dock);        
    }
    else if (action.action == "undock")
    {
        nodeConfig.blackboard->set("msec", 1000);
        nodeConfig.blackboard->set("init_location", action.parameters[0]);
        nodeConfig.blackboard->set("agent_", action.parameters[1]);
        BT::TreeNode* Undock = new UndockActionNode("Undock", nodeConfig);
        sequenceNode->addChild(Undock);     

    }
    else if (action.action== "move_base")
    {   

        nodeConfig.blackboard->set("init_location", action.parameters[0]);
        nodeConfig.blackboard->set("goal_location", action.parameters[1]);
        nodeConfig.blackboard->set("agent_", action.parameters[2]);
        BT::TreeNode* MoveBaseNode = new MoveBaseActionNode("MoveBase", nodeConfig);
        sequenceNode->addChild(MoveBaseNode); 

    } 
    else if (action.action == "wait_until_loading")
    {
        
        nodeConfig.blackboard->set("location_loading", action.parameters[0]);
        nodeConfig.blackboard->set("object_loading", action.parameters[1]);
        nodeConfig.blackboard->set("agent_", action.parameters[2]); 
        nodeConfig.blackboard->set("msec", 1000);          
        BT::TreeNode* WaitLoading= new WaitLoadingActionNode("WaitLoading", nodeConfig);
        sequenceNode->addChild(WaitLoading);
    } 
    else if (action.action == "wait_until_unload")
    {
        nodeConfig.blackboard->set("location_unload", action.parameters[0]);
        nodeConfig.blackboard->set("object_unload", action.parameters[1]);
        nodeConfig.blackboard->set("agent_", action.parameters[2]); 
        nodeConfig.blackboard->set("msec", 1000);          
        BT::TreeNode* WaitUnload = new WaitUnloadActionNode("WaitUnload", nodeConfig);
        sequenceNode->addChild(WaitUnload);
    }
    else
    {
        ROS_ERROR("Unknown action:  %s", action.action.c_str());
    }
}

reactiveSeq->addChild(sequenceNode);


 ROS_INFO("Execute Behavior Tree");

while (reactiveSeq->executeTick() == BT::NodeStatus::RUNNING)
{   
    ros::spinOnce();
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
}`

Thanks for help

i-am-so-confused

You are basically avoid using everything I recommend 😆

  1. Tree should be created using the factory, not programmatically, otherwise there are a number of features that will not work.
  2. Static parameters should be passed in the constructor as explained in Tutorial 8
  3. Do not sleep like this. In your while loop, use Tree::sleep

So, I understand that point 1 could be difficult if you are using PDDL

Still, you are not providing an example that I can compile and run.
I can't spend hours trying to understand your problem, without all the information; I don't even understand in which nodes need_align_base is set/get.

Unless you send a simple hello world example in a ZIP file, that reproduces the issues, without necessarily use ROS or PDDL, I can not help you.

At a first sight, despite many sins being committed, your code should (accidentally) work.

For instance, take in mind that this is broken:

ComputeShiftRelativeDistancesActionNode::ComputeShiftRelativeDistancesActionNode(const std::string& name, const BT::NodeConfiguration& config)
    : BT::StatefulActionNode(name, config), 
{

    need_align_base = true;
    // Set the boolean output variable on the blackboard
    config.blackboard->set("need_align_base", need_align_base);

}

BT::PortsList ComputeShiftRelativeDistancesActionNode::providedPorts()
{
    return {
        BT::OutputPort<bool>("need_align_base"),
    };
}

There is no relationship between the "need_align_base" in the config.blackboard->set and the one in providedPorts().
Just because they have the same name, it doesn't mean that they are pointing to the same thing.

See tutorial 2

There IS a solution to this that I implemented the last week, but if you are not understanding why your code is wrong, pointing you to the solution will have little use!

Hi,

Correct me if I'm wrong,

In point 1 the BT should be created from an xml file? Because I use the "factory" library in the implementation. Only programmatically.

Points 2 and 3 are relatively simple to change. I'll change it so that it's correct with the documentation. However, it is working as desired.

My question, which was the initial one, is how do I send a parameter from one node to another that isn't static? I couldn't find the answer in tutorials.

Sorry for the mistakes :/

  1. The static providedPort method is only used by the factory
  2. The factory has NO effect, unless you create a tree from XML.
  3. Ports (getInput / setOutput) will probably not work correctly, unless the factory is used.

As a consequence, not using the factory `XML limits some of the functionalities.

Said that:

  • if all your nodes are created programmatically
  • if all the access to the blackboard is done with config.blackboard
  • if there are NO subtrees, only a single tree.

Then, it should work, even if this is not how the library is intended to use.

My question, which was the initial one, is how do I send a parameter from one node to another that isn't static? I couldn't find the answer in tutorials.

  1. If you want to pass information that changes at run-time from one node to another use ports (preferably) or blackboard (discouraged)
  2. If the information is passed to the constructor of the node and should is not updated ever again, follow Tutorial 8

What you show in the initial message of this thread SHOULD work and the only way I can understand why it doesn't is with code that I can EASILY compile and run (this is the 3rd time I am asking for it).

I am closing this issue. Once you submit the information I asked, I will be happy to help you