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"