Commit aa3f01bb authored by Marjon Blondeel's avatar Marjon Blondeel
Browse files

Merge branch 'development' into 'master'

Development

See merge request !1
parents e874ea79 6c3e32de
.pio
CMakeListsPrivate.txt
cmake-build-*/
CMakeLists.txt
docs/_build/
docs/xml/
image: alpine
pages:
stage: deploy
script:
- apk update && apk add doxygen graphviz python3 python3-dev py3-pip
- cd docs/
- pip install -r requirements.txt
- doxygen Doxyfile
- sphinx-build -b html -Dbreathe_projects.MPFirm=xml "." "_build"
- rm -rf public && mv _build/ public/
artifacts:
paths:
- public
only:
- development
This diff is collapsed.
This diff is collapsed.
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'PMSV Warehouse Robot'
copyright = '2020, Niels Post'
author = 'Niels Post'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"breathe",
"sphinx_copybutton"
]
breathe_default_project = "MPFirm"
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
\ No newline at end of file
doxygen Doxyfile
sphinx-build -b html -Dbreathe_projects.MPFirm=xml "." "_build"
MP-Firm
============
Introduction
##############
This is the software documentation for the firmware for a moving platform robot built for the Warehouse PMSV project.
The build process, and installation process for the robot and for the controller software can be found in `this <https://www.instructables.com/Building-a-Moving-Platform-Robot-From-Scratch>`_ instructable.
These documentation pages aim to give a more in-depth view in how MP-Firm works, and what it can do.
The base version of MP-firm curently allows the following:
- **Movement** MP-firm can move the Moving Platform using standard stepper motor drivers (Any driver module using a step and direction pin)
- **Communication** A robot using MP-firm can wirelessly receive commands and transmit measurement through RF, using an NRF24l01.
- **Configuration** Wirelessly setting and retrieving values, and storing them in the flash memory across reboots.
.. toctree::
:maxdepth: 2
:caption: Contents:
./pages/general
./pages/installation
./pages/commands
./pages/extending_mpfirm
./pages/editing_and_building_documentation
.. role:: bash(code)
:language: bash
Indices and tables
####################
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
Implemented Commands
======================
Below are the commands currently implemented in MP-firm as of January 2021.
--------------------
General Commands
-------------------
--------------------
REBOOT
^^^^^^^^^^^^
Performs a software reset on the robot.
**Parameters**
None
**Return Data**
None
------------------------
SET_ID
^^^^^^^^^^^^
Used to assign ID's to robots. The command is meant to be sent to all robots at once.
Robots will only respond in the following two cases:
- The robot has no id (ID 0) assigned. The robot responds with SUCCESS, and changes its ID to the one provided.
- The robot already has the requested ID. The robot responds with ID_ALREADY_SET, so the controller can connect mark the id as in use
**Parameters**
.. image:: /_static/SET_ID.png
**Return Data**
None
------------------
Action Commands
------------------
---------------
CANCEL_MOVEMENT
^^^^^^^^^^^^^^^^^^^
Stop all movement currently being processed by the robot.
Avoid this command as much as possible, since it also cancels any movement easing that would usually be performed.
**Parameters**
None
**Return Data**
None
-------------------
START_MOVE_MM
^^^^^^^^^^^^^^^^
Start a movement of a specified amount of millimeters forward or backward.
**Parameters**
.. image:: /_static/START_MOVE_MM.png
**Return Data**
None
-----------------------
START_ROTATE_DEGREES
^^^^^^^^^^^^^^^^^^^^^^
Start rotating a specified amount of degrees left (direction 0) or right (direction 1)
**Parameters**
.. image:: /_static/START_ROTATE_DEGREES.png
**Return Data**
None
Measurement Commands
--------------------------
-------------------------
GET_MEASUREMENT_RAW (unimplemented)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-------------------------
GET_DISTANCE_SENSOR (unimplemented)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
--------------------------------
Configuration Commands
-------------------------
-------------------------------
SET_VALUE
^^^^^^^^^^^^^^
Set a configuration to a new value.
Note that depending on the type of the value, data may consist of more than one byte (see GET_INFO).
**Parameters**
.. image:: /_static/START_ROTATE_DEGREES.png
**Return Data**
None
-----------------
GET_VALUE
^^^^^^^^^^^^
Get the current value of a configuration.
Depending on the type of the value, return data length may vary (See GET_INFO).
**Parameters**
.. image:: /_static/GET_VALUE.png
**Return Data**
-------------------------
LOAD
^^^^^^^^^^^^
Reload all configuration values from the flash memory of the robot. The robot automatically does this at startup.
**Parameters**
None
**Return Data**
None
---------------------
STORE
^^^^^^^^^^
Store the current configuration values to the flash memory.
**Parameters**
None
**Return Data**
None
------------------------
PRINT_ALL
^^^^^^^^^^^^^^^^
Print the names of all configuration values. Note that printing happens on the robot, through its serial interface.
**Parameters**
None
**Return Data**
None
------------------------
GET_INFO
^^^^^^^^^^^^^^^^
Get the type and name of a configurationvalue.
Available Types:
- 'f': 4 byte floating point
- 'c': 1 byte unsigned integer
- 'i': 4 byte signed integer
- 'b': 1 byte boolean (only looks at the lsb)
**Parameters**
.. image:: /_static/GET_INFO.png
**Return Data**
.. image:: /_static/GET_INFO_RETURN.png
---------------------
GET_CONFIGURATION_COUNT
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Get the amount of configurationvalues that are used by the robot. It can be assumed that when the robot
has N configuration values, all configuration ids are between 1 and N.
**Parameters**
None
**Return Data**
.. image:: /_static/GET_CONFIGCOUNT_RETURN.png
Changing and building the documentation
=========================================
Generating this documentation page is done using Doxygen and Sphinx, using Breathe to combine the two.
Should you want to make your own version of this page, this chapter explains how.
Requirements
""""""""""""""""
Before starting, you need the following.
- `Python 3 and Pip <https://www.python.org/downloads/>`_
- `Doxygen <https://www.doxygen.nl/download.html>`_
When both are installed, run the following to install the python packages required:
.. code-block:: bash
cd docs
pip install -r requirements.txt
After this, you should be ready to go
Changing the documentation
""""""""""""""""""""""""""""""
The main structure of the documentation is defined in ``docs/index.rst``. In here, multiple sections are included from ``docs/pages``.
All documentation is written in RestructuredText, with some extensions of it being added by sphinx and breathe.
While writing documentation, you can reference the following manuals:
- `Sphinx RST cheatsheet <https://thomas-cokelaer.info/tutorials/sphinx/rest_syntax.html>`_.
- `Breathe RST directives <https://breathe.readthedocs.io/en/latest/directives.html>`_.
Building the documentation
""""""""""""""""""""""""""""
To build the documentation, you need to execute two steps. Both of these steps need to be executed in the ``docs/`` folder.
These steps are automatically executed by ``docs/documentation.bat``
1. First, use doxygen to create the XML files Breathe uses to document code:
.. code-block:: bash
doxygen Doxyfile
2. Next, generate the HTML pages using sphinx:
.. code-block:: bash
sphinx-build -b html -Dbreathe_projects.MPFirm=xml "." "_build"
The documentation should now be generated in ``docs/_build``. To view the documentation, just open ``docs/_build/index.html``
\ No newline at end of file
.. toctree::
:maxdepth: 2
:caption: Contents:
Extending MP-Firm
========================
Introduction
##################
This page gives information on how to get started extending the functionality of MP-Firm.
Adding a communication method
################################
By default, MP-Firm uses RF to communicate through an `NRF24l01 chip <https://www.nordicsemi.com/Products/Low-power-short-range-wireless/nRF24-series/>`_.
To make it easy to use different communication methods, MP-Firm has a boundary class called the CommunicationBoundary. This boundary is
responsible for all messages that are sent and received. To use a different communication method, you just need to create a CommunicationBoundary for it,
and make MP-Firm aware of it.
In the following example, we will be creating a CommunicationBoundary that uses the Arduino UART interface to communicate.
This could be used to receive and send commands over the USB interface.
Creating the MessageBoundary
""""""""""""""""""""""""""""""
A CommunicationBoundary has the following methods:
.. doxygenclass:: BaseCommunicationBoundary
:members:
:outline:
An example of a USB CommunicationBoundary could be:
.. code-block:: c++
:linenos:
class UartCommunicationBoundary: public MessageBoundary {
UARTClass &interface;
public:
UartCommunicationBoundary(UARTClass &anInterface);
private:
bool sendMessage(const Response &command) override{
// Write all command data
auto size_written = interface.write((const uint8_t *) (&command), command.size());
// Check if the bytes written matches the command size to determine success
return size_written == command.size();
}
bool isMessageAvailable() override() {
// Check if there are enough bytes available for a command header
return interface.available() >= 2;
}
Command getNextMessage() override {
uint8_t data[ControllerMessage::maxMessageSize() + 1];
// Read the next command
// Note that in this boundary, we use a ~ to indicate the end of a command,
// This is because UART itself does not indicate ends of messages
// We add one to the maximum message size, since the ~ also needs to be read
size_t length = interface.readBytesUntil('~', data, ControllerMessage::maxMessageSize() + 1);
// Parse the data.
// If ill-formatted, parse() will automatically return an INVALID command.
return Command::parse(data, length);
}
};
Adding the created CommunicationBoundary to MP-firm
""""""""""""""""""""""""""""""""""""""""""""""""""""""""
When the CommunicationBoundary is done, you just need to tell MP-firm to use it when communicating.
This is done in ``main.cpp``.
First, create the boundary object. In this case we also pass Arduino's ``Serial`` Object, to tell the boundary to use the standard Serial interface (USB).
While doing this, you could remove the CommunicationBoundary used before, since it is no longer needed.
.. code-block:: c++
:linenos:
UartCommunicationBoundary uartBoundary{Serial};
Finally, pass the created boundary to the CommunicationController during its construction.
Look up the following line:
.. code-block:: c++
:linenos:
CommunicationController communicationController{nrfBoundary, commandControllers, lengthof(commandControllers)};
Change it to use your boundary instead
.. code-block:: c++
:linenos:
CommunicationController communicationController{uartBoundary, commandControllers, lengthof(commandControllers)};
---------------------
Adding New Commands
################################
All commands in MP-firm are identified by the combination of a category ID (3 bits) and a command ID (5 bits).
The easiest way to add functionality to MP-firm is to create a new command category and add your custom commands to it.
This chapter describes the process for this.
Adding a category ID
""""""""""""""""""""""""
First, find a category ID that is not used yet. Category ID's are stored in ``include/communication/CommandRegistry.hpp`` in the ``CommandCategory`` enum class.
Add your Category ID to the CommandCategory enum. For this example we'll add a command category FOOBAR = 4.
The result might look as follows:
.. code-block:: c++
:linenos:
enum class CommandCategory : uint8_t {
GENERAL = 0,
ACTION = 1,
MEASUREMENT = 2,
FOOBAR = 4
};
While editing this file, it is recommended to add an enum for the commands in your custom category.
This enum can later be used to easily check which command was sent.
For the example, our category only accepts two commands, so the enum may look like:
.. code-block:: c++
:linenos:
enum FooBarCommand {
FOO = 0,
BAR = 1,
};
Creating a command controller
""""""""""""""""""""""""""""""""
After this, you need a way to handle commands in the category. This can be done by extending BaseCommandController.
A CommandController has the following methods:
.. doxygenclass:: BaseCommandController
:members:
:outline:
An example could be:
.. code-block:: c++
:linenos:
class CommandController_FooBar: public BaseCommandController {
RobotMessage handle(const Command &cmd) override {
switch(static_cast<FooBarCommand>(cmd.command_id)) { // The
case 1: // Print Foo when command 1 was issued
Serial.println("Foo");
break;
case 2: // Print Bar when command 1 was issued
Serial.println("Bar");
break;
default: // Unknown command; Return an error code
return Response(cmd.message_id, SuccessCode::UNKNOWN_COMMAND);
}
return Response(cmd.message_id, SuccessCode::SUCCESS);
}
uint8_t getCategoryID() override {