From 3a4e00a409b0f1ad20e24bfd9331cca0b02ad264 Mon Sep 17 00:00:00 2001 From: Russell Gilbert Date: Fri, 20 Feb 2026 15:27:42 +0000 Subject: [PATCH] Adds basic logging and refactors main loop Adds a basic logging system using std::clog with color codes for different log levels. Refactors the main loop to use the logging system and improve readability. Changes DixonNodeState to use atomic exchange for setting node status and logs status changes. Removes the old main.cpp and adds a new one that uses a separate thread for signal handling. Updates the CMakeLists.txt to link against gpiod and spdlog. Moves version information into a version.txt file and creates a custom target to bump the version using a python script. --- src/RobotNode/.run/Dixon-Attach.run.xml | 4 +- src/RobotNode/CMakeLists.txt | 17 +++---- src/RobotNode/CardioCenter/Heart.cpp | 1 + src/RobotNode/DixonBrain.cpp | 13 +++--- src/RobotNode/DixonBrain.h | 5 +- src/RobotNode/DixonNodeState.cpp | 13 ++++-- src/RobotNode/DixonNodeState.h | 6 ++- src/RobotNode/Logging/DixonLogger.h | 59 +++++++++++++++++++++++ src/RobotNode/main.cpp | 62 ++++++++++++++++++------- src/RobotNode/version.txt | 2 +- utils/deploy_dixon | 2 +- 11 files changed, 140 insertions(+), 44 deletions(-) create mode 100644 src/RobotNode/Logging/DixonLogger.h diff --git a/src/RobotNode/.run/Dixon-Attach.run.xml b/src/RobotNode/.run/Dixon-Attach.run.xml index af036a6..e10562a 100644 --- a/src/RobotNode/.run/Dixon-Attach.run.xml +++ b/src/RobotNode/.run/Dixon-Attach.run.xml @@ -1,7 +1,7 @@  - + - + \ No newline at end of file diff --git a/src/RobotNode/CMakeLists.txt b/src/RobotNode/CMakeLists.txt index 2362a0c..ef4d75a 100644 --- a/src/RobotNode/CMakeLists.txt +++ b/src/RobotNode/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.10) -# 1. Define the Version Bumper Target +# Define the Version Bumper Target # This target is like a "recipe" that isn't cooked until we ask for it. add_custom_target(BumpVersion COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/bump_version.py @@ -8,7 +8,7 @@ add_custom_target(BumpVersion COMMENT "Bumping version in version.txt..." ) -# 2. Read the version for the Project metadata +# Read the version for the Project metadata # This still happens during "Reload" so CMake knows the project version. file(READ "${CMAKE_CURRENT_SOURCE_DIR}/version.txt" RAW_VERSION) string(STRIP "${RAW_VERSION}" DIXON_VERSION_FROM_FILE) @@ -29,9 +29,10 @@ add_executable(dixon DixonBrain.cpp DixonBrain.h CardioCenter/Heart.cpp - CardioCenter/Heart.h) + CardioCenter/Heart.h + Logging/DixonLogger.h) -# 3. Create the Dependency +# Create the Dependency # This tells CMake: "Before you build Dixon, you MUST run BumpVersion." add_dependencies(dixon BumpVersion) @@ -41,15 +42,9 @@ configure_file(Version.h.in "${CMAKE_CURRENT_BINARY_DIR}/Version.h") # Consolidated Include Paths target_include_directories(dixon PRIVATE "${CMAKE_CURRENT_BINARY_DIR}" # For generated Version.h -# "${CMAKE_CURRENT_SOURCE_DIR}/extern/libgpiod/include" # For libgpiod headers ) -# Link the gpiod libraries -#target_link_libraries(dixon PRIVATE -# "${CMAKE_CURRENT_SOURCE_DIR}/extern/libgpiod/aarch64/libgpiodcxx.so.2" -# "${CMAKE_CURRENT_SOURCE_DIR}/extern/libgpiod/aarch64/libgpiod.so.3") - -# Link the gpiod libraries +# Link the gpiod & spdlog libraries target_link_libraries(dixon PRIVATE gpiodcxx gpiod) diff --git a/src/RobotNode/CardioCenter/Heart.cpp b/src/RobotNode/CardioCenter/Heart.cpp index 7881654..6453e15 100644 --- a/src/RobotNode/CardioCenter/Heart.cpp +++ b/src/RobotNode/CardioCenter/Heart.cpp @@ -32,6 +32,7 @@ namespace cardio void Heart::stop() { + std::cout << "Heart::stop() called.\n"; isOn_ = false; request_.set_value(DEFAULT_HEART_PIN, gpiod::line::value::INACTIVE); } diff --git a/src/RobotNode/DixonBrain.cpp b/src/RobotNode/DixonBrain.cpp index e05226f..0f0d6cf 100644 --- a/src/RobotNode/DixonBrain.cpp +++ b/src/RobotNode/DixonBrain.cpp @@ -2,18 +2,20 @@ #include #include #include +#include "Logging/DixonLogger.h" DixonBrain::DixonBrain() : heart_(), - state_(DixonNodeState::instance()) + state_(DixonNodeState::instance()), + logger_("DixonBrain") { - std::cout << "DixonBrain initialised\n"; + logger_.info("DixonBrain created."); } DixonBrain::~DixonBrain() { stop(); - std::cout << "DixonBrain destroyed\n"; + logger_.info("DixonBrain destroyed."); } void DixonBrain::start() @@ -25,7 +27,7 @@ void DixonBrain::start() loopThread_ = std::thread(&DixonBrain::runLoop, this); - std::cout << "DixonBrain started\n"; + logger_.info("DixonBrain started."); } void DixonBrain::stop() @@ -35,7 +37,6 @@ void DixonBrain::stop() state_.setNodeStatus(NodeStatus::Stopping); - if (loopThread_.joinable()) loopThread_.join(); @@ -43,7 +44,7 @@ void DixonBrain::stop() state_.setNodeStatus(NodeStatus::Stopped); - std::cout << "DixonBrain stopped\n"; + logger_.info("DixonBrain stopped."); } void DixonBrain::runLoop() diff --git a/src/RobotNode/DixonBrain.h b/src/RobotNode/DixonBrain.h index 4f20b7a..8fbd01c 100644 --- a/src/RobotNode/DixonBrain.h +++ b/src/RobotNode/DixonBrain.h @@ -1,7 +1,8 @@ #pragma once +#include #include "DixonNodeState.h" #include "CardioCenter/Heart.h" -#include +#include "Logging/DixonLogger.h" class DixonBrain { @@ -15,7 +16,9 @@ public: private: void runLoop(); + cardio::Heart heart_; DixonNodeState& state_; std::thread loopThread_; + Dixon::DixonLogger logger_; }; \ No newline at end of file diff --git a/src/RobotNode/DixonNodeState.cpp b/src/RobotNode/DixonNodeState.cpp index 4741a7b..e3d9778 100644 --- a/src/RobotNode/DixonNodeState.cpp +++ b/src/RobotNode/DixonNodeState.cpp @@ -1,12 +1,15 @@ #include "DixonNodeState.h" +#include +#include + DixonNodeState& DixonNodeState::instance() { static DixonNodeState instance; return instance; } -void DixonNodeState::setConnected(bool value) +void DixonNodeState::setConnected(const bool value) { connected_ = value; } @@ -18,10 +21,14 @@ bool DixonNodeState::isConnected() const void DixonNodeState::setNodeStatus(NodeStatus value) { - node_status_ = value; + NodeStatus oldStatus = node_status_.exchange(value); + + logger_.info("Node status changed: {} -> {}", + static_cast(oldStatus), + static_cast(value)); } NodeStatus DixonNodeState::getNodeStatus() const { - return node_status_; + return node_status_.load(); } diff --git a/src/RobotNode/DixonNodeState.h b/src/RobotNode/DixonNodeState.h index a686810..c9235c3 100644 --- a/src/RobotNode/DixonNodeState.h +++ b/src/RobotNode/DixonNodeState.h @@ -1,10 +1,12 @@ #pragma once #include +#include "Logging/DixonLogger.h" enum class NodeStatus : int { Starting, Running, + StopPending, Stopping, Stopped }; @@ -26,9 +28,11 @@ public: DixonNodeState& operator=(DixonNodeState&&) = delete; private: - DixonNodeState() = default; + DixonNodeState() : logger_("DixonNodeState"){} ~DixonNodeState() = default; + Dixon::DixonLogger logger_; + std::atomic connected_ = false; std::atomic node_status_ = NodeStatus::Stopped; }; \ No newline at end of file diff --git a/src/RobotNode/Logging/DixonLogger.h b/src/RobotNode/Logging/DixonLogger.h new file mode 100644 index 0000000..15d6f39 --- /dev/null +++ b/src/RobotNode/Logging/DixonLogger.h @@ -0,0 +1,59 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace Dixon { + class DixonLogger { + private: + std::string_view m_name; + + // Internal helper to keep the code DRY (Don't Repeat Yourself) + template + void log_output(std::string_view level, std::string_view color, std::string_view fmt, Args&&... args) const { + try { + auto now = std::chrono::system_clock::now(); + std::string message = std::vformat(fmt, std::make_format_args(args...)); + + static std::mutex logMutex; + std::lock_guard lock(logMutex); + + // \033[0m resets the color back to normal at the end + std::clog << std::format("[{:%T}] [{}] [{}{}{}] {}\n", + now, m_name, color, level, "\033[0m", message); + } catch (const std::exception& e) { + std::cerr << "Log Error: " << e.what() << std::endl; + } + } + + public: + explicit DixonLogger(std::string_view name) : m_name(name) {} + + template + void debug(std::string_view fmt, Args&&... args) const { + log_output("DEBUG", "\033[36m", fmt, std::forward(args)...); // Cyan + } + + template + void info(std::string_view fmt, Args&&... args) const { + log_output("INFO", "\033[32m", fmt, std::forward(args)...); // Green + } + + template + void warn(std::string_view fmt, Args&&... args) const { + log_output("WARN", "\033[33m", fmt, std::forward(args)...); // Yellow + } + + template + void error(std::string_view fmt, Args&&... args) const { + log_output("ERROR", "\033[31m", fmt, std::forward(args)...); // Red + } + + template + void critical(std::string_view fmt, Args&&... args) const { + log_output("CRITICAL", "\033[1;31;47m", fmt, std::forward(args)...); // Bold Red on White + } + }; +} \ No newline at end of file diff --git a/src/RobotNode/main.cpp b/src/RobotNode/main.cpp index 68c1e69..d3a40c0 100644 --- a/src/RobotNode/main.cpp +++ b/src/RobotNode/main.cpp @@ -2,26 +2,43 @@ #include #include "DixonBrain.h" #include "Version.h" // The generated header +#include "Logging/DixonLogger.h" - -void signal_handler(int signal) { - if (signal == SIGINT || signal == SIGTERM) { - std::cout << "\nShutdown signal received (" << signal << ")." << std::endl; - - // Use your atomic flag to tell the brain to stop looping - DixonNodeState::instance().setNodeStatus(NodeStatus::Stopping); +void signal_thread_func(sigset_t set) { + int sig; + // Loop so we can catch multiple signals if the shutdown takes a moment + while (DixonNodeState::instance().getNodeStatus() != NodeStatus::Stopping) { + if (sigwait(&set, &sig) == 0) { + std::cout << "\nShutdown signal received (" << sig << ")." << std::endl; + DixonNodeState::instance().setNodeStatus(NodeStatus::Stopping); + // After setting 'Stopping', the loop will exit on next check + } } } +sigset_t prepare_signal_set() +{ + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGINT); + sigaddset(&set, SIGTERM); + return set; +} + int main() { - // The "Splash Screen" - std::cout << "Starting Dixon v" << DIXON_VERSION << "...\n"; + Dixon::DixonLogger logger("main"); - std::signal(SIGINT, signal_handler); - std::signal(SIGTERM, signal_handler); + // The "Splash Screen" + logger.info("Dixon core is starting. Version: {}", "1.x.x"); + + const sigset_t set = prepare_signal_set(); + pthread_sigmask(SIG_BLOCK, &set, nullptr); + + std::thread sig_thread(signal_thread_func, set); + sig_thread.detach(); // Let it run independently // Create the brain controller DixonBrain brain; @@ -29,23 +46,32 @@ int main() // Start the brain's control loop brain.start(); - std::cout << "Dixon is alive...\n"; + logger.info("Dixon core is alive."); + const DixonNodeState& state = DixonNodeState::instance(); auto count = 1; - while (DixonNodeState::instance().getNodeStatus() == NodeStatus::Running) + while (state.getNodeStatus() != NodeStatus::Stopped) { + if (state.getNodeStatus() == NodeStatus::StopPending) + { + logger.info("calling 'stop' on Dixon Brain."); + brain.stop(); + } + count++; - if (count>10) + if (count>100) { count = 0; - std::cout << "Dixon state is " << static_cast( DixonNodeState::instance().getNodeStatus()) << "\n"; + auto const currentState = static_cast(DixonNodeState::instance().getNodeStatus()); + logger.info("Dixon is alive, state is {}.", currentState); + //log->log(spdlog::level::info, "Dixon state is {}", + //static_cast(D); } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } - // Stop the brain cleanly - brain.stop(); - std::cout << "Dixon has left the building...\n"; + + logger.info("Dixon has left the building."); return 0; } \ No newline at end of file diff --git a/src/RobotNode/version.txt b/src/RobotNode/version.txt index c1cf2f9..de0434d 100644 --- a/src/RobotNode/version.txt +++ b/src/RobotNode/version.txt @@ -1 +1 @@ -1.0.33 \ No newline at end of file +1.0.47 \ No newline at end of file diff --git a/utils/deploy_dixon b/utils/deploy_dixon index 0388985..86141f9 100755 --- a/utils/deploy_dixon +++ b/utils/deploy_dixon @@ -2,7 +2,7 @@ # Configuration LOCAL_BIN="$HOME/dev/Dixon/src/RobotNode/cmake-build-pi5-debug/dixon" -REMOTE_TARGET="russellg59@192.168.1.224" +REMOTE_TARGET="russellg59@dixon1" REMOTE_DIR="/home/russellg59/dixon" REMOTE_EXE="$REMOTE_DIR/dixon"