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.
This commit is contained in:
parent
e8c3e0f8c2
commit
3a4e00a409
11 changed files with 140 additions and 44 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Dixon-Attach" type="CLion_Remote" version="1" remoteCommand="tcp:Dixon1:9090" symbolFile="$PROJECT_DIR$/cmake-build-pi/dixon" sysroot="">
|
<configuration default="false" name="Dixon-Attach" type="CLion_Remote" version="1" remoteCommand="tcp:Dixon1:9090" symbolFile="$PROJECT_DIR$/cmake-build-pi5-debug/dixon" sysroot="">
|
||||||
<debugger kind="GDB" isBundled="true" />
|
<debugger kind="GDB" isBundled="true" />
|
||||||
<pathMapping remote=":/mnt/C/source/Dixon/src/Robotnode" local="$PROJECT_DIR$" />
|
<pathMapping remote=":$USER_HOME$/dixon" local="$PROJECT_DIR$" />
|
||||||
<method v="2" />
|
<method v="2" />
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
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.
|
# This target is like a "recipe" that isn't cooked until we ask for it.
|
||||||
add_custom_target(BumpVersion
|
add_custom_target(BumpVersion
|
||||||
COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/bump_version.py
|
COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/bump_version.py
|
||||||
|
|
@ -8,7 +8,7 @@ add_custom_target(BumpVersion
|
||||||
COMMENT "Bumping version in version.txt..."
|
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.
|
# This still happens during "Reload" so CMake knows the project version.
|
||||||
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/version.txt" RAW_VERSION)
|
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/version.txt" RAW_VERSION)
|
||||||
string(STRIP "${RAW_VERSION}" DIXON_VERSION_FROM_FILE)
|
string(STRIP "${RAW_VERSION}" DIXON_VERSION_FROM_FILE)
|
||||||
|
|
@ -29,9 +29,10 @@ add_executable(dixon
|
||||||
DixonBrain.cpp
|
DixonBrain.cpp
|
||||||
DixonBrain.h
|
DixonBrain.h
|
||||||
CardioCenter/Heart.cpp
|
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."
|
# This tells CMake: "Before you build Dixon, you MUST run BumpVersion."
|
||||||
add_dependencies(dixon BumpVersion)
|
add_dependencies(dixon BumpVersion)
|
||||||
|
|
||||||
|
|
@ -41,15 +42,9 @@ configure_file(Version.h.in "${CMAKE_CURRENT_BINARY_DIR}/Version.h")
|
||||||
# Consolidated Include Paths
|
# Consolidated Include Paths
|
||||||
target_include_directories(dixon PRIVATE
|
target_include_directories(dixon PRIVATE
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}" # For generated Version.h
|
"${CMAKE_CURRENT_BINARY_DIR}" # For generated Version.h
|
||||||
# "${CMAKE_CURRENT_SOURCE_DIR}/extern/libgpiod/include" # For libgpiod headers
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Link the gpiod libraries
|
# Link the gpiod & spdlog 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
|
|
||||||
target_link_libraries(dixon PRIVATE
|
target_link_libraries(dixon PRIVATE
|
||||||
gpiodcxx
|
gpiodcxx
|
||||||
gpiod)
|
gpiod)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ namespace cardio
|
||||||
|
|
||||||
void Heart::stop()
|
void Heart::stop()
|
||||||
{
|
{
|
||||||
|
std::cout << "Heart::stop() called.\n";
|
||||||
isOn_ = false;
|
isOn_ = false;
|
||||||
request_.set_value(DEFAULT_HEART_PIN, gpiod::line::value::INACTIVE);
|
request_.set_value(DEFAULT_HEART_PIN, gpiod::line::value::INACTIVE);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,20 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include "Logging/DixonLogger.h"
|
||||||
|
|
||||||
DixonBrain::DixonBrain()
|
DixonBrain::DixonBrain()
|
||||||
: heart_(),
|
: heart_(),
|
||||||
state_(DixonNodeState::instance())
|
state_(DixonNodeState::instance()),
|
||||||
|
logger_("DixonBrain")
|
||||||
{
|
{
|
||||||
std::cout << "DixonBrain initialised\n";
|
logger_.info("DixonBrain created.");
|
||||||
}
|
}
|
||||||
|
|
||||||
DixonBrain::~DixonBrain()
|
DixonBrain::~DixonBrain()
|
||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
std::cout << "DixonBrain destroyed\n";
|
logger_.info("DixonBrain destroyed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void DixonBrain::start()
|
void DixonBrain::start()
|
||||||
|
|
@ -25,7 +27,7 @@ void DixonBrain::start()
|
||||||
|
|
||||||
loopThread_ = std::thread(&DixonBrain::runLoop, this);
|
loopThread_ = std::thread(&DixonBrain::runLoop, this);
|
||||||
|
|
||||||
std::cout << "DixonBrain started\n";
|
logger_.info("DixonBrain started.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void DixonBrain::stop()
|
void DixonBrain::stop()
|
||||||
|
|
@ -35,7 +37,6 @@ void DixonBrain::stop()
|
||||||
|
|
||||||
state_.setNodeStatus(NodeStatus::Stopping);
|
state_.setNodeStatus(NodeStatus::Stopping);
|
||||||
|
|
||||||
|
|
||||||
if (loopThread_.joinable())
|
if (loopThread_.joinable())
|
||||||
loopThread_.join();
|
loopThread_.join();
|
||||||
|
|
||||||
|
|
@ -43,7 +44,7 @@ void DixonBrain::stop()
|
||||||
|
|
||||||
state_.setNodeStatus(NodeStatus::Stopped);
|
state_.setNodeStatus(NodeStatus::Stopped);
|
||||||
|
|
||||||
std::cout << "DixonBrain stopped\n";
|
logger_.info("DixonBrain stopped.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void DixonBrain::runLoop()
|
void DixonBrain::runLoop()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <thread>
|
||||||
#include "DixonNodeState.h"
|
#include "DixonNodeState.h"
|
||||||
#include "CardioCenter/Heart.h"
|
#include "CardioCenter/Heart.h"
|
||||||
#include <thread>
|
#include "Logging/DixonLogger.h"
|
||||||
|
|
||||||
class DixonBrain
|
class DixonBrain
|
||||||
{
|
{
|
||||||
|
|
@ -15,7 +16,9 @@ public:
|
||||||
private:
|
private:
|
||||||
void runLoop();
|
void runLoop();
|
||||||
|
|
||||||
|
|
||||||
cardio::Heart heart_;
|
cardio::Heart heart_;
|
||||||
DixonNodeState& state_;
|
DixonNodeState& state_;
|
||||||
std::thread loopThread_;
|
std::thread loopThread_;
|
||||||
|
Dixon::DixonLogger logger_;
|
||||||
};
|
};
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
#include "DixonNodeState.h"
|
#include "DixonNodeState.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
DixonNodeState& DixonNodeState::instance()
|
DixonNodeState& DixonNodeState::instance()
|
||||||
{
|
{
|
||||||
static DixonNodeState instance;
|
static DixonNodeState instance;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DixonNodeState::setConnected(bool value)
|
void DixonNodeState::setConnected(const bool value)
|
||||||
{
|
{
|
||||||
connected_ = value;
|
connected_ = value;
|
||||||
}
|
}
|
||||||
|
|
@ -18,10 +21,14 @@ bool DixonNodeState::isConnected() const
|
||||||
|
|
||||||
void DixonNodeState::setNodeStatus(NodeStatus value)
|
void DixonNodeState::setNodeStatus(NodeStatus value)
|
||||||
{
|
{
|
||||||
node_status_ = value;
|
NodeStatus oldStatus = node_status_.exchange(value);
|
||||||
|
|
||||||
|
logger_.info("Node status changed: {} -> {}",
|
||||||
|
static_cast<int>(oldStatus),
|
||||||
|
static_cast<int>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeStatus DixonNodeState::getNodeStatus() const
|
NodeStatus DixonNodeState::getNodeStatus() const
|
||||||
{
|
{
|
||||||
return node_status_;
|
return node_status_.load();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include<atomic>
|
#include<atomic>
|
||||||
|
#include "Logging/DixonLogger.h"
|
||||||
|
|
||||||
|
|
||||||
enum class NodeStatus : int {
|
enum class NodeStatus : int {
|
||||||
Starting,
|
Starting,
|
||||||
Running,
|
Running,
|
||||||
|
StopPending,
|
||||||
Stopping,
|
Stopping,
|
||||||
Stopped
|
Stopped
|
||||||
};
|
};
|
||||||
|
|
@ -26,9 +28,11 @@ public:
|
||||||
DixonNodeState& operator=(DixonNodeState&&) = delete;
|
DixonNodeState& operator=(DixonNodeState&&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DixonNodeState() = default;
|
DixonNodeState() : logger_("DixonNodeState"){}
|
||||||
~DixonNodeState() = default;
|
~DixonNodeState() = default;
|
||||||
|
|
||||||
|
Dixon::DixonLogger logger_;
|
||||||
|
|
||||||
std::atomic<bool> connected_ = false;
|
std::atomic<bool> connected_ = false;
|
||||||
std::atomic<NodeStatus> node_status_ = NodeStatus::Stopped;
|
std::atomic<NodeStatus> node_status_ = NodeStatus::Stopped;
|
||||||
};
|
};
|
||||||
59
src/RobotNode/Logging/DixonLogger.h
Normal file
59
src/RobotNode/Logging/DixonLogger.h
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#pragma once
|
||||||
|
#include <iostream>
|
||||||
|
#include <format>
|
||||||
|
#include <mutex>
|
||||||
|
#include <chrono>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace Dixon {
|
||||||
|
class DixonLogger {
|
||||||
|
private:
|
||||||
|
std::string_view m_name;
|
||||||
|
|
||||||
|
// Internal helper to keep the code DRY (Don't Repeat Yourself)
|
||||||
|
template<typename... Args>
|
||||||
|
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<std::mutex> 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<typename... Args>
|
||||||
|
void debug(std::string_view fmt, Args&&... args) const {
|
||||||
|
log_output("DEBUG", "\033[36m", fmt, std::forward<Args>(args)...); // Cyan
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
void info(std::string_view fmt, Args&&... args) const {
|
||||||
|
log_output("INFO", "\033[32m", fmt, std::forward<Args>(args)...); // Green
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
void warn(std::string_view fmt, Args&&... args) const {
|
||||||
|
log_output("WARN", "\033[33m", fmt, std::forward<Args>(args)...); // Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
void error(std::string_view fmt, Args&&... args) const {
|
||||||
|
log_output("ERROR", "\033[31m", fmt, std::forward<Args>(args)...); // Red
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
void critical(std::string_view fmt, Args&&... args) const {
|
||||||
|
log_output("CRITICAL", "\033[1;31;47m", fmt, std::forward<Args>(args)...); // Bold Red on White
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -2,26 +2,43 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "DixonBrain.h"
|
#include "DixonBrain.h"
|
||||||
#include "Version.h" // The generated header
|
#include "Version.h" // The generated header
|
||||||
|
#include "Logging/DixonLogger.h"
|
||||||
|
|
||||||
|
|
||||||
|
void signal_thread_func(sigset_t set) {
|
||||||
void signal_handler(int signal) {
|
int sig;
|
||||||
if (signal == SIGINT || signal == SIGTERM) {
|
// Loop so we can catch multiple signals if the shutdown takes a moment
|
||||||
std::cout << "\nShutdown signal received (" << signal << ")." << std::endl;
|
while (DixonNodeState::instance().getNodeStatus() != NodeStatus::Stopping) {
|
||||||
|
if (sigwait(&set, &sig) == 0) {
|
||||||
// Use your atomic flag to tell the brain to stop looping
|
std::cout << "\nShutdown signal received (" << sig << ")." << std::endl;
|
||||||
DixonNodeState::instance().setNodeStatus(NodeStatus::Stopping);
|
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()
|
int main()
|
||||||
{
|
{
|
||||||
// The "Splash Screen"
|
Dixon::DixonLogger logger("main");
|
||||||
std::cout << "Starting Dixon v" << DIXON_VERSION << "...\n";
|
|
||||||
|
|
||||||
std::signal(SIGINT, signal_handler);
|
// The "Splash Screen"
|
||||||
std::signal(SIGTERM, signal_handler);
|
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
|
// Create the brain controller
|
||||||
DixonBrain brain;
|
DixonBrain brain;
|
||||||
|
|
@ -29,23 +46,32 @@ int main()
|
||||||
// Start the brain's control loop
|
// Start the brain's control loop
|
||||||
brain.start();
|
brain.start();
|
||||||
|
|
||||||
std::cout << "Dixon is alive...\n";
|
logger.info("Dixon core is alive.");
|
||||||
|
const DixonNodeState& state = DixonNodeState::instance();
|
||||||
|
|
||||||
auto count = 1;
|
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++;
|
count++;
|
||||||
if (count>10)
|
if (count>100)
|
||||||
{
|
{
|
||||||
count = 0;
|
count = 0;
|
||||||
std::cout << "Dixon state is " << static_cast<int>( DixonNodeState::instance().getNodeStatus()) << "\n";
|
auto const currentState = static_cast<int>(DixonNodeState::instance().getNodeStatus());
|
||||||
|
logger.info("Dixon is alive, state is {}.", currentState);
|
||||||
|
//log->log(spdlog::level::info, "Dixon state is {}",
|
||||||
|
//static_cast<int>(D);
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
}
|
}
|
||||||
// Stop the brain cleanly
|
|
||||||
brain.stop();
|
logger.info("Dixon has left the building.");
|
||||||
std::cout << "Dixon has left the building...\n";
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -1 +1 @@
|
||||||
1.0.33
|
1.0.47
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
LOCAL_BIN="$HOME/dev/Dixon/src/RobotNode/cmake-build-pi5-debug/dixon"
|
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_DIR="/home/russellg59/dixon"
|
||||||
REMOTE_EXE="$REMOTE_DIR/dixon"
|
REMOTE_EXE="$REMOTE_DIR/dixon"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue