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

Draft: Add ros_bt_py technology info + examples #41

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export PYTHONWARNINGS="ignore:setup.py install is deprecated,ignore:easy_install

export ROS_WS=/delib_ws
export WORKSPACE_ROOT=$ROS_WS # For FlexBE compatibility
export ROS_DOMAIN_ID=0
export ROS_DOMAIN_ID=66
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not commit? (or rather, we need to fix this)


function print_ros_variables () {
echo -e "ROS Distro: \t" $ROS_DISTRO
Expand Down
91 changes: 91 additions & 0 deletions technologies/ros_bt_py/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# ros\_bt\_py Setup for ROSCon 2024 Deliberation Technologies Workshop
---

## Installation and Setup

For the ROSCon '24 Deliberation Technologies workshop all required software is included in the Docker container.
To build locally at home see the [installation instructions](https://fzi-forschungszentrum-informatik.github.io/ros2_ros_bt_py/index.html).

## Getting Started

To get started with `ros_bt_py` as part of the workshop no additonal packages would be required.
We provide generic nodes for interacting with ROS Topics, Services and Actions, which only require having the messages available in your environment when starting `ros_bt_py`.
Nevertheless we provided the `ros_bt_py_pyrobosim` package containing some additonal nodes, providing convinience wrappers, making interacting with `pyrobosim` simpler.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Nevertheless we provided the `ros_bt_py_pyrobosim` package containing some additonal nodes, providing convinience wrappers, making interacting with `pyrobosim` simpler.
Nevertheless we provided the `ros_bt_py_pyrobosim` package containing some additional nodes, providing convenience wrappers, making interacting with `pyrobosim` simpler.


First run one of the problem scenarios from the workshop:

```bash
ros2 run delib_ws_worlds run --ros-args -p problem_number:=1
```

To run `ros_bt_py` with these convinience nodes you need to run:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
To run `ros_bt_py` with these convinience nodes you need to run:
To run `ros_bt_py` with these convenience nodes you need to run:

```bash
ros2 launch ros_bt_py_pyrobosim ros_bt_py_pyrobosim.launch.py
```

Alternativly, to launch the regular `ros_bt_py` without any additional nodes run:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Alternativly, to launch the regular `ros_bt_py` without any additional nodes run:
Alternatively, to launch the regular `ros_bt_py` without any additional nodes run:

```bash
ros2 launch ros_bt_py ros_bt_py.launch.py
```

After launching you can click one of the shown URLs to open our WebUI in a browser.
Commonly this is just `http://localhost:8085`.

## Usage

The WebUI has four distinct sections:
* On the left you have the list of available nodes.
These can be moved into the main editing area at the center to add them to the tree.
Some nodes (e.g. EnumFields) require you to click on the node in the list and add required details at the bottom of the editing window before they can be added to the tree via the `AddToTree` button.
* In the center there is the main editing window.
By moving nodes into this area their position within the tree can be adjusted.
When connecting nodes from the data terminals at either the left (inputs) or right (outputs) side of a node you can created data connections between nodes.
The color of the node will indicate its current state:
* Dark Red - Shutdown
* Blue - Idle
* Yellow - Running
* Red - Failure
* Green - Succeed
* The top bar provides execution controls and reports the current status of the tree.
Also the load / save controls for trees is located here.
Trees can be loaded from ROS packages and from the `.ros` directory.
Saving is unfortunately only possible in the `.ros` directory.
* The center bottom area is where attributes on a clicked node can be edited.
ROS message types and builtin python types are automatically completed when a type field is edited.
>
> BUG: When updating node attributs and clicking apply, a popup will say that changes are discarded. This a bug and the changes will apply fully as intended.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> BUG: When updating node attributs and clicking apply, a popup will say that changes are discarded. This a bug and the changes will apply fully as intended.
> BUG: When updating node attributes and clicking apply, a popup will say that changes are discarded. This a bug and the changes will apply fully as intended.

> This will be fixed with the next GUI update.
>

This explains the basics of the `ros_bt_py` UI.
In the following some more details on specific features are given:

### Data Flow

`ros_bt_py` uses a typed datagraph to share data between nodes.
Each node can expose inputs and outputs which can be connected to other nodes.

All inputs are mandatory, meaning that if an input is not connected or at runtime no value is present, a runtime error will be raised.
Outputs are populated on the tick the node succeeds.

Data connections are strongly typed for ROS messages and primitive python types.
While container types (e.g arrays, lists, tuple) are checked, their content types are not.
Connecting an `list[int]` output to an `list[string]` input will result in a runtime error.

### ROS Actions & Servuces

`ros_bt_py` has generic nodes for interacting with ROS services, topics and actions.
The `Serivce` and `Action` nodes allow to call an arbitrary ROS service/action.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The `Serivce` and `Action` nodes allow to call an arbitrary ROS service/action.
The `Service` and `Action` nodes allow to call an arbitrary ROS service/action.

Within both nodes options, the endpoint and type must be specified.
The `FieldsToMessage` and `MessageToFields` nodes can be used to construct and destruct the `Request/Goal` and `Result` messages from these nodes.
`Constant` nodes can be used to create primitve python datatype constants.
>
> BUG: It should also be possible to generate full ROS message types in constants, but unfortunately due to changes to ROS/Python type introspection
> this is not possible. ROS messages need to be constructed using the `FieldsToMessage` node.
>

## ToDo's

- [ ] Provide example solutiont trees for all problems.
- [ ] Add convinience nodes for pyrobosim interaction.
Comment on lines +89 to +90
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- [ ] Provide example solutiont trees for all problems.
- [ ] Add convinience nodes for pyrobosim interaction.
- [ ] Provide example solution trees for all problems.
- [ ] Add convenience nodes for pyrobosim interaction.


Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import os

from ament_index_python import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource


def generate_launch_description():

# include default bt_py launch file
bt_py_include = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(
get_package_share_directory('ros_bt_py'),
'launch/ros_bt_py.launch.py')),
launch_arguments={
'enable_web_interface': 'True',
'node_modules': "['ros_bt_py.nodes','ros_bt_py.ros_nodes','ros_bt_py_pyrobosim.nodes']",
}.items()
)

