diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index a510eb91b..a99604c0e 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -37,13 +37,13 @@ jobs:
submodules: recursive
path: catkin_ws/src/mil
- name: Setup ROS Noetic
- uses: ros-tooling/setup-ros@v0.3
+ uses: ros-tooling/setup-ros@v0.7
with:
required-ros-distributions: noetic
- name: Install pip dependencies
run: |
cd $GITHUB_WORKSPACE/catkin_ws/src/mil
- pip install -r requirements.txt
+ pip3 install -r requirements.txt
# We want to run a full test suite in CI - this includes the BlueView
# tests!
- name: Install BlueView Sonar SDK
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index cb5d978ed..7751457af 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -15,7 +15,7 @@ repos:
hooks:
- id: yamllint
- repo: https://github.com/psf/black
- rev: 23.7.0
+ rev: 23.9.1
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-clang-format
@@ -23,12 +23,12 @@ repos:
hooks:
- id: clang-format
- repo: https://github.com/PyCQA/autoflake
- rev: v2.2.0
+ rev: v2.2.1
hooks:
- id: autoflake
args: [--remove-all-unused-imports, --ignore-init-module-imports]
- repo: https://github.com/shellcheck-py/shellcheck-py
- rev: v0.9.0.5
+ rev: v0.9.0.6
hooks:
- id: shellcheck
exclude: ^docker|deprecated|NaviGator/simulation/VRX
@@ -40,7 +40,7 @@ repos:
exclude: ^docker|deprecated|NaviGator/simulation/VRX
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
- rev: 'v0.0.282'
+ rev: 'v0.0.292'
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
diff --git a/NaviGator/utils/navigator_tools/navigator_tools/object_database_helper.py b/NaviGator/utils/navigator_tools/navigator_tools/object_database_helper.py
index 7d73fc1aa..5cb8762cb 100644
--- a/NaviGator/utils/navigator_tools/navigator_tools/object_database_helper.py
+++ b/NaviGator/utils/navigator_tools/navigator_tools/object_database_helper.py
@@ -1,4 +1,3 @@
-"""Use the DBHelper class to interface with the Database without having to deal with ROS things."""
import asyncio
import sys
import time
@@ -14,10 +13,29 @@
class DBHelper:
- """DBHelper class."""
+ """
+ Use the DBHelper class to interface with the Database without having to deal with ROS things.
+
+ Attributes:
+ found (bool): checks whether the object is what the user has been looking for.
+ nh (axros.NodeHandle): processes database requests.
+ position (int): the current position of the object.
+ rot (): one of the dimensional values.
+ new_object_subscriber (): an object that is being called from the database.
+ ensuring_objects (bool): a variable state for the object.
+ ensuring_object_dep (): a list of objects being used for storage.
+ ensuring_object_cb (): this is considered when it is an "ensuring_objects".
+ looking_for (): a setter variable to which a name is being assigned.
+ is_found (bool): determines whether the object is found or not.
+ """
def __init__(self, nh):
- """Initialize the DB helper class."""
+ """
+ Initialize the DB helper class.
+
+ Args:
+ nh(NodeHandle): NodeHandle object
+ """
self.found = set()
self.nh = nh
self.position = None
@@ -30,7 +48,12 @@ def __init__(self, nh):
self.is_found = False
async def init_(self, navigator=None):
- """Initialize the axros parts of the DBHelper."""
+ """
+ Initialize the axros parts of the DBHelper.
+
+ Args:
+ navigator (NaviGator | None): Base NaviGator object
+ """
# self._sub_database = yield self.nh.subscribe('/database/objects', PerceptionObjectArray, self.object_cb)
self._database = self.nh.get_service_client("/database/requests", ObjectDBQuery)
self.navigator = navigator
@@ -43,9 +66,24 @@ async def init_(self, navigator=None):
return self
def _odom_cb(self, odom):
+ """
+ Sets the position and the rot values.
+
+ Args:
+ odom (object): Odom
+ """
self.position, self.rot = nt.odometry_to_numpy(odom)[0]
async def get_object_by_id(self, my_id):
+ """
+ Retrieves the object with the associated "my_id".
+
+ Args:
+ my_id (int): an "id" of the object is passed in.
+
+ Returns:
+ An individual object is being returned with a unique id.
+ """
print(my_id)
req = ObjectDBQueryRequest()
req.name = "all"
@@ -57,7 +95,8 @@ async def begin_observing(self, cb):
"""
Get notified when a new object is added to the database.
- cb : The callback that is called when a new object is added to the database
+ Args:
+ cb : The callback that is called when a new object is added to the database.
"""
self.new_object_subscriber = cb
req = ObjectDBQueryRequest()
@@ -78,6 +117,12 @@ async def begin_observing(self, cb):
self.new_object_subscriber(o)
async def get_unknown_and_low_conf(self):
+ """
+ Gets unknown objects that are of low confidence.
+
+ Returns:
+ Objects that meet certain criteria.
+ """
req = ObjectDBQueryRequest()
req.name = "all"
resp = await self._database(req)
@@ -91,9 +136,21 @@ async def get_unknown_and_low_conf(self):
return m
def set_looking_for(self, name):
+ """
+ Setting to a name that is being looked for.
+
+ Args:
+ name (string): name of the object.
+ """
self.looking_for = name
- def is_found_func(self):
+ def is_found_func(self) -> bool:
+ """
+ Determines whether the object is being found or not.
+
+ Returns:
+ The existence of the object is returned.
+ """
if self.is_found:
self.looking_for = None
self.is_found = False
@@ -101,7 +158,12 @@ def is_found_func(self):
return False
def object_cb(self, perception_objects):
- """Callback for the object database."""
+ """
+ Callback for the object database.
+
+ Args:
+ perception_objects (object): a list of objects that are passed.
+ """
self.total_num = len(perception_objects.objects)
for o in perception_objects.objects:
if o.name == self.looking_for:
@@ -120,12 +182,23 @@ def object_cb(self, perception_objects):
self.ensuring_object_cb(missings_objs)
def remove_found(self, name):
- """Remove an object that has been listed as found."""
+ """
+ Remove an object that has been listed as found.
+
+ Args:
+ name (string): a name of the object is being passed in.
+ """
self.found.remove(name)
def ensure_object_permanence(self, object_dep, cb):
- """Ensure that all the objects in the object_dep list remain in the database.
- Call the callback if this isn't true."""
+ """
+ Ensure that all the objects in the object_dep list remain in the database.
+ Call the callback if this isn't true.
+
+ Args:
+ object_dep: a new object is passed in order to be set.
+ cb : The callback that is called when a new object is added to the database.
+ """
if object_dep is None or cb is None:
return
self.ensuring_objects = True
@@ -133,10 +206,21 @@ def ensure_object_permanence(self, object_dep, cb):
self.ensuring_object_dep = object_dep
def stop_ensuring_object_permanence(self):
- """Stop ensuring that objects remain in the database."""
+ """
+ Stop ensuring that objects remain in the database.
+ """
self.ensuring_objects = False
- def _wait_for_position(self, timeout=10):
+ def _wait_for_position(self, timeout=10) -> bool:
+ """
+ A possible position is being stored in a variable.
+
+ Args:
+ timeout (int): time limit to retrieve something.
+
+ Returns:
+ bool: Determines whether the position is found within the time limit.
+ """
count = 0
while self.position is None:
if self.navigator is not None:
@@ -148,7 +232,15 @@ def _wait_for_position(self, timeout=10):
return True
async def get_closest_object(self, objects):
- """Get the closest mission."""
+ """
+ Get the closest mission.
+
+ Args:
+ objects (object): a list of objects are being passed in.
+
+ Returns:
+ A object with the closest mission / distance is returned.
+ """
pobjs = []
for obj in objects:
req = ObjectDBQueryRequest()
@@ -171,6 +263,15 @@ async def get_closest_object(self, objects):
return min_obj
async def _dist(self, x):
+ """
+ Finds the distance of the object keeping into account its current position.
+
+ Args:
+ x (object): a specific object is being passed in order to retrieve its position.
+
+ Returns:
+ the distance between the two objects is being returned.
+ """
if self.position is None:
success = asyncio.create_task(self._wait_for_position)
if not success:
@@ -188,7 +289,18 @@ async def get_object(
thresh=50,
thresh_strict=50,
):
- """Get an object from the database."""
+ """
+ Get an object from the database.
+
+ Args:
+ object_name (string): the name of the object is passed in.
+ volume_only (bool): determines whether it is volume-based or not.
+ thresh (int): a maximum distance is passed in.
+ thresh_strict(int): a more strict maximum distance is passed in.
+
+ Returns:
+ closest_potential_object (object): returns the object that is within the closest range.
+ """
if volume_only:
req = ObjectDBQueryRequest()
req.name = object_name
@@ -233,6 +345,15 @@ async def get_object(
return closest_potential_object
def wait_for_additional_objects(self, timeout=60):
+ """
+ Returns true/false whether additional objects are being passed in.
+
+ Args:
+ timeout (int): maximum time limit of 60 seconds to run the program.
+
+ Returns:
+ (bool): determines whether additional objects are added.
+ """
num_items = self.num_items
start = time.time()
while timeout < time.time() - start:
@@ -241,14 +362,36 @@ def wait_for_additional_objects(self, timeout=60):
return False
def set_color(self, color, name):
- """Set the color of an object in the database."""
+ """
+ Sets the color of an object in the database.
+
+ Args:
+ color: the new color of the object is passed in.
+ name: the name of the object is passed in.
+ """
raise NotImplementedError
def set_fake_position(self, pos):
- """Set the position of a fake perception object."""
+ """
+ Sets the position of a fake perception object.
+
+ Args:
+ pos (int): the new position of the object is passed in.
+ """
raise NotImplementedError
async def get_objects_in_radius(self, pos, radius, objects="all"):
+ """
+ Retrieves the objects that are present within the radius.
+
+ Args:
+ pos (int): the position of the object is passed in.
+ radius (int): the radius of the object is passed in.
+ objects (object): this is a list of all the objects that needs to be compared.
+
+ Returns:
+ ans (object): returns the objects that are present within the radius.
+ """
req = ObjectDBQueryRequest()
req.name = "all"
resp = await self._database(req)
diff --git a/NaviGator/utils/navigator_tools/nodes/estimated_object_setter.py b/NaviGator/utils/navigator_tools/nodes/estimated_object_setter.py
index 99845b004..57d4b7c01 100755
--- a/NaviGator/utils/navigator_tools/nodes/estimated_object_setter.py
+++ b/NaviGator/utils/navigator_tools/nodes/estimated_object_setter.py
@@ -24,7 +24,7 @@ async def main(name, lla):
name = "_".join(txt.title() for txt in name.split("_"))
point = await convert.request(CoordinateConversionRequest(frame="lla", point=lla))
- await db(ObjectDBQueryRequest(cmd="{}={p[0]}, {p[1]}".format(name, p=point.enu)))
+ await db(ObjectDBQueryRequest(cmd=f"{name}={point.enu[0]}, {point.enu[1]}"))
if __name__ == "__main__":
diff --git a/docs/images/gazebo/Bottom_Toolbar.png b/docs/images/gazebo/Bottom_Toolbar.png
new file mode 100644
index 000000000..de1fb6d98
Binary files /dev/null and b/docs/images/gazebo/Bottom_Toolbar.png differ
diff --git a/docs/images/gazebo/Interface.png b/docs/images/gazebo/Interface.png
new file mode 100644
index 000000000..b6f5018c4
Binary files /dev/null and b/docs/images/gazebo/Interface.png differ
diff --git a/docs/images/gazebo/Labeled_Interface.png b/docs/images/gazebo/Labeled_Interface.png
new file mode 100644
index 000000000..871ddca52
Binary files /dev/null and b/docs/images/gazebo/Labeled_Interface.png differ
diff --git a/docs/images/gazebo/Menus.png b/docs/images/gazebo/Menus.png
new file mode 100644
index 000000000..d6c39d62e
Binary files /dev/null and b/docs/images/gazebo/Menus.png differ
diff --git a/docs/images/gazebo/Model_Editor.png b/docs/images/gazebo/Model_Editor.png
new file mode 100644
index 000000000..362f7cb16
Binary files /dev/null and b/docs/images/gazebo/Model_Editor.png differ
diff --git a/docs/images/gazebo/Mouse.png b/docs/images/gazebo/Mouse.png
new file mode 100644
index 000000000..728c8fc8f
Binary files /dev/null and b/docs/images/gazebo/Mouse.png differ
diff --git a/docs/images/gazebo/TrackPad.png b/docs/images/gazebo/TrackPad.png
new file mode 100644
index 000000000..1231be57b
Binary files /dev/null and b/docs/images/gazebo/TrackPad.png differ
diff --git a/docs/images/gazebo/Upper_Toolbar.png b/docs/images/gazebo/Upper_Toolbar.png
new file mode 100644
index 000000000..68e36e69a
Binary files /dev/null and b/docs/images/gazebo/Upper_Toolbar.png differ
diff --git a/docs/software/gazebo_guide.md b/docs/software/gazebo_guide.md
new file mode 100644
index 000000000..752147b10
--- /dev/null
+++ b/docs/software/gazebo_guide.md
@@ -0,0 +1,327 @@
+# Gazebo Guide
+
+This is a guide on how to use Gazebo, including launching the sub and
+viewing it inside Gazebo, how Gazebo can be controlled and manipulated, how to
+use Gazebo to make world files, and where you can find more info on Gazebo.
+
+## Brief Introduction
+
+Gazebo is an open-source 3D dynamic simulator, that allows us to design, model,
+and test robots and their behavior in a virtual world. Similar to game engines
+like Unity, Gazebo takes in scripts and models and performs physics simulations
+on them. While it is similar, Gazebo offers physics simulations at a higher level
+of fidelity, a suite of sensors, and interfaces for both users and programs.
+
+There are many versions of Gazebo, but this guide was written for **Gazebo
+Classic 11** as this is the version of Gazebo currently being used at MIL.
+
+### Running Gazebo
+
+There are many ways run Gazebo.
+
+* Click the "Show Applications" button on Ubuntu (the apps button located in
+ the bottom left corner). Then search for the Gazebo Icon and press that icon
+ to open Gazebo.
+
+* You can press Alt + F2 (or Alt+Fn+F2) to bring up the "Run a Command" window.
+ Then type "gazebo" and press Enter to open Gazebo.
+
+* You can also open a terminal and type "gazebo" and it will open Gazebo.
+
+To launch Gazebo will all the necessary files for simulating Subjugator,
+follow these steps:
+
+1. Open a terminal window and execute the following command. This command uses
+ ROS to start all the relevant ROS nodes and to load the world file for
+ subjugator. This also starts a Gazebo Sever.
+
+ ```bash
+ roslaunch subjugator_launch gazebo.launch --screen
+ ```
+
+ :::{note}
+ `--screen` forces all ROS node output to the screen. It is used for debugging.
+ :::
+
+1. Then in another terminal window run this command to start the Gazebo
+ graphical client, which connects to the Gazebo Sever.
+
+ ```bash
+ gazebogui
+ ```
+
+1. Then in another terminal window run this command and then press Shift-C to
+ unkill the sub to allow movement.
+
+ ```bash
+ amonitor kill
+ ```
+
+1. Execute the following command to start a specific mission, replacing
+ "StartGate2022" with the name of the desired mission:
+
+ ```bash
+ mission run StartGate2022
+ ```
+
+## How to use Gazebo
+
+### User Interface
+
+When you launch Gazebo you will be greeted by its user interface.
+
+![Gazebo Interface](/images/gazebo/Interface.png)
+
+The Gazebo interface consists of three main sections: The **Left Panel**, the
+**Scene**, and the **Right Panel**. By default the Right Panel is hidden. This
+is because we do not have anything selected. To show the right panel you can
+always Click and drag the bar on the right to open it.
+
+![Gazebo Labeled Interface](/images/gazebo/Labeled_Interface.png)
+
+#### Left Panel
+
+The Left Panel has three tabs, each with different features. You can see
+these tabs at the top of the Left Panel. You can click on them to switch
+between them. The tabs are:
+
+##### World Tab
+
+The World Tab displays the models that are currently in the scene. Within this
+tab, you can view and modify various model parameters, like their pose (their
+position and rotation). Additionally, you can expand the GUI option to adjust
+the camera view angle by modifying the camera pose.
+
+##### Insert Tab
+
+The Insert Tab allows you to add new models (objects) to the Gazebo simulation.
+Here, you will find a list of file paths where your models are saved. To view
+the model list, click on the arrow located on the left side of each path to
+expand the folder. Select the desired model and click again in the scene to
+place it.
+
+##### Layers Tab
+
+The Layers tab organizes and displays different visualization groups within
+the simulation. Layers can contain one or more models, and enabling or disabling
+a layer will show or hide all the models within it. While not mandatory, layers
+can be helpful for organizing your simulation. Note that this tab may be empty
+if no layers are defined.
+
+To define a layer, you will need to edit a model's SDF file. To add an
+object's visuals to a layer you will need to add a `` tag for information
+and then a `` tag with the layer number under each `` tag. Below
+is an example:
+
+```xml
+
+
+ 0
+
+ ...
+
+```
+
+#### Scene
+
+The Scene is the main window where objects are animated, and you interact with
+the environment. Two toolbars are available:
+
+##### The Upper Toolbar
+
+The Upper Toolbar consists of various buttons that allow you to select, move,
+rotate, and scale objects. It also provides options to create simple shapes,
+as well as copy and paste objects.
+
+![Gazebo Upper Toolbar](/images/gazebo/Upper_Toolbar.png)
+
+##### The Bottom Toolbar
+
+The Bottom Toolbar displays information about the simulation time and its
+relationship to real time. It helps you track the progress of your simulation.
+
+![Gazebo Bottom Toolbar](/images/gazebo/Bottom_Toolbar.png)
+
+#### Right Panel
+
+The Right Panel is used to interact with the mobile parts (joints) of a
+selected model. It provides controls and settings specific to manipulating
+the joints of a model.
+
+#### Menus (File, Edit, Camera, View, Window, Help)
+
+Most Linux apps have menus. These menus are usually tabs (file, edit, ...) at
+the top left of an application. If you don't see it move your cursor to the
+top of the application window and the menus should appear. Below describes
+the features of each menu that Gazebo has.
+
+![Gazebo Menus](/images/gazebo/Menus.png)
+
+#### Mouse
+
+It is recommended that you use a mouse when using Gazebo. Below is a diagram
+showing all the mouse controls.
+
+![Gazebo Mouse Controls](/images/gazebo/Mouse.png)
+
+However, if you want to use a trackpad you can. Below are the controls for
+the trackpad:
+
+![Gazebo Mouse Controls](/images/gazebo/TrackPad.png)
+
+## How to Create Models
+
+The structure for most models in Gazebo is that the model is a folder that
+contains a .config file, .SDF file(s), and .dae or .stl file(s). The config
+file contains meta information about the model. The .SDF file contains
+important simulation information like model definitions, the model's
+positioning, its physical properties, etc. The .dae or .stl files contain 3D
+mesh information. When creating a model it's recommended that you have all
+these components.
+
+### Model Editor
+
+You can use the **Model Editor** to create simple models all within Gazebo,
+but for more complex models you will want to create/write your own SDF files
+and .dae files.
+
+To enter the **Model Editor**, click on Edit in the menu bar and select Model Editor.
+
+The Model Editor Interface looks similar to the regular Gazebo UI with some
+slight changes. The left panel and the top toolbar have been changed to
+contain only buttons and features for editing and creating parts of a model.
+The bottom toolbar is now hidden as the simulation is paused.
+
+![Gazebo Model Editor](/images/gazebo/Model_Editor.png)
+
+When entering the Model Editor all other models will turn white. This can make
+it hard to see the model you are currently working on if you have a lot of
+models in your scene. So it may be easier to open a blank Gazebo world and
+create the model using the Model Editor there. Then when you exit the Model
+Editor it will ask you to save the model. This will save the model as a folder
+on your computer. Then you can go back to the original world and insert
+this model, by going to the insert tab (note this is the regular insert tab,
+not the one in the model editor) and adding that model folder's file path.
+
+:::{note}
+When inserting a model, make sure that the file path you pick is the path to
+the parent directory. This directory contains the model folder you want to
+insert. Do not put the path to the model folder. Often this parent
+directory will contain all the models you want to use. The file hierarchy
+might look like this: where models is the parent directory and contains the
+models model1 and buoys.
+
+```
+ models/
+ ├── model_1/
+ │ ├── model.config
+ │ ├── model1.sdf
+ │ ├── model1.dae
+ │ └── ...
+ ├── buoys/
+ │ ├── model.config
+ │ ├── green.sdf
+ │ ├── red.sdf
+ │ └── ...
+```
+
+:::
+
+#### Insert Tab
+
+The Insert Tab allows you to add new parts, including links and models, to
+your model. You have two options for inserting shapes (links):
+
+* Simple Shapes: Click on the desired shape in the left panel and then click
+ again in the scene to place it.
+
+* Custom Shapes: You can add COLLADA (.dae), 3D Systems (.stl), Wavefront
+ (.obj), and W3C SVG (.svg) files as custom shapes. Create these shapes
+ using 3D modeling software like Blender.
+
+You can also insert other models into your model as nested models. These
+models can be obtained from the Gazebo Model Database
+(http://gazebosim.org/models/), which should be listed as one of your file
+paths under Model Databases. For example, if you need a depth sensor, you can
+add a depth sensor model from the database to your model.
+
+#### Model Tab
+
+The Model Tab displays the settings for the model you are creating. Here, you
+can change the model's name and modify its basic parameters. Additionally, you
+can add plugins to give your model functionality here as well.
+
+#### Placing Shapes
+
+Once you insert a shape, you can use the toolbar to move, rotate, and scale
+it. For finer control, you can double-click the shape or right-click and
+select "Open Link Inspector" to access the link inspector. In the link
+inspector, you can modify the shape's position, rotation, and scale to
+achieve the desired configuration. Make sure to adjust the scale in both the
+Visual and Collision tabs.
+
+#### Adding Joints
+
+To constrain the motion between shapes, you can add joints. Follow these steps:
+
+* Click on the joint icon in the toolbar (a line connecting two points).
+
+* In the Joint Creation Window, select the parent and child links (shapes)
+ of the joint.
+
+* Select the type of joint you need in the Joint Types section near the top
+ of the window.
+
+* Select the joint axis. Some joints do not have an axis.
+
+* Align the link (shape). Use the align links section to align the parent
+ and the child with each other.
+
+#### Adding a Plugin
+
+To control your model, you need to create a plugin. You can do this in the
+Model Tab by specifying the necessary details for the plugin.
+
+You can find more information on how to create your own custom plugins [here](https://classic.gazebosim.org/tutorials?tut=ros_gzplugins).
+
+#### Model Creation Workflow Example
+
+To illustrate the model creation process, let's consider creating a car model
+using Blender:
+
+* Create .dae files for the wheels, chassis, and other parts in Blender.
+
+* Insert these shapes into the Model Editor.
+
+* Use the toolbar and link inspector to position each shape precisely.
+
+* Add joints between the shapes to enable motion constraints.
+
+* Finally, create a plugin to control the model's behavior.
+
+### World File
+
+A World in Gazebo is used to describe the collection of robots and objects,
+and global parameters like the sky, ambient light, and physics properties.
+A World is the entire virtual environment that you have been
+working in. The World stores important information like where all the models
+are, their properties, and important global properties.
+
+You can save the World file by selecting File and Save World As.
+
+:::{note}
+When using roslaunch to start Gazebo, it is crucial to update the World file
+if you make any changes to the simulation environment. At MIL, there is a
+dedicated `worlds` folder where Gazebo World files are saved. When you update
+a World file, ensure that you replace the old file in this folder. Failing
+to do so will result in the continued use of the old World file when
+launching Gazebo using roslaunch.
+:::
+
+## More Info
+
+If you ever need more information on how any aspect of Gazebo works or how to
+use ROS with Gazebo you can check out the official Gazebo Documentation [here](https://classic.gazebosim.org/tutorials).
+Some of the images used in this guide are sourced from here and we are grateful
+to the creators for their exceptional work, which has been instrumental in
+writing this guide.
diff --git a/docs/software/index.rst b/docs/software/index.rst
index 61b036c7c..9e779a0eb 100644
--- a/docs/software/index.rst
+++ b/docs/software/index.rst
@@ -13,10 +13,12 @@ Various documentation related to practices followed by the MIL Software team.
help
zobelisk
asyncio
+ rqt
rostest
Bash Style Guide
C++ Style Guide
Python Style Guide
+ Gazebo Guide
Migrating to ROS Noetic
Installing NVIDIA Drivers for RTX 2080
Installing Ubuntu 18.04 on an M-series Apple computer
diff --git a/docs/software/rqt.md b/docs/software/rqt.md
new file mode 100644
index 000000000..258c43522
--- /dev/null
+++ b/docs/software/rqt.md
@@ -0,0 +1,437 @@
+# Implementing GUI Based Tools Using `rqt`
+
+## Introduction
+
+RQT (ROS Qt-based GUI Framework) is a powerful tool that provides a graphical
+user interface for ROS (Robot Operating System) applications. It allows users
+to monitor ROS topics, interact with nodes, visualize data, and more. This
+document serves as a user guide for understanding and effectively using RQT.
+
+### Benefits of RQT
+
+- **Graphical Interface**: RQT provides a user-friendly graphical interface,
+ making it easier to visualize data, monitor topics, and interact with nodes.
+ This is especially helpful for users who prefer a GUI based approach over
+ the command line.
+
+- **Centralized Tool**: RQT serves as a centralized tool for various ROS tasks,
+ including visualization, configuration, and debugging. This reduces the need
+ to switch between multiple terminals and tools, streamlining the development
+ process.
+
+- **Extensibility**: RQT's plugin architecture allows users to create custom
+ plugins to tailor the tool to their specific application needs. This
+ extensibility significantly enhances RQT's capabilities and to various
+ robotic systems.
+
+- **Integration with ROS**: RQT is tightly integrated with ROS, ensuring
+ seamless interaction with ROS nodes, topics, and services. This integration
+ simplifies the process of debugging and analyzing data in real-time.
+
+## Installation
+
+Before using RQT, make sure you have ROS installed on your system. If ROS is
+not yet installed, follow the official ROS installation guide for your
+operating system. Once ROS is installed, you can install RQT using the package
+manager:
+
+```bash
+sudo apt-get install ros-noetic-rqt
+```
+
+## Getting Started
+
+### Launching RQT
+
+To launch RQT, open a terminal and run the following command:
+
+```bash
+rqt
+```
+
+This command will launch the RQT Graphical User Interface, displaying a
+default layout with available plugins.
+
+## Monitoring Information with RQT
+
+RQT provides various plugins to monitor information published on specific ROS
+topics. The most common plugin for this purpose is the Plot plugin.
+
+### Subscribing to Specific Topics
+
+1. Open RQT using the command mentioned in the previous section.
+1. Click on the "Plugins" menu in the top toolbar and select "Topics" >
+ "Message Publisher."
+1. A new "Message Publisher" tab will appear. Click on the "+" button to
+ subscribe to a specific topic.
+1. Choose the desired topic from the list and click "OK."
+1. Now, you can monitor the information published on the selected topic in
+ real-time.
+
+### Displaying Data with Plot Plugin
+
+1. Open RQT using the command mentioned earlier.
+1. Click on the "Plugins" menu in the top toolbar and select
+ "Visualization" > "Plot."
+1. A new "Plot" tab will appear. Click on the "+" button to add a plot.
+1. Choose the topic you want to visualize from the list, select the message
+ field, and click "OK."
+1. The plot will start displaying the data sent over the selected topic.
+
+## Configuring RQT
+
+RQT allows users to customize its appearance and layout to suit their preferences.
+
+### Theme Configuration
+
+1. Open RQT using the command mentioned earlier.
+1. Click on the "View" menu in the top toolbar and select "Settings."
+1. In the "Settings" window, go to the "Appearance" tab.
+1. Here, you can change the color scheme, font size, and other visual settings.
+
+### Layout Configuration
+
+1. Open RQT using the command mentioned earlier.
+1. Rearrange existing plugins by clicking on their tab and dragging them to
+ a new position.
+1. To create a new tab, right-click on any existing tab and select "Add New Tab."
+1. Drag and drop desired plugins onto the new tab.
+1. Save your customized layout by going to the "View" menu and selecting
+ "Perspectives" > "Save Perspective."
+
+## Writing RQT Plugins
+
+RQT allows users to create their custom plugins to extend its functionality.
+Writing RQT plugins is essential when the available plugins do not fully
+meet your application's requirements.
+
+### Plugin Structure
+
+To write an RQT plugin, you need to follow a specific directory structure and
+include Python or C++ code to implement the plugin's functionality. The
+essential components of an RQT plugin are:
+
+- **Plugin XML (`plugin.xml`)**: This file defines the plugin and its
+ dependencies, specifying essential information like name, description,
+ and which ROS packages it depends on.
+
+- **Python or C++ Code**: This code contains the actual implementation of the
+ plugin's functionality. For Python plugins, the code should extend the
+ `rqt_gui_py::Plugin` class, while for C++ plugins, it should extend the
+ `rqt_gui_cpp::Plugin` class.
+
+### Implementing a Basic Plugin
+
+In this section, we will walk through an example of implementing a basic RQT
+plugin. We will create a simple plugin to display the current time published
+by a ROS topic. The plugin will consist of a label that updates with the
+current time whenever a new message is received.
+
+#### Step 1: Create the Plugin Package
+
+Create a new ROS package for your plugin using the `catkin_create_pkg` command:
+
+```bash
+catkin_create_pkg my_rqt_plugin rospy rqt_gui_py
+```
+
+The package dependencies `rospy` and `rqt_gui_py` are required to work with
+ROS and create a Python-based RQT plugin.
+
+#### Step 2: Implement the Plugin Code
+
+Next, create a Python script for your plugin. In the `src` directory of your
+package, create a file named `my_rqt_plugin.py` with the following content:
+
+```python
+#!/usr/bin/env python
+
+import rospy
+from rqt_gui_py.plugin import Plugin
+from python_qt_binding.QtWidgets import QLabel, QVBoxLayout, QWidget
+from std_msgs.msg import String
+import time
+
+class MyRqtPlugin(Plugin):
+
+ def __init__(self, context):
+ super(MyRqtPlugin, self).__init__(context)
+ self.setObjectName('MyRqtPlugin')
+
+ # Create the main widget and layout
+ self._widget = QWidget()
+ layout = QVBoxLayout()
+ self._widget.setLayout(layout)
+
+ # Create a label to display the current time
+ self._label = QLabel("Current Time: N/A")
+ layout.addWidget(self._label)
+
+ # Subscribe to the topic that provides the current time
+ rospy.Subscriber('/current_time', String, self._update_time)
+
+ # Add the widget to the context
+ context.add_widget(self._widget)
+
+ def _update_time(self, msg):
+ # Update the label with the received time message
+ self._label.setText(f"Current Time: {msg.data}")
+
+ def shutdown_plugin(self):
+ # Unregister the subscriber when the plugin is shut down
+ rospy.Subscriber('/current_time', String, self._update_time)
+
+ def save_settings(self, plugin_settings, instance_settings):
+ # Save the plugin's settings when needed
+ pass
+
+ def restore_settings(self, plugin_settings, instance_settings):
+ # Restore the plugin's settings when needed
+ pass
+
+```
+
+In this code, we create a plugin class named `MyRqtPlugin`, which inherits
+from `rqt_gui_py.Plugin`. The `__init__` method sets up the GUI elements, such
+as a label, and subscribes to the `/current_time` topic to receive time updates.
+
+#### Step 3: Add the Plugin XML File
+
+Create a file named `plugin.xml` in the root of your package to define the
+plugin. Add the following content to the `plugin.xml`:
+
+```xml
+
+
+
+ A custom RQT plugin to display the current time.
+
+
+
+ settings
+ Display the current time.
+
+
+
+```
+
+This XML file defines the plugin class, its description, and the icon to be
+displayed in the RQT GUI.
+
+#### Step 4: Build the Plugin
+
+Build your plugin package using `catkin_make`:
+
+```bash
+catkin_make
+```
+
+#### Step 5: Launch RQT and Load the Plugin
+
+Launch RQT using the command mentioned earlier:
+
+```bash
+rqt
+```
+
+Once RQT is open, navigate to the "Plugins" menu and select "My RQT Plugin"
+from the list. The plugin will be loaded, and you should see the label
+displaying the current time.
+
+### Adding Custom Functionality
+
+The example plugin we implemented is a simple demonstration of how to create
+an RQT plugin. Depending on your application's requirements, you can add more
+sophisticated features, such as custom data visualization, interactive
+controls, and integration with other ROS elements.
+
+You can also improve the plugin by adding error handling, additional options
+for time formatting, and the ability to pause/resume the time updates.
+
+## Implementing a More Advanced Plugin
+
+In this section, we will implement a more advanced RQT plugin that visualizes
+data from multiple ROS topics using a 2D plot. This plugin will allow users to
+choose which topics to plot and specify the message field to display on the X
+and Y axes. Let's call this plugin "ROS Data Plotter."
+
+### Step 1: Create the Plugin Package
+
+Create a new ROS package for your plugin using the `catkin_create_pkg` command:
+
+```bash
+catkin_create_pkg ros_data_plotter rospy rqt_gui_py matplotlib
+```
+
+The package dependencies `rospy`, `rqt_gui_py`, and `matplotlib` are required
+to work with ROS, create a Python-based RQT plugin, and plot data, respectively.
+
+### Step 2: Implement the Plugin Code
+
+Next, create a Python script for your plugin. In the `src` directory of your
+package, create a file named `ros_data_plotter.py` with the following content:
+
+```python
+#!/usr/bin/env python
+
+import rospy
+from rqt_gui_py.plugin import Plugin
+from python_qt_binding.QtWidgets import QWidget, QVBoxLayout, QComboBox
+from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
+from matplotlib.figure import Figure
+from std_msgs.msg import Float32 # Modify this import based on your data type
+
+class ROSDataPlotter(Plugin):
+
+ def __init__(self, context):
+ super(ROSDataPlotter, self).__init__(context)
+ self.setObjectName('ROSDataPlotter')
+
+ # Create the main widget and layout
+ self._widget = QWidget()
+ layout = QVBoxLayout()
+ self._widget.setLayout(layout)
+
+ # Create a combo box to select topics and message fields
+ self._topic_field_selector = QComboBox()
+ self._topic_field_selector.currentIndexChanged.connect(self._update_plot)
+ layout.addWidget(self._topic_field_selector)
+
+ # Create the matplotlib figure and plot
+ self._figure = Figure()
+ self._canvas = FigureCanvas(self._figure)
+ layout.addWidget(self._canvas)
+
+ # Initialize the plot
+ self._update_plot()
+
+ # Add the widget to the context
+ context.add_widget(self._widget)
+
+ def _update_plot(self):
+ # Clear the previous plot data
+ self._figure.clear()
+ axes = self._figure.add_subplot(111)
+
+ # Get the selected topic and field from the combo box
+ selected_topic_field = self._topic_field_selector.currentText()
+ topic, field = selected_topic_field.split(' - ')
+
+ # Subscribe to the selected topic
+ rospy.Subscriber(topic, Float32, self._plot_data) # Modify this based on your data type
+
+ def _plot_data(self, msg):
+ # Get the selected topic and field from the combo box
+ selected_topic_field = self._topic_field_selector.currentText()
+ topic, field = selected_topic_field.split(' - ')
+
+ # Plot the data on the graph
+ # Modify this part based on your data type and plot requirements
+ x_data = rospy.Time.now().to_sec() # Use timestamp as X-axis
+ y_data = getattr(msg, field) # Use the specified field from the message as Y-axis data
+ axes = self._figure.add_subplot(111)
+ axes.plot(x_data, y_data)
+
+ # Refresh the plot
+ self._canvas.draw()
+
+ def shutdown_plugin(self):
+ # Unsubscribe from the topic when the plugin is shut down
+ self._topic_field_selector.clear()
+ rospy.Subscriber(topic, Float32, self._plot_data)
+
+ def save_settings(self, plugin_settings, instance_settings):
+ # Save the plugin's settings when needed
+ pass
+
+ def restore_settings(self, plugin_settings, instance_settings):
+ # Restore the plugin's settings when needed
+ pass
+
+```
+
+In this code, we create a plugin class named `ROSDataPlotter`, which inherits
+from `rqt_gui_py.Plugin`. The `__init__` method sets up the GUI elements,
+such as a combo box to select topics and message fields, and the matplotlib
+figure to plot the data. The `_update_plot` method updates the plot whenever
+a new topic or message field is selected from the combo box. The `_plot_data`
+method is called when new messages are received on the selected topic, and it
+plots the data on the graph.
+
+### Step 3: Add the Plugin XML File
+
+Create a file named `plugin.xml` in the root of your package to define the
+plugin. Add the following content to the `plugin.xml`:
+
+```xml
+
+
+
+ An advanced RQT plugin to plot data from multiple ROS topics.
+
+
+
+ settings
+ Plot data from selected ROS topics.
+
+
+
+```
+
+This XML file defines the plugin class, its description, and the icon
+to be displayed in the RQT GUI.
+
+### Step 4: Build the Plugin
+
+Build your plugin package using `catkin_make`:
+
+```bash
+catkin_make
+```
+
+### Step 5: Launch RQT and Load the Plugin
+
+Launch RQT
+
+ using the command mentioned earlier:
+
+```bash
+rqt
+```
+
+Once RQT is open, navigate to the "Plugins" menu and select "ROS Data Plotter"
+from the list. The plugin will be loaded, and you should see a combo box to
+select topics and fields, and a graph to display the plotted data.
+
+### Adding Custom Functionality
+
+In this more advanced example, we created an RQT plugin that allows users to
+plot data from multiple ROS topics. The example focused on plotting Float32
+messages, but you can modify the `_plot_data` method to handle different
+message types or plot multiple data series on the same graph.
+
+You can further enhance the plugin by adding features like legends, axis
+labels, custom plot styles, data smoothing, and real-time updates.
+Additionally, you can provide options to save the plotted data as CSV
+or images for further analysis.
+
+With custom plugins, you have the freedom to tailor RQT to suit your
+specific application's visualization needs, making it a versatile tool
+for ROS developers.
+
+## External Resources
+
+- [ROS wiki page for rqt](https://wiki.ros.org/rqt)
+- [GeorgiaTech RoboJacket's rqt tutorial](https://www.youtube.com/watch?v=o90IaCRje2I)
+- [Creating Custom rqt Plugins](https://github.com/ChoKasem/rqt_tut)
+
+## Conclusion
+
+Congratulations! You have now learned the basics of using RQT, including
+monitoring information sent over specific topics, configuring RQT to your
+preferences, writing your custom plugins, and understanding the importance
+of using RQT in ROS development. RQT provides a user-friendly and powerful
+graphical interface for ROS applications, making it an essential tool for
+developers and researchers working with ROS-based robotic systems. By
+leveraging RQT's capabilities, you can streamline your development process
+and gain valuable insights into your robot's behavior.