From 32e6767b5acfc57ced0a58c269ff5ae1d6f5cfd2 Mon Sep 17 00:00:00 2001
From: Scott Lahteine <thinkyhead@users.noreply.github.com>
Date: Mon, 4 Apr 2022 15:57:03 -0500
Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20DOGM=20Display=20Sleep=20(#23992)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: borland1 <barryorlando@hotmail.com>
---
 Marlin/Configuration_adv.h                 | 11 ++++
 Marlin/src/core/language.h                 |  1 +
 Marlin/src/gcode/gcode.h                   |  6 +++
 Marlin/src/gcode/lcd/M255.cpp              | 58 ++++++++++++++++++++++
 Marlin/src/inc/Conditionals_adv.h          |  6 +++
 Marlin/src/inc/SanityCheck.h               | 11 ++++
 Marlin/src/lcd/dogm/marlinui_DOGM.cpp      |  5 ++
 Marlin/src/lcd/language/language_en.h      |  1 +
 Marlin/src/lcd/marlinui.cpp                | 14 ++++++
 Marlin/src/lcd/marlinui.h                  |  8 +++
 Marlin/src/lcd/menu/menu_configuration.cpp |  2 +
 Marlin/src/module/settings.cpp             | 17 ++++++-
 ini/features.ini                           |  3 +-
 13 files changed, 141 insertions(+), 2 deletions(-)
 create mode 100644 Marlin/src/gcode/lcd/M255.cpp

diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index 2aa30143486..3ae6c1b3134 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -1696,6 +1696,17 @@
   // Western only. Not available for Cyrillic, Kana, Turkish, Greek, or Chinese.
   //#define USE_SMALL_INFOFONT
 
+  /**
+   * Graphical Display Sleep
+   *
+   * The U8G library provides sleep / wake functions for SH1106, SSD1306,
+   * SSD1309, and some other DOGM displays.
+   * Enable this option to save energy and prevent OLED pixel burn-in.
+   * Adds the menu item Configuration > LCD Timeout (m) to set a wait period
+   * from 0 (disabled) to 99 minutes.
+   */
+  //#define DISPLAY_SLEEP_MINUTES 2  // (minutes) Timeout before turning off the screen
+
   /**
    * ST7920-based LCDs can emulate a 16 x 4 character display using
    * the ST7920 character-generator for very fast screen updates.
diff --git a/Marlin/src/core/language.h b/Marlin/src/core/language.h
index 89c0babc256..a7710333c18 100644
--- a/Marlin/src/core/language.h
+++ b/Marlin/src/core/language.h
@@ -303,6 +303,7 @@
 #define STR_MATERIAL_HEATUP                 "Material heatup parameters"
 #define STR_LCD_CONTRAST                    "LCD Contrast"
 #define STR_LCD_BRIGHTNESS                  "LCD Brightness"
+#define STR_DISPLAY_SLEEP                   "Display Sleep"
 #define STR_UI_LANGUAGE                     "UI Language"
 #define STR_Z_PROBE_OFFSET                  "Z-Probe Offset"
 #define STR_TEMPERATURE_UNITS               "Temperature Units"
diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h
index a4ce273e51f..215d0d4f9b9 100644
--- a/Marlin/src/gcode/gcode.h
+++ b/Marlin/src/gcode/gcode.h
@@ -202,6 +202,7 @@
  * M226 - Wait until a pin is in a given state: "M226 P<pin> S<state>" (Requires DIRECT_PIN_CONTROL)
  * M240 - Trigger a camera to take a photograph. (Requires PHOTO_GCODE)
  * M250 - Set LCD contrast: "M250 C<contrast>" (0-63). (Requires LCD support)
+ * M255 - Set LCD sleep time: "M255 S<minutes>" (0-99). (Requires an LCD with brightness or sleep/wake)
  * M256 - Set LCD brightness: "M256 B<brightness>" (0-255). (Requires an LCD with brightness control)
  * M260 - i2c Send Data (Requires EXPERIMENTAL_I2CBUS)
  * M261 - i2c Request Data (Requires EXPERIMENTAL_I2CBUS)
@@ -879,6 +880,11 @@ private:
     static void M250_report(const bool forReplay=true);
   #endif
 
+  #if HAS_DISPLAY_SLEEP
+    static void M255();
+    static void M255_report(const bool forReplay=true);
+  #endif
+
   #if HAS_LCD_BRIGHTNESS
     static void M256();
     static void M256_report(const bool forReplay=true);
diff --git a/Marlin/src/gcode/lcd/M255.cpp b/Marlin/src/gcode/lcd/M255.cpp
new file mode 100644
index 00000000000..cfdf27b8a1f
--- /dev/null
+++ b/Marlin/src/gcode/lcd/M255.cpp
@@ -0,0 +1,58 @@
+/**
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ */
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_GCODE_M255
+
+#include "../gcode.h"
+#include "../../lcd/marlinui.h"
+
+/**
+ * M255: Set the LCD sleep timeout (in minutes)
+ *  S<minutes> - Period of inactivity required for display / backlight sleep
+ */
+void GcodeSuite::M255() {
+  if (parser.seenval('S')) {
+    #if HAS_DISPLAY_SLEEP
+      const int m = parser.value_int();
+      ui.sleep_timeout_minutes = constrain(m, SLEEP_TIMEOUT_MIN, SLEEP_TIMEOUT_MAX);
+    #else
+      const int s = parser.value_int() * 60;
+      ui.lcd_backlight_timeout = constrain(s, LCD_BKL_TIMEOUT_MIN, LCD_BKL_TIMEOUT_MAX);
+    #endif
+  }
+  else
+    M255_report();
+}
+
+void GcodeSuite::M255_report(const bool forReplay/*=true*/) {
+  report_heading_etc(forReplay, F(STR_DISPLAY_SLEEP));
+  SERIAL_ECHOLNPGM("  M255 S",
+    #if HAS_DISPLAY_SLEEP
+      ui.sleep_timeout_minutes, " ; (minutes)"
+    #else
+      ui.lcd_backlight_timeout, " ; (seconds)"
+    #endif
+  );
+}
+
+#endif // HAS_GCODE_M255
diff --git a/Marlin/src/inc/Conditionals_adv.h b/Marlin/src/inc/Conditionals_adv.h
index 9d7f790db7f..be110e21899 100644
--- a/Marlin/src/inc/Conditionals_adv.h
+++ b/Marlin/src/inc/Conditionals_adv.h
@@ -628,6 +628,12 @@
 #if ALL(HAS_RESUME_CONTINUE, PRINTER_EVENT_LEDS, SDSUPPORT)
   #define HAS_LEDS_OFF_FLAG 1
 #endif
+#ifdef DISPLAY_SLEEP_MINUTES
+  #define HAS_DISPLAY_SLEEP 1
+#endif
+#if HAS_DISPLAY_SLEEP || LCD_BACKLIGHT_TIMEOUT
+  #define HAS_GCODE_M255 1
+#endif
 
 #if EITHER(DIGIPOT_MCP4018, DIGIPOT_MCP4451)
   #define HAS_MOTOR_CURRENT_I2C 1
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index f24291b9628..c5c606a0b4a 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -2991,6 +2991,17 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
   #endif
 #endif
 
+/**
+ * Display Sleep is not supported by these common displays
+ */
+#if HAS_DISPLAY_SLEEP
+  #if ANY(IS_U8GLIB_LM6059_AF, IS_U8GLIB_ST7565_64128, REPRAPWORLD_GRAPHICAL_LCD, FYSETC_MINI, ENDER2_STOCKDISPLAY, MINIPANEL)
+    #error "DISPLAY_SLEEP_MINUTES is not supported by your display."
+  #elif !WITHIN(DISPLAY_SLEEP_MINUTES, 0, 255)
+    #error "DISPLAY_SLEEP_MINUTES must be between 0 and 255."
+  #endif
+#endif
+
 /**
  * Some boards forbid the use of -1 Native USB
  */
diff --git a/Marlin/src/lcd/dogm/marlinui_DOGM.cpp b/Marlin/src/lcd/dogm/marlinui_DOGM.cpp
index fc862e54398..19611f678d3 100644
--- a/Marlin/src/lcd/dogm/marlinui_DOGM.cpp
+++ b/Marlin/src/lcd/dogm/marlinui_DOGM.cpp
@@ -342,6 +342,11 @@ void MarlinUI::draw_kill_screen() {
 
 void MarlinUI::clear_lcd() { } // Automatically cleared by Picture Loop
 
+#if HAS_DISPLAY_SLEEP
+  void MarlinUI::sleep_on()  { u8g.sleepOn(); }
+  void MarlinUI::sleep_off() { u8g.sleepOff(); }
+#endif
+
 #if HAS_LCD_BRIGHTNESS
 
   void MarlinUI::_set_brightness() {
diff --git a/Marlin/src/lcd/language/language_en.h b/Marlin/src/lcd/language/language_en.h
index bb6776d6e54..01470f80073 100644
--- a/Marlin/src/lcd/language/language_en.h
+++ b/Marlin/src/lcd/language/language_en.h
@@ -422,6 +422,7 @@ namespace Language_en {
   LSTR MSG_CONTRAST                       = _UxGT("LCD Contrast");
   LSTR MSG_BRIGHTNESS                     = _UxGT("LCD Brightness");
   LSTR MSG_LCD_TIMEOUT_SEC                = _UxGT("LCD Timeout (s)");
+  LSTR MSG_SCREEN_TIMEOUT                 = _UxGT("LCD Timeout (m)");
   LSTR MSG_BRIGHTNESS_OFF                 = _UxGT("Backlight Off");
   LSTR MSG_STORE_EEPROM                   = _UxGT("Store Settings");
   LSTR MSG_LOAD_EEPROM                    = _UxGT("Load Settings");
diff --git a/Marlin/src/lcd/marlinui.cpp b/Marlin/src/lcd/marlinui.cpp
index 48e3f08fa99..d2af634896d 100644
--- a/Marlin/src/lcd/marlinui.cpp
+++ b/Marlin/src/lcd/marlinui.cpp
@@ -192,6 +192,15 @@ constexpr uint8_t epps = ENCODER_PULSES_PER_STEP;
     WRITE(LCD_BACKLIGHT_PIN, HIGH);
   }
 
+#elif HAS_DISPLAY_SLEEP
+
+  uint8_t MarlinUI::sleep_timeout_minutes; // Initialized by settings.load()
+  millis_t MarlinUI::screen_timeout_millis = 0;
+  void MarlinUI::refresh_screen_timeout() {
+    screen_timeout_millis = sleep_timeout_minutes ? millis() + sleep_timeout_minutes * 60UL * 1000UL : 0;
+    sleep_off();
+  }
+
 #endif
 
 void MarlinUI::init() {
@@ -1061,6 +1070,8 @@ void MarlinUI::init() {
 
           #if LCD_BACKLIGHT_TIMEOUT
             refresh_backlight_timeout();
+          #elif HAS_DISPLAY_SLEEP
+            refresh_screen_timeout();
           #endif
 
           refresh(LCDVIEW_REDRAW_NOW);
@@ -1172,6 +1183,9 @@ void MarlinUI::init() {
           WRITE(LCD_BACKLIGHT_PIN, LOW); // Backlight off
           backlight_off_ms = 0;
         }
+      #elif HAS_DISPLAY_SLEEP
+        if (screen_timeout_millis && ELAPSED(ms, screen_timeout_millis))
+          sleep_on();
       #endif
 
       // Change state of drawing flag between screen updates
diff --git a/Marlin/src/lcd/marlinui.h b/Marlin/src/lcd/marlinui.h
index d9404541d26..4cff3c30f27 100644
--- a/Marlin/src/lcd/marlinui.h
+++ b/Marlin/src/lcd/marlinui.h
@@ -279,6 +279,14 @@ public:
     static uint16_t lcd_backlight_timeout;
     static millis_t backlight_off_ms;
     static void refresh_backlight_timeout();
+  #elif HAS_DISPLAY_SLEEP
+    #define SLEEP_TIMEOUT_MIN 0
+    #define SLEEP_TIMEOUT_MAX 99
+    static uint8_t sleep_timeout_minutes;
+    static millis_t screen_timeout_millis;
+    static void refresh_screen_timeout();
+    static void sleep_on();
+    static void sleep_off();
   #endif
 
   #if HAS_DWIN_E3V2_BASIC
diff --git a/Marlin/src/lcd/menu/menu_configuration.cpp b/Marlin/src/lcd/menu/menu_configuration.cpp
index c66df850244..b8267caef61 100644
--- a/Marlin/src/lcd/menu/menu_configuration.cpp
+++ b/Marlin/src/lcd/menu/menu_configuration.cpp
@@ -547,6 +547,8 @@ void menu_configuration() {
   //
   #if LCD_BACKLIGHT_TIMEOUT && LCD_BKL_TIMEOUT_MIN < LCD_BKL_TIMEOUT_MAX
     EDIT_ITEM(uint16_4, MSG_LCD_TIMEOUT_SEC, &ui.lcd_backlight_timeout, LCD_BKL_TIMEOUT_MIN, LCD_BKL_TIMEOUT_MAX, ui.refresh_backlight_timeout);
+  #elif HAS_DISPLAY_SLEEP
+    EDIT_ITEM(uint8, MSG_SCREEN_TIMEOUT, &ui.sleep_timeout_minutes, SLEEP_TIMEOUT_MIN, SLEEP_TIMEOUT_MAX, ui.refresh_screen_timeout);
   #endif
 
   #if ENABLED(FWRETRACT)
diff --git a/Marlin/src/module/settings.cpp b/Marlin/src/module/settings.cpp
index 55dc0b40710..6526a3c1922 100644
--- a/Marlin/src/module/settings.cpp
+++ b/Marlin/src/module/settings.cpp
@@ -402,7 +402,9 @@ typedef struct SettingsDataStruct {
   // Display Sleep
   //
   #if LCD_BACKLIGHT_TIMEOUT
-    uint16_t lcd_backlight_timeout;                     // (G-code needed)
+    uint16_t lcd_backlight_timeout;                     // M255 S
+  #elif HAS_DISPLAY_SLEEP
+    uint8_t sleep_timeout_minutes;                      // M255 S
   #endif
 
   //
@@ -631,6 +633,8 @@ void MarlinSettings::postprocess() {
 
   #if LCD_BACKLIGHT_TIMEOUT
     ui.refresh_backlight_timeout();
+  #elif HAS_DISPLAY_SLEEP
+    ui.refresh_screen_timeout();
   #endif
 }
 
@@ -1146,6 +1150,8 @@ void MarlinSettings::postprocess() {
     //
     #if LCD_BACKLIGHT_TIMEOUT
       EEPROM_WRITE(ui.lcd_backlight_timeout);
+    #elif HAS_DISPLAY_SLEEP
+      EEPROM_WRITE(ui.sleep_timeout_minutes);
     #endif
 
     //
@@ -2095,6 +2101,8 @@ void MarlinSettings::postprocess() {
       //
       #if LCD_BACKLIGHT_TIMEOUT
         EEPROM_READ(ui.lcd_backlight_timeout);
+      #elif HAS_DISPLAY_SLEEP
+        EEPROM_READ(ui.sleep_timeout_minutes);
       #endif
 
       //
@@ -3172,6 +3180,8 @@ void MarlinSettings::reset() {
   //
   #if LCD_BACKLIGHT_TIMEOUT
     ui.lcd_backlight_timeout = LCD_BACKLIGHT_TIMEOUT;
+  #elif HAS_DISPLAY_SLEEP
+    ui.sleep_timeout_minutes = DISPLAY_SLEEP_MINUTES;
   #endif
 
   //
@@ -3502,6 +3512,11 @@ void MarlinSettings::reset() {
     //
     TERN_(HAS_LCD_CONTRAST, gcode.M250_report(forReplay));
 
+    //
+    // Display Sleep
+    //
+    TERN_(HAS_GCODE_M255, gcode.M255_report(forReplay));
+
     //
     // LCD Brightness
     //
diff --git a/ini/features.ini b/ini/features.ini
index fac9cf9b579..1ee8f3d8955 100644
--- a/ini/features.ini
+++ b/ini/features.ini
@@ -37,7 +37,7 @@ USES_LIQUIDCRYSTAL_I2C                 = marcoschwartz/LiquidCrystal_I2C@1.1.4
 USES_LIQUIDTWI2                        = LiquidTWI2@1.2.7
 HAS_LCDPRINT                           = src_filter=+<src/lcd/lcdprint.cpp>
 HAS_MARLINUI_HD44780                   = src_filter=+<src/lcd/HD44780>
-HAS_MARLINUI_U8GLIB                    = U8glib-HAL@~0.5.0
+HAS_MARLINUI_U8GLIB                    = U8glib-HAL@~0.5.2
                                          src_filter=+<src/lcd/dogm>
 HAS_(FSMC|SPI|LTDC)_TFT                = src_filter=+<src/HAL/STM32/tft> +<src/HAL/STM32F1/tft> +<src/lcd/tft_io>
 HAS_FSMC_TFT                           = src_filter=+<src/HAL/STM32/tft/tft_fsmc.cpp> +<src/HAL/STM32F1/tft/tft_fsmc.cpp>
@@ -202,6 +202,7 @@ HAS_RESUME_CONTINUE                    = src_filter=+<src/gcode/lcd/M0_M1.cpp>
 LCD_SET_PROGRESS_MANUALLY              = src_filter=+<src/gcode/lcd/M73.cpp>
 HAS_STATUS_MESSAGE                     = src_filter=+<src/gcode/lcd/M117.cpp>
 HAS_LCD_CONTRAST                       = src_filter=+<src/gcode/lcd/M250.cpp>
+HAS_GCODE_M255                         = src_filter=+<src/gcode/lcd/M255.cpp>
 HAS_LCD_BRIGHTNESS                     = src_filter=+<src/gcode/lcd/M256.cpp>
 HAS_BUZZER                             = src_filter=+<src/gcode/lcd/M300.cpp>
 TOUCH_SCREEN_CALIBRATION               = src_filter=+<src/gcode/lcd/M995.cpp>