2020-11-06 22:12:48 +01:00
# include "wled.h"
/*
* Methods to handle saving and loading presets to / from the filesystem
*/
2022-01-01 12:52:50 +01:00
# ifdef ARDUINO_ARCH_ESP32
static char * tmpRAMbuffer = nullptr ;
# endif
static volatile byte presetToApply = 0 ;
static volatile byte callModeToApply = 0 ;
2022-10-08 18:25:51 +02:00
static volatile byte presetToSave = 0 ;
2022-11-16 20:55:21 +01:00
static volatile int8_t saveLedmap = - 1 ;
2022-11-26 03:57:17 +01:00
static char quickLoad [ 9 ] ;
2022-10-08 18:25:51 +02:00
static char saveName [ 33 ] ;
static bool includeBri = true , segBounds = true , selectedOnly = false , playlistSave = false ; ;
2022-12-13 14:27:44 +01:00
static const char * getFileName ( bool persist = true ) {
2022-10-08 18:25:51 +02:00
return persist ? " /presets.json " : " /tmp.json " ;
}
static void doSaveState ( ) {
bool persist = ( presetToSave < 251 ) ;
2022-12-13 14:27:44 +01:00
const char * filename = getFileName ( persist ) ;
2022-10-08 18:25:51 +02:00
2022-12-13 14:27:44 +01:00
if ( ! requestJSONBufferLock ( 10 ) ) return ; // will set fileDoc
2022-10-08 18:25:51 +02:00
2022-11-16 20:55:21 +01:00
initPresetsFile ( ) ; // just in case if someone deleted presets.json using /edit
2022-10-08 18:25:51 +02:00
JsonObject sObj = doc . to < JsonObject > ( ) ;
DEBUG_PRINTLN ( F ( " Serialize current state " ) ) ;
if ( playlistSave ) {
serializePlaylist ( sObj ) ;
if ( includeBri ) sObj [ " on " ] = true ;
} else {
serializeState ( sObj , true , includeBri , segBounds , selectedOnly ) ;
}
sObj [ " n " ] = saveName ;
if ( quickLoad [ 0 ] ) sObj [ F ( " ql " ) ] = quickLoad ;
2022-11-16 20:55:21 +01:00
if ( saveLedmap > = 0 ) sObj [ F ( " ledmap " ) ] = saveLedmap ;
2022-10-09 12:09:46 +02:00
/*
2022-10-08 18:25:51 +02:00
# ifdef WLED_DEBUG
DEBUG_PRINTLN ( F ( " Serialized preset " ) ) ;
serializeJson ( doc , Serial ) ;
DEBUG_PRINTLN ( ) ;
# endif
2022-10-09 12:09:46 +02:00
*/
2022-10-08 18:25:51 +02:00
# if defined(ARDUINO_ARCH_ESP32)
if ( ! persist ) {
if ( tmpRAMbuffer ! = nullptr ) free ( tmpRAMbuffer ) ;
size_t len = measureJson ( * fileDoc ) + 1 ;
DEBUG_PRINTLN ( len ) ;
// if possible use SPI RAM on ESP32
2023-05-28 22:50:19 +02:00
# if defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM)
2022-10-08 18:25:51 +02:00
if ( psramFound ( ) )
tmpRAMbuffer = ( char * ) ps_malloc ( len ) ;
else
# endif
tmpRAMbuffer = ( char * ) malloc ( len ) ;
if ( tmpRAMbuffer ! = nullptr ) {
serializeJson ( * fileDoc , tmpRAMbuffer , len ) ;
} else {
writeObjectToFileUsingId ( filename , presetToSave , fileDoc ) ;
}
} else
# endif
writeObjectToFileUsingId ( filename , presetToSave , fileDoc ) ;
if ( persist ) presetsModifiedTime = toki . second ( ) ; //unix time
releaseJSONBufferLock ( ) ;
updateFSInfo ( ) ;
// clean up
2022-11-16 20:55:21 +01:00
saveLedmap = - 1 ;
2022-10-08 18:25:51 +02:00
presetToSave = 0 ;
saveName [ 0 ] = ' \0 ' ;
quickLoad [ 0 ] = ' \0 ' ;
playlistSave = false ;
}
2022-01-01 12:52:50 +01:00
2023-01-06 09:24:29 +01:00
bool getPresetName ( byte index , String & name )
2022-10-25 23:42:26 +02:00
{
if ( ! requestJSONBufferLock ( 9 ) ) return false ;
bool presetExists = false ;
2022-12-13 14:27:44 +01:00
if ( readObjectFromFileUsingId ( getFileName ( ) , index , & doc ) )
2023-01-06 09:24:29 +01:00
{
2022-10-25 23:42:26 +02:00
JsonObject fdo = doc . as < JsonObject > ( ) ;
if ( fdo [ " n " ] ) {
name = ( const char * ) ( fdo [ " n " ] ) ;
presetExists = true ;
}
}
releaseJSONBufferLock ( ) ;
return presetExists ;
}
2022-11-16 20:55:21 +01:00
void initPresetsFile ( )
{
2022-12-13 14:27:44 +01:00
if ( WLED_FS . exists ( getFileName ( ) ) ) return ;
2022-11-16 20:55:21 +01:00
StaticJsonDocument < 64 > doc ;
JsonObject sObj = doc . to < JsonObject > ( ) ;
sObj . createNestedObject ( " 0 " ) ;
2022-12-13 14:27:44 +01:00
File f = WLED_FS . open ( getFileName ( ) , " w " ) ;
2022-11-16 20:55:21 +01:00
if ( ! f ) {
errorFlag = ERR_FS_GENERAL ;
return ;
}
serializeJson ( doc , f ) ;
f . close ( ) ;
}
2022-09-20 21:17:44 +02:00
bool applyPreset ( byte index , byte callMode )
2020-11-06 22:12:48 +01:00
{
2022-06-01 22:11:25 +02:00
DEBUG_PRINT ( F ( " Request to apply preset: " ) ) ;
DEBUG_PRINTLN ( index ) ;
2022-01-01 12:52:50 +01:00
presetToApply = index ;
callModeToApply = callMode ;
return true ;
}
2023-06-22 10:06:19 +02:00
// apply preset or fallback to a effect and palette if it doesn't exist
void applyPresetWithFallback ( uint8_t index , uint8_t callMode , uint8_t effectID , uint8_t paletteID )
{
applyPreset ( index , callMode ) ;
//these two will be overwritten if preset exists in handlePresets()
effectCurrent = effectID ;
effectPalette = paletteID ;
}
2022-09-20 21:17:44 +02:00
void handlePresets ( )
2022-01-01 12:52:50 +01:00
{
2022-10-08 18:25:51 +02:00
if ( presetToSave ) {
doSaveState ( ) ;
return ;
}
2022-12-13 14:27:44 +01:00
if ( presetToApply = = 0 | | fileDoc ) return ; // no preset waiting to apply, or JSON buffer is already allocated, return to loop until free
2022-04-24 19:30:14 +02:00
bool changePreset = false ;
2022-06-01 22:11:25 +02:00
uint8_t tmpPreset = presetToApply ; // store temporary since deserializeState() may call applyPreset()
uint8_t tmpMode = callModeToApply ;
2022-04-24 19:30:14 +02:00
2022-01-01 12:52:50 +01:00
JsonObject fdo ;
2022-12-13 14:27:44 +01:00
const char * filename = getFileName ( tmpPreset < 255 ) ;
2022-01-01 12:52:50 +01:00
// allocate buffer
if ( ! requestJSONBufferLock ( 9 ) ) return ; // will also assign fileDoc
2022-06-01 22:11:25 +02:00
presetToApply = 0 ; //clear request for preset
callModeToApply = 0 ;
2022-07-17 15:58:41 +02:00
DEBUG_PRINT ( F ( " Applying preset: " ) ) ;
2022-06-01 22:11:25 +02:00
DEBUG_PRINTLN ( tmpPreset ) ;
2022-01-01 12:52:50 +01:00
# ifdef ARDUINO_ARCH_ESP32
2022-06-01 22:11:25 +02:00
if ( tmpPreset = = 255 & & tmpRAMbuffer ! = nullptr ) {
2022-01-01 12:52:50 +01:00
deserializeJson ( * fileDoc , tmpRAMbuffer ) ;
errorFlag = ERR_NONE ;
} else
# endif
{
2022-06-01 22:11:25 +02:00
errorFlag = readObjectFromFileUsingId ( filename , tmpPreset , fileDoc ) ? ERR_NONE : ERR_FS_PLOAD ;
2020-11-06 22:12:48 +01:00
}
2022-01-01 12:52:50 +01:00
fdo = fileDoc - > as < JsonObject > ( ) ;
2022-01-03 22:23:03 +01:00
//HTTP API commands
const char * httpwin = fdo [ " win " ] ;
if ( httpwin ) {
2022-04-24 19:47:55 +02:00
String apireq = " win " ; // reduce flash string usage
apireq + = F ( " &IN& " ) ; // internal call
2022-01-03 22:23:03 +01:00
apireq + = httpwin ;
2022-06-01 22:11:25 +02:00
handleSet ( nullptr , apireq , false ) ; // may call applyPreset() via PL=
2022-04-16 16:28:43 +02:00
setValuesFromFirstSelectedSeg ( ) ; // fills legacy values
2022-04-24 19:30:14 +02:00
changePreset = true ;
2022-01-03 22:23:03 +01:00
} else {
2022-06-01 22:11:25 +02:00
if ( ! fdo [ " seg " ] . isNull ( ) | | ! fdo [ " on " ] . isNull ( ) | | ! fdo [ " bri " ] . isNull ( ) | | ! fdo [ " nl " ] . isNull ( ) | | ! fdo [ " ps " ] . isNull ( ) | | ! fdo [ F ( " playlist " ) ] . isNull ( ) ) changePreset = true ;
2023-06-06 20:56:33 +02:00
if ( ! ( tmpMode = = CALL_MODE_BUTTON_PRESET & & fdo [ " ps " ] . is < const char * > ( ) & & strchr ( fdo [ " ps " ] . as < const char * > ( ) , ' ~ ' ) ! = strrchr ( fdo [ " ps " ] . as < const char * > ( ) , ' ~ ' ) ) )
fdo . remove ( " ps " ) ; // remove load request for presets to prevent recursive crash (if not called by button and contains preset cycling string "1~5~")
2022-06-01 22:11:25 +02:00
deserializeState ( fdo , CALL_MODE_NO_NOTIFY , tmpPreset ) ; // may change presetToApply by calling applyPreset()
2022-01-03 22:23:03 +01:00
}
2023-06-30 15:03:32 +02:00
if ( ! errorFlag & & tmpPreset < 255 & & changePreset ) currentPreset = tmpPreset ;
2020-11-06 22:12:48 +01:00
2022-01-01 12:52:50 +01:00
# if defined(ARDUINO_ARCH_ESP32)
//Aircoookie recommended not to delete buffer
2022-06-01 22:11:25 +02:00
if ( tmpPreset = = 255 & & tmpRAMbuffer ! = nullptr ) {
2022-01-01 12:52:50 +01:00
free ( tmpRAMbuffer ) ;
tmpRAMbuffer = nullptr ;
2020-11-06 22:12:48 +01:00
}
2022-01-01 12:52:50 +01:00
# endif
releaseJSONBufferLock ( ) ; // will also clear fileDoc
2022-12-05 22:56:44 +01:00
if ( changePreset ) notify ( tmpMode ) ; // force UDP notification
stateUpdated ( tmpMode ) ; // was colorUpdated() if anything breaks
2022-06-01 22:11:25 +02:00
updateInterfaces ( tmpMode ) ;
2020-11-06 22:12:48 +01:00
}
2022-01-01 12:52:50 +01:00
//called from handleSet(PS=) [network callback (fileDoc==nullptr), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)]
2022-10-08 18:25:51 +02:00
void savePreset ( byte index , const char * pname , JsonObject sObj )
2020-11-06 22:12:48 +01:00
{
2022-03-15 09:55:23 +01:00
if ( index = = 0 | | ( index > 250 & & index < 255 ) ) return ;
2022-10-08 18:25:51 +02:00
if ( pname ) strlcpy ( saveName , pname , 33 ) ;
else {
if ( sObj [ " n " ] . is < const char * > ( ) ) strlcpy ( saveName , sObj [ " n " ] . as < const char * > ( ) , 33 ) ;
else sprintf_P ( saveName , PSTR ( " Preset %d " ) , index ) ;
2022-01-01 12:52:50 +01:00
}
2020-11-29 22:07:12 +01:00
2022-10-08 18:25:51 +02:00
DEBUG_PRINT ( F ( " Saving preset ( " ) ) ; DEBUG_PRINT ( index ) ; DEBUG_PRINT ( F ( " ) " ) ) ; DEBUG_PRINTLN ( saveName ) ;
2021-11-03 14:52:22 +01:00
2022-10-08 18:25:51 +02:00
presetToSave = index ;
playlistSave = false ;
2022-11-26 03:57:17 +01:00
if ( sObj [ F ( " ql " ) ] . is < const char * > ( ) ) strlcpy ( quickLoad , sObj [ F ( " ql " ) ] . as < const char * > ( ) , 9 ) ; // client limits QL to 2 chars, buffer for 8 bytes to allow unicode
2022-12-13 14:27:44 +01:00
2023-11-18 18:22:47 +01:00
if ( sObj . size ( ) = = 0 | | sObj [ " o " ] . isNull ( ) ) { // no "o" means not a playlist or custom API call, saving of state is async (not immediately)
includeBri = sObj [ " ib " ] . as < bool > ( ) | | sObj . size ( ) = = 0 | | index = = 255 ; // temporary preset needs brightness
segBounds = sObj [ " sb " ] . as < bool > ( ) | | sObj . size ( ) = = 0 | | index = = 255 ; // temporary preset needs bounds
2022-10-08 18:25:51 +02:00
selectedOnly = sObj [ F ( " sc " ) ] . as < bool > ( ) ;
2022-11-16 20:55:21 +01:00
saveLedmap = sObj [ F ( " ledmap " ) ] | - 1 ;
2022-10-08 18:25:51 +02:00
} else {
2022-12-13 14:27:44 +01:00
// this is a playlist or API call
2022-10-08 18:25:51 +02:00
if ( sObj [ F ( " playlist " ) ] . isNull ( ) ) {
2023-01-29 11:27:14 +01:00
// we will save API call immediately (often causes presets.json corruption)
2022-12-13 14:27:44 +01:00
presetToSave = 0 ;
if ( index > 250 | | ! fileDoc ) return ; // cannot save API calls to temporary preset (255)
sObj . remove ( " o " ) ;
sObj . remove ( " v " ) ;
sObj . remove ( " time " ) ;
sObj . remove ( F ( " error " ) ) ;
sObj . remove ( F ( " psave " ) ) ;
if ( sObj [ " n " ] . isNull ( ) ) sObj [ " n " ] = saveName ;
initPresetsFile ( ) ; // just in case if someone deleted presets.json using /edit
2023-01-29 11:27:14 +01:00
writeObjectToFileUsingId ( getFileName ( index < 255 ) , index , fileDoc ) ;
2022-12-13 14:27:44 +01:00
presetsModifiedTime = toki . second ( ) ; //unix time
updateFSInfo ( ) ;
2022-01-01 12:52:50 +01:00
} else {
2022-10-08 18:25:51 +02:00
// store playlist
2023-01-29 11:27:14 +01:00
// WARNING: playlist will be loaded in json.cpp after this call and will have repeat counter increased by 1
2022-10-08 18:25:51 +02:00
includeBri = true ; // !sObj["on"].isNull();
playlistSave = true ;
2020-11-29 22:07:12 +01:00
}
2022-10-08 18:25:51 +02:00
}
2020-11-06 22:12:48 +01:00
}
void deletePreset ( byte index ) {
StaticJsonDocument < 24 > empty ;
2022-12-13 14:27:44 +01:00
writeObjectToFileUsingId ( getFileName ( ) , index , & empty ) ;
2021-05-25 09:59:19 +02:00
presetsModifiedTime = toki . second ( ) ; //unix time
2020-11-06 22:12:48 +01:00
updateFSInfo ( ) ;
}