diff --git a/lib/Marlin/Marlin/src/gcode/gcode.h b/lib/Marlin/Marlin/src/gcode/gcode.h index c23a4a1870..e3cecd7244 100644 --- a/lib/Marlin/Marlin/src/gcode/gcode.h +++ b/lib/Marlin/Marlin/src/gcode/gcode.h @@ -308,6 +308,13 @@ enum AxisRelative : uint8_t { REL_X, REL_Y, REL_Z, REL_E, E_MODE_ABS, E_MODE_REL }; +#if ENABLED(SDSUPPORT) || ENABLED(SDCARD_GCODES) +namespace M27_handler { + extern uint32_t sd_auto_report_delay; + void print_sd_status(); +} // namespace M27_handler +#endif + class GcodeSuite { public: diff --git a/src/common/marlin_server.cpp b/src/common/marlin_server.cpp index 81d234234d..bd836c18ba 100644 --- a/src/common/marlin_server.cpp +++ b/src/common/marlin_server.cpp @@ -543,6 +543,15 @@ void handle_nfc() { #endif +void print_sd_report() { + static uint32_t last_sd_report = 0; + uint32_t current_time = ticks_s(); + if (M27_handler::sd_auto_report_delay && (current_time - last_sd_report) >= M27_handler::sd_auto_report_delay) { + M27_handler::print_sd_status(); + last_sd_report = current_time; + } +} + #if ENABLED(PRUSA_MMU2) /// Helper function that enqueues gcodes to safely unload filament from nozzle back to mmu /// @@ -641,6 +650,10 @@ static void cycle() { print_fan_spd(); +#if ENABLED(SDSUPPORT) || ENABLED(SDCARD_GCODES) + print_sd_report(); +#endif + #ifdef MINDA_BROKEN_CABLE_DETECTION print_Z_probe_cnt(); #endif diff --git a/src/common/media.cpp b/src/common/media.cpp index 0abe30df25..55f25e18cf 100644 --- a/src/common/media.cpp +++ b/src/common/media.cpp @@ -320,7 +320,7 @@ void media_print_start() { media_print_size_estimate = media_print_file->get_gcode_stream_size_estimate(); // Do not remove, needed for 3rd party tools such as octoprint to get status about the gcode file being opened - SERIAL_ECHOLNPAIR(MSG_SD_FILE_OPENED, marlin_vars()->media_SFN_path.get_ptr(), " Size:", media_print_size_estimate); + SERIAL_ECHOLNPAIR(MSG_SD_FILE_OPENED, marlin_vars()->media_SFN_path.get_ptr(), MSG_SD_SIZE, media_print_size_estimate); gcode_filter.reset(); osSignalSet(prefetch_thread_id, PREFETCH_SIGNAL_START); diff --git a/src/marlin_stubs/host/M115.cpp b/src/marlin_stubs/host/M115.cpp index fcb4309146..6f36e62c2c 100644 --- a/src/marlin_stubs/host/M115.cpp +++ b/src/marlin_stubs/host/M115.cpp @@ -194,6 +194,9 @@ void GcodeSuite::M115() { #endif ); + // EXTENDED_M20 + cap_line(PSTR("EXTENDED_M20"), true); + // THERMAL_PROTECTION cap_line(PSTR("THERMAL_PROTECTION") #if ((ENABLED(THERMAL_PROTECTION_HOTENDS) || HAS_DWARF()) && (ENABLED(THERMAL_PROTECTION_BED) || !HAS_HEATED_BED || HAS_MODULARBED()) && (ENABLED(THERMAL_PROTECTION_CHAMBER) || !HAS_HEATED_CHAMBER)) diff --git a/src/marlin_stubs/sdcard/M20-M30_M32-M34.cpp b/src/marlin_stubs/sdcard/M20-M30_M32-M34.cpp index fa525bc45b..58a9fac166 100644 --- a/src/marlin_stubs/sdcard/M20-M30_M32-M34.cpp +++ b/src/marlin_stubs/sdcard/M20-M30_M32-M34.cpp @@ -5,6 +5,92 @@ #include "media.hpp" #include "marlin_vars.hpp" +struct ListControl { + bool print_lfn : 1; + bool print_time : 1; + uint8_t recursion_count; + time_t tz_offset_seconds; +}; + +// Forward reference (for recursion) +static void list_files(const char *const dir_path, struct ListControl *lc); +// Depends on stack size/RAM, etc +// Set to 0 to disallow recursion, Marlin MAX is 6 +#define MAX_RECURSION_DEPTH 4 +// Device root name for FatFS +#define ROOT_PREFIX "/usb" + +// Routine to output single dirent info in Marlin M20 format +// Note: Use of 'alloca' prohibits inline +static void __attribute__((noinline)) list_single_entry(const char *dir_path, struct dirent *entry, struct ListControl *lc) { + // Construct path to sub-dir. + int len = strlen(dir_path) + strlen(entry->d_name) + 2; + char *path = reinterpret_cast(alloca(len)); + strcpy(path, dir_path); + strcat(path, "/"); + strcat(path, entry->d_name); + + if (entry->d_type != DT_DIR) { + struct stat fstats; + + // Hide ROOT_PREFIX + SERIAL_ECHO(&path[sizeof(ROOT_PREFIX) - 1]); + int rc = stat(path, &fstats); + if (rc == 0) { + SERIAL_ECHOPAIR(PSTR(" "), fstats.st_size); + if (lc->print_time) { + struct tm lt; + time_t t = fstats.st_mtim.tv_sec + lc->tz_offset_seconds; + localtime_r(&t, <); + // M20 Date in high-word + t = ((lt.tm_year + 1900 - 1980) << 9 | (lt.tm_mon + 1) << 5 | lt.tm_mday) << 16; + // M20 Time in low-word + t |= lt.tm_hour << 11 | lt.tm_min << 5 | int((lt.tm_sec - (lt.tm_sec % 2)) / 2); + SERIAL_ECHOPGM(" 0x"); + SERIAL_PRINT((unsigned int)t, HEX); + } + if (lc->print_lfn) { + SERIAL_ECHOPAIR(PSTR(" \""), entry->lfn, PSTR("\"")); + } + } + SERIAL_EOL(); + } else { + // Check recursion depth + if (lc->recursion_count == 0) { + return; + } + + if (lc->print_lfn) { + SERIAL_ECHOPAIR(PSTR("DIR_ENTER: "), &path[sizeof(ROOT_PREFIX) - 1], PSTR("/ \""), entry->lfn, PSTR("\"")); + SERIAL_EOL(); + } + // List sub-directory calling 'list_files' recursively + lc->recursion_count--; + list_files(path, lc); + lc->recursion_count++; + if (lc->print_lfn) { + SERIAL_ECHOLNPGM("DIR_EXIT"); + } + } + return; +} + +// List all files in a single directory +// Note that this function is called recursively +static void list_files(const char *const dir_path, struct ListControl *lc) { + DIR *dir; + dir = opendir(dir_path); + if (dir != NULL) { + struct dirent *entry; + while ((entry = readdir(dir)) != NULL && entry->d_name[0]) { + // Output single directory entry or perform recursion into sub-dir + list_single_entry(dir_path, entry, lc); + } + closedir(dir); + } + return; +} + /** \addtogroup G-Codes * @{ */ @@ -13,16 +99,18 @@ * M20 - List SD card on serial port */ void GcodeSuite::M20() { - SERIAL_ECHOLNPGM(MSG_BEGIN_FILE_LIST); - DIR *dir; - dir = opendir("/usb/"); - if (dir != NULL) { - struct dirent *entry; - while ((entry = readdir(dir)) != NULL && entry->d_name[0]) { - SERIAL_ECHOLN(entry->d_name); - } - closedir(dir); + ListControl lc; + if (parser.seen('L')) { + lc.print_lfn = 1; + } + if (parser.seen('T')) { + lc.print_time = 1; } + // Get timezone offset to report local filetime + lc.tz_offset_seconds = time_tools::calculate_total_timezone_offset_minutes() * 60; + lc.recursion_count = MAX_RECURSION_DEPTH; + SERIAL_ECHOLNPGM(MSG_BEGIN_FILE_LIST); + list_files(ROOT_PREFIX, &lc); SERIAL_ECHOLNPGM(MSG_END_FILE_LIST); } @@ -44,22 +132,42 @@ void GcodeSuite::M22() { * M23 - Select SD file */ void GcodeSuite::M23() { - // Simplify3D includes the size, so zero out all spaces (#7227) - for (char *fn = parser.string_arg; *fn; ++fn) { - if (*fn == ' ') { - *fn = '\0'; - } + char namebuf[marlin_vars()->media_SFN_path.max_length()]; + // Simplify3D includes the size, terminate name at first space + char *idx = strchr(parser.string_arg, ' '); + if (idx) { + *idx = '\0'; } - marlin_vars()->media_SFN_path.set(parser.string_arg); + // Need to prepend root device name + strcpy(namebuf, PSTR(ROOT_PREFIX)); + strncpy(&namebuf[sizeof(ROOT_PREFIX) - 1], parser.string_arg, sizeof(namebuf) - sizeof(ROOT_PREFIX)); + // Do not remove. Used by third party tools to detect that a file has been selected - SERIAL_ECHOLNPGM(MSG_SD_FILE_SELECTED); + SERIAL_ECHO_START(); + SERIAL_ECHOPAIR(PSTR("Now doing file: "), parser.string_arg); + SERIAL_EOL(); + + struct stat fstats; + int rc = stat(namebuf, &fstats); + if (rc == 0) { + marlin_vars()->media_SFN_path.set(namebuf); + // Do not remove, needed for 3rd party tools such as octoprint to get notification about the gcode file being opened + SERIAL_ECHOLNPAIR(MSG_SD_FILE_OPENED, parser.string_arg, MSG_SD_SIZE, fstats.st_size); + SERIAL_ECHOLNPGM(MSG_SD_FILE_SELECTED); + } else { + SERIAL_ECHOLNPAIR(MSG_SD_OPEN_FILE_FAIL, parser.string_arg); + } } /** * M24 - Start/resume SD print */ void GcodeSuite::M24() { - marlin_server::print_resume(); + if (media_print_get_state() == media_print_state_PAUSED) { + marlin_server::print_resume(); + } else { + marlin_server::print_start(marlin_vars()->media_SFN_path.get_ptr(), marlin_server::PreviewSkipIfAble::all); + } } /** @@ -89,19 +197,27 @@ void GcodeSuite::M26() { * * - `C` - Report current file's short file name instead */ +uint32_t M27_handler::sd_auto_report_delay = 0; + +void M27_handler::print_sd_status() { + if (media_print_get_state() != media_print_state_NONE) { + SERIAL_ECHOPGM(MSG_SD_PRINTING_BYTE); + SERIAL_ECHO(media_print_get_position()); + SERIAL_CHAR('/'); + SERIAL_ECHOLN(media_print_get_size()); + } else { + SERIAL_ECHOLNPGM(MSG_SD_NOT_PRINTING); + } +} + void GcodeSuite::M27() { if (parser.seen('C')) { SERIAL_ECHOPGM("Current file: "); SERIAL_ECHOLN(marlin_vars()->media_SFN_path.get_ptr()); + } else if (parser.seen('S')) { + M27_handler::sd_auto_report_delay = parser.byteval('S'); } else { - if (media_print_get_state() != media_print_state_NONE) { - SERIAL_ECHOPGM(MSG_SD_PRINTING_BYTE); - SERIAL_ECHO(media_print_get_position()); - SERIAL_CHAR('/'); - SERIAL_ECHOLN(media_print_get_size()); - } else { - SERIAL_ECHOLNPGM(MSG_SD_NOT_PRINTING); - } + M27_handler::print_sd_status(); } }