diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp
index 2724006dd193a..cd0b11483fc34 100644
--- a/Marlin/src/MarlinCore.cpp
+++ b/Marlin/src/MarlinCore.cpp
@@ -1151,7 +1151,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("] ");
+ TS('[', 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 0000000000000..9eb05c8e95328
--- /dev/null
+++ b/Marlin/src/core/mstring.h
@@ -0,0 +1,219 @@
+/**
+ * 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 Arduino string class providing operators for all common tasks
+ * and conversion from F() and PSTR() strings into SRAM strings, that reside
+ * on the stack or persistently.
+ *
+ * Usage:
+ * Create a string object with the usual construction methods.
+ */
+
+#include "types.h"
+#include "utility.h" // AXIS_CHAR
+#include "../lcd/fontutils.h"
+
+#ifndef DEFAULT_MSTRING_SIZE
+ #define DEFAULT_MSTRING_SIZE 20
+#endif
+
+// Light declarations for serial.h
+template void SERIAL_ECHO(T x);
+template void SERIAL_ECHOLN(T x);
+
+//#define DJB2_HASH // 32-bit hash with Djb2 algorithm
+
+#define START_OF_UTF8_CHAR(C) (((C) & 0xC0u) != 0x80U)
+
+typedef struct { float value; int prec; } prec_float_t;
+
+/**
+ * A simple string template class to compose temporary strings on the stack
+ */
+template
+class MString {
+ public:
+ char str[SIZE+1];
+ MString() { str[0] = '\0'; str[SIZE] = '\0'; }
+
+ template
+ MString(const T v) { set(v); }
+
+ static_assert(SIZE > 0, "Bad SIZE for MString <= 0");
+
+ // Chainable String Setters
+ MString& set() { str[0] = '\0'; return *this; }
+ MString& set(const char *s) { strncpy(str, s, SIZE); return *this; }
+ MString& set_P(PGM_P const s) { strncpy_P(str, s, SIZE); return *this; }
+ MString& set(FSTR_P const f) { set_P(FTOP(f)); return *this; }
+ MString& set(const MString &s) { strncpy(str, s.str, SIZE); return *this; }
+ MString& set(const bool &b) { set(b ? F("true") : F("false")); return *this; }
+ MString& set(const char c) { str[0] = c; str[1] = '\0'; return *this; }
+ MString& set(const short &i) { snprintf(str, SIZE, "%d", i); return *this; }
+ MString& set(const int &i) { snprintf(str, SIZE, "%d", i); return *this; }
+ MString& set(const long &l) { snprintf(str, SIZE, "%ld", l); return *this; }
+ MString& set(const unsigned char &i) { snprintf(str, SIZE, "%u", i); return *this; }
+ MString& set(const unsigned short &i) { snprintf(str, SIZE, "%u", i); return *this; }
+ MString& set(const unsigned int &i) { snprintf(str, SIZE, "%u", i); return *this; }
+ MString& set(const unsigned long &l) { snprintf(str, SIZE, "%lu", l); return *this; }
+ MString& set(const float &f) { set(prec_float_t({ f, SERIAL_FLOAT_PRECISION })); return *this; }
+ MString& set(const prec_float_t &pf) { char f1[12]; set(dtostrf(pf.value, 0, pf.prec, f1)); return *this; }
+ MString& set(const serial_char_t &v) { set(char(v.c)); return *this; }
+ MString& set(const xyz_pos_t &v) { set(); append(v); return *this; }
+ MString& set(const xyze_pos_t &v) { set(); append(v); return *this; }
+
+ // Chainable String appenders
+ MString& append() { return *this; } // for macros that might emit no output
+ MString& append(const char *s) { strncat(str, s, SIZE); return *this; }
+ MString& append(const MString &s) { append(s.str); return *this; }
+ MString& append_P(PGM_P const s) { strncat_P(str, s, SIZE); return *this; }
+ MString& append(FSTR_P const f) { append_P(FTOP(f)); return *this; }
+ MString& append(const bool &b) { append(b ? F("true") : F("false")); return *this; }
+ MString& append(const char c) { const int last = strlen(str); if (last < SIZE) { str[last] = c; str[last+1] = '\0'; }
+ return *this; }
+ MString& append(const short &i) { char buf[32]; sprintf(buf, "%d", i); append(buf); return *this; }
+ MString& append(const int &i) { char buf[32]; sprintf(buf, "%d", i); append(buf); return *this; }
+ MString& append(const long &l) { char buf[32]; sprintf(buf, "%ld", l); append(buf); return *this; }
+ MString& append(const unsigned char &i) { char buf[32]; sprintf(buf, "%u", i); append(buf); return *this; }
+ MString& append(const unsigned short &i) { char buf[32]; sprintf(buf, "%u", i); append(buf); return *this; }
+ MString& append(const unsigned int &i) { char buf[32]; sprintf(buf, "%u", i); append(buf); return *this; }
+ MString& append(const unsigned long &l) { char buf[32]; sprintf(buf, "%lu", l); append(buf); return *this; }
+ MString& append(const float &f) { append(prec_float_t({ f, SERIAL_FLOAT_PRECISION })); return *this; }
+ MString& append(const prec_float_t &pf) { char f1[12]; append(dtostrf(pf.value, 0, pf.prec, f1)); return *this; }
+ MString& append(const serial_char_t &v) { append(char(v.c)); return *this; }
+ MString& append(const xyz_pos_t &v) { LOOP_NUM_AXES(i) append(AXIS_CHAR(i), v[i], ' '); return *this; }
+ MString& append(const xyze_pos_t &v) { LOOP_LOGICAL_AXES(i) append(AXIS_CHAR(i), v[i], ' '); return *this; }
+
+ MString& eol() { append('\n'); }
+
+ // 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...); }
+
+ MString& operator=(const char *s) { set(s); return *this; }
+ MString& operator=(FSTR_P const f) { set(f); return *this; }
+ MString& operator=(const MString &s) { set(s.str); return *this; }
+ MString& operator=(const bool &b) { set(b); return *this; }
+ MString& operator=(const char &c) { set(c); return *this; }
+ MString& operator=(const short &i) { set(i); return *this; }
+ MString& operator=(const int &i) { set(i); return *this; }
+ MString& operator=(const long &l) { set(l); return *this; }
+ MString& operator=(const unsigned char &i) { set(i); return *this; }
+ MString& operator=(const unsigned short &i) { set(i); return *this; }
+ MString& operator=(const unsigned int &i) { set(i); return *this; }
+ MString& operator=(const unsigned long &l) { set(l); return *this; }
+ MString& operator=(const float &f) { set(f); return *this; }
+ MString& operator=(const prec_float_t &p) { set(p); return *this; }
+ MString& operator=(const serial_char_t &c) { set(c); return *this; }
+ MString& operator=(const xyz_pos_t &v) { set(v); return *this; }
+ MString& operator=(const xyze_pos_t &v) { set(v); return *this; }
+
+ MString& operator+=(const char *s) { append(s); return *this; }
+ MString& operator+=(FSTR_P const f) { append(f); return *this; }
+ MString& operator+=(const MString &s) { append(s); return *this; }
+ MString& operator+=(const bool &b) { append(b); return *this; }
+ MString& operator+=(const char &c) { append(c); return *this; }
+ MString& operator+=(const short &i) { append(i); return *this; }
+ MString& operator+=(const int &i) { append(i); return *this; }
+ MString& operator+=(const long &l) { append(l); return *this; }
+ MString& operator+=(const unsigned char &i) { append(i); return *this; }
+ MString& operator+=(const unsigned short &i) { append(i); return *this; }
+ MString& operator+=(const unsigned int &i) { append(i); return *this; }
+ MString& operator+=(const unsigned long &l) { append(l); return *this; }
+ MString& operator+=(const float &f) { append(f); return *this; }
+ MString& operator+=(const prec_float_t &p) { append(p); return *this; }
+ MString& operator+=(const serial_char_t &c) { append(c); return *this; }
+ MString& operator+=(const xyz_pos_t &v) { append(v); return *this; }
+ MString& operator+=(const xyze_pos_t &v) { append(v); return *this; }
+
+ MString operator+(const char *s) { return MString(str, s); }
+ MString operator+(FSTR_P const f) { return MString(str, f); }
+ MString operator+(const MString &s) { return MString(str, s); }
+ MString operator+(const bool &b) { return MString(str, b); }
+ MString operator+(const char c) { return MString(str, c); }
+ MString operator+(const short &i) { return MString(str, i); }
+ MString operator+(const int &i) { return MString(str, i); }
+ MString operator+(const long &l) { return MString(str, l); }
+ MString operator+(const unsigned char &i) { return MString(str, i); }
+ MString operator+(const unsigned short &i) { return MString(str, i); }
+ MString operator+(const unsigned int &i) { return MString(str, i); }
+ MString operator+(const unsigned long &l) { return MString(str, l); }
+ MString operator+(const float &f) { return MString(str, f); }
+ MString operator+(const prec_float_t &p) { return MString(str, p); }
+ MString operator+(const serial_char_t &c) { return MString(str, c); }
+ MString operator+(const xyz_pos_t &v) { return MString(str, v); }
+ MString operator+(const xyze_pos_t &v) { return MString(str, v); }
+
+ #ifndef __AVR__
+ MString(const double d) { set(d); }
+ MString& set(const double &d) { char d1[12]; dtostrf(d, 0, SERIAL_FLOAT_PRECISION, d1); return set(d1); }
+ MString& append(const double &d) { char d1[12]; dtostrf(d, 0, SERIAL_FLOAT_PRECISION, d1); return append(d1); }
+ MString& operator=(const double &d) { set(d); return *this; }
+ MString& operator+=(const double &d) { append(d); return *this; }
+ MString operator+(const double &d) { return MString(str, d); }
+ #endif
+
+ char operator[](const int i) const { return str[i]; }
+
+ char* buffer() { return str; }
+ size_t 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 size_t len = length();
+ hash_t hval = hash_t(len);
+ for (size_t i = 0; i < len; 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, const size_t len) const { strncpy(dst, str, len); }
+
+ MString& clear() { return set(); }
+ MString& concat(const int &i) { if (i <= SIZE) str[i] = '\0'; return *this; }
+ MString& echo() { SERIAL_ECHO(str); return *this; }
+ MString& echoln() { SERIAL_ECHOLN(str); return *this; }
+};
+
+#ifndef TS_SIZE
+ #define TS_SIZE 20
+#endif
+#define TS(V...) MString(V)
diff --git a/Marlin/src/core/serial.h b/Marlin/src/core/serial.h
index c19bc087833dd..912c716804812 100644
--- a/Marlin/src/core/serial.h
+++ b/Marlin/src/core/serial.h
@@ -149,7 +149,6 @@ template
void SERIAL_ECHO(T x) { SERIAL_IMPL.print(x); }
// Wrapper for ECHO commands to interpret a char
-typedef struct SerialChar { char c; SerialChar(char n) : c(n) { } } serial_char_t;
inline void SERIAL_ECHO(serial_char_t x) { SERIAL_IMPL.write(x.c); }
#define AS_CHAR(C) serial_char_t(C)
#define AS_DIGIT(C) AS_CHAR('0' + (C))
@@ -169,11 +168,11 @@ inline void SERIAL_FLUSH() { SERIAL_IMPL.flush(); }
inline void SERIAL_FLUSHTX() { SERIAL_IMPL.flushTX(); }
// Serial echo and error prefixes
-#define SERIAL_ECHO_START() serial_echo_start()
-#define SERIAL_ERROR_START() serial_error_start()
+#define SERIAL_ECHO_START() serial_echo_start()
+#define SERIAL_ERROR_START() serial_error_start()
// Serial end-of-line
-#define SERIAL_EOL() SERIAL_CHAR('\n')
+#define SERIAL_EOL() SERIAL_CHAR('\n')
// Print a single PROGMEM, PGM_P, or PSTR() string.
void serial_print_P(PGM_P str);
diff --git a/Marlin/src/core/types.h b/Marlin/src/core/types.h
index 335aa3a334492..8736e3fe458a0 100644
--- a/Marlin/src/core/types.h
+++ b/Marlin/src/core/types.h
@@ -219,6 +219,9 @@ typedef const_float_t const_celsius_float_t;
#define MMM_TO_MMS(MM_M) feedRate_t(static_cast(MM_M) / 60.0f)
#define MMS_TO_MMM(MM_S) (static_cast(MM_S) * 60.0f)
+// Packaged character for AS_CHAR macro and other usage
+typedef struct SerialChar { char c; SerialChar(char n) : c(n) { } } serial_char_t;
+
//
// Coordinates structures for XY, XYZ, XYZE...
//
diff --git a/Marlin/src/core/utility.cpp b/Marlin/src/core/utility.cpp
index 84e4c1f696967..b62c35aa9cd24 100644
--- a/Marlin/src/core/utility.cpp
+++ b/Marlin/src/core/utility.cpp
@@ -25,6 +25,10 @@
#include "../MarlinCore.h"
#include "../module/temperature.h"
+#if ENABLED(MARLIN_DEV_MODE)
+ MarlinError marlin_error_number; // Error Number - Marlin can beep X times periodically, display, and emit...
+#endif
+
void safe_delay(millis_t ms) {
while (ms > 50) {
ms -= 50;
diff --git a/Marlin/src/core/utility.h b/Marlin/src/core/utility.h
index 10c8201610ea0..c903e2cec0522 100644
--- a/Marlin/src/core/utility.h
+++ b/Marlin/src/core/utility.h
@@ -87,3 +87,22 @@ const xyze_char_t axis_codes LOGICAL_AXIS_ARRAY('E', 'X', 'Y', 'Z', AXIS4_NAME,
#define AXIS_CHAR(A) axis_codes[A]
#define IAXIS_CHAR(A) iaxis_codes[A]
#endif
+
+#if ENABLED(MARLIN_DEV_MODE)
+ enum MarlinError : uint8_t {
+ ERR_NONE,
+ ERR_STRING_RANGE, // A string buffer was too small to set the whole blob
+ ERR_ASSERTION, // An assertion was triggered
+ ERR_MALFUNCTION,
+ ERR_MEMORY_LEAK,
+ ERR_COMMS_SERIAL,
+ ERR_COMMS_SPI,
+ ERR_PLANNER_STARVED,
+ ERR_TMC_SHUTDOWN,
+ ERR_PROCEDURE_FAILED,
+ ERR_TOO_WACK,
+ ERR_PLAID_IN_SUMMER
+ };
+ extern MarlinError marlin_error_number; // Error Number - Marlin can beep, display, and emit...
+ inline void error(const MarlinError err) { marlin_error_number = err; }
+#endif
diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
index a02918ff297ec..eca3d3eaf24b0 100644
--- a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
+++ b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
@@ -1606,12 +1606,11 @@ void unified_bed_leveling::smart_fill_mesh() {
vector_3 normal = vector_3(lsf_results.A, lsf_results.B, 1).get_normal();
if (param.V_verbosity > 2) {
- SERIAL_ECHOPAIR_F("bed plane normal = [", normal.x, 7);
- SERIAL_CHAR(',');
- SERIAL_ECHO_F(normal.y, 7);
- SERIAL_CHAR(',');
- SERIAL_ECHO_F(normal.z, 7);
- SERIAL_ECHOLNPGM("]");
+ MString<100>(F("bed plane normal = ["),
+ prec_float_t({normal.x, 7}), ',',
+ prec_float_t({normal.y, 7}), ',',
+ prec_float_t({normal.z, 7}), ']'
+ ).echoln();
}
matrix_3x3 rotation = matrix_3x3::create_look_at(vector_3(lsf_results.A, lsf_results.B, 1));
@@ -1620,24 +1619,22 @@ void unified_bed_leveling::smart_fill_mesh() {
float mx = get_mesh_x(i), my = get_mesh_y(j), mz = z_values[i][j];
if (DEBUGGING(LEVELING)) {
- DEBUG_ECHOPAIR_F("before rotation = [", mx, 7);
- DEBUG_CHAR(',');
- DEBUG_ECHO_F(my, 7);
- DEBUG_CHAR(',');
- DEBUG_ECHO_F(mz, 7);
- DEBUG_ECHOPGM("] ---> ");
+ MString<100>(F("before rotation = ["),
+ prec_float_t({mx, 7}), ',',
+ prec_float_t({my, 7}), ',',
+ prec_float_t({mz, 7}), F("] ---> ")
+ ).echo();
DEBUG_DELAY(20);
}
rotation.apply_rotation_xyz(mx, my, mz);
if (DEBUGGING(LEVELING)) {
- DEBUG_ECHOPAIR_F("after rotation = [", mx, 7);
- DEBUG_CHAR(',');
- DEBUG_ECHO_F(my, 7);
- DEBUG_CHAR(',');
- DEBUG_ECHO_F(mz, 7);
- DEBUG_ECHOLNPGM("]");
+ MString<100>(F("after rotation = ["),
+ prec_float_t({mx, 7}), ',',
+ prec_float_t({my, 7}), ',',
+ prec_float_t({mz, 7}), ']'
+ ).echoln();
DEBUG_DELAY(20);
}
@@ -1647,17 +1644,18 @@ void unified_bed_leveling::smart_fill_mesh() {
if (DEBUGGING(LEVELING)) {
rotation.debug(F("rotation matrix:\n"));
- DEBUG_ECHOPAIR_F("LSF Results A=", lsf_results.A, 7);
- DEBUG_ECHOPAIR_F(" B=", lsf_results.B, 7);
- DEBUG_ECHOLNPAIR_F(" D=", lsf_results.D, 7);
+ MString<100>(
+ F("LSF Results A="), prec_float_t({lsf_results.A, 7}),
+ F(" B="), prec_float_t({lsf_results.B, 7}),
+ F(" D="), prec_float_t({lsf_results.D, 7})
+ ).echoln();
DEBUG_DELAY(55);
- DEBUG_ECHOPAIR_F("bed plane normal = [", normal.x, 7);
- DEBUG_CHAR(',');
- DEBUG_ECHO_F(normal.y, 7);
- DEBUG_CHAR(',');
- DEBUG_ECHO_F(normal.z, 7);
- DEBUG_ECHOLNPGM("]");
+ MString<100>(F("bed plane normal = ["),
+ prec_float_t({normal.x, 7}), ',',
+ prec_float_t({normal.y, 7}), ',',
+ prec_float_t({normal.z, 7}), ']'
+ ).echoln();
DEBUG_EOL();
/**
diff --git a/Marlin/src/feature/cancel_object.cpp b/Marlin/src/feature/cancel_object.cpp
index bffd2bb72020d..cff82312554c5 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 BOTH(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(TS(GET_TEXT_F(MSG_PRINTING_OBJECT), int(active_object)).str);
else
ui.reset_status();
#endif
diff --git a/Marlin/src/feature/stepper_driver_safety.cpp b/Marlin/src/feature/stepper_driver_safety.cpp
index b8762da9b0c02..2e60edbedd13c 100644
--- a/Marlin/src/feature/stepper_driver_safety.cpp
+++ b/Marlin/src/feature/stepper_driver_safety.cpp
@@ -32,7 +32,7 @@ void stepper_driver_backward_error(FSTR_P const fstr) {
SERIAL_ERROR_START();
SERIAL_ECHOF(fstr);
SERIAL_ECHOLNPGM(" driver is backward!");
- ui.status_printf(2, F(S_FMT S_FMT), FTOP(fstr), GET_TEXT(MSG_DRIVER_BACKWARD));
+ ui.set_status(TS(fstr, GET_TEXT_F(MSG_DRIVER_BACKWARD)).str, 2);
}
void stepper_driver_backward_check() {
diff --git a/Marlin/src/inc/Conditionals_LCD.h b/Marlin/src/inc/Conditionals_LCD.h
index e41bc0d48977c..fead5a0b1969e 100644
--- a/Marlin/src/inc/Conditionals_LCD.h
+++ b/Marlin/src/inc/Conditionals_LCD.h
@@ -513,7 +513,7 @@
#define HAS_LCDPRINT 1
#endif
-#if HAS_DISPLAY || HAS_DWIN_E3V2
+#if ANY(HAS_DISPLAY, HAS_DWIN_E3V2, HOST_STATUS_MESSAGE)
#define HAS_STATUS_MESSAGE 1
#endif
diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h
index 9edcd6f1aa9ad..372acd1b0148a 100644
--- a/Marlin/src/inc/Conditionals_post.h
+++ b/Marlin/src/inc/Conditionals_post.h
@@ -2434,6 +2434,9 @@
#if ANY_AXIS_HAS(SW_SERIAL)
#define HAS_TMC_SW_SERIAL 1
#endif
+#ifndef SERIAL_FLOAT_PRECISION
+ #define SERIAL_FLOAT_PRECISION 2
+#endif
#if DISABLED(SENSORLESS_HOMING)
#undef SENSORLESS_BACKOFF_MM
diff --git a/Marlin/src/lcd/HD44780/marlinui_HD44780.cpp b/Marlin/src/lcd/HD44780/marlinui_HD44780.cpp
index da4db8b2d4197..f8ad29c6aff48 100644
--- a/Marlin/src/lcd/HD44780/marlinui_HD44780.cpp
+++ b/Marlin/src/lcd/HD44780/marlinui_HD44780.cpp
@@ -496,7 +496,7 @@ void MarlinUI::clear_lcd() { lcd.clear(); }
void MarlinUI::draw_kill_screen() {
lcd_uint_t x = 0, y = 0;
- lcd_put_u8str(x, y, status_message);
+ lcd_put_u8str(x, y, status_message.str);
y = 2;
#if LCD_HEIGHT >= 4
lcd_put_u8str(x, y++, GET_TEXT_F(MSG_HALTED));
@@ -677,13 +677,13 @@ 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) {
// The string isn't scrolling and may not fill the screen
- lcd_put_u8str(status_message);
+ lcd_put_u8str(status_message.str);
// Fill the rest with spaces
while (slen < LCD_WIDTH) { lcd_put_wchar(' '); ++slen; }
@@ -706,7 +706,7 @@ void MarlinUI::draw_status_message(const bool blink) {
if (--chars) { // Draw a third space if there's room
lcd_put_wchar(' ');
if (--chars)
- lcd_put_u8str_max(status_message, chars); // Print a second copy of the message
+ lcd_put_u8str_max(status_message.str, chars); // Print a second copy of the message
}
}
}
@@ -719,10 +719,10 @@ 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);
+ lcd_put_u8str_max(status_message.str, LCD_WIDTH);
// Fill the rest with spaces if there are missing spaces
while (slen < LCD_WIDTH) {
diff --git a/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp b/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp
index 46564bb1e6e29..ac316192a737b 100644
--- a/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp
+++ b/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp
@@ -419,8 +419,8 @@ 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_put_u8str(status_message);
+ lcd_moveto((LCD_WIDTH - status_message.glyphs()) / 2 + 1, 3);
+ lcd_put_u8str(status_message.str);
center_text(GET_TEXT_F(MSG_HALTED), 5);
center_text(GET_TEXT_F(MSG_PLEASE_RESET), 6);
lcd.print_screen();
@@ -660,7 +660,7 @@ void MarlinUI::draw_status_message(const bool blink) {
#endif // FILAMENT_LCD_DISPLAY && SDSUPPORT
// 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)
@@ -670,7 +670,7 @@ void MarlinUI::draw_status_message(const bool blink) {
if (slen <= LCD_WIDTH) {
// The string isn't scrolling and may not fill the screen
- lcd_put_u8str(status_message);
+ lcd_put_u8str(status_message.str);
// Fill the rest with spaces
while (slen < LCD_WIDTH) { lcd.write(' '); ++slen; }
@@ -693,7 +693,7 @@ void MarlinUI::draw_status_message(const bool blink) {
if (--chars) { // Draw a third space if there's room
lcd.write(' ');
if (--chars)
- lcd_put_u8str_max(status_message, chars); // Print a second copy of the message
+ lcd_put_u8str_max(status_message.str, chars); // Print a second copy of the message
}
}
}
@@ -708,7 +708,7 @@ void MarlinUI::draw_status_message(const bool blink) {
UNUSED(blink);
// Just print the string to the LCD
- lcd_put_u8str_max(status_message, LCD_WIDTH);
+ lcd_put_u8str_max(status_message.str, LCD_WIDTH);
// Fill the rest with spaces if there are missing spaces
while (slen < LCD_WIDTH) {
diff --git a/Marlin/src/lcd/dogm/marlinui_DOGM.cpp b/Marlin/src/lcd/dogm/marlinui_DOGM.cpp
index 3c661a44294a5..6a1fe7123859f 100644
--- a/Marlin/src/lcd/dogm/marlinui_DOGM.cpp
+++ b/Marlin/src/lcd/dogm/marlinui_DOGM.cpp
@@ -334,7 +334,7 @@ void MarlinUI::draw_kill_screen() {
u8g.firstPage();
do {
set_font(FONT_MENU);
- lcd_put_u8str(x, h4 * 1, status_message);
+ lcd_put_u8str(x, h4 * 1, status_message.str);
lcd_put_u8str(x, h4 * 2, GET_TEXT_F(MSG_HALTED));
lcd_put_u8str(x, h4 * 3, GET_TEXT_F(MSG_PLEASE_RESET));
} while (u8g.nextPage());
diff --git a/Marlin/src/lcd/dogm/status_screen_DOGM.cpp b/Marlin/src/lcd/dogm/status_screen_DOGM.cpp
index 010a1397f620c..c4ac5cedc16e2 100644
--- a/Marlin/src/lcd/dogm/status_screen_DOGM.cpp
+++ b/Marlin/src/lcd/dogm/status_screen_DOGM.cpp
@@ -962,7 +962,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 = utf8_strlen(status_message.str);
#if HAS_POWER_MONITOR
if (power_monitor.display_enabled()) {
@@ -978,7 +978,7 @@ void MarlinUI::draw_status_message(const bool blink) {
if (slen <= lcd_width) {
// The string fits within the line. Print with no scrolling
- lcd_put_u8str(status_message);
+ lcd_put_u8str(status_message.str);
while (slen < lcd_width) { lcd_put_wchar(' '); ++slen; }
}
else {
@@ -1003,7 +1003,7 @@ void MarlinUI::draw_status_message(const bool blink) {
if (--chars) { // Draw a third space if there's room
lcd_put_wchar(' ');
if (--chars) { // Print a second copy of the message
- lcd_put_u8str_max(status_message, pixel_width - (rlen + 2) * (MENU_FONT_WIDTH));
+ lcd_put_u8str_max(status_message.str, pixel_width - (rlen + 2) * (MENU_FONT_WIDTH));
lcd_put_wchar(' ');
}
}
@@ -1016,7 +1016,7 @@ void MarlinUI::draw_status_message(const bool blink) {
UNUSED(blink);
// Just print the string to the LCD
- lcd_put_u8str_max(status_message, pixel_width);
+ lcd_put_u8str_max(status_message.str, pixel_width);
// Fill the rest with spaces
for (; slen < lcd_width; ++slen) lcd_put_wchar(' ');
diff --git a/Marlin/src/lcd/dogm/status_screen_lite_ST7920.cpp b/Marlin/src/lcd/dogm/status_screen_lite_ST7920.cpp
index 492a79a311088..fb66b3be8fd68 100644
--- a/Marlin/src/lcd/dogm/status_screen_lite_ST7920.cpp
+++ b/Marlin/src/lcd/dogm/status_screen_lite_ST7920.cpp
@@ -619,12 +619,12 @@ 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;
+ const char *str = ui.status_message.str;
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
@@ -772,11 +772,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;
}
@@ -811,7 +810,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/common/dwin_api.h b/Marlin/src/lcd/e3v2/common/dwin_api.h
index dc97ef2723fae..a72ca3f28001d 100644
--- a/Marlin/src/lcd/e3v2/common/dwin_api.h
+++ b/Marlin/src/lcd/e3v2/common/dwin_api.h
@@ -22,6 +22,7 @@
#pragma once
#include "../../../inc/MarlinConfig.h"
+#include "../../../core/mstring.h"
#if ENABLED(DWIN_MARLINUI_LANDSCAPE)
#define DWIN_WIDTH 480
diff --git a/Marlin/src/lcd/e3v2/marlinui/ui_common.cpp b/Marlin/src/lcd/e3v2/marlinui/ui_common.cpp
index 0727ab0b70caf..faaf8be3db19c 100644
--- a/Marlin/src/lcd/e3v2/marlinui/ui_common.cpp
+++ b/Marlin/src/lcd/e3v2/marlinui/ui_common.cpp
@@ -160,9 +160,9 @@ void MarlinUI::draw_kill_screen() {
DWIN_ICON_Show(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);
+ lcd_put_u8str(status_message.str);
slen = utf8_strlen(S(GET_TEXT_F(MSG_HALTED)));
lcd_moveto(cx - (slen / 2), cy);
@@ -186,13 +186,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 || !ui.did_first_redraw;
@@ -202,7 +197,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) {
@@ -210,7 +205,7 @@ void MarlinUI::draw_status_message(const bool blink) {
if (status_changed()) {
// The string isn't scrolling and may not fill the screen
- lcd_put_u8str(status_message);
+ lcd_put_u8str(status_message.str);
// Fill the rest with spaces
while (slen < max_status_chars) { lcd_put_wchar(' '); ++slen; }
@@ -232,7 +227,7 @@ void MarlinUI::draw_status_message(const bool blink) {
if (--chars) { // Draw a second dot if there's space
lcd_put_wchar('.');
if (--chars)
- lcd_put_u8str_max(status_message, chars); // Print a second copy of the message
+ lcd_put_u8str_max(status_message.str, chars); // Print a second copy of the message
}
}
@@ -248,10 +243,10 @@ 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);
+ lcd_put_u8str_max(status_message.str, max_status_chars);
// Fill the rest with spaces if there are missing spaces
while (slen < max_status_chars) { lcd_put_wchar(' '); ++slen; }
diff --git a/Marlin/src/lcd/e3v2/proui/dwin.cpp b/Marlin/src/lcd/e3v2/proui/dwin.cpp
index 07b134471b583..af4f2591200bd 100644
--- a/Marlin/src/lcd/e3v2/proui/dwin.cpp
+++ b/Marlin/src/lcd/e3v2/proui/dwin.cpp
@@ -28,6 +28,7 @@
*/
#include "../../../inc/MarlinConfig.h"
+#include "../../../core/mstring.h"
#if ENABLED(DWIN_LCD_PROUI)
@@ -564,17 +565,14 @@ void DWIN_DrawStatusLine(FSTR_P fstr) {
// Clear & reset status line
void DWIN_ResetStatusLine() {
- ui.status_message[0] = 0;
+ ui.status_message.clear();
DWIN_CheckStatusMessage();
}
-// Djb2 hash algorithm
+// Check for a change in the status message
void DWIN_CheckStatusMessage() {
- static uint32_t old_hash = 0;
- char * str = &ui.status_message[0];
- uint32_t hash = 5381;
- char c;
- while ((c = *str++)) hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
+ static MString<>::hash_t old_hash = 0x0000;
+ const MString<>::hash_t hash = ui.status_message.hash();
hash_changed = hash != old_hash;
old_hash = hash;
};
@@ -583,12 +581,12 @@ 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) {
if (hash_changed) {
- DWIN_DrawStatusLine(ui.status_message);
+ DWIN_DrawStatusLine(ui.status_message.str);
hash_changed = false;
}
}
@@ -610,7 +608,7 @@ void DWIN_DrawStatusMessage() {
if (--chars) { // Draw a second dot if there's space
DWINUI::Draw_Char(HMI_data.StatusTxt_Color, '.');
if (--chars)
- DWINUI::Draw_String(HMI_data.StatusTxt_Color, ui.status_message, chars); // Print a second copy of the message
+ DWINUI::Draw_String(HMI_data.StatusTxt_Color, ui.status_message.str, chars); // Print a second copy of the message
}
}
MarlinUI::advance_status_scroll();
@@ -619,8 +617,8 @@ void DWIN_DrawStatusMessage() {
#else
if (hash_changed) {
- ui.status_message[LCD_WIDTH] = 0;
- DWIN_DrawStatusLine(ui.status_message);
+ ui.status_message.concat(LCD_WIDTH);
+ DWIN_DrawStatusLine(ui.status_message.str);
hash_changed = false;
}
diff --git a/Marlin/src/lcd/fontutils.cpp b/Marlin/src/lcd/fontutils.cpp
index a97e63ac4d3a3..eea71dd1b4932 100644
--- a/Marlin/src/lcd/fontutils.cpp
+++ b/Marlin/src/lcd/fontutils.cpp
@@ -175,13 +175,8 @@ static inline uint8_t utf8_strlen_cb(const char *pstart, read_byte_cb_t cb_read_
return cnt;
}
-uint8_t utf8_strlen(const char *pstart) {
- return utf8_strlen_cb(pstart, read_byte_ram);
-}
-
-uint8_t utf8_strlen_P(PGM_P pstart) {
- return utf8_strlen_cb(pstart, read_byte_rom);
-}
+uint8_t utf8_strlen(const char *pstart) { return utf8_strlen_cb(pstart, read_byte_ram); }
+uint8_t utf8_strlen_P(PGM_P pstart) { return utf8_strlen_cb(pstart, read_byte_rom); }
static inline uint8_t utf8_byte_pos_by_char_num_cb(const char *pstart, read_byte_cb_t cb_read_byte, const uint8_t charnum) {
uint8_t *p = (uint8_t *)pstart;
diff --git a/Marlin/src/lcd/marlinui.cpp b/Marlin/src/lcd/marlinui.cpp
index c1b600593457a..d819fd1f51af9 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) && EITHER(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
@@ -624,7 +624,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;
}
}
@@ -1407,7 +1407,7 @@ void MarlinUI::init() {
#include "extui/ui_api.h"
#endif
- bool MarlinUI::has_status() { return (status_message[0] != '\0'); }
+ bool MarlinUI::has_status() { return !status_message.empty(); }
void MarlinUI::set_status(const char * const cstr, const bool persist) {
if (alert_level) return;
@@ -1429,9 +1429,7 @@ void MarlinUI::init() {
};
// 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';
+ status_message.set(cstr).concat(pend - cstr);
finish_status(persist);
}
@@ -1482,37 +1480,42 @@ void MarlinUI::init() {
}
/**
- * 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;
+ }
- PGM_P const pstr = FTOP(fstr);
+ /**
+ * 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.
+ */
+ void MarlinUI::set_status(const char *ustr, int8_t level, const bool pgm) {
+ if (set_alert_level(level)) return;
// Since the message is encoded in UTF8 it must
// only be cut on a character boundary.
// Get a pointer to the null terminator
- PGM_P pend = pstr + strlen_P(pstr);
+ const char *uend = ustr + (pgm ? strlen_P(ustr) : strlen(ustr));
// 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;
+ while ((uend - ustr) > MAX_MESSAGE_LENGTH) {
+ --uend;
+ while (!START_OF_UTF8_CHAR(pgm ? char(pgm_read_byte(uend)) : *uend)) --uend;
};
// 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 ? status_message.set_P(ustr) : status_message.set(ustr)).concat(uend - ustr);
- TERN_(HOST_STATUS_NOTIFICATIONS, hostui.notify(fstr));
+ TERN_(HOST_STATUS_NOTIFICATIONS, pgm ? hostui.notify(FPSTR(ustr)) : hostui.notify(ustr));
finish_status(level > 0);
}
@@ -1526,17 +1529,14 @@ void MarlinUI::init() {
#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;
+ if (set_alert_level(level)) return;
va_list args;
va_start(args, FTOP(fmt));
- vsnprintf_P(status_message, MAX_MESSAGE_LENGTH, FTOP(fmt), args);
+ vsnprintf_P(status_message.str, MAX_MESSAGE_LENGTH, FTOP(fmt), args);
va_end(args);
- TERN_(HOST_STATUS_NOTIFICATIONS, hostui.notify(status_message));
+ TERN_(HOST_STATUS_NOTIFICATIONS, hostui.notify(status_message.str));
finish_status(level > 0);
}
@@ -1572,24 +1572,24 @@ void MarlinUI::init() {
status_scroll_offset = 0;
#endif
- TERN_(EXTENSIBLE_UI, ExtUI::onStatusChanged(status_message));
- TERN_(DWIN_CREALITY_LCD, DWIN_StatusChanged(status_message));
+ TERN_(EXTENSIBLE_UI, ExtUI::onStatusChanged(status_message.str));
+ TERN_(DWIN_CREALITY_LCD, DWIN_StatusChanged(status_message.str));
TERN_(DWIN_LCD_PROUI, DWIN_CheckStatusMessage());
- TERN_(DWIN_CREALITY_LCD_JYERSUI, CrealityDWIN.Update_Status(status_message));
+ TERN_(DWIN_CREALITY_LCD_JYERSUI, CrealityDWIN.Update_Status(status_message.str));
}
#if ENABLED(STATUS_MESSAGE_SCROLLING)
void MarlinUI::advance_status_scroll() {
// Advance by one UTF8 code-word
- if (status_scroll_offset < utf8_strlen(status_message))
- while (!START_OF_UTF8_CHAR(status_message[++status_scroll_offset]));
+ if (status_scroll_offset < utf8_strlen(status_message.str))
+ while (!START_OF_UTF8_CHAR(status_message.str[++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.str + status_scroll_offset;
len = utf8_strlen(out);
return out;
}
diff --git a/Marlin/src/lcd/marlinui.h b/Marlin/src/lcd/marlinui.h
index 1c2c484323229..eaa6f6e39016b 100644
--- a/Marlin/src/lcd/marlinui.h
+++ b/Marlin/src/lcd/marlinui.h
@@ -25,6 +25,7 @@
#include "../sd/cardreader.h"
#include "../module/motion.h"
#include "../libs/buzzer.h"
+#include "../core/mstring.h"
#include "buttons.h"
@@ -54,8 +55,6 @@
#include "e3v2/proui/dwin.h"
#endif
-#define START_OF_UTF8_CHAR(C) (((C) & 0xC0u) != 0x80U)
-
typedef bool (*statusResetFunc_t)();
#if HAS_WIRED_LCD
@@ -348,7 +347,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
@@ -361,6 +360,13 @@ class MarlinUI {
static char* status_and_len(uint8_t &len);
#endif
+ /**
+ * 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(FSTR_P const fstr, int8_t level=0) { set_status_P(FTOP(fstr), level); }
+
static bool has_status();
static void reset_status(const bool no_welcome=false);
static void set_alert_status(FSTR_P const fstr);
@@ -368,7 +374,10 @@ class MarlinUI {
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 void set_status(FSTR_P const fstr, int8_t level=0);
static constexpr bool has_status() { return false; }
static void reset_status(const bool=false) {}
static void set_alert_status(FSTR_P const) {}
@@ -377,8 +386,39 @@ class MarlinUI {
#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, ...);
+ static void set_status(const MString &s, const bool persist=false) { set_status(s.str, persist); }
+
+ /**
+ * 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);
+
+ /**
+ * 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(const char * const ustr, int8_t level, const bool pgm);
+
+ /**
+ * 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.
+ * @param pgm Program string flag. Only relevant on AVR.
+ */
+ static void set_status_P(PGM_P const pstr, int8_t level=0) { set_status(pstr, level, true); }
+
+ /**
+ * Set Status with a MString and alert level.
+ * @param s A MString to set as the status.
+ * @param level Alert level. Negative to ignore and reset the level. Non-zero never expires.
+ */
+ static void set_status(const MString &s, int8_t level=0) { set_status(s.str, level, false); }
+
+ static void status_printf(int8_t level, FSTR_P const fmt, ...); // TODO: Replace with use of MString
#if HAS_DISPLAY
diff --git a/Marlin/src/lcd/tft/ui_1024x600.cpp b/Marlin/src/lcd/tft/ui_1024x600.cpp
index ad9f8111815a3..e972f96d791d5 100644
--- a/Marlin/src/lcd/tft/ui_1024x600.cpp
+++ b/Marlin/src/lcd/tft/ui_1024x600.cpp
@@ -99,7 +99,7 @@ void MarlinUI::draw_kill_screen() {
uint16_t line = 2;
menu_line(line++, COLOR_KILL_SCREEN_BG);
- tft_string.set(status_message);
+ tft_string.set(status_message.str);
tft_string.trim();
tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_MENU_TEXT, tft_string);
@@ -352,7 +352,7 @@ void MarlinUI::draw_status_screen() {
// status message
tft.canvas(0, y, TFT_WIDTH, FONT_LINE_HEIGHT - 5);
tft.set_background(COLOR_BACKGROUND);
- tft_string.set(status_message);
+ tft_string.set(status_message.str);
tft_string.trim();
tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_STATUS_MESSAGE, tft_string);
}
diff --git a/Marlin/src/lcd/tft/ui_320x240.cpp b/Marlin/src/lcd/tft/ui_320x240.cpp
index 56887478f0a61..3f12a07df1ae9 100644
--- a/Marlin/src/lcd/tft/ui_320x240.cpp
+++ b/Marlin/src/lcd/tft/ui_320x240.cpp
@@ -98,7 +98,7 @@ void MarlinUI::draw_kill_screen() {
tft.canvas(0, 60, TFT_WIDTH, 20);
tft.set_background(COLOR_KILL_SCREEN_BG);
- tft_string.set(status_message);
+ tft_string.set(status_message.str);
tft_string.trim();
tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_KILL_SCREEN_TEXT, tft_string);
@@ -336,7 +336,7 @@ void MarlinUI::draw_status_screen() {
// status message
tft.canvas(0, 216, 320, 20);
tft.set_background(COLOR_BACKGROUND);
- tft_string.set(status_message);
+ tft_string.set(status_message.str);
tft_string.trim();
tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_STATUS_MESSAGE, tft_string);
diff --git a/Marlin/src/lcd/tft/ui_480x320.cpp b/Marlin/src/lcd/tft/ui_480x320.cpp
index d4a04d690080b..a286cc8b6cb1f 100644
--- a/Marlin/src/lcd/tft/ui_480x320.cpp
+++ b/Marlin/src/lcd/tft/ui_480x320.cpp
@@ -99,7 +99,7 @@ void MarlinUI::draw_kill_screen() {
uint16_t line = 2;
menu_line(line++, COLOR_KILL_SCREEN_BG);
- tft_string.set(status_message);
+ tft_string.set(status_message.str);
tft_string.trim();
tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_MENU_TEXT, tft_string);
@@ -346,7 +346,7 @@ void MarlinUI::draw_status_screen() {
// status message
tft.canvas(0, y, TFT_WIDTH, FONT_LINE_HEIGHT - 5);
tft.set_background(COLOR_BACKGROUND);
- tft_string.set(status_message);
+ tft_string.set(status_message.str);
tft_string.trim();
tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_STATUS_MESSAGE, tft_string);
}
diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp
index 17ab08d4c20ec..2361d69b44886 100644
--- a/Marlin/src/module/temperature.cpp
+++ b/Marlin/src/module/temperature.cpp
@@ -3906,24 +3906,16 @@ 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_CHAR(':');
- SERIAL_PRINT(c, SFP);
- SERIAL_ECHOPGM(" /");
- SERIAL_PRINT(t, SFP);
+ #define SFP _MIN(SERIAL_FLOAT_PRECISION, 2)
+
+ MString<50> s(' ', k);
+ if (TERN0(HAS_MULTI_HOTEND, e >= 0)) s += char('0' + e);
+ s += TS(':', prec_float_t({c, SFP}), F(" /"), prec_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);
}
@@ -3954,23 +3946,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));
+ MString<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)
@@ -4057,11 +4046,12 @@ void Temperature::isr() {
next_temp_ms = now + 1000UL;
print_heater_states(target_extruder);
#if TEMP_RESIDENCY_TIME > 0
- SERIAL_ECHOPGM(" W:");
+ MString<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();
}
@@ -4193,11 +4183,12 @@ void Temperature::isr() {
next_temp_ms = now + 1000UL;
print_heater_states(active_extruder);
#if TEMP_BED_RESIDENCY_TIME > 0
- SERIAL_ECHOPGM(" W:");
+ MString<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();
}
@@ -4286,7 +4277,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.");
+ MString<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);
@@ -4324,9 +4315,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;
+ MString<60>(wants_to_cool ? F("Cooldown") : F("Heatup"), F(" complete, target probe temperature reached.")).echoln();
+ break;
}
}
@@ -4386,11 +4376,12 @@ void Temperature::isr() {
next_temp_ms = now + 1000UL;
print_heater_states(active_extruder);
#if TEMP_CHAMBER_RESIDENCY_TIME > 0
- SERIAL_ECHOPGM(" W:");
+ MString<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();
}
@@ -4484,11 +4475,12 @@ void Temperature::isr() {
next_temp_ms = now + 1000UL;
print_heater_states(active_extruder);
#if TEMP_COOLER_RESIDENCY_TIME > 0
- SERIAL_ECHOPGM(" W:");
+ MString<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();
}