diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index fa1b470ab2d..578bf144fc5 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -2115,6 +2115,15 @@
  */
 //#define EMERGENCY_PARSER
 
+/**
+ * Realtime Reporting
+ * Add support for commands S000 State, P000 Pause, and R000 Resume
+ */
+//#define REALTIME_REPORTING_COMMANDS
+#if ENABLED(REALTIME_REPORTING_COMMANDS)
+  //#define FULL_REPORT_TO_HOST_FEATURE   // Auto-report the machine status like Grbl CNC
+#endif
+
 // Bad Serial-connections can miss a received command by sending an 'ok'
 // Therefore some clients abort after 30 seconds in a timeout.
 // Some other clients start sending commands while receiving a 'wait'.
diff --git a/Marlin/src/feature/e_parser.h b/Marlin/src/feature/e_parser.h
index 659e516787f..c12fd14ad19 100644
--- a/Marlin/src/feature/e_parser.h
+++ b/Marlin/src/feature/e_parser.h
@@ -34,29 +34,33 @@
 // External references
 extern bool wait_for_user, wait_for_heatup;
 
+#if ENABLED(REALTIME_REPORTING_COMMANDS)
+  // From motion.h, which cannot be included here
+  void report_current_position_moving();
+  void quickpause_stepper();
+  void quickresume_stepper();
+#endif
+
 class EmergencyParser {
 
 public:
 
-  // Currently looking for: M108, M112, M410, M876
-  enum State : char {
+  // Currently looking for: M108, M112, M410, M876 S[0-9], S000, P000, R000
+  enum State : uint8_t {
     EP_RESET,
     EP_N,
     EP_M,
     EP_M1,
-    EP_M10,
-    EP_M108,
-    EP_M11,
-    EP_M112,
-    EP_M4,
-    EP_M41,
-    EP_M410,
+    EP_M10, EP_M108,
+    EP_M11, EP_M112,
+    EP_M4, EP_M41, EP_M410,
     #if ENABLED(HOST_PROMPT_SUPPORT)
-      EP_M8,
-      EP_M87,
-      EP_M876,
-      EP_M876S,
-      EP_M876SN,
+      EP_M8, EP_M87, EP_M876, EP_M876S, EP_M876SN,
+    #endif
+    #if ENABLED(REALTIME_REPORTING_COMMANDS)
+      EP_S, EP_S0, EP_S00, EP_GRBL_STATUS,
+      EP_R, EP_R0, EP_R00, EP_GRBL_RESUME,
+      EP_P, EP_P0, EP_P00, EP_GRBL_PAUSE,
     #endif
     EP_IGNORE // to '\n'
   };
@@ -71,7 +75,6 @@ public:
   EmergencyParser() { enable(); }
 
   FORCE_INLINE static void enable()  { enabled = true; }
-
   FORCE_INLINE static void disable() { enabled = false; }
 
   FORCE_INLINE static void update(State &state, const uint8_t c) {
@@ -79,21 +82,45 @@ public:
       case EP_RESET:
         switch (c) {
           case ' ': case '\n': case '\r': break;
-          case 'N': state = EP_N;      break;
-          case 'M': state = EP_M;      break;
-          default: state  = EP_IGNORE;
+          case 'N': state = EP_N; break;
+          case 'M': state = EP_M; break;
+          #if ENABLED(REALTIME_REPORTING_COMMANDS)
+            case 'S': state = EP_S; break;
+            case 'P': state = EP_P; break;
+            case 'R': state = EP_R; break;
+          #endif
+          default: state = EP_IGNORE;
         }
         break;
 
       case EP_N:
         switch (c) {
           case '0' ... '9':
-          case '-': case ' ':   break;
-          case 'M': state = EP_M;      break;
-          default:  state = EP_IGNORE;
+          case '-': case ' ':     break;
+          case 'M': state = EP_M; break;
+          #if ENABLED(REALTIME_REPORTING_COMMANDS)
+            case 'S': state = EP_S; break;
+            case 'P': state = EP_P; break;
+            case 'R': state = EP_R; break;
+          #endif
+          default: state = EP_IGNORE;
         }
         break;
 
+      #if ENABLED(REALTIME_REPORTING_COMMANDS)
+        case EP_S:   state = (c == '0') ? EP_S0          : EP_IGNORE; break;
+        case EP_S0:  state = (c == '0') ? EP_S00         : EP_IGNORE; break;
+        case EP_S00: state = (c == '0') ? EP_GRBL_STATUS : EP_IGNORE; break;
+
+        case EP_R:   state = (c == '0') ? EP_R0          : EP_IGNORE; break;
+        case EP_R0:  state = (c == '0') ? EP_R00         : EP_IGNORE; break;
+        case EP_R00: state = (c == '0') ? EP_GRBL_RESUME : EP_IGNORE; break;
+
+        case EP_P:   state = (c == '0') ? EP_P0          : EP_IGNORE; break;
+        case EP_P0:  state = (c == '0') ? EP_P00         : EP_IGNORE; break;
+        case EP_P00: state = (c == '0') ? EP_GRBL_PAUSE  : EP_IGNORE; break;
+      #endif
+
       case EP_M:
         switch (c) {
           case ' ': break;
@@ -114,48 +141,34 @@ public:
         }
         break;
 
-      case EP_M10:
-        state = (c == '8') ? EP_M108 : EP_IGNORE;
-        break;
-
-      case EP_M11:
-        state = (c == '2') ? EP_M112 : EP_IGNORE;
-        break;
-
-      case EP_M4:
-        state = (c == '1') ? EP_M41 : EP_IGNORE;
-        break;
-
-      case EP_M41:
-        state = (c == '0') ? EP_M410 : EP_IGNORE;
-        break;
+      case EP_M10: state = (c == '8') ? EP_M108 : EP_IGNORE; break;
+      case EP_M11: state = (c == '2') ? EP_M112 : EP_IGNORE; break;
+      case EP_M4:  state = (c == '1') ? EP_M41  : EP_IGNORE; break;
+      case EP_M41: state = (c == '0') ? EP_M410 : EP_IGNORE; break;
 
       #if ENABLED(HOST_PROMPT_SUPPORT)
-      case EP_M8:
-        state = (c == '7') ? EP_M87 : EP_IGNORE;
-        break;
 
-      case EP_M87:
-        state = (c == '6') ? EP_M876 : EP_IGNORE;
-        break;
+        case EP_M8:  state = (c == '7') ? EP_M87  : EP_IGNORE; break;
+        case EP_M87: state = (c == '6') ? EP_M876 : EP_IGNORE; break;
 
-      case EP_M876:
-        switch (c) {
-          case ' ': break;
-          case 'S': state = EP_M876S; break;
-          default:  state = EP_IGNORE; break;
-        }
-        break;
+        case EP_M876:
+          switch (c) {
+            case ' ': break;
+            case 'S': state = EP_M876S; break;
+            default: state = EP_IGNORE; break;
+          }
+          break;
+
+        case EP_M876S:
+          switch (c) {
+            case ' ': break;
+            case '0' ... '9':
+              state = EP_M876SN;
+              M876_reason = uint8_t(c - '0');
+              break;
+          }
+          break;
 
-      case EP_M876S:
-        switch (c) {
-          case ' ': break;
-          case '0' ... '9':
-            state = EP_M876SN;
-            M876_reason = (uint8_t)(c - '0');
-            break;
-        }
-        break;
       #endif
 
       case EP_IGNORE:
@@ -171,6 +184,11 @@ public:
             #if ENABLED(HOST_PROMPT_SUPPORT)
               case EP_M876SN: host_response_handler(M876_reason); break;
             #endif
+            #if ENABLED(REALTIME_REPORTING_COMMANDS)
+              case EP_GRBL_STATUS: report_current_position_moving(); break;
+              case EP_GRBL_PAUSE: quickpause_stepper(); break;
+              case EP_GRBL_RESUME: quickresume_stepper(); break;
+            #endif
             default: break;
           }
           state = EP_RESET;
diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp
index 423857dbb05..a10b2b89b1c 100644
--- a/Marlin/src/gcode/bedlevel/abl/G29.cpp
+++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp
@@ -217,9 +217,10 @@ public:
  *     There's no extra effect if you have a fixed Z probe.
  */
 G29_TYPE GcodeSuite::G29() {
-
   TERN_(PROBE_MANUALLY, static) G29_State abl;
 
+  TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_PROBE));
+
   reset_stepper_timeout();
 
   const bool seenQ = EITHER(DEBUG_LEVELING_FEATURE, PROBE_MANUALLY) && parser.seen('Q');
@@ -897,6 +898,8 @@ G29_TYPE GcodeSuite::G29() {
 
   report_current_position();
 
+  TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE));
+
   G29_RETURN(ISNAN(abl.measured_z));
 }
 
