EDITABLE_DISPLAY_TIMEOUT (#26517)

Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
This commit is contained in:
Andrew 2023-12-13 00:33:03 -05:00 committed by GitHub
parent 81cfa23882
commit 06710e54de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 102 additions and 82 deletions

View file

@ -1471,11 +1471,6 @@
#define FEEDRATE_CHANGE_BEEP_FREQUENCY 440
#endif
//
// LCD Backlight Timeout
//
//#define LCD_BACKLIGHT_TIMEOUT_MINS 1 // (minutes) Timeout before turning off the backlight
#if HAS_BED_PROBE && ANY(HAS_MARLINUI_MENU, HAS_TFT_LVGL_UI)
//#define PROBE_OFFSET_WIZARD // Add a Probe Z Offset calibration option to the LCD menu
#if ENABLED(PROBE_OFFSET_WIZARD)
@ -2206,6 +2201,15 @@
//#define TFT_BTOKMENU_COLOR 0x145F // 00010 100010 11111 Cyan
#endif
//
// LCD Backlight Timeout
// Requires a display with a controllable backlight
//
//#define LCD_BACKLIGHT_TIMEOUT_MINS 1 // (minutes) Timeout before turning off the backlight
#if defined(DISPLAY_SLEEP_MINUTES) || defined(LCD_BACKLIGHT_TIMEOUT_MINS)
#define EDITABLE_DISPLAY_TIMEOUT // Edit timeout with M255 S<minutes> and a menu item
#endif
//
// ADC Button Debounce
//

View file

@ -198,7 +198,7 @@ typedef Flags<8> flags_8_t;
typedef Flags<16> flags_16_t;
// Flags for some axis states, with per-axis aliases xyzijkuvwe
typedef struct AxisFlags {
typedef struct {
union {
struct Flags<LOGICAL_AXES> flags;
struct { bool LOGICAL_AXIS_LIST(e:1, x:1, y:1, z:1, i:1, j:1, k:1, u:1, v:1, w:1); };
@ -212,7 +212,7 @@ typedef struct AxisFlags {
FI bool operator[](const int n) const { return flags[n]; }
FI int size() const { return sizeof(flags); }
FI operator bool() const { return flags; }
} axis_flags_t;
} AxisFlags;
//
// Enumerated axis indices

View file

@ -195,7 +195,7 @@ void BDS_Leveling::process() {
safe_delay(10);
if (config_state == BDS_CALIBRATE_START) {
config_state = BDS_CALIBRATING;
REMEMBER(gsit, gcode.stepper_inactive_time, SEC_TO_MS(60 * 5));
REMEMBER(gsit, gcode.stepper_inactive_time, MIN_TO_MS(5));
SERIAL_ECHOLNPGM("c_z0:", planner.get_axis_position_mm(Z_AXIS), "-", pos_zero_offset);
// Move the z axis instead of enabling the Z axis with M17

View file

@ -772,7 +772,8 @@ void unified_bed_leveling::shift_mesh_height() {
const grid_count_t point_num = (GRID_MAX_POINTS - count) + 1;
SERIAL_ECHOLNPGM("Probing mesh point ", point_num, "/", GRID_MAX_POINTS, ".");
TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/%i"), GET_TEXT(MSG_PROBING_POINT), point_num, int(GRID_MAX_POINTS)));
TERN_(LCD_BACKLIGHT_TIMEOUT_MINS, ui.refresh_backlight_timeout());
TERN_(HAS_BACKLIGHT_TIMEOUT, ui.refresh_backlight_timeout());
TERN_(DWIN_LCD_PROUI, dwinRedrawScreen());
#if HAS_MARLINUI_MENU
if (ui.button_pressed()) {

View file

@ -256,7 +256,7 @@
say_waiting_for_probe_heating();
SERIAL_ECHOLNPGM(" Bed:", target_bed, " Probe:", target_probe);
const millis_t probe_timeout_ms = millis() + SEC_TO_MS(900UL);
const millis_t probe_timeout_ms = millis() + MIN_TO_MS(15);
while (thermalManager.degProbe() < target_probe) {
if (report_temps(next_temp_report, probe_timeout_ms)) {
SERIAL_ECHOLNPGM("!Probe heating timed out.");

View file

@ -793,7 +793,7 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
case 250: M250(); break; // M250: Set LCD contrast
#endif
#if HAS_GCODE_M255
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
case 255: M255(); break; // M255: Set LCD Sleep/Backlight Timeout (Minutes)
#endif

View file

@ -909,7 +909,7 @@ private:
static void M250_report(const bool forReplay=true);
#endif
#if HAS_GCODE_M255
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
static void M255();
static void M255_report(const bool forReplay=true);
#endif

View file

@ -21,7 +21,7 @@
*/
#include "../../inc/MarlinConfig.h"
#if HAS_GCODE_M255
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
#include "../gcode.h"
#include "../../lcd/marlinui.h"
@ -51,4 +51,4 @@ void GcodeSuite::M255_report(const bool forReplay/*=true*/) {
);
}
#endif // HAS_GCODE_M255
#endif // EDITABLE_DISPLAY_TIMEOUT

View file

@ -893,11 +893,11 @@
#if ALL(HAS_RESUME_CONTINUE, PRINTER_EVENT_LEDS, HAS_MEDIA)
#define HAS_LEDS_OFF_FLAG 1
#endif
#if DISPLAY_SLEEP_MINUTES || TOUCH_IDLE_SLEEP_MINS
#if defined(DISPLAY_SLEEP_MINUTES) || defined(TOUCH_IDLE_SLEEP_MINS)
#define HAS_DISPLAY_SLEEP 1
#endif
#if HAS_DISPLAY_SLEEP || LCD_BACKLIGHT_TIMEOUT_MINS
#define HAS_GCODE_M255 1
#ifdef LCD_BACKLIGHT_TIMEOUT_MINS
#define HAS_BACKLIGHT_TIMEOUT 1
#endif
#if ANY(DIGIPOT_MCP4018, DIGIPOT_MCP4451)

View file

@ -2762,7 +2762,7 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L
#endif
#endif
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if HAS_BACKLIGHT_TIMEOUT
#if !HAS_ENCODER_ACTION && DISABLED(HAS_DWIN_E3V2)
#error "LCD_BACKLIGHT_TIMEOUT_MINS requires an LCD with encoder or keypad."
#elif ENABLED(NEOPIXEL_BKGD_INDEX_FIRST)

View file

@ -87,9 +87,7 @@ EncoderState encoderReceiveAnalyze() {
#if PIN_EXISTS(LCD_LED)
//LED_Action();
#endif
#if LCD_BACKLIGHT_TIMEOUT_MINS
ui.refresh_backlight_timeout();
#endif
TERN_(HAS_BACKLIGHT_TIMEOUT, ui.refresh_backlight_timeout());
if (!ui.backlight) {
ui.refresh_brightness();
return ENCODER_DIFF_NO;
@ -161,9 +159,7 @@ EncoderState encoderReceiveAnalyze() {
temp_diff = 0;
}
if (temp_diffState != ENCODER_DIFF_NO) {
#if LCD_BACKLIGHT_TIMEOUT_MINS
ui.refresh_backlight_timeout();
#endif
TERN_(HAS_BACKLIGHT_TIMEOUT, ui.refresh_backlight_timeout());
if (!ui.backlight) ui.refresh_brightness();
}
return temp_diffState;

View file

@ -1249,7 +1249,7 @@ void eachMomentUpdate() {
static millis_t next_var_update_ms = 0, next_rts_update_ms = 0, next_status_update_ms = 0;
const millis_t ms = millis();
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if HAS_BACKLIGHT_TIMEOUT
if (ui.backlight_off_ms && ELAPSED(ms, ui.backlight_off_ms)) {
turnOffBacklight(); // Backlight off
ui.backlight_off_ms = 0;
@ -2235,7 +2235,7 @@ void setMoveZ() { hmiValue.axis = Z_AXIS; setPFloatOnClick(Z_MIN_POS, Z_MAX_POS,
#endif
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
void applyTimer() { ui.backlight_timeout_minutes = menuData.value; }
void setTimer() { setIntOnClick(ui.backlight_timeout_min, ui.backlight_timeout_max, ui.backlight_timeout_minutes, applyTimer); }
#endif
@ -3123,7 +3123,7 @@ void drawAdvancedSettingsMenu() {
#if HAS_LOCKSCREEN
MENU_ITEM(ICON_Lock, MSG_LOCKSCREEN, onDrawMenuItem, dwinLockScreen);
#endif
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
EDIT_ITEM(ICON_Brightness, MSG_SCREEN_TIMEOUT, onDrawPIntMenu, setTimer, &ui.backlight_timeout_minutes);
#endif
#if ENABLED(SOUND_MENU_ITEM)
@ -3347,7 +3347,7 @@ void drawTuneMenu() {
EDIT_ITEM(ICON_Brightness, MSG_BRIGHTNESS, onDrawPInt8Menu, setBrightness, &ui.brightness);
MENU_ITEM(ICON_Brightness, MSG_BRIGHTNESS_OFF, onDrawMenuItem, turnOffBacklight);
#endif
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
EDIT_ITEM(ICON_Brightness, MSG_SCREEN_TIMEOUT, onDrawPIntMenu, setTimer, &ui.backlight_timeout_minutes);
#endif
#if ENABLED(CASE_LIGHT_MENU)

View file

@ -73,9 +73,7 @@ void Plot::update(const_float_t value) {
dwinDrawPoint(COLOR_YELLOW, 1, 1, x2 - 1, y);
}
graphpoints++;
#if LCD_BACKLIGHT_TIMEOUT_MINS
ui.refresh_backlight_timeout();
#endif
TERN_(HAS_BACKLIGHT_TIMEOUT, ui.refresh_backlight_timeout());
}
#endif // PROUI_TUNING_GRAPH

View file

@ -24,7 +24,7 @@
#include "../MarlinCore.h" // for printingIsPaused
#if LED_POWEROFF_TIMEOUT > 0 || ALL(HAS_WIRED_LCD, PRINTER_EVENT_LEDS) || (defined(LCD_BACKLIGHT_TIMEOUT_MINS) && defined(NEOPIXEL_BKGD_INDEX_FIRST))
#if LED_POWEROFF_TIMEOUT > 0 || ALL(HAS_WIRED_LCD, PRINTER_EVENT_LEDS) || (HAS_BACKLIGHT_TIMEOUT && defined(NEOPIXEL_BKGD_INDEX_FIRST))
#include "../feature/leds/leds.h"
#endif
@ -185,10 +185,14 @@ constexpr uint8_t epps = ENCODER_PULSES_PER_STEP;
volatile int8_t encoderDiff; // Updated in update_buttons, added to encoderPosition every LCD update
#endif
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if HAS_BACKLIGHT_TIMEOUT
constexpr uint8_t MarlinUI::backlight_timeout_min, MarlinUI::backlight_timeout_max;
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
uint8_t MarlinUI::backlight_timeout_minutes; // Initialized by settings.load()
#else
constexpr uint8_t MarlinUI::backlight_timeout_minutes;
#endif
constexpr uint8_t MarlinUI::backlight_timeout_min, MarlinUI::backlight_timeout_max;
millis_t MarlinUI::backlight_off_ms = 0;
void MarlinUI::refresh_backlight_timeout() {
@ -203,12 +207,16 @@ constexpr uint8_t epps = ENCODER_PULSES_PER_STEP;
#elif HAS_DISPLAY_SLEEP
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
uint8_t MarlinUI::sleep_timeout_minutes; // Initialized by settings.load()
#else
constexpr uint8_t MarlinUI::sleep_timeout_minutes;
#endif
constexpr uint8_t MarlinUI::sleep_timeout_min, MarlinUI::sleep_timeout_max;
uint8_t MarlinUI::sleep_timeout_minutes; // Initialized by settings.load()
millis_t MarlinUI::screen_timeout_millis = 0;
millis_t MarlinUI::screen_timeout_ms = 0;
void MarlinUI::refresh_screen_timeout() {
screen_timeout_millis = sleep_timeout_minutes ? millis() + sleep_timeout_minutes * 60UL * 1000UL : 0;
screen_timeout_ms = sleep_timeout_minutes ? millis() + sleep_timeout_minutes * 60UL * 1000UL : 0;
sleep_display(false);
}
@ -1092,7 +1100,7 @@ void MarlinUI::init() {
if (encoderPastThreshold || lcd_clicked) {
reset_status_timeout(ms);
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if HAS_BACKLIGHT_TIMEOUT
refresh_backlight_timeout();
#elif HAS_DISPLAY_SLEEP
refresh_screen_timeout();
@ -1202,8 +1210,7 @@ void MarlinUI::init() {
return_to_status();
#endif
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if HAS_BACKLIGHT_TIMEOUT
if (backlight_off_ms && ELAPSED(ms, backlight_off_ms)) {
#ifdef NEOPIXEL_BKGD_INDEX_FIRST
neo.set_background_off();
@ -1214,7 +1221,7 @@ void MarlinUI::init() {
backlight_off_ms = 0;
}
#elif HAS_DISPLAY_SLEEP
if (screen_timeout_millis && ELAPSED(ms, screen_timeout_millis))
if (screen_timeout_ms && ELAPSED(ms, screen_timeout_ms))
sleep_display();
#endif

View file

@ -272,17 +272,25 @@ public:
FORCE_INLINE static void refresh_brightness() { set_brightness(brightness); }
#endif
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if HAS_BACKLIGHT_TIMEOUT
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
static uint8_t backlight_timeout_minutes;
#else
static constexpr uint8_t backlight_timeout_minutes = LCD_BACKLIGHT_TIMEOUT_MINS;
#endif
static constexpr uint8_t backlight_timeout_min = 0;
static constexpr uint8_t backlight_timeout_max = 99;
static uint8_t backlight_timeout_minutes;
static millis_t backlight_off_ms;
static void refresh_backlight_timeout();
#elif HAS_DISPLAY_SLEEP
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
static uint8_t sleep_timeout_minutes;
#else
static constexpr uint8_t sleep_timeout_minutes = DISPLAY_SLEEP_MINUTES;
#endif
static constexpr uint8_t sleep_timeout_min = 0;
static constexpr uint8_t sleep_timeout_max = 99;
static uint8_t sleep_timeout_minutes;
static millis_t screen_timeout_millis;
static millis_t screen_timeout_ms;
static void refresh_screen_timeout();
static void sleep_display(const bool sleep=true);
#endif

View file

@ -618,11 +618,13 @@ void menu_configuration() {
//
// Set display backlight / sleep timeout
//
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
#if HAS_BACKLIGHT_TIMEOUT
EDIT_ITEM(uint8, MSG_SCREEN_TIMEOUT, &ui.backlight_timeout_minutes, ui.backlight_timeout_min, ui.backlight_timeout_max, ui.refresh_backlight_timeout);
#elif HAS_DISPLAY_SLEEP
EDIT_ITEM(uint8, MSG_SCREEN_TIMEOUT, &ui.sleep_timeout_minutes, ui.sleep_timeout_min, ui.sleep_timeout_max, ui.refresh_screen_timeout);
#endif
#endif
#if ENABLED(FWRETRACT)
SUBMENU(MSG_RETRACT, menu_config_retract);

View file

@ -308,7 +308,7 @@ bool Touch::get_point(int16_t * const x, int16_t * const y) {
next_touch_ms = millis() + 100;
safe_delay(20);
}
next_sleep_ms = millis() + SEC_TO_MS(ui.sleep_timeout_minutes * 60);
next_sleep_ms = millis() + MIN_TO_MS(ui.sleep_timeout_minutes);
}
#endif // HAS_TOUCH_SLEEP

View file

@ -147,7 +147,7 @@ uint8_t TouchButtons::read_buttons() {
WRITE(TFT_BACKLIGHT_PIN, HIGH);
#endif
}
next_sleep_ms = millis() + SEC_TO_MS(ui.sleep_timeout_minutes * 60);
next_sleep_ms = millis() + MIN_TO_MS(ui.sleep_timeout_minutes);
}
#endif // HAS_TOUCH_SLEEP

View file

@ -431,11 +431,13 @@ typedef struct SettingsDataStruct {
//
// Display Sleep
//
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
#if HAS_BACKLIGHT_TIMEOUT
uint8_t backlight_timeout_minutes; // M255 S
#elif HAS_DISPLAY_SLEEP
uint8_t sleep_timeout_minutes; // M255 S
#endif
#endif
//
// Controller fan settings
@ -704,12 +706,8 @@ void MarlinSettings::postprocess() {
// Moved as last update due to interference with Neopixel init
TERN_(HAS_LCD_CONTRAST, ui.refresh_contrast());
TERN_(HAS_LCD_BRIGHTNESS, ui.refresh_brightness());
#if LCD_BACKLIGHT_TIMEOUT_MINS
ui.refresh_backlight_timeout();
#elif HAS_DISPLAY_SLEEP
ui.refresh_screen_timeout();
#endif
TERN_(HAS_BACKLIGHT_TIMEOUT, ui.refresh_backlight_timeout());
TERN_(HAS_DISPLAY_SLEEP, ui.refresh_screen_timeout());
}
#if ALL(PRINTCOUNTER, EEPROM_SETTINGS)
@ -1249,11 +1247,13 @@ void MarlinSettings::postprocess() {
//
// LCD Backlight / Sleep Timeout
//
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
#if HAS_BACKLIGHT_TIMEOUT
EEPROM_WRITE(ui.backlight_timeout_minutes);
#elif HAS_DISPLAY_SLEEP
EEPROM_WRITE(ui.sleep_timeout_minutes);
#endif
#endif
//
// Controller Fan
@ -2294,11 +2294,13 @@ void MarlinSettings::postprocess() {
//
// LCD Backlight / Sleep Timeout
//
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
#if HAS_BACKLIGHT_TIMEOUT
EEPROM_READ(ui.backlight_timeout_minutes);
#elif HAS_DISPLAY_SLEEP
EEPROM_READ(ui.sleep_timeout_minutes);
#endif
#endif
//
// Controller Fan
@ -3451,11 +3453,13 @@ void MarlinSettings::reset() {
//
// LCD Backlight / Sleep Timeout
//
#if LCD_BACKLIGHT_TIMEOUT_MINS
#if ENABLED(EDITABLE_DISPLAY_TIMEOUT)
#if HAS_BACKLIGHT_TIMEOUT
ui.backlight_timeout_minutes = LCD_BACKLIGHT_TIMEOUT_MINS;
#elif HAS_DISPLAY_SLEEP
ui.sleep_timeout_minutes = TERN(TOUCH_SCREEN, TOUCH_IDLE_SLEEP_MINS, DISPLAY_SLEEP_MINUTES);
#endif
#endif
//
// Controller Fan
@ -3827,7 +3831,7 @@ void MarlinSettings::reset() {
//
// Display Sleep
//
TERN_(HAS_GCODE_M255, gcode.M255_report(forReplay));
TERN_(EDITABLE_DISPLAY_TIMEOUT, gcode.M255_report(forReplay));
//
// LCD Brightness

View file

@ -699,7 +699,7 @@ class Temperature {
// Convert the given heater_id_t to idle array index
static IdleIndex idle_index_for_id(const int8_t heater_id) {
TERN_(HAS_HEATED_BED, if (heater_id == H_BED) return IDLE_INDEX_BED);
OPTCODE(HAS_HEATED_BED, if (heater_id == H_BED) return IDLE_INDEX_BED)
return (IdleIndex)_MAX(heater_id, 0);
}
@ -1052,7 +1052,7 @@ class Temperature {
}
// Start watching the Bed to make sure it's really heating up
static void start_watching_bed() { TERN_(WATCH_BED, watch_bed.restart(degBed(), degTargetBed())); }
static void start_watching_bed() { OPTCODE(WATCH_BED, watch_bed.restart(degBed(), degTargetBed())) }
static void setTargetBed(const celsius_t celsius) {
#if PREHEAT_TIME_BED_MS > 0
@ -1108,7 +1108,7 @@ class Temperature {
start_watching_chamber();
}
// Start watching the Chamber to make sure it's really heating up
static void start_watching_chamber() { TERN_(WATCH_CHAMBER, watch_chamber.restart(degChamber(), degTargetChamber())); }
static void start_watching_chamber() { OPTCODE(WATCH_CHAMBER, watch_chamber.restart(degChamber(), degTargetChamber())) }
#endif
#if HAS_TEMP_COOLER
@ -1158,7 +1158,7 @@ class Temperature {
start_watching_cooler();
}
// Start watching the Cooler to make sure it's really cooling down
static void start_watching_cooler() { TERN_(WATCH_COOLER, watch_cooler.restart(degCooler(), degTargetCooler())); }
static void start_watching_cooler() { OPTCODE(WATCH_COOLER, watch_cooler.restart(degCooler(), degTargetCooler())) }
#endif
/**
@ -1391,9 +1391,9 @@ class Temperature {
// Convert the given heater_id_t to runaway state array index
static RunawayIndex runaway_index_for_id(const int8_t heater_id) {
TERN_(THERMAL_PROTECTION_CHAMBER, if (heater_id == H_CHAMBER) return RUNAWAY_IND_CHAMBER);
TERN_(THERMAL_PROTECTION_COOLER, if (heater_id == H_COOLER) return RUNAWAY_IND_COOLER);
TERN_(THERMAL_PROTECTION_BED, if (heater_id == H_BED) return RUNAWAY_IND_BED);
OPTCODE(THERMAL_PROTECTION_CHAMBER, if (heater_id == H_CHAMBER) return RUNAWAY_IND_CHAMBER)
OPTCODE(THERMAL_PROTECTION_COOLER, if (heater_id == H_COOLER) return RUNAWAY_IND_COOLER)
OPTCODE(THERMAL_PROTECTION_BED, if (heater_id == H_BED) return RUNAWAY_IND_BED)
return (RunawayIndex)_MAX(heater_id, 0);
}

View file

@ -330,7 +330,7 @@ SET_PROGRESS_MANUALLY = build_src_filter=+<src/gcode/lcd/M73.cp
HAS_STATUS_MESSAGE = build_src_filter=+<src/gcode/lcd/M117.cpp>
HAS_PREHEAT = build_src_filter=+<src/gcode/lcd/M145.cpp>
HAS_LCD_CONTRAST = build_src_filter=+<src/gcode/lcd/M250.cpp>
HAS_GCODE_M255 = build_src_filter=+<src/gcode/lcd/M255.cpp>
EDITABLE_DISPLAY_TIMEOUT = build_src_filter=+<src/gcode/lcd/M255.cpp>
HAS_LCD_BRIGHTNESS = build_src_filter=+<src/gcode/lcd/M256.cpp>
HAS_SOUND = build_src_filter=+<src/gcode/lcd/M300.cpp>
HAS_MULTI_LANGUAGE = build_src_filter=+<src/gcode/lcd/M414.cpp>