An ESP-IDF project base with clang-tidy, clangd, and CI pre-configured, host-side unit tests ready to run, and optional ESP-Matter support. Start writing firmware, not tooling config.
- clang-format, clang-tidy, and clangd work out of the box —
sanitize_compile_db.pystrips GCC-specific flags from ESP-IDF'scompile_commands.jsonand injects the correct toolchain/sysroot; works on both Xtensa and RISC-V targets - CI pre-configured — GitHub Actions for static analysis and formatting; auto-selects the correct Docker image when Matter is enabled
- Host-side unit tests — Unity via CMake
FetchContent; test business logic without hardware - App structure —
main/entry point +components/app/business logic, ready to expand - Optional ESP-Matter — connectedhomeip integration with C++17 pinning for Matter components
| Tool | Version | Notes |
|---|---|---|
| ESP-IDF | v5.4+ | Required |
| CMake | 3.16+ | For host tests |
| clang-format / clang-tidy / clangd | 18+ | Recommended |
| ESP-Matter | release_v1.5+ | Optional |
Click "Use this template" → "Create a new repository" on GitHub, then clone your new repo.
Rename the project in CMakeLists.txt:
project(your-project-name)Build and flash:
make esp-build
make esp-flashm # flash + open serial monitor.
├── .github/
│ ├── workflows/
│ │ ├── esp-lint.yml # clang-tidy on ESP target (auto-picks Matter image if needed)
│ │ └── formatting.yml # clang-format check
│ └── disabled_workflows/
│ └── host-checks.yml # host tests + lint (enable when you add testable components)
├── components/
│ └── app/ # Main application component
│ ├── include/app.h # app_ctx_t, app_init(), app_run()
│ ├── app.c
│ └── CMakeLists.txt
├── main/
│ ├── main.c # app_main() — calls app_init() then app_run(), handles errors
│ ├── idf_component.yml # IDF component manager dependencies
│ └── CMakeLists.txt
├── tests/
│ └── host/ # Host-side unit tests (Unity, no hardware needed)
│ ├── cmake/
│ │ └── AddUnityTest.cmake
│ └── CMakeLists.txt
├── .clang-format # LLVM-based style, 85 col limit, 4-space indent
├── .clang-tidy # Static analysis rules, all warnings as errors
├── .clangd # LSP config — strips GCC flags for IDE compatibility
├── sanitize_compile_db.py # Patches IDF's compile_commands.json for clang-tidy
├── sdkconfig.defaults # IDF defaults (partition table hints)
├── CMakeLists.txt # Top-level build
└── Makefile # Convenience targets for all common workflows
app_main() calls app_init() then app_run() on app_ctx_t and handles errors uniformly. All business logic goes in components/app/ or new components alongside it; main.c stays minimal.
Dependencies pulled via IDF component manager (main/idf_component.yml):
| Library | Purpose |
|---|---|
slog |
Structured tag-based logging (SLOGI, SLOGE, …) |
embedlibs |
Embedded utilities (EXTERN_C_BEGIN/END, etc.) |
| Target | Description |
|---|---|
esp-build |
Build the project (idf.py build) |
esp-flash |
Flash to device |
esp-flashm |
Flash and open serial monitor |
esp-monitor |
Open serial monitor only |
esp-lint |
Run clang-tidy on ESP sources |
esp-sanitize-db |
Patch build/compile_commands.json → build/tidy/compile_commands.json (called by esp-lint) |
esp-menuconfig |
Open IDF menuconfig |
esp-clean |
Full clean (build artifacts, managed components, cache) |
Override the default serial port: make esp-flash PORT_ESP=/dev/ttyUSB0
| Target | Description |
|---|---|
host-build |
Build host test suite (CMake + Unity) |
host-test |
Run tests on the host machine |
host-lint |
Run clang-tidy on host-compatible components |
host-fullcheck |
host-test + host-lint |
host-clean |
Remove host build artifacts |
| Target | Description |
|---|---|
check-format |
Verify formatting with clang-format (dry-run) |
lint |
esp-lint + host-lint + check-format |
fullcheck |
lint + host-test |
clean |
Clean all (ESP + host) |
| Target | Description |
|---|---|
use-esp |
Symlink compile_commands.json → ESP build DB |
use-host |
Symlink compile_commands.json → host test build DB |
The .clangd config strips ESP-IDF's GCC-specific flags (-mlongcalls, -mcpu=*, -march=*, etc.) so that clangd works correctly in VS Code, CLion, Neovim, and any other LSP-capable editor.
Switch compile_commands.json between build targets:
make use-esp # point clangd at the ESP build DB
make use-host # point clangd at the host test build DBThe tests/host/ directory uses Unity fetched automatically via CMake FetchContent. Tests run natively — no hardware required.
make host-testTo add a component to the host test suite:
- Add the component name to
LIBS2TESTintests/host/CMakeLists.txt. - Create a
tests/directory inside the component. - Add a
CMakeLists.txtusing theadd_unity_test()helper:
add_unity_test(my_component_tests
SRCS test_my_component.c
LIBS my_component
)Enable the host-checks.yml workflow once you have tests to run.
-
Set the environment variable:
export ESP_MATTER_PATH=/path/to/esp-matter -
Enable in
CMakeLists.txt:set(ESP_MATTER_ENABLED true)
-
Clean and rebuild:
make esp-clean make esp-build
C++ standard: ESP-IDF defaults to GNU C++23 (-std=gnu++2b). When Matter is enabled, Matter/CHIP components are compiled with -std=gnu++17 -w to avoid C++23 incompatibilities in upstream connectedhomeip; your code stays on C++23.
CI: The esp-lint workflow auto-detects ESP_MATTER_ENABLED and selects the correct Docker image — ghcr.io/sivakov512/esp-idf for plain builds or ghcr.io/sivakov512/esp-matter when Matter is active.
Based on LLVM style with project tweaks:
- 4-space indentation, 85-column limit
- Tabs → spaces
- Braces:
Attach - Sorted includes, blocks preserved
Checks enabled: clang-diagnostic-*, clang-analyzer-*, bugprone-*, performance-*, portability-*, modernize-*, readability-*, misc-*, plus select google-* rules. All warnings are treated as errors.
Naming conventions:
| Context | Convention | Example |
|---|---|---|
| C functions | snake_case |
app_init |
| C structs / typedefs | snake_case_t |
app_ctx_t |
| C++ classes / enums | CamelCase |
WifiManager |
| C++ methods | snake_case |
connect |
| C++ members | snake_case_ |
retry_count_ |
| Class constants | kCamelCase |
kMaxRetries |
| Enum values | UPPER_CASE |
STATE_IDLE |
| Macros & global constants | UPPER_CASE |
TAG |
| Global variables | g_ prefix |
g_event_loop |
| Namespaces | lower_case |
wifi |
See .clang-tidy for the complete configuration.