diff --git a/Marlin/src/gcode/bedlevel/mbl/G29.cpp b/Marlin/src/gcode/bedlevel/mbl/G29.cpp
index 1ec514c3ec3..2da584fd49f 100644
--- a/Marlin/src/gcode/bedlevel/mbl/G29.cpp
+++ b/Marlin/src/gcode/bedlevel/mbl/G29.cpp
@@ -60,6 +60,8 @@ inline void echo_not_entered(const char c) { SERIAL_CHAR(c); SERIAL_ECHOLNPGM("
  */
 void GcodeSuite::G29() {
 
+  TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_PROBE));
+
   static int mbl_probe_index = -1;
 
   MeshLevelingState state = (MeshLevelingState)parser.byteval('S', (int8_t)MeshReport);
@@ -187,6 +189,8 @@ void GcodeSuite::G29() {
   }
 
   report_current_position();
+
+  TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE));
 }
 
 #endif // MESH_BED_LEVELING
diff --git a/Marlin/src/gcode/bedlevel/ubl/G29.cpp b/Marlin/src/gcode/bedlevel/ubl/G29.cpp
index 2ef3ab4ceca..932503d72b9 100644
--- a/Marlin/src/gcode/bedlevel/ubl/G29.cpp
+++ b/Marlin/src/gcode/bedlevel/ubl/G29.cpp
@@ -31,6 +31,17 @@
 #include "../../gcode.h"
 #include "../../../feature/bedlevel/bedlevel.h"
 
