diff --git a/.travis.yml b/.travis.yml index 9e5e54798..56b9c1738 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,7 +67,7 @@ script: - opt_enable REPRAP_DISCOUNT_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS - opt_enable BLINKM PCA9632 RGB_LED NEOPIXEL_LED AUTO_POWER_CONTROL NOZZLE_PARK_FEATURE FILAMENT_RUNOUT_SENSOR - opt_enable AUTO_BED_LEVELING_LINEAR Z_MIN_PROBE_REPEATABILITY_TEST DEBUG_LEVELING_FEATURE SKEW_CORRECTION SKEW_CORRECTION_FOR_Z SKEW_CORRECTION_GCODE - - opt_enable_adv ARC_P_CIRCLES ADVANCED_PAUSE_FEATURE CNC_WORKSPACE_PLANES CNC_COORDINATE_SYSTEMS + - opt_enable_adv ARC_P_CIRCLES ADVANCED_PAUSE_FEATURE CNC_WORKSPACE_PLANES CNC_COORDINATE_SYSTEMS POWER_LOSS_RECOVERY - opt_enable_adv FWRETRACT MAX7219_DEBUG LED_CONTROL_MENU CASE_LIGHT_ENABLE CASE_LIGHT_USE_NEOPIXEL CODEPENDENT_XY_HOMING - opt_set GRID_MAX_POINTS_X 16 - opt_set_adv FANMUX0_PIN 53 diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 1cc2bdc4e..86eb04ab2 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -555,6 +555,16 @@ // Add an option in the menu to run all auto#.g files //#define MENU_ADDAUTOSTART + /** + * Continue after Power-Loss (Creality3D) + * + * Store the current state to the SD Card at the start of each layer + * during SD printing. If the recovery file is found at boot time, present + * an option on the LCD screen to continue the print from the last-known + * point in the file. + */ + //#define POWER_LOSS_RECOVERY + /** * Sort SD file listings in alphabetical order. * diff --git a/Marlin/src/Marlin.cpp b/Marlin/src/Marlin.cpp index ce4ffa0db..6c9ded576 100644 --- a/Marlin/src/Marlin.cpp +++ b/Marlin/src/Marlin.cpp @@ -126,6 +126,10 @@ #include "feature/pause.h" #endif +#if ENABLED(POWER_LOSS_RECOVERY) + #include "feature/power_loss_recovery.h" +#endif + #if ENABLED(FILAMENT_RUNOUT_SENSOR) #include "feature/runout.h" #endif @@ -876,6 +880,10 @@ void setup() { pe_magnet_init(); #endif + #if ENABLED(POWER_LOSS_RECOVERY) + do_print_job_recovery(); + #endif + #if ENABLED(USE_WATCHDOG) // Reinit watchdog after HAL_get_reset_source call watchdog_init(); #endif diff --git a/Marlin/src/config/default/Configuration_adv.h b/Marlin/src/config/default/Configuration_adv.h index 1cc2bdc4e..86eb04ab2 100644 --- a/Marlin/src/config/default/Configuration_adv.h +++ b/Marlin/src/config/default/Configuration_adv.h @@ -555,6 +555,16 @@ // Add an option in the menu to run all auto#.g files //#define MENU_ADDAUTOSTART + /** + * Continue after Power-Loss (Creality3D) + * + * Store the current state to the SD Card at the start of each layer + * during SD printing. If the recovery file is found at boot time, present + * an option on the LCD screen to continue the print from the last-known + * point in the file. + */ + //#define POWER_LOSS_RECOVERY + /** * Sort SD file listings in alphabetical order. * diff --git a/Marlin/src/feature/power_loss_recovery.cpp b/Marlin/src/feature/power_loss_recovery.cpp new file mode 100644 index 000000000..343952367 --- /dev/null +++ b/Marlin/src/feature/power_loss_recovery.cpp @@ -0,0 +1,235 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/** + * power_loss_recovery.cpp - Resume an SD print after power-loss + */ + +#include "../inc/MarlinConfigPre.h" + +#if ENABLED(POWER_LOSS_RECOVERY) + +#include "power_loss_recovery.h" + +#include "../lcd/ultralcd.h" +#include "../gcode/queue.h" +#include "../module/planner.h" +#include "../module/printcounter.h" +#include "../module/temperature.h" +#include "../sd/cardreader.h" +#include "../core/serial.h" + +// Recovery data +job_recovery_info_t job_recovery_info; +JobRecoveryPhase job_recovery_phase = JOB_RECOVERY_IDLE; +uint8_t job_recovery_commands_count; //=0 +char job_recovery_commands[BUFSIZE + APPEND_CMD_COUNT][MAX_CMD_SIZE]; + +// Private +static char sd_filename[MAXPATHNAMELENGTH]; + +#if ENABLED(DEBUG_POWER_LOSS_RECOVERY) + void debug_print_job_recovery(const bool recovery) { + 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 == job_recovery_info.valid_foot) { + SERIAL_PROTOCOLPGM("current_position"); + LOOP_XYZE(i) SERIAL_PROTOCOLPAIR(": ", job_recovery_info.current_position[i]); + SERIAL_EOL(); + SERIAL_PROTOCOLLNPAIR("feedrate: ", job_recovery_info.feedrate); + SERIAL_PROTOCOLPGM("target_temperature"); + HOTEND_LOOP() SERIAL_PROTOCOLPAIR(": ", job_recovery_info.target_temperature[e]); + SERIAL_EOL(); + SERIAL_PROTOCOLPGM("fanSpeeds"); + for(uint8_t i = 0; i < FAN_COUNT; i++) SERIAL_PROTOCOLPAIR(": ", job_recovery_info.fanSpeeds[i]); + SERIAL_EOL(); + #if HAS_LEVELING + SERIAL_PROTOCOLPAIR("leveling: ", int(job_recovery_info.leveling)); + SERIAL_PROTOCOLLNPAIR(" fade: ", int(job_recovery_info.fade)); + #endif + SERIAL_PROTOCOLLNPAIR("target_temperature_bed: ", job_recovery_info.target_temperature_bed); + SERIAL_PROTOCOLLNPAIR("cmd_queue_index_r: ", job_recovery_info.cmd_queue_index_r); + SERIAL_PROTOCOLLNPAIR("commands_in_queue: ", job_recovery_info.commands_in_queue); + if (recovery) + for (uint8_t i = 0; i < job_recovery_commands_count; i++) SERIAL_PROTOCOLLNPAIR("> ", job_recovery_commands[i]); + else + for (uint8_t i = 0; i < job_recovery_info.commands_in_queue; i++) SERIAL_PROTOCOLLNPAIR("> ", job_recovery_info.command_queue[i]); + SERIAL_PROTOCOLLNPAIR("sd_filename: ", sd_filename); + SERIAL_PROTOCOLLNPAIR("sdpos: ", job_recovery_info.sdpos); + SERIAL_PROTOCOLLNPAIR("print_job_elapsed: ", job_recovery_info.print_job_elapsed); + } + else + SERIAL_PROTOCOLLNPGM("INVALID DATA"); + } + } +#endif // DEBUG_POWER_LOSS_RECOVERY + +/** + * Check for Print Job Recovery + * If the file has a saved state, populate the job_recovery_commands queue + */ +void do_print_job_recovery() { + //if (job_recovery_commands_count > 0) return; + memset(&job_recovery_info, 0, sizeof(job_recovery_info)); + ZERO(job_recovery_commands); + + if (!card.cardOK) card.initsd(); + + if (card.cardOK) { + + #if ENABLED(DEBUG_POWER_LOSS_RECOVERY) + SERIAL_PROTOCOLLNPAIR("Init job recovery info. Size: ", (int)sizeof(job_recovery_info)); + #endif + + if (card.jobRecoverFileExists()) { + card.openJobRecoveryFile(true); + card.loadJobRecoveryInfo(); + card.closeJobRecoveryFile(); + //card.removeJobRecoveryFile(); + + if (job_recovery_info.valid_head && job_recovery_info.valid_head == job_recovery_info.valid_foot) { + + uint8_t ind = 0; + + #if HAS_LEVELING + strcpy_P(job_recovery_commands[ind++], PSTR("M420 S0 Z0")); // Leveling off before G92 or G28 + #endif + + 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("G28 R0" + #if !IS_KINEMATIC + " X Y" // Home X and Y for Cartesian + #endif + )); + + #if HAS_LEVELING + // Restore leveling state before G92 sets Z + // This ensures the steppers correspond to the native Z + sprintf_P(job_recovery_commands[ind++], PSTR("M420 S%i Z%s"), int(job_recovery_info.leveling), job_recovery_info.fade); + #endif + + char str_1[16], str_2[16]; + dtostrf(job_recovery_info.current_position[Z_AXIS] + 2, 1, 3, str_1); + dtostrf(job_recovery_info.current_position[E_AXIS] + #if ENABLED(SAVE_EACH_CMD_MODE) + - 5 + #endif + , 1, 3, str_2 + ); + 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; + while (job_recovery_info.commands_in_queue) { + strcpy(job_recovery_commands[ind++], job_recovery_info.command_queue[r]); + job_recovery_info.commands_in_queue--; + r = (r + 1) % BUFSIZE; + } + + job_recovery_commands_count = ind; + + #if ENABLED(DEBUG_POWER_LOSS_RECOVERY) + debug_print_job_recovery(true); + #endif + + card.openFile(sd_filename, true); + card.setIndex(job_recovery_info.sdpos); + } + else { + if (job_recovery_info.valid_head != job_recovery_info.valid_foot) + LCD_ALERTMESSAGEPGM("INVALID DATA"); + memset(&job_recovery_info, 0, sizeof(job_recovery_info)); + } + } + } +} + +/** + * Save the current machine state to the "bin" file + */ +void save_job_recovery_info() { + #if SAVE_INFO_INTERVAL_MS > 0 + static millis_t next_save_ms; // = 0; // Init on reset + millis_t ms = millis(); + #endif + if ( + #if SAVE_INFO_INTERVAL_MS > 0 + ELAPSED(ms, next_save_ms) || + #endif + #if ENABLED(SAVE_EACH_CMD_MODE) + true + #else + (current_position[Z_AXIS] > 0 && current_position[Z_AXIS] > job_recovery_info.current_position[Z_AXIS]) + #endif + ) { + #if SAVE_INFO_INTERVAL_MS > 0 + next_save_ms = ms + SAVE_INFO_INTERVAL_MS; + #endif + + // Head and foot will match if valid data was saved + if (!++job_recovery_info.valid_head) ++job_recovery_info.valid_head; // non-zero in sequence + job_recovery_info.valid_foot = job_recovery_info.valid_head; + + // Machine state + COPY(job_recovery_info.current_position, current_position); + job_recovery_info.feedrate = feedrate_mm_s; + COPY(job_recovery_info.target_temperature, thermalManager.target_temperature); + job_recovery_info.target_temperature_bed = thermalManager.target_temperature_bed; + COPY(job_recovery_info.fanSpeeds, fanSpeeds); + + #if HAS_LEVELING + job_recovery_info.leveling = planner.leveling_active; + job_recovery_info.fade = ( + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + planner.z_fade_height + #else + 0 + #endif + ); + #endif + + // Commands in the queue + job_recovery_info.cmd_queue_index_r = cmd_queue_index_r; + job_recovery_info.commands_in_queue = commands_in_queue; + COPY(job_recovery_info.command_queue, command_queue); + + // Elapsed print job time + job_recovery_info.print_job_elapsed = print_job_timer.duration() * 1000UL; + + // SD file position + card.getAbsFilename(sd_filename); + job_recovery_info.sdpos = card.getIndex(); + + #if ENABLED(DEBUG_POWER_LOSS_RECOVERY) + SERIAL_PROTOCOLLNPGM("Saving job_recovery_info"); + debug_print_job_recovery(false); + #endif + + card.openJobRecoveryFile(false); + (void)card.saveJobRecoveryInfo(); + } +} + +#endif // POWER_LOSS_RECOVERY diff --git a/Marlin/src/feature/power_loss_recovery.h b/Marlin/src/feature/power_loss_recovery.h new file mode 100644 index 000000000..2cab07e4a --- /dev/null +++ b/Marlin/src/feature/power_loss_recovery.h @@ -0,0 +1,86 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/** + * power_loss_recovery.h - Resume an SD print after power-loss + */ + +#ifndef _POWER_LOSS_RECOVERY_H_ +#define _POWER_LOSS_RECOVERY_H_ + +#include "../sd/cardreader.h" +#include "../core/types.h" +#include "../inc/MarlinConfigPre.h" + +#define SAVE_INFO_INTERVAL_MS 0 +//#define SAVE_EACH_CMD_MODE +//#define DEBUG_POWER_LOSS_RECOVERY + +typedef struct { + uint8_t valid_head; + + // Machine state + float current_position[NUM_AXIS], feedrate; + int16_t target_temperature[HOTENDS], + target_temperature_bed, + fanSpeeds[FAN_COUNT]; + + #if HAS_LEVELING + bool leveling; + float fade; + #endif + + // Command queue + uint8_t cmd_queue_index_r, commands_in_queue; + char command_queue[BUFSIZE][MAX_CMD_SIZE]; + + // SD File position + uint32_t sdpos; + + // Job elapsed time + millis_t print_job_elapsed; + + uint8_t valid_foot; +} job_recovery_info_t; + +extern job_recovery_info_t job_recovery_info; + +enum JobRecoveryPhase : unsigned char { + JOB_RECOVERY_IDLE, + JOB_RECOVERY_MAYBE, + JOB_RECOVERY_YES +}; +extern JobRecoveryPhase job_recovery_phase; + +#if HAS_LEVELING + #define APPEND_CMD_COUNT 7 +#else + #define APPEND_CMD_COUNT 5 +#endif + +extern char job_recovery_commands[BUFSIZE + APPEND_CMD_COUNT][MAX_CMD_SIZE]; +extern uint8_t job_recovery_commands_count; + +void do_print_job_recovery(); +void save_job_recovery_info(); + +#endif // _POWER_LOSS_RECOVERY_H_ diff --git a/Marlin/src/gcode/calibrate/M100.cpp b/Marlin/src/gcode/calibrate/M100.cpp index 8b01f85d3..fe6e58689 100644 --- a/Marlin/src/gcode/calibrate/M100.cpp +++ b/Marlin/src/gcode/calibrate/M100.cpp @@ -25,6 +25,7 @@ #if ENABLED(M100_FREE_MEMORY_WATCHER) #include "../gcode.h" +#include "../queue.h" #include "../../libs/hex_print_routines.h" #include "../../Marlin.h" // for idle() @@ -59,8 +60,6 @@ #define TEST_BYTE ((char) 0xE5) -extern char command_queue[BUFSIZE][MAX_CMD_SIZE]; - extern char* __brkval; extern size_t __heap_start, __heap_end, __flp; extern char __bss_end; diff --git a/Marlin/src/gcode/queue.cpp b/Marlin/src/gcode/queue.cpp index 7ac8c7912..a8f5e62da 100644 --- a/Marlin/src/gcode/queue.cpp +++ b/Marlin/src/gcode/queue.cpp @@ -37,6 +37,10 @@ #include "../feature/leds/leds.h" #endif +#if ENABLED(POWER_LOSS_RECOVERY) + #include "../feature/power_loss_recovery.h" +#endif + /** * GCode line number handling. Hosts may opt to include line numbers when * sending commands to Marlin, and lines will be checked for sequentiality. @@ -115,7 +119,7 @@ inline void _commit_command(bool say_ok * Return true if the command was successfully added. * Return false for a full buffer, or if the 'command' is a comment. */ -inline bool _enqueuecommand(const char* cmd, bool say_ok +inline bool _enqueuecommand(const char* cmd, bool say_ok=false #if NUM_SERIAL > 1 , int16_t port = -1 #endif @@ -133,8 +137,8 @@ inline bool _enqueuecommand(const char* cmd, bool say_ok /** * Enqueue with Serial Echo */ -bool enqueue_and_echo_command(const char* cmd, bool say_ok/*=false*/) { - if (_enqueuecommand(cmd, say_ok)) { +bool enqueue_and_echo_command(const char* cmd) { + if (_enqueuecommand(cmd)) { SERIAL_ECHO_START(); SERIAL_ECHOPAIR(MSG_ENQUEUEING, cmd); SERIAL_CHAR('"'); @@ -486,6 +490,22 @@ inline void get_serial_commands() { } } + #if ENABLED(POWER_LOSS_RECOVERY) + + inline bool drain_job_recovery_commands() { + static uint8_t job_recovery_commands_index = 0; // Resets on reboot + if (job_recovery_commands_count) { + if (_enqueuecommand(job_recovery_commands[job_recovery_commands_index])) { + ++job_recovery_commands_index; + if (!--job_recovery_commands_count) job_recovery_phase = JOB_RECOVERY_IDLE; + } + return true; + } + return false; + } + + #endif + #endif // SDSUPPORT /** @@ -501,6 +521,11 @@ void get_available_commands() { get_serial_commands(); + #if ENABLED(POWER_LOSS_RECOVERY) + // Commands for power-loss recovery take precedence + if (job_recovery_phase == JOB_RECOVERY_YES && drain_job_recovery_commands()) return; + #endif + #if ENABLED(SDSUPPORT) get_sdcard_commands(); #endif @@ -543,8 +568,12 @@ void advance_command_queue() { ok_to_send(); } } - else + else { gcode.process_next_command(); + #if ENABLED(POWER_LOSS_RECOVERY) + if (card.cardOK && card.sdprinting) save_job_recovery_info(); + #endif + } #else diff --git a/Marlin/src/gcode/queue.h b/Marlin/src/gcode/queue.h index 29c2ac86b..5b37f14ae 100644 --- a/Marlin/src/gcode/queue.h +++ b/Marlin/src/gcode/queue.h @@ -95,7 +95,7 @@ void enqueue_and_echo_commands_P(const char * const pgcode); /** * Enqueue with Serial Echo */ -bool enqueue_and_echo_command(const char* cmd, bool say_ok=false); +bool enqueue_and_echo_command(const char* cmd); #define HAS_LCD_QUEUE_NOW (ENABLED(MALYAN_LCD) || (ENABLED(ULTIPANEL) && (ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(PID_AUTOTUNE_MENU) || ENABLED(ADVANCED_PAUSE_FEATURE)))) #define HAS_QUEUE_NOW (ENABLED(SDSUPPORT) || HAS_LCD_QUEUE_NOW) diff --git a/Marlin/src/gcode/sdcard/M20-M30_M32-M34_M928.cpp b/Marlin/src/gcode/sdcard/M20-M30_M32-M34_M928.cpp index 59d45492a..8fbec3a2d 100644 --- a/Marlin/src/gcode/sdcard/M20-M30_M32-M34_M928.cpp +++ b/Marlin/src/gcode/sdcard/M20-M30_M32-M34_M928.cpp @@ -29,13 +29,16 @@ #include "../../module/printcounter.h" #include "../../module/stepper.h" -#if ENABLED(PARK_HEAD_ON_PAUSE) - #include "../../feature/pause.h" - #include "../queue.h" +#if ENABLED(POWER_LOSS_RECOVERY) + #include "../../feature/power_loss_recovery.h" #endif -#if NUM_SERIAL > 1 - #include "../../gcode/queue.h" +#if ENABLED(PARK_HEAD_ON_PAUSE) + #include "../../feature/pause.h" +#endif + +#if ENABLED(PARK_HEAD_ON_PAUSE) || NUM_SERIAL > 1 + #include "../queue.h" #endif /** @@ -78,6 +81,10 @@ void GcodeSuite::M23() { * M24: Start or Resume SD Print */ void GcodeSuite::M24() { + #if ENABLED(POWER_LOSS_RECOVERY) + card.removeJobRecoveryFile(); + #endif + #if ENABLED(PARK_HEAD_ON_PAUSE) resume_print(); #endif diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h index 2714739bd..4f5e26a47 100644 --- a/Marlin/src/inc/Conditionals_post.h +++ b/Marlin/src/inc/Conditionals_post.h @@ -1334,7 +1334,7 @@ #define GRID_MAX_POINTS ((GRID_MAX_POINTS_X) * (GRID_MAX_POINTS_Y)) // Add commands that need sub-codes to this list -#define USE_GCODE_SUBCODES ENABLED(G38_PROBE_TARGET) || ENABLED(CNC_COORDINATE_SYSTEMS) +#define USE_GCODE_SUBCODES ENABLED(G38_PROBE_TARGET) || ENABLED(CNC_COORDINATE_SYSTEMS) || ENABLED(POWER_LOSS_RECOVERY) // Parking Extruder #if ENABLED(PARKING_EXTRUDER) diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 323d470a9..165690a2a 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -462,7 +462,7 @@ static_assert(X_MAX_LENGTH >= X_BED_SIZE && Y_MAX_LENGTH >= Y_BED_SIZE, #elif ENABLED(BABYSTEP_ZPROBE_OFFSET) && !HAS_BED_PROBE #error "BABYSTEP_ZPROBE_OFFSET requires a probe." #elif ENABLED(BABYSTEP_ZPROBE_GFX_OVERLAY) && !ENABLED(DOGLCD) - #error "BABYSTEP_ZPROBE_GFX_OVERLAY requires a DOGLCD." + #error "BABYSTEP_ZPROBE_GFX_OVERLAY requires a Graphical LCD." #elif ENABLED(BABYSTEP_ZPROBE_GFX_OVERLAY) && !ENABLED(BABYSTEP_ZPROBE_OFFSET) #error "BABYSTEP_ZPROBE_GFX_OVERLAY requires a BABYSTEP_ZPROBE_OFFSET." #endif @@ -1708,4 +1708,8 @@ static_assert(COUNT(sanity_arr_3) <= XYZE_N, "DEFAULT_MAX_ACCELERATION has too m #endif #endif +#if ENABLED(POWER_LOSS_RECOVERY) && !ENABLED(ULTIPANEL) + #error "POWER_LOSS_RECOVERY currently requires an LCD Controller." +#endif + #endif // _SANITYCHECK_H_ diff --git a/Marlin/src/lcd/malyanlcd.cpp b/Marlin/src/lcd/malyanlcd.cpp index ea2bdd728..7c627225e 100644 --- a/Marlin/src/lcd/malyanlcd.cpp +++ b/Marlin/src/lcd/malyanlcd.cpp @@ -302,7 +302,7 @@ void process_lcd_s_command(const char* command) { case 'H': // Home all axis - enqueue_and_echo_command("G28", false); + enqueue_and_echo_commands_P(PSTR("G28")); break; case 'L': { diff --git a/Marlin/src/lcd/ultralcd.cpp b/Marlin/src/lcd/ultralcd.cpp index 106b80597..72edb5065 100644 --- a/Marlin/src/lcd/ultralcd.cpp +++ b/Marlin/src/lcd/ultralcd.cpp @@ -43,6 +43,10 @@ #include "../feature/pause.h" #endif +#if ENABLED(POWER_LOSS_RECOVERY) + #include "../feature/power_loss_recovery.h" +#endif + #if ENABLED(PRINTCOUNTER) && ENABLED(LCD_INFO_MENU) #include "../libs/duration_t.h" #endif @@ -842,10 +846,70 @@ void kill_screen(const char* lcd_msg) { abort_sd_printing = true; lcd_setstatusPGM(PSTR(MSG_PRINT_ABORTED), -1); lcd_return_to_status(); + + #if ENABLED(POWER_LOSS_RECOVERY) + card.openJobRecoveryFile(false); + job_recovery_info.valid_head = job_recovery_info.valid_foot = 0; + (void)card.saveJobRecoveryInfo(); + card.closeJobRecoveryFile(); + job_recovery_commands_count = 0; + #endif } #endif // SDSUPPORT + #if ENABLED(POWER_LOSS_RECOVERY) + + static void lcd_sdcard_recover_job() { + char cmd[20]; + + // Return to status now + lcd_return_to_status(); + + // Turn leveling off and home + enqueue_and_echo_commands_P(PSTR("M420 S0\nG28" + #if !IS_KINEMATIC + " X Y" + #endif + )); + + // Restore the bed temperature + sprintf_P(cmd, PSTR("M190 S%i"), job_recovery_info.target_temperature_bed); + enqueue_and_echo_command(cmd); + + // Restore all hotend temperatures + HOTEND_LOOP() { + sprintf_P(cmd, PSTR("M109 S%i"), job_recovery_info.target_temperature[e]); + enqueue_and_echo_command(cmd); + } + + // Restore print cooling fan speeds + for (uint8_t i = 0; i < FAN_COUNT; i++) { + sprintf_P(cmd, PSTR("M106 P%i S%i"), i, job_recovery_info.fanSpeeds[i]); + enqueue_and_echo_command(cmd); + } + + // Start draining the job recovery command queue + job_recovery_phase = JOB_RECOVERY_YES; + + // Resume the print job timer + if (job_recovery_info.print_job_elapsed) + print_job_timer.resume(job_recovery_info.print_job_elapsed); + + // Start getting commands from SD + card.startFileprint(); + } + + static void lcd_job_recovery_menu() { + defer_return_to_status = true; + START_MENU(); + MENU_ITEM(function, MSG_RESUME_PRINT, lcd_sdcard_recover_job); + MENU_ITEM(function, MSG_STOP_PRINT, lcd_sdcard_stop); + END_MENU(); + } + + #endif // POWER_LOSS_RECOVERY + #if ENABLED(MENU_ITEM_CASE_LIGHT) #include "../feature/caselight.h" @@ -5047,6 +5111,13 @@ void lcd_update() { #endif // SDSUPPORT && SD_DETECT_PIN + #if ENABLED(POWER_LOSS_RECOVERY) + if (job_recovery_commands_count && job_recovery_phase == JOB_RECOVERY_IDLE) { + lcd_goto_screen(lcd_job_recovery_menu); + job_recovery_phase = JOB_RECOVERY_MAYBE; // Waiting for a response + } + #endif + const millis_t ms = millis(); if (ELAPSED(ms, next_lcd_update_ms) #if ENABLED(DOGLCD) diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp index bc9f6212d..42696c28a 100644 --- a/Marlin/src/sd/cardreader.cpp +++ b/Marlin/src/sd/cardreader.cpp @@ -33,6 +33,10 @@ #include "../core/language.h" #include "../gcode/queue.h" +#if ENABLED(POWER_LOSS_RECOVERY) + #include "../feature/power_loss_recovery.h" +#endif + #if ENABLED(ADVANCED_PAUSE_FEATURE) #include "../feature/pause.h" #endif @@ -968,6 +972,15 @@ void CardReader::printingHasFinished() { } else { sdprinting = false; + + #if ENABLED(POWER_LOSS_RECOVERY) + openJobRecoveryFile(false); + job_recovery_info.valid_head = job_recovery_info.valid_foot = 0; + (void)saveJobRecoveryInfo(); + closeJobRecoveryFile(); + job_recovery_commands_count = 0; + #endif + #if ENABLED(SD_FINISHED_STEPPERRELEASE) && defined(SD_FINISHED_RELEASECOMMAND) stepper.cleaning_buffer_counter = 1; // The command will fire from the Stepper ISR #endif @@ -1006,4 +1019,46 @@ void CardReader::printingHasFinished() { } #endif // AUTO_REPORT_SD_STATUS +#if ENABLED(POWER_LOSS_RECOVERY) + + char job_recovery_file_name[4] = "bin"; + + void CardReader::openJobRecoveryFile(const bool read) { + if (!cardOK) return; + if (jobRecoveryFile.isOpen()) return; + if (!jobRecoveryFile.open(&root, job_recovery_file_name, read ? O_READ : O_CREAT | O_WRITE | O_TRUNC | O_SYNC)) { + SERIAL_PROTOCOLPAIR(MSG_SD_OPEN_FILE_FAIL, job_recovery_file_name); + SERIAL_PROTOCOLCHAR('.'); + SERIAL_EOL(); + } + else + SERIAL_PROTOCOLLNPAIR(MSG_SD_WRITE_TO_FILE, job_recovery_file_name); + } + + void CardReader::closeJobRecoveryFile() { jobRecoveryFile.close(); } + + bool CardReader::jobRecoverFileExists() { + return jobRecoveryFile.open(&root, job_recovery_file_name, O_READ); + } + + int16_t CardReader::saveJobRecoveryInfo() { + jobRecoveryFile.seekSet(0); + const int16_t ret = jobRecoveryFile.write(&job_recovery_info, sizeof(job_recovery_info)); + if (ret == -1) SERIAL_PROTOCOLLNPGM("Power-loss file write failed."); + return ret; + } + + int16_t CardReader::loadJobRecoveryInfo() { + return jobRecoveryFile.read(&job_recovery_info, sizeof(job_recovery_info)); + } + + void CardReader::removeJobRecoveryFile() { + if (jobRecoveryFile.remove(&root, job_recovery_file_name)) + SERIAL_PROTOCOLLNPGM("Power-loss file deleted."); + else + SERIAL_PROTOCOLLNPGM("Power-loss file delete failed."); + } + +#endif // POWER_LOSS_RECOVERY + #endif // SDSUPPORT diff --git a/Marlin/src/sd/cardreader.h b/Marlin/src/sd/cardreader.h index 66a23d47c..a66c69887 100644 --- a/Marlin/src/sd/cardreader.h +++ b/Marlin/src/sd/cardreader.h @@ -103,11 +103,21 @@ public: #endif #endif + #if ENABLED(POWER_LOSS_RECOVERY) + void openJobRecoveryFile(const bool read); + void closeJobRecoveryFile(); + bool jobRecoverFileExists(); + int16_t saveJobRecoveryInfo(); + int16_t loadJobRecoveryInfo(); + void removeJobRecoveryFile(); + #endif + FORCE_INLINE void pauseSDPrint() { sdprinting = false; } FORCE_INLINE bool isFileOpen() { return file.isOpen(); } FORCE_INLINE bool eof() { return sdpos >= filesize; } FORCE_INLINE int16_t get() { sdpos = file.curPosition(); return (int16_t)file.read(); } - FORCE_INLINE void setIndex(long index) { sdpos = index; file.seekSet(index); } + FORCE_INLINE void setIndex(const uint32_t index) { sdpos = index; file.seekSet(index); } + FORCE_INLINE uint32_t getIndex() { return sdpos; } FORCE_INLINE uint8_t percentDone() { return (isFileOpen() && filesize) ? sdpos / ((filesize + 99) / 100) : 0; } FORCE_INLINE char* getWorkDirName() { workDir.getFilename(filename); return filename; } @@ -191,6 +201,10 @@ private: SdVolume volume; SdFile file; + #if ENABLED(POWER_LOSS_RECOVERY) + SdFile jobRecoveryFile; + #endif + #define SD_PROCEDURE_DEPTH 1 #define MAXPATHNAMELENGTH (FILENAME_LENGTH*MAX_DIR_DEPTH + MAX_DIR_DEPTH + 1) uint8_t file_subcall_ctr;