generated from wessel/boilerplate
added retakeGradeDiterminator and Retakeschduler
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,5 +1,6 @@
|
||||
string student_name
|
||||
string course_name
|
||||
---
|
||||
---
|
||||
float32 result
|
||||
---
|
||||
string status
|
||||
|
||||
Reference in New Issue
Block a user