-void GcodeSuite::G29() { ubl.G29(); }
+#if ENABLED(FULL_REPORT_TO_HOST_FEATURE)
+  #include "../../../module/motion.h"
+#endif
+
+void GcodeSuite::G29() {
+
+  TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_PROBE));
+
+  ubl.G29();
+
+  TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE));
+}
 
 #endif // AUTO_BED_LEVELING_UBL
diff --git a/Marlin/src/gcode/calibrate/G28.cpp b/Marlin/src/gcode/calibrate/G28.cpp
index 73bfc3bdc6c..10e094cba7c 100644
--- a/Marlin/src/gcode/calibrate/G28.cpp
+++ b/Marlin/src/gcode/calibrate/G28.cpp
@@ -211,6 +211,8 @@ void GcodeSuite::G28() {
 
   TERN_(LASER_MOVE_G28_OFF, cutter.set_inline_enabled(false));  // turn off laser
 
+  TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_HOMING));
+
   #if ENABLED(DUAL_X_CARRIAGE)
     bool IDEX_saved_duplication_state = extruder_duplication_enabled;
     DualXMode IDEX_saved_mode = dual_x_carriage_mode;
@@ -479,6 +481,8 @@ void GcodeSuite::G28() {
   if (ENABLED(NANODLP_Z_SYNC) && (doZ || ENABLED(NANODLP_ALL_AXIS)))
     SERIAL_ECHOLNPGM(STR_Z_MOVE_COMP);
 
+  TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE));
+
   #if HAS_L64XX
     // Set L6470 absolute position registers to counts
     // constexpr *might* move this to PROGMEM.
