Non-blocking, time-driven C++14 library for LED control (blink, breathe, fade). LEDs group for parallel or sequential control.
Hard constraints (core logic in src/): no float, dynamic allocation, exceptions, RTTI, delay(), or blocking ops. Templates over virtual functions. Backwards-compatible public API; break only with a major version bump.
| Path | Purpose |
|---|---|
src/ |
Library source (.h/.cpp) |
test/ |
Host unit tests (Catch2, separate Makefile) |
examples/ |
MCU .ino sketches |
.tools/ |
Dev tools (doc site generator, act log analyser) |
platformio.ini |
PlatformIO config |
devbox.json |
Dev environment |
PlatformIO + Make inside devbox shell. Discover targets with make help.
test/Makefile targets: test, clean, clobber, coverage (HTML in test/report/).
- Format:
.clang-format(Google).make format/make format-check(legacy:make lint). - Static analysis:
make lint-tidy(clang-tidy); seedoc/LINTING_GUIDE.md. - Naming:
PascalCaseclasses/methods,snake_case_private members,kPascalCaseconstants,lowercase_taliases. - Prefer
constexprover#define. Noconstexpron functions withif(C++14 limit);if (sizeof(Brightness) == 1)stands in forif constexpruntil C++17. - Use
= deleteandoverride.
Strict separation: state machine / effect calculation / hardware access. Never put platform-specific code in jled_base.h.
src/jled_effects.h/.cpp:BrightnessEvaluator+ effects (Blink,Breathe,Candle, ...),scale/lerp/fadeon_func.src/jled_base.h: platform-agnosticTJLed<Hal, Clock, B>state machine + fluent API.src/jled_group_base.h:TJLedGroup,TJLedAny,TJLedRef(grouping, type erasure).src/jled.h: platform detection; exposesJLed,JLedHD,JLedGroup,JLedHDGroup,JLedRef,JLedRefGroup.src/*_hal.h: per-platform HAL (Arduino, ESP32, ESP8266, mbed, Pico, STM32Cube), two abstractions each: PWM (e.g.ArduinoHal::analogWrite(Brightness)) and Clock (e.g.ArduinoClock::millis()).
Effects: structs with Period() and Eval(t), stateless and copyable (see ConstantBrightnessEvaluator).
Resolution: JLed/JLedHD are template instances; higher resolution = smoother PWM.
Memory: fixed buffers or placement new, never dynamic.
Fluent API via CRTP (methods return B&):
JLed led = JLed(21).DelayBefore(1500).Breathe(500).Repeat(5).MaxBrightness(150);- Catch2 (amalgamated in
test/catch2/).TEST_CASE("what", "[tag]"),SECTION()for variations. Tags:[jled],[sequence],[hal]. Test evaluators by callingEval(t)at various time points. HAL mocks intest/Arduino.h,test/esp-idf/. Register new test files intest/Makefile. - Tests are whitebox: read and understand the code path before writing a test for it, not just the public behavior.
- Keep tests simple: test the specific behavior at hand, don't add setup, helpers, or cases beyond what's needed to cover it.
- Use a fixture (
TEST_CASE_METHOD) where applicable, e.g.ArduinoMockFixtureintest/test_arduino_hal.cpp.
- New effect: evaluator in
src/jled_effects.h, fluent method insrc/jled_base.h(refBlinkBrightnessEvaluator/TJLed::Blink). Add tests, an example, aREADME.mdentry. - New HAL: copy
src/arduino_hal.h, add detection insrc/jled.h, addtest/test_[platform]_hal.cpp. The HAL concept'sanalogWrite()requires two arguments,analogWrite<Color>(Color val, bool invert); if your HAL has no native inversion capability, implement a plain single-argumentanalogWrite(Color val)and wrap it inInvertableHal<YourHal>(src/invertable_hal.h) to get software inversion for free. The HAL concept also requiresvoid SetLowActive(bool), called once fromLowActive(); a HAL with a hardware polarity register uses it to pre-arm that register, whileInvertableHalimplements it as a no-op for HALs without one. SeeEsp32Hal/PicoHalfor examples of the former. - Bug fix: failing test first, then fix, then
make test,make coverage,make lint.
Every change adds tests. Run make lint && make test before commit. Don't change a test to make it pass; fix the code. Correctness over completeness: don't guess or invent APIs/files/configs; label assumptions; ask when unsure.
GitHub Actions on push/PR to master: lint, then unit tests + coverage (Coveralls). All must pass.
make ci-act runs the build jobs locally via act (~10min): the single examples matrix, which covers the Arduino boards plus the dedicated nucleo_f401re_mbed and nucleo_f401re_stm32cube rows, logging NDJSON to .act-logs/:
.tools/act-log/act-log.py report # summary table; exits 1 on failures
.tools/act-log/act-log.py report <unit> # full log for one unit, e.g. uno or nucleo_f401re_mbedA "unit" is one summary row: a matrix board (e.g. uno, nucleo_f401re_mbed). Status: OK built, FAIL build failed (code bug), INFRA never reached build (act issue, not code). Ignore NDJSON jobResult (buggy for parallel jobs); the analyser uses stepResult.
Auto-generated microsite at https://jandelgado.github.io/jled/ from git tags + master. Generator in .tools/doc-site/ (see its README.md).