diff --git a/include/utils/sequencer.h b/include/com/sequencer.h similarity index 94% rename from include/utils/sequencer.h rename to include/com/sequencer.h index 3f4ac6c..8e15918 100644 --- a/include/utils/sequencer.h +++ b/include/com/sequencer.h @@ -1,7 +1,7 @@ /*! - * \file utils/sequencer.h + * \file com/sequencer.h * \brief - * A terminal-like device communication automation tool + * A script based automation tool for send/receive communications * * \copyright Copyright (C) 2021 Christos Choutouridis * @@ -28,8 +28,8 @@ * */ -#ifndef TBX_UTILS_SEQUENCER_H_ -#define TBX_UTILS_SEQUENCER_H_ +#ifndef TBX_COM_SEQUENCER_H_ +#define TBX_COM_SEQUENCER_H_ #include @@ -147,14 +147,19 @@ class sequencer { size_t value; //!< Used by \c GOTO to indicate the next sequencer's step. }; + //! A no_action action_t static constexpr action_t no_action = {action_t::NO, 0}; + //! A next action_t static constexpr action_t next = {action_t::NEXT, 0}; - + //! A goto action_t template template static constexpr action_t go_to = {action_t::GOTO, static_cast(GOTO)}; + //! An exit ok action_t static constexpr action_t exit_ok = {action_t::EXIT, 0}; + //! An exit error action_t static constexpr action_t exit_error = {action_t::EXIT, static_cast(-1)}; + //! A generic exit action_t template template static constexpr action_t exit = {action_t::EXIT, static_cast(Status)}; @@ -421,6 +426,8 @@ class sequencer { clock_t mark = clock_(); seq_status_t status{seq_status_t::CONTINUE}; do { + if (step >= Steps) + return exit_error.value; const record_t& record = script[step]; // get reference ot current line if (step != p_step) { // renew time marker in each step @@ -452,7 +459,7 @@ class sequencer { std::tie(step, status) = action_ (script, s); break; } - } while (script[++s].control == control_t::OR_EXPECT); + } while ((++s < Steps) && (script[s].control == control_t::OR_EXPECT)); } if (record.timeout && (clock_() - mark) >= record.timeout) return exit_error.value; @@ -468,7 +475,7 @@ class sequencer { std::tie(step, status) = action_ (script, s); break; } - } while (script[++s].control == control_t::OR_DETECT); + } while ((++s < Steps) && (script[s].control == control_t::OR_DETECT)); } if (record.timeout && (clock_() - mark) >= record.timeout) return exit_error.value; @@ -484,4 +491,4 @@ class sequencer { } -#endif /* TBX_UTILS_SEQUENCER_H_ */ +#endif /* TBX_COM_SEQUENCER_H_ */ diff --git a/include/drv/BG95_base.h b/include/drv/ATmodem.h similarity index 76% rename from include/drv/BG95_base.h rename to include/drv/ATmodem.h index f29400a..5c3d2f1 100644 --- a/include/drv/BG95_base.h +++ b/include/drv/ATmodem.h @@ -1,7 +1,7 @@ /*! - * \file cont/BG95_base.h + * \file drv/ATmodem.h * \brief - * BG95 driver functionality as CRTP base class + * ATmodem driver functionality as CRTP base class * * \copyright Copyright (C) 2021 Christos Choutouridis * @@ -28,8 +28,8 @@ * */ -#ifndef TBX_DRV_BG95_base_H_ -#define TBX_DRV_BG95_base_H_ +#ifndef TBX_DRV_ATmodem_H_ +#define TBX_DRV_ATmodem_H_ #define __cplusplus 201703L @@ -37,7 +37,7 @@ #include #include #include -#include +#include #include #include @@ -63,12 +63,12 @@ namespace tbx { * \tparam Delimiter */ template -class BG95_base - : public sequencer, char, N>{ +class ATmodem + : public sequencer, char, N>{ _CRTP_IMPL(Impl_t); // local type dispatch - using base_type = sequencer; + using base_type = sequencer; //! \name Public types //! @{ @@ -106,10 +106,10 @@ class BG95_base //!@{ 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 + 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 @@ -139,6 +139,28 @@ class BG95_base //! \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 ( @@ -154,6 +176,9 @@ class BG95_base 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) { @@ -253,29 +278,45 @@ class BG95_base * 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, T* value, clock_t timeout =0) { - char buffer[N]; - char token[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 + transmit(cmd.data()); // send command for (auto ex = expected.begin() ; ex != expected.end() ; ) { - clock_t mark = clock(); // load the answer with timeout + clock_t mark = clock(); // load the answer with timeout size_t sz =0; - do { - sz = receive(buffer); - if ((timeout != 0 )&& ((clock() - mark) >= timeout)) - return false; - } while (!sz); - - for (size_t i=0 ; i (ex++, {&buffer[i], sz-i}, token); - if (!step) return false; - if (marker) extract(token, value); - i += step; - } + 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; } @@ -324,4 +365,4 @@ class BG95_base } // namespace tbx; -#endif /* TBX_DRV_BG95_base_H_ */ +#endif /* #ifndef TBX_DRV_ATmodem_H_ */ diff --git a/test/tests/BG95_base.cpp b/test/tests/ATmodem.cpp similarity index 68% rename from test/tests/BG95_base.cpp rename to test/tests/ATmodem.cpp index f0f0574..3436fd2 100644 --- a/test/tests/BG95_base.cpp +++ b/test/tests/ATmodem.cpp @@ -1,5 +1,5 @@ /*! - * \file sequencer.cpp + * \file ATmodem.cpp * * \copyright Copyright (C) 2020 Christos Choutouridis * @@ -26,15 +26,14 @@ * * */ -#include +#include #include + #include -//#include -//#include #include #include - +#include #ifndef WIN_TRHEADS #include #include @@ -43,17 +42,18 @@ #include #endif -namespace test_bg95_base { +namespace test_ATmodem { using namespace tbx; - + // test settings + constexpr size_t Size = 128; + using data_type = char; // BG95 implementer mock template - class BG95 : public BG95_base, N> { - - using Q = equeue; - using base_type = BG95_base, N>; + class BG95 : public ATmodem, N> { + using base_type = ATmodem, N>; + using Queue = equeue; public: enum class event { @@ -114,15 +114,13 @@ namespace test_bg95_base { } // data - Q rx_q{}; + Queue 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, [&](){ + // ATmodem driver requirements + BG95() noexcept : + rx_q(Queue::data_match::MATCH_PUSH, base_type::delimiter, [&](){ lines.fetch_add(1, std::memory_order_acq_rel); }), lines(0) { } @@ -146,7 +144,6 @@ namespace test_bg95_base { return nullpos - data; } size_t put (const char* data, size_t n) { - std::cerr << " "; const char* reply = cmd_responce (data); while (*reply) rx_q << *reply++; @@ -168,28 +165,43 @@ namespace test_bg95_base { 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(TATmodem, 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); + EXPECT_EQ (!std::is_copy_assignable>::value, true); + + EXPECT_EQ ((std::is_same_v::value_type, data_type>), true); + EXPECT_EQ ((std::is_same_v::pointer_type, data_type*>), true); + 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(TBG95_base, inetd_non_blocking) { - BG95<256> modem; - char buffer[256]; + TEST(TATmodem, inetd_non_blocking) { + BG95 modem; + char buffer[Size]; - const BG95<256>::inetd_handlers<2> async = {{ - {"+QMTOPEN:", BG95<256>::starts_with, handler}, - {"+QMT", BG95<256>::starts_with, handler}, + const BG95::inetd_handlers<2> async = {{ + {"+QMTSTAT:", BG95::starts_with, handler}, + {"+QMTRECV", BG95::contains, handler}, }}; clear_flag(); modem.inetd(false, &async); EXPECT_EQ (handler_flag, false); - modem.async(BG95<256>::event::MQTT_DISCONNECT); + 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() @@ -225,41 +237,37 @@ namespace test_bg95_base { EXPECT_EQ (modem.receive(buffer), 0UL); } -// TEST(TBG95_base, run) { -// BG95<256> modem; -// -// using Control = BG95<256>::control_t; -// using Action = BG95<256>::action_t; -// -// const BG95<256>::inetd_handlers<2> async = {{ -// {"+QMTOPEN:", BG95<256>::starts_with, handler}, -// {"+QMT", BG95<256>::starts_with, handler}, -// }}; -// const BG95<256>::script_t<7> script = {{ -// {Control::NOP, "", BG95<256>::nil, BG95<256>::nil, {Action::GOTO, 1}, 1000}, -// {Control::SEND, "ATE0\r\n", BG95<256>::nil, BG95<256>::nil, {Action::NEXT, 0}, 0}, -// {Control::EXPECT, "OK\r\n", BG95<256>::ends_with, BG95<256>::nil, {Action::NEXT, 0}, 1000}, -// {Control::OR_EXPECT,"ERROR", BG95<256>::contains, BG95<256>::nil, {Action::EXIT_ERROR, 0}, 0}, -// {Control::SEND, "AT+CSQ\r\n",BG95<256>::nil, BG95<256>::nil, {Action::NEXT, 0}, 0}, -// {Control::EXPECT, "OK\r\n", BG95<256>::ends_with, BG95<256>::nil, {Action::EXIT_OK, 0}, 1000}, -// {Control::OR_EXPECT,"ERROR", BG95<256>::contains, BG95<256>::nil, {Action::EXIT_ERROR, 0}, 0}, -// }}; -// -// std::mutex m; -// m.lock(); -// std::thread th1 ([&](){ -// do -// modem.inetd(false, &async); -// while (!m.try_lock()); -// m.unlock(); -// }); -// EXPECT_EQ (modem.run(script), true); -// m.unlock(); // stop and join inetd -// th1.join(); -// } - - TEST(TBG95_base, command) { - BG95<256> modem; + TEST(TATmodem, 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::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::OR_EXPECT,"ERROR", BG95::contains, BG95::nil, BG95::exit_error, 0} + }}; + + std::mutex m; + m.lock(); + std::thread th1 ([&](){ + do + modem.inetd(false); + while (!m.try_lock()); + m.unlock(); + }); + EXPECT_EQ (modem.run(script), BG95::exit_ok.value); + m.unlock(); // stop and join inetd + th1.join(); + } + + TEST(TATmodem, transmit) { + + } + + TEST(TATmodem, command) { + BG95 modem; std::mutex m; m.lock(); @@ -269,13 +277,25 @@ namespace test_bg95_base { while (!m.try_lock()); m.unlock(); }); - int status; - EXPECT_EQ (modem.command("AT+CREG?\r\n", "\r\n+CREG: 0,%\r\n\r\nOK\r\n", &status), true); - EXPECT_EQ (status, 5); - char substr[32]; - EXPECT_EQ (modem.command("AT+CREG?\r\n", "\r\n%\r\n\r\nOK\r\n", substr), true); - EXPECT_EQ (strcmp("+CREG: 0,5", substr), 0); + EXPECT_EQ (modem.command("AT\r\n", "Something else", 1000), false); + EXPECT_EQ (modem.command("AT\r\n", "\r\nOK\r\n", 1000), true); + + 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); + + int status1, status2; + EXPECT_EQ (modem.command("AT+CREG?\r\n", "\r\n+CREG: 0,%\r\n\r\nOK\r\n", 1000, &status1), true); + EXPECT_EQ (status1, 5); + EXPECT_EQ (modem.command("AT+CREG?\r\n", "\r\n+CREG: %,%\r\n\r\nOK\r\n", 1000, &status1, &status2), true); + EXPECT_EQ (status1, 0); + EXPECT_EQ (status2, 5); + + char substr1[32], substr2[32]; + EXPECT_EQ (modem.command("AT+CREG?\r\n", "\r\n%\r\n\r\n%\r\n", 1000, substr1, substr2), true); + EXPECT_EQ (strcmp("+CREG: 0,5", substr1), 0); + EXPECT_EQ (strcmp("OK", substr2), 0); m.unlock(); // stop and join inetd th1.join(); diff --git a/test/tests/deque.cpp b/test/tests/deque.cpp index bf7310a..9bc9516 100644 --- a/test/tests/deque.cpp +++ b/test/tests/deque.cpp @@ -62,6 +62,11 @@ namespace Tdeque { TEST(Tdeque, concept) { using deque_t = deque; + 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); + EXPECT_EQ (!std::is_copy_assignable::value, true); + EXPECT_EQ (true, !is_span::value); EXPECT_EQ (true, !is_std_array::value); EXPECT_EQ (true, !std::is_array::value); diff --git a/test/tests/sequencer.cpp b/test/tests/sequencer.cpp index 8c92c54..8a5a03c 100644 --- a/test/tests/sequencer.cpp +++ b/test/tests/sequencer.cpp @@ -26,7 +26,7 @@ * * */ -#include +#include #include #include @@ -422,4 +422,30 @@ namespace test_sequencer { EXPECT_EQ (s.run(script4), Seq::exit_ok.value); EXPECT_EQ (handler_flag, true); } + + TEST(Tsequencer, run_boundaries) { + Seq s; + + const Seq::script_t<1> script1 = {{ + {Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::next, 0}, + }}; + EXPECT_EQ (s.run(script1), Seq::exit_error.value); + + const Seq::script_t<1> script2 = {{ + {Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::go_to<1>, 0}, + }}; + EXPECT_EQ (s.run(script2), Seq::exit_error.value); + + const Seq::script_t<1> script3 = {{ + {Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::go_to<(size_t)-1>, 0}, + }}; + EXPECT_EQ (s.run(script3), Seq::exit_error.value); + + const Seq::script_t<1> script4 = {{ + {Seq::control_t::EXPECT, "abc", Seq::nil, Seq::nil, Seq::next, 1000}, + }}; + s.clear_clock(); + EXPECT_EQ (s.run(script4), Seq::exit_error.value); + EXPECT_GT (s.clock(), (clock_t)1000); + } }