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.
This commit is contained in:
Russell Gilbert 2026-02-08 12:55:44 +00:00
parent 6a8f4f364b
commit 957f789faa
6 changed files with 62 additions and 39 deletions

View file

@ -38,4 +38,10 @@
*.app
# debug information files
*.dwo
*.dwo
# Local dependencies
extern/
# PC-side build directories
cmake-build-*/

View file

@ -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})
# 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")

View file

@ -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<std::chrono::milliseconds>
(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();
}
}
return chip.prepare_request()
.add_line_settings(lineOffset, settings)
.do_request();
}
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <gpiod.hpp>
#include <chrono>
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_;
};
}

View file

@ -4,7 +4,8 @@
#include <thread>
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));
}

View file

@ -1,5 +1,6 @@
#pragma once
#include "DixonNodeState.h"
#include "CardioCenter/Heart.h"
#include <thread>
class DixonBrain
@ -14,6 +15,7 @@ public:
private:
void runLoop();
cardio::Heart heart_;
DixonNodeState& state_;
std::thread loopThread_;
};