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