.ino files should be created SPARINGLY and ONLY when truly justified.
- Testing minor code changes - Use existing test files or unit tests
- Quick API validation - Use unit tests or modify existing examples
- Debugging specific functions - Use test files, not new sketches
- One-off experiments - Create temporary test files instead
- Small feature tests - Extend existing relevant examples
Use Pattern: temp_<feature>.ino or test_<api>.ino
// temp_json_api.ino - Testing new JSON fetch functionality
// test_networking.ino - Validating network stack changes- ✅ FOR: Testing new APIs during development
- ✅ FOR: Quick prototyping and validation
- ✅ DELETE AFTER USE - These are temporary by design
Use Pattern: examples/<FeatureName>/<FeatureName>.ino
// examples/JsonFetchApi/JsonFetchApi.ino - Comprehensive JSON API example
// examples/NetworkStack/NetworkStack.ino - Major networking features- ✅ FOR: Large, comprehensive new features
- ✅ FOR: APIs that warrant dedicated examples
- ✅ FOR: Features that users will commonly implement
- ✅ PERMANENT - These become part of the example library
Before creating ANY .ino file, ask:
-
🤔 Is this testing a new API?
- YES → Create
temp_<name>.ino, delete after testing - NO → Consider alternatives
- YES → Create
-
🤔 Is this a significant new feature that users will commonly use?
- YES → Create
examples/<FeatureName>/<FeatureName>.ino - NO → Use existing examples or test files
- YES → Create
-
🤔 Can I modify an existing example instead?
- YES → Extend existing example rather than creating new
- NO → Proceed with creation
-
🤔 Is this just for debugging/validation?
- YES → Use unit tests or temporary test files
- NO → Consider if it meets the "significant feature" criteria
For Feature Examples (.ino files that stay):
- ✅ Demonstrates complete, real-world usage patterns
- ✅ Covers multiple aspects of the feature comprehensively
- ✅ Provides educational value for users
- ✅ Shows best practices and common use cases
- ✅ Is likely to be referenced by multiple users
For Temporary Testing (.ino files that get deleted):
- ✅ Clearly named as temporary (temp_, test_)
- ✅ Focused on specific API validation
- ✅ Will be deleted after development cycle
- ✅ Too complex for unit test framework
test_basic_led.ino- Use existing Blink exampledebug_colors.ino- Use existing ColorPalette examplequick_brightness.ino- Use unit tests or modify existing examplevalidate_pins.ino- Use PinTest example or unit tests
temp_new_wifi_api.ino- Testing major new WiFi functionality (temporary)examples/MachineLearning/MachineLearning.ino- New ML integration feature (permanent)temp_performance_test.ino- Validating optimization changes (temporary)
- Temporary files: Creator must delete when testing is complete
- Feature examples: Must be maintained and updated as API evolves
- Abandoned files: Regular cleanup reviews to remove unused examples
Remember: The examples directory is user-facing documentation. Every .ino file should provide clear value to FastLED users.
DO NOT use std:: prefixed functions or headers in examples. This project provides its own STL-equivalent implementations under the fl:: namespace.
Examples of what to avoid and use instead:
Headers:
- ❌
#include <vector>→ ✅#include "fl/vector.h" - ❌
#include <string>→ ✅#include "fl/string.h" - ❌
#include <optional>→ ✅#include "fl/optional.h" - ❌
#include <memory>→ ✅#include "fl/stl/scoped_ptr.h"or#include "fl/stl/shared_ptr.h"
Functions and classes:
- ❌
std::move()→ ✅fl::move() - ❌
std::vector→ ✅fl::vector - ❌
std::string→ ✅fl::string
Why: The project maintains its own implementations to ensure compatibility across all supported platforms and to avoid bloating the library with unnecessary STL dependencies.
🚨 CRITICAL: Always use proper RAII patterns - smart pointers, moveable objects, or wrapper classes instead of raw pointers for resource management.
Resource Management Options:
- ✅ PREFERRED:
fl::shared_ptr<T>for shared ownership (multiple references to same object) - ✅ PREFERRED:
fl::unique_ptr<T>for exclusive ownership (single owner, automatic cleanup) - ✅ PREFERRED: Moveable wrapper objects (like
fl::promise<T>) for safe copying and transferring of unique resources - ✅ ACCEPTABLE:
fl::vector<T>storing objects by value when objects support move/copy semantics - ❌ AVOID:
fl::vector<T*>storing raw pointers - usefl::vector<fl::shared_ptr<T>>orfl::vector<fl::unique_ptr<T>> - ❌ AVOID: Manual
new/delete- usefl::make_shared<T>()orfl::make_unique<T>()
Examples:
// ✅ GOOD - Using smart pointers
fl::vector<fl::shared_ptr<HttpClient>> mActiveClients;
auto client = fl::make_shared<HttpClient>();
mActiveClients.push_back(client);
// ✅ GOOD - Using unique_ptr for exclusive ownership
fl::unique_ptr<HttpClient> client = fl::make_unique<HttpClient>();
// ✅ GOOD - Moveable wrapper objects (fl::promise example)
fl::vector<fl::promise<Response>> mActivePromises; // Copyable wrapper around unique future
fl::promise<Response> promise = fetch.get(url).execute();
mActivePromises.push_back(promise); // Safe copy, shared internal state
// ❌ BAD - Raw pointers require manual memory management
fl::vector<Promise*> mActivePromises; // Memory leaks possible
Promise* promise = new Promise(); // Who calls delete?Use FL_WARN for debug printing in examples. This ensures consistent debug output that works in both unit tests and live application testing.
Usage:
- ✅
FL_WARN("Debug message: " << message); - ❌
FL_WARN("Value: %d", value);
NO emoticons or emoji characters are allowed in C++ source files. This ensures professional, maintainable code that works correctly across all platforms and development environments.
Examples of what NOT to do in .ino files:
// ❌ BAD - Emoticons in comments
// 🎯 This function handles user input
// ❌ BAD - Emoticons in log messages
FL_WARN("✅ Operation successful!");
FL_WARN("❌ Error occurred: " << error_msg);
// ❌ BAD - Emoticons in string literals
const char* status = "🔄 Processing...";Examples of correct alternatives:
// ✅ GOOD - Clear text in comments
// TUTORIAL: This function handles user input
// ✅ GOOD - Text prefixes in log messages
FL_WARN("SUCCESS: Operation completed successfully!");
FL_WARN("ERROR: Failed to process request: " << error_msg);
// ✅ GOOD - Descriptive text in string literals
const char* status = "PROCESSING: Request in progress...";🎯 PREFERRED: Use the modern fl::Json class for all JSON operations. FastLED provides an ideal JSON API that prioritizes type safety, ergonomics, and crash-proof operation.
✅ IDIOMATIC JSON USAGE:
// NEW: Clean, safe, idiomatic API
fl::Json json = fl::Json::parse(jsonStr);
int brightness = json["config"]["brightness"] | 128; // Gets value or 128 default
string name = json["device"]["name"] | string("default"); // Type-safe with default
bool enabled = json["features"]["networking"] | false; // Never crashes
// Array operations
if (json["effects"].contains("rainbow")) {
// Safe array checking
}❌ DISCOURAGED: Verbose legacy API:
// OLD: Verbose, error-prone API (still works, but not recommended)
fl::JsonDocument doc;
fl::string error;
fl::parseJson(jsonStr, &doc, &error);
int brightness = doc["config"]["brightness"].as<int>(); // Can crash if missing📚 Reference Example: See examples/Json/Json.ino for comprehensive usage patterns and API comparison.
FastLED examples support three build modes for host-based compilation, mirroring the unit test system:
# Compile all examples in quick mode (fastest)
uv run test.py --examples
# Compile specific examples in quick mode
uv run test.py --examples Blink DemoReel100
# Direct invocation (quick mode)
uv run python ci/util/meson_example_runner.py BlinkCharacteristics:
- Flags:
-O0 -g1(minimal debug info for stack traces) - Use case: Fast iteration and testing
- Build directory:
.build/meson-quick/examples/ - Compilation speed: 80 examples in ~0.24s (394 examples/second with PCH)
- Binary size: Baseline (e.g., Blink: 2.8M)
# Compile all examples with debug symbols and sanitizers
uv run test.py --examples --debug
# Compile specific examples in debug mode
uv run test.py --examples Blink --debug
# Compile and execute in debug mode
uv run test.py --examples Blink --debug --full
# Direct invocation (debug mode)
uv run python ci/util/meson_example_runner.py Blink --debugCharacteristics:
- Flags:
-O0 -g3 -fsanitize=address -fsanitize=undefined(full debug info + sanitizers) - Use case: Debugging crashes, memory issues, undefined behavior
- Build directory:
.build/meson-debug/examples/ - Sanitizers: AddressSanitizer (ASan) + UndefinedBehaviorSanitizer (UBSan)
- Binary size: 3.3x larger than quick mode (e.g., Blink: 9.1M)
- Debug symbols: Full DWARF debug info for GDB debugging
Debug Mode Benefits:
- Detects heap buffer overflows
- Detects use-after-free errors
- Detects memory leaks
- Detects integer overflow
- Detects null pointer dereference
- Detects misaligned memory access
- Full source-level debugging with GDB
# Compile all examples optimized for performance
uv run test.py --examples --build-mode release
# Compile specific examples in release mode
uv run test.py --examples Blink --build-mode release
# Direct invocation (release mode)
uv run python ci/util/meson_example_runner.py Blink --build-mode releaseCharacteristics:
- Flags:
-O0 -g1(optimized, minimal debug info) - Use case: Performance testing and production builds
- Build directory:
.build/meson-release/examples/ - Binary size: Smallest (e.g., Blink: 2.3M, 20% smaller than quick mode)
Examples use separate build directories per mode to enable caching and prevent flag conflicts:
.build/
├── meson-quick/examples/ # Quick mode (80 DLLs)
├── meson-debug/examples/ # Debug mode (80 DLLs)
└── meson-release/examples/ # Release mode (80 DLLs)
Benefits:
- All three modes can coexist simultaneously
- Switching modes does not invalidate other mode's cache
- No cleanup overhead when switching modes
- Instant mode switching (just change directory)
- Each mode maintains independent build cache
Marker Files:
Each build directory contains a .debug_config marker file that tracks the debug mode state:
.build/meson-quick/.debug_config → False
.build/meson-debug/.debug_config → True
.build/meson-release/.debug_config → False
Step 1: Identify problematic example
# Compile and run in quick mode (fast iteration)
uv run test.py --examples ExampleNameStep 2: Compile with debug symbols and sanitizers
# Enable full debug mode with sanitizers
uv run test.py --examples ExampleName --debug --fullStep 3: Analyze sanitizer output Sanitizers will print detailed error messages when memory errors or undefined behavior are detected:
=================================================================
==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...
#0 0x... in function_name example-ExampleName.cpp:42
=================================================================
Step 4: Use GDB for deeper investigation (if needed)
# Load example in GDB
gdb .build/meson-debug/examples/example-ExampleName.exe
# Common GDB commands
(gdb) run # Execute program
(gdb) bt # Show backtrace when crashed
(gdb) bt full # Show backtrace with local variables
(gdb) frame 3 # Switch to frame 3
(gdb) print variable_name # Print variable value
(gdb) info locals # Show all local variables| Mode | Examples | Compilation | Binary Size | Debug Info | Sanitizers | Success Rate |
|---|---|---|---|---|---|---|
| Quick | 80/80 | ~0.24s | Baseline | Minimal | None | 100% |
| Debug | 80/80 | Slower | 3.3x larger | Full (-g3) | ASan+UBSan | 100% |
| Release | 80/80 | ~0.24s | 0.8x | Minimal | None | 100% |
Notes:
- Debug mode binaries are significantly larger due to debug symbols and sanitizer instrumentation
- Release mode produces smallest binaries (20% smaller than quick mode)
- All modes successfully compile 100% of examples (80 out of 80)
- PCH (precompiled headers) dramatically speeds compilation in all modes
# Compile examples for specific platforms
uv run ci/ci-compile.py uno --examples Blink
uv run ci/ci-compile.py esp32dev --examples Blink
uv run ci/ci-compile.py teensy31 --examples Blink
bash compile uno --examples Blink🎯 HOW TO COMPILE ANY ARDUINO SKETCH TO WASM:
Basic Compilation Commands:
# Compile any sketch directory to WASM
uv run ci/wasm_compile.py path/to/your/sketch
# Quick compile test (compile only, no browser)
uv run ci/wasm_compile.py path/to/your/sketch --just-compile
# Compile examples/Blink to WASM
uv run ci/wasm_compile.py examples/Blink --just-compile
# Compile examples/NetTest to WASM (test fetch API)
uv run ci/wasm_compile.py examples/NetTest --just-compile
# Compile examples/DemoReel100 to WASM
uv run ci/wasm_compile.py examples/DemoReel100 --just-compileOutput: Creates fastled_js/ folder with:
fastled.js- JavaScript loaderfastled.wasm- WebAssembly binaryindex.html- HTML page to run the sketch
Run in Browser:
# Simple HTTP server to test
cd fastled_js
python -m http.server 8000
# Open http://localhost:8000🚨 REQUIREMENTS:
- Docker must be installed and running
- Internet connection (for Docker image download on first run)
- ~1GB RAM for Docker container during compilation
🚨 MANDATORY: Always test WASM compilation after platform file changes
Platform Testing Commands:
# Test WASM platform changes (for platform developers)
uv run ci/wasm_compile.py examples/wasm --just-compile
# Quick compile test for any sketch (compile only, no browser)
uv run ci/wasm_compile.py examples/Blink --just-compile
# Quick compile test for NetTest example
uv run ci/wasm_compile.py examples/NetTest --just-compile
# Quick test without full build
uv run ci/wasm_compile.py examples/wasm --quickWatch For These Error Patterns:
error: conflicting types for 'function_name'error: redefinition of 'function_name'warning: attribute declaration must precede definitionRuntimeError: unreachable(often async-related)
MANDATORY RULES:
- ALWAYS test WASM compilation after modifying any WASM platform files
- USE
uv run ci/wasm_compile.pyfor validation - WATCH for unified build conflicts in compilation output
- VERIFY async operations work properly in browser environment
ALWAYS use the FastLED compiler control macros from fl/compiler_control.h for warning suppression. This ensures consistent cross-compiler support and proper handling of platform differences.
Correct Warning Suppression Pattern:
#include "fl/compiler_control.h"
// Suppress specific warning around problematic code
FL_DISABLE_WARNING_PUSH
FL_DISABLE_FORMAT_TRUNCATION // Use specific warning macros
// ... code that triggers warnings ...
FL_DISABLE_WARNING_POPAvailable Warning Suppression Macros:
- ✅
FL_DISABLE_WARNING_PUSH/FL_DISABLE_WARNING_POP- Standard push/pop pattern - ✅
FL_DISABLE_WARNING(warning_name)- Generic warning suppression (use sparingly) - ✅
FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS- Clang global constructor warnings - ✅
FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED- Clang self-assignment warnings - ✅
FL_DISABLE_FORMAT_TRUNCATION- GCC format truncation warnings
What NOT to do:
- ❌ NEVER use raw
#pragmadirectives - they don't handle compiler differences - ❌ NEVER write manual
#ifdef __clang__/#ifdef __GNUC__blocks - use the macros - ❌ NEVER ignore warnings without suppression - fix the issue or suppress appropriately
DO NOT use try-catch blocks or C++ exception handling in examples. FastLED is designed to work on embedded systems like Arduino where exception handling may not be available or desired due to memory and performance constraints.
Use Error Handling Alternatives:
- ✅ Return error codes:
bool function() { return false; }or custom error enums - ✅ Optional types:
fl::optional<T>for functions that may not return a value - ✅ Assertions:
FL_ASSERT(condition)for debug-time validation - ✅ Early returns:
if (!valid) return false;for error conditions - ✅ Status objects: Custom result types that combine success/failure with data
Examples of proper error handling:
// Good: Using return codes
bool initializeHardware() {
if (!setupPins()) {
FL_WARN("Failed to setup pins");
return false;
}
return true;
}
// Good: Using fl::optional
fl::optional<float> calculateValue(int input) {
if (input < 0) {
return fl::nullopt; // No value, indicates error
}
return fl::make_optional(sqrt(input));
}
// Good: Using early returns
void processData(const uint8_t* data, size_t len) {
if (!data || len == 0) {
FL_WARN("Invalid input data");
return; // Early return on error
}
// Process data...
}🚨 ALL AGENTS: Read examples/AGENTS.md before concluding example work to refresh memory about .ino file creation rules and example coding standards.