Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Line Following with Agent-Based Decision Making #147

Merged
merged 39 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c11acae
Add Training State to APP Reinforcement Learning
Aug 5, 2024
8a75912
Handle failure to send data over the serial Mux protocol
Aug 5, 2024
e79d46f
Delete unnecessary functions and add a timer to handle state changes
Aug 5, 2024
cd3542b
Add constraint to SetSpeedTarget function to limit motor speeds betwe…
Aug 5, 2024
e79697f
Add Agent, TrajectoryBuffer, and Networks classes, and integrate them…
Aug 5, 2024
4976b06
Add new Command ID
Aug 5, 2024
5c5b28d
Add missing comment
Aug 6, 2024
e66ae2f
resolve pylint import error and correct comments
Aug 6, 2024
93d2b39
Add plotting script to analyze training logs
Aug 7, 2024
849771d
Add comments and fix import errors
Aug 8, 2024
0a5001c
added comments, renamed variables, and updated values
Aug 19, 2024
0de39f7
remove trailing whitespace from code file
Aug 19, 2024
cecb427
Reset sendLineSensorsData function to its original state to fix issue…
Aug 22, 2024
4e2aec2
Fix missing semicolon
Aug 22, 2024
2935cb3
Apply coding style corrections
Aug 26, 2024
5002ecd
Add comments and refactor functions from agent to trajectory_buffer c…
Aug 26, 2024
d4f6d5a
Replace match-case with if-else in RobotController class for Python 3…
Aug 26, 2024
da2ac90
Replace member variable with local variable in loop() to prevent side…
Sep 3, 2024
d39d111
Increase the duration of the selected mode and the duration to handle…
Sep 3, 2024
812fbb5
Rename classes, fix bugs in calculate_advantages, and update hyperpar…
Sep 3, 2024
f8f5bc2
Correct return type of getSelectedMode function in ReadyState class
Sep 3, 2024
b399688
improve code formatting and update comments for clarity
Sep 9, 2024
0260fad
Add conditions to verify and identify failed data
Sep 9, 2024
2add9fa
refactor: enhance clarity in RL_Supervisor, Agent, and networks code
Sep 9, 2024
437d25c
remove unnecessary pylint disable comment from trajectory_buffer
Sep 9, 2024
96c0b49
add pylint disable for import-error due to Python 3.9/3.10 library co…
Sep 9, 2024
f496711
corrected incorrect variable initialization
Sep 9, 2024
6537e80
Use a requirements file to cache pip
gabryelreyes Sep 10, 2024
51bb66a
Limit python linting to 3.9 and 3.11
gabryelreyes Sep 10, 2024
6463269
Remove disable pylint import errors
gabryelreyes Sep 10, 2024
d652a06
Merge pull request #154 from BlueAndi/dev/test_reqs_ci
akrambzeo Sep 10, 2024
eb7643c
Refactored conditional logic by replacing with if-else blocks for bet…
Sep 10, 2024
3da1583
Refactored conditional logic by replacing with if-else blocks for bet…
Sep 10, 2024
fe2e802
Merge branch 'feature/RL' of https://github.com/BlueAndi/RadonUlzer i…
Sep 10, 2024
736d4e8
Add requiremen.txt for python
Sep 10, 2024
9a0a78b
add fixed package versions in requirements.txt
Sep 10, 2024
fc76987
Add more dependencies to requirements.txt Python
Sep 10, 2024
277885c
Add a comment in agent.py
Sep 10, 2024
4ae6987
refactor: change condition to explicit boolean comparison
Sep 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ jobs:
needs: intro
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11"]
python-version: ["3.9", "3.10"]

# Steps represent a sequence of tasks that will be executed as part of the job.
steps:
Expand All @@ -209,11 +209,11 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: .github/workflows/requirements.txt

- name: Install dependencies
run: |
pip install pylint "git+https://github.com/gabryelreyes/SerialMuxProt.git#egg=SerialMuxProt&subdirectory=python/SerialMuxProt"
run: pip install -r .github/workflows/requirements.txt

- name: Analysing the code with pylint
run: |
pylint ./webots/controllers/*/*.py
run: pylint ./webots/controllers/*/*.py
10 changes: 10 additions & 0 deletions .github/workflows/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
platformio==6.1.15
numpy==1.26.4
tensorflow==2.10.0
tensorflow-probability==0.15.0
keras==2.10.0
matplotlib==3.9.0
pandas==2.2.2
pytest==8.3.2
pylint==3.2.7
git+https://github.com/gabryelreyes/SerialMuxProt.git#egg=SerialMuxProt&subdirectory=python/SerialMuxProt
78 changes: 55 additions & 23 deletions lib/APPReinforcementLearning/src/App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include <Odometry.h>
#include "ReadyState.h"
#include <Logging.h>
#include "TrainingState.h"