diff --git a/Marlin/src/gcode/calibrate/G33.cpp b/Marlin/src/gcode/calibrate/G33.cpp
index 0bcab206af4..db1d456d704 100644
--- a/Marlin/src/gcode/calibrate/G33.cpp
+++ b/Marlin/src/gcode/calibrate/G33.cpp
@@ -387,6 +387,8 @@ static float auto_tune_a() {
  */
 void GcodeSuite::G33() {
 
+  TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_PROBE));
+
   const int8_t probe_points = parser.intval('P', DELTA_CALIBRATION_DEFAULT_POINTS);
   if (!WITHIN(probe_points, 0, 10)) {
     SERIAL_ECHOLNPGM("?(P)oints implausible (0-10).");
@@ -645,6 +647,8 @@ void GcodeSuite::G33() {
   while (((zero_std_dev < test_precision && iterations < 31) || iterations <= force_iterations) && zero_std_dev > calibration_precision);
 
   ac_cleanup(TERN_(HAS_MULTI_HOTEND, old_tool_index));
+
+  TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE));
 }
 
 #endif // DELTA_AUTO_CALIBRATION
diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp
index 574e93aeccc..efea0876027 100644
--- a/Marlin/src/gcode/gcode.cpp
+++ b/Marlin/src/gcode/gcode.cpp
@@ -289,8 +289,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
     }
   #endif
 
-  // Handle a known G, M, or T
+  // Handle a known command or reply "unknown command"
+
   switch (parser.command_letter) {
+
     case 'G': switch (parser.codenum) {
 
       case 0: case 1:                                             // G0: Fast Move, G1: Linear Move
@@ -995,6 +997,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
       case 'D': D(parser.codenum); break;                         // Dn: Debug codes
     #endif
 
+    #if ENABLED(REALTIME_REPORTING_COMMANDS)
+      case 'S': case 'P': case 'R': break;                        // Invalid S, P, R commands already filtered
+    #endif
+
     default:
       #if ENABLED(WIFI_CUSTOM_COMMAND)
         if (wifi_custom_command(parser.command_ptr)) break;
@@ -1087,12 +1093,15 @@ void GcodeSuite::process_subcommands_now(char * gcode) {
         case IN_HANDLER:
         case IN_PROCESS:
           SERIAL_ECHO_MSG(STR_BUSY_PROCESSING);
+          TERN_(FULL_REPORT_TO_HOST_FEATURE, report_current_position_moving());
           break;
         case PAUSED_FOR_USER:
           SERIAL_ECHO_MSG(STR_BUSY_PAUSED_FOR_USER);
+          TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_HOLD));
           break;
         case PAUSED_FOR_INPUT:
           SERIAL_ECHO_MSG(STR_BUSY_PAUSED_FOR_INPUT);
+          TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_HOLD));
           break;
         default:
           break;
diff --git a/Marlin/src/gcode/host/M114.cpp b/Marlin/src/gcode/host/M114.cpp
index 75356ff66fe..dd62f0ad2e2 100644
--- a/Marlin/src/gcode/host/M114.cpp
+++ b/Marlin/src/gcode/host/M114.cpp
@@ -176,6 +176,8 @@
     const xyze_float_t diff = from_steppers - leveled;
     SERIAL_ECHOPGM("Diff:   ");
     report_xyze(diff);
+
+    TERN_(FULL_REPORT_TO_HOST_FEATURE, report_current_grblstate_moving());
   }
 
 #endif // M114_DETAIL
@@ -211,4 +213,6 @@ void GcodeSuite::M114() {
 
   TERN_(M114_LEGACY, planner.synchronize());
   report_current_position_projected();
+
+  TERN_(FULL_REPORT_TO_HOST_FEATURE, report_current_grblstate_moving());
 }
