diff --git a/src/CatenaBase.h b/src/CatenaBase.h index 58c71f7..611a167 100644 --- a/src/CatenaBase.h +++ b/src/CatenaBase.h @@ -57,7 +57,7 @@ Copyright notice: (((major) << 24u) | ((minor) << 16u) | ((patch) << 8u) | (local)) #define CATENA_ARDUINO_PLATFORM_VERSION \ - CATENA_ARDUINO_PLATFORM_VERSION_CALC(0, 21, 2, 0) /* v0.21.2 */ + CATENA_ARDUINO_PLATFORM_VERSION_CALC(0, 21, 3, 1) /* v0.21.3-1 */ #define CATENA_ARDUINO_PLATFORM_VERSION_GET_MAJOR(v) \ (((v) >> 24u) & 0xFFu) diff --git a/src/Catena_Serial.h b/src/Catena_Serial.h index b397258..ec34289 100644 --- a/src/Catena_Serial.h +++ b/src/Catena_Serial.h @@ -28,10 +28,11 @@ Copyright and License: namespace McciCatena { +/// /// \brief Simple architecture that lets us use serial-like ports interchangably. /// /// \details -/// The Arduino system omittted a virutal base class that all Serial ports +/// The Arduino system omitted a virtual base class that all Serial ports /// can be derived from. This means you can't have a pointer to a "serial-like" /// thing, which is very inconvenient. Even if we put one in our Arduino /// variant, it would not help with third-party libraries. @@ -46,7 +47,7 @@ namespace McciCatena { class cSerialAbstract { public: - // constructor + /// \brief constructor cSerialAbstract() {}; // neither copyable nor movable. @@ -100,8 +101,10 @@ template class cSerial : public cSerialAbstract { public: - // constructor + /// \brief constructor in case caller has a pointer to the underlying port. cSerial(T *pPort) : m_pPort(pPort) {} + + /// \brief constructor in case caller has the LV of the underlying port. cSerial(T &Port) : m_pPort(&Port) {} // neither copyable nor movable. @@ -110,48 +113,141 @@ class cSerial : public cSerialAbstract cSerial(const cSerial&&) = delete; cSerial& operator=(const cSerial&&) = delete; + /// \brief return the number of bytes available in the read buffer. virtual int available() const override { return this->m_pPort->available(); } + /// \brief initialize the port, setting the baudrate. virtual void begin(unsigned long ulBaudRate) const override { this->m_pPort->begin(ulBaudRate); } +private: + // + // The following section deals with the polymorphism in underlying serial ports: + // some have port::begin(baudrate, config), others only have begin(). We use + // C++ template metaprogramming to allow us to use the advanced APIs if available, + // otherwise to ignore the config parameter. + // + // We follow Walter Brown's method https://youtu.be/a0FliKwcwXE?t=2276 for querying + // properties at compile time. + // + // First, the default `hasBeginWithConfig`. The compiler will choose this template + // if no template is found that is well-formed and more specific, and this will + // result in a structure that is a specialiation of std::false_type. + // + template + struct hasBeginWithConfig + : /* inherit from */ std::false_type /* so it has ::value == false */ + { /* no extra contents */ }; + + // + // Second, we define a version that will only be well-formed if the target class + // has U::begin(unsigned long, uint16_t), and it will be essentially identical to + // std::true_type. + // + template + struct hasBeginWithConfig< + U, + std::void_t< + /* packing a list of types, so take the type of... */ + decltype( + /* check whether we can invoke begin() with our two params */ + std::declval().begin( + (unsigned long)0, + (std::uint16_t)0 + ) + ) + > + > + : /* inherit from */ std::true_type /* so it has ::value == true */ + { /* no extra contents */ }; + + // + // Now overload beginWithConfig() with two versions, one that calls the 2-arg + // version of begin(), the other that calls the one-arg version. + // + // If we had C++ 2017, we could use if constexpr(), but it's not much worse to have + // overloads with two functions. + // + // This matches if the third argment is of type std::true_type. + // + template + static void beginWithConfig(U *pT, unsigned long ulBaudRate, uint16_t config, std::true_type) + { + pT->begin(ulBaudRate, config); + } + + // + // This overload matches if the the third argument is of type std::false_type, and + // uses the one-argument begin(). + // + template + static void beginWithConfig(U* pT, unsigned long ulBaudRate, uint16_t config, std::false_type) + { + pT->begin(ulBaudRate); + } + +public: + // + // finally, the begin() method here uses the hasBeginWithConfig<> template + // to select the approprialte beginWithConfig() overload. + // + + /// + /// \brief initialize port, setting baud rate and configuring. + /// virtual void begin(unsigned long ulBaudRate, uint16_t config) const override { - this->m_pPort->begin(ulBaudRate, config); + this->beginWithConfig( + this->m_pPort, + ulBaudRate, + config, + hasBeginWithConfig /* create object */ {} + ); } + /// + /// \brief wait for the write buffer to be drained to serial port. + /// virtual void drainWrite() const override { this->m_pPort->flush(); } + /// + /// \brief read one byte from a port. + /// + /// \returns next byte (in 0..255) or a negative number if read buffer is empty. + /// virtual int read() const override { return this->m_pPort->read(); } + /// \brief write a buffer, and return the number of bytes actually written. virtual size_t write(const uint8_t *buffer, size_t size) const override { return this->m_pPort->write(buffer, size); } + /// \brief invoke end() method of concrete port. virtual void end() const override { this->m_pPort->end(); } + /// \brief return pointer to underlying port viewed as a stream. virtual Stream & stream() override { return *m_pPort; } private: - T *m_pPort; + T *m_pPort; ///< pointer to concrete UART. }; } // namespace McciCatena