Flags for MarlinSerial instance features (#21318)
This commit is contained in:
		| @@ -27,11 +27,12 @@ | |||||||
|   // Useful macro for stopping the CPU on an unexpected condition |   // Useful macro for stopping the CPU on an unexpected condition | ||||||
|   // This is used like SERIAL_ECHOPAIR, that is: a key-value call of the local variables you want |   // This is used like SERIAL_ECHOPAIR, that is: a key-value call of the local variables you want | ||||||
|   // to dump to the serial port before stopping the CPU. |   // to dump to the serial port before stopping the CPU. | ||||||
|   #define BUG_ON(V...) do { SERIAL_ECHOPAIR(ONLY_FILENAME, __LINE__, ": "); SERIAL_ECHOLNPAIR(V); SERIAL_FLUSHTX(); *(char*)0 = 42; } while(0) |                           // \/ Don't replace by SERIAL_ECHOPAIR since ONLY_FILENAME cannot be transformed to a PGM string on Arduino and it breaks building | ||||||
|  |   #define BUG_ON(V...) do { SERIAL_ECHO(ONLY_FILENAME); SERIAL_ECHO(__LINE__); SERIAL_ECHOLN(": "); SERIAL_ECHOLNPAIR(V); SERIAL_FLUSHTX(); *(char*)0 = 42; } while(0) | ||||||
| #elif ENABLED(MARLIN_DEV_MODE) | #elif ENABLED(MARLIN_DEV_MODE) | ||||||
|   // Don't stop the CPU here, but at least dump the bug on the serial port |   // Don't stop the CPU here, but at least dump the bug on the serial port | ||||||
|   //#define BUG_ON(V...) do { SERIAL_ECHOPAIR(ONLY_FILENAME, __LINE__, ": BUG!\n"); SERIAL_ECHOLNPAIR(V); SERIAL_FLUSHTX(); } while(0) |                           // \/ Don't replace by SERIAL_ECHOPAIR since ONLY_FILENAME cannot be transformed to a PGM string on Arduino and it breaks building | ||||||
|   #define BUG_ON(V...) NOOP |   #define BUG_ON(V...) do { SERIAL_ECHO(ONLY_FILENAME); SERIAL_ECHO(__LINE__); SERIAL_ECHOLN(": BUG!"); SERIAL_ECHOLNPAIR(V); SERIAL_FLUSHTX(); } while(0) | ||||||
| #else | #else | ||||||
|   // Release mode, let's ignore the bug |   // Release mode, let's ignore the bug | ||||||
|   #define BUG_ON(V...) NOOP |   #define BUG_ON(V...) NOOP | ||||||
|   | |||||||
| @@ -318,6 +318,16 @@ | |||||||
|  |  | ||||||
|   #endif |   #endif | ||||||
|  |  | ||||||
|  |   // Allow manipulating enumeration value like flags without ugly cast everywhere | ||||||
|  |   #define ENUM_FLAGS(T) \ | ||||||
|  |     FORCE_INLINE constexpr T operator&(T x, T y) { return static_cast<T>(static_cast<int>(x) & static_cast<int>(y)); } \ | ||||||
|  |     FORCE_INLINE constexpr T operator|(T x, T y) { return static_cast<T>(static_cast<int>(x) | static_cast<int>(y)); } \ | ||||||
|  |     FORCE_INLINE constexpr T operator^(T x, T y) { return static_cast<T>(static_cast<int>(x) ^ static_cast<int>(y)); } \ | ||||||
|  |     FORCE_INLINE constexpr T operator~(T x)      { return static_cast<T>(~static_cast<int>(x)); } \ | ||||||
|  |     FORCE_INLINE T & operator&=(T &x, T y) { return x &= y; } \ | ||||||
|  |     FORCE_INLINE T & operator|=(T &x, T y) { return x |= y; } \ | ||||||
|  |     FORCE_INLINE T & operator^=(T &x, T y) { return x ^= y; } | ||||||
|  |  | ||||||
|   // C++11 solution that is standard compliant. <type_traits> is not available on all platform |   // C++11 solution that is standard compliant. <type_traits> is not available on all platform | ||||||
|   namespace Private { |   namespace Private { | ||||||
|     template<bool, typename _Tp = void> struct enable_if { }; |     template<bool, typename _Tp = void> struct enable_if { }; | ||||||
| @@ -357,23 +367,43 @@ | |||||||
|       return *str ? findStringEnd(str + 1) : str; |       return *str ? findStringEnd(str + 1) : str; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Check whether a string contains a slash |     // Check whether a string contains a specific character | ||||||
|     constexpr bool containsSlash(const char *str) { |     constexpr bool contains(const char *str, const char ch) { | ||||||
|       return *str == '/' ? true : (*str ? containsSlash(str + 1) : false); |       return *str == ch ? true : (*str ? contains(str + 1, ch) : false); | ||||||
|     } |     } | ||||||
|     // Find the last position of the slash |     // Find the last position of the specific character (should be called with findStringEnd) | ||||||
|     constexpr const char* findLastSlashPos(const char *str) { |     constexpr const char* findLastPos(const char *str, const char ch) { | ||||||
|       return *str == '/' ? (str + 1) : findLastSlashPos(str - 1); |       return *str == ch ? (str + 1) : findLastPos(str - 1, ch); | ||||||
|     } |     } | ||||||
|     // Compile-time evaluation of the last part of a file path |     // Compile-time evaluation of the last part of a file path | ||||||
|     // Typically used to shorten the path to file in compiled strings |     // Typically used to shorten the path to file in compiled strings | ||||||
|     // CompileTimeString::baseName(__FILE__) returns "macros.h" and not /path/to/Marlin/src/core/macros.h |     // CompileTimeString::baseName(__FILE__) returns "macros.h" and not /path/to/Marlin/src/core/macros.h | ||||||
|     constexpr const char* baseName(const char *str) { |     constexpr const char* baseName(const char *str) { | ||||||
|       return containsSlash(str) ? findLastSlashPos(findStringEnd(str)) : str; |       return contains(str, '/') ? findLastPos(findStringEnd(str), '/') : str; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Find the first occurence of a character in a string (or return the last position in the string) | ||||||
|  |     constexpr const char* findFirst(const char *str, const char ch) { | ||||||
|  |       return *str == ch || *str == 0 ? (str + 1) : findFirst(str + 1, ch); | ||||||
|  |     } | ||||||
|  |     // Compute the string length at compile time | ||||||
|  |     constexpr unsigned stringLen(const char *str) { | ||||||
|  |       return *str == 0 ? 0 : 1 + stringLen(str + 1); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   #define ONLY_FILENAME CompileTimeString::baseName(__FILE__) |   #define ONLY_FILENAME CompileTimeString::baseName(__FILE__) | ||||||
|  |   /** Get the templated type name. This does not depends on RTTI, but on the preprocessor, so it should be quite safe to use even on old compilers. | ||||||
|  |       WARNING: DO NOT RENAME THIS FUNCTION (or change the text inside the function to match what the preprocessor will generate) | ||||||
|  |       The name is chosen very short since the binary will store "const char* gtn(T*) [with T = YourTypeHere]" so avoid long function name here */ | ||||||
|  |   template <typename T> | ||||||
|  |   inline const char* gtn(T*) { | ||||||
|  |     // It works on GCC by instantiating __PRETTY_FUNCTION__ and parsing the result. So the syntax here is very limited to GCC output | ||||||
|  |     constexpr unsigned verboseChatLen = sizeof("const char* gtn(T*) [with T = ") - 1; | ||||||
|  |     static char templateType[sizeof(__PRETTY_FUNCTION__) - verboseChatLen] = {}; | ||||||
|  |     __builtin_memcpy(templateType, __PRETTY_FUNCTION__ + verboseChatLen, sizeof(__PRETTY_FUNCTION__) - verboseChatLen - 2); | ||||||
|  |     return templateType; | ||||||
|  |   } | ||||||
|  |  | ||||||
| #else | #else | ||||||
|  |  | ||||||
|   | |||||||
| @@ -146,7 +146,7 @@ inline void SERIAL_ECHO(serial_char_t x) { SERIAL_IMPL.write(x.c); } | |||||||
| #define AS_CHAR(C) serial_char_t(C) | #define AS_CHAR(C) serial_char_t(C) | ||||||
|  |  | ||||||
| // SERIAL_ECHO_F prints a floating point value with optional precision | // SERIAL_ECHO_F prints a floating point value with optional precision | ||||||
| inline void SERIAL_ECHO_F(EnsureDouble x, int digit = 2) { SERIAL_IMPL.print(x, digit); } | inline void SERIAL_ECHO_F(EnsureDouble x, int digit=2) { SERIAL_IMPL.print(x, digit); } | ||||||
|  |  | ||||||
| template <typename T> | template <typename T> | ||||||
| void SERIAL_ECHOLN(T x) { SERIAL_IMPL.println(x); } | void SERIAL_ECHOLN(T x) { SERIAL_IMPL.println(x); } | ||||||
|   | |||||||
| @@ -45,10 +45,6 @@ struct serial_index_t { | |||||||
|   constexpr serial_index_t() : index(-1) {} |   constexpr serial_index_t() : index(-1) {} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // flushTX is not implemented in all HAL, so use SFINAE to call the method where it is. |  | ||||||
| CALL_IF_EXISTS_IMPL(void, flushTX); |  | ||||||
| CALL_IF_EXISTS_IMPL(bool, connected, true); |  | ||||||
|  |  | ||||||
| // In order to catch usage errors in code, we make the base to encode number explicit | // In order to catch usage errors in code, we make the base to encode number explicit | ||||||
| // If given a number (and not this enum), the compiler will reject the overload, falling back to the (double, digit) version | // If given a number (and not this enum), the compiler will reject the overload, falling back to the (double, digit) version | ||||||
| // We don't want hidden conversion of the first parameter to double, so it has to be as hard to do for the compiler as creating this enum | // We don't want hidden conversion of the first parameter to double, so it has to be as hard to do for the compiler as creating this enum | ||||||
| @@ -59,19 +55,34 @@ enum class PrintBase { | |||||||
|   Bin = 2 |   Bin = 2 | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // A simple forward struct that prevent the compiler to select print(double, int) as a default overload for any type different than | // A simple feature list enumeration | ||||||
| // double or float. For double or float, a conversion exists so the call will be transparent | enum class SerialFeature { | ||||||
|  |   None                = 0x00, | ||||||
|  |   MeatPack            = 0x01,   //!< Enabled when Meatpack is present | ||||||
|  |   BinaryFileTransfer  = 0x02,   //!< Enabled for BinaryFile transfer support (in the future) | ||||||
|  |   Virtual             = 0x04,   //!< Enabled for virtual serial port (like Telnet / Websocket / ...) | ||||||
|  |   Hookable            = 0x08,   //!< Enabled if the serial class supports a setHook method | ||||||
|  | }; | ||||||
|  | ENUM_FLAGS(SerialFeature); | ||||||
|  |  | ||||||
|  | // flushTX is not implemented in all HAL, so use SFINAE to call the method where it is. | ||||||
|  | CALL_IF_EXISTS_IMPL(void, flushTX); | ||||||
|  | CALL_IF_EXISTS_IMPL(bool, connected, true); | ||||||
|  | CALL_IF_EXISTS_IMPL(SerialFeature, features, SerialFeature::None); | ||||||
|  |  | ||||||
|  | // A simple forward struct to prevent the compiler from selecting print(double, int) as a default overload | ||||||
|  | // for any type other than double/float. For double/float, a conversion exists so the call will be invisible. | ||||||
| struct EnsureDouble { | struct EnsureDouble { | ||||||
|   double a; |   double a; | ||||||
|   FORCE_INLINE operator double() { return a; } |   FORCE_INLINE operator double() { return a; } | ||||||
|   // If the compiler breaks on ambiguity here, it's likely because you're calling print(X, base) with X not a double or a float, and a |   // If the compiler breaks on ambiguity here, it's likely because print(X, base) is called with X not a double/float, and | ||||||
|   // base that's not one of PrintBase's value. This exact code is made to detect such error, you NEED to set a base explicitely like this: |   // a base that's not a PrintBase value. This code is made to detect the error. You MUST set a base explicitly like this: | ||||||
|   // SERIAL_PRINT(v, PrintBase::Hex) |   // SERIAL_PRINT(v, PrintBase::Hex) | ||||||
|   FORCE_INLINE EnsureDouble(double a) : a(a) {} |   FORCE_INLINE EnsureDouble(double a) : a(a) {} | ||||||
|   FORCE_INLINE EnsureDouble(float a) : a(a) {} |   FORCE_INLINE EnsureDouble(float a) : a(a) {} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // Using Curiously Recurring Template Pattern here to avoid virtual table cost when compiling. | // Using Curiously-Recurring Template Pattern here to avoid virtual table cost when compiling. | ||||||
| // Since the real serial class is known at compile time, this results in the compiler writing | // Since the real serial class is known at compile time, this results in the compiler writing | ||||||
| // a completely efficient code. | // a completely efficient code. | ||||||
| template <class Child> | template <class Child> | ||||||
| @@ -85,27 +96,44 @@ struct SerialBase { | |||||||
|     SerialBase(const bool) {} |     SerialBase(const bool) {} | ||||||
|   #endif |   #endif | ||||||
|  |  | ||||||
|  |   #define SerialChild static_cast<Child*>(this) | ||||||
|  |  | ||||||
|   // Static dispatch methods below: |   // Static dispatch methods below: | ||||||
|   // The most important method here is where it all ends to: |   // The most important method here is where it all ends to: | ||||||
|   size_t write(uint8_t c)           { return static_cast<Child*>(this)->write(c); } |   size_t write(uint8_t c)           { return SerialChild->write(c); } | ||||||
|  |  | ||||||
|   // Called when the parser finished processing an instruction, usually build to nothing |   // Called when the parser finished processing an instruction, usually build to nothing | ||||||
|   void msgDone()                    { static_cast<Child*>(this)->msgDone(); } |   void msgDone() const              { SerialChild->msgDone(); } | ||||||
|   // Called upon initialization |  | ||||||
|   void begin(const long baudRate)   { static_cast<Child*>(this)->begin(baudRate); } |   // Called on initialization | ||||||
|   // Called upon destruction |   void begin(const long baudRate)   { SerialChild->begin(baudRate); } | ||||||
|   void end()                        { static_cast<Child*>(this)->end(); } |  | ||||||
|  |   // Called on destruction | ||||||
|  |   void end()                        { SerialChild->end(); } | ||||||
|  |  | ||||||
|   /** Check for available data from the port |   /** Check for available data from the port | ||||||
|       @param index  The port index, usually 0 */ |       @param index  The port index, usually 0 */ | ||||||
|   int available(serial_index_t index = 0)  { return static_cast<Child*>(this)->available(index); } |   int available(serial_index_t index=0) const { return SerialChild->available(index); } | ||||||
|  |  | ||||||
|   /** Read a value from the port |   /** Read a value from the port | ||||||
|       @param index  The port index, usually 0 */ |       @param index  The port index, usually 0 */ | ||||||
|   int  read(serial_index_t index = 0)      { return static_cast<Child*>(this)->read(index); } |   int read(serial_index_t index=0)        { return SerialChild->read(index); } | ||||||
|  |  | ||||||
|  |   /** Combine the features of this serial instance and return it | ||||||
|  |       @param index  The port index, usually 0 */ | ||||||
|  |   SerialFeature features(serial_index_t index=0) const { return static_cast<const Child*>(this)->features(index);  } | ||||||
|  |  | ||||||
|  |   // Check if the serial port has a feature | ||||||
|  |   bool has_feature(serial_index_t index, SerialFeature flag) const { (features(index) & flag) != SerialFeature::None; } | ||||||
|  |  | ||||||
|   // Check if the serial port is connected (usually bypassed) |   // Check if the serial port is connected (usually bypassed) | ||||||
|   bool connected()                  { return static_cast<Child*>(this)->connected(); } |   bool connected() const            { return SerialChild->connected(); } | ||||||
|  |  | ||||||
|   // Redirect flush |   // Redirect flush | ||||||
|   void flush()                      { static_cast<Child*>(this)->flush(); } |   void flush()                      { SerialChild->flush(); } | ||||||
|  |  | ||||||
|   // Not all implementation have a flushTX, so let's call them only if the child has the implementation |   // Not all implementation have a flushTX, so let's call them only if the child has the implementation | ||||||
|   void flushTX()                    { CALL_IF_EXISTS(void, static_cast<Child*>(this), flushTX); } |   void flushTX()                    { CALL_IF_EXISTS(void, SerialChild, flushTX); } | ||||||
|  |  | ||||||
|   // Glue code here |   // Glue code here | ||||||
|   FORCE_INLINE void write(const char *str)                    { while (*str) write(*str++); } |   FORCE_INLINE void write(const char *str)                    { while (*str) write(*str++); } | ||||||
|   | |||||||
| @@ -65,6 +65,8 @@ struct BaseSerial : public SerialBase< BaseSerial<SerialT> >, public SerialT { | |||||||
|   bool connected()              { return CALL_IF_EXISTS(bool, static_cast<SerialT*>(this), connected);; } |   bool connected()              { return CALL_IF_EXISTS(bool, static_cast<SerialT*>(this), connected);; } | ||||||
|   void flushTX()                { CALL_IF_EXISTS(void, static_cast<SerialT*>(this), flushTX); } |   void flushTX()                { CALL_IF_EXISTS(void, static_cast<SerialT*>(this), flushTX); } | ||||||
|  |  | ||||||
|  |   SerialFeature features(serial_index_t index) const { return CALL_IF_EXISTS(SerialFeature, static_cast<const SerialT*>(this), features, index);  } | ||||||
|  |  | ||||||
|   // We have 2 implementation of the same method in both base class, let's say which one we want |   // We have 2 implementation of the same method in both base class, let's say which one we want | ||||||
|   using SerialT::available; |   using SerialT::available; | ||||||
|   using SerialT::read; |   using SerialT::read; | ||||||
| @@ -98,10 +100,11 @@ struct ConditionalSerial : public SerialBase< ConditionalSerial<SerialT> > { | |||||||
|   bool connected()          { return CALL_IF_EXISTS(bool, &out, connected); } |   bool connected()          { return CALL_IF_EXISTS(bool, &out, connected); } | ||||||
|   void flushTX()            { CALL_IF_EXISTS(void, &out, flushTX); } |   void flushTX()            { CALL_IF_EXISTS(void, &out, flushTX); } | ||||||
|  |  | ||||||
|   int available(serial_index_t )  { return (int)out.available(); } |   int available(serial_index_t)   { return (int)out.available(); } | ||||||
|   int read(serial_index_t )       { return (int)out.read(); } |   int read(serial_index_t)        { return (int)out.read(); } | ||||||
|   int available()                 { return (int)out.available(); } |   int available()                 { return (int)out.available(); } | ||||||
|   int read()                      { return (int)out.read(); } |   int read()                      { return (int)out.read(); } | ||||||
|  |   SerialFeature features(serial_index_t index) const  { return CALL_IF_EXISTS(SerialFeature, &out, features, index);  } | ||||||
|  |  | ||||||
|   ConditionalSerial(bool & conditionVariable, SerialT & out, const bool e) : BaseClassT(e), condition(conditionVariable), out(out) {} |   ConditionalSerial(bool & conditionVariable, SerialT & out, const bool e) : BaseClassT(e), condition(conditionVariable), out(out) {} | ||||||
| }; | }; | ||||||
| @@ -126,6 +129,7 @@ struct ForwardSerial : public SerialBase< ForwardSerial<SerialT> > { | |||||||
|   int read(serial_index_t)      { return (int)out.read(); } |   int read(serial_index_t)      { return (int)out.read(); } | ||||||
|   int available()               { return (int)out.available(); } |   int available()               { return (int)out.available(); } | ||||||
|   int read()                    { return (int)out.read(); } |   int read()                    { return (int)out.read(); } | ||||||
|  |   SerialFeature features(serial_index_t index) const  { return CALL_IF_EXISTS(SerialFeature, &out, features, index);  } | ||||||
|  |  | ||||||
|   ForwardSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {} |   ForwardSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {} | ||||||
| }; | }; | ||||||
| @@ -163,9 +167,15 @@ struct RuntimeSerial : public SerialBase< RuntimeSerial<SerialT> >, public Seria | |||||||
|  |  | ||||||
|   // Underlying implementation might use Arduino's bool operator |   // Underlying implementation might use Arduino's bool operator | ||||||
|   bool connected() { |   bool connected() { | ||||||
|     return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, static_cast<SerialT*>(this), connected) : static_cast<SerialT*>(this)->operator bool(); |     return Private::HasMember_connected<SerialT>::value | ||||||
|  |       ? CALL_IF_EXISTS(bool, static_cast<SerialT*>(this), connected) | ||||||
|  |       : static_cast<SerialT*>(this)->operator bool(); | ||||||
|   } |   } | ||||||
|   void flushTX()                { CALL_IF_EXISTS(void, static_cast<SerialT*>(this), flushTX); } |  | ||||||
|  |   void flushTX() { CALL_IF_EXISTS(void, static_cast<SerialT*>(this), flushTX); } | ||||||
|  |  | ||||||
|  |   // Append Hookable for this class | ||||||
|  |   SerialFeature features(serial_index_t index) const  { return SerialFeature::Hookable | CALL_IF_EXISTS(SerialFeature, static_cast<const SerialT*>(this), features, index);  } | ||||||
|  |  | ||||||
|   void setHook(WriteHook writeHook = 0, EndOfMessageHook eofHook = 0, void * userPointer = 0) { |   void setHook(WriteHook writeHook = 0, EndOfMessageHook eofHook = 0, void * userPointer = 0) { | ||||||
|     // Order is important here as serial code can be called inside interrupts |     // Order is important here as serial code can be called inside interrupts | ||||||
| @@ -251,6 +261,15 @@ struct MultiSerial : public SerialBase< MultiSerial<Serial0T, Serial1T, offset, | |||||||
|     if (portMask.enabled(SecondOutput))  CALL_IF_EXISTS(void, &serial1, flushTX); |     if (portMask.enabled(SecondOutput))  CALL_IF_EXISTS(void, &serial1, flushTX); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Forward feature queries | ||||||
|  |   SerialFeature features(serial_index_t index) const  { | ||||||
|  |     if (index.within(0 + offset, step + offset - 1)) | ||||||
|  |       return serial0.features(index); | ||||||
|  |     else if (index.within(step + offset, 2 * step + offset - 1)) | ||||||
|  |       return serial1.features(index); | ||||||
|  |     return SerialFeature::None; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   MultiSerial(Serial0T & serial0, Serial1T & serial1, const SerialMask mask = Both, const bool e = false) : |   MultiSerial(Serial0T & serial0, Serial1T & serial1, const SerialMask mask = Both, const bool e = false) : | ||||||
|     BaseClassT(e), |     BaseClassT(e), | ||||||
|     portMask(mask), serial0(serial0), serial1(serial1) {} |     portMask(mask), serial0(serial0), serial1(serial1) {} | ||||||
|   | |||||||
| @@ -142,6 +142,8 @@ struct MeatpackSerial : public SerialBase <MeatpackSerial < SerialT >> { | |||||||
|   // Existing instances implement Arduino's operator bool, so use that if it's available |   // Existing instances implement Arduino's operator bool, so use that if it's available | ||||||
|   bool connected()                    { return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; } |   bool connected()                    { return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; } | ||||||
|   void flushTX()                      { CALL_IF_EXISTS(void, &out, flushTX); } |   void flushTX()                      { CALL_IF_EXISTS(void, &out, flushTX); } | ||||||
|  |   SerialFeature features(serial_index_t index) const  { return SerialFeature::MeatPack | CALL_IF_EXISTS(SerialFeature, &out, features, index);  } | ||||||
|  |  | ||||||
|  |  | ||||||
|   int available(serial_index_t index) { |   int available(serial_index_t index) { | ||||||
|     if (charCount) return charCount;          // The buffer still has data |     if (charCount) return charCount;          // The buffer still has data | ||||||
|   | |||||||
| @@ -167,6 +167,11 @@ | |||||||
|         dump_delay_accuracy_check(); |         dump_delay_accuracy_check(); | ||||||
|         break; |         break; | ||||||
|  |  | ||||||
|  |       case 7: // D7 dump the current serial port type (hence configuration) | ||||||
|  |         SERIAL_ECHOLNPAIR("Current serial configuration RX_BS:", RX_BUFFER_SIZE, ", TX_BS:", TX_BUFFER_SIZE); | ||||||
|  |         SERIAL_ECHOLN(gtn(&SERIAL_IMPL)); | ||||||
|  |         break; | ||||||
|  |  | ||||||
|       case 100: { // D100 Disable heaters and attempt a hard hang (Watchdog Test) |       case 100: { // D100 Disable heaters and attempt a hard hang (Watchdog Test) | ||||||
|         SERIAL_ECHOLNPGM("Disabling heaters and attempting to trigger Watchdog"); |         SERIAL_ECHOLNPGM("Disabling heaters and attempting to trigger Watchdog"); | ||||||
|         SERIAL_ECHOLNPGM("(USE_WATCHDOG " TERN(USE_WATCHDOG, "ENABLED", "DISABLED") ")"); |         SERIAL_ECHOLNPGM("(USE_WATCHDOG " TERN(USE_WATCHDOG, "ENABLED", "DISABLED") ")"); | ||||||
|   | |||||||
| @@ -22,6 +22,8 @@ | |||||||
|  |  | ||||||
| #include "../gcode.h" | #include "../gcode.h" | ||||||
| #include "../../inc/MarlinConfig.h" | #include "../../inc/MarlinConfig.h" | ||||||
|  | #include "../queue.h"           // for getting the command port | ||||||
|  |  | ||||||
|  |  | ||||||
| #if ENABLED(M115_GEOMETRY_REPORT) | #if ENABLED(M115_GEOMETRY_REPORT) | ||||||
|   #include "../../module/motion.h" |   #include "../../module/motion.h" | ||||||
| @@ -59,6 +61,9 @@ void GcodeSuite::M115() { | |||||||
|  |  | ||||||
|   #if ENABLED(EXTENDED_CAPABILITIES_REPORT) |   #if ENABLED(EXTENDED_CAPABILITIES_REPORT) | ||||||
|  |  | ||||||
|  |     // The port that sent M115 | ||||||
|  |     serial_index_t port = queue.ring_buffer.command_port(); | ||||||
|  |  | ||||||
|     // PAREN_COMMENTS |     // PAREN_COMMENTS | ||||||
|     TERN_(PAREN_COMMENTS, cap_line(PSTR("PAREN_COMMENTS"), true)); |     TERN_(PAREN_COMMENTS, cap_line(PSTR("PAREN_COMMENTS"), true)); | ||||||
|  |  | ||||||
| @@ -69,7 +74,7 @@ void GcodeSuite::M115() { | |||||||
|     cap_line(PSTR("SERIAL_XON_XOFF"), ENABLED(SERIAL_XON_XOFF)); |     cap_line(PSTR("SERIAL_XON_XOFF"), ENABLED(SERIAL_XON_XOFF)); | ||||||
|  |  | ||||||
|     // BINARY_FILE_TRANSFER (M28 B1) |     // BINARY_FILE_TRANSFER (M28 B1) | ||||||
|     cap_line(PSTR("BINARY_FILE_TRANSFER"), ENABLED(BINARY_FILE_TRANSFER)); |     cap_line(PSTR("BINARY_FILE_TRANSFER"), ENABLED(BINARY_FILE_TRANSFER)); // TODO: Use SERIAL_IMPL.has_feature(port, SerialFeature::BinaryFileTransfer) once implemented | ||||||
|  |  | ||||||
|     // EEPROM (M500, M501) |     // EEPROM (M500, M501) | ||||||
|     cap_line(PSTR("EEPROM"), ENABLED(EEPROM_SETTINGS)); |     cap_line(PSTR("EEPROM"), ENABLED(EEPROM_SETTINGS)); | ||||||
| @@ -148,7 +153,7 @@ void GcodeSuite::M115() { | |||||||
|     cap_line(PSTR("COOLER_TEMPERATURE"), ENABLED(HAS_COOLER)); |     cap_line(PSTR("COOLER_TEMPERATURE"), ENABLED(HAS_COOLER)); | ||||||
|  |  | ||||||
|     // MEATPACK Compression |     // MEATPACK Compression | ||||||
|     cap_line(PSTR("MEATPACK"), ENABLED(HAS_MEATPACK)); |     cap_line(PSTR("MEATPACK"), SERIAL_IMPL.has_feature(port, SerialFeature::MeatPack)); | ||||||
|  |  | ||||||
|     // Machine Geometry |     // Machine Geometry | ||||||
|     #if ENABLED(M115_GEOMETRY_REPORT) |     #if ENABLED(M115_GEOMETRY_REPORT) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user