This project is a Django application that aims to be integrated in a ROS2 python package.
Your ROS2 package will then be able to start a node that will export CRUD (Create, Read, Update, Delete) ROS services for Django models.
The goal is to have a way to easily store and retrieve structured data in a ROS2 ecosystem via services, with the power of django framework that includes schema migration, admin interface, field validation, and many more.
For now, install it directly from GitHub
pip install git+https://github.com/robocc/ros2-django.git
Next, create a ROS2 package following the example in example_pkg/
The quickest way is to copy/use example_pkg in your workspace. It contains a basic django app with 2 ros models and ros2-django configured.
cd example_pkg
colcon build
source install/local_setup.bash # re-source ros workspace
./manage.py migrate # create database
./manage.py createsuperuser # create admin user
./example_pkg_node.py # start ros nodeYou can also run
./manage.py runserverand connect to http://localhost:8000 to access admin interface
Once ROS node is started, you can create/get data to and from django, e.g. via ros2 cli
$ ros2 service call /example_pkg/list_map example_pkg/srv/ListMap
response:
example_pkg.srv.ListMap_Response(success=True, message='', maps=[example_pkg.msg.Map(poseonmaps=[example_pkg.msg.PoseOnMap(id=1, name='Test', pose=geometry_msgs.msg.Pose(position=geometry_msgs.msg.Point(x=4.0, y=5.0, z=0.0), orientation=geometry_msgs.msg.Quaternion(x=0.0, y=0.0, z=0.0, w=1.0)), id_map=1)], id=1, name='TEst', description='fsadf', webp=[])])
$ ros2 service call /example_pkg/set_pose_on_map example_pkg/srv/SetPoseOnMap 'poseonmap:
id: 1
name: Test2
pose:
position:
x: 5.0
y: 0.0
z: 0.0
orientation:
x: 4.0
y: 0.0
z: 0.0
w: 1.0
id_map: -1'
requester: making request: example_pkg.srv.SetPoseOnMap_Request(poseonmap=example_pkg.msg.PoseOnMap(id=1, name='Test2', pose=geometry_msgs.msg.Pose(position=geometry_msgs.msg.Point(x=5.0, y=0.0, z=0.0), orientation=geometry_msgs.msg.Quaternion(x=4.0, y=0.0, z=0.0, w=1.0)), id_map=-1))
response:
example_pkg.srv.SetPoseOnMap_Response(success=True, message='', id=1)From now, you can edit your application models.py and create your model objects.
- All the models you want to export to ROS should inherit from
ros2_django.models.RosModel - All the fields you want to export to ROS should be from module
ros2_django.fields(for exampleros2_django.fields.RosCharField) - A specific
ros2_django.fields.RosMsgFieldallows you to store/retrieve directly a ROS message by specifying its type - Custom fields can be created, they should inherit from both
ros2_django.fields.RosFieldMixinand correspondant Django field
Consider the following example of models.py
from django.db import models
from ros2_django.models import RosModel
import ros2_django.fields
from geometry_msgs.msg import Pose
class Map(RosModel):
id = ros2_django.fields.RosBigAutoField(primary_key=True)
name = ros2_django.fields.RosCharField(max_length=128)
description = ros2_django.fields.RosTextField(blank=True)
webp = ros2_django.fields.RosImageField(blank=True, null=True)
def __str__(self):
return f"[{self.id}] {self.name}"
class PoseOnMap(RosModel):
id = ros2_django.fields.RosBigAutoField(primary_key=True)
name = ros2_django.fields.RosCharField(max_length=128)
pose = ros2_django.fields.RosMsgField(ros_msg=Pose, ros_type="geometry_msgs/Pose")
map = ros2_django.fields.RosForeignKey(
"Map",
on_delete=models.CASCADE,
)In this example, Map and PoseOnMap messages will be created, with all the fields. And the pose field of PoseOnMap will be a standard geometry_msgs/Pose.
This will be done automatically with the example CMakeLists.txt
Once models.py is written, you can create your database with standard ./manage.py makemigrations and ./manage.py migrate.
You now also can run ./manage.py gen_ros_msgs example_app out/ that will generate all messages and services definition for ROS in directory out/.
For each RosModel named Foo, it will generate:
Foo.msgwith all its fields, including reverse relationsFooRaw.msgwith all its fields without relations (not generated if its the same asFoo.msg)GetFoo.srvandGetFooRaw.srvservices to get aFooorFooRawvia its idSetFoo.srvandSetFooRaw.srvto create/set aFooorFooRawobject via its id (creation withid=-1)ListFoo.srvandListFooRaw.srvto list allFooorFooRawDeleteFoo.srvandDeleteFooRaw.srvto remove aFooorFooRawvia its id
Theses services will be implemented and served via ros2_django.ros_node.ROS2DjangoNode
Example interfaces for previous model example:
Map.msg:
PoseOnMap[] poseonmaps
int64 id
string name
string description
uint8[] webp
MapRaw.msg:
int64 id
string name
string description
uint8[] webp
PoseOnMap.msg:
int64 id
string name
geometry_msgs/Pose pose
int64 id_map
GetMap.srv:
int64 id
---
bool success
string message
Map map
SetMapRaw.srv:
MapRaw map
---
bool success
string message
int64 id
ListMap.srv:
---
bool success
string message
Map[] maps
In order to customize interface services, one can declare a RosMeta class inside model.
Current accepted attributes are:
ros_search: list of fields (or fields tuple for and search) that we can search upon forGetandSetservices.- Following example will generate a
GetFooByNameandSetFooByNameservice
Generatedclass Foo(RosModel): id = ros2_django.fields.RosBigAutoField(primary_key=True) name = ros2_django.fields.RosCharField(max_length=128) class RosMeta: ros_search = ['name']
GetFooByName.srvstring name --- bool success string message Foo foo- Following example will generate a
ros_filter: list of fields to filter upon forListservices- Following example will generate a
ListBarByFooto list allBarthat depends on a specificFoo
Generatedclass Bar(RosModel): id = ros2_django.fields.RosBigAutoField(primary_key=True) foo = ros2_django.fields.RosForeignKey( "Foo", on_delete=models.CASCADE, ) class RosMeta: ros_filter = ['foo']
ListBarByFoo.srvint64 id_foo --- bool success string message Bar[] bars- Following example will generate a
ros_ignore_fields: list of fields not included in the final messageros_thin_fields: if present, will restrict which fields will be filled inListservices to gain bandwidth
example_pkg folder contains a ready-to-use package using ros2-django. These instructions are for people who want to do it manually
- Create a ROS2 package with a
CMakeLists.txt(not a Python package)ros2 pkg create --build-type ament_cmake example_pkg
- Create a standard Django project and app in your package
django-admin startproject example_django_project ../manage.py startapp example_app
- In
example_django_project/settings.py- Add
ros2_djangoinINSTALLED_APPS - Set
ROS_INTERFACES_MODULE_NAMEto ROS package name (example_pkgin this case)
- Add
- Setup routes/admin stuff for Django if you want (cf. django documentation)
- Edit your
CMakeLists.txtto generate interfaces and install python packages (cf. example) - Create a node starter python file that will spin
ros2_django.ros_node.ROS2DjangoNode(cf. example)
