All right, so let's say you switch to retinab and now you need to implement your behavior
please.
Probably below is going to present an option for that.
Thank you very much.
All right, yeah.
Thank you for this nice transition.
So I'm Robin.
I'm from the Technical University in Downset.
And I work mostly with aerial robotics.
And for a few years ago, I started working with my master thesis,
and I tried to develop an automated mission management tool
for using it with PX4.
And I tried to use behavior trees for this,
and this is how this project is developed,
because there was no real integration with Rust
through that that allowed me to use it in PX4 as I wanted.
So yeah, I don't have the time to explain to you
what behavior trees are, but I was just giving you a small introduction.
So why do we need something like behavior trees,
or state machines?
It basically gives us the possibility for intelligent robotics
to implement decision-making logic and reactive mechanisms.
And usually behavior trees, they promise,
composability, modularity, and this reactivity that I spoke of.
And it doesn't let us drown in this spaghetti logic
that state machines usually do,
because in state machines each state has a transition
to another and the more states, the more suggestions you have.
So this can get very complicated once your project scales.
So I looked into the ecosystem, and I found that there's lots of frameworks
or lots of implementations of behavior trees,
and also some integrations into Rust.
And I found that the Python integrations are usually a bit more usable
than the C++ ones.
But I wanted to have the deterministic timing of C++
and also the robustness in resource-constrained environments
since I was working with drones.
And this could become an issue when the future.
So I decided to do this in C++,
and there obviously there was behavior TCPP,
and the video by now you should be very flattered
since it's another default call at dev talk that mentions your work.
And I tried to find different ways of integrating that into Rust
and have two, for example, or behavior tree Rust.
Since in my opinion, it wasn't quite fitting my needs to set us
by me, basically.
So I set basically three design goals.
I wanted to develop a domain-agnostic development framework
for behavior trees that allows us to do behavior-based control
and automated planning, without depending on
what we have to or something like this, for example.
I also wanted to reduce the configuration overhead,
that usually comes with C++ projects,
and to lower the barrier to entry.
And also I wanted to focus really on modularity and reusability
since I wanted to be a generalized framework.
Okay, so with this motivation out of the way,
how does it work now in detail?
So behavior trees would usually use in a robotics
architecture like this, where you have your user domain with your notes,
or I call it skills here.
For example, this could be the easy-nav framework,
and then you would have the behavior trees for orchestrating your skills,
your functions of your robots,
and behavior trees therefore represent policies or plans,
and they use functions of the robot through clients
or decolon notes with behavior trees.
So I think for getting more into detail,
we need like a little examples.
So I created a little robot simulation
that we're going to follow along here.
We're going to use for explaining the behavior tree framework,
and this robot exposes two interfaces.
So one sensory interface for reading the position of the hand,
the current position, and a actuator basically.
So we can send velocity commands to the hand,
so it moves to the left or to the right.
And for creating a wave behavior now,
what we do is to create the behavior,
or the build request for the executor for the behavior.
And for behavior tree CPP, it looks something like this,
so we have an XML schema,
and on the right, you see the visual representation of it.
And the behavior in this case, it's pretty simple.
We want to move to an end position for doing the wave,
and maybe repeat it for once or twice,
and then we want to move back to the center.
So this is the first example I'm going to show you,
and I'm going to show you how to implement it with RAPMS.
So first of all, we need notes.
Since all of the behavior trees,
they are implemented or they use these notes
that call the robot skills,
and there's potentially lots of them.
So we need a way to organize them.
So we have a node manifest in RAPMS.
It's basically just YAML configurations,
where you specify a plugin,
and then some other configurations.
And for example, for this behavior,
we have four notes.
So we have move for the site,
stop movement, and hand reach end position, and hand is centered.
And you might have noticed that we reused
this existing implementation of a velocity publisher,
in this case,
but by just configuring different ports,
different default values for the velocity port,
we give it a different semantic meaning.
So we have two distinct notes,
but we're reusing the C++ implementation,
which allows us to really write as little code
as we really need to write,
and not just change a little thing,
and we just have the same code and repeat ourselves.
And this is also the case for the position interface.
So with these notes,
we need to go into our C-Mac list of our package,
and it's a C++ project.
We configure a C++ project with C-Mac,
and it looks something like this with this framework.
So first of all, we need to register our plugins,
in a C-Mac all like that.
Now, remember, we have two plugins,
and in the second code block,
you see that we link these plugins
with the node manifests that I've showed you before.
So this is where we do the mapping between
the registration names of the notes,
and the implementation that we want to use
when we're calling these notes.
So in this integration into C-Mac,
we get some benefits out of it.
So first of all,
we register our configurations,
workspace-wise,
so we can reuse it in the future
in our distributed workspaces,
and we can do some compile-time validation checks
that helps avoiding runtime errors
that are usually harder to debug.
And also it allows us to generate code automatically,
for example the XML file for the node models
that you usually use with visual editors,
like Brut,
and also in this very specific to our APMS,
we generate a C++ header
that we use with our together with our builder API.
And I'm going to show you what exactly this is
in the coming slides.
So now we have the node manifest,
we have the builder requests,
so we're basically able to deploy the behavior
by now.
And for this we call another macro in the configuration file,
where we just specify our build request,
that is basically an XML file,
and we give it an alias,
and also we link to this behavior,
or to this definition of the behavior,
we link a specific node manifest,
and we can do this both for doing the waving to the left,
and the waving to the right.
And don't need to redefine our behavior or something like this.
We just switch out the nodes,
and then we have a different behavior.
So this is what's denoted by this side variable here.
So we would do this called twice for once left and once right.
And then also we basically with this configuration,
we're able to just run this behavior
with our unified CLI tool for running behaviors,
and then we define the identity of the behavior we want to run,
and we might also just define some blackboard values
to give some variables to the behavior.
And then it looks something like this.
So we call this this command here,
and the hand moves to the left,
repeats its twice,
and the same thing happens for doing it on the left.
And then it looks something like this.
So we call this this command here,
and the hand moves to the left,
repeats its twice,
and the same thing happens for doing it to the right,
and we just need to change a little namespace here.
And we have two different, two very distinct behaviors,
but using minimal code,
minimal configuration,
and minimal XML definition.
Because we maximize the reusability through this framework.
Okay, so by now I've shown you how you work with this framework,
using the existing practices that you know,
when you've tried behavior to CPP.
But let me tell you, there's more to this framework
than just to plain behavior trees.
So it's just defining the behavior tree as an XML file.
There's also the possibility to customize your behavior generation pipeline.
And this basically needs us to understand what a behavior is.
So in our IPMS, we're defining a behavior as a collection of components.
So first of all, the most important component probably is a build request.
So this is the XML definition I've showed you before.
And then we have a build handler,
and this is a very innovative approach, I'd say.
So we are just going to find this component as the algorithm that is responsible for creating
the run out of the executable behavior tree out of the build request.
And this can be anything like depending on your use case,
you just write your plugin,
define a format for your build requests,
and you create a behavior tree that fits your needs.
So the idea of the build handler is interesting,
so but why do we need it?
Let's say we want to move the hand now twice to the left, twice to the right,
and then once left, once right.
So very specific behavior that we couldn't do with our current definition,
because we would only wave left or right.
And now you might say, obviously we can just rewrite our behavior,
we could just quickly sketch a new behavior,
it is not a big thing,
but that's not really the cool thing to do.
We want to automate this because potentially we want to kind of also integrate
some variables for this build mechanism,
and this doesn't solve probably potentially very beneficial
if we automate this because of the future,
because of scalability basically.
And for this behavior now,
I want to show you how to solve this by creating a new build handler.
So we create also a new build request
that specifies only the very interesting parts of the request.
So only if you want to move left or right and when.
So this is basically a sequence of characters with L&Rs.
And this is what for what build build handler,
and this is what build handler like this could look like.
And you see here we just import or create a new behavior tree
from our existing behaviors that we have in our workspace already.
So in this case we import the behavior tree for moving to the end position,
and we do this for both moving to the left and to the right,
and then rename it, we rename it,
and this is all happening through the tree build API.
And in the second code blog we parse the build request
that we give the build handler,
and it generates the behavior tree,
the corresponding behavior tree,
automatically by just executing this for loop,
and then inserting the subtries in the sequence
where we're supposed to be.
And then we're returning this tree and it can be executed by the executor.
So this is the implementation,
obviously we need to register this again with the configuration in CMake.
So we call another macro just to register the build handler,
so this is its name, and then we call the macro for registering a behavior.
And now you might have noticed this says register behavior,
the previous one said register trees.
So this is a bit more of an abstract definition now,
because it's not only a tree,
it's a whole new definition of a behavior.
Since we have a different format for the build request,
and we want to handle this build request with a specific algorithm.
So this is what we specify here.
I'm just going to statically register the behavior definition here,
the request, and link it with this builder.
So the executor knows during runtime how to interpret this message.
So we can run this behavior with the same CLI tool as I've shown you before.
And this is what it looks like.
You know, see there's lots of behaviors already in the workspace,
also a custom one, and this one we're going to execute now.
So it sends the builder request and the hand moves left, left, right, right, left, right.
And you could also do this just dynamically.
You could just load the build handler and send an arbitrary builder request
that just respects the format.
You have to find with this build handler and change the behavior
that's been generated during runtime as you like in your application.
And now you also have like this, this,
the engine of reactivity that people usually want with behavior trees,
because you could not only just hard code the reactivity in the behavior,
but you can also just make the behavior generation reactive by itself.
So the executor is basically, this is all unified within one executor note
in this framework that just awaits these,
well, these definitions, the components of the behavior definition.
So they request handler and then entry point in the node manifest,
optionally, and also is compliant with the rawst to action interface.
And this is basically happening just like with standard rawst,
there's only one command that you have to like run and this is like a standalone executable
that doesn't require an F2.
So is the main diagnostic solution,
and yeah, you can start creating your own behavior definitions for use cases.
Okay, so I'm coming to the answer of my talk now.
I've officially also released this project with the rawst index.
It's up for you to install it with as any other project that you install
individually, and I encourage you to give it a try,
and streamline your rawst to project.
And also there's something interesting coming up.
I'm actually working together with a group of students in my university, computer science students,
and we're trying to create like a behavior management tool that is web-based.
So we have all our behaviors that we've registered in our workspace,
at hand, at a visualization in the browser,
and we can run it as we like.
So we have basically a remote mission manager always in the browser.
If we want to manage our mission with behavior trees.
Okay, thank you very much for this for your attention.
And all the material or the code is on GitHub.
Also there's extensive user guides for this project.
And I found that the AI generated docs are actually also very helpful,
especially if you pipe them into some LLMs.
It's very interesting what's possible.
Okay, thank you very much.
I have plenty of time for questions, anybody?
No, nobody.
Anything online?
No?
Oh, there's one.
Yeah, thanks for the presentation.
So you mentioned that this is kind of integrated with the NAFER framework.
No, it is not.
It's not.
But it's the main agnostic.
Okay, I was wondering if it's also possible to use this for like the movement framework.
Yeah, so well, I mean, the problem is that with NAF2,
the implement of their own behavior executor.
And it is not compatible with the way that like this executor works with the customization options
and the modularity because it instantiates the behavior trees in a different way.
So the problem here is that you cannot really reuse existing notes for your other projects.
You should kind of, you must like create a custom bridge for these functionalities.
Yeah.
Anybody else?
We have plenty of time.
So your command line says that it's doing a build.
So at run time you're doing code compilation.
That's that right.
Yeah, well, at run time we're doing the redundant code compilation.
At run time.
To what exactly are you referring?
I might ask.
So if you're building a custom behavior, you've got your custom behavior builder.
Yeah.
Are you, is that just running at run time?
It's already compiled.
Well, it's a plugin.
It's a plugin that is loaded dynamically to the executor.
So this code is loaded once you request to behavior to be started.
And then the request for the behavior.
So the definition of it, as it's a string, is given to this, to this algorithm.
And then the algorithm generates the behavior that we can return to the executor and the executor executed.
Got it.
Thank you.
So there's no compilation, but there's generation of the tree.
I don't know.
Maybe this was kind of misleading.
No, no.
I think I just misunderstood.
I'd add a quick check as well.
I think the answer is yes, but I wanted to check, which is, can you call the custom builders from other nodes?
Yes.
So you know, it's complying with the rust to action interface.
And you see there's a field saying build handler.
So if I understand the question correctly, you can just specify this build handler by your different way, by your other node.
And give it to the executor and the executor loads it dynamically, as you'd like.
Yeah.
Thank you.
All right, so if we have no more question, we do have.
Okay.
Thank you for the talk.
I have a question regarding the durability.
If we use a custom node, and we messed up the build of the node,
how do we find what is the problem?
Can we see the generated XML or a way to deliver it?
Okay.
Well, yeah.
So I mean, if there's a problem with your implementation and it throws like a runtime error, and this is,
I mean, obviously you have the source code in your project, but the model of it is generated.
And you can see the XML representation of the model, but I'm not really sure if I understand the question correctly,
because if it's a runtime error, then you have to debug it on your own, obviously.
I cannot help you with this, but if you don't know how the node is actually expecting inputs or something,
then there's a compatibility issue.
This is what these node models are for.
So they tell the behavior tree what inputs it allows, which arguments, and it's doing this building.
It's also checking for compatibility issues.
When you like insert nodes here in this builder API, it validates that it's compatible with one another.
And it's the right plugin, which is loaded.
So you don't have to do all this linking and this configuration management by your own.
All right.
Let's have a little bit longer break with about eight minutes until the next stop.
Thank you so much.