Fix and improve POWER_LOSS_RECOVERY (#11186)

This commit is contained in:
Scott Lahteine 2018-07-02 23:21:18 -05:00 committed by GitHub
parent 84187dca5c
commit 0702c78e62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 131 additions and 71 deletions

View File

@ -1220,7 +1220,7 @@ inline void get_serial_commands() {
if (job_recovery_commands_count) { if (job_recovery_commands_count) {
if (_enqueuecommand(job_recovery_commands[job_recovery_commands_index])) { if (_enqueuecommand(job_recovery_commands[job_recovery_commands_index])) {
++job_recovery_commands_index; ++job_recovery_commands_index;
if (!--job_recovery_commands_count) job_recovery_phase = JOB_RECOVERY_IDLE; if (!--job_recovery_commands_count) job_recovery_phase = JOB_RECOVERY_DONE;
} }
return true; return true;
} }
@ -7090,6 +7090,9 @@ inline void gcode_M17() {
* M23: Open a file * M23: Open a file
*/ */
inline void gcode_M23() { inline void gcode_M23() {
#if ENABLED(POWER_LOSS_RECOVERY)
card.removeJobRecoveryFile();
#endif
// Simplify3D includes the size, so zero out all spaces (#7227) // Simplify3D includes the size, so zero out all spaces (#7227)
for (char *fn = parser.string_arg; *fn; ++fn) if (*fn == ' ') *fn = '\0'; for (char *fn = parser.string_arg; *fn; ++fn) if (*fn == ' ') *fn = '\0';
card.openFile(parser.string_arg, true); card.openFile(parser.string_arg, true);
@ -7099,15 +7102,21 @@ inline void gcode_M17() {
* M24: Start or Resume SD Print * M24: Start or Resume SD Print
*/ */
inline void gcode_M24() { inline void gcode_M24() {
#if ENABLED(POWER_LOSS_RECOVERY)
card.removeJobRecoveryFile();
#endif
#if ENABLED(PARK_HEAD_ON_PAUSE) #if ENABLED(PARK_HEAD_ON_PAUSE)
resume_print(); resume_print();
#endif #endif
#if ENABLED(POWER_LOSS_RECOVERY)
if (parser.seenval('S')) card.setIndex(parser.value_long());
#endif
card.startFileprint(); card.startFileprint();
#if ENABLED(POWER_LOSS_RECOVERY)
if (parser.seenval('T'))
print_job_timer.resume(parser.value_long());
else
#endif
print_job_timer.start(); print_job_timer.start();
} }
@ -14637,7 +14646,7 @@ void setup() {
#endif #endif
#if ENABLED(POWER_LOSS_RECOVERY) #if ENABLED(POWER_LOSS_RECOVERY)
do_print_job_recovery(); check_print_job_recovery();
#endif #endif
#if ENABLED(USE_WATCHDOG) #if ENABLED(USE_WATCHDOG)
@ -14678,6 +14687,9 @@ void loop() {
for (uint8_t i = 0; i < FAN_COUNT; i++) fanSpeeds[i] = 0; for (uint8_t i = 0; i < FAN_COUNT; i++) fanSpeeds[i] = 0;
#endif #endif
wait_for_heatup = false; wait_for_heatup = false;
#if ENABLED(POWER_LOSS_RECOVERY)
card.removeJobRecoveryFile();
#endif
} }
#endif #endif

View File

@ -501,9 +501,13 @@ void CardReader::checkautostart() {
if (!cardOK) initsd(); if (!cardOK) initsd();
if (cardOK) { if (cardOK
#if ENABLED(POWER_LOSS_RECOVERY)
&& !jobRecoverFileExists() // Don't run auto#.g when a resume file exists
#endif
) {
char autoname[10]; char autoname[10];
sprintf_P(autoname, PSTR("auto%i.g"), autostart_index); sprintf_P(autoname, PSTR("auto%i.g"), int(autostart_index));
dir_t p; dir_t p;
root.rewind(); root.rewind();
while (root.readDir(&p, NULL) > 0) { while (root.readDir(&p, NULL) > 0) {
@ -943,20 +947,24 @@ void CardReader::printingHasFinished() {
SERIAL_PROTOCOLCHAR('.'); SERIAL_PROTOCOLCHAR('.');
SERIAL_EOL(); SERIAL_EOL();
} }
else else if (!read)
SERIAL_PROTOCOLLNPAIR(MSG_SD_WRITE_TO_FILE, job_recovery_file_name); SERIAL_PROTOCOLLNPAIR(MSG_SD_WRITE_TO_FILE, job_recovery_file_name);
} }
void CardReader::closeJobRecoveryFile() { jobRecoveryFile.close(); } void CardReader::closeJobRecoveryFile() { jobRecoveryFile.close(); }
bool CardReader::jobRecoverFileExists() { bool CardReader::jobRecoverFileExists() {
return jobRecoveryFile.open(&root, job_recovery_file_name, O_READ); const bool exists = jobRecoveryFile.open(&root, job_recovery_file_name, O_READ);
if (exists) jobRecoveryFile.close();
return exists;
} }
int16_t CardReader::saveJobRecoveryInfo() { int16_t CardReader::saveJobRecoveryInfo() {
jobRecoveryFile.seekSet(0); jobRecoveryFile.seekSet(0);
const int16_t ret = jobRecoveryFile.write(&job_recovery_info, sizeof(job_recovery_info)); const int16_t ret = jobRecoveryFile.write(&job_recovery_info, sizeof(job_recovery_info));
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
if (ret == -1) SERIAL_PROTOCOLLNPGM("Power-loss file write failed."); if (ret == -1) SERIAL_PROTOCOLLNPGM("Power-loss file write failed.");
#endif
return ret; return ret;
} }
@ -966,14 +974,15 @@ void CardReader::printingHasFinished() {
void CardReader::removeJobRecoveryFile() { void CardReader::removeJobRecoveryFile() {
job_recovery_info.valid_head = job_recovery_info.valid_foot = job_recovery_commands_count = 0; job_recovery_info.valid_head = job_recovery_info.valid_foot = job_recovery_commands_count = 0;
const bool success = jobRecoveryFile.remove(&root, job_recovery_file_name); if (jobRecoverFileExists()) {
closefile();
removeFile(job_recovery_file_name);
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY) #if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
SERIAL_PROTOCOLPGM("Power-loss file delete"); SERIAL_PROTOCOLPGM("Power-loss file delete");
serialprintPGM(success ? PSTR("d.\n") : PSTR(" failed.\n")); serialprintPGM(jobRecoverFileExists() ? PSTR(" failed.\n") : PSTR("d.\n"));
#else
UNUSED(success);
#endif #endif
} }
}
#endif // POWER_LOSS_RECOVERY #endif // POWER_LOSS_RECOVERY

View File

@ -117,7 +117,7 @@ public:
public: public:
bool saving, logging, sdprinting, cardOK, filenameIsDir; bool saving, logging, sdprinting, cardOK, filenameIsDir;
char filename[FILENAME_LENGTH], longFilename[LONG_FILENAME_LENGTH]; char filename[FILENAME_LENGTH], longFilename[LONG_FILENAME_LENGTH];
int autostart_index; int8_t autostart_index;
private: private:
SdFile root, workDir, workDirParents[MAX_DIR_DEPTH]; SdFile root, workDir, workDirParents[MAX_DIR_DEPTH];
uint8_t workDirDepth; uint8_t workDirDepth;

View File

@ -666,6 +666,9 @@
#ifndef MSG_STOP_PRINT #ifndef MSG_STOP_PRINT
#define MSG_STOP_PRINT _UxGT("Stop print") #define MSG_STOP_PRINT _UxGT("Stop print")
#endif #endif
#ifndef MSG_POWER_LOSS_RECOVERY
#define MSG_POWER_LOSS_RECOVERY _UxGT("Power-Loss Recovery")
#endif
#ifndef MSG_CARD_MENU #ifndef MSG_CARD_MENU
#define MSG_CARD_MENU _UxGT("Print from SD") #define MSG_CARD_MENU _UxGT("Print from SD")
#endif #endif

View File

@ -42,14 +42,14 @@ job_recovery_info_t job_recovery_info;
JobRecoveryPhase job_recovery_phase = JOB_RECOVERY_IDLE; JobRecoveryPhase job_recovery_phase = JOB_RECOVERY_IDLE;
uint8_t job_recovery_commands_count; //=0 uint8_t job_recovery_commands_count; //=0
char job_recovery_commands[BUFSIZE + APPEND_CMD_COUNT][MAX_CMD_SIZE]; char job_recovery_commands[BUFSIZE + APPEND_CMD_COUNT][MAX_CMD_SIZE];
// Extern // Extern
extern uint8_t commands_in_queue, cmd_queue_index_r; extern uint8_t active_extruder, commands_in_queue, cmd_queue_index_r;
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY) #if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
void debug_print_job_recovery(const bool recovery) { void debug_print_job_recovery(const bool recovery) {
SERIAL_PROTOCOLPAIR("valid_head:", (int)job_recovery_info.valid_head); SERIAL_PROTOCOLLNPGM("---- Job Recovery Info ----");
SERIAL_PROTOCOLLNPAIR(" valid_foot:", (int)job_recovery_info.valid_foot); SERIAL_PROTOCOLPAIR("valid_head:", int(job_recovery_info.valid_head));
SERIAL_PROTOCOLLNPAIR(" valid_foot:", int(job_recovery_info.valid_foot));
if (job_recovery_info.valid_head) { if (job_recovery_info.valid_head) {
if (job_recovery_info.valid_head == job_recovery_info.valid_foot) { if (job_recovery_info.valid_head == job_recovery_info.valid_foot) {
SERIAL_PROTOCOLPGM("current_position: "); SERIAL_PROTOCOLPGM("current_position: ");
@ -59,6 +59,11 @@ extern uint8_t commands_in_queue, cmd_queue_index_r;
} }
SERIAL_EOL(); SERIAL_EOL();
SERIAL_PROTOCOLLNPAIR("feedrate: ", job_recovery_info.feedrate); SERIAL_PROTOCOLLNPAIR("feedrate: ", job_recovery_info.feedrate);
#if HOTENDS > 1
SERIAL_PROTOCOLLNPAIR("active_hotend: ", int(job_recovery_info.active_hotend));
#endif
SERIAL_PROTOCOLPGM("target_temperature: "); SERIAL_PROTOCOLPGM("target_temperature: ");
HOTEND_LOOP() { HOTEND_LOOP() {
SERIAL_PROTOCOL(job_recovery_info.target_temperature[e]); SERIAL_PROTOCOL(job_recovery_info.target_temperature[e]);
@ -83,8 +88,8 @@ extern uint8_t commands_in_queue, cmd_queue_index_r;
SERIAL_PROTOCOLPAIR("leveling: ", int(job_recovery_info.leveling)); SERIAL_PROTOCOLPAIR("leveling: ", int(job_recovery_info.leveling));
SERIAL_PROTOCOLLNPAIR(" fade: ", int(job_recovery_info.fade)); SERIAL_PROTOCOLLNPAIR(" fade: ", int(job_recovery_info.fade));
#endif #endif
SERIAL_PROTOCOLLNPAIR("cmd_queue_index_r: ", job_recovery_info.cmd_queue_index_r); SERIAL_PROTOCOLLNPAIR("cmd_queue_index_r: ", int(job_recovery_info.cmd_queue_index_r));
SERIAL_PROTOCOLLNPAIR("commands_in_queue: ", job_recovery_info.commands_in_queue); SERIAL_PROTOCOLLNPAIR("commands_in_queue: ", int(job_recovery_info.commands_in_queue));
if (recovery) if (recovery)
for (uint8_t i = 0; i < job_recovery_commands_count; i++) SERIAL_PROTOCOLLNPAIR("> ", job_recovery_commands[i]); for (uint8_t i = 0; i < job_recovery_commands_count; i++) SERIAL_PROTOCOLLNPAIR("> ", job_recovery_commands[i]);
else else
@ -96,15 +101,17 @@ extern uint8_t commands_in_queue, cmd_queue_index_r;
else else
SERIAL_PROTOCOLLNPGM("INVALID DATA"); SERIAL_PROTOCOLLNPGM("INVALID DATA");
} }
SERIAL_PROTOCOLLNPGM("---------------------------");
} }
#endif // DEBUG_POWER_LOSS_RECOVERY #endif // DEBUG_POWER_LOSS_RECOVERY
/** /**
* Check for Print Job Recovery * Check for Print Job Recovery during setup()
* If the file has a saved state, populate the job_recovery_commands queue *
* If a saved state exists, populate job_recovery_commands with
* commands to restore the machine state and continue the file.
*/ */
void do_print_job_recovery() { void check_print_job_recovery() {
//if (job_recovery_commands_count > 0) return;
memset(&job_recovery_info, 0, sizeof(job_recovery_info)); memset(&job_recovery_info, 0, sizeof(job_recovery_info));
ZERO(job_recovery_commands); ZERO(job_recovery_commands);
@ -113,7 +120,7 @@ void do_print_job_recovery() {
if (card.cardOK) { if (card.cardOK) {
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY) #if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
SERIAL_PROTOCOLLNPAIR("Init job recovery info. Size: ", (int)sizeof(job_recovery_info)); SERIAL_PROTOCOLLNPAIR("Init job recovery info. Size: ", int(sizeof(job_recovery_info)));
#endif #endif
if (card.jobRecoverFileExists()) { if (card.jobRecoverFileExists()) {
@ -133,7 +140,9 @@ void do_print_job_recovery() {
strcpy_P(job_recovery_commands[ind++], PSTR("G92.0 Z0")); // Ensure Z is equal to 0 strcpy_P(job_recovery_commands[ind++], PSTR("G92.0 Z0")); // Ensure Z is equal to 0
strcpy_P(job_recovery_commands[ind++], PSTR("G1 Z2")); // Raise Z by 2mm (we hope!) strcpy_P(job_recovery_commands[ind++], PSTR("G1 Z2")); // Raise Z by 2mm (we hope!)
strcpy_P(job_recovery_commands[ind++], PSTR("G28 R0" strcpy_P(job_recovery_commands[ind++], PSTR("G28 R0"
#if !IS_KINEMATIC #if ENABLED(MARLIN_DEV_MODE)
" S"
#elif !IS_KINEMATIC
" X Y" // Home X and Y for Cartesian " X Y" // Home X and Y for Cartesian
#endif #endif
)); ));
@ -141,10 +150,12 @@ void do_print_job_recovery() {
char str_1[16], str_2[16]; char str_1[16], str_2[16];
#if HAS_LEVELING #if HAS_LEVELING
if (job_recovery_info.fade || job_recovery_info.leveling) {
// Restore leveling state before G92 sets Z // Restore leveling state before G92 sets Z
// This ensures the steppers correspond to the native Z // This ensures the steppers correspond to the native Z
dtostrf(job_recovery_info.fade, 1, 1, str_1); dtostrf(job_recovery_info.fade, 1, 1, str_1);
sprintf_P(job_recovery_commands[ind++], PSTR("M420 S%i Z%s"), int(job_recovery_info.leveling), str_1); sprintf_P(job_recovery_commands[ind++], PSTR("M420 S%i Z%s"), int(job_recovery_info.leveling), str_1);
}
#endif #endif
dtostrf(job_recovery_info.current_position[Z_AXIS] + 2, 1, 3, str_1); dtostrf(job_recovery_info.current_position[Z_AXIS] + 2, 1, 3, str_1);
@ -156,23 +167,21 @@ void do_print_job_recovery() {
); );
sprintf_P(job_recovery_commands[ind++], PSTR("G92.0 Z%s E%s"), str_1, str_2); // Current Z + 2 and E sprintf_P(job_recovery_commands[ind++], PSTR("G92.0 Z%s E%s"), str_1, str_2); // Current Z + 2 and E
strcpy_P(job_recovery_commands[ind++], PSTR("M117 Continuing...")); uint8_t r = job_recovery_info.cmd_queue_index_r, c = job_recovery_info.commands_in_queue;
while (c--) {
uint8_t r = job_recovery_info.cmd_queue_index_r;
while (job_recovery_info.commands_in_queue) {
strcpy(job_recovery_commands[ind++], job_recovery_info.command_queue[r]); strcpy(job_recovery_commands[ind++], job_recovery_info.command_queue[r]);
job_recovery_info.commands_in_queue--;
r = (r + 1) % BUFSIZE; r = (r + 1) % BUFSIZE;
} }
if (job_recovery_info.sd_filename[0] == '/') job_recovery_info.sd_filename[0] = ' ';
sprintf_P(job_recovery_commands[ind++], PSTR("M23 %s"), job_recovery_info.sd_filename);
sprintf_P(job_recovery_commands[ind++], PSTR("M24 S%ld T%ld"), job_recovery_info.sdpos, job_recovery_info.print_job_elapsed);
job_recovery_commands_count = ind; job_recovery_commands_count = ind;
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY) #if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
debug_print_job_recovery(true); debug_print_job_recovery(true);
#endif #endif
card.openFile(job_recovery_info.sd_filename, true);
card.setIndex(job_recovery_info.sdpos);
} }
else { else {
if (job_recovery_info.valid_head != job_recovery_info.valid_foot) if (job_recovery_info.valid_head != job_recovery_info.valid_foot)
@ -212,6 +221,11 @@ void save_job_recovery_info() {
// Machine state // Machine state
COPY(job_recovery_info.current_position, current_position); COPY(job_recovery_info.current_position, current_position);
job_recovery_info.feedrate = feedrate_mm_s; job_recovery_info.feedrate = feedrate_mm_s;
#if HOTENDS > 1
job_recovery_info.active_hotend = active_extruder;
#endif
COPY(job_recovery_info.target_temperature, thermalManager.target_temperature); COPY(job_recovery_info.target_temperature, thermalManager.target_temperature);
#if HAS_HEATED_BED #if HAS_HEATED_BED
@ -239,14 +253,14 @@ void save_job_recovery_info() {
COPY(job_recovery_info.command_queue, command_queue); COPY(job_recovery_info.command_queue, command_queue);
// Elapsed print job time // Elapsed print job time
job_recovery_info.print_job_elapsed = print_job_timer.duration() * 1000UL; job_recovery_info.print_job_elapsed = print_job_timer.duration();
// SD file position // SD file position
card.getAbsFilename(job_recovery_info.sd_filename); card.getAbsFilename(job_recovery_info.sd_filename);
job_recovery_info.sdpos = card.getIndex(); job_recovery_info.sdpos = card.getIndex();
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY) #if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
SERIAL_PROTOCOLLNPGM("Saving job_recovery_info"); SERIAL_PROTOCOLLNPGM("Saving...");
debug_print_job_recovery(false); debug_print_job_recovery(false);
#endif #endif

View File

@ -40,6 +40,11 @@ typedef struct {
// Machine state // Machine state
float current_position[NUM_AXIS], feedrate; float current_position[NUM_AXIS], feedrate;
#if HOTENDS > 1
uint8_t active_hotend;
#endif
int16_t target_temperature[HOTENDS]; int16_t target_temperature[HOTENDS];
#if HAS_HEATED_BED #if HAS_HEATED_BED
@ -74,20 +79,21 @@ extern job_recovery_info_t job_recovery_info;
enum JobRecoveryPhase : unsigned char { enum JobRecoveryPhase : unsigned char {
JOB_RECOVERY_IDLE, JOB_RECOVERY_IDLE,
JOB_RECOVERY_MAYBE, JOB_RECOVERY_MAYBE,
JOB_RECOVERY_YES JOB_RECOVERY_YES,
JOB_RECOVERY_DONE
}; };
extern JobRecoveryPhase job_recovery_phase; extern JobRecoveryPhase job_recovery_phase;
#if HAS_LEVELING #if HAS_LEVELING
#define APPEND_CMD_COUNT 7 #define APPEND_CMD_COUNT 9
#else #else
#define APPEND_CMD_COUNT 5 #define APPEND_CMD_COUNT 7
#endif #endif
extern char job_recovery_commands[BUFSIZE + APPEND_CMD_COUNT][MAX_CMD_SIZE]; extern char job_recovery_commands[BUFSIZE + APPEND_CMD_COUNT][MAX_CMD_SIZE];
extern uint8_t job_recovery_commands_count; extern uint8_t job_recovery_commands_count;
void do_print_job_recovery(); void check_print_job_recovery();
void save_job_recovery_info(); void save_job_recovery_info();
#endif // _POWER_LOSS_RECOVERY_H_ #endif // _POWER_LOSS_RECOVERY_H_

View File

@ -861,17 +861,13 @@ void lcd_quick_feedback(const bool clear_buttons) {
abort_sd_printing = true; abort_sd_printing = true;
lcd_setstatusPGM(PSTR(MSG_PRINT_ABORTED), -1); lcd_setstatusPGM(PSTR(MSG_PRINT_ABORTED), -1);
lcd_return_to_status(); lcd_return_to_status();
#if ENABLED(POWER_LOSS_RECOVERY)
card.removeJobRecoveryFile();
#endif
} }
#endif // SDSUPPORT #endif // SDSUPPORT
#if ENABLED(POWER_LOSS_RECOVERY) #if ENABLED(POWER_LOSS_RECOVERY)
static void lcd_sdcard_recover_job() { static void lcd_power_loss_recovery_resume() {
char cmd[20]; char cmd[20];
// Return to status now // Return to status now
@ -879,45 +875,65 @@ void lcd_quick_feedback(const bool clear_buttons) {
// Turn leveling off and home // Turn leveling off and home
enqueue_and_echo_commands_P(PSTR("M420 S0\nG28" enqueue_and_echo_commands_P(PSTR("M420 S0\nG28"
#if !IS_KINEMATIC #if ENABLED(MARLIN_DEV_MODE)
" S"
#elif !IS_KINEMATIC
" X Y" " X Y"
#endif #endif
)); ));
#if HAS_HEATED_BED #if HAS_HEATED_BED
const int16_t bt = job_recovery_info.target_temperature_bed;
if (bt) {
// Restore the bed temperature // Restore the bed temperature
sprintf_P(cmd, PSTR("M190 S%i"), job_recovery_info.target_temperature_bed); sprintf_P(cmd, PSTR("M190 S%i"), bt);
enqueue_and_echo_command(cmd); enqueue_and_echo_command(cmd);
}
#endif #endif
// Restore all hotend temperatures // Restore all hotend temperatures
HOTEND_LOOP() { HOTEND_LOOP() {
sprintf_P(cmd, PSTR("M109 S%i"), job_recovery_info.target_temperature[e]); const int16_t et = job_recovery_info.target_temperature[e];
if (et) {
#if HOTENDS > 1
sprintf_P(cmd, PSTR("T%i"), e);
enqueue_and_echo_command(cmd);
#endif
sprintf_P(cmd, PSTR("M109 S%i"), et);
enqueue_and_echo_command(cmd); enqueue_and_echo_command(cmd);
} }
}
#if HOTENDS > 1
sprintf_P(cmd, PSTR("T%i"), job_recovery_info.active_hotend);
enqueue_and_echo_command(cmd);
#endif
// Restore print cooling fan speeds // Restore print cooling fan speeds
for (uint8_t i = 0; i < FAN_COUNT; i++) { for (uint8_t i = 0; i < FAN_COUNT; i++) {
sprintf_P(cmd, PSTR("M106 P%i S%i"), i, job_recovery_info.fanSpeeds[i]); int16_t f = job_recovery_info.fanSpeeds[i];
if (f) {
sprintf_P(cmd, PSTR("M106 P%i S%i"), i, f);
enqueue_and_echo_command(cmd); enqueue_and_echo_command(cmd);
} }
}
// Start draining the job recovery command queue // Start draining the job recovery command queue
job_recovery_phase = JOB_RECOVERY_YES; job_recovery_phase = JOB_RECOVERY_YES;
}
// Resume the print job timer static void lcd_power_loss_recovery_cancel() {
if (job_recovery_info.print_job_elapsed) card.removeJobRecoveryFile();
print_job_timer.resume(job_recovery_info.print_job_elapsed); card.autostart_index = 0;
lcd_return_to_status();
// Start getting commands from SD
card.startFileprint();
} }
static void lcd_job_recovery_menu() { static void lcd_job_recovery_menu() {
defer_return_to_status = true; defer_return_to_status = true;
START_MENU(); START_MENU();
MENU_ITEM(function, MSG_RESUME_PRINT, lcd_sdcard_recover_job); STATIC_ITEM(MSG_POWER_LOSS_RECOVERY);
MENU_ITEM(function, MSG_STOP_PRINT, lcd_sdcard_stop); MENU_ITEM(function, MSG_RESUME_PRINT, lcd_power_loss_recovery_resume);
MENU_ITEM(function, MSG_STOP_PRINT, lcd_power_loss_recovery_cancel);
END_MENU(); END_MENU();
} }