return LaunchDescription([bt_py_include])

21 changes: 21 additions & 0 deletions technologies/ros_bt_py/ros_bt_py_pyrobosim/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>ros_bt_py_pyrobosim</name>
<version>1.0.0</version>
<description>BT nodes for the ROSCon2024 workshop</description>
<maintainer email="[email protected]">David Oberacker</maintainer>
<license>TODO: License declaration</license>

<depend>pyrobosim_msgs</depend>
<depend>ros_bt_py</depend>

<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>

<export>
<build_type>ament_python</build_type>
</export>
</package>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import actions
from . import services
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import rclpy

from ros_bt_py.node_config import NodeConfig
from ros_bt_py.node import define_bt_node

from ros_bt_py.ros_nodes.action import ActionForSetType
from ros_bt_py_interfaces.msg import Node as NodeMsg

from pyrobosim_msgs.action import ExecuteTaskAction
from pyrobosim_msgs.msg import ExecutionResult

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from pyrobosim_msgs.msg import ObjectState, RobotState

from ros_bt_py_interfaces.msg import Node as NodeMsg
from ros_bt_py_interfaces.msg import UtilityBounds

from ros_bt_py.debug_manager import DebugManager
from ros_bt_py.node import Leaf, define_bt_node
from ros_bt_py.node_config import NodeConfig, OptionRef
from ros_bt_py.exceptions import BehaviorTreeException

from abc import ABC, abstractmethod
from typing import Optional, Dict, Tuple, List
from ros_bt_py.ros_nodes.service import ServiceForSetType

from pyrobosim_msgs.srv import RequestWorldState


@define_bt_node(
NodeConfig(
version="0.1.0",
options={},
inputs={"robot_name": str, "object_name": str},
outputs={},
max_children=0,
)
)
class CheckIfCarriesObject(ServiceForSetType):

def set_service_type(self):
self._service_type = RequestWorldState

# Set all outputs to none (define output key while overwriting)
def set_output_none(self):
pass

# Sets the service request message, sent to the service.
def set_request(self):
self._last_request = RequestWorldState.Request()

# Sets the output (in relation to the response) (define output key while overwriting)
# it should return True, if the node state should be SUCCEEDED after receiving the message and
# False. if it should be in the FAILED state
def set_outputs(self):
response: RequestWorldState.Response = self._service_request_future.result()
robot_with_matching_name: List[RobotState] = [robot for robot in response.state.robots if robot.name == self.inputs["robot_name"]]
if len(robot_with_matching_name) != 1:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily true; if this is 0, then the robot was not found, right?

self.logerr("Found more then one matching robot!")
raise BehaviorTreeException("Found more then one matching robot!")
Comment on lines +47 to +48
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.logerr("Found more then one matching robot!")
raise BehaviorTreeException("Found more then one matching robot!")
message = "Found more than one matching robot!"
self.logerr(message)
raise BehaviorTreeException(message)

either way note the typo: "then" -> "than"

robot = robot_with_matching_name[0]
return robot.holding_object and robot.manipulated_object == self.inputs["object_name"]

@define_bt_node(
NodeConfig(
version="0.1.0",
options={},
inputs={"object_name": str},
outputs={
"location": str
},
max_children=0,
)
)
class GetObjectPosition(ServiceForSetType):

def set_service_type(self):
self._service_type = RequestWorldState

# Set all outputs to none (define output key while overwriting)
def set_output_none(self):
self.outputs["location"] = ""
pass

# Sets the service request message, sent to the service.
def set_request(self):
self._last_request = RequestWorldState.Request()

# Sets the output (in relation to the response) (define output key while overwriting)
# it should return True, if the node state should be SUCCEEDED after receiving the message and
# False. if it should be in the FAILED state
def set_outputs(self):
response: RequestWorldState.Response = self._service_request_future.result()
obj_with_matching_name: List[ObjectState] = [obj for obj in response.state.objects if obj.name == self.inputs["object_name"]]
if len(obj_with_matching_name) != 1:
self.logerr("Found something other than one matching object!")
Comment on lines +83 to +84
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this one, the error message is correct in that it's just "other than one" -- but it might still be nice to separate the empty vs. multiple-element case

(although, PyRoboSim should not let you add duplicate objects and robots with the same name, it's still good to have)

return False
obj = obj_with_matching_name[0]
self.outputs["location"] = obj.parent
return True
4 changes: 4 additions & 0 deletions technologies/ros_bt_py/ros_bt_py_pyrobosim/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[develop]
script_dir=$base/lib/ros_bt_py_pyrobosim
[install]
install_scripts=$base/lib/ros_bt_py_pyrobosim
29 changes: 29 additions & 0 deletions technologies/ros_bt_py/ros_bt_py_pyrobosim/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os
from glob import glob
from setuptools import find_packages, setup

package_name = 'ros_bt_py_pyrobosim'

setup(
name=package_name,
version='1.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*.launch.py'))),
(os.path.join('share', package_name, 'trees'), glob(os.path.join('trees', '*.yaml')))
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='David Oberacker',
maintainer_email='[email protected]',
description='BT nodes for the ROSCon2024 workshop',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
],
},
)
Loading
Loading