Skip to content

Navigation Menu

Sign in
Sign up

Repository files navigation

C++ Linux Hardware Bus Driver Simulator

A C++20 Linux user-space driver-style project that communicates with a simulated embedded peripheral through a compact binary request/response protocol.

The hardware endpoint is implemented in Python and exposed through a UNIX domain socket, so the complete command path can be built, tested and demonstrated on a normal Linux workstation without physical hardware. The C++ driver depends only on the ITransport abstraction, which keeps the protocol and high-level device API independent from the development transport.

What the project demonstrates

  • Modern C++20 with RAII and std::span.
  • A transport-independent DeviceDriver API.
  • Binary big-endian frame encoding and decoding.
  • CRC-32 integrity checking.
  • Request/response correlation with command and sequence validation.
  • Bounded payload sizes.
  • End-to-end read timeouts for stream framing.
  • Retry handling for timeouts, CRC failures and invalid correlated responses.
  • Linux UNIX domain socket I/O.
  • Deterministic mock-based unit tests.
  • Python peripheral simulation.
  • CMake + CTest integration.
  • Optional AddressSanitizer + UndefinedBehaviorSanitizer builds.
  • GitHub Actions CI with GCC, Clang and sanitizers.

Project scope

This repository is intentionally a user-space driver simulation, not a Linux kernel module and not a real UART, SPI or I2C implementation.

The UNIX domain socket is the reproducible development transport. A real serial, SPI, I2C or other hardware transport can be added later by implementing lhbd::transport::ITransport without changing the high-level driver API or binary codec.

Repository layout

.
├── .github/workflows/ci.yml
├── examples/demo_commands.md
├── include/lhbd/
│ ├── core/DeviceDriver.hpp
│ ├── protocol/
│ │ ├── Crc32.hpp
│ │ ├── Frame.hpp
│ │ ├── FrameCodec.hpp
│ │ └── Protocol.hpp
│ ├── transport/
│ │ ├── ITransport.hpp
│ │ ├── MockTransport.hpp
│ │ └── UnixSocketTransport.hpp
│ └── utils/Logger.hpp
├── scripts/run_integration_test.py
├── simulator/device_simulator.py
├── src/
│ ├── apps/device_cli.cpp
│ ├── core/DeviceDriver.cpp
│ ├── protocol/
│ │ ├── Crc32.cpp
│ │ └── FrameCodec.cpp
│ ├── transport/
│ │ ├── MockTransport.cpp
│ │ └── UnixSocketTransport.cpp
│ └── utils/Logger.cpp
├── tests/test_main.cpp
├── CMakeLists.txt
├── Makefile
└── README.md

Requirements

Recommended environment:

  • Linux;
  • CMake 3.18 or newer;
  • a C++20 compiler such as GCC or Clang;
  • Python 3.10 or newer for the simulator and integration test;
  • make if you want to use the convenience Makefile;
  • zip only for make zip.

No third-party C++ library is required.

Build and test

CMake

cmake -S . -B build \
 -DCMAKE_BUILD_TYPE=Release \
 -DLHBD_WARNINGS_AS_ERRORS=ON
cmake --build build --parallel 2
ctest --test-dir build --output-on-failure

When Python 3.10+ is available, CTest registers both:

  • lhbd_unit_tests;
  • lhbd_integration_test.

If Python is not available, the C++ unit test is still built and registered.

Makefile

make test

Useful targets:

make build
make unit-test
make integration
make sanitize
make clean
make help

make sanitize builds a separate build-san/ tree with AddressSanitizer and UndefinedBehaviorSanitizer enabled.

Run the simulator and CLI

Build the project first, then use two terminals.

Terminal 1:

python3 simulator/device_simulator.py --socket /tmp/lhbd_device.sock

Terminal 2:

./build/lhbd_cli --socket /tmp/lhbd_device.sock --demo

Typical output:

PING: OK
WRITE REG 0x0010: OK
READ REG 0x0010: 305419896
TEMPERATURE: 22.04...
STATUS: mode=1 flags=0 uptime=...

The exact sensor value and uptime vary between runs.

CLI commands

lhbd_cli [--socket <path>] --demo
lhbd_cli [--socket <path>] --ping
lhbd_cli [--socket <path>] --read-register <address>
lhbd_cli [--socket <path>] --write-register <address> <value>
lhbd_cli [--socket <path>] --sensor temperature|voltage|current|gyro_x
lhbd_cli [--socket <path>] --status
lhbd_cli [--socket <path>] --reset

Examples:

