Merge pull request #3611 from Aircoookie/suspend

Suspend strip during operations
This commit is contained in:
Blaž Kristan 2024-01-09 18:33:44 +01:00 committed by GitHub
commit 1ed7e6cb1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 99 additions and 79 deletions

View File

@ -535,7 +535,7 @@ typedef struct Segment {
#endif
static void handleRandomPalette();
void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1, uint8_t segId = 255);
void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1);
bool setColor(uint8_t slot, uint32_t c); //returns true if changed
void setCCT(uint16_t k);
void setOpacity(uint8_t o);
@ -559,17 +559,17 @@ typedef struct Segment {
inline void markForReset(void) { reset = true; } // setOption(SEG_OPTION_RESET, true)
// transition functions
void startTransition(uint16_t dur); // transition has to start before actual segment values change
void stopTransition(void);
void startTransition(uint16_t dur); // transition has to start before actual segment values change
void stopTransition(void); // ends transition mode by destroying transition structure
void handleTransition(void);
#ifndef WLED_DISABLE_MODE_BLEND
void swapSegenv(tmpsegd_t &tmpSegD); // copies segment data into specifed buffer, if buffer is not a transition buffer, segment data is overwritten from transition buffer
void restoreSegenv(tmpsegd_t &tmpSegD); // restores segment data from buffer, if buffer is not transition buffer, changed values are copied to transition buffer
#endif
uint16_t progress(void); // transition progression between 0-65535
uint8_t currentBri(bool useCct = false);
uint8_t currentMode(void);
uint32_t currentColor(uint8_t slot);
uint16_t progress(void); // transition progression between 0-65535
uint8_t currentBri(bool useCct = false); // current segment brightness/CCT (blended while in transition)
uint8_t currentMode(void); // currently active effect/mode (while in transition)
uint32_t currentColor(uint8_t slot); // currently active segment color (blended while in transition)
CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal);
CRGBPalette16 &currentPalette(CRGBPalette16 &tgt, uint8_t paletteID);
@ -598,9 +598,9 @@ typedef struct Segment {
uint32_t color_wheel(uint8_t pos);
// 2D matrix
uint16_t virtualWidth(void) const;
uint16_t virtualHeight(void) const;
uint16_t nrOfVStrips(void) const;
uint16_t virtualWidth(void) const; // segment width in virtual pixels (accounts for groupping and spacing)
uint16_t virtualHeight(void) const; // segment height in virtual pixels (accounts for groupping and spacing)
uint16_t nrOfVStrips(void) const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D)
#ifndef WLED_DISABLE_2D
uint16_t XY(uint16_t x, uint16_t y); // support function to get relative index within segment
void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color
@ -698,6 +698,7 @@ class WS2812FX { // 96 bytes
_colors_t{0,0,0},
_virtualSegmentLength(0),
// true private variables
_suspend(false),
_length(DEFAULT_LED_COUNT),
_brightness(DEFAULT_BRIGHTNESS),
_transitionDur(750),
@ -746,38 +747,39 @@ class WS2812FX { // 96 bytes
void
#ifdef WLED_DEBUG
printSize(),
printSize(), // prints memory usage for strip components
#endif
finalizeInit(),
service(void),
setMode(uint8_t segid, uint8_t m),
setColor(uint8_t slot, uint32_t c),
setCCT(uint16_t k),
setBrightness(uint8_t b, bool direct = false),
setRange(uint16_t i, uint16_t i2, uint32_t col),
purgeSegments(bool force = false),
finalizeInit(), // initialises strip components
service(void), // executes effect functions when due and calls strip.show()
setMode(uint8_t segid, uint8_t m), // sets effect/mode for given segment (high level API)
setColor(uint8_t slot, uint32_t c), // sets color (in slot) for given segment (high level API)
setCCT(uint16_t k), // sets global CCT (either in relative 0-255 value or in K)
setBrightness(uint8_t b, bool direct = false), // sets strip brightness
setRange(uint16_t i, uint16_t i2, uint32_t col), // used for clock overlay
purgeSegments(void), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint)
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1),
setMainSegmentId(uint8_t n),
resetSegments(),
makeAutoSegments(bool forceReset = false),
fixInvalidSegments(),
setPixelColor(unsigned n, uint32_t c),
show(void),
resetSegments(), // marks all segments for reset
makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs
fixInvalidSegments(), // fixes incorrect segment configuration
setPixelColor(unsigned n, uint32_t c), // paints absolute strip pixel with index n and color c
show(void), // initiates LED output
setTargetFps(uint8_t fps),
addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name), // add effect to the list; defined in FX.cpp
setupEffectData(void); // add default effects to the list; defined in FX.cpp
setupEffectData(void); // add default effects to the list; defined in FX.cpp
inline void restartRuntime() { for (Segment &seg : _segments) seg.markForReset(); }
inline void restartRuntime() { for (Segment &seg : _segments) seg.markForReset(); }
inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); }
inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); }
inline void fill(uint32_t c) { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline)
// outsmart the compiler :) by correctly overloading
inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); }
inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); }
inline void setPixelColor(unsigned n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); }
inline void trigger(void) { _triggered = true; } // Forces the next frame to be computed on all active segments.
inline void setShowCallback(show_callback cb) { _callback = cb; }
inline void setTransition(uint16_t t) { _transitionDur = t; }
inline void setPixelColor(unsigned n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); }
inline void fill(uint32_t c) { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline)
inline void trigger(void) { _triggered = true; } // Forces the next frame to be computed on all active segments.
inline void setShowCallback(show_callback cb) { _callback = cb; }
inline void setTransition(uint16_t t) { _transitionDur = t; } // sets transition time (in ms)
inline void appendSegment(const Segment &seg = Segment()) { if (_segments.size() < getMaxSegments()) _segments.push_back(seg); }
inline void suspend(void) { _suspend = true; } // will suspend (and canacel) strip.service() execution
inline void resume(void) { _suspend = false; } // will resume strip.service() execution
bool
paletteFade,
@ -788,9 +790,11 @@ class WS2812FX { // 96 bytes
isUpdating(void),
deserializeMap(uint8_t n=0);
inline bool isServicing(void) { return _isServicing; }
inline bool hasWhiteChannel(void) {return _hasWhiteChannel;}
inline bool isOffRefreshRequired(void) {return _isOffRefreshRequired;}
inline bool isServicing(void) { return _isServicing; } // returns true if strip.service() is executing
inline bool hasWhiteChannel(void) { return _hasWhiteChannel; } // returns true if strip contains separate white chanel
inline bool isOffRefreshRequired(void) { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset)
inline bool isSuspended(void) { return _suspend; } // returns true if strip.service() execution is suspended
inline bool needsUpdate(void) { return _triggered; } // returns true if strip received a trigger() request
uint8_t
paletteBlend,
@ -801,32 +805,32 @@ class WS2812FX { // 96 bytes
getActiveSegsLightCapabilities(bool selectedOnly = false),
setPixelSegment(uint8_t n);
inline uint8_t getBrightness(void) { return _brightness; }
inline uint8_t getMaxSegments(void) { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value)
inline uint8_t getSegmentsNum(void) { return _segments.size(); } // returns currently present segments
inline uint8_t getCurrSegmentId(void) { return _segment_index; }
inline uint8_t getMainSegmentId(void) { return _mainSegment; }
inline uint8_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT; } // will only return built-in palette count
inline uint8_t getTargetFps() { return _targetFps; }
inline uint8_t getModeCount() { return _modeCount; }
inline uint8_t getBrightness(void) { return _brightness; } // returns current strip brightness
inline uint8_t getMaxSegments(void) { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value)
inline uint8_t getSegmentsNum(void) { return _segments.size(); } // returns currently present segments
inline uint8_t getCurrSegmentId(void) { return _segment_index; } // returns current segment index (only valid while strip.isServicing())
inline uint8_t getMainSegmentId(void) { return _mainSegment; } // returns main segment index
inline uint8_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT; } // will only return built-in palette count
inline uint8_t getTargetFps() { return _targetFps; } // returns rough FPS value for las 2s interval
inline uint8_t getModeCount() { return _modeCount; } // returns number of registered modes/effects
uint16_t
getLengthPhysical(void),
getLengthTotal(void), // will include virtual/nonexistent pixels in matrix
getFps();
inline uint16_t getFrameTime(void) { return _frametime; }
inline uint16_t getMinShowDelay(void) { return MIN_SHOW_DELAY; }
inline uint16_t getLength(void) { return _length; } // 2D matrix may have less pixels than W*H
inline uint16_t getTransition(void) { return _transitionDur; }
inline uint16_t getFrameTime(void) { return _frametime; } // returns amount of time a frame should take (in ms)
inline uint16_t getMinShowDelay(void) { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant)
inline uint16_t getLength(void) { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H)
inline uint16_t getTransition(void) { return _transitionDur; } // returns currently set transition time (in ms)
uint32_t
now,
timebase,
getPixelColor(uint16_t);
inline uint32_t getLastShow(void) { return _lastShow; }
inline uint32_t segColor(uint8_t i) { return _colors_t[i]; }
inline uint32_t getLastShow(void) { return _lastShow; } // returns millis() timestamp of last strip.show() call
inline uint32_t segColor(uint8_t i) { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition
const char *
getModeData(uint8_t id = 0) { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); }
@ -835,9 +839,9 @@ class WS2812FX { // 96 bytes
getModeDataSrc(void) { return &(_modeData[0]); } // vectors use arrays for underlying data
Segment& getSegment(uint8_t id);
inline Segment& getFirstSelectedSeg(void) { return _segments[getFirstSelectedSegId()]; }
inline Segment& getMainSegment(void) { return _segments[getMainSegmentId()]; }
inline Segment* getSegments(void) { return &(_segments[0]); }
inline Segment& getFirstSelectedSeg(void) { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected"
inline Segment& getMainSegment(void) { return _segments[getMainSegmentId()]; } // returns reference to main segment
inline Segment* getSegments(void) { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully)
// 2D support (panels)
bool
@ -873,7 +877,7 @@ class WS2812FX { // 96 bytes
std::vector<Panel> panel;
#endif
void setUpMatrix();
void setUpMatrix(); // sets up automatic matrix ledmap from panel configuration
// outsmart the compiler :) by correctly overloading
inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor((unsigned)(y * Segment::maxWidth + x), c); }
@ -897,6 +901,8 @@ class WS2812FX { // 96 bytes
friend class Segment;
private:
volatile bool _suspend;
uint16_t _length;
uint8_t _brightness;
uint16_t _transitionDur;
@ -930,9 +936,10 @@ class WS2812FX { // 96 bytes
uint16_t _qStart, _qStop, _qStartY, _qStopY;
uint8_t _qGrouping, _qSpacing;
uint16_t _qOffset;
/*
void
setUpSegmentFromQueuedChanges(void);
*/
};
extern const char JSON_mode_names[];

View File

@ -469,7 +469,7 @@ void Segment::handleRandomPalette() {
}
// segId is given when called from network callback, changes are queued if that segment is currently in its effect function
void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y, uint8_t segId) {
void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) {
// return if neither bounds nor grouping have changed
bool boundsUnchanged = (start == i1 && stop == i2);
#ifndef WLED_DISABLE_2D
@ -1136,13 +1136,15 @@ void WS2812FX::finalizeInit(void) {
void WS2812FX::service() {
unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days
now = nowUp + timebase;
if (nowUp - _lastShow < MIN_SHOW_DELAY) return;
if (nowUp - _lastShow < MIN_SHOW_DELAY || _suspend) return;
bool doShow = false;
_isServicing = true;
_segment_index = 0;
Segment::handleRandomPalette(); // move it into for loop when each segment has individual random palette
for (segment &seg : _segments) {
if (_suspend) return; // immediately stop processing segments if suspend requested during service()
// process transition (mode changes in the middle of transition)
seg.handleTransition();
// reset the segment runtime data if needed
@ -1189,7 +1191,7 @@ void WS2812FX::service() {
seg.next_time = nowUp + delay;
}
if (_segment_index == _queuedChangesSegId) setUpSegmentFromQueuedChanges();
// if (_segment_index == _queuedChangesSegId) setUpSegmentFromQueuedChanges();
_segment_index++;
}
_virtualSegmentLength = 0;
@ -1393,18 +1395,18 @@ bool WS2812FX::hasCCTBus(void) {
return false;
}
void WS2812FX::purgeSegments(bool force) {
void WS2812FX::purgeSegments() {
// remove all inactive segments (from the back)
int deleted = 0;
if (_segments.size() <= 1) return;
for (size_t i = _segments.size()-1; i > 0; i--)
if (_segments[i].stop == 0 || force) {
if (_segments[i].stop == 0) {
deleted++;
_segments.erase(_segments.begin() + i);
}
if (deleted) {
_segments.shrink_to_fit();
/*if (_mainSegment >= _segments.size())*/ setMainSegmentId(0);
setMainSegmentId(0);
}
}
@ -1419,7 +1421,7 @@ void WS2812FX::setSegment(uint8_t segId, uint16_t i1, uint16_t i2, uint8_t group
appendSegment(Segment(0, strip.getLengthTotal()));
segId = getSegmentsNum()-1; // segments are added at the end of list
}
/*
if (_queuedChangesSegId == segId) _queuedChangesSegId = 255; // cancel queued change if already queued for this segment
if (segId < getMaxSegments() && segId == getCurrSegmentId() && isServicing()) { // queue change to prevent concurrent access
@ -1431,17 +1433,19 @@ void WS2812FX::setSegment(uint8_t segId, uint16_t i1, uint16_t i2, uint8_t group
DEBUG_PRINT(F("Segment queued: ")); DEBUG_PRINTLN(segId);
return; // queued changes are applied immediately after effect function returns
}
*/
suspend();
_segments[segId].setUp(i1, i2, grouping, spacing, offset, startY, stopY);
resume();
if (segId > 0 && segId == getSegmentsNum()-1 && i2 <= i1) _segments.pop_back(); // if last segment was deleted remove it from vector
}
/*
void WS2812FX::setUpSegmentFromQueuedChanges() {
if (_queuedChangesSegId >= getSegmentsNum()) return;
getSegment(_queuedChangesSegId).setUp(_qStart, _qStop, _qGrouping, _qSpacing, _qOffset, _qStartY, _qStopY);
_segments[_queuedChangesSegId].setUp(_qStart, _qStop, _qGrouping, _qSpacing, _qOffset, _qStartY, _qStopY);
_queuedChangesSegId = 255;
}
*/
void WS2812FX::resetSegments() {
_segments.clear(); // destructs all Segment as part of clearing
#ifndef WLED_DISABLE_2D

View File

@ -117,9 +117,8 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (stop > start && of > len -1) of = len -1;
// update segment (delete if necessary)
// do not call seg.setUp() here, as it may cause a crash due to concurrent access if the segment is currently drawing effects
// WS2812FX handles queueing of the change
strip.setSegment(id, start, stop, grp, spc, of, startY, stopY);
seg.setUp(start, stop, grp, spc, of, startY, stopY); // strip needs to be suspended for this to work without issues
if (newSeg) seg.refreshLightCapabilities(); // fix for #3403
if (seg.reset && seg.stop == 0) {
@ -388,6 +387,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
int it = 0;
JsonVariant segVar = root["seg"];
if (!segVar.isNull()) strip.suspend();
if (segVar.is<JsonObject>())
{
int id = segVar["id"] | -1;
@ -415,6 +415,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
}
if (strip.getSegmentsNum() > 3 && deleted >= strip.getSegmentsNum()/2U) strip.purgeSegments(); // batch deleting more than half segments
}
strip.resume();
usermods.readFromJsonState(root);

View File

@ -24,6 +24,8 @@ static void doSaveState() {
bool persist = (presetToSave < 251);
const char *filename = getFileName(persist);
unsigned long start = millis();
while (strip.isUpdating() && millis()-start < (2*FRAMETIME_FIXED)+1) yield(); // wait 2 frames
if (!requestJSONBufferLock(10)) return; // will set fileDoc
initPresetsFile(); // just in case if someone deleted presets.json using /edit
@ -132,7 +134,9 @@ void applyPresetWithFallback(uint8_t index, uint8_t callMode, uint8_t effectID,
void handlePresets()
{
if (presetToSave) {
strip.suspend();
doSaveState();
strip.resume();
return;
}

View File

@ -286,10 +286,10 @@ void parseNotifyPacket(uint8_t *udpIn) {
uint16_t stopY = version > 11 ? (udpIn[34+ofs] << 8 | udpIn[35+ofs]) : 1;
uint16_t offset = (udpIn[7+ofs] << 8 | udpIn[8+ofs]);
if (!receiveSegmentOptions) {
//selseg.setUp(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY);
// we have to use strip.setSegment() instead of selseg.setUp() to prevent crash if segment would change during drawing
DEBUG_PRINTF("Set segment w/o options: %d [%d,%d;%d,%d]\n", id, (int)start, (int)stop, (int)startY, (int)stopY);
strip.setSegment(id, start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY);
strip.resume();
continue; // we do receive bounds, but not options
}
selseg.options = (selseg.options & 0x0071U) | (udpIn[9 +ofs] & 0x0E); // ignore selected, freeze, reset & transitional
@ -326,12 +326,14 @@ void parseNotifyPacket(uint8_t *udpIn) {
}
if (receiveSegmentBounds) {
DEBUG_PRINTF("Set segment w/ options: %d [%d,%d;%d,%d]\n", id, (int)start, (int)stop, (int)startY, (int)stopY);
// we have to use strip.setSegment() instead of selseg.setUp() to prevent crash if segment would change during drawing
strip.setSegment(id, start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY);
strip.resume();
} else {
// we have to use strip.setSegment() instead of selseg.setUp() to prevent crash if segment would change during drawing
DEBUG_PRINTF("Set segment grouping: %d [%d,%d]\n", id, (int)udpIn[5+ofs], (int)udpIn[6+ofs]);
strip.setSegment(id, selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY);
strip.resume();
}
}
stateChanged = true;

View File

@ -108,7 +108,7 @@ void WLED::loop()
handlePresets();
yield();
if (!offMode || strip.isOffRefreshRequired())
if (!offMode || strip.isOffRefreshRequired() || strip.needsUpdate())
strip.service();
#ifdef ESP8266
else if (!noWifiSleep)
@ -857,7 +857,7 @@ void WLED::handleConnection()
DEBUG_PRINT(F("Heap too low! "));
DEBUG_PRINTLN(heap);
forceReconnect = true;
strip.purgeSegments(true); // remove all but one segments from memory
strip.resetSegments();
} else if (heap < MIN_HEAP_SIZE) {
strip.purgeSegments();
}

View File

@ -273,8 +273,9 @@ void initServer()
#endif
usermods.onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
lastEditTime = millis(); // make sure PIN does not lock during update
strip.suspend();
#ifdef ESP8266
strip.purgeSegments(true); // free as much memory as you can
strip.resetSegments(); // free as much memory as you can
Update.runAsync(true);
#endif
Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
@ -285,6 +286,7 @@ void initServer()
DEBUG_PRINTLN(F("Update Success"));
} else {
DEBUG_PRINTLN(F("Update Failed"));
strip.resume();
usermods.onUpdateBegin(false); // notify usermods that update has failed (some may require task init)
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().enableWatchdog();