/* WS2812FX_fcn.cpp contains all utility functions Harm Aldick - 2016 www.aldick.org LICENSE The MIT License (MIT) Copyright (c) 2016 Harm Aldick Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Modified heavily for WLED */ #include "wled.h" #include "FX.h" #include "palettes.h" #ifdef SEGMENT #undef SEGMENT #define SEGMENT _segments[_segment_index] #endif #ifdef SEGCOLOR #undef SEGCOLOR #define SEGCOLOR(x) _colors_t[x] #endif #ifdef SEGENV #undef SEGENV #define SEGENV _segment_runtimes[_segment_index] #endif #ifdef SEGLEN #undef SEGLEN #define SEGLEN _virtualSegmentLength #endif /* Custom per-LED mapping has moved! Create a file "ledmap.json" using the edit page. this is just an example (30 LEDs). It will first set all even, then all uneven LEDs. {"map":[ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29]} another example. Switches direction every 5 LEDs. {"map":[ 0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14, 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]} */ //factory defaults LED setup //#define PIXEL_COUNTS 30, 30, 30, 30 //#define DATA_PINS 16, 1, 3, 4 //#define DEFAULT_LED_TYPE TYPE_WS2812_RGB #ifndef PIXEL_COUNTS #define PIXEL_COUNTS DEFAULT_LED_COUNT #endif #ifndef DATA_PINS #define DATA_PINS LEDPIN #endif #ifndef DEFAULT_LED_TYPE #define DEFAULT_LED_TYPE TYPE_WS2812_RGB #endif #ifndef DEFAULT_LED_COLOR_ORDER #define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB #endif #if MAX_NUM_SEGMENTS < WLED_MAX_BUSSES #error "Max segments must be at least max number of busses!" #endif bool Segment::setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false; if (c == colors[slot]) return false; uint8_t b = (slot == 1) ? cct : opacity; ColorTransition::startTransition(b, colors[slot], segn, slot); colors[slot] = c; return true; } void Segment::setCCT(uint16_t k, uint8_t segn) { if (segn >= MAX_NUM_SEGMENTS) return; if (k > 255) { //kelvin value, convert to 0-255 if (k < 1900) k = 1900; if (k > 10091) k = 10091; k = (k - 1900) >> 5; } if (cct == k) return; ColorTransition::startTransition(cct, colors[1], segn, 1); cct = k; } void Segment::setOpacity(uint8_t o, uint8_t segn) { if (segn >= MAX_NUM_SEGMENTS) return; if (opacity == o) return; ColorTransition::startTransition(opacity, colors[0], segn, 0); opacity = o; } void Segment::setOption(uint8_t n, bool val, uint8_t segn) { bool prevOn = false; if (n == SEG_OPTION_ON) { prevOn = getOption(SEG_OPTION_ON); if (!val && prevOn) { //fade off ColorTransition::startTransition(opacity, colors[0], segn, 0); } } if (val) options |= 0x01 << n; else options &= ~(0x01 << n); if (n == SEG_OPTION_ON && val && !prevOn) { //fade on ColorTransition::startTransition(0, colors[0], segn, 0); } } // 2D matrix uint16_t Segment::virtualWidth() { uint16_t groupLen = groupLength(); uint16_t vWidth = ((getOption(SEG_OPTION_TRANSPOSED) ? height() : width()) + groupLen - 1) / groupLen; if (getOption(SEG_OPTION_MIRROR)) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED return vWidth; } uint16_t Segment::virtualHeight() { uint16_t groupLen = groupLength(); uint16_t vHeight = ((getOption(SEG_OPTION_TRANSPOSED) ? width() : height()) + groupLen - 1) / groupLen; if (getOption(SEG_OPTION_MIRROR_Y)) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED return vHeight; } // 1D strip uint16_t Segment::virtualLength() { uint16_t groupLen = groupLength(); uint16_t vLength = (length() + groupLen - 1) / groupLen; if (getOption(SEG_OPTION_MIRROR)) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED return vLength; } uint8_t Segment::differs(Segment& b) { uint8_t d = 0; if (start != b.start) d |= SEG_DIFFERS_BOUNDS; if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS; if (offset != b.offset) d |= SEG_DIFFERS_GSO; if (grouping != b.grouping) d |= SEG_DIFFERS_GSO; if (spacing != b.spacing) d |= SEG_DIFFERS_GSO; if (opacity != b.opacity) d |= SEG_DIFFERS_BRI; if (mode != b.mode) d |= SEG_DIFFERS_FX; if (speed != b.speed) d |= SEG_DIFFERS_FX; if (intensity != b.intensity) d |= SEG_DIFFERS_FX; if (palette != b.palette) d |= SEG_DIFFERS_FX; if (custom1 != b.custom1) d |= SEG_DIFFERS_FX; if (custom2 != b.custom2) d |= SEG_DIFFERS_FX; if (custom3 != b.custom3) d |= SEG_DIFFERS_FX; if (startY != b.startY) d |= SEG_DIFFERS_BOUNDS; if (stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS; //bit pattern: msb first: [transposed mirrorY reverseY] transitional (tbd) paused needspixelstate mirrored on reverse selected if ((options & 0b11100101110) != (b.options & 0b11100101110)) d |= SEG_DIFFERS_OPT; if ((options & 0x01) != (b.options & 0x01)) d |= SEG_DIFFERS_SEL; for (uint8_t i = 0; i < NUM_COLORS; i++) if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; return d; } void Segment::refreshLightCapabilities() { if (!isActive()) { _capabilities = 0; return; } uint8_t capabilities = 0; for (uint8_t b = 0; b < busses.getNumBusses(); b++) { Bus *bus = busses.getBus(b); if (bus == nullptr || bus->getLength()==0) break; if (!bus->isOk()) continue; if (bus->getStart() >= stop) continue; if (bus->getStart() + bus->getLength() <= start) continue; uint8_t type = bus->getType(); if (type != TYPE_ANALOG_1CH && (cctFromRgb || type != TYPE_ANALOG_2CH)) capabilities |= 0x01; // segment supports RGB (full color) if (bus->isRgbw()) capabilities |= 0x02; // segment supports white channel if (!cctFromRgb) { switch (type) { case TYPE_ANALOG_5CH: case TYPE_ANALOG_2CH: capabilities |= 0x04; //segment supports white CCT } } if (correctWB && type != TYPE_ANALOG_1CH) capabilities |= 0x04; //white balance correction (uses CCT slider) uint8_t aWM = Bus::getAutoWhiteMode()<255 ? Bus::getAutoWhiteMode() : bus->getAWMode(); bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed if (bus->isRgbw() && (whiteSlider || !(capabilities & 0x01))) capabilities |= 0x08; // allow white channel adjustments (AWM allows or is not RGB) } _capabilities = capabilities; } bool Segment_runtime::allocateData(uint16_t len){ if (data && _dataLen == len) return true; //already allocated WS2812FX *instance = WS2812FX::getInstance(); deallocateData(); if (instance->_usedSegmentData + len > MAX_SEGMENT_DATA) return false; //not enough memory // if possible use SPI RAM on ESP32 #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) if (psramFound()) data = (byte*) ps_malloc(len); else #endif data = (byte*) malloc(len); if (!data) return false; //allocation failed instance->_usedSegmentData += len; _dataLen = len; memset(data, 0, len); return true; } void Segment_runtime::deallocateData(){ free(data); data = nullptr; WS2812FX::getInstance()->_usedSegmentData -= _dataLen; _dataLen = 0; } /** * If reset of this segment was request, clears runtime * settings of this segment. * Must not be called while an effect mode function is running * because it could access the data buffer and this method * may free that data buffer. */ void Segment_runtime::resetIfRequired() { if (_requiresReset) { next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; deallocateData(); _requiresReset = false; } } void ColorTransition::startTransition(uint8_t oldBri, uint32_t oldCol, uint8_t segn, uint8_t slot) { WS2812FX *instance = WS2812FX::getInstance(); if (segn >= MAX_NUM_SEGMENTS || slot >= NUM_COLORS || instance->_transitionDur == 0) return; if (instance->_brightness == 0) return; //do not need transitions if master bri is off if (!instance->_segments[segn].getOption(SEG_OPTION_ON)) return; //not if segment is off either uint8_t tIndex = 0xFF; //none found uint16_t tProgression = 0; uint8_t s = segn + (slot << 6); //merge slot and segment into one byte for (uint8_t i = 0; i < MAX_NUM_TRANSITIONS; i++) { uint8_t tSeg = instance->transitions[i].segment; //see if this segment + color already has a running transition if (tSeg == s) { tIndex = i; break; } if (tSeg == 0xFF) { //free transition tIndex = i; tProgression = 0xFFFF; } } if (tIndex == 0xFF) { //no slot found yet for (uint8_t i = 0; i < MAX_NUM_TRANSITIONS; i++) { //find most progressed transition to overwrite uint16_t prog = instance->transitions[i].progress(); if (prog > tProgression) { tIndex = i; tProgression = prog; } } } ColorTransition& t = instance->transitions[tIndex]; if (t.segment == s) //this is an active transition on the same segment+color { bool wasTurningOff = (oldBri == 0); t.briOld = t.currentBri(wasTurningOff, slot); t.colorOld = t.currentColor(oldCol); } else { t.briOld = oldBri; t.colorOld = oldCol; uint8_t prevSeg = t.segment & 0x3F; if (prevSeg < MAX_NUM_SEGMENTS) instance->_segments[prevSeg].setOption(SEG_OPTION_TRANSITIONAL, false); } t.transitionDur = instance->_transitionDur; t.transitionStart = millis(); t.segment = s; instance->_segments[segn].setOption(SEG_OPTION_TRANSITIONAL, true); //refresh immediately, required for Solid mode if (instance->_segment_runtimes[segn].next_time > t.transitionStart + 22) instance->_segment_runtimes[segn].next_time = t.transitionStart; } uint16_t ColorTransition::progress(bool allowEnd) { //transition progression between 0-65535 WS2812FX *instance = WS2812FX::getInstance(); uint32_t timeNow = millis(); if (timeNow - transitionStart > transitionDur) { if (allowEnd) { uint8_t segn = segment & 0x3F; if (segn < MAX_NUM_SEGMENTS) instance->_segments[segn].setOption(SEG_OPTION_TRANSITIONAL, false); segment = 0xFF; } return 0xFFFF; } uint32_t elapsed = timeNow - transitionStart; uint32_t prog = elapsed * 0xFFFF / transitionDur; return (prog > 0xFFFF) ? 0xFFFF : prog; } uint8_t ColorTransition::currentBri(bool turningOff, uint8_t slot) { WS2812FX *instance = WS2812FX::getInstance(); uint8_t segn = segment & 0x3F; if (segn >= MAX_NUM_SEGMENTS) return 0; uint8_t briNew = instance->_segments[segn].opacity; if (slot == 0) { if (!instance->_segments[segn].getOption(SEG_OPTION_ON) || turningOff) briNew = 0; } else { //transition slot 1 brightness for CCT transition briNew = instance->_segments[segn].cct; } uint32_t prog = progress() + 1; return ((briNew * prog) + (briOld * (0x10000 - prog))) >> 16; } //do not call this method from system context (network callback) void WS2812FX::finalizeInit(void) { //reset segment runtimes for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { _segment_runtimes[i].markForReset(); _segment_runtimes[i].resetIfRequired(); } _hasWhiteChannel = _isOffRefreshRequired = false; //if busses failed to load, add default (fresh install, FS issue, ...) if (busses.getNumBusses() == 0) { DEBUG_PRINTLN(F("No busses, init default")); const uint8_t defDataPins[] = {DATA_PINS}; const uint16_t defCounts[] = {PIXEL_COUNTS}; const uint8_t defNumBusses = ((sizeof defDataPins) / (sizeof defDataPins[0])); const uint8_t defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); uint16_t prevLen = 0; for (uint8_t i = 0; i < defNumBusses && i < WLED_MAX_BUSSES; i++) { uint8_t defPin[] = {defDataPins[i]}; uint16_t start = prevLen; uint16_t count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; prevLen += count; BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY); busses.add(defCfg); } } _length = 0; for (uint8_t i=0; igetStart() + bus->getLength() > MAX_LEDS) break; //RGBW mode is enabled if at least one of the strips is RGBW _hasWhiteChannel |= bus->isRgbw(); //refresh is required to remain off if at least one of the strips requires the refresh. _isOffRefreshRequired |= bus->isOffRefreshRequired(); uint16_t busEnd = bus->getStart() + bus->getLength(); if (busEnd > _length) _length = busEnd; #ifdef ESP8266 if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue; uint8_t pins[5]; if (!bus->getPins(pins)) continue; BusDigital* bd = static_cast(bus); if (pins[0] == 3) bd->reinit(); #endif } //segments are created in makeAutoSegments(); setBrightness(_brightness); } void WS2812FX::service() { uint32_t nowUp = millis(); // Be aware, millis() rolls over every 49 days now = nowUp + timebase; if (nowUp - _lastShow < MIN_SHOW_DELAY) return; bool doShow = false; for(uint8_t i=0; i < MAX_NUM_SEGMENTS; i++) { //if (realtimeMode && useMainSegmentOnly && i == getMainSegmentId()) continue; _segment_index = i; // reset the segment runtime data if needed, called before isActive to ensure deleted // segment's buffers are cleared SEGENV.resetIfRequired(); if (!SEGMENT.isActive()) continue; // last condition ensures all solid segments are updated at the same time if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) { if (SEGMENT.grouping == 0) SEGMENT.grouping = 1; //sanity check doShow = true; uint16_t delay = FRAMETIME; if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen _virtualSegmentLength = SEGMENT.virtualLength(); _bri_t = SEGMENT.opacity; _colors_t[0] = SEGMENT.colors[0]; _colors_t[1] = SEGMENT.colors[1]; _colors_t[2] = SEGMENT.colors[2]; uint8_t _cct_t = SEGMENT.cct; if (!SEGMENT.getOption(SEG_OPTION_ON)) _bri_t = 0; for (uint8_t t = 0; t < MAX_NUM_TRANSITIONS; t++) { if ((transitions[t].segment & 0x3F) != i) continue; uint8_t slot = transitions[t].segment >> 6; if (slot == 0) _bri_t = transitions[t].currentBri(); if (slot == 1) _cct_t = transitions[t].currentBri(false, 1); _colors_t[slot] = transitions[t].currentColor(SEGMENT.colors[slot]); } if (!cctFromRgb || correctWB) busses.setSegmentCCT(_cct_t, correctWB); for (uint8_t c = 0; c < NUM_COLORS; c++) { _colors_t[c] = gamma32(_colors_t[c]); } handle_palette(); delay = (*_mode[SEGMENT.mode])(); if (SEGMENT.mode != FX_MODE_HALLOWEEN_EYES) SEGENV.call++; } SEGENV.next_time = nowUp + delay; } } _virtualSegmentLength = 0; busses.setSegmentCCT(-1); if(doShow) { yield(); show(); } _triggered = false; } // anti-aliased normalized version of setPixelColor() void /*IRAM_ATTR*/ WS2812FX::setPixelColor(float i, byte r, byte g, byte b, byte w, bool aa) { if (i<0.0f || i>1.0f) return; // not normalized float fC = i * (_virtualSegmentLength-1); if (aa) { uint16_t iL = roundf(fC-0.49f); uint16_t iR = roundf(fC+0.49f); float dL = fC - iL; float dR = iR - fC; uint32_t cIL = getPixelColor(iL); uint32_t cIR = getPixelColor(iR); if (iR!=iL) { // blend L pixel cIL = color_blend(RGBW32(r,g,b,w), cIL, uint8_t(dL*255.0f)); setPixelColor(iL, R(cIL), G(cIL), B(cIL), W(cIL)); // blend R pixel cIR = color_blend(RGBW32(r,g,b,w), cIR, uint8_t(dR*255.0f)); setPixelColor(iR, R(cIR), G(cIR), B(cIR), W(cIR)); } else { // exact match (x & y land on a pixel) setPixelColor(iL, r, g, b, w); } } else { setPixelColor(uint16_t(roundf(fC)), r, g, b, w); } } void IRAM_ATTR WS2812FX::setPixelColor(int i, byte r, byte g, byte b, byte w) { uint8_t segIdx = _virtualSegmentLength ? _segment_index : _mainSegment; if (isMatrix && _virtualSegmentLength) { // map linear pixel into 2D segment area (even for 1D segments, expanding vertically) uint16_t h = _segments[segIdx].virtualHeight(); // segment height in logical pixels for (uint16_t y = 0; y < h; y++) { // expand 1D effect vertically setPixelColorXY(i, y * _segments[segIdx].groupLength(), r, g, b, w); } return; } if (_virtualSegmentLength || (realtimeMode && useMainSegmentOnly)) { //color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments) if (_virtualSegmentLength && _bri_t < 255) { // _virtualSegmentLength!=0 -> from segment/FX r = scale8(r, _bri_t); g = scale8(g, _bri_t); b = scale8(b, _bri_t); w = scale8(w, _bri_t); } uint32_t col = RGBW32(r, g, b, w); uint16_t len = _segments[segIdx].length(); // length of segment in number of pixels // get physical pixel address (taking into account start, grouping, spacing [and offset]) i = i * _segments[segIdx].groupLength(); if (_segments[segIdx].getOption(SEG_OPTION_REVERSED)) { // is segment reversed? if (_segments[segIdx].getOption(SEG_OPTION_MIRROR)) { // is segment mirrored? i = (len - 1) / 2 - i; //only need to index half the pixels } else { i = (len - 1) - i; } } i += _segments[segIdx].start; // starting pixel in a group // set all the pixels in the group for (uint16_t j = 0; j < _segments[segIdx].grouping; j++) { uint16_t indexSet = i + ((_segments[segIdx].getOption(SEG_OPTION_REVERSED)) ? -j : j); if (indexSet >= _segments[segIdx].start && indexSet < _segments[segIdx].stop) { if (_segments[segIdx].getOption(SEG_OPTION_MIRROR)) { //set the corresponding mirrored pixel uint16_t indexMir = _segments[segIdx].stop - indexSet + _segments[segIdx].start - 1; indexMir += _segments[segIdx].offset; // offset/phase if (indexMir >= _segments[segIdx].stop) indexMir -= len; // wrap if (indexMir < customMappingSize) indexMir = customMappingTable[indexMir]; busses.setPixelColor(indexMir, col); } indexSet += _segments[segIdx].offset; // offset/phase if (indexSet >= _segments[segIdx].stop) indexSet -= len; // wrap if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet]; busses.setPixelColor(indexSet, col); } } } else { if (i < customMappingSize) i = customMappingTable[i]; busses.setPixelColor(i, RGBW32(r, g, b, w)); } } //DISCLAIMER //The following function attemps to calculate the current LED power usage, //and will limit the brightness to stay below a set amperage threshold. //It is NOT a measurement and NOT guaranteed to stay within the ablMilliampsMax margin. //Stay safe with high amperage and have a reasonable safety margin! //I am NOT to be held liable for burned down garages! //fine tune power estimation constants for your setup #define MA_FOR_ESP 100 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA) //you can set it to 0 if the ESP is powered by USB and the LEDs by external void WS2812FX::estimateCurrentAndLimitBri() { //power limit calculation //each LED can draw up 195075 "power units" (approx. 53mA) //one PU is the power it takes to have 1 channel 1 step brighter per brightness step //so A=2,R=255,G=0,B=0 would use 510 PU per LED (1mA is about 3700 PU) bool useWackyWS2815PowerModel = false; byte actualMilliampsPerLed = milliampsPerLed; if(milliampsPerLed == 255) { useWackyWS2815PowerModel = true; actualMilliampsPerLed = 12; // from testing an actual strip } if (ablMilliampsMax < 150 || actualMilliampsPerLed == 0) { //0 mA per LED and too low numbers turn off calculation currentMilliamps = 0; busses.setBrightness(_brightness); return; } uint16_t pLen = getLengthPhysical(); uint32_t puPerMilliamp = 195075 / actualMilliampsPerLed; uint32_t powerBudget = (ablMilliampsMax - MA_FOR_ESP) * puPerMilliamp; //100mA for ESP power if (powerBudget > puPerMilliamp * pLen) { //each LED uses about 1mA in standby, exclude that from power budget powerBudget -= puPerMilliamp * pLen; } else { powerBudget = 0; } uint32_t powerSum = 0; for (uint8_t b = 0; b < busses.getNumBusses(); b++) { Bus *bus = busses.getBus(b); if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses uint16_t len = bus->getLength(); uint32_t busPowerSum = 0; for (uint16_t i = 0; i < len; i++) { //sum up the usage of each LED uint32_t c = bus->getPixelColor(i); byte r = R(c), g = G(c), b = B(c), w = W(c); if(useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation busPowerSum += (MAX(MAX(r,g),b)) * 3; } else { busPowerSum += (r + g + b + w); } } if (bus->isRgbw()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less busPowerSum *= 3; busPowerSum = busPowerSum >> 2; //same as /= 4 } powerSum += busPowerSum; } uint32_t powerSum0 = powerSum; powerSum *= _brightness; if (powerSum > powerBudget) //scale brightness down to stay in current limit { float scale = (float)powerBudget / (float)powerSum; uint16_t scaleI = scale * 255; uint8_t scaleB = (scaleI > 255) ? 255 : scaleI; uint8_t newBri = scale8(_brightness, scaleB); busses.setBrightness(newBri); //to keep brightness uniform, sets virtual busses too currentMilliamps = (powerSum0 * newBri) / puPerMilliamp; } else { currentMilliamps = powerSum / puPerMilliamp; busses.setBrightness(_brightness); } currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate currentMilliamps += pLen; //add standby power back to estimate } void WS2812FX::show(void) { // avoid race condition, caputre _callback value show_callback callback = _callback; if (callback) callback(); estimateCurrentAndLimitBri(); // some buses send asynchronously and this method will return before // all of the data has been sent. // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods busses.show(); unsigned long now = millis(); unsigned long diff = now - _lastShow; uint16_t fpsCurr = 200; if (diff > 0) fpsCurr = 1000 / diff; _cumulativeFps = (3 * _cumulativeFps + fpsCurr) >> 2; _lastShow = now; } /** * Returns a true value if any of the strips are still being updated. * On some hardware (ESP32), strip updates are done asynchronously. */ bool WS2812FX::isUpdating() { return !busses.canAllShow(); } /** * Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough. * Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accurary varies */ uint16_t WS2812FX::getFps() { if (millis() - _lastShow > 2000) return 0; return _cumulativeFps +1; } void WS2812FX::setTargetFps(uint8_t fps) { if (fps > 0 && fps <= 120) _targetFps = fps; _frametime = 1000 / _targetFps; } void WS2812FX::setMode(uint8_t segid, uint8_t m) { if (segid >= MAX_NUM_SEGMENTS) return; if (m >= getModeCount()) m = getModeCount() - 1; if (_segments[segid].mode != m) { _segment_runtimes[segid].markForReset(); _segments[segid].mode = m; } } //applies to all active and selected segments void WS2812FX::setColor(uint8_t slot, uint32_t c) { if (slot >= NUM_COLORS) return; for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { if (_segments[i].isActive() && _segments[i].isSelected()) { _segments[i].setColor(slot, c, i); } } } void WS2812FX::setCCT(uint16_t k) { for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { if (_segments[i].isActive() && _segments[i].isSelected()) { _segments[i].setCCT(k, i); } } } void WS2812FX::setBrightness(uint8_t b, bool direct) { if (gammaCorrectBri) b = gamma8(b); if (_brightness == b) return; _brightness = b; if (_brightness == 0) { //unfreeze all segments on power off for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { _segments[i].setOption(SEG_OPTION_FREEZE, false); } } if (direct) { // would be dangerous if applied immediately (could exceed ABL), but will not output until the next show() busses.setBrightness(b); } else { unsigned long t = millis(); if (_segment_runtimes[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) show(); //apply brightness change immediately if no refresh soon } } uint8_t WS2812FX::getFirstSelectedSegId(void) { for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { if (_segments[i].isActive() && _segments[i].isSelected()) return i; } // if none selected, use the main segment return getMainSegmentId(); } void WS2812FX::setMainSegmentId(uint8_t n) { if (n >= MAX_NUM_SEGMENTS) return; //use supplied n if active, or first active if (_segments[n].isActive()) { _mainSegment = n; return; } for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { if (_segments[i].isActive()) { _mainSegment = i; return; } } _mainSegment = 0; return; } uint8_t WS2812FX::getLastActiveSegmentId(void) { for (uint8_t i = MAX_NUM_SEGMENTS -1; i > 0; i--) { if (_segments[i].isActive()) return i; } return 0; } uint8_t WS2812FX::getActiveSegmentsNum(void) { uint8_t c = 0; for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { if (_segments[i].isActive()) c++; } return c; } uint32_t WS2812FX::getPixelColor(uint16_t i) { if (isMatrix) return getPixelColorXY(i%matrixWidth, i/matrixWidth); // compatibility w/ non-effect fn // get physical pixel if (SEGMENT.getOption(SEG_OPTION_REVERSED)) i = SEGMENT.virtualLength() - i - 1; i *= SEGMENT.groupLength(); //if (SEGMENT.getOption(SEG_OPTION_REVERSED)) { // if (SEGMENT.getOption(SEG_OPTION_MIRROR)) i = (SEGMENT.length() - 1) / 2 - i; //only need to index half the pixels // else i = (SEGMENT.length() - 1) - i; //} i += SEGMENT.start; if (_virtualSegmentLength) { /* offset/phase */ i += SEGMENT.offset; if (i >= SEGMENT.stop) i -= SEGMENT.length(); } if (i < customMappingSize) i = customMappingTable[i]; if (i >= _length) return 0; return busses.getPixelColor(i); } uint16_t WS2812FX::getLengthPhysical(void) { uint16_t len = 0; for (uint8_t b = 0; b < busses.getNumBusses(); b++) { Bus *bus = busses.getBus(b); if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses len += bus->getLength(); } return len; } //used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw. //returns if there is an RGBW bus (supports RGB and White, not only white) //not influenced by auto-white mode, also true if white slider does not affect output white channel bool WS2812FX::hasRGBWBus(void) { for (uint8_t b = 0; b < busses.getNumBusses(); b++) { Bus *bus = busses.getBus(b); if (bus == nullptr || bus->getLength()==0) break; switch (bus->getType()) { case TYPE_SK6812_RGBW: case TYPE_TM1814: case TYPE_ANALOG_4CH: return true; } } return false; } bool WS2812FX::hasCCTBus(void) { if (cctFromRgb && !correctWB) return false; for (uint8_t b = 0; b < busses.getNumBusses(); b++) { Bus *bus = busses.getBus(b); if (bus == nullptr || bus->getLength()==0) break; switch (bus->getType()) { case TYPE_ANALOG_5CH: case TYPE_ANALOG_2CH: return true; } } return false; } void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) { if (n >= MAX_NUM_SEGMENTS) return; Segment& seg = _segments[n]; //return if neither bounds nor grouping have changed bool boundsUnchanged = (seg.start == i1 && seg.stop == i2); if (isMatrix) { boundsUnchanged &= (seg.startY == startY && seg.stopY == stopY); } if (boundsUnchanged && (!grouping || (seg.grouping == grouping && seg.spacing == spacing)) && (offset == UINT16_MAX || offset == seg.offset)) return; if (seg.stop) setRange(seg.start, seg.stop -1, 0); //turn old segment range off if (i2 <= i1) //disable segment { seg.stop = 0; if (seg.name) { delete[] seg.name; seg.name = nullptr; } // if main segment is deleted, set first active as main segment if (n == _mainSegment) setMainSegmentId(0); return; } if (isMatrix) { if (i1 < matrixWidth) seg.start = i1; seg.stop = i2 > matrixWidth ? matrixWidth : i2; if (startY < matrixHeight) seg.startY = startY; seg.stopY = stopY > matrixHeight ? matrixHeight : MAX(1,stopY); } else { if (i1 < _length) seg.start = i1; seg.stop = i2 > _length ? _length : i2; seg.startY = 0; seg.stopY = 1; } if (grouping) { seg.grouping = grouping; seg.spacing = spacing; } if (offset < UINT16_MAX) seg.offset = offset; _segment_runtimes[n].markForReset(); if (!boundsUnchanged) seg.refreshLightCapabilities(); } void WS2812FX::restartRuntime() { for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { _segment_runtimes[i].markForReset(); } } void WS2812FX::resetSegments() { for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) if (_segments[i].name) delete[] _segments[i].name; _mainSegment = 0; memset(_segments, 0, sizeof(_segments)); //memset(_segment_runtimes, 0, sizeof(_segment_runtimes)); _segment_index = 0; _segments[0].mode = DEFAULT_MODE; _segments[0].colors[0] = DEFAULT_COLOR; _segments[0].start = 0; _segments[0].speed = DEFAULT_SPEED; _segments[0].intensity = DEFAULT_INTENSITY; _segments[0].stop = _length; _segments[0].grouping = 1; _segments[0].setOption(SEG_OPTION_SELECTED, 1); _segments[0].setOption(SEG_OPTION_ON, 1); _segments[0].opacity = 255; _segments[0].cct = 127; _segments[0].custom1 = DEFAULT_C1; _segments[0].custom2 = DEFAULT_C2; _segments[0].custom3 = DEFAULT_C3; for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++) { _segments[i].colors[0] = color_wheel(i*51); _segments[i].grouping = 1; _segments[i].setOption(SEG_OPTION_ON, 1); _segments[i].opacity = 255; _segments[i].cct = 127; _segments[i].speed = DEFAULT_SPEED; _segments[i].intensity = DEFAULT_INTENSITY; _segments[i].custom1 = DEFAULT_C1; _segments[i].custom2 = DEFAULT_C2; _segments[i].custom3 = DEFAULT_C3; _segment_runtimes[i].markForReset(); } _segment_runtimes[0].markForReset(); } void WS2812FX::makeAutoSegments(bool forceReset) { if (isMatrix) { // only create 1 2D segment uint8_t mainSeg = getMainSegmentId(); if (forceReset) { for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { setSegment(i, 0, 0); } } if (getActiveSegmentsNum() < 2) { setSegment(mainSeg, 0, matrixWidth, 1, 0, 0, 0, matrixHeight); } } else if (autoSegments) { //make one segment per bus uint16_t segStarts[MAX_NUM_SEGMENTS] = {0}; uint16_t segStops [MAX_NUM_SEGMENTS] = {0}; uint8_t s = 0; for (uint8_t i = 0; i < busses.getNumBusses(); i++) { Bus* b = busses.getBus(i); segStarts[s] = b->getStart(); segStops[s] = segStarts[s] + b->getLength(); //check for overlap with previous segments for (uint8_t j = 0; j < s; j++) { if (segStops[j] > segStarts[s] && segStarts[j] < segStops[s]) { //segments overlap, merge segStarts[j] = min(segStarts[s],segStarts[j]); segStops [j] = max(segStops [s],segStops [j]); segStops[s] = 0; s--; } } s++; } for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { _segments[i].setOption(SEG_OPTION_SELECTED, true, i); setSegment(i, segStarts[i], segStops[i]); } } else { //expand the main seg to the entire length, but only if there are no other segments, or reset is forced uint8_t mainSeg = getMainSegmentId(); if (forceReset) { for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { setSegment(i, 0, 0); } } if (getActiveSegmentsNum() < 2) { setSegment(mainSeg, 0, _length); } } fixInvalidSegments(); } void WS2812FX::fixInvalidSegments() { //make sure no segment is longer than total (sanity check) for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { if (_segments[i].start >= _length) setSegment(i, 0, 0); if (_segments[i].stop > _length) setSegment(i, _segments[i].start, _length); // this is always called as the last step after finalizeInit(), update covered bus types getSegment(i).refreshLightCapabilities(); } } //true if all segments align with a bus, or if a segment covers the total length bool WS2812FX::checkSegmentAlignment() { for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { if (_segments[i].start >= _segments[i].stop) continue; //inactive segment bool aligned = false; for (uint8_t b = 0; bgetStart() && _segments[i].stop == bus->getStart() + bus->getLength()) aligned = true; } if (_segments[i].start == 0 && _segments[i].stop == _length) aligned = true; if (!aligned) return false; } return true; } //After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply) //Note: If called in an interrupt (e.g. JSON API), original segment must be restored, //otherwise it can lead to a crash on ESP32 because _segment_index is modified while in use by the main thread uint8_t WS2812FX::setPixelSegment(uint8_t n) { uint8_t prevSegId = _segment_index; if (n < MAX_NUM_SEGMENTS) { _segment_index = n; _virtualSegmentLength = SEGMENT.virtualLength(); } return prevSegId; } void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) { if (i2 >= i) { for (uint16_t x = i; x <= i2; x++) setPixelColor(x, col); } else { for (uint16_t x = i2; x <= i; x++) setPixelColor(x, col); } } void WS2812FX::setTransitionMode(bool t) { unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled for (uint16_t i = 0; i < MAX_NUM_SEGMENTS; i++) { _segments[i].setOption(SEG_OPTION_TRANSITIONAL, t); if (t && _segments[i].mode == FX_MODE_STATIC && _segment_runtimes[i].next_time > waitMax) _segment_runtimes[i].next_time = waitMax; } } /* * Fills segment with color */ void WS2812FX::fill(uint32_t c, uint8_t seg) { uint8_t oldSeg; if (seg != 255) oldSeg = setPixelSegment(seg); const uint16_t cols = isMatrix ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); const uint16_t rows = SEGMENT.virtualHeight(); // will be 1 for 1D for(uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { if (isMatrix) setPixelColorXY(x, y, c); else setPixelColor(x, c); } if (seg != 255) setPixelSegment(oldSeg); } /* * Blends the specified color with the existing pixel color. */ void WS2812FX::blendPixelColor(uint16_t n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); } /* * fade out function, higher rate = quicker fade */ void WS2812FX::fade_out(uint8_t rate) { const uint16_t cols = isMatrix ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); const uint16_t rows = SEGMENT.virtualHeight(); // will be 1 for 1D rate = (255-rate) >> 1; float mappedRate = float(rate) +1.1; uint32_t color = SEGCOLOR(1); // target color int w2 = W(color); int r2 = R(color); int g2 = G(color); int b2 = B(color); for (uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { color = isMatrix ? getPixelColorXY(x, y) : getPixelColor(x); int w1 = W(color); int r1 = R(color); int g1 = G(color); int b1 = B(color); int wdelta = (w2 - w1) / mappedRate; int rdelta = (r2 - r1) / mappedRate; int gdelta = (g2 - g1) / mappedRate; int bdelta = (b2 - b1) / mappedRate; // if fade isn't complete, make sure delta is at least 1 (fixes rounding issues) wdelta += (w2 == w1) ? 0 : (w2 > w1) ? 1 : -1; rdelta += (r2 == r1) ? 0 : (r2 > r1) ? 1 : -1; gdelta += (g2 == g1) ? 0 : (g2 > g1) ? 1 : -1; bdelta += (b2 == b1) ? 0 : (b2 > b1) ? 1 : -1; if (isMatrix) setPixelColorXY(x, y, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); else setPixelColor(x, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); } } // fades all pixels to black using nscale8() void WS2812FX::fadeToBlackBy(uint8_t fadeBy) { const uint16_t cols = isMatrix ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); const uint16_t rows = SEGMENT.virtualHeight(); // will be 1 for 1D for (uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { if (isMatrix) setPixelColorXY(x, y, CRGB(getPixelColorXY(x,y)).nscale8(255-fadeBy)); else setPixelColor(x, CRGB(getPixelColor(x)).nscale8(255-fadeBy)); } } /* * blurs segment content, source: FastLED colorutils.cpp */ void WS2812FX::blur(uint8_t blur_amount) { if (isMatrix) { // compatibility with 2D const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); for (uint16_t i = 0; i < rows; i++) blurRow(i, blur_amount); // blur all rows for (uint16_t k = 0; k < cols; k++) blurCol(k, blur_amount); // blur all columns return; } uint8_t keep = 255 - blur_amount; uint8_t seep = blur_amount >> 1; CRGB carryover = CRGB::Black; for(uint16_t i = 0; i < _virtualSegmentLength; i++) { CRGB cur = CRGB(getPixelColor(i)); CRGB part = cur; part.nscale8(seep); cur.nscale8(keep); cur += carryover; if(i > 0) { uint32_t c = getPixelColor(i-1); uint8_t r = R(c); uint8_t g = G(c); uint8_t b = B(c); setPixelColor(i-1, qadd8(r, part.red), qadd8(g, part.green), qadd8(b, part.blue)); } setPixelColor(i,cur.red, cur.green, cur.blue); carryover = part; } } uint16_t WS2812FX::triwave16(uint16_t in) { if (in < 0x8000) return in *2; return 0xFFFF - (in - 0x8000)*2; } /* * Generates a tristate square wave w/ attac & decay * @param x input value 0-255 * @param pulsewidth 0-127 * @param attdec attac & decay, max. pulsewidth / 2 * @returns signed waveform value */ int8_t WS2812FX::tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { int8_t a = 127; if (x > 127) { a = -127; x -= 127; } if (x < attdec) { //inc to max return (int16_t) x * a / attdec; } else if (x < pulsewidth - attdec) { //max return a; } else if (x < pulsewidth) { //dec to 0 return (int16_t) (pulsewidth - x) * a / attdec; } return 0; } /* * Put a value 0 to 255 in to get a color value. * The colours are a transition r -> g -> b -> back to r * Inspired by the Adafruit examples. */ uint32_t WS2812FX::color_wheel(uint8_t pos) { if (SEGMENT.palette) return color_from_palette(pos, false, true, 0); pos = 255 - pos; if(pos < 85) { return ((uint32_t)(255 - pos * 3) << 16) | ((uint32_t)(0) << 8) | (pos * 3); } else if(pos < 170) { pos -= 85; return ((uint32_t)(0) << 16) | ((uint32_t)(pos * 3) << 8) | (255 - pos * 3); } else { pos -= 170; return ((uint32_t)(pos * 3) << 16) | ((uint32_t)(255 - pos * 3) << 8) | (0); } } /* * Returns a new, random wheel index with a minimum distance of 42 from pos. */ uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) { uint8_t r = 0, x = 0, y = 0, d = 0; while(d < 42) { r = random8(); x = abs(pos - r); y = 255 - x; d = MIN(x, y); } return r; } void WS2812FX::load_gradient_palette(uint8_t index) { byte i = constrain(index, 0, GRADIENT_PALETTE_COUNT -1); byte tcp[72]; //support gradient palettes with up to 18 entries memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i])), 72); targetPalette.loadDynamicGradientPalette(tcp); } /* * FastLED palette modes helper function. Limitation: Due to memory reasons, multiple active segments with FastLED will disable the Palette transitions */ void WS2812FX::handle_palette(void) { bool singleSegmentMode = (_segment_index == _segment_index_palette_last); _segment_index_palette_last = _segment_index; byte paletteIndex = SEGMENT.palette; if (paletteIndex == 0) //default palette. Differs depending on effect { switch (SEGMENT.mode) { case FX_MODE_FIRE_2012 : paletteIndex = 35; break; //heat palette case FX_MODE_COLORWAVES : paletteIndex = 26; break; //landscape 33 case FX_MODE_FILLNOISE8 : paletteIndex = 9; break; //ocean colors case FX_MODE_NOISE16_1 : paletteIndex = 20; break; //Drywet case FX_MODE_NOISE16_2 : paletteIndex = 43; break; //Blue cyan yellow case FX_MODE_NOISE16_3 : paletteIndex = 35; break; //heat palette case FX_MODE_NOISE16_4 : paletteIndex = 26; break; //landscape 33 case FX_MODE_GLITTER : paletteIndex = 11; break; //rainbow colors case FX_MODE_SUNRISE : paletteIndex = 35; break; //heat palette case FX_MODE_FLOW : paletteIndex = 6; break; //party } } if (SEGMENT.mode >= FX_MODE_METEOR && paletteIndex == 0) paletteIndex = 4; switch (paletteIndex) { case 0: //default palette. Exceptions for specific effects above targetPalette = PartyColors_p; break; case 1: {//periodically replace palette with a random one. Doesn't work with multiple FastLED segments if (!singleSegmentMode) { targetPalette = PartyColors_p; break; //fallback } if (millis() - _lastPaletteChange > 1000 + ((uint32_t)(255-SEGMENT.intensity))*100) { targetPalette = CRGBPalette16( CHSV(random8(), 255, random8(128, 255)), CHSV(random8(), 255, random8(128, 255)), CHSV(random8(), 192, random8(128, 255)), CHSV(random8(), 255, random8(128, 255))); _lastPaletteChange = millis(); } break;} case 2: {//primary color only CRGB prim = CRGB(SEGCOLOR(0)); targetPalette = CRGBPalette16(prim); break;} case 3: {//primary + secondary CRGB prim = CRGB(SEGCOLOR(0)); CRGB sec = CRGB(SEGCOLOR(1)); targetPalette = CRGBPalette16(prim,prim,sec,sec); break;} case 4: {//primary + secondary + tertiary CRGB prim = CRGB(SEGCOLOR(0)); CRGB sec = CRGB(SEGCOLOR(1)); CRGB ter = CRGB(SEGCOLOR(2)); targetPalette = CRGBPalette16(ter,sec,prim); break;} case 5: {//primary + secondary (+tert if not off), more distinct CRGB prim = CRGB(SEGCOLOR(0)); CRGB sec = CRGB(SEGCOLOR(1)); if (SEGCOLOR(2)) { CRGB ter = CRGB(SEGCOLOR(2)); targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,ter,ter,ter,ter,ter,prim); } else { targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec); } break;} case 6: //Party colors targetPalette = PartyColors_p; break; case 7: //Cloud colors targetPalette = CloudColors_p; break; case 8: //Lava colors targetPalette = LavaColors_p; break; case 9: //Ocean colors targetPalette = OceanColors_p; break; case 10: //Forest colors targetPalette = ForestColors_p; break; case 11: //Rainbow colors targetPalette = RainbowColors_p; break; case 12: //Rainbow stripe colors targetPalette = RainbowStripeColors_p; break; default: //progmem palettes load_gradient_palette(paletteIndex -13); } if (singleSegmentMode && paletteFade && SEGENV.call > 0) //only blend if just one segment uses FastLED mode { nblendPaletteTowardPalette(currentPalette, targetPalette, 48); } else { currentPalette = targetPalette; } } /* * Gets a single color from the currently selected palette. * @param i Palette Index (if mapping is true, the full palette will be _virtualSegmentLength long, if false, 255). Will wrap around automatically. * @param mapping if true, LED position in segment is considered for color * @param wrap FastLED palettes will usally wrap back to the start smoothly. Set false to get a hard edge * @param mcol If the default palette 0 is selected, return the standard color 0, 1 or 2 instead. If >2, Party palette is used instead * @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling) * @returns Single color from palette */ uint32_t IRAM_ATTR WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) { if ((SEGMENT.palette == 0 && mcol < 3) || _no_rgb) { uint32_t color = SEGCOLOR(mcol); if (pbri == 255) return color; return RGBW32(scale8_video(R(color),pbri), scale8_video(G(color),pbri), scale8_video(B(color),pbri), scale8_video(W(color),pbri)); } uint8_t paletteIndex = i; if (mapping && _virtualSegmentLength > 1) paletteIndex = (i*255)/(_virtualSegmentLength -1); if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" CRGB fastled_col; fastled_col = ColorFromPalette(currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND); return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, 0); } //load custom mapping table from JSON file (called from finalizeInit() or deserializeState()) void WS2812FX::deserializeMap(uint8_t n) { if (isMatrix) return; // 2D support creates its own ledmap char fileName[32]; strcpy_P(fileName, PSTR("/ledmap")); if (n) sprintf(fileName +7, "%d", n); strcat(fileName, ".json"); bool isFile = WLED_FS.exists(fileName); if (!isFile) { // erase custom mapping if selecting nonexistent ledmap.json (n==0) if (!n && customMappingTable != nullptr) { customMappingSize = 0; delete[] customMappingTable; customMappingTable = nullptr; } return; } if (!requestJSONBufferLock(7)) return; DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName); if (!readObjectFromFile(fileName, nullptr, &doc)) { releaseJSONBufferLock(); return; //if file does not exist just exit } // erase old custom ledmap if (customMappingTable != nullptr) { customMappingSize = 0; delete[] customMappingTable; customMappingTable = nullptr; } JsonArray map = doc[F("map")]; if (!map.isNull() && map.size()) { // not an empty map customMappingSize = map.size(); customMappingTable = new uint16_t[customMappingSize]; for (uint16_t i=0; i128) 5 sliders and 3 colors and the palette will be shown // If effective (@) // - a ; seperates slider controls (left) from color controls (middle) and palette control (right) // - if left, middle or right is empty no controls are shown // - a , seperates slider controls (max 5) or color controls (max 3). Palette has only one value // - a ! means that the default is used. // - For sliders: Effect speeds, Effect intensity, Custom 1, Custom 2, Custom 3 // - For colors: Fx color, Background color, Custom // - For palette: prompt for color palette OR palette ID if numeric (will hide palette selection) // // Note: If palette is on and no colors are specified 1,2 and 3 is shown in each color circle. // If a color is specified, the 1,2 or 3 is replaced by that specification. // Note: Effects can override default pattern behaviour // - FadeToBlack can override the background setting // - Defining SEGCOL() can override a specific palette using these values (e.g. Color Gradient) const char JSON_mode_names[] PROGMEM = R"=====(["Mode names have moved"])====="; /* R"=====([ "Solid", "Blink@!,;!,!,;!", "Breathe@!,;!,!;!", "Wipe@!,!;!,!,;!", "Wipe Random@!,;1,2,3;!", "Random Colors@!,Fade time;1,2,3;!", "Sweep@!,!;!,!,;!", "Dynamic@!,!;1,2,3;!", "Colorloop@!,Saturation;1,2,3;!", "Rainbow@!,Size;1,2,3;!", "Scan@!,# of dots;!,!,;!", "Scan Dual@!,# of dots;!,!,;!", "Fade@!,;!,!,;!", "Theater@!,Gap size;!,!,;!", "Theater Rainbow@!,Gap size;1,2,3;!", "Running@!,Wave width;!,!,;!", "Saw@!,Width;!,!,;!", "Twinkle@!,;!,!,;!", "Dissolve@Repeat speed,Dissolve speed;!,!,;!", "Dissolve Rnd@Repeat speed,Dissolve speed;,!,;!", "Sparkle@!,;!,!,;!", "Sparkle Dark@!,!;Bg,Fx,;!", "Sparkle+@!,!;Bg,Fx,;!", "Strobe@!,;!,!,;!", "Strobe Rainbow@!,;,!,;!", "Strobe Mega@!,!;!,!,;!", "Blink Rainbow@Frequency,Blink duration;!,!,;!", "Android@!,Width;!,!,;!", "Chase@!,Width;!,!,!;!", "Chase Random@!,Width;!,,!;!", "Chase Rainbow@!,Width;!,!,;0", "Chase Flash@!,;Bg,Fx,!;!", "Chase Flash Rnd@!,;,Fx,;!", "Rainbow Runner@!,Size;Bg,,;!", "Colorful@!,Saturation;1,2,3;!", "Traffic Light@!,;,!,;!", "Sweep Random", "Chase 2@!,Width;!,!,;!", "Aurora@!=24,!;1,2,3;!=50", "Stream", "Scanner", "Lighthouse", "Fireworks@Sharpness=96,Frequency=192;!,2,;!=11", "Rain@Fade rate=128,Frequency=128;!,2,;!", "Tetrix@!=224,Width=0;!,!,;!=11", "Fire Flicker@!,!;!,,;!", "Gradient@!,Spread=16;!,!,;!", "Loading@!,Fade=16;!,!,;!", "Police@!,Width;,Bg,;0", "Fairy", "Two Dots@!,Dot size;1,2,Bg;!", "Fairy Twinkle", "Running Dual", "Halloween", "Chase 3@!,Size;1,2,3;0", "Tri Wipe@!,Width;1,2,3;0", "Tri Fade", "Lightning", "ICU", "Multi Comet", "Scanner Dual", "Stream 2", "Oscillate", "Pride 2015@!,;;", "Juggle@!=16,Trail=240;!,!,;!", "Palette@!,;1,2,3;!", "Fire 2012@Spark rate=120,Decay=64;1,2,3;!", "Colorwaves", "Bpm@!=64,;1,2,3;!", "Fill Noise", "Noise 1", "Noise 2", "Noise 3", "Noise 4", "Colortwinkles@Fade speed,Spawn speed;1,2,3;!", "Lake@!,;1,2,3;!", "Meteor@!,Trail length;!,!,;!", "Meteor Smooth@!,Trail length;!,!,;!", "Railway", "Ripple", "Twinklefox", "Twinklecat", "Halloween Eyes@Duration,Eye fade time;Fx,Bg,;!", "Solid Pattern@Fg size,Bg size;Fg,Bg,;!=0", "Solid Pattern Tri@,Size;1,2,3;!=0", "Spots@Spread,Width;!,!,;!", "Spots Fade@Spread,Width;!,!,;!", "Glitter", "Candle@Flicker rate=96,Flicker intensity=224;!,!,;0", "Fireworks Starburst", "Fireworks 1D@Gravity,Firing side;!,!,;!", "Bouncing Balls@Gravity,# of balls;!,!,;!", "Sinelon", "Sinelon Dual", "Sinelon Rainbow", "Popcorn", "Drip@Gravity,# of drips;!,!;!", "Plasma@Phase,;1,2,3;!", "Percent@,% of fill;!,!,;!", "Ripple Rainbow", "Heartbeat@!,!;!,!,;!", "Pacifica", "Candle Multi@Flicker rate=96,Flicker intensity=224;!,!,;0", "Solid Glitter@,!;!,,;0", "Sunrise@Time [min]=60,;;0", "Phased", "Twinkleup@!,Intensity;!,!,;!", "Noise Pal", "Sine", "Phased Noise", "Flow", "Chunchun@!,Gap size;!,!,;!", "Dancing Shadows@!,# of shadows;!,,;!", "Washing Machine", "Candy Cane@!,Width;;", "Blends@Shift speed,Blend speed;1,2,3,!", "TV Simulator", "Dynamic Smooth", "2D Black Hole@Fade rate,Outer Y freq.,Outer X freq.,Inner X freq.,Inner Y freq.;;", "2D DNA@Scroll speed,Blur;;!", "2D DNA Spiral@Scroll speed,Blur;;!", "2D Drift@Rotation speed,Blur amount;;!", "2D Firenoise@X scale,Y scale;;", "2D Frizzles@X frequency,Y frequency;;!", "2D Hipnotic@X scale,Y scale;;!", "2D Lissajous@X frequency,Fadetime;!,!,!;!", "2D Matrix@Falling speed,Spawning rate,Trail,Custom color;Spawn,Trail;", "2D Akemi@Color speed,Dance;Head palette,Arms & Legs,Eyes & Mouth;Face palette", "2D Colored Bursts@Speed,Number of lines;;!", "2D Game Of Life@!,;!,!;!", "2D Julia@,Max iterations per pixel,X center,Y center,Area size;;!", "2D Metaballs@Speed;!,!,!;!", "2D Noise@Speed,Scale;!,!,!;!", "2D Plasma Ball@Speed;!,!,!;!", "2D Polar Lights@Speed,X scale,Palette;!,!,!;!", "2D Pulser@Speed,Blur;;!", "2D Sindots@Speed,Dot distance;;!", "2D Squared Swirl@,,,,Blur;,,;!", "2D Sun Radiation@Variance,Brightness;;", "2D Tartan@X scale,Y scale;;!", "2D Waverly@Fade rate,Sensitivity;;!", "2D Spaceships@Fade rate,Blur;!,!,!;!", "2D Crazy Bees@Fade rate,Blur;;", "2D Ghost Rider@Fade rate,Blur;!,!,!;!", "2D Blobs@!,# blobs;!,!,!;!" ])====="; */ const char JSON_palette_names[] PROGMEM = R"=====([ "Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", "Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash", "Pastel","Sunset 2","Beech","Vintage","Departure","Landscape","Beach","Sherbet","Hult","Hult 64", "Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn", "Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", "Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf", "Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide", "Candy2" ])=====";