diff --git a/Marlin/src/gcode/motion/G0_G1.cpp b/Marlin/src/gcode/motion/G0_G1.cpp
index 9ac49bd93cf..64c07d1d890 100644
--- a/Marlin/src/gcode/motion/G0_G1.cpp
+++ b/Marlin/src/gcode/motion/G0_G1.cpp
@@ -54,6 +54,7 @@ void GcodeSuite::G0_G1(TERN_(HAS_FAST_MOVES, const bool fast_move/*=false*/)) {
         | (parser.seen('Z') ? _BV(Z_AXIS) : 0) )
     #endif
   ) {
+    TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_RUNNING));
 
     #ifdef G0_FEEDRATE
       feedRate_t old_feedrate;
@@ -116,6 +117,9 @@ void GcodeSuite::G0_G1(TERN_(HAS_FAST_MOVES, const bool fast_move/*=false*/)) {
         planner.synchronize();
         SERIAL_ECHOLNPGM(STR_Z_MOVE_COMP);
       }
+      TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE));
+    #else
+      TERN_(FULL_REPORT_TO_HOST_FEATURE, report_current_grblstate_moving());
     #endif
   }
 }
diff --git a/Marlin/src/gcode/motion/G2_G3.cpp b/Marlin/src/gcode/motion/G2_G3.cpp
index 61e50247f3f..5a8324362a2 100644
--- a/Marlin/src/gcode/motion/G2_G3.cpp
+++ b/Marlin/src/gcode/motion/G2_G3.cpp
@@ -306,6 +306,8 @@ void plan_arc(
 void GcodeSuite::G2_G3(const bool clockwise) {
   if (MOTION_CONDITIONS) {
 
+    TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_RUNNING));
+
     #if ENABLED(SF_ARC_FIX)
       const bool relative_mode_backup = relative_mode;
       relative_mode = true;
@@ -364,6 +366,8 @@ void GcodeSuite::G2_G3(const bool clockwise) {
     }
     else
       SERIAL_ERROR_MSG(STR_ERR_ARC_ARGS);
+
+    TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE));
   }
 }
 
