Browse Source

Flags for MarlinSerial instance features (#21318)

X-Ryl669 3 years ago
parent
commit
139c149486
No account linked to committer's email address

+ 4
- 3
Marlin/src/core/bug_on.h View File

@@ -27,11 +27,12 @@
27 27
   // Useful macro for stopping the CPU on an unexpected condition
28 28
   // This is used like SERIAL_ECHOPAIR, that is: a key-value call of the local variables you want
29 29
   // to dump to the serial port before stopping the CPU.
30
-  #define BUG_ON(V...) do { SERIAL_ECHOPAIR(ONLY_FILENAME, __LINE__, ": "); SERIAL_ECHOLNPAIR(V); SERIAL_FLUSHTX(); *(char*)0 = 42; } while(0)
30
+                          // \/ Don't replace by SERIAL_ECHOPAIR since ONLY_FILENAME cannot be transformed to a PGM string on Arduino and it breaks building
31
+  #define BUG_ON(V...) do { SERIAL_ECHO(ONLY_FILENAME); SERIAL_ECHO(__LINE__); SERIAL_ECHOLN(": "); SERIAL_ECHOLNPAIR(V); SERIAL_FLUSHTX(); *(char*)0 = 42; } while(0)
31 32
 #elif ENABLED(MARLIN_DEV_MODE)
32 33
   // Don't stop the CPU here, but at least dump the bug on the serial port
33
-  //#define BUG_ON(V...) do { SERIAL_ECHOPAIR(ONLY_FILENAME, __LINE__, ": BUG!\n"); SERIAL_ECHOLNPAIR(V); SERIAL_FLUSHTX(); } while(0)
34
-  #define BUG_ON(V...) NOOP
34
+                          // \/ Don't replace by SERIAL_ECHOPAIR since ONLY_FILENAME cannot be transformed to a PGM string on Arduino and it breaks building
35
+  #define BUG_ON(V...) do { SERIAL_ECHO(ONLY_FILENAME); SERIAL_ECHO(__LINE__); SERIAL_ECHOLN(": BUG!"); SERIAL_ECHOLNPAIR(V); SERIAL_FLUSHTX(); } while(0)
35 36
 #else
36 37
   // Release mode, let's ignore the bug
37 38
   #define BUG_ON(V...) NOOP

+ 37
- 7
Marlin/src/core/macros.h View File

@@ -318,6 +318,16 @@
318 318
 
319 319
   #endif
320 320
 
321
+  // Allow manipulating enumeration value like flags without ugly cast everywhere
322
+  #define ENUM_FLAGS(T) \
323
+    FORCE_INLINE constexpr T operator&(T x, T y) { return static_cast<T>(static_cast<int>(x) & static_cast<int>(y)); } \
324
+    FORCE_INLINE constexpr T operator|(T x, T y) { return static_cast<T>(static_cast<int>(x) | static_cast<int>(y)); } \
325
+    FORCE_INLINE constexpr T operator^(T x, T y) { return static_cast<T>(static_cast<int>(x) ^ static_cast<int>(y)); } \
326
+    FORCE_INLINE constexpr T operator~(T x)      { return static_cast<T>(~static_cast<int>(x)); } \
327
+    FORCE_INLINE T & operator&=(T &x, T y) { return x &= y; } \
328
+    FORCE_INLINE T & operator|=(T &x, T y) { return x |= y; } \
329
+    FORCE_INLINE T & operator^=(T &x, T y) { return x ^= y; }
330
+
321 331
   // C++11 solution that is standard compliant. <type_traits> is not available on all platform
322 332
   namespace Private {
323 333
     template<bool, typename _Tp = void> struct enable_if { };
@@ -357,23 +367,43 @@
357 367
       return *str ? findStringEnd(str + 1) : str;
358 368
     }
359 369
 
360
-    // Check whether a string contains a slash
361
-    constexpr bool containsSlash(const char *str) {
362
-      return *str == '/' ? true : (*str ? containsSlash(str + 1) : false);
370
+    // Check whether a string contains a specific character
371
+    constexpr bool contains(const char *str, const char ch) {
372
+      return *str == ch ? true : (*str ? contains(str + 1, ch) : false);
363 373
     }
364
-    // Find the last position of the slash
365
-    constexpr const char* findLastSlashPos(const char *str) {
366
-      return *str == '/' ? (str + 1) : findLastSlashPos(str - 1);
374
+    // Find the last position of the specific character (should be called with findStringEnd)
375
+    constexpr const char* findLastPos(const char *str, const char ch) {
376
+      return *str == ch ? (str + 1) : findLastPos(str - 1, ch);
367 377
     }
368 378
     // Compile-time evaluation of the last part of a file path
369 379
     // Typically used to shorten the path to file in compiled strings
370 380
     // CompileTimeString::baseName(__FILE__) returns "macros.h" and not /path/to/Marlin/src/core/macros.h
371 381
     constexpr const char* baseName(const char *str) {
372
-      return containsSlash(str) ? findLastSlashPos(findStringEnd(str)) : str;
382
+      return contains(str, '/') ? findLastPos(findStringEnd(str), '/') : str;
383
+    }
384
+
385
+    // Find the first occurence of a character in a string (or return the last position in the string)
386
+    constexpr const char* findFirst(const char *str, const char ch) {
387
+      return *str == ch || *str == 0 ? (str + 1) : findFirst(str + 1, ch);
388
+    }
389
+    // Compute the string length at compile time
390
+    constexpr unsigned stringLen(const char *str) {
391
+      return *str == 0 ? 0 : 1 + stringLen(str + 1);
373 392
     }
374 393
   }
375 394
 
376 395
   #define ONLY_FILENAME CompileTimeString::baseName(__FILE__)
396
+  /** 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.
397
+      WARNING: DO NOT RENAME THIS FUNCTION (or change the text inside the function to match what the preprocessor will generate)
398
+      The name is chosen very short since the binary will store "const char* gtn(T*) [with T = YourTypeHere]" so avoid long function name here */
399
+  template <typename T>
400
+  inline const char* gtn(T*) {
401
+    // It works on GCC by instantiating __PRETTY_FUNCTION__ and parsing the result. So the syntax here is very limited to GCC output
402
+    constexpr unsigned verboseChatLen = sizeof("const char* gtn(T*) [with T = ") - 1;
403
+    static char templateType[sizeof(__PRETTY_FUNCTION__) - verboseChatLen] = {};
404
+    __builtin_memcpy(templateType, __PRETTY_FUNCTION__ + verboseChatLen, sizeof(__PRETTY_FUNCTION__) - verboseChatLen - 2);
405
+    return templateType;
406
+  }
377 407
 
378 408
 #else
379 409
 

+ 1
- 1
Marlin/src/core/serial.h View File

@@ -146,7 +146,7 @@ inline void SERIAL_ECHO(serial_char_t x) { SERIAL_IMPL.write(x.c); }
146 146
 #define AS_CHAR(C) serial_char_t(C)
147 147
 
148 148
 // SERIAL_ECHO_F prints a floating point value with optional precision
149
-inline void SERIAL_ECHO_F(EnsureDouble x, int digit = 2) { SERIAL_IMPL.print(x, digit); }
149
+inline void SERIAL_ECHO_F(EnsureDouble x, int digit=2) { SERIAL_IMPL.print(x, digit); }
150 150
 
151 151
 template <typename T>
152 152
 void SERIAL_ECHOLN(T x) { SERIAL_IMPL.println(x); }

+ 48
- 20
Marlin/src/core/serial_base.h View File

@@ -45,10 +45,6 @@ struct serial_index_t {
45 45
   constexpr serial_index_t() : index(-1) {}
46 46
 };
47 47
 
48
-// flushTX is not implemented in all HAL, so use SFINAE to call the method where it is.
49
-CALL_IF_EXISTS_IMPL(void, flushTX);
50
-CALL_IF_EXISTS_IMPL(bool, connected, true);
51
-
52 48
 // In order to catch usage errors in code, we make the base to encode number explicit
53 49
 // If given a number (and not this enum), the compiler will reject the overload, falling back to the (double, digit) version
54 50
 // 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 {
59 55
   Bin = 2
60 56
 };
61 57
 
62
-// A simple forward struct that prevent the compiler to select print(double, int) as a default overload for any type different than
63
-// double or float. For double or float, a conversion exists so the call will be transparent
58
+// A simple feature list enumeration
59
+enum class SerialFeature {
60
+  None                = 0x00,
61
+  MeatPack            = 0x01,   //!< Enabled when Meatpack is present
62
+  BinaryFileTransfer  = 0x02,   //!< Enabled for BinaryFile transfer support (in the future)
63
+  Virtual             = 0x04,   //!< Enabled for virtual serial port (like Telnet / Websocket / ...)
64
+  Hookable            = 0x08,   //!< Enabled if the serial class supports a setHook method
65
+};
66
+ENUM_FLAGS(SerialFeature);
67
+
68
+// flushTX is not implemented in all HAL, so use SFINAE to call the method where it is.
69
+CALL_IF_EXISTS_IMPL(void, flushTX);
70
+CALL_IF_EXISTS_IMPL(bool, connected, true);
71
+CALL_IF_EXISTS_IMPL(SerialFeature, features, SerialFeature::None);
72
+
73
+// A simple forward struct to prevent the compiler from selecting print(double, int) as a default overload
74
+// for any type other than double/float. For double/float, a conversion exists so the call will be invisible.
64 75
 struct EnsureDouble {
65 76
   double a;
66 77
   FORCE_INLINE operator double() { return a; }
67
-  // 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
68
-  // 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:
78
+  // If the compiler breaks on ambiguity here, it's likely because print(X, base) is called with X not a double/float, and
79
+  // a base that's not a PrintBase value. This code is made to detect the error. You MUST set a base explicitly like this:
69 80
   // SERIAL_PRINT(v, PrintBase::Hex)
70 81
   FORCE_INLINE EnsureDouble(double a) : a(a) {}
71 82
   FORCE_INLINE EnsureDouble(float a) : a(a) {}
72 83
 };
73 84
 
74
-// Using Curiously Recurring Template Pattern here to avoid virtual table cost when compiling.
85
+// Using Curiously-Recurring Template Pattern here to avoid virtual table cost when compiling.
75 86
 // Since the real serial class is known at compile time, this results in the compiler writing
76 87
 // a completely efficient code.
77 88
 template <class Child>
@@ -85,27 +96,44 @@ struct SerialBase {
85 96
     SerialBase(const bool) {}
86 97
   #endif
87 98
 
99
+  #define SerialChild static_cast<Child*>(this)
100
+
88 101
   // Static dispatch methods below:
89 102
   // The most important method here is where it all ends to:
90
-  size_t write(uint8_t c)           { return static_cast<Child*>(this)->write(c); }
103
+  size_t write(uint8_t c)           { return SerialChild->write(c); }
104
+
91 105
   // Called when the parser finished processing an instruction, usually build to nothing
92
-  void msgDone()                    { static_cast<Child*>(this)->msgDone(); }
93
-  // Called upon initialization
94
-  void begin(const long baudRate)   { static_cast<Child*>(this)->begin(baudRate); }
95
-  // Called upon destruction
96
-  void end()                        { static_cast<Child*>(this)->end(); }
106
+  void msgDone() const              { SerialChild->msgDone(); }
107
+
108
+  // Called on initialization
109
+  void begin(const long baudRate)   { SerialChild->begin(baudRate); }
110
+
111
+  // Called on destruction
112
+  void end()                        { SerialChild->end(); }
113
+
97 114
   /** Check for available data from the port
98 115
       @param index  The port index, usually 0 */
99
-  int available(serial_index_t index = 0)  { return static_cast<Child*>(this)->available(index); }
116
+  int available(serial_index_t index=0) const { return SerialChild->available(index); }
117
+
100 118
   /** Read a value from the port
101 119
       @param index  The port index, usually 0 */
102
-  int  read(serial_index_t index = 0)      { return static_cast<Child*>(this)->read(index); }
120
+  int read(serial_index_t index=0)        { return SerialChild->read(index); }
121
+
122
+  /** Combine the features of this serial instance and return it
123
+      @param index  The port index, usually 0 */
124
+  SerialFeature features(serial_index_t index=0) const { return static_cast<const Child*>(this)->features(index);  }
125
+
126
+  // Check if the serial port has a feature
127
+  bool has_feature(serial_index_t index, SerialFeature flag) const { (features(index) & flag) != SerialFeature::None; }
128
+
103 129
   // Check if the serial port is connected (usually bypassed)
104
-  bool connected()                  { return static_cast<Child*>(this)->connected(); }
130
+  bool connected() const            { return SerialChild->connected(); }
131
+
105 132
   // Redirect flush
106
-  void flush()                      { static_cast<Child*>(this)->flush(); }
133
+  void flush()                      { SerialChild->flush(); }
134
+
107 135
   // Not all implementation have a flushTX, so let's call them only if the child has the implementation
108
-  void flushTX()                    { CALL_IF_EXISTS(void, static_cast<Child*>(this), flushTX); }
136
+  void flushTX()                    { CALL_IF_EXISTS(void, SerialChild, flushTX); }
109 137
 
110 138
   // Glue code here
111 139
   FORCE_INLINE void write(const char *str)                    { while (*str) write(*str++); }

+ 23
- 4
Marlin/src/core/serial_hook.h View File

@@ -65,6 +65,8 @@ struct BaseSerial : public SerialBase< BaseSerial<SerialT> >, public SerialT {
65 65
   bool connected()              { return CALL_IF_EXISTS(bool, static_cast<SerialT*>(this), connected);; }
66 66
   void flushTX()                { CALL_IF_EXISTS(void, static_cast<SerialT*>(this), flushTX); }
67 67
 
68
+  SerialFeature features(serial_index_t index) const { return CALL_IF_EXISTS(SerialFeature, static_cast<const SerialT*>(this), features, index);  }
69
+
68 70
   // We have 2 implementation of the same method in both base class, let's say which one we want
69 71
   using SerialT::available;
70 72
   using SerialT::read;
@@ -98,10 +100,11 @@ struct ConditionalSerial : public SerialBase< ConditionalSerial<SerialT> > {
98 100
   bool connected()          { return CALL_IF_EXISTS(bool, &out, connected); }
99 101
   void flushTX()            { CALL_IF_EXISTS(void, &out, flushTX); }
100 102
 
101
-  int available(serial_index_t )  { return (int)out.available(); }
102
-  int read(serial_index_t )       { return (int)out.read(); }
103
+  int available(serial_index_t)   { return (int)out.available(); }
104
+  int read(serial_index_t)        { return (int)out.read(); }
103 105
   int available()                 { return (int)out.available(); }
104 106
   int read()                      { return (int)out.read(); }
107
+  SerialFeature features(serial_index_t index) const  { return CALL_IF_EXISTS(SerialFeature, &out, features, index);  }
105 108
 
106 109
   ConditionalSerial(bool & conditionVariable, SerialT & out, const bool e) : BaseClassT(e), condition(conditionVariable), out(out) {}
107 110
 };
@@ -126,6 +129,7 @@ struct ForwardSerial : public SerialBase< ForwardSerial<SerialT> > {
126 129
   int read(serial_index_t)      { return (int)out.read(); }
127 130
   int available()               { return (int)out.available(); }
128 131
   int read()                    { return (int)out.read(); }
132
+  SerialFeature features(serial_index_t index) const  { return CALL_IF_EXISTS(SerialFeature, &out, features, index);  }
129 133
 
130 134
   ForwardSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {}
131 135
 };
@@ -163,9 +167,15 @@ struct RuntimeSerial : public SerialBase< RuntimeSerial<SerialT> >, public Seria
163 167
 
164 168
   // Underlying implementation might use Arduino's bool operator
165 169
   bool connected() {
166
-    return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, static_cast<SerialT*>(this), connected) : static_cast<SerialT*>(this)->operator bool();
170
+    return Private::HasMember_connected<SerialT>::value
171
+      ? CALL_IF_EXISTS(bool, static_cast<SerialT*>(this), connected)
172
+      : static_cast<SerialT*>(this)->operator bool();
167 173
   }
168
-  void flushTX()                { CALL_IF_EXISTS(void, static_cast<SerialT*>(this), flushTX); }
174
+
175
+  void flushTX() { CALL_IF_EXISTS(void, static_cast<SerialT*>(this), flushTX); }
176
+
177
+  // Append Hookable for this class
178
+  SerialFeature features(serial_index_t index) const  { return SerialFeature::Hookable | CALL_IF_EXISTS(SerialFeature, static_cast<const SerialT*>(this), features, index);  }
169 179
 
170 180
   void setHook(WriteHook writeHook = 0, EndOfMessageHook eofHook = 0, void * userPointer = 0) {
171 181
     // Order is important here as serial code can be called inside interrupts
@@ -251,6 +261,15 @@ struct MultiSerial : public SerialBase< MultiSerial<Serial0T, Serial1T, offset,
251 261
     if (portMask.enabled(SecondOutput))  CALL_IF_EXISTS(void, &serial1, flushTX);
252 262
   }
253 263
 
264
+  // Forward feature queries
265
+  SerialFeature features(serial_index_t index) const  {
266
+    if (index.within(0 + offset, step + offset - 1))
267
+      return serial0.features(index);
268
+    else if (index.within(step + offset, 2 * step + offset - 1))
269
+      return serial1.features(index);
270
+    return SerialFeature::None;
271
+  }
272
+
254 273
   MultiSerial(Serial0T & serial0, Serial1T & serial1, const SerialMask mask = Both, const bool e = false) :
255 274
     BaseClassT(e),
256 275
     portMask(mask), serial0(serial0), serial1(serial1) {}

+ 2
- 0
Marlin/src/feature/meatpack.h View File

@@ -142,6 +142,8 @@ struct MeatpackSerial : public SerialBase <MeatpackSerial < SerialT >> {
142 142
   // Existing instances implement Arduino's operator bool, so use that if it's available
143 143
   bool connected()                    { return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; }
144 144
   void flushTX()                      { CALL_IF_EXISTS(void, &out, flushTX); }
145
+  SerialFeature features(serial_index_t index) const  { return SerialFeature::MeatPack | CALL_IF_EXISTS(SerialFeature, &out, features, index);  }
146
+
145 147
 
146 148
   int available(serial_index_t index) {
147 149
     if (charCount) return charCount;          // The buffer still has data

+ 5
- 0
Marlin/src/gcode/gcode_d.cpp View File

@@ -167,6 +167,11 @@
167 167
         dump_delay_accuracy_check();
168 168
         break;
169 169
 
170
+      case 7: // D7 dump the current serial port type (hence configuration)
171
+        SERIAL_ECHOLNPAIR("Current serial configuration RX_BS:", RX_BUFFER_SIZE, ", TX_BS:", TX_BUFFER_SIZE);
172
+        SERIAL_ECHOLN(gtn(&SERIAL_IMPL));
173
+        break;
174
+
170 175
       case 100: { // D100 Disable heaters and attempt a hard hang (Watchdog Test)
171 176
         SERIAL_ECHOLNPGM("Disabling heaters and attempting to trigger Watchdog");
172 177
         SERIAL_ECHOLNPGM("(USE_WATCHDOG " TERN(USE_WATCHDOG, "ENABLED", "DISABLED") ")");

+ 7
- 2
Marlin/src/gcode/host/M115.cpp View File

@@ -22,6 +22,8 @@
22 22
 
23 23
 #include "../gcode.h"
24 24
 #include "../../inc/MarlinConfig.h"
25
+#include "../queue.h"           // for getting the command port
26
+
25 27
 
26 28
 #if ENABLED(M115_GEOMETRY_REPORT)
27 29
   #include "../../module/motion.h"
@@ -59,6 +61,9 @@ void GcodeSuite::M115() {
59 61
 
60 62
   #if ENABLED(EXTENDED_CAPABILITIES_REPORT)
61 63
 
64
+    // The port that sent M115
65
+    serial_index_t port = queue.ring_buffer.command_port();
66
+
62 67
     // PAREN_COMMENTS
63 68
     TERN_(PAREN_COMMENTS, cap_line(PSTR("PAREN_COMMENTS"), true));
64 69
 
@@ -69,7 +74,7 @@ void GcodeSuite::M115() {
69 74
     cap_line(PSTR("SERIAL_XON_XOFF"), ENABLED(SERIAL_XON_XOFF));
70 75
 
71 76
     // BINARY_FILE_TRANSFER (M28 B1)
72
-    cap_line(PSTR("BINARY_FILE_TRANSFER"), ENABLED(BINARY_FILE_TRANSFER));
77
+    cap_line(PSTR("BINARY_FILE_TRANSFER"), ENABLED(BINARY_FILE_TRANSFER)); // TODO: Use SERIAL_IMPL.has_feature(port, SerialFeature::BinaryFileTransfer) once implemented
73 78
 
74 79
     // EEPROM (M500, M501)
75 80
     cap_line(PSTR("EEPROM"), ENABLED(EEPROM_SETTINGS));
@@ -148,7 +153,7 @@ void GcodeSuite::M115() {
148 153
     cap_line(PSTR("COOLER_TEMPERATURE"), ENABLED(HAS_COOLER));
149 154
 
150 155
     // MEATPACK Compression
151
-    cap_line(PSTR("MEATPACK"), ENABLED(HAS_MEATPACK));
156
+    cap_line(PSTR("MEATPACK"), SERIAL_IMPL.has_feature(port, SerialFeature::MeatPack));
152 157
 
153 158
     // Machine Geometry
154 159
     #if ENABLED(M115_GEOMETRY_REPORT)

Loading…
Cancel
Save