Skip to content
Merged
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
39 changes: 39 additions & 0 deletions .github/workflows/docs-deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Deploy Documentation

on:
push:
branches:
- master
- feature/tutorial
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

hmm, thanks to this is https://robotika.github.io/osgar/ already automatically updated


jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Really? It is 2026.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh, you mean "very old Python version"? Well, some people still need the RealSense backward compatibility, but maybe there is already some replacement? Thanks for comments

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I am currently trying to use version 3.12. Pyrealsense2 also works (At least here).
I don't know how to ARM platforms (RPi).


- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Install dependencies
run: |
uv pip install --system -r requirements.txt
uv pip install --system -r doc/requirements.txt
uv pip install --system .

- name: Build documentation
run: |
uv run sphinx-build -b html doc/sphinx doc/sphinx/_build/html

- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
if: github.event_name == 'push'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./doc/sphinx/_build/html
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ ign-logs/
auto-save-list
tramp
.\#*
doc/sphinx/_build/
1 change: 1 addition & 0 deletions doc/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sphinx
2 changes: 1 addition & 1 deletion doc/sphinx/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

# General information about the project.
project = 'osgar'
copyright = '2018, robotika.cz'
copyright = '2018-2026, robotika.cz'
author = 'robotika.cz'

# The version info for the project you're documenting, acts as replacement for
Expand Down
1 change: 1 addition & 0 deletions doc/sphinx/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Welcome to osgar's documentation!
:maxdepth: 2
:caption: Contents:

tutorial
logging
eduro
drivers
Expand Down
237 changes: 237 additions & 0 deletions doc/sphinx/tutorial.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
MyRobot Tutorial
================

This tutorial explains how to create a simple simulated robot and a control
application using OSGAR. We will use the ``myrobot`` example as a reference.

The ``myrobot`` example consists of three main files:

- ``myrobot.py``: The robot driver, which simulates the robot's behavior.
- ``myapp.py``: The application, which controls the robot.
- ``myrobot.json``: The configuration file, which connects the robot and the
application.

The Robot Driver - ``myrobot.py``
---------------------------------

The ``myrobot.py`` file defines a simple simulated robot with differential
drive. It receives speed commands and simulates the robot's movement,
publishing its new pose (position and orientation).

The driver inherits from ``osgar.node.Node`` and implements ``on_desired_speed``
and ``on_tick`` methods to handle incoming messages. The ``update()`` method
is provided by the base class and automatically dispatches messages to the
corresponding ``on_<channel>`` handlers.

Here is the code for ``myrobot.py``:

.. code-block:: python

"""
Example of robot diver outside OSGAR package
- simulation of differential robot
"""
import math

from osgar.node import Node


class MyRobot(Node):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If this is a tutorial, couldn't there be more comments?

def __init__(self, config, bus):
super().__init__(config, bus)
bus.register('pose2d', 'emergency_stop')
self.pose = (0, 0, 0)
self.speed, self.angular_speed = 0, 0
self.desired_speed, self.desired_angular_speed = 0, 0
self.last_update = None

def send_pose(self):
x, y, heading = self.pose
self.publish('pose2d', [round(x*1000), round(y*1000),
round(math.degrees(heading)*100)])

def update_pose(self):
if self.last_update is not None:
t = (self.time - self.last_update).total_seconds()
self.speed = self.desired_speed # TODO motion model
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The TODO things are confusing there.

self.angular_speed = self.desired_angular_speed

x, y, heading = self.pose
x += math.cos(heading) * self.speed * t
y += math.sin(heading) * self.speed * t
heading += self.angular_speed * t
self.pose = (x, y, heading)
self.last_update = self.time

def on_desired_speed(self, data):
self.update_pose()
self.desired_speed, self.desired_angular_speed = data[0]/1000.0, math.radians(data[1]/100.0)
self.send_pose()

def on_tick(self, data):
self.update_pose()
self.send_pose()

# vim: expandtab sw=4 ts=4


The Application - ``myapp.py``
------------------------------

The ``myapp.py`` file defines an application that controls the robot. It sends
speed commands to make the robot move in a figure eight.

Here is the code for ``myapp.py``:

.. code-block:: python

"""
Example of a simple application controling robot to move in figure 8
"""
import math
from datetime import timedelta

from osgar.node import Node
from osgar.bus import BusShutdownException


def distance(pose1, pose2):
return math.hypot(pose1[0] - pose2[0], pose1[1] - pose2[1])


class MyApp(Node):
def __init__(self, config, bus):
super().__init__(config, bus)
bus.register('desired_speed')
self.max_speed = config.get('max_speed', 0.1)
self.max_angular_speed = math.radians(50) # TODO config
self.verbose = False
self.last_position = (0, 0, 0)
self.is_moving = False
self.pose2d = None # TODO should be defined by super().__init__()

