@polderlabs/bizar 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +364 -0
- package/cli/audit.mjs +144 -0
- package/cli/banner.mjs +41 -0
- package/cli/bin.mjs +186 -0
- package/cli/copy.mjs +508 -0
- package/cli/export.mjs +87 -0
- package/cli/init.mjs +147 -0
- package/cli/install.mjs +390 -0
- package/cli/plan-templates.mjs +523 -0
- package/cli/plan.mjs +2087 -0
- package/cli/prompts.mjs +163 -0
- package/cli/update.mjs +273 -0
- package/cli/utils.mjs +153 -0
- package/config/AGENTS.md +282 -0
- package/config/agents/baldr.md +148 -0
- package/config/agents/forseti.md +112 -0
- package/config/agents/frigg.md +101 -0
- package/config/agents/heimdall.md +157 -0
- package/config/agents/hermod.md +144 -0
- package/config/agents/mimir.md +115 -0
- package/config/agents/odin.md +309 -0
- package/config/agents/quick.md +78 -0
- package/config/agents/semble-search.md +44 -0
- package/config/agents/thor.md +97 -0
- package/config/agents/tyr.md +96 -0
- package/config/agents/vidarr.md +100 -0
- package/config/agents/vor.md +140 -0
- package/config/commands/audit.md +1 -0
- package/config/commands/explain.md +1 -0
- package/config/commands/init.md +1 -0
- package/config/commands/learn.md +1 -0
- package/config/commands/pr-review.md +1 -0
- package/config/commands/tailscale-serve.md +96 -0
- package/config/hooks/README.md +29 -0
- package/config/hooks/post-tool-use.md +16 -0
- package/config/hooks/pre-tool-use.md +16 -0
- package/config/opencode.json +52 -0
- package/config/opencode.json.template +52 -0
- package/config/rules/general.md +8 -0
- package/config/rules/git.md +11 -0
- package/config/rules/javascript.md +10 -0
- package/config/rules/python.md +10 -0
- package/config/rules/testing.md +10 -0
- package/config/skills/bizar/README.md +9 -0
- package/config/skills/bizar/SKILL.md +187 -0
- package/config/skills/cpp-coding-standards/README.md +28 -0
- package/config/skills/cpp-coding-standards/SKILL.md +634 -0
- package/config/skills/cpp-coding-standards/agents/openai.yaml +4 -0
- package/config/skills/cpp-coding-standards/references/concurrency.md +320 -0
- package/config/skills/cpp-coding-standards/references/error-handling.md +229 -0
- package/config/skills/cpp-coding-standards/references/memory-safety.md +216 -0
- package/config/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
- package/config/skills/cpp-coding-standards/references/review-checklist.md +96 -0
- package/config/skills/cpp-testing/README.md +28 -0
- package/config/skills/cpp-testing/SKILL.md +304 -0
- package/config/skills/cpp-testing/agents/openai.yaml +4 -0
- package/config/skills/cpp-testing/references/coverage.md +370 -0
- package/config/skills/cpp-testing/references/framework-compare.md +175 -0
- package/config/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
- package/config/skills/cpp-testing/references/mocking.md +364 -0
- package/config/skills/cpp-testing/references/tdd-workflow.md +308 -0
- package/config/skills/embedded-esp-idf/README.md +41 -0
- package/config/skills/embedded-esp-idf/SKILL.md +439 -0
- package/config/skills/embedded-esp-idf/agents/openai.yaml +4 -0
- package/config/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
- package/config/skills/embedded-esp-idf/references/host-tests.md +164 -0
- package/config/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
- package/config/skills/embedded-esp-idf/references/kconfig.md +159 -0
- package/config/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
- package/config/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
- package/config/skills/embedded-esp-idf/references/nvs.md +121 -0
- package/config/skills/embedded-esp-idf/references/packed-structs.md +192 -0
- package/config/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
- package/config/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
- package/config/skills/self-improvement/SKILL.md +64 -0
- package/package.json +47 -0
- package/templates/plan/htmx.min.js +1 -0
- package/templates/plan/library/bug-investigation.mdx +79 -0
- package/templates/plan/library/decision-record.mdx +71 -0
- package/templates/plan/library/feature-design.mdx +92 -0
- package/templates/plan/meta.json.template +8 -0
- package/templates/plan/plan.canvas.template +1711 -0
- package/templates/plan/plan.html.template +937 -0
- package/templates/plan/plan.mdx.template +46 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Host-side tests
|
|
2
|
+
|
|
3
|
+
ESP-IDF firmware that depends on FreeRTOS, drivers, and NVS cannot run on the host. But policy modules, payload encoders/decoders, pure-C++ algorithms, and protocol parsers can — and should — be unit-tested on the host with a normal C++ toolchain.
|
|
4
|
+
|
|
5
|
+
`(AMS7)` AMS7's host tests are deliberately lightweight: one `.cpp` test file + one `run_*_test.sh` shell wrapper. No CMake host build, no fakes library, no GoogleTest. The whole test is `g++ -std=c++17 ... && ./a.out`.
|
|
6
|
+
|
|
7
|
+
For larger projects with GoogleTest / Catch2 / fakes, see the **`cpp-testing`** skill.
|
|
8
|
+
|
|
9
|
+
## AMS7 pattern (test + run script)
|
|
10
|
+
|
|
11
|
+
A typical test file in `tests/connectivity/`:
|
|
12
|
+
|
|
13
|
+
```cpp
|
|
14
|
+
// tests/connectivity/espnow_imu_frame_test.cpp
|
|
15
|
+
#include <cassert>
|
|
16
|
+
#include <cstdio>
|
|
17
|
+
|
|
18
|
+
#include "espnow_imu_frame.hpp"
|
|
19
|
+
|
|
20
|
+
int main() {
|
|
21
|
+
core_espnow_imu_frame_v1_t frame = {};
|
|
22
|
+
espnow_imu_frame_init(&frame, 0x1234U, 9U, 55U,
|
|
23
|
+
1, -2, 3, -4, 5, -6);
|
|
24
|
+
|
|
25
|
+
assert(frame.magic == CORE_ESPNOW_IMU_MAGIC);
|
|
26
|
+
assert(frame.version == CORE_ESPNOW_IMU_VERSION);
|
|
27
|
+
assert(frame.frame_type == CORE_ESPNOW_FRAME_IMU_RAW);
|
|
28
|
+
assert(frame.id == 0x1234U);
|
|
29
|
+
assert(frame.seq == 9U);
|
|
30
|
+
assert(frame.age_ms == 55U);
|
|
31
|
+
assert(frame.accel_x == 1);
|
|
32
|
+
assert(frame.accel_y == -2);
|
|
33
|
+
assert(frame.accel_z == 3);
|
|
34
|
+
assert(frame.gyro_x == -4);
|
|
35
|
+
assert(frame.gyro_y == 5);
|
|
36
|
+
assert(frame.gyro_z == -6);
|
|
37
|
+
|
|
38
|
+
std::puts("espnow_imu_frame_test: ok");
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
And the wrapper `tests/connectivity/run_espnow_imu_frame_test.sh`:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
#!/usr/bin/env bash
|
|
47
|
+
set -euo pipefail
|
|
48
|
+
|
|
49
|
+
g++ -std=c++17 \
|
|
50
|
+
tests/connectivity/espnow_imu_frame_test.cpp \
|
|
51
|
+
main/connectivity/espnow_imu_frame.cpp \
|
|
52
|
+
-I main/connectivity \
|
|
53
|
+
-o /tmp/espnow_imu_frame_test
|
|
54
|
+
|
|
55
|
+
/tmp/espnow_imu_frame_test
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Run it:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
bash tests/connectivity/run_espnow_imu_frame_test.sh
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The wrapper does three things: compile, link, run. Add `g++` flags (`-Wall -Wextra -Werror`) for stricter checking.
|
|
65
|
+
|
|
66
|
+
## What can be host-tested
|
|
67
|
+
|
|
68
|
+
- **Packed structs and frame encoders** — `espnow_*_frame_*`, `gateway_command_*`, `marker_*`. Pure data marshalling.
|
|
69
|
+
- **Policy modules** — `connectivity_mode_policy`, `electrode_detach_gate`, `acquisition_command_policy`. Pure decision logic.
|
|
70
|
+
- **Pure algorithms** — `hr_fused`, `resp_fallback`, `summary_metrics`. Make sure they don't `malloc` or call FreeRTOS and they're trivially host-testable.
|
|
71
|
+
- **State machines** — anything that's a transition table plus a few event handlers.
|
|
72
|
+
|
|
73
|
+
## What must NOT be host-tested
|
|
74
|
+
|
|
75
|
+
- Anything that includes `<freertos/FreeRTOS.h>`.
|
|
76
|
+
- Anything that includes `esp_log.h` (unless you fake it).
|
|
77
|
+
- Anything that includes driver headers (`driver/spi_master.h`, `driver/i2c.h`).
|
|
78
|
+
- Anything that pulls in NVS, BLE, Wi-Fi, ESP-NOW stacks.
|
|
79
|
+
|
|
80
|
+
If a header you want to test transitively includes one of these, factor the pure logic out into a separate translation unit. The split is usually: `policy.hpp/cpp` for pure logic, `policy_runtime.cpp` for the FreeRTOS-aware glue.
|
|
81
|
+
|
|
82
|
+
## Fakes for FreeRTOS (when you need them)
|
|
83
|
+
|
|
84
|
+
If you have a real CMake host project (not the AMS7 minimal pattern) and want to test code that touches FreeRTOS, use the **`cpp-testing`** skill's fakes recipe. The minimum useful fakes:
|
|
85
|
+
|
|
86
|
+
- `fake_freertos.h` — declares the FreeRTOS API surface used by the code under test, with hooks (`xQueueCreate` returns a recorded handle, etc.).
|
|
87
|
+
- `fake_log.h` — empty `ESP_LOG*` macros or a recorded message list.
|
|
88
|
+
|
|
89
|
+
Keep fakes local to the test, not in `main/`. Use `BUILD_ONLY_FAKES` or a separate `tests/host/` CMake target.
|
|
90
|
+
|
|
91
|
+
## CMake host project (alternative to per-test shell scripts)
|
|
92
|
+
|
|
93
|
+
For larger suites:
|
|
94
|
+
|
|
95
|
+
```cmake
|
|
96
|
+
# tests/host/CMakeLists.txt
|
|
97
|
+
cmake_minimum_required(VERSION 3.16)
|
|
98
|
+
project(ams7_host_tests CXX)
|
|
99
|
+
|
|
100
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
101
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
102
|
+
|
|
103
|
+
add_executable(test_espnow_imu
|
|
104
|
+
../../main/connectivity/espnow_imu_frame.cpp
|
|
105
|
+
espnow_imu_frame_test.cpp
|
|
106
|
+
)
|
|
107
|
+
target_include_directories(test_espnow_imu PRIVATE
|
|
108
|
+
${CMAKE_SOURCE_DIR}/../../main/connectivity
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
cmake -S tests/host -B tests/host/build
|
|
114
|
+
cmake --build tests/host/build
|
|
115
|
+
./tests/host/build/test_espnow_imu
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Tradeoffs vs the AMS7 minimal pattern:
|
|
119
|
+
|
|
120
|
+
| Aspect | AMS7 shell wrapper | CMake host project |
|
|
121
|
+
|------------------|---------------------------------|---------------------------|
|
|
122
|
+
| Setup cost | Zero — one shell script per test| One CMakeLists to maintain |
|
|
123
|
+
| Linking firmware | Direct `g++ ... file.cpp` | Same, via CMake rules |
|
|
124
|
+
| Adding fakes | None | Add a `fake/` directory |
|
|
125
|
+
| Test discovery | Manual | `ctest` |
|
|
126
|
+
| CI integration | `find tests -name run_*.sh` | `ctest --output-on-failure` |
|
|
127
|
+
| Build parallelism| Sequential | Parallel via `cmake --build` |
|
|
128
|
+
|
|
129
|
+
Both are valid. AMS7 picks the shell wrapper because every test is one file and one assertion set — `ctest` overhead is not worth it.
|
|
130
|
+
|
|
131
|
+
## Tests vs fixtures
|
|
132
|
+
|
|
133
|
+
AMS7 has `tests/fixtures/` for replay inputs (recorded sensor traces used to drive host tests):
|
|
134
|
+
|
|
135
|
+
```cpp
|
|
136
|
+
// tests/fixtures/replay/ecg_2024-09-12.bin — raw bytes recorded from a real run
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Load fixtures in tests via `std::ifstream` rather than copy-pasting bytes into the test source.
|
|
140
|
+
|
|
141
|
+
## Common pitfalls
|
|
142
|
+
|
|
143
|
+
- **Including `<esp_log.h>` from a header that's host-tested.** Fix: forward-declare the log macros in the header, include the real one only in `.cpp`. Or include the AMS7 fake `tests/support/host_include/esp_log.h` if one exists.
|
|
144
|
+
- **`malloc` in pure logic.** Symptom: host test runs fine but firmware crashes from fragmentation. Fix: allocate in the wrapper, pass a buffer in.
|
|
145
|
+
- **Globals with static initialization.** Symptom: works on host, fails on target because init order is different. Fix: prefer explicit `init()` calls.
|
|
146
|
+
- **Asserting on floating-point equality.** Use a tolerance: `assert(std::abs(a - b) < 1e-3)`.
|
|
147
|
+
- **Forgetting `-Werror` in CI.** Compile-only warnings become ship-time bugs. The AMS7 host tests are silent on warnings by default; consider adding `-Wall -Wextra` to your shell wrappers.
|
|
148
|
+
- **Test passes on host, fails on firmware.** The two are usually equivalent for pure logic, but always cross-check with a firmware-side integration test (a debug print, a recorded trace, or an ESP-NOW roundtrip).
|
|
149
|
+
|
|
150
|
+
## When to grow beyond the minimal pattern
|
|
151
|
+
|
|
152
|
+
Stay with the shell wrapper while:
|
|
153
|
+
|
|
154
|
+
- Test count is < ~30.
|
|
155
|
+
- Each test is one `.cpp` plus one shell script.
|
|
156
|
+
- No fakes needed.
|
|
157
|
+
- CI just iterates `tests/ -name run_*.sh`.
|
|
158
|
+
|
|
159
|
+
Migrate to a CMake host project when:
|
|
160
|
+
|
|
161
|
+
- You need `ctest` for CI parallelism and reporting.
|
|
162
|
+
- You need fakes (FreeRTOS, log, drivers).
|
|
163
|
+
- A test needs to link multiple firmware modules together.
|
|
164
|
+
- You want fixtures automatically discovered.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# idf.py commands
|
|
2
|
+
|
|
3
|
+
`idf.py` is the single entry point to ESP-IDF v5. All commands assume the IDF environment is sourced:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
source esp/esp-idf/export.sh
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
If `idf.py` is not on PATH, `scripts/idf_env.sh` will print the source line and tell you whether the env is healthy.
|
|
10
|
+
|
|
11
|
+
## Core workflow
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
idf.py build # compile (incremental)
|
|
15
|
+
idf.py -p /dev/ttyUSB0 flash # write image to board
|
|
16
|
+
idf.py -p /dev/ttyUSB0 monitor # serial console (Ctrl-] to exit)
|
|
17
|
+
idf.py -p /dev/ttyUSB0 flash monitor # flash then monitor in one shot
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`-p` is the serial port. Linux: `/dev/ttyUSB0` or `/dev/ttyACM0`. macOS: `/dev/cu.usbserial-*`. Windows: `COM3`.
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
idf.py menuconfig # TUI Kconfig editor
|
|
26
|
+
idf.py menuconfig --no-target # don't ask to set target
|
|
27
|
+
idf.py save-defconfig # write minimal defconfig from current settings
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`menuconfig` writes the result to `sdkconfig`. To apply a layered profile:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
idf.py -B build_espnow_flow -C . \
|
|
34
|
+
--sdkconfig sdkconfig.espnow_flow \
|
|
35
|
+
build flash
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The `-B build_<name>` puts the build output in `build_<name>/`, leaving the default `build/` clean.
|
|
39
|
+
|
|
40
|
+
## Build inspection
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
idf.py size # text: app partition + IRAM + DRAM
|
|
44
|
+
idf.py size --format json # machine-readable for tooling
|
|
45
|
+
idf.py size --format csv # spreadsheet-friendly
|
|
46
|
+
idf.py size --output-file size.json
|
|
47
|
+
idf.py size-components # per-component breakdown
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`idf.py size` exit code is 0 on success regardless of size; size policy lives in project-specific tools.
|
|
51
|
+
|
|
52
|
+
## Cleaning
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
idf.py clean # remove build artifacts but keep config
|
|
56
|
+
idf.py fullclean # remove build/ AND sdkconfig (then menuconfig again)
|
|
57
|
+
idf.py -B build_espnow_flow clean # clean a specific build dir
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`fullclean` is destructive — it deletes your local `sdkconfig` overrides. Commit your `sdkconfig` and any profile files first.
|
|
61
|
+
|
|
62
|
+
## Target and project
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
idf.py set-target esp32 # esp32 / esp32s2 / esp32s3 / esp32c3 / esp32h2
|
|
66
|
+
idf.py create-project foo # scaffold a new project from a template
|
|
67
|
+
idf.py add-dependency "owner/repo^1.2.3" # pull into managed_components/
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
After `set-target`, ESP-IDF regenerates component configs — clean build artifacts:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
idf.py set-target esp32s3 && idf.py build
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Partition table
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
idf.py partition-table # regenerate partition_table/*.bin from partitions.csv
|
|
80
|
+
idf.py erase-flash # erase entire flash (use with care)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`partitions.csv` is at the project root; `idf.py menuconfig → Partition Table` selects which CSV to use. Common layouts:
|
|
84
|
+
|
|
85
|
+
- `partitions_singleapp.csv` — single OTA-less app partition
|
|
86
|
+
- `partitions_two_ota.csv` — A/B OTA slots (default for OTA-enabled projects)
|
|
87
|
+
- A custom CSV — declare `app`, `data`, `nvs`, `phy_init`, `storage` partitions with explicit sizes
|
|
88
|
+
|
|
89
|
+
`(AMS7)` The AMS7 partition table reserves a large `storage` partition for SD-aware OTA staging.
|
|
90
|
+
|
|
91
|
+
## Monitor filters
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
idf.py -p /dev/ttyUSB0 monitor --print-filter="*:I" # only INFO and above
|
|
95
|
+
idf.py monitor --print-filter="ams7driver:I espnow_summary:W" # per-tag levels
|
|
96
|
+
idf.py monitor --elf <elf_path> # decode panic backtraces
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`-d` disables line-ending conversion (binary output). `-t` adds a timestamp prefix.
|
|
100
|
+
|
|
101
|
+
Inside the monitor, useful key bindings:
|
|
102
|
+
|
|
103
|
+
- `Ctrl-]` — exit
|
|
104
|
+
- `Ctrl-T` — toggle menu (send break, change filter, etc.)
|
|
105
|
+
|
|
106
|
+
## Build flags
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
idf.py -DCMAKE_BUILD_TYPE=Debug build # Debug / Release / RelWithDebInfo / MinSizeRel
|
|
110
|
+
idf.py -DCONFIG_COMPILER_OPTIMIZATION_PERF=y build # enable IDF performance optimizations
|
|
111
|
+
idf.py -DSDKCONFIG_DEFAULTS=sdkconfig.espnow_flow build
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`-DSDKCONFIG_DEFAULTS` is equivalent to the `--sdkconfig` CLI flag and is useful for CI.
|
|
115
|
+
|
|
116
|
+
## OpenOCD / debugging
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
idf.py openocd # start OpenOCD for JTAG debugging
|
|
120
|
+
idf.py gdb # start xtensa-esp32-elf-gdb
|
|
121
|
+
idf.py gdbgui # GDB with a frontend
|
|
122
|
+
idf.py app-flash # flash only the app partition
|
|
123
|
+
idf.py bootloader-flash # flash only the bootloader
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
GDB requires an `openocd` server on the default port (3333).
|
|
127
|
+
|
|
128
|
+
## OTA
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
idf.py app # build the app, ready for OTA push
|
|
132
|
+
idf.py ota # ... or `idf.py app` then push via HTTP
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
For SD-card OTA staging: copy `build/<project>.bin` to the SD card under the OTA path your bootloader expects, then reboot.
|
|
136
|
+
|
|
137
|
+
## Useful exit-code patterns
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# CI: build, then size check
|
|
141
|
+
idf.py build && idf.py size
|
|
142
|
+
|
|
143
|
+
# AMS7: full CI gate
|
|
144
|
+
source esp/esp-idf/export.sh
|
|
145
|
+
idf.py build
|
|
146
|
+
tools/check_idf_size_budget.py
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`scripts/size_check.sh` wraps the AMS7-specific size policy and falls back to `idf.py size` when run in a non-AMS7 project.
|
|
150
|
+
|
|
151
|
+
## Common pitfalls
|
|
152
|
+
|
|
153
|
+
- **Forgot to source the env.** Symptom: `idf.py: command not found`. Fix: `source esp/esp-idf/export.sh` per shell session.
|
|
154
|
+
- **Wrong target.** Symptom: linker errors about `esp_image_header_t` size mismatches or missing `esp_hw_support`. Fix: `idf.py set-target esp32` then rebuild.
|
|
155
|
+
- **`sdkconfig` drift.** Symptom: subtle behavior changes after a `git pull`. Fix: `idf.py menuconfig` to re-resolve, or `git checkout sdkconfig` and re-apply.
|
|
156
|
+
- **`-B build_<flow>` left stale.** Symptom: build picks up old sources. Fix: `idf.py -B build_<flow> fullclean`.
|
|
157
|
+
- **Monitor hangs on macOS.** Use `/dev/cu.usbserial-*` not `/dev/tty.usbserial-*` (the `tty.` node blocks open while the driver holds it).
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Kconfig
|
|
2
|
+
|
|
3
|
+
Kconfig is the build-time configuration system ESP-IDF inherits from the Linux kernel. Options live in `Kconfig` files, are presented through `idf.py menuconfig`, and resolve to `CONFIG_*` macros visible in C/C++.
|
|
4
|
+
|
|
5
|
+
## Where Kconfig files live
|
|
6
|
+
|
|
7
|
+
ESP-IDF automatically sources a `Kconfig` file from every registered component. The top-level `Kconfig.projbuild` is required and seeds the project menu. Conventions:
|
|
8
|
+
|
|
9
|
+
- `main/Kconfig` — app-level options. Always include `main "AMS7 Project Options"` style menus.
|
|
10
|
+
- `components/<name>/Kconfig` — component-private options; only visible if the component is included in the build.
|
|
11
|
+
- `Kconfig.projbuild` — root-level options that gate top-of-tree decisions.
|
|
12
|
+
|
|
13
|
+
`(AMS7)` AMS7 puts its entire menu under `main/Kconfig` (single file, ~430 lines), with the menu title `"AMS7 Project Options"`. Every project-defined option uses the `AMS7_*` prefix.
|
|
14
|
+
|
|
15
|
+
## Adding a simple bool option
|
|
16
|
+
|
|
17
|
+
```kconfig
|
|
18
|
+
menu "Connectivity Workflow"
|
|
19
|
+
|
|
20
|
+
config AMS7_SUMMARY_ENABLE
|
|
21
|
+
bool "Enable ESP-NOW summary workflow"
|
|
22
|
+
default n
|
|
23
|
+
help
|
|
24
|
+
Master switch for the recording workflow that starts with BLE
|
|
25
|
+
control and then switches to ESP-NOW summary mode after
|
|
26
|
+
acquisition begins. Keep disabled to preserve the BLE-only
|
|
27
|
+
workflow.
|
|
28
|
+
|
|
29
|
+
endmenu
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The `CONFIG_AMS7_SUMMARY_ENABLE` macro is then visible everywhere in C/C++:
|
|
33
|
+
|
|
34
|
+
```cpp
|
|
35
|
+
#if CONFIG_AMS7_SUMMARY_ENABLE
|
|
36
|
+
init_espnow_summary();
|
|
37
|
+
#endif
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Types
|
|
41
|
+
|
|
42
|
+
| Type | C macro | Notes |
|
|
43
|
+
|-------------|--------------------------------------|----------------------------------------|
|
|
44
|
+
| `bool` | `CONFIG_FOO` (1 or undefined) | `default y` or `default n` |
|
|
45
|
+
| `int` | `CONFIG_FOO` (int literal) | `range MIN MAX` is recommended |
|
|
46
|
+
| `hex` | `CONFIG_FOO` (hex literal) | Useful for bitmasks and addresses |
|
|
47
|
+
| `string` | `CONFIG_FOO` (string literal) | `default "literal"` |
|
|
48
|
+
| `choice` | selects one of several values | Use for mutually-exclusive options |
|
|
49
|
+
|
|
50
|
+
```kconfig
|
|
51
|
+
config AMS7_HR_HOLD_MS
|
|
52
|
+
int "Hold time for fused HR before fallback (ms)"
|
|
53
|
+
depends on AMS7_VITALS_ENABLE
|
|
54
|
+
range 0 60000
|
|
55
|
+
default 10000
|
|
56
|
+
help
|
|
57
|
+
How long to keep reporting fused HR after the fused pipeline
|
|
58
|
+
reports a confident beat, before falling back to the simple
|
|
59
|
+
backend. 0 disables the hold.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
In code:
|
|
63
|
+
|
|
64
|
+
```cpp
|
|
65
|
+
#if CONFIG_AMS7_HR_HOLD_MS > 0
|
|
66
|
+
const TickType_t kHoldTicks = pdMS_TO_TICKS(CONFIG_AMS7_HR_HOLD_MS);
|
|
67
|
+
#endif
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`menuconfig` values are stored in `sdkconfig` as `CONFIG_<NAME>=<value>` lines. `idf.py menuconfig` is the editor.
|
|
71
|
+
|
|
72
|
+
## Dependencies and implications
|
|
73
|
+
|
|
74
|
+
```kconfig
|
|
75
|
+
config AMS7_BLE_VITALS_ENABLE
|
|
76
|
+
bool "Enable custom BLE vitals characteristic"
|
|
77
|
+
depends on AMS7_VITALS_ENABLE
|
|
78
|
+
default y
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- `depends on AMS7_VITALS_ENABLE` — option is **invisible** unless `AMS7_VITALS_ENABLE=y`. Selecting it auto-selects the dep.
|
|
82
|
+
- `select AMS7_ESPNOW_ENABLE` — when **this** option is enabled, force `AMS7_ESPNOW_ENABLE=y`.
|
|
83
|
+
- `imply AMS7_ESPNOW_LIVE_ECG_ENABLE` — when **this** option is enabled, set `AMS7_ESPNOW_LIVE_ECG_ENABLE=y` unless the user explicitly disabled it (softer than `select`).
|
|
84
|
+
|
|
85
|
+
`select` chains can cause cycles if used carelessly — prefer `depends on` for prerequisites.
|
|
86
|
+
|
|
87
|
+
## Ranges
|
|
88
|
+
|
|
89
|
+
```kconfig
|
|
90
|
+
config AMS7_ESPNOW_WIFI_CHANNEL
|
|
91
|
+
int "ESP-NOW Wi-Fi channel"
|
|
92
|
+
range 1 13
|
|
93
|
+
default 6
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The value is validated by menuconfig; `range` rejects out-of-range entries.
|
|
97
|
+
|
|
98
|
+
## Choices
|
|
99
|
+
|
|
100
|
+
```kconfig
|
|
101
|
+
choice AMS7_HR_BACKEND
|
|
102
|
+
prompt "Heart-rate backend"
|
|
103
|
+
default AMS7_HR_BACKEND_FUSED
|
|
104
|
+
depends on AMS7_VITALS_ENABLE
|
|
105
|
+
|
|
106
|
+
config AMS7_HR_BACKEND_SIMPLE
|
|
107
|
+
bool "Simple R-peak detector"
|
|
108
|
+
|
|
109
|
+
config AMS7_HR_BACKEND_FUSED
|
|
110
|
+
bool "Fused multi-source pipeline"
|
|
111
|
+
|
|
112
|
+
endchoice
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Only one of the listed `config`s can be `y` at a time. Use `# CONFIG_AMS7_HR_BACKEND_FUSED is not set` to switch.
|
|
116
|
+
|
|
117
|
+
## Sourcing custom Kconfig files
|
|
118
|
+
|
|
119
|
+
In a component's top-level `Kconfig` (sourced automatically), include sub-files:
|
|
120
|
+
|
|
121
|
+
```kconfig
|
|
122
|
+
mainmenu "My Component"
|
|
123
|
+
|
|
124
|
+
menu "Sensor Options"
|
|
125
|
+
source "Kconfig.sensor"
|
|
126
|
+
endmenu
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Or in `CMakeLists.txt`, register the file as part of the component:
|
|
130
|
+
|
|
131
|
+
```cmake
|
|
132
|
+
idf_component_register(
|
|
133
|
+
...
|
|
134
|
+
KCONFIG
|
|
135
|
+
"Kconfig"
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
For project-wide overlays, create `sdkconfig.espnow_flow` (or similar) and pass `--sdkconfig` to `idf.py`. Layers are applied in order: defaults, then `sdkconfig`, then any `--sdkconfig` files.
|
|
140
|
+
|
|
141
|
+
## Help text
|
|
142
|
+
|
|
143
|
+
Always include a `help` block. menuconfig shows it on the option's help line; the project documentation pulls it from `Kconfig` when generating reference. Keep it under ~10 lines.
|
|
144
|
+
|
|
145
|
+
## AMS7 conventions (project-specific)
|
|
146
|
+
|
|
147
|
+
- Prefix every project-defined option with `AMS7_`. Never use `CONFIG_FOO` for an AMS7-introduced symbol.
|
|
148
|
+
- Group options by feature in `menu "..."` blocks. AMS7 groups: Connectivity Workflow, Vitals, ESP-NOW, Logging, etc.
|
|
149
|
+
- Default to `n` for any feature that increases IRAM/flash — acquisition builds are size-sensitive.
|
|
150
|
+
- For "trial debug" toggles, name them `*_TRIAL_DEBUG_LOG_ENABLE` and gate `ESP_LOG*` calls behind them — never compile trial-debug logs into release by default.
|
|
151
|
+
- Add a `help` block on every option. AMS7 reviewers check for missing help text.
|
|
152
|
+
|
|
153
|
+
## Common pitfalls
|
|
154
|
+
|
|
155
|
+
- **Forgetting `default`.** menuconfig prompts for a value on first use; this is annoying. Always set a sensible default.
|
|
156
|
+
- **`select` cycle.** `A select B; B select A` causes menuconfig to error. Use `depends on` instead.
|
|
157
|
+
- **Component Kconfig included in disabled component.** If a component's Kconfig has options, that component must be discoverable. Check `EXTRA_COMPONENT_DIRS` in `CMakeLists.txt`.
|
|
158
|
+
- **Renaming an option without a migration.** Renaming `AMS7_X` to `AMS7_Y` leaves `sdkconfig` referring to the old name. `menuconfig` will silently drop the new option. Fix: add a `config AMS7_Y` with `default AMS7_X` then remove `AMS7_X`.
|
|
159
|
+
- **`range` on a `bool`.** Doesn't make sense — Kconfig will refuse. Use a `choice` instead.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Logging discipline
|
|
2
|
+
|
|
3
|
+
ESP-IDF logging uses `ESP_LOG*` macros. They are cheap when disabled (compile out), acceptable at moderate rates, and dangerous at acquisition-frame rates.
|
|
4
|
+
|
|
5
|
+
## The macros
|
|
6
|
+
|
|
7
|
+
```cpp
|
|
8
|
+
ESP_LOGE(TAG, "fmt", ...); // error — always shown at default verbosity
|
|
9
|
+
ESP_LOGW(TAG, "fmt", ...); // warning — recoverable issue
|
|
10
|
+
ESP_LOGI(TAG, "fmt", ...); // info — state transitions
|
|
11
|
+
ESP_LOGD(TAG, "fmt", ...); // debug — verbose, off by default
|
|
12
|
+
ESP_LOGV(TAG, "fmt", ...); // verbose — trace, off by default
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Each is gated by a per-level compile-time switch (`CONFIG_LOG_DEFAULT_LEVEL`) and a per-tag runtime level (`esp_log_level_set("tag", ESP_LOG_WARN)`).
|
|
16
|
+
|
|
17
|
+
## Tags
|
|
18
|
+
|
|
19
|
+
Use a stable per-module tag — a file-scope `static const char *TAG`:
|
|
20
|
+
|
|
21
|
+
```cpp
|
|
22
|
+
static const char *TAG = "ams7driver";
|
|
23
|
+
ESP_LOGI(TAG, "Start acquisition stack_hw=%lu", (unsigned long)stack_hw);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Tags show up as `[tag]` prefixes in the serial monitor and let you filter:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
idf.py monitor --print-filter="ams7driver:I espnow_summary:W"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Pick short, stable tags. AMS7 module tags: `ams7driver`, `espnow_summary`, `ble_prph`, `acqsystem`, `dps310`, `ads129x`, `icm42688p`, `sdcard`.
|
|
33
|
+
|
|
34
|
+
## Format strings
|
|
35
|
+
|
|
36
|
+
Format strings are validated against arguments at compile time when GCC sees the format attribute:
|
|
37
|
+
|
|
38
|
+
```cpp
|
|
39
|
+
ESP_LOGI(TAG, "Hold=%lu ms", (unsigned long)ms); // %lu for unsigned long
|
|
40
|
+
ESP_LOGI(TAG, "Cap=%" PRIu32, cap); // PRIu32 macro for uint32_t
|
|
41
|
+
ESP_LOGI(TAG, "Ratio=%f", ratio); // %f for double, %f for float (promoted)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Common gotchas:
|
|
45
|
+
|
|
46
|
+
- `%lu` for `uint32_t` is **wrong on platforms where `long` is 64-bit**. Use `%" PRIu32 "` instead.
|
|
47
|
+
- `%d` for `size_t` is wrong on 64-bit hosts; use `%zu`.
|
|
48
|
+
- `%p` for arbitrary pointer; cast to `void *` first.
|
|
49
|
+
|
|
50
|
+
## Gating with Kconfig
|
|
51
|
+
|
|
52
|
+
Wrap debug logs in a `CONFIG_*` guard so a release build can drop them entirely:
|
|
53
|
+
|
|
54
|
+
```cpp
|
|
55
|
+
#if CONFIG_AMS7_RESP_TRIAL_DEBUG_LOG_ENABLE
|
|
56
|
+
ESP_LOGI(TAG, "resp sample processed src=%d q=%u", src, q);
|
|
57
|
+
#endif
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`(AMS7)` AMS7 names these options `*_TRIAL_DEBUG_LOG_ENABLE` and defaults them to `n` (or `y` only during early integration). They are the right toggle for "I want to see this while tuning the algorithm but not in production."
|
|
61
|
+
|
|
62
|
+
## Universal anti-patterns
|
|
63
|
+
|
|
64
|
+
- **Per-frame logs in acquisition.** At 125–500 Hz this is unprintable and floods the UART. The host monitor loses the frames you actually need.
|
|
65
|
+
- **Logs in ISRs.** `ESP_LOG*` is not `IRAM_ATTR`; calling it from an ISR pulls the whole logging chain into IRAM. Use `xQueueSendFromISR` to a logging task instead.
|
|
66
|
+
- **Logging from `app_main`'s setup before serial is up.** First few lines can be lost. Wait for `ESP_LOGI(TAG, "boot")` to appear before assuming the console is alive.
|
|
67
|
+
- **Sensitive data in logs.** Avoid logging peer MACs, subject IDs, or anything that crosses a wire to a host that doesn't need it. Add a `LOG_REDACT` flag if the project warrants it.
|
|
68
|
+
|
|
69
|
+
## AMS7 acquisition-logging rules
|
|
70
|
+
|
|
71
|
+
`(AMS7)` Acquisition builds must not log per-sample or per-frame. Acceptable log rates:
|
|
72
|
+
|
|
73
|
+
- **Per event**: BLE connect/disconnect, ESP-NOW peer add/remove, acquisition start/stop, file open/close, error conditions. These are inherently low-rate.
|
|
74
|
+
- **Periodic aggregate**: low-rate counters via a `pl:` (payload) console line every N ms. Example:
|
|
75
|
+
|
|
76
|
+
```cpp
|
|
77
|
+
// Throttled to 1 Hz regardless of underlying sample rate
|
|
78
|
+
if ((now_ms - last_pl_ms) >= 1000) {
|
|
79
|
+
last_pl_ms = now_ms;
|
|
80
|
+
ESP_LOGI(TAG, "pl: beats=%lu rr_ms=%lu q=%u en=%u src=%d",
|
|
81
|
+
beats, rr_ms, quality, envelope, source);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The `pl:` prefix is grep-friendly — host-side tooling can pluck aggregate lines from the serial stream without parsing every byte.
|
|
86
|
+
|
|
87
|
+
- **Trial-debug toggles**: `CONFIG_AMS7_RESP_TRIAL_DEBUG_LOG_ENABLE`, `CONFIG_AMS7_HR_FUSED_DEBUG_LOG_ENABLE`, etc. Default `n` for release builds. When enabled, log per-decision but still throttled.
|
|
88
|
+
|
|
89
|
+
## Tagging aggregate lines
|
|
90
|
+
|
|
91
|
+
Use a tag prefix that distinguishes aggregate from per-event so the host can filter:
|
|
92
|
+
|
|
93
|
+
```cpp
|
|
94
|
+
// Aggregate (high-volume, low-rate)
|
|
95
|
+
ESP_LOGI("ams7_pl", "pl: hr=%lu rr_ms=%lu q=%u", hr_bpm, rr_ms, q);
|
|
96
|
+
|
|
97
|
+
// Per-event (low-volume, descriptive)
|
|
98
|
+
ESP_LOGI(TAG, "Acquisition start mode=%d stack_hw=%lu", mode, stack_hw);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Runtime level control
|
|
102
|
+
|
|
103
|
+
`esp_log_level_set` lets you bump a tag's verbosity at runtime:
|
|
104
|
+
|
|
105
|
+
```cpp
|
|
106
|
+
esp_log_level_set("espnow_summary", ESP_LOG_DEBUG); // noisy on demand
|
|
107
|
+
esp_log_level_set("ams7driver", ESP_LOG_WARN); // quiet a noisy module
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This is the right tool for "I want to debug one module without rebuilding."
|
|
111
|
+
|
|
112
|
+
## Common pitfalls
|
|
113
|
+
|
|
114
|
+
- **Logs in a tight loop.** Symptom: dropouts in other tasks, overflowing the UART ringbuffer. Fix: throttle or remove.
|
|
115
|
+
- **Float in `ESP_LOG*`.** ESP-IDF supports `%f` but pulling in `<stdio.h>` float formatting adds code; consider integer milliunits instead.
|
|
116
|
+
- **Missing format attribute.** ESP-IDF declares `__attribute__((format(printf, ...)))` on `ESP_LOG*` so format mismatches are warnings. Don't bypass with a `static_cast`.
|
|
117
|
+
- **Stale `TAG`.** Renaming a file but leaving the old `TAG`. Tag should match the module name in `idf.py monitor` filters.
|
|
118
|
+
- **Compile-time vs runtime gate confusion.** `CONFIG_LOG_DEFAULT_LEVEL` is compile-time; `esp_log_level_set` is runtime. A tag's level is `min(compile_max, runtime_set, default)`.
|