Playlist additions
This commit is contained in:
parent
374457df70
commit
623694ab73
12
CHANGELOG.md
12
CHANGELOG.md
@ -2,6 +2,18 @@
|
|||||||
|
|
||||||
### Builds after release 0.12.0
|
### Builds after release 0.12.0
|
||||||
|
|
||||||
|
#### Build 2106100
|
||||||
|
|
||||||
|
- Added support for multiple buttons with various types (PR #1977)
|
||||||
|
- Fixed infinite playlists (PR #2020)
|
||||||
|
- Added `r` to playlist object, allows for shuffle regardless of the `repeat` value
|
||||||
|
- Improved accuracy of NTP time sync
|
||||||
|
- Added possibility for WLED UDP sync to sync system time
|
||||||
|
- Improved UDP sync accuracy, if both sender and receiver are NTP synced
|
||||||
|
- Fixed a cache issue with restored tabs
|
||||||
|
- Cache CORS request
|
||||||
|
- Disable WiFi sleep by default on ESP32
|
||||||
|
|
||||||
#### Build 2105230
|
#### Build 2105230
|
||||||
|
|
||||||
- No longer retain MQTT `/v` topic to alleviate storage loads on MQTT broker
|
- No longer retain MQTT `/v` topic to alleviate storage loads on MQTT broker
|
||||||
|
@ -192,6 +192,9 @@
|
|||||||
#define SEG_OPTION_FREEZE 5 //Segment contents will not be refreshed
|
#define SEG_OPTION_FREEZE 5 //Segment contents will not be refreshed
|
||||||
#define SEG_OPTION_TRANSITIONAL 7
|
#define SEG_OPTION_TRANSITIONAL 7
|
||||||
|
|
||||||
|
//Playlist option byte
|
||||||
|
#define PL_OPTION_SHUFFLE 0x01
|
||||||
|
|
||||||
// WLED Error modes
|
// WLED Error modes
|
||||||
#define ERR_NONE 0 // All good :)
|
#define ERR_NONE 0 // All good :)
|
||||||
#define ERR_EEP_COMMIT 2 // Could not commit to EEPROM (wrong flash layout?)
|
#define ERR_EEP_COMMIT 2 // Could not commit to EEPROM (wrong flash layout?)
|
||||||
|
@ -97,8 +97,8 @@ void handleIR();
|
|||||||
#include "src/dependencies/json/AsyncJson-v6.h"
|
#include "src/dependencies/json/AsyncJson-v6.h"
|
||||||
#include "FX.h"
|
#include "FX.h"
|
||||||
|
|
||||||
void deserializeSegment(JsonObject elem, byte it);
|
void deserializeSegment(JsonObject elem, byte it, byte presetId = 0);
|
||||||
bool deserializeState(JsonObject root);
|
bool deserializeState(JsonObject root, byte presetId = 0);
|
||||||
void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true);
|
void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true);
|
||||||
void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true);
|
void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true);
|
||||||
void serializeInfo(JsonObject root);
|
void serializeInfo(JsonObject root);
|
||||||
@ -155,7 +155,7 @@ void _drawOverlayCronixie();
|
|||||||
//playlist.cpp
|
//playlist.cpp
|
||||||
void shufflePlaylist();
|
void shufflePlaylist();
|
||||||
void unloadPlaylist();
|
void unloadPlaylist();
|
||||||
void loadPlaylist(JsonObject playlistObject);
|
void loadPlaylist(JsonObject playlistObject, byte presetId = 0);
|
||||||
void handlePlaylist();
|
void handlePlaylist();
|
||||||
|
|
||||||
//presets.cpp
|
//presets.cpp
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* JSON API (De)serialization
|
* JSON API (De)serialization
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void deserializeSegment(JsonObject elem, byte it)
|
void deserializeSegment(JsonObject elem, byte it, byte presetId)
|
||||||
{
|
{
|
||||||
byte id = elem["id"] | it;
|
byte id = elem["id"] | it;
|
||||||
if (id < strip.getMaxSegments())
|
if (id < strip.getMaxSegments())
|
||||||
@ -95,13 +95,18 @@ void deserializeSegment(JsonObject elem, byte it)
|
|||||||
|
|
||||||
//temporary, strip object gets updated via colorUpdated()
|
//temporary, strip object gets updated via colorUpdated()
|
||||||
if (id == strip.getMainSegmentId()) {
|
if (id == strip.getMainSegmentId()) {
|
||||||
|
byte effectPrev = effectCurrent;
|
||||||
effectCurrent = elem[F("fx")] | effectCurrent;
|
effectCurrent = elem[F("fx")] | effectCurrent;
|
||||||
|
if (!presetId && effectCurrent != effectPrev) unloadPlaylist(); //stop playlist if active and FX changed manually
|
||||||
effectSpeed = elem[F("sx")] | effectSpeed;
|
effectSpeed = elem[F("sx")] | effectSpeed;
|
||||||
effectIntensity = elem[F("ix")] | effectIntensity;
|
effectIntensity = elem[F("ix")] | effectIntensity;
|
||||||
effectPalette = elem["pal"] | effectPalette;
|
effectPalette = elem["pal"] | effectPalette;
|
||||||
} else { //permanent
|
} else { //permanent
|
||||||
byte fx = elem[F("fx")] | seg.mode;
|
byte fx = elem[F("fx")] | seg.mode;
|
||||||
if (fx != seg.mode && fx < strip.getModeCount()) strip.setMode(id, fx);
|
if (fx != seg.mode && fx < strip.getModeCount()) {
|
||||||
|
strip.setMode(id, fx);
|
||||||
|
if (!presetId) unloadPlaylist(); //stop playlist if active and FX changed manually
|
||||||
|
}
|
||||||
seg.speed = elem[F("sx")] | seg.speed;
|
seg.speed = elem[F("sx")] | seg.speed;
|
||||||
seg.intensity = elem[F("ix")] | seg.intensity;
|
seg.intensity = elem[F("ix")] | seg.intensity;
|
||||||
seg.palette = elem["pal"] | seg.palette;
|
seg.palette = elem["pal"] | seg.palette;
|
||||||
@ -156,7 +161,7 @@ void deserializeSegment(JsonObject elem, byte it)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool deserializeState(JsonObject root)
|
bool deserializeState(JsonObject root, byte presetId)
|
||||||
{
|
{
|
||||||
strip.applyToAllSelected = false;
|
strip.applyToAllSelected = false;
|
||||||
bool stateResponse = root[F("v")] | false;
|
bool stateResponse = root[F("v")] | false;
|
||||||
@ -168,13 +173,16 @@ bool deserializeState(JsonObject root)
|
|||||||
|
|
||||||
if (root["on"].is<const char*>() && root["on"].as<const char*>()[0] == 't') toggleOnOff();
|
if (root["on"].is<const char*>() && root["on"].as<const char*>()[0] == 't') toggleOnOff();
|
||||||
|
|
||||||
int tr = root[F("transition")] | -1;
|
int tr = -1;
|
||||||
|
if (!presetId || currentPlaylist < 0) { //do not apply transition time from preset if playlist active, as it would override playlist transition times
|
||||||
|
tr = root[F("transition")] | -1;
|
||||||
if (tr >= 0)
|
if (tr >= 0)
|
||||||
{
|
{
|
||||||
transitionDelay = tr;
|
transitionDelay = tr;
|
||||||
transitionDelay *= 100;
|
transitionDelay *= 100;
|
||||||
transitionDelayTemp = transitionDelay;
|
transitionDelayTemp = transitionDelay;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tr = root[F("tt")] | -1;
|
tr = root[F("tt")] | -1;
|
||||||
if (tr >= 0)
|
if (tr >= 0)
|
||||||
@ -245,20 +253,20 @@ bool deserializeState(JsonObject root)
|
|||||||
{
|
{
|
||||||
if (lowestActive == 99) lowestActive = s;
|
if (lowestActive == 99) lowestActive = s;
|
||||||
if (sg.isSelected()) {
|
if (sg.isSelected()) {
|
||||||
deserializeSegment(segVar, s);
|
deserializeSegment(segVar, s, presetId);
|
||||||
didSet = true;
|
didSet = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!didSet && lowestActive < strip.getMaxSegments()) deserializeSegment(segVar, lowestActive);
|
if (!didSet && lowestActive < strip.getMaxSegments()) deserializeSegment(segVar, lowestActive, presetId);
|
||||||
} else { //set only the segment with the specified ID
|
} else { //set only the segment with the specified ID
|
||||||
deserializeSegment(segVar, it);
|
deserializeSegment(segVar, it, presetId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
JsonArray segs = segVar.as<JsonArray>();
|
JsonArray segs = segVar.as<JsonArray>();
|
||||||
for (JsonObject elem : segs)
|
for (JsonObject elem : segs)
|
||||||
{
|
{
|
||||||
deserializeSegment(elem, it);
|
deserializeSegment(elem, it, presetId);
|
||||||
it++;
|
it++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,7 +288,11 @@ bool deserializeState(JsonObject root)
|
|||||||
deletePreset(ps);
|
deletePreset(ps);
|
||||||
}
|
}
|
||||||
ps = root["ps"] | -1; //load preset (clears state request!)
|
ps = root["ps"] | -1; //load preset (clears state request!)
|
||||||
if (ps >= 0) {applyPreset(ps); return stateResponse;}
|
if (ps >= 0) {
|
||||||
|
if (!presetId) unloadPlaylist(); //stop playlist if preset changed manually
|
||||||
|
applyPreset(ps);
|
||||||
|
return stateResponse;
|
||||||
|
}
|
||||||
|
|
||||||
//HTTP API commands
|
//HTTP API commands
|
||||||
const char* httpwin = root["win"];
|
const char* httpwin = root["win"];
|
||||||
@ -293,7 +305,7 @@ bool deserializeState(JsonObject root)
|
|||||||
|
|
||||||
JsonObject playlist = root[F("playlist")];
|
JsonObject playlist = root[F("playlist")];
|
||||||
if (!playlist.isNull()) {
|
if (!playlist.isNull()) {
|
||||||
loadPlaylist(playlist);
|
loadPlaylist(playlist, presetId);
|
||||||
noNotification = true; //do not notify both for this request and the first playlist entry
|
noNotification = true; //do not notify both for this request and the first playlist entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,18 +5,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
typedef struct PlaylistEntry {
|
typedef struct PlaylistEntry {
|
||||||
uint8_t preset;
|
uint8_t preset; //ID of the preset to apply
|
||||||
uint16_t dur;
|
uint16_t dur; //Duration of the entry (in tenths of seconds)
|
||||||
uint16_t tr;
|
uint16_t tr; //Duration of the transition TO this entry (in tenths of seconds)
|
||||||
} ple;
|
} ple;
|
||||||
|
|
||||||
bool playlistEndless = false;
|
byte playlistRepeat = 1; //how many times to repeat the playlist (0 = infinitely)
|
||||||
int8_t playlistRepeat = 1;
|
byte playlistEndPreset = 0; //what preset to apply after playlist end (0 = stay on last preset)
|
||||||
byte playlistEndPreset = 0;
|
byte playlistOptions = 0; //bit 0: shuffle playlist after each iteration. bits 1-7 TBD
|
||||||
|
|
||||||
PlaylistEntry *playlistEntries = nullptr;
|
PlaylistEntry *playlistEntries = nullptr;
|
||||||
byte playlistLen;
|
byte playlistLen; //number of playlist entries
|
||||||
int8_t playlistIndex = -1;
|
int8_t playlistIndex = -1;
|
||||||
uint16_t playlistEntryDur = 0;
|
uint16_t playlistEntryDur = 0; //duration of the current entry in tenths of seconds
|
||||||
|
|
||||||
|
//values we need to keep about the parent playlist while inside sub-playlist
|
||||||
|
//int8_t parentPlaylistIndex = -1;
|
||||||
|
//byte parentPlaylistRepeat = 0;
|
||||||
|
//byte parentPlaylistPresetId = 0; //for re-loading
|
||||||
|
|
||||||
|
|
||||||
void shufflePlaylist() {
|
void shufflePlaylist() {
|
||||||
@ -42,12 +48,12 @@ void unloadPlaylist() {
|
|||||||
playlistEntries = nullptr;
|
playlistEntries = nullptr;
|
||||||
}
|
}
|
||||||
currentPlaylist = playlistIndex = -1;
|
currentPlaylist = playlistIndex = -1;
|
||||||
playlistLen = playlistEntryDur = 0;
|
playlistLen = playlistEntryDur = playlistOptions = 0;
|
||||||
DEBUG_PRINTLN(F("Playlist unloaded."));
|
DEBUG_PRINTLN(F("Playlist unloaded."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void loadPlaylist(JsonObject playlistObj) {
|
void loadPlaylist(JsonObject playlistObj, byte presetId) {
|
||||||
unloadPlaylist();
|
unloadPlaylist();
|
||||||
|
|
||||||
JsonArray presets = playlistObj["ps"];
|
JsonArray presets = playlistObj["ps"];
|
||||||
@ -68,12 +74,12 @@ void loadPlaylist(JsonObject playlistObj) {
|
|||||||
it = 0;
|
it = 0;
|
||||||
JsonArray durations = playlistObj["dur"];
|
JsonArray durations = playlistObj["dur"];
|
||||||
if (durations.isNull()) {
|
if (durations.isNull()) {
|
||||||
playlistEntries[0].dur = playlistObj["dur"] | 100;
|
playlistEntries[0].dur = playlistObj["dur"] | 100; //10 seconds as fallback
|
||||||
it = 1;
|
it = 1;
|
||||||
} else {
|
} else {
|
||||||
for (int dur : durations) {
|
for (int dur : durations) {
|
||||||
if (it >= playlistLen) break;
|
if (it >= playlistLen) break;
|
||||||
playlistEntries[it].dur = (dur > 0) ? dur : presetCycleTime;
|
playlistEntries[it].dur = (dur > 1) ? dur : 100;
|
||||||
it++;
|
it++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,12 +99,19 @@ void loadPlaylist(JsonObject playlistObj) {
|
|||||||
}
|
}
|
||||||
for (int i = it; i < playlistLen; i++) playlistEntries[i].tr = playlistEntries[it -1].tr;
|
for (int i = it; i < playlistLen; i++) playlistEntries[i].tr = playlistEntries[it -1].tr;
|
||||||
|
|
||||||
playlistRepeat = playlistObj[F("repeat")] | 0;
|
int rep = playlistObj[F("repeat")];
|
||||||
|
bool shuffle = false;
|
||||||
|
if (rep < 0) { //support negative values as infinite + shuffle
|
||||||
|
rep = 0; shuffle = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
playlistRepeat = rep;
|
||||||
|
if (playlistRepeat > 0) playlistRepeat++; //add one extra repetition immediately since it will be deducted on first start
|
||||||
playlistEndPreset = playlistObj[F("end")] | 0;
|
playlistEndPreset = playlistObj[F("end")] | 0;
|
||||||
|
shuffle = shuffle || playlistObj["r"];
|
||||||
|
if (shuffle) playlistOptions += PL_OPTION_SHUFFLE;
|
||||||
|
|
||||||
if (playlistRepeat <= 0) playlistRepeat--; // make it endless (-2 == endless & random)
|
currentPlaylist = presetId;
|
||||||
|
|
||||||
currentPlaylist = 0; //TODO here we need the preset ID where the playlist is saved
|
|
||||||
DEBUG_PRINTLN(F("Playlist loaded."));
|
DEBUG_PRINTLN(F("Playlist loaded."));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,16 +125,16 @@ void handlePlaylist() {
|
|||||||
|
|
||||||
++playlistIndex %= playlistLen; // -1 at 1st run (limit to playlistLen)
|
++playlistIndex %= playlistLen; // -1 at 1st run (limit to playlistLen)
|
||||||
|
|
||||||
if (!playlistRepeat && !playlistIndex) { //stop if repeat == 0 and restart of playlist
|
// playlist roll-over
|
||||||
|
if (!playlistIndex) {
|
||||||
|
if (playlistRepeat == 1) { //stop if all repetitions are done
|
||||||
unloadPlaylist();
|
unloadPlaylist();
|
||||||
if (playlistEndPreset) applyPreset(playlistEndPreset);
|
if (playlistEndPreset) applyPreset(playlistEndPreset);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// playlist roll-over
|
if (playlistRepeat > 1) playlistRepeat--; // decrease repeat count on each index reset if not an endless playlist
|
||||||
if (!playlistIndex) {
|
// playlistRepeat == 0: endless loop
|
||||||
// playlistRepeat < 0 => endless loop
|
if (playlistOptions & PL_OPTION_SHUFFLE) shufflePlaylist(); // shuffle playlist and start over
|
||||||
if (playlistRepeat > 0) playlistRepeat--; // decrease repeat count on each index reset if not an endless playlist
|
|
||||||
if (playlistRepeat < -1) shufflePlaylist(); // shuffle playlist and start over
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonTransitionOnce = true;
|
jsonTransitionOnce = true;
|
||||||
|
@ -14,7 +14,7 @@ bool applyPreset(byte index)
|
|||||||
#ifdef WLED_DEBUG_FS
|
#ifdef WLED_DEBUG_FS
|
||||||
serializeJson(*fileDoc, Serial);
|
serializeJson(*fileDoc, Serial);
|
||||||
#endif
|
#endif
|
||||||
deserializeState(fdo);
|
deserializeState(fdo, index);
|
||||||
} else {
|
} else {
|
||||||
DEBUGFS_PRINTLN(F("Make read buf"));
|
DEBUGFS_PRINTLN(F("Make read buf"));
|
||||||
DynamicJsonDocument fDoc(JSON_BUFFER_SIZE);
|
DynamicJsonDocument fDoc(JSON_BUFFER_SIZE);
|
||||||
@ -24,7 +24,7 @@ bool applyPreset(byte index)
|
|||||||
#ifdef WLED_DEBUG_FS
|
#ifdef WLED_DEBUG_FS
|
||||||
serializeJson(fDoc, Serial);
|
serializeJson(fDoc, Serial);
|
||||||
#endif
|
#endif
|
||||||
deserializeState(fdo);
|
deserializeState(fdo, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!errorFlag) {
|
if (!errorFlag) {
|
||||||
@ -35,6 +35,7 @@ bool applyPreset(byte index)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//persist=false is not currently honored
|
||||||
void savePreset(byte index, bool persist, const char* pname, JsonObject saveobj)
|
void savePreset(byte index, bool persist, const char* pname, JsonObject saveobj)
|
||||||
{
|
{
|
||||||
if (index == 0 || index > 250) return;
|
if (index == 0 || index > 250) return;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// version code in format yymmddb (b = daily build)
|
// version code in format yymmddb (b = daily build)
|
||||||
#define VERSION 2106070
|
#define VERSION 2106100
|
||||||
|
|
||||||
//uncomment this if you have a "my_config.h" file you'd like to use
|
//uncomment this if you have a "my_config.h" file you'd like to use
|
||||||
//#define WLED_USE_MY_CONFIG
|
//#define WLED_USE_MY_CONFIG
|
||||||
|
Loading…
Reference in New Issue
Block a user