Flags for MarlinSerial instance features (#21318)
This commit is contained in:
parent
3f7cd45df4
commit
139c149486
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
// 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>
|
||||
void SERIAL_ECHOLN(T x) { SERIAL_IMPL.println(x); }
|
||||
|
@ -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;
|
||||
@ -98,10 +100,11 @@ struct ConditionalSerial : public SerialBase< ConditionalSerial<SerialT> > {
|
||||
bool connected() { return CALL_IF_EXISTS(bool, &out, connected); }
|
||||
void flushTX() { CALL_IF_EXISTS(void, &out, flushTX); }
|
||||
|
||||
int available(serial_index_t ) { return (int)out.available(); }
|
||||
int read(serial_index_t ) { return (int)out.read(); }
|
||||
int available(serial_index_t) { return (int)out.available(); }
|
||||
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,9 +167,15 @@ 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); }
|
||||
|
||||
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
|
||||
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user