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-02-02 20:53:16 +01:00
bool applyPreset ( byte index , byte callMode , bool fromJson )
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 ;
2022-04-16 16:28:43 +02:00
// the following is needed in case of HTTP JSON API call to return correct state to the caller
// fromJson is true in case when deserializeState() was called with presetId==0
if ( fromJson ) handlePresets ( true ) ; // force immediate processing
2022-01-01 12:52:50 +01:00
return true ;
}
2022-04-16 16:28:43 +02:00
void handlePresets ( bool force )
2022-01-01 12:52:50 +01:00
{
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-06-01 22:11:25 +02:00
if ( tmpPreset = = 0 | | ( fileDoc & & ! force ) ) return ; // JSON buffer already allocated and not force apply or no preset waiting
2022-01-01 12:52:50 +01:00
JsonObject fdo ;
2022-06-01 22:11:25 +02:00
const char * filename = tmpPreset < 255 ? " /presets.json " : " /tmp.json " ;
2022-01-01 12:52:50 +01:00
2022-04-16 16:28:43 +02:00
//crude way to determine if this was called by a network request
uint8_t core = 1 ;
# ifdef ARDUINO_ARCH_ESP32
2022-09-17 13:41:27 +02:00
# if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
// this does not make sense on single core
core = xPortGetCoreID ( ) ;
# endif
2022-04-16 16:28:43 +02:00
# endif
//only allow use of fileDoc from the core responsible for network requests (AKA HTTP JSON API)
//do not use active network request doc from preset called by main loop (playlist, schedule, ...)
2022-06-01 22:11:25 +02:00
if ( fileDoc & & core & & force & & tmpPreset < 255 ) {
DEBUG_PRINT ( F ( " Force applying preset: " ) ) ;
DEBUG_PRINTLN ( presetToApply ) ;
presetToApply = 0 ; //clear request for preset
callModeToApply = 0 ;
2022-04-16 16:28:43 +02:00
// this will overwrite doc with preset content but applyPreset() is the last in such case and content of doc is no longer needed
2022-06-01 22:11:25 +02:00
errorFlag = readObjectFromFileUsingId ( filename , tmpPreset , fileDoc ) ? ERR_NONE : ERR_FS_PLOAD ;
2022-04-24 19:30:14 +02:00
2022-04-16 16:28:43 +02:00
JsonObject fdo = fileDoc - > as < JsonObject > ( ) ;
2022-04-24 19:30:14 +02:00
2022-06-01 22:11:25 +02:00
//HTTP API commands
const char * httpwin = fdo [ " win " ] ;
if ( httpwin ) {
String apireq = " win " ; // reduce flash string usage
apireq + = F ( " &IN& " ) ; // internal call
apireq + = httpwin ;
handleSet ( nullptr , apireq , false ) ; // may call applyPreset() via PL=
setValuesFromFirstSelectedSeg ( ) ; // fills legacy values
changePreset = true ;
} else {
if ( ! fdo [ " seg " ] . isNull ( ) ) unloadPlaylist ( ) ; // if preset contains "seg" we must unload playlist
if ( ! fdo [ " seg " ] . isNull ( ) | | ! fdo [ " on " ] . isNull ( ) | | ! fdo [ " bri " ] . isNull ( ) | | ! fdo [ " ps " ] . isNull ( ) | | ! fdo [ F ( " playlist " ) ] . isNull ( ) ) changePreset = true ;
fdo . remove ( " ps " ) ; //remove load request for presets to prevent recursive crash
deserializeState ( fdo , tmpMode , tmpPreset ) ; // may call applyPreset() which will overwrite presetToApply
}
2022-06-03 22:22:18 +02:00
if ( ! errorFlag & & changePreset ) presetCycCurr = currentPreset = tmpPreset ;
2022-06-01 22:11:25 +02:00
colorUpdated ( tmpMode ) ;
2022-04-16 16:28:43 +02:00
return ;
}
2022-04-24 19:30:14 +02:00
if ( force ) return ; // something went wrong with force option (most likely WS request), quit and wait for async load
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 ;
2022-01-03 22:23:03 +01:00
fdo . remove ( " ps " ) ; //remove load request for presets to prevent recursive crash
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
}
2022-06-03 22:22:18 +02:00
if ( ! errorFlag & & tmpPreset < 255 & & changePreset ) presetCycCurr = 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-06-01 22:11:25 +02:00
colorUpdated ( tmpMode ) ;
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-03-15 09:55:23 +01:00
void savePreset ( byte index , const char * pname , JsonObject saveobj )
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-02-19 11:37:48 +01:00
char tmp [ 12 ] ;
2020-11-06 22:12:48 +01:00
JsonObject sObj = saveobj ;
2022-01-01 12:52:50 +01:00
bool bufferAllocated = false ;
2020-11-06 22:12:48 +01:00
2022-03-15 09:55:23 +01:00
bool persist = ( index ! = 255 ) ;
2022-02-02 22:00:31 +01:00
const char * filename = persist ? " /presets.json " : " /tmp.json " ;
2021-10-04 20:22:04 +02:00
2021-11-03 14:52:22 +01:00
if ( ! fileDoc ) {
2022-01-01 12:52:50 +01:00
// called from handleSet() HTTP API
2021-11-14 16:56:34 +01:00
if ( ! requestJSONBufferLock ( 10 ) ) return ;
2022-01-01 12:52:50 +01:00
sObj = fileDoc - > to < JsonObject > ( ) ;
bufferAllocated = true ;
}
2022-02-19 11:37:48 +01:00
if ( sObj [ " n " ] . isNull ( ) & & pname = = nullptr ) {
sprintf_P ( tmp , PSTR ( " Preset %d " ) , index ) ;
sObj [ " n " ] = tmp ;
} else if ( pname ) sObj [ " n " ] = pname ;
2020-11-29 22:07:12 +01:00
2022-01-01 12:52:50 +01:00
sObj . remove ( F ( " psave " ) ) ;
sObj . remove ( F ( " v " ) ) ;
2021-11-03 14:52:22 +01:00
2022-01-01 12:52:50 +01:00
if ( ! sObj [ " o " ] ) {
DEBUGFS_PRINTLN ( F ( " Serialize current state " ) ) ;
if ( sObj [ " ib " ] . isNull ( ) & & sObj [ " sb " ] . isNull ( ) ) serializeState ( sObj , true ) ;
else serializeState ( sObj , true , sObj [ " ib " ] , sObj [ " sb " ] ) ;
if ( persist ) currentPreset = index ;
}
sObj . remove ( " o " ) ;
sObj . remove ( " ib " ) ;
sObj . remove ( " sb " ) ;
2022-01-14 14:27:11 +01:00
sObj . remove ( F ( " sc " ) ) ;
2022-01-01 12:52:50 +01:00
sObj . remove ( F ( " error " ) ) ;
sObj . remove ( F ( " time " ) ) ;
2020-11-06 22:12:48 +01:00
2022-01-01 12:52:50 +01:00
# if defined(ARDUINO_ARCH_ESP32)
if ( index = = 255 ) {
if ( tmpRAMbuffer ! = nullptr ) free ( tmpRAMbuffer ) ;
size_t len = measureJson ( * fileDoc ) + 1 ;
DEBUG_PRINTLN ( len ) ;
// if possible use SPI RAM on ESP32
# ifdef WLED_USE_PSRAM
if ( psramFound ( ) )
tmpRAMbuffer = ( char * ) ps_malloc ( len ) ;
else
# endif
tmpRAMbuffer = ( char * ) malloc ( len ) ;
if ( tmpRAMbuffer ! = nullptr ) {
serializeJson ( * fileDoc , tmpRAMbuffer , len ) ;
} else {
writeObjectToFileUsingId ( filename , index , fileDoc ) ;
2020-11-29 22:07:12 +01:00
}
2022-01-01 12:52:50 +01:00
} else
# endif
writeObjectToFileUsingId ( filename , index , fileDoc ) ;
2020-11-06 22:12:48 +01:00
2021-10-04 20:22:04 +02:00
if ( persist ) presetsModifiedTime = toki . second ( ) ; //unix time
2022-01-01 12:52:50 +01:00
if ( bufferAllocated ) releaseJSONBufferLock ( ) ;
2020-11-06 22:12:48 +01:00
updateFSInfo ( ) ;
}
void deletePreset ( byte index ) {
StaticJsonDocument < 24 > empty ;
writeObjectToFileUsingId ( " /presets.json " , index , & empty ) ;
2021-05-25 09:59:19 +02:00
presetsModifiedTime = toki . second ( ) ; //unix time
2020-11-06 22:12:48 +01:00
updateFSInfo ( ) ;
}