/******************************************************************************
* Compiler Switches
Expand Down Expand Up @@ -95,6 +96,7 @@ void App::loop()
{
Board::getInstance().process();
Speedometer::getInstance().process();
bool isDataSent = true;

if (true == m_controlInterval.isTimeout())
{
Expand Down Expand Up @@ -123,35 +125,56 @@ void App::loop()
payload = {SMPChannelPayload::Status::DONE};
}

/* Ignoring return value, as error handling is not available. */
(void)m_smpServer.sendData(m_serialMuxProtChannelIdStatus, &payload, sizeof(payload));
isDataSent = m_smpServer.sendData(m_serialMuxProtChannelIdStatus, &payload, sizeof(payload));
akrambzeo marked this conversation as resolved.
Show resolved Hide resolved
akrambzeo marked this conversation as resolved.
Show resolved Hide resolved

m_statusTimer.restart();
}

/* Send periodically line sensor data. */
if (true == m_sendLineSensorsDataInterval.isTimeout() &&
(&DrivingState::getInstance() == m_systemStateMachine.getState()))
if (false == isDataSent)
{
sendLineSensorsData();

m_sendLineSensorsDataInterval.restart();
/* Failed to send Status data to the Supervisor. Go to error state. */
ErrorState::getInstance().setErrorMsg("SD_SF");
m_systemStateMachine.setState(&ErrorState::getInstance());
}

/* Send Mode selected to The Supervisor. */
if (&ReadyState::getInstance() == m_systemStateMachine.getState() && (false == m_modeSelectionSent))
else
{
uint8_t mode_options = ReadyState::getInstance().getSelectedMode();

if (0U < mode_options)
/* Send periodically line sensor data. */
if (true == m_sendLineSensorsDataInterval.isTimeout() &&
(&DrivingState::getInstance() == m_systemStateMachine.getState()))
{
SMPChannelPayload::Mode payload =
(1 == mode_options) ? SMPChannelPayload::Mode::DRIVING_MODE : SMPChannelPayload::Mode::TRAINING_MODE;
isDataSent = sendLineSensorsData();

/* Ignoring return value, as error handling is not available. */
(void)m_smpServer.sendData(m_serialMuxProtChannelIdMode, &payload, sizeof(payload));

m_modeSelectionSent = true;
m_sendLineSensorsDataInterval.restart();
}
if (false == isDataSent)
{
/* Failed to send Sensor data to the Supervisor. Go to error state. */
ErrorState::getInstance().setErrorMsg("SEND_SF");
m_systemStateMachine.setState(&ErrorState::getInstance());
}
else
{
/* Send Mode selected to The Supervisor. */
if (&ReadyState::getInstance() == m_systemStateMachine.getState() && (false == m_modeSelectionSent))
{
uint8_t mode_options = ReadyState::getInstance().getSelectedMode();

if (0U < mode_options)
{
SMPChannelPayload::Mode payload =
(1 == mode_options) ? SMPChannelPayload::Mode::DRIVING_MODE : SMPChannelPayload::Mode::TRAINING_MODE;

isDataSent = m_smpServer.sendData(m_serialMuxProtChannelIdMode, &payload, sizeof(payload));

m_modeSelectionSent = true;
}
}
if (false == isDataSent)
{
/* Failed to send Mode data to the Supervisor. Go to error state. */
ErrorState::getInstance().setErrorMsg("MD_SF");
m_systemStateMachine.setState(&ErrorState::getInstance());
}
}
}

Expand All @@ -174,6 +197,13 @@ void App::handleRemoteCommand(const Command& cmd)
m_modeSelectionSent = false;
break;

case SMPChannelPayload::CmdId::CMD_ID_SET_TRAINING_STATE:
Odometry::getInstance().clearPosition();
Odometry::getInstance().clearMileage();

m_systemStateMachine.setState(&TrainingState::getInstance());
break;

default:
/* Nothing to do. */
break;
Expand All @@ -188,12 +218,13 @@ void App::handleRemoteCommand(const Command& cmd)
* Private Methods
*****************************************************************************/

void App::sendLineSensorsData() const
bool App::sendLineSensorsData() const
{
ILineSensors& lineSensors = Board::getInstance().getLineSensors();
uint8_t maxLineSensors = lineSensors.getNumLineSensors();
const uint16_t* lineSensorValues = lineSensors.getSensorValues();
uint8_t lineSensorIdx = 0U;
bool isPayloadSent = true;
LineSensorData payload;

if (LINE_SENSOR_CHANNEL_DLC == (maxLineSensors * sizeof(uint16_t)))
Expand All @@ -206,8 +237,9 @@ void App::sendLineSensorsData() const
}
}

