diff --git a/include/com/sequencer.h b/include/com/sequencer.h index 8e15918..f2c4c4e 100644 --- a/include/com/sequencer.h +++ b/include/com/sequencer.h @@ -225,7 +225,7 @@ class sequencer { * \param stream2 What we search [The record's token] * \return True on success, false otherwise */ - static constexpr auto equals = [](const string_view stream1, const string_view stream2) -> bool { + static constexpr auto equals = [](const string_view stream1, const string_view stream2) noexcept -> bool { return (stream1 == stream2); }; @@ -236,7 +236,7 @@ class sequencer { * \param prefix What we search [The record's token] * \return True on success, false otherwise */ - static constexpr auto starts_with = [](const string_view stream, const string_view prefix) -> bool { + static constexpr auto starts_with = [](const string_view stream, const string_view prefix) noexcept -> bool { return (stream.rfind(prefix, 0) != string_view::npos); }; @@ -265,18 +265,18 @@ class sequencer { * \param needle What we search [The record's token] * \return True on success, false otherwise */ - static constexpr auto contains = [](const string_view haystack, const string_view needle) -> bool { + static constexpr auto contains = [](const string_view haystack, const string_view needle) noexcept -> bool { return (haystack.find(needle) != string_view::npos); }; //! Always false predicate - static constexpr auto always_true = [](const string_view s1, const string_view s2) -> bool { + static constexpr auto always_true = [](const string_view s1, const string_view s2) noexcept -> bool { (void)s1; (void)s2; return true; }; //! Always false predicate - static constexpr auto always_false = [](const string_view s1, const string_view s2) -> bool { + static constexpr auto always_false = [](const string_view s1, const string_view s2) noexcept -> bool { (void)s1; (void)s2; return false; }; @@ -302,7 +302,7 @@ class sequencer { size_t get_ (Data_t* data) { return impl().get (data); } size_t contents_ (Data_t* data) { return impl().contents(data); } size_t put_ (const Data_t* data, size_t n) { return impl().put (data, n); } - clock_t clock_ () { return impl().clock(); } + clock_t clock_ () noexcept { return impl().clock(); } //! @} //! \name Private functionality @@ -364,7 +364,7 @@ class sequencer { public: //! \return The buffer size of the sequencer - constexpr size_t size() const { return N; } + constexpr size_t size() const noexcept { return N; } /*! * \brief diff --git a/include/drv/ATmodem.h b/include/drv/ATmodem.h deleted file mode 100644 index 5c3d2f1..0000000 --- a/include/drv/ATmodem.h +++ /dev/null @@ -1,368 +0,0 @@ -/*! - * \file drv/ATmodem.h - * \brief - * ATmodem driver functionality as CRTP base class - * - * \copyright Copyright (C) 2021 Christos Choutouridis - * - *
License
- * The MIT License (MIT) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - *
- */ - -#ifndef TBX_DRV_ATmodem_H_ -#define TBX_DRV_ATmodem_H_ - -#define __cplusplus 201703L - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -namespace tbx { - -/*! - * \class BG95_base - * \brief - * - * \example implementation example - * \code - - * \endcode - * - * \tparam Impl_t - * \tparam Cont_t - * \tparam N - * \tparam Delimiter - */ -template -class ATmodem - : public sequencer, char, N>{ - _CRTP_IMPL(Impl_t); - - // local type dispatch - using base_type = sequencer; - - //! \name Public types - //! @{ - public: - using value_type = char; - using pointer_type = char*; - using size_type = size_t; - using string_view = typename base_type::string_view; - - using action_t = typename base_type::action_t; - using control_t = typename base_type::control_t; - using match_ft = typename base_type::match_ft; - using handler_ft = typename base_type::handler_ft; - template - using script_t = typename base_type::template script_t; - - - - //! Publish delimiter - constexpr static char delimiter = Delimiter; - - //! Required types for inetd async handler operation - //! @{ - struct inetd_handler_t { - string_view token; - match_ft match; - handler_ft handler; - }; - template - using inetd_handlers = std::array; - //! @} - //! @} - - //! \name Constructor / Destructor - //!@{ - protected: - //!< \brief A default constructor from derived only - ATmodem() noexcept = default; - ~ATmodem () = default; //!< \brief Allow destructor from derived only - ATmodem(const ATmodem&) = delete; //!< No copies - ATmodem& operator= (const ATmodem&) = delete; //!< No copy assignments - //!@} - - //! \name Sequencer interface requirements - //! Forwarded to implementer the calls and cascade the the incoming channel - //! sequencer::get --resolved--> this->receive() --calls--> impl().get() - //! @{ - friend base_type; - - private: - size_t get_ (char* data) { - return impl().get (data); - } - size_t get (char* data) { - return receive (data); - } - size_t put (const char* data, size_t n) { - return impl().put (data, n); - } - size_t contents (char* data) { - return impl().contents(data); - } - clock_t clock () { - return impl().clock(); - } - //! @} - - //! \name Private functionality - //! @{ - private: - - template - struct typelist { - using type = typelist; //!< act as identity - }; - - template - struct front_impl { - using type = void; - }; - - template - struct front_impl> { - using type = Head; - }; - - //! Return the first element in \c meta::typelist \c List. - //! - //! Complexity \f$ O(1) \f$. - template - using front = typename front_impl::type; - - template - void extract (const char* str, T* value) { - static_assert ( - std::is_same_v, int> - || std::is_same_v, double> - || std::is_same_v, char>, - "Not supported conversion type."); - if constexpr (std::is_same_v, int>) { - *value = std::atoi(str); - } else if (std::is_same_v, double>) { - *value = std::atof(str); - } else if (std::is_same_v, char>) { - std::strcpy(value, str); - } - } - void extract (const char* str, void* value) { - (void)*str; (void)value; - } - - template - std::pair parse (const char* expected, const string_view buffer, char* token) { - do { - if (*expected == Marker) { - // We have Marker. Copy to token the next part of buffer, from begin up to expected[1] where - // the expected[1] character is and return the size of that part. - auto next = std::find(buffer.begin(), buffer.end(), expected[1]); - if (next == buffer.end()) - break; - char* nullpos = std::copy(buffer.begin(), next, token); - *nullpos =0; - return std::make_pair(next - buffer.begin(), true); - } - else if (*expected == buffer.front()) { - // We have character match, copy the character to token and return 1 (the char size) - *token++ = buffer.front(); - *token =0; - return std::make_pair(1, false); - } - } while (0); - - // Fail to parse - *token =0; - return std::make_pair(0, false); - } - - //! @} - - //! \name public functionality - //! @{ - public: - - /*! - * \brief - * Transmit data to modem - * \param data Pointer to data to send - * \param n The size of data buffer - * \return The number of transmitted chars - */ - size_t transmit (const char* data, size_t n) { - return put (data, n); - } - - size_t transmit (const char* data) { - return put (data, strlen(data)); - } - - /*! - * \brief - * Try to receive data from modem. If there are data copy them to \c data pointer and retur - * the size. Otherwise return zero. In the case \c wait is true block until there are data to get. - * - * \param data Pointer to data buffer to write - * \param wait Flag to select blocking / non-blocking functionality - * \return The number of copied data. - */ - size_t receive (char* data, bool wait =false) { - do { - if (streams_.load(std::memory_order_acquire)) { - size_t n =0; - do { - *data << rx_q; - ++n; - } while (*data++ != delimiter); - *data =0; - streams_.fetch_sub(1, std::memory_order_acq_rel); - return n; - } - } while (wait); // on wait flag we block until available stream - return 0; - } - - /*! - * \brief - * Send a command to modem and check if the response matches to - * \c expected. If so read any token inside response marked with - * \c Marker, convert the value into type \c T and write it to \c t - * - * \param cmd The comand to send (null terminated) - * \param expected The expected response - * \param t The value to return - * \param timeout The timeout in CPU time (leave it for 0 - no timeout) - * - * \tparam T The type of the value to read from response marked with \c Marker - * \tparam Marker The marker character - * \return True on success - * - * example - * \code - * BG95<256> modem; - * std::thread th1 ([&](){ - * modem.inetd(false); - * }); - * int status; - * bool done = modem.command("AT+CREG?\r\n", "\r\n+CREG: 0,%\r\n\r\nOK\r\n", status); - * if (done && status == 1) - * std::cout << "Connected to home network\n" - * \endcode - * element_type (&arr)[N] - */ - template - bool command (const string_view cmd, const string_view expected, clock_t timeout, Ts* ...values) { - - constexpr size_t Nr = sizeof...(Ts); - front>* vargs[Nr] = {values...}; - - if constexpr (Nr == 0) { (void)vargs; } - - char buffer[N], token[N]; - size_t v =0; - - transmit(cmd.data()); // send command - - for (auto ex = expected.begin() ; ex != expected.end() ; ) { - clock_t mark = clock(); // load the answer with timeout - size_t sz =0; - bool redo = false; do { - do { - sz = receive(buffer); - if ((timeout != 0 )&& ((clock() - mark) >= timeout)) - return false; - } while (!sz); - - redo = false; // have faith to parse successfully - for (size_t i=0 ; i (ex++, {&buffer[i], sz-i}, token); - if (!step) { - redo = true; // fail to parse, reload and retry - break; - } - if constexpr (Nr > 0) { - if (marker && v< Nr) - extract(token, vargs[v++]); - } - i += step; - } - } while (redo); - } - return true; - } - - /*! - * \brief - * inetd daemon functionality provided as member function of the driver. This should be running - * in the background either as consecutive calls from an periodic ISR with \c loop = false, or - * as a thread in an RTOS environment with \c loop = true. - * - * \tparam Nm The number of handler array entries - * - * \param async_handles Reference to asynchronous handler array - * \param loop Flag to indicate blocking mode. If true blocking. - */ - template - void inetd (bool loop =true, const inetd_handlers* inetd_handlers =nullptr) { - std::array buffer; - size_t resp_size; - do { - if ((resp_size = get_(buffer.data())) != 0) { - // on data check for async handlers - bool match = false; - if (inetd_handlers != nullptr) { - for (auto& h : *inetd_handlers) - match |= base_type::check_handle({buffer.data(), resp_size}, h.token, h.match, h.handler); - } - // if no match forward data to receive channel. - if (!match) { - char* it = buffer.data(); - do { - rx_q << *it; - } while (*it++ != delimiter); - streams_.fetch_add(1, std::memory_order_acq_rel); - } - } - } while (loop); - } - - //! @} - - private: - equeue rx_q{}; - std::atomic streams_{}; -}; - -} // namespace tbx; - -#endif /* #ifndef TBX_DRV_ATmodem_H_ */ diff --git a/include/drv/cli_device.h b/include/drv/cli_device.h new file mode 100644 index 0000000..2baac20 --- /dev/null +++ b/include/drv/cli_device.h @@ -0,0 +1,493 @@ +/*! + * \file drv/cli_device.h + * \brief + * command line device driver functionality as CRTP base class + * + * \copyright Copyright (C) 2021 Christos Choutouridis + * + *
License
+ * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *
+ */ + +#ifndef TBX_DRV_CLI_DEVICE_H_ +#define TBX_DRV_CLI_DEVICE_H_ + +#define __cplusplus 201703L + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace tbx { + +/*! + * \class cli_device + * \brief + * Its a base class for command-line based devices + * + * Inherits the sequencer functionality and provides a command interface for sending + * commands and parse the response. + * + * \example implementation example + * \code + * class BG95 : public cli_device { + * using base_type = cli_device; + * using Queue = equeue; + * Queue RxQ{}; + * std::atomic lines{}; + * public: + * // cli_device driver requirements + * BG95() noexcept : + * RxQ(Queue::data_match::MATCH_PUSH, base_type::delimiter, [&](){ + * lines.fetch_add(1, std::memory_order_acq_rel); + * }), lines(0) { } + * void feed(char x) { RxQ << x; } // To be used inside ISR + * size_t get(char* data, bool wait =false) { + * do { + * if (lines.load(std::memory_order_acquire)) { + * size_t n =0; + * do{ + * *data << RxQ; + * ++n; + * } while (*data++ != base_type::delimiter); + * lines.fetch_sub(1, std::memory_order_acq_rel); + * return n; + * } + * } while (wait); + * return 0; + * } + * size_t contents(char* data) { + * char* nullpos = std::copy(RxQ.begin(), RxQ.end(), data); + * *nullpos =0; + * return nullpos - data; + * } + * size_t put (const char* data, size_t n) { + * // send data to BG95 + * return n; + * } + * clock_t clock() noexcept { //return CPU time } + * }; + * \endcode + * + * \tparam Impl_t The type of derived class + * \tparam N The size of the queue buffer for the receive/command interface + * \tparam Delimiter The incoming data delimiter [default line buffered -- Delimiter = '\n'] + */ +template +class cli_device + : public sequencer, char, N>{ + _CRTP_IMPL(Impl_t); + + // local type dispatch + using base_type = sequencer; + + //! \name Public types + //! @{ + public: + using value_type = char; + using pointer_type = char*; + using size_type = size_t; + using string_view = typename base_type::string_view; + + using action_t = typename base_type::action_t; + using control_t = typename base_type::control_t; + using match_ft = typename base_type::match_ft; + using handler_ft = typename base_type::handler_ft; + template + using script_t = typename base_type::template script_t; + + //! Publish delimiter + constexpr static char delimiter = Delimiter; + + enum flush_type { keep =0, flush }; + + //! Required types for inetd async handler operation + //! @{ + + /*! + * inetd handler structure for asynchronous incoming data dispatching + */ + struct inetd_handler_t { + string_view token; //!< The token we match against + match_ft match; //!< The predicate we use to match + handler_ft handler; //!< The handler to call on match + }; + //! Alias template for the async handler array + template + using inetd_handlers = std::array; + //! @} + //! @} + + //! \name object lifetime + //!@{ + protected: + //!< \brief A default constructor from derived only + cli_device() noexcept = default; + ~cli_device () = default; //!< \brief Allow destructor from derived only + cli_device(const cli_device&) = delete; //!< No copies + cli_device& operator= (const cli_device&) = delete; //!< No copy assignments + //!@} + + //! \name Sequencer interface requirements + //! Forwarded to implementer the calls and cascade the the incoming channel + //! @{ + friend base_type; + + private: + size_t get_ (char* data) { + return impl().get (data); + } + size_t get (char* data) { + return receive (data); + } + size_t contents (char* data) { + return impl().contents(data); + } + size_t put (const char* data, size_t n) { + return impl().put (data, n); + } + clock_t clock () noexcept { + return impl().clock(); + } + //! @} + + //! \name Private functionality + //! @{ + private: + + //! typelist "container". A container of template parameter type arguments + template + struct typelist { + using type = typelist; //!< act as identity + }; + + //! front functionality: get the first type of the typelist "container" + template + struct front_impl { + using type = void; + }; + + template + struct front_impl> { + using type = Head; + }; + + //! Return the first element in \c typelist \c List. + //! + //! Complexity \f$ O(1) \f$. + template + using front = typename front_impl::type; + + /*! + * Convert the text pointed by \c str to a value and store it to + * \c value. The type of conversion is deduced by the compiler + * \tparam T The type of the value + * \param str pointer to string with the value + * \param value pointer to converted value + */ + template + void extract_ (const char* str, T* value) { + static_assert ( + std::is_same_v, int> + || std::is_same_v, double> + || std::is_same_v, char>, + "Not supported conversion type."); + if constexpr (std::is_same_v, int>) { + *value = std::atoi(str); + } else if (std::is_same_v, double>) { + *value = std::atof(str); + } else if (std::is_same_v, char>) { + std::strcpy(value, str); + } + } + //! Specialization (as overload function) to handle void* types + void extract_ (const char* str, void* value) noexcept { + (void)*str; (void)value; + } + + /*! + * Parse a chunk of the buffer based on \c expected character + * + * Tries to match the \c *expected character in buffer and if so it copies the + * character to token. + * If the \c *expected is the \c Marker character, copy the entire chunk of the buffer + * up to the character that matches the next expected character (expected[1]). + * If there is no next expected character or if its not found in the buffer, + * copy the entire buffer. + * + * \tparam Marker The special character to indicate chunk extraction + * + * \param expected The character to parse/remove from the buffer + * \param buffer The buffer we parse + * \param token Pointer to store the parsed tokens + * \return A (number of characters parsed, marker found) pair + */ + template + std::pair parse_ (const char* expected, const string_view buffer, char* token) { + do { + if (*expected == Marker) { + // We have Marker. Copy the entire chunk of the buffer + // up to the character that matches the next expected character (expected[1]). + // If there is none next expected character or if its not found in the buffer, + // copy the entire buffer. + auto next = std::find(buffer.begin(), buffer.end(), expected[1]); + char* nullpos = std::copy(buffer.begin(), next, token); + *nullpos =0; + return std::make_pair(next - buffer.begin(), true); + } + else if (*expected == buffer.front()) { + // We have character match, copy the character to token and return 1 (the char size) + *token++ = buffer.front(); + *token =0; + return std::make_pair(1, false); + } + } while (0); + + // Fail to parse + *token =0; + return std::make_pair(0, false); + } + + + /*! + * Analyze the response of a command based on \c expected. + * + * Tries to receive data with timeout and match them against expected string_view. + * For each Marker inside the expected string the value gets extracted, converted and + * copied to \c vargs pointer array. + * + * \param expected The expected string view + * \param timeout the timeout in CPU time + * \param vargs Pointer to variable arguments array + * \param nargs Size of variable arguments array + * \return + */ + template + bool response_ (const string_view expected, clock_t timeout, T* vargs, size_t nargs) { + char buffer[N], token[N], *pbuffer = buffer; + + size_t v =0, sz =0; + for (auto ex = expected.begin() ; ex != expected.end() ; ) { + clock_t mark = clock(); // mark the time + while (sz <= 0) { // if buffer is empty get buffer with timeout + sz = receive(buffer); + pbuffer = buffer; + if ((timeout != 0 )&& ((clock() - mark) >= timeout)) + return false; + } + // try to parse + auto [step, marker] = parse_ (ex, {pbuffer, sz}, token); + if (!step) + return false; // discard buffer and fail + + if (marker && v < nargs) + extract_(token, vargs[v++]); + + pbuffer += step; + sz -= (step <= sz) ? step: sz; + ++ex; + } + return true; + } + //! @} + + //! \name public functionality + //! @{ + public: + + /*! + * \brief + * Transmit data to modem + * \param data Pointer to data to send + * \param n The size of data buffer + * \return The number of transmitted chars + */ + size_t transmit (const char* data, size_t n) { + if (data == nullptr) + return 0; + return put (data, n); + } + + /*! + * \brief + * Transmit data to modem + * \param data Pointer to data to send + * \return The number of transmitted chars + */ + size_t transmit (const char* data) { + if (data == nullptr) + return 0; + return put (data, std::strlen(data)); + } + + /*! + * \brief + * Try to receive data from modem. If there are data copy them to \c data pointer and return + * the size. Otherwise return zero. In the case \c wait is true block until there are data to get. + * + * \param data Pointer to data buffer to write + * \param wait Flag to select blocking / non-blocking functionality + * \return The number of copied data. + */ + size_t receive (char* data, bool wait =false) { + do { + if (streams_.load(std::memory_order_acquire)) { + size_t n =0; + do { + *data << rx_q; + ++n; + } while (*data++ != delimiter); + *data =0; + streams_.fetch_sub(1, std::memory_order_acq_rel); + return n; + } + } while (wait); // on wait flag we block until available stream + return 0; + } + + //! Clears the incoming data buffer + void clear () noexcept { + rx_q.clear(); + } + + //! \return Returns the size of the incoming data buffer + size_t size() noexcept { + return rx_q.size(); + } + + /*! + * \brief + * Send a command to modem and check if the response matches to \c expected. + * + * This function executes 3 steps. + * - Clears the incoming buffer if requested by template parameter + * - Sends the command to device + * - Waits to get the response and parse it accordingly to \c expected \see response_() + * + * The user can mark spots inside the expected string using the \c Marker ['%'] character. + * These spots will be extracted to tokens upon parsing. If the user passes \c values parameters, + * then the extracted tokens will be converted to the type of the \c values (\c Ts) and copied to them + * one by one. If the values are less than spots, the rest of the tokens get discarded. + * + * \param cmd The command to send (null terminated) + * \param expected The expected response + * \param timeout The timeout in CPU time (leave it for 0 - no timeout) + * \param values The value pointer arguments to get the converted tokens + * + * \tparam Flush Flag to indicate if we flush the buffer before command or not + * \tparam Marker The marker character + * \tparam Ts The type of the values to read from response marked with \c Marker + * \warning The types MUST be the same + * + * \return True on success + * + * \example examples + * \code + * Derived cli; + * int status; + * char str[32]; + * + * // discard 3 lines and expect OK\r\n at the end with 1000[CPU time] timeout + * cli.command("AT+CREG?\r\n", "%%%OK\r\n", 1000); + * + * // extract a number from response without timeout (blocking) + * cli.command("AT+CREG?\r\n", "\r\n+CREG: 0,%\r\n\r\nOK\r\n", 0, &status); + * + * // extract a number and discard the last 2 lines + * cli.command("AT+CREG?\r\n", "\r\n+CREG: 0,%\r\n%%", 1000, &status); + * + * // discard first line, read the 2nd to str, discard the 3rd line. + * // expect the last to be "OK\r\n" + * cli.command("AT+CREG?\r\n", "", 100000); + * cli.command("", "%", 1000); + * cli.command("", "%%", 1000, str); + * cli.command("", "OK\r\n", 1000); + * \endcode + */ + template + bool command (const string_view cmd, const string_view expected, clock_t timeout, Ts* ...values) { + constexpr size_t Nr = sizeof...(Ts); + + front>* vargs[Nr] = {values...}; // read all args to local buffer + if constexpr (Flush == flush) { + clear (); + } + if (transmit(cmd.data(), cmd.size()) != cmd.size()) // send command + return false; + // parse the response and return the status + return response_(expected, timeout, vargs, Nr); + } + + /*! + * \brief + * inetd daemon functionality provided as member function of the driver. This should be running + * in the background either as consecutive calls from an periodic ISR with \c loop = false, or + * as a thread in an RTOS environment with \c loop = true. + * + * \tparam Nm The number of handler array entries + * + * \param async_handles Reference to asynchronous handler array + * \param loop Flag to indicate blocking mode. If true blocking. + */ + template + void inetd (bool loop =true, const inetd_handlers* inetd_handlers =nullptr) { + std::array buffer; + size_t resp_size; + do { + if ((resp_size = get_(buffer.data())) != 0) { + // on data check for async handlers + bool match = false; + if (inetd_handlers != nullptr) { + for (auto& h : *inetd_handlers) + match |= base_type::check_handle({buffer.data(), resp_size}, h.token, h.match, h.handler); + } + // if no match forward data to receive channel. + if (!match) { + char* it = buffer.data(); + do { + rx_q << *it; + } while (*it++ != delimiter); + streams_.fetch_add(1, std::memory_order_acq_rel); + } + } + } while (loop); + } + + //! @} + + private: + equeue rx_q{}; + std::atomic streams_{}; +}; + +} // namespace tbx; + +#endif /* #ifndef TBX_DRV_CLI_DEVICE_H_ */ diff --git a/test/Makefile b/test/Makefile index 157780b..05f7a68 100644 --- a/test/Makefile +++ b/test/Makefile @@ -107,7 +107,7 @@ OCOPY := objcopy # Compiler flags for debug and release DEB_CFLAGS := -std=gnu++17 -DDEBUG -g3 -Wall -Wextra -fmessage-length=0 -REL_CFLAGS := -std=gnu++17 -g3 -Wall -Wextra -O2 -fmessage-length=0 +REL_CFLAGS := -std=gnu++17 -Wall -Wextra -O2 -fmessage-length=0 # Pre-defines PRE_DEFS := ifeq ($(OS), Windows_NT) @@ -223,13 +223,15 @@ debug: $(BUILD_DIR)/$(TARGET) .PHONY: test_asan -test_asan: CFLAGS += -fsanitize=address -fsanitize=leak -fsanitize=bounds-strict +test_asan: CFLAGS := $(REL_CFLAGS) +test_asan: CFLAGS += -g3 -fsanitize=address -fsanitize=leak -fsanitize=bounds-strict test_asan: LDFLAGS += -fsanitize=address -fsanitize=leak -fsanitize=bounds-strict test_asan: $(BUILD_DIR)/$(TARGET) .PHONY: test_tsan -test_tsan: CFLAGS += -fsanitize=thread +test_asan: CFLAGS := $(REL_CFLAGS) +test_tsan: CFLAGS += -g3 -fsanitize=thread test_tsan: LDFLAGS += -fsanitize=thread test_tsan: $(BUILD_DIR)/$(TARGET) diff --git a/test/tests/ATmodem.cpp b/test/tests/cli_device.cpp similarity index 59% rename from test/tests/ATmodem.cpp rename to test/tests/cli_device.cpp index 3436fd2..f60e5f2 100644 --- a/test/tests/ATmodem.cpp +++ b/test/tests/cli_device.cpp @@ -1,5 +1,5 @@ /*! - * \file ATmodem.cpp + * \file cli_device.cpp * * \copyright Copyright (C) 2020 Christos Choutouridis * @@ -26,7 +26,7 @@ * * */ -#include +#include #include #include @@ -42,17 +42,17 @@ #include #endif -namespace test_ATmodem { +namespace test_cli_device { using namespace tbx; // test settings constexpr size_t Size = 128; using data_type = char; - // BG95 implementer mock + // cli_device implementer mock. We simulate a BG95 ATmodem for that purpose template - class BG95 : public ATmodem, N> { - using base_type = ATmodem, N>; + class BG95 : public cli_device, N> { + using base_type = cli_device, N>; using Queue = equeue; public: @@ -70,7 +70,8 @@ namespace test_ATmodem { const char* resp; }; - std::array cmd_map = {{ + std::array cmd_map = {{ + {"", ""}, {"ERROR", "\r\nERROR\r\n"}, {"ATE0\r\n", "\r\nATE0\r\nOK\r\n"}, {"AT\r\n", "\r\nOK\r\n"}, @@ -99,11 +100,13 @@ namespace test_ATmodem { {event::MQTT_RXDATA, "\r\n+QMTRECV: 0,1,\"/path/topic1\",\"BR: hello to all of my subscribers\""} }}; const char* cmd_responce (const char* cmd) { - for (auto& it : cmd_map) { - if (!strcmp(it.cmd, cmd)) - return it.resp; + if (cmd != nullptr) { + for (auto& it : cmd_map) { + if (!std::strcmp(it.cmd, cmd)) + return it.resp; + } } - return cmd_map[0].resp; + return cmd_map[1].resp; } const char* event_responce (const event e) { for (auto& it : event_map) { @@ -114,13 +117,14 @@ namespace test_ATmodem { } // data - Queue rx_q{}; + Queue RxQ{}; std::atomic lines{}; + clock_t t=0; public: - // ATmodem driver requirements + // cli_device driver requirements BG95() noexcept : - rx_q(Queue::data_match::MATCH_PUSH, base_type::delimiter, [&](){ + RxQ(Queue::data_match::MATCH_PUSH, base_type::delimiter, [&](){ lines.fetch_add(1, std::memory_order_acq_rel); }), lines(0) { } @@ -129,7 +133,7 @@ namespace test_ATmodem { if (lines.load(std::memory_order_acquire)) { size_t n =0; do{ - *data << rx_q; + *data << RxQ; ++n; } while (*data++ != base_type::delimiter); lines.fetch_sub(1, std::memory_order_acq_rel); @@ -139,24 +143,25 @@ namespace test_ATmodem { return 0; } size_t contents(char* data) { - char* nullpos = std::copy(rx_q.begin(), rx_q.end(), data); + char* nullpos = std::copy(RxQ.begin(), RxQ.end(), data); *nullpos =0; return nullpos - data; } size_t put (const char* data, size_t n) { const char* reply = cmd_responce (data); while (*reply) - rx_q << *reply++; + RxQ << *reply++; return n; } - clock_t clock() { static clock_t t=0; return ++t; } + clock_t clock() noexcept { return ++t; } + void clear_clock() noexcept { t=0; } // extra helper for testing purposes void async (event e) { const char* reply =event_responce (e); while (*reply) - rx_q << *reply++; + RxQ << *reply++; } }; @@ -171,7 +176,7 @@ namespace test_ATmodem { handler_flag = false; } - TEST(TATmodem, traits) { + TEST(Tcli_device, traits) { EXPECT_EQ ( std::is_default_constructible>::value, true); EXPECT_EQ ( std::is_nothrow_default_constructible>::value, true); EXPECT_EQ (!std::is_copy_constructible>::value, true); @@ -182,22 +187,28 @@ namespace test_ATmodem { EXPECT_EQ ((std::is_same_v::size_type, size_t>), true); EXPECT_EQ ((std::is_same_v::string_view, std::basic_string_view>), true); - BG95 m; - EXPECT_EQ (m.size(), Size); } /* * Test inetd in non blocking mode */ - TEST(TATmodem, inetd_non_blocking) { + TEST(Tcli_device, txrx_inetd) { BG95 modem; char buffer[Size]; + size_t s =0; const BG95::inetd_handlers<2> async = {{ {"+QMTSTAT:", BG95::starts_with, handler}, {"+QMTRECV", BG95::contains, handler}, }}; + s = modem.transmit("", std::strlen("")); + EXPECT_EQ (s, 0UL); + s = modem.transmit(""); + EXPECT_EQ (s, 0UL); + s = modem.transmit(nullptr); + EXPECT_EQ (s, 0UL); + clear_flag(); modem.inetd(false, &async); EXPECT_EQ (handler_flag, false); @@ -212,7 +223,7 @@ namespace test_ATmodem { modem.inetd(false, &async); EXPECT_EQ (handler_flag, false); EXPECT_NE (modem.receive(buffer), 0UL); // "\r\n" in buffer - EXPECT_EQ (strcmp(buffer, "\r\n"), 0); + EXPECT_EQ (std::strcmp(buffer, "\r\n"), 0); clear_flag(); modem.inetd(false, &async); @@ -221,31 +232,31 @@ namespace test_ATmodem { EXPECT_EQ (modem.receive(buffer), 0UL); modem.inetd(false, &async); // parse "\r\n" EXPECT_NE (modem.receive(buffer), 0UL); - EXPECT_EQ (strcmp(buffer, "\r\n"), 0); + EXPECT_EQ (std::strcmp(buffer, "\r\n"), 0); modem.inetd(false, &async); // parse "+CSQ: 19,99\r\n" EXPECT_NE (modem.receive(buffer), 0UL); - EXPECT_EQ (strcmp(buffer, "+CSQ: 19,99\r\n"), 0); + EXPECT_EQ (std::strcmp(buffer, "+CSQ: 19,99\r\n"), 0); modem.inetd(false, &async); // parse "\r\n" EXPECT_NE (modem.receive(buffer), 0UL); - EXPECT_EQ (strcmp(buffer, "\r\n"), 0); + EXPECT_EQ (std::strcmp(buffer, "\r\n"), 0); modem.inetd(false, &async); // parse "OK\r\n" EXPECT_NE (modem.receive(buffer), 0UL); - EXPECT_EQ (strcmp(buffer, "OK\r\n"), 0); + EXPECT_EQ (std::strcmp(buffer, "OK\r\n"), 0); modem.inetd(false, &async); // nothing to parse modem.inetd(false, &async); modem.inetd(false, &async); EXPECT_EQ (modem.receive(buffer), 0UL); } - TEST(TATmodem, run) { + TEST(Tcli_device, run) { BG95 modem; using Control = BG95::control_t; const BG95::script_t<4> script = {{ - {Control::NOP, "", BG95::nil, BG95::nil, BG95::go_to<1>, 1000}, + {Control::NOP, "", BG95::nil, BG95::nil, BG95::go_to<1>, 100000}, {Control::SEND, "ATE0\r\n", BG95::nil, BG95::nil, BG95::next, 0}, - {Control::EXPECT, "OK\r\n", BG95::ends_with, BG95::nil, BG95::exit_ok, 1000}, + {Control::EXPECT, "OK\r\n", BG95::ends_with, BG95::nil, BG95::exit_ok, 100000}, {Control::OR_EXPECT,"ERROR", BG95::contains, BG95::nil, BG95::exit_error, 0} }}; @@ -262,12 +273,26 @@ namespace test_ATmodem { th1.join(); } - TEST(TATmodem, transmit) { + TEST(Tcli_device, clear_size) { + BG95 modem; + char buffer[Size]; + + modem.clear(); + EXPECT_EQ (modem.size(), 0UL); + EXPECT_EQ (modem.receive(buffer), 0UL); + + modem.transmit("abcd", 4); + modem.inetd(false); + modem.inetd(false); + EXPECT_NE (modem.size(), 0UL); + modem.clear(); + EXPECT_EQ (modem.size(), 0UL); } - TEST(TATmodem, command) { + TEST(Tcli_device, command_non_extraction) { BG95 modem; + char buffer[Size]; std::mutex m; m.lock(); @@ -278,24 +303,117 @@ namespace test_ATmodem { m.unlock(); }); - EXPECT_EQ (modem.command("AT\r\n", "Something else", 1000), false); - EXPECT_EQ (modem.command("AT\r\n", "\r\nOK\r\n", 1000), true); + auto run_receive = [&](size_t times) -> size_t { + size_t s =0; + for (size_t i=0 ; i::flush>("AT+CREG?\r\n", "%OK\r\n", 0), false); + EXPECT_GT (run_receive(100000), 0UL); + + // returns: "\r\n+CREG: 0,5\r\n\r\nOK\r\n + EXPECT_EQ (modem.command::flush>("AT+CREG?\r\n", "%%%OK\r\n", 0), true); + EXPECT_EQ (modem.size(), 0UL); + EXPECT_EQ (run_receive(100000), 0UL); + + // returns: "\r\n+CREG: 0,5\r\n\r\nOK\r\n + EXPECT_EQ (modem.command::flush>("AT+CREG?\r\n", "%", 0), true); + EXPECT_GT (run_receive(100000), 0UL); + + EXPECT_EQ (modem.command::flush>("AT\r\n", "%%", 0), true); // returns: "\r\nOK\r\n" + EXPECT_EQ (modem.size(), 0UL); + EXPECT_EQ (run_receive(100000), 0UL); + + EXPECT_EQ (modem.command::flush>("AT\r\n", "%%%", 10000), false); // returns: "\r\nOK\r\n" + EXPECT_EQ (modem.size(), 0UL); + EXPECT_EQ (run_receive(100000), 0UL); + + // returns: "\r\n+CREG: 0,5\r\n\r\nOK\r\n + EXPECT_EQ (modem.command("AT+CREG?\r\n", "", 0), true); + EXPECT_EQ (modem.command("", "%", 0), true); + EXPECT_EQ (modem.command("", "%%", 0), true); + EXPECT_EQ (modem.command("", "%", 0), true); + EXPECT_EQ (modem.command("", "%", 10000), false); + EXPECT_EQ (modem.size(), 0UL); + EXPECT_EQ (run_receive(100000), 0UL); + + m.unlock(); // stop and join inetd + th1.join(); + } + + TEST(Tcli_device, command_extraction) { + BG95 modem; + char buffer[Size]; + + std::mutex m; + m.lock(); + std::thread th1 ([&](){ + do + modem.inetd(false); + while (!m.try_lock()); + m.unlock(); + }); - EXPECT_EQ (modem.command("AT\r\n", "%OK\r\n", 1000), true); - EXPECT_EQ (modem.command("AT+CREG?\r\n", "%OK\r\n", 1000), false); - EXPECT_EQ (modem.command("AT+CREG?\r\n", "%%%OK\r\n", 1000), true); + auto run_receive = [&](size_t times) -> size_t { + size_t s =0; + for (size_t i=0 ; i("AT+CREG?\r\n", "", 100000), true); + EXPECT_EQ (modem.command("", "%", 100000, substr1), true); + EXPECT_EQ (std::strcmp("\r\n", substr1), 0); + + EXPECT_EQ (modem.command("", "%%", 100000, substr1, substr2), true); + EXPECT_EQ (std::strcmp("+CREG: 0,5\r\n", substr1), 0); + EXPECT_EQ (std::strcmp("\r\n", substr2), 0); + + EXPECT_EQ (modem.command("", "%", 100000, substr1), true); + EXPECT_EQ (std::strcmp("OK\r\n", substr1), 0); + + EXPECT_EQ (modem.command("", "%", 10000), false); + + EXPECT_EQ (modem.size(), 0UL); + EXPECT_EQ (run_receive(100000), 0UL); m.unlock(); // stop and join inetd th1.join();