added retakeGradeDiterminator and Retakeschduler

This commit is contained in:
Evanovesky
2025-10-05 18:03:54 +02:00
committed by Vincent W
parent 644697326d
commit 81a6df3e2c
7 changed files with 433 additions and 30 deletions

View File

@@ -10,28 +10,14 @@
* [04-09-2025] Wessel T: Implement template
*/
#include <cstdlib>
#include "rclcpp/rclcpp.hpp"
namespace lessons::zero::tmp {
class NodeTemplate : public rclcpp::Node {
public:
NodeTemplate()
: Node("node_template")
{
}
private:
};
} // namespace lessons::zero::template
#include "nodes/RetakeGradeDeterminator.hpp"
int main(int argc,char *argv[]) {
rclcpp::init(argc,argv);
auto node = std::make_shared<lessons::zero::tmp::NodeTemplate>();
auto node = std::make_shared<assignments::one::retake_grade_determinator::RetakeGradeDeterminator>();
rclcpp::spin(node);
rclcpp::shutdown();

View File

@@ -0,0 +1,211 @@
#include "RetakeGradeDeterminator.hpp"
namespace assignments::one::retake_grade_determinator {
RetakeGradeDeterminator::RetakeGradeDeterminator() : Node("retake_grade_determinator") {
this->declare_parameter("grade_collection_amount", 5);
grade_collection_amount_ = this->get_parameter("grade_collection_amount").as_int();
db_manager_ = std::make_unique<DatabaseManager>(this->get_logger());
student_publisher_ = this->create_publisher<g2_2025_interfaces::msg::Student>(
"student_course_management", 10
);
exam_subscriber_ = this->create_subscription<g2_2025_interfaces::msg::Exam>(
"exam_results", 10,
std::bind(
&RetakeGradeDeterminator::retake_exam_results_callback,
this,
std::placeholders::_1
)
);
exam_service_client_ = this->create_client<g2_2025_interfaces::srv::Exams>("grade_calculator_service");
// Create action server for retake requests
retake_action_server_ = rclcpp_action::create_server<RetakeAction>(
this,
"retake_action",
std::bind(&RetakeGradeDeterminator::handle_goal, this, std::placeholders::_1, std::placeholders::_2),
std::bind(&RetakeGradeDeterminator::handle_cancel, this, std::placeholders::_1),
std::bind(&RetakeGradeDeterminator::handle_accepted, this, std::placeholders::_1)
);
}
void RetakeGradeDeterminator::retake_exam_results_callback(
const g2_2025_interfaces::msg::Exam::SharedPtr msg
) {
student_course_combo_.student_name = msg->student_name;
student_course_combo_.course_name = msg->course_name;
data_map_[student_course_combo_].push_back(msg->result);
auto grade_collection_as_ulong = static_cast<unsigned long>(grade_collection_amount_);
if (data_map_[student_course_combo_].size() == grade_collection_as_ulong) {
RCLCPP_INFO(this->get_logger(),
"%s // %s: results sent to calculator",
msg->student_name.c_str(), msg->course_name.c_str()
);
retake_grade_calculator_request(student_course_combo_);
}
}
void RetakeGradeDeterminator::retake_grade_calculator_request(StudentCourse combo) {
if (!exam_service_client_->wait_for_service(std::chrono::seconds(1))) {
RCLCPP_WARN(this->get_logger(), "Service not available");
return;
}
auto request = std::make_shared<g2_2025_interfaces::srv::Exams::Request>();
request->course_name = combo.course_name;
request->student_name = combo.student_name;
request->exam_grades = data_map_[combo];
auto callback = [this, combo](rclcpp::Client<g2_2025_interfaces::srv::Exams>::SharedFuture future)
{
this->retake_grade_calculator_response(future, combo);
};
exam_service_client_->async_send_request(request, callback);
}
void RetakeGradeDeterminator::retake_grade_calculator_response(
rclcpp::Client<g2_2025_interfaces::srv::Exams>::SharedFuture future,
StudentCourse studentCourseCombo
) {
if (!db_manager_ || !db_manager_->is_connected()) {
RCLCPP_WARN(this->get_logger(), "no database connection");
return;
}
auto response = future.get();
auto student_message = g2_2025_interfaces::msg::Student();
student_message.student_name = studentCourseCombo.student_name;
student_message.course_name = studentCourseCombo.course_name;
student_message.timestamp = this->now();
student_publisher_->publish(student_message);
RCLCPP_INFO(this->get_logger(),
"%s // %s passed with grade %d after retake",
studentCourseCombo.student_name.c_str(), studentCourseCombo.course_name.c_str(), response->result
);
db_manager_->store_final_course_result(
studentCourseCombo,
grade_collection_amount_,
response->result
);
}
rclcpp_action::GoalResponse RetakeGradeDeterminator::handle_goal(
const rclcpp_action::GoalUUID & uuid,
std::shared_ptr<const RetakeAction::Goal> goal)
{
(void)uuid;
RCLCPP_INFO(this->get_logger(),
"Received retake goal request for student: %s, course: %s",
goal->student_name.c_str(), goal->course_name.c_str());
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
rclcpp_action::CancelResponse RetakeGradeDeterminator::handle_cancel(
const std::shared_ptr<GoalHandleRetake> goal_handle)
{
RCLCPP_INFO(this->get_logger(), "Received request to cancel retake goal");
(void)goal_handle;
return rclcpp_action::CancelResponse::ACCEPT;
}
void RetakeGradeDeterminator::handle_accepted(const std::shared_ptr<GoalHandleRetake> goal_handle)
{
// Execute the retake process in a separate thread
std::thread{std::bind(&RetakeGradeDeterminator::execute_retake, this, std::placeholders::_1), goal_handle}.detach();
}
void RetakeGradeDeterminator::execute_retake(const std::shared_ptr<GoalHandleRetake> goal_handle)
{
RCLCPP_INFO(this->get_logger(), "Executing retake action");
const auto goal = goal_handle->get_goal();
auto feedback = std::make_shared<RetakeAction::Feedback>();
auto result = std::make_shared<RetakeAction::Result>();
// Create StudentCourse object from the received action goal
StudentCourse combo;
combo.student_name = goal->student_name;
combo.course_name = goal->course_name;
// Publish feedback
feedback->status = "Processing retake for " + goal->student_name + " in " + goal->course_name;
goal_handle->publish_feedback(feedback);
// Check if we have exam data for this student-course combination
if (data_map_.find(combo) != data_map_.end() && !data_map_[combo].empty()) {
// We have exam data, proceed with retake calculation
feedback->status = "Found exam data, calculating retake grade...";
goal_handle->publish_feedback(feedback);
// Call the grade calculator service with existing exam data
if (!exam_service_client_->wait_for_service(std::chrono::seconds(1))) {
RCLCPP_WARN(this->get_logger(), "Grade calculator service not available");
result->result = -1.0f;
goal_handle->abort(result);
return;
}
auto request = std::make_shared<g2_2025_interfaces::srv::Exams::Request>();
request->course_name = combo.course_name;
request->student_name = combo.student_name;
request->exam_grades = data_map_[combo];
try {
auto service_response = exam_service_client_->async_send_request(request);
auto response = service_response.get();
// Store the new retake grade in database
if (db_manager_ && db_manager_->is_connected()) {
db_manager_->store_final_course_result(
combo,
grade_collection_amount_,
response->result
);
// Publish student message
auto student_message = g2_2025_interfaces::msg::Student();
student_message.student_name = combo.student_name;
student_message.course_name = combo.course_name;
student_message.timestamp = this->now();
student_publisher_->publish(student_message);
result->result = static_cast<float>(response->result);
RCLCPP_INFO(this->get_logger(),
"Retake completed for %s // %s with new grade: %d",
goal->student_name.c_str(), goal->course_name.c_str(), response->result);
goal_handle->succeed(result);
} else {
RCLCPP_WARN(this->get_logger(), "No database connection for storing retake result");
result->result = -1.0f;
goal_handle->abort(result);
}
} catch (const std::exception& e) {
RCLCPP_ERROR(this->get_logger(), "Service call failed: %s", e.what());
result->result = -1.0f;
goal_handle->abort(result);
}
} else {
// No exam data available for retake
RCLCPP_WARN(this->get_logger(),
"No exam data available for retake: %s // %s",
goal->student_name.c_str(), goal->course_name.c_str());
result->result = -1.0f;
goal_handle->abort(result);
}
}
}

View File

@@ -0,0 +1,63 @@
#pragma once
#include <memory>
#include <string>
#include <random>
#include <vector>
#include <chrono>
#include <thread>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "g2_2025_interfaces/msg/exam.hpp"
#include "g2_2025_interfaces/msg/student.hpp"
#include "g2_2025_interfaces/srv/exams.hpp"
#include "g2_2025_interfaces/action/retake.hpp"
#include "database/DatabaseManager.hpp"
#include "database/StudentCourse.hpp"
namespace assignments::one::retake_grade_determinator {
class RetakeGradeDeterminator : public rclcpp::Node {
public:
RetakeGradeDeterminator();
private:
rclcpp::Subscription<g2_2025_interfaces::msg::Exam>::SharedPtr exam_subscriber_;
rclcpp::Publisher<g2_2025_interfaces::msg::Student>::SharedPtr student_publisher_;
rclcpp::Client<g2_2025_interfaces::srv::Exams>::SharedPtr exam_service_client_;
rclcpp::TimerBase::SharedPtr timer_;
// Action server for retake requests
using RetakeAction = g2_2025_interfaces::action::Retake;
using GoalHandleRetake = rclcpp_action::ServerGoalHandle<RetakeAction>;
rclcpp_action::Server<RetakeAction>::SharedPtr retake_action_server_;
std::unique_ptr<DatabaseManager> db_manager_;
StudentCourse student_course_combo_;
StudentCourseResultMap data_map_;
// Params
int grade_collection_amount_;
void retake_grade_calculator_request(StudentCourse combo);
void retake_exam_results_callback(const g2_2025_interfaces::msg::Exam::SharedPtr msg);
void retake_grade_calculator_response(
rclcpp::Client<g2_2025_interfaces::srv::Exams>::SharedFuture future,
StudentCourse studentCourseCombo
);
// Action server callbacks
rclcpp_action::GoalResponse handle_goal(
const rclcpp_action::GoalUUID & uuid,
std::shared_ptr<const RetakeAction::Goal> goal);
rclcpp_action::CancelResponse handle_cancel(
const std::shared_ptr<GoalHandleRetake> goal_handle);
void handle_accepted(const std::shared_ptr<GoalHandleRetake> goal_handle);
void execute_retake(const std::shared_ptr<GoalHandleRetake> goal_handle);
};
} // namespace assignments::one::retake_grade_determinator

View File

@@ -13,25 +13,14 @@
#include <cstdlib>
#include "rclcpp/rclcpp.hpp"
#include "nodes/RetakeScheduler.hpp"
namespace lessons::zero::tmp {
class NodeTemplate : public rclcpp::Node {
public:
NodeTemplate()
: Node("node_template")
{
}
private:
};
} // namespace lessons::zero::template
int main(int argc,char *argv[]) {
rclcpp::init(argc,argv);
auto node = std::make_shared<lessons::zero::tmp::NodeTemplate>();
auto node = std::make_shared<assignments::one::retake_scheduler::RetakeScheduler>();
rclcpp::spin(node);
rclcpp::shutdown();

View File

@@ -0,0 +1,121 @@
#include "RetakeScheduler.hpp"
namespace assignments::one::retake_scheduler {
RetakeScheduler::RetakeScheduler() : Node("retake_scheduler") {
db_manager_ = std::make_unique<DatabaseManager>(this->get_logger());
// Create action client to communicate with RetakeGradeDeterminator
retake_action_client_ = rclcpp_action::create_client<RetakeAction>(this, "retake_action");
// Check for failing students periodically
timer_ = this->create_wall_timer(
std::chrono::seconds(60), // Check every 60 seconds
std::bind(&RetakeScheduler::check_failing_students, this)
);
}
void RetakeScheduler::check_failing_students() {
if (!db_manager_ || !db_manager_->is_connected()) {
RCLCPP_WARN(this->get_logger(), "No database connection for checking failing students");
return;
}
// Get all student-course combinations that need checking
auto pending_combinations = db_manager_->queue_pending_combinations();
for (const auto& combo : pending_combinations) {
int final_grade = db_manager_->get_final_course_grade(combo);
if (final_grade >= 10 && final_grade <= 54) {
RCLCPP_WARN(this->get_logger(),
"Found failing student: %s // %s with grade %d (between 10-54) - initiating retake",
combo.student_name.c_str(), combo.course_name.c_str(), final_grade
);
// Trigger retake scheduler for this student
retake_scheduler(combo);
} else if (final_grade > 0 && final_grade < 10) {
RCLCPP_INFO(this->get_logger(),
"Student %s // %s has grade %d (below 10) - no retake possible",
combo.student_name.c_str(), combo.course_name.c_str(), final_grade
);
}
}
}
void RetakeScheduler::retake_scheduler(StudentCourse combo) {
RCLCPP_INFO(this->get_logger(),
"Scheduling retake for %s in course %s",
combo.student_name.c_str(), combo.course_name.c_str()
);
// Wait for action server to be available
if (!retake_action_client_->wait_for_action_server(std::chrono::seconds(5))) {
RCLCPP_ERROR(this->get_logger(),
"Retake action server not available for %s // %s",
combo.student_name.c_str(), combo.course_name.c_str()
);
return;
}
// Create and send retake goal
auto goal_msg = RetakeAction::Goal();
goal_msg.student_name = combo.student_name;
goal_msg.course_name = combo.course_name;
auto send_goal_options = rclcpp_action::Client<RetakeAction>::SendGoalOptions();
// Set up result callback
send_goal_options.result_callback =
[this, combo](const rclcpp_action::ClientGoalHandle<RetakeAction>::WrappedResult & result) {
switch (result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
RCLCPP_INFO(this->get_logger(),
"Retake successful for %s // %s with new grade: %.2f",
combo.student_name.c_str(), combo.course_name.c_str(),
result.result->result
);
break;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_WARN(this->get_logger(),
"Retake aborted for %s // %s",
combo.student_name.c_str(), combo.course_name.c_str()
);
break;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_WARN(this->get_logger(),
"Retake canceled for %s // %s",
combo.student_name.c_str(), combo.course_name.c_str()
);
break;
default:
RCLCPP_ERROR(this->get_logger(),
"Retake failed for %s // %s with unknown result code",
combo.student_name.c_str(), combo.course_name.c_str()
);
break;
}
};
// Set up feedback callback
send_goal_options.feedback_callback =
[this, combo](
rclcpp_action::ClientGoalHandle<RetakeAction>::SharedPtr,
const std::shared_ptr<const RetakeAction::Feedback> feedback) {
RCLCPP_INFO(this->get_logger(),
"Retake feedback for %s // %s: %s",
combo.student_name.c_str(), combo.course_name.c_str(),
feedback->status.c_str()
);
};
// Send the goal
RCLCPP_INFO(this->get_logger(),
"Sending retake action goal for %s // %s",
combo.student_name.c_str(), combo.course_name.c_str()
);
retake_action_client_->async_send_goal(goal_msg, send_goal_options);
}
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <chrono>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "g2_2025_interfaces/action/retake.hpp"
#include "database/DatabaseManager.hpp"
#include "database/StudentCourse.hpp"
namespace assignments::one::retake_scheduler {
class RetakeScheduler : public rclcpp::Node {
public:
RetakeScheduler();
private:
rclcpp::TimerBase::SharedPtr timer_;
std::unique_ptr<DatabaseManager> db_manager_;
using RetakeAction = g2_2025_interfaces::action::Retake;
rclcpp_action::Client<RetakeAction>::SharedPtr retake_action_client_;
void check_failing_students();
void retake_scheduler(StudentCourse combo);
};
} // namespace assignments::one::retake_scheduler

View File

@@ -1,5 +1,6 @@
string student_name
string course_name
---
---
float32 result
---
string status