/* Ignoring return value, as error handling is not available. */
(void)m_smpServer.sendData(m_serialMuxProtChannelIdLineSensors, &payload, sizeof(payload));
isPayloadSent = m_smpServer.sendData(m_serialMuxProtChannelIdLineSensors, &payload, sizeof(payload));

return isPayloadSent;
}

bool App::setupSerialMuxProt()
Expand Down
4 changes: 3 additions & 1 deletion lib/APPReinforcementLearning/src/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,10 @@ class App

/**
* Send line sensors data via SerialMuxProt.
*
* @return true if data has been sent, false otherwise.
*/
void sendLineSensorsData() const;
bool sendLineSensorsData() const;

/**
* Copy construction of an instance.
Expand Down
10 changes: 8 additions & 2 deletions lib/APPReinforcementLearning/src/DrivingState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,15 @@ void DrivingState::exit()
m_observationTimer.stop();
}

void DrivingState::setTargetSpeeds(int16_t leftMotor, int16_t rightMotor)
void DrivingState::setTargetSpeeds(int16_t leftSpeed, int16_t rightSpeed)
{
DifferentialDrive::getInstance().setLinearSpeed(leftMotor, rightMotor);
DifferentialDrive& diffDrive = DifferentialDrive::getInstance();
const int16_t MAX_MOTOR_SPEED = diffDrive.getMaxMotorSpeed();
const int16_t MIN_MOTOR_SPEED = 0;
BlueAndi marked this conversation as resolved.
Show resolved Hide resolved
leftSpeed = constrain(leftSpeed, MIN_MOTOR_SPEED, MAX_MOTOR_SPEED);
rightSpeed = constrain(rightSpeed, MIN_MOTOR_SPEED, MAX_MOTOR_SPEED);

DifferentialDrive::getInstance().setLinearSpeed(leftSpeed, rightSpeed);
}

bool DrivingState::isAbortRequired()
Expand Down
62 changes: 7 additions & 55 deletions lib/APPReinforcementLearning/src/ReadyState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,6 @@
* Local Variables
*****************************************************************************/

const uint16_t ReadyState::SENSOR_VALUE_MAX = Board::getInstance().getLineSensors().getSensorValueMax();

/* Initialize the required sensor IDs to be generic. */
const uint8_t ReadyState::SENSOR_ID_MOST_LEFT = 0U;
const uint8_t ReadyState::SENSOR_ID_MIDDLE = Board::getInstance().getLineSensors().getNumLineSensors() / 2U;
const uint8_t ReadyState::SENSOR_ID_MOST_RIGHT = Board::getInstance().getLineSensors().getNumLineSensors() - 1U;

/******************************************************************************
* Public Methods
*****************************************************************************/
Expand All @@ -87,7 +80,8 @@ void ReadyState::entry()
display.print(m_lapTime);
display.print("ms");
}
m_modeTimeoutTimer.start(mode_selected_period);
m_stateTransitionTimer.start(STATE_TRANSITION_PERIOD);
m_modeTimeoutTimer.start(MODE_SELECTED_PERIOD);
m_mode = IDLE;
m_isLastStartStopLineDetected = false;
m_isButtonAPressed = false;
Expand All @@ -99,11 +93,6 @@ void ReadyState::process(StateMachine& sm)
IButton& buttonA = Board::getInstance().getButtonA();
IButton& buttonB = Board::getInstance().getButtonB();

/* Get each sensor value. */
ILineSensors& lineSensors = Board::getInstance().getLineSensors();
uint8_t maxLineSensors = lineSensors.getNumLineSensors();
const uint16_t* lineSensorValues = lineSensors.getSensorValues();

/* Shall the driving mode be released? */
if (true == Util::isButtonTriggered(buttonA, m_isButtonAPressed))
{
Expand Down Expand Up @@ -132,16 +121,11 @@ void ReadyState::process(StateMachine& sm)
;
}

/**Drive forward until START LINE is crossed */
driveUntilStartLineisCrossed();

if (false == (isStartStopLineDetected(lineSensorValues, maxLineSensors)) && (true == m_isLastStartStopLineDetected))
/* Set DrivingState */
if (true == m_stateTransitionTimer.isTimeout())
{
sm.setState(&DrivingState::getInstance());
}
else
{
m_isLastStartStopLineDetected = isStartStopLineDetected(lineSensorValues, maxLineSensors);
m_stateTransitionTimer.restart();
}
}

Expand All @@ -156,7 +140,7 @@ void ReadyState::setLapTime(uint32_t lapTime)
m_lapTime = lapTime;
}