# TODO refactor to common "serializer"
def send_speed_cmd(self, speed, angular_speed):
return self.publish('desired_speed', [round(speed*1000), round(math.degrees(angular_speed)*100)])

# TODO refactor to common driver (used from sick2018 example)
def go_straight(self, how_far):
print(self.time, "go_straight %.1f" % how_far, self.last_position)
start_pose = self.last_position
if how_far >= 0:
self.send_speed_cmd(self.max_speed, 0.0)
else:
self.send_speed_cmd(-self.max_speed, 0.0)
while distance(start_pose, self.last_position) < abs(how_far):
self.update()
self.send_speed_cmd(0.0, 0.0)

def turn(self, angle, with_stop=True):
print(self.time, "turn %.1f" % math.degrees(angle))
start_pose = self.last_position
if angle >= 0:
self.send_speed_cmd(0.0, self.max_angular_speed)
else:
self.send_speed_cmd(0.0, -self.max_angular_speed)
while abs(start_pose[2] - self.last_position[2]) < abs(angle):
self.update()
if with_stop:
self.send_speed_cmd(0.0, 0.0)
start_time = self.time
while self.time - start_time < timedelta(seconds=2):
self.update()
if not self.is_moving:
break
print(self.time, 'stop at', self.time - start_time)

def on_pose2d(self, data):
prev = self.pose2d
self.pose2d = data
x_mm, y_mm, heading_mdeg = self.pose2d
self.last_position = (x_mm/1000.0, y_mm/1000.0,
math.radians(heading_mdeg/100.0))
self.is_moving = (prev != self.pose2d)

def run(self):
try:
print("MyApp example - figure 8!")
step_size = 0.5 # meters
deg90 = math.radians(90)

for i in range(4):
self.go_straight(step_size)
self.turn(deg90)

for i in range(4):
self.go_straight(step_size)
self.turn(-deg90)

print("END")
except BusShutdownException:
pass


if __name__ == "__main__":
from osgar.launcher import launch

launch(app=MyApp, description='Navigate figure eight', prefix='myapp-')

# vim: expandtab sw=4 ts=4

The Configuration - ``myrobot.json``
------------------------------------

The ``myrobot.json`` file defines the modules (the robot and the application)
and the connections between them. We use the built-in ``timer`` driver.

Here is the content of ``myrobot.json``:

.. code-block:: json

{
"version": 2,
"robot": {
"modules": {
"app": {
"driver": "myapp:MyApp",
"in": ["emergency_stop", "pose2d"],
"out": ["desired_speed"],
"init": {
"max_speed": 0.5
}
},
"myrobot": {
"driver": "myrobot:MyRobot",
"in": ["desired_speed", "tick"],
"out": ["emergency_stop", "pose2d"],
"init": {}
},
"timer": {
"driver": "timer",
"in": [],
"out": ["tick"],
"init": {
"sleep": 0.1
}
}
},
"links": [["app.desired_speed", "myrobot.desired_speed"],
["myrobot.emergency_stop", "app.emergency_stop"],
["myrobot.pose2d", "app.pose2d"],
["timer.tick", "myrobot.tick"]]
}
}

Running the Example
-------------------

To run the example, you need to execute the ``osgar.record`` module with the
``myrobot.json`` configuration file. You also need to add the current directory
and the ``examples/myrobot`` directory to your ``PYTHONPATH`` so that OSGAR
can find the modules.

.. code-block:: bash

PYTHONPATH=.:examples/myrobot python3 -m osgar.record examples/myrobot/myrobot.json --duration 10

This will create a log file with the recorded data from the simulation.
30 changes: 17 additions & 13 deletions examples/myrobot/myapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from datetime import timedelta

from osgar.node import Node
from osgar.bus import BusShutdownException


def distance(pose1, pose2):
Expand Down Expand Up @@ -65,19 +66,22 @@ def on_pose2d(self, data):
self.is_moving = (prev != self.pose2d)

def run(self):
print("MyApp example - figure 8!")
step_size = 0.5 # meters
deg90 = math.radians(90)

for i in range(4):
self.go_straight(step_size)
self.turn(deg90)

for i in range(4):
self.go_straight(step_size)
self.turn(-deg90)

print("END")
try:
print("MyApp example - figure 8!")
step_size = 0.5 # meters
deg90 = math.radians(90)

for i in range(4):
self.go_straight(step_size)
self.turn(deg90)

for i in range(4):
self.go_straight(step_size)
self.turn(-deg90)

print("END")
except BusShutdownException:
pass


if __name__ == "__main__":
Expand Down
33 changes: 0 additions & 33 deletions examples/myrobot/myrobot-slots.json

This file was deleted.

Loading