Commit aa097de1 authored by Niels Post's avatar Niels Post
Browse files

- Massive Refactoring. Moved to the Entity Controller Boundary system.

- Removed some high level commands that were to specific. A wireless command should not have to send an amount of steps
- Custom handlers could now be added
- Described the process of adding a custom command handler in the sphinx documentation
- Renamed most of the project to MP-Firm
parent c6a7a99f
......@@ -1194,7 +1194,7 @@ IGNORE_PREFIX =
# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
# The default value is: YES.
GENERATE_HTML = YES
GENERATE_HTML = NO
# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
......@@ -1766,7 +1766,7 @@ EXTRA_SEARCH_MAPPINGS =
# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
# The default value is: YES.
GENERATE_LATEX = YES
GENERATE_LATEX = NO
# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
......
......@@ -31,7 +31,7 @@ extensions = [
"breathe"
]
breathe_default_project = "PMSVRobot"
breathe_default_project = "MPFirm"
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
......
doxygen Doxyfile
sphinx-build -b html -Dbreathe_projects.PMSVRobot=./xml "." "_build"
sphinx-build -b html -Dbreathe_projects.MPFirm=xml "." "_build"
.. PMSV Warehouse Robot documentation master file, created by
.. MP-Firm documentation master file, created by
sphinx-quickstart on Tue Oct 27 13:49:22 2020.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to PMSV Warehouse Robot's documentation!
MP-Firm
================================================
.. toctree::
:maxdepth: 2
:maxdepth: 3
:caption: Contents:
Introduction
==================
This is the software documentation for the firmware for a moving platform robot built for the Warehouse PMSV project (link here).
This page is aimed at developers who want to extend the functionality of MP-Firm or adjust it to their own needs. For instructions on how to use the firmware as-is, please refer to this instructable (todo).
MP-Firm is built in an object-oriented way using the Entity-Controller-Boundary (ECB) architecture. This means that classes are divided in 3 categories:
- *Entities* are objects meant for storing data. Entities have little to no knowledge of application logic.
- *Boundaries* are objects that interact with the user/environment. In the case of MP-Firm most boundaries are classes that control hardware components.
- *Controllers* are classes where actual application logic takes place. Controllers interact with entities and boundaries to make everything work together.
Because of this modularity, expanding MP-Firm to add functionality is rather easy. In the following few sections,
some use cases for expansions are explained, along with some short implementation examples.
Extending MP-Firm
====================
Adding Custom Commands
------------------------
All commands in MP-firm consist of a category ID (3 bits) and a command ID (5 bits).
When adding commands, it is easiest to define a custom category for it.
Adding a category ID
^^^^^^^^^^^^^^^^^^^^^^
First, find a category ID that is not used yet. Category ID's are stored in ``include/entity/CommandData.hpp``.
Add your Category ID to the CommandCategory enum. For this example we'll add a command category FOOBAR = 4.
Creating a command handler
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
After this, you need a way to handle commands in the category. This can be done by extending CommandCategoryController.
An example could be:
.. code-block:: c++
:linenos:
class FooBarCommandController: public CommandCategoryController {
RobotMessage handle(const ControllerMessage &cmd) override {
switch(cmd.command_id) {
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 RobotMessage(cmd.message_id, SuccessCode::UNKNOWN_COMMAND);
}
return RobotMessage(cmd.message_id, SuccessCode::SUCCESS);
}
uint8_t getCategoryID() override {
return static_cast<uint8_t>(CommandCategory::FOOBAR);
}
}
Adding the command handler to MP-Firm
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When the CommandCategoryController is implemented, you just need to make MP-firm aware of it.
This can be done in main.cpp, using the following steps:
Construct the freshly built Controller in setup:
.. code-block:: c++
:linenos:
FooBarCommandController foobarCommandController{};
Add it to the CommandCategoryHandlers array:
.. code-block:: c++
:linenos:
CommandCategoryController *commandCategoryHandlers[] = {
&generalCommandHandler,
&actionCommandHandler,
&measurementCommandHandler,
&foobarCommandController
};
All done! When you send a command with the category id 4, and the command id 1, the robot should send "Foo" over its USB interface.
Communication
==================
......@@ -20,25 +112,25 @@ Communication
Commands
-------------------
.. doxygenstruct:: Command
.. doxygenstruct:: ControllerMessage
:members:
.. doxygenstruct:: ReturnCommand
.. doxygenstruct:: RobotMessage
:members:
Handlers
-------------------
.. doxygenclass:: CommandHandler
.. doxygenclass:: CommandCategoryController
:members:
.. doxygenclass:: GeneralCommandHandler
.. doxygenclass:: GeneralCommandController
:members:
.. doxygenclass:: ActionCommandHandler
.. doxygenclass:: ActionCommandController
:members:
.. doxygenclass:: MeasurementCommandHandler
.. doxygenclass:: MeasurementCommandController
:members:
Indices and tables
......
#pragma once
#include <entity/RobotMessage.hpp>
#include <entity/ControllerMessage.hpp>
/**
* The commandboundary is a boundary used for transmitting and receiving commands.
* The CommandBoundary can be extended and implemented for use with various communication methods, wireless or wired.
*
* It is assumed that communication is two way between 1 controller and 1 robot (it is not a busline)
*/
class CommandBoundary {
public:
/**
* Transmit a returncommand to the Controller
* @param command The command to send
*/
virtual bool sendMessage(const RobotMessage &command) = 0;
/**
* Should check if a command from the controller was received or is available
*/
virtual bool isMessageAvailable() = 0;
/**
* Parse and return the next received command
*
* This method should use Command::validate to make sure a command is valid, and then use Command::parse to parse the data.
*/
virtual ControllerMessage getNextMessage() = 0;
};
\ No newline at end of file
#pragma once
#include <boundary/communication/CommandBoundary.hpp>
#include <RF24.h>
class NRFBoundary: public CommandBoundary {
/// The radio used to communicate with the other party
RF24 nrfRadio;
public:
NRFBoundary(uint16_t pin_ce, uint16_t pin_csn, uint8_t channel, uint64_t readingPipe, uint64_t writingPipe,
bool enableAutoAck = true, bool enableDynamicPayloads = true, uint8_t staticPayloadLength = 0,
rf24_pa_dbm_e paLevel = RF24_PA_MIN);
bool sendMessage(const RobotMessage &command) override;
bool isMessageAvailable() override;
ControllerMessage getNextMessage() override;
};
\ No newline at end of file
#pragma once
#include <cstdint>
class MotorBoundary{
public:
virtual void halt() = 0;
virtual void update() = 0;
virtual void setDirection(bool dir) = 0;
virtual bool startMoveMM(uint16_t mm) = 0;
};
\ No newline at end of file
#pragma once
#include <cstdint>
#include <boundary/movement/MotorBoundary.hpp>
#include "AccelStepper.h"
/**
* A boundary used for a single Stepper Motor
*/
class MotorBoundary {
class AccelStepperBoundary: public MotorBoundary {
/// Stepper motor object that is used to move the motor
AccelStepper stepper;
bool direction = true;
bool direction = true;
uint16_t stepsPerMM;
public:
/**
* Create a MotorBoundary
......@@ -21,7 +23,7 @@ public:
* @param dir_pin Pin number connected to the dir pin of the stepper driver
* @param steps_per_mm The number of steps a motor steps fo moving a millimeter
*/
MotorBoundary(uint8_t enable_pin, uint8_t step_pin, uint8_t dir_pin, uint8_t steps_per_mm);
AccelStepperBoundary(uint8_t enable_pin, uint8_t step_pin, uint8_t dir_pin, uint8_t steps_per_mm);
/**
* Start a movement of a specified amount of steps
......@@ -32,16 +34,18 @@ public:
*/
bool startMoveSteps(uint16_t steps);
bool startMoveMM(uint16_t mm) override;
/**
* Stop all movement currently being processed
*/
void halt();
void halt() override;
/**
* Update the movement currently being processed.
* Usually steps the motor at least once
*/
void update();
void update() override;
/**
* Change the direction this motor will rotate.
......@@ -49,6 +53,6 @@ public:
* Note: this does not change directions for movements that are already started
* @param dir The new directin (true for forward, false for backward.
*/
void setDirection(bool dir);
void setDirection(bool dir) override;
};
\ No newline at end of file
#pragma once
#include <communication/command/Command.hpp>
#include <communication/command/ReturnCommand.hpp>
#include <entity/RobotMessage.hpp>
#include <entity/ControllerMessage.hpp>
/**
* Interface to handle commands in a specific category
*/
class CommandHandler {
class CommandCategoryController {
public:
/**
* Handle a Command
* Get the category ID of commands that this CommandCategoryController handles
* @return The category ID for this Controller
*/
virtual uint8_t getCategoryID() = 0;
/**
* This method is called when a command is received with a category_id equal to getCategoryID().
* @param cmd Command to handle
* @return The result of the command. This should be sent back to the sender of the command
*/
virtual ReturnCommand handle(const Command &cmd) = 0;
virtual RobotMessage handle(const ControllerMessage &cmd) = 0;
};
#pragma once
#include <communication/handler/CommandHandler.hpp>
#include <controller/CommandCategoryController.hpp>
#include <entity/RobotMessage.hpp>
#include <entity/ControllerMessage.hpp>
class MovementController;
......@@ -8,7 +10,7 @@ class MovementController;
/**
* Handles commands in the category \refitem{CommandCategory::ACTION}
*/
class ActionCommandHandler : public CommandHandler {
class ActionCommandController : public CommandCategoryController {
/// MovementController, used for performing movement actions
MovementController &movementController;
public:
......@@ -17,11 +19,13 @@ public:
* @param cmd Command to handle
* @return The result of the action. This should be sent back to the sender of the command
*/
ReturnCommand handle(const Command &cmd) override;
RobotMessage handle(const ControllerMessage &cmd) override;
/**
* Construct the ActionCommandHandler
* @param movementController The robot's movementController
*/
ActionCommandHandler(MovementController &movementController);
ActionCommandController(MovementController &movementController);
uint8_t getCategoryID() override;
};
#pragma once
#include <communication/handler/CommandHandler.hpp>
#include <controller/CommandCategoryController.hpp>
/**
* Handles commands in the category \refitem{CommandCategory::GENERAL}
*/
class GeneralCommandHandler : public CommandHandler {
class GeneralCommandController : public CommandCategoryController {
public:
/**
* Handle a General Command
* @param cmd Command to handle
* @return The result of the command. This should be sent back to the sender of the command
*/
ReturnCommand handle(const Command &cmd) override;
RobotMessage handle(const ControllerMessage &cmd) override;
uint8_t getCategoryID() override;
GeneralCommandController();
};
#pragma once
#include <communication/handler/CommandHandler.hpp>
#include <controller/CommandCategoryController.hpp>
/**
* Handles commands in the category \refitem{CommandCategory::MEASUREMENT}
*/
class MeasurementCommandHandler : public CommandHandler {
class MeasurementCommandController : public CommandCategoryController {
public:
/**
* Handle a Measurement Command
* @param cmd Command to handle
* @return The result of the command, usually containing a measurement. This should be sent back to the sender of the command
*/
ReturnCommand handle(const Command &cmd) override;
RobotMessage handle(const ControllerMessage &cmd) override;
uint8_t getCategoryID() override;
};
#pragma once
#include <RF24.h>
#include <communication/handler/GeneralCommandHandler.hpp>
#include <communication/handler/ActionCommandHandler.hpp>
#include <communication/handler/MeasurementCommandHandler.hpp>
#include <util/StackQueue.hpp>
#include <communication/command/Command.hpp>
#include <communication/command/ReturnCommand.hpp>
#include <entity/StackQueue.hpp>
#include <boundary/communication/CommandBoundary.hpp>
#include "CommandCategoryController.hpp"
class PMSVSettings;
class ReturnCommand;
class RobotMessage;
template <class T, std::size_t N>
constexpr std::size_t array_size(const T (&array)[N]) noexcept
{
return N;
}
/**
* Controller for the wireless communication aspect of the robot.
......@@ -22,33 +25,34 @@ class ReturnCommand;
* The controller then transmits the ReturnCode from the handler to the sender of the command
*/
template<size_t commandQueueMaxLength, size_t handlerCount>
class CommunicationController {
private:
/// The radio used to communicate with the other party
RF24 nrfRadio;
/// A handler for commands in the category GENERAL
GeneralCommandHandler generalCommandHandler;
/// A handler for commands in the category ACTION
ActionCommandHandler actionCommandHandler;
/// A handler for commands in the category MEASUREMENT
MeasurementCommandHandler measurementCommandHandler;
CommandBoundary &commandBoundary;
/// Queue of commands that are still to be handled
StackQueue<Command, 20> commandQueue;
StackQueue <ControllerMessage, commandQueueMaxLength> commandQueue;
///
CommandCategoryController *commandCategoryHandlers[handlerCount];
protected:
/**
* Retrieve the next command from the queue, and handle it.
*/
void handleQueuedCommand();
/**
* Send a ReturnCommand as a result of a Command
* @param returnCommand The returnCommand to send
*/
void sendReturnCommand(const ReturnCommand &returnCommand);
void handleQueuedCommand() {
if (!commandQueue.empty()) {
const auto &command = commandQueue.front();
for (CommandCategoryController *handler: commandCategoryHandlers) {
if (command.category_id == handler->getCategoryID()) {
commandBoundary.sendMessage(handler->handle(command));
commandQueue.pop();
return;
}
}
Serial.println("Command category not recognized");
}
}
/**
* If data is available on RF24, try to parse the data as command, and add it to the commandQueue
......@@ -56,15 +60,32 @@ protected:
* When no data is available, this function returns
* When a command is inproperly formatted, the function automatically transmits the error code BAD_PARAMETERS to the sender
*/
void parseNextCommand();
void parseNextCommand() {
if (commandBoundary.isMessageAvailable()) {
ControllerMessage cmd = commandBoundary.getNextMessage();
if (cmd.isValid()) {
commandQueue.push(cmd);
}
}
}
public:
CommunicationController(const PMSVSettings &settings, MovementController &movementController);
CommunicationController(CommandBoundary &commandBoundary,
CommandCategoryController *commandCatHandlers[handlerCount])
: commandBoundary(commandBoundary), commandQueue() {
for (size_t i = 0; i < handlerCount; i++) {
commandCategoryHandlers[i] = commandCatHandlers[i];
}
}
/**
* Check for received messages and handle them.
*/
void update();
void update() {
parseNextCommand();
handleQueuedCommand();
}
};
......
#pragma once
#include <movement/MotorBoundary.hpp>
#include <communication/command/ReturnCommand.hpp>
#include <entity/RobotMessage.hpp>
#include <boundary/movement/MotorBoundary.hpp>
class PMSVSettings;
class MovementController {
/// Boundary for the motor on the right side of the robot
MotorBoundary leftMotor;
MotorBoundary &leftMotor;
/// Boundary for the motor on the right side of the robot
MotorBoundary rightMotor;
MotorBoundary &rightMotor;
/// Reference to the settings object to use to convert values
const PMSVSettings &settings;
/**
* Try to move both motors a given amount of steps. (Internal use only)
*
* @param steps The amount of steps to move the motors
* @return a ReturnCode determining if the start was successful
*/
ReturnCode tryMoveSteps(long steps);
SuccessCode tryMoveMM(uint16_t mm) const;
public:
/**
* Change the speed of movement.
......@@ -29,7 +22,7 @@ public:
* @param speed New speed of movement
* @return
*/
ReturnCode setSpeed(uint16_t speed);
SuccessCode setSpeed(uint16_t speed);
/**
* Change the direction of movement
......@@ -38,22 +31,15 @@ public:
* @param dir New direction, true being forward, and false backward
* @return
*/
ReturnCode setDirection(bool dir);
SuccessCode setDirection(bool dir);
/**
* Start a movement of a specified distance
*
* @param cm Distance in CM
* @param mm Distance in CM
* @return
*/
ReturnCode startMoveCM(uint16_t cm);
/**
* Start a movement by moving motors a specified amount of steps
* @param steps The amount of steps to move
* @return
*/
ReturnCode startMoveSteps(uint16_t steps);
SuccessCode startMoveMM(uint16_t mm);
/**
* Start a rotation of a specified angle
......@@ -61,21 +47,13 @@ public:
* @param rotate_direction The rotation direction (true for right, false for left)
* @return
*/
ReturnCode startRotateDegrees(uint16_t degrees, bool rotate_direction);