2016-07-22 13:28:01 +00:00
# include "Marlin.h"
2020-10-26 07:42:26 +00:00
# include "cmdqueue.h"
2016-07-22 13:28:01 +00:00
# include "cardreader.h"
# include "ultralcd.h"
# include "stepper.h"
# include "temperature.h"
# include "language.h"
# ifdef SDSUPPORT
2017-12-10 10:08:50 +00:00
# define LONGEST_FILENAME (longFilename[0] ? longFilename : filename)
2016-07-22 13:28:01 +00:00
CardReader : : CardReader ( )
{
2017-12-10 10:08:50 +00:00
# ifdef SDCARD_SORT_ALPHA
sort_count = 0 ;
# if SDSORT_GCODE
sort_alpha = true ;
sort_folders = FOLDER_SORTING ;
//sort_reverse = false;
# endif
# endif
2016-07-22 13:28:01 +00:00
filesize = 0 ;
sdpos = 0 ;
sdprinting = false ;
cardOK = false ;
saving = false ;
logging = false ;
autostart_atmillis = 0 ;
workDirDepth = 0 ;
file_subcall_ctr = 0 ;
memset ( workDirParents , 0 , sizeof ( workDirParents ) ) ;
autostart_stilltocheck = true ; //the SD start is delayed, because otherwise the serial cannot answer fast enough to make contact with the host software.
lastnr = 0 ;
//power to SD reader
# if SDPOWER > -1
SET_OUTPUT ( SDPOWER ) ;
WRITE ( SDPOWER , HIGH ) ;
# endif //SDPOWER
2019-01-27 21:48:51 +00:00
autostart_atmillis = _millis ( ) + 5000 ;
2016-07-22 13:28:01 +00:00
}
char * createFilename ( char * buffer , const dir_t & p ) //buffer>12characters
{
char * pos = buffer ;
for ( uint8_t i = 0 ; i < 11 ; i + + )
{
if ( p . name [ i ] = = ' ' ) continue ;
if ( i = = 8 )
{
* pos + + = ' . ' ;
}
* pos + + = p . name [ i ] ;
}
* pos + + = 0 ;
return buffer ;
}
2017-12-10 10:08:50 +00:00
/**
+ * Dive into a folder and recurse depth - first to perform a pre - set operation lsAction :
2021-01-22 09:13:44 +00:00
+ * LS_Count - Add + 1 to nrFiles for every file within the parent
+ * LS_GetFilename - Get the filename of the file indexed by nrFiles
+ * LS_SerialPrint - Print the full path and size of each file to serial output
+ * LS_SerialPrint_LFN - Print the full path , long filename and size of each file to serial output
2017-12-10 10:08:50 +00:00
+ */
void CardReader : : lsDive ( const char * prepend , SdFile parent , const char * const match /*=NULL*/ ) {
dir_t p ;
uint8_t cnt = 0 ;
// Read the next entry from a directory
while ( parent . readDir ( p , longFilename ) > 0 ) {
// If the entry is a directory and the action is LS_SerialPrint
if ( DIR_IS_SUBDIR ( & p ) & & lsAction ! = LS_Count & & lsAction ! = LS_GetFilename ) {
// Get the short name for the item, which we know is a folder
char lfilename [ FILENAME_LENGTH ] ;
createFilename ( lfilename , p ) ;
// Allocate enough stack space for the full path to a folder, trailing slash, and nul
bool prepend_is_empty = ( prepend [ 0 ] = = ' \0 ' ) ;
int len = ( prepend_is_empty ? 1 : strlen ( prepend ) ) + strlen ( lfilename ) + 1 + 1 ;
char path [ len ] ;
// Append the FOLDERNAME12/ to the passed string.
// It contains the full path to the "parent" argument.
// We now have the full path to the item in this folder.
strcpy ( path , prepend_is_empty ? " / " : prepend ) ; // root slash if prepend is empty
strcat ( path , lfilename ) ; // FILENAME_LENGTH-1 characters maximum
strcat ( path , " / " ) ; // 1 character
// Serial.print(path);
// Get a new directory object using the full path
// and dive recursively into it.
2021-01-22 09:13:44 +00:00
if ( lsAction = = LS_SerialPrint_LFN )
printf_P ( PSTR ( " DIR_ENTER: %s \" %s \" \n " ) , path , longFilename [ 0 ] ? longFilename : lfilename ) ;
2017-12-10 10:08:50 +00:00
SdFile dir ;
if ( ! dir . open ( parent , lfilename , O_READ ) ) {
2021-01-22 09:13:44 +00:00
if ( lsAction = = LS_SerialPrint | | lsAction = = LS_SerialPrint_LFN ) {
2017-12-10 10:08:50 +00:00
//SERIAL_ECHO_START();
2019-05-07 10:23:09 +00:00
//SERIAL_ECHOPGM(_i("Cannot open subdir"));////MSG_SD_CANT_OPEN_SUBDIR
2017-12-10 10:08:50 +00:00
//SERIAL_ECHOLN(lfilename);
}
}
lsDive ( path , dir ) ;
// close() is done automatically by destructor of SdFile
2021-01-22 09:13:44 +00:00
if ( lsAction = = LS_SerialPrint_LFN )
puts_P ( PSTR ( " DIR_EXIT " ) ) ;
2017-12-10 10:08:50 +00:00
}
else {
uint8_t pn0 = p . name [ 0 ] ;
if ( pn0 = = DIR_NAME_FREE ) break ;
if ( pn0 = = DIR_NAME_DELETED | | pn0 = = ' . ' ) continue ;
if ( longFilename [ 0 ] = = ' . ' ) continue ;
if ( ! DIR_IS_FILE_OR_SUBDIR ( & p ) | | ( p . attributes & DIR_ATT_HIDDEN ) ) continue ;
filenameIsDir = DIR_IS_SUBDIR ( & p ) ;
if ( ! filenameIsDir & & ( p . name [ 8 ] ! = ' G ' | | p . name [ 9 ] = = ' ~ ' ) ) continue ;
switch ( lsAction ) {
case LS_Count :
nrFiles + + ;
break ;
2021-01-22 09:13:44 +00:00
case LS_SerialPrint_LFN :
2017-12-10 10:08:50 +00:00
case LS_SerialPrint :
createFilename ( filename , p ) ;
SERIAL_PROTOCOL ( prepend ) ;
SERIAL_PROTOCOL ( filename ) ;
2017-12-11 10:30:49 +00:00
MYSERIAL . write ( ' ' ) ;
2021-01-22 09:13:44 +00:00
if ( lsAction = = LS_SerialPrint_LFN )
printf_P ( PSTR ( " \" %s \" " ) , LONGEST_FILENAME ) ;
2017-12-10 10:08:50 +00:00
SERIAL_PROTOCOLLN ( p . fileSize ) ;
2021-01-25 09:46:51 +00:00
manage_heater ( ) ;
2017-12-10 10:08:50 +00:00
break ;
case LS_GetFilename :
//SERIAL_ECHOPGM("File: ");
createFilename ( filename , p ) ;
2017-12-11 10:30:49 +00:00
cluster = parent . curCluster ( ) ;
position = parent . curPosition ( ) ;
2017-12-10 10:08:50 +00:00
/*MYSERIAL.println(filename);
SERIAL_ECHOPGM ( " Write date: " ) ;
writeDate = p . lastWriteDate ;
MYSERIAL . println ( writeDate ) ;
writeTime = p . lastWriteTime ;
SERIAL_ECHOPGM ( " Creation date: " ) ;
MYSERIAL . println ( p . creationDate ) ;
SERIAL_ECHOPGM ( " Access date: " ) ;
MYSERIAL . println ( p . lastAccessDate ) ;
SERIAL_ECHOLNPGM ( " " ) ; */
2020-02-14 08:09:15 +00:00
crmodDate = p . lastWriteDate ;
crmodTime = p . lastWriteTime ;
// There are scenarios when simple modification time is not enough (on MS Windows)
// For example - extract an old g-code from an archive onto the SD card.
// In such case the creation time is current time (which is correct), but the modification time
// stays the same - i.e. old.
// Therefore let's pick the most recent timestamp from both creation and modification timestamps
if ( crmodDate < p . creationDate | | ( crmodDate = = p . creationDate & & crmodTime < p . creationTime ) ) {
crmodDate = p . creationDate ;
crmodTime = p . creationTime ;
}
2017-12-10 10:08:50 +00:00
//writeDate = p.lastAccessDate;
if ( match ! = NULL ) {
if ( strcasecmp ( match , filename ) = = 0 ) return ;
}
else if ( cnt = = nrFiles ) return ;
cnt + + ;
break ;
}
}
} // while readDir
2016-07-22 13:28:01 +00:00
}
2021-01-22 09:13:44 +00:00
void CardReader : : ls ( bool printLFN )
2016-07-22 13:28:01 +00:00
{
2021-01-22 09:13:44 +00:00
lsAction = printLFN ? LS_SerialPrint_LFN : LS_SerialPrint ;
2017-12-10 10:08:50 +00:00
//if(lsAction==LS_Count)
//nrFiles=0;
2016-07-22 13:28:01 +00:00
root . rewind ( ) ;
lsDive ( " " , root ) ;
}
void CardReader : : initsd ( )
{
cardOK = false ;
if ( root . isOpen ( ) )
root . close ( ) ;
# ifdef SDSLOW
if ( ! card . init ( SPI_HALF_SPEED , SDSS )
# if defined(LCD_SDSS) && (LCD_SDSS != SDSS)
& & ! card . init ( SPI_HALF_SPEED , LCD_SDSS )
# endif
)
# else
if ( ! card . init ( SPI_FULL_SPEED , SDSS )
# if defined(LCD_SDSS) && (LCD_SDSS != SDSS)
& & ! card . init ( SPI_FULL_SPEED , LCD_SDSS )
# endif
)
# endif
{
//if (!card.init(SPI_HALF_SPEED,SDSS))
SERIAL_ECHO_START ;
2019-05-07 10:23:09 +00:00
SERIAL_ECHOLNRPGM ( _n ( " SD init fail " ) ) ; ////MSG_SD_INIT_FAIL
2016-07-22 13:28:01 +00:00
}
else if ( ! volume . init ( & card ) )
{
SERIAL_ERROR_START ;
2019-05-07 10:23:09 +00:00
SERIAL_ERRORLNRPGM ( _n ( " volume.init failed " ) ) ; ////MSG_SD_VOL_INIT_FAIL
2016-07-22 13:28:01 +00:00
}
else if ( ! root . openRoot ( & volume ) )
{
SERIAL_ERROR_START ;
2019-05-07 10:23:09 +00:00
SERIAL_ERRORLNRPGM ( _n ( " openRoot failed " ) ) ; ////MSG_SD_OPENROOT_FAIL
2016-07-22 13:28:01 +00:00
}
else
{
cardOK = true ;
SERIAL_ECHO_START ;
2019-05-07 10:23:09 +00:00
SERIAL_ECHOLNRPGM ( _n ( " SD card ok " ) ) ; ////MSG_SD_CARD_OK
2016-07-22 13:28:01 +00:00
}
workDir = root ;
curDir = & root ;
2019-09-29 18:09:11 +00:00
workDirDepth = 0 ;
2017-12-10 10:08:50 +00:00
# ifdef SDCARD_SORT_ALPHA
presort ( ) ;
# endif
2016-07-22 13:28:01 +00:00
/*
if ( ! workDir . openRoot ( & volume ) )
{
2018-11-01 13:07:16 +00:00
SERIAL_ECHOLNPGM ( MSG_SD_WORKDIR_FAIL ) ;
2016-07-22 13:28:01 +00:00
}
*/
}
void CardReader : : setroot ( )
{
/*if(!workDir.openRoot(&volume))
{
2018-11-01 13:07:16 +00:00
SERIAL_ECHOLNPGM ( MSG_SD_WORKDIR_FAIL ) ;
2016-07-22 13:28:01 +00:00
} */
workDir = root ;
curDir = & workDir ;
2017-12-10 10:08:50 +00:00
# ifdef SDCARD_SORT_ALPHA
presort ( ) ;
# endif
2016-07-22 13:28:01 +00:00
}
void CardReader : : release ( )
{
sdprinting = false ;
cardOK = false ;
2020-08-28 14:32:07 +00:00
SERIAL_ECHO_START ;
SERIAL_ECHOLNRPGM ( _n ( " SD card released " ) ) ; ////MSG_SD_CARD_RELEASED
2016-07-22 13:28:01 +00:00
}
void CardReader : : startFileprint ( )
{
if ( cardOK )
{
sdprinting = true ;
2019-10-20 15:34:54 +00:00
Stopped = false ;
2017-12-10 10:08:50 +00:00
# ifdef SDCARD_SORT_ALPHA
2017-12-11 10:30:49 +00:00
//flush_presort();
2017-12-10 10:08:50 +00:00
# endif
2016-07-22 13:28:01 +00:00
}
}
2018-07-17 15:31:40 +00:00
void CardReader : : openLogFile ( const char * name )
2016-07-22 13:28:01 +00:00
{
logging = true ;
openFile ( name , false ) ;
}
2017-11-27 05:20:51 +00:00
void CardReader : : getDirName ( char * name , uint8_t level )
{
workDirParents [ level ] . getFilename ( name ) ;
}
uint16_t CardReader : : getWorkDirDepth ( ) {
return workDirDepth ;
}
2016-07-22 13:28:01 +00:00
void CardReader : : getAbsFilename ( char * t )
{
uint8_t cnt = 0 ;
* t = ' / ' ; t + + ; cnt + + ;
for ( uint8_t i = 0 ; i < workDirDepth ; i + + )
{
workDirParents [ i ] . getFilename ( t ) ; //SDBaseFile.getfilename!
while ( * t ! = 0 & & cnt < MAXPATHNAMELENGTH )
{ t + + ; cnt + + ; } //crawl counter forward.
}
if ( cnt < MAXPATHNAMELENGTH - 13 )
file . getFilename ( t ) ;
else
t [ 0 ] = 0 ;
}
2018-08-06 18:49:40 +00:00
/**
* @ brief Dive into subfolder
*
* Method sets curDir to point to root , in case fileName is null .
* Method sets curDir to point to workDir , in case fileName path is relative
* ( doesn ' t start with ' / ' )
* Method sets curDir to point to dir , which is specified by absolute path
* specified by fileName . In such case fileName is updated so it points to
* file name without the path .
*
* @ param [ in , out ] fileName
* expects file name including path
* in case of absolute path , file name without path is returned
* @ param [ in , out ] dir SdFile object to operate with ,
* in case of absolute path , curDir is modified to point to dir ,
* so it is not possible to create on stack inside this function ,
* as curDir would point to destroyed object .
*/
void CardReader : : diveSubfolder ( const char * fileName , SdFile & dir )
{
curDir = & root ;
if ( ! fileName ) return ;
const char * dirname_start , * dirname_end ;
if ( fileName [ 0 ] = = ' / ' ) // absolute path
{
dirname_start = fileName + 1 ;
while ( * dirname_start )
{
dirname_end = strchr ( dirname_start , ' / ' ) ;
//SERIAL_ECHO("start:");SERIAL_ECHOLN((int)(dirname_start-name));
//SERIAL_ECHO("end :");SERIAL_ECHOLN((int)(dirname_end-name));
if ( dirname_end & & dirname_end > dirname_start )
{
const size_t maxLen = 12 ;
char subdirname [ maxLen + 1 ] ;
subdirname [ maxLen ] = 0 ;
2018-08-06 18:57:17 +00:00
const size_t len = ( ( static_cast < size_t > ( dirname_end - dirname_start ) ) > maxLen ) ? maxLen : ( dirname_end - dirname_start ) ;
2018-08-06 18:49:40 +00:00
strncpy ( subdirname , dirname_start , len ) ;
SERIAL_ECHOLN ( subdirname ) ;
if ( ! dir . open ( curDir , subdirname , O_READ ) )
{
2018-11-22 17:15:22 +00:00
SERIAL_PROTOCOLRPGM ( MSG_SD_OPEN_FILE_FAIL ) ;
2018-08-06 18:49:40 +00:00
SERIAL_PROTOCOL ( subdirname ) ;
2020-06-01 15:51:28 +00:00
SERIAL_PROTOCOLLN ( ' . ' ) ;
2018-08-06 18:49:40 +00:00
return ;
}
else
{
//SERIAL_ECHOLN("dive ok");
}
curDir = & dir ;
dirname_start = dirname_end + 1 ;
}
else // the reminder after all /fsa/fdsa/ is the filename
{
fileName = dirname_start ;
//SERIAL_ECHOLN("remaider");
//SERIAL_ECHOLN(fname);
break ;
}
}
}
else //relative path
{
curDir = & workDir ;
}
}
2016-07-22 13:28:01 +00:00
2018-07-17 15:31:40 +00:00
void CardReader : : openFile ( const char * name , bool read , bool replace_current /*=true*/ )
2016-07-22 13:28:01 +00:00
{
if ( ! cardOK )
return ;
if ( file . isOpen ( ) ) //replacing current file by new file, or subfile call
{
if ( ! replace_current )
{
if ( ( int ) file_subcall_ctr > ( int ) SD_PROCEDURE_DEPTH - 1 )
{
2019-11-29 20:49:22 +00:00
// SERIAL_ERROR_START;
// SERIAL_ERRORPGM("trying to call sub-gcode files with too many levels. MAX level is:");
// SERIAL_ERRORLN(SD_PROCEDURE_DEPTH);
kill ( _n ( " trying to call sub-gcode files with too many levels. " ) , 1 ) ;
2016-07-22 13:28:01 +00:00
return ;
}
SERIAL_ECHO_START ;
SERIAL_ECHOPGM ( " SUBROUTINE CALL target: \" " ) ;
SERIAL_ECHO ( name ) ;
SERIAL_ECHOPGM ( " \" parent: \" " ) ;
//store current filename and position
getAbsFilename ( filenames [ file_subcall_ctr ] ) ;
SERIAL_ECHO ( filenames [ file_subcall_ctr ] ) ;
SERIAL_ECHOPGM ( " \" pos " ) ;
SERIAL_ECHOLN ( sdpos ) ;
filespos [ file_subcall_ctr ] = sdpos ;
file_subcall_ctr + + ;
}
else
{
SERIAL_ECHO_START ;
SERIAL_ECHOPGM ( " Now doing file: " ) ;
SERIAL_ECHOLN ( name ) ;
}
file . close ( ) ;
}
else //opening fresh file
{
file_subcall_ctr = 0 ; //resetting procedure depth in case user cancels print while in procedure
SERIAL_ECHO_START ;
SERIAL_ECHOPGM ( " Now fresh file: " ) ;
SERIAL_ECHOLN ( name ) ;
}
sdprinting = false ;
2019-10-20 15:34:54 +00:00
2016-07-22 13:28:01 +00:00
SdFile myDir ;
2018-07-17 15:31:40 +00:00
const char * fname = name ;
2018-08-06 18:49:40 +00:00
diveSubfolder ( fname , myDir ) ;
2016-07-22 13:28:01 +00:00
if ( read )
{
if ( file . open ( curDir , fname , O_READ ) )
{
filesize = file . fileSize ( ) ;
2019-05-07 10:23:09 +00:00
SERIAL_PROTOCOLRPGM ( _N ( " File opened: " ) ) ; ////MSG_SD_FILE_OPENED
2016-07-22 13:28:01 +00:00
SERIAL_PROTOCOL ( fname ) ;
2019-05-07 10:23:09 +00:00
SERIAL_PROTOCOLRPGM ( _n ( " Size: " ) ) ; ////MSG_SD_SIZE
2016-07-22 13:28:01 +00:00
SERIAL_PROTOCOLLN ( filesize ) ;
sdpos = 0 ;
2019-05-07 10:23:09 +00:00
SERIAL_PROTOCOLLNRPGM ( _N ( " File selected " ) ) ; ////MSG_SD_FILE_SELECTED
2016-07-22 13:28:01 +00:00
getfilename ( 0 , fname ) ;
lcd_setstatus ( longFilename [ 0 ] ? longFilename : fname ) ;
2021-01-26 07:24:18 +00:00
lcd_setstatuspgm ( PSTR ( " SD-PRINTING " ) ) ;
2016-07-22 13:28:01 +00:00
}
else
{
2018-11-22 17:15:22 +00:00
SERIAL_PROTOCOLRPGM ( MSG_SD_OPEN_FILE_FAIL ) ;
2016-07-22 13:28:01 +00:00
SERIAL_PROTOCOL ( fname ) ;
2020-06-01 15:51:28 +00:00
SERIAL_PROTOCOLLN ( ' . ' ) ;
2016-07-22 13:28:01 +00:00
}
}
else
{ //write
if ( ! file . open ( curDir , fname , O_CREAT | O_APPEND | O_WRITE | O_TRUNC ) )
{
2018-11-22 17:15:22 +00:00
SERIAL_PROTOCOLRPGM ( MSG_SD_OPEN_FILE_FAIL ) ;
2016-07-22 13:28:01 +00:00
SERIAL_PROTOCOL ( fname ) ;
2020-06-01 15:51:28 +00:00
SERIAL_PROTOCOLLN ( ' . ' ) ;
2016-07-22 13:28:01 +00:00
}
else
{
saving = true ;
2019-05-07 10:23:09 +00:00
SERIAL_PROTOCOLRPGM ( _N ( " Writing to file: " ) ) ; ////MSG_SD_WRITE_TO_FILE
2016-07-22 13:28:01 +00:00
SERIAL_PROTOCOLLN ( name ) ;
lcd_setstatus ( fname ) ;
}
}
}
2018-07-17 15:31:40 +00:00
void CardReader : : removeFile ( const char * name )
2016-07-22 13:28:01 +00:00
{
2018-08-06 18:49:40 +00:00
if ( ! cardOK ) return ;
file . close ( ) ;
sdprinting = false ;
SdFile myDir ;
const char * fname = name ;
diveSubfolder ( fname , myDir ) ;
2016-07-22 13:28:01 +00:00
if ( file . remove ( curDir , fname ) )
{
SERIAL_PROTOCOLPGM ( " File deleted: " ) ;
SERIAL_PROTOCOLLN ( fname ) ;
sdpos = 0 ;
2017-12-10 10:08:50 +00:00
# ifdef SDCARD_SORT_ALPHA
presort ( ) ;
# endif
2016-07-22 13:28:01 +00:00
}
else
{
SERIAL_PROTOCOLPGM ( " Deletion failed, File: " ) ;
SERIAL_PROTOCOL ( fname ) ;
2021-01-26 07:24:18 +00:00
SERIAL_PROTOCOLLN ( ' . ' ) ;
2016-07-22 13:28:01 +00:00
}
}
2017-12-06 13:55:53 +00:00
uint32_t CardReader : : getFileSize ( )
{
return filesize ;
}
2021-02-01 12:54:37 +00:00
void CardReader : : getStatus ( bool arg_P )
2016-07-22 13:28:01 +00:00
{
2021-01-31 13:06:20 +00:00
if ( isPrintPaused )
{
if ( saved_printing & & ( saved_printing_type = = PRINTING_TYPE_SD ) )
SERIAL_PROTOCOLLNPGM ( " SD print paused " ) ;
else
SERIAL_PROTOCOLLNPGM ( " Print saved " ) ;
}
else if ( sdprinting )
{
2021-02-01 12:54:37 +00:00
if ( arg_P )
2021-01-31 13:06:20 +00:00
{
SERIAL_PROTOCOL ( ' / ' ) ;
for ( uint8_t i = 0 ; i < getWorkDirDepth ( ) ; i + + )
printf_P ( PSTR ( " %s/ " ) , dir_names [ i ] ) ;
puts ( filename ) ;
}
else
SERIAL_PROTOCOLLN ( LONGEST_FILENAME ) ;
SERIAL_PROTOCOLRPGM ( _N ( " SD printing byte " ) ) ; ////MSG_SD_PRINTING_BYTE
SERIAL_PROTOCOL ( sdpos ) ;
SERIAL_PROTOCOL ( ' / ' ) ;
SERIAL_PROTOCOLLN ( filesize ) ;
uint16_t time = ( _millis ( ) - starttime ) / 60000U ;
SERIAL_PROTOCOL ( itostr2 ( time / 60 ) ) ;
SERIAL_PROTOCOL ( ' : ' ) ;
SERIAL_PROTOCOLLN ( itostr2 ( time % 60 ) ) ;
}
else
SERIAL_PROTOCOLLNPGM ( " Not SD printing " ) ;
2016-07-22 13:28:01 +00:00
}
void CardReader : : write_command ( char * buf )
{
char * begin = buf ;
char * npos = 0 ;
char * end = buf + strlen ( buf ) - 1 ;
file . writeError = false ;
if ( ( npos = strchr ( buf , ' N ' ) ) ! = NULL )
{
begin = strchr ( npos , ' ' ) + 1 ;
end = strchr ( npos , ' * ' ) - 1 ;
}
end [ 1 ] = ' \r ' ;
end [ 2 ] = ' \n ' ;
end [ 3 ] = ' \0 ' ;
file . write ( begin ) ;
if ( file . writeError )
{
SERIAL_ERROR_START ;
2018-11-22 16:57:52 +00:00
SERIAL_ERRORLNRPGM ( MSG_SD_ERR_WRITE_TO_FILE ) ;
2016-07-22 13:28:01 +00:00
}
}
2017-03-24 18:47:50 +00:00
# define CHUNK_SIZE 64
void CardReader : : write_command_no_newline ( char * buf )
{
file . write ( buf , CHUNK_SIZE ) ;
if ( file . writeError )
{
SERIAL_ERROR_START ;
2018-11-22 16:57:52 +00:00
SERIAL_ERRORLNRPGM ( MSG_SD_ERR_WRITE_TO_FILE ) ;
2021-01-26 07:24:18 +00:00
SERIAL_PROTOCOLLNPGM ( " An error while writing to the SD Card. " ) ;
2017-03-24 18:47:50 +00:00
}
}
2016-07-22 13:28:01 +00:00
void CardReader : : checkautostart ( bool force )
{
if ( ! force )
{
if ( ! autostart_stilltocheck )
return ;
2019-01-27 21:48:51 +00:00
if ( autostart_atmillis < _millis ( ) )
2016-07-22 13:28:01 +00:00
return ;
}
autostart_stilltocheck = false ;
if ( ! cardOK )
{
initsd ( ) ;
if ( ! cardOK ) //fail
return ;
}
char autoname [ 30 ] ;
sprintf_P ( autoname , PSTR ( " auto%i.g " ) , lastnr ) ;
for ( int8_t i = 0 ; i < ( int8_t ) strlen ( autoname ) ; i + + )
autoname [ i ] = tolower ( autoname [ i ] ) ;
dir_t p ;
root . rewind ( ) ;
bool found = false ;
while ( root . readDir ( p , NULL ) > 0 )
{
for ( int8_t i = 0 ; i < ( int8_t ) strlen ( ( char * ) p . name ) ; i + + )
p . name [ i ] = tolower ( p . name [ i ] ) ;
//Serial.print((char*)p.name);
//Serial.print(" ");
//Serial.println(autoname);
if ( p . name [ 9 ] ! = ' ~ ' ) //skip safety copies
if ( strncmp ( ( char * ) p . name , autoname , 5 ) = = 0 )
{
char cmd [ 30 ] ;
// M23: Select SD file
sprintf_P ( cmd , PSTR ( " M23 %s " ) , autoname ) ;
enquecommand ( cmd ) ;
// M24: Start/resume SD print
enquecommand_P ( PSTR ( " M24 " ) ) ;
found = true ;
}
}
if ( ! found )
lastnr = - 1 ;
else
lastnr + + ;
}
void CardReader : : closefile ( bool store_location )
{
file . sync ( ) ;
file . close ( ) ;
saving = false ;
logging = false ;
if ( store_location )
{
//future: store printer state, filename and position for continuing a stopped print
// so one can unplug the printer and continue printing the next day.
}
}
void CardReader : : getfilename ( uint16_t nr , const char * const match /*=NULL*/ )
{
curDir = & workDir ;
lsAction = LS_GetFilename ;
nrFiles = nr ;
curDir - > rewind ( ) ;
lsDive ( " " , * curDir , match ) ;
}
2017-12-11 10:30:49 +00:00
void CardReader : : getfilename_simple ( uint32_t position , const char * const match /*=NULL*/ )
{
curDir = & workDir ;
lsAction = LS_GetFilename ;
nrFiles = 0 ;
curDir - > seekSet ( position ) ;
lsDive ( " " , * curDir , match ) ;
}
2016-07-22 13:28:01 +00:00
uint16_t CardReader : : getnrfilenames ( )
{
curDir = & workDir ;
lsAction = LS_Count ;
nrFiles = 0 ;
curDir - > rewind ( ) ;
lsDive ( " " , * curDir ) ;
//SERIAL_ECHOLN(nrFiles);
return nrFiles ;
}
void CardReader : : chdir ( const char * relpath )
{
SdFile newfile ;
SdFile * parent = & root ;
if ( workDir . isOpen ( ) )
parent = & workDir ;
if ( ! newfile . open ( * parent , relpath , O_READ ) )
{
SERIAL_ECHO_START ;
2019-05-07 10:23:09 +00:00
SERIAL_ECHORPGM ( _n ( " Cannot enter subdir: " ) ) ; ////MSG_SD_CANT_ENTER_SUBDIR
2016-07-22 13:28:01 +00:00
SERIAL_ECHOLN ( relpath ) ;
}
else
{
if ( workDirDepth < MAX_DIR_DEPTH ) {
for ( int d = + + workDirDepth ; d - - ; )
workDirParents [ d + 1 ] = workDirParents [ d ] ;
workDirParents [ 0 ] = * parent ;
}
workDir = newfile ;
2017-12-11 10:30:49 +00:00
# ifdef SDCARD_SORT_ALPHA
presort ( ) ;
# endif
2016-07-22 13:28:01 +00:00
}
}
void CardReader : : updir ( )
{
if ( workDirDepth > 0 )
{
- - workDirDepth ;
workDir = workDirParents [ 0 ] ;
2018-07-25 13:51:00 +00:00
for ( unsigned int d = 0 ; d < workDirDepth ; d + + )
{
workDirParents [ d ] = workDirParents [ d + 1 ] ;
}
2017-12-11 10:30:49 +00:00
# ifdef SDCARD_SORT_ALPHA
2018-07-25 13:51:00 +00:00
presort ( ) ;
2017-12-11 10:30:49 +00:00
# endif
2016-07-22 13:28:01 +00:00
}
}
2017-12-10 10:08:50 +00:00
# ifdef SDCARD_SORT_ALPHA
/**
* Get the name of a file in the current directory by sort - index
*/
void CardReader : : getfilename_sorted ( const uint16_t nr ) {
getfilename (
# if SDSORT_GCODE
sort_alpha & &
# endif
( nr < sort_count ) ? sort_order [ nr ] : nr
) ;
}
/**
* Read all the files and produce a sort key
*
* We can do this in 3 ways . . .
* - Minimal RAM : Read two filenames at a time sorting along . . .
* - Some RAM : Buffer the directory just for this sort
* - Most RAM : Buffer the directory and return filenames from RAM
*/
void CardReader : : presort ( ) {
2017-12-11 10:30:49 +00:00
if ( farm_mode | | IS_SD_INSERTED = = false ) return ; //sorting is not used in farm mode
2017-12-10 10:08:50 +00:00
uint8_t sdSort = eeprom_read_byte ( ( uint8_t * ) EEPROM_SD_SORT ) ;
if ( sdSort = = SD_SORT_NONE ) return ; //sd sort is turned off
# if SDSORT_GCODE
if ( ! sort_alpha ) return ;
# endif
2017-12-11 10:30:49 +00:00
KEEPALIVE_STATE ( IN_HANDLER ) ;
2017-12-10 10:08:50 +00:00
// Throw away old sort index
flush_presort ( ) ;
// If there are files, sort up to the limit
uint16_t fileCnt = getnrfilenames ( ) ;
if ( fileCnt > 0 ) {
// Never sort more than the max allowed
// If you use folders to organize, 20 may be enough
2017-12-11 10:30:49 +00:00
if ( fileCnt > SDSORT_LIMIT ) {
2020-05-12 20:23:40 +00:00
lcd_show_fullscreen_message_and_wait_P ( _i ( " Some files will not be sorted. Max. No. of files in 1 folder for sorting is 100. " ) ) ; ////MSG_FILE_CNT c=20 r=6
2017-12-11 10:30:49 +00:00
fileCnt = SDSORT_LIMIT ;
}
2018-07-16 15:54:16 +00:00
lcd_clear ( ) ;
2017-12-11 10:30:49 +00:00
# if !SDSORT_USES_RAM
lcd_set_progress ( ) ;
# endif
2018-07-16 16:08:01 +00:00
lcd_puts_at_P ( 0 , 1 , _i ( " Sorting files " ) ) ; ////MSG_SORTING c=20 r=1
2017-12-10 10:08:50 +00:00
// Sort order is always needed. May be static or dynamic.
# if SDSORT_DYNAMIC_RAM
sort_order = new uint8_t [ fileCnt ] ;
# endif
// Use RAM to store the entire directory during pre-sort.
// SDSORT_LIMIT should be set to prevent over-allocation.
# if SDSORT_USES_RAM
// If using dynamic ram for names, allocate on the heap.
# if SDSORT_CACHE_NAMES
# if SDSORT_DYNAMIC_RAM
sortshort = new char * [ fileCnt ] ;
sortnames = new char * [ fileCnt ] ;
# endif
# elif SDSORT_USES_STACK
char sortnames [ fileCnt ] [ LONG_FILENAME_LENGTH ] ;
2019-04-16 10:15:46 +00:00
uint16_t modification_time [ fileCnt ] ;
uint16_t modification_date [ fileCnt ] ;
2017-12-10 10:08:50 +00:00
# endif
// Folder sorting needs 1 bit per entry for flags.
# if HAS_FOLDER_SORTING
# if SDSORT_DYNAMIC_RAM
isDir = new uint8_t [ ( fileCnt + 7 ) > > 3 ] ;
# elif SDSORT_USES_STACK
uint8_t isDir [ ( fileCnt + 7 ) > > 3 ] ;
# endif
# endif
# else // !SDSORT_USES_RAM
2017-12-11 10:30:49 +00:00
uint32_t positions [ fileCnt ] ;
2017-12-10 10:08:50 +00:00
// By default re-read the names from SD for every compare
// retaining only two filenames at a time. This is very
// slow but is safest and uses minimal RAM.
char name1 [ LONG_FILENAME_LENGTH + 1 ] ;
2020-02-14 08:09:15 +00:00
uint16_t crmod_time_bckp ;
uint16_t crmod_date_bckp ;
2017-12-10 10:08:50 +00:00
# endif
2017-12-11 10:30:49 +00:00
position = 0 ;
2017-12-10 10:08:50 +00:00
if ( fileCnt > 1 ) {
// Init sort order.
for ( uint16_t i = 0 ; i < fileCnt ; i + + ) {
2017-12-11 10:30:49 +00:00
if ( ! IS_SD_INSERTED ) return ;
manage_heater ( ) ;
2017-12-10 10:08:50 +00:00
sort_order [ i ] = i ;
2017-12-11 10:30:49 +00:00
positions [ i ] = position ;
getfilename ( i ) ;
2017-12-10 10:08:50 +00:00
// If using RAM then read all filenames now.
# if SDSORT_USES_RAM
getfilename ( i ) ;
# if SDSORT_DYNAMIC_RAM
// Use dynamic method to copy long filename
sortnames [ i ] = strdup ( LONGEST_FILENAME ) ;
# if SDSORT_CACHE_NAMES
// When caching also store the short name, since
// we're replacing the getfilename() behavior.
sortshort [ i ] = strdup ( filename ) ;
# endif
# else
// Copy filenames into the static array
strcpy ( sortnames [ i ] , LONGEST_FILENAME ) ;
2020-02-14 08:09:15 +00:00
modification_time [ i ] = crmodTime ;
modification_date [ i ] = crmodDate ;
2017-12-10 10:08:50 +00:00
# if SDSORT_CACHE_NAMES
strcpy ( sortshort [ i ] , filename ) ;
# endif
# endif
// char out[30];
// sprintf_P(out, PSTR("---- %i %s %s"), i, filenameIsDir ? "D" : " ", sortnames[i]);
// SERIAL_ECHOLN(out);
# if HAS_FOLDER_SORTING
const uint16_t bit = i & 0x07 , ind = i > > 3 ;
if ( bit = = 0 ) isDir [ ind ] = 0x00 ;
if ( filenameIsDir ) isDir [ ind ] | = _BV ( bit ) ;
# endif
# endif
}
2017-12-11 10:30:49 +00:00
# ifdef QUICKSORT
quicksort ( 0 , fileCnt - 1 ) ;
# else //Qicksort not defined, use Bubble Sort
uint32_t counter = 0 ;
uint16_t total = 0.5 * ( fileCnt - 1 ) * ( fileCnt ) ;
// Compare names from the array or just the two buffered names
# if SDSORT_USES_RAM
# define _SORT_CMP_NODIR() (strcasecmp(sortnames[o1], sortnames[o2]) > 0)
2019-04-16 10:15:46 +00:00
# define _SORT_CMP_TIME_NODIR() (((modification_date[o1] == modification_date[o2]) && (modification_time[o1] < modification_time[o2])) || \
( modification_date [ o1 ] < modification_date [ o2 ] ) )
2017-12-11 10:30:49 +00:00
# else
# define _SORT_CMP_NODIR() (strcasecmp(name1, name2) > 0) //true if lowercase(name1) > lowercase(name2)
2020-02-14 08:09:15 +00:00
# define _SORT_CMP_TIME_NODIR() (((crmod_date_bckp == crmodDate) && (crmod_time_bckp > crmodTime)) || \
( crmod_date_bckp > crmodDate ) )
2017-12-11 10:30:49 +00:00
# endif
# if HAS_FOLDER_SORTING
# if SDSORT_USES_RAM
// Folder sorting needs an index and bit to test for folder-ness.
const uint8_t ind1 = o1 > > 3 , bit1 = o1 & 0x07 ,
ind2 = o2 > > 3 , bit2 = o2 & 0x07 ;
# define _SORT_CMP_DIR(fs) \
( ( ( isDir [ ind1 ] & _BV ( bit1 ) ) ! = 0 ) = = ( ( isDir [ ind2 ] & _BV ( bit2 ) ) ! = 0 ) \
? _SORT_CMP_NODIR ( ) \
: ( isDir [ fs > 0 ? ind1 : ind2 ] & ( fs > 0 ? _BV ( bit1 ) : _BV ( bit2 ) ) ) ! = 0 )
# define _SORT_CMP_TIME_DIR(fs) \
( ( ( isDir [ ind1 ] & _BV ( bit1 ) ) ! = 0 ) = = ( ( isDir [ ind2 ] & _BV ( bit2 ) ) ! = 0 ) \
? _SORT_CMP_TIME_NODIR ( ) \
: ( isDir [ fs > 0 ? ind1 : ind2 ] & ( fs > 0 ? _BV ( bit1 ) : _BV ( bit2 ) ) ) ! = 0 )
# else
# define _SORT_CMP_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_NODIR() : (fs > 0 ? dir1 : !dir1))
# define _SORT_CMP_TIME_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_TIME_NODIR() : (fs < 0 ? dir1 : !dir1))
# endif
# endif
2017-12-10 10:08:50 +00:00
for ( uint16_t i = fileCnt ; - - i ; ) {
2017-12-11 10:30:49 +00:00
if ( ! IS_SD_INSERTED ) return ;
2017-12-10 10:08:50 +00:00
bool didSwap = false ;
# if !SDSORT_USES_RAM //show progresss bar only if slow sorting method is used
2017-12-11 10:30:49 +00:00
int8_t percent = ( counter * 100 ) / total ; //((counter * 100) / pow((fileCnt-1),2));
2017-12-10 10:08:50 +00:00
for ( int column = 0 ; column < 20 ; column + + ) {
2018-07-16 17:29:27 +00:00
if ( column < ( percent / 5 ) )
{
2021-01-25 13:58:57 +00:00
lcd_putc_at ( column , 2 , ' \x01 ' ) ; //simple progress bar
2018-07-16 17:29:27 +00:00
}
2017-12-10 10:08:50 +00:00
}
counter + + ;
# endif
//MYSERIAL.println(int(i));
for ( uint16_t j = 0 ; j < i ; + + j ) {
2017-12-11 10:30:49 +00:00
if ( ! IS_SD_INSERTED ) return ;
manage_heater ( ) ;
2017-12-10 10:08:50 +00:00
const uint16_t o1 = sort_order [ j ] , o2 = sort_order [ j + 1 ] ;
// The most economical method reads names as-needed
// throughout the loop. Slow if there are many.
# if !SDSORT_USES_RAM
2017-12-11 10:30:49 +00:00
counter + + ;
getfilename_simple ( positions [ o1 ] ) ;
2017-12-10 10:08:50 +00:00
strcpy ( name1 , LONGEST_FILENAME ) ; // save (or getfilename below will trounce it)
2020-02-14 08:09:15 +00:00
crmod_date_bckp = crmodDate ;
crmod_time_bckp = crmodTime ;
2017-12-10 10:08:50 +00:00
# if HAS_FOLDER_SORTING
bool dir1 = filenameIsDir ;
# endif
2017-12-11 10:30:49 +00:00
getfilename_simple ( positions [ o2 ] ) ;
2017-12-10 10:08:50 +00:00
char * name2 = LONGEST_FILENAME ; // use the string in-place
# endif // !SDSORT_USES_RAM
// Sort the current pair according to settings.
if (
# if HAS_FOLDER_SORTING
( sdSort = = SD_SORT_TIME & & _SORT_CMP_TIME_DIR ( FOLDER_SORTING ) ) | | ( sdSort = = SD_SORT_ALPHA & & _SORT_CMP_DIR ( FOLDER_SORTING ) )
# else
( sdSort = = SD_SORT_TIME & & _SORT_CMP_TIME_NODIR ( ) ) | | ( sdSort = = SD_SORT_ALPHA & & _SORT_CMP_NODIR ( ) )
# endif
)
{
sort_order [ j ] = o2 ;
sort_order [ j + 1 ] = o1 ;
didSwap = true ;
}
}
if ( ! didSwap ) break ;
} //end of bubble sort loop
2017-12-11 10:30:49 +00:00
# endif
2017-12-10 10:08:50 +00:00
// Using RAM but not keeping names around
# if (SDSORT_USES_RAM && !SDSORT_CACHE_NAMES)
# if SDSORT_DYNAMIC_RAM
for ( uint16_t i = 0 ; i < fileCnt ; + + i ) free ( sortnames [ i ] ) ;
# if HAS_FOLDER_SORTING
free ( isDir ) ;
# endif
# endif
# endif
}
else {
sort_order [ 0 ] = 0 ;
# if (SDSORT_USES_RAM && SDSORT_CACHE_NAMES)
getfilename ( 0 ) ;
# if SDSORT_DYNAMIC_RAM
sortnames = new char * [ 1 ] ;
sortnames [ 0 ] = strdup ( LONGEST_FILENAME ) ; // malloc
sortshort = new char * [ 1 ] ;
sortshort [ 0 ] = strdup ( filename ) ; // malloc
isDir = new uint8_t [ 1 ] ;
# else
strcpy ( sortnames [ 0 ] , LONGEST_FILENAME ) ;
strcpy ( sortshort [ 0 ] , filename ) ;
# endif
isDir [ 0 ] = filenameIsDir ? 0x01 : 0x00 ;
# endif
}
sort_count = fileCnt ;
}
# if !SDSORT_USES_RAM //show progresss bar only if slow sorting method is used
2018-07-16 17:29:27 +00:00
for ( int column = 0 ; column < = 19 ; column + + )
{
2021-01-25 13:58:57 +00:00
lcd_putc_at ( column , 2 , ' \x01 ' ) ; //simple progress bar
2018-07-16 17:29:27 +00:00
}
2019-01-27 21:48:51 +00:00
_delay ( 300 ) ;
2017-12-11 10:30:49 +00:00
lcd_set_degree ( ) ;
2018-07-16 15:54:16 +00:00
lcd_clear ( ) ;
2017-12-10 10:08:50 +00:00
# endif
2017-12-11 10:30:49 +00:00
lcd_update ( 2 ) ;
KEEPALIVE_STATE ( NOT_BUSY ) ;
2018-07-22 14:26:12 +00:00
lcd_timeoutToStatus . start ( ) ;
2017-12-10 10:08:50 +00:00
}
void CardReader : : flush_presort ( ) {
if ( sort_count > 0 ) {
# if SDSORT_DYNAMIC_RAM
delete sort_order ;
# if SDSORT_CACHE_NAMES
for ( uint8_t i = 0 ; i < sort_count ; + + i ) {
free ( sortshort [ i ] ) ; // strdup
free ( sortnames [ i ] ) ; // strdup
}
delete sortshort ;
delete sortnames ;
# endif
# endif
sort_count = 0 ;
}
}
# endif // SDCARD_SORT_ALPHA
2016-07-22 13:28:01 +00:00
void CardReader : : printingHasFinished ( )
{
st_synchronize ( ) ;
if ( file_subcall_ctr > 0 ) //heading up to a parent file that called current as a procedure.
{
file . close ( ) ;
file_subcall_ctr - - ;
openFile ( filenames [ file_subcall_ctr ] , true , true ) ;
setIndex ( filespos [ file_subcall_ctr ] ) ;
startFileprint ( ) ;
}
else
{
quickStop ( ) ;
file . close ( ) ;
sdprinting = false ;
if ( SD_FINISHED_STEPPERRELEASE )
{
2017-12-06 13:55:53 +00:00
finishAndDisableSteppers ( ) ;
//enquecommand_P(PSTR(SD_FINISHED_RELEASECOMMAND));
2016-07-22 13:28:01 +00:00
}
autotempShutdown ( ) ;
2017-12-10 10:08:50 +00:00
# ifdef SDCARD_SORT_ALPHA
2017-12-12 14:26:48 +00:00
//presort();
2017-12-10 10:08:50 +00:00
# endif
2016-07-22 13:28:01 +00:00
}
}
bool CardReader : : ToshibaFlashAir_GetIP ( uint8_t * ip )
{
memset ( ip , 0 , 4 ) ;
return card . readExtMemory ( 1 , 1 , 0x400 + 0x150 , 4 , ip ) ;
}
# endif //SDSUPPORT