@@ -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/crtp.h> | |||
#include <cont/range.h> | |||
#include <ctime> | |||
#include <array> | |||
#include <string_view> | |||
#include <limits> | |||
#include <type_traits> | |||
#include <functional> | |||
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 <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); | |||
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 | |||
//! @{ | |||
@@ -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<Data_t> | |||
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<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. | |||
* \code | |||
@@ -144,11 +184,8 @@ class sequencer_t { | |||
* }}; | |||
* \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 | |||
//!@{ | |||
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<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 | |||
* 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 <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]; | |||
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<Nhandles>& 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<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; | |||
} // switch (it.control) | |||
} | |||
return status_t::OK; | |||
return (status == status_t::OK); | |||
} | |||
}; | |||
/*! | |||
* 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, | |||
sequencer_t<Impl_t, Data_t, N>::action_t::NO, | |||
sequencer<Impl_t, Cont_t, Data_t, N>::action_t::NO, | |||
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 | |||
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 | |||
@@ -26,9 +26,191 @@ | |||
* | |||
*/ | |||
#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); | |||
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 { | |||
using namespace tbx; | |||
// Sequencer implementer mock | |||
class Seq : public sequencer_t<Seq, char, 128> { | |||
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<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: | |||
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<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} | |||
}}; | |||
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<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}, | |||
/* 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<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}, | |||
}}; | |||
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}, | |||
}}; | |||
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<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}, | |||
/* 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); | |||
} | |||
} |