FastLED 3.9.15
Loading...
Searching...
No Matches
channel.cpp.hpp
Go to the documentation of this file.
1
3
4#include "platforms/is_platform.h"
8#include "fl/channels/data.h"
10#include "fl/channels/manager.h"
11#include "fl/stl/atomic.h"
12#include "fl/log/log.h"
13#include "fl/channels/options.h"
15#include "pixel_controller.h"
16#include "fl/system/trace.h"
17
18#include "fl/system/pin.h"
20#include "fl/chipsets/ucs7604.h"
21#include "fl/math/ease.h"
22#include "fl/stl/iterator.h"
24#include "fl/math/xymap.h"
25#include "fl/stl/singleton.h"
26#include "fl/stl/noexcept.h"
27
28namespace fl {
29
30namespace {
31
35inline void applyWhiteCfg(CLEDController& ctrl,
36 const ChannelOptions& options) FL_NOEXCEPT {
37 if (auto* p = options.mWhiteCfg.ptr<Rgbww>()) {
38 ctrl.setRgbww(*p);
39 } else if (auto* p = options.mWhiteCfg.ptr<Rgbw>()) {
40 ctrl.setRgbw(*p);
41 } else {
42 // Empty alternative (or default-constructed) → plain RGB.
43 ctrl.clearWhiteChannel();
44 }
45}
46
49 private:
57
58 public:
67 const XYMap* addressing,
68 EOrder rgbOrder,
69 Rgbw rgbw,
70 Rgbww rgbww,
71 const fl::string& channelName)
72 : mPixelIterator(pixels, rgbOrder, rgbw, rgbww) {
73
74 // Apply addressing transformation if configured
75 if (addressing) {
76 u16 numLeds = pixels.size();
77 u16 width = addressing->getWidth();
78 u16 height = addressing->getHeight();
79 u16 expectedLeds = width * height;
80
81 // Validate that XYMap dimensions match channel LED count
82 if (expectedLeds != numLeds) {
83 FL_ERROR("Channel '" << channelName << "': XYMap dimensions (" << width << "x" << height
84 << "=" << expectedLeds << ") don't match LED count (" << numLeds
85 << "). Addressing transformation may produce unexpected results.");
86 }
87
88 // Cast mData to CRGB array
89 const CRGB* pixelArray = (const CRGB*)pixels.mData;
90
91 // Get thread-local buffer (reuses allocation if size unchanged)
93 buffer.clear();
94 buffer.resize(numLeds);
95
96 // Fill buffer by mapping each physical index to its source
97 for (u16 physicalIdx = 0; physicalIdx < numLeds; physicalIdx++) {
98 u16 x = physicalIdx % width;
99 u16 y = physicalIdx / width;
100 u16 sourceIdx = addressing->mapToIndex(x, y);
101 buffer[physicalIdx] = (sourceIdx < numLeds) ? pixelArray[sourceIdx] : CRGB::Black;
102 }
103
104 // Construct PixelController with reordered buffer
105 mAddressedController.emplace(
106 buffer.data(), buffer.size(),
108 mPixelIterator = PixelIteratorAny(mAddressedController.value(), rgbOrder, rgbw, rgbww);
109 }
110 }
111
113 PixelIterator& get() { return mPixelIterator.get(); }
114 const PixelIterator& get() const { return mPixelIterator.get(); }
115};
116
131static void emitDisabledDriverError(const fl::string& channelName,
132 const fl::string& driverName,
133 const fl::string& exclusive) FL_NOEXCEPT {
134 if (!exclusive.empty()) {
135 FL_ERROR("Channel '" << channelName << "': bound driver '" << driverName
136 << "' is currently DISABLED by exclusive-driver selection '"
137 << exclusive << "'. Frame will be silently dropped. "
138 << "Resolve with: FastLED.enableDrivers<fl::Bus::"
139 << driverName << ">() or FastLED.enableAllDrivers().");
140 } else {
141 FL_ERROR("Channel '" << channelName << "': bound driver '" << driverName
142 << "' is currently DISABLED. Frame will be silently dropped. "
143 << "Resolve with: FastLED.enableDrivers<fl::Bus::"
144 << driverName << ">() or FastLED.enableAllDrivers().");
145 }
146}
147
148} // anonymous namespace
149
150
151i32 Channel::nextId() {
152 static fl::atomic<i32> gNextChannelId(0); // okay static in header
153 return gNextChannelId.fetch_add(1);
154}
155
156fl::string Channel::makeName(i32 id, const fl::optional<fl::string>& configName) {
157 if (configName.has_value()) {
158 return configName.value();
159 }
160 // Auto-naming `"Channel_<id>"` only matters for diagnostics — in release
161 // builds (NDEBUG → FASTLED_LOG_VERBOSITY=0 per Stage 1) every FL_WARN /
162 // FL_ERROR site that reads mName collapses to `do { } while(0)`, so the
163 // string and the supporting `fl::to_string` + `operator+` chain go
164 // unused. Empty in release saves the `fl::to_string<i64>` instantiation
165 // plus the heap allocation per Channel ctor. See #2942 / #2886.
166#if FASTLED_LOG_RUNTIME_ENABLED
167 return "Channel_" + fl::to_string(static_cast<i64>(id));
168#else
169 (void)id;
170 return {};
171#endif
172}
173
174ChannelPtr Channel::create(const ChannelConfig &config) {
175 // Late binding strategy: Always create with empty driver
176 // Engine binding happens on first showPixels() call:
177 // - Affinity channels: Look up by name and cache
178 // - Non-affinity channels: Select dynamically each frame
179
180 auto channel = fl::make_shared<Channel>(config.chipset, config.mLeds,
181 config.rgb_order, config.options);
182 channel->mName = makeName(channel->mId, config.mName);
183 auto& events = ChannelEvents::instance();
184 events.onChannelCreated(*channel);
185 return channel;
186}
187
188int Channel::getPin() const {
189 if (const ClocklessChipset* cs = mChipset.ptr<ClocklessChipset>()) {
190 return cs->pin;
191 }
192 if (const SpiChipsetConfig* spi = mChipset.ptr<SpiChipsetConfig>()) {
193 return spi->dataPin;
194 }
195 return -1;
196}
197
198const ChipsetTimingConfig& Channel::getTiming() const {
199 if (const ClocklessChipset* cs = mChipset.ptr<ClocklessChipset>()) {
200 return cs->timing;
201 }
202 static const ChipsetTimingConfig sEmpty(0, 0, 0, 0);
203 return sEmpty;
204}
205
206Channel::Channel(const ChipsetVariant& chipset, EOrder rgbOrder, RegistrationMode mode)
207 : CPixelLEDController<RGB>(mode)
208 , mChipset(chipset)
209 , mRgbOrder(rgbOrder)
210 , mDriver()
211 , mBus(Bus::AUTO)
212 , mId(nextId())
213 , mName(makeName(mId)) {
214 // NOTE: Do NOT call fl::pinMode() here — see comment in the
215 // Channel(ChipsetVariant, span, EOrder, ChannelOptions) constructor.
217}
218
219Channel::Channel(const ChipsetVariant& chipset, fl::span<CRGB> leds,
220 EOrder rgbOrder, const ChannelOptions& options)
221 : CPixelLEDController<RGB>(RegistrationMode::DeferRegister) // Defer registration until FastLED.add()
222 , mChipset(chipset)
223 , mRgbOrder(rgbOrder)
224 , mDriver() // Empty weak_ptr - late binding on first showPixels()
225 , mBus(options.mBus) // Bus selection (#2459)
226 , mId(nextId())
227 , mName(makeName(mId)) {
228 // NOTE: Do NOT call fl::pinMode() here. The pin may already be
229 // configured for SPI output by a persistent driver (ChannelEngineSpi).
230 // Calling pinMode(InputPulldown) would disconnect the SPI MOSI from the
231 // GPIO matrix, causing subsequent transmissions to produce no output.
232 // The driver will configure the pin when it first uses it.
233
234 // Set the LED data array
235 setLeds(leds);
236
237 // Sync mSettings from ChannelOptions so the mWhiteCfg variant (and any
238 // other future fields) survives to showPixels(). The setter calls below
239 // are then idempotent overlays on top of the same data.
240 mSettings = options;
241
242 // Set color correction/temperature/dither/rgbw from ChannelOptions
243 setCorrection(options.mCorrection);
245 setDither(options.mDitherMode);
246 applyWhiteCfg(*this, options);
247
248 // Create ChannelData during construction with chipset variant
250}
251
252// Backwards-compatible constructor (deprecated)
253Channel::Channel(int pin, const ChipsetTimingConfig& timing, fl::span<CRGB> leds,
254 EOrder rgbOrder, const ChannelOptions& options)
255 : CPixelLEDController<RGB>(RegistrationMode::DeferRegister) // Defer registration until FastLED.add()
256 , mChipset(ClocklessChipset(pin, timing)) // Convert to variant
257 , mRgbOrder(rgbOrder)
258 , mDriver() // Empty weak_ptr - late binding on first showPixels()
259 , mBus(options.mBus) // Bus selection (#2459)
260 , mId(nextId())
261 , mName(makeName(mId)) {
262 // NOTE: Do NOT call fl::pinMode() here — see comment in the
263 // Channel(ChipsetVariant, span, EOrder, ChannelOptions) constructor.
264
265 // Set the LED data array
266 setLeds(leds);
267
268 // Sync mSettings from ChannelOptions so the mWhiteCfg variant survives
269 // to showPixels(). See sibling ChipsetVariant constructor for rationale.
270 mSettings = options;
271
272 // Set color correction/temperature/dither/rgbw from ChannelOptions
273 setCorrection(options.mCorrection);
275 setDither(options.mDitherMode);
276 applyWhiteCfg(*this, options);
277
278 // Create ChannelData during construction
280}
281
282Channel::~Channel() FL_NOEXCEPT {
283 auto& events = ChannelEvents::instance();
284 events.onChannelBeginDestroy(*this);
285}
286
287void Channel::applyConfig(const ChannelConfig& config) {
288 mRgbOrder = config.rgb_order;
289 if (config.mName.has_value()) {
290 mName = config.mName.value();
291 }
292 setLeds(config.mLeds);
293 // Sync mSettings from incoming options so the mWhiteCfg variant survives
294 // to showPixels(). Setters below are idempotent overlays on the same data.
295 mSettings = config.options;
299 applyWhiteCfg(*this, config.options);
300 auto& events = ChannelEvents::instance();
301 events.onChannelConfigured(*this, config);
302}
303
304int Channel::getClockPin() const {
305 if (const SpiChipsetConfig* spi = mChipset.ptr<SpiChipsetConfig>()) {
306 return spi->clockPin;
307 }
308 return -1; // Clockless chipsets don't have a clock pin
309}
310
311
312namespace {
313
321 ClocklessEncoder encoder, const ChannelOptions& settings,
322 EOrder rgbOrder) {
323 // Map encoder enum to UCS7604Mode
324 UCS7604Mode mode;
325 switch (encoder) {
328 break;
331 break;
334 break;
335 default:
336 // Should never happen — caller already checked
338 break;
339 }
340
341 // Get current control from global UCS7604 brightness
343
344 // Reorder current control values to match wire order (same logic as UCS7604ControllerT)
345 u8 rgb_currents[3] = {current.r, current.g, current.b};
346 u8 pos0 = (static_cast<int>(rgbOrder) >> 6) & 0x3;
347 u8 pos1 = (static_cast<int>(rgbOrder) >> 3) & 0x3;
348 u8 pos2 = (static_cast<int>(rgbOrder) >> 0) & 0x3;
349 UCS7604CurrentControl wire_current(
350 rgb_currents[pos0], rgb_currents[pos1], rgb_currents[pos2], current.w);
351
352 // Get gamma LUT
353 float gamma = settings.mGamma.value_or(2.8f);
355
356 bool is_rgbw = pixelIterator.get_rgbw().active();
357 size_t num_leds = pixelIterator.size();
358
359 // (#2558) UCS7604 is a 4-channel chipset (always RGBW). If the user
360 // configured this channel for 5-channel RGBWW (warm-W + cool-W), the
361 // chipset can't carry the second W byte. Warn loudly — silently dropping
362 // the white channels would be very hard to diagnose. The pixel iterator
363 // continues to emit RGB-only bytes (is_rgbw=false) so the strip still
364 // lights up, just without the W diode.
365 if (pixelIterator.get_rgbww().active() && !is_rgbw) {
366 FL_WARN_ONCE("UCS7604 cannot carry 5-channel RGBWW — this chipset "
367 "always emits 4-channel RGBW. The warm/cool white "
368 "channels in your Rgbww config will be dropped. "
369 "Set ChannelOptions.mWhiteCfg = Rgbw{...} instead to "
370 "silence this warning.");
371 }
372
373 // Encode into the data buffer
374 encodeUCS7604(pixelIterator, num_leds, fl::back_inserter(*data),
375 mode, wire_current, is_rgbw, gamma8.get());
376}
377
378} // anonymous namespace
379
391fl::shared_ptr<IChannelDriver> Channel::resolveDynamicDriver() {
392#if defined(FASTLED_DISABLE_DYNAMIC_DRIVER) && FASTLED_DISABLE_DYNAMIC_DRIVER
393 // Body excluded via FASTLED_DISABLE_DYNAMIC_DRIVER (#2926). The
394 // showPixels call site is also gated so this is unreachable; the
395 // empty body lets the linker dead-strip ChannelManager::findDriverByName
396 // and selectDriverForChannel along with this symbol.
397 return {};
398#else
399 // Build busKey only when we actually need it (mBus != AUTO).
400 fl::string busKey;
401 if (mBus != Bus::AUTO) {
403 }
404
407 mDriver = driver;
408
409 // #2455 / #2459: one-shot diagnostic when a typed-Bus miss happens.
410 // Probe via `findDriverByName` (silent) to distinguish "driver wasn't
411 // instantiated" from "driver exists but canHandle() rejected this
412 // chipset" — resolution paths differ. The mBusWarned guard suppresses
413 // duplicate logs on subsequent shows of the same channel.
414#if FASTLED_LOG_RUNTIME_ENABLED
415 if (mBus != Bus::AUTO && !mBusWarned &&
416 (!driver || driver->getName() != busKey)) {
417 auto busDriver = ChannelManager::instance().findDriverByName(busKey);
418 if (!busDriver) {
419 // Typed Bus miss — emit the actionable hint with the three
420 // currently-shipping remediations (option 3 added in #2460).
421 FL_ERROR("Channel '" << mName << "': Driver '" << busKey
422 << "' wasn't instantiated. Resolve with: "
423 << "(1) fl::enableDrivers<fl::Bus::" << busKey << ">() "
424 << "(links only this driver), "
425 << "(2) FastLED.enableAllDrivers() (links every driver), or "
426 << "(3) FastLED.addLeds<..., fl::Bus::" << busKey << ">(...) "
427 << "(legacy API; pins Bus + triggers linker keep-alive). "
428 << "Defaulting to AUTO/priority dispatch.");
429 } else {
430 // Registered, but canHandle() said no — bus/chipset mismatch.
431 FL_ERROR("Channel '" << mName << "': Driver '" << busKey
432 << "' is registered but cannot handle this channel's chipset "
433 << "(bus/chipset mismatch). Defaulting to AUTO/priority dispatch.");
434 }
435 mBusWarned = true;
436 }
437 if (!driver) {
438 FL_ERROR("Channel '" << mName << "': No compatible driver found - cannot transmit");
439 }
440#endif // FASTLED_LOG_RUNTIME_ENABLED — release skips the per-frame
441 // driver->getName() != busKey compare + the silent
442 // findDriverByName probe (used only to differentiate the
443 // FL_ERROR message). The functional return value below is
444 // preserved in both builds. See #2952.
445 return driver;
446#endif // !FASTLED_DISABLE_DYNAMIC_DRIVER
447}
448
449void Channel::showPixels(PixelController<RGB, 1, 0xFFFFFFFF> &pixels) {
451
452 // Safety check: don't modify buffer if driver is currently transmitting it
453 if (mChannelData->isInUse()) {
454 FL_WARN("Channel '" << mName << "': showPixels() called while mChannelData is in use by driver, attempting to wait");
455 auto driver = mDriver.lock();
456 if (!driver) {
457 FL_ERROR("Channel '" << mName << "': No driver bound yet the mChannelData is in use - cannot transmit");
458 return;
459 }
460 // wait until the driver is in a READY state.
461 bool ok = driver->waitForReady();
462 if (!ok) {
463 FL_ERROR("Channel '" << mName << "': Timeout occurred while waiting for driver to become READY");
464 return;
465 }
466 FL_WARN("Channel '" << mName << "': Engine became READY after waiting");
467 }
468
469 // Phase 5b of #2428: if the driver was pre-bound via setDriver() (legacy
470 // addLeds<>-style controllers naming BusTraits<Bus::X>::instancePtr() in
471 // their constructor), bypass ChannelManager entirely. Channels created via
472 // the manager-based API (Channel::create(cfg) without affinity) keep their
473 // existing per-frame re-selection so users can swap drivers at runtime.
474 //
475 // **Fast path (#2773 item 2.1):** the legacy `addLeds<NEOPIXEL>` flow is
476 // by far the hot per-frame path on stock Blink. It needs no busKey
477 // construction, no dynamic driver lookup, no busKey-miss diagnostics,
478 // and no fallback `FL_ERROR` reporting — the driver was already pre-bound
479 // in the controller's constructor. Pulling all of that boilerplate out
480 // of `showPixels` lets the compiler keep the hot path compact and lets
481 // the slow path's `fl::string` ops / `ChannelManager::selectDriverForChannel`
482 // / diagnostic literals tree-shake on the slow-path branch's coldness.
484 if (mDriverPreBound) {
485 driver = mDriver.lock();
486 if (!driver) {
487 // Pre-bound driver got destroyed (singleton shutdown, etc.). Silent
488 // bail — this is unrecoverable from showPixels.
489 return;
490 }
491 } else {
492#if !defined(FASTLED_DISABLE_DYNAMIC_DRIVER) || !FASTLED_DISABLE_DYNAMIC_DRIVER
493 driver = resolveDynamicDriver();
494 if (!driver) {
495 return;
496 }
497#else
498 // Dynamic-driver lookup gated out via FASTLED_DISABLE_DYNAMIC_DRIVER
499 // (#2926). The else branch is dead at runtime for every legacy
500 // `addLeds<>` flavor — those pre-bind their driver in the ctor. The
501 // gate lets `--gc-sections` drop the resolveDynamicDriver body plus
502 // the ChannelManager::findDriverByName / selectDriverForChannel
503 // chain (~400-900 B). Channels created via `Channel::create(cfg)`
504 // without a pre-bound driver silently emit nothing under this flag —
505 // user accepts the constraint.
506 return;
507#endif
508 }
509
510 // Build pixel iterator with optional addressing transformation
511 // (#2558) Pass both Rgbw and Rgbww from the channel options; the iterator
512 // carries both, and the encoder dispatch below picks the right path based
513 // on which variant alternative ChannelOptions::mWhiteCfg holds.
514 ReorderingPixelIteratorAny iterator(pixels, mScreenMap.getXYMap(), mRgbOrder,
515 mSettings.rgbw(), mSettings.rgbww(),
516 mName);
517 PixelIterator& pixelIterator = iterator.get();
518
519 // Encode pixels based on chipset type
520 auto& data = mChannelData->getData();
521 data.clear();
522
523 if (mChipset.is<ClocklessChipset>()) {
524 // Clockless chipsets: dispatch based on encoder type
525 const ClocklessChipset* clockless = mChipset.ptr<ClocklessChipset>();
526 switch (clockless->encoder) {
528 pixelIterator.writeWS2812(&data);
529 break;
530#if !defined(FASTLED_DISABLE_UCS7604) || !FASTLED_DISABLE_UCS7604
531 // Gated by FASTLED_DISABLE_UCS7604 (#2920). For WS2812-only
532 // sketches the UCS7604 case is dead at runtime, but each
533 // `writeUCS7604(...)` reference is statically reachable,
534 // keeping the encoder bodies linked. Setting
535 // `-DFASTLED_DISABLE_UCS7604=1` drops the case + the
536 // `encodeUCS7604_16bit_RGB` / `encodeUCS7604_16bit_RGBW`
537 // template instantiations (~400-600 B). When the gate is
538 // enabled, calling showPixels() on a UCS7604 channel
539 // silently emits nothing.
543 writeUCS7604(&data, pixelIterator, clockless->encoder,
545 break;
546#endif // !FASTLED_DISABLE_UCS7604
547 }
548#if !defined(FASTLED_DISABLE_SPI_CHIPSETS) || !FASTLED_DISABLE_SPI_CHIPSETS
549 } else if (mChipset.is<SpiChipsetConfig>()) {
550 // SPI chipsets: dispatch based on chipset type (zero allocation).
551 //
552 // Gated by FASTLED_DISABLE_SPI_CHIPSETS (#2913). For NEOPIXEL-only
553 // sketches on ESP32-S3 the SPI branch is dead at runtime, but the
554 // compiler cannot prove that — each pixelIterator.writeXXX(...)
555 // reference below is statically reachable, keeping ~720 B of
556 // encoder bodies (writeAPA102, writeSK9822, writeLPD8806,
557 // writeSM16716) plus the 11-case switch table linked. Setting
558 // `-DFASTLED_DISABLE_SPI_CHIPSETS=1` in build_flags drops the
559 // whole branch and recovers ~1.0-1.2 KB on a NEOPIXEL Blink.
560 //
561 // When the gate is enabled, calling showPixels() on an
562 // SpiChipsetConfig channel silently emits nothing — the user
563 // accepts that constraint by setting the flag.
565 const SpiEncoder& config = spi->timing;
566
567 // Switch on enum WITHOUT default case - compiler will warn if new enum values are added
568 // TODO: Consolidate these PixelIterator methods with template controllers in src/fl/chipsets/
569 switch (config.chipset) {
573 pixelIterator.writeAPA102(&data, false);
574 break;
575
579 pixelIterator.writeAPA102(&data, true);
580 break;
581
583 pixelIterator.writeSK9822(&data, false);
584 break;
585
587 pixelIterator.writeSK9822(&data, true);
588 break;
589
591 pixelIterator.writeWS2801(&data);
592 break;
593
595 pixelIterator.writeWS2803(&data);
596 break;
597
599 pixelIterator.writeP9813(&data);
600 break;
601
603 pixelIterator.writeLPD8806(&data);
604 break;
605
607 pixelIterator.writeLPD6803(&data);
608 break;
609
611 pixelIterator.writeSM16716(&data);
612 break;
613
615 pixelIterator.writeHD108(&data);
616 break;
617 }
618 // No default case - compiler will error if any enum value is missing
619 }
620#endif // !FASTLED_DISABLE_SPI_CHIPSETS
621
622 // Fire event after encoding completes
623 {
624 auto& events = ChannelEvents::instance();
625 events.onChannelDataEncoded(*this, *mChannelData);
626 }
627
628
629
630 // #2517: detect the silent-drop scenario before enqueuing — if the
631 // resolved driver is registered with ChannelManager but currently
632 // disabled (typically by `FastLED.setExclusiveDriver<OtherBus>()`),
633 // `ChannelManager::onEndFrame()` will skip its `show()` call and the
634 // frame is silently dropped on the floor. Emit an actionable
635 // FL_ERROR so users can diagnose the missing output.
636 //
637 // One-shot via `mDisabledDriverWarned` to avoid per-frame spam.
638 // Reset the latch whenever the driver returns to ENABLED so a later
639 // disable re-emits the diagnostic.
640 fl::string driverName = driver->getName();
641 auto status = ChannelManager::instance().driverStatus(driverName);
643#if FASTLED_LOG_RUNTIME_ENABLED
645 emitDisabledDriverError(
646 mName, driverName,
647 ChannelManager::instance().exclusiveDriverName());
649 }
650#endif
651 // Skip the enqueue — the data wouldn't be sent anyway, and dropping
652 // it here matches the existing behaviour (rather than leaving stale
653 // bytes in the driver's pending queue across an enable/disable flip).
654 // The drop happens in both debug and release; only the diagnostic
655 // and the one-shot latch are gated. In release, the empty
656 // emitDisabledDriverError + the exclusiveDriverName() call + the
657 // 3-arg fl::string chain dead-strip (see #2950).
658 return;
660#if FASTLED_LOG_RUNTIME_ENABLED
661 // Reset the one-shot guard so a future disable re-emits the diagnostic.
662 mDisabledDriverWarned = false;
663#endif
664 }
665 // NOT_REGISTERED: driver is not managed by ChannelManager (e.g. a custom
666 // test driver, or one that was removed). Fall through to enqueue and let
667 // the driver decide what to do — this is the historic behaviour.
668
669 // Enqueue for transmission (will be sent when driver->show() is called)
670 driver->enqueue(mChannelData);
671 auto& events = ChannelEvents::instance();
672 events.onChannelEnqueued(*this, driver->getName());
673}
674
675void Channel::init() {
676 // TODO: Implement initialization
677}
678
679// Stub driver - provides no-op implementation for testing or unsupported platforms
680namespace {
682public:
683 virtual ~StubChannelEngine() FL_NOEXCEPT = default;
684
685 virtual bool canHandle(const ChannelDataPtr& data) const override {
686 (void)data;
687 return true; // Test driver accepts all channel types
688 }
689
690 virtual void enqueue(ChannelDataPtr /*channelData*/) override {
691 // No-op: stub driver does nothing
692 static bool warned = false;
693 if (!warned) {
694 FL_DBG("StubChannelEngine: No-op enqueue (use for testing or unsupported platforms)");
695 warned = true;
696 }
697 }
698
699 virtual void show() override {
700 // No-op: no hardware to drive
701 }
702
703 virtual DriverState poll() override {
704 return DriverState(DriverState::READY); // Always "ready" (does nothing)
705 }
706
707 virtual fl::string getName() const override {
708 return fl::string::from_literal("STUB");
709 }
710
711 virtual Capabilities getCapabilities() const override {
712 return Capabilities(true, true); // Stub accepts both clockless and SPI
713 }
714};
715
716} // anonymous namespace
717
719 static StubChannelEngine instance;
720 return &instance;
721}
722
723// Re-exposed protected base class methods
724void Channel::addToDrawList() {
725 if (isInList()) {
726 FL_WARN("Channel '" << mName << "': Skipping addToDrawList() - already in draw list");
727 return;
728 }
730 // Fire event after adding to draw list (detectable even if user bypasses FastLED.add())
731 auto& events = ChannelEvents::instance();
732 events.onChannelAdded(*this);
733}
734
735void Channel::removeFromDrawList() {
736 if (!isInList()) {
737 FL_WARN("Channel '" << mName << "': Skipping removeFromDrawList() - not in draw list");
738 return;
739 }
741 // Fire event after removing from draw list (detectable even if user bypasses FastLED.remove())
742 auto& events = ChannelEvents::instance();
743 events.onChannelRemoved(*this);
744
745 // Clear driver weak_ptr when removed from draw list
746 mDriver.reset();
747}
748
749int Channel::size() const {
751}
752
753void Channel::showLeds(u8 brightness) {
755}
756
757bool Channel::isInDrawList() const {
759}
760
764
768
769CRGB Channel::getCorrection() {
771}
772
773CRGB Channel::getTemperature() {
775}
776
777u8 Channel::getDither() {
779}
780
781Rgbw Channel::getRgbw() const {
783}
784
785Channel& Channel::setGamma(float gamma) {
786 mSettings.mGamma = gamma;
787 return *this;
788}
789
790fl::optional<float> Channel::getGamma() const {
791 return mSettings.mGamma;
792}
793
794fl::string Channel::getEngineName() const {
795 // Lock the weak_ptr to get a shared_ptr
796 auto driver = mDriver.lock();
797 if (driver) {
798 return driver->getName();
799 }
800 return fl::string(); // Return empty string if no driver bound
801}
802
803Channel& Channel::setScreenMap(const fl::XYMap& map, float diameter) {
804 fl::ScreenMap screenmap = map.toScreenMap();
805 if (diameter <= 0.0f) {
806 screenmap.setDiameter(.15f); // Default diameter for small matrices
807 } else {
808 screenmap.setDiameter(diameter);
809 }
812 return *this;
813}
814
815Channel& Channel::setScreenMap(const fl::ScreenMap& map) {
816 mScreenMap = map;
818 return *this;
819}
820
821Channel& Channel::setScreenMap(fl::u16 width, fl::u16 height, float diameter) {
823 return setScreenMap(xymap, diameter);
824}
825
826Channel& Channel::setScreenMap(const fl::XMap& map) {
827 // Convert 1D XMap to 2D XYMap (width=length, height=1) and reuse existing logic
829 return setScreenMap(xymap, .15f); // Use default diameter for 1D strips
830}
831
832const fl::ScreenMap& Channel::getScreenMap() const {
833 return mScreenMap;
834}
835
836bool Channel::hasScreenMap() const {
837 return mScreenMap.getLength() > 0;
838}
839
840} // namespace fl
fl::UISlider brightness("Brightness", BRIGHTNESS, 0, 255)
XYMap xymap
fl::ScreenMap screenmap
Rgbw rgbw
ESP32-P4 Parallel IO (PARLIO) LED channel.
CPixelLEDController(RegistrationMode mode)
T fetch_add(T value) FL_NOEXCEPT
Definition atomic.h:157
CLEDController & setCorrection(CRGB correction) FL_NOEXCEPT
The color corrction to use for this controller, expressed as a CRGB object.
VIRTUAL_IF_NOT_AVR void showLeds(fl::u8 brightness) FL_NOEXCEPT
fl::u8 getDither() FL_NOEXCEPT
Get the dithering option currently set for this controller.
void removeFromDrawList() FL_NOEXCEPT
Remove this controller from the draw list.
RegistrationMode
Registration mode for constructor.
@ DeferRegister
Defer registration until addToList() is called.
bool isInList() const FL_NOEXCEPT
Check if this controller is in the linked list.
CLEDController & setLeds(CRGB *data, int nLeds) FL_NOEXCEPT
Set the default array of LEDs to be used by this controller.
CLEDController & setDither(fl::u8 ditherMode=BINARY_DITHER) FL_NOEXCEPT
Set the dithering mode for this controller to use.
CRGB * leds() FL_NOEXCEPT
Pointer to the CRGB array for this controller.
void addToList() FL_NOEXCEPT
Add this controller to the linked list.
Rgbw getRgbw() const FL_NOEXCEPT
virtual int size() const FL_NOEXCEPT
How many LEDs does this controller manage?
CRGB getCorrection() FL_NOEXCEPT
Get the correction value used by this controller.
CLEDController & setTemperature(CRGB temperature) FL_NOEXCEPT
Set the color temperature, aka white point, for this controller.
CRGB getTemperature() FL_NOEXCEPT
Get the color temperature, aka white point, for this controller.
FL_NO_INLINE fl::shared_ptr< IChannelDriver > resolveDynamicDriver()
Cold slow-path helper for showPixels() when the driver was NOT pre-bound via setDriver().
EOrder mRgbOrder
Definition channel.h:255
static fl::string makeName(i32 id, const fl::optional< fl::string > &configName=fl::optional< fl::string >())
fl::span< CRGB > leds()
Get the LED array as a span (non-const)
ChipsetVariant mChipset
Definition channel.h:254
CLEDController * asController()
Get pointer to base CLEDController for linked list traversal.
Definition channel.h:129
bool mDriverPreBound
Definition channel.h:257
bool mBusWarned
Definition channel.h:265
fl::ScreenMap mScreenMap
Definition channel.h:280
ChannelDataPtr mChannelData
Definition channel.h:279
static i32 nextId()
Channel(const ChipsetVariant &chipset, EOrder rgbOrder, RegistrationMode mode)
Protected constructor for template subclasses (e.g., ClocklessIdf5)
bool mDisabledDriverWarned
Definition channel.h:270
fl::weak_ptr< IChannelDriver > mDriver
Definition channel.h:256
Channel & setScreenMap(const fl::XYMap &map, float diameter=-1.f)
Set screen map for JS canvas visualization from XYMap.
const i32 mId
Definition channel.h:276
fl::string mName
Definition channel.h:277
ChannelOptions mSettings
Definition channel.h:278
static ChannelDataPtr create(const ChipsetVariant &chipset, fl::vector_psram< u8 > &&encodedData=fl::vector_psram< u8 >()) FL_NOEXCEPT
Create channel transmission data (modern variant-based API)
Definition data.cpp.hpp:12
fl::shared_ptr< IChannelDriver > selectDriverForChannel(const ChannelDataPtr &data, const fl::string &affinity) FL_NOEXCEPT
Select best driver for channel data (used by Channel::showPixels)
fl::shared_ptr< IChannelDriver > findDriverByName(const fl::string &name) const FL_NOEXCEPT
Silent counterpart to getDriverByName().
@ STATUS_ENABLED
Driver is registered and enabled.
Definition manager.h:173
@ STATUS_DISABLED
Driver is registered but disabled.
Definition manager.h:172
static ChannelManager & instance() FL_NOEXCEPT
Get the global singleton instance.
DriverStatus driverStatus(const fl::string &name) const FL_NOEXCEPT
Look up a driver's registration status without logging on miss.
static void onCanvasUiSet(CLEDController *strip, const ScreenMap &xymap) FL_NOEXCEPT
static fl::shared_ptr< const Gamma8 > getOrCreate(float gamma) FL_NOEXCEPT
Definition ease.cpp.hpp:459
IChannelDriver() FL_NOEXCEPT=default
Minimal interface for LED channel transmission drivers.
Definition driver.h:49
T & value() FL_NOEXCEPT
Definition optional.h:112
bool has_value() const FL_NOEXCEPT
Definition optional.h:42
void writeHD108(CONTAINER_UIN8_T *out) FL_NOEXCEPT
Encode pixels in HD108 format (zero allocation)
void writeAPA102(CONTAINER_UIN8_T *out, bool hd_gamma=false) FL_NOEXCEPT
Encode pixels in APA102/DOTSTAR format (zero allocation)
void writeP9813(CONTAINER_UIN8_T *out) FL_NOEXCEPT
Encode pixels in P9813 format (zero allocation)
void writeWS2801(CONTAINER_UIN8_T *out) FL_NOEXCEPT
Encode pixels in WS2801 format (zero allocation)
int size() FL_NOEXCEPT
void writeSM16716(CONTAINER_UIN8_T *out) FL_NOEXCEPT
Encode pixels in SM16716 format (zero allocation)
void writeLPD6803(CONTAINER_UIN8_T *out) FL_NOEXCEPT
Encode pixels in LPD6803 format (zero allocation)
void writeWS2812(CONTAINER_UIN8_T *out) FL_NOEXCEPT
void writeSK9822(CONTAINER_UIN8_T *out, bool hd_gamma=false) FL_NOEXCEPT
Encode pixels in SK9822 format (zero allocation)
Rgbw get_rgbw() const FL_NOEXCEPT
Rgbww get_rgbww() const FL_NOEXCEPT
void writeWS2803(CONTAINER_UIN8_T *out) FL_NOEXCEPT
Encode pixels in WS2803 format (zero allocation)
void writeLPD8806(CONTAINER_UIN8_T *out) FL_NOEXCEPT
Encode pixels in LPD8806 format (zero allocation)
Adapter class that creates a PixelIterator from any color order.
u16 mapToIndex(const u16 &x, const u16 &y) const FL_NOEXCEPT
u16 getWidth() const FL_NOEXCEPT
u16 getHeight() const FL_NOEXCEPT
static XYMap fromXMap(const XMap &xmap) FL_NOEXCEPT
Create an XYMap from an XMap (treats 1D as 2D with height=1)
Definition xymap.cpp.hpp:60
static XYMap constructRectangularGrid(u16 width, u16 height, u16 offset=0) FL_NOEXCEPT
Definition xymap.cpp.hpp:35
PixelIterator & get()
Get the constructed pixel iterator.
ReorderingPixelIteratorAny(PixelController< RGB, 1, 0xFFFFFFFF > &pixels, const XYMap *addressing, EOrder rgbOrder, Rgbw rgbw, Rgbww rgbww, const fl::string &channelName)
Construct pixel iterator with optional addressing transformation.
static fl::vector< CRGB > & getReorderBufferTLS()
Get thread-local buffer for addressing transformation.
fl::optional< PixelController< RGB, 1, 0xFFFFFFFF > > mAddressedController
virtual Capabilities getCapabilities() const override
Get driver capabilities (clockless, SPI, or both)
virtual bool canHandle(const ChannelDataPtr &data) const override
Check if this driver can handle the given channel data.
virtual DriverState poll() override
Query driver state and perform maintenance.
virtual void show() override
Trigger transmission of enqueued data.
virtual fl::string getName() const override
Get the driver name for affinity binding.
virtual void enqueue(ChannelDataPtr) override
Enqueue channel data for transmission.
T * get() const FL_NOEXCEPT
Definition shared_ptr.h:334
static string from_literal(const char *literal) FL_NOEXCEPT
fl::size size() const FL_NOEXCEPT
T * data() FL_NOEXCEPT
Definition vector.h:619
void clear() FL_NOEXCEPT
Definition vector.h:634
void resize(fl::size n) FL_NOEXCEPT
Definition vector.h:593
Channel transmission data - lightweight DTO for driver transmission.
#define DISABLE_DITHER
Disable dithering.
Definition dither_mode.h:10
UCS7604 LED chipset encoder implementation.
#define FL_WARN(X)
Definition log.h:276
#define FL_ERROR(X)
Definition log.h:219
#define FL_DBG
Definition log.h:388
#define FL_WARN_ONCE(X)
Definition log.h:278
Centralized logging categories for FastLED hardware interfaces and subsystems.
Unified manager for channel drivers with priority-based fallback.
void applyWhiteCfg(CLEDController &ctrl, const ChannelOptions &options) FL_NOEXCEPT
Apply the white-channel selection from ChannelOptions to a CLEDController.
static FL_NO_INLINE void emitDisabledDriverError(const fl::string &channelName, const fl::string &driverName, const fl::string &exclusive) FL_NOEXCEPT
Out-of-line cold-path emitter for the #2517 silent-drop DISABLED-driver diagnostic in Channel::showPi...
void writeUCS7604(fl::vector_psram< u8 > *data, PixelIterator &pixelIterator, ClocklessEncoder encoder, const ChannelOptions &settings, EOrder rgbOrder)
Encode UCS7604 pixel data into the channel data buffer.
CurrentControl brightness()
Get current global UCS7604 brightness value.
string to_string(T value) FL_NOEXCEPT
Definition string.h:450
unsigned char u8
Definition stdint.h:131
ClocklessEncoder
Identifies which encoder to use for clockless chipsets in the Channel API.
@ CLOCKLESS_ENCODER_UCS7604_8BIT
UCS7604 8-bit 800KHz.
@ CLOCKLESS_ENCODER_UCS7604_16BIT
UCS7604 16-bit 800KHz.
@ CLOCKLESS_ENCODER_UCS7604_16BIT_1600
UCS7604 16-bit 1600KHz.
@ CLOCKLESS_ENCODER_WS2812
Default, no preamble (WS2812 and compatible)
AtomicFake< T > atomic
Definition atomic.h:26
IChannelDriver * getStubChannelEngine()
Get stub channel driver for testing or unsupported platforms.
MapRedBlackTree< Key, T, Compare, fl::allocator_slab< char > > map
Definition map.h:283
u8 u8 height
Definition blur.h:186
back_insert_iterator< Container > back_inserter(Container &c) FL_NOEXCEPT
Helper function to create a back_insert_iterator.
Definition iterator.h:139
const char * busName(Bus b) FL_NOEXCEPT
Canonical driver-name string for a Bus value.
Definition bus.h:167
Optional< T > optional
Definition optional.h:16
fl::variant< ClocklessChipset, SpiChipsetConfig > ChipsetVariant
Definition config.h:153
Bus
Driver identifier for compile-time bus selection.
Definition bus.h:60
@ AUTO
Sentinel: defer to DefaultBus<Chipset>::value.
Definition bus.h:61
fl::i64 i64
Definition s16x16x4.h:222
u8 width
Definition blur.h:186
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
UCS7604Mode
UCS7604 protocol configuration modes.
Definition ucs7604.h:28
@ UCS7604_MODE_8BIT_800KHZ
Definition ucs7604.h:29
@ UCS7604_MODE_16BIT_1600KHZ
Definition ucs7604.h:31
@ UCS7604_MODE_16BIT_800KHZ
Definition ucs7604.h:30
EOrder
RGB color channel orderings, used when instantiating controllers to determine what order the controll...
Definition eorder.h:13
@ RGB
Red, Green, Blue (0012)
Definition eorder.h:14
constexpr u32 gamma(float g) FL_NOEXCEPT
Definition gamma_lut.h:36
@ SM16716
SM16716 LED chipset.
@ LPD6803
LPD6803 LED chipset.
@ APA102HD
APA102 LED chipset with 5-bit gamma correction.
@ HD107
Same as APA102, but in turbo 40-mhz mode.
@ DOTSTARHD
APA102HD LED chipset alias.
@ SK9822HD
SK9822 LED chipset with 5-bit gamma correction.
@ SK9822
SK9822 LED chipset.
@ DOTSTAR
APA102 LED chipset alias.
@ P9813
P9813 LED chipset.
@ APA102
APA102 LED chipset.
@ HD108
16-bit variant of HD107, always gamma corrected. No SD (standard definition) option available - all H...
@ LPD8806
LPD8806 LED chipset.
@ WS2803
WS2803 LED chipset.
@ WS2801
WS2801 LED chipset.
@ HD107HD
Same as APA102HD, but in turbo 40-mhz mode.
void encodeUCS7604(PixelIterator &pixel_iter, size_t num_leds, OutputIterator out, UCS7604Mode mode, const UCS7604CurrentControl &current, bool is_rgbw, const Gamma8 *gamma=nullptr)
Encode complete UCS7604 frame (preamble + padding + pixel data)
Definition ucs7604.h:209
Base definition for an LED controller.
Definition crgb.hpp:179
Low level pixel data writing class.
#define FL_NO_INLINE
#define FL_NOEXCEPT
FASTLED_FORCE_INLINE int size() const
Get the length of the LED strip.
ColorAdjustment mColorAdjustment
const fl::u8 * mData
pointer to the underlying LED data
Pixel controller class.
@ Black
<div style='background:#000000;width:4em;height:4em;'></div>
Definition crgb.h:510
Representation of an 8-bit RGB pixel (Red, Green, Blue)
Definition crgb.h:38
fl::optional< fl::string > mName
Optional user-specified name (if not set, Channel generates one automatically)
Definition config.h:279
ChannelOptions options
Optional channel settings (correction, temperature, dither, rgbw, affinity)
Definition config.h:273
fl::span< CRGB > mLeds
LED data array.
Definition config.h:267
ChipsetVariant chipset
Chipset configuration (clockless or SPI)
Definition config.h:264
EOrder rgb_order
RGB channel ordering.
Definition config.h:270
Configuration for a single LED channel.
Definition config.h:163
static ChannelEvents & instance()
fl::optional< float > mGamma
Definition options.h:51
fl::u8 mDitherMode
Definition options.h:46
Optional channel configuration parameters All fields have sensible defaults and can be overridden as ...
Definition options.h:43
Runtime bit-period timing for a clockless chipset.
ClocklessEncoder encoder
Byte-level encoding pipeline (default: WS2812)
Definition config.h:35
Clockless chipset configuration (single data pin)
Definition config.h:32
Driver capabilities.
Definition driver.h:151
Driver state with optional error message.
Definition driver.h:165
FASTLED_FORCE_INLINE bool active() const FL_NOEXCEPT
Definition rgbw.h:182
FASTLED_FORCE_INLINE bool active() const FL_NOEXCEPT
Definition rgbww.h:79
Per-strip RGBWW configuration.
Definition rgbww.h:60
SPI chipset configuration (data + clock pins)
Definition config.h:102
SPI encoder configuration for LED protocols.
Definition spi.h:38
u8 g
Green channel current (0x0-0xF)
Definition ucs7604.h:37
u8 b
Blue channel current (0x0-0xF)
Definition ucs7604.h:38
u8 w
White channel current (0x0-0xF)
Definition ucs7604.h:39
u8 r
Red channel current (0x0-0xF)
Definition ucs7604.h:36
UCS7604 current control structure with 4-bit fields for each channel.
Definition ucs7604.h:35
#define FL_SCOPED_TRACE
Definition trace.h:128
UCS7604 LED chipset controller implementation for FastLED.