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()