diff --git a/src/g2_2025_grade_calculator_pkg/src/retake_grade_determinator/main.cpp b/src/g2_2025_grade_calculator_pkg/src/retake_grade_determinator/main.cpp index 7279717..f386358 100644 --- a/src/g2_2025_grade_calculator_pkg/src/retake_grade_determinator/main.cpp +++ b/src/g2_2025_grade_calculator_pkg/src/retake_grade_determinator/main.cpp @@ -10,28 +10,14 @@ * [04-09-2025] Wessel T: Implement template */ -#include - #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(); + auto node = std::make_shared(); rclcpp::spin(node); rclcpp::shutdown(); diff --git a/src/g2_2025_grade_calculator_pkg/src/retake_grade_determinator/nodes/RetakeGradeDeterminator.cpp b/src/g2_2025_grade_calculator_pkg/src/retake_grade_determinator/nodes/RetakeGradeDeterminator.cpp new file mode 100644 index 0000000..58c1956 --- /dev/null +++ b/src/g2_2025_grade_calculator_pkg/src/retake_grade_determinator/nodes/RetakeGradeDeterminator.cpp @@ -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(this->get_logger()); + + student_publisher_ = this->create_publisher( + "student_course_management", 10 + ); + + exam_subscriber_ = this->create_subscription( + "exam_results", 10, + std::bind( + &RetakeGradeDeterminator::retake_exam_results_callback, + this, + std::placeholders::_1 + ) + ); + exam_service_client_ = this->create_client("grade_calculator_service"); + + // Create action server for retake requests + retake_action_server_ = rclcpp_action::create_server( + 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(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(); + request->course_name = combo.course_name; + request->student_name = combo.student_name; + request->exam_grades = data_map_[combo]; + + auto callback = [this, combo](rclcpp::Client::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::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 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 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 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 goal_handle) +{ + RCLCPP_INFO(this->get_logger(), "Executing retake action"); + + const auto goal = goal_handle->get_goal(); + auto feedback = std::make_shared(); + auto result = std::make_shared(); + + // 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(); + 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(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); + } +} + +} \ No newline at end of file diff --git a/src/g2_2025_grade_calculator_pkg/src/retake_grade_determinator/nodes/RetakeGradeDeterminator.hpp b/src/g2_2025_grade_calculator_pkg/src/retake_grade_determinator/nodes/RetakeGradeDeterminator.hpp new file mode 100644 index 0000000..a8a290c --- /dev/null +++ b/src/g2_2025_grade_calculator_pkg/src/retake_grade_determinator/nodes/RetakeGradeDeterminator.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#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::SharedPtr exam_subscriber_; + rclcpp::Publisher::SharedPtr student_publisher_; + + + rclcpp::Client::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; + rclcpp_action::Server::SharedPtr retake_action_server_; + + std::unique_ptr 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::SharedFuture future, + StudentCourse studentCourseCombo + ); + + // Action server callbacks + rclcpp_action::GoalResponse handle_goal( + const rclcpp_action::GoalUUID & uuid, + std::shared_ptr goal); + rclcpp_action::CancelResponse handle_cancel( + const std::shared_ptr goal_handle); + void handle_accepted(const std::shared_ptr goal_handle); + void execute_retake(const std::shared_ptr goal_handle); +}; + +} // namespace assignments::one::retake_grade_determinator diff --git a/src/g2_2025_grade_calculator_pkg/src/retake_scheduler/main.cpp b/src/g2_2025_grade_calculator_pkg/src/retake_scheduler/main.cpp index 7279717..e4f3577 100644 --- a/src/g2_2025_grade_calculator_pkg/src/retake_scheduler/main.cpp +++ b/src/g2_2025_grade_calculator_pkg/src/retake_scheduler/main.cpp @@ -13,25 +13,14 @@ #include #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(); + auto node = std::make_shared(); rclcpp::spin(node); rclcpp::shutdown(); diff --git a/src/g2_2025_grade_calculator_pkg/src/retake_scheduler/nodes/RetakeScheduler.cpp b/src/g2_2025_grade_calculator_pkg/src/retake_scheduler/nodes/RetakeScheduler.cpp new file mode 100644 index 0000000..85a7ef1 --- /dev/null +++ b/src/g2_2025_grade_calculator_pkg/src/retake_scheduler/nodes/RetakeScheduler.cpp @@ -0,0 +1,121 @@ +#include "RetakeScheduler.hpp" + +namespace assignments::one::retake_scheduler { +RetakeScheduler::RetakeScheduler() : Node("retake_scheduler") { + db_manager_ = std::make_unique(this->get_logger()); + + // Create action client to communicate with RetakeGradeDeterminator + retake_action_client_ = rclcpp_action::create_client(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::SendGoalOptions(); + + // Set up result callback + send_goal_options.result_callback = + [this, combo](const rclcpp_action::ClientGoalHandle::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::SharedPtr, + const std::shared_ptr 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); +} +} \ No newline at end of file diff --git a/src/g2_2025_grade_calculator_pkg/src/retake_scheduler/nodes/RetakeScheduler.hpp b/src/g2_2025_grade_calculator_pkg/src/retake_scheduler/nodes/RetakeScheduler.hpp new file mode 100644 index 0000000..9d9f3dd --- /dev/null +++ b/src/g2_2025_grade_calculator_pkg/src/retake_scheduler/nodes/RetakeScheduler.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +#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 db_manager_; + + using RetakeAction = g2_2025_interfaces::action::Retake; + rclcpp_action::Client::SharedPtr retake_action_client_; + + void check_failing_students(); + void retake_scheduler(StudentCourse combo); +}; + +} // namespace assignments::one::retake_scheduler diff --git a/src/g2_2025_interfaces/action/Retake.action b/src/g2_2025_interfaces/action/Retake.action index bc8d36b..737390b 100644 --- a/src/g2_2025_interfaces/action/Retake.action +++ b/src/g2_2025_interfaces/action/Retake.action @@ -1,5 +1,6 @@ string student_name string course_name --- ---- float32 result +--- +string status