@ -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 explicit ly 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 + + ) ; }