diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a3a24df --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "test/mingw-std-threads"] + path = test/mingw-std-threads + url = https://github.com/meganz/mingw-std-threads.git diff --git a/include/drv/BG95_base.h b/include/drv/BG95_base.h new file mode 100644 index 0000000..1c94318 --- /dev/null +++ b/include/drv/BG95_base.h @@ -0,0 +1,321 @@ +/*! + * \file cont/BG95_base.h + * \brief + * BG95 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_BG95_base_H_ +#define TBX_DRV_BG95_base_H_ + + +#include +#include +#include +#include +#include + +#include +#include +#include + +//#include +#include + +namespace tbx { + +/*! + * \class BG95_base + * \brief + * + * \example implementation example + * \code + * using Queue = equeue; + * + * class BG95 : + * public BG95_base { + * using base_type = BG95_base; + * using range_t = typename Queue::range_t; + * private: // data + * Queue rx_q{}; + * std::atomic lines{}; + * public: + * BG95() : + * rx_q(equeue::data_match::MATCH_PUSH, base_type::delimiter, [&](){ + * lines.fetch_add(1, std::memory_order_acq_rel); + * }), lines(0) { + * // init code here ... + * } + * // ISR handler + * void usart_isr_ (void) { + * rx_q << // char from ISR + * } + * // CRTP requirements + * size_t get(char* data, bool wait =false) { + * do { + * if (lines.load(std::memory_order_acquire)) { + * size_t n =0; + * do{ + * *data << rx_q; + * ++n; + * } while (*data++ != base_type::delimiter); + * lines.fetch_sub(1, std::memory_order_acq_rel); + * return n; + * } + * } while (wait); + * return 0; + * } + * size_t put (const char* data, size_t n) { + * // send data to UART + * return n; + * } + * const range_t contents() const { return range_t {rx_q.begin(), rx_q.end()}; } + * clock_t clock() { } // return systems CPU time + * }; + * \endcode + * + * \tparam Impl_t + * \tparam Cont_t + * \tparam N + * \tparam Delimiter + */ +template +class BG95_base + : public sequencer, Cont_t, char, N>{ + _CRTP_IMPL(Impl_t); + + static_assert( + std::is_same_v, + "Cont_t must be a container of type char" + ); + + // local type dispatch + using base_type = sequencer; + using str_view_t = typename base_type::str_view_t; + using range_t = typename Cont_t::range_t; + using status_t = typename base_type::status_t; + + //! \name Public types + //! @{ + public: + using action_t = typename base_type::action_t; + using control_t = typename base_type::control_t; + using match_t = typename base_type::match_t; + using handler_t = typename base_type::handler_ft; + template + using script_t = typename base_type::template script_t; + + //! Publish delimiter + constexpr static char delimiter = Delimiter; + + //! Required typenames for async operation + //! @{ + template + using async_handlers = std::array; + //! @} + //! @} + + //! \name Constructor / Destructor + //!@{ + protected: + //!< \brief A default constructor from derived only + BG95_base() = default; + ~BG95_base () = default; //!< \brief Allow destructor from derived only + BG95_base(const BG95_base&) = delete; //!< No copies + BG95_base& operator= (const BG95_base&) = 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); + } + const range_t contents () const { + return impl().contents(); + } + clock_t clock () { + return impl().clock(); + } + //! @} + + //! \name Private functionality + //! @{ + private: + + //! @} + + //! \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 + * 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 async_handlers* async_handles =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 (async_handles != nullptr) { + for (auto& h : *async_handles) + match |= base_type::check_handle (h, buffer.data()); + } + // 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); + } + + template + bool configure (const script_t& script) { + return base_type::run (script); + } + +// // General API +// static constexpr typename base_type::handle_t error_handle_ = { +// "ERROR", match_t::CONTAINS, nullptr, action_t::EXIT_ERROR, 0 +// }; + + template + void parse (char* str, size_t n, char next, T value) { + auto next_ptr = std::find(str, &str[n], next); + char save = *next_ptr; + *next_ptr =0; + + if constexpr (std::is_same_v, int>) { + sscanf(str, "%d", &value); + } else if (std::is_same_v, float>) { + sscanf(str, "%f", &value); + } else if (std::is_same_v, double>) { + sscanf(str, "%lf", &value); + } else if (std::is_same_v, char>) { + sscanf(str, "%c", &value); + } else if (std::is_same_v, char*>) { + strcpy(value, str); + } + *next_ptr = save; + } + + // cmd: "AT+CREG?" + // expected: "\r\n+CREG: 0,%\r\nOK\r\n" + template + bool command (const str_view_t cmd, const str_view_t expected, T& t) { + char buffer[N]; + + transmit(cmd); + + for (size_t pos =0 ; pos < expected.size(); ) { + str_view_t ex = expected.substr(pos); // get starting point of expected + size_t sz = receive(buffer, 1); // load the answer + for (size_t i ; i rx_q{}; + std::atomic streams_{}; +}; + +} // namespace tbx; + +#endif /* TBX_DRV_BG95_base_H_ */ diff --git a/include/utils/sequencer.h b/include/utils/sequencer.h index 5e3fff9..bbe8363 100644 --- a/include/utils/sequencer.h +++ b/include/utils/sequencer.h @@ -34,14 +34,19 @@ #include #include +#include + #include #include #include +#include +#include +#include namespace tbx { /*! - * \class sequencer_t + * \class sequencer * \brief * A CRTP base class to provide the sequencer functionality. * @@ -59,14 +64,26 @@ namespace tbx { * match the units in \c record_t::timeout field. * * \tparam Impl_t The type of derived class + * \tparam Cont_t The container type holding the data of type \c Data_t for the derived class. * \tparam Data_t The char-like stream item type. Usually \c char * \tparam N The size of the sequence buffer to temporary store each line from get(). + * + * \note + * We need access to derived class container to sneaky get a range of the data beside + * the normal data flow, in order to implement the \see control_t::DETECT operation. */ -template -class sequencer_t { +template +class sequencer { _CRTP_IMPL(Impl_t); - using str_view_t = std::basic_string_view; + static_assert( + std::is_same_v, + "Cont_t must be a container of type Data_t" + ); + + // local type dispatch + using str_view_t = std::basic_string_view; + using range_t = typename Cont_t::range_t; //! \name Public types //! @{ @@ -88,8 +105,18 @@ class sequencer_t { //! \brief The control type of the script entry. enum class control_t { NOP, //!< No command, dont send or expect anything, used for delays - SEND, //!< Send data to implementation - EXPECT //!< Expects data from implementation + SEND, //!< Send data to implementation through put() + EXPECT, //!< Expects data from implementation via get() + DETECT //!< Detects data into rx buffer without receiving them via contents() + + //! \note + //! The \c DETECT extra incoming channel serve the purpose of sneak into receive + //! buffer and check for data without getting them. This is useful when the receive driver + //! is buffered with a delimiter and we seek for data that don't follow the delimiter pattern. + //! + //! For example: + //! A modem sends reponses with '\n' termination but for some "special" command it opens a cursor + //! lets say ">$ " without '\n' at the end. }; //! \enum match_t @@ -102,18 +129,18 @@ class sequencer_t { * Match handler function pointer type. * Expects a pointer to buffer and a size and returns status */ - using handler_ft = status_t (*) (const Data_t*, size_t); + using handler_ft = void (*) (const Data_t*, size_t); /*! - * \struct block_t + * \struct handle_t * \brief - * The script line block. + * The script record handle block. * - * Each script "line" contains up to 2 blocks for matching functionality. Each block + * Each script record contains some blocks for matching functionality. Each block * has a token and a matching type. If the response matches the token, the sequencer calls * the handler and perform the action. */ - struct block_t { + struct handle_t { std::basic_string_view token; //!< The token for the match match_t match_type; //!< The matching type functionality @@ -129,6 +156,19 @@ class sequencer_t { * * Each line consist from a control, 2 blocks and a timeout. The control says if we send or receive data. * The blocks contain the data and the matching information. And the timeout guards the entire line. + */ + template + struct record_t { + control_t control; //!< The type of the entry + std::array + block; //!< The matching blocks + clock_t timeout; //!< Timeout in CPU time + }; + + /*! + * \struct script_t + * \brief + * Describes the sequencer's script. * * The user can create arrays as the example bellow to act as a script. * \code @@ -144,11 +184,8 @@ class sequencer_t { * }}; * \endcode */ - struct record_t { - control_t control; //!< The type of the entry - std::array block; //!< The matching block - clock_t timeout; //!< Timeout in CPU time - }; + template + using script_t = std::array, Nrecords>; //! @} @@ -156,10 +193,10 @@ class sequencer_t { //! \name Constructor / Destructor //!@{ protected: - ~sequencer_t () = default; //!< \brief Allow destructor from derived only - sequencer_t () = default; //!< \brief A default constructor from derived only - sequencer_t(const sequencer_t&) = delete; //!< No copies - sequencer_t& operator= (const sequencer_t&) = delete; //!< No copy assignments + ~sequencer () = default; //!< \brief Allow destructor from derived only + sequencer () = default; //!< \brief A default constructor from derived only + sequencer(const sequencer&) = delete; //!< No copies + sequencer& operator= (const sequencer&) = delete; //!< No copy assignments //!@} //! \name Sequencer interface requirements for implementer @@ -167,6 +204,7 @@ class sequencer_t { private: size_t get_ (Data_t* data) { return impl().get (data); } size_t put_ (const Data_t* data, size_t n) { return impl().put (data, n); } + const range_t contents_ () const { return impl().contents(); } clock_t clock_ () { return impl().clock(); } //! @} @@ -180,7 +218,7 @@ class sequencer_t { * \param prefix What we search * \return True on success, false otherwise */ - bool starts_with_ (const str_view_t stream, const str_view_t prefix) { + static bool starts_with_ (const str_view_t stream, const str_view_t prefix) { return (stream.rfind(prefix, 0) != str_view_t::npos); } @@ -191,7 +229,9 @@ class sequencer_t { * \param postfix What we search * \return True on success, false otherwise */ - bool ends_with_ (const str_view_t stream, const str_view_t postfix) { + static bool ends_with_ (const str_view_t stream, const str_view_t postfix) { + if (stream.size() < postfix.size()) + return false; return ( stream.compare( stream.size() - postfix.size(), @@ -207,7 +247,7 @@ class sequencer_t { * \param needle What we search * \return True on success, false otherwise */ - bool contains_ (const str_view_t haystack, const str_view_t needle) { + static bool contains_ (const str_view_t haystack, const str_view_t needle) { return (haystack.find(needle) != str_view_t::npos); } /*! @@ -221,7 +261,7 @@ class sequencer_t { * \param go_idx The new value of the step in the case of GOTO type * \return The new sequencer's step value */ - size_t step_ (size_t current_idx, action_t action, size_t go_idx =0) { + static size_t step_ (size_t current_idx, action_t action, size_t go_idx =0) { switch (action) { default: case action_t::NO: return current_idx; @@ -232,6 +272,27 @@ class sequencer_t { return 0; } } + static status_t action_ (size_t& step, const handle_t& block, const str_view_t buffer = str_view_t{}) { + if (block.handler != nullptr) + block.handler(buffer.begin(), buffer.size()); + switch (block.action) { + case action_t::EXIT_OK: + step = std::numeric_limits::max(); + return status_t::OK; + case action_t::EXIT_ERROR: + step = std::numeric_limits::max(); + return status_t::ERROR; + default: + step = step_(step, block.action, block.idx); + return status_t::OK; + } + + } + + //! @} + + + public: /*! * \brief * Checks if the \c needle matches the \c haystack. @@ -241,7 +302,7 @@ class sequencer_t { * \param needle The stream we search * \return True on match */ - bool match_(match_t type, const str_view_t haystack, const str_view_t needle) { + static bool match (const str_view_t haystack, const str_view_t needle, match_t type) { switch (type) { default: case match_t::NO: return true; @@ -253,10 +314,25 @@ class sequencer_t { case match_t::nCONTAINS: return !contains_(haystack, needle); } } - //! @} + /*! + * \brief + * A static functionality to provide access to sequencer's inner matching mechanism. + * Checks the \c buffer against \c handle and calls its action if needed. + * + * \param handle Reference to handle + * \param buffer The buffer to check + * \return True on match, false otherwise + */ + static bool check_handle (const handle_t& handle, const str_view_t buffer) { + size_t tmp{}; + if (match(buffer, handle.token, handle.match_type)) { + action_ (tmp, handle, buffer); + return true; + } + return false; + } - public: /*! * \brief * Run the script array @@ -264,28 +340,38 @@ class sequencer_t { * The main sequencer functionality. It starts with the first entry of the array. * - If the entry is \c NOP it executes the action after the timeout. * \c token and \c handler are discarded. - * - If the entry is \c SEND it uses the first block's token to send and executes the action after that. + * - If the entry is \c SEND it uses the first handle block's token to send and executes the action after that. * \c timeout is discarded. * - If the entry is \c EXCEPTS it continuously try to receive data using implementation's get until one - * of the blocks match. + * of the handle blocks match. + * On match: + * - Calls the handler if there is one + * - Executes the action + * - Skips the next handle blocks if there is any. + * If there is no match on timeout it return status_t::EXIT_ERROR + * - If the entry is \c DETECT it continuously try to detect data using implementation's contents until one + * of the handle blocks match. * On match: * - Calls the handler if there is one * - Executes the action - * - Skips the next block if there is one + * - Skips the next handle blocks if there is any. * If there is no match on timeout it return status_t::EXIT_ERROR * * \tparam Steps The number of steps of the script + * \tparam Nhandles The number of handle blocks in the each script record. + * * \param script Reference to script to run * \return The status of entire operation as described above */ - template - status_t run (const std::array& script) { + template + bool run (const script_t& script) { Data_t buffer[N]; - size_t resp_size; + size_t resp_size{}; + status_t status{}; clock_t mark = clock_(); for (size_t step =0, p_step =0 ; step < Steps ; ) { - const record_t& it = script[step]; + const record_t& it = script[step]; if (step != p_step) { p_step = step; @@ -294,64 +380,63 @@ class sequencer_t { switch (it.control) { default: case control_t::NOP: - if ((clock_() - mark) >= it.timeout) { - switch (it.block[0].action) { - case action_t::EXIT_OK: return status_t::OK; - case action_t::EXIT_ERROR: return status_t::ERROR; - default: - step = step_(step, it.block[0].action, it.block[0].idx); - break; - } - } + if ((clock_() - mark) >= it.timeout) + status = action_ (step, it.block[0]); break; case control_t::SEND: - put_(it.block[0].token.data(), it.block[0].token.size()); - switch (it.block[0].action) { - case action_t::EXIT_OK: return status_t::OK; - case action_t::EXIT_ERROR: return status_t::ERROR; - default: - step = step_(step, it.block[0].action, it.block[0].idx); - break; - } + if (put_(it.block[0].token.data(), it.block[0].token.size()) != it.block[0].token.size()) + return false; + status = action_ (step, it.block[0]); break; case control_t::EXPECT: resp_size = get_(buffer); if (resp_size) { for (auto& block : it.block) { - if (match_(block.match_type, buffer, block.token)) { - if (block.handler != nullptr) - block.handler(buffer, resp_size); - switch (block.action) { - case action_t::EXIT_OK: return status_t::OK; - case action_t::EXIT_ERROR: return status_t::ERROR; - default: - step = step_(step, block.action, block.idx); - break; - } + if (match( + {buffer, resp_size}, + block.token, + block.match_type)) { + status = action_ (step, block, {buffer, resp_size}); break; } } } - if ((clock_() - mark) >= it.timeout) - return status_t::ERROR; + if (it.timeout && (clock_() - mark) >= it.timeout) + return false; + break; + case control_t::DETECT: + auto data = contents_(); + if (data.begin() != data.end()) { + for (auto& block : it.block) { + if (match( + {data.begin(), static_cast(data.end() - data.begin())}, + block.token, + block.match_type)) { + status = action_ (step, block, {buffer, resp_size}); + break; + } + } + } + if (it.timeout && (clock_() - mark) >= it.timeout) + return false; break; } // switch (it.control) } - return status_t::OK; + return (status == status_t::OK); } }; /*! * An "empty" block for convenience. */ -template -constexpr typename sequencer_t::block_t Sequencer_null_block = { +template +constexpr typename sequencer::handle_t Sequencer_null_block = { "", - sequencer_t::match_t::NO, + sequencer::match_t::NO, nullptr, - sequencer_t::action_t::NO, + sequencer::action_t::NO, 0 }; diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..de9823e --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,8 @@ +# Binaries +bin/ +# Eclipse related +/Debug/ +.settings/ +.project +.cproject + diff --git a/test/Makefile b/test/Makefile index 91fecd1..396620a 100644 --- a/test/Makefile +++ b/test/Makefile @@ -85,7 +85,9 @@ SRC_FILES_LIST := # Include directories list(space seperated). Relative path INC_DIR_LIST := ../include gtest - +ifeq ($(OS), Windows_NT) + INC_DIR_LIST += mingw-std-threads +endif # Exclude files list(space seperated). Filenames only. # EXC_FILE_LIST := bad.cpp old.cpp @@ -104,10 +106,13 @@ ODUMP := objdump OCOPY := objcopy # Compiler flags for debug and release -DEB_CFLAGS := -std=c++17 -DDEBUG -g3 -Wall -Wextra -REL_CFLAGS := -std=c++17 -Wall -Wextra -O2 +DEB_CFLAGS := -std=gnu++17 -DDEBUG -g3 -Wall -Wextra -fmessage-length=0 +REL_CFLAGS := -std=gnu++17 -g3 -Wall -Wextra -O2 -fmessage-length=0 # Pre-defines -# PRE_DEFS := MYCAB=1729 SUPER_MODE +PRE_DEFS := +ifeq ($(OS), Windows_NT) + PRE_DEFS += WIN_TRHEADS +endif # ============== Linker settings ============== # Linker flags @@ -183,8 +188,8 @@ $(BUILD_DIR)/$(TARGET): $(OBJ) @mkdir -p $(@D) @echo Linking to target: $(TARGET) $(DOCKER) $(CXX) $(LDFLAGS) $(MAP_FLAG) -o $(@D)/$(TARGET) $(OBJ) - $(DOCKER) $(ODUMP) -h -S $(BUILD_DIR)/$(TARGET) > $(BUILD_DIR)/$(basename $(TARGET)).list - $(DOCKER) $(OCOPY) -O ihex $(BUILD_DIR)/$(TARGET) $(BUILD_DIR)/$(basename $(TARGET)).hex + $(DOCKER) $(ODUMP) -h -S $(BUILD_DIR)/$(TARGET) > $(BUILD_DIR)/$(basename $(TARGET)).list +# $(DOCKER) $(OCOPY) -O ihex $(BUILD_DIR)/$(TARGET) $(BUILD_DIR)/$(basename $(TARGET)).hex @echo @echo Print size information @$(CSIZE) $(@D)/$(TARGET) @@ -217,8 +222,8 @@ build-clang: $(BUILD_DIR)/$(TARGET) debug: $(BUILD_DIR)/$(TARGET) .PHONY: release -release: CFLAGS := $(REL_FLAGS) -release: clean $(BUILD_DIR)/$(TARGET) +release: CFLAGS := $(REL_CFLAGS) +release: $(BUILD_DIR)/$(TARGET) .PHONY: all all: clean release diff --git a/test/main.cpp b/test/main.cpp index eaad5da..0c399ad 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -26,9 +26,191 @@ * */ #include +#include -GTEST_API_ int main(int argc, char **argv) { +//#include +//#include +//#include +////#include +// +////#include +//#include +//#include +// +//#ifndef WIN_TRHEADS +//#include +//#include +//#else +//#include +//#include +//#endif +// +//using namespace tbx; +// +//using Q = equeue; +// +//// BG95 implementer mock +//class BG95 : public BG95_base { +// using base_type = BG95_base; +// +// public: +// enum class event { +// MQTT_DISCONNECT, MQTT_RXDATA +// }; +// // simulated modem operation +// private: +// struct cmd_pair { +// const char *cmd; +// const char *resp; +// }; +// struct event_pair { +// event e; +// const char* resp; +// }; +// +// 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"}, +// {"AT+QCFG=\"nwscanseq\"\r\n", "\r\n+QCFG: \"nwscanseq\",020301\r\n"}, +// {"AT+QCFG=\"nwscanseq\",010302\r\n", "\r\nOK\r\n"}, +// {"AT+CREG?\r\n", "\r\n+CREG: 0,5\r\n\r\nOK\r\n"}, +// {"AT+CSQ\r\n", "\r\n+CSQ: 19,99\r\n\r\nOK\r\n"}, +// {"AT+QNWINFO\r\n", "\r\n+QNWINFO: \"EDGE\",\"20201\",\"GSM 1800\",865\r\n\r\nOK\r\n"}, +// // Files +// {"AT+QFLST\r\n", "\r\n+QFLST: \"cacert.pem\",1220\r\n+QFLST: \"security/\",2\r\nOK\r\n"}, +// // MQTT config +// {"AT+QSSLCFG=\"ignorelocaltime\",2,1\r\n", "\r\nOK\r\n"}, +// {"AT+QSSLCFG=\"seclevel\",2,1\r\n", "\r\nOK\r\n"}, +// {"AT+QSSLCFG=\"sslversion\",2,4\r\n", "\r\nOK\r\n"}, +// {"AT+QSSLCFG=\"ciphersuite\",2\r\n", "\r\n+QSSLCFG: \"ciphersuite\",2,0XFFFF\r\n\r\nOK\r\n"}, +// {"AT+QMTCFG=\"ssl\",0,1,2\r\n", "\r\nOK\r\n"}, +// {"AT+QMTCFG=\"keepalive\",0,3600\r\n", "\r\nOK\r\n"}, +// // MQTT +// {"AT+QMTOPEN=0,\"server.com\",8883\r\n", "\r\nOK\r\n\r\n+QMTOPEN: 0,0\r\n"}, +// {"AT+QMTCONN=0,\"myID\",\"user\",\"pass\"\r\n", "\r\nOK\r\n\r\n+QMTCONN: 0,0,0\r\n"}, +// {"AT+QMTSUB=0,1,\"/path/topic1\",2\r\n", "\r\nOK\r\n\r\n+QMTSUB: 0,1,0,2\r\n"}, +// {"AT+QMTPUB=0,0,0,0,\"/path/topic2\",9\r\n", "\r\n> \r\nOK\r\n\r\n+QMTPUB: 0,0,0\r\n"}, +// }}; +// std::array event_map {{ +// {event::MQTT_DISCONNECT, "\r\n+QMTSTAT: 0,1\r\n"}, +// {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; +// } +// return cmd_map[0].resp; +// } +// const char* event_responce (const event e) { +// for (auto& it : event_map) { +// if (e == it.e) +// return it.resp; +// } +// return nullptr; // non reachable +// } +// +// // data +// Q rx_q{}; +// std::atomic lines{}; +// public: +// using range_t = typename Q::range_t; +// +// public: +// // BG95_base driver requirements +// BG95() : +// rx_q(Q::data_match::MATCH_PUSH, base_type::delimiter, [&](){ +// lines.fetch_add(1, std::memory_order_acq_rel); +// }), lines(0) { } +// +// size_t get(char* data, bool wait =false) { +// do { +// if (lines.load(std::memory_order_acquire)) { +// size_t n =0; +// do{ +// *data << rx_q; +// ++n; +// } while (*data++ != base_type::delimiter); +// lines.fetch_sub(1, std::memory_order_acq_rel); +// return n; +// } +// } while (wait); +// return 0; +// } +// size_t put (const char* data, size_t n) { +// const char* reply = cmd_responce (data); +// while (*reply) +// rx_q << *reply++; +// return n; +// } +// const range_t contents() const { +// return range_t {rx_q.begin(), rx_q.end()}; +// } +// clock_t clock() { static clock_t t=0; return ++t; } +// +// // extra helper for testing purposes +// void async (event e) { +// const char* reply =event_responce (e); +// while (*reply) +// rx_q << *reply++; +// } +//}; +// +//// Behavior flag +//bool handler_flag = false; +//void handler (const char* data, size_t n) { +// (void)*data; +// (void)n; +//// std::cout << "* handler called\n"; +// handler_flag = true; +//} +//void clear_flag () { +// handler_flag = false; +//} +// +//int main(int argc, char **argv) try { +// BG95 modem; +// +// const BG95::async_handlers<2> async = {{ +// {"+QMTOPEN:", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, +// {"+QMT", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, +// }}; +// const BG95::script_t<5, 2> script = {{ +// /* 0 */{BG95::control_t::NOP, {"", BG95::match_t::NO, nullptr, BG95::action_t::GOTO, 1}, 1000}, +// /* 1 */{BG95::control_t::SEND, {"ATE0\r\n", BG95::match_t::NO, nullptr, BG95::action_t::NEXT, 0}, 0}, +// /* 2 */{BG95::control_t::EXPECT, {{ +// {"OK\r\n", BG95::match_t::ENDS_WITH, nullptr, BG95::action_t::NEXT, 0}, +// {"ERROR", BG95::match_t::CONTAINS, nullptr, BG95::action_t::EXIT_ERROR, 0} }}, +// 1000 +// }, +// /* 3 */{BG95::control_t::SEND, {"AT+CSQ\r\n", BG95::match_t::NO, nullptr, BG95::action_t::NEXT, 0}, 0}, +// /* 4 */{BG95::control_t::EXPECT, {{ +// {"OK\r\n", BG95::match_t::ENDS_WITH, nullptr, BG95::action_t::EXIT_OK, 0}, +// {"ERROR", BG95::match_t::CONTAINS, nullptr, BG95::action_t::EXIT_ERROR, 0} }}, +// 1000 +// }, +// }}; +// +// std::atomic lock(true); +// std::thread th1 ([&](){ +// do +// modem.inetd(async, false); +// while (lock.load(std::memory_order_acquire)); +// }); +// EXPECT_EQ (modem.run(script), true); +// lock.store(false, std::memory_order_acq_rel); +// th1.join(); +// +//} +//catch (std::exception& e) { +// std::cout << "Exception: " << e.what() << '\n'; +//} + +GTEST_API_ int main(int argc, char **argv) try { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } - +catch (std::exception& e) { + std::cout << "Exception: " << e.what() << '\n'; +} diff --git a/test/mingw-std-threads b/test/mingw-std-threads new file mode 160000 index 0000000..f6365f9 --- /dev/null +++ b/test/mingw-std-threads @@ -0,0 +1 @@ +Subproject commit f6365f900fb9b1cd6014c8d1cf13ceacf8faf3de diff --git a/test/tests/BG95_base.cpp b/test/tests/BG95_base.cpp new file mode 100644 index 0000000..2958a04 --- /dev/null +++ b/test/tests/BG95_base.cpp @@ -0,0 +1,270 @@ +/*! + * \file sequencer.cpp + * + * \copyright Copyright (C) 2020 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. + *
+ * + */ +#include +#include +#include +//#include + +//#include +#include +#include + +#ifndef WIN_TRHEADS +#include +#include +#else +#include +#include +#endif + +namespace test_bg95_base { + using namespace tbx; + + using Q = equeue; + + // BG95 implementer mock + class BG95 : public BG95_base { + using base_type = BG95_base; + + public: + enum class event { + MQTT_DISCONNECT, MQTT_RXDATA + }; + // simulated modem operation + private: + struct cmd_pair { + const char *cmd; + const char *resp; + }; + struct event_pair { + event e; + const char* resp; + }; + + 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"}, + {"AT+QCFG=\"nwscanseq\"\r\n", "\r\n+QCFG: \"nwscanseq\",020301\r\n"}, + {"AT+QCFG=\"nwscanseq\",010302\r\n", "\r\nOK\r\n"}, + {"AT+CREG?\r\n", "\r\n+CREG: 0,5\r\n\r\nOK\r\n"}, + {"AT+CSQ\r\n", "\r\n+CSQ: 19,99\r\n\r\nOK\r\n"}, + {"AT+QNWINFO\r\n", "\r\n+QNWINFO: \"EDGE\",\"20201\",\"GSM 1800\",865\r\n\r\nOK\r\n"}, + // Files + {"AT+QFLST\r\n", "\r\n+QFLST: \"cacert.pem\",1220\r\n+QFLST: \"security/\",2\r\nOK\r\n"}, + // MQTT config + {"AT+QSSLCFG=\"ignorelocaltime\",2,1\r\n", "\r\nOK\r\n"}, + {"AT+QSSLCFG=\"seclevel\",2,1\r\n", "\r\nOK\r\n"}, + {"AT+QSSLCFG=\"sslversion\",2,4\r\n", "\r\nOK\r\n"}, + {"AT+QSSLCFG=\"ciphersuite\",2\r\n", "\r\n+QSSLCFG: \"ciphersuite\",2,0XFFFF\r\n\r\nOK\r\n"}, + {"AT+QMTCFG=\"ssl\",0,1,2\r\n", "\r\nOK\r\n"}, + {"AT+QMTCFG=\"keepalive\",0,3600\r\n", "\r\nOK\r\n"}, + // MQTT + {"AT+QMTOPEN=0,\"server.com\",8883\r\n", "\r\nOK\r\n\r\n+QMTOPEN: 0,0\r\n"}, + {"AT+QMTCONN=0,\"myID\",\"user\",\"pass\"\r\n", "\r\nOK\r\n\r\n+QMTCONN: 0,0,0\r\n"}, + {"AT+QMTSUB=0,1,\"/path/topic1\",2\r\n", "\r\nOK\r\n\r\n+QMTSUB: 0,1,0,2\r\n"}, + {"AT+QMTPUB=0,0,0,0,\"/path/topic2\",9\r\n", "\r\n> \r\nOK\r\n\r\n+QMTPUB: 0,0,0\r\n"}, + }}; + std::array event_map {{ + {event::MQTT_DISCONNECT, "\r\n+QMTSTAT: 0,1\r\n"}, + {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; + } + return cmd_map[0].resp; + } + const char* event_responce (const event e) { + for (auto& it : event_map) { + if (e == it.e) + return it.resp; + } + return nullptr; // non reachable + } + + // data + Q rx_q{}; + std::atomic lines{}; + public: + using range_t = typename Q::range_t; + + public: + // BG95_base driver requirements + BG95() : + rx_q(Q::data_match::MATCH_PUSH, base_type::delimiter, [&](){ + lines.fetch_add(1, std::memory_order_acq_rel); + }), lines(0) { } + + size_t get(char* data, bool wait =false) { + do { + if (lines.load(std::memory_order_acquire)) { + size_t n =0; + do{ + *data << rx_q; + ++n; + } while (*data++ != base_type::delimiter); + lines.fetch_sub(1, std::memory_order_acq_rel); + return n; + } + } while (wait); + return 0; + } + size_t put (const char* data, size_t n) { + std::cerr << " "; + const char* reply = cmd_responce (data); + while (*reply) + rx_q << *reply++; + return n; + } + const range_t contents() const { + return range_t {rx_q.begin(), rx_q.end()}; + } + clock_t clock() { static clock_t t=0; return ++t; } + + // extra helper for testing purposes + void async (event e) { + const char* reply =event_responce (e); + while (*reply) + rx_q << *reply++; + } + }; + + // Behavior flag + bool handler_flag = false; + void handler (const char* data, size_t n) { + (void)*data; + (void)n; +// std::cout << "* handler called\n"; + handler_flag = true; + } + void clear_flag () { + handler_flag = false; + } + /* + * Test inetd in non blocking mode + */ + TEST(TBG95_base, inetd_non_blocking) { + BG95 modem; + char buffer[256]; + + const BG95::async_handlers<2> async = {{ + {"+QMTOPEN:", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, + {"+QMT", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, + }}; + + clear_flag(); + modem.inetd(false, &async); + EXPECT_EQ (handler_flag, false); + modem.async(BG95::event::MQTT_DISCONNECT); + modem.inetd(false, &async); // parse "\r\n" + EXPECT_EQ (handler_flag, false); + modem.inetd(false, &async); // parse "+QMT*\r\n" and dispatch to handler() + EXPECT_EQ (handler_flag, true); + clear_flag(); // nothing to parse + modem.inetd(false, &async); + modem.inetd(false, &async); + 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); + + clear_flag(); + modem.inetd(false, &async); + EXPECT_EQ (handler_flag, false); + modem.transmit("AT+CSQ\r\n", 8); + 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); + 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); + modem.inetd(false, &async); // parse "\r\n" + EXPECT_NE (modem.receive(buffer), 0UL); + EXPECT_EQ (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); + modem.inetd(false, &async); // nothing to parse + modem.inetd(false, &async); + modem.inetd(false, &async); + EXPECT_EQ (modem.receive(buffer), 0UL); + } + + TEST(TBG95_base, run) { + BG95 modem; + + const BG95::async_handlers<2> async = {{ + {"+QMTOPEN:", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, + {"+QMT", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, + }}; + const BG95::script_t<5> script = {{ + /* 0 */{BG95::control_t::NOP, {"", BG95::match_t::NO, nullptr, BG95::action_t::GOTO, 1}, 1000}, + /* 1 */{BG95::control_t::SEND, {"ATE0\r\n", BG95::match_t::NO, nullptr, BG95::action_t::NEXT, 0}, 0}, + /* 2 */{BG95::control_t::EXPECT, {{ + {"OK\r\n", BG95::match_t::ENDS_WITH, nullptr, BG95::action_t::NEXT, 0}, + {"ERROR", BG95::match_t::CONTAINS, nullptr, BG95::action_t::EXIT_ERROR, 0} }}, + 1000 + }, + /* 3 */{BG95::control_t::SEND, {"AT+CSQ\r\n", BG95::match_t::NO, nullptr, BG95::action_t::NEXT, 0}, 0}, + /* 4 */{BG95::control_t::EXPECT, {{ + {"OK\r\n", BG95::match_t::ENDS_WITH, nullptr, BG95::action_t::EXIT_OK, 0}, + {"ERROR", BG95::match_t::CONTAINS, nullptr, BG95::action_t::EXIT_ERROR, 0} }}, + 1000 + }, + }}; + + std::atomic lock(true); + std::thread th1 ([&](){ + do + modem.inetd(false, &async); + while (lock.load(std::memory_order_acquire)); + }); + EXPECT_EQ (modem.run(script), true); + lock.store(false, std::memory_order_acq_rel); + th1.join(); + } + + TEST(TBG95_base, command) { + BG95 modem; + + std::atomic lock(true); + std::thread th1 ([&](){ + do + modem.inetd(false); + while (lock.load(std::memory_order_acquire)); + }); + EXPECT_EQ (modem.registered(), true); + lock.store(false, std::memory_order_acq_rel); + th1.join(); + + } +} diff --git a/test/tests/sequencer.cpp b/test/tests/sequencer.cpp index 3b11451..abdae80 100644 --- a/test/tests/sequencer.cpp +++ b/test/tests/sequencer.cpp @@ -33,18 +33,26 @@ namespace test_sequencer { using namespace tbx; - // Sequencer implementer mock - class Seq : public sequencer_t { + struct seq_cont_t { const char *msg_[10] = { "", "", "", "\r\nOK\r\n", "", "", "+CCLK = \"21/08/26-12:16:30+12\"\r\nOK\r\n" "", "", "\r\nERROR\r\n" }; + using value_type = char; + using range_t = range; + }; + seq_cont_t seq_cont; + + // Sequencer implementer mock + class Seq : public sequencer { + using range_t = typename seq_cont_t::range_t; + public: size_t get(char* data) { static int msg =0; - size_t len = strlen(msg_[msg]); - strcpy(data, msg_[msg++]); + size_t len = strlen(seq_cont.msg_[msg]); + strcpy(data, seq_cont.msg_[msg++]); if (msg >= 10) msg =0; return len; } @@ -52,12 +60,14 @@ namespace test_sequencer { (void)*data; return n; } + const range_t contents () const { + return range_t {&seq_cont.msg_[6][0], &seq_cont.msg_[6][5]}; + } clock_t clock() { static clock_t t=0; return ++t; } - static status_t my_handler (const char* data, size_t size) { + static void my_handler (const char* data, size_t size) { (void)*data; (void)size; - return Seq::status_t::OK; } }; @@ -66,37 +76,37 @@ namespace test_sequencer { */ TEST(Tsequencer, run_delay) { Seq s; - const std::array script = {{ + const std::array, 1> script = {{ /* 0 */{Seq::control_t::NOP, {"", Seq::match_t::NO, nullptr, Seq::action_t::EXIT_OK, 0}, 1000} }}; - EXPECT_EQ ((int)Seq::status_t::OK, (int)s.run(script)); + EXPECT_EQ (s.run(script), true); } TEST(Tsequencer, run_dummy_output) { Seq s; - const std::array script = {{ + const std::array, 2> script = {{ /* 0 */{Seq::control_t::SEND, {"", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000}, /* 1 */{Seq::control_t::SEND, {"", Seq::match_t::NO, nullptr, Seq::action_t::EXIT_OK, 0}, 1000} }}; - EXPECT_EQ ((int)Seq::status_t::OK, (int)s.run(script)); + EXPECT_EQ (s.run(script), true); } TEST(Tsequencer, run_exits) { Seq s; - const std::array script1 = {{ + const std::array, 1> script1 = {{ /* 0 */{Seq::control_t::SEND, {"", Seq::match_t::NO, nullptr, Seq::action_t::EXIT_OK, 0}, 1000}, }}; - EXPECT_EQ ((int)Seq::status_t::OK, (int)s.run(script1)); + EXPECT_EQ (s.run(script1), true); - const std::array script2 = {{ + const std::array, 1> script2 = {{ /* 0 */{Seq::control_t::SEND, {"", Seq::match_t::NO, nullptr, Seq::action_t::EXIT_ERROR, 0}, 1000}, }}; - EXPECT_EQ ((int)Seq::status_t::ERROR, (int)s.run(script2)); + EXPECT_EQ (s.run(script2), false); } TEST(Tsequencer, run_sequence) { Seq s; - const std::array script = {{ + const std::array, 9> script = {{ /* 0 */{Seq::control_t::NOP, {"", Seq::match_t::NO, nullptr, Seq::action_t::GOTO, 1}, 1000}, /* 1 */{Seq::control_t::SEND, {"ATE0\r\n", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000}, /* 2 */{Seq::control_t::EXPECT, {{ @@ -104,20 +114,25 @@ namespace test_sequencer { {"ERROR", Seq::match_t::CONTAINS, nullptr, Seq::action_t::EXIT_ERROR, 0} }}, 1000 }, - /* 3 */{Seq::control_t::SEND, {"AT+CCLK?", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000}, - /* 4 */{Seq::control_t::EXPECT, {{ + /* 3 */{Seq::control_t::DETECT, {{ + {"+CCLK", Seq::match_t::CONTAINS, nullptr, Seq::action_t::NEXT, 0}, + {"ERROR", Seq::match_t::CONTAINS, nullptr, Seq::action_t::EXIT_ERROR, 0} }}, + 1000 + }, + /* 4 */{Seq::control_t::SEND, {"AT+CCLK?", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000}, + /* 5 */{Seq::control_t::EXPECT, {{ {"OK\r\n", Seq::match_t::ENDS_WITH, Seq::my_handler, Seq::action_t::NEXT, 0}, {"ERROR", Seq::match_t::CONTAINS, nullptr, Seq::action_t::EXIT_ERROR, 0} }}, 1000 }, - /* 5 */{Seq::control_t::SEND, {"AT+CT?", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000}, - /* 6 */{Seq::control_t::EXPECT, {{ + /* 6 */{Seq::control_t::SEND, {"AT+CT?", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000}, + /* 7 */{Seq::control_t::EXPECT, {{ {"OK\r\n", Seq::match_t::ENDS_WITH, nullptr, Seq::action_t::NEXT, 0}, {"ERROR", Seq::match_t::CONTAINS, nullptr, Seq::action_t::EXIT_ERROR, 0} }}, 1000 }, - /* 7 */{Seq::control_t::SEND, {"AT+POWD=0", Seq::match_t::NO, nullptr, Seq::action_t::EXIT_OK, 0}, 1000} + /* 8 */{Seq::control_t::SEND, {"AT+POWD=0", Seq::match_t::NO, nullptr, Seq::action_t::EXIT_OK, 0}, 1000} }}; - EXPECT_EQ ((int)Seq::status_t::ERROR, (int)s.run(script)); + EXPECT_EQ (s.run(script), false); } }