Merge pull request '[PR] Add nodes grade_calculator and final_grade_determinator' (#2) from 1-grade-generator/cijfer-determinator-calculator into 1-grade-generator/master

Reviewed-on: http://git.wessel.gg/inholland/ros2-assignments/pulls/2
Reviewed-by: Wessel T <contact@wessel.gg>
This commit was merged in pull request #2.
This commit is contained in:
2025-10-02 11:50:28 +02:00
10 changed files with 242 additions and 36 deletions

View File

@@ -36,11 +36,21 @@ target_link_libraries(exam_result_generator pqxx pq tomlplusplus::tomlplusplus)
add_executable(final_grade_determinator
src/final_grade_determinator/main.cpp
src/database/DatabaseManager.cpp
src/config/ConfigManager.cpp
src/final_grade_determinator/nodes/FinalGradeDeterminator.cpp
)
target_include_directories(final_grade_determinator PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/src/final_grade_determinator
)
ament_target_dependencies(final_grade_determinator rclcpp g2_2025_interfaces)
target_link_libraries(final_grade_determinator pqxx pq tomlplusplus::tomlplusplus)
add_executable(grade_calculator
src/grade_calculator/main.cpp
src/grade_calculator/nodes/GradeCalculator.cpp
)
ament_target_dependencies(grade_calculator rclcpp g2_2025_interfaces)

View File

@@ -18,6 +18,14 @@ struct StudentCourse {
bool operator==(const StudentCourse& other) const {
return student_name == other.student_name && course_name == other.course_name;
}
bool operator<(const StudentCourse& other) const {
return student_name < other.student_name
|| (student_name == other.student_name
&& course_name < other.course_name);
}
};
typedef std::map<StudentCourse, std::vector<int>> StudentCourseResultMap;
} // namespace assignments::one

View File

@@ -65,6 +65,7 @@ void ExamResultGenerator::generate_random_result() {
// Publish exam result
auto exam_msg = g2_2025_interfaces::msg::Exam();
exam_msg.student_name = selected.student_name;
exam_msg.course_name = selected.course_name;
exam_msg.result = grade;

View File

@@ -2,36 +2,19 @@
* Action server node template for ROS2
*
* Node description:
* Template action server that demonstrates action server implementation
* with goal handling, feedback publishing, and cancellation support
*
* Reviewed by: <x>
* Changelog:
* [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/FinalGradeDeterminator.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::final_grade_determinator::FinalGradeDeterminator>();
rclcpp::spin(node);
rclcpp::shutdown();

View File

@@ -0,0 +1,95 @@
#include "FinalGradeDeterminator.hpp"
namespace assignments::one::final_grade_determinator {
FinalGradeDeterminator::FinalGradeDeterminator() : Node("final_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());
// Create publisher for exam results
student_publisher_ = this->create_publisher<g2_2025_interfaces::msg::Student>(
"student_course_management", 10
);
// Create subscriber for adding/removing student/course combinations
exam_subscriber_ = this->create_subscription<g2_2025_interfaces::msg::Exam>(
"exam_results", 10,
std::bind(
&FinalGradeDeterminator::exam_results_callback,
this,
std::placeholders::_1
)
);
exam_service_client_= this->create_client<g2_2025_interfaces::srv::Exams>("grade_calculator_service");
}
void FinalGradeDeterminator::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()
);
grade_calculator_request(student_course_combo_);
}
}
void FinalGradeDeterminator::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];
// Callback is used due to ros2 not liking passing multiple arguments in async calls
auto callback = [this, combo](rclcpp::Client<g2_2025_interfaces::srv::Exams>::SharedFuture future)
{
this->grade_calculator_response(future, combo);
};
exam_service_client_->async_send_request(request, callback);
}
void FinalGradeDeterminator::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 is %d",
studentCourseCombo.student_name.c_str(), studentCourseCombo.course_name.c_str(), response->result
);
db_manager_->store_final_course_result(
studentCourseCombo,
grade_collection_amount_,
response->result
);
}
} // namespace assignments::one::final_grade_determinator

View File

@@ -0,0 +1,44 @@
#pragma once
#include <memory>
#include <string>
#include <random>
#include <vector>
#include <chrono>
#include "rclcpp/rclcpp.hpp"
#include "g2_2025_interfaces/msg/exam.hpp"
#include "g2_2025_interfaces/msg/student.hpp"
#include "g2_2025_interfaces/srv/exams.hpp"
#include "database/DatabaseManager.hpp"
#include "database/StudentCourse.hpp"
namespace assignments::one::final_grade_determinator {
class FinalGradeDeterminator : public rclcpp::Node {
public:
FinalGradeDeterminator();
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_;
std::unique_ptr<DatabaseManager> db_manager_;
StudentCourse student_course_combo_;
StudentCourseResultMap data_map_;
// Params
int grade_collection_amount_;
void grade_calculator_request(StudentCourse combo);
void exam_results_callback(const g2_2025_interfaces::msg::Exam::SharedPtr msg);
void grade_calculator_response(
rclcpp::Client<g2_2025_interfaces::srv::Exams>::SharedFuture future,
StudentCourse studentCourseCombo
);
};
} // namespace assignments::one::final_grade_determinator

View File

@@ -10,28 +10,13 @@
* [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/GradeCalculator.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::grade_calculator::GradeCalculator>();
rclcpp::spin(node);
rclcpp::shutdown();

View File

@@ -0,0 +1,48 @@
#include "GradeCalculator.hpp"
namespace assignments::one::grade_calculator {
GradeCalculator::GradeCalculator() : Node("grade_calculator") {
grade_calculator_service_server_ = this->create_service<g2_2025_interfaces::srv::Exams>(
"grade_calculator_service",
std::bind(
&GradeCalculator::grade_calculator_callback,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
RCLCPP_INFO(this->get_logger(), "Grade calculator service server started");
}
void GradeCalculator::grade_calculator_callback(
const g2_2025_interfaces::srv::Exams::Request::SharedPtr request,
const g2_2025_interfaces::srv::Exams::Response::SharedPtr response
) {
if (request->exam_grades.size() != 0){
grades_total_ = std::accumulate(request->exam_grades.begin(), request->exam_grades.end(), 0);
auto lowercase = request->student_name;
std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), ::tolower);
grade_result_ = (grades_total_) / request->exam_grades.size();
if (lowercase.compare("wessel") == 0){
grade_result_ += 10;
}
if (grade_result_ > 100) {
grade_result_ = 100;
} else if (grade_result_ < 10){
grade_result_ = 10;
}
}
response->result = grade_result_;
}
} // namespace assignments::one::grade_calculator

View File

@@ -0,0 +1,31 @@
#pragma once
#include <memory>
#include <string>
#include <random>
#include <vector>
#include "rclcpp/rclcpp.hpp"
#include "g2_2025_interfaces/msg/exam.hpp"
#include "g2_2025_interfaces/msg/student.hpp"
#include "g2_2025_interfaces/srv/exams.hpp"
namespace assignments::one::grade_calculator {
class GradeCalculator : public rclcpp::Node {
public:
GradeCalculator();
private:
rclcpp::Service<g2_2025_interfaces::srv::Exams>::SharedPtr grade_calculator_service_server_;
int grades_total_;
int grade_result_ = 0;
void grade_calculator_callback(
const g2_2025_interfaces::srv::Exams::Request::SharedPtr request,
const g2_2025_interfaces::srv::Exams::Response::SharedPtr response
);
};
}

View File

@@ -1,3 +1,4 @@
string student_name
string course_name
int32 result
builtin_interfaces/Time timestamp