-
Notifications
You must be signed in to change notification settings - Fork 3
RaftLibraryDeveloperGuide
Building applications with Raft is documented in Quick Start and Build Process. This page covers the other side — building, testing and debugging Raft libraries themselves — and the conventions that let a library ship its own examples and tests without forking the build system.
The Raft libraries (RaftCore, RaftSysMods, RaftI2C, RaftWebServer, …) are normally consumed via FetchContent inside an application project. To make them practical to develop on, each library follows a small set of conventions:
- A
raftdevlibs/override mechanism in application projects, so a library can be edited in place without being clobbered by the build. - An in-library
unit_tests/folder that builds the library as a Raft project, with the library itself injected as a component. - An in-library
examples/folder containing one or more standalone Raft applications. - An in-library
linux_unit_tests/folder for host-side tests that compile with plaing++— no ESP-IDF needed. - An in-library
devdocs/folder for design notes that may be promoted into this wiki.
The most important developer-assistance feature is the raftdevlibs/ override folder. It is the recommended way to develop Raft libraries against a real application.
When RaftBootstrap.cmake is about to fetch a component listed in RAFT_COMPONENTS, it first checks for:
<project-root>/raftdevlibs/<ComponentName>
If that directory exists, it is used in place of the GitHub fetch. The build prints:
============================================================
LOCAL (DEBUG) LIBRARY: RaftCore
Using: /path/to/project/raftdevlibs/RaftCore
(Skipping fetch from https://github.com/robdobsn/RaftCore.git)
============================================================
Why this matters for library developers:
-
No risk of overwrite.
raftdevlibs/lives at the project root, alongsidemain/andsystypes/. The Raft build never writes into it — fetched libraries land inbuild/_deps/<component>-src/, which gets blown away onidf.py fullclean. Anything you put inraftdevlibs/survives every kind of clean. - Real-world testing. You are running the library exactly as a downstream user would, including SysType layering, config merging, GenWebUI and partition handling.
- Per-library, not per-project. Override only the library you are working on; everything else is still fetched and pinned, so you do not accidentally drift the rest of the system.
-
No code changes required. The override is detected purely from directory existence — the application's
CMakeLists.txt,features.cmakeandRAFT_COMPONENTSall stay untouched.
Typical setup:
my-app/
├── raftdevlibs/
│ └── RaftI2C/ ← `git clone https://github.com/robdobsn/RaftI2C` here
├── main/
├── systypes/
└── CMakeLists.txt
The folder name must exactly match the component name in RAFT_COMPONENTS (case-sensitive). Add raftdevlibs/ to .gitignore so it does not leak into the application repo. Full reference: Local Raft Development Libraries.
Tip. When iterating on multiple libraries together (e.g. a
RaftCorechange consumed by a newRaftSysModsfeature), put both clones inraftdevlibs/. The build will use both local copies and resolvefind_package/REQUIRESbetween them transparently.
Most Raft libraries ship a unit_tests/ folder at the repo root that builds the library as a Raft project against itself:
RaftCore/
├── components/ ← the library being tested
├── unit_tests/
│ ├── CMakeLists.txt ← bootstraps a Raft project
│ ├── main/
│ │ ├── CMakeLists.txt ← test sources + REQUIRES RaftCore
│ │ └── *.cpp ← Unity tests
│ └── systypes/
│ └── unittest/
│ ├── features.cmake
│ ├── partitions.csv
│ └── sdkconfig.defaults
The trick that makes this work is in unit_tests/CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
# Use the local RaftCore (parent directory) instead of fetching from GitHub
set(FETCHCONTENT_SOURCE_DIR_RAFTCORE "${CMAKE_SOURCE_DIR}/.." CACHE PATH "" FORCE)
# Bootstrap as a normal Raft project
set(BOOTSTRAP_URL "https://github.com/robdobsn/RaftCore/releases/download/v1.37.1/RaftBootstrap.cmake")
file(DOWNLOAD ${BOOTSTRAP_URL} "${CMAKE_BINARY_DIR}/RaftBootstrap.cmake")
include("${CMAKE_BINARY_DIR}/RaftBootstrap.cmake")
# Add parent directory to find components
list(APPEND EXTRA_COMPONENT_DIRS "..")
project(${_systype_name} DEPENDS ${ADDED_PROJECT_DEPENDENCIES})Two CMake mechanisms do the heavy lifting:
-
FETCHCONTENT_SOURCE_DIR_<NAME>— a built-inFetchContentoverride. SettingFETCHCONTENT_SOURCE_DIR_RAFTCOREtellsFetchContent_Declare(raftcore …)to use that directory verbatim instead of cloning. Here${CMAKE_SOURCE_DIR}/..is the library checkout itself, so the library tests its own working tree. This is theunit_tests/-folder equivalent ofraftdevlibs/— necessary becauseunit_tests/lives one level below the library, and the library cannot put itself in its ownraftdevlibs/folder. -
list(APPEND EXTRA_COMPONENT_DIRS "..")— points ESP-IDF's component scanner at the library'scomponents/tree, so test targets canREQUIRES RaftCoredirectly without involving the bootstrap registration.
The matching unit_tests/main/CMakeLists.txt then registers the test runner against Unity:
idf_component_register(
SRCS
"test_app_main.cpp"
"RaftJson_test_values.cpp"
# … other test files …
INCLUDE_DIRS "."
REQUIRES esp_system unity RaftCore nvs_flash
WHOLE_ARCHIVE
)Building and running:
cd RaftCore/unit_tests
raft run -s unittest # build, flash, monitor on a connected device
# or, with raw idf.py:
idf.py -DSYSTYPE=unittest build flash monitorWHOLE_ARCHIVE is important — without it the linker discards Unity test functions that are only referenced by name from the runner.
- Create the test source under
<lib>/unit_tests/main/MyFeature_test.cppusingTEST_CASEmacros. - Add the file to
SRCSinunit_tests/main/CMakeLists.txt. - If the test needs new dependencies, append them to
REQUIRES. -
cd unit_tests && raft run -s unittest— runs on hardware via Unity.
The Raft tests run on real ESP32 hardware (or QEMU) so they can exercise NVS, the file system, BLE/WiFi stacks and the comms channels in the same shape end-users see. Pure host-side checks live in linux_unit_tests/ (below).
Examples follow the same EXTRA_COMPONENT_DIRS ".." pattern as the unit tests, but are organised as full demonstrator applications under an examples/ folder:
RaftWebServer/
├── components/
└── examples/
├── basic/
│ ├── CMakeLists.txt
│ ├── main/
│ ├── systypes/
│ │ └── BasicWebServer/
│ ├── Makefile
│ ├── test.py
│ └── dependencies.lock
└── perftest/
└── …
The example's top-level CMakeLists.txt is identical to a normal Raft application except it pulls in the parent library:
cmake_minimum_required(VERSION 3.16)
set(BOOTSTRAP_URL "https://github.com/robdobsn/RaftCore/releases/download/v1.37.1/RaftBootstrap.cmake")
file(DOWNLOAD ${BOOTSTRAP_URL} "${CMAKE_BINARY_DIR}/RaftBootstrap.cmake")
include("${CMAKE_BINARY_DIR}/RaftBootstrap.cmake")
project(${_systype_name} DEPENDS ${ADDED_PROJECT_DEPENDENCIES})Each example has its own systypes/<Name>/, partitions.csv and features.cmake. To use the parent library directly (rather than re-fetching it from GitHub), append EXTRA_COMPONENT_DIRS ".." and/or FETCHCONTENT_SOURCE_DIR_<NAME> as in the unit_tests/ pattern. Some libraries (e.g. RaftI2C's TestWebUI/) also call FetchContent_Populate(raftcore) and include(${raftcore_SOURCE_DIR}/scripts/RaftProject.cmake) directly, which is useful when an example needs a WebUI build and an FSImage.
- Create
examples/<MyExample>/with the standard Raft layout (main/,systypes/<MyExample>/, top-levelCMakeLists.txt). - In the
CMakeLists.txt, addlist(APPEND EXTRA_COMPONENT_DIRS "../..")so the parent library is available as a component. - List the parent library in your example
main/CMakeLists.txtREQUIRES. - Optionally add a
Makefileshortcut forraft build,raft flashetc. and atest.pyfor CI smoke tests.
For tests that should not require a flashed device, several libraries ship a linux_unit_tests/ folder built with plain g++ and a Makefile. Host-side tests are useful for parsers, expression evaluation, JSON handling, device-record code generation and other CPU-only logic.
RaftI2C/linux_unit_tests/
├── Makefile ← build with `make`
├── main.cpp ← test driver
├── utils.cpp / utils.h ← thin host-side stubs
├── Logger.h, esp_attr.h ← shims for ESP-IDF symbols
├── TestDevTypeRecs.json
└── DeviceTypeRecords_generated.h ← produced by ConvertJsonToC.py
The library's Makefile typically:
- Adds
-Ipaths into the real component source directories (-I../components/...), so the same.cppfiles compile against host stubs. - Optionally clones
RaftCoreinto./RaftCore/viapython3 ../scripts/FetchGitRepo.pyif the test needs RaftCore sources too. - Runs any code-generation scripts (e.g.
ProcessDevTypeJsonToC.py) before compiling. - Builds a single executable that runs all tests when invoked.
Running:
cd RaftI2C/linux_unit_tests
make
./linux_unit_testsBoth VS Code workspace folders for RaftCore and RaftI2C ship a default make build task targeting linux_unit_tests/, so Ctrl+Shift+B runs them directly.
Use linux_unit_tests/ for fast iteration. Use unit_tests/ (Unity on hardware) when the code under test depends on FreeRTOS, NVS, network stacks or any other ESP-IDF runtime.
Each library ships a devdocs/ folder at its repo root containing design notes, plans and investigations that are too rough or too implementation-specific for the public wiki. Examples:
RaftCore/devdocs/bus-device-tracking-refactor-plan.mdRaftI2C/devdocs/i2c-adaptive-yield-plan.mdRaftSysMods/devdocs/BLE-publish-throughput-analysis.md
These are intended to be living documents — write them as you investigate or refactor, and promote the relevant parts into a wiki page once the work has stabilised. The documentation improvement plan tracks promotion candidates.
Conventions:
- Markdown only.
- One file per topic. Date-prefix or version-prefix the filename if the doc captures a specific point-in-time investigation.
- Cross-link to wiki pages with
[Title](../WikiPageName)when you reference public docs from a devdoc. - Do not duplicate API details that belong in the wiki — link to them.
A few smaller conveniences that come up while developing libraries:
-
Pinned bootstrap URL. The
BOOTSTRAP_URLin everyCMakeLists.txtpins to a specific RaftCore release. To test with an in-development bootstrap, overrideRAFTCORE_GIT_TAG/RAFTCORE_GIT_REPO_URLor — simpler — drop a freshRaftCorecheckout intoraftdevlibs/. Full mechanics: RaftBuildProcess. -
raftCLI.raft build,raft flash,raft monitor,raft run,raft debugandraft otaare wrappers aroundidf.pythat understand SysType selection. See Raft CLI. -
dependencies.lock. Each top-level project (and each example) has a
dependencies.lockat its root. Commit it to pin transitive ESP component versions; delete it to allow upgrades on the nextidf.py reconfigure. -
Generated headers. Several libraries generate C/C++ headers from JSON inputs (e.g.
RaftI2C/scripts/ConvertJsonToC.py,RaftCore/scripts/GenDeviceTypes.py). When editing the JSON, run the generator script — or trigger a CMake reconfigure — before rebuilding. -
Docker images. Several libraries ship a
Dockerfilenext tounit_tests/that pre-bakes ESP-IDF and the Python tooling. Useful for CI and reproducible builds.compose.yaml(where present) wires up the dev container. -
VS Code tasks.
RaftCore/linux_unit_tests/andRaftI2C/linux_unit_tests/define default build tasks soCtrl+Shift+Bbuilds the host-side tests of whichever workspace folder is active. -
raft debug. Streams the device's log buffer over the network without taking the serial port, including across BLE and WiFi. See Remote Logging.
-
Local Raft Development Libraries — the
raftdevlibs/override mechanism in detail. -
Raft Build Process — bootstrap stages,
RAFT_COMPONENTS,FetchContentflow. -
Raft CLI —
raft build/flash/run/debug/ota. - SysTypes — variant configuration consumed by every example, test and application.
- Writing Your First SysMod — the application-side equivalent of this guide.
Getting Started
- Quick Start
- Architecture at a Glance
- Writing Your First SysMod
- Adding a Comms Channel
- Adding an I2C Device Type
- PlatformIO / Arduino
Scaffolding & Building
- Raft CLI
- SysTypes
- Top-Level SysType
- Build Process
- WebUI Build Pipeline
- File System
- Partitions & Flash
- Local Dev Libraries
- Library Developer Guide
Architecture
Built-in SysMods
- NetworkManager
- BLEManager
- WebServer
- MQTTManager
- SerialConsole
- CommandSerial
- CommandSocket
- CommandFile
- FileManager
- LogManager
- ESPOTAUpdate
- StatePublisher
- Remote Logging
- Data Source Registration
Comms & Protocols
- Stack Overview
- Comms Channels
- ProtocolExchange
- RICREST Protocol
- Real-Time Streams
- Adding REST Endpoints
- Built-in REST Endpoints
- File Download (OKTO)
- OTA Update Flow
Devices & Buses
- DeviceManager
- Device Manager REST API
- Device Factory & Classes
- Device Type Records
- Adding an I2C Device Type
- Device Data Publishing
- Data Logger
- I2C Bus
- I2C Device Scanning
- I2C ID & Polling
- MotorControl Overview
- MotorControl Config
- MotorControl Commands
Helpers
Reference