diff --git a/Marlin/src/gcode/parser.cpp b/Marlin/src/gcode/parser.cpp
index f7812bf3f63..9f0d09563e2 100644
--- a/Marlin/src/gcode/parser.cpp
+++ b/Marlin/src/gcode/parser.cpp
@@ -106,8 +106,10 @@ void GCodeParser::reset() {
 
 #endif
 
-// Populate all fields by parsing a single line of GCode
-// 58 bytes of SRAM are used to speed up seen/value
+/**
+ * Populate the command line state (command_letter, codenum, subcode, and string_arg)
+ * by parsing a single line of GCode. 58 bytes of SRAM are used to speed up seen/value.
+ */
 void GCodeParser::parse(char *p) {
 
   reset(); // No codes to report
@@ -147,10 +149,12 @@ void GCodeParser::parse(char *p) {
     #define SIGNED_CODENUM 1
   #endif
 
-  // Bail if the letter is not G, M, or T
-  // (or a valid parameter for the current motion mode)
+  /**
+   * Screen for good command letters. G, M, and T are always accepted.
+   * With Motion Modes enabled any axis letter can come first.
+   * With Realtime Reporting, commands S000, P000, and R000 are allowed.
+   */
   switch (letter) {
-
     case 'G': case 'M': case 'T': TERN_(MARLIN_DEV_MODE, case 'D':)
       // Skip spaces to get the numeric part
       while (*p == ' ') p++;
@@ -227,6 +231,15 @@ void GCodeParser::parse(char *p) {
       break;
     #endif // GCODE_MOTION_MODES
 
+    #if ENABLED(REALTIME_REPORTING_COMMANDS)
+      case 'S': case 'P': case 'R': {
+        codenum = 0;                  // The only valid codenum is 0
+        uint8_t digits = 0;
+        while (*p++ == '0') digits++; // Count up '0' characters
+        command_letter = (digits == 3) ? letter : '?'; // Three '0' digits is a good command
+      } return;                       // No parameters, so return
+    #endif
+
     default: return;
   }
 
diff --git a/Marlin/src/module/motion.cpp b/Marlin/src/module/motion.cpp
index 6058db37b34..584e894ae6b 100644
--- a/Marlin/src/module/motion.cpp
+++ b/Marlin/src/module/motion.cpp
@@ -230,6 +230,50 @@ void report_current_position_projected() {
   stepper.report_a_position(planner.position);
 }
 
+#if EITHER(FULL_REPORT_TO_HOST_FEATURE, REALTIME_REPORTING_COMMANDS)
+
+  M_StateEnum M_State_grbl = M_INIT;
+
+  /**
+   * Output the current grbl compatible state to serial while moving
+   */
+  void report_current_grblstate_moving() { SERIAL_ECHOLNPAIR("S_XYZ:", int(M_State_grbl)); }
+
+  /**
+   * Output the current position (processed) to serial while moving
+   */
+  void report_current_position_moving() {
+
+    get_cartesian_from_steppers();
+    const xyz_pos_t lpos = cartes.asLogical();
+    SERIAL_ECHOPAIR("X:", lpos.x, " Y:", lpos.y, " Z:", lpos.z, " E:", current_position.e);
+
+    stepper.report_positions();
+    #if IS_SCARA
+      scara_report_positions();
+    #endif
+
+    report_current_grblstate_moving();
+  }
+
+  /**
+   * Set a Grbl-compatible state from the current marlin_state
+   */
+  M_StateEnum grbl_state_for_marlin_state() {
+    switch (marlin_state) {
+      case MF_INITIALIZING: return M_INIT;
+      case MF_SD_COMPLETE:  return M_ALARM;
+      case MF_WAITING:      return M_IDLE;
+      case MF_STOPPED:      return M_END;
+      case MF_RUNNING:      return M_RUNNING;
+      case MF_PAUSED:       return M_HOLD;
+      case MF_KILLED:       return M_ERROR;
+      default:              return M_IDLE;
+    }
+  }
+
+#endif
+
 /**
  * Run out the planner buffer and re-sync the current
  * position from the last-updated stepper positions.
@@ -241,6 +285,20 @@ void quickstop_stepper() {
   sync_plan_position();
 }
 
+#if ENABLED(REALTIME_REPORTING_COMMANDS)
+
+  void quickpause_stepper() {
+    planner.quick_pause();
+    //planner.synchronize();
+  }
+
+  void quickresume_stepper() {
+    planner.quick_resume();
+    //planner.synchronize();
+  }
+
+#endif
+
 /**
  * Set the planner/stepper positions directly from current_position with
  * no kinematic translation. Used for homing axes and cartesian/core syncing.
diff --git a/Marlin/src/module/motion.h b/Marlin/src/module/motion.h
index 2cfc8406a58..647b3af52af 100644
--- a/Marlin/src/module/motion.h
+++ b/Marlin/src/module/motion.h
@@ -211,14 +211,49 @@ void report_real_position();
 void report_current_position();
 void report_current_position_projected();
 
+#if EITHER(FULL_REPORT_TO_HOST_FEATURE, REALTIME_REPORTING_COMMANDS)
+  #define HAS_GRBL_STATE 1
+  /**
+   * Machine states for GRBL or TinyG
+   */
+  enum M_StateEnum : uint8_t {
+    M_INIT = 0, //  0 machine is initializing
+    M_RESET,    //  1 machine is ready for use
+    M_ALARM,    //  2 machine is in alarm state (soft shut down)
+    M_IDLE,     //  3 program stop or no more blocks (M0, M1, M60)
+    M_END,      //  4 program end via M2, M30
+    M_RUNNING,  //  5 motion is running
+    M_HOLD,     //  6 motion is holding
+    M_PROBE,    //  7 probe cycle active
+    M_CYCLING,  //  8 machine is running (cycling)
+    M_HOMING,   //  9 machine is homing
+    M_JOGGING,  // 10 machine is jogging
+    M_ERROR     // 11 machine is in hard alarm state (shut down)
+  };
+  extern M_StateEnum M_State_grbl;
+  M_StateEnum grbl_state_for_marlin_state();
+  void report_current_grblstate_moving();
+  void report_current_position_moving();
+
+  #if ENABLED(FULL_REPORT_TO_HOST_FEATURE)
+    inline void set_and_report_grblstate(const M_StateEnum state) {
+      M_State_grbl = state;
+      report_current_grblstate_moving();
+    }
+  #endif
+
+  #if ENABLED(REALTIME_REPORTING_COMMANDS)
+    void quickpause_stepper();
+    void quickresume_stepper();
+  #endif
+#endif
+
 void get_cartesian_from_steppers();
 void set_current_from_steppers_for_axis(const AxisEnum axis);
 
 void quickstop_stepper();
 
 /**
- * sync_plan_position
- *
  * Set the planner/stepper positions directly from current_position with
  * no kinematic translation. Used for homing axes and cartesian/core syncing.
  */
diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp
index b528eb9d3b9..f11f2738676 100644
--- a/Marlin/src/module/planner.cpp
+++ b/Marlin/src/module/planner.cpp
@@ -1650,6 +1650,24 @@ void Planner::quick_stop() {
   stepper.quick_stop();
 }
 
+#if ENABLED(REALTIME_REPORTING_COMMANDS)
+
+  void Planner::quick_pause() {
+    // Suspend until quick_resume is called
+    // Don't empty buffers or queues
+    const bool did_suspend = stepper.suspend();
+    if (did_suspend)
+      TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_HOLD));
+  }
+
+  // Resume if suspended
+  void Planner::quick_resume() {
+    TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(grbl_state_for_marlin_state()));
+    stepper.wake_up();
+  }
+
+#endif
+
 void Planner::endstop_triggered(const AxisEnum axis) {
   // Record stepper position and discard the current block
   stepper.endstop_triggered(axis);
diff --git a/Marlin/src/module/planner.h b/Marlin/src/module/planner.h
index 398339f04e3..30eeb758a47 100644
--- a/Marlin/src/module/planner.h
+++ b/Marlin/src/module/planner.h
@@ -873,6 +873,13 @@ class Planner {
     // a Full Shutdown is required, or when endstops are hit)
     static void quick_stop();
 
+    #if ENABLED(REALTIME_REPORTING_COMMANDS)
+      // Force a quick pause of the machine (e.g., when a pause is required in the middle of move).
+      // NOTE: Hard-stops will lose steps so encoders are highly recommended if using these!
+      static void quick_pause();
+      static void quick_resume();
+    #endif
+
     // Called when an endstop is triggered. Causes the machine to stop inmediately
     static void endstop_triggered(const AxisEnum axis);
 
diff --git a/buildroot/tests/LPC1768 b/buildroot/tests/LPC1768
index eef0857dac8..152a49852a1 100755
--- a/buildroot/tests/LPC1768
+++ b/buildroot/tests/LPC1768
@@ -26,8 +26,8 @@ restore_configs
 opt_set MOTHERBOARD BOARD_MKS_SBASE \
         EXTRUDERS 2 TEMP_SENSOR_1 1 \
         NUM_SERVOS 2 SERVO_DELAY '{ 300, 300 }'
-opt_enable SWITCHING_NOZZLE SWITCHING_NOZZLE_E1_SERVO_NR ULTIMAKERCONTROLLER
-exec_test $1 $2 "MKS SBASE with SWITCHING_NOZZLE" "$3"
+opt_enable SWITCHING_NOZZLE SWITCHING_NOZZLE_E1_SERVO_NR ULTIMAKERCONTROLLER REALTIME_REPORTING_COMMANDS FULL_REPORT_TO_HOST_FEATURE
+exec_test $1 $2 "MKS SBASE with SWITCHING_NOZZLE, Grbl Realtime Report" "$3"
 
 restore_configs
 opt_set MOTHERBOARD BOARD_RAMPS_14_RE_ARM_EEB \