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 | ||||
|   // 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. | ||||
|   #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) | ||||
|   // 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) | ||||
|   #define BUG_ON(V...) NOOP | ||||
|                           // \/ 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(": BUG!"); SERIAL_ECHOLNPAIR(V); SERIAL_FLUSHTX(); } while(0) | ||||
| #else | ||||
|   // Release mode, let's ignore the bug | ||||
|   #define BUG_ON(V...) NOOP | ||||
|   | ||||
| @@ -318,6 +318,16 @@ | ||||
|  | ||||
|   #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 | ||||
|   namespace Private { | ||||
|     template<bool, typename _Tp = void> struct enable_if { }; | ||||
| @@ -357,23 +367,43 @@ | ||||
|       return *str ? findStringEnd(str + 1) : str; | ||||
|     } | ||||
|  | ||||
|     // Check whether a string contains a slash | ||||
|     constexpr bool containsSlash(const char *str) { | ||||
|       return *str == '/' ? true : (*str ? containsSlash(str + 1) : false); | ||||
|     // Check whether a string contains a specific character | ||||
|     constexpr bool contains(const char *str, const char ch) { | ||||
|       return *str == ch ? true : (*str ? contains(str + 1, ch) : false); | ||||
|     } | ||||
|     // Find the last position of the slash | ||||
|     constexpr const char* findLastSlashPos(const char *str) { | ||||
|       return *str == '/' ? (str + 1) : findLastSlashPos(str - 1); | ||||
|     // Find the last position of the specific character (should be called with findStringEnd) | ||||
|     constexpr const char* findLastPos(const char *str, const char ch) { | ||||
|       return *str == ch ? (str + 1) : findLastPos(str - 1, ch); | ||||
|     } | ||||
|     // Compile-time evaluation of the last part of a file path | ||||
|     // 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 | ||||
|     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__) | ||||
|   /** 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 | ||||
|  | ||||
|   | ||||
| @@ -45,10 +45,6 @@ struct serial_index_t { | ||||
|   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 | ||||
| // 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 | ||||
| @@ -59,19 +55,34 @@ enum class PrintBase { | ||||
|   Bin = 2 | ||||
| }; | ||||
|  | ||||
| // A simple forward struct that prevent the compiler to select print(double, int) as a default overload for any type different than | ||||
| // double or float. For double or float, a conversion exists so the call will be transparent | ||||
| // A simple feature list enumeration | ||||
| 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 { | ||||
|   double 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 | ||||
|   // 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: | ||||
|   // If the compiler breaks on ambiguity here, it's likely because print(X, base) is called with X not a double/float, and | ||||
|   // 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) | ||||
|   FORCE_INLINE EnsureDouble(double 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 | ||||
| // a completely efficient code. | ||||
| template <class Child> | ||||
| @@ -85,27 +96,44 @@ struct SerialBase { | ||||
|     SerialBase(const bool) {} | ||||
|   #endif | ||||
|  | ||||
|   #define SerialChild static_cast<Child*>(this) | ||||
|  | ||||
|   // Static dispatch methods below: | ||||
|   // 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 | ||||
|   void msgDone()                    { static_cast<Child*>(this)->msgDone(); } | ||||
|   // Called upon initialization | ||||
|   void begin(const long baudRate)   { static_cast<Child*>(this)->begin(baudRate); } | ||||
|   // Called upon destruction | ||||
|   void end()                        { static_cast<Child*>(this)->end(); } | ||||
|   void msgDone() const              { SerialChild->msgDone(); } | ||||
|  | ||||
|   // Called on initialization | ||||
|   void begin(const long baudRate)   { SerialChild->begin(baudRate); } | ||||
|  | ||||
|   // Called on destruction | ||||
|   void end()                        { SerialChild->end(); } | ||||
|  | ||||
|   /** Check for available data from the port | ||||
|       @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 | ||||
|       @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) | ||||
|   bool connected()                  { return static_cast<Child*>(this)->connected(); } | ||||
|   bool connected() const            { return SerialChild->connected(); } | ||||
|  | ||||
|   // 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 | ||||
|   void flushTX()                    { CALL_IF_EXISTS(void, static_cast<Child*>(this), flushTX); } | ||||
|   void flushTX()                    { CALL_IF_EXISTS(void, SerialChild, flushTX); } | ||||
|  | ||||
|   // Glue code here | ||||
|   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);; } | ||||
|   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 | ||||
|   using SerialT::available; | ||||
|   using SerialT::read; | ||||
| @@ -102,6 +104,7 @@ struct ConditionalSerial : public SerialBase< ConditionalSerial<SerialT> > { | ||||
|   int read(serial_index_t)        { return (int)out.read(); } | ||||
|   int available()                 { return (int)out.available(); } | ||||
|   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) {} | ||||
| }; | ||||
| @@ -126,6 +129,7 @@ struct ForwardSerial : public SerialBase< ForwardSerial<SerialT> > { | ||||
|   int read(serial_index_t)      { return (int)out.read(); } | ||||
|   int available()               { return (int)out.available(); } | ||||
|   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) {} | ||||
| }; | ||||
| @@ -163,10 +167,16 @@ struct RuntimeSerial : public SerialBase< RuntimeSerial<SerialT> >, public Seria | ||||
|  | ||||
|   // Underlying implementation might use Arduino's bool operator | ||||
|   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); } | ||||
|  | ||||
|   // 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) { | ||||
|     // Order is important here as serial code can be called inside interrupts | ||||
|     // When setting a hook, the user pointer must be set first so if writeHook is called as soon as it's set, it'll be valid | ||||
| @@ -251,6 +261,15 @@ struct MultiSerial : public SerialBase< MultiSerial<Serial0T, Serial1T, offset, | ||||
|     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) : | ||||
|     BaseClassT(e), | ||||
|     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 | ||||
|   bool connected()                    { return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; } | ||||
|   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) { | ||||
|     if (charCount) return charCount;          // The buffer still has data | ||||
|   | ||||
| @@ -167,6 +167,11 @@ | ||||
|         dump_delay_accuracy_check(); | ||||
|         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) | ||||
|         SERIAL_ECHOLNPGM("Disabling heaters and attempting to trigger Watchdog"); | ||||
|         SERIAL_ECHOLNPGM("(USE_WATCHDOG " TERN(USE_WATCHDOG, "ENABLED", "DISABLED") ")"); | ||||
|   | ||||
| @@ -22,6 +22,8 @@ | ||||
|  | ||||
| #include "../gcode.h" | ||||
| #include "../../inc/MarlinConfig.h" | ||||
| #include "../queue.h"           // for getting the command port | ||||
|  | ||||
|  | ||||
| #if ENABLED(M115_GEOMETRY_REPORT) | ||||
|   #include "../../module/motion.h" | ||||
| @@ -59,6 +61,9 @@ void GcodeSuite::M115() { | ||||
|  | ||||
|   #if ENABLED(EXTENDED_CAPABILITIES_REPORT) | ||||
|  | ||||
|     // The port that sent M115 | ||||
|     serial_index_t port = queue.ring_buffer.command_port(); | ||||
|  | ||||
|     // PAREN_COMMENTS | ||||
|     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)); | ||||
|  | ||||
|     // 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) | ||||
|     cap_line(PSTR("EEPROM"), ENABLED(EEPROM_SETTINGS)); | ||||
| @@ -148,7 +153,7 @@ void GcodeSuite::M115() { | ||||
|     cap_line(PSTR("COOLER_TEMPERATURE"), ENABLED(HAS_COOLER)); | ||||
|  | ||||
|     // MEATPACK Compression | ||||
|     cap_line(PSTR("MEATPACK"), ENABLED(HAS_MEATPACK)); | ||||
|     cap_line(PSTR("MEATPACK"), SERIAL_IMPL.has_feature(port, SerialFeature::MeatPack)); | ||||
|  | ||||
|     // Machine Geometry | ||||
|     #if ENABLED(M115_GEOMETRY_REPORT) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user