1 |
ron |
1 |
/**
|
|
|
2 |
* Marlin 3D Printer Firmware
|
|
|
3 |
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
|
|
4 |
*
|
|
|
5 |
* Based on Sprinter and grbl.
|
|
|
6 |
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
|
|
|
7 |
*
|
|
|
8 |
* This program is free software: you can redistribute it and/or modify
|
|
|
9 |
* it under the terms of the GNU General Public License as published by
|
|
|
10 |
* the Free Software Foundation, either version 3 of the License, or
|
|
|
11 |
* (at your option) any later version.
|
|
|
12 |
*
|
|
|
13 |
* This program is distributed in the hope that it will be useful,
|
|
|
14 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
15 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
16 |
* GNU General Public License for more details.
|
|
|
17 |
*
|
|
|
18 |
* You should have received a copy of the GNU General Public License
|
|
|
19 |
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
20 |
*
|
|
|
21 |
*/
|
|
|
22 |
|
|
|
23 |
#include "MarlinConfig.h"
|
|
|
24 |
|
|
|
25 |
#if ENABLED(SDSUPPORT)
|
|
|
26 |
|
|
|
27 |
#include "cardreader.h"
|
|
|
28 |
|
|
|
29 |
#include "ultralcd.h"
|
|
|
30 |
#include "stepper.h"
|
|
|
31 |
#include "language.h"
|
|
|
32 |
#include "printcounter.h"
|
|
|
33 |
|
|
|
34 |
#if ENABLED(POWER_LOSS_RECOVERY)
|
|
|
35 |
#include "power_loss_recovery.h"
|
|
|
36 |
#endif
|
|
|
37 |
|
|
|
38 |
CardReader::CardReader() {
|
|
|
39 |
#if ENABLED(SDCARD_SORT_ALPHA)
|
|
|
40 |
sort_count = 0;
|
|
|
41 |
#if ENABLED(SDSORT_GCODE)
|
|
|
42 |
sort_alpha = true;
|
|
|
43 |
sort_folders = FOLDER_SORTING;
|
|
|
44 |
//sort_reverse = false;
|
|
|
45 |
#endif
|
|
|
46 |
#endif
|
|
|
47 |
sdprinting = cardOK = saving = logging = false;
|
|
|
48 |
filesize = 0;
|
|
|
49 |
sdpos = 0;
|
|
|
50 |
file_subcall_ctr = 0;
|
|
|
51 |
|
|
|
52 |
workDirDepth = 0;
|
|
|
53 |
ZERO(workDirParents);
|
|
|
54 |
|
|
|
55 |
// Disable autostart until card is initialized
|
|
|
56 |
autostart_index = -1;
|
|
|
57 |
|
|
|
58 |
//power to SD reader
|
|
|
59 |
#if SDPOWER > -1
|
|
|
60 |
OUT_WRITE(SDPOWER, HIGH);
|
|
|
61 |
#endif
|
|
|
62 |
}
|
|
|
63 |
|
|
|
64 |
char *createFilename(char *buffer, const dir_t &p) { //buffer > 12characters
|
|
|
65 |
char *pos = buffer;
|
|
|
66 |
for (uint8_t i = 0; i < 11; i++) {
|
|
|
67 |
if (p.name[i] == ' ') continue;
|
|
|
68 |
if (i == 8) *pos++ = '.';
|
|
|
69 |
*pos++ = p.name[i];
|
|
|
70 |
}
|
|
|
71 |
*pos++ = 0;
|
|
|
72 |
return buffer;
|
|
|
73 |
}
|
|
|
74 |
|
|
|
75 |
/**
|
|
|
76 |
* Dive into a folder and recurse depth-first to perform a pre-set operation lsAction:
|
|
|
77 |
* LS_Count - Add +1 to nrFiles for every file within the parent
|
|
|
78 |
* LS_GetFilename - Get the filename of the file indexed by nrFile_index
|
|
|
79 |
* LS_SerialPrint - Print the full path and size of each file to serial output
|
|
|
80 |
*/
|
|
|
81 |
|
|
|
82 |
uint16_t nrFile_index;
|
|
|
83 |
|
|
|
84 |
void CardReader::lsDive(const char *prepend, SdFile parent, const char * const match/*=NULL*/) {
|
|
|
85 |
dir_t p;
|
|
|
86 |
uint8_t cnt = 0;
|
|
|
87 |
|
|
|
88 |
// Read the next entry from a directory
|
|
|
89 |
while (parent.readDir(&p, longFilename) > 0) {
|
|
|
90 |
|
|
|
91 |
// If the entry is a directory and the action is LS_SerialPrint
|
|
|
92 |
if (DIR_IS_SUBDIR(&p) && lsAction != LS_Count && lsAction != LS_GetFilename) {
|
|
|
93 |
|
|
|
94 |
// Get the short name for the item, which we know is a folder
|
|
|
95 |
char dosFilename[FILENAME_LENGTH];
|
|
|
96 |
createFilename(dosFilename, p);
|
|
|
97 |
|
|
|
98 |
// Allocate enough stack space for the full path to a folder, trailing slash, and nul
|
|
|
99 |
const bool prepend_is_empty = (!prepend || prepend[0] == '\0');
|
|
|
100 |
const int len = (prepend_is_empty ? 1 : strlen(prepend)) + strlen(dosFilename) + 1 + 1;
|
|
|
101 |
char path[len];
|
|
|
102 |
|
|
|
103 |
// Append the FOLDERNAME12/ to the passed string.
|
|
|
104 |
// It contains the full path to the "parent" argument.
|
|
|
105 |
// We now have the full path to the item in this folder.
|
|
|
106 |
strcpy(path, prepend_is_empty ? "/" : prepend); // root slash if prepend is empty
|
|
|
107 |
strcat(path, dosFilename); // FILENAME_LENGTH-1 characters maximum
|
|
|
108 |
strcat(path, "/"); // 1 character
|
|
|
109 |
|
|
|
110 |
// Serial.print(path);
|
|
|
111 |
|
|
|
112 |
// Get a new directory object using the full path
|
|
|
113 |
// and dive recursively into it.
|
|
|
114 |
SdFile dir;
|
|
|
115 |
if (!dir.open(&parent, dosFilename, O_READ)) {
|
|
|
116 |
if (lsAction == LS_SerialPrint) {
|
|
|
117 |
SERIAL_ECHO_START();
|
|
|
118 |
SERIAL_ECHOPGM(MSG_SD_CANT_OPEN_SUBDIR);
|
|
|
119 |
SERIAL_ECHOLN(dosFilename);
|
|
|
120 |
}
|
|
|
121 |
}
|
|
|
122 |
lsDive(path, dir);
|
|
|
123 |
// close() is done automatically by destructor of SdFile
|
|
|
124 |
}
|
|
|
125 |
else {
|
|
|
126 |
uint8_t pn0 = p.name[0];
|
|
|
127 |
if (pn0 == DIR_NAME_FREE) break;
|
|
|
128 |
if (pn0 == DIR_NAME_DELETED || pn0 == '.') continue;
|
|
|
129 |
if (longFilename[0] == '.') continue;
|
|
|
130 |
|
|
|
131 |
if (!DIR_IS_FILE_OR_SUBDIR(&p) || (p.attributes & DIR_ATT_HIDDEN)) continue;
|
|
|
132 |
|
|
|
133 |
filenameIsDir = DIR_IS_SUBDIR(&p);
|
|
|
134 |
|
|
|
135 |
if (!filenameIsDir && (p.name[8] != 'G' || p.name[9] == '~')) continue;
|
|
|
136 |
|
|
|
137 |
switch (lsAction) { // 1 based file count
|
|
|
138 |
case LS_Count:
|
|
|
139 |
nrFiles++;
|
|
|
140 |
break;
|
|
|
141 |
|
|
|
142 |
case LS_SerialPrint:
|
|
|
143 |
createFilename(filename, p);
|
|
|
144 |
if (prepend) SERIAL_PROTOCOL(prepend);
|
|
|
145 |
SERIAL_PROTOCOL(filename);
|
|
|
146 |
SERIAL_PROTOCOLCHAR(' ');
|
|
|
147 |
SERIAL_PROTOCOLLN(p.fileSize);
|
|
|
148 |
break;
|
|
|
149 |
|
|
|
150 |
case LS_GetFilename:
|
|
|
151 |
createFilename(filename, p);
|
|
|
152 |
if (match != NULL) {
|
|
|
153 |
if (strcasecmp(match, filename) == 0) return;
|
|
|
154 |
}
|
|
|
155 |
else if (cnt == nrFile_index) return; // 0 based index
|
|
|
156 |
cnt++;
|
|
|
157 |
break;
|
|
|
158 |
}
|
|
|
159 |
|
|
|
160 |
}
|
|
|
161 |
} // while readDir
|
|
|
162 |
}
|
|
|
163 |
|
|
|
164 |
void CardReader::ls() {
|
|
|
165 |
lsAction = LS_SerialPrint;
|
|
|
166 |
root.rewind();
|
|
|
167 |
lsDive(NULL, root);
|
|
|
168 |
}
|
|
|
169 |
|
|
|
170 |
#if ENABLED(LONG_FILENAME_HOST_SUPPORT)
|
|
|
171 |
|
|
|
172 |
/**
|
|
|
173 |
* Get a long pretty path based on a DOS 8.3 path
|
|
|
174 |
*/
|
|
|
175 |
void CardReader::printLongPath(char *path) {
|
|
|
176 |
lsAction = LS_GetFilename;
|
|
|
177 |
|
|
|
178 |
int i, pathLen = strlen(path);
|
|
|
179 |
|
|
|
180 |
// SERIAL_ECHOPGM("Full Path: "); SERIAL_ECHOLN(path);
|
|
|
181 |
|
|
|
182 |
// Zero out slashes to make segments
|
|
|
183 |
for (i = 0; i < pathLen; i++) if (path[i] == '/') path[i] = '\0';
|
|
|
184 |
|
|
|
185 |
SdFile diveDir = root; // start from the root for segment 1
|
|
|
186 |
for (i = 0; i < pathLen;) {
|
|
|
187 |
|
|
|
188 |
if (path[i] == '\0') i++; // move past a single nul
|
|
|
189 |
|
|
|
190 |
char *segment = &path[i]; // The segment after most slashes
|
|
|
191 |
|
|
|
192 |
// If a segment is empty (extra-slash) then exit
|
|
|
193 |
if (!*segment) break;
|
|
|
194 |
|
|
|
195 |
// Go to the next segment
|
|
|
196 |
while (path[++i]) { }
|
|
|
197 |
|
|
|
198 |
// SERIAL_ECHOPGM("Looking for segment: "); SERIAL_ECHOLN(segment);
|
|
|
199 |
|
|
|
200 |
// Find the item, setting the long filename
|
|
|
201 |
diveDir.rewind();
|
|
|
202 |
lsDive(NULL, diveDir, segment);
|
|
|
203 |
|
|
|
204 |
// Print /LongNamePart to serial output
|
|
|
205 |
SERIAL_PROTOCOLCHAR('/');
|
|
|
206 |
SERIAL_PROTOCOL(longFilename[0] ? longFilename : "???");
|
|
|
207 |
|
|
|
208 |
// If the filename was printed then that's it
|
|
|
209 |
if (!filenameIsDir) break;
|
|
|
210 |
|
|
|
211 |
// SERIAL_ECHOPGM("Opening dir: "); SERIAL_ECHOLN(segment);
|
|
|
212 |
|
|
|
213 |
// Open the sub-item as the new dive parent
|
|
|
214 |
SdFile dir;
|
|
|
215 |
if (!dir.open(&diveDir, segment, O_READ)) {
|
|
|
216 |
SERIAL_EOL();
|
|
|
217 |
SERIAL_ECHO_START();
|
|
|
218 |
SERIAL_ECHOPGM(MSG_SD_CANT_OPEN_SUBDIR);
|
|
|
219 |
SERIAL_ECHO(segment);
|
|
|
220 |
break;
|
|
|
221 |
}
|
|
|
222 |
|
|
|
223 |
diveDir.close();
|
|
|
224 |
diveDir = dir;
|
|
|
225 |
|
|
|
226 |
} // while i<pathLen
|
|
|
227 |
|
|
|
228 |
SERIAL_EOL();
|
|
|
229 |
}
|
|
|
230 |
|
|
|
231 |
#endif // LONG_FILENAME_HOST_SUPPORT
|
|
|
232 |
|
|
|
233 |
/**
|
|
|
234 |
* Echo the DOS 8.3 filename (and long filename, if any)
|
|
|
235 |
*/
|
|
|
236 |
void CardReader::printFilename() {
|
|
|
237 |
if (file.isOpen()) {
|
|
|
238 |
char dosFilename[FILENAME_LENGTH];
|
|
|
239 |
file.getFilename(dosFilename);
|
|
|
240 |
SERIAL_ECHO(dosFilename);
|
|
|
241 |
#if ENABLED(LONG_FILENAME_HOST_SUPPORT)
|
|
|
242 |
getfilename(0, dosFilename);
|
|
|
243 |
if (longFilename[0]) {
|
|
|
244 |
SERIAL_ECHO(' ');
|
|
|
245 |
SERIAL_ECHO(longFilename);
|
|
|
246 |
}
|
|
|
247 |
#endif
|
|
|
248 |
}
|
|
|
249 |
else
|
|
|
250 |
SERIAL_ECHOPGM("(no file)");
|
|
|
251 |
|
|
|
252 |
SERIAL_EOL();
|
|
|
253 |
}
|
|
|
254 |
|
|
|
255 |
void CardReader::initsd() {
|
|
|
256 |
cardOK = false;
|
|
|
257 |
if (root.isOpen()) root.close();
|
|
|
258 |
|
|
|
259 |
#ifndef SPI_SPEED
|
|
|
260 |
#define SPI_SPEED SPI_FULL_SPEED
|
|
|
261 |
#endif
|
|
|
262 |
|
|
|
263 |
if (!sd2card.init(SPI_SPEED, SDSS)
|
|
|
264 |
#if defined(LCD_SDSS) && (LCD_SDSS != SDSS)
|
|
|
265 |
&& !sd2card.init(SPI_SPEED, LCD_SDSS)
|
|
|
266 |
#endif
|
|
|
267 |
) {
|
|
|
268 |
//if (!sd2card.init(SPI_HALF_SPEED,SDSS))
|
|
|
269 |
SERIAL_ECHO_START();
|
|
|
270 |
SERIAL_ECHOLNPGM(MSG_SD_INIT_FAIL);
|
|
|
271 |
}
|
|
|
272 |
else if (!volume.init(&sd2card)) {
|
|
|
273 |
SERIAL_ERROR_START();
|
|
|
274 |
SERIAL_ERRORLNPGM(MSG_SD_VOL_INIT_FAIL);
|
|
|
275 |
}
|
|
|
276 |
else if (!root.openRoot(&volume)) {
|
|
|
277 |
SERIAL_ERROR_START();
|
|
|
278 |
SERIAL_ERRORLNPGM(MSG_SD_OPENROOT_FAIL);
|
|
|
279 |
}
|
|
|
280 |
else {
|
|
|
281 |
cardOK = true;
|
|
|
282 |
SERIAL_ECHO_START();
|
|
|
283 |
SERIAL_ECHOLNPGM(MSG_SD_CARD_OK);
|
|
|
284 |
}
|
|
|
285 |
setroot();
|
|
|
286 |
}
|
|
|
287 |
|
|
|
288 |
void CardReader::release() {
|
|
|
289 |
sdprinting = false;
|
|
|
290 |
cardOK = false;
|
|
|
291 |
}
|
|
|
292 |
|
|
|
293 |
void CardReader::openAndPrintFile(const char *name) {
|
|
|
294 |
char cmd[4 + strlen(name) + 1]; // Room for "M23 ", filename, and null
|
|
|
295 |
sprintf_P(cmd, PSTR("M23 %s"), name);
|
|
|
296 |
for (char *c = &cmd[4]; *c; c++) *c = tolower(*c);
|
|
|
297 |
enqueue_and_echo_command_now(cmd);
|
|
|
298 |
enqueue_and_echo_commands_P(PSTR("M24"));
|
|
|
299 |
}
|
|
|
300 |
|
|
|
301 |
void CardReader::startFileprint() {
|
|
|
302 |
if (cardOK) {
|
|
|
303 |
sdprinting = true;
|
|
|
304 |
#if SD_RESORT
|
|
|
305 |
flush_presort();
|
|
|
306 |
#endif
|
|
|
307 |
}
|
|
|
308 |
}
|
|
|
309 |
|
|
|
310 |
void CardReader::stopSDPrint(
|
|
|
311 |
#if SD_RESORT
|
|
|
312 |
const bool re_sort/*=false*/
|
|
|
313 |
#endif
|
|
|
314 |
) {
|
|
|
315 |
#if ENABLED(ADVANCED_PAUSE_FEATURE)
|
|
|
316 |
did_pause_print = 0;
|
|
|
317 |
#endif
|
|
|
318 |
sdprinting = abort_sd_printing = false;
|
|
|
319 |
if (isFileOpen()) file.close();
|
|
|
320 |
#if SD_RESORT
|
|
|
321 |
if (re_sort) presort();
|
|
|
322 |
#endif
|
|
|
323 |
}
|
|
|
324 |
|
|
|
325 |
void CardReader::openLogFile(char * const path) {
|
|
|
326 |
logging = true;
|
|
|
327 |
openFile(path, false);
|
|
|
328 |
}
|
|
|
329 |
|
|
|
330 |
void appendAtom(SdFile &file, char *& dst, uint8_t &cnt) {
|
|
|
331 |
file.getFilename(dst);
|
|
|
332 |
while (*dst && cnt < MAXPATHNAMELENGTH) { dst++; cnt++; }
|
|
|
333 |
if (cnt < MAXPATHNAMELENGTH) { *dst = '/'; dst++; cnt++; }
|
|
|
334 |
}
|
|
|
335 |
|
|
|
336 |
void CardReader::getAbsFilename(char *t) {
|
|
|
337 |
*t++ = '/'; // Root folder
|
|
|
338 |
uint8_t cnt = 1;
|
|
|
339 |
|
|
|
340 |
for (uint8_t i = 0; i < workDirDepth; i++) // Loop to current work dir
|
|
|
341 |
appendAtom(workDirParents[i], t, cnt);
|
|
|
342 |
|
|
|
343 |
if (cnt < MAXPATHNAMELENGTH - (FILENAME_LENGTH)) {
|
|
|
344 |
appendAtom(file, t, cnt);
|
|
|
345 |
--t;
|
|
|
346 |
}
|
|
|
347 |
*t = '\0';
|
|
|
348 |
}
|
|
|
349 |
|
|
|
350 |
void CardReader::openFile(char * const path, const bool read, const bool subcall/*=false*/) {
|
|
|
351 |
|
|
|
352 |
if (!cardOK) return;
|
|
|
353 |
|
|
|
354 |
uint8_t doing = 0;
|
|
|
355 |
if (isFileOpen()) { // Replacing current file or doing a subroutine
|
|
|
356 |
if (subcall) {
|
|
|
357 |
if (file_subcall_ctr > SD_PROCEDURE_DEPTH - 1) {
|
|
|
358 |
SERIAL_ERROR_START();
|
|
|
359 |
SERIAL_ERRORPGM("trying to call sub-gcode files with too many levels. MAX level is:");
|
|
|
360 |
SERIAL_ERRORLN((int)SD_PROCEDURE_DEPTH);
|
|
|
361 |
kill(PSTR(MSG_KILLED));
|
|
|
362 |
return;
|
|
|
363 |
}
|
|
|
364 |
|
|
|
365 |
// Store current filename (based on workDirParents) and position
|
|
|
366 |
getAbsFilename(proc_filenames[file_subcall_ctr]);
|
|
|
367 |
filespos[file_subcall_ctr] = sdpos;
|
|
|
368 |
|
|
|
369 |
SERIAL_ECHO_START();
|
|
|
370 |
SERIAL_ECHOPAIR("SUBROUTINE CALL target:\"", path);
|
|
|
371 |
SERIAL_ECHOPAIR("\" parent:\"", proc_filenames[file_subcall_ctr]);
|
|
|
372 |
SERIAL_ECHOLNPAIR("\" pos", sdpos);
|
|
|
373 |
file_subcall_ctr++;
|
|
|
374 |
}
|
|
|
375 |
else
|
|
|
376 |
doing = 1;
|
|
|
377 |
}
|
|
|
378 |
else if (subcall) { // Returning from a subcall?
|
|
|
379 |
SERIAL_ECHO_START();
|
|
|
380 |
SERIAL_ECHOLNPGM("END SUBROUTINE");
|
|
|
381 |
}
|
|
|
382 |
else { // Opening fresh file
|
|
|
383 |
doing = 2;
|
|
|
384 |
file_subcall_ctr = 0; // Reset procedure depth in case user cancels print while in procedure
|
|
|
385 |
}
|
|
|
386 |
|
|
|
387 |
if (doing) {
|
|
|
388 |
SERIAL_ECHO_START();
|
|
|
389 |
SERIAL_ECHOPGM("Now ");
|
|
|
390 |
serialprintPGM(doing == 1 ? PSTR("doing") : PSTR("fresh"));
|
|
|
391 |
SERIAL_ECHOLNPAIR(" file: ", path);
|
|
|
392 |
}
|
|
|
393 |
|
|
|
394 |
stopSDPrint();
|
|
|
395 |
|
|
|
396 |
SdFile *curDir;
|
|
|
397 |
const char * const fname = diveToFile(curDir, path, false);
|
|
|
398 |
if (!fname) return;
|
|
|
399 |
|
|
|
400 |
if (read) {
|
|
|
401 |
if (file.open(curDir, fname, O_READ)) {
|
|
|
402 |
filesize = file.fileSize();
|
|
|
403 |
sdpos = 0;
|
|
|
404 |
SERIAL_PROTOCOLPAIR(MSG_SD_FILE_OPENED, fname);
|
|
|
405 |
SERIAL_PROTOCOLLNPAIR(MSG_SD_SIZE, filesize);
|
|
|
406 |
SERIAL_PROTOCOLLNPGM(MSG_SD_FILE_SELECTED);
|
|
|
407 |
|
|
|
408 |
getfilename(0, fname);
|
|
|
409 |
lcd_setstatus(longFilename[0] ? longFilename : fname);
|
|
|
410 |
//if (longFilename[0]) {
|
|
|
411 |
// SERIAL_PROTOCOLPAIR(MSG_SD_FILE_LONG_NAME, longFilename);
|
|
|
412 |
//}
|
|
|
413 |
}
|
|
|
414 |
else {
|
|
|
415 |
SERIAL_PROTOCOLPAIR(MSG_SD_OPEN_FILE_FAIL, fname);
|
|
|
416 |
SERIAL_PROTOCOLCHAR('.');
|
|
|
417 |
SERIAL_EOL();
|
|
|
418 |
}
|
|
|
419 |
}
|
|
|
420 |
else { //write
|
|
|
421 |
if (!file.open(curDir, fname, O_CREAT | O_APPEND | O_WRITE | O_TRUNC)) {
|
|
|
422 |
SERIAL_PROTOCOLPAIR(MSG_SD_OPEN_FILE_FAIL, fname);
|
|
|
423 |
SERIAL_PROTOCOLCHAR('.');
|
|
|
424 |
SERIAL_EOL();
|
|
|
425 |
}
|
|
|
426 |
else {
|
|
|
427 |
saving = true;
|
|
|
428 |
SERIAL_PROTOCOLLNPAIR(MSG_SD_WRITE_TO_FILE, path);
|
|
|
429 |
lcd_setstatus(fname);
|
|
|
430 |
}
|
|
|
431 |
}
|
|
|
432 |
}
|
|
|
433 |
|
|
|
434 |
void CardReader::removeFile(const char * const name) {
|
|
|
435 |
if (!cardOK) return;
|
|
|
436 |
|
|
|
437 |
stopSDPrint();
|
|
|
438 |
|
|
|
439 |
SdFile *curDir;
|
|
|
440 |
const char * const fname = diveToFile(curDir, name, false);
|
|
|
441 |
if (!fname) return;
|
|
|
442 |
|
|
|
443 |
if (file.remove(curDir, fname)) {
|
|
|
444 |
SERIAL_PROTOCOLPGM("File deleted:");
|
|
|
445 |
SERIAL_PROTOCOLLN(fname);
|
|
|
446 |
sdpos = 0;
|
|
|
447 |
#if ENABLED(SDCARD_SORT_ALPHA)
|
|
|
448 |
presort();
|
|
|
449 |
#endif
|
|
|
450 |
}
|
|
|
451 |
else {
|
|
|
452 |
SERIAL_PROTOCOLPGM("Deletion failed, File: ");
|
|
|
453 |
SERIAL_PROTOCOL(fname);
|
|
|
454 |
SERIAL_PROTOCOLCHAR('.');
|
|
|
455 |
}
|
|
|
456 |
}
|
|
|
457 |
|
|
|
458 |
void CardReader::getStatus() {
|
|
|
459 |
if (cardOK && sdprinting) {
|
|
|
460 |
SERIAL_PROTOCOLPGM(MSG_SD_PRINTING_BYTE);
|
|
|
461 |
SERIAL_PROTOCOL(sdpos);
|
|
|
462 |
SERIAL_PROTOCOLCHAR('/');
|
|
|
463 |
SERIAL_PROTOCOLLN(filesize);
|
|
|
464 |
}
|
|
|
465 |
else
|
|
|
466 |
SERIAL_PROTOCOLLNPGM(MSG_SD_NOT_PRINTING);
|
|
|
467 |
}
|
|
|
468 |
|
|
|
469 |
void CardReader::write_command(char *buf) {
|
|
|
470 |
char* begin = buf;
|
|
|
471 |
char* npos = NULL;
|
|
|
472 |
char* end = buf + strlen(buf) - 1;
|
|
|
473 |
|
|
|
474 |
file.writeError = false;
|
|
|
475 |
if ((npos = strchr(buf, 'N')) != NULL) {
|
|
|
476 |
begin = strchr(npos, ' ') + 1;
|
|
|
477 |
end = strchr(npos, '*') - 1;
|
|
|
478 |
}
|
|
|
479 |
end[1] = '\r';
|
|
|
480 |
end[2] = '\n';
|
|
|
481 |
end[3] = '\0';
|
|
|
482 |
file.write(begin);
|
|
|
483 |
if (file.writeError) {
|
|
|
484 |
SERIAL_ERROR_START();
|
|
|
485 |
SERIAL_ERRORLNPGM(MSG_SD_ERR_WRITE_TO_FILE);
|
|
|
486 |
}
|
|
|
487 |
}
|
|
|
488 |
|
|
|
489 |
//
|
|
|
490 |
// Run the next autostart file. Called:
|
|
|
491 |
// - On boot after successful card init
|
|
|
492 |
// - After finishing the previous autostart file
|
|
|
493 |
// - From the LCD command to run the autostart file
|
|
|
494 |
//
|
|
|
495 |
|
|
|
496 |
void CardReader::checkautostart() {
|
|
|
497 |
|
|
|
498 |
if (autostart_index < 0 || sdprinting) return;
|
|
|
499 |
|
|
|
500 |
if (!cardOK) initsd();
|
|
|
501 |
|
|
|
502 |
if (cardOK
|
|
|
503 |
#if ENABLED(POWER_LOSS_RECOVERY)
|
|
|
504 |
&& !jobRecoverFileExists() // Don't run auto#.g when a resume file exists
|
|
|
505 |
#endif
|
|
|
506 |
) {
|
|
|
507 |
char autoname[10];
|
|
|
508 |
sprintf_P(autoname, PSTR("auto%i.g"), int(autostart_index));
|
|
|
509 |
dir_t p;
|
|
|
510 |
root.rewind();
|
|
|
511 |
while (root.readDir(&p, NULL) > 0) {
|
|
|
512 |
for (int8_t i = (int8_t)strlen((char*)p.name); i--;) p.name[i] = tolower(p.name[i]);
|
|
|
513 |
if (p.name[9] != '~' && strncmp((char*)p.name, autoname, 5) == 0) {
|
|
|
514 |
openAndPrintFile(autoname);
|
|
|
515 |
autostart_index++;
|
|
|
516 |
return;
|
|
|
517 |
}
|
|
|
518 |
}
|
|
|
519 |
}
|
|
|
520 |
autostart_index = -1;
|
|
|
521 |
}
|
|
|
522 |
|
|
|
523 |
void CardReader::beginautostart() {
|
|
|
524 |
autostart_index = 0;
|
|
|
525 |
setroot();
|
|
|
526 |
}
|
|
|
527 |
|
|
|
528 |
void CardReader::closefile(const bool store_location) {
|
|
|
529 |
file.sync();
|
|
|
530 |
file.close();
|
|
|
531 |
saving = logging = false;
|
|
|
532 |
|
|
|
533 |
if (store_location) {
|
|
|
534 |
//future: store printer state, filename and position for continuing a stopped print
|
|
|
535 |
// so one can unplug the printer and continue printing the next day.
|
|
|
536 |
}
|
|
|
537 |
}
|
|
|
538 |
|
|
|
539 |
/**
|
|
|
540 |
* Get the name of a file in the current directory by index
|
|
|
541 |
* with optional name to match.
|
|
|
542 |
*/
|
|
|
543 |
void CardReader::getfilename(uint16_t nr, const char * const match/*=NULL*/) {
|
|
|
544 |
#if ENABLED(SDSORT_CACHE_NAMES)
|
|
|
545 |
if (match != NULL) {
|
|
|
546 |
while (nr < sort_count) {
|
|
|
547 |
if (strcasecmp(match, sortshort[nr]) == 0) break;
|
|
|
548 |
nr++;
|
|
|
549 |
}
|
|
|
550 |
}
|
|
|
551 |
if (nr < sort_count) {
|
|
|
552 |
strcpy(filename, sortshort[nr]);
|
|
|
553 |
strcpy(longFilename, sortnames[nr]);
|
|
|
554 |
filenameIsDir = TEST(isDir[nr>>3], nr & 0x07);
|
|
|
555 |
return;
|
|
|
556 |
}
|
|
|
557 |
#endif // SDSORT_CACHE_NAMES
|
|
|
558 |
lsAction = LS_GetFilename;
|
|
|
559 |
nrFile_index = nr;
|
|
|
560 |
workDir.rewind();
|
|
|
561 |
lsDive(NULL, workDir, match);
|
|
|
562 |
}
|
|
|
563 |
|
|
|
564 |
uint16_t CardReader::getnrfilenames() {
|
|
|
565 |
lsAction = LS_Count;
|
|
|
566 |
nrFiles = 0;
|
|
|
567 |
workDir.rewind();
|
|
|
568 |
lsDive(NULL, workDir);
|
|
|
569 |
//SERIAL_ECHOLN(nrFiles);
|
|
|
570 |
return nrFiles;
|
|
|
571 |
}
|
|
|
572 |
|
|
|
573 |
/**
|
|
|
574 |
* Dive to the given file path, with optional echo.
|
|
|
575 |
* On exit set curDir and return the name part of the path.
|
|
|
576 |
* A NULL result indicates an unrecoverable error.
|
|
|
577 |
*/
|
|
|
578 |
const char* CardReader::diveToFile(SdFile*& curDir, const char * const path, const bool echo) {
|
|
|
579 |
SdFile myDir;
|
|
|
580 |
if (path[0] != '/') { curDir = &workDir; return path; }
|
|
|
581 |
|
|
|
582 |
curDir = &root;
|
|
|
583 |
const char *dirname_start = &path[1];
|
|
|
584 |
while (dirname_start) {
|
|
|
585 |
char * const dirname_end = strchr(dirname_start, '/');
|
|
|
586 |
if (dirname_end <= dirname_start) break;
|
|
|
587 |
const uint8_t len = dirname_end - dirname_start;
|
|
|
588 |
char dosSubdirname[len + 1];
|
|
|
589 |
strncpy(dosSubdirname, dirname_start, len);
|
|
|
590 |
dosSubdirname[len] = 0;
|
|
|
591 |
|
|
|
592 |
if (echo) SERIAL_ECHOLN(dosSubdirname);
|
|
|
593 |
|
|
|
594 |
if (!myDir.open(curDir, dosSubdirname, O_READ)) {
|
|
|
595 |
SERIAL_PROTOCOLPAIR(MSG_SD_OPEN_FILE_FAIL, dosSubdirname);
|
|
|
596 |
SERIAL_PROTOCOLCHAR('.');
|
|
|
597 |
SERIAL_EOL();
|
|
|
598 |
return NULL;
|
|
|
599 |
}
|
|
|
600 |
curDir = &myDir;
|
|
|
601 |
dirname_start = dirname_end + 1;
|
|
|
602 |
}
|
|
|
603 |
return dirname_start;
|
|
|
604 |
}
|
|
|
605 |
|
|
|
606 |
void CardReader::chdir(const char * relpath) {
|
|
|
607 |
SdFile newDir;
|
|
|
608 |
SdFile *parent = workDir.isOpen() ? &workDir : &root;
|
|
|
609 |
|
|
|
610 |
if (newDir.open(parent, relpath, O_READ)) {
|
|
|
611 |
workDir = newDir;
|
|
|
612 |
if (workDirDepth < MAX_DIR_DEPTH)
|
|
|
613 |
workDirParents[workDirDepth++] = workDir;
|
|
|
614 |
#if ENABLED(SDCARD_SORT_ALPHA)
|
|
|
615 |
presort();
|
|
|
616 |
#endif
|
|
|
617 |
}
|
|
|
618 |
else {
|
|
|
619 |
SERIAL_ECHO_START();
|
|
|
620 |
SERIAL_ECHOPGM(MSG_SD_CANT_ENTER_SUBDIR);
|
|
|
621 |
SERIAL_ECHOLN(relpath);
|
|
|
622 |
}
|
|
|
623 |
}
|
|
|
624 |
|
|
|
625 |
int8_t CardReader::updir() {
|
|
|
626 |
if (workDirDepth > 0) { // At least 1 dir has been saved
|
|
|
627 |
workDir = --workDirDepth ? workDirParents[workDirDepth - 1] : root; // Use parent, or root if none
|
|
|
628 |
#if ENABLED(SDCARD_SORT_ALPHA)
|
|
|
629 |
presort();
|
|
|
630 |
#endif
|
|
|
631 |
}
|
|
|
632 |
return workDirDepth;
|
|
|
633 |
}
|
|
|
634 |
|
|
|
635 |
void CardReader::setroot() {
|
|
|
636 |
/*if (!workDir.openRoot(&volume)) {
|
|
|
637 |
SERIAL_ECHOLNPGM(MSG_SD_WORKDIR_FAIL);
|
|
|
638 |
}*/
|
|
|
639 |
workDir = root;
|
|
|
640 |
#if ENABLED(SDCARD_SORT_ALPHA)
|
|
|
641 |
presort();
|
|
|
642 |
#endif
|
|
|
643 |
}
|
|
|
644 |
|
|
|
645 |
#if ENABLED(SDCARD_SORT_ALPHA)
|
|
|
646 |
|
|
|
647 |
/**
|
|
|
648 |
* Get the name of a file in the current directory by sort-index
|
|
|
649 |
*/
|
|
|
650 |
void CardReader::getfilename_sorted(const uint16_t nr) {
|
|
|
651 |
getfilename(
|
|
|
652 |
#if ENABLED(SDSORT_GCODE)
|
|
|
653 |
sort_alpha &&
|
|
|
654 |
#endif
|
|
|
655 |
(nr < sort_count) ? sort_order[nr] : nr
|
|
|
656 |
);
|
|
|
657 |
}
|
|
|
658 |
|
|
|
659 |
/**
|
|
|
660 |
* Read all the files and produce a sort key
|
|
|
661 |
*
|
|
|
662 |
* We can do this in 3 ways...
|
|
|
663 |
* - Minimal RAM: Read two filenames at a time sorting along...
|
|
|
664 |
* - Some RAM: Buffer the directory just for this sort
|
|
|
665 |
* - Most RAM: Buffer the directory and return filenames from RAM
|
|
|
666 |
*/
|
|
|
667 |
void CardReader::presort() {
|
|
|
668 |
|
|
|
669 |
// Throw away old sort index
|
|
|
670 |
flush_presort();
|
|
|
671 |
|
|
|
672 |
// Sorting may be turned off
|
|
|
673 |
#if ENABLED(SDSORT_GCODE)
|
|
|
674 |
if (!sort_alpha) return;
|
|
|
675 |
#endif
|
|
|
676 |
|
|
|
677 |
// If there are files, sort up to the limit
|
|
|
678 |
uint16_t fileCnt = getnrfilenames();
|
|
|
679 |
if (fileCnt > 0) {
|
|
|
680 |
|
|
|
681 |
// Never sort more than the max allowed
|
|
|
682 |
// If you use folders to organize, 20 may be enough
|
|
|
683 |
if (fileCnt > SDSORT_LIMIT) fileCnt = SDSORT_LIMIT;
|
|
|
684 |
|
|
|
685 |
// Sort order is always needed. May be static or dynamic.
|
|
|
686 |
#if ENABLED(SDSORT_DYNAMIC_RAM)
|
|
|
687 |
sort_order = new uint8_t[fileCnt];
|
|
|
688 |
#endif
|
|
|
689 |
|
|
|
690 |
// Use RAM to store the entire directory during pre-sort.
|
|
|
691 |
// SDSORT_LIMIT should be set to prevent over-allocation.
|
|
|
692 |
#if ENABLED(SDSORT_USES_RAM)
|
|
|
693 |
|
|
|
694 |
// If using dynamic ram for names, allocate on the heap.
|
|
|
695 |
#if ENABLED(SDSORT_CACHE_NAMES)
|
|
|
696 |
#if ENABLED(SDSORT_DYNAMIC_RAM)
|
|
|
697 |
sortshort = new char*[fileCnt];
|
|
|
698 |
sortnames = new char*[fileCnt];
|
|
|
699 |
#endif
|
|
|
700 |
#elif ENABLED(SDSORT_USES_STACK)
|
|
|
701 |
char sortnames[fileCnt][SORTED_LONGNAME_MAXLEN];
|
|
|
702 |
#endif
|
|
|
703 |
|
|
|
704 |
// Folder sorting needs 1 bit per entry for flags.
|
|
|
705 |
#if HAS_FOLDER_SORTING
|
|
|
706 |
#if ENABLED(SDSORT_DYNAMIC_RAM)
|
|
|
707 |
isDir = new uint8_t[(fileCnt + 7) >> 3];
|
|
|
708 |
#elif ENABLED(SDSORT_USES_STACK)
|
|
|
709 |
uint8_t isDir[(fileCnt + 7) >> 3];
|
|
|
710 |
#endif
|
|
|
711 |
#endif
|
|
|
712 |
|
|
|
713 |
#else // !SDSORT_USES_RAM
|
|
|
714 |
|
|
|
715 |
// By default re-read the names from SD for every compare
|
|
|
716 |
// retaining only two filenames at a time. This is very
|
|
|
717 |
// slow but is safest and uses minimal RAM.
|
|
|
718 |
char name1[LONG_FILENAME_LENGTH + 1];
|
|
|
719 |
|
|
|
720 |
#endif
|
|
|
721 |
|
|
|
722 |
if (fileCnt > 1) {
|
|
|
723 |
|
|
|
724 |
// Init sort order.
|
|
|
725 |
for (uint16_t i = 0; i < fileCnt; i++) {
|
|
|
726 |
sort_order[i] = i;
|
|
|
727 |
// If using RAM then read all filenames now.
|
|
|
728 |
#if ENABLED(SDSORT_USES_RAM)
|
|
|
729 |
getfilename(i);
|
|
|
730 |
#if ENABLED(SDSORT_DYNAMIC_RAM)
|
|
|
731 |
// Use dynamic method to copy long filename
|
|
|
732 |
sortnames[i] = strdup(longest_filename());
|
|
|
733 |
#if ENABLED(SDSORT_CACHE_NAMES)
|
|
|
734 |
// When caching also store the short name, since
|
|
|
735 |
// we're replacing the getfilename() behavior.
|
|
|
736 |
sortshort[i] = strdup(filename);
|
|
|
737 |
#endif
|
|
|
738 |
#else
|
|
|
739 |
// Copy filenames into the static array
|
|
|
740 |
#if SORTED_LONGNAME_MAXLEN != LONG_FILENAME_LENGTH
|
|
|
741 |
strncpy(sortnames[i], longest_filename(), SORTED_LONGNAME_MAXLEN);
|
|
|
742 |
sortnames[i][SORTED_LONGNAME_MAXLEN - 1] = '\0';
|
|
|
743 |
#else
|
|
|
744 |
strncpy(sortnames[i], longest_filename(), SORTED_LONGNAME_MAXLEN);
|
|
|
745 |
#endif
|
|
|
746 |
#if ENABLED(SDSORT_CACHE_NAMES)
|
|
|
747 |
strcpy(sortshort[i], filename);
|
|
|
748 |
#endif
|
|
|
749 |
#endif
|
|
|
750 |
// char out[30];
|
|
|
751 |
// sprintf_P(out, PSTR("---- %i %s %s"), i, filenameIsDir ? "D" : " ", sortnames[i]);
|
|
|
752 |
// SERIAL_ECHOLN(out);
|
|
|
753 |
#if HAS_FOLDER_SORTING
|
|
|
754 |
const uint16_t bit = i & 0x07, ind = i >> 3;
|
|
|
755 |
if (bit == 0) isDir[ind] = 0x00;
|
|
|
756 |
if (filenameIsDir) isDir[ind] |= _BV(bit);
|
|
|
757 |
#endif
|
|
|
758 |
#endif
|
|
|
759 |
}
|
|
|
760 |
|
|
|
761 |
// Bubble Sort
|
|
|
762 |
for (uint16_t i = fileCnt; --i;) {
|
|
|
763 |
bool didSwap = false;
|
|
|
764 |
for (uint16_t j = 0; j < i; ++j) {
|
|
|
765 |
const uint16_t o1 = sort_order[j], o2 = sort_order[j + 1];
|
|
|
766 |
|
|
|
767 |
// Compare names from the array or just the two buffered names
|
|
|
768 |
#if ENABLED(SDSORT_USES_RAM)
|
|
|
769 |
#define _SORT_CMP_NODIR() (strcasecmp(sortnames[o1], sortnames[o2]) > 0)
|
|
|
770 |
#else
|
|
|
771 |
#define _SORT_CMP_NODIR() (strcasecmp(name1, name2) > 0)
|
|
|
772 |
#endif
|
|
|
773 |
|
|
|
774 |
#if HAS_FOLDER_SORTING
|
|
|
775 |
#if ENABLED(SDSORT_USES_RAM)
|
|
|
776 |
// Folder sorting needs an index and bit to test for folder-ness.
|
|
|
777 |
const uint8_t ind1 = o1 >> 3, bit1 = o1 & 0x07,
|
|
|
778 |
ind2 = o2 >> 3, bit2 = o2 & 0x07;
|
|
|
779 |
#define _SORT_CMP_DIR(fs) \
|
|
|
780 |
(((isDir[ind1] & _BV(bit1)) != 0) == ((isDir[ind2] & _BV(bit2)) != 0) \
|
|
|
781 |
? _SORT_CMP_NODIR() \
|
|
|
782 |
: (isDir[fs > 0 ? ind1 : ind2] & (fs > 0 ? _BV(bit1) : _BV(bit2))) != 0)
|
|
|
783 |
#else
|
|
|
784 |
#define _SORT_CMP_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_NODIR() : (fs > 0 ? dir1 : !dir1))
|
|
|
785 |
#endif
|
|
|
786 |
#endif
|
|
|
787 |
|
|
|
788 |
// The most economical method reads names as-needed
|
|
|
789 |
// throughout the loop. Slow if there are many.
|
|
|
790 |
#if DISABLED(SDSORT_USES_RAM)
|
|
|
791 |
getfilename(o1);
|
|
|
792 |
strcpy(name1, longest_filename()); // save (or getfilename below will trounce it)
|
|
|
793 |
#if HAS_FOLDER_SORTING
|
|
|
794 |
bool dir1 = filenameIsDir;
|
|
|
795 |
#endif
|
|
|
796 |
getfilename(o2);
|
|
|
797 |
char *name2 = longest_filename(); // use the string in-place
|
|
|
798 |
#endif // !SDSORT_USES_RAM
|
|
|
799 |
|
|
|
800 |
// Sort the current pair according to settings.
|
|
|
801 |
if (
|
|
|
802 |
#if HAS_FOLDER_SORTING
|
|
|
803 |
#if ENABLED(SDSORT_GCODE)
|
|
|
804 |
sort_folders ? _SORT_CMP_DIR(sort_folders) : _SORT_CMP_NODIR()
|
|
|
805 |
#else
|
|
|
806 |
_SORT_CMP_DIR(FOLDER_SORTING)
|
|
|
807 |
#endif
|
|
|
808 |
#else
|
|
|
809 |
_SORT_CMP_NODIR()
|
|
|
810 |
#endif
|
|
|
811 |
) {
|
|
|
812 |
sort_order[j] = o2;
|
|
|
813 |
sort_order[j + 1] = o1;
|
|
|
814 |
didSwap = true;
|
|
|
815 |
}
|
|
|
816 |
}
|
|
|
817 |
if (!didSwap) break;
|
|
|
818 |
}
|
|
|
819 |
// Using RAM but not keeping names around
|
|
|
820 |
#if ENABLED(SDSORT_USES_RAM) && DISABLED(SDSORT_CACHE_NAMES)
|
|
|
821 |
#if ENABLED(SDSORT_DYNAMIC_RAM)
|
|
|
822 |
for (uint16_t i = 0; i < fileCnt; ++i) free(sortnames[i]);
|
|
|
823 |
#if HAS_FOLDER_SORTING
|
|
|
824 |
free(isDir);
|
|
|
825 |
#endif
|
|
|
826 |
#endif
|
|
|
827 |
#endif
|
|
|
828 |
}
|
|
|
829 |
else {
|
|
|
830 |
sort_order[0] = 0;
|
|
|
831 |
#if ENABLED(SDSORT_USES_RAM) && ENABLED(SDSORT_CACHE_NAMES)
|
|
|
832 |
getfilename(0);
|
|
|
833 |
#if ENABLED(SDSORT_DYNAMIC_RAM)
|
|
|
834 |
sortnames = new char*[1];
|
|
|
835 |
sortnames[0] = strdup(longest_filename()); // malloc
|
|
|
836 |
#if ENABLED(SDSORT_CACHE_NAMES)
|
|
|
837 |
sortshort = new char*[1];
|
|
|
838 |
sortshort[0] = strdup(filename); // malloc
|
|
|
839 |
#endif
|
|
|
840 |
isDir = new uint8_t[1];
|
|
|
841 |
#else
|
|
|
842 |
#if SORTED_LONGNAME_MAXLEN != LONG_FILENAME_LENGTH
|
|
|
843 |
strncpy(sortnames[0], longest_filename(), SORTED_LONGNAME_MAXLEN);
|
|
|
844 |
sortnames[0][SORTED_LONGNAME_MAXLEN - 1] = '\0';
|
|
|
845 |
#else
|
|
|
846 |
strncpy(sortnames[0], longest_filename(), SORTED_LONGNAME_MAXLEN);
|
|
|
847 |
#endif
|
|
|
848 |
#if ENABLED(SDSORT_CACHE_NAMES)
|
|
|
849 |
strcpy(sortshort[0], filename);
|
|
|
850 |
#endif
|
|
|
851 |
#endif
|
|
|
852 |
isDir[0] = filenameIsDir ? 0x01 : 0x00;
|
|
|
853 |
#endif
|
|
|
854 |
}
|
|
|
855 |
|
|
|
856 |
sort_count = fileCnt;
|
|
|
857 |
}
|
|
|
858 |
}
|
|
|
859 |
|
|
|
860 |
void CardReader::flush_presort() {
|
|
|
861 |
if (sort_count > 0) {
|
|
|
862 |
#if ENABLED(SDSORT_DYNAMIC_RAM)
|
|
|
863 |
delete sort_order;
|
|
|
864 |
#if ENABLED(SDSORT_CACHE_NAMES)
|
|
|
865 |
for (uint8_t i = 0; i < sort_count; ++i) {
|
|
|
866 |
free(sortshort[i]); // strdup
|
|
|
867 |
free(sortnames[i]); // strdup
|
|
|
868 |
}
|
|
|
869 |
delete sortshort;
|
|
|
870 |
delete sortnames;
|
|
|
871 |
#endif
|
|
|
872 |
#endif
|
|
|
873 |
sort_count = 0;
|
|
|
874 |
}
|
|
|
875 |
}
|
|
|
876 |
|
|
|
877 |
#endif // SDCARD_SORT_ALPHA
|
|
|
878 |
|
|
|
879 |
uint16_t CardReader::get_num_Files() {
|
|
|
880 |
return
|
|
|
881 |
#if ENABLED(SDCARD_SORT_ALPHA) && SDSORT_USES_RAM && SDSORT_CACHE_NAMES
|
|
|
882 |
nrFiles // no need to access the SD card for filenames
|
|
|
883 |
#else
|
|
|
884 |
getnrfilenames()
|
|
|
885 |
#endif
|
|
|
886 |
;
|
|
|
887 |
}
|
|
|
888 |
|
|
|
889 |
void CardReader::printingHasFinished() {
|
|
|
890 |
planner.synchronize();
|
|
|
891 |
file.close();
|
|
|
892 |
if (file_subcall_ctr > 0) { // Heading up to a parent file that called current as a procedure.
|
|
|
893 |
file_subcall_ctr--;
|
|
|
894 |
openFile(proc_filenames[file_subcall_ctr], true, true);
|
|
|
895 |
setIndex(filespos[file_subcall_ctr]);
|
|
|
896 |
startFileprint();
|
|
|
897 |
}
|
|
|
898 |
else {
|
|
|
899 |
sdprinting = false;
|
|
|
900 |
|
|
|
901 |
#if ENABLED(POWER_LOSS_RECOVERY)
|
|
|
902 |
removeJobRecoveryFile();
|
|
|
903 |
#endif
|
|
|
904 |
|
|
|
905 |
#if ENABLED(SD_FINISHED_STEPPERRELEASE) && defined(SD_FINISHED_RELEASECOMMAND)
|
|
|
906 |
planner.finish_and_disable();
|
|
|
907 |
#endif
|
|
|
908 |
print_job_timer.stop();
|
|
|
909 |
if (print_job_timer.duration() > 60)
|
|
|
910 |
enqueue_and_echo_commands_P(PSTR("M31"));
|
|
|
911 |
#if ENABLED(SDCARD_SORT_ALPHA)
|
|
|
912 |
presort();
|
|
|
913 |
#endif
|
|
|
914 |
#if ENABLED(ULTRA_LCD) && ENABLED(LCD_SET_PROGRESS_MANUALLY)
|
|
|
915 |
progress_bar_percent = 0;
|
|
|
916 |
#endif
|
|
|
917 |
#if ENABLED(SD_REPRINT_LAST_SELECTED_FILE)
|
|
|
918 |
lcd_reselect_last_file();
|
|
|
919 |
#endif
|
|
|
920 |
}
|
|
|
921 |
}
|
|
|
922 |
|
|
|
923 |
#if ENABLED(AUTO_REPORT_SD_STATUS)
|
|
|
924 |
uint8_t CardReader::auto_report_sd_interval = 0;
|
|
|
925 |
millis_t CardReader::next_sd_report_ms;
|
|
|
926 |
|
|
|
927 |
void CardReader::auto_report_sd_status() {
|
|
|
928 |
millis_t current_ms = millis();
|
|
|
929 |
if (auto_report_sd_interval && ELAPSED(current_ms, next_sd_report_ms)) {
|
|
|
930 |
next_sd_report_ms = current_ms + 1000UL * auto_report_sd_interval;
|
|
|
931 |
getStatus();
|
|
|
932 |
}
|
|
|
933 |
}
|
|
|
934 |
#endif // AUTO_REPORT_SD_STATUS
|
|
|
935 |
|
|
|
936 |
#if ENABLED(POWER_LOSS_RECOVERY)
|
|
|
937 |
|
|
|
938 |
char job_recovery_file_name[4] = "bin";
|
|
|
939 |
|
|
|
940 |
void CardReader::openJobRecoveryFile(const bool read) {
|
|
|
941 |
if (!cardOK) return;
|
|
|
942 |
if (jobRecoveryFile.isOpen()) return;
|
|
|
943 |
if (!jobRecoveryFile.open(&root, job_recovery_file_name, read ? O_READ : O_CREAT | O_WRITE | O_TRUNC | O_SYNC)) {
|
|
|
944 |
SERIAL_PROTOCOLPAIR(MSG_SD_OPEN_FILE_FAIL, job_recovery_file_name);
|
|
|
945 |
SERIAL_PROTOCOLCHAR('.');
|
|
|
946 |
SERIAL_EOL();
|
|
|
947 |
}
|
|
|
948 |
else if (!read)
|
|
|
949 |
SERIAL_PROTOCOLLNPAIR(MSG_SD_WRITE_TO_FILE, job_recovery_file_name);
|
|
|
950 |
}
|
|
|
951 |
|
|
|
952 |
void CardReader::closeJobRecoveryFile() { jobRecoveryFile.close(); }
|
|
|
953 |
|
|
|
954 |
bool CardReader::jobRecoverFileExists() {
|
|
|
955 |
const bool exists = jobRecoveryFile.open(&root, job_recovery_file_name, O_READ);
|
|
|
956 |
if (exists) jobRecoveryFile.close();
|
|
|
957 |
return exists;
|
|
|
958 |
}
|
|
|
959 |
|
|
|
960 |
int16_t CardReader::saveJobRecoveryInfo() {
|
|
|
961 |
jobRecoveryFile.seekSet(0);
|
|
|
962 |
const int16_t ret = jobRecoveryFile.write(&job_recovery_info, sizeof(job_recovery_info));
|
|
|
963 |
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
|
|
|
964 |
if (ret == -1) SERIAL_PROTOCOLLNPGM("Power-loss file write failed.");
|
|
|
965 |
#endif
|
|
|
966 |
return ret;
|
|
|
967 |
}
|
|
|
968 |
|
|
|
969 |
int16_t CardReader::loadJobRecoveryInfo() {
|
|
|
970 |
return jobRecoveryFile.read(&job_recovery_info, sizeof(job_recovery_info));
|
|
|
971 |
}
|
|
|
972 |
|
|
|
973 |
void CardReader::removeJobRecoveryFile() {
|
|
|
974 |
job_recovery_info.valid_head = job_recovery_info.valid_foot = job_recovery_commands_count = 0;
|
|
|
975 |
if (jobRecoverFileExists()) {
|
|
|
976 |
closefile();
|
|
|
977 |
removeFile(job_recovery_file_name);
|
|
|
978 |
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
|
|
|
979 |
SERIAL_PROTOCOLPGM("Power-loss file delete");
|
|
|
980 |
serialprintPGM(jobRecoverFileExists() ? PSTR(" failed.\n") : PSTR("d.\n"));
|
|
|
981 |
#endif
|
|
|
982 |
}
|
|
|
983 |
}
|
|
|
984 |
|
|
|
985 |
#endif // POWER_LOSS_RECOVERY
|
|
|
986 |
|
|
|
987 |
#endif // SDSUPPORT
|