uint8_t ReadyState::getSelectedMode()
ReadyState::Mode ReadyState::getSelectedMode()
{
return m_mode;
}
Expand All @@ -174,45 +158,13 @@ ReadyState::ReadyState() :
m_isButtonAPressed(false),
m_isButtonBPressed(false),
m_modeTimeoutTimer(),
m_stateTransitionTimer(),
m_lapTime(0),
m_isLastStartStopLineDetected(false),
m_mode(IDLE)
{
}

/**Drive forward until START LINE is crossed */
void ReadyState::driveUntilStartLineisCrossed()
{
DifferentialDrive& diffDrive = DifferentialDrive::getInstance();
int16_t top_speed = 2000; /* Set a top speed of 2000 */
int16_t leftMotor = top_speed / 2U; /* Drive at half speed */
int16_t rightMotor = top_speed / 2U; /* Drive at half speed */
diffDrive.setLinearSpeed(leftMotor, rightMotor);
}

bool ReadyState::isStartStopLineDetected(const uint16_t* lineSensorValues, uint8_t length) const
{
bool isDetected = false;
const uint32_t LINE_MAX_30 = (SENSOR_VALUE_MAX * 3U) / 10U; /* 30 % of max. value */
const uint32_t LINE_MAX_70 = (SENSOR_VALUE_MAX * 7U) / 10U; /* 70 % of max. value */

/*
* === = ===
* + + + + +
* L M R
*/
if ((LINE_MAX_30 <= lineSensorValues[SENSOR_ID_MOST_LEFT]) &&
(LINE_MAX_70 > lineSensorValues[SENSOR_ID_MIDDLE - 1U]) &&
(LINE_MAX_70 <= lineSensorValues[SENSOR_ID_MIDDLE]) &&
(LINE_MAX_70 > lineSensorValues[SENSOR_ID_MIDDLE + 1U]) &&
(LINE_MAX_30 <= lineSensorValues[SENSOR_ID_MOST_RIGHT]))
{
isDetected = true;
}

return isDetected;
}

/******************************************************************************
* External Functions
*****************************************************************************/
Expand Down
46 changes: 17 additions & 29 deletions lib/APPReinforcementLearning/src/ReadyState.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@
class ReadyState : public IState
{
public:
/**
* The mode that can be selected.
*/
enum Mode: uint8_t
{
IDLE = 0, /**< No mode has been selected*/
DRIVING_MODE, /**< Driving mode Selected. */
TRAINING_MODE /**< Training mode Selected. */
};

/**
* Get state instance.
*
Expand Down Expand Up @@ -100,24 +110,18 @@ class ReadyState : public IState
/**
* Set the selected mode.
*
* @return It returns 1 if DrivingMode is selected or 2 if TrainingMode is selected.
* @return It returns the selected Mode.
*/
uint8_t getSelectedMode();
Mode getSelectedMode();

protected:
private:
/**
* The mode that can be selected.
*/
enum Mode
{
IDLE = 0, /**< No mode has been selected*/
DRIVING_MODE, /**< Driving mode Selected. */
TRAINING_MODE /**< Training mode Selected. */
};

/** Duration of the selected mode in ms. This is the maximum time to select a mode. */
static const uint32_t mode_selected_period = 200U;
static const uint32_t MODE_SELECTED_PERIOD = 2000U;

/** Duration to handle State changes in ms. */
static const uint32_t STATE_TRANSITION_PERIOD = 2100U;

/**
* The line sensor threshold (normalized) used to detect the track.
Expand Down Expand Up @@ -153,7 +157,7 @@ class ReadyState : public IState
uint32_t m_lapTime; /**< Lap time in ms of the last successful driven round. */
bool m_isButtonAPressed; /**< Is the button A pressed (last time)? */
bool m_isButtonBPressed; /**< Is the button B pressed (last time)? */

SimpleTimer m_stateTransitionTimer; /**< Timer to handle State changes */
/**
* Default constructor.
*/
Expand Down Expand Up @@ -183,22 +187,6 @@ class ReadyState : public IState
* @returns Reference to ErrorState instance.
*/
ReadyState& operator=(const ReadyState& state);

/*
* The robot moves from its current position
* until it crosses and leaves the start line.
*/
void driveUntilStartLineisCrossed();

/**
* Is the start/stop line detected?
*
* @param[in] lineSensorValues The line sensor values as array.
* @param[in] length The number of line sensor values.
*
* @return If start/stop line detected, it will return true otherwise false.
*/
bool isStartStopLineDetected(const uint16_t* lineSensorValues, uint8_t length) const;
};

/******************************************************************************
Expand Down
Loading