@@ -0,0 +1,3 @@ | |||||
[submodule "test/mingw-std-threads"] | |||||
path = test/mingw-std-threads | |||||
url = https://github.com/meganz/mingw-std-threads.git |
@@ -0,0 +1,321 @@ | |||||
/*! | |||||
* \file cont/BG95_base.h | |||||
* \brief | |||||
* BG95 driver functionality as CRTP base class | |||||
* | |||||
* \copyright Copyright (C) 2021 Christos Choutouridis <christos@choutouridis.net> | |||||
* | |||||
* <dl class=\"section copyright\"><dt>License</dt><dd> | |||||
* 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. | |||||
* </dd></dl> | |||||
*/ | |||||
#ifndef TBX_DRV_BG95_base_H_ | |||||
#define TBX_DRV_BG95_base_H_ | |||||
#include <core/core.h> | |||||
#include <core/crtp.h> | |||||
#include <cont/equeue.h> | |||||
#include <cont/range.h> | |||||
#include <utils/sequencer.h> | |||||
#include <cstring> | |||||
#include <cstdio> | |||||
#include <algorithm> | |||||
//#include <iostream> | |||||
#include <atomic> | |||||
namespace tbx { | |||||
/*! | |||||
* \class BG95_base | |||||
* \brief | |||||
* | |||||
* \example implementation example | |||||
* \code | |||||
* using Queue = equeue<char, 512, true>; | |||||
* | |||||
* class BG95 : | |||||
* public BG95_base<BG95, Queue, 256> { | |||||
* using base_type = BG95_base<BG95, Queue, 256>; | |||||
* using range_t = typename Queue::range_t; | |||||
* private: // data | |||||
* Queue rx_q{}; | |||||
* std::atomic<size_t> lines{}; | |||||
* public: | |||||
* BG95() : | |||||
* rx_q(equeue<char, 512, true>::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<typename Impl_t, typename Cont_t, size_t N, char Delimiter ='\n'> | |||||
class BG95_base | |||||
: public sequencer<BG95_base<Impl_t, Cont_t, N, Delimiter>, Cont_t, char, N>{ | |||||
_CRTP_IMPL(Impl_t); | |||||
static_assert( | |||||
std::is_same_v<typename Cont_t::value_type, char>, | |||||
"Cont_t must be a container of type char" | |||||
); | |||||
// local type dispatch | |||||
using base_type = sequencer<BG95_base, Cont_t, char, N>; | |||||
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<size_t Nr, size_t Nh =2> | |||||
using script_t = typename base_type::template script_t<Nr, Nh>; | |||||
//! Publish delimiter | |||||
constexpr static char delimiter = Delimiter; | |||||
//! Required typenames for async operation | |||||
//! @{ | |||||
template <size_t Nm> | |||||
using async_handlers = std::array<typename base_type::handle_t, Nm>; | |||||
//! @} | |||||
//! @} | |||||
//! \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 <size_t Nm =0> | |||||
void inetd (bool loop =true, const async_handlers<Nm>* async_handles =nullptr) { | |||||
std::array<char, N> 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 <size_t Steps, size_t Nhandles> | |||||
bool configure (const script_t<Steps, Nhandles>& 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<typename T> | |||||
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<std::remove_cv<T>, int>) { | |||||
sscanf(str, "%d", &value); | |||||
} else if (std::is_same_v<std::remove_cv<T>, float>) { | |||||
sscanf(str, "%f", &value); | |||||
} else if (std::is_same_v<std::remove_cv<T>, double>) { | |||||
sscanf(str, "%lf", &value); | |||||
} else if (std::is_same_v<std::remove_cv<T>, char>) { | |||||
sscanf(str, "%c", &value); | |||||
} else if (std::is_same_v<std::remove_cv<T>, char*>) { | |||||
strcpy(value, str); | |||||
} | |||||
*next_ptr = save; | |||||
} | |||||
// cmd: "AT+CREG?" | |||||
// expected: "\r\n+CREG: 0,%\r\nOK\r\n" | |||||
template <typename T, char Marker = '%'> | |||||
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<sz ; ) { | |||||
if (ex[i] == Marker) | |||||
parse(buffer[i], sz, ex[i+1], t); // parse and convert | |||||
else if (ex[i] == buffer[i]) | |||||
++i; // consume current character | |||||
else | |||||
return false; // Fail to parse | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
//! @} | |||||
private: | |||||
equeue<char, N> rx_q{}; | |||||
std::atomic<size_t> streams_{}; | |||||
}; | |||||
} // namespace tbx; | |||||
#endif /* TBX_DRV_BG95_base_H_ */ |
@@ -34,14 +34,19 @@ | |||||
#include <core/core.h> | #include <core/core.h> | ||||
#include <core/crtp.h> | #include <core/crtp.h> | ||||
#include <cont/range.h> | |||||
#include <ctime> | #include <ctime> | ||||
#include <array> | #include <array> | ||||
#include <string_view> | #include <string_view> | ||||
#include <limits> | |||||
#include <type_traits> | |||||
#include <functional> | |||||
namespace tbx { | namespace tbx { | ||||
/*! | /*! | ||||
* \class sequencer_t | |||||
* \class sequencer | |||||
* \brief | * \brief | ||||
* A CRTP base class to provide the sequencer functionality. | * A CRTP base class to provide the sequencer functionality. | ||||
* | * | ||||
@@ -59,14 +64,26 @@ namespace tbx { | |||||
* match the units in \c record_t::timeout field. | * match the units in \c record_t::timeout field. | ||||
* | * | ||||
* \tparam Impl_t The type of derived class | * \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 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(). | * \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 <typename Impl_t, typename Data_t, size_t N> | |||||
class sequencer_t { | |||||
template <typename Impl_t, typename Cont_t, typename Data_t, size_t N> | |||||
class sequencer { | |||||
_CRTP_IMPL(Impl_t); | _CRTP_IMPL(Impl_t); | ||||
using str_view_t = std::basic_string_view<Data_t>; | |||||
static_assert( | |||||
std::is_same_v<typename Cont_t::value_type, Data_t>, | |||||
"Cont_t must be a container of type Data_t" | |||||
); | |||||
// local type dispatch | |||||
using str_view_t = std::basic_string_view<Data_t>; | |||||
using range_t = typename Cont_t::range_t; | |||||
//! \name Public types | //! \name Public types | ||||
//! @{ | //! @{ | ||||
@@ -88,8 +105,18 @@ class sequencer_t { | |||||
//! \brief The control type of the script entry. | //! \brief The control type of the script entry. | ||||
enum class control_t { | enum class control_t { | ||||
NOP, //!< No command, dont send or expect anything, used for delays | 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 | //! \enum match_t | ||||
@@ -102,18 +129,18 @@ class sequencer_t { | |||||
* Match handler function pointer type. | * Match handler function pointer type. | ||||
* Expects a pointer to buffer and a size and returns status | * 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 | * \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 | * has a token and a matching type. If the response matches the token, the sequencer calls | ||||
* the handler and perform the action. | * the handler and perform the action. | ||||
*/ | */ | ||||
struct block_t { | |||||
struct handle_t { | |||||
std::basic_string_view<Data_t> | std::basic_string_view<Data_t> | ||||
token; //!< The token for the match | token; //!< The token for the match | ||||
match_t match_type; //!< The matching type functionality | 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. | * 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. | * The blocks contain the data and the matching information. And the timeout guards the entire line. | ||||
*/ | |||||
template<size_t Nhandles =2> | |||||
struct record_t { | |||||
control_t control; //!< The type of the entry | |||||
std::array<handle_t, Nhandles> | |||||
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. | * The user can create arrays as the example bellow to act as a script. | ||||
* \code | * \code | ||||
@@ -144,11 +184,8 @@ class sequencer_t { | |||||
* }}; | * }}; | ||||
* \endcode | * \endcode | ||||
*/ | */ | ||||
struct record_t { | |||||
control_t control; //!< The type of the entry | |||||
std::array<block_t, 2> block; //!< The matching block | |||||
clock_t timeout; //!< Timeout in CPU time | |||||
}; | |||||
template <size_t Nrecords, size_t Nhandles =2> | |||||
using script_t = std::array<record_t<Nhandles>, Nrecords>; | |||||
//! @} | //! @} | ||||
@@ -156,10 +193,10 @@ class sequencer_t { | |||||
//! \name Constructor / Destructor | //! \name Constructor / Destructor | ||||
//!@{ | //!@{ | ||||
protected: | 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 | //! \name Sequencer interface requirements for implementer | ||||
@@ -167,6 +204,7 @@ class sequencer_t { | |||||
private: | private: | ||||
size_t get_ (Data_t* data) { return impl().get (data); } | 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); } | 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(); } | clock_t clock_ () { return impl().clock(); } | ||||
//! @} | //! @} | ||||
@@ -180,7 +218,7 @@ class sequencer_t { | |||||
* \param prefix What we search | * \param prefix What we search | ||||
* \return True on success, false otherwise | * \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); | return (stream.rfind(prefix, 0) != str_view_t::npos); | ||||
} | } | ||||
@@ -191,7 +229,9 @@ class sequencer_t { | |||||
* \param postfix What we search | * \param postfix What we search | ||||
* \return True on success, false otherwise | * \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 ( | return ( | ||||
stream.compare( | stream.compare( | ||||
stream.size() - postfix.size(), | stream.size() - postfix.size(), | ||||
@@ -207,7 +247,7 @@ class sequencer_t { | |||||
* \param needle What we search | * \param needle What we search | ||||
* \return True on success, false otherwise | * \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); | 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 | * \param go_idx The new value of the step in the case of GOTO type | ||||
* \return The new sequencer's step value | * \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) { | switch (action) { | ||||
default: | default: | ||||
case action_t::NO: return current_idx; | case action_t::NO: return current_idx; | ||||
@@ -232,6 +272,27 @@ class sequencer_t { | |||||
return 0; | 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<size_t>::max(); | |||||
return status_t::OK; | |||||
case action_t::EXIT_ERROR: | |||||
step = std::numeric_limits<size_t>::max(); | |||||
return status_t::ERROR; | |||||
default: | |||||
step = step_(step, block.action, block.idx); | |||||
return status_t::OK; | |||||
} | |||||
} | |||||
//! @} | |||||
public: | |||||
/*! | /*! | ||||
* \brief | * \brief | ||||
* Checks if the \c needle matches the \c haystack. | * Checks if the \c needle matches the \c haystack. | ||||
@@ -241,7 +302,7 @@ class sequencer_t { | |||||
* \param needle The stream we search | * \param needle The stream we search | ||||
* \return True on match | * \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) { | switch (type) { | ||||
default: | default: | ||||
case match_t::NO: return true; | case match_t::NO: return true; | ||||
@@ -253,10 +314,25 @@ class sequencer_t { | |||||
case match_t::nCONTAINS: return !contains_(haystack, needle); | 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 | * \brief | ||||
* Run the script array | * Run the script array | ||||
@@ -264,28 +340,38 @@ class sequencer_t { | |||||
* The main sequencer functionality. It starts with the first entry of the array. | * 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. | * - If the entry is \c NOP it executes the action after the timeout. | ||||
* \c token and \c handler are discarded. | * \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. | * \c timeout is discarded. | ||||
* - If the entry is \c EXCEPTS it continuously try to receive data using implementation's get until one | * - 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: | * On match: | ||||
* - Calls the handler if there is one | * - Calls the handler if there is one | ||||
* - Executes the action | * - 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 | * If there is no match on timeout it return status_t::EXIT_ERROR | ||||
* | * | ||||
* \tparam Steps The number of steps of the script | * \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 | * \param script Reference to script to run | ||||
* \return The status of entire operation as described above | * \return The status of entire operation as described above | ||||
*/ | */ | ||||
template <size_t Steps> | |||||
status_t run (const std::array<record_t, Steps>& script) { | |||||
template <size_t Steps, size_t Nhandles> | |||||
bool run (const script_t<Steps, Nhandles>& script) { | |||||
Data_t buffer[N]; | Data_t buffer[N]; | ||||
size_t resp_size; | |||||
size_t resp_size{}; | |||||
status_t status{}; | |||||
clock_t mark = clock_(); | clock_t mark = clock_(); | ||||
for (size_t step =0, p_step =0 ; step < Steps ; ) { | for (size_t step =0, p_step =0 ; step < Steps ; ) { | ||||
const record_t& it = script[step]; | |||||
const record_t<Nhandles>& it = script[step]; | |||||
if (step != p_step) { | if (step != p_step) { | ||||
p_step = step; | p_step = step; | ||||
@@ -294,64 +380,63 @@ class sequencer_t { | |||||
switch (it.control) { | switch (it.control) { | ||||
default: | default: | ||||
case control_t::NOP: | 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; | break; | ||||
case control_t::SEND: | 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; | break; | ||||
case control_t::EXPECT: | case control_t::EXPECT: | ||||
resp_size = get_(buffer); | resp_size = get_(buffer); | ||||
if (resp_size) { | if (resp_size) { | ||||
for (auto& block : it.block) { | 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; | 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<size_t>(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; | break; | ||||
} // switch (it.control) | } // switch (it.control) | ||||
} | } | ||||
return status_t::OK; | |||||
return (status == status_t::OK); | |||||
} | } | ||||
}; | }; | ||||
/*! | /*! | ||||
* An "empty" block for convenience. | * An "empty" block for convenience. | ||||
*/ | */ | ||||
template <typename Impl_t, typename Data_t, size_t N> | |||||
constexpr typename sequencer_t<Impl_t, Data_t, N>::block_t Sequencer_null_block = { | |||||
template <typename Impl_t, typename Cont_t, typename Data_t, size_t N> | |||||
constexpr typename sequencer<Impl_t, Cont_t, Data_t, N>::handle_t Sequencer_null_block = { | |||||
"", | "", | ||||
sequencer_t<Impl_t, Data_t, N>::match_t::NO, | |||||
sequencer<Impl_t, Cont_t, Data_t, N>::match_t::NO, | |||||
nullptr, | nullptr, | ||||
sequencer_t<Impl_t, Data_t, N>::action_t::NO, | |||||
sequencer<Impl_t, Cont_t, Data_t, N>::action_t::NO, | |||||
0 | 0 | ||||
}; | }; | ||||
@@ -0,0 +1,8 @@ | |||||
# Binaries | |||||
bin/ | |||||
# Eclipse related | |||||
/Debug/ | |||||
.settings/ | |||||
.project | |||||
.cproject | |||||
@@ -85,7 +85,9 @@ SRC_FILES_LIST := | |||||
# Include directories list(space seperated). Relative path | # Include directories list(space seperated). Relative path | ||||
INC_DIR_LIST := ../include gtest | INC_DIR_LIST := ../include gtest | ||||
ifeq ($(OS), Windows_NT) | |||||
INC_DIR_LIST += mingw-std-threads | |||||
endif | |||||
# Exclude files list(space seperated). Filenames only. | # Exclude files list(space seperated). Filenames only. | ||||
# EXC_FILE_LIST := bad.cpp old.cpp | # EXC_FILE_LIST := bad.cpp old.cpp | ||||
@@ -104,10 +106,13 @@ ODUMP := objdump | |||||
OCOPY := objcopy | OCOPY := objcopy | ||||
# Compiler flags for debug and release | # 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-defines | ||||
# PRE_DEFS := MYCAB=1729 SUPER_MODE | |||||
PRE_DEFS := | |||||
ifeq ($(OS), Windows_NT) | |||||
PRE_DEFS += WIN_TRHEADS | |||||
endif | |||||
# ============== Linker settings ============== | # ============== Linker settings ============== | ||||
# Linker flags | # Linker flags | ||||
@@ -183,8 +188,8 @@ $(BUILD_DIR)/$(TARGET): $(OBJ) | |||||
@mkdir -p $(@D) | @mkdir -p $(@D) | ||||
@echo Linking to target: $(TARGET) | @echo Linking to target: $(TARGET) | ||||
$(DOCKER) $(CXX) $(LDFLAGS) $(MAP_FLAG) -o $(@D)/$(TARGET) $(OBJ) | $(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 | ||||
@echo Print size information | @echo Print size information | ||||
@$(CSIZE) $(@D)/$(TARGET) | @$(CSIZE) $(@D)/$(TARGET) | ||||
@@ -217,8 +222,8 @@ build-clang: $(BUILD_DIR)/$(TARGET) | |||||
debug: $(BUILD_DIR)/$(TARGET) | debug: $(BUILD_DIR)/$(TARGET) | ||||
.PHONY: release | .PHONY: release | ||||
release: CFLAGS := $(REL_FLAGS) | |||||
release: clean $(BUILD_DIR)/$(TARGET) | |||||
release: CFLAGS := $(REL_CFLAGS) | |||||
release: $(BUILD_DIR)/$(TARGET) | |||||
.PHONY: all | .PHONY: all | ||||
all: clean release | all: clean release | ||||
@@ -26,9 +26,191 @@ | |||||
* | * | ||||
*/ | */ | ||||
#include <gtest/gtest.h> | #include <gtest/gtest.h> | ||||
#include <exception> | |||||
GTEST_API_ int main(int argc, char **argv) { | |||||
//#include <drv/BG95_base.h> | |||||
//#include <gtest/gtest.h> | |||||
//#include <cont/equeue.h> | |||||
////#include <map> | |||||
// | |||||
////#include <iostream> | |||||
//#include <cstring> | |||||
//#include <utility> | |||||
// | |||||
//#ifndef WIN_TRHEADS | |||||
//#include <mutex> | |||||
//#include <thread> | |||||
//#else | |||||
//#include <mingw.thread.h> | |||||
//#include <mingw.mutex.h> | |||||
//#endif | |||||
// | |||||
//using namespace tbx; | |||||
// | |||||
//using Q = equeue<char, 512, true>; | |||||
// | |||||
//// BG95 implementer mock | |||||
//class BG95 : public BG95_base<BG95, Q, 256> { | |||||
// using base_type = BG95_base<BG95, Q, 256>; | |||||
// | |||||
// 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_pair, 19> 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_pair, 2> 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<size_t> 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<bool> 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); | testing::InitGoogleTest(&argc, argv); | ||||
return RUN_ALL_TESTS(); | return RUN_ALL_TESTS(); | ||||
} | } | ||||
catch (std::exception& e) { | |||||
std::cout << "Exception: " << e.what() << '\n'; | |||||
} |
@@ -0,0 +1 @@ | |||||
Subproject commit f6365f900fb9b1cd6014c8d1cf13ceacf8faf3de |
@@ -0,0 +1,270 @@ | |||||
/*! | |||||
* \file sequencer.cpp | |||||
* | |||||
* \copyright Copyright (C) 2020 Christos Choutouridis <christos@choutouridis.net> | |||||
* | |||||
* <dl class=\"section copyright\"><dt>License</dt><dd> | |||||
* 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. | |||||
* </dd></dl> | |||||
* | |||||
*/ | |||||
#include <drv/BG95_base.h> | |||||
#include <gtest/gtest.h> | |||||
#include <cont/equeue.h> | |||||
//#include <map> | |||||
//#include <iostream> | |||||
#include <cstring> | |||||
#include <utility> | |||||
#ifndef WIN_TRHEADS | |||||
#include <mutex> | |||||
#include <thread> | |||||
#else | |||||
#include <mingw.thread.h> | |||||
#include <mingw.mutex.h> | |||||
#endif | |||||
namespace test_bg95_base { | |||||
using namespace tbx; | |||||
using Q = equeue<char, 512, true>; | |||||
// BG95 implementer mock | |||||
class BG95 : public BG95_base<BG95, Q, 256> { | |||||
using base_type = BG95_base<BG95, Q, 256>; | |||||
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_pair, 19> 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_pair, 2> 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<size_t> 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<bool> 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<bool> 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(); | |||||
} | |||||
} |
@@ -33,18 +33,26 @@ | |||||
namespace test_sequencer { | namespace test_sequencer { | ||||
using namespace tbx; | using namespace tbx; | ||||
// Sequencer implementer mock | |||||
class Seq : public sequencer_t<Seq, char, 128> { | |||||
struct seq_cont_t { | |||||
const char *msg_[10] = { | const char *msg_[10] = { | ||||
"", "", "", "\r\nOK\r\n", | "", "", "", "\r\nOK\r\n", | ||||
"", "", "+CCLK = \"21/08/26-12:16:30+12\"\r\nOK\r\n" | "", "", "+CCLK = \"21/08/26-12:16:30+12\"\r\nOK\r\n" | ||||
"", "", "\r\nERROR\r\n" | "", "", "\r\nERROR\r\n" | ||||
}; | }; | ||||
using value_type = char; | |||||
using range_t = range<const char*>; | |||||
}; | |||||
seq_cont_t seq_cont; | |||||
// Sequencer implementer mock | |||||
class Seq : public sequencer<Seq, seq_cont_t, char, 128> { | |||||
using range_t = typename seq_cont_t::range_t; | |||||
public: | public: | ||||
size_t get(char* data) { | size_t get(char* data) { | ||||
static int msg =0; | 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; | if (msg >= 10) msg =0; | ||||
return len; | return len; | ||||
} | } | ||||
@@ -52,12 +60,14 @@ namespace test_sequencer { | |||||
(void)*data; | (void)*data; | ||||
return n; | 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; } | 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)*data; | ||||
(void)size; | (void)size; | ||||
return Seq::status_t::OK; | |||||
} | } | ||||
}; | }; | ||||
@@ -66,37 +76,37 @@ namespace test_sequencer { | |||||
*/ | */ | ||||
TEST(Tsequencer, run_delay) { | TEST(Tsequencer, run_delay) { | ||||
Seq s; | Seq s; | ||||
const std::array<Seq::record_t, 1> script = {{ | |||||
const std::array<Seq::record_t<1>, 1> script = {{ | |||||
/* 0 */{Seq::control_t::NOP, {"", Seq::match_t::NO, nullptr, Seq::action_t::EXIT_OK, 0}, 1000} | /* 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) { | TEST(Tsequencer, run_dummy_output) { | ||||
Seq s; | Seq s; | ||||
const std::array<Seq::record_t, 2> script = {{ | |||||
const std::array<Seq::record_t<1>, 2> script = {{ | |||||
/* 0 */{Seq::control_t::SEND, {"", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000}, | /* 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} | /* 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) { | TEST(Tsequencer, run_exits) { | ||||
Seq s; | Seq s; | ||||
const std::array<Seq::record_t, 1> script1 = {{ | |||||
const std::array<Seq::record_t<1>, 1> script1 = {{ | |||||
/* 0 */{Seq::control_t::SEND, {"", Seq::match_t::NO, nullptr, Seq::action_t::EXIT_OK, 0}, 1000}, | /* 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<Seq::record_t, 1> script2 = {{ | |||||
const std::array<Seq::record_t<1>, 1> script2 = {{ | |||||
/* 0 */{Seq::control_t::SEND, {"", Seq::match_t::NO, nullptr, Seq::action_t::EXIT_ERROR, 0}, 1000}, | /* 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) { | TEST(Tsequencer, run_sequence) { | ||||
Seq s; | Seq s; | ||||
const std::array<Seq::record_t, 8> script = {{ | |||||
const std::array<Seq::record_t<2>, 9> script = {{ | |||||
/* 0 */{Seq::control_t::NOP, {"", Seq::match_t::NO, nullptr, Seq::action_t::GOTO, 1}, 1000}, | /* 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}, | /* 1 */{Seq::control_t::SEND, {"ATE0\r\n", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000}, | ||||
/* 2 */{Seq::control_t::EXPECT, {{ | /* 2 */{Seq::control_t::EXPECT, {{ | ||||
@@ -104,20 +114,25 @@ namespace test_sequencer { | |||||
{"ERROR", Seq::match_t::CONTAINS, nullptr, Seq::action_t::EXIT_ERROR, 0} }}, | {"ERROR", Seq::match_t::CONTAINS, nullptr, Seq::action_t::EXIT_ERROR, 0} }}, | ||||
1000 | 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}, | {"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} }}, | {"ERROR", Seq::match_t::CONTAINS, nullptr, Seq::action_t::EXIT_ERROR, 0} }}, | ||||
1000 | 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}, | {"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} }}, | {"ERROR", Seq::match_t::CONTAINS, nullptr, Seq::action_t::EXIT_ERROR, 0} }}, | ||||
1000 | 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); | |||||
} | } | ||||
} | } |