diff --git a/Marlin/Marlin_main.cpp b/Marlin/Marlin_main.cpp
index 5dab1f40e6..0866a08dd5 100644
--- a/Marlin/Marlin_main.cpp
+++ b/Marlin/Marlin_main.cpp
@@ -5285,8 +5285,8 @@ inline void gcode_M121() { endstops.enable_globally(false); }
 
     uint8_t bytes = code_seen('B') ? code_value_byte() : 1;
 
-    if (i2c.addr > 0 && bytes > 0 && bytes <= 32) {
-      i2c.reqbytes(bytes);
+    if (i2c.addr && bytes && bytes <= TWIBUS_BUFFER_SIZE) {
+      i2c.relay(bytes);
     }
     else {
       SERIAL_ERROR_START;
diff --git a/Marlin/twibus.cpp b/Marlin/twibus.cpp
index ab04fa368b..bf17db3c28 100644
--- a/Marlin/twibus.cpp
+++ b/Marlin/twibus.cpp
@@ -86,32 +86,72 @@ void TWIBus::send() {
   this->reset();
 }
 
-void TWIBus::echodata(uint8_t bytes, const char prefix[], uint8_t adr) {
+// static
+void TWIBus::echoprefix(uint8_t bytes, const char prefix[], uint8_t adr) {
   SERIAL_ECHO_START;
   serialprintPGM(prefix);
   SERIAL_ECHOPAIR(": from:", adr);
   SERIAL_ECHOPAIR(" bytes:", bytes);
   SERIAL_ECHOPGM (" data:");
+}
+
+// static
+void TWIBus::echodata(uint8_t bytes, const char prefix[], uint8_t adr) {
+  echoprefix(bytes, prefix, adr);
   while (bytes-- && Wire.available()) SERIAL_CHAR(Wire.read());
   SERIAL_EOL;
 }
 
-void TWIBus::reqbytes(const uint8_t bytes) {
-  if (!this->addr) return;
+void TWIBus::echobuffer(const char prefix[], uint8_t adr) {
+  echoprefix(this->buffer_s, prefix, adr);
+  for (uint8_t i = 0; i < this->buffer_s; i++) SERIAL_CHAR(this->buffer[i]);
+  SERIAL_EOL;
+}
+
+bool TWIBus::request(const uint8_t bytes) {
+  if (!this->addr) return false;
 
   #if ENABLED(DEBUG_TWIBUS)
-    debug(PSTR("reqbytes"), bytes);
+    debug(PSTR("request"), bytes);
   #endif
 
   // requestFrom() is a blocking function
   Wire.requestFrom(this->addr, bytes);
 
-  // Wait until all bytes arrive, or timeout
+  // Wait for all bytes to arrive
   millis_t t = millis() + this->timeout;
-  while (Wire.available() < bytes && PENDING(millis(), t)) { /*nada*/ }
+  while (Wire.available() < bytes)
+    if (ELAPSED(millis(), t)) {
+      #if ENABLED(DEBUG_TWIBUS)
+        SERIAL_ECHO_START;
+        SERIAL_ECHOLNPGM("i2c timeout");
+      #endif
+      return false;
+    }
 
-  // Simply echo the data to the bus
-  this->echodata(bytes, PSTR("i2c-reply"), this->addr);
+  return true;
+}
+
+void TWIBus::relay(const uint8_t bytes) {
+  #if ENABLED(DEBUG_TWIBUS)
+    debug(PSTR("relay"), bytes);
+  #endif
+
+  if (this->request(bytes))
+    echodata(bytes, PSTR("i2c-reply"), this->addr);
+}
+
+uint8_t TWIBus::capture(char *dst, const uint8_t bytes) {
+  this->reset();
+  uint8_t count = 0;
+  while (count < bytes && Wire.available())
+    dst[count++] = Wire.read();
+  return count;
+}
+
+// static
+void TWIBus::flush() {
+  while (Wire.available()) Wire.read();
 }
 
 #if I2C_SLAVE_ADDRESS > 0
@@ -120,7 +160,7 @@ void TWIBus::reqbytes(const uint8_t bytes) {
     #if ENABLED(DEBUG_TWIBUS)
       debug(PSTR("receive"), bytes);
     #endif
-    this->echodata(bytes, PSTR("i2c-receive"), 0);
+    echodata(bytes, PSTR("i2c-receive"), 0);
   }
 
   void TWIBus::reply(char str[]/*=NULL*/) {
@@ -142,6 +182,7 @@ void TWIBus::reqbytes(const uint8_t bytes) {
 
 #if ENABLED(DEBUG_TWIBUS)
 
+  // static
   void TWIBus::prefix(const char func[]) {
     SERIAL_ECHOPGM("TWIBus::");
     serialprintPGM(func);
diff --git a/Marlin/twibus.h b/Marlin/twibus.h
index 32928a8e15..d578a6d1fb 100644
--- a/Marlin/twibus.h
+++ b/Marlin/twibus.h
@@ -33,6 +33,8 @@
 typedef void (*twiReceiveFunc_t)(int bytes);
 typedef void (*twiRequestFunc_t)();
 
+#define TWIBUS_BUFFER_SIZE 32
+
 /**
  * TWIBUS class
  *
@@ -70,7 +72,7 @@ class TWIBus {
      * @brief Internal buffer
      * @details A fixed buffer. TWI commands can be no longer than this.
      */
-    char buffer[32];
+    char buffer[TWIBUS_BUFFER_SIZE];
 
 
   public:
@@ -134,6 +136,14 @@ class TWIBus {
      */
     void address(const uint8_t adr);
 
+    /**
+     * @brief Prefix for echo to serial
+     * @details Echo a label, length, address, and "data:"
+     *
+     * @param bytes the number of bytes to request
+     */
+    static void echoprefix(uint8_t bytes, const char prefix[], uint8_t adr);
+
     /**
      * @brief Echo data on the bus to serial
      * @details Echo some number of bytes from the bus
@@ -144,14 +154,48 @@ class TWIBus {
     static void echodata(uint8_t bytes, const char prefix[], uint8_t adr);
 
     /**
-     * @brief Request data from the slave device
-     * @details Request a number of bytes from a slave device.
-     *          This implementation simply sends the data to serial
-     *          in a parser-friendly format.
+     * @brief Echo data in the buffer to serial
+     * @details Echo the entire buffer to serial
+     *          to serial in a parser-friendly format.
      *
      * @param bytes the number of bytes to request
      */
-    void reqbytes(const uint8_t bytes);
+    void echobuffer(const char prefix[], uint8_t adr);
+
+    /**
+     * @brief Request data from the slave device and wait.
+     * @details Request a number of bytes from a slave device.
+     *          Wait for the data to arrive until the timeout
+     *          interval expires. Return true on success.
+     *
+     * @param bytes the number of bytes to request
+     * @return status of the request: true=success, false=timeout
+     */
+    bool request(const uint8_t bytes);
+
+    /**
+     * @brief Capture data from the bus into the buffer.
+     * @details Capture data after a request has succeeded.
+     *
+     * @param bytes the number of bytes to request
+     * @return the number of bytes captured to the buffer
+     */
+    uint8_t capture(char *dst, const uint8_t bytes);
+
+    /**
+     * @brief Flush the i2c bus.
+     * @details Get all bytes on the bus and throw them away.
+     */
+    static void flush();
+
+    /**
+     * @brief Request data from the slave device, echo to serial.
+     * @details Request a number of bytes from a slave device and output
+     *          the returned data to serial in a parser-friendly format.
+     *
+     * @param bytes the number of bytes to request
+     */
+    void relay(const uint8_t bytes);
 
     #if I2C_SLAVE_ADDRESS > 0
 
@@ -181,6 +225,7 @@ class TWIBus {
       /**
        * @brief Send a reply to the bus
        * @details Send the buffer and clear it.
+       *          If a string is passed, write it into the buffer first.
        */
       void reply(char str[]=NULL);
       inline void reply(const char str[]) { this->reply((char*)str); }