From 957f789faa8000a3e9cc4fb50bc4ddc8ab63dbd6 Mon Sep 17 00:00:00 2001 From: Russell Gilbert Date: Sun, 8 Feb 2026 12:55:44 +0000 Subject: [PATCH] Adds heartbeat functionality using GPIO Introduces a heartbeat mechanism to the robot node using GPIO. This allows for external monitoring of the node's active state. The heartbeat is implemented using a GPIO pin that toggles at a regular interval, indicating that the system is running. Updates CMakeLists to link the project with gpiod libraries staged locally. Adds .gitignore entries for local dependencies and PC build directories. --- src/RobotNode/.gitignore | 8 ++++- src/RobotNode/CMakeLists.txt | 26 +++++++-------- src/RobotNode/CardioCenter/Heart.cpp | 49 ++++++++++++++++------------ src/RobotNode/CardioCenter/Heart.h | 10 ++++-- src/RobotNode/DixonBrain.cpp | 6 +++- src/RobotNode/DixonBrain.h | 2 ++ 6 files changed, 62 insertions(+), 39 deletions(-) diff --git a/src/RobotNode/.gitignore b/src/RobotNode/.gitignore index 91b645d..5f963d5 100644 --- a/src/RobotNode/.gitignore +++ b/src/RobotNode/.gitignore @@ -38,4 +38,10 @@ *.app # debug information files -*.dwo \ No newline at end of file +*.dwo + +# Local dependencies +extern/ + +# PC-side build directories +cmake-build-*/ \ No newline at end of file diff --git a/src/RobotNode/CMakeLists.txt b/src/RobotNode/CMakeLists.txt index 955da47..438c2e1 100644 --- a/src/RobotNode/CMakeLists.txt +++ b/src/RobotNode/CMakeLists.txt @@ -1,5 +1,4 @@ -# Sets the minimum CMake version required to handle modern C++ features -cmake_minimum_required(VERSION 3.10) # Version 4.1 doesn't exist yet (standard is 3.x) +cmake_minimum_required(VERSION 3.10) # Defines the project name project(Dixon) @@ -7,14 +6,9 @@ project(Dixon) # Forces the compiler to use C++20 features set(CMAKE_CXX_STANDARD 20) -# Load the PkgConfig tool to find libraries automatically -find_package(PkgConfig REQUIRED) - -# Search for libgpiod v2.x C++ bindings -pkg_check_modules(GPIOD REQUIRED libgpiodcxx) - # Define the executable and its source files -add_executable(Dixon main.cpp +add_executable(Dixon + main.cpp DixonNodeState.cpp DixonNodeState.h DixonBrain.cpp @@ -22,8 +16,14 @@ add_executable(Dixon main.cpp CardioCenter/Heart.cpp CardioCenter/Heart.h) -# Add the Include directories found by PkgConfig (where the .hpp files live) -target_include_directories(Dixon PRIVATE ${GPIOD_INCLUDE_DIRS}) +# Header Path: Use the local v2.1 headers staged in the project +target_include_directories(Dixon PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/extern/libgpiod/include") -# Link the specific libraries found by PkgConfig -target_link_libraries(Dixon PRIVATE ${GPIOD_LIBRARIES}) \ No newline at end of file + +# 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") + +# Tell the linker: "Trust me, the Pi has the rest of the C++ and C libraries" +target_link_options(Dixon PRIVATE "-Wl,--allow-shlib-undefined") \ No newline at end of file diff --git a/src/RobotNode/CardioCenter/Heart.cpp b/src/RobotNode/CardioCenter/Heart.cpp index 49dc742..7a6d353 100644 --- a/src/RobotNode/CardioCenter/Heart.cpp +++ b/src/RobotNode/CardioCenter/Heart.cpp @@ -3,33 +3,40 @@ namespace cardio { - Heart::Heart(const char* chipName, unsigned int lineOffset) - : _isOn(false), - _request(setup_request(chipName, lineOffset)) + Heart::Heart(const char* chipName, unsigned int lineOffset) + : chip_(std::string("/dev/") + chipName), + isOn_(false), + _lastBeat(std::chrono::steady_clock::now()), + request_(get_line_request(chip_, lineOffset)) { } void Heart::beat() { - _isOn = !_isOn; - auto val = _isOn ? gpiod::line::value::ACTIVE : gpiod::line::value::INACTIVE; - - _request.set_value(HEART_LINE_INDEX, val); + const auto now = std::chrono::steady_clock::now(); + if (const auto elapsed = std::chrono::duration_cast + (now - _lastBeat); elapsed.count() >= 500) + { + isOn_ = !isOn_; + const auto val = isOn_ ? gpiod::line::value::ACTIVE : gpiod::line::value::INACTIVE; + request_.set_value(DEFAULT_HEART_PIN, val); + _lastBeat = now; + } } - gpiod::line_request Heart::setup_request(const char* chipName, const unsigned int lineOffset) - { - std::string chipPath = std::string("/dev/") + chipName; - gpiod::chip chip(chipPath); + void Heart::stop() + { + isOn_ = false; + request_.set_value(DEFAULT_HEART_PIN, gpiod::line::value::INACTIVE); + } - auto settings = gpiod::line_settings() - .set_direction(gpiod::line::direction::OUTPUT); + gpiod::line_request Heart::get_line_request(gpiod::chip& chip, const unsigned int lineOffset) + { + const auto settings = gpiod::line_settings() + .set_direction(gpiod::line::direction::OUTPUT); - auto line_cfg = gpiod::line_config(); - line_cfg.add_line_settings(lineOffset, settings); - - return chip.prepare_request() - .set_line_config(line_cfg) - .do_request(); - } -} \ No newline at end of file + return chip.prepare_request() + .add_line_settings(lineOffset, settings) + .do_request(); + } +} diff --git a/src/RobotNode/CardioCenter/Heart.h b/src/RobotNode/CardioCenter/Heart.h index fe355bc..adaa6f6 100644 --- a/src/RobotNode/CardioCenter/Heart.h +++ b/src/RobotNode/CardioCenter/Heart.h @@ -1,5 +1,6 @@ #pragma once #include +#include namespace cardio { @@ -9,13 +10,16 @@ namespace cardio public: explicit Heart(const char* chipName = GPIO_CHIP_NAME, unsigned int lineOffset = DEFAULT_HEART_PIN); void beat(); + void stop(); private: + static gpiod::line_request get_line_request(gpiod::chip&, unsigned int lineOffset); static constexpr auto GPIO_CHIP_NAME = "gpiochip0"; static constexpr unsigned int DEFAULT_HEART_PIN = 17; static constexpr unsigned int HEART_LINE_INDEX = 0; - static gpiod::line_request setup_request(const char* chipName, unsigned int lineOffset); - bool _isOn; - gpiod::line_request _request; + std::chrono::steady_clock::time_point _lastBeat; + gpiod::chip chip_; + bool isOn_; + gpiod::line_request request_; }; } diff --git a/src/RobotNode/DixonBrain.cpp b/src/RobotNode/DixonBrain.cpp index 8e9ca53..0047d32 100644 --- a/src/RobotNode/DixonBrain.cpp +++ b/src/RobotNode/DixonBrain.cpp @@ -4,7 +4,8 @@ #include DixonBrain::DixonBrain() - : state_(DixonNodeState::instance()) + : heart_(), + state_(DixonNodeState::instance()) { std::cout << "DixonBrain initialised\n"; } @@ -37,6 +38,8 @@ void DixonBrain::stop() if (loopThread_.joinable()) loopThread_.join(); + heart_.stop(); + std::cout << "DixonBrain stopped\n"; } @@ -44,6 +47,7 @@ void DixonBrain::runLoop() { while (state_.isBrainRunning()) { + heart_.beat(); // TODO: main control logic std::this_thread::sleep_for(std::chrono::milliseconds(10)); } diff --git a/src/RobotNode/DixonBrain.h b/src/RobotNode/DixonBrain.h index d4b4099..4f20b7a 100644 --- a/src/RobotNode/DixonBrain.h +++ b/src/RobotNode/DixonBrain.h @@ -1,5 +1,6 @@ #pragma once #include "DixonNodeState.h" +#include "CardioCenter/Heart.h" #include class DixonBrain @@ -14,6 +15,7 @@ public: private: void runLoop(); + cardio::Heart heart_; DixonNodeState& state_; std::thread loopThread_; }; \ No newline at end of file