./build/lhbd_cli --socket /tmp/lhbd_device.sock --ping
./build/lhbd_cli --socket /tmp/lhbd_device.sock --read-register 0x0001
./build/lhbd_cli --socket /tmp/lhbd_device.sock --write-register 0x0010 0x12345678
./build/lhbd_cli --socket /tmp/lhbd_device.sock --sensor voltage
./build/lhbd_cli --socket /tmp/lhbd_device.sock --status
./build/lhbd_cli --socket /tmp/lhbd_device.sock --reset

Register addresses are limited to 16 bits and register values to 32 bits. Invalid numeric input and unknown sensor names are rejected by the CLI instead of being silently truncated or mapped to another sensor.

Binary protocol

Each frame uses a fixed 16-byte header followed by an optional payload and a 4-byte CRC-32.

Offset Size Field
0 4 Magic = 0x4C484244 ("LHBD")
4 1 Protocol version
5 1 Command
6 1 Status
7 1 Reserved
8 2 Sequence number
10 2 Reserved
12 4 Payload length
16 N Payload
16+N 4 CRC-32 over header + payload

All multi-byte integer fields are encoded in big-endian order. Payloads are limited to 4096 bytes.

The implemented commands are:

  • Ping;
  • ReadRegister;
  • WriteRegister;
  • ReadSensor;
  • GetStatus;
  • Reset.

The simulator exposes four sensor IDs:

  • temperature;
  • voltage;
  • current;
  • gyro X.

Timeout and retry behavior

DeviceDriver assigns a sequence number to each logical command. The same sequence is retained when the command is retried.

A response is accepted only when:

  • the frame structure is valid;
  • the CRC is valid;
  • the response sequence matches the request sequence;
  • the response command matches the request command.

Timeouts, CRC failures and invalid correlated responses are retried up to DriverConfig::maxRetries.

The UNIX socket transport applies one absolute timeout to the complete frame read, including header, payload and CRC. A peer that sends only a partial frame therefore cannot leave the read blocked indefinitely.

Fault injection

The simulator can deliberately delay, drop or corrupt responses:

python3 simulator/device_simulator.py \
 --socket /tmp/lhbd_device.sock \
 --delay-ms 50 \
 --drop-rate 0.3 \
 --corrupt-rate 0.2

--drop-rate and --corrupt-rate must be between 0.0 and 1.0, and --delay-ms must be non-negative.

This is useful for observing timeout, retry and CRC statistics from the CLI.

Integration test

The integration script launches the Python simulator, waits for its socket, executes the C++ CLI demo, validates representative output and cleans up the process/socket afterward.

Run it directly with:

python3 scripts/run_integration_test.py \
 --binary build/lhbd_cli \
 --socket /tmp/lhbd_integration.sock

Or simply run:

make integration

Driver API example

#include "lhbd/core/DeviceDriver.hpp"
#include "lhbd/transport/UnixSocketTransport.hpp"
#include <memory>
int main() {
 auto transport = std::make_unique<lhbd::transport::UnixSocketTransport>(
 "/tmp/lhbd_device.sock");
 lhbd::core::DeviceDriver driver(std::move(transport));
 if (!driver.connect()) {
 return 1;
 }
 if (!driver.writeRegister(0x0010, 0x12345678)) {
 return 2;
 }
 const auto value = driver.readRegister(0x0010);
 const auto temperature = driver.readSensor(
 lhbd::protocol::SensorId::Temperature);
 return value && temperature ? 0 : 3;
}

Tests currently cover

  • the standard CRC-32 reference vector;
  • frame encode/decode round trips;
  • CRC corruption detection;
  • malformed and oversized frames;
  • ping requests;
  • register reads and writes;
  • sensor and status decoding;
  • reset handling;
  • timeout retries;
  • CRC retries;
  • response-command mismatch detection and retry;
  • driver configuration edge cases;
  • the complete Python simulator + C++ CLI path.

CI

.github/workflows/ci.yml builds and tests the project on GitHub Actions with:

  • GCC;
  • Clang;
  • warnings treated as errors;
  • AddressSanitizer + UndefinedBehaviorSanitizer.

Design limitations

The current implementation deliberately remains small and demonstrative:

  • communication is synchronous and request/response oriented;
  • one DeviceDriver instance is not designed for concurrent transactions from multiple threads;
  • the simulator is a development fixture, not a security boundary;
  • no physical UART/SPI/I2C backend is included;
  • retries reuse the same sequence number, which is suitable for the current idempotent simulated operations but should be reconsidered before adding non-idempotent commands.

Those constraints keep the repository focused on protocol framing, driver abstraction, error handling and testability rather than board-specific hardware setup.

About

Créer un mini-driver utilisateur C++ qui communique avec un périphérique simulé via un bus type I2C / SPI / UART.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /