To receive notifications about scheduled maintenance, please subscribe to the mailing-list gitlab-operations@sympa.ethz.ch. You can subscribe to the mailing-list at https://sympa.ethz.ch

Commit 2f1f307e authored by beuchatp's avatar beuchatp
Browse files

Added files for Picker node and integrated into student GUI, PPSClient, and...

Added files for Picker node and integrated into student GUI, PPSClient, and Paramter service, needs testing
parent be3de539
......@@ -74,7 +74,7 @@ CoordinatorRow::CoordinatorRow(QWidget *parent, int agentID) :
battery_voltage_standby_empty = 3.30f;
battery_voltage_standby_full = 4.20f;
// > When in a "flying" type of state
battery_voltage_flying_empty = 2.80f;
battery_voltage_flying_empty = 2.60f;
battery_voltage_flying_full = 3.70f;
// SET THE STARTING RADIO STATUS TO BE: DISCONNECTED
......
......@@ -54,6 +54,7 @@
#define MPC_CONTROLLER 4
#define REMOTE_CONTROLLER 5
#define TUNING_CONTROLLER 6
#define PICKER_CONTROLLER 7
// Commands for CrazyRadio
......@@ -75,6 +76,7 @@
#define CMD_USE_MPC_CONTROLLER 4
#define CMD_USE_REMOTE_CONTROLLER 5
#define CMD_USE_TUNING_CONTROLLER 6
#define CMD_USE_PICKER_CONTROLLER 7
#define CMD_CRAZYFLY_TAKE_OFF 11
#define CMD_CRAZYFLY_LAND 12
......@@ -106,6 +108,7 @@
#define LOAD_YAML_MPC_CONTROLLER_AGENT 4
#define LOAD_YAML_REMOTE_CONTROLLER_AGENT 5
#define LOAD_YAML_TUNING_CONTROLLER_AGENT 6
#define LOAD_YAML_PICKER_CONTROLLER_AGENT 7
#define LOAD_YAML_SAFE_CONTROLLER_COORDINATOR 11
#define LOAD_YAML_DEMO_CONTROLLER_COORDINATOR 12
......@@ -113,6 +116,22 @@
#define LOAD_YAML_MPC_CONTROLLER_COORDINATOR 14
#define LOAD_YAML_REMOTE_CONTROLLER_COORDINATOR 15
#define LOAD_YAML_TUNING_CONTROLLER_COORDINATOR 16
#define LOAD_YAML_PICKER_CONTROLLER_COORDINATOR 17
// FOR WHICH BUTTON WAS PRESSED IN THE PICKER CONTOLLER
#define PICKER_BUTTON_GOTOSTART 1
#define PICKER_BUTTON_CONNECT 2
#define PICKER_BUTTON_PICKUP 3
#define PICKER_BUTTON_GOTOEND 4
#define PICKER_BUTTON_PUTDOWN 5
#define PICKER_BUTTON_DISCONNECT 6
#define PICKER_BUTTON_1 11
#define PICKER_BUTTON_2 12
#define PICKER_BUTTON_3 13
#define PICKER_BUTTON_4 14
// Universal constants
#define PI 3.141592653589
......@@ -157,6 +176,7 @@ private slots:
void on_load_mpc_yaml_button_clicked();
void on_load_remote_yaml_button_clicked();
void on_load_tuning_yaml_button_clicked();
void on_load_picker_yaml_button_clicked();
// # Enable controllers
void on_enable_safe_controller_clicked();
......@@ -165,6 +185,7 @@ private slots:
void on_enable_mpc_controller_clicked();
void on_enable_remote_controller_clicked();
void on_enable_tuning_controller_clicked();
void on_enable_picker_controller_clicked();
......@@ -192,6 +213,28 @@ private slots:
void on_tuning_slider_vertical_valueChanged(int value);
void on_tuning_slider_heading_valueChanged(int value);
// Interations with the PICKER controller tab
// > For the buttons
void on_picker_gotostart_button_clicked();
void on_picker_connect_button_clicked();
void on_picker_pickup_button_clicked();
void on_picker_gotoend_button_clicked();
void on_picker_putdown_button_clicked();
void on_picker_disconnect_button_clicked();
void on_picker_1_button_clicked();
void on_picker_2_button_clicked();
void on_picker_3_button_clicked();
void on_picker_4_button_clicked();
// > For the sliders
void on_picker_x_slider_valueChanged(int value);
void on_picker_y_slider_valueChanged(int value);
void on_picker_z_slider_valueChanged(int value);
void on_picker_mass_slider_valueChanged(int value);
// > For the dial
void on_picker_yaw_dial_valueChanged(int value);
private:
......@@ -212,6 +255,7 @@ private:
ros::Timer m_timer_yaml_file_for_mpc_controller;
ros::Timer m_timer_yaml_file_for_remote_controller;
ros::Timer m_timer_yaml_file_for_tuning_controller;
ros::Timer m_timer_yaml_file_for_picker_controller;
int m_student_id;
......@@ -221,6 +265,7 @@ private:
Setpoint m_demo_setpoint;
Setpoint m_student_setpoint;
Setpoint m_mpc_setpoint;
Setpoint m_picker_setpoint;
int m_flying_state;
QMutex m_flying_state_mutex;
......@@ -276,6 +321,19 @@ private:
ros::Publisher tuningHeadingGainPublisher;
// > For the PICKER CONTROLLER
ros::Publisher pickerButtonPressedPublisher;
ros::Publisher pickerZSetpointPublisher;
ros::Publisher pickerYawSetpointPublisher;
ros::Publisher pickerMassPublisher;
ros::Publisher pickerXAdjustmentPublisher;
ros::Publisher pickerYAdjustmentPublisher;
ros::Subscriber pickerSetpointSubscriber;
ros::Publisher demoCustomButtonPublisher;
ros::Publisher studentCustomButtonPublisher;
......@@ -307,10 +365,15 @@ private:
void demoSetpointCallback(const Setpoint& newSetpoint);
void studentSetpointCallback(const Setpoint& newSetpoint);
void mpcSetpointCallback(const Setpoint& newSetpoint);
void pickerSetpointCallback(const Setpoint& newSetpoint);
void remoteDataCallback(const CrazyflieData& objectData);
void remoteControlSetpointCallback(const CrazyflieData& setpointData);
// > For actually sending the button message
void send_picker_button_clicked_message(int button_index);
void DBChangedCallback(const std_msgs::Int32& msg);
......@@ -322,6 +385,7 @@ private:
void mpcYamlFileTimerCallback(const ros::TimerEvent&);
void remoteYamlFileTimerCallback(const ros::TimerEvent&);
void tuningYamlFileTimerCallback(const ros::TimerEvent&);
void pickerYamlFileTimerCallback(const ros::TimerEvent&);
......@@ -338,6 +402,7 @@ private:
void initialize_demo_setpoint();
void initialize_student_setpoint();
void initialize_mpc_setpoint();
void initialize_picker_setpoint();
void disableGUI();
......@@ -348,6 +413,7 @@ private:
void highlightMpcControllerTab();
void highlightRemoteControllerTab();
void highlightTuningControllerTab();
void highlightPickerControllerTab();
bool setpointInsideBox(Setpoint setpoint, CrazyflieContext context);
Setpoint correctSetpointBox(Setpoint setpoint, CrazyflieContext context);
......
......@@ -98,6 +98,16 @@ MainWindow::MainWindow(int argc, char **argv, QWidget *parent) :
tuningHeadingGainPublisher = nodeHandle.advertise<std_msgs::Int32>("TuningControllerService/HeadingGain", 1);
// > For the PICKER CONTROLLER
pickerButtonPressedPublisher = nodeHandle.advertise<std_msgs::Int32>("PickerControllerService/ButtonPressed", 1);
pickerZSetpointPublisher = nodeHandle.advertise<std_msgs::Float32>("PickerControllerService/ZSetpoint", 1);
pickerYawSetpointPublisher = nodeHandle.advertise<std_msgs::Float32>("PickerControllerService/YawSetpoint", 1);
pickerMassPublisher = nodeHandle.advertise<std_msgs::Float32>("PickerControllerService/Mass", 1);
pickerXAdjustmentPublisher = nodeHandle.advertise<std_msgs::Float32>("PickerControllerService/XAdjustment", 1);
pickerYAdjustmentPublisher = nodeHandle.advertise<std_msgs::Float32>("PickerControllerService/YAdjustment", 1);
pickerSetpointSubscriber = nodeHandle.subscribe("PickerControllerService/Setpoint", 1, &MainWindow::pickerSetpointCallback, this);
// subscribers
crazyRadioStatusSubscriber = nodeHandle.subscribe("CrazyRadio/CrazyRadioStatus", 1, &MainWindow::crazyRadioStatusCallback, this);
......@@ -211,6 +221,7 @@ MainWindow::MainWindow(int argc, char **argv, QWidget *parent) :
initialize_demo_setpoint();
initialize_student_setpoint();
initialize_mpc_setpoint();
initialize_picker_setpoint();
}
......@@ -244,8 +255,9 @@ void MainWindow::highlightSafeControllerTab()
ui->tabWidget->tabBar()->setTabTextColor(3, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(4, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(5, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(6, Qt::black);
}
void MainWindow::highlightDemoControllerTab()
void MainWindow::highlightPickerControllerTab()
{
ui->tabWidget->tabBar()->setTabTextColor(0, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(1, Qt::green);
......@@ -253,8 +265,9 @@ void MainWindow::highlightDemoControllerTab()
ui->tabWidget->tabBar()->setTabTextColor(3, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(4, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(5, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(6, Qt::black);
}
void MainWindow::highlightStudentControllerTab()
void MainWindow::highlightDemoControllerTab()
{
ui->tabWidget->tabBar()->setTabTextColor(0, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(1, Qt::black);
......@@ -262,8 +275,9 @@ void MainWindow::highlightStudentControllerTab()
ui->tabWidget->tabBar()->setTabTextColor(3, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(4, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(5, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(6, Qt::black);
}
void MainWindow::highlightMpcControllerTab()
void MainWindow::highlightStudentControllerTab()
{
ui->tabWidget->tabBar()->setTabTextColor(0, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(1, Qt::black);
......@@ -271,8 +285,9 @@ void MainWindow::highlightMpcControllerTab()
ui->tabWidget->tabBar()->setTabTextColor(3, Qt::green);
ui->tabWidget->tabBar()->setTabTextColor(4, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(5, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(6, Qt::black);
}
void MainWindow::highlightRemoteControllerTab()
void MainWindow::highlightMpcControllerTab()
{
ui->tabWidget->tabBar()->setTabTextColor(0, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(1, Qt::black);
......@@ -280,8 +295,9 @@ void MainWindow::highlightRemoteControllerTab()
ui->tabWidget->tabBar()->setTabTextColor(3, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(4, Qt::green);
ui->tabWidget->tabBar()->setTabTextColor(5, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(6, Qt::black);
}
void MainWindow::highlightTuningControllerTab()
void MainWindow::highlightRemoteControllerTab()
{
ui->tabWidget->tabBar()->setTabTextColor(0, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(1, Qt::black);
......@@ -289,7 +305,19 @@ void MainWindow::highlightTuningControllerTab()
ui->tabWidget->tabBar()->setTabTextColor(3, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(4, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(5, Qt::green);
ui->tabWidget->tabBar()->setTabTextColor(6, Qt::black);
}
void MainWindow::highlightTuningControllerTab()
{
ui->tabWidget->tabBar()->setTabTextColor(0, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(1, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(2, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(3, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(4, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(5, Qt::black);
ui->tabWidget->tabBar()->setTabTextColor(6, Qt::green);
}
void MainWindow::DBChangedCallback(const std_msgs::Int32& msg)
{
......@@ -319,6 +347,9 @@ void MainWindow::controllerUsedChangedCallback(const std_msgs::Int32& msg)
case TUNING_CONTROLLER:
highlightTuningControllerTab();
break;
case PICKER_CONTROLLER:
highlightPickerControllerTab();
break;
default:
break;
}
......@@ -364,6 +395,15 @@ void MainWindow::mpcSetpointCallback(const Setpoint& newSetpoint)
ui->current_setpoint_yaw_mpc->setText(QString::number(newSetpoint.yaw * RAD2DEG, 'f', 1));
}
void MainWindow::pickerSetpointCallback(const Setpoint& newSetpoint)
{
m_picker_setpoint = newSetpoint;
// here we get the new setpoint, need to update it in GUI
ui->picker_z_slider->setValue( int(newSetpoint.z) );
ui->picker_yaw_dial->setValue( int(newSetpoint.yaw * RAD2DEG) );
}
void MainWindow::flyingStateChangedCallback(const std_msgs::Int32& msg)
{
// PUT THE CURRENT STATE INTO THE CLASS VARIABLE
......@@ -969,6 +1009,17 @@ void MainWindow::initialize_mpc_setpoint()
this->mpcSetpointPublisher.publish(msg_setpoint);
}
void MainWindow::initialize_picker_setpoint()
{
Setpoint msg_setpoint;
msg_setpoint.x = 0;
msg_setpoint.y = 0;
msg_setpoint.z = 0.4;
msg_setpoint.yaw = 0;
this->pickerSetpointPublisher.publish(msg_setpoint);
}
void MainWindow::on_set_setpoint_button_demo_clicked()
{
Setpoint msg_setpoint;
......@@ -1222,6 +1273,35 @@ void MainWindow::tuningYamlFileTimerCallback(const ros::TimerEvent&)
void MainWindow::on_load_picker_yaml_button_clicked()
{
// Set the "load picker yaml" button to be disabled
ui->load_picker_yaml_button->setEnabled(false);
// Send a message requesting the parameters from the YAML
// file to be reloaded for the picker controller
std_msgs::Int32 msg;
msg.data = LOAD_YAML_PICKER_CONTROLLER_AGENT;
this->requestLoadControllerYamlPublisher.publish(msg);
ROS_INFO("[STUDENT GUI] Request load of picker controller YAML published");
// Start a timer which will enable the button in its callback
// > This is required because the agent node waits some time between
// re-loading the values from the YAML file and then assigning then
// to the local variable of the agent.
// > Thus we use this timer to prevent the user from clicking the
// button in the GUI repeatedly.
ros::NodeHandle nodeHandle("~");
m_timer_yaml_file_for_picker_controller = nodeHandle.createTimer(ros::Duration(1.5), &MainWindow::pickerYamlFileTimerCallback, this, true);
}
void MainWindow::pickerYamlFileTimerCallback(const ros::TimerEvent&)
{
// Enble the "load picker yaml" button again
ui->load_picker_yaml_button->setEnabled(true);
}
void MainWindow::requestLoadControllerYaml_from_my_GUI_Callback(const std_msgs::Int32& msg)
{
// Extract from the "msg" for which controller the YAML
......@@ -1324,6 +1404,21 @@ void MainWindow::requestLoadControllerYaml_from_my_GUI_Callback(const std_msgs::
break;
case LOAD_YAML_PICKER_CONTROLLER_AGENT:
case LOAD_YAML_PICKER_CONTROLLER_COORDINATOR:
// Set the "load picker yaml" button to be disabled
ui->load_picker_yaml_button->setEnabled(false);
// Start a timer which will enable the button in its callback
// > This is required because the agent node waits some time between
// re-loading the values from the YAML file and then assigning then
// to the local variable of the agent.
// > Thus we use this timer to prevent the user from clicking the
// button in the GUI repeatedly.
m_timer_yaml_file_for_picker_controller = nodeHandle.createTimer(ros::Duration(1.5), &MainWindow::pickerYamlFileTimerCallback, this, true);
break;
default:
ROS_INFO("Unknown 'all controllers to load yaml' command, thus nothing will be disabled");
break;
......@@ -1377,6 +1472,13 @@ void MainWindow::on_enable_tuning_controller_clicked()
this->PPSClientCommandPublisher.publish(msg);
}
void MainWindow::on_enable_picker_controller_clicked()
{
std_msgs::Int32 msg;
msg.data = CMD_USE_PICKER_CONTROLLER;
this->PPSClientCommandPublisher.publish(msg);
}
// # Custom command buttons - FOR DEMO CONTROLLER
......@@ -1672,3 +1774,127 @@ void MainWindow::on_tuning_slider_heading_valueChanged(int value)
}
// PICKER CONTROLLER TAB
// > FOR THE BUTTONS
void MainWindow::send_picker_button_clicked_message(int button_index){
// Initialise the message
std_msgs::Int32 msg;
// Set the msg data
msg.data = button_index;
// Publish the message
this->pickerButtonPressedPublisher.publish(msg);
}
void MainWindow::on_picker_gotostart_button_clicked()
{
// Call the function that sends the message
send_picker_button_clicked_message(PICKER_BUTTON_GOTOSTART);
}
void MainWindow::on_picker_connect_button_clicked()
{
// Call the function that sends the message
send_picker_button_clicked_message(PICKER_BUTTON_CONNECT);
}
void MainWindow::on_picker_pickup_button_clicked()
{
// Call the function that sends the message
send_picker_button_clicked_message(PICKER_BUTTON_PICKUP);
}
void MainWindow::on_picker_gotoend_button_clicked()
{
// Call the function that sends the message
send_picker_button_clicked_message(PICKER_BUTTON_GOTOEND);
}
void MainWindow::on_picker_putdown_button_clicked()
{
// Call the function that sends the message
send_picker_button_clicked_message(PICKER_BUTTON_PUTDOWN);
}
void MainWindow::on_picker_disconnect_button_clicked()
{
// Call the function that sends the message
send_picker_button_clicked_message(PICKER_BUTTON_DISCONNECT);
}
void on_picker_1_button_clicked()
{
// Call the function that sends the message
send_picker_button_clicked_message(PICKER_BUTTON_1);
}
void on_picker_2_button_clicked()
{
// Call the function that sends the message
send_picker_button_clicked_message(PICKER_BUTTON_1);
}
void on_picker_3_button_clicked()
{
// Call the function that sends the message
send_picker_button_clicked_message(PICKER_BUTTON_3);
}
void on_picker_4_button_clicked()
{
// Call the function that sends the message
send_picker_button_clicked_message(PICKER_BUTTON_4);
}
// > FOR THE SLIDERS AND DIAL
void on_picker_x_slider_valueChanged(int value)
{
// Initialise the message
std_msgs::Float32 msg;
// Set the msg data
msg.data = float(value) / 100.0f;
// Publish the message
this->pickerXAdjustmentPublisher.publish(msg);
}
void on_picker_y_slider_valueChanged(int value)
{
// Initialise the message
std_msgs::Float32 msg;
// Set the msg data
msg.data = float(value) / 100.0f;
// Publish the message
this->pickerYAdjustmentPublisher.publish(msg);
}
void on_picker_z_slider_valueChanged(int value)
{
// Initialise the message
std_msgs::Float32 msg;
// Set the msg data
msg.data = float(value) / 100.0f;
// Publish the message
this->pickerZSetpointPublisher.publish(msg);
}
void on_picker_mass_slider_valueChanged(int value)
{
// Initialise the message
std_msgs::Float32 msg;
// Set the msg data
msg.data = float(value);
// Publish the message
this->pickerMassPublisher.publish(msg);
}
void on_picker_yaw_dial_valueChanged(int value)
{
// Initialise the message
std_msgs::Float32 msg;
// Set the msg data
msg.data = float(value) * DEG2RAD;
// Publish the message
this->pickerYawSetpointPublisher.publish(msg);
}
......@@ -89,6 +89,7 @@
#define MPC_CONTROLLER 4
#define REMOTE_CONTROLLER 5
#define TUNING_CONTROLLER 6
#define PICKER_CONTROLLER 7
// The constants that "command" changes in the
// operation state of this agent
......@@ -98,6 +99,7 @@
#define CMD_USE_MPC_CONTROLLER 4
#define CMD_USE_REMOTE_CONTROLLER 5
#define CMD_USE_TUNING_CONTROLLER 6
#define CMD_USE_PICKER_CONTROLLER 7
#define CMD_CRAZYFLY_TAKE_OFF 11
......@@ -158,6 +160,8 @@ ros::ServiceClient mpcController;
ros::ServiceClient remoteController;
// The Tuning controller specified in the ClientConfig.yaml
ros::ServiceClient tuningController;
// The Picker controller specified in the ClientConfig.yaml
ros::ServiceClient pickerController;
//values for safteyCheck
......@@ -303,6 +307,7 @@ void loadStudentController();
void loadMpcController();
void loadRemoteController();
void loadTuningController();
void loadPickerController();
void sendMessageUsingController(int controller);
void setInstantController(int controller);
......
......@@ -41,6 +41,7 @@
#define LOAD_YAML_MPC_CONTROLLER_AGENT 4
#define LOAD_YAML_REMOTE_CONTROLLER_AGENT 5
#define LOAD_YAML_TUNING_CONTROLLER_AGENT 6
#define LOAD_YAML_PICKER_CONTROLLER_AGENT 7
#define LOAD_YAML_SAFE_CONTROLLER_COORDINATOR 11
#define LOAD_YAML_DEMO_CONTROLLER_COORDINATOR 12
......@@ -48,6 +49,7 @@
#define LOAD_YAML_MPC_CONTROLLER_COORDINATOR 14
#define LOAD_YAML_REMOTE_CONTROLLER_COORDINATOR 15
#define LOAD_YAML_TUNING_CONTROLLER_COORDINATOR 16
#define LOAD_YAML_PICKER_CONTROLLER_COORDINATOR 17
// For sending commands to the controller node informing
......@@ -63,10 +65,12 @@
#define FETCH_YAML_MPC_CONTROLLER_FROM_OWN_AGENT 4
#define FETCH_YAML_REMOTE_CONTROLLER_FROM_OWN_AGENT 5
#define FETCH_YAML_TUNING_CONTROLLER_FROM_OWN_AGENT 6
#define FETCH_YAML_PICKER_CONTROLLER_FROM_OWN_AGENT 7
#define FETCH_YAML_SAFE_CONTROLLER_FROM_COORDINATOR 11
#define FETCH_YAML_DEMO_CONTROLLER_FROM_COORDINATOR 12
#define FETCH_YAML_STUDENT_CONTROLLER_FROM_COORDINATOR 13
#define FETCH_YAML_MPC_CONTROLLER_FROM_COORDINATOR 14
#define FETCH_YAML_REMOTE_CONTROLLER_FROM_COORDINATOR 15
#define FETCH_YAML_TUNING_CONTROLLER_FROM_COORDINATOR 16
\ No newline at end of file
#define FETCH_YAML_TUNING_CONTROLLER_FROM_COORDINATOR 16
#define FETCH_YAML_PICKER_CONTROLLER_FROM_COORDINATOR 17
\ No newline at end of file
// Copyright (C) 2017, ETH Zurich, D-ITET, Paul Beuchat
//
// This file is part of D-FaLL-System.
//
// D-FaLL-System is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// D-FaLL-System is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with D-FaLL-System. If not, see <http://www.gnu.org/licenses/>.
//
//
// ----------------------------------------------------------------------------------
// DDDD FFFFF L L SSSS Y Y SSSS TTTTT EEEEE M M
// D D F aaa L L S Y Y S T E MM MM
// D D --- FFFF a a L L --- SSS Y SSS T EEE M M M
// D D F a aa L L S Y S T E M M
// DDDD F aa a LLLL LLLL SSSS Y SSSS T EEEEE M M
//
//
// DESCRIPTION:
// Place for students to implement their controller