From 12c345bd4206624a0d6121d33a81944c472ea8af Mon Sep 17 00:00:00 2001 From: Scott Lahteine Date: Tue, 27 Jun 2023 13:19:36 -0500 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20String=20helper=20class=20?= =?UTF-8?q?(#24390)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Marlin/src/MarlinCore.cpp | 2 +- Marlin/src/core/mstring.h | 305 ++++++++++++++++++ Marlin/src/core/serial.h | 49 +++ Marlin/src/feature/cancel_object.cpp | 2 +- Marlin/src/feature/powerloss.cpp | 122 +++---- Marlin/src/feature/runout.cpp | 8 +- Marlin/src/feature/tmc_util.cpp | 45 ++- Marlin/src/gcode/calibrate/G33.cpp | 31 +- Marlin/src/gcode/calibrate/G34_M422.cpp | 47 +-- Marlin/src/gcode/gcode.cpp | 5 +- Marlin/src/gcode/lcd/M0_M1.cpp | 2 +- Marlin/src/gcode/lcd/M117.cpp | 2 +- Marlin/src/gcode/probe/G30.cpp | 16 +- Marlin/src/inc/Conditionals_LCD.h | 2 +- Marlin/src/inc/MarlinConfig.h | 1 + Marlin/src/lcd/HD44780/marlinui_HD44780.cpp | 4 +- Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp | 4 +- Marlin/src/lcd/dogm/status_screen_DOGM.cpp | 2 +- .../lcd/dogm/status_screen_lite_ST7920.cpp | 23 +- Marlin/src/lcd/e3v2/creality/dwin.cpp | 6 +- Marlin/src/lcd/e3v2/jyersui/dwin.cpp | 195 ++++++----- Marlin/src/lcd/e3v2/marlinui/ui_common.cpp | 15 +- Marlin/src/lcd/e3v2/proui/bedlevel_tools.cpp | 52 ++- Marlin/src/lcd/e3v2/proui/dwin.cpp | 108 +++---- Marlin/src/lcd/e3v2/proui/gcode_preview.cpp | 22 +- Marlin/src/lcd/e3v2/proui/printstats.cpp | 16 +- Marlin/src/lcd/marlinui.cpp | 176 +++++----- Marlin/src/lcd/marlinui.h | 120 ++++++- Marlin/src/lcd/menu/menu_bed_leveling.cpp | 6 +- Marlin/src/lcd/menu/menu_configuration.cpp | 5 +- Marlin/src/lcd/menu/menu_x_twist.cpp | 6 +- Marlin/src/module/motion.cpp | 29 +- Marlin/src/module/probe.cpp | 2 +- Marlin/src/module/settings.cpp | 4 +- Marlin/src/module/temperature.cpp | 71 ++-- Marlin/src/tests/marlin_tests.cpp | 34 ++ 36 files changed, 949 insertions(+), 590 deletions(-) create mode 100644 Marlin/src/core/mstring.h diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index 914277b6096c..1406aee897ad 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -1145,7 +1145,7 @@ void setup() { #if ENABLED(MARLIN_DEV_MODE) auto log_current_ms = [&](PGM_P const msg) { SERIAL_ECHO_START(); - SERIAL_CHAR('['); SERIAL_ECHO(millis()); SERIAL_ECHOPGM("] "); + TSS('[', millis(), F("] ")).echo(); SERIAL_ECHOLNPGM_P(msg); }; #define SETUP_LOG(M) log_current_ms(PSTR(M)) diff --git a/Marlin/src/core/mstring.h b/Marlin/src/core/mstring.h new file mode 100644 index 000000000000..67591ba3aead --- /dev/null +++ b/Marlin/src/core/mstring.h @@ -0,0 +1,305 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ +#pragma once + +/** + * Lightweight string class template providing operators for all common tasks + * and conversion from F() and PSTR() strings into SRAM strings that reside + * on the stack or persistently, with overflow prevention. + * + * Examples: + * + * MString<50> mystr(F("Hello "), intvar, " World"); // "Hello 3 World" + * + * mystr.append(" (", p_float_t(123.4, 2), ')'); // "Hello 3 World (123.40)" + * + * mystr.clear(); + * + * mystr.append(spaces_t(10), repchr_t('-', 5)); // Repeats are sometimes cheaper than strings + * + * mystr.appendf(F(" ... %i/%i"), count, total); // Works like printf, requires F string + * + */ + +#include "types.h" +#include "utility.h" // AXIS_CHAR +#include "../lcd/utf8.h" + +#ifndef DEFAULT_MSTRING_SIZE + #define DEFAULT_MSTRING_SIZE 20 +#endif + +//#define UNSAFE_MSTRING // Don't initialize the string and don't terminate strncpy +//#define USE_SPRINTF // Use sprintf instead of snprintf +//#define DJB2_HASH // 32-bit hash with Djb2 algorithm +//#define MSTRING_DEBUG // Debug string operations to diagnose memory leaks +//#define FASTER_APPEND // Append without using an intermediate buffer + +// Declare externs for serial debug output +template extern void SERIAL_ECHO(T x); +template extern void SERIAL_ECHOLN(T x); +extern void SERIAL_ECHO(serial_char_t x); +extern void SERIAL_CHAR(char c); + +#define START_OF_UTF8_CHAR(C) (((C) & 0xC0u) != 0x80U) + +#if ENABLED(USE_SPRINTF) + #define SNPRINTF(A, S, V...) sprintf(A, V) + #define SNPRINTF_P(A, S, V...) sprintf_P(A, V) +#else + #define SNPRINTF(V...) snprintf(V) + #define SNPRINTF_P(V...) snprintf_P(V) +#endif + +/** + * @brief MString class template + * @details A class template providing convenient string operators, + * very similar to the Arduino String class, as it turns out. + * + * @tparam SIZE The pre-allocated storage for the string buffer + */ +template +class MString { +protected: + char str[SIZE+1]; +public: + MString() { safety(0); safety(SIZE); } + + template + MString(const T v) { set(v); safety(SIZE); } + + static_assert(SIZE > 0, "Bad SIZE for MString!"); + + void debug(FSTR_P const f) { + #if ENABLED(MSTRING_DEBUG) + SERIAL_ECHO(FTOP(f)); + SERIAL_CHAR(':'); + SERIAL_ECHO(uintptr_t(str)); + SERIAL_CHAR(' '); + SERIAL_ECHO(length()); + SERIAL_CHAR(' '); + SERIAL_ECHOLN(str); + #endif + } + + void safety(const int n) { if (SAFE && n <= SIZE) str[n] = '\0'; } + + // Chainable String Setters + MString& set() { str[0] = '\0'; debug(F("clear")); return *this; } + MString& set(char *s) { strncpy(str, s, SIZE); debug(F("string")); return *this; } + MString& set(const char *s) { return set(const_cast(s)); } + MString& set_P(PGM_P const s) { strncpy_P(str, s, SIZE); debug(F("pstring")); return *this; } + MString& set(FSTR_P const f) { return set_P(FTOP(f)); } + MString& set(const MString &s) { strncpy(str, s.str, SIZE); debug(F("MString")); return *this; } + MString& set(const bool &b) { return set(b ? F("true") : F("false")); } + MString& set(const char c) { str[0] = c; if (1 < SIZE) str[1] = '\0'; debug(F("char")); return *this; } + MString& set(const short &i) { SNPRINTF_P(str, SIZE, PSTR("%d"), i); debug(F("short")); return *this; } + MString& set(const int &i) { SNPRINTF_P(str, SIZE, PSTR("%d"), i); debug(F("int")); return *this; } + MString& set(const long &l) { SNPRINTF_P(str, SIZE, PSTR("%ld"), l); debug(F("long")); return *this; } + MString& set(const unsigned char &i) { SNPRINTF_P(str, SIZE, PSTR("%u"), i); debug(F("uchar")); return *this; } + MString& set(const unsigned short &i) { SNPRINTF_P(str, SIZE, PSTR("%u"), i); debug(F("ushort")); return *this; } + MString& set(const unsigned int &i) { SNPRINTF_P(str, SIZE, PSTR("%u"), i); debug(F("uint")); return *this; } + MString& set(const unsigned long &l) { SNPRINTF_P(str, SIZE, PSTR("%lu"), l); debug(F("ulong")); return *this; } + MString& set(const float &f) { return set(p_float_t(f, SERIAL_FLOAT_PRECISION)); } + MString& set(const p_float_t &pf) { return set(w_float_t(pf.value, 1, pf.prec)); } + MString& set(const w_float_t &wf) { char f1[20]; return set(dtostrf(wf.value, wf.width, wf.prec, f1)); } + MString& set(const serial_char_t &v) { return set(char(v.c)); } + MString& set(const xyz_pos_t &v) { set(); return append(v); } + MString& set(const xyze_pos_t &v) { set(); return append(v); } + + MString& setn(char *s, int len) { int c = _MIN(len, SIZE); strncpy(str, s, c); str[c] = '\0'; debug(F("string")); return *this; } + MString& setn(const char *s, int len) { return setn(const_cast(s), len); } + MString& setn_P(PGM_P const s, int len) { int c = _MIN(len, SIZE); strncpy_P(str, s, c); str[c] = '\0'; debug(F("pstring")); return *this; } + MString& setn(FSTR_P const f, int len) { return setn_P(FTOP(f), len); } + + // set(repchr_t('-', 10)) + MString& set(const repchr_t &s) { int c = _MIN(s.count, SIZE); memset(str, s.asc, c); str[c] = '\0'; debug(F("")); return *this; } + + // set(spaces_t(10)) + MString& set(const spaces_t &s) { repchr_t r(' ', s.count); return set(r); } + + // Set with format string and arguments, like printf + template + MString& setf_P(PGM_P const fmt, Args... more) { SNPRINTF_P(str, SIZE, fmt, more...); debug(F("setf_P")); return *this; } + + template + MString& setf(const char *fmt, Args... more) { SNPRINTF(str, SIZE, fmt, more...); debug(F("setf")); return *this; } + + template + MString& setf(FSTR_P const fmt, Args... more) { return setf_P(FTOP(fmt), more...); } + + // Chainable String appenders + MString& append() { debug(F("nil")); return *this; } // for macros that might emit no output + MString& append(char *s) { int sz = length(); if (sz < SIZE) strncpy(str + sz, s, SIZE - sz); debug(F("string")); return *this; } + MString& append(const char *s) { return append(const_cast(s)); } + MString& append_P(PGM_P const s) { int sz = length(); if (sz < SIZE) strncpy_P(str + sz, s, SIZE - sz); debug(F("pstring")); return *this; } + MString& append(FSTR_P const f) { return append_P(FTOP(f)); } + MString& append(const MString &s) { return append(s.str); } + MString& append(const bool &b) { return append(b ? F("true") : F("false")); } + MString& append(const char c) { int sz = length(); if (sz < SIZE) { str[sz] = c; if (sz < SIZE - 1) str[sz + 1] = '\0'; } return *this; } + #if ENABLED(FASTER_APPEND) + MString& append(const short &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%d", i); return *this; } + MString& append(const int &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%d", i); return *this; } + MString& append(const long &l) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%ld", l); return *this; } + MString& append(const unsigned char &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%u", i); return *this; } + MString& append(const unsigned short &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%u", i); return *this; } + MString& append(const unsigned int &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%u", i); return *this; } + MString& append(const unsigned long &l) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%lu", l); return *this; } + #else + MString& append(const short &i) { char buf[20]; sprintf(buf, "%d", i); return append(buf); } + MString& append(const int &i) { char buf[20]; sprintf(buf, "%d", i); return append(buf); } + MString& append(const long &l) { char buf[20]; sprintf(buf, "%ld", l); return append(buf); } + MString& append(const unsigned char &i) { char buf[20]; sprintf(buf, "%u", i); return append(buf); } + MString& append(const unsigned short &i) { char buf[20]; sprintf(buf, "%u", i); return append(buf); } + MString& append(const unsigned int &i) { char buf[20]; sprintf(buf, "%u", i); return append(buf); } + MString& append(const unsigned long &l) { char buf[20]; sprintf(buf, "%lu", l); return append(buf); } + #endif + MString& append(const float &f) { return append(p_float_t(f, SERIAL_FLOAT_PRECISION)); } + MString& append(const p_float_t &pf) { return append(w_float_t(pf.value, 1, pf.prec)); } + MString& append(const w_float_t &wf) { char f1[20]; return append(dtostrf(wf.value, wf.width, wf.prec, f1)); } + MString& append(const serial_char_t &v) { return append(char(v.c)); } + MString& append(const xyz_pos_t &v) { LOOP_NUM_AXES(i) { if (i) append(' '); append(AXIS_CHAR(i), v[i]); } debug(F("xyz")); return *this; } + MString& append(const xyze_pos_t &v) { LOOP_LOGICAL_AXES(i) { if (i) append(' '); append(AXIS_CHAR(i), v[i]); } debug(F("xyze")); return *this; } + + // Append only if the given space is available + MString& appendn(char *s, int len) { int sz = length(), c = _MIN(len, SIZE - sz); if (c > 0) { strncpy(str + sz, s, c); str[sz + c] = '\0'; } debug(F("string")); return *this; } + MString& appendn(const char *s, int len) { return appendn(const_cast(s), len); } + MString& appendn_P(PGM_P const s, int len) { int sz = length(), c = _MIN(len, SIZE - sz); if (c > 0) { strncpy_P(str + sz, s, c); str[sz + c] = '\0'; } debug(F("pstring")); return *this; } + MString& appendn(FSTR_P const f, int len) { return appendn_P(FTOP(f), len); } + + // append(repchr_t('-', 10)) + MString& append(const repchr_t &s) { + const int sz = length(), c = _MIN(s.count, SIZE - sz); + if (c > 0) { memset(str + sz, s.asc, c); safety(sz + c); } + debug(F("repchr")); + return *this; + } + + // append(spaces_t(10)) + MString& append(const spaces_t &s) { return append(repchr_t(' ', s.count)); } + + template + MString& appendf_P(PGM_P const fmt, Args... more) { + int sz = length(); + if (sz < SIZE) SNPRINTF_P(str + sz, SIZE - sz, fmt, more...); + debug(F("appendf_P")); + return *this; + } + + template + MString& appendf(const char *fmt, Args... more) { + const int sz = length(); + if (sz < SIZE) SNPRINTF(str + sz, SIZE - sz, fmt, more...); + debug(F("appendf")); + return *this; + } + + template + MString& appendf(FSTR_P const fmt, Args... more) { return appendf_P(FTOP(fmt), more...); } + + // Instantiate with a list of things + template + MString(T arg1, Args... more) { set(arg1); append(more...); } + + // Take a list of any number of arguments and append them to the string + template + MString& append(T arg1, Args... more) { return append(arg1).append(more...); } + + // Take a list of any number of arguments and set them in the string + template + MString& set(T arg1, Args... more) { return set(arg1).append(more...); } + + // Operator = as shorthand for set() + template + MString& operator=(const T &v) { return set(v); } + + // Operator += as shorthand for append() + template + MString& operator+=(const T &v) { return append(v); } + + // Operator + as shorthand for append-to-copy + template + MString operator+(const T &v) { return MString(str, v); } + + #ifndef __AVR__ + MString(const double d) { set(d); } + MString& set(const double &f) { return set(p_double_t(f, SERIAL_FLOAT_PRECISION)); } + MString& set(const p_double_t &pf) { return set(w_double_t(pf.value, 1, pf.prec)); } + MString& set(const w_double_t &wf) { char d1[20]; return set(dtostrf(wf.value, wf.width, wf.prec, d1)); } + MString& append(const double &f) { return append(p_double_t(f, SERIAL_FLOAT_PRECISION)); } + MString& append(const p_double_t &pf) { return append(w_double_t(pf.value, 1, pf.prec)); } + MString& append(const w_double_t &wf) { char d1[20]; return append(dtostrf(wf.value, wf.width, wf.prec, d1)); } + #endif + + // Get the character at a given index + char operator[](const int i) const { return str[i]; } + + // Cast to char* (explicit?) + operator char* () { return str; } + + // Use &mystring as shorthand for mystring.str + char* operator&() { return str; } + + // Return the buffer address (same as &) + char* buffer() { return str; } + + int length() const { return strlen(str); } + int glyphs() { return utf8_strlen(str); } + bool empty() { return !str[0]; } + + // Quick hash to detect change (e.g., to avoid expensive drawing) + typedef IF::type hash_t; + hash_t hash() const { + #if ENABLED(DJB2_HASH) + hash_t hval = 5381; + char c; + while ((c = *str++)) hval += (hval << 5) + c; // = hval * 33 + c + #else + const int sz = length(); + hash_t hval = hash_t(sz); + for (int i = 0; i < sz; i++) hval = ((hval << 1) | (hval >> 15)) ^ str[i]; // ROL, XOR + #endif + return hval; + } + + void copyto(char * const dst) const { strcpy(dst, str); } + void copyto(char * const dst, int len) const { strncpy(dst, str, len); } + + MString& clear() { return set(); } + MString& eol() { return append('\n'); } + MString& trunc(const int &i) { if (i <= SIZE) str[i] = '\0'; debug(F("trunc")); return *this; } + + // Truncate on a Unicode boundary + MString& utrunc(const int &n=SIZE) { + const int sz = length(); + if (sz && n <= sz) + for (int i = n; i >= 0; i--) if (START_OF_UTF8_CHAR(str[i])) { str[i] = '\0'; break; } + debug(F("utrunc")); + return *this; + } + +}; + +#ifndef TS_SIZE + #define TS_SIZE 63 +#endif +#define TS(V...) MString(V) diff --git a/Marlin/src/core/serial.h b/Marlin/src/core/serial.h index 09f453d3b5a8..1c2369698597 100644 --- a/Marlin/src/core/serial.h +++ b/Marlin/src/core/serial.h @@ -251,6 +251,55 @@ inline void print_pos(const xyze_pos_t &xyze, FSTR_P const prefix=nullptr, FSTR_ #define SERIAL_POS(SUFFIX,VAR) do { print_pos(VAR, F(" " STRINGIFY(VAR) "="), F(" : " SUFFIX "\n")); }while(0) #define SERIAL_XYZ(PREFIX,V...) do { print_pos(V, F(PREFIX)); }while(0) +/** + * Extended string that can echo itself to serial + */ +template +class SString : public MString { +public: + typedef MString super; + using super::str; + using super::debug; + + SString() : super() {} + + template + SString(T arg1, Args... more) : super(arg1, more...) {} + + SString& set() { super::set(); return *this; } + + template + SString& setf_P(PGM_P const fmt, Args... more) { snprintf_P(str, SIZE, fmt, more...); debug(F("setf_P")); return *this; } + + template + SString& setf(const char *fmt, Args... more) { snprintf(str, SIZE, fmt, more...); debug(F("setf")); return *this; } + + template + SString& setf(FSTR_P const fmt, Args... more) { return setf_P(FTOP(fmt), more...); } + + template + SString& set(const T &v) { super::set(v); return *this; } + + template + SString& append(const T &v) { super::append(v); return *this; } + + template + SString& set(T arg1, Args... more) { set(arg1).append(more...); return *this; } + + template + SString& append(T arg1, Args... more) { append(arg1).append(more...); return *this; } + + SString& clear() { set(); return *this; } + SString& eol() { append('\n'); return *this; } + SString& trunc(const int &i) { super::trunc(i); return *this; } + + // Extended with methods to print to serial + SString& echo() { SERIAL_ECHO(str); return *this; } + SString& echoln() { SERIAL_ECHOLN(str); return *this; } +}; + +#define TSS(V...) SString<>(V) + // // Commonly-used strings in serial output // diff --git a/Marlin/src/feature/cancel_object.cpp b/Marlin/src/feature/cancel_object.cpp index 9b658315ed8b..0040f6ed9df8 100644 --- a/Marlin/src/feature/cancel_object.cpp +++ b/Marlin/src/feature/cancel_object.cpp @@ -46,7 +46,7 @@ void CancelObject::set_active_object(const int8_t obj) { #if ALL(HAS_STATUS_MESSAGE, CANCEL_OBJECTS_REPORTING) if (active_object >= 0) - ui.status_printf(0, F(S_FMT " %i"), GET_TEXT(MSG_PRINTING_OBJECT), int(active_object)); + ui.set_status(MString<30>(GET_TEXT_F(MSG_PRINTING_OBJECT), ' ', active_object)); else ui.reset_status(); #endif diff --git a/Marlin/src/feature/powerloss.cpp b/Marlin/src/feature/powerloss.cpp index 24601cc620b8..8db86afe5156 100644 --- a/Marlin/src/feature/powerloss.cpp +++ b/Marlin/src/feature/powerloss.cpp @@ -84,11 +84,10 @@ PrintJobRecovery recovery; // Allow power-loss recovery to be aborted #define PLR_CAN_ABORT -#if ENABLED(PLR_CAN_ABORT) - #define PROCESS_SUBCOMMANDS_NOW(cmd) do { if (card.flag.abort_sd_printing) return; gcode.process_subcommands_now(cmd); }while(0) -#else - #define PROCESS_SUBCOMMANDS_NOW(cmd) gcode.process_subcommands_now(cmd) -#endif +#define PROCESS_SUBCOMMANDS_NOW(cmd) do{ \ + if (TERN0(PLR_CAN_ABORT, card.flag.abort_sd_printing)) return; \ + gcode.process_subcommands_now(cmd); \ + }while(0) /** * Clear the recovery info @@ -272,11 +271,8 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POW #if POWER_LOSS_ZRAISE // Raise the Z axis now - if (zraise) { - char cmd[20], str_1[16]; - sprintf_P(cmd, PSTR("G0Z%s"), dtostrf(zraise, 1, 3, str_1)); - gcode.process_subcommands_now(cmd); - } + if (zraise) + gcode.process_subcommands_now(TS(F("G0Z"), p_float_t(zraise, 3))); #else UNUSED(zraise); #endif @@ -360,9 +356,6 @@ void PrintJobRecovery::write() { * Resume the saved print job */ void PrintJobRecovery::resume() { - - char cmd[MAX_CMD_SIZE+16], str_1[16], str_2[16]; - const uint32_t resume_sdpos = info.sdpos; // Get here before the stepper ISR overwrites it // Apply the dry-run flag if enabled @@ -388,12 +381,9 @@ void PrintJobRecovery::resume() { #endif #if HAS_HEATED_BED + // Restore the bed temperature const celsius_t bt = info.target_temperature_bed; - if (bt) { - // Restore the bed temperature - sprintf_P(cmd, PSTR("M190S%i"), bt); - PROCESS_SUBCOMMANDS_NOW(cmd); - } + if (bt) PROCESS_SUBCOMMANDS_NOW(TS(F("M190S"), bt)); #endif // Heat hotend enough to soften material @@ -401,12 +391,8 @@ void PrintJobRecovery::resume() { HOTEND_LOOP() { const celsius_t et = _MAX(info.target_temperature[e], 180); if (et) { - #if HAS_MULTI_HOTEND - sprintf_P(cmd, PSTR("T%iS"), e); - PROCESS_SUBCOMMANDS_NOW(cmd); - #endif - sprintf_P(cmd, PSTR("M109S%i"), et); - PROCESS_SUBCOMMANDS_NOW(cmd); + TERN_(HAS_MULTI_HOTEND, PROCESS_SUBCOMMANDS_NOW(TS('T', e, 'S'))); + PROCESS_SUBCOMMANDS_NOW(TS(F("M109S"), et)); } } #endif @@ -427,11 +413,11 @@ void PrintJobRecovery::resume() { float z_now = z_raised; // If Z homing goes to max then just move back to the "raised" position - sprintf_P(cmd, PSTR( - "G28R0\n" // Home all axes (no raise) - "G1Z%sF1200" // Move Z down to (raised) height - ), dtostrf(z_now, 1, 3, str_1)); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(TS( + F( "G28R0\n" // Home all axes (no raise) + "G1F1200Z") // Move Z down to (raised) height + , p_float_t(z_now, 3) + )); #elif DISABLED(BELTPRINTER) @@ -443,15 +429,13 @@ void PrintJobRecovery::resume() { #if !HOMING_Z_DOWN // Set Z to the real position - sprintf_P(cmd, PSTR("G92.9Z%s"), dtostrf(z_now, 1, 3, str_1)); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(TS(F("G92.9Z"), p_float_t(z_now, 3))); #endif // Does Z need to be raised now? It should be raised before homing XY. if (z_raised > z_now) { z_now = z_raised; - sprintf_P(cmd, PSTR("G1Z%sF600"), dtostrf(z_now, 1, 3, str_1)); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(TS(F("G1F600Z"), p_float_t(z_now, 3))); } // Home XY with no Z raise @@ -462,8 +446,7 @@ void PrintJobRecovery::resume() { #if HOMING_Z_DOWN // Move to a safe XY position and home Z while avoiding the print. const xy_pos_t p = xy_pos_t(POWER_LOSS_ZHOME_POS) TERN_(HOMING_Z_WITH_PROBE, - probe.offset_xy); - sprintf_P(cmd, PSTR("G1X%sY%sF1000\nG28HZ"), dtostrf(p.x, 1, 3, str_1), dtostrf(p.y, 1, 3, str_2)); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(TS(F("G1F1000X"), p_float_t(p.x, 3), 'Y', p_float_t(p.y, 3), F("\nG28HZ"))); #endif // Mark all axes as having been homed (no effect on current_position) @@ -473,39 +456,30 @@ void PrintJobRecovery::resume() { // Restore Z fade and possibly re-enable bed leveling compensation. // Leveling may already be enabled due to the ENABLE_LEVELING_AFTER_G28 option. // TODO: Add a G28 parameter to leave leveling disabled. - sprintf_P(cmd, PSTR("M420S%cZ%s"), '0' + (char)info.flag.leveling, dtostrf(info.fade, 1, 1, str_1)); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(TS(F("M420S"), '0' + (char)info.flag.leveling, 'Z', p_float_t(info.fade, 1))); #if !HOMING_Z_DOWN // The physical Z was adjusted at power-off so undo the M420S1 correction to Z with G92.9. - sprintf_P(cmd, PSTR("G92.9Z%s"), dtostrf(z_now, 1, 1, str_1)); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(TS(F("G92.9Z"), p_float_t(z_now, 1))); #endif #endif #if ENABLED(POWER_LOSS_RECOVER_ZHOME) // Z was homed down to the bed, so move up to the raised height. z_now = z_raised; - sprintf_P(cmd, PSTR("G1Z%sF600"), dtostrf(z_now, 1, 3, str_1)); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(TS(F("G1F600Z"), p_float_t(z_now, 3))); #endif // Recover volumetric extrusion state #if DISABLED(NO_VOLUMETRICS) #if HAS_MULTI_EXTRUDER - EXTRUDER_LOOP() { - sprintf_P(cmd, PSTR("M200T%iD%s"), e, dtostrf(info.filament_size[e], 1, 3, str_1)); - PROCESS_SUBCOMMANDS_NOW(cmd); - } - if (!info.flag.volumetric_enabled) { - sprintf_P(cmd, PSTR("M200T%iD0"), info.active_extruder); - PROCESS_SUBCOMMANDS_NOW(cmd); - } + EXTRUDER_LOOP() + PROCESS_SUBCOMMANDS_NOW(TS(F("M200T"), e, F("D"), p_float_t(info.filament_size[e], 3))); + if (!info.flag.volumetric_enabled) + PROCESS_SUBCOMMANDS_NOW(TS(F("M200D0T"), info.active_extruder)); #else - if (info.flag.volumetric_enabled) { - sprintf_P(cmd, PSTR("M200D%s"), dtostrf(info.filament_size[0], 1, 3, str_1)); - PROCESS_SUBCOMMANDS_NOW(cmd); - } + if (info.flag.volumetric_enabled) + PROCESS_SUBCOMMANDS_NOW(TS(F("M200D"), p_float_t(info.filament_size[0], 3))); #endif #endif @@ -514,30 +488,22 @@ void PrintJobRecovery::resume() { HOTEND_LOOP() { const celsius_t et = info.target_temperature[e]; if (et) { - #if HAS_MULTI_HOTEND - sprintf_P(cmd, PSTR("T%iS"), e); - PROCESS_SUBCOMMANDS_NOW(cmd); - #endif - sprintf_P(cmd, PSTR("M109S%i"), et); - PROCESS_SUBCOMMANDS_NOW(cmd); + TERN_(HAS_MULTI_HOTEND, PROCESS_SUBCOMMANDS_NOW(TS('T', e, 'S'))); + PROCESS_SUBCOMMANDS_NOW(TS(F("M109S"), et)); } } #endif // Restore the previously active tool (with no_move) #if HAS_MULTI_EXTRUDER || HAS_MULTI_HOTEND - sprintf_P(cmd, PSTR("T%i S"), info.active_extruder); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(TS('T', info.active_extruder, 'S')); #endif // Restore print cooling fan speeds #if HAS_FAN FANS_LOOP(i) { const int f = info.fan_speed[i]; - if (f) { - sprintf_P(cmd, PSTR("M106P%iS%i"), i, f); - PROCESS_SUBCOMMANDS_NOW(cmd); - } + if (f) PROCESS_SUBCOMMANDS_NOW(TS(F("M106P"), i, 'S', f)); } #endif @@ -563,8 +529,7 @@ void PrintJobRecovery::resume() { // Additional purge on resume if configured #if POWER_LOSS_PURGE_LEN - sprintf_P(cmd, PSTR("G1F3000E%d"), (POWER_LOSS_PURGE_LEN) + (POWER_LOSS_RETRACT_LEN)); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(TS(F("G1F3000E"), (POWER_LOSS_PURGE_LEN) + (POWER_LOSS_RETRACT_LEN))); #endif #if ENABLED(NOZZLE_CLEAN_FEATURE) @@ -572,23 +537,18 @@ void PrintJobRecovery::resume() { #endif // Move back over to the saved XY - sprintf_P(cmd, PSTR("G1X%sY%sF3000"), - dtostrf(info.current_position.x, 1, 3, str_1), - dtostrf(info.current_position.y, 1, 3, str_2) - ); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(TS( + F("G1F3000X"), p_float_t(info.current_position.x, 3), 'Y', p_float_t(info.current_position.y, 3) + )); // Move back down to the saved Z for printing - sprintf_P(cmd, PSTR("G1Z%sF600"), dtostrf(z_print, 1, 3, str_1)); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(TS(F("G1F600Z"), p_float_t(z_print, 3))); // Restore the feedrate - sprintf_P(cmd, PSTR("G1F%d"), info.feedrate); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(TS(F("G1F"), info.feedrate)); // Restore E position with G92.9 - sprintf_P(cmd, PSTR("G92.9E%s"), dtostrf(info.current_position.e, 1, 3, str_1)); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(TS(F("G92.9E"), p_float_t(info.current_position.e, 3))); TERN_(GCODE_REPEAT_MARKERS, repeat = info.stored_repeat); TERN_(HAS_HOME_OFFSET, home_offset = info.home_offset); @@ -604,10 +564,8 @@ void PrintJobRecovery::resume() { enable(true); // Resume the SD file from the last position - sprintf_P(cmd, M23_STR, &info.sd_filename[0]); - PROCESS_SUBCOMMANDS_NOW(cmd); - sprintf_P(cmd, PSTR("M24S%ldT%ld"), resume_sdpos, info.print_job_elapsed); - PROCESS_SUBCOMMANDS_NOW(cmd); + PROCESS_SUBCOMMANDS_NOW(MString(F("M23 "), info.sd_filename)); + PROCESS_SUBCOMMANDS_NOW(TS(F("M24S"), resume_sdpos, 'T', info.print_job_elapsed)); } #if ENABLED(DEBUG_POWER_LOSS_RECOVERY) diff --git a/Marlin/src/feature/runout.cpp b/Marlin/src/feature/runout.cpp index 44ebd85b327f..a1d4d259b768 100644 --- a/Marlin/src/feature/runout.cpp +++ b/Marlin/src/feature/runout.cpp @@ -128,12 +128,12 @@ void event_filament_runout(const uint8_t extruder) { if (run_runout_script) { #if MULTI_FILAMENT_SENSOR - char script[strlen(FILAMENT_RUNOUT_SCRIPT) + 1]; - sprintf_P(script, PSTR(FILAMENT_RUNOUT_SCRIPT), tool); + MString script; + script.setf(F(FILAMENT_RUNOUT_SCRIPT), AS_CHAR(tool)); #if ENABLED(FILAMENT_RUNOUT_SENSOR_DEBUG) - SERIAL_ECHOLNPGM("Runout Command: ", script); + SERIAL_ECHOLNPGM("Runout Command: ", &script); #endif - queue.inject(script); + queue.inject(&script); #else #if ENABLED(FILAMENT_RUNOUT_SENSOR_DEBUG) SERIAL_ECHOPGM("Runout Command: "); diff --git a/Marlin/src/feature/tmc_util.cpp b/Marlin/src/feature/tmc_util.cpp index 556a60891457..bff6872e4da8 100644 --- a/Marlin/src/feature/tmc_util.cpp +++ b/Marlin/src/feature/tmc_util.cpp @@ -213,47 +213,46 @@ template void report_driver_otpw(TMC &st) { - char timestamp[14]; + MString<13> timestamp; duration_t elapsed = print_job_timer.duration(); const bool has_days = (elapsed.value > 60*60*24L); - (void)elapsed.toDigital(timestamp, has_days); - SERIAL_EOL(); - SERIAL_ECHO(timestamp); - SERIAL_ECHOPGM(": "); + (void)elapsed.toDigital(×tamp, has_days); + TSS('\n', timestamp, F(": ")).echo(); st.printLabel(); - SERIAL_ECHOLNPGM(" driver overtemperature warning! (", st.getMilliamps(), "mA)"); + SString<50>(F(" driver overtemperature warning! ("), st.getMilliamps(), F("mA)")).echoln(); } template void report_polled_driver_data(TMC &st, const TMC_driver_data &data) { const uint32_t pwm_scale = get_pwm_scale(st); st.printLabel(); - SERIAL_CHAR(':'); SERIAL_ECHO(pwm_scale); + SString<60> report(':', pwm_scale); #if ENABLED(TMC_DEBUG) #if HAS_TMCX1X0 || HAS_TMC220x - SERIAL_CHAR('/'); SERIAL_ECHO(data.cs_actual); + report.append('/', data.cs_actual); #endif #if HAS_STALLGUARD - SERIAL_CHAR('/'); + report += '/'; if (data.sg_result_reasonable) - SERIAL_ECHO(data.sg_result); + report += data.sg_result; else - SERIAL_CHAR('-'); + report += '-'; #endif #endif - SERIAL_CHAR('|'); - if (st.error_count) SERIAL_CHAR('E'); // Error - if (data.is_ot) SERIAL_CHAR('O'); // Over-temperature - if (data.is_otpw) SERIAL_CHAR('W'); // over-temperature pre-Warning + report += '|'; + if (st.error_count) report += 'E'; // Error + if (data.is_ot) report += 'O'; // Over-temperature + if (data.is_otpw) report += 'W'; // over-temperature pre-Warning #if ENABLED(TMC_DEBUG) - if (data.is_stall) SERIAL_CHAR('G'); // stallGuard - if (data.is_stealth) SERIAL_CHAR('T'); // stealthChop - if (data.is_standstill) SERIAL_CHAR('I'); // standstIll - #endif - if (st.flag_otpw) SERIAL_CHAR('F'); // otpw Flag - SERIAL_CHAR('|'); - if (st.otpw_count > 0) SERIAL_ECHO(st.otpw_count); - SERIAL_CHAR('\t'); + if (data.is_stall) report += 'G'; // stallGuard + if (data.is_stealth) report += 'T'; // stealthChop + if (data.is_standstill) report += 'I'; // standstIll + #endif + if (st.flag_otpw) report += 'F'; // otpw Flag + report += '|'; + if (st.otpw_count > 0) report += st.otpw_count; + report += '\t'; + report.echo(); } #if CURRENT_STEP_DOWN > 0 diff --git a/Marlin/src/gcode/calibrate/G33.cpp b/Marlin/src/gcode/calibrate/G33.cpp index ba6cccffab78..a3cb7f0b9d22 100644 --- a/Marlin/src/gcode/calibrate/G33.cpp +++ b/Marlin/src/gcode/calibrate/G33.cpp @@ -638,26 +638,26 @@ void GcodeSuite::G33() { SERIAL_ECHOPGM("std dev:", p_float_t(zero_std_dev_min, 3)); } SERIAL_EOL(); - char mess[21]; - strcpy_P(mess, PSTR("Calibration sd:")); + + MString<20> msg(F("Calibration sd:")); if (zero_std_dev_min < 1) - sprintf_P(&mess[15], PSTR("0.%03i"), (int)LROUND(zero_std_dev_min * 1000.0f)); + msg.appendf(F("0.%03i"), (int)LROUND(zero_std_dev_min * 1000.0f)); else - sprintf_P(&mess[15], PSTR("%03i.x"), (int)LROUND(zero_std_dev_min)); - ui.set_status(mess); + msg.appendf(F("%03i.x"), (int)LROUND(zero_std_dev_min)); + ui.set_status(msg); print_calibration_settings(_endstop_results, _angle_results); SERIAL_ECHOLNPGM("Save with M500 and/or copy to Configuration.h"); } else { // !end iterations - char mess[15]; + SString<14> msg; if (iterations < 31) - sprintf_P(mess, PSTR("Iteration : %02i"), (unsigned int)iterations); + msg.setf(F("Iteration : %02i"), (unsigned int)iterations); else - strcpy_P(mess, PSTR("No convergence")); - SERIAL_ECHO(mess); + msg.set(F("No convergence")); + msg.echo(); SERIAL_ECHO_SP(32); SERIAL_ECHOLNPGM("std dev:", p_float_t(zero_std_dev, 3)); - ui.set_status(mess); + ui.set_status(msg); if (verbose_level > 1) print_calibration_settings(_endstop_results, _angle_results); } @@ -667,15 +667,12 @@ void GcodeSuite::G33() { SERIAL_ECHO(enddryrun); SERIAL_ECHO_SP(35); SERIAL_ECHOLNPGM("std dev:", p_float_t(zero_std_dev, 3)); - - char mess[21]; - strcpy_P(mess, FTOP(enddryrun)); - strcpy_P(&mess[11], PSTR(" sd:")); + MString<30> msg(enddryrun, F(" sd:")); if (zero_std_dev < 1) - sprintf_P(&mess[15], PSTR("0.%03i"), (int)LROUND(zero_std_dev * 1000.0f)); + msg.appendf(F("0.%03i"), (int)LROUND(zero_std_dev * 1000.0f)); else - sprintf_P(&mess[15], PSTR("%03i.x"), (int)LROUND(zero_std_dev)); - ui.set_status(mess); + msg.appendf(F("%03i.x"), (int)LROUND(zero_std_dev)); + ui.set_status(msg); } ac_home(); } diff --git a/Marlin/src/gcode/calibrate/G34_M422.cpp b/Marlin/src/gcode/calibrate/G34_M422.cpp index 39f3d2d5dfdc..f76df4316cd6 100644 --- a/Marlin/src/gcode/calibrate/G34_M422.cpp +++ b/Marlin/src/gcode/calibrate/G34_M422.cpp @@ -277,42 +277,21 @@ void GcodeSuite::G34() { ); #endif - SERIAL_ECHOLNPGM("\n" - "Z2-Z1=", ABS(z_measured[1] - z_measured[0]) - #if TRIPLE_Z - , " Z3-Z2=", ABS(z_measured[2] - z_measured[1]) - , " Z3-Z1=", ABS(z_measured[2] - z_measured[0]) - #if QUAD_Z - , " Z4-Z3=", ABS(z_measured[3] - z_measured[2]) - , " Z4-Z2=", ABS(z_measured[3] - z_measured[1]) - , " Z4-Z1=", ABS(z_measured[3] - z_measured[0]) - #endif - #endif - ); + SERIAL_EOL(); - #if HAS_STATUS_MESSAGE - char fstr1[10]; - char msg[6 + (6 + 5) * NUM_Z_STEPPERS + 1] - #if TRIPLE_Z - , fstr2[10], fstr3[10] - #if QUAD_Z - , fstr4[10], fstr5[10], fstr6[10] - #endif - #endif - ; - sprintf_P(msg, - PSTR("1:2=%s" TERN_(TRIPLE_Z, " 3-2=%s 3-1=%s") TERN_(QUAD_Z, " 4-3=%s 4-2=%s 4-1=%s")), - dtostrf(ABS(z_measured[1] - z_measured[0]), 1, 3, fstr1) - OPTARG(TRIPLE_Z, - dtostrf(ABS(z_measured[2] - z_measured[1]), 1, 3, fstr2), - dtostrf(ABS(z_measured[2] - z_measured[0]), 1, 3, fstr3)) - OPTARG(QUAD_Z, - dtostrf(ABS(z_measured[3] - z_measured[2]), 1, 3, fstr4), - dtostrf(ABS(z_measured[3] - z_measured[1]), 1, 3, fstr5), - dtostrf(ABS(z_measured[3] - z_measured[0]), 1, 3, fstr6)) - ); - ui.set_status(msg); + SString<15 + TERN0(TRIPLE_Z, 30) + TERN0(QUAD_Z, 45)> msg(F("1:2="), p_float_t(ABS(z_measured[1] - z_measured[0]), 3)); + #if TRIPLE_Z + msg.append(F(" 3-2="), p_float_t(ABS(z_measured[2] - z_measured[1]), 3)) + .append(F(" 3-1="), p_float_t(ABS(z_measured[2] - z_measured[0]), 3)); #endif + #if QUAD_Z + msg.append(F(" 4-3="), p_float_t(ABS(z_measured[3] - z_measured[2]), 3)) + .append(F(" 4-2="), p_float_t(ABS(z_measured[3] - z_measured[1]), 3)) + .append(F(" 4-1="), p_float_t(ABS(z_measured[3] - z_measured[0]), 3)); + #endif + + msg.echoln(); + ui.set_status(msg); auto decreasing_accuracy = [](const_float_t v1, const_float_t v2) { if (v1 < v2 * 0.7f) { diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index 1701d7b86e10..28ae5caccf1b 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -1272,10 +1272,7 @@ void GcodeSuite::process_subcommands_now(FSTR_P fgcode) { for (;;) { PGM_P const delim = strchr_P(pgcode, '\n'); // Get address of next newline const size_t len = delim ? delim - pgcode : strlen_P(pgcode); // Get the command length - char cmd[len + 1]; // Allocate a stack buffer - strncpy_P(cmd, pgcode, len); // Copy the command to the stack - cmd[len] = '\0'; // End with a nul - parser.parse(cmd); // Parse the command + parser.parse(MString().setn_P(pgcode, len)); // Parse the command process_parsed_command(true); // Process it (no "ok") if (!delim) break; // Last command? pgcode = delim + 1; // Get the next command diff --git a/Marlin/src/gcode/lcd/M0_M1.cpp b/Marlin/src/gcode/lcd/M0_M1.cpp index 120a36a57828..4e4625e9483d 100644 --- a/Marlin/src/gcode/lcd/M0_M1.cpp +++ b/Marlin/src/gcode/lcd/M0_M1.cpp @@ -58,7 +58,7 @@ void GcodeSuite::M0_M1() { #if HAS_MARLINUI_MENU if (parser.string_arg) - ui.set_status(parser.string_arg, true); + ui.set_status_no_expire(parser.string_arg); else { LCD_MESSAGE(MSG_USERWAIT); #if ENABLED(LCD_PROGRESS_BAR) && PROGRESS_MSG_EXPIRE > 0 diff --git a/Marlin/src/gcode/lcd/M117.cpp b/Marlin/src/gcode/lcd/M117.cpp index 86023e12e3b4..57a26851fd07 100644 --- a/Marlin/src/gcode/lcd/M117.cpp +++ b/Marlin/src/gcode/lcd/M117.cpp @@ -33,7 +33,7 @@ void GcodeSuite::M117() { if (parser.string_arg && parser.string_arg[0]) - ui.set_status(parser.string_arg, true); + ui.set_status_no_expire(parser.string_arg); else ui.reset_status(); diff --git a/Marlin/src/gcode/probe/G30.cpp b/Marlin/src/gcode/probe/G30.cpp index d0f7b8de59e5..412b549a3efc 100644 --- a/Marlin/src/gcode/probe/G30.cpp +++ b/Marlin/src/gcode/probe/G30.cpp @@ -77,18 +77,18 @@ void GcodeSuite::G30() { const ProbePtRaise raise_after = parser.boolval('E', true) ? PROBE_PT_STOW : PROBE_PT_NONE; - TERN_(HAS_PTC, ptc.set_enabled(!parser.seen('C') || parser.value_bool())); + TERN_(HAS_PTC, ptc.set_enabled(parser.boolval('C', true))); const float measured_z = probe.probe_at_point(probepos, raise_after); TERN_(HAS_PTC, ptc.set_enabled(true)); if (!isnan(measured_z)) { - SERIAL_ECHOLNPGM("Bed X: ", probepos.asLogical().x, " Y: ", probepos.asLogical().y, " Z: ", measured_z); + const xy_pos_t lpos = probepos.asLogical(); + SString<30> msg( + F("Bed X:"), p_float_t(lpos.x, 1), + F( " Y:"), p_float_t(lpos.y, 1), + F( " Z:"), p_float_t(measured_z, 2) + ); + msg.echoln(); #if ANY(DWIN_LCD_PROUI, DWIN_CREALITY_LCD_JYERSUI) - char msg[31], str_1[6], str_2[6], str_3[6]; - sprintf_P(msg, PSTR("X:%s, Y:%s, Z:%s"), - dtostrf(probepos.x, 1, 1, str_1), - dtostrf(probepos.y, 1, 1, str_2), - dtostrf(measured_z, 1, 2, str_3) - ); ui.set_status(msg); #endif } diff --git a/Marlin/src/inc/Conditionals_LCD.h b/Marlin/src/inc/Conditionals_LCD.h index b33720b2a5f3..0ab42cb22f6c 100644 --- a/Marlin/src/inc/Conditionals_LCD.h +++ b/Marlin/src/inc/Conditionals_LCD.h @@ -542,7 +542,7 @@ #define HAS_UTF8_UTILS 1 #endif -#if HAS_DISPLAY || HAS_DWIN_E3V2 +#if ANY(HAS_DISPLAY, HAS_DWIN_E3V2) #define HAS_STATUS_MESSAGE 1 #endif diff --git a/Marlin/src/inc/MarlinConfig.h b/Marlin/src/inc/MarlinConfig.h index 40c7c2482ec8..cd82ec545762 100644 --- a/Marlin/src/inc/MarlinConfig.h +++ b/Marlin/src/inc/MarlinConfig.h @@ -55,6 +55,7 @@ // Include all core headers #include "../core/language.h" #include "../core/utility.h" + #include "../core/mstring.h" #include "../core/serial.h" #endif diff --git a/Marlin/src/lcd/HD44780/marlinui_HD44780.cpp b/Marlin/src/lcd/HD44780/marlinui_HD44780.cpp index 1aa53241ad99..c5b4df46f28c 100644 --- a/Marlin/src/lcd/HD44780/marlinui_HD44780.cpp +++ b/Marlin/src/lcd/HD44780/marlinui_HD44780.cpp @@ -731,7 +731,7 @@ void MarlinUI::draw_status_message(const bool blink) { static bool last_blink = false; // Get the UTF8 character count of the string - uint8_t slen = utf8_strlen(status_message); + uint8_t slen = status_message.glyphs(); // If the string fits into the LCD, just print it and do not scroll it if (slen <= LCD_WIDTH) { @@ -773,7 +773,7 @@ void MarlinUI::draw_status_message(const bool blink) { UNUSED(blink); // Get the UTF8 character count of the string - uint8_t slen = utf8_strlen(status_message); + uint8_t slen = status_message.glyphs(); // Just print the string to the LCD lcd_put_u8str_max(status_message, LCD_WIDTH); diff --git a/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp b/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp index ee7154ae417c..02e5f05d92a1 100644 --- a/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp +++ b/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp @@ -419,7 +419,7 @@ void MarlinUI::draw_kill_screen() { if (!PanelDetected) return; lcd.clear_buffer(); lcd_moveto(0, 3); lcd.write(COLOR_ERROR); - lcd_moveto((LCD_WIDTH - utf8_strlen(status_message)) / 2 + 1, 3); + lcd_moveto((LCD_WIDTH - status_message.glyphs()) / 2 + 1, 3); lcd_put_u8str(status_message); center_text(GET_TEXT_F(MSG_HALTED), 5); center_text(GET_TEXT_F(MSG_PLEASE_RESET), 6); @@ -692,7 +692,7 @@ void MarlinUI::draw_status_message(const bool blink) { #endif // FILAMENT_LCD_DISPLAY && HAS_MEDIA // Get the UTF8 character count of the string - uint8_t slen = utf8_strlen(status_message); + uint8_t slen = status_message.glyphs(); #if ENABLED(STATUS_MESSAGE_SCROLLING) diff --git a/Marlin/src/lcd/dogm/status_screen_DOGM.cpp b/Marlin/src/lcd/dogm/status_screen_DOGM.cpp index 12cee1fc80d4..5bce83849f83 100644 --- a/Marlin/src/lcd/dogm/status_screen_DOGM.cpp +++ b/Marlin/src/lcd/dogm/status_screen_DOGM.cpp @@ -915,7 +915,7 @@ void MarlinUI::draw_status_message(const bool blink) { // Get the UTF8 character count of the string uint8_t lcd_width = LCD_WIDTH, pixel_width = LCD_PIXEL_WIDTH, - slen = utf8_strlen(status_message); + slen = status_message.glyphs(); #if HAS_POWER_MONITOR if (power_monitor.display_enabled()) { diff --git a/Marlin/src/lcd/dogm/status_screen_lite_ST7920.cpp b/Marlin/src/lcd/dogm/status_screen_lite_ST7920.cpp index 8e709416288f..a6e942b7066e 100644 --- a/Marlin/src/lcd/dogm/status_screen_lite_ST7920.cpp +++ b/Marlin/src/lcd/dogm/status_screen_lite_ST7920.cpp @@ -568,16 +568,14 @@ void ST7920_Lite_Status_Screen::draw_feedrate_percentage(const uint16_t percenta } void ST7920_Lite_Status_Screen::draw_status_message() { - const char *str = ui.status_message; - set_ddram_address(DDRAM_LINE_4); begin_data(); #if ENABLED(STATUS_MESSAGE_SCROLLING) - uint8_t slen = utf8_strlen(str); + uint8_t slen = ui.status_message.glyphs(); if (slen <= TEXT_MODE_LCD_WIDTH) { // String fits the LCD, so just print it - write_str(str); + write_str(ui.status_message); while (slen < TEXT_MODE_LCD_WIDTH) { write_byte(' '); ++slen; } } else { // String is larger than the available space in ST7920_Lite_Status_Screen:: @@ -595,7 +593,7 @@ void ST7920_Lite_Status_Screen::draw_status_message() { write_byte(' '); if (--chars) { // Draw a third space if there's room write_byte(' '); - if (--chars) write_str(str, chars); // Print a second copy of the message + if (--chars) write_str(ui.status_message, chars); // Print a second copy of the message } } } @@ -604,8 +602,8 @@ void ST7920_Lite_Status_Screen::draw_status_message() { #else - uint8_t slen = utf8_strlen(str); - write_str(str, TEXT_MODE_LCD_WIDTH); + uint8_t slen = ui.status_message.glyphs(); + write_str(ui.status_message, TEXT_MODE_LCD_WIDTH); for (; slen < TEXT_MODE_LCD_WIDTH; ++slen) write_byte(' '); #endif @@ -853,11 +851,10 @@ bool ST7920_Lite_Status_Screen::position_changed() { } bool ST7920_Lite_Status_Screen::status_changed() { - uint8_t checksum = 0; - for (const char *p = ui.status_message; *p; p++) checksum ^= *p; - static uint8_t last_checksum = 0; - bool changed = last_checksum != checksum; - if (changed) last_checksum = checksum; + static MString<>::hash_t last_hash = 0; + const MString<>::hash_t hash = ui.status_message.hash(); + const bool changed = last_hash != hash; + if (changed) last_hash = hash; return changed; } @@ -892,7 +889,7 @@ void ST7920_Lite_Status_Screen::update_status_or_position(bool forceUpdate) { if (forceUpdate || status_changed()) { TERN_(STATUS_MESSAGE_SCROLLING, ui.status_scroll_offset = 0); #if STATUS_EXPIRE_SECONDS - countdown = ui.status_message[0] ? STATUS_EXPIRE_SECONDS : 0; + countdown = !ui.status_message.empty() ? STATUS_EXPIRE_SECONDS : 0; #endif draw_status_message(); blink_changed(); // Clear changed flag diff --git a/Marlin/src/lcd/e3v2/creality/dwin.cpp b/Marlin/src/lcd/e3v2/creality/dwin.cpp index 322cb37d2884..5e66b31cfa46 100644 --- a/Marlin/src/lcd/e3v2/creality/dwin.cpp +++ b/Marlin/src/lcd/e3v2/creality/dwin.cpp @@ -1215,7 +1215,7 @@ void drawPrintProgressBar() { void drawPrintProgressElapsed() { constexpr uint16_t x = 45, y = 192; - duration_t elapsed = print_job_timer.duration(); // print timer + duration_t elapsed = print_job_timer.duration(); // Print timer dwinDrawIntValue(true, true, 1, font8x16, Color_White, Color_Bg_Black, 2, x, y, elapsed.value / 3600); dwinDrawString(false, font8x16, Color_White, Color_Bg_Black, x + 8 * 2, y, F(":")); dwinDrawIntValue(true, true, 1, font8x16, Color_White, Color_Bg_Black, 2, x + 8 * 3, y, (elapsed.value % 3600) / 60); @@ -4104,7 +4104,7 @@ void eachMomentUpdate() { queue.inject(F("G1 F1200 X0 Y0")); } - if (card.isPrinting() && checkkey == PrintProcess) { // print process + if (card.isPrinting() && checkkey == PrintProcess) { // Print process const uint8_t card_pct = card.percentDone(); static uint8_t last_cardpercentValue = 101; if (last_cardpercentValue != card_pct) { // print percent @@ -4115,7 +4115,7 @@ void eachMomentUpdate() { } } - duration_t elapsed = print_job_timer.duration(); // print timer + duration_t elapsed = print_job_timer.duration(); // Print timer // Print time so far static uint16_t last_Printtime = 0; diff --git a/Marlin/src/lcd/e3v2/jyersui/dwin.cpp b/Marlin/src/lcd/e3v2/jyersui/dwin.cpp index 04d5dfd7444f..564ec539af20 100644 --- a/Marlin/src/lcd/e3v2/jyersui/dwin.cpp +++ b/Marlin/src/lcd/e3v2/jyersui/dwin.cpp @@ -183,7 +183,6 @@ float valuemax; uint8_t valueunit; uint8_t valuetype; -char cmd[MAX_CMD_SIZE+16], str_1[16], str_2[16], str_3[16]; char statusmsg[64]; char filename[LONG_FILENAME_LENGTH]; bool printing = false; @@ -267,8 +266,9 @@ class TextScroller { uint8_t tilt_grid = 1; void manual_value_update(bool undefined=false) { - sprintf_P(cmd, PSTR("M421 I%i J%i Z%s %s"), mesh_x, mesh_y, dtostrf(current_position.z, 1, 3, str_1), undefined ? "N" : ""); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("M421I"), mesh_x, 'J', mesh_y, 'Z', p_float_t(current_position.z, 3), undefined ? "N" : "") + ); planner.synchronize(); } @@ -313,8 +313,9 @@ class TextScroller { #else void manual_value_update() { - sprintf_P(cmd, PSTR("G29 I%i J%i Z%s"), mesh_x, mesh_y, dtostrf(current_position.z, 1, 3, str_1)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G29I"), mesh_x, 'J', mesh_y, 'Z', p_float_t(current_position.z, 3)) + ); planner.synchronize(); } @@ -329,10 +330,8 @@ class TextScroller { } else { crealityDWIN.popupHandler(MoveWait); - sprintf_P(cmd, PSTR("G0 F300 Z%s"), dtostrf(Z_CLEARANCE_BETWEEN_PROBES, 1, 3, str_1)); - gcode.process_subcommands_now(cmd); - sprintf_P(cmd, PSTR("G42 F4000 I%i J%i"), mesh_x, mesh_y); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now(TS(F("G0F300Z"), p_float_t(current_position.z, 3))); + gcode.process_subcommands_now(TS(F("G42 F4000 I"), mesh_x, 'J', mesh_y)); planner.synchronize(); current_position.z = goto_mesh_value ? bedlevel.z_values[mesh_x][mesh_y] : Z_CLEARANCE_BETWEEN_PROBES; planner.buffer_line(current_position, homing_feedrate(Z_AXIS), active_extruder); @@ -377,7 +376,6 @@ class TextScroller { } // Draw value square grid - char buf[8]; GRID_LOOP(x, y) { const auto start_x_px = padding_x + x * cell_width_px; const auto end_x_px = start_x_px + cell_width_px - 1 - gridline_width; @@ -402,14 +400,15 @@ class TextScroller { dwinDrawString(false, font6x12, Color_White, Color_Bg_Blue, start_x_px + cell_width_px / 2 - 5, start_y_px + offset_y, F("X")); } else { // has value + MString<12> msg; if (GRID_MAX_POINTS_X < 10) - sprintf_P(buf, PSTR("%s"), dtostrf(abs(bedlevel.z_values[x][y]), 1, 2, str_1)); + msg.set(p_float_t(abs(bedlevel.z_values[x][y]), 2)); else - sprintf_P(buf, PSTR("%02i"), (uint16_t)(abs(bedlevel.z_values[x][y] - (int16_t)bedlevel.z_values[x][y]) * 100)); - offset_x = cell_width_px / 2 - 3 * (strlen(buf)) - 2; + msg.setf(F("%02i"), uint16_t(abs(bedlevel.z_values[x][y] - int16_t(bedlevel.z_values[x][y])) * 100)); + offset_x = cell_width_px / 2 - 3 * msg.length() - 2; if (!(GRID_MAX_POINTS_X < 10)) dwinDrawString(false, font6x12, Color_White, Color_Bg_Blue, start_x_px - 2 + offset_x, start_y_px + offset_y /*+ square / 2 - 6*/, F(".")); - dwinDrawString(false, font6x12, Color_White, Color_Bg_Blue, start_x_px + 1 + offset_x, start_y_px + offset_y /*+ square / 2 - 6*/, buf); + dwinDrawString(false, font6x12, Color_White, Color_Bg_Blue, start_x_px + 1 + offset_x, start_y_px + offset_y /*+ square / 2 - 6*/, msg); } safe_delay(10); LCD_SERIAL.flushTX(); @@ -418,21 +417,22 @@ class TextScroller { } void setMeshViewerStatus() { // TODO: draw gradient with values as a legend instead - float v_max = abs(get_max_value()), v_min = abs(get_min_value()), range = _MAX(v_min, v_max); - if (v_min > 3e+10F) v_min = 0.0000001; - if (v_max > 3e+10F) v_max = 0.0000001; - if (range > 3e+10F) range = 0.0000001; - char msg[46]; + float v1, v2, + v_min = abs(get_min_value()), + v_max = abs(get_max_value()); if (viewer_asymmetric_range) { - dtostrf(-v_min, 1, 3, str_1); - dtostrf( v_max, 1, 3, str_2); + if (v_min > 3e+10F) v_min = 0.0000001; + if (v_max > 3e+10F) v_max = 0.0000001; + v1 = -v_min; + v2 = v_max; } else { - dtostrf(-range, 1, 3, str_1); - dtostrf( range, 1, 3, str_2); + float range = _MAX(v_min, v_max); + if (range > 3e+10F) range = 0.0000001; + v1 = -range; + v2 = range; } - sprintf_P(msg, PSTR("Red %s..0..%s Green"), str_1, str_2); - crealityDWIN.updateStatus(msg); + crealityDWIN.updateStatus(TS(F("Red "), p_float_t(v1, 3) , F("..0.."), p_float_t(v2, 3), F(" Green"))); drawing_mesh = false; } @@ -1199,8 +1199,7 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool thermalManager.wait_for_hotend(0); } popupHandler(FilChange); - sprintf_P(cmd, PSTR("M600 B1 R%i"), thermalManager.temp_hotend[0].target); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now(TS(F("M600 B1 R"), thermalManager.temp_hotend[0].target)); } #endif } @@ -1445,15 +1444,17 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool popupHandler(MoveWait); if (use_probe) { #if HAS_BED_PROBE - sprintf_P(cmd, PSTR("G0 F4000\nG0 Z10\nG0 X%s Y%s"), dtostrf(probe_x_min, 1, 3, str_1), dtostrf(probe_y_min, 1, 3, str_2)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G0F4000\nG0Z10\nG0X"), p_float_t(probe_x_min, 3), 'Y', p_float_t(probe_y_min, 3)) + ); planner.synchronize(); popupHandler(ManualProbing); #endif } else { - sprintf_P(cmd, PSTR("G0 F4000\nG0 Z10\nG0 X%s Y%s\nG0 F300 Z%s"), dtostrf(corner_pos, 1, 3, str_1), dtostrf(corner_pos, 1, 3, str_2), dtostrf(mlev_z_pos, 1, 3, str_3)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G0F4000\nG0Z10\nG0X"), p_float_t(corner_pos, 3), 'Y', p_float_t(corner_pos, 3), F("\nG0F300Z"), p_float_t(mlev_z_pos, 3)) + ); planner.synchronize(); redrawMenu(); } @@ -1466,15 +1467,17 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool popupHandler(MoveWait); if (use_probe) { #if HAS_BED_PROBE - sprintf_P(cmd, PSTR("G0 F4000\nG0 Z10\nG0 X%s Y%s"), dtostrf(probe_x_min, 1, 3, str_1), dtostrf(probe_y_max, 1, 3, str_2)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G0F4000\nG0Z10\nG0X"), p_float_t(probe_x_min, 3), 'Y', p_float_t(probe_y_max, 3)) + ); planner.synchronize(); popupHandler(ManualProbing); #endif } else { - sprintf_P(cmd, PSTR("G0 F4000\nG0 Z10\nG0 X%s Y%s\nG0 F300 Z%s"), dtostrf(corner_pos, 1, 3, str_1), dtostrf((Y_BED_SIZE + Y_MIN_POS) - corner_pos, 1, 3, str_2), dtostrf(mlev_z_pos, 1, 3, str_3)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G0F4000\nG0Z10\nG0X"), p_float_t(corner_pos, 3), 'Y', p_float_t((Y_BED_SIZE + Y_MIN_POS) - corner_pos, 3), F("\nG0F300Z"), p_float_t(mlev_z_pos, 3)) + ); planner.synchronize(); redrawMenu(); } @@ -1487,15 +1490,17 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool popupHandler(MoveWait); if (use_probe) { #if HAS_BED_PROBE - sprintf_P(cmd, PSTR("G0 F4000\nG0 Z10\nG0 X%s Y%s"), dtostrf(probe_x_max, 1, 3, str_1), dtostrf(probe_y_max, 1, 3, str_2)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G0F4000\nG0Z10\nG0X"), p_float_t(probe_x_max, 3), 'Y', p_float_t(probe_y_max, 3), F("\nG0F300Z"), p_float_t(mlev_z_pos, 3)) + ); planner.synchronize(); popupHandler(ManualProbing); #endif } else { - sprintf_P(cmd, PSTR("G0 F4000\nG0 Z10\nG0 X%s Y%s\nG0 F300 Z%s"), dtostrf((X_BED_SIZE + X_MIN_POS) - corner_pos, 1, 3, str_1), dtostrf((Y_BED_SIZE + Y_MIN_POS) - corner_pos, 1, 3, str_2), dtostrf(mlev_z_pos, 1, 3, str_3)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G0F4000\nG0Z10\nG0X"), p_float_t((X_BED_SIZE + X_MIN_POS) - corner_pos, 3), 'Y', p_float_t((Y_BED_SIZE + Y_MIN_POS) - corner_pos, 3), F("\nG0F300Z"), p_float_t(mlev_z_pos, 3)) + ); planner.synchronize(); redrawMenu(); } @@ -1508,15 +1513,17 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool popupHandler(MoveWait); if (use_probe) { #if HAS_BED_PROBE - sprintf_P(cmd, PSTR("G0 F4000\nG0 Z10\nG0 X%s Y%s"), dtostrf(probe_x_max, 1, 3, str_1), dtostrf(probe_y_min, 1, 3, str_2)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G0F4000\nG0Z10\nG0X"), p_float_t(probe_x_max, 3), 'Y', p_float_t(probe_y_min, 3)) + ); planner.synchronize(); popupHandler(ManualProbing); #endif } else { - sprintf_P(cmd, PSTR("G0 F4000\nG0 Z10\nG0 X%s Y%s\nG0 F300 Z%s"), dtostrf((X_BED_SIZE + X_MIN_POS) - corner_pos, 1, 3, str_1), dtostrf(corner_pos, 1, 3, str_2), dtostrf(mlev_z_pos, 1, 3, str_3)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G0F4000\nG0Z10\nG0X"), p_float_t((X_BED_SIZE + X_MIN_POS) - corner_pos, 3), 'Y', p_float_t(corner_pos, 3), F("\nG0F300Z"), p_float_t(mlev_z_pos, 3)) + ); planner.synchronize(); redrawMenu(); } @@ -1529,15 +1536,17 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool popupHandler(MoveWait); if (use_probe) { #if HAS_BED_PROBE - sprintf_P(cmd, PSTR("G0 F4000\nG0 Z10\nG0 X%s Y%s"), dtostrf(X_MAX_POS / 2.0f - probe.offset.x, 1, 3, str_1), dtostrf(Y_MAX_POS / 2.0f - probe.offset.y, 1, 3, str_2)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G0F4000\nG0Z10\nG0X"), p_float_t((X_MAX_POS) / 2.0f - probe.offset.x, 3), 'Y', p_float_t((Y_MAX_POS) / 2.0f - probe.offset.y, 3)) + ); planner.synchronize(); popupHandler(ManualProbing); #endif } else { - sprintf_P(cmd, PSTR("G0 F4000\nG0 Z10\nG0 X%s Y%s\nG0 F300 Z%s"), dtostrf((X_BED_SIZE + X_MIN_POS) / 2.0f, 1, 3, str_1), dtostrf((Y_BED_SIZE + Y_MIN_POS) / 2.0f, 1, 3, str_2), dtostrf(mlev_z_pos, 1, 3, str_3)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G0F4000\nG0Z10\nG0X"), p_float_t((X_BED_SIZE + X_MIN_POS) - corner_pos, 3), 'Y', p_float_t((Y_BED_SIZE + Y_MIN_POS) / 2.0f, 3), F("\nG0F300Z"), p_float_t(mlev_z_pos, 3)) + ); planner.synchronize(); redrawMenu(); } @@ -1582,16 +1591,17 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool drawMenuItem(row, ICON_Homing, F("Home Z Axis")); else { popupHandler(Home); - gcode.process_subcommands_now(F("G28 Z")); + gcode.process_subcommands_now(F("G28Z")); popupHandler(MoveWait); #if ENABLED(Z_SAFE_HOMING) planner.synchronize(); - sprintf_P(cmd, PSTR("G0 F4000 X%s Y%s"), dtostrf(Z_SAFE_HOMING_X_POINT, 1, 3, str_1), dtostrf(Z_SAFE_HOMING_Y_POINT, 1, 3, str_2)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G0F4000X"), p_float_t(Z_SAFE_HOMING_X_POINT, 3), 'Y', p_float_t(Z_SAFE_HOMING_Y_POINT, 3)); + ); #else gcode.process_subcommands_now(F("G0 F4000 X117.5 Y117.5")); #endif - gcode.process_subcommands_now(F("G0 F300 Z0")); + gcode.process_subcommands_now(F("G0F300Z0")); planner.synchronize(); redrawMenu(); } @@ -1610,12 +1620,13 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool popupHandler(MoveWait); #if ENABLED(Z_SAFE_HOMING) planner.synchronize(); - sprintf_P(cmd, PSTR("G0 F4000 X%s Y%s"), dtostrf(Z_SAFE_HOMING_X_POINT, 1, 3, str_1), dtostrf(Z_SAFE_HOMING_Y_POINT, 1, 3, str_2)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G0F4000X"), p_float_t(Z_SAFE_HOMING_X_POINT, 3), 'Y', p_float_t(Z_SAFE_HOMING_Y_POINT, 3)); + ); #else gcode.process_subcommands_now(F("G0 F4000 X117.5 Y117.5")); #endif - gcode.process_subcommands_now(F("G0 F300 Z0")); + gcode.process_subcommands_now(F("G0F300Z0")); planner.synchronize(); redrawMenu(); } @@ -1780,8 +1791,7 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool thermalManager.wait_for_hotend(0); } popupHandler(FilChange); - sprintf_P(cmd, PSTR("M600 B1 R%i"), thermalManager.temp_hotend[0].target); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now(TS(F("M600B1R"), thermalManager.temp_hotend[0].target)); } } break; @@ -2125,8 +2135,7 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool drawMenuItem(row, ICON_HotendTemp, F("Autotune")); else { popupHandler(PIDWait); - sprintf_P(cmd, PSTR("M303 E0 C%i S%i U1"), PID_cycles, PID_e_temp); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now(TS(F("M303E0C"), PID_cycles, 'S', PID_e_temp, 'U')); planner.synchronize(); redrawMenu(); } @@ -2192,8 +2201,7 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool drawMenuItem(row, ICON_HotendTemp, F("Autotune")); else { popupHandler(PIDWait); - sprintf_P(cmd, PSTR("M303 E-1 C%i S%i U1"), PID_cycles, PID_bed_temp); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now(TS(F("M303E-1C"), PID_cycles, 'S', PID_bed_temp, 'U')); planner.synchronize(); redrawMenu(); } @@ -2917,8 +2925,9 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool if (draw) drawMenuItem(row, ICON_StepY, F("M48 Probe Test")); else { - sprintf_P(cmd, PSTR("G28O\nM48 X%s Y%s P%i"), dtostrf((X_BED_SIZE + X_MIN_POS) / 2.0f, 1, 3, str_1), dtostrf((Y_BED_SIZE + Y_MIN_POS) / 2.0f, 1, 3, str_2), testcount); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G28O\nM48X" , p_float_t((X_BED_SIZE + X_MIN_POS) / 2.0f, 3), 'Y', p_float_t((Y_BED_SIZE + Y_MIN_POS) / 2.0f, 3), 'P', testcount)) + ); } break; case PROBE_TEST_COUNT: @@ -2970,9 +2979,7 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool #if AXIS_IS_TMC(Y) case TMC_STEPPER_CURRENT_Y: - static float stepper_current_y; - if (draw) { drawMenuItem(row, ICON_StepY, F("Stepper Y current")); stepper_current_y = stepperY.getMilliamps(); @@ -2986,9 +2993,7 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool #if AXIS_IS_TMC(Z) case TMC_STEPPER_CURRENT_Z: - static float stepper_current_z; - if (draw) { drawMenuItem(row, ICON_StepZ, F("Stepper Z current")); stepper_current_z = stepperZ.getMilliamps(); @@ -3002,17 +3007,14 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool #if AXIS_IS_TMC(E0) case TMC_STEPPER_CURRENT_E: - static float stepper_current_e; - if (draw) { drawMenuItem(row, ICON_StepE, F("Stepper E current")); stepper_current_e = stepperE0.getMilliamps(); drawFloat(stepper_current_e, row, false, 1); } - else { + else modifyValue(stepper_current_e, TMC_MIN_CURRENT, TMC_MAX_CURRENT, 1, []{ stepperE0.rms_current(stepper_current_e); }); - } break; #endif }; @@ -3036,18 +3038,21 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool drawMenuItem(row, ICON_Back, F("Back")); #if ENABLED(PRINTCOUNTER) - char row1[50], row2[50], buf[32]; + printStatistics ps = print_job_timer.getStats(); + drawMenuItem(INFO_PRINTCOUNT, ICON_HotendTemp, + TS(ps.totalPrints, F(" prints, "), ps.finishedPrints, F(" finished")), + TS(p_float_t(ps.filamentUsed / 1000, 2), F(" m filament used")), + false, true + ); - sprintf_P(row1, PSTR("%i prints, %i finished"), ps.totalPrints, ps.finishedPrints); - sprintf_P(row2, PSTR("%s m filament used"), dtostrf(ps.filamentUsed / 1000, 1, 2, str_1)); - drawMenuItem(INFO_PRINTCOUNT, ICON_HotendTemp, row1, row2, false, true); + char buf[32]; + drawMenuItem(INFO_PRINTTIME, ICON_PrintTime, + MString<50>(F("Printed: "), duration_t(print_job_timer.getStats().printTime).toString(buf)), + MString<50>(F("Longest: "), duration_t(print_job_timer.getStats().longestPrint).toString(buf)), + false, true + ); - duration_t(print_job_timer.getStats().printTime).toString(buf); - sprintf_P(row1, PSTR("Printed: %s"), buf); - duration_t(print_job_timer.getStats().longestPrint).toString(buf); - sprintf_P(row2, PSTR("Longest: %s"), buf); - drawMenuItem(INFO_PRINTTIME, ICON_PrintTime, row1, row2, false, true); #endif drawMenuItem(INFO_SIZE, ICON_PrintSize, F(MACHINE_SIZE), nullptr, false, true); @@ -3117,11 +3122,10 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool gcode.home_all_axes(true); popupHandler(Level); if (mesh_conf.tilt_grid > 1) { - sprintf_P(cmd, PSTR("G29 J%i"), mesh_conf.tilt_grid); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now(TS(F("G29J"), mesh_conf.tilt_grid)); } else - gcode.process_subcommands_now(F("G29 J")); + gcode.process_subcommands_now(F("G29J")); planner.synchronize(); redrawMenu(); } @@ -3140,8 +3144,8 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool #endif #if HAS_BED_PROBE popupHandler(Level); - gcode.process_subcommands_now(F("G29 P0\nG29 P1")); - gcode.process_subcommands_now(F("G29 P3\nG29 P3\nG29 P3\nG29 P3\nG29 P3\nG29 P3\nG29 P3\nG29 P3\nG29 P3\nG29 P3\nG29 P3\nG29 P3\nG29 P3\nG29 P3\nG29 P3\nM420 S1")); + gcode.process_subcommands_now(F("G29P0\nG29P1")); + gcode.process_subcommands_now(F("G29P3\nG29P3\nG29P3\nG29P3\nG29P3\nG29P3\nG29P3\nG29P3\nG29P3\nG29P3\nG29P3\nG29P3\nG29P3\nG29P3\nG29P3\nM420S1")); planner.synchronize(); updateStatus("Probed all reachable points"); popupHandler(SaveLevel); @@ -3884,8 +3888,7 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool switch (last_menu) { case Prepare: popupHandler(FilChange); - sprintf_P(cmd, PSTR("M600 B1 R%i"), thermalManager.temp_hotend[0].target); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now(TS(F("M600 B1 R"), thermalManager.temp_hotend[0].target)); break; #if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES) case ChangeFilament: @@ -3904,8 +3907,7 @@ void CrealityDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool break; case CHANGEFIL_CHANGE: popupHandler(FilChange); - sprintf_P(cmd, PSTR("M600 B1 R%i"), thermalManager.temp_hotend[0].target); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now(TS(F("M600 B1 R"), thermalManager.temp_hotend[0].target)); break; } break; @@ -4205,8 +4207,7 @@ void CrealityDWIN::valueControl() { sync_plan_position(); } else if (active_menu == Tune && selection == TUNE_ZOFFSET) { - sprintf_P(cmd, PSTR("M290 Z%s"), dtostrf((tempvalue / valueunit - zoffsetvalue), 1, 3, str_1)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now(TS(F("M290Z"), p_float_t((tempvalue / valueunit - zoffsetvalue), 3))); } if (TERN0(HAS_HOTEND, valuepointer == &thermalManager.temp_hotend[0].pid.Ki) || TERN0(HAS_HEATED_BED, valuepointer == &thermalManager.temp_bed.pid.Ki)) tempvalue = scalePID_i(tempvalue); @@ -4388,14 +4389,11 @@ void CrealityDWIN::printScreenControl() { card.startOrResumeFilePrinting(); TERN_(POWER_LOSS_RECOVERY, recovery.prepare()); #else - char cmd[20]; #if HAS_HEATED_BED - sprintf_P(cmd, PSTR("M140 S%i"), pausebed); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now(TS(F("M140 S"), pausebed)); #endif #if HAS_EXTRUDERS - sprintf_P(cmd, PSTR("M109 S%i"), pausetemp); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now(TS(F("M109 S"), pausetemp)); #endif TERN_(HAS_FAN, thermalManager.fan_speed[0] = pausefan); planner.synchronize(); @@ -4494,10 +4492,8 @@ void CrealityDWIN::popupControl() { #if HAS_BED_PROBE case ManualProbing: if (selection == 0) { - char buf[80]; const float dif = probe.probe_at_point(current_position.x, current_position.y, PROBE_PT_STOW, 0, false) - corner_avg; - sprintf_P(buf, dif > 0 ? PSTR("Corner is %smm high") : PSTR("Corner is %smm low"), dtostrf(abs(dif), 1, 3, str_1)); - updateStatus(buf); + updateStatus(TS(F("Corner is "), p_float_t(abs(dif), 3), "mm ", dif > 0 ? F("high") : F("low"))); } else { redrawMenu(true, true, false); @@ -4517,8 +4513,7 @@ void CrealityDWIN::popupControl() { thermalManager.wait_for_hotend(0); } popupHandler(FilChange); - sprintf_P(cmd, PSTR("M600 B1 R%i"), thermalManager.temp_hotend[0].target); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now(TS(F("M600B1R"), thermalManager.temp_hotend[0].target)); } } else diff --git a/Marlin/src/lcd/e3v2/marlinui/ui_common.cpp b/Marlin/src/lcd/e3v2/marlinui/ui_common.cpp index 817699fd8a97..3cf89e7b3e0f 100644 --- a/Marlin/src/lcd/e3v2/marlinui/ui_common.cpp +++ b/Marlin/src/lcd/e3v2/marlinui/ui_common.cpp @@ -159,7 +159,7 @@ void MarlinUI::draw_kill_screen() { dwinIconShow(ICON, ICON_Halted, (LCD_PIXEL_WIDTH - 96) / 2, 40); #endif - uint8_t slen = utf8_strlen(status_message); + uint8_t slen = status_message.glyphs(); lcd_moveto(cx - (slen / 2), cy - 1); lcd_put_u8str(status_message); @@ -185,13 +185,8 @@ void MarlinUI::draw_status_message(const bool blink) { constexpr uint8_t max_status_chars = (LCD_PIXEL_WIDTH) / (STAT_FONT_WIDTH); auto status_changed = []{ - static uint16_t old_hash = 0x0000; - uint16_t hash = 0x0000; - for (uint8_t i = 0; i < MAX_MESSAGE_LENGTH; i++) { - const char c = ui.status_message[i]; - if (!c) break; - hash = ((hash << 1) | (hash >> 15)) ^ c; - } + static MString<>::hash_t old_hash = 0x0000; + const MString<>::hash_t hash = ui.status_message.hash(); const bool hash_changed = hash != old_hash; old_hash = hash; return hash_changed || !did_first_redraw; @@ -201,7 +196,7 @@ void MarlinUI::draw_status_message(const bool blink) { static bool last_blink = false; // Get the UTF8 character count of the string - uint8_t slen = utf8_strlen(status_message); + uint8_t slen = status_message.glyphs(); // If the string fits into the LCD, just print it and do not scroll it if (slen <= max_status_chars) { @@ -247,7 +242,7 @@ void MarlinUI::draw_status_message(const bool blink) { if (status_changed()) { // Get the UTF8 character count of the string - uint8_t slen = utf8_strlen(status_message); + uint8_t slen = status_message.glyphs(); // Just print the string to the LCD lcd_put_u8str_max(status_message, max_status_chars); diff --git a/Marlin/src/lcd/e3v2/proui/bedlevel_tools.cpp b/Marlin/src/lcd/e3v2/proui/bedlevel_tools.cpp index 435da10a2aee..d4e2262be17f 100644 --- a/Marlin/src/lcd/e3v2/proui/bedlevel_tools.cpp +++ b/Marlin/src/lcd/e3v2/proui/bedlevel_tools.cpp @@ -75,12 +75,13 @@ uint8_t BedLevelToolsClass::mesh_y = 0; uint8_t BedLevelToolsClass::tilt_grid = 1; bool drawing_mesh = false; -char cmd[MAX_CMD_SIZE+16], str_1[16], str_2[16], str_3[16]; #if ENABLED(AUTO_BED_LEVELING_UBL) void BedLevelToolsClass::manual_value_update(const uint8_t mesh_x, const uint8_t mesh_y, bool undefined/*=false*/) { - sprintf_P(cmd, PSTR("M421 I%i J%i Z%s %s"), mesh_x, mesh_y, dtostrf(current_position.z, 1, 3, str_1), undefined ? "N" : ""); + MString cmd; + cmd.set(F("M421 I"), mesh_x, 'J', mesh_y, 'Z', p_float_t(current_position.z, 3)); + if (undefined) cmd += F(" N"); gcode.process_subcommands_now(cmd); planner.synchronize(); } @@ -126,8 +127,9 @@ char cmd[MAX_CMD_SIZE+16], str_1[16], str_2[16], str_3[16]; #else void BedLevelToolsClass::manual_value_update(const uint8_t mesh_x, const uint8_t mesh_y) { - sprintf_P(cmd, PSTR("G29 I%i J%i Z%s"), mesh_x, mesh_y, dtostrf(current_position.z, 1, 3, str_1)); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now( + TS(F("G29 I"), mesh_x, 'J', mesh_y, 'Z', p_float_t(current_position.z, 3)) + ); planner.synchronize(); } @@ -144,10 +146,8 @@ void BedLevelToolsClass::manual_move(const uint8_t mesh_x, const uint8_t mesh_y, else { DWIN_Show_Popup(ICON_BLTouch, F("Moving to Point"), F("Please wait until done.")); HMI_SaveProcessID(NothingToDo); - sprintf_P(cmd, PSTR("G0 F300 Z%s"), dtostrf(Z_CLEARANCE_BETWEEN_PROBES, 1, 3, str_1)); - gcode.process_subcommands_now(cmd); - sprintf_P(cmd, PSTR("G42 F4000 I%i J%i"), mesh_x, mesh_y); - gcode.process_subcommands_now(cmd); + gcode.process_subcommands_now(TS(F("G0 F300 Z"), p_float_t(Z_CLEARANCE_BETWEEN_PROBES, 3))); + gcode.process_subcommands_now(TS(F("G42 F4000 I"), mesh_x, F(" J"), mesh_y)); planner.synchronize(); current_position.z = goto_mesh_value ? bedlevel.z_values[mesh_x][mesh_y] : Z_CLEARANCE_BETWEEN_PROBES; planner.buffer_line(current_position, homing_feedrate(Z_AXIS), active_extruder); @@ -170,13 +170,13 @@ void BedLevelToolsClass::MoveToZ() { bedLevelTools.manual_move(bedLevelTools.mesh_x, bedLevelTools.mesh_y, true); } void BedLevelToolsClass::ProbeXY() { - const uint16_t zclear = Z_CLEARANCE_DEPLOY_PROBE; - sprintf_P(cmd, PSTR("G0Z%i\nG30X%sY%s"), - zclear, - dtostrf(bedlevel.get_mesh_x(bedLevelTools.mesh_x), 1, 2, str_1), - dtostrf(bedlevel.get_mesh_y(bedLevelTools.mesh_y), 1, 2, str_2) + gcode.process_subcommands_now( + MString( + F("G28O\nG0Z"), uint16_t(Z_CLEARANCE_DEPLOY_PROBE), + F("\nG30X"), p_float_t(bedlevel.get_mesh_x(bedLevelTools.mesh_x), 2), + F("Y"), p_float_t(bedlevel.get_mesh_y(bedLevelTools.mesh_y), 2) + ) ); - gcode.process_subcommands_now(cmd); } void BedLevelToolsClass::mesh_reset() { @@ -277,20 +277,16 @@ bool BedLevelToolsClass::meshvalidate() { void BedLevelToolsClass::Set_Mesh_Viewer_Status() { // TODO: draw gradient with values as a legend instead float v_max = abs(get_max_value()), v_min = abs(get_min_value()), range = _MAX(v_min, v_max); - if (v_min > 3e+10F) v_min = 0.0000001; - if (v_max > 3e+10F) v_max = 0.0000001; - if (range > 3e+10F) range = 0.0000001; - char msg[46]; - if (viewer_asymmetric_range) { - dtostrf(-v_min, 1, 3, str_1); - dtostrf( v_max, 1, 3, str_2); - } - else { - dtostrf(-range, 1, 3, str_1); - dtostrf( range, 1, 3, str_2); - } - sprintf_P(msg, PSTR("Red %s..0..%s Green"), str_1, str_2); - ui.set_status(msg); + if (v_min > 3e+10f) v_min = 0.0000001; + if (v_max > 3e+10f) v_max = 0.0000001; + if (range > 3e+10f) range = 0.0000001; + ui.set_status( + &MString<45>( + F("Red "), p_float_t(viewer_asymmetric_range ? -v_min : -range, 3), + F("..0.."), p_float_t(viewer_asymmetric_range ? v_max : range, 3), + F(" Green") + ) + ); drawing_mesh = false; } diff --git a/Marlin/src/lcd/e3v2/proui/dwin.cpp b/Marlin/src/lcd/e3v2/proui/dwin.cpp index 31da209b5543..084739af36dd 100644 --- a/Marlin/src/lcd/e3v2/proui/dwin.cpp +++ b/Marlin/src/lcd/e3v2/proui/dwin.cpp @@ -480,7 +480,7 @@ void DWIN_DrawStatusLine(FSTR_P fstr) { DWIN_DrawStatusLine(FTOP(fstr)); } // Clear & reset status line void DWIN_ResetStatusLine() { - ui.status_message[0] = 0; + ui.status_message.clear(); DWIN_CheckStatusMessage(); } @@ -492,18 +492,19 @@ uint32_t GetHash(char * str) { return hash; } +// Check for a change in the status message void DWIN_CheckStatusMessage() { - static uint32_t old_hash = 0; - uint32_t hash = GetHash(&ui.status_message[0]); + static MString<>::hash_t old_hash = 0x0000; + const MString<>::hash_t hash = ui.status_message.hash(); hash_changed = hash != old_hash; old_hash = hash; -}; +} void DWIN_DrawStatusMessage() { #if ENABLED(STATUS_MESSAGE_SCROLLING) // Get the UTF8 character count of the string - uint8_t slen = utf8_strlen(ui.status_message); + uint8_t slen = ui.status_message.glyphs(); // If the string fits the status line do not scroll it if (slen <= LCD_WIDTH) { @@ -539,7 +540,7 @@ void DWIN_DrawStatusMessage() { #else if (hash_changed) { - ui.status_message[LCD_WIDTH] = 0; + ui.status_message.trunc(LCD_WIDTH); DWIN_DrawStatusLine(ui.status_message); hash_changed = false; } @@ -567,17 +568,16 @@ void Draw_Print_ProgressBar() { } void Draw_Print_ProgressElapsed() { - char buf[10]; + MString<12> buf; duration_t elapsed = print_job_timer.duration(); // Print timer - sprintf_P(buf, PSTR("%02i:%02i "), (uint16_t)(elapsed.value / 3600), ((uint16_t)elapsed.value % 3600) / 60); + buf.setf(F("%02i:%02i "), uint16_t(elapsed.value / 3600), (uint16_t(elapsed.value) % 3600) / 60); DWINUI::Draw_String(HMI_data.Text_Color, HMI_data.Background_Color, 47, 192, buf); } #if ENABLED(SHOW_REMAINING_TIME) void Draw_Print_ProgressRemain() { - const uint32_t _remain_time = ui.get_remaining_time(); - char buf[10]; - sprintf_P(buf, PSTR("%02i:%02i "), (uint16_t)(_remain_time / 3600), ((uint16_t)_remain_time % 3600) / 60); + MString<12> buf; + buf.setf(F("%02i:%02i "), _remain_time / 3600, (_remain_time % 3600) / 60); DWINUI::Draw_String(HMI_data.Text_Color, HMI_data.Background_Color, 181, 192, buf); } #endif @@ -1497,8 +1497,9 @@ void DWIN_LevelingDone() { #if HAS_MESH void DWIN_MeshUpdate(const int8_t cpos, const int8_t tpos, const_float_t zval) { - char str_1[6] = ""; - ui.status_printf(0, F(S_FMT " %i/%i Z=%s"), GET_TEXT_F(MSG_PROBING_POINT), cpos, tpos, dtostrf(zval, 1, 2, str_1)); + ui.set_status( + &MString<32>(GET_TEXT_F(MSG_PROBING_POINT), ' ', cpos, '/', tpos, F(" Z="), p_float_t(zval, 2)) + ); } #endif @@ -2051,30 +2052,25 @@ void AutoHome() { queue.inject_P(G28_STR); } SetPFloatOnClick(Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX, 2, ApplyZOffset, LiveZOffset); } -#endif // HAS_ZOFFSET_ITEM + void SetMoveZto0() { + #if ENABLED(Z_SAFE_HOMING) + gcode.process_subcommands_now(MString<54>(F("G28XYO\nG28Z\nG0F5000X"), Z_SAFE_HOMING_X_POINT, F("Y"), Z_SAFE_HOMING_Y_POINT, F("\nG0Z0F300\nM400"))); + #else + TERN_(HAS_LEVELING, set_bed_leveling_enabled(false)); + gcode.process_subcommands_now(F("G28Z\nG0Z0F300\nM400")); + #endif + ui.reset_status(); + DONE_BUZZ(true); + } -void SetMoveZto0() { - #if ENABLED(Z_SAFE_HOMING) - char cmd[54], str_1[5], str_2[5]; - sprintf_P(cmd, PSTR("G28XYO\nG28Z\nG0X%sY%sF5000\nG0Z0F300\nM400"), - dtostrf(Z_SAFE_HOMING_X_POINT, 1, 1, str_1), - dtostrf(Z_SAFE_HOMING_Y_POINT, 1, 1, str_2) - ); - gcode.process_subcommands_now(cmd); - #else - TERN_(HAS_LEVELING, set_bed_leveling_enabled(false)); - gcode.process_subcommands_now(F("G28Z\nG0Z0F300\nM400")); + #if !HAS_BED_PROBE + void HomeZandDisable() { + SetMoveZto0(); + DisableMotors(); + } #endif - ui.reset_status(); - DONE_BUZZ(true); -} -#if DISABLED(HAS_BED_PROBE) - void HomeZandDisable() { - SetMoveZto0(); - DisableMotors(); - } -#endif +#endif // HAS_ZOFFSET_ITEM #if HAS_PREHEAT #define _DoPreheat(N) void DoPreheat##N() { ui.preheat_all(N-1); }\ @@ -2322,20 +2318,16 @@ void SetFlow() { SetPIntOnClick(MIN_PRINT_FLOW, MAX_PRINT_FLOW, []{ planner.refr #if HAS_BED_PROBE float Tram(const uint8_t point) { - char cmd[100] = ""; static bool inLev = false; - float xpos = 0, ypos = 0, zval = 0; - char str_1[6] = "", str_2[6] = "", str_3[6] = ""; if (inLev) return NAN; + float xpos = 0, ypos = 0, zval = 0; TramXY(point, xpos, ypos); if (HMI_data.FullManualTramming) { - sprintf_P(cmd, PSTR("M420S0\nG28O\nG90\nG0Z5F300\nG0X%sY%sF5000\nG0Z0F300"), - dtostrf(xpos, 1, 1, str_1), - dtostrf(ypos, 1, 1, str_2) - ); - queue.inject(cmd); + queue.inject(MString<100>( + F("M420S0\nG28O\nG90\nG0F300Z5\nG0F5000X"), p_float_t(xpos, 1), 'Y', p_float_t(ypos, 1), F("\nG0F300Z0") + )); } else { // AUTO_BED_LEVELING_BILINEAR does not define MESH_INSET @@ -2360,14 +2352,8 @@ void SetFlow() { SetPIntOnClick(MIN_PRINT_FLOW, MAX_PRINT_FLOW, []{ planner.refr zval = probe.probe_at_point(xpos, ypos, PROBE_PT_STOW); if (isnan(zval)) LCD_MESSAGE(MSG_ZPROBE_OUT); - else { - sprintf_P(cmd, PSTR("X:%s, Y:%s, Z:%s"), - dtostrf(xpos, 1, 1, str_1), - dtostrf(ypos, 1, 1, str_2), - dtostrf(zval, 1, 2, str_3) - ); - ui.set_status(cmd); - } + else + ui.set_status(TS(F("X:"), p_float_t(xpos, 1), F(" Y:"), p_float_t(ypos, 1), F(" Z:"))); inLev = false; } return zval; @@ -2378,10 +2364,9 @@ void SetFlow() { SetPIntOnClick(MIN_PRINT_FLOW, MAX_PRINT_FLOW, []{ planner.refr void Tram(const uint8_t point) { float xpos = 0, ypos = 0; TramXY(point, xpos, ypos); - - char cmd[100] = "", str_1[6] = "", str_2[6] = ""; - sprintf_P(cmd, PSTR("M420S0\nG28O\nG90\nG0Z5F300\nG0X%sY%sF5000\nG0Z0F300"), dtostrf(xpos, 1, 1, str_1), dtostrf(ypos, 1, 1, str_2)); - queue.inject(cmd); + queue.inject(MString<100>( + F("M420S0\nG28O\nG90\nG0F300Z5\nG0F5000X"), p_float_t(xpos, 1), 'Y', p_float_t(ypos, 1), F("\nG0F300Z0") + )); } #endif @@ -3762,13 +3747,9 @@ void Draw_Steps_Menu() { #if DWIN_PID_TUNE void SetPID(celsius_t t, heater_id_t h) { - char cmd[53] = ""; - char str_1[5] = "", str_2[5] = ""; - sprintf_P(cmd, PSTR("G28OXY\nG0Z5F300\nG0X%sY%sF5000\nM84\nM400"), - dtostrf(X_CENTER, 1, 1, str_1), - dtostrf(Y_CENTER, 1, 1, str_2) + gcode.process_subcommands_now( + MString<60>(F("G28OXY\nG0Z5F300\nG0X"), X_CENTER, F("Y"), Y_CENTER, F("F5000\nM84\nM400")) ); - gcode.process_subcommands_now(cmd); thermalManager.PID_autotune(t, h, HMI_data.PidCycles, true); } void SetPidCycles() { SetPIntOnClick(3, 50); } @@ -3968,11 +3949,8 @@ void Draw_Steps_Menu() { void UBLMeshTilt() { NOLESS(bedlevel.storage_slot, 0); - char buf[9]; - if (bedLevelTools.tilt_grid > 1) { - sprintf_P(buf, PSTR("G29J%i"), bedLevelTools.tilt_grid); - gcode.process_subcommands_now(buf); - } + if (bedLevelTools.tilt_grid > 1) + gcode.process_subcommands_now(TS(F("G29J"), bedLevelTools.tilt_grid)); else gcode.process_subcommands_now(F("G29J")); LCD_MESSAGE(MSG_UBL_MESH_TILTED); diff --git a/Marlin/src/lcd/e3v2/proui/gcode_preview.cpp b/Marlin/src/lcd/e3v2/proui/gcode_preview.cpp index 3dddbe6f610a..c0b12c5946fd 100644 --- a/Marlin/src/lcd/e3v2/proui/gcode_preview.cpp +++ b/Marlin/src/lcd/e3v2/proui/gcode_preview.cpp @@ -198,26 +198,24 @@ bool Has_Preview() { void Preview_DrawFromSD() { if (Has_Preview()) { - char buf[46]; - char str_1[6] = ""; - char str_2[6] = ""; - char str_3[6] = ""; + MString<45> buf; + char str_1[6] = "", str_2[6] = "", str_3[6] = ""; dwinDrawRectangle(1, HMI_data.Background_Color, 0, 0, DWIN_WIDTH, STATUS_Y - 1); if (fileprop.time) { - sprintf_P(buf, PSTR("Estimated time: %i:%02i"), (uint16_t)fileprop.time / 3600, ((uint16_t)fileprop.time % 3600) / 60); - DWINUI::Draw_String(20, 10, buf); + buf.setf(F("Estimated time: %i:%02i"), (uint16_t)fileprop.time / 3600, ((uint16_t)fileprop.time % 3600) / 60); + DWINUI::Draw_String(20, 10, &buf); } if (fileprop.filament) { - sprintf_P(buf, PSTR("Filament used: %s m"), dtostrf(fileprop.filament, 1, 2, str_1)); - DWINUI::Draw_String(20, 30, buf); + buf.setf(F("Filament used: %s m"), dtostrf(fileprop.filament, 1, 2, str_1)); + DWINUI::Draw_String(20, 30, &buf); } if (fileprop.layer) { - sprintf_P(buf, PSTR("Layer height: %s mm"), dtostrf(fileprop.layer, 1, 2, str_1)); - DWINUI::Draw_String(20, 50, buf); + buf.setf(F("Layer height: %s mm"), dtostrf(fileprop.layer, 1, 2, str_1)); + DWINUI::Draw_String(20, 50, &buf); } if (fileprop.width) { - sprintf_P(buf, PSTR("Volume: %sx%sx%s mm"), dtostrf(fileprop.width, 1, 1, str_1), dtostrf(fileprop.length, 1, 1, str_2), dtostrf(fileprop.height, 1, 1, str_3)); - DWINUI::Draw_String(20, 70, buf); + buf.setf(F("Volume: %sx%sx%s mm"), dtostrf(fileprop.width, 1, 1, str_1), dtostrf(fileprop.length, 1, 1, str_2), dtostrf(fileprop.height, 1, 1, str_3)); + DWINUI::Draw_String(20, 70, &buf); } DWINUI::Draw_Button(BTN_Print, 26, 290); DWINUI::Draw_Button(BTN_Cancel, 146, 290); diff --git a/Marlin/src/lcd/e3v2/proui/printstats.cpp b/Marlin/src/lcd/e3v2/proui/printstats.cpp index 093040ebf463..993b5b7b70f1 100644 --- a/Marlin/src/lcd/e3v2/proui/printstats.cpp +++ b/Marlin/src/lcd/e3v2/proui/printstats.cpp @@ -43,7 +43,6 @@ PrintStatsClass PrintStats; void PrintStatsClass::Draw() { - char buf[50] = ""; char str[30] = ""; constexpr int8_t MRG = 30; @@ -53,18 +52,13 @@ void PrintStatsClass::Draw() { DWINUI::Draw_Button(BTN_Continue, 86, 250); printStatistics ps = print_job_timer.getStats(); - sprintf_P(buf, PSTR(S_FMT ": %i"), GET_TEXT(MSG_INFO_PRINT_COUNT), ps.totalPrints); - DWINUI::Draw_String(MRG, 80, buf); - sprintf_P(buf, PSTR(S_FMT ": %i"), GET_TEXT(MSG_INFO_COMPLETED_PRINTS), ps.finishedPrints); - DWINUI::Draw_String(MRG, 100, buf); + DWINUI::Draw_String(MRG, 80, TS(GET_TEXT_F(MSG_INFO_PRINT_COUNT), F(": "), ps.totalPrints)); + DWINUI::Draw_String(MRG, 100, TS(GET_TEXT_F(MSG_INFO_COMPLETED_PRINTS), F(": "), ps.finishedPrints)); duration_t(print_job_timer.getStats().printTime).toDigital(str, true); - sprintf_P(buf, PSTR(S_FMT ": %s"), GET_TEXT(MSG_INFO_PRINT_TIME), str); - DWINUI::Draw_String(MRG, 120, buf); + DWINUI::Draw_String(MRG, 120, MString<50>(GET_TEXT_F(MSG_INFO_PRINT_TIME), F(": "), str)); duration_t(print_job_timer.getStats().longestPrint).toDigital(str, true); - sprintf_P(buf, PSTR(S_FMT ": %s"), GET_TEXT(MSG_INFO_PRINT_LONGEST), str); - DWINUI::Draw_String(MRG, 140, buf); - sprintf_P(buf, PSTR(S_FMT ": %s m"), GET_TEXT(MSG_INFO_PRINT_FILAMENT), dtostrf(ps.filamentUsed / 1000, 1, 2, str)); - DWINUI::Draw_String(MRG, 160, buf); + DWINUI::Draw_String(MRG, 140, MString<50>(GET_TEXT(MSG_INFO_PRINT_LONGEST), F(": "), str)); + DWINUI::Draw_String(MRG, 160, TS(GET_TEXT_F(MSG_INFO_PRINT_FILAMENT), F(": "), p_float_t(ps.filamentUsed / 1000, 2), F(" m"))); } void PrintStatsClass::Reset() { diff --git a/Marlin/src/lcd/marlinui.cpp b/Marlin/src/lcd/marlinui.cpp index b443e2269477..e71e29cf5053 100644 --- a/Marlin/src/lcd/marlinui.cpp +++ b/Marlin/src/lcd/marlinui.cpp @@ -71,7 +71,7 @@ constexpr uint8_t epps = ENCODER_PULSES_PER_STEP; #if ENABLED(STATUS_MESSAGE_SCROLLING) && ANY(HAS_WIRED_LCD, DWIN_LCD_PROUI) uint8_t MarlinUI::status_scroll_offset; // = 0 #endif - char MarlinUI::status_message[MAX_MESSAGE_LENGTH + 1]; + MString MarlinUI::status_message; uint8_t MarlinUI::alert_level; // = 0 #if HAS_STATUS_MESSAGE_TIMEOUT millis_t MarlinUI::status_message_expire_ms; // = 0 @@ -502,10 +502,6 @@ void MarlinUI::init() { #endif // HAS_MARLINUI_MENU - //////////////////////////////////////////// - ///////////// Keypad Handling ////////////// - //////////////////////////////////////////// - #if IS_RRW_KEYPAD && HAS_ENCODER_ACTION volatile uint8_t MarlinUI::keypad_buttons; @@ -651,7 +647,7 @@ void MarlinUI::init() { // Expire the message if a job is active and the bar has ticks if (get_progress_percent() > 2 && !print_job_timer.isPaused()) { if (ELAPSED(ms, expire_status_ms)) { - status_message[0] = '\0'; + status_message.clear(); expire_status_ms = 0; } } @@ -786,10 +782,6 @@ void MarlinUI::init() { #endif } - //////////////////////////////////////////// - /////////////// Manual Move //////////////// - //////////////////////////////////////////// - #if HAS_MARLINUI_MENU ManualMove MarlinUI::manual_move{}; @@ -1449,49 +1441,27 @@ void MarlinUI::init() { #endif // HAS_WIRED_LCD -#if HAS_STATUS_MESSAGE +void MarlinUI::host_notify_P(PGM_P const pstr) { + TERN_(HOST_STATUS_NOTIFICATIONS, hostui.notify_P(pstr)); +} +void MarlinUI::host_notify(const char * const cstr) { + TERN_(HOST_STATUS_NOTIFICATIONS, hostui.notify(cstr)); +} +void MarlinUI::host_status() { + TERN_(HOST_STATUS_NOTIFICATIONS, hostui.notify(status_message)); +} - //////////////////////////////////////////// - ////////////// Status Message ////////////// - //////////////////////////////////////////// +#include + +#if HAS_STATUS_MESSAGE #if ENABLED(EXTENSIBLE_UI) #include "extui/ui_api.h" #endif - bool MarlinUI::has_status() { return (status_message[0] != '\0'); } - - void MarlinUI::set_status(const char * const cstr, const bool persist) { - if (alert_level) return; - - TERN_(HOST_STATUS_NOTIFICATIONS, hostui.notify(cstr)); - - // Here we have a problem. The message is encoded in UTF8, so - // arbitrarily cutting it will be a problem. We MUST be sure - // that there is no cutting in the middle of a multibyte character! - - // Get a pointer to the null terminator - const char* pend = cstr + strlen(cstr); - - // If length of supplied UTF8 string is greater than - // our buffer size, start cutting whole UTF8 chars - while ((pend - cstr) > MAX_MESSAGE_LENGTH) { - --pend; - while (!START_OF_UTF8_CHAR(*pend)) --pend; - }; - - // At this point, we have the proper cut point. Use it - uint8_t maxLen = pend - cstr; - strncpy(status_message, cstr, maxLen); - status_message[maxLen] = '\0'; - - finish_status(persist); - } - /** * Reset the status message */ - void MarlinUI::reset_status(const bool no_welcome) { #if SERVICE_INTERVAL_1 > 0 static PGMSTR(service1, "> " SERVICE_NAME_1 "!"); @@ -1508,7 +1478,7 @@ void MarlinUI::init() { msg = GET_TEXT_F(MSG_PRINT_PAUSED); #if HAS_MEDIA else if (IS_SD_PRINTING()) - return set_status(card.longest_filename(), true); + return set_status_no_expire(card.longest_filename()); #endif else if (print_job_timer.isRunning()) msg = GET_TEXT_F(MSG_PRINTING); @@ -1530,65 +1500,85 @@ void MarlinUI::init() { else return; - set_status(msg, -1); + set_min_status(msg); } /** - * Set Status with a fixed string and alert level. - * @param fstr A constant F-string to set as the status. + * Try to set the alert level. * @param level Alert level. Negative to ignore and reset the level. Non-zero never expires. + * @return TRUE if the level could NOT be set. */ - void MarlinUI::set_status(FSTR_P const fstr, int8_t level) { - // Alerts block lower priority messages + bool MarlinUI::set_alert_level(int8_t &level) { if (level < 0) level = alert_level = 0; - if (level < alert_level) return; + if (level < alert_level) return true; alert_level = level; + return false; + } + + /** + * @brief Set Status with a C- or P-string and alert level. + * + * @param ustr A C- or P-string, according to pgm. + * @param level Alert level. Negative to ignore and reset the level. Non-zero never expires. + * @param pgm Program string flag. Only relevant on AVR. + */ + void MarlinUI::_set_status_and_level(const char * const ustr, int8_t level, const bool pgm) { + if (set_alert_level(level)) return; - PGM_P const pstr = FTOP(fstr); + pgm ? host_notify_P(ustr) : host_notify(ustr); - // Since the message is encoded in UTF8 it must - // only be cut on a character boundary. + MString<30> msg; + pgm ? msg.set_P(ustr) : msg.set(ustr); + status_message.set(&msg).utrunc(MAX_MESSAGE_LENGTH); - // Get a pointer to the null terminator - PGM_P pend = pstr + strlen_P(pstr); + finish_status(level > 0); // Persist if the status has a level + } - // If length of supplied UTF8 string is greater than - // the buffer size, start cutting whole UTF8 chars - while ((pend - pstr) > MAX_MESSAGE_LENGTH) { - --pend; - while (!START_OF_UTF8_CHAR(pgm_read_byte(pend))) --pend; - }; + /** + * @brief Set Status with a C- or P-string and persistence flag. + * + * @param ustr A C- or P-string, according to pgm. + * @param persist Don't expire (Requires STATUS_EXPIRE_SECONDS) - and set alert level to 1. + * @param pgm Program string flag. Only relevant on AVR. + */ + void MarlinUI::_set_status(const char * const ustr, const bool persist, const bool pgm) { + if (alert_level) return; - // At this point, we have the proper cut point. Use it - uint8_t maxLen = pend - pstr; - strncpy_P(status_message, pstr, maxLen); - status_message[maxLen] = '\0'; + pgm ? host_notify_P(ustr) : host_notify(ustr); - TERN_(HOST_STATUS_NOTIFICATIONS, hostui.notify(fstr)); + // Remove the last partial Unicode glyph, if any + (pgm ? status_message.set_P(ustr) : status_message.set(ustr)).utrunc(MAX_MESSAGE_LENGTH); - finish_status(level > 0); + finish_status(persist); } - void MarlinUI::set_alert_status(FSTR_P const fstr) { - set_status(fstr, 1); + /** + * @brief Set Alert with a C- or P-string and alert level. + * + * @param ustr A C- or P-string, according to pgm. + * @param level Alert level. Negative to ignore and reset the level. Non-zero never expires. + * @param pgm Program string flag. Only relevant on AVR. + */ + void MarlinUI::_set_alert(const char * const ustr, const int8_t level, const bool pgm) { + pgm ? set_status_and_level_P(ustr, level) : set_status_and_level(ustr, level); TERN_(HAS_TOUCH_SLEEP, wakeup_screen()); TERN_(HAS_MARLINUI_MENU, return_to_status()); } - #include - - void MarlinUI::status_printf(int8_t level, FSTR_P const fmt, ...) { - // Alerts block lower priority messages - if (level < 0) level = alert_level = 0; - if (level < alert_level) return; - alert_level = level; + /** + * @brief Set a status with a format string and parameters. + * + * @param pfmt A constant format P-string + */ + void MarlinUI::status_printf_P(int8_t level, PGM_P const fmt, ...) { + if (set_alert_level(level)) return; va_list args; - va_start(args, FTOP(fmt)); - vsnprintf_P(status_message, MAX_MESSAGE_LENGTH, FTOP(fmt), args); + va_start(args, fmt); + vsnprintf_P(status_message, MAX_MESSAGE_LENGTH, fmt, args); va_end(args); - TERN_(HOST_STATUS_NOTIFICATIONS, hostui.notify(status_message)); + host_status(); finish_status(level > 0); } @@ -1634,14 +1624,14 @@ void MarlinUI::init() { void MarlinUI::advance_status_scroll() { // Advance by one UTF8 code-word - if (status_scroll_offset < utf8_strlen(status_message)) + if (status_scroll_offset < status_message.glyphs()) while (!START_OF_UTF8_CHAR(status_message[++status_scroll_offset])); else status_scroll_offset = 0; } char* MarlinUI::status_and_len(uint8_t &len) { - char *out = status_message + status_scroll_offset; + char *out = &status_message + status_scroll_offset; len = utf8_strlen(out); return out; } @@ -1653,14 +1643,24 @@ void MarlinUI::init() { // // Send the status line as a host notification // - void MarlinUI::set_status(const char * const cstr, const bool) { - TERN(HOST_PROMPT_SUPPORT, hostui.notify(cstr), UNUSED(cstr)); + void MarlinUI::_set_status(const char * const cstr, const bool, const bool pgm) { + host_notify(cstr); + } + void MarlinUI::_set_alert(const char * const cstr, const int8_t, const bool pgm) { + host_notify(cstr); } - void MarlinUI::set_status(FSTR_P const fstr, const int8_t) { - TERN(HOST_PROMPT_SUPPORT, hostui.notify(fstr), UNUSED(fstr)); + void MarlinUI::_set_status_and_level(const char * const ustr, const int8_t=0, const bool pgm) { + pgm ? host_notify_P(ustr) : host_notify(ustr); } - void MarlinUI::status_printf(int8_t, FSTR_P const fstr, ...) { - TERN(HOST_PROMPT_SUPPORT, hostui.notify(fstr), UNUSED(fstr)); + void MarlinUI::status_printf_P(int8_t level, PGM_P const fmt, ...) { + MString<30> msg; + + va_list args; + va_start(args, fmt); + vsnprintf_P(&msg, 30, fmt, args); + va_end(args); + + host_status(); } #endif // !HAS_STATUS_MESSAGE diff --git a/Marlin/src/lcd/marlinui.h b/Marlin/src/lcd/marlinui.h index 82d9fff64e06..8891ebf58158 100644 --- a/Marlin/src/lcd/marlinui.h +++ b/Marlin/src/lcd/marlinui.h @@ -53,8 +53,6 @@ #include "e3v2/proui/dwin.h" #endif -#define START_OF_UTF8_CHAR(C) (((C) & 0xC0u) != 0x80U) - typedef bool (*statusResetFunc_t)(); #if HAS_WIRED_LCD @@ -360,6 +358,11 @@ class MarlinUI { static constexpr uint8_t get_progress_percent() { return 0; } #endif + static void host_notify_P(PGM_P const fstr); + static void host_notify(FSTR_P const fstr) { host_notify_P(FTOP(fstr)); } + static void host_notify(const char * const cstr); + static void host_status(); + #if HAS_STATUS_MESSAGE #if ANY(HAS_WIRED_LCD, DWIN_LCD_PROUI) @@ -372,7 +375,7 @@ class MarlinUI { #define MAX_MESSAGE_LENGTH 63 #endif - static char status_message[]; + static MString status_message; static uint8_t alert_level; // Higher levels block lower levels #if HAS_STATUS_MESSAGE_TIMEOUT @@ -385,24 +388,115 @@ class MarlinUI { static char* status_and_len(uint8_t &len); #endif - static bool has_status(); + static bool has_status() { return !status_message.empty(); } + + /** + * Try to set the alert level. + * @param level Alert level. Negative to ignore and reset the level. Non-zero never expires. + * @return TRUE if the level could NOT be set. + */ + static bool set_alert_level(int8_t &level); + static void reset_status(const bool no_welcome=false); - static void set_alert_status(FSTR_P const fstr); static void reset_alert_level() { alert_level = 0; } static statusResetFunc_t status_reset_callback; static void set_status_reset_fn(const statusResetFunc_t fn=nullptr) { status_reset_callback = fn; } + #else + + #define MAX_MESSAGE_LENGTH 1 static constexpr bool has_status() { return false; } + + static bool set_alert_level(int8_t) { return false; } + static void reset_status(const bool=false) {} - static void set_alert_status(FSTR_P const) {} static void reset_alert_level() {} + static void set_status_reset_fn(const statusResetFunc_t=nullptr) {} + #endif - static void set_status(const char * const cstr, const bool persist=false); - static void set_status(FSTR_P const fstr, const int8_t level=0); - static void status_printf(int8_t level, FSTR_P const fmt, ...); + /** + * @brief Set Status with a C- or P-string and alert level. + * + * @param ustr A C- or P-string, according to pgm. + * @param level Alert level. Negative to ignore and reset the level. Non-zero never expires. + * @param pgm Program string flag. Only relevant on AVR. + */ + static void _set_status_and_level(const char * const ustr, int8_t level, const bool pgm=false); + + /** + * @brief Set Status with a C- or P-string and persistence flag. + * + * @param ustr A C- or P-string, according to pgm. + * @param persist Don't expire (Requires STATUS_EXPIRE_SECONDS) - and set alert level to 1. + * @param pgm Program string flag. Only relevant on AVR. + */ + static void _set_status(const char * const ustr, const bool persist, const bool pgm=false); + + /** + * @brief Set Alert with a C- or P-string and alert level. + * + * @param ustr A C- or P-string, according to pgm. + * @param level Alert level. Negative to ignore and reset the level. Non-zero never expires. + * @param pgm Program string flag. Only relevant on AVR. + */ + static void _set_alert(const char * const ustr, int8_t level, const bool pgm=false); + + static void set_status(const char * const cstr, const bool persist=false) { _set_status(cstr, persist, false); } + static void set_status_P(PGM_P const pstr, const bool persist=false) { _set_status(pstr, persist, true); } + static void set_status(FSTR_P const fstr, const bool persist=false) { set_status_P(FTOP(fstr), persist); } + + static void set_alert(const char * const cstr, const int8_t level=1) { _set_alert(cstr, level, false); } + static void set_alert_P(PGM_P const pstr, const int8_t level=1) { _set_alert(pstr, level, true); } + static void set_alert(FSTR_P const fstr, const int8_t level=1) { set_alert_P(FTOP(fstr), level); } + + /** + * @brief Set Status with a C-string and alert level. + * + * @param fstr A constant F-string to set as the status. + * @param level Alert level. Negative to ignore and reset the level. Non-zero never expires. + */ + static void set_status_and_level(const char * const cstr, const int8_t level) { _set_status_and_level(cstr, level, false); } + + /** + * @brief Set Status with a P-string and alert level. + * + * @param ustr A C- or P-string, according to pgm. + * @param level Alert level. Negative to ignore and reset the level. Non-zero never expires. + */ + static void set_status_and_level_P(PGM_P const pstr, const int8_t level) { _set_status_and_level(pstr, level, true); } + + /** + * @brief Set Status with a fixed string and alert level. + * + * @param fstr A constant F-string to set as the status. + * @param level Alert level. Negative to ignore and reset the level. Non-zero never expires. + */ + static void set_status_and_level(FSTR_P const fstr, const int8_t level) { set_status_and_level_P(FTOP(fstr), level); } + + static void set_max_status(FSTR_P const fstr) { set_status_and_level(fstr, 127); } + static void set_min_status(FSTR_P const fstr) { set_status_and_level(fstr, -1); } + + /** + * @brief Set a persistent status with a C-string. + * + * @param cstr A C-string to set as the status. + */ + static void set_status_no_expire_P(PGM_P const pstr) { set_status_P(pstr, true); } + static void set_status_no_expire(const char * const cstr) { set_status(cstr, true); } + static void set_status_no_expire(FSTR_P const fstr) { set_status(fstr, true); } + + /** + * @brief Set a status with a format string and parameters. + * + * @param pfmt A constant format P-string + */ + static void status_printf_P(int8_t level, PGM_P const pfmt, ...); + + template + static void status_printf(int8_t level, FSTR_P const ffmt, Args... more) { status_printf_P(level, FTOP(ffmt), more...); } #if HAS_DISPLAY @@ -812,7 +906,7 @@ class MarlinUI { #define LCD_MESSAGE_F(S) ui.set_status(F(S)) #define LCD_MESSAGE(M) ui.set_status(GET_TEXT_F(M)) -#define LCD_MESSAGE_MIN(M) ui.set_status(GET_TEXT_F(M), -1) -#define LCD_MESSAGE_MAX(M) ui.set_status(GET_TEXT_F(M), 99) -#define LCD_ALERTMESSAGE_F(S) ui.set_alert_status(F(S)) -#define LCD_ALERTMESSAGE(M) ui.set_alert_status(GET_TEXT_F(M)) +#define LCD_MESSAGE_MIN(M) ui.set_min_status(GET_TEXT_F(M)) +#define LCD_MESSAGE_MAX(M) ui.set_max_status(GET_TEXT_F(M)) +#define LCD_ALERTMESSAGE_F(S) ui.set_alert(F(S)) +#define LCD_ALERTMESSAGE(M) ui.set_alert(GET_TEXT_F(M)) diff --git a/Marlin/src/lcd/menu/menu_bed_leveling.cpp b/Marlin/src/lcd/menu/menu_bed_leveling.cpp index d8396ad832b7..4ff5c922bdaf 100644 --- a/Marlin/src/lcd/menu/menu_bed_leveling.cpp +++ b/Marlin/src/lcd/menu/menu_bed_leveling.cpp @@ -156,9 +156,9 @@ void change_Z_edit_mesh(); // void _lcd_level_bed_moving() { if (ui.should_draw()) { - char msg[10]; - sprintf_P(msg, PSTR("%i / %u"), int(manual_probe_index + 1), total_probe_points); - MenuEditItemBase::draw_edit_screen(GET_TEXT_F(MSG_LEVEL_BED_NEXT_POINT), msg); + MString<9> msg; + msg.setf(F("%i / %u"), int(manual_probe_index + 1), total_probe_points); + MenuEditItemBase::draw_edit_screen(GET_TEXT_F(MSG_LEVEL_BED_NEXT_POINT), &msg); } ui.refresh(LCDVIEW_CALL_NO_REDRAW); if (!ui.wait_for_move) ui.goto_screen(_lcd_level_bed_get_z); diff --git a/Marlin/src/lcd/menu/menu_configuration.cpp b/Marlin/src/lcd/menu/menu_configuration.cpp index 51bc64965585..1b16dd204e9a 100644 --- a/Marlin/src/lcd/menu/menu_configuration.cpp +++ b/Marlin/src/lcd/menu/menu_configuration.cpp @@ -332,10 +332,7 @@ void menu_advanced_settings(); void bltouch_report() { FSTR_P const mode0 = F("OD"), mode1 = F("5V"); DEBUG_ECHOLNPGM("BLTouch Mode: ", bltouch.od_5v_mode ? mode1 : mode0, " (Default ", TERN(BLTOUCH_SET_5V_MODE, mode1, mode0), ")"); - char mess[21]; - strcpy_P(mess, PSTR("BLTouch Mode: ")); - strcpy_P(&mess[15], bltouch.od_5v_mode ? FTOP(mode1) : FTOP(mode0)); - ui.set_status(mess); + ui.set_status(MString<18>(F("BLTouch Mode: "), bltouch.od_5v_mode ? mode1 : mode0)); ui.return_to_status(); } #endif diff --git a/Marlin/src/lcd/menu/menu_x_twist.cpp b/Marlin/src/lcd/menu/menu_x_twist.cpp index 56872b73ee25..6162a5e30d3b 100644 --- a/Marlin/src/lcd/menu/menu_x_twist.cpp +++ b/Marlin/src/lcd/menu/menu_x_twist.cpp @@ -112,9 +112,9 @@ void xatc_wizard_menu() { // void xatc_wizard_moving() { if (ui.should_draw()) { - char msg[10]; - sprintf_P(msg, PSTR("%i / %u"), manual_probe_index + 1, XATC_MAX_POINTS); - MenuEditItemBase::draw_edit_screen(GET_TEXT_F(MSG_LEVEL_BED_NEXT_POINT), msg); + MString<9> msg; + msg.setf(F("%i / %u"), manual_probe_index + 1, XATC_MAX_POINTS); + MenuEditItemBase::draw_edit_screen(GET_TEXT_F(MSG_LEVEL_BED_NEXT_POINT), &msg); } ui.refresh(LCDVIEW_CALL_NO_REDRAW); if (!ui.wait_for_move) ui.goto_screen(xatc_wizard_menu); diff --git a/Marlin/src/module/motion.cpp b/Marlin/src/module/motion.cpp index 835bdc7c204d..3970273af0f1 100644 --- a/Marlin/src/module/motion.cpp +++ b/Marlin/src/module/motion.cpp @@ -1623,20 +1623,21 @@ void prepare_line_to_destination() { } bool homing_needed_error(main_axes_bits_t axis_bits/*=main_axes_mask*/) { - if ((axis_bits &= axes_should_home(axis_bits))) { - char all_axes[] = STR_AXES_MAIN, need[NUM_AXES + 1]; - uint8_t n = 0; - LOOP_NUM_AXES(i) if (TEST(axis_bits, i)) need[n++] = all_axes[i]; - need[n] = '\0'; - - char msg[30]; - sprintf_P(msg, GET_TEXT(MSG_HOME_FIRST), need); - SERIAL_ECHO_START(); - SERIAL_ECHOLN(msg); - ui.set_status(msg); - return true; - } - return false; + if (!(axis_bits &= axes_should_home(axis_bits))) return false; + + char all_axes[] = STR_AXES_MAIN, need[NUM_AXES + 1]; + uint8_t n = 0; + LOOP_NUM_AXES(i) if (TEST(axis_bits, i)) need[n++] = all_axes[i]; + need[n] = '\0'; + + SString<30> msg; + msg.setf(GET_EN_TEXT_F(MSG_HOME_FIRST), need); + SERIAL_ECHO_START(); + msg.echoln(); + + msg.setf(GET_TEXT_F(MSG_HOME_FIRST), need); + ui.set_status(msg); + return true; } /** diff --git a/Marlin/src/module/probe.cpp b/Marlin/src/module/probe.cpp index 204617ba8394..e426e3bb4bdd 100644 --- a/Marlin/src/module/probe.cpp +++ b/Marlin/src/module/probe.cpp @@ -358,7 +358,7 @@ FORCE_INLINE void probe_specific_action(const bool deploy) { FSTR_P const ds_str = deploy ? GET_TEXT_F(MSG_MANUAL_DEPLOY) : GET_TEXT_F(MSG_MANUAL_STOW); ui.return_to_status(); // To display the new status message - ui.set_status(ds_str, 99); + ui.set_max_status(ds_str); SERIAL_ECHOLN(deploy ? GET_EN_TEXT_F(MSG_MANUAL_DEPLOY) : GET_EN_TEXT_F(MSG_MANUAL_STOW)); OKAY_BUZZ(); diff --git a/Marlin/src/module/settings.cpp b/Marlin/src/module/settings.cpp index 7a31afd5163f..ce046040c77d 100644 --- a/Marlin/src/module/settings.cpp +++ b/Marlin/src/module/settings.cpp @@ -831,14 +831,14 @@ void MarlinSettings::postprocess() { */ bool MarlinSettings::save() { float dummyf = 0; - char ver[4] = "ERR"; + MString<4> ver(F("ERR")); if (!EEPROM_START(EEPROM_OFFSET)) return false; EEPROM_Error eeprom_error = ERR_EEPROM_NOERR; // Write or Skip version. (Flash doesn't allow rewrite without erase.) - TERN(FLASH_EEPROM_EMULATION, EEPROM_SKIP, EEPROM_WRITE)(ver); + TERN(FLASH_EEPROM_EMULATION, EEPROM_SKIP, EEPROM_WRITE)(&ver); #if ENABLED(EEPROM_INIT_NOW) EEPROM_SKIP(build_hash); // Skip the hash slot which will be written later diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp index 183b758e881f..b780bfb8d28f 100644 --- a/Marlin/src/module/temperature.cpp +++ b/Marlin/src/module/temperature.cpp @@ -4194,22 +4194,17 @@ void Temperature::isr() { case H_REDUNDANT: k = 'R'; break; #endif } - SERIAL_CHAR(' ', k); - #if HAS_MULTI_HOTEND - if (e >= 0) SERIAL_CHAR('0' + e); - #endif - #ifdef SERIAL_FLOAT_PRECISION - #define SFP _MIN(SERIAL_FLOAT_PRECISION, 2) - #else - #define SFP 2 - #endif - SERIAL_ECHO(AS_CHAR(':'), p_float_t(c, SFP)); - if (show_t) { SERIAL_ECHOPGM(" /", p_float_t(t, SFP)); } + #define SFP _MIN(SERIAL_FLOAT_PRECISION, 2) + + SString<50> s(' ', k); + if (TERN0(HAS_MULTI_HOTEND, e >= 0)) s += char('0' + e); + s += TS(':', p_float_t(c, SFP)); + if (show_t) { s += F(" /"); s += p_float_t(t, SFP); } #if ENABLED(SHOW_TEMP_ADC_VALUES) // Temperature MAX SPI boards do not have an OVERSAMPLENR defined - SERIAL_ECHOPGM(" (", TERN(HAS_MAXTC_LIBRARIES, k == 'T', false) ? r : r * RECIPROCAL(OVERSAMPLENR)); - SERIAL_CHAR(')'); + s.append(F(" ("), TERN(HAS_MAXTC_LIBRARIES, k == 'T', false) ? r : r * RECIPROCAL(OVERSAMPLENR), ')'); #endif + s.echo(); delay(2); } @@ -4243,23 +4238,20 @@ void Temperature::isr() { #if HAS_MULTI_HOTEND HOTEND_LOOP() print_heater_state((heater_id_t)e, degHotend(e), degTargetHotend(e) OPTARG(SHOW_TEMP_ADC_VALUES, rawHotendTemp(e))); #endif - SERIAL_ECHOPGM(" @:", getHeaterPower((heater_id_t)target_extruder)); + SString<100> s(F(" @:"), getHeaterPower((heater_id_t)target_extruder)); #if HAS_HEATED_BED - SERIAL_ECHOPGM(" B@:", getHeaterPower(H_BED)); + s.append(" B@:", getHeaterPower(H_BED)); #endif #if HAS_HEATED_CHAMBER - SERIAL_ECHOPGM(" C@:", getHeaterPower(H_CHAMBER)); + s.append(" C@:", getHeaterPower(H_CHAMBER)); #endif #if HAS_COOLER - SERIAL_ECHOPGM(" C@:", getHeaterPower(H_COOLER)); + s.append(" C@:", getHeaterPower(H_COOLER)); #endif #if HAS_MULTI_HOTEND - HOTEND_LOOP() { - SERIAL_ECHOPGM(" @", e); - SERIAL_CHAR(':'); - SERIAL_ECHO(getHeaterPower((heater_id_t)e)); - } + HOTEND_LOOP() s.append(F(" @"), e, ':', getHeaterPower((heater_id_t)e)); #endif + s.echo(); } #if ENABLED(AUTO_REPORT_TEMPERATURES) @@ -4347,11 +4339,12 @@ void Temperature::isr() { next_temp_ms = now + 1000UL; print_heater_states(target_extruder); #if TEMP_RESIDENCY_TIME > 0 - SERIAL_ECHOPGM(" W:"); + SString<20> s(F(" W:")); if (residency_start_ms) - SERIAL_ECHO(long((SEC_TO_MS(TEMP_RESIDENCY_TIME) - (now - residency_start_ms)) / 1000UL)); + s += long((SEC_TO_MS(TEMP_RESIDENCY_TIME) - (now - residency_start_ms)) / 1000UL); else - SERIAL_CHAR('?'); + s += '?'; + s.echo(); #endif SERIAL_EOL(); } @@ -4487,11 +4480,12 @@ void Temperature::isr() { next_temp_ms = now + 1000UL; print_heater_states(active_extruder); #if TEMP_BED_RESIDENCY_TIME > 0 - SERIAL_ECHOPGM(" W:"); + SString<20> s(F(" W:")); if (residency_start_ms) - SERIAL_ECHO(long((SEC_TO_MS(TEMP_BED_RESIDENCY_TIME) - (now - residency_start_ms)) / 1000UL)); + s += long((SEC_TO_MS(TEMP_BED_RESIDENCY_TIME) - (now - residency_start_ms)) / 1000UL); else - SERIAL_CHAR('?'); + s += '?'; + s.echo(); #endif SERIAL_EOL(); } @@ -4583,7 +4577,7 @@ void Temperature::isr() { const bool wants_to_cool = isProbeAboveTemp(target_temp), will_wait = !(wants_to_cool && no_wait_for_cooling); if (will_wait) - SERIAL_ECHOLNPGM("Waiting for probe to ", wants_to_cool ? F("cool down") : F("heat up"), " to ", target_temp, " degrees."); + SString<60>(F("Waiting for probe to "), wants_to_cool ? F("cool down") : F("heat up"), F(" to "), target_temp, F(" degrees.")).echoln(); #if DISABLED(BUSY_WHILE_HEATING) && ENABLED(HOST_KEEPALIVE_FEATURE) KEEPALIVE_STATE(NOT_BUSY); @@ -4622,9 +4616,8 @@ void Temperature::isr() { // Loop until the temperature is very close target if (!(wants_to_cool ? isProbeAboveTemp(target_temp) : isProbeBelowTemp(target_temp))) { - SERIAL_ECHOLN(wants_to_cool ? PSTR("Cooldown") : PSTR("Heatup")); - SERIAL_ECHOLNPGM(" complete, target probe temperature reached."); - break; + SString<60>(wants_to_cool ? F("Cooldown") : F("Heatup"), F(" complete, target probe temperature reached.")).echoln(); + break; } } @@ -4687,11 +4680,12 @@ void Temperature::isr() { next_temp_ms = now + 1000UL; print_heater_states(active_extruder); #if TEMP_CHAMBER_RESIDENCY_TIME > 0 - SERIAL_ECHOPGM(" W:"); + SString<20> s(F(" W:")); if (residency_start_ms) - SERIAL_ECHO(long((SEC_TO_MS(TEMP_CHAMBER_RESIDENCY_TIME) - (now - residency_start_ms)) / 1000UL)); + s += long((SEC_TO_MS(TEMP_CHAMBER_RESIDENCY_TIME) - (now - residency_start_ms)) / 1000UL); else - SERIAL_CHAR('?'); + s += '?'; + s.echo(); #endif SERIAL_EOL(); } @@ -4788,11 +4782,12 @@ void Temperature::isr() { next_temp_ms = now + 1000UL; print_heater_states(active_extruder); #if TEMP_COOLER_RESIDENCY_TIME > 0 - SERIAL_ECHOPGM(" W:"); + SString<20> s(F(" W:")); if (residency_start_ms) - SERIAL_ECHO(long((SEC_TO_MS(TEMP_COOLER_RESIDENCY_TIME) - (now - residency_start_ms)) / 1000UL)); + s += long((SEC_TO_MS(TEMP_COOLER_RESIDENCY_TIME) - (now - residency_start_ms)) / 1000UL); else - SERIAL_CHAR('?'); + s += '?'; + s.echo(); #endif SERIAL_EOL(); } diff --git a/Marlin/src/tests/marlin_tests.cpp b/Marlin/src/tests/marlin_tests.cpp index 89e5664345d8..3d14f094a2bf 100644 --- a/Marlin/src/tests/marlin_tests.cpp +++ b/Marlin/src/tests/marlin_tests.cpp @@ -37,6 +37,40 @@ // Startup tests are run at the end of setup() void runStartupTests() { // Call post-setup tests here to validate behaviors. + + // String with cutoff at 20 chars: + // "F-string, 1234.50, 2" + SString<20> str20; + str20 = F("F-string, "); + str20.append(1234.5f).append(',').append(' ') + .append(2345.67).append(',').append(' ') + .echoln(); + + // Truncate to "F-string" + str20.trunc(8).echoln(); + + // 100 dashes, but chopped down to DEFAULT_MSTRING_SIZE (20) + TSS(repchr_t('-', 100)).echoln(); + + // Hello World!-123456------ str(F("Hello")); + str.append(F(" World!")); + str += '-'; + str += "123"; + str += F("456"); + str += repchr_t('-', 6); + str += Spaces(3); + str += "< spaces!"; + str.eol(); + str += "^ eol!"; + + str.append("...", 1234.5f, '*', p_float_t(2345.602, 3), F(" = "), 1234.5 * 2345.602).echoln(); + + // Print it again with SERIAL_ECHOLN + auto print_char_ptr = [](char * const str) { SERIAL_ECHOLN(str); }; + print_char_ptr(str); + } // Periodic tests are run from within loop()