FastLED 3.9.15
Loading...
Searching...
No Matches
async_logger.cpp.hpp
Go to the documentation of this file.
1
3
5#include "fl/stl/cstdio.h"
7#include "fl/math/math.h"
8#include "fl/log/log.h" // For FL_ERROR macro
9#include "fl/task/task.h" // For fl::task
10#include "fl/task/scheduler.h" // For fl::task::Scheduler
11#include "fl/stl/noexcept.h"
12
13namespace fl {
14
15namespace detail {
19 void printLoggerDisabledError(const char* category_name, const char* define_name) {
20 FL_ERROR(category_name << " ASYNC LOGGING NOT ENABLED. "
21 << "Add '#define " << define_name << "' before including FastLED.h");
22 }
23} // namespace detail
24
25// ============================================================================
26// Background flush infrastructure (timer-based automatic flushing)
27// ============================================================================
28
29namespace detail {
30 // Global state for background flushing
32 volatile bool mNeedsFlush; // Flag set by ISR, cleared by service function
33 fl::isr::handle mTimerHandle; // ISR timer handle
34 fl::size mMessagesPerTick; // Max messages to flush per timer tick
35 bool mEnabled; // Whether background flushing is enabled
36
38 : mNeedsFlush(false)
39 , mTimerHandle() // Default constructor
41 , mEnabled(false) {}
42 };
43
44 void FL_IRAM async_log_flush_timer_isr(void* user_data) {
45 BackgroundFlushState* state = static_cast<BackgroundFlushState*>(user_data);
46 state->mNeedsFlush = true;
47 // Debug: Toggle a counter to verify ISR is firing (visible in debugger)
48 static volatile fl::u32 isr_fire_count = 0;
49 isr_fire_count = isr_fire_count + 1;
50 }
51} // namespace detail
52
53// ============================================================================
54// AsyncLogger implementation using AsyncLogQueue backend (zero heap allocation)
55// ============================================================================
56
58
59void AsyncLogger::push(const fl::string& msg) {
60 mQueue.push(msg);
61}
62
63void AsyncLogger::push(const char* msg) {
64 mQueue.push(msg);
65}
66
68 const char* msg;
69 fl::u16 len;
70
71 // 256-byte stack buffer - handles most messages in a single chunk
72 // Avoids heap allocation entirely by chunking long messages
73 char buffer[256];
74
75 while (mQueue.tryPop(&msg, &len)) {
76 fl::u16 offset = 0;
77
78 // Process message in chunks to avoid heap allocation
79 while (offset < len) {
80 fl::u16 chunk_size = min(len - offset, static_cast<fl::u16>(sizeof(buffer) - 1));
81 fl::memcpy(buffer, msg + offset, chunk_size);
82 buffer[chunk_size] = '\0'; // Null-terminate
83
84 offset += chunk_size;
85
86 if (offset >= len) {
87 // Last chunk or whole message - use println (adds newline)
88 fl::println(buffer);
89 } else {
90 // Not last chunk - use print (no newline)
91 fl::print(buffer);
92 }
93 }
94
95 mQueue.commit();
96 }
97}
98
99fl::size AsyncLogger::size() const {
100 return mQueue.size();
101}
102
103bool AsyncLogger::empty() const {
104 return mQueue.empty();
105}
106
108 // Drain queue without printing
109 const char* msg;
110 fl::u16 len;
111 while (mQueue.tryPop(&msg, &len)) {
112 mQueue.commit();
113 }
114}
115
117 return mQueue.droppedCount();
118}
119
120fl::size AsyncLogger::flushN(fl::size maxMessages) {
121 fl::size flushed = 0;
122 const char* msg;
123 fl::u16 len;
124
125 // 256-byte stack buffer - handles most messages in a single chunk
126 // Avoids heap allocation entirely by chunking long messages
127 char buffer[256];
128
129 while (flushed < maxMessages && mQueue.tryPop(&msg, &len)) {
130 fl::u16 offset = 0;
131
132 // Process message in chunks to avoid heap allocation
133 while (offset < len) {
134 fl::u16 chunk_size = min(len - offset, static_cast<fl::u16>(sizeof(buffer) - 1));
135 fl::memcpy(buffer, msg + offset, chunk_size);
136 buffer[chunk_size] = '\0'; // Null-terminate
137
138 offset += chunk_size;
139
140 if (offset >= len) {
141 // Last chunk or whole message - use println (adds newline)
142 fl::println(buffer);
143 } else {
144 // Not last chunk - use print (no newline)
145 fl::print(buffer);
146 }
147 }
148
149 mQueue.commit();
150 flushed++;
151 }
152
153 return flushed;
154}
155
156bool AsyncLogger::enableBackgroundFlush(fl::u32 interval_ms, fl::size messages_per_tick) {
158
159 // If already enabled, disable first
160 if (state.mEnabled) {
162 }
163
164 // Configure flush parameters
165 state.mMessagesPerTick = messages_per_tick;
166
167 // Setup ISR timer configuration
168 fl::isr::config config;
170 config.user_data = &state;
171 config.frequency_hz = 1000 / interval_ms; // Convert ms to Hz
172 config.priority = fl::isr::ISR_PRIORITY_LOW; // Low priority to avoid interfering with LED timing
174
175 // Attach timer ISR (returns 0 on success, negative on error)
176 if (fl::isr::attach_timer_handler(config, &state.mTimerHandle) != 0) {
177 return false; // Platform doesn't support timers or attachment failed
178 }
179
180 state.mEnabled = true;
181 return true;
182}
183
186
187 if (state.mEnabled && state.mTimerHandle.is_valid()) {
188 fl::isr::detach_handler(state.mTimerHandle);
189 state.mTimerHandle = fl::isr::handle(); // Reset to default (invalid) handle
190 state.mEnabled = false;
191 state.mNeedsFlush = false;
192 }
193}
194
198
199// ============================================================================
200// Background flush service function (call from main loop)
201// ============================================================================
202
204 // Get background flush state
206
207 // Quick check - return immediately if no flush needed
208 if (!state.mNeedsFlush) {
209 return;
210 }
211
212 // Clear flag
213 state.mNeedsFlush = false;
214
215 // Flush all instantiated async loggers (uses ActiveLoggerRegistry)
216 // Only flushes loggers that have been accessed via template functions
217 fl::size n = state.mMessagesPerTick;
219 logger.flushN(n);
220 });
221}
222
223// ============================================================================
224// Auto-instantiating service task implementation
225// ============================================================================
226
227namespace detail {
228
233
235 : mIntervalMs(16)
237 , mTask()
238{
239 // Create and register task with scheduler
240 // Default 16ms interval = 60 Hz (one frame at 60fps)
242 .then([this]() {
243 // Service all registered async loggers
244 this->serviceLoggers();
245 });
246
248}
249
251 mIntervalMs = interval_ms;
252
253 // Dynamically update task interval if task exists
254 if (mTask.is_valid()) {
255 mTask.set_interval_ms(interval_ms);
256 }
257}
258
259void AsyncLoggerServiceTask::setMessagesPerTick(fl::size messages_per_tick) {
260 mMessagesPerTick = messages_per_tick;
261}
262
264 // Flush N messages from all registered loggers
266 logger.flushN(mMessagesPerTick);
267 });
268}
269
270} // namespace detail
271
272// ============================================================================
273// Public configuration API
274// ============================================================================
275
276void configureAsyncLogService(u32 interval_ms, fl::size messages_per_tick) {
279}
280
281} // namespace fl
TestState state
ISR-safe async logger using SPSC queue backend (zero heap allocation)
fl::size flushN(fl::size maxMessages)
Flush up to N messages from queue (bounded flush)
fl::u32 droppedCount() const
bool isBackgroundFlushEnabled() const
Check if background flushing is enabled.
void push(const fl::string &msg)
AsyncLogQueue< 128, 4096 > mQueue
bool enableBackgroundFlush(fl::u32 interval_ms, fl::size messages_per_tick=5)
Enable background timer-based flushing (opt-in)
AsyncLogger() FL_NOEXCEPT
void disableBackgroundFlush()
Disable background flushing.
fl::size size() const
ISR-safe async logger wrapper (zero heap allocation) Uses embedded AsyncLogQueue instead of heap-allo...
static T & instance() FL_NOEXCEPT
Definition singleton.h:41
void setMessagesPerTick(fl::size messages_per_tick)
Configure number of messages to flush per service call.
void serviceLoggers()
Service all registered loggers (called by task)
void setInterval(u32 interval_ms)
Change the service interval (default 16ms)
static AsyncLoggerServiceTask & instance()
Handle & then(function< void()> on_then) FL_NOEXCEPT
Definition task.cpp.hpp:276
static Scheduler & instance()
int add_task(Handle t)
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
ISR handler types and API declarations.
#define FL_ERROR(X)
Definition log.h:219
Centralized logging categories for FastLED hardware interfaces and subsystems.
void FL_IRAM async_log_flush_timer_isr(void *user_data)
void printLoggerDisabledError(const char *category_name, const char *define_name)
Print error message for disabled logger (non-template helper) Called from checkLoggerEnabled template...
Compile-time linker keep-alive hook for a single fl::Bus.
Definition bus_traits.h:48
constexpr u32 ISR_FLAG_IRAM_SAFE
Definition constants.h:21
int detach_handler(handle &h)
Detach an ISR handler.
int attach_timer_handler(const config &cfg, handle *out_handle)
Attach a timer-based ISR handler.
constexpr u8 ISR_PRIORITY_LOW
Definition constants.h:13
Handle every_ms(int interval_ms)
Definition task.cpp.hpp:320
FL_DISABLE_WARNING_PUSH U constexpr common_type_t< T, U > min(T a, U b) FL_NOEXCEPT
Definition math.h:71
void * memcpy(void *dest, const void *src, size_t n) FL_NOEXCEPT
void print(const char *str)
void configureAsyncLogService(u32 interval_ms, fl::size messages_per_tick)
Configure async logger automatic servicing task.
void async_log_service()
void println(const char *str) FL_NOEXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_IRAM
#define FL_NOEXCEPT
Task scheduler — manages timer and frame-based tasks.
static ActiveLoggerRegistry & instance()
handler_fn handler
Definition handler.h:20
void * user_data
Definition handler.h:21
Configuration for ISR attachment.
Definition handler.h:19
Opaque handle to an attached ISR.
Definition handler.h:36