Compare commits
23
Commits
28bf870959
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
795655edda | ||
|
|
c84fd4a614 | ||
|
|
43cfb349e9 | ||
|
|
be77dffa01 | ||
|
|
fce49ee963 | ||
|
|
887bbf391e | ||
|
|
e02f7dad6b | ||
|
|
d16242ecbc | ||
|
|
ca944cb062 | ||
|
|
4bce68697f | ||
|
|
1537c1e996 | ||
|
|
01ace5eb00 | ||
|
|
d9da3917a9 | ||
|
|
d2f8165ec6 | ||
|
|
cd23eb4963 | ||
|
|
f7c904e9c2 | ||
|
|
ca38dfa2f1 | ||
|
|
fd64d8726c | ||
|
|
bab3fd04fe | ||
|
|
2c67fb6069 | ||
|
|
f9b2b8ff6b | ||
|
|
1d03f37e67 | ||
|
|
0a04a480f3 |
@@ -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,524 @@
|
|||||||
|
/*!
|
||||||
|
* \file com/sequencer.h
|
||||||
|
* \brief
|
||||||
|
* A script based automation tool for send/receive communications
|
||||||
|
*
|
||||||
|
* \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_COM_SEQUENCER_H_
|
||||||
|
#define TBX_COM_SEQUENCER_H_
|
||||||
|
|
||||||
|
#include <core/core.h>
|
||||||
|
#include <core/crtp.h>
|
||||||
|
#include <cont/range.h>
|
||||||
|
|
||||||
|
#include <ctime>
|
||||||
|
#include <array>
|
||||||
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
namespace tbx {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \class sequencer
|
||||||
|
* \brief
|
||||||
|
* A CRTP base class to provide the sequencer functionality.
|
||||||
|
*
|
||||||
|
* Sequencer is a script engine with receive/transmit functionalities based on predicates. It has:
|
||||||
|
* - A program counter like variable named \c step.
|
||||||
|
* - \c step actions like NEXT, GOTO exit with status etc...
|
||||||
|
* - Input data match predicates to trigger those actions.
|
||||||
|
* - Input data handlers to trigger external functionality on predicate match
|
||||||
|
* - Output data handlers to "edit" data before transmiting them
|
||||||
|
* - A small predicate set provided to the user. (starts_with, ends_with, contains).
|
||||||
|
*
|
||||||
|
* Sequencer can automate communication with a terminal-like device such as AT-command modems, can
|
||||||
|
* be used to implement communication protocols, or even small http servers.
|
||||||
|
*
|
||||||
|
* It can operate based on a script array and handle the outgoing commands and incoming responses.
|
||||||
|
* The user can create matching rules on received data and hook handlers and actions on them.
|
||||||
|
*
|
||||||
|
* The derived class (implementation) has to provide:
|
||||||
|
* 1) size_t get(Data_t* data);
|
||||||
|
* This function return 0 or a number of Data_t items. The data points to buffer for the input data.
|
||||||
|
*
|
||||||
|
* 3) size_t contents_ (Data_t* data);
|
||||||
|
* This function return 0 or a number of Data_t items without removing them from the implementer's container
|
||||||
|
* The data points to buffer for the input data.
|
||||||
|
*
|
||||||
|
* 2) size_t put(const Data_t* data, size_t n);
|
||||||
|
* This function sends to implementation the data pointed by \c data witch have size \c n.
|
||||||
|
*
|
||||||
|
* 4) clock_t clock();
|
||||||
|
* This function return a number to be used as time. The units of this function may be arbitrary but they
|
||||||
|
* match the units in \c record_t::timeout field.
|
||||||
|
*
|
||||||
|
* \tparam Impl_t The type of 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 {
|
||||||
|
_CRTP_IMPL(Impl_t);
|
||||||
|
|
||||||
|
//! \name Public types
|
||||||
|
//! @{
|
||||||
|
public:
|
||||||
|
using value_type = Data_t;
|
||||||
|
using pointer_type = Data_t*;
|
||||||
|
using size_type = size_t;
|
||||||
|
using string_view = std::basic_string_view<Data_t>;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* The sequencer engine status. A variable of this type is returned by
|
||||||
|
* \see action_().
|
||||||
|
*/
|
||||||
|
enum class seq_status_t {
|
||||||
|
CONTINUE, //!< Means we keep looping
|
||||||
|
EXIT //!< Means, we exit with status the one indicated by \c action_t of the \c record_t
|
||||||
|
};
|
||||||
|
|
||||||
|
//! \enum control_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 through put()
|
||||||
|
EXPECT, //!< Expects data from implementation via get()
|
||||||
|
OR_EXPECT, //!< Expects data from implementation via get() in conjunction with previous EXPECT
|
||||||
|
DETECT, //!< Detects data into rx buffer without receiving them via contents()
|
||||||
|
OR_DETECT, //!< Detects data into rx buffer without receiving them via contents() in conjunction with
|
||||||
|
//!< previous DETECT
|
||||||
|
OTHERWISE //!< An "else" path if the EXPECT[, OR_EXPECT[, OR_EXPECT ... ]] block timesout.
|
||||||
|
|
||||||
|
//! \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 responses with '\n' termination but for some "special" command it opens a cursor
|
||||||
|
//! lets say ">$ " without '\n' at the end.
|
||||||
|
};
|
||||||
|
|
||||||
|
//! \enum action_t
|
||||||
|
//! \brief
|
||||||
|
//! Possible response actions for the sequencer. This is the
|
||||||
|
//! equivalent of changing the program counter of the sequencer
|
||||||
|
//! and is composed by a type and a value.
|
||||||
|
//!
|
||||||
|
struct action_t {
|
||||||
|
enum {
|
||||||
|
NO =0, //!< Do not change sequencer's step
|
||||||
|
NEXT, //!< Go to next sequencer step. In case of EXPECT/DETECT block of records
|
||||||
|
//!< skip the entire block of EXPECT[, OR_EXPECT[, OR_EXPECT ...]] and go
|
||||||
|
//!< to the next (non OR_*) control record.
|
||||||
|
GOTO, //!< Manually sets the step counter to the number of the \c step member.
|
||||||
|
EXIT, //!< Instruct for an exit returning the action.value as status
|
||||||
|
} type;
|
||||||
|
size_t value; //!< Used by \c GOTO to indicate the next sequencer's step.
|
||||||
|
};
|
||||||
|
|
||||||
|
//! A no_action action_t
|
||||||
|
static constexpr action_t no_action = {action_t::NO, 0};
|
||||||
|
//! A next action_t
|
||||||
|
static constexpr action_t next = {action_t::NEXT, 0};
|
||||||
|
//! A goto action_t template
|
||||||
|
template <size_t GOTO>
|
||||||
|
static constexpr action_t go_to = {action_t::GOTO, static_cast<size_t>(GOTO)};
|
||||||
|
|
||||||
|
//! An exit ok action_t
|
||||||
|
static constexpr action_t exit_ok = {action_t::EXIT, 0};
|
||||||
|
//! An exit error action_t
|
||||||
|
static constexpr action_t exit_error = {action_t::EXIT, static_cast<size_t>(-1)};
|
||||||
|
//! A generic exit action_t template
|
||||||
|
template <size_t Status>
|
||||||
|
static constexpr action_t exit = {action_t::EXIT, static_cast<size_t>(Status)};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Match binary predicate function pointer type.
|
||||||
|
* Expects two string views and return a boolean.
|
||||||
|
* It is used by EXPECT/DETECT blocks to trigger their {handler, action} pair.
|
||||||
|
*/
|
||||||
|
using match_ft = bool (*) (const string_view haystack, const string_view needle);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Send/Receive handler function pointer type.
|
||||||
|
* Expects a pointer to buffer and a size and returns status.
|
||||||
|
* It is used on predicate match on EXPECT/DETECT blocks, or as data wrapper on SEND blocks.
|
||||||
|
*/
|
||||||
|
using handler_ft = void (*) (const Data_t*, size_t);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \struct record_t
|
||||||
|
* \brief
|
||||||
|
* Describes the sequencer's script record entry (line).
|
||||||
|
*/
|
||||||
|
struct record_t {
|
||||||
|
control_t control; //!< The control type of the entry
|
||||||
|
string_view token; //!< String view to token data. [MUST BE null terminated].
|
||||||
|
//!< This is passed as 2nd argument to match predicate on EXPECT/DETECT, or as
|
||||||
|
//! {data, size} pair to SEND handler and put_().
|
||||||
|
//!< If unused set it to ""
|
||||||
|
match_ft match; //!< Match predicate to used in EXPECT/DETECT blocks
|
||||||
|
//!< If unused set it to nullptr
|
||||||
|
handler_ft handler; //!< The handler to called if the match is successful, or before put_()
|
||||||
|
//!< If unused set it to nullptr
|
||||||
|
action_t action; //!< Indicates the step manipulation if the match is successful or after NOP and put_()
|
||||||
|
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
|
||||||
|
* Seq s;
|
||||||
|
* const Seq::script_t<4> script = {{
|
||||||
|
* {Seq::control_t::NOP, "", Seq::nil, Seq::nil, {Seq::action_t::GOTO, 1}, 1000},
|
||||||
|
*
|
||||||
|
* {Seq::control_t::SEND, "ATE0\r\n", Seq::nil, Seq::nil, {Seq::action_t::NEXT, 0}, 0},
|
||||||
|
* {Seq::control_t::EXPECT, "OK\r\n", Seq::ends_with, Seq::nil, {Seq::action_t::EXIT_OK, 0}, 1000},
|
||||||
|
* {Seq::control_t::OR_EXPECT, "ERROR", Seq::contains, Seq::nil, {Seq::action_t::EXIT_ERROR, 0}, 0}
|
||||||
|
* }};
|
||||||
|
* s.run(script);
|
||||||
|
* \endcode
|
||||||
|
*/
|
||||||
|
template <size_t Nrecords>
|
||||||
|
using script_t = std::array<record_t, Nrecords>;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Check if the \c stream1 is equal to \c stream2
|
||||||
|
* \param stream1 The stream in witch we search [The input buffer]
|
||||||
|
* \param stream2 What we search [The record's token]
|
||||||
|
* \return True on success, false otherwise
|
||||||
|
*/
|
||||||
|
static constexpr auto equals = [](const string_view stream1, const string_view stream2) noexcept -> bool {
|
||||||
|
return (stream1 == stream2);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Check if the \c stream starts with the \c prefix
|
||||||
|
* \param stream The stream in witch we search [The input buffer]
|
||||||
|
* \param prefix What we search [The record's token]
|
||||||
|
* \return True on success, false otherwise
|
||||||
|
*/
|
||||||
|
static constexpr auto starts_with = [](const string_view stream, const string_view prefix) noexcept -> bool {
|
||||||
|
return (stream.rfind(prefix, 0) != string_view::npos);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Check if the \c stream ends with the \c postfix
|
||||||
|
* \param stream The stream in witch we search [The input buffer]
|
||||||
|
* \param postfix What we search [The record's token]
|
||||||
|
* \return True on success, false otherwise
|
||||||
|
*/
|
||||||
|
static constexpr auto ends_with = [](const string_view stream, const string_view postfix) -> bool {
|
||||||
|
if (stream.size() < postfix.size())
|
||||||
|
return false;
|
||||||
|
return (
|
||||||
|
stream.compare(
|
||||||
|
stream.size() - postfix.size(),
|
||||||
|
postfix.size(),
|
||||||
|
postfix) == 0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Check if the \c haystack contains the \c needle
|
||||||
|
* \param haystack The stream in witch we search [The input buffer]
|
||||||
|
* \param needle What we search [The record's token]
|
||||||
|
* \return True on success, false otherwise
|
||||||
|
*/
|
||||||
|
static constexpr auto contains = [](const string_view haystack, const string_view needle) noexcept -> bool {
|
||||||
|
return (haystack.find(needle) != string_view::npos);
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Always false predicate
|
||||||
|
static constexpr auto always_true = [](const string_view s1, const string_view s2) noexcept -> bool {
|
||||||
|
(void)s1; (void)s2;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Always false predicate
|
||||||
|
static constexpr auto always_false = [](const string_view s1, const string_view s2) noexcept -> bool {
|
||||||
|
(void)s1; (void)s2;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Empty predicate or handler
|
||||||
|
static constexpr auto nil = nullptr;
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//! \name Object lifetime
|
||||||
|
//!@{
|
||||||
|
protected:
|
||||||
|
~sequencer () = default; //!< \brief Allow destructor from derived only
|
||||||
|
constexpr sequencer () noexcept = 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
|
||||||
|
//! @{
|
||||||
|
private:
|
||||||
|
size_t get_ (Data_t* data) { return impl().get (data); }
|
||||||
|
size_t contents_ (Data_t* data) { return impl().contents(data); }
|
||||||
|
size_t put_ (const Data_t* data, size_t n) { return impl().put (data, n); }
|
||||||
|
clock_t clock_ () noexcept { return impl().clock(); }
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
//! \name Private functionality
|
||||||
|
//! @{
|
||||||
|
private:
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Check if there is a handler and call it
|
||||||
|
* \param handler The handler to check
|
||||||
|
* \param buffer String view to buffer to pass to handler
|
||||||
|
* \return True if handler is called
|
||||||
|
*/
|
||||||
|
constexpr bool handle_ (handler_ft handler, const string_view buffer = string_view{}) {
|
||||||
|
if (handler != nullptr) {
|
||||||
|
handler (buffer.begin(), buffer.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Return the new sequencer's step value and the sequencer's loop status as pair.
|
||||||
|
*
|
||||||
|
* \param script Reference to entire script.
|
||||||
|
* \param step The current step
|
||||||
|
* \return new step - status pair
|
||||||
|
*/
|
||||||
|
template <size_t Steps>
|
||||||
|
constexpr std::pair<size_t, seq_status_t> action_ (const script_t<Steps>& script, size_t step) {
|
||||||
|
control_t skip_while{};
|
||||||
|
size_t s;
|
||||||
|
|
||||||
|
switch (script[step].action.type) {
|
||||||
|
default:
|
||||||
|
case action_t::NO: return std::make_pair(step, seq_status_t::CONTINUE);
|
||||||
|
case action_t::NEXT:
|
||||||
|
switch (script[step].control) {
|
||||||
|
case control_t::NOP: return std::make_pair(++step, seq_status_t::CONTINUE);
|
||||||
|
case control_t::SEND: return std::make_pair(++step, seq_status_t::CONTINUE);
|
||||||
|
case control_t::EXPECT:
|
||||||
|
case control_t::OR_EXPECT: skip_while = control_t::OR_EXPECT; break;
|
||||||
|
case control_t::DETECT:
|
||||||
|
case control_t::OR_DETECT: skip_while = control_t::OR_DETECT; break;
|
||||||
|
case control_t::OTHERWISE: skip_while = control_t::OTHERWISE; break;
|
||||||
|
}
|
||||||
|
s = step;
|
||||||
|
while (script[++s].control == skip_while)
|
||||||
|
;
|
||||||
|
return std::make_pair(s, seq_status_t::CONTINUE);
|
||||||
|
case action_t::GOTO: return std::make_pair(script[step].action.value, seq_status_t::CONTINUE);
|
||||||
|
case action_t::EXIT: return std::make_pair(script[step].action.value, seq_status_t::EXIT);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t Steps>
|
||||||
|
size_t expect_end (const script_t<Steps>& script, size_t step) {
|
||||||
|
while ((++step < Steps) && (script[step].control == control_t::OR_EXPECT)) ;
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t Steps>
|
||||||
|
size_t detect_end (const script_t<Steps>& script, size_t step) {
|
||||||
|
while ((++step < Steps) && (script[step].control == control_t::OR_DETECT)) ;
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
//! \return The buffer size of the sequencer
|
||||||
|
constexpr size_t size() const noexcept { return N; }
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \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 buffer The buffer to check (1st parameter to match)
|
||||||
|
* \param token String view to check against buffer (2nd parameter to match)
|
||||||
|
* \param handler Function pointer to match predicate to use
|
||||||
|
* \param handle Reference to handle to call on match
|
||||||
|
*
|
||||||
|
* \return True on match, false otherwise
|
||||||
|
*/
|
||||||
|
constexpr bool check_handle (const string_view buffer, const string_view token, match_ft match, handler_ft handle) {
|
||||||
|
if (match != nullptr && match(buffer, token))
|
||||||
|
return handle_ (handle, buffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Run the script array
|
||||||
|
*
|
||||||
|
* The main sequencer functionality. It starts with the first entry of the array.
|
||||||
|
*
|
||||||
|
* - If the record is \c NOP it executes the action after the timeout.
|
||||||
|
* \c NOP uses {\c action_t, \c timeout}.
|
||||||
|
* - If the record is \c SEND passes the token to handler (if any), then to put_() and executes the action after that.
|
||||||
|
* \c SEND uses {\c token, \c handler, \c action_t}
|
||||||
|
* - If the record is \c EXCEPT it continuously try to receive data using \see get_()
|
||||||
|
* * If no data until timeout, exit with failure
|
||||||
|
* * On data reception for this record AND for each OR_EXPECT that follows, calls the match predicate
|
||||||
|
* by passing the received data and token.
|
||||||
|
* On predicate match
|
||||||
|
* - Calls the handler if there is one
|
||||||
|
* - Executes the action. No farther EXPECT, OR_EXPECT, ... checks are made.
|
||||||
|
* - If the record is \c DETECT it continuously try to receive data using \see contents_()
|
||||||
|
* * If no data until timeout, exit with failure
|
||||||
|
* * On data reception for this record AND for each OR_DETECT that follows, calls the match predicate
|
||||||
|
* by passing the received data and token.
|
||||||
|
* On predicate match
|
||||||
|
* - Calls the handler if there is one
|
||||||
|
* - Executes the action. No farther DETECT, OR_DETECT, ... checks are made.
|
||||||
|
*
|
||||||
|
* \tparam Steps The number of records of the script
|
||||||
|
*
|
||||||
|
* \param script Reference to script to run
|
||||||
|
* \return The status of entire operation as described above
|
||||||
|
* \arg 0 Success
|
||||||
|
* \arg (size_t)-1 Failure
|
||||||
|
* \arg other Arbitrary return status
|
||||||
|
*/
|
||||||
|
template <size_t Steps>
|
||||||
|
size_t run (const script_t<Steps>& script) {
|
||||||
|
Data_t buffer[N];
|
||||||
|
size_t resp_size;
|
||||||
|
|
||||||
|
size_t step =0, p_step =0;
|
||||||
|
clock_t mark = clock_();
|
||||||
|
|
||||||
|
seq_status_t status{seq_status_t::CONTINUE}; do {
|
||||||
|
if (step >= Steps)
|
||||||
|
return exit_error.value;
|
||||||
|
const record_t& record = script[step]; // get reference ot current line
|
||||||
|
|
||||||
|
if (step != p_step) { // renew time marker in each step
|
||||||
|
p_step = step;
|
||||||
|
mark = clock_();
|
||||||
|
}
|
||||||
|
switch (record.control) {
|
||||||
|
default:
|
||||||
|
case control_t::NOP:
|
||||||
|
if ((clock_() - mark) >= record.timeout)
|
||||||
|
std::tie(step, status) = action_ (script, step);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case control_t::SEND:
|
||||||
|
if (record.handler != nullptr)
|
||||||
|
record.handler(record.token.data(), record.token.size());
|
||||||
|
if (put_(record.token.data(), record.token.size()) != record.token.size())
|
||||||
|
return exit_error.value;
|
||||||
|
std::tie(step, status) = action_ (script, step);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case control_t::EXPECT:
|
||||||
|
case control_t::OR_EXPECT:
|
||||||
|
resp_size = get_(buffer);
|
||||||
|
if (resp_size) {
|
||||||
|
for (size_t s = step ; s < expect_end(script, step) ; ++s) {
|
||||||
|
if (script[s].match != nullptr && script[s].match({buffer, resp_size}, script[s].token)) {
|
||||||
|
handle_ (script[s].handler, {buffer, resp_size});
|
||||||
|
std::tie(step, status) = action_ (script, s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (record.timeout && (clock_() - mark) >= record.timeout) {
|
||||||
|
size_t s = expect_end(script, step);
|
||||||
|
if ((s < Steps) && (script[s].control == control_t::OTHERWISE)) {
|
||||||
|
handle_ (script[s].handler, {buffer, resp_size});
|
||||||
|
std::tie(step, status) = action_ (script, s);
|
||||||
|
} else {
|
||||||
|
return exit_error.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case control_t::DETECT:
|
||||||
|
case control_t::OR_DETECT:
|
||||||
|
resp_size = contents_(buffer);
|
||||||
|
if (resp_size) {
|
||||||
|
for (size_t s = step ; s < detect_end(script, step) ; ++s) {
|
||||||
|
if (script[s].match != nullptr && script[s].match({buffer, resp_size}, script[s].token)) {
|
||||||
|
handle_ (script[s].handler, {buffer, resp_size});
|
||||||
|
std::tie(step, status) = action_ (script, s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (record.timeout && (clock_() - mark) >= record.timeout) {
|
||||||
|
size_t s = detect_end(script, step);
|
||||||
|
if ((s < Steps) && (script[s].control == control_t::OTHERWISE)) {
|
||||||
|
handle_ (script[s].handler, {buffer, resp_size});
|
||||||
|
std::tie(step, status) = action_ (script, s);
|
||||||
|
} else {
|
||||||
|
return exit_error.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case control_t::OTHERWISE:
|
||||||
|
handle_ (script[step].handler, {buffer, resp_size});
|
||||||
|
std::tie(step, status) = action_ (script, step);
|
||||||
|
break;
|
||||||
|
} // switch (record.control)
|
||||||
|
|
||||||
|
} while ( status == seq_status_t::CONTINUE);
|
||||||
|
|
||||||
|
return step; // step here is set by action_ as the return status
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif /* TBX_COM_SEQUENCER_H_ */
|
||||||
+62
-30
@@ -33,8 +33,9 @@
|
|||||||
|
|
||||||
#include <core/core.h>
|
#include <core/core.h>
|
||||||
#include <core/ring_iterator.h>
|
#include <core/ring_iterator.h>
|
||||||
|
#include <cont/range.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
namespace tbx {
|
namespace tbx {
|
||||||
|
|
||||||
@@ -50,16 +51,21 @@ namespace tbx {
|
|||||||
* We use a ring buffer of size \c N+1. We start the front iterator at the last location of the buffer
|
* We use a ring buffer of size \c N+1. We start the front iterator at the last location of the buffer
|
||||||
* and the rear on the first. This way when the queue is full the iterators are pointing to the same location.
|
* and the rear on the first. This way when the queue is full the iterators are pointing to the same location.
|
||||||
*
|
*
|
||||||
* \tparam Data_t The char-like queued item type. Usually \c char
|
* \tparam Data_t The char-like queued item type. Usually \c char
|
||||||
* \tparam N The size of deque
|
* \tparam N The size of deque
|
||||||
|
* \tparam SemiAtomic True for semi-atomic operation. In that case the \c ring_iterator is also atomic.
|
||||||
|
* \note
|
||||||
|
* SemiAtomic means it is safe to access different ends from different threads. For example one thread can
|
||||||
|
* push only from front and another can pop from back to implement a queue.
|
||||||
*/
|
*/
|
||||||
template <typename Data_t, size_t N>
|
template <typename Data_t, size_t N, bool SemiAtomic =false>
|
||||||
class deque {
|
class deque {
|
||||||
public:
|
public:
|
||||||
// meta-identity type
|
// meta-identity type
|
||||||
using type = deque<Data_t, N>;
|
using type = deque<Data_t, N>;
|
||||||
using buffer_t = std::array<Data_t, N+1>; // We need N+1 spaces ring buffer for N spaces deque
|
using buffer_t = std::array<Data_t, N+1>; // We need N+1 spaces ring buffer for N spaces deque
|
||||||
using iterator_t = ring_iterator<Data_t*, N+1>;
|
using iterator_t = ring_iterator<Data_t*, N+1, SemiAtomic>;
|
||||||
|
using range_t = range<iterator_t>;
|
||||||
|
|
||||||
// STL
|
// STL
|
||||||
using value_type = Data_t;
|
using value_type = Data_t;
|
||||||
@@ -79,13 +85,18 @@ class deque {
|
|||||||
constexpr deque () noexcept :
|
constexpr deque () noexcept :
|
||||||
data_{},
|
data_{},
|
||||||
f{data_.data(), N},
|
f{data_.data(), N},
|
||||||
r{data_.data()} { }
|
r{data_.data()} {
|
||||||
|
if constexpr (SemiAtomic)
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
//! fill contructor
|
//! fill contructor
|
||||||
constexpr deque(const Data_t& value) noexcept {
|
constexpr deque(const Data_t& value) noexcept {
|
||||||
data_.fill(value);
|
data_.fill(value);
|
||||||
f = iterator(data_.data(), N);
|
f = iterator(data_.data(), N);
|
||||||
r = iterator(data_.data(), N);
|
r = iterator(data_.data(), N);
|
||||||
|
if constexpr (SemiAtomic)
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Initializer list contructor
|
//! Initializer list contructor
|
||||||
@@ -93,7 +104,10 @@ class deque {
|
|||||||
constexpr deque(It&& ...it) noexcept :
|
constexpr deque(It&& ...it) noexcept :
|
||||||
data_{{std::forward<It>(it)...}},
|
data_{{std::forward<It>(it)...}},
|
||||||
f(data_.data(), N),
|
f(data_.data(), N),
|
||||||
r(data_.data(), sizeof...(It)) { }
|
r(data_.data(), sizeof...(It)) {
|
||||||
|
if constexpr (SemiAtomic)
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
deque(const deque&) = delete; //!< No copies
|
deque(const deque&) = delete; //!< No copies
|
||||||
deque& operator= (const deque&) = delete; //!< No copy assignments
|
deque& operator= (const deque&) = delete; //!< No copy assignments
|
||||||
@@ -103,9 +117,9 @@ class deque {
|
|||||||
//! \name Iterators
|
//! \name Iterators
|
||||||
//! @{
|
//! @{
|
||||||
public:
|
public:
|
||||||
constexpr iterator begin() noexcept { return f+1; }
|
constexpr iterator begin() noexcept { iterator ret = f; return ++ret; }
|
||||||
constexpr const_iterator begin() const noexcept { return f+1; }
|
constexpr const_iterator begin() const noexcept { iterator ret = f; return ++ret; }
|
||||||
constexpr const_iterator cbegin() const noexcept { return f+1; }
|
constexpr const_iterator cbegin() const noexcept { iterator ret = f; return ++ret; }
|
||||||
|
|
||||||
constexpr iterator end() noexcept { return r; }
|
constexpr iterator end() noexcept { return r; }
|
||||||
constexpr const_iterator end() const noexcept { return r; }
|
constexpr const_iterator end() const noexcept { return r; }
|
||||||
@@ -115,9 +129,9 @@ class deque {
|
|||||||
constexpr const_reverse_iterator rbegin() const noexcept { return r; }
|
constexpr const_reverse_iterator rbegin() const noexcept { return r; }
|
||||||
constexpr const_reverse_iterator crbegin() const noexcept { return r; }
|
constexpr const_reverse_iterator crbegin() const noexcept { return r; }
|
||||||
|
|
||||||
constexpr reverse_iterator rend() noexcept { return f+1; }
|
constexpr reverse_iterator rend() noexcept { reverse_iterator ret = f; return ++ret; }
|
||||||
constexpr const_reverse_iterator rend() const noexcept { return f+1; }
|
constexpr const_reverse_iterator rend() const noexcept { reverse_iterator ret = f; return ++ret; }
|
||||||
constexpr const_reverse_iterator crend() const noexcept { return f+1; }
|
constexpr const_reverse_iterator crend() const noexcept { reverse_iterator ret = f; return ++ret; }
|
||||||
//! @}
|
//! @}
|
||||||
|
|
||||||
//! \name Capacity
|
//! \name Capacity
|
||||||
@@ -125,7 +139,10 @@ class deque {
|
|||||||
public:
|
public:
|
||||||
//! \return The size of the deque. The items currently in queue.
|
//! \return The size of the deque. The items currently in queue.
|
||||||
constexpr size_t size() noexcept {
|
constexpr size_t size() noexcept {
|
||||||
return full() ? N: (r - f) -1;
|
return r - (f +1);
|
||||||
|
}
|
||||||
|
constexpr size_t size() const noexcept {
|
||||||
|
return r - (f +1);
|
||||||
}
|
}
|
||||||
//! \return The maximum size of the deque. The items the queue can hold.
|
//! \return The maximum size of the deque. The items the queue can hold.
|
||||||
constexpr size_t max_size() noexcept { return N; }
|
constexpr size_t max_size() noexcept { return N; }
|
||||||
@@ -134,7 +151,8 @@ class deque {
|
|||||||
//! \return True if the deque is empty
|
//! \return True if the deque is empty
|
||||||
constexpr bool empty() noexcept { return size() == 0 ? true : false; }
|
constexpr bool empty() noexcept { return size() == 0 ? true : false; }
|
||||||
//! \return True if the deque is full
|
//! \return True if the deque is full
|
||||||
constexpr bool full() noexcept { return (r == f) ? true : false; }
|
constexpr bool full() noexcept { return size() == N ? true : false; }
|
||||||
|
|
||||||
//! @}
|
//! @}
|
||||||
|
|
||||||
//! \name Member access
|
//! \name Member access
|
||||||
@@ -145,41 +163,55 @@ class deque {
|
|||||||
constexpr void clear() noexcept {
|
constexpr void clear() noexcept {
|
||||||
f = iterator_t(data_.data(), N);
|
f = iterator_t(data_.data(), N);
|
||||||
r = iterator_t(data_.data());
|
r = iterator_t(data_.data());
|
||||||
|
if constexpr (SemiAtomic)
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
}
|
}
|
||||||
//! \brief Push an item in the front of the deque
|
//! \brief Push an item in the front of the deque
|
||||||
//! \param it The item to push
|
//! \param it The item to push
|
||||||
constexpr void push_front (const Data_t& it) {
|
constexpr void push_front (const Data_t& it) noexcept {
|
||||||
if (full()) return;
|
if (full()) return;
|
||||||
*f-- = it;
|
*f = it;
|
||||||
}
|
--f; // keep this separate for thread safety
|
||||||
//! \brief Extract an item from the front of the deque and remove it from the deque
|
|
||||||
//! \param it The item to push
|
|
||||||
constexpr Data_t pop_front () {
|
|
||||||
if (empty()) return Data_t{};
|
|
||||||
return *++f;
|
|
||||||
}
|
}
|
||||||
//! \brief Push an item in the back of the deque
|
//! \brief Push an item in the back of the deque
|
||||||
//! \param it The item to push
|
//! \param it The item to push
|
||||||
constexpr void push_back (const Data_t& it) {
|
constexpr void push_back (const Data_t& it) noexcept {
|
||||||
if (full()) return;
|
if (full()) return;
|
||||||
*r++ = it;
|
*r = it;
|
||||||
|
++r; // keep this separate for thread safety
|
||||||
|
}
|
||||||
|
//! \brief Extract an item from the front of the deque and remove it from the deque
|
||||||
|
//! \param it The item to push
|
||||||
|
constexpr Data_t pop_front () noexcept {
|
||||||
|
if (empty()) return Data_t{};
|
||||||
|
return *++f;
|
||||||
}
|
}
|
||||||
//! \brief Extract an item from the back of the deque and remove it from the deque
|
//! \brief Extract an item from the back of the deque and remove it from the deque
|
||||||
//! \param it The item to push
|
//! \param it The item to push
|
||||||
constexpr Data_t pop_back () {
|
constexpr Data_t pop_back () noexcept {
|
||||||
if (empty()) return Data_t{};
|
if (empty()) return Data_t{};
|
||||||
return *--r;
|
return *--r;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \brief Get a reference to the item in the front of the deque without extracting it.
|
//! \brief Get a reference to the item in the front of the deque without extracting it.
|
||||||
//! \return Reference to the item
|
//! \return Reference to the item
|
||||||
constexpr Data_t& front() noexcept { return *(f+1); }
|
constexpr Data_t& front() noexcept { iterator_t it = f; return *++it; }
|
||||||
constexpr const Data_t& front() const noexcept { return *(f+1); }
|
constexpr const Data_t& front() const noexcept { iterator_t it = f; return *++it; }
|
||||||
|
|
||||||
//! \brief Get a reference to the item in the front of the deque without extracting it.
|
//! \brief Get a reference to the item in the front of the deque without extracting it.
|
||||||
//! \return Reference to the item
|
//! \return Reference to the item
|
||||||
constexpr Data_t& back() noexcept { return *(r-1); }
|
constexpr Data_t& back() noexcept { iterator_t it = r; return *--it; }
|
||||||
constexpr const Data_t& back() const noexcept { return *(r-1); }
|
constexpr const Data_t& back() const noexcept { iterator_t it = r; return *--it; }
|
||||||
|
|
||||||
|
//! \brief Get a pointer to the begin of the items on the deque
|
||||||
|
//! \return
|
||||||
|
constexpr Data_t* data() noexcept { return &front(); }
|
||||||
|
constexpr const Data_t* data() const noexcept { return &front(); }
|
||||||
|
|
||||||
|
//! \brief Get a range for the data in queue
|
||||||
|
//! \return A begin-end iterator pair struct
|
||||||
|
constexpr range_t contents () noexcept { iterator_t b = f; return {++b, r}; }
|
||||||
|
constexpr const range_t contents () const noexcept { iterator_t b = f; return {++b, r}; }
|
||||||
|
|
||||||
//! @}
|
//! @}
|
||||||
private:
|
private:
|
||||||
|
|||||||
+70
-36
@@ -55,17 +55,22 @@ namespace tbx {
|
|||||||
* pushed or popped from the deque. If the criteria match we call call the callable of type
|
* pushed or popped from the deque. If the criteria match we call call the callable of type
|
||||||
* \c Fn
|
* \c Fn
|
||||||
*
|
*
|
||||||
* \tparam Data_t The char-like queued item type. Usually \c char
|
* \tparam Data_t The char-like queued item type. Usually \c char
|
||||||
* \tparam N The size of edeque
|
* \tparam N The size of edeque
|
||||||
* \tparam Fn The type of Callable
|
* \tparam SemiAtomic True for semi-atomic operation. In that case the \c ring_iterator is also atomic.
|
||||||
|
* \tparam Fn The type of Callable
|
||||||
|
* \note
|
||||||
|
* SemiAtomic means it is safe to access different ends from different threads. For example one thread can
|
||||||
|
* push only from front and another can pop from back to implement a queue.
|
||||||
*/
|
*/
|
||||||
template <typename Data_t, size_t N, typename Fn = std::function<void()>>
|
template <typename Data_t, size_t N, bool SemiAtomic =false, typename Fn = std::function<void()>>
|
||||||
class edeque : public deque<Data_t, N> {
|
class edeque : public deque<Data_t, N, SemiAtomic> {
|
||||||
public:
|
public:
|
||||||
// meta-identity types
|
// meta-identity types
|
||||||
using type = edeque<Data_t, N>;
|
using type = edeque<Data_t, N, SemiAtomic, Fn>;
|
||||||
using base_type = deque<Data_t, N>;
|
using base_type = deque<Data_t, N, SemiAtomic>;
|
||||||
using callable_t = Fn;
|
using callable_t = Fn;
|
||||||
|
using range_t = typename base_type::range_t;
|
||||||
|
|
||||||
// STL
|
// STL
|
||||||
using value_type = typename base_type::value_type;
|
using value_type = typename base_type::value_type;
|
||||||
@@ -89,7 +94,11 @@ class edeque : public deque<Data_t, N> {
|
|||||||
enum class size_match { DISABLED =0, EQ, NE, LT, LE, GT, GE };
|
enum class size_match { DISABLED =0, EQ, NE, LT, LE, GT, GE };
|
||||||
//! \enum data_match
|
//! \enum data_match
|
||||||
//! The type of matching for data based match
|
//! The type of matching for data based match
|
||||||
enum class data_match { DISABLED =0, MATCH, MISMATCH};
|
enum class data_match { DISABLED =0, MATCH_PUSH, MATCH_POP, MISMATCH_PUSH, MISMATCH_POP};
|
||||||
|
|
||||||
|
// TODO: trigger mode for one-shot or repeated functionality
|
||||||
|
// enum class trigger_mode { ONE_SHOT, REPEATED };
|
||||||
|
|
||||||
//! \struct size_trigger
|
//! \struct size_trigger
|
||||||
//! Size trigger data type
|
//! Size trigger data type
|
||||||
struct size_trigger {
|
struct size_trigger {
|
||||||
@@ -120,16 +129,16 @@ class edeque : public deque<Data_t, N> {
|
|||||||
constexpr edeque () noexcept :
|
constexpr edeque () noexcept :
|
||||||
base_type() { }
|
base_type() { }
|
||||||
|
|
||||||
//!
|
//! Size trigger constructor
|
||||||
constexpr edeque (size_match match, size_t size, callable_t&& fn) :
|
constexpr edeque (size_match match, size_t size, callable_t&& fn) noexcept :
|
||||||
base_type(),
|
base_type(),
|
||||||
mode_{match_mode::SIZE},
|
mode_{match_mode::SIZE},
|
||||||
callback_{std::forward<callable_t>(fn)} {
|
callback_{std::forward<callable_t>(fn)} {
|
||||||
trigger_.tsize.type = match;
|
trigger_.tsize.type = match;
|
||||||
trigger_.tsize.size = size;
|
trigger_.tsize.size = size;
|
||||||
}
|
}
|
||||||
|
//! Data trigger constructor
|
||||||
constexpr edeque (data_match match, Data_t value, callable_t&& fn) :
|
constexpr edeque (data_match match, Data_t value, callable_t&& fn) noexcept :
|
||||||
base_type(),
|
base_type(),
|
||||||
mode_{match_mode::DATA},
|
mode_{match_mode::DATA},
|
||||||
callback_{std::forward<callable_t>(fn)} {
|
callback_{std::forward<callable_t>(fn)} {
|
||||||
@@ -144,7 +153,7 @@ class edeque : public deque<Data_t, N> {
|
|||||||
//! \brief
|
//! \brief
|
||||||
//! Manually checks the size trigger and calls it we have match.
|
//! Manually checks the size trigger and calls it we have match.
|
||||||
//! \return True if the callable has called.
|
//! \return True if the callable has called.
|
||||||
bool check_trigger () {
|
bool check_trigger () noexcept {
|
||||||
return check_trigger_size_();
|
return check_trigger_size_();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +163,7 @@ class edeque : public deque<Data_t, N> {
|
|||||||
//! \param match The match type
|
//! \param match The match type
|
||||||
//! \param size The size for with we check against
|
//! \param size The size for with we check against
|
||||||
//! \param fn The callable to call on match
|
//! \param fn The callable to call on match
|
||||||
void set_trigger (size_match match, size_t size, callable_t&& fn) {
|
void set_trigger (size_match match, size_t size, callable_t&& fn) noexcept {
|
||||||
mode_ = match_mode::SIZE;
|
mode_ = match_mode::SIZE;
|
||||||
trigger_.tsize.type = match;
|
trigger_.tsize.type = match;
|
||||||
trigger_.tsize.size = size;
|
trigger_.tsize.size = size;
|
||||||
@@ -167,7 +176,7 @@ class edeque : public deque<Data_t, N> {
|
|||||||
//! \param match The match type
|
//! \param match The match type
|
||||||
//! \param value The value for with we check against
|
//! \param value The value for with we check against
|
||||||
//! \param fn The callable to call on match
|
//! \param fn The callable to call on match
|
||||||
void set_trigger (data_match match, Data_t value, callable_t&& fn) {
|
void set_trigger (data_match match, Data_t value, callable_t&& fn) noexcept {
|
||||||
mode_ = match_mode::DATA;
|
mode_ = match_mode::DATA;
|
||||||
trigger_.tdata.type = match;
|
trigger_.tdata.type = match;
|
||||||
trigger_.tdata.value= value;
|
trigger_.tdata.value= value;
|
||||||
@@ -175,36 +184,36 @@ class edeque : public deque<Data_t, N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//! \brief Manually clears the trigger
|
//! \brief Manually clears the trigger
|
||||||
void clear_trigger () {
|
void clear_trigger () noexcept {
|
||||||
mode_ = match_mode{};
|
mode_ = match_mode{};
|
||||||
trigger_ = trigger{};
|
trigger_ = trigger{};
|
||||||
callback_ = callable_t{};
|
callback_ = callable_t{};
|
||||||
}
|
}
|
||||||
//! @}
|
//! @}
|
||||||
|
|
||||||
//! \name Base class overwrites
|
//! \name Base class uses and overwrites
|
||||||
//! @{
|
//! @{
|
||||||
void push_front (const Data_t& it) {
|
void push_front (const Data_t& it) noexcept {
|
||||||
base_type::push_front(it);
|
base_type::push_front(it);
|
||||||
check_trigger_async_(it);
|
check_trigger_push_async_(it);
|
||||||
}
|
}
|
||||||
Data_t pop_front () {
|
void push_back (const Data_t& it) noexcept {
|
||||||
|
base_type::push_back(it);
|
||||||
|
check_trigger_push_async_(it);
|
||||||
|
}
|
||||||
|
Data_t pop_front () noexcept {
|
||||||
Data_t t = base_type::pop_front();
|
Data_t t = base_type::pop_front();
|
||||||
check_trigger_async_(t);
|
check_trigger_pop_async_(t);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
void push_back (const Data_t& it) {
|
Data_t pop_back () noexcept {
|
||||||
base_type::push_back(it);
|
|
||||||
check_trigger_async_(it);
|
|
||||||
}
|
|
||||||
Data_t pop_back () {
|
|
||||||
Data_t t = base_type::pop_back();
|
Data_t t = base_type::pop_back();
|
||||||
check_trigger_async_(t);
|
check_trigger_pop_async_(t);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
//! @}
|
//! @}
|
||||||
|
|
||||||
//! \name Public interface
|
//! \name Private functionality
|
||||||
//! @{
|
//! @{
|
||||||
private:
|
private:
|
||||||
//! \brief
|
//! \brief
|
||||||
@@ -230,28 +239,53 @@ class edeque : public deque<Data_t, N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//! \brief
|
//! \brief
|
||||||
//! Manually checks the data trigger and calls it we have match.
|
//! Manually checks the data trigger on push and calls it we have match.
|
||||||
//! \param it The item to check against
|
//! \param it The item to check against
|
||||||
//! \return True if the callable has called.
|
//! \return True if the callable has called.
|
||||||
bool check_trigger_value_ (const Data_t& it) {
|
bool check_trigger_push_value_ (const Data_t& it) {
|
||||||
bool match;
|
bool match;
|
||||||
switch (trigger_.tdata.type) {
|
switch (trigger_.tdata.type) {
|
||||||
default:
|
default:
|
||||||
case data_match::DISABLED: match = false; break;
|
case data_match::DISABLED: match = false; break;
|
||||||
case data_match::MATCH: match = (it == trigger_.tdata.value); break;
|
case data_match::MATCH_PUSH: match = (it == trigger_.tdata.value); break;
|
||||||
case data_match::MISMATCH: match = (it != trigger_.tdata.value); break;
|
case data_match::MISMATCH_PUSH: match = (it != trigger_.tdata.value); break;
|
||||||
}
|
}
|
||||||
if (match)
|
if (match)
|
||||||
callback_();
|
callback_();
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Wrapper for both triggers
|
//! \brief
|
||||||
bool check_trigger_async_ (const Data_t& it) {
|
//! Manually checks the data trigger on pop and calls it we have match.
|
||||||
|
//! \param it The item to check against
|
||||||
|
//! \return True if the callable has called.
|
||||||
|
bool check_trigger_pop_value_ (const Data_t& it) {
|
||||||
|
bool match;
|
||||||
|
switch (trigger_.tdata.type) {
|
||||||
|
default:
|
||||||
|
case data_match::DISABLED: match = false; break;
|
||||||
|
case data_match::MATCH_POP: match = (it == trigger_.tdata.value); break;
|
||||||
|
case data_match::MISMATCH_POP: match = (it != trigger_.tdata.value); break;
|
||||||
|
}
|
||||||
|
if (match)
|
||||||
|
callback_();
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Wrapper for both triggers at push
|
||||||
|
bool check_trigger_push_async_ (const Data_t& it) {
|
||||||
switch (mode_) {
|
switch (mode_) {
|
||||||
default:
|
default:
|
||||||
case match_mode::SIZE: return check_trigger_size_();
|
case match_mode::SIZE: return check_trigger_size_();
|
||||||
case match_mode::DATA: return check_trigger_value_(it);
|
case match_mode::DATA: return check_trigger_push_value_(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//! Wrapper for both triggers at pop
|
||||||
|
bool check_trigger_pop_async_ (const Data_t& it) {
|
||||||
|
switch (mode_) {
|
||||||
|
default:
|
||||||
|
case match_mode::SIZE: return check_trigger_size_();
|
||||||
|
case match_mode::DATA: return check_trigger_pop_value_(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//! @}
|
//! @}
|
||||||
|
|||||||
+22
-20
@@ -49,16 +49,20 @@ namespace tbx {
|
|||||||
*
|
*
|
||||||
* We also provide stream operators.
|
* We also provide stream operators.
|
||||||
*
|
*
|
||||||
* \tparam Data_t The char-like queued item type. Usually \c char
|
* \tparam Data_t The char-like queued item type. Usually \c char
|
||||||
* \tparam N The size of edeque
|
* \tparam N The size of edeque
|
||||||
* \tparam Fn The type of Callable
|
* \tparam SemiAtomic True for semi-atomic operation. In that case the \c ring_iterator is also atomic.
|
||||||
|
* \tparam Fn The type of Callable
|
||||||
|
* \note
|
||||||
|
* SemiAtomic means it is safe to for one thread to push only from front and another can pop.
|
||||||
*/
|
*/
|
||||||
template <typename Data_t, size_t N, typename Fn = std::function<void()>>
|
template <typename Data_t, size_t N, bool SemiAtomic =false, typename Fn = std::function<void()>>
|
||||||
class equeue : public edeque<Data_t, N, Fn> {
|
class equeue : public edeque<Data_t, N, SemiAtomic, Fn> {
|
||||||
public:
|
public:
|
||||||
// meta-identity types
|
// meta-identity types
|
||||||
using equeue_t = equeue<Data_t, N>;
|
using equeue_t = equeue<Data_t, N, SemiAtomic, Fn>;
|
||||||
using base_type = edeque<Data_t, N, Fn>;
|
using base_type = edeque<Data_t, N, SemiAtomic, Fn>;
|
||||||
|
using range_t = typename base_type::range_t;
|
||||||
|
|
||||||
// STL
|
// STL
|
||||||
using value_type = typename base_type::value_type;
|
using value_type = typename base_type::value_type;
|
||||||
@@ -77,10 +81,7 @@ class equeue : public edeque<Data_t, N, Fn> {
|
|||||||
//! Default constructor
|
//! Default constructor
|
||||||
constexpr equeue () noexcept : base_type() { }
|
constexpr equeue () noexcept : base_type() { }
|
||||||
|
|
||||||
//! fill contructor
|
//! Forward constructor
|
||||||
constexpr equeue(const Data_t& value) noexcept : base_type(value) { }
|
|
||||||
|
|
||||||
//! Initializer list contructor
|
|
||||||
template <typename ...It>
|
template <typename ...It>
|
||||||
constexpr equeue(It&& ...it) noexcept : base_type(std::forward<It>(it)...) { }
|
constexpr equeue(It&& ...it) noexcept : base_type(std::forward<It>(it)...) { }
|
||||||
|
|
||||||
@@ -91,26 +92,26 @@ class equeue : public edeque<Data_t, N, Fn> {
|
|||||||
public:
|
public:
|
||||||
//! \brief Push an item in the back of the queue
|
//! \brief Push an item in the back of the queue
|
||||||
//! \param it The item to push
|
//! \param it The item to push
|
||||||
void push (const Data_t& it) {
|
void push (const Data_t& it) noexcept {
|
||||||
base_type::push_back(it);
|
base_type::push_back(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \brief Extract an item from the front of the queue and remove it from the queue
|
//! \brief Extract an item from the front of the queue and remove it from the queue
|
||||||
//! \param it The item to push
|
//! \param it The item to push
|
||||||
Data_t pop () {
|
Data_t pop () noexcept {
|
||||||
return base_type::pop_front();
|
return base_type::pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \brief Push an item in the back of the queue
|
//! \brief Push an item in the back of the queue
|
||||||
//! \param it The item to push
|
//! \param it The item to push
|
||||||
equeue_t& operator<< (const Data_t& it) {
|
equeue_t& operator<< (const Data_t& it) noexcept {
|
||||||
push(it);
|
push(it);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \brief Push an item in the back of the queue
|
//! \brief Push an item in the back of the queue
|
||||||
//! \param it The item to push
|
//! \param it The item to push
|
||||||
equeue_t& operator>> (Data_t& it) {
|
equeue_t& operator>> (Data_t& it) noexcept {
|
||||||
it = pop();
|
it = pop();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -125,16 +126,17 @@ class equeue : public edeque<Data_t, N, Fn> {
|
|||||||
*
|
*
|
||||||
* This definition enables the "data << equeue" syntax for pop operation
|
* This definition enables the "data << equeue" syntax for pop operation
|
||||||
*
|
*
|
||||||
* \tparam Data_t The char-like queued item type. Usually \c char
|
* \tparam Data_t The char-like queued item type. Usually \c char
|
||||||
* \tparam N The size of queue
|
* \tparam N The size of queue
|
||||||
* \tparam Fn The type of Callable
|
* \tparam SemiAtomic True for semi-atomic operation. In that case the \c ring_iterator is also atomic.
|
||||||
|
* \tparam Fn The type of Callable
|
||||||
*
|
*
|
||||||
* \param it The item to write to
|
* \param it The item to write to
|
||||||
* \param q The queue to read from
|
* \param q The queue to read from
|
||||||
* \return Reference to the returned item
|
* \return Reference to the returned item
|
||||||
*/
|
*/
|
||||||
template <typename Data_t, size_t N, typename Fn = std::function<void()>>
|
template <typename Data_t, size_t N, bool SemiAtomic =false, typename Fn = std::function<void()>>
|
||||||
Data_t& operator<< (Data_t& it, equeue<Data_t, N, Fn>& q) {
|
Data_t& operator<< (Data_t& it, equeue<Data_t, N, SemiAtomic, Fn>& q) noexcept {
|
||||||
it = q.pop();
|
it = q.pop();
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-14
@@ -48,15 +48,19 @@ namespace tbx {
|
|||||||
*
|
*
|
||||||
* We also provide stream operators.
|
* We also provide stream operators.
|
||||||
*
|
*
|
||||||
* \tparam Data_t The char-like queued item type. Usually \c char
|
* \tparam Data_t The char-like queued item type. Usually \c char
|
||||||
* \tparam N The size of queue
|
* \tparam N The size of queue
|
||||||
|
* \tparam SemiAtomic True for semi-atomic operation. In that case the \c ring_iterator is also atomic.
|
||||||
|
* \note
|
||||||
|
* SemiAtomic means it is safe to for one thread to push only from front and another can pop.
|
||||||
*/
|
*/
|
||||||
template <typename Data_t, size_t N>
|
template <typename Data_t, size_t N, bool SemiAtomic =false>
|
||||||
class queue : public deque<Data_t, N> {
|
class queue : public deque<Data_t, N, SemiAtomic> {
|
||||||
public:
|
public:
|
||||||
// meta-identity types
|
// meta-identity types
|
||||||
using queue_t = queue<Data_t, N>;
|
using queue_t = queue<Data_t, N, SemiAtomic>;
|
||||||
using base_type = deque<Data_t, N>;
|
using base_type = deque<Data_t, N, SemiAtomic>;
|
||||||
|
using range_t = typename base_type::range_t;
|
||||||
|
|
||||||
// STL
|
// STL
|
||||||
using value_type = typename base_type::value_type;
|
using value_type = typename base_type::value_type;
|
||||||
@@ -89,26 +93,26 @@ class queue : public deque<Data_t, N> {
|
|||||||
public:
|
public:
|
||||||
//! \brief Push an item in the back of the queue
|
//! \brief Push an item in the back of the queue
|
||||||
//! \param it The item to push
|
//! \param it The item to push
|
||||||
constexpr void push (const Data_t& it) {
|
constexpr void push (const Data_t& it) noexcept {
|
||||||
base_type::push_back(it);
|
base_type::push_back(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \brief Extract an item from the front of the queue and remove it from the queue
|
//! \brief Extract an item from the front of the queue and remove it from the queue
|
||||||
//! \param it The item to push
|
//! \param it The item to push
|
||||||
constexpr Data_t pop () {
|
constexpr Data_t pop () noexcept {
|
||||||
return base_type::pop_front();
|
return base_type::pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \brief Push an item in the back of the queue
|
//! \brief Push an item in the back of the queue
|
||||||
//! \param it The item to push
|
//! \param it The item to push
|
||||||
constexpr queue_t& operator<< (const Data_t& it) {
|
constexpr queue_t& operator<< (const Data_t& it) noexcept {
|
||||||
push(it);
|
push(it);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \brief Pop an item from the front of the queue
|
//! \brief Pop an item from the front of the queue
|
||||||
//! \param it The item to write to
|
//! \param it The item to write to
|
||||||
constexpr queue_t& operator>> (Data_t& it) {
|
constexpr queue_t& operator>> (Data_t& it) noexcept {
|
||||||
it = pop();
|
it = pop();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -121,15 +125,16 @@ class queue : public deque<Data_t, N> {
|
|||||||
*
|
*
|
||||||
* This definition enables the "data << queue" syntax for pop operation
|
* This definition enables the "data << queue" syntax for pop operation
|
||||||
*
|
*
|
||||||
* \tparam Data_t The char-like queued item type. Usually \c char
|
* \tparam Data_t The char-like queued item type. Usually \c char
|
||||||
* \tparam N The size of queue
|
* \tparam N The size of queue
|
||||||
|
* \tparam SemiAtomic True for semi-atomic operation. In that case the \c ring_iterator is also atomic.
|
||||||
*
|
*
|
||||||
* \param it The item to write to
|
* \param it The item to write to
|
||||||
* \param q The queue to read from
|
* \param q The queue to read from
|
||||||
* \return Reference to the returned item
|
* \return Reference to the returned item
|
||||||
*/
|
*/
|
||||||
template <typename Data_t, size_t N>
|
template <typename Data_t, size_t N, bool SemiAtomic =false>
|
||||||
constexpr Data_t& operator<< (Data_t& it, queue<Data_t, N>& q) {
|
constexpr Data_t& operator<< (Data_t& it, queue<Data_t, N, SemiAtomic>& q) noexcept {
|
||||||
it = q.pop();
|
it = q.pop();
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*!
|
||||||
|
* \file cont/range.h
|
||||||
|
* \brief
|
||||||
|
* A plain definition of a range struct with agregate initialization
|
||||||
|
* and begin-end pairs.
|
||||||
|
*
|
||||||
|
* \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_CONT_RANGE_H_
|
||||||
|
#define TBX_CONT_RANGE_H_
|
||||||
|
|
||||||
|
#include <core/core.h>
|
||||||
|
|
||||||
|
namespace tbx {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* A plain definition of a range struct with begin-end pairs.
|
||||||
|
*
|
||||||
|
* \tparam Iter_t The iterator type of the range
|
||||||
|
*/
|
||||||
|
template <typename Iter_t>
|
||||||
|
struct range {
|
||||||
|
Iter_t b{}, e{};
|
||||||
|
|
||||||
|
// range () = default;
|
||||||
|
// range (const Iter_t& first, const Iter_t& last) noexcept :
|
||||||
|
// b(first), e(last) { }
|
||||||
|
// range (Iter_t first, Iter_t last) noexcept :
|
||||||
|
// b(first), e(last) { }
|
||||||
|
|
||||||
|
Iter_t begin() { return b; }
|
||||||
|
const Iter_t begin() const { return b; }
|
||||||
|
const Iter_t cbegin() const { return b; }
|
||||||
|
Iter_t end() { return e; }
|
||||||
|
const Iter_t end() const { return e; }
|
||||||
|
const Iter_t cend() const { return e; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif /* TBX_CONT_RANGE_H_ */
|
||||||
@@ -0,0 +1,607 @@
|
|||||||
|
/*
|
||||||
|
* This is an implementation of C++20's std::span
|
||||||
|
* http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/n4820.pdf
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Copyright Tristan Brindle 2018.
|
||||||
|
// Distributed under the Boost Software License, Version 1.0.
|
||||||
|
// https://www.boost.org/LICENSE_1_0.txt
|
||||||
|
|
||||||
|
#ifndef TBX_CONT_SPAN_H_
|
||||||
|
#define TBX_CONT_SPAN_H_
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#ifndef TBX_SPAN_NO_EXCEPTIONS
|
||||||
|
// Attempt to discover whether we're being compiled with exception support
|
||||||
|
#if !(defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND))
|
||||||
|
#define TBX_SPAN_NO_EXCEPTIONS
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef TBX_SPAN_NO_EXCEPTIONS
|
||||||
|
#include <cstdio>
|
||||||
|
#include <stdexcept>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Various feature test macros
|
||||||
|
|
||||||
|
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
|
||||||
|
#define TBX_SPAN_HAVE_CPP17
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)
|
||||||
|
#define TBX_SPAN_HAVE_CPP14
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace tbx {
|
||||||
|
|
||||||
|
// Establish default contract checking behavior
|
||||||
|
#if !defined(TBX_SPAN_THROW_ON_CONTRACT_VIOLATION) && \
|
||||||
|
!defined(TBX_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) && \
|
||||||
|
!defined(TBX_SPAN_NO_CONTRACT_CHECKING)
|
||||||
|
#if defined(NDEBUG) || !defined(TBX_SPAN_HAVE_CPP14)
|
||||||
|
#define TBX_SPAN_NO_CONTRACT_CHECKING
|
||||||
|
#else
|
||||||
|
#define TBX_SPAN_TERMINATE_ON_CONTRACT_VIOLATION
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(TBX_SPAN_THROW_ON_CONTRACT_VIOLATION)
|
||||||
|
struct contract_violation_error : std::logic_error {
|
||||||
|
explicit contract_violation_error(const char* msg) : std::logic_error(msg)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void contract_violation(const char* msg)
|
||||||
|
{
|
||||||
|
throw contract_violation_error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(TBX_SPAN_TERMINATE_ON_CONTRACT_VIOLATION)
|
||||||
|
[[noreturn]] inline void contract_violation(const char* /*unused*/)
|
||||||
|
{
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(TBX_SPAN_NO_CONTRACT_CHECKING)
|
||||||
|
#define TBX_SPAN_STRINGIFY(cond) #cond
|
||||||
|
#define TBX_SPAN_EXPECT(cond) \
|
||||||
|
cond ? (void) 0 : contract_violation("Expected " TBX_SPAN_STRINGIFY(cond))
|
||||||
|
#else
|
||||||
|
#define TBX_SPAN_EXPECT(cond)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(TBX_SPAN_HAVE_CPP17) || defined(__cpp_inline_variables)
|
||||||
|
#define TBX_SPAN_INLINE_VAR inline
|
||||||
|
#else
|
||||||
|
#define TBX_SPAN_INLINE_VAR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(TBX_SPAN_HAVE_CPP14) || \
|
||||||
|
(defined(__cpp_constexpr) && __cpp_constexpr >= 201304)
|
||||||
|
#define TBX_SPAN_HAVE_CPP14_CONSTEXPR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(TBX_SPAN_HAVE_CPP14_CONSTEXPR)
|
||||||
|
#define TBX_SPAN_CONSTEXPR14 constexpr
|
||||||
|
#else
|
||||||
|
#define TBX_SPAN_CONSTEXPR14
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(TBX_SPAN_HAVE_CPP14_CONSTEXPR) && \
|
||||||
|
(!defined(_MSC_VER) || _MSC_VER > 1900)
|
||||||
|
#define TBX_SPAN_CONSTEXPR_ASSIGN constexpr
|
||||||
|
#else
|
||||||
|
#define TBX_SPAN_CONSTEXPR_ASSIGN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(TBX_SPAN_NO_CONTRACT_CHECKING)
|
||||||
|
#define TBX_SPAN_CONSTEXPR11 constexpr
|
||||||
|
#else
|
||||||
|
#define TBX_SPAN_CONSTEXPR11 TBX_SPAN_CONSTEXPR14
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(TBX_SPAN_HAVE_CPP17) || defined(__cpp_deduction_guides)
|
||||||
|
#define TBX_SPAN_HAVE_DEDUCTION_GUIDES
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(TBX_SPAN_HAVE_CPP17) || defined(__cpp_lib_byte)
|
||||||
|
#define TBX_SPAN_HAVE_STD_BYTE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(TBX_SPAN_HAVE_CPP17) || defined(__cpp_lib_array_constexpr)
|
||||||
|
#define TBX_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(TBX_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC)
|
||||||
|
#define TBX_SPAN_ARRAY_CONSTEXPR constexpr
|
||||||
|
#else
|
||||||
|
#define TBX_SPAN_ARRAY_CONSTEXPR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef TBX_SPAN_HAVE_STD_BYTE
|
||||||
|
using byte = std::byte;
|
||||||
|
#else
|
||||||
|
using byte = unsigned char;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(TBX_SPAN_HAVE_CPP17)
|
||||||
|
#define TBX_SPAN_NODISCARD [[nodiscard]]
|
||||||
|
#else
|
||||||
|
#define TBX_SPAN_NODISCARD
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TBX_SPAN_INLINE_VAR constexpr std::size_t dynamic_extent = SIZE_MAX;
|
||||||
|
|
||||||
|
template <typename ElementType, std::size_t Extent = dynamic_extent>
|
||||||
|
class span;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template <typename E, std::size_t S>
|
||||||
|
struct span_storage {
|
||||||
|
constexpr span_storage() noexcept = default;
|
||||||
|
|
||||||
|
constexpr span_storage(E* p_ptr, std::size_t /*unused*/) noexcept
|
||||||
|
: ptr(p_ptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
E* ptr = nullptr;
|
||||||
|
static constexpr std::size_t size = S;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename E>
|
||||||
|
struct span_storage<E, dynamic_extent> {
|
||||||
|
constexpr span_storage() noexcept = default;
|
||||||
|
|
||||||
|
constexpr span_storage(E* p_ptr, std::size_t p_size) noexcept
|
||||||
|
: ptr(p_ptr), size(p_size)
|
||||||
|
{}
|
||||||
|
|
||||||
|
E* ptr = nullptr;
|
||||||
|
std::size_t size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reimplementation of C++17 std::size() and std::data()
|
||||||
|
#if defined(TBX_SPAN_HAVE_CPP17) || \
|
||||||
|
defined(__cpp_lib_nonmember_container_access)
|
||||||
|
using std::data;
|
||||||
|
using std::size;
|
||||||
|
#else
|
||||||
|
template <class C>
|
||||||
|
constexpr auto size(const C& c) -> decltype(c.size())
|
||||||
|
{
|
||||||
|
return c.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, std::size_t N>
|
||||||
|
constexpr std::size_t size(const T (&)[N]) noexcept
|
||||||
|
{
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class C>
|
||||||
|
constexpr auto data(C& c) -> decltype(c.data())
|
||||||
|
{
|
||||||
|
return c.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class C>
|
||||||
|
constexpr auto data(const C& c) -> decltype(c.data())
|
||||||
|
{
|
||||||
|
return c.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, std::size_t N>
|
||||||
|
constexpr T* data(T (&array)[N]) noexcept
|
||||||
|
{
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class E>
|
||||||
|
constexpr const E* data(std::initializer_list<E> il) noexcept
|
||||||
|
{
|
||||||
|
return il.begin();
|
||||||
|
}
|
||||||
|
#endif // TBX_SPAN_HAVE_CPP17
|
||||||
|
|
||||||
|
#if defined(TBX_SPAN_HAVE_CPP17) || defined(__cpp_lib_void_t)
|
||||||
|
using std::void_t;
|
||||||
|
#else
|
||||||
|
template <typename...>
|
||||||
|
using void_t = void;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using uncvref_t =
|
||||||
|
typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||||
|
|
||||||
|
template <typename>
|
||||||
|
struct is_span : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T, std::size_t S>
|
||||||
|
struct is_span<span<T, S>> : std::true_type {};
|
||||||
|
|
||||||
|
template <typename>
|
||||||
|
struct is_std_array : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
struct is_std_array<std::array<T, N>> : std::true_type {};
|
||||||
|
|
||||||
|
template <typename, typename = void>
|
||||||
|
struct has_size_and_data : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct has_size_and_data<T, void_t<decltype(detail::size(std::declval<T>())),
|
||||||
|
decltype(detail::data(std::declval<T>()))>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename C, typename U = uncvref_t<C>>
|
||||||
|
struct is_container {
|
||||||
|
static constexpr bool value =
|
||||||
|
!is_span<U>::value && !is_std_array<U>::value &&
|
||||||
|
!std::is_array<U>::value && has_size_and_data<C>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using remove_pointer_t = typename std::remove_pointer<T>::type;
|
||||||
|
|
||||||
|
template <typename, typename, typename = void>
|
||||||
|
struct is_container_element_type_compatible : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T, typename E>
|
||||||
|
struct is_container_element_type_compatible<
|
||||||
|
T, E,
|
||||||
|
typename std::enable_if<
|
||||||
|
!std::is_same<typename std::remove_cv<decltype(
|
||||||
|
detail::data(std::declval<T>()))>::type,
|
||||||
|
void>::value>::type>
|
||||||
|
: std::is_convertible<
|
||||||
|
remove_pointer_t<decltype(detail::data(std::declval<T>()))> (*)[],
|
||||||
|
E (*)[]> {};
|
||||||
|
|
||||||
|
template <typename, typename = size_t>
|
||||||
|
struct is_complete : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct is_complete<T, decltype(sizeof(T))> : std::true_type {};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename ElementType, std::size_t Extent>
|
||||||
|
class span {
|
||||||
|
static_assert(std::is_object<ElementType>::value,
|
||||||
|
"A span's ElementType must be an object type (not a "
|
||||||
|
"reference type or void)");
|
||||||
|
static_assert(detail::is_complete<ElementType>::value,
|
||||||
|
"A span's ElementType must be a complete type (not a forward "
|
||||||
|
"declaration)");
|
||||||
|
static_assert(!std::is_abstract<ElementType>::value,
|
||||||
|
"A span's ElementType cannot be an abstract class type");
|
||||||
|
|
||||||
|
using storage_type = detail::span_storage<ElementType, Extent>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// constants and types
|
||||||
|
using element_type = ElementType;
|
||||||
|
using value_type = typename std::remove_cv<ElementType>::type;
|
||||||
|
using size_type = std::size_t;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using pointer = element_type*;
|
||||||
|
using const_pointer = const element_type*;
|
||||||
|
using reference = element_type&;
|
||||||
|
using const_reference = const element_type&;
|
||||||
|
using iterator = pointer;
|
||||||
|
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||||
|
|
||||||
|
static constexpr size_type extent = Extent;
|
||||||
|
|
||||||
|
// [span.cons], span constructors, copy, assignment, and destructor
|
||||||
|
template <
|
||||||
|
std::size_t E = Extent,
|
||||||
|
typename std::enable_if<(E == dynamic_extent || E <= 0), int>::type = 0>
|
||||||
|
constexpr span() noexcept
|
||||||
|
{}
|
||||||
|
|
||||||
|
TBX_SPAN_CONSTEXPR11 span(pointer ptr, size_type count)
|
||||||
|
: storage_(ptr, count)
|
||||||
|
{
|
||||||
|
TBX_SPAN_EXPECT(extent == dynamic_extent || count == extent);
|
||||||
|
}
|
||||||
|
|
||||||
|
TBX_SPAN_CONSTEXPR11 span(pointer first_elem, pointer last_elem)
|
||||||
|
: storage_(first_elem, last_elem - first_elem)
|
||||||
|
{
|
||||||
|
TBX_SPAN_EXPECT(extent == dynamic_extent ||
|
||||||
|
last_elem - first_elem ==
|
||||||
|
static_cast<std::ptrdiff_t>(extent));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N, std::size_t E = Extent,
|
||||||
|
typename std::enable_if<
|
||||||
|
(E == dynamic_extent || N == E) &&
|
||||||
|
detail::is_container_element_type_compatible<
|
||||||
|
element_type (&)[N], ElementType>::value,
|
||||||
|
int>::type = 0>
|
||||||
|
constexpr span(element_type (&arr)[N]) noexcept : storage_(arr, N)
|
||||||
|
{}
|
||||||
|
|
||||||
|
template <std::size_t N, std::size_t E = Extent,
|
||||||
|
typename std::enable_if<
|
||||||
|
(E == dynamic_extent || N == E) &&
|
||||||
|
detail::is_container_element_type_compatible<
|
||||||
|
std::array<value_type, N>&, ElementType>::value,
|
||||||
|
int>::type = 0>
|
||||||
|
TBX_SPAN_ARRAY_CONSTEXPR span(std::array<value_type, N>& arr) noexcept
|
||||||
|
: storage_(arr.data(), N)
|
||||||
|
{}
|
||||||
|
|
||||||
|
template <std::size_t N, std::size_t E = Extent,
|
||||||
|
typename std::enable_if<
|
||||||
|
(E == dynamic_extent || N == E) &&
|
||||||
|
detail::is_container_element_type_compatible<
|
||||||
|
const std::array<value_type, N>&, ElementType>::value,
|
||||||
|
int>::type = 0>
|
||||||
|
TBX_SPAN_ARRAY_CONSTEXPR span(const std::array<value_type, N>& arr) noexcept
|
||||||
|
: storage_(arr.data(), N)
|
||||||
|
{}
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename Container, std::size_t E = Extent,
|
||||||
|
typename std::enable_if<
|
||||||
|
E == dynamic_extent && detail::is_container<Container>::value &&
|
||||||
|
detail::is_container_element_type_compatible<
|
||||||
|
Container&, ElementType>::value,
|
||||||
|
int>::type = 0>
|
||||||
|
constexpr span(Container& cont)
|
||||||
|
// : storage_(detail::data(cont), detail::size(cont))
|
||||||
|
: storage_(cont.data(), cont.size())
|
||||||
|
{}
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename Container, std::size_t E = Extent,
|
||||||
|
typename std::enable_if<
|
||||||
|
E == dynamic_extent && detail::is_container<Container>::value &&
|
||||||
|
detail::is_container_element_type_compatible<
|
||||||
|
const Container&, ElementType>::value,
|
||||||
|
int>::type = 0>
|
||||||
|
constexpr span(const Container& cont)
|
||||||
|
: storage_(detail::data(cont), detail::size(cont))
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr span(const span& other) noexcept = default;
|
||||||
|
|
||||||
|
template <typename OtherElementType, std::size_t OtherExtent,
|
||||||
|
typename std::enable_if<
|
||||||
|
(Extent == OtherExtent || Extent == dynamic_extent) &&
|
||||||
|
std::is_convertible<OtherElementType (*)[],
|
||||||
|
ElementType (*)[]>::value,
|
||||||
|
int>::type = 0>
|
||||||
|
constexpr span(const span<OtherElementType, OtherExtent>& other) noexcept
|
||||||
|
: storage_(other.data(), other.size())
|
||||||
|
{}
|
||||||
|
|
||||||
|
~span() noexcept = default;
|
||||||
|
|
||||||
|
TBX_SPAN_CONSTEXPR_ASSIGN span&
|
||||||
|
operator=(const span& other) noexcept = default;
|
||||||
|
|
||||||
|
// [span.sub], span subviews
|
||||||
|
template <std::size_t Count>
|
||||||
|
TBX_SPAN_CONSTEXPR11 span<element_type, Count> first() const
|
||||||
|
{
|
||||||
|
TBX_SPAN_EXPECT(Count <= size());
|
||||||
|
return {data(), Count};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t Count>
|
||||||
|
TBX_SPAN_CONSTEXPR11 span<element_type, Count> last() const
|
||||||
|
{
|
||||||
|
TBX_SPAN_EXPECT(Count <= size());
|
||||||
|
return {data() + (size() - Count), Count};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t Offset, std::size_t Count = dynamic_extent>
|
||||||
|
using subspan_return_t =
|
||||||
|
span<ElementType, Count != dynamic_extent
|
||||||
|
? Count
|
||||||
|
: (Extent != dynamic_extent ? Extent - Offset
|
||||||
|
: dynamic_extent)>;
|
||||||
|
|
||||||
|
template <std::size_t Offset, std::size_t Count = dynamic_extent>
|
||||||
|
TBX_SPAN_CONSTEXPR11 subspan_return_t<Offset, Count> subspan() const
|
||||||
|
{
|
||||||
|
TBX_SPAN_EXPECT(Offset <= size() &&
|
||||||
|
(Count == dynamic_extent || Offset + Count <= size()));
|
||||||
|
return {data() + Offset,
|
||||||
|
Count != dynamic_extent ? Count : size() - Offset};
|
||||||
|
}
|
||||||
|
|
||||||
|
TBX_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>
|
||||||
|
first(size_type count) const
|
||||||
|
{
|
||||||
|
TBX_SPAN_EXPECT(count <= size());
|
||||||
|
return {data(), count};
|
||||||
|
}
|
||||||
|
|
||||||
|
TBX_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>
|
||||||
|
last(size_type count) const
|
||||||
|
{
|
||||||
|
TBX_SPAN_EXPECT(count <= size());
|
||||||
|
return {data() + (size() - count), count};
|
||||||
|
}
|
||||||
|
|
||||||
|
TBX_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>
|
||||||
|
subspan(size_type offset, size_type count = dynamic_extent) const
|
||||||
|
{
|
||||||
|
TBX_SPAN_EXPECT(offset <= size() &&
|
||||||
|
(count == dynamic_extent || offset + count <= size()));
|
||||||
|
return {data() + offset,
|
||||||
|
count == dynamic_extent ? size() - offset : count};
|
||||||
|
}
|
||||||
|
|
||||||
|
// [span.obs], span observers
|
||||||
|
constexpr size_type size() const noexcept { return storage_.size; }
|
||||||
|
|
||||||
|
constexpr size_type size_bytes() const noexcept
|
||||||
|
{
|
||||||
|
return size() * sizeof(element_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
TBX_SPAN_NODISCARD constexpr bool empty() const noexcept
|
||||||
|
{
|
||||||
|
return size() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [span.elem], span element access
|
||||||
|
TBX_SPAN_CONSTEXPR11 reference operator[](size_type idx) const
|
||||||
|
{
|
||||||
|
TBX_SPAN_EXPECT(idx < size());
|
||||||
|
return *(data() + idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
TBX_SPAN_CONSTEXPR11 reference front() const
|
||||||
|
{
|
||||||
|
TBX_SPAN_EXPECT(!empty());
|
||||||
|
return *data();
|
||||||
|
}
|
||||||
|
|
||||||
|
TBX_SPAN_CONSTEXPR11 reference back() const
|
||||||
|
{
|
||||||
|
TBX_SPAN_EXPECT(!empty());
|
||||||
|
return *(data() + (size() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr pointer data() const noexcept { return storage_.ptr; }
|
||||||
|
|
||||||
|
// [span.iterators], span iterator support
|
||||||
|
constexpr iterator begin() const noexcept { return data(); }
|
||||||
|
|
||||||
|
constexpr iterator end() const noexcept { return data() + size(); }
|
||||||
|
|
||||||
|
TBX_SPAN_ARRAY_CONSTEXPR reverse_iterator rbegin() const noexcept
|
||||||
|
{
|
||||||
|
return reverse_iterator(end());
|
||||||
|
}
|
||||||
|
|
||||||
|
TBX_SPAN_ARRAY_CONSTEXPR reverse_iterator rend() const noexcept
|
||||||
|
{
|
||||||
|
return reverse_iterator(begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
storage_type storage_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef TBX_SPAN_HAVE_DEDUCTION_GUIDES
|
||||||
|
|
||||||
|
/* Deduction Guides */
|
||||||
|
template <class T, size_t N>
|
||||||
|
span(T (&)[N])->span<T, N>;
|
||||||
|
|
||||||
|
template <class T, size_t N>
|
||||||
|
span(std::array<T, N>&)->span<T, N>;
|
||||||
|
|
||||||
|
template <class T, size_t N>
|
||||||
|
span(const std::array<T, N>&)->span<const T, N>;
|
||||||
|
|
||||||
|
template <class Container>
|
||||||
|
span(Container&)->span<typename Container::value_type>;
|
||||||
|
|
||||||
|
template <class Container>
|
||||||
|
span(const Container&)->span<const typename Container::value_type>;
|
||||||
|
|
||||||
|
#endif // TCB_HAVE_DEDUCTION_GUIDES
|
||||||
|
|
||||||
|
template <typename ElementType, std::size_t Extent>
|
||||||
|
constexpr span<ElementType, Extent>
|
||||||
|
make_span(span<ElementType, Extent> s) noexcept
|
||||||
|
{
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
constexpr span<T, N> make_span(T (&arr)[N]) noexcept
|
||||||
|
{
|
||||||
|
return {arr};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
TBX_SPAN_ARRAY_CONSTEXPR span<T, N> make_span(std::array<T, N>& arr) noexcept
|
||||||
|
{
|
||||||
|
return {arr};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
TBX_SPAN_ARRAY_CONSTEXPR span<const T, N>
|
||||||
|
make_span(const std::array<T, N>& arr) noexcept
|
||||||
|
{
|
||||||
|
return {arr};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Container>
|
||||||
|
constexpr span<typename Container::value_type> make_span(Container& cont)
|
||||||
|
{
|
||||||
|
return {cont};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Container>
|
||||||
|
constexpr span<const typename Container::value_type>
|
||||||
|
make_span(const Container& cont)
|
||||||
|
{
|
||||||
|
return {cont};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ElementType, std::size_t Extent>
|
||||||
|
span<const byte, ((Extent == dynamic_extent) ? dynamic_extent
|
||||||
|
: sizeof(ElementType) * Extent)>
|
||||||
|
as_bytes(span<ElementType, Extent> s) noexcept
|
||||||
|
{
|
||||||
|
return {reinterpret_cast<const byte*>(s.data()), s.size_bytes()};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
class ElementType, size_t Extent,
|
||||||
|
typename std::enable_if<!std::is_const<ElementType>::value, int>::type = 0>
|
||||||
|
span<byte, ((Extent == dynamic_extent) ? dynamic_extent
|
||||||
|
: sizeof(ElementType) * Extent)>
|
||||||
|
as_writable_bytes(span<ElementType, Extent> s) noexcept
|
||||||
|
{
|
||||||
|
return {reinterpret_cast<byte*>(s.data()), s.size_bytes()};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N, typename E, std::size_t S>
|
||||||
|
constexpr auto get(span<E, S> s) -> decltype(s[N])
|
||||||
|
{
|
||||||
|
return s[N];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tbx
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
|
||||||
|
template <typename ElementType, size_t Extent>
|
||||||
|
class tuple_size<tbx::span<ElementType, Extent>>
|
||||||
|
: public integral_constant<size_t, Extent> {};
|
||||||
|
|
||||||
|
template <typename ElementType>
|
||||||
|
class tuple_size<tbx::span<
|
||||||
|
ElementType, tbx::dynamic_extent>>; // not defined
|
||||||
|
|
||||||
|
template <size_t I, typename ElementType, size_t Extent>
|
||||||
|
class tuple_element<I, tbx::span<ElementType, Extent>> {
|
||||||
|
public:
|
||||||
|
static_assert(Extent != tbx::dynamic_extent &&
|
||||||
|
I < Extent,
|
||||||
|
"");
|
||||||
|
using type = ElementType;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // end namespace std
|
||||||
|
|
||||||
|
#endif // TBX_CONT_SPAN_H_
|
||||||
@@ -35,10 +35,11 @@
|
|||||||
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
namespace tbx {
|
namespace tbx {
|
||||||
|
|
||||||
template<typename Iter_t, size_t N>
|
template<typename Iter_t, size_t N, bool Atomic=false>
|
||||||
class ring_iterator {
|
class ring_iterator {
|
||||||
//! \name STL iterator traits "forwarding"
|
//! \name STL iterator traits "forwarding"
|
||||||
//! @{
|
//! @{
|
||||||
@@ -164,6 +165,9 @@ class ring_iterator {
|
|||||||
return N;
|
return N;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr operator Iter_t() noexcept { return iter_; }
|
||||||
|
constexpr operator const Iter_t() const noexcept { return iter_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Iter_t base_;
|
Iter_t base_;
|
||||||
Iter_t iter_;
|
Iter_t iter_;
|
||||||
@@ -226,6 +230,224 @@ noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<typename Iter_t, size_t N>
|
||||||
|
class ring_iterator<Iter_t, N, true> {
|
||||||
|
//! \name STL iterator traits "forwarding"
|
||||||
|
//! @{
|
||||||
|
protected:
|
||||||
|
using traits_type = std::iterator_traits<Iter_t>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using iterator_type = Iter_t;
|
||||||
|
using iterator_category = typename traits_type::iterator_category;
|
||||||
|
using value_type = typename traits_type::value_type;
|
||||||
|
using difference_type = typename traits_type::difference_type;
|
||||||
|
using reference = typename traits_type::reference;
|
||||||
|
using pointer = typename traits_type::pointer;
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
|
||||||
|
//! \name Constructor / Destructor
|
||||||
|
//! @{
|
||||||
|
public:
|
||||||
|
constexpr ring_iterator(const Iter_t base =nullptr) noexcept :
|
||||||
|
base_(base), iter_(base) { }
|
||||||
|
|
||||||
|
constexpr ring_iterator(const Iter_t base, size_t elem) noexcept :
|
||||||
|
base_(base), iter_(base + elem) { }
|
||||||
|
|
||||||
|
constexpr ring_iterator(const ring_iterator& it) noexcept :
|
||||||
|
base_(it.base_) {
|
||||||
|
iter_ = it.iter_.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr ring_iterator& operator= (const ring_iterator& it) noexcept {
|
||||||
|
base_ = it.base_;
|
||||||
|
iter_ = it.iter_.load(std::memory_order_acquire);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
//! \name Forward iterator requirements
|
||||||
|
//! @{
|
||||||
|
public:
|
||||||
|
constexpr reference operator*() const noexcept {
|
||||||
|
return *iter_.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr pointer operator->() const noexcept {
|
||||||
|
return iter_.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr ring_iterator& operator++() noexcept {
|
||||||
|
Iter_t itnew, it = iter_.load(std::memory_order_acquire);
|
||||||
|
do {
|
||||||
|
itnew = it;
|
||||||
|
if (static_cast<size_t>(++itnew - base_) >= N)
|
||||||
|
itnew = base_;
|
||||||
|
} while (!iter_.compare_exchange_weak(it, itnew, std::memory_order_acq_rel));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
constexpr ring_iterator operator++(int) noexcept {
|
||||||
|
ring_iterator ret = *this;
|
||||||
|
Iter_t itnew, it = iter_.load(std::memory_order_acquire);
|
||||||
|
do {
|
||||||
|
itnew = it;
|
||||||
|
if (static_cast<size_t>(++itnew - base_) >= N)
|
||||||
|
itnew = base_;
|
||||||
|
} while (!iter_.compare_exchange_weak(it, itnew, std::memory_order_acq_rel));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
//! \name Bidirectional iterator requirements
|
||||||
|
//! @{
|
||||||
|
public:
|
||||||
|
constexpr ring_iterator& operator--() noexcept {
|
||||||
|
Iter_t itnew, it = iter_.load(std::memory_order_acquire);
|
||||||
|
do {
|
||||||
|
itnew = it;
|
||||||
|
if (--itnew < base_)
|
||||||
|
itnew = base_ + N -1;
|
||||||
|
} while (!iter_.compare_exchange_weak(it, itnew, std::memory_order_acq_rel));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
constexpr ring_iterator operator--(int) noexcept {
|
||||||
|
ring_iterator ret = *this;
|
||||||
|
Iter_t itnew, it = iter_.load(std::memory_order_acquire);
|
||||||
|
do {
|
||||||
|
itnew = it;
|
||||||
|
if (--itnew < base_)
|
||||||
|
itnew = base_ + N -1;
|
||||||
|
} while (!iter_.compare_exchange_weak(it, itnew, std::memory_order_acq_rel));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
//! \name Random access iterator requirements
|
||||||
|
//! @{
|
||||||
|
constexpr reference operator[](difference_type n) const noexcept {
|
||||||
|
difference_type k = iter_.load(std::memory_order_acquire) - base_; // ptrdiff from base_
|
||||||
|
return (static_cast<size_t>(k + n) < N) ?
|
||||||
|
base_[k + n] : // on range
|
||||||
|
base_[k + n - N]; // out of range, loop
|
||||||
|
}
|
||||||
|
constexpr ring_iterator& operator+=(difference_type n) noexcept {
|
||||||
|
Iter_t itnew, it = iter_.load(std::memory_order_acquire);
|
||||||
|
do {
|
||||||
|
itnew = it;
|
||||||
|
difference_type k = it - base_; // ptrdiff from base_
|
||||||
|
itnew += (static_cast<size_t>(k + n) < N) ?
|
||||||
|
n : // on range
|
||||||
|
n - N; // out of range, loop
|
||||||
|
} while (!iter_.compare_exchange_weak(it, itnew, std::memory_order_acquire));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
constexpr ring_iterator operator+(difference_type n) const noexcept {
|
||||||
|
difference_type k = iter_.load(std::memory_order_acquire) - base_; // ptrdiff from base_
|
||||||
|
return (static_cast<size_t>(k + n) < N) ?
|
||||||
|
ring_iterator(base_, k + n) : // on range
|
||||||
|
ring_iterator(base_, k + n - N); // out of range, loop
|
||||||
|
}
|
||||||
|
constexpr ring_iterator& operator-=(difference_type n) noexcept {
|
||||||
|
Iter_t itnew, it = iter_.load(std::memory_order_acquire);
|
||||||
|
do {
|
||||||
|
itnew = it;
|
||||||
|
difference_type k = it - base_; // ptrdiff from base_
|
||||||
|
itnew -= ((k - n) < 0)?
|
||||||
|
n - N: // out of range, loop
|
||||||
|
n; // on range
|
||||||
|
} while (!iter_.compare_exchange_weak(it, itnew, std::memory_order_acquire));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
constexpr ring_iterator operator-(difference_type n) const noexcept {
|
||||||
|
difference_type k = iter_.load(std::memory_order_acquire) - base_; // ptrdiff from base_
|
||||||
|
return ((k - n) < 0) ?
|
||||||
|
ring_iterator(base_, k - n + N) : // out of range, loop
|
||||||
|
ring_iterator(base_, k - n); // on range
|
||||||
|
}
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
//! \name Data members and access
|
||||||
|
//! @{
|
||||||
|
constexpr const Iter_t& base() const noexcept {
|
||||||
|
return base_;
|
||||||
|
}
|
||||||
|
constexpr const Iter_t iter() const noexcept {
|
||||||
|
return iter_.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
constexpr size_t size() noexcept {
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr operator Iter_t() noexcept { return iter_.load(std::memory_order_acquire); }
|
||||||
|
constexpr operator const Iter_t() const noexcept { return iter_.load(std::memory_order_acquire); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Iter_t base_;
|
||||||
|
std::atomic<Iter_t> iter_;
|
||||||
|
//! @}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forward iterator requirements
|
||||||
|
template<typename Iter_L, typename Iter_R, size_t N>
|
||||||
|
inline bool operator==(const ring_iterator<Iter_L, N, true>& lhs, const ring_iterator<Iter_R, N, true>& rhs)
|
||||||
|
noexcept {
|
||||||
|
return lhs.iter() == rhs.iter();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Iter_L, typename Iter_R, size_t N>
|
||||||
|
inline bool operator!=(const ring_iterator<Iter_L, N, true>& lhs, const ring_iterator<Iter_R, N, true>& rhs)
|
||||||
|
noexcept {
|
||||||
|
return lhs.iter() != rhs.iter();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Random access iterator requirements
|
||||||
|
template<typename Iter_L, typename Iter_R, size_t N>
|
||||||
|
inline bool operator<(const ring_iterator<Iter_L, N, true>& lhs, const ring_iterator<Iter_R, N, true>& rhs)
|
||||||
|
noexcept {
|
||||||
|
return lhs.iter() < rhs.iter();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Iter_L, typename Iter_R, size_t N>
|
||||||
|
inline bool operator<=(const ring_iterator<Iter_L, N, true>& lhs, const ring_iterator<Iter_R, N, true>& rhs)
|
||||||
|
noexcept {
|
||||||
|
return lhs.iter() <= rhs.iter();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Iter_L, typename Iter_R, size_t N>
|
||||||
|
inline bool operator>(const ring_iterator<Iter_L, N, true>& lhs, const ring_iterator<Iter_R, N, true>& rhs)
|
||||||
|
noexcept {
|
||||||
|
return lhs.iter() > rhs.iter();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Iter_L, typename Iter_R, size_t N>
|
||||||
|
inline bool operator>=(const ring_iterator<Iter_L, N, true>& lhs, const ring_iterator<Iter_R, N, true>& rhs)
|
||||||
|
noexcept {
|
||||||
|
return lhs.iter() >= rhs.iter();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Iter_L, typename Iter_R, size_t N>
|
||||||
|
inline auto operator-(const ring_iterator<Iter_L, N, true>& lhs, const ring_iterator<Iter_R, N, true>& rhs)
|
||||||
|
noexcept
|
||||||
|
-> decltype(lhs.iter() - rhs.iter()) {
|
||||||
|
auto diff = lhs.iter() - rhs.iter();
|
||||||
|
return diff < 0 ?
|
||||||
|
diff + N : // loop
|
||||||
|
diff; // no loop
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Iter, size_t N>
|
||||||
|
inline ring_iterator<Iter, N, true> operator+(std::ptrdiff_t lhs, const ring_iterator<Iter, N, true>& rhs)
|
||||||
|
noexcept {
|
||||||
|
ring_iterator<Iter, N, true> it(rhs.iter());
|
||||||
|
return it += lhs;
|
||||||
|
}
|
||||||
|
|
||||||
} //namespace tbx;
|
} //namespace tbx;
|
||||||
|
|
||||||
#endif /* TBX_CORE_RING_ITERATOR_H_ */
|
#endif /* TBX_CORE_RING_ITERATOR_H_ */
|
||||||
|
|||||||
@@ -0,0 +1,495 @@
|
|||||||
|
/*!
|
||||||
|
* \file drv/cli_device.h
|
||||||
|
* \brief
|
||||||
|
* command line device 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_CLI_DEVICE_H_
|
||||||
|
#define TBX_DRV_CLI_DEVICE_H_
|
||||||
|
|
||||||
|
#include <core/core.h>
|
||||||
|
#include <core/crtp.h>
|
||||||
|
#include <cont/equeue.h>
|
||||||
|
#include <com/sequencer.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
namespace tbx {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \class cli_device
|
||||||
|
* \brief
|
||||||
|
* Its a base class for command-line based devices
|
||||||
|
*
|
||||||
|
* Inherits the sequencer functionality and provides a command interface for sending
|
||||||
|
* commands and parse the response.
|
||||||
|
*
|
||||||
|
* \example implementation example
|
||||||
|
* \code
|
||||||
|
* class BG95 : public cli_device<BG95, 256> {
|
||||||
|
* using base_type = cli_device<BG95, 256>;
|
||||||
|
* using Queue = equeue<typename base_type::value_type, 256, true>;
|
||||||
|
* Queue RxQ{};
|
||||||
|
* std::atomic<size_t> lines{};
|
||||||
|
* public:
|
||||||
|
* // cli_device driver requirements
|
||||||
|
* BG95() noexcept :
|
||||||
|
* RxQ(Queue::data_match::MATCH_PUSH, base_type::delimiter, [&](){
|
||||||
|
* lines.fetch_add(1, std::memory_order_acq_rel);
|
||||||
|
* }), lines(0) { }
|
||||||
|
* void feed(char x) { RxQ << x; } // To be used inside ISR
|
||||||
|
* size_t get(char* data, bool wait =false) {
|
||||||
|
* do {
|
||||||
|
* if (lines.load(std::memory_order_acquire)) {
|
||||||
|
* size_t n =0;
|
||||||
|
* do{
|
||||||
|
* *data << RxQ;
|
||||||
|
* ++n;
|
||||||
|
* } while (*data++ != base_type::delimiter);
|
||||||
|
* lines.fetch_sub(1, std::memory_order_acq_rel);
|
||||||
|
* return n;
|
||||||
|
* }
|
||||||
|
* } while (wait);
|
||||||
|
* return 0;
|
||||||
|
* }
|
||||||
|
* size_t contents(char* data) {
|
||||||
|
* char* nullpos = std::copy(RxQ.begin(), RxQ.end(), data);
|
||||||
|
* *nullpos =0;
|
||||||
|
* return nullpos - data;
|
||||||
|
* }
|
||||||
|
* size_t put (const char* data, size_t n) {
|
||||||
|
* // send data to BG95
|
||||||
|
* return n;
|
||||||
|
* }
|
||||||
|
* clock_t clock() noexcept { //return CPU time }
|
||||||
|
* };
|
||||||
|
* \endcode
|
||||||
|
*
|
||||||
|
* \tparam Impl_t The type of derived class
|
||||||
|
* \tparam N The size of the queue buffer for the receive/command interface
|
||||||
|
* \tparam Delimiter The incoming data delimiter [default line buffered -- Delimiter = '\n']
|
||||||
|
*/
|
||||||
|
template<typename Impl_t, size_t N, char Delimiter ='\n'>
|
||||||
|
class cli_device
|
||||||
|
: public sequencer<cli_device<Impl_t, N, Delimiter>, char, N>{
|
||||||
|
_CRTP_IMPL(Impl_t);
|
||||||
|
|
||||||
|
// local type dispatch
|
||||||
|
using base_type = sequencer<cli_device, char, N>;
|
||||||
|
|
||||||
|
//! \name Public types
|
||||||
|
//! @{
|
||||||
|
public:
|
||||||
|
using value_type = char;
|
||||||
|
using pointer_type = char*;
|
||||||
|
using size_type = size_t;
|
||||||
|
using string_view = typename base_type::string_view;
|
||||||
|
|
||||||
|
using action_t = typename base_type::action_t;
|
||||||
|
using control_t = typename base_type::control_t;
|
||||||
|
using match_ft = typename base_type::match_ft;
|
||||||
|
using handler_ft = typename base_type::handler_ft;
|
||||||
|
template<size_t Nm>
|
||||||
|
using script_t = typename base_type::template script_t<Nm>;
|
||||||
|
|
||||||
|
//! Publish delimiter
|
||||||
|
constexpr static char delimiter = Delimiter;
|
||||||
|
|
||||||
|
enum Flush_t { Keep =0, Flush };
|
||||||
|
enum Receive_t { Get =0, Detect };
|
||||||
|
|
||||||
|
//! Required types for inetd async handler operation
|
||||||
|
//! @{
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* inetd handler structure for asynchronous incoming data dispatching
|
||||||
|
*/
|
||||||
|
struct inetd_handler_t {
|
||||||
|
string_view token; //!< The token we match against
|
||||||
|
match_ft match; //!< The predicate we use to match
|
||||||
|
handler_ft handler; //!< The handler to call on match
|
||||||
|
};
|
||||||
|
//! Alias template for the async handler array
|
||||||
|
template <size_t Nm>
|
||||||
|
using inetd_handlers = std::array<inetd_handler_t, Nm>;
|
||||||
|
//! @}
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
//! \name object lifetime
|
||||||
|
//!@{
|
||||||
|
protected:
|
||||||
|
//!< \brief A default constructor from derived only
|
||||||
|
cli_device() noexcept = default;
|
||||||
|
~cli_device () = default; //!< \brief Allow destructor from derived only
|
||||||
|
cli_device(const cli_device&) = delete; //!< No copies
|
||||||
|
cli_device& operator= (const cli_device&) = delete; //!< No copy assignments
|
||||||
|
//!@}
|
||||||
|
|
||||||
|
//! \name Sequencer interface requirements
|
||||||
|
//! Forwarded to implementer the calls and cascade the the incoming channel
|
||||||
|
//! @{
|
||||||
|
friend base_type;
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t get_ (char* data) {
|
||||||
|
return impl().get (data);
|
||||||
|
}
|
||||||
|
size_t get (char* data) {
|
||||||
|
return receive (data);
|
||||||
|
}
|
||||||
|
size_t contents (char* data) {
|
||||||
|
return impl().contents(data);
|
||||||
|
}
|
||||||
|
size_t put (const char* data, size_t n) {
|
||||||
|
return impl().put (data, n);
|
||||||
|
}
|
||||||
|
clock_t clock () noexcept {
|
||||||
|
return impl().clock();
|
||||||
|
}
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
//! \name Private functionality
|
||||||
|
//! @{
|
||||||
|
private:
|
||||||
|
|
||||||
|
//! typelist "container". A container of template parameter type arguments
|
||||||
|
template <typename... Ts>
|
||||||
|
struct typelist {
|
||||||
|
using type = typelist; //!< act as identity
|
||||||
|
};
|
||||||
|
|
||||||
|
//! front functionality: get the first type of the typelist "container"
|
||||||
|
template <typename L>
|
||||||
|
struct front_impl {
|
||||||
|
using type = void;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Head, typename... Tail>
|
||||||
|
struct front_impl<typelist<Head, Tail...>> {
|
||||||
|
using type = Head;
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Return the first element in \c typelist \c List.
|
||||||
|
//!
|
||||||
|
//! Complexity \f$ O(1) \f$.
|
||||||
|
template <typename List>
|
||||||
|
using front = typename front_impl<List>::type;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Convert the text pointed by \c str to a value and store it to
|
||||||
|
* \c value. The type of conversion is deduced by the compiler
|
||||||
|
* \tparam T The type of the value
|
||||||
|
* \param str pointer to string with the value
|
||||||
|
* \param value pointer to converted value
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
void extract_ (const char* str, T* value) {
|
||||||
|
static_assert (
|
||||||
|
std::is_same_v<std::remove_cv_t<T>, int>
|
||||||
|
|| std::is_same_v<std::remove_cv_t<T>, double>
|
||||||
|
|| std::is_same_v<std::remove_cv_t<T>, char>,
|
||||||
|
"Not supported conversion type.");
|
||||||
|
if constexpr (std::is_same_v<std::remove_cv_t<T>, int>) {
|
||||||
|
*value = std::atoi(str);
|
||||||
|
} else if (std::is_same_v<std::remove_cv_t<T>, double>) {
|
||||||
|
*value = std::atof(str);
|
||||||
|
} else if (std::is_same_v<std::remove_cv_t<T>, char>) {
|
||||||
|
std::strcpy(value, str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//! Specialization (as overload function) to handle void* types
|
||||||
|
void extract_ (const char* str, void* value) noexcept {
|
||||||
|
(void)*str; (void)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Parse a chunk of the buffer based on \c expected character
|
||||||
|
*
|
||||||
|
* Tries to match the \c *expected character in buffer and if so it copies the
|
||||||
|
* character to token.
|
||||||
|
* If the \c *expected is the \c Marker character, copy the entire chunk of the buffer
|
||||||
|
* up to the character that matches the next expected character (expected[1]).
|
||||||
|
* If there is no next expected character or if its not found in the buffer,
|
||||||
|
* copy the entire buffer.
|
||||||
|
*
|
||||||
|
* \tparam Marker The special character to indicate chunk extraction
|
||||||
|
*
|
||||||
|
* \param expected The character to parse/remove from the buffer
|
||||||
|
* \param buffer The buffer we parse
|
||||||
|
* \param token Pointer to store the parsed tokens
|
||||||
|
* \return A (number of characters parsed, marker found) pair
|
||||||
|
*/
|
||||||
|
template <char Marker>
|
||||||
|
std::pair<size_t, bool> parse_ (const char* expected, const string_view buffer, char* token) {
|
||||||
|
do {
|
||||||
|
if (*expected == Marker) {
|
||||||
|
// We have Marker. Copy the entire chunk of the buffer
|
||||||
|
// up to the character that matches the next expected character (expected[1]).
|
||||||
|
// If there is none next expected character or if its not found in the buffer,
|
||||||
|
// copy the entire buffer.
|
||||||
|
auto next = std::find(buffer.begin(), buffer.end(), expected[1]);
|
||||||
|
char* nullpos = std::copy(buffer.begin(), next, token);
|
||||||
|
*nullpos =0;
|
||||||
|
return std::make_pair(next - buffer.begin(), true);
|
||||||
|
}
|
||||||
|
else if (*expected == buffer.front()) {
|
||||||
|
// We have character match, copy the character to token and return 1 (the char size)
|
||||||
|
*token++ = buffer.front();
|
||||||
|
*token =0;
|
||||||
|
return std::make_pair(1, false);
|
||||||
|
}
|
||||||
|
} while (0);
|
||||||
|
|
||||||
|
// Fail to parse
|
||||||
|
*token =0;
|
||||||
|
return std::make_pair(0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
//! \name public functionality
|
||||||
|
//! @{
|
||||||
|
public:
|
||||||
|
|
||||||
|
//! Clears the incoming data buffer
|
||||||
|
void clear () noexcept {
|
||||||
|
rx_q.clear();
|
||||||
|
streams_.store(size_t(0), std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! \return Returns the size of the incoming data buffer
|
||||||
|
size_t size() noexcept {
|
||||||
|
return rx_q.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Transmit data to modem
|
||||||
|
* \param data Pointer to data to send
|
||||||
|
* \param n The size of data buffer
|
||||||
|
* \return The number of transmitted chars
|
||||||
|
*/
|
||||||
|
size_t transmit (const char* data, size_t n) {
|
||||||
|
if (data == nullptr)
|
||||||
|
return 0;
|
||||||
|
return put (data, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Transmit data to modem
|
||||||
|
* \param data Pointer to data to send
|
||||||
|
* \return The number of transmitted chars
|
||||||
|
*/
|
||||||
|
size_t transmit (const char* data) {
|
||||||
|
if (data == nullptr)
|
||||||
|
return 0;
|
||||||
|
return put (data, std::strlen(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Try to receive data from modem. If there are data copy them to \c data pointer and return
|
||||||
|
* the size. Otherwise return zero. In the case \c wait is true block until there are data to get.
|
||||||
|
*
|
||||||
|
* \param data Pointer to data buffer to write
|
||||||
|
* \param wait Flag to select blocking / non-blocking functionality
|
||||||
|
* \return The number of copied data.
|
||||||
|
*/
|
||||||
|
size_t receive (char* data, bool wait =false) {
|
||||||
|
do {
|
||||||
|
if (streams_.load(std::memory_order_acquire)) {
|
||||||
|
size_t n =0;
|
||||||
|
do {
|
||||||
|
*data << rx_q;
|
||||||
|
++n;
|
||||||
|
} while (*data++ != delimiter);
|
||||||
|
*data =0;
|
||||||
|
streams_.fetch_sub(1, std::memory_order_acq_rel);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
} while (wait); // on wait flag we block until available stream
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Analyze the response of a command based on \c expected.
|
||||||
|
*
|
||||||
|
* Tries to receive data via get() path with timeout and match them against expected string_view.
|
||||||
|
* For each Marker inside the expected string the value gets extracted, converted and
|
||||||
|
* copied to \c vargs pointer array.
|
||||||
|
*
|
||||||
|
* \param expected The expected string view
|
||||||
|
* \param timeout the timeout in CPU time
|
||||||
|
* \param vargs Pointer to variable arguments array
|
||||||
|
* \param nargs Size of variable arguments array
|
||||||
|
* \return
|
||||||
|
*/
|
||||||
|
template<Receive_t Recv, char Marker, typename T>
|
||||||
|
bool response (const string_view expected, clock_t timeout, T* vargs, size_t nargs) {
|
||||||
|
char buffer[N], token[N], *pbuffer = buffer;
|
||||||
|
|
||||||
|
size_t v =0, sz =0;
|
||||||
|
for (auto ex = expected.begin() ; ex != expected.end() ; ) {
|
||||||
|
clock_t mark = clock(); // mark the time
|
||||||
|
while (sz <= 0) { // if buffer is empty get buffer with timeout
|
||||||
|
if constexpr (Recv == Get)
|
||||||
|
sz = receive(buffer);
|
||||||
|
else
|
||||||
|
sz = contents(buffer);
|
||||||
|
pbuffer = buffer;
|
||||||
|
if ((timeout != 0 )&& ((clock() - mark) >= timeout))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// try to parse
|
||||||
|
auto [step, marker] = parse_<Marker> (ex, {pbuffer, sz}, token);
|
||||||
|
if (!step)
|
||||||
|
return false; // discard buffer and fail
|
||||||
|
|
||||||
|
if (marker && v < nargs)
|
||||||
|
extract_(token, vargs[v++]);
|
||||||
|
|
||||||
|
pbuffer += step;
|
||||||
|
sz -= (step <= sz) ? step: sz;
|
||||||
|
++ex;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Send a command to modem and check if the response matches to \c expected.
|
||||||
|
*
|
||||||
|
* This function executes 3 steps.
|
||||||
|
* - Clears the incoming buffer if requested by template parameter
|
||||||
|
* - Sends the command to device
|
||||||
|
* - Waits to get the response and parse it accordingly to \c expected \see response()
|
||||||
|
*
|
||||||
|
* The user can mark spots inside the expected string using the \c Marker ['%'] character.
|
||||||
|
* These spots will be extracted to tokens upon parsing. If the user passes \c values parameters,
|
||||||
|
* then the extracted tokens will be converted to the type of the \c values (\c Ts) and copied to them
|
||||||
|
* one by one. If the values are less than spots, the rest of the tokens get discarded.
|
||||||
|
*
|
||||||
|
* \param cmd The command to send (null terminated)
|
||||||
|
* \param expected The expected response
|
||||||
|
* \param timeout The timeout in CPU time (leave it for 0 - no timeout)
|
||||||
|
* \param values The value pointer arguments to get the converted tokens
|
||||||
|
*
|
||||||
|
* \tparam Flush Flag to indicate if we Flush the buffer before command or not
|
||||||
|
* \tparam Marker The marker character
|
||||||
|
* \tparam Ts The type of the values to read from response marked with \c Marker
|
||||||
|
* \warning The types MUST be the same
|
||||||
|
*
|
||||||
|
* \return True on success
|
||||||
|
*
|
||||||
|
* \example examples
|
||||||
|
* \code
|
||||||
|
* Derived cli;
|
||||||
|
* int status;
|
||||||
|
* char str[32];
|
||||||
|
*
|
||||||
|
* // discard 3 lines and expect OK\r\n at the end with 1000[CPU time] timeout
|
||||||
|
* cli.command("AT+CREG?\r\n", "%%%OK\r\n", 1000);
|
||||||
|
*
|
||||||
|
* // extract a number from response without timeout (blocking)
|
||||||
|
* cli.command<Flush>("AT+CREG?\r\n", "\r\n+CREG: 0,%\r\n\r\nOK\r\n", 0, &status);
|
||||||
|
*
|
||||||
|
* // extract a number and discard the last 2 lines
|
||||||
|
* cli.command<Flush>("AT+CREG?\r\n", "\r\n+CREG: 0,%\r\n%%", 1000, &status);
|
||||||
|
*
|
||||||
|
* // discard first line, read the 2nd to str, discard the 3rd line.
|
||||||
|
* // expect the last to be "OK\r\n"
|
||||||
|
* cli.command<Flush>("AT+CREG?\r\n", "", 100000);
|
||||||
|
* cli.command<Keep>("", "%", 1000);
|
||||||
|
* cli.command<Keep>("", "%%", 1000, str);
|
||||||
|
* cli.command<Keep>("", "OK\r\n", 1000);
|
||||||
|
* \endcode
|
||||||
|
*/
|
||||||
|
template<Receive_t Recv =Get, Flush_t Flsh =Flush, char Marker = '%', typename ...Ts>
|
||||||
|
bool command (const string_view cmd, const string_view expected, clock_t timeout, Ts* ...values) {
|
||||||
|
constexpr size_t Nr = sizeof...(Ts);
|
||||||
|
|
||||||
|
front<typelist<Ts...>>* vargs[Nr] = {values...}; // read all args to local buffer
|
||||||
|
if constexpr (Flsh == Flush) {
|
||||||
|
clear ();
|
||||||
|
}
|
||||||
|
if (transmit(cmd.data(), cmd.size()) != cmd.size()) // send command
|
||||||
|
return false;
|
||||||
|
// parse the response and return the status
|
||||||
|
return response<Recv, Marker>(expected, timeout, vargs, Nr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* inetd daemon functionality provided as member function of the driver. This should be running
|
||||||
|
* in the background either as consecutive calls from an periodic ISR with \c loop = false, or
|
||||||
|
* as a thread in an RTOS environment with \c loop = true.
|
||||||
|
*
|
||||||
|
* \tparam Nm The number of handler array entries
|
||||||
|
*
|
||||||
|
* \param async_handles Reference to asynchronous handler array
|
||||||
|
* \param loop Flag to indicate blocking mode. If true blocking.
|
||||||
|
*/
|
||||||
|
template <size_t Nm =0>
|
||||||
|
void inetd (bool loop =true, const inetd_handlers<Nm>* inetd_handlers =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 (inetd_handlers != nullptr) {
|
||||||
|
for (auto& h : *inetd_handlers)
|
||||||
|
match |= base_type::check_handle({buffer.data(), resp_size}, h.token, h.match, h.handler);
|
||||||
|
}
|
||||||
|
// if no match forward data to receive channel.
|
||||||
|
if (!match) {
|
||||||
|
char* it = buffer.data();
|
||||||
|
do {
|
||||||
|
rx_q << *it;
|
||||||
|
} while (*it++ != delimiter);
|
||||||
|
streams_.fetch_add(1, std::memory_order_acq_rel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
private:
|
||||||
|
equeue<char, N, true> rx_q{};
|
||||||
|
std::atomic<size_t> streams_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace tbx;
|
||||||
|
|
||||||
|
#endif /* #ifndef TBX_DRV_CLI_DEVICE_H_ */
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*!
|
||||||
|
* \file drv/gpio.h
|
||||||
|
* \brief
|
||||||
|
* A STM32 gpio wrapper 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_GPIO_H_
|
||||||
|
#define TBX_DRV_GPIO_H_
|
||||||
|
|
||||||
|
#include <core/core.h>
|
||||||
|
|
||||||
|
namespace tbx {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* CRTP class for gpio digital input-output.
|
||||||
|
*
|
||||||
|
* The derived class requirements are:
|
||||||
|
* - bool read_impl ()
|
||||||
|
* - void write_impl(bool)
|
||||||
|
*
|
||||||
|
* \tparam Impl_t The type of derived class
|
||||||
|
*/
|
||||||
|
template <typename Impl_t>
|
||||||
|
class DigitalInOut {
|
||||||
|
public:
|
||||||
|
_CRTP_IMPL(Impl_t);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \name Object lifetime
|
||||||
|
*/
|
||||||
|
//! @{
|
||||||
|
protected:
|
||||||
|
DigitalInOut() noexcept = default;
|
||||||
|
~DigitalInOut() = default;
|
||||||
|
DigitalInOut(const DigitalInOut&) = delete; //!< No copy constructions
|
||||||
|
DigitalInOut operator=(const DigitalInOut&) = delete; //!< No copies
|
||||||
|
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
//! \name Public interface
|
||||||
|
//! @{
|
||||||
|
public:
|
||||||
|
//! Reads the state of the pin. This is true for both input and output pins.
|
||||||
|
//! \return The state of the pin
|
||||||
|
bool read () noexcept { return impl().read_impl (); }
|
||||||
|
//! Write a new state to pin. If the pin is set for output, otherwise this state will remain
|
||||||
|
//! to pin registers and reflect to the pin state if we select output mode
|
||||||
|
void write (bool state) noexcept { impl().write_impl(state); }
|
||||||
|
|
||||||
|
//! Implicit conversion to bool for reading operations
|
||||||
|
operator bool () noexcept {
|
||||||
|
return read();
|
||||||
|
}
|
||||||
|
//! Stream from bool for write operations
|
||||||
|
DigitalInOut& operator<< (bool state) noexcept {
|
||||||
|
write(state);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
//! Right stream to bool for read operations
|
||||||
|
DigitalInOut& operator>> (bool& state) noexcept {
|
||||||
|
state = read();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
//! @}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* This definition enables the "data << pin" syntax for read operation
|
||||||
|
*
|
||||||
|
* \tparam Impl_t The derived class type of the DigitalInOut
|
||||||
|
*
|
||||||
|
* \param lhs Left hand site operand
|
||||||
|
* \param rhs Right hand site operand
|
||||||
|
* \return The read value
|
||||||
|
*/
|
||||||
|
template<typename Impl_t>
|
||||||
|
bool operator<<(bool& lhs, DigitalInOut<Impl_t>& rhs) noexcept {
|
||||||
|
lhs = rhs.read();
|
||||||
|
return lhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //#ifndef TBX_DRV_STM32GPIO_H_
|
||||||
@@ -0,0 +1,536 @@
|
|||||||
|
/*!
|
||||||
|
* \file drv/liquid_crystal.h
|
||||||
|
* \brief
|
||||||
|
* A liquid crystal display driver
|
||||||
|
*
|
||||||
|
* \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_LIQUID_CRYSTAL_H_
|
||||||
|
#define TBX_DRV_LIQUID_CRYSTAL_H_
|
||||||
|
|
||||||
|
#include <core/core.h>
|
||||||
|
#include <core/crtp.h>
|
||||||
|
#include <utils/print.h>
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
namespace tbx {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \class liquid_crystal
|
||||||
|
* \brief
|
||||||
|
* A CRTP driver class for liquid crystal display with parallel interface,
|
||||||
|
* based on Hitachi HD44780 (Samsung KS0066U, or compatible).
|
||||||
|
*
|
||||||
|
* The driver inherits from Print
|
||||||
|
* The Implementation requirements are:
|
||||||
|
* - void bus_impl(data); To set the 4bit/8bit bus
|
||||||
|
* - void rs_pin_impl(state); To set/clear RS pin
|
||||||
|
* - void en_pin_impl(state); To set/clear EN pin
|
||||||
|
* - void power_pin_impl(state); To set/clear PWR pin
|
||||||
|
* - void bl_pin_impl(state); To set/clear BackLight pin
|
||||||
|
* - void delay_usec_impl(usec); To provide delay in usec
|
||||||
|
*
|
||||||
|
* \tparam Impl_t The derived class
|
||||||
|
* \tparam Lines The lines of the LCD
|
||||||
|
* \tparam Columns The coluns of the LCD
|
||||||
|
* \tparam BusSize The LCD bus size (4 or 8 bits)
|
||||||
|
*/
|
||||||
|
template <typename Impl_t, size_t Lines, size_t Columns, size_t BusSize =4>
|
||||||
|
class liquid_crystal : public Print<liquid_crystal<Impl_t, Lines, Columns, BusSize>, char>{
|
||||||
|
friend Print<liquid_crystal, char>;
|
||||||
|
_CRTP_IMPL(Impl_t);
|
||||||
|
static_assert((BusSize == 4) || (BusSize == 8), "BusSize must be either 4 or 8.");
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr static size_t lines = Lines;
|
||||||
|
constexpr static size_t columns = Columns;
|
||||||
|
/*!
|
||||||
|
* Public enumerator to be used as argument to the init() function.
|
||||||
|
* Selects the font size and thus the number of active lines
|
||||||
|
*/
|
||||||
|
enum class FontSize :uint8_t { dots_5x8, dots_5x10 };
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
constexpr static uint8_t Cmd_cls = 0x01;
|
||||||
|
constexpr static uint8_t Cmd_RetHome = 0x02;
|
||||||
|
constexpr static uint8_t Cmd_EntryMode = 0x04;
|
||||||
|
constexpr static uint8_t Cmd_DispCtrl = 0x08;
|
||||||
|
constexpr static uint8_t Cmd_Shift = 0x10;
|
||||||
|
constexpr static uint8_t Cmd_FunSet = 0x20;
|
||||||
|
constexpr static uint8_t Cmd_SetGRamAddr= 0x40;
|
||||||
|
constexpr static uint8_t Cmd_SetDRamAddr= 0x80;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Entry Mode Set -----> 0 0 0 0 0 1 I/D S
|
||||||
|
* ----------------------------------------------------
|
||||||
|
* I/D = 1 Increment Curs
|
||||||
|
* 0 Decrement
|
||||||
|
* S = 1 Display shift
|
||||||
|
* 0 Not
|
||||||
|
*/
|
||||||
|
constexpr static uint8_t Entry_Right = 0x00;
|
||||||
|
constexpr static uint8_t Entry_Left = 0x02;
|
||||||
|
constexpr static uint8_t Entry_ShiftInc = 0x01;
|
||||||
|
constexpr static uint8_t Entry_ShiftDec = 0x00;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DispOnOffControll --> 0 0 0 0 1 D C B
|
||||||
|
* -------------------------------------------------
|
||||||
|
* D = Display On
|
||||||
|
* C = Cursor On
|
||||||
|
* B = Blinking On
|
||||||
|
*/
|
||||||
|
constexpr static uint8_t Display_On = 0x04;
|
||||||
|
constexpr static uint8_t Display_Off = 0x00;
|
||||||
|
constexpr static uint8_t Cursor_On = 0x02;
|
||||||
|
constexpr static uint8_t Cursor_Off = 0x00;
|
||||||
|
constexpr static uint8_t Blink_On = 0x01;
|
||||||
|
constexpr static uint8_t Blink_Off = 0x00;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cursor/Display Shift --> 0 0 0 1 S/C R/L x x
|
||||||
|
* ---------------------------------------------------
|
||||||
|
* S/C = 1 Display Shift
|
||||||
|
* 0 Cursor Shift
|
||||||
|
* R/L = 1 Shift Right
|
||||||
|
* 0 Shift left
|
||||||
|
*/
|
||||||
|
constexpr static uint8_t DisMove_Display= 0x08;
|
||||||
|
constexpr static uint8_t DisMove_Cursor = 0x00;
|
||||||
|
constexpr static uint8_t DisMove_Right = 0x04;
|
||||||
|
constexpr static uint8_t DisMove_Left = 0x00;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FunctionSet ------> 0 0 1 DL N F x x
|
||||||
|
* ---------------------------------------------------
|
||||||
|
* DL = 1 8bit
|
||||||
|
* 0 4bit
|
||||||
|
* N = 1 2 lines
|
||||||
|
* 0 1 line
|
||||||
|
* F = 1 5x10 dots
|
||||||
|
* 0 5x8 dots
|
||||||
|
*/
|
||||||
|
constexpr static uint8_t FunSet_8bitMode= 0x10;
|
||||||
|
constexpr static uint8_t FunSet_4bitMode= 0x00;
|
||||||
|
constexpr static uint8_t FunSet_2Line = 0x08;
|
||||||
|
constexpr static uint8_t FunSet_1Line = 0x00;
|
||||||
|
constexpr static uint8_t FunSet_5x10dots= 0x04;
|
||||||
|
constexpr static uint8_t FunSet_5x8dots = 0x00;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Helper class to keep track of the display cursor. As we dont read the cursor
|
||||||
|
* position on the display, in order to implement backspace operation we need to
|
||||||
|
* keep track the cursor position manually.
|
||||||
|
*/
|
||||||
|
struct Cursor {
|
||||||
|
uint8_t inc_x() noexcept {
|
||||||
|
if (++x_ > Columns) x_ =1;
|
||||||
|
return x_;
|
||||||
|
}
|
||||||
|
uint8_t dec_x() noexcept {
|
||||||
|
if (--x_ < 1) x_ =Columns;
|
||||||
|
return x_;
|
||||||
|
}
|
||||||
|
uint8_t inc_y () noexcept {
|
||||||
|
if (++y_ > max_lines) y_ =1;
|
||||||
|
return y_;
|
||||||
|
}
|
||||||
|
uint8_t dec_y () noexcept {
|
||||||
|
if (--y_ < 1) y_ =max_lines;
|
||||||
|
return y_;
|
||||||
|
}
|
||||||
|
uint8_t operator++() noexcept { return inc_x(); }
|
||||||
|
uint8_t operator--() noexcept { return dec_x(); }
|
||||||
|
void set(uint8_t x, uint8_t y) noexcept {
|
||||||
|
x_ = x;
|
||||||
|
y_ = y;
|
||||||
|
}
|
||||||
|
uint8_t get_x() noexcept { return x_; }
|
||||||
|
uint8_t get_y() noexcept { return y_; }
|
||||||
|
void set_lines (uint8_t l) noexcept { max_lines = l; }
|
||||||
|
private:
|
||||||
|
uint8_t x_, y_;
|
||||||
|
uint8_t max_lines;
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
//! \name Implementation requirements
|
||||||
|
//! @{
|
||||||
|
void BUS (uint8_t data) { impl().bus_impl(data); }
|
||||||
|
void RS_Pin (bool state) { impl().rs_pin_impl(state); }
|
||||||
|
void EN_Pin (bool state) { impl().en_pin_impl(state); }
|
||||||
|
void PWR_Pin (bool state) { impl().power_pin_impl(state); }
|
||||||
|
void BL_Pin (bool state) { impl().bl_pin_impl(state); }
|
||||||
|
void delay_usec(size_t usec) {
|
||||||
|
impl().delay_usec_impl(usec);
|
||||||
|
}
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
//! \name Print interface requirements
|
||||||
|
//! @{
|
||||||
|
size_t write_impl (const char* str, size_t size) {
|
||||||
|
size_t ret =0;
|
||||||
|
while (*str && ret < size) {
|
||||||
|
putchar (*str++);
|
||||||
|
++ret;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
size_t write_impl (const char ch) {
|
||||||
|
return (putchar(ch) == ch) ? 1:0;
|
||||||
|
}
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
//! \name Object lifetime
|
||||||
|
//! @{
|
||||||
|
liquid_crystal() noexcept = default; //!< Construct from derived only
|
||||||
|
//~liquid_crystal() = default;
|
||||||
|
liquid_crystal(const liquid_crystal&) = delete; //!< No copies
|
||||||
|
liquid_crystal& operator= (const liquid_crystal&) = delete; //!< No copies
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! Send enable pulse to display
|
||||||
|
void pulse_enable () {
|
||||||
|
EN_Pin(0);
|
||||||
|
delay_usec (2); // time to settle BUS pin voltages
|
||||||
|
EN_Pin(1);
|
||||||
|
delay_usec (2); // >450 [nsec]
|
||||||
|
EN_Pin(0);
|
||||||
|
delay_usec (50); // > 37 [usec]
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Writes 4/8bit data to display and pulse the EN pin
|
||||||
|
//! \param data The data to write
|
||||||
|
void write_bits (uint8_t data) {
|
||||||
|
if constexpr (BusSize == 4) BUS (data & 0x0F);
|
||||||
|
else BUS (data);
|
||||||
|
pulse_enable ();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Sends commands or character to display by controlling RS pin
|
||||||
|
* \param data The data to send
|
||||||
|
* \param mode Character/command mode (RS pin state)
|
||||||
|
*/
|
||||||
|
void send (uint8_t data, uint8_t mode) {
|
||||||
|
RS_Pin (mode);
|
||||||
|
if constexpr (BusSize == 4) {
|
||||||
|
write_bits (data >> 4);
|
||||||
|
write_bits (data & 0x0F);
|
||||||
|
} else {
|
||||||
|
write_bits (data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//! Send a command to display
|
||||||
|
void command (uint8_t c) { send(c, 0); }
|
||||||
|
//! Send a character to display
|
||||||
|
void character (uint8_t c) { send(c, 1); }
|
||||||
|
|
||||||
|
public:
|
||||||
|
//! \name Public API
|
||||||
|
//! @{
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Initialize the display. After construction the object is valid but reflects the init state
|
||||||
|
* of display configuration. In order for the display to be functional it needs initialization.
|
||||||
|
* So the user has to call this function. This function requires a settled environment, so usually
|
||||||
|
* its called after main().
|
||||||
|
* \param mode 4bit or 8bit mode
|
||||||
|
* \param fontSize 5x8 or 5x10 dots font size.
|
||||||
|
*/
|
||||||
|
void init (FontSize fontSize =FontSize::dots_5x8) {
|
||||||
|
disp_mode_ = disp_mode_init_; // Set values to LCD's startup configuration
|
||||||
|
disp_control_ = disp_control_init_;
|
||||||
|
disp_function_=disp_function_init_;
|
||||||
|
|
||||||
|
// Read user configuration
|
||||||
|
if constexpr (BusSize == 4)
|
||||||
|
// note: keep this runtime, so the disp_function reflects lcd's configuration state
|
||||||
|
disp_function_ &= ~FunSet_8bitMode;
|
||||||
|
else
|
||||||
|
disp_function_ |= FunSet_8bitMode;
|
||||||
|
|
||||||
|
if (fontSize == FontSize::dots_5x10) {
|
||||||
|
disp_function_ |= FunSet_5x10dots;
|
||||||
|
disp_function_ &= ~FunSet_2Line;
|
||||||
|
cursor_.set_lines(Lines>>1);
|
||||||
|
} else {
|
||||||
|
disp_function_ &= ~FunSet_5x10dots;
|
||||||
|
disp_function_ |= FunSet_2Line;
|
||||||
|
cursor_.set_lines(Lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
// start with All-zeros
|
||||||
|
BUS (0); EN_Pin(0); RS_Pin(0); BL_Pin(0);
|
||||||
|
|
||||||
|
delay_usec(100000);
|
||||||
|
if constexpr (BusSize == 4) {
|
||||||
|
// 4bit BUS
|
||||||
|
write_bits (0x03); // 1t try
|
||||||
|
delay_usec(20000);
|
||||||
|
write_bits (0x03); // 2nd try
|
||||||
|
delay_usec(5000);
|
||||||
|
write_bits (0x03); // 3rd try
|
||||||
|
delay_usec(5000);
|
||||||
|
write_bits (0x02); // We set 4 bit interface
|
||||||
|
delay_usec (10000);
|
||||||
|
} else {
|
||||||
|
// 8bit BUS
|
||||||
|
write_bits (Cmd_FunSet | disp_function_); // 1st try
|
||||||
|
delay_usec(20000);
|
||||||
|
write_bits (Cmd_FunSet | disp_function_); // 2nd try
|
||||||
|
delay_usec(5000);
|
||||||
|
write_bits (Cmd_FunSet | disp_function_); // 3rd try
|
||||||
|
delay_usec(5000);
|
||||||
|
}
|
||||||
|
command (Cmd_FunSet | disp_function_); // Finally we set #lines and font size
|
||||||
|
delay_usec(5000);
|
||||||
|
|
||||||
|
command (Cmd_DispCtrl | Display_Off); // Display off
|
||||||
|
delay_usec(5000);
|
||||||
|
command (Cmd_cls); // Clear screen
|
||||||
|
delay_usec(5000);
|
||||||
|
command (Cmd_EntryMode | disp_mode_); // Entry mode set
|
||||||
|
delay_usec(5000);
|
||||||
|
|
||||||
|
command (Cmd_RetHome); // Return home
|
||||||
|
delay_usec(10000);
|
||||||
|
display(true); // Finally display On, done.
|
||||||
|
cursor_.set(1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Utility function to enable/disable power to display. This has an effect IFF there is a
|
||||||
|
//! power pin on the board
|
||||||
|
void power (bool en) {
|
||||||
|
PWR_Pin(en);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Utility function to enable/disable backlight. This has an effect IFF there is a
|
||||||
|
//! backlight pin on the board
|
||||||
|
void backlight (bool en) {
|
||||||
|
BL_Pin(en);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Utility function to send on/off command to display.
|
||||||
|
void display (bool en) {
|
||||||
|
if (en) disp_control_ |= Display_On;
|
||||||
|
else disp_control_ &= ~Display_On;
|
||||||
|
|
||||||
|
command (Cmd_DispCtrl | disp_control_);
|
||||||
|
delay_usec(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Utility function to enable/disable display cursor.
|
||||||
|
void cursor (bool en) {
|
||||||
|
if (en) disp_control_ |= Cursor_On;
|
||||||
|
else disp_control_ &= ~Cursor_On;
|
||||||
|
|
||||||
|
command (Cmd_DispCtrl | disp_control_);
|
||||||
|
delay_usec(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Utility function to enable/disable cursor blinking.
|
||||||
|
void blink (bool en) {
|
||||||
|
if (en) disp_control_ |= Blink_On;
|
||||||
|
else disp_control_ &= ~Blink_On;
|
||||||
|
|
||||||
|
command (Cmd_DispCtrl | disp_control_);
|
||||||
|
delay_usec(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Utility function to enable/disable autoscroll.
|
||||||
|
void autoscroll (bool en) {
|
||||||
|
if (en) disp_mode_ |= Entry_ShiftInc;
|
||||||
|
else disp_mode_ &= ~Entry_ShiftInc;
|
||||||
|
|
||||||
|
command (Cmd_EntryMode | disp_mode_);
|
||||||
|
delay_usec(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Tool to set display cursor
|
||||||
|
* \param x The column position (starting with 1)
|
||||||
|
* \param y The line position (starting with 1 at the top of the display)
|
||||||
|
*/
|
||||||
|
void set_cursor (uint8_t x, uint8_t y) {
|
||||||
|
uint8_t offset;
|
||||||
|
switch (y) {
|
||||||
|
default:
|
||||||
|
case 1: offset = 0x0; break;
|
||||||
|
case 2: offset = 0x40; break;
|
||||||
|
case 3: offset = 0x0 + Columns; break;
|
||||||
|
case 4: offset = 0x40 + Columns; break;
|
||||||
|
}
|
||||||
|
command( Cmd_SetDRamAddr | offset | (x-1));
|
||||||
|
cursor_.set(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Utility function to set left to right entry mode.
|
||||||
|
//! \note
|
||||||
|
//! This is the default
|
||||||
|
void set_left_to_right () {
|
||||||
|
disp_mode_ |= Entry_Left;
|
||||||
|
command (Cmd_EntryMode | disp_mode_);
|
||||||
|
delay_usec(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Utility function to set right to left entry mode.
|
||||||
|
void set_right_to_left () {
|
||||||
|
disp_mode_ &= ~Entry_Left;
|
||||||
|
command (Cmd_EntryMode | disp_mode_);
|
||||||
|
delay_usec(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Command to scroll display left one position
|
||||||
|
void scroll_left () {
|
||||||
|
command (Cmd_Shift | DisMove_Display | DisMove_Left);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Command to scroll display right one position
|
||||||
|
void scroll_right () {
|
||||||
|
command (Cmd_Shift | DisMove_Display | DisMove_Right);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Clears the display and return home
|
||||||
|
void clear() {
|
||||||
|
command (Cmd_cls);
|
||||||
|
cursor_.set(1, 1);
|
||||||
|
delay_usec(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! return home without clearing the display
|
||||||
|
void home() {
|
||||||
|
command (Cmd_RetHome);
|
||||||
|
cursor_.set(1, 1);
|
||||||
|
delay_usec(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Create custom character and store it to LCD.
|
||||||
|
* \param location The location to store the character [0..7] allowed
|
||||||
|
* \param charmap The character map buffer with the font
|
||||||
|
*/
|
||||||
|
void create_char (uint8_t location, uint8_t charmap[]) {
|
||||||
|
location &= 0x7; // we only have 8 locations 0-7
|
||||||
|
command(Cmd_SetGRamAddr | (location << 3));
|
||||||
|
for (size_t i=0; i<8; ++i) {
|
||||||
|
character(charmap[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Send an ascii character to liquid crystal display.
|
||||||
|
* \param ch the character to send
|
||||||
|
* \return the character send.
|
||||||
|
*
|
||||||
|
* \note
|
||||||
|
* This is the driver's "putchar()" functionality to glue.
|
||||||
|
* Tailor this function to redirect stdout to display.
|
||||||
|
*/
|
||||||
|
int putchar (int ch) {
|
||||||
|
// LCD Character dispatcher
|
||||||
|
switch (ch) {
|
||||||
|
case 0:
|
||||||
|
// don't send null termination to device
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
cursor_.inc_y();
|
||||||
|
set_cursor (1, cursor_.get_y());
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
set_cursor (1, cursor_.get_y());
|
||||||
|
break;
|
||||||
|
case '\v':
|
||||||
|
home ();
|
||||||
|
break;
|
||||||
|
case '\f':
|
||||||
|
clear();
|
||||||
|
break;
|
||||||
|
case '\b':
|
||||||
|
--cursor_;
|
||||||
|
set_cursor (cursor_.get_x(), cursor_.get_y());
|
||||||
|
character (' ');
|
||||||
|
--cursor_;
|
||||||
|
set_cursor (cursor_.get_x(), cursor_.get_y());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
character (ch);
|
||||||
|
++cursor_;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//ANSI C (C99) compatible mode
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! \name Data members
|
||||||
|
//! @{
|
||||||
|
|
||||||
|
//! The init entry mode of the display after power up
|
||||||
|
static constexpr uint8_t disp_mode_init_ = Entry_Left | Entry_ShiftDec;
|
||||||
|
//! The init control mode of the display after power up
|
||||||
|
static constexpr uint8_t disp_control_init_ = Display_Off | Cursor_Off | Blink_Off;
|
||||||
|
//! The init function set of the display after power up
|
||||||
|
static constexpr uint8_t disp_function_init_= FunSet_8bitMode | FunSet_1Line | FunSet_5x8dots;
|
||||||
|
|
||||||
|
Cursor cursor_{};
|
||||||
|
uint8_t disp_mode_ {disp_mode_init_};
|
||||||
|
uint8_t disp_control_ {disp_control_init_};
|
||||||
|
uint8_t disp_function_ {disp_function_init_};
|
||||||
|
/*!
|
||||||
|
* \note
|
||||||
|
* When the display powers up, it is configured as follows:
|
||||||
|
* 1. Display clear
|
||||||
|
* 2. Function set: 0x10
|
||||||
|
* DL = 1; 8-bit interface data
|
||||||
|
* N = 0; 1-line display
|
||||||
|
* F = 0; 5x8 dot character font
|
||||||
|
* 3. Display on/off control: 0x00
|
||||||
|
* D = 0; Display off
|
||||||
|
* C = 0; Cursor off
|
||||||
|
* B = 0; Blinking off
|
||||||
|
* 4. Entry mode set: 0x02
|
||||||
|
* I/D = 1; Increment by 1
|
||||||
|
* S = 0; No shift
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! @}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* TBX_DRV_LIQUID_CRYSTAL_H_ */
|
||||||
@@ -0,0 +1,720 @@
|
|||||||
|
/*!
|
||||||
|
* \file drv/sd_spi.h
|
||||||
|
* \brief
|
||||||
|
* SD card driver using SPI interface
|
||||||
|
*
|
||||||
|
* \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_SD_SPI_H_
|
||||||
|
#define TBX_DRV_SD_SPI_H_
|
||||||
|
|
||||||
|
#include <core/core.h>
|
||||||
|
#include <core/crtp.h>
|
||||||
|
|
||||||
|
//#include <drv/diskio.h>
|
||||||
|
|
||||||
|
#include <ctime>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace tbx {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
*
|
||||||
|
* http://elm-chan.org/docs/mmc/mmc_e.html
|
||||||
|
*
|
||||||
|
* CRTP requirements
|
||||||
|
* bool WP_impl (); // write protect, true => write protect
|
||||||
|
* bool CD_impl (); // check disk present, true => present
|
||||||
|
* void CS_impl (bool select); // Chip select, true => select
|
||||||
|
* void PWR_impl(bool state); // SD power, true => power the card
|
||||||
|
* data_type SPI_rw_impl (data_type); // SPI read-write functionality
|
||||||
|
* bool SPI_set_clk_impl(uint32_t clk); // SPI set clock functionality
|
||||||
|
* clock_t clock_impl(); // get system's CPU time
|
||||||
|
*/
|
||||||
|
template <typename Impl_t>
|
||||||
|
class sd_card {
|
||||||
|
_CRTP_IMPL(Impl_t);
|
||||||
|
|
||||||
|
using data_type = uint8_t;
|
||||||
|
|
||||||
|
// Driver settings
|
||||||
|
constexpr static clock_t SD_WaitTimeout = 500; // 500 [CPU time]
|
||||||
|
constexpr static clock_t SD_PowerTimeout= 250; // 250 [CPU time]
|
||||||
|
constexpr static clock_t SD_RxTimeout = 100; // 100 [CPU time]
|
||||||
|
constexpr static clock_t SD_InitTimeout = 2000; // 2000 [CPU time]
|
||||||
|
constexpr static uint32_t MaxInitClock = 400000; // 400000 [Hz]
|
||||||
|
|
||||||
|
// MMC/SDC definitions
|
||||||
|
constexpr static data_type CMD_MSB = 0x40;
|
||||||
|
constexpr static data_type CMD_CRC_LSB = 0x01;
|
||||||
|
|
||||||
|
constexpr static data_type CMD0 = (CMD_MSB | 0); //!< GO_IDLE_STATE
|
||||||
|
constexpr static data_type CMD1 = (CMD_MSB | 1); //!< SEND_OP_COND (MMC)
|
||||||
|
constexpr static data_type CMD8 = (CMD_MSB | 8); //!< SEND_IF_COND
|
||||||
|
constexpr static data_type CMD9 = (CMD_MSB | 9); //!< SEND_CSD
|
||||||
|
constexpr static data_type CMD10 = (CMD_MSB | 10); //!< SEND_CID
|
||||||
|
constexpr static data_type CMD12 = (CMD_MSB | 12); //!< STOP_TRANSMISSION
|
||||||
|
constexpr static data_type CMD16 = (CMD_MSB | 16); //!< SET_BLOCKLEN
|
||||||
|
constexpr static data_type CMD17 = (CMD_MSB | 17); //!< READ_SINGLE_BLOCK
|
||||||
|
constexpr static data_type CMD18 = (CMD_MSB | 18); //!< READ_MULTIPLE_BLOCK
|
||||||
|
constexpr static data_type CMD23 = (CMD_MSB | 23); //!< SET_BLOCK_COUNT (MMC)
|
||||||
|
constexpr static data_type CMD24 = (CMD_MSB | 24); //!< WRITE_BLOCK
|
||||||
|
constexpr static data_type CMD25 = (CMD_MSB | 25); //!< WRITE_MULTIPLE_BLOCK
|
||||||
|
constexpr static data_type CMD55 = (CMD_MSB | 55); //!< APP_CMD
|
||||||
|
constexpr static data_type CMD58 = (CMD_MSB | 58); //!< READ_OCR
|
||||||
|
|
||||||
|
constexpr static data_type ACMD13 = (0xC0 + 13); //!< SD_STATUS (SDC)
|
||||||
|
constexpr static data_type ACMD23 = (0xC0 + 23); //!< SET_WR_BLK_ERASE_COUNT (SDC)
|
||||||
|
constexpr static data_type ACMD41 = (0xC0 + 41); //!< SEND_OP_COND (SDC)
|
||||||
|
|
||||||
|
constexpr static data_type R1_READY_STATE = 0x00; //!< status for card in the ready state
|
||||||
|
constexpr static data_type R1_IDLE_STATE = 0x01; //!< status for card in the idle state
|
||||||
|
constexpr static data_type R1_ILLEGAL_COMMAND = 0x04; //!< status bit for illegal command
|
||||||
|
constexpr static data_type DATA_START_BLOCK = 0xFE; //!< start data token for read or write single block
|
||||||
|
constexpr static data_type STOP_TRAN_TOKEN = 0xFD; //!< stop token for write multiple blocks
|
||||||
|
constexpr static data_type WRITE_MULTIPLE_TOKEN= 0xFC; //!< start data token for write multiple blocks
|
||||||
|
constexpr static data_type DATA_RES_MASK = 0x1F; //!< mask for data response tokens after a write block operation
|
||||||
|
constexpr static data_type DATA_RES_ACCEPTED = 0x05; //!< write data accepted token
|
||||||
|
|
||||||
|
// MMC card type flags (MMC_GET_TYPE)
|
||||||
|
//! \note
|
||||||
|
//! These types are compatible with FatFS types
|
||||||
|
constexpr static data_type CT_NONE = 0x00;
|
||||||
|
constexpr static data_type CT_MMC = 0x01; //!< MMC ver 3
|
||||||
|
constexpr static data_type CT_SD1 = 0x02; //!< SD ver 1
|
||||||
|
constexpr static data_type CT_SD2 = 0x04; //!< SD ver 2
|
||||||
|
constexpr static data_type CT_SDC = (CT_SD1|CT_SD2); //!< SD
|
||||||
|
constexpr static data_type CT_BLOCK = 0x08; //!< Block addressing
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum status_t : uint8_t {
|
||||||
|
ST_OK =0,
|
||||||
|
ST_NOINIT = 1,
|
||||||
|
ST_NODISK = 2,
|
||||||
|
ST_WRPROTECT = 3,
|
||||||
|
ST_ERROR = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ioctl_cmd {
|
||||||
|
// Fatfs compatibility
|
||||||
|
IOCTL_SYNC =0, //!< Flush disk cache (for write functions)
|
||||||
|
IOCTL_GET_SECTOR_COUNT =1, //!< Get media size (for only f_mkfs())
|
||||||
|
IOCTL_GET_SECTOR_SIZE =2, //!< Get sector size (for multiple sector size (_MAX_SS >= 1024))
|
||||||
|
IOCTL_GET_BLOCK_SIZE =3, //!< Get erase block size (for only f_mkfs())
|
||||||
|
IOCTL_ERASE_SECTOR =4, //!< Force erased a block of sectors (for only _USE_ERASE)
|
||||||
|
|
||||||
|
// Generics
|
||||||
|
IOCTL_POWER =5, //!< Get/Set power status
|
||||||
|
IOCTL_LOCK =6, //!< Lock/Unlock media removal
|
||||||
|
IOCTL_EJECT =7, //!< Eject media
|
||||||
|
IOCTL_FORMAT =8, //!< Create physical format on the media
|
||||||
|
|
||||||
|
// SD/MMC specific
|
||||||
|
IOCTL_MMC_GET_TYPE =10, //!< Get card type
|
||||||
|
IOCTL_MMC_GET_CSD =11, //!< Get CSD
|
||||||
|
IOCTL_MMC_GET_CID =12, //!< Get CID
|
||||||
|
IOCTL_MMC_GET_OCR =13, //!< Get OCR
|
||||||
|
IOCTL_MMC_GET_SDSTAT =14, //!< Get SD status
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
sd_card() :
|
||||||
|
status{ST_NOINIT} { }
|
||||||
|
|
||||||
|
sd_card(const sd_card&) = delete;
|
||||||
|
sd_card& operator=(const sd_card&) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Calculate the maximum data transfer rate per one data line
|
||||||
|
* from the CSD.
|
||||||
|
* TRAN_SPEED is the CSD[103..96]
|
||||||
|
*
|
||||||
|
* TRAN_SPEED bit code
|
||||||
|
* ---------------------------------------------------
|
||||||
|
* 2:0 | transfer rate unit
|
||||||
|
* | 0=100kbit/s, 1=1Mbit/s, 2=10Mbit/s,
|
||||||
|
* | 3=100Mbit/s, 4... 7=reserved
|
||||||
|
* ---------------------------------------------------
|
||||||
|
* 6:3 | time value
|
||||||
|
* --------------------------------------------------
|
||||||
|
* 7 | Reserved
|
||||||
|
*
|
||||||
|
* \param csd Pointer to CSD array 128bit.
|
||||||
|
* \return The maximum spi baud rate.
|
||||||
|
*/
|
||||||
|
uint32_t csd2bautrate (data_type *csd) {
|
||||||
|
data_type brmul = 0;
|
||||||
|
uint32_t br = 100000; // 100Kbit
|
||||||
|
|
||||||
|
// Mask [2..0] bits of TRAN_SPEED
|
||||||
|
brmul = csd[3] & 0x07;
|
||||||
|
while (brmul--)
|
||||||
|
br *= 10;
|
||||||
|
return br;
|
||||||
|
}
|
||||||
|
|
||||||
|
void delay (clock_t t) {
|
||||||
|
clock_t mark = impl().clock_impl();
|
||||||
|
while (impl().clock_impl() - mark < t)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Check if SD Card is present.
|
||||||
|
* \return The sd card present status
|
||||||
|
* \arg false Is NOT present
|
||||||
|
* \arg true Is present.
|
||||||
|
*/
|
||||||
|
bool is_present () {
|
||||||
|
return impl().CD_impl();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Check if SD Card is write protected.
|
||||||
|
* \return The write protect status
|
||||||
|
* \arg false Is NOT write protected
|
||||||
|
* \arg true Is write protected.
|
||||||
|
*/
|
||||||
|
bool is_write_protected () {
|
||||||
|
return impl().WP_impl();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Powers up or down the SD Card.
|
||||||
|
* \param on On/Off flag.
|
||||||
|
* \return The new power state state
|
||||||
|
*/
|
||||||
|
bool power (bool on) {
|
||||||
|
impl().PWR_impl(on);
|
||||||
|
return pwr_flag = on;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Check if SD Card is powered.
|
||||||
|
* \return The power status
|
||||||
|
* \arg false The drive is not powered
|
||||||
|
* \arg true The drive is powered
|
||||||
|
*/
|
||||||
|
bool power () { return pwr_flag; }
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Chip-select control
|
||||||
|
* \param state True to Select, false to de-select.
|
||||||
|
*/
|
||||||
|
void select() {
|
||||||
|
spi_tx(0xFF);
|
||||||
|
impl().CS_impl(false);
|
||||||
|
spi_tx(0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief De-select SD Card and release SPI bus
|
||||||
|
* \return None.
|
||||||
|
*/
|
||||||
|
void release () {
|
||||||
|
spi_tx(0xFF);
|
||||||
|
impl().CS_impl(true);
|
||||||
|
spi_tx(0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Transmit a byte to SD/MMC via SPI
|
||||||
|
* \param data The data to send to the SPI bus.
|
||||||
|
*/
|
||||||
|
void spi_tx (data_type data) {
|
||||||
|
impl().SPI_rw_impl (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Receive a byte to SD/MMC via SPI.
|
||||||
|
* \return The data received from SPI bus.
|
||||||
|
*/
|
||||||
|
data_type spi_rx () {
|
||||||
|
return impl().SPI_rw_impl (0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Keep calling spi_rx until response \c resp.
|
||||||
|
*
|
||||||
|
* \param resp the response we wait for
|
||||||
|
* \param timeout timeout for the operation
|
||||||
|
* \return
|
||||||
|
* \arg true Ready
|
||||||
|
* \arg false NOT ready.
|
||||||
|
*/
|
||||||
|
bool spi_wait_for (data_type resp, clock_t timeout) {
|
||||||
|
data_type res;
|
||||||
|
clock_t mark = impl().clock_impl();
|
||||||
|
|
||||||
|
do
|
||||||
|
res = spi_rx ();
|
||||||
|
while ((res != resp) && ((impl().clock_impl() - mark) < timeout));
|
||||||
|
|
||||||
|
return (res == resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool activate (bool state) {
|
||||||
|
if (state) {
|
||||||
|
power(true); // power on with delay
|
||||||
|
delay (SD_PowerTimeout);
|
||||||
|
impl().CS_impl(1); // make sure CS is high
|
||||||
|
for (size_t i=0 ; i<10 ; ++i) // 80 dummy clocks with DI high
|
||||||
|
spi_tx(0xFF);
|
||||||
|
status = ST_NOINIT; // mark the status
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
power(false); // power off
|
||||||
|
impl().CS_impl(0); // keep CS pin voltage low
|
||||||
|
status = ST_NOINIT; // mark the status
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Receive a data packet from MMC/SD
|
||||||
|
*
|
||||||
|
* \param buffer Pointer to data buffer to store received data
|
||||||
|
* \param n Byte count (must be multiple of 4)
|
||||||
|
* \return The operation status
|
||||||
|
* \arg false Fail
|
||||||
|
* \arg true Success.
|
||||||
|
*/
|
||||||
|
bool rx_datablock (data_type* buffer, size_t n) {
|
||||||
|
|
||||||
|
if (! spi_wait_for(DATA_START_BLOCK, SD_RxTimeout))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Receive the data block into buffer and make sure
|
||||||
|
* we receive multiples of 4
|
||||||
|
*/
|
||||||
|
n += (n%4) ? 4-(n%4):0;
|
||||||
|
for ( ; n>0 ; --n)
|
||||||
|
*buffer++ = spi_rx ();
|
||||||
|
|
||||||
|
spi_rx (); // Discard CRC
|
||||||
|
spi_rx ();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Transmit a data block (512bytes) to MMC/SD
|
||||||
|
*
|
||||||
|
* \param buffer Pointer to 512 byte data block to be transmitted
|
||||||
|
* \param token Data/Stop token
|
||||||
|
* \return The operation status
|
||||||
|
* \arg false Fail
|
||||||
|
* \arg true Success.
|
||||||
|
*/
|
||||||
|
bool tx_datablock (const data_type* buffer, data_type token) {
|
||||||
|
if (!spi_wait_for(0xFF, SD_WaitTimeout))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
spi_tx(token); // transmit data token
|
||||||
|
if (token != STOP_TRAN_TOKEN) {
|
||||||
|
// if its data token, transmit the 512 byte block
|
||||||
|
size_t cnt = 512;
|
||||||
|
do
|
||||||
|
spi_tx(*buffer++);
|
||||||
|
while (--cnt);
|
||||||
|
spi_tx(0xFF); // CRC (Dummy)
|
||||||
|
spi_tx(0xFF);
|
||||||
|
data_type r = spi_rx(); // Receive data response
|
||||||
|
if ((r & DATA_RES_MASK) != DATA_RES_ACCEPTED) // If not accepted, return with error
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Send a command packet to SD/MMC and return the response
|
||||||
|
*
|
||||||
|
* \param cmd Command byte
|
||||||
|
* \param arg Argument
|
||||||
|
* \return The response as operation status
|
||||||
|
*/
|
||||||
|
data_type command (data_type cmd, uint32_t arg) {
|
||||||
|
data_type n, r;
|
||||||
|
|
||||||
|
if (cmd & 0x80) {
|
||||||
|
/*!
|
||||||
|
* SD_ACMD<n> is the command sequence of CMD55-SD_CMD<n>
|
||||||
|
*/
|
||||||
|
cmd &= 0x7F;
|
||||||
|
r = command (CMD55, 0);
|
||||||
|
if (r > 1)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send command packet
|
||||||
|
spi_tx (cmd); // Start + Command index
|
||||||
|
spi_tx ((data_type)(arg>>24)); // Argument [31..24]
|
||||||
|
spi_tx ((data_type)(arg>>16)); // Argument [23..16]
|
||||||
|
spi_tx ((data_type)(arg>>8)); // Argument [15..8]
|
||||||
|
spi_tx ((data_type)arg); // Argument [7..0]
|
||||||
|
|
||||||
|
if (cmd == CMD0) n = 0x94; // Valid CRC for CMD0(0)
|
||||||
|
else if (cmd == CMD8) n = 0x86; // Valid CRC for CMD8(0x1AA)
|
||||||
|
else n = 0x00;
|
||||||
|
spi_tx (n | CMD_CRC_LSB);
|
||||||
|
|
||||||
|
// Receive command response
|
||||||
|
if (cmd == CMD12)
|
||||||
|
spi_rx (); // Skip a stuff byte when stop reading
|
||||||
|
|
||||||
|
// Wait for a valid response in timeout of 0xFF attempts
|
||||||
|
size_t nn = 0xFF;
|
||||||
|
do
|
||||||
|
r = spi_rx ();
|
||||||
|
while ((r & 0x80) && --nn);
|
||||||
|
|
||||||
|
return r; // Return with the response value
|
||||||
|
}
|
||||||
|
|
||||||
|
bool do_command_until (data_type done, data_type cmd, uint32_t arg, clock_t timeout) {
|
||||||
|
clock_t mark = impl().clock_impl();
|
||||||
|
data_type ret;
|
||||||
|
do
|
||||||
|
ret = command (cmd, arg);
|
||||||
|
while (ret != done && impl().clock_impl() - mark < timeout);
|
||||||
|
return ret == done;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool get_CSD (data_type* csd) {
|
||||||
|
bool ret = false;
|
||||||
|
select(); // select card's CS
|
||||||
|
if (command (CMD9, 0) == R1_READY_STATE && rx_datablock (csd, 16)) // READ_CSD
|
||||||
|
ret = true;
|
||||||
|
release(); // release card's CS
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_CID (data_type* cid) {
|
||||||
|
bool ret = false;
|
||||||
|
select(); // select card's CS
|
||||||
|
if (command (CMD10, 0) == R1_READY_STATE && rx_datablock (cid, 16)) // READ_CID
|
||||||
|
ret = true;
|
||||||
|
release(); // release card's CS
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_OCR (data_type* ocr) {
|
||||||
|
bool ret = false;
|
||||||
|
select(); // select card's CS
|
||||||
|
// Receive OCR as an R3 response (4 bytes)
|
||||||
|
if (command (CMD58, 0) == 0) { // READ_OCR
|
||||||
|
for (size_t n = 0; n < 4; ++n)
|
||||||
|
*ocr++ = spi_rx ();
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
release(); // release card's CS
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bool get_SDSTAT (data_type* sdstat) {
|
||||||
|
// bool ret = false;
|
||||||
|
// select(); // select card's CS
|
||||||
|
// if (command (ACMD13, 0) == 0) { // SD_STATUS
|
||||||
|
// spi_rx ();
|
||||||
|
// if (rx_datablock (sdstat, 64))
|
||||||
|
// ret = true;
|
||||||
|
// }
|
||||||
|
// release(); // release card's CS
|
||||||
|
// return ret;
|
||||||
|
// }
|
||||||
|
|
||||||
|
bool sync () {
|
||||||
|
select(); // select card's CS
|
||||||
|
bool st = spi_wait_for(0xFF, SD_WaitTimeout); // flush
|
||||||
|
release(); // release card's CS
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t sector_count() {
|
||||||
|
size_t ret =0;
|
||||||
|
data_type csd[16];
|
||||||
|
|
||||||
|
select(); // select card's CS
|
||||||
|
if (get_CSD(csd)) {
|
||||||
|
if ((csd[0] >> 6) == 1) {
|
||||||
|
// SDC version 2.00
|
||||||
|
size_t csize = csd[9] + ((uint16_t)csd[8] << 8) + 1;
|
||||||
|
ret = csize << 10;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// SDC version 1.XX or MMC
|
||||||
|
uint8_t n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
|
||||||
|
size_t csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;
|
||||||
|
ret = csize << (n - 9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
release(); // release card's CS
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t sector_size() const { return 512; }
|
||||||
|
|
||||||
|
size_t block_size() {
|
||||||
|
size_t ret =0;
|
||||||
|
data_type csd[16];
|
||||||
|
|
||||||
|
select(); // select card's CS
|
||||||
|
if (card_type & CT_SD2) {
|
||||||
|
// SDC version 2.00
|
||||||
|
if (command (ACMD13, 0) == R1_READY_STATE) {
|
||||||
|
spi_rx (); // Read SD status
|
||||||
|
if (rx_datablock (csd, 16)) { // Read partial block
|
||||||
|
for (size_t n = 64 - 16; n; n--) // Purge trailing data
|
||||||
|
spi_rx ();
|
||||||
|
ret = 16UL << (csd[10] >> 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// SDC version 1.XX or MMC
|
||||||
|
if (get_CSD(csd)) { // Read CSD
|
||||||
|
if (card_type & CT_SD1) // SDC version 1.XX
|
||||||
|
ret = (((csd[10] & 63) << 1) + ((uint16_t)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1);
|
||||||
|
else // MMC
|
||||||
|
ret = ((uint16_t)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
release(); // release card's CS
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* De-Initialize SD Drive.
|
||||||
|
* \return None
|
||||||
|
*/
|
||||||
|
void deinit () {
|
||||||
|
card_type = data_type{};
|
||||||
|
status = status_t{};
|
||||||
|
activate (0); // finally power off the card
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Initialize SD Drive.
|
||||||
|
*
|
||||||
|
* \return The status of the operation
|
||||||
|
* \arg false On error.
|
||||||
|
* \arg true On success.
|
||||||
|
*/
|
||||||
|
bool init () {
|
||||||
|
uint32_t clk;
|
||||||
|
data_type ocr[4], csd[16];
|
||||||
|
|
||||||
|
clk = 400000; // Start at lower clk
|
||||||
|
impl().SPI_set_clk_impl(clk);
|
||||||
|
|
||||||
|
activate (0); // Initially power off the card
|
||||||
|
if (!is_present()) { // check for presence
|
||||||
|
status = ST_NODISK;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
activate (1); // activate and wait for PowerTimeout delay
|
||||||
|
|
||||||
|
select(); // select card
|
||||||
|
data_type type = CT_NONE;
|
||||||
|
|
||||||
|
if (command (CMD0, 0) == R1_IDLE_STATE) { // Command to enter Idle state
|
||||||
|
if (command (CMD8, 0x1AA) == 1) { // check SD version
|
||||||
|
// SDHC
|
||||||
|
for (size_t n=0 ; n<4 ; ++n) // Get trailing return value of R7 response
|
||||||
|
ocr[n] = spi_rx ();
|
||||||
|
if (ocr[2] == 0x01 && ocr[3] == 0xAA) {
|
||||||
|
// Wait for leaving idle state (ACMD41 with HCS bit)
|
||||||
|
bool st = do_command_until(R1_READY_STATE, ACMD41, 1UL << 30, SD_InitTimeout);
|
||||||
|
|
||||||
|
if (st && get_OCR(ocr))
|
||||||
|
type = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data_type cmd;
|
||||||
|
// SDSC or MMC
|
||||||
|
if (command (ACMD41, 0) <= 1) { // SDSC
|
||||||
|
type = CT_SD1; cmd = ACMD41;
|
||||||
|
} else { // MMC
|
||||||
|
type = CT_MMC; cmd = CMD1;
|
||||||
|
}
|
||||||
|
// Wait for leaving idle state (ACMD41 || CMD1)
|
||||||
|
bool st = do_command_until(R1_READY_STATE, cmd, 0, SD_InitTimeout);
|
||||||
|
// On failure, set R/W block length to 512 (For FAT compatibility)
|
||||||
|
if (!st || command (CMD16, 512) != R1_READY_STATE)
|
||||||
|
type = CT_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
card_type = type;
|
||||||
|
release (); // Initialization ended
|
||||||
|
|
||||||
|
if (type != CT_NONE) {
|
||||||
|
// Success
|
||||||
|
get_CSD(csd);
|
||||||
|
clk = csd2bautrate(csd);
|
||||||
|
impl().SPI_set_clk_impl(clk);
|
||||||
|
status = ST_OK;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
activate(0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t get_status () const { return status; }
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Read Sector(s)
|
||||||
|
*
|
||||||
|
* \param sector Start sector number (LBA)
|
||||||
|
* \param buf Pointer to the data buffer to store read data
|
||||||
|
* \param count Sector (512 bytes) count (1..255)
|
||||||
|
* \return The status of the operation
|
||||||
|
* \arg false On error.
|
||||||
|
* \arg true On success.
|
||||||
|
*/
|
||||||
|
bool read (size_t sector, data_type *buf, size_t count) {
|
||||||
|
|
||||||
|
if (status != ST_OK) return false;
|
||||||
|
|
||||||
|
if (!(card_type & CT_BLOCK)) // Convert to byte address if needed
|
||||||
|
sector *= 512;
|
||||||
|
select();
|
||||||
|
if (count == 1) { //Single block read
|
||||||
|
if (command (CMD17, sector) == 0)
|
||||||
|
if (rx_datablock (buf, 512))
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
else { // Multiple block read
|
||||||
|
if (command (CMD18, sector) == 0) {
|
||||||
|
do {
|
||||||
|
if (!rx_datablock (buf, 512))
|
||||||
|
break;
|
||||||
|
buf += 512;
|
||||||
|
} while (--count);
|
||||||
|
command (CMD12, 0); // STOP_TRANSMISSION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
release ();
|
||||||
|
return (count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Write Sector(s)
|
||||||
|
*
|
||||||
|
* \param sector Start sector number (LBA)
|
||||||
|
* \param buf Pointer to the data to be written
|
||||||
|
* \param count Sector(512 bytes) count (1..255)
|
||||||
|
* \return The status of the operation
|
||||||
|
* \arg false On error.
|
||||||
|
* \arg true On success.
|
||||||
|
*/
|
||||||
|
bool write (size_t sector, const data_type *buf, size_t count) {
|
||||||
|
|
||||||
|
if (!count) return false;
|
||||||
|
if (status != ST_OK) return false;
|
||||||
|
|
||||||
|
if (!(card_type & CT_BLOCK)) // Convert to byte address if needed
|
||||||
|
sector *= 512;
|
||||||
|
|
||||||
|
select();
|
||||||
|
if (count == 1) { // Single block write
|
||||||
|
if ((command (CMD24, sector) == 0) && tx_datablock (buf, 0xFE))
|
||||||
|
count = 0;
|
||||||
|
} else { // Multiple block write
|
||||||
|
if (card_type & CT_SDC)
|
||||||
|
command (ACMD23, count);
|
||||||
|
if (command (CMD25, sector) == 0) {
|
||||||
|
do {
|
||||||
|
if (!tx_datablock (buf, WRITE_MULTIPLE_TOKEN))
|
||||||
|
break;
|
||||||
|
buf += 512;
|
||||||
|
} while (--count);
|
||||||
|
if (!tx_datablock (0, STOP_TRAN_TOKEN)) // STOP token
|
||||||
|
count = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
release ();
|
||||||
|
return (count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ioctl (ioctl_cmd cmd, void* buffer) {
|
||||||
|
switch (cmd) {
|
||||||
|
// SD/MMC specific
|
||||||
|
case IOCTL_MMC_GET_TYPE: *(data_type*)buffer = card_type; return true;
|
||||||
|
case IOCTL_MMC_GET_CSD: return get_CSD ((data_type*)buffer);
|
||||||
|
case IOCTL_MMC_GET_CID: return get_CID ((data_type*)buffer);
|
||||||
|
case IOCTL_MMC_GET_OCR: return get_OCR ((data_type*)buffer);
|
||||||
|
case IOCTL_MMC_GET_SDSTAT: return false;
|
||||||
|
|
||||||
|
// Generic
|
||||||
|
case IOCTL_POWER:
|
||||||
|
switch (*(data_type*)buffer) {
|
||||||
|
case 0: *((data_type*)buffer+1) = (data_type)activate(0); return true;
|
||||||
|
case 1: *((data_type*)buffer+1) = (data_type)activate(0); return true;
|
||||||
|
case 2: *((data_type*)buffer+1) = (data_type)power(); return true;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IOCTL_LOCK:
|
||||||
|
case IOCTL_EJECT:
|
||||||
|
case IOCTL_FORMAT:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// FatFS compatibility
|
||||||
|
case IOCTL_SYNC: return sync();
|
||||||
|
case IOCTL_GET_SECTOR_COUNT:return (*(size_t*) buffer = sector_count() != 0);
|
||||||
|
case IOCTL_GET_SECTOR_SIZE: return (*(size_t*) buffer = sector_size() != 0);
|
||||||
|
case IOCTL_GET_BLOCK_SIZE: return (*(size_t*) buffer = block_size() != 0);
|
||||||
|
case IOCTL_ERASE_SECTOR: return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
data_type card_type{};
|
||||||
|
status_t status{};
|
||||||
|
bool pwr_flag{};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace tbx
|
||||||
|
|
||||||
|
#endif /* TBX_DRV_SD_SPI_H_ */
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*!
|
||||||
|
* \file tbx.h
|
||||||
|
* \brief
|
||||||
|
* Main tbx header
|
||||||
|
*
|
||||||
|
* \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_H_
|
||||||
|
#define TBX_H_
|
||||||
|
|
||||||
|
#include <com/sequencer.h>
|
||||||
|
|
||||||
|
#include <cont/deque.h>
|
||||||
|
#include <cont/edeque.h>
|
||||||
|
#include <cont/queue.h>
|
||||||
|
#include <cont/equeue.h>
|
||||||
|
|
||||||
|
#include <utils/json.h>
|
||||||
|
#include <utils/shared.h>
|
||||||
|
#include <utils/print.h>
|
||||||
|
#include <utils/timer_delay.h>
|
||||||
|
|
||||||
|
#include <drv/cli_device.h>
|
||||||
|
#include <drv/liquid_crystal.h>
|
||||||
|
#include <drv/gpio.h>
|
||||||
|
#include <drv/sd_spi.h>
|
||||||
|
|
||||||
|
#endif /* TBX_H_ */
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
/*!
|
||||||
|
* \file files.h
|
||||||
|
* \brief
|
||||||
|
* File functionality header
|
||||||
|
*
|
||||||
|
* \copyright Copyright (C) 2021 Christos Choutouridis <christos@choutouridis.net>
|
||||||
|
*
|
||||||
|
* <dl class=\"section copyright\"><dt>License</dt><dd>
|
||||||
|
* All Rights Reserved.
|
||||||
|
*
|
||||||
|
* NOTICE: All information contained herein is, and remains
|
||||||
|
* the property of Christos Choutouridis. The intellectual
|
||||||
|
* and technical concepts contained herein are proprietary to
|
||||||
|
* Christos Choutouridis and are protected by copyright law.
|
||||||
|
* Dissemination of this information or reproduction of this material
|
||||||
|
* is strictly forbidden unless prior written permission is obtained
|
||||||
|
* from Christos Choutouridis.
|
||||||
|
* </dd></dl>
|
||||||
|
*/
|
||||||
|
#ifndef JSON_H_
|
||||||
|
#define JSON_H_
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cctype>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <string_view>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
using size_t = std::size_t;
|
||||||
|
|
||||||
|
struct jpair_t {
|
||||||
|
std::string_view key;
|
||||||
|
std::string_view value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<size_t N>
|
||||||
|
struct json_dec_t {
|
||||||
|
|
||||||
|
using string_view = std::string_view;
|
||||||
|
|
||||||
|
json_dec_t(const char* buffer, size_t size) noexcept :
|
||||||
|
buffer_(buffer, size), valid_(true) {
|
||||||
|
enum state_t {
|
||||||
|
ST=0, KEY, COLON, VALUE, SP
|
||||||
|
} state = ST;
|
||||||
|
|
||||||
|
size_t pairs =0;
|
||||||
|
char* begin = nullptr;
|
||||||
|
int s =0;
|
||||||
|
bool str_value = false; // flag to indicate the value is string
|
||||||
|
|
||||||
|
for (size_t i=0 ; i<size ; ++i) {
|
||||||
|
switch (state) {
|
||||||
|
case ST:
|
||||||
|
if (std::isspace(buffer[i]))
|
||||||
|
continue; // skip white space
|
||||||
|
if (buffer[i] == '{')
|
||||||
|
state = KEY;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEY:
|
||||||
|
if (pairs >= N) {
|
||||||
|
valid_ = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (std::isspace(buffer[i]))
|
||||||
|
continue; // skip white space
|
||||||
|
if (buffer[i] == '\"') {
|
||||||
|
if (!begin)
|
||||||
|
begin = (char*)&buffer[i+1];
|
||||||
|
else {
|
||||||
|
s = (char*)&buffer[i] - begin;
|
||||||
|
if (s > 0)
|
||||||
|
pairs_[pairs].key = std::string_view{begin, (size_t)s};
|
||||||
|
else
|
||||||
|
pairs_[pairs].key = std::string_view{};
|
||||||
|
begin =nullptr;
|
||||||
|
s =0;
|
||||||
|
state = COLON;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (buffer[i] == '}')
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COLON:
|
||||||
|
if (std::isspace(buffer[i]))
|
||||||
|
continue; // skip white space
|
||||||
|
if (buffer[i] == ':')
|
||||||
|
state = VALUE;
|
||||||
|
else {
|
||||||
|
valid_ = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VALUE:
|
||||||
|
if (pairs >= N) {
|
||||||
|
valid_ = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!begin && std::isspace(buffer[i])) // consume pre-spaces
|
||||||
|
continue;
|
||||||
|
else if (!begin && !std::isspace(buffer[i])) { // first character
|
||||||
|
if (buffer[i] == '\"') {
|
||||||
|
begin = (char*)&buffer[i+1];
|
||||||
|
str_value = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
begin = (char*)&buffer[i];
|
||||||
|
str_value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (begin) {
|
||||||
|
if (str_value && (buffer[i] == '\"')) {
|
||||||
|
s = (char*)&buffer[i] - begin;
|
||||||
|
if (s > 0)
|
||||||
|
pairs_[pairs].value = std::string_view{begin, (size_t)s};
|
||||||
|
else
|
||||||
|
pairs_[pairs].value = std::string_view{};
|
||||||
|
++pairs;
|
||||||
|
begin =nullptr;
|
||||||
|
s =0;
|
||||||
|
state = SP;
|
||||||
|
}
|
||||||
|
else if (!str_value && (std::isspace(buffer[i]) || buffer[i] == ',' || buffer[i] == '}')) {
|
||||||
|
s = (char*)&buffer[i] - begin;
|
||||||
|
if (s > 0)
|
||||||
|
pairs_[pairs].value = std::string_view{begin, (size_t)s};
|
||||||
|
else
|
||||||
|
pairs_[pairs].value = std::string_view{};
|
||||||
|
++pairs;
|
||||||
|
begin =nullptr;
|
||||||
|
s =0;
|
||||||
|
if (std::isspace(buffer[i]))
|
||||||
|
state = SP;
|
||||||
|
else if (buffer[i] == ',')
|
||||||
|
state = KEY;
|
||||||
|
else if (buffer[i] == '}')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SP:
|
||||||
|
if (std::isspace(buffer[i]))
|
||||||
|
continue; // skip white space
|
||||||
|
else if (buffer[i] == ',')
|
||||||
|
state = KEY;
|
||||||
|
else if (buffer[i] == '}')
|
||||||
|
return;
|
||||||
|
else {
|
||||||
|
valid_ = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template<typename T>
|
||||||
|
T get (const char* key) {
|
||||||
|
T t{};
|
||||||
|
for (auto& it : pairs_) {
|
||||||
|
if (it.key.compare(key) == 0) {
|
||||||
|
extract_(it.value, &t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
bool is_valid() const { return valid_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
/*!
|
||||||
|
* Convert the text pointed by \c str to a value and store it to
|
||||||
|
* \c value. The type of conversion is deduced by the compiler
|
||||||
|
* \tparam T The type of the value
|
||||||
|
* \param str pointer to string with the value
|
||||||
|
* \param value pointer to converted value
|
||||||
|
*/
|
||||||
|
void extract_(std::string_view str, bool* value) {
|
||||||
|
*value = (
|
||||||
|
!std::strncmp(str.data(), "true", str.size()) ||
|
||||||
|
!std::strncmp(str.data(), "True", str.size()) ||
|
||||||
|
!std::strncmp(str.data(), "TRUE", str.size()) ||
|
||||||
|
!std::strncmp(str.data(), "1", str.size())
|
||||||
|
) ? true : false;
|
||||||
|
}
|
||||||
|
void extract_(std::string_view str, int* value) {
|
||||||
|
*value = std::atoi(str.data());
|
||||||
|
}
|
||||||
|
void extract_(std::string_view str, unsigned int* value) {
|
||||||
|
*value = (unsigned int)std::atoi(str.data());
|
||||||
|
}
|
||||||
|
void extract_(std::string_view str, long* value) {
|
||||||
|
*value = std::atol(str.data());
|
||||||
|
}
|
||||||
|
void extract_(std::string_view str, unsigned long* value) {
|
||||||
|
*value = (unsigned long)std::atol(str.data());
|
||||||
|
}
|
||||||
|
void extract_(std::string_view str, double* value) {
|
||||||
|
*value = std::atof(str.data());
|
||||||
|
}
|
||||||
|
void extract_(std::string_view str, char** value) {
|
||||||
|
*value = (char*)str.data();
|
||||||
|
}
|
||||||
|
void extract_(std::string_view str, string_view* value) {
|
||||||
|
*value = str;
|
||||||
|
}
|
||||||
|
//! Specialization (as overload function) to handle void* types
|
||||||
|
void extract_ (const char* str, void* value) noexcept {
|
||||||
|
(void)*str; (void)value;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::string_view buffer_;
|
||||||
|
std::array<jpair_t, N> pairs_;
|
||||||
|
bool valid_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* JSON_H_ */
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
/*!
|
||||||
|
* \file utils/print.h
|
||||||
|
* \brief
|
||||||
|
* A CRTP base class to provide print interface
|
||||||
|
*
|
||||||
|
* \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_UTILS_PRINT_H_
|
||||||
|
#define TBX_UTILS_PRINT_H_
|
||||||
|
|
||||||
|
#include <core/core.h>
|
||||||
|
#include <core/crtp.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <math.h>
|
||||||
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace tbx {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \class Print
|
||||||
|
* \brief
|
||||||
|
* A CRTP print interface
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
* - size_t write_impl(const Char_t* buffer, size_t size) : Return the number of \c Char_t written
|
||||||
|
* - size_t write_impl(const Char_t ch) : Return the number of \c Char_t written (normally one).
|
||||||
|
*
|
||||||
|
* \tparam Impl_t The derived type
|
||||||
|
* \tparam Char_t The char type to use
|
||||||
|
*/
|
||||||
|
template <typename Impl_t, typename Char_t>
|
||||||
|
class Print {
|
||||||
|
_CRTP_IMPL(Impl_t);
|
||||||
|
|
||||||
|
public:
|
||||||
|
using value_type = Char_t;
|
||||||
|
using pointer_type = Char_t*;
|
||||||
|
using iterator_type = Char_t*;
|
||||||
|
using const_iterator_type = const Char_t*;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using size_type = size_t;
|
||||||
|
using str_view_t = std::basic_string_view<Char_t>;
|
||||||
|
|
||||||
|
//! Enumerator for number base formating
|
||||||
|
enum class Base {
|
||||||
|
BIN =2, OCT =8, DEC =10, HEX =16
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! \name CRTP requirements
|
||||||
|
//! @{
|
||||||
|
size_t write_(const Char_t* buffer, size_t size) {
|
||||||
|
return impl().write_impl(buffer, size);
|
||||||
|
}
|
||||||
|
size_t write_(const Char_t ch) {
|
||||||
|
return impl().write_impl(ch);
|
||||||
|
}
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Print() noexcept = default; //!< Construct from derived only
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! Helper tool to convert strong enums to their underlying type
|
||||||
|
template <typename E>
|
||||||
|
constexpr typename std::underlying_type_t<E> value(E e) noexcept {
|
||||||
|
return static_cast<typename std::underlying_type_t<E>>(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t print_unsigned(unsigned long n, Base base); // integer conversion base tool
|
||||||
|
size_t print_double(double number, uint8_t digits); // double conversion base tool
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Prints a string view
|
||||||
|
* \param str The string view to print
|
||||||
|
* \return The number of printed \c Char_t
|
||||||
|
*/
|
||||||
|
size_t print(const str_view_t str) {
|
||||||
|
return write_(str.data(), str.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Prints a string
|
||||||
|
* \param str Pointer to string to print
|
||||||
|
* \return The number of printed \c Char_t
|
||||||
|
*/
|
||||||
|
size_t print(const Char_t* str) {
|
||||||
|
if (str == nullptr)
|
||||||
|
return 0;
|
||||||
|
return write_(str, std::strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Prints a buffer of size \c size. If there is a null termination
|
||||||
|
* before the end of the buffer, prints up to termination.
|
||||||
|
*
|
||||||
|
* \param str Pointer to string buffer to print
|
||||||
|
* \param size The size of buffer
|
||||||
|
* \return The number of printed \c Char_t
|
||||||
|
*/
|
||||||
|
size_t print(const Char_t* str, size_t size) {
|
||||||
|
if (str == nullptr)
|
||||||
|
return 0;
|
||||||
|
return write_(str, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Prints a \c Char_t
|
||||||
|
* \param ch The Char_t to print
|
||||||
|
* \return The number of printed \c Char_t
|
||||||
|
*/
|
||||||
|
size_t print(Char_t ch) {
|
||||||
|
return write_ (ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Convert and print a long.
|
||||||
|
* \param n The number to print
|
||||||
|
* \param base The number base to use for conversion.
|
||||||
|
* \return The number of printed \c Char_t
|
||||||
|
*/
|
||||||
|
size_t print(long n, Base base =Base::DEC) {
|
||||||
|
size_t cnt =0;
|
||||||
|
if (n < 0) {
|
||||||
|
n = -n;
|
||||||
|
cnt = write_ ('-');
|
||||||
|
}
|
||||||
|
return cnt + print_unsigned((unsigned long)n, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Convert and print an int.
|
||||||
|
* \param n The number to print
|
||||||
|
* \param base The number base to use for conversion.
|
||||||
|
* \return The number of printed \c Char_t
|
||||||
|
*/
|
||||||
|
size_t print(int n, Base base= Base::DEC) {
|
||||||
|
return print ((long)n, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Convert and print an unsigned long.
|
||||||
|
* \param n The number to print
|
||||||
|
* \param base The number base to use for conversion.
|
||||||
|
* \return The number of printed \c Char_t
|
||||||
|
*/
|
||||||
|
size_t print(unsigned long n, Base base= Base::DEC) {
|
||||||
|
return print_unsigned ((unsigned long)n, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Convert and print an unsigned int.
|
||||||
|
* \param n The number to print
|
||||||
|
* \param base The number base to use for conversion.
|
||||||
|
* \return The number of printed \c Char_t
|
||||||
|
*/
|
||||||
|
size_t print(unsigned int n, Base base= Base::DEC) {
|
||||||
|
return print_unsigned ((unsigned long)n, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Convert and print adouble
|
||||||
|
* \param n The number to print
|
||||||
|
* \param digits The number of decimal digits to print
|
||||||
|
* \return The number of printed \c Char_t
|
||||||
|
*/
|
||||||
|
size_t print(double n, uint8_t digits = 2) {
|
||||||
|
return print_double (n, digits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Perfect forwarder to print functionality with a new line termination
|
||||||
|
* \tparam Ts The types of parameters
|
||||||
|
* \param args The arguments to pass
|
||||||
|
* \return The number of printed \c Char_t
|
||||||
|
*/
|
||||||
|
template <typename ...Ts>
|
||||||
|
size_t println(Ts&& ...args) {
|
||||||
|
size_t r = print (std::forward<Ts>(args)...);
|
||||||
|
r += write_ ('\n');
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Converts and prints an unsigned long
|
||||||
|
*
|
||||||
|
* \tparam Impl_t The derived type
|
||||||
|
* \tparam Char_t The char type to use
|
||||||
|
*
|
||||||
|
* \param n The number to print
|
||||||
|
* \param base The number base to use
|
||||||
|
* \return The number of printed \c Char_t
|
||||||
|
*/
|
||||||
|
template <typename Impl_t, typename Char_t>
|
||||||
|
size_t Print<Impl_t, Char_t>::print_unsigned(unsigned long n, Base base) {
|
||||||
|
Char_t buf[8 *sizeof(Char_t) * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte.
|
||||||
|
Char_t *str = &buf[sizeof(buf) - 1];
|
||||||
|
|
||||||
|
*str = '\0';
|
||||||
|
|
||||||
|
do {
|
||||||
|
Char_t c = n % value(base);
|
||||||
|
n /= value(base);
|
||||||
|
|
||||||
|
*--str = c < 10 ? c + '0' : c + 'A' - 10;
|
||||||
|
} while(n);
|
||||||
|
|
||||||
|
return write_(str, std::strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Converts and prints a double
|
||||||
|
*
|
||||||
|
* \note
|
||||||
|
* Internally, this implementation uses a long to store the integer part of the number.
|
||||||
|
* Thus overflows for numbers bigger than std::numeric_limits<long>::max() / min().
|
||||||
|
* For these numbers it prints "ovf" instead.
|
||||||
|
*
|
||||||
|
* \tparam Impl_t The derived type
|
||||||
|
* \tparam Char_t The char type to use
|
||||||
|
*
|
||||||
|
* \param n The number to print
|
||||||
|
* \param digits The number of decimal digits to print
|
||||||
|
* \return The number of printed \c Char_t
|
||||||
|
*/
|
||||||
|
template <typename Impl_t, typename Char_t>
|
||||||
|
size_t Print<Impl_t, Char_t>::print_double(double number, uint8_t digits) {
|
||||||
|
size_t n = 0;
|
||||||
|
|
||||||
|
if (std::isnan(number)) return print("nan");
|
||||||
|
if (std::isinf(number)) return print("inf");
|
||||||
|
if (number > (double)std::numeric_limits<long>::max())
|
||||||
|
return print ("ovf");
|
||||||
|
if (number < (double)std::numeric_limits<long>::min())
|
||||||
|
return print ("-ovf");
|
||||||
|
|
||||||
|
// Handle negative numbers
|
||||||
|
if (number < 0.0) {
|
||||||
|
n += write_ ('-');
|
||||||
|
number = -number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round correctly so that print(1.999, 2) prints as "2.00"
|
||||||
|
double rounding = 0.5;
|
||||||
|
for (uint8_t i=0; i<digits; ++i)
|
||||||
|
rounding /= 10.0;
|
||||||
|
|
||||||
|
number += rounding;
|
||||||
|
|
||||||
|
// Extract the integer part of the number and print it
|
||||||
|
unsigned long int_part = (unsigned long)number;
|
||||||
|
double remainder = number - (double)int_part;
|
||||||
|
n += print(int_part);
|
||||||
|
|
||||||
|
// Print the decimal point, but only if there are digits beyond
|
||||||
|
if (digits > 0) {
|
||||||
|
n += print('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract digits from the remainder one at a time
|
||||||
|
while (digits-- > 0) {
|
||||||
|
remainder *= 10.0;
|
||||||
|
unsigned int toPrint = (unsigned int)(remainder);
|
||||||
|
n += print(toPrint);
|
||||||
|
remainder -= toPrint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif /* TBX_UTILS_PRINT_H_ */
|
||||||
@@ -1,359 +0,0 @@
|
|||||||
/*!
|
|
||||||
* \file utils/sequencer.h
|
|
||||||
* \brief
|
|
||||||
* A terminal-like device communication automation tool
|
|
||||||
*
|
|
||||||
* \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_UTILS_SEQUENCER_H_
|
|
||||||
#define TBX_UTILS_SEQUENCER_H_
|
|
||||||
|
|
||||||
|
|
||||||
#include <core/core.h>
|
|
||||||
#include <core/crtp.h>
|
|
||||||
#include <ctime>
|
|
||||||
#include <array>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace tbx {
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \class sequencer_t
|
|
||||||
* \brief
|
|
||||||
* A CRTP base class to provide the sequencer functionality.
|
|
||||||
*
|
|
||||||
* Sequencer can automate communication with a terminal-like device such as AT-command modems etc...
|
|
||||||
* It can operate based on a script array and handle the outgoing commands and incoming responses.
|
|
||||||
* The user can create matching rules on received data and hook handlers and actions on them.
|
|
||||||
*
|
|
||||||
* The derived class (implementation) has to provide:
|
|
||||||
* 1) size_t get(Data_t* data);
|
|
||||||
* This function return 0 or a number of Data_t items. The data points to buffer for the input data.
|
|
||||||
* 2) size_t put(const Data_t* data, size_t n);
|
|
||||||
* This function sends to implementation the data pointed by \c data witch have size \c n.
|
|
||||||
* 3) clock_t clock();
|
|
||||||
* This function return a number to be used as time. The units of this function may be arbitrary but they
|
|
||||||
* match the units in \c record_t::timeout field.
|
|
||||||
*
|
|
||||||
* \tparam Impl_t The type of 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().
|
|
||||||
*/
|
|
||||||
template <typename Impl_t, typename Data_t, size_t N>
|
|
||||||
class sequencer_t {
|
|
||||||
_CRTP_IMPL(Impl_t);
|
|
||||||
|
|
||||||
using str_view_t = std::basic_string_view<Data_t>;
|
|
||||||
|
|
||||||
//! \name Public types
|
|
||||||
//! @{
|
|
||||||
public:
|
|
||||||
//! \enum status_t
|
|
||||||
//! \brief The sequencer run status
|
|
||||||
enum class status_t {
|
|
||||||
OK, ERROR
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
//! \enum action_t
|
|
||||||
//! \brief Possible response actions for the sequencer
|
|
||||||
enum class action_t {
|
|
||||||
NO, NEXT, GOTO, EXIT_OK, EXIT_ERROR
|
|
||||||
};
|
|
||||||
|
|
||||||
//! \enum control_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
|
|
||||||
};
|
|
||||||
|
|
||||||
//! \enum match_t
|
|
||||||
//! \brief Token match types
|
|
||||||
enum class match_t {
|
|
||||||
NO, STARTS_WITH, ENDS_WITH, CONTAINS, nSTARTS_WITH, nENDS_WITH, nCONTAINS
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \struct block_t
|
|
||||||
* \brief
|
|
||||||
* The script line block.
|
|
||||||
*
|
|
||||||
* Each script "line" contains up to 2 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 {
|
|
||||||
std::basic_string_view<Data_t>
|
|
||||||
token; //!< The token for the match
|
|
||||||
match_t match_type; //!< The matching type functionality
|
|
||||||
handler_ft handler; //!< The handler to called if the match is successful.
|
|
||||||
action_t action; //!< The action to be performer if the match is successful
|
|
||||||
size_t idx; //!< The index for the action_t::GOTO action. Otherwise can be left 0.
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \struct record_t
|
|
||||||
* \brief
|
|
||||||
* Describes the sequencer's script record entry (line).
|
|
||||||
*
|
|
||||||
* 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 user can create arrays as the example bellow to act as a script.
|
|
||||||
* \code
|
|
||||||
* const std::array<Seq::record_t, 8> script = {{
|
|
||||||
* / * 0 * / {Seq::control_t::NOP, {"", Seq::match_t::NO, nullptr, Seq::action_t::GOTO, 1}, 1000}, //delay 1000 clocks
|
|
||||||
* / * 1 * / {Seq::control_t::SEND, {"ATE0\r\n", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000},
|
|
||||||
* / * 2 * / {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
|
|
||||||
* },
|
|
||||||
* // ...
|
|
||||||
* }};
|
|
||||||
* \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
|
|
||||||
};
|
|
||||||
//! @}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//! \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
|
|
||||||
//!@}
|
|
||||||
|
|
||||||
//! \name Sequencer interface requirements for implementer
|
|
||||||
//! @{
|
|
||||||
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); }
|
|
||||||
clock_t clock_ () { return impl().clock(); }
|
|
||||||
//! @}
|
|
||||||
|
|
||||||
//! \name Private functionality
|
|
||||||
//! @{
|
|
||||||
private:
|
|
||||||
/*!
|
|
||||||
* \brief
|
|
||||||
* Check if the \c stream starts with the \c prefix
|
|
||||||
* \param stream The stream in witch we search
|
|
||||||
* \param prefix What we search
|
|
||||||
* \return True on success, false otherwise
|
|
||||||
*/
|
|
||||||
bool starts_with_ (const str_view_t stream, const str_view_t prefix) {
|
|
||||||
return (stream.rfind(prefix, 0) != str_view_t::npos);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief
|
|
||||||
* Check if the \c stream ends with the \c postfix
|
|
||||||
* \param stream The stream in witch we search
|
|
||||||
* \param postfix What we search
|
|
||||||
* \return True on success, false otherwise
|
|
||||||
*/
|
|
||||||
bool ends_with_ (const str_view_t stream, const str_view_t postfix) {
|
|
||||||
return (
|
|
||||||
stream.compare(
|
|
||||||
stream.size() - postfix.size(),
|
|
||||||
postfix.size(),
|
|
||||||
postfix) == 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief
|
|
||||||
* Check if the \c haystack contains the \c needle
|
|
||||||
* \param haystack The stream in witch we search
|
|
||||||
* \param needle What we search
|
|
||||||
* \return True on success, false otherwise
|
|
||||||
*/
|
|
||||||
bool contains_ (const str_view_t haystack, const str_view_t needle) {
|
|
||||||
return (haystack.find(needle) != str_view_t::npos);
|
|
||||||
}
|
|
||||||
/*!
|
|
||||||
* \brief
|
|
||||||
* Return the new sequencer's step value.
|
|
||||||
*
|
|
||||||
* Step is index to the sequencer's script array.
|
|
||||||
*
|
|
||||||
* \param current_idx The current step value
|
|
||||||
* \param action The advancing type
|
|
||||||
* \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) {
|
|
||||||
switch (action) {
|
|
||||||
default:
|
|
||||||
case action_t::NO: return current_idx;
|
|
||||||
case action_t::NEXT: return ++current_idx;
|
|
||||||
case action_t::GOTO: return go_idx;
|
|
||||||
case action_t::EXIT_OK:
|
|
||||||
case action_t::EXIT_ERROR:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*!
|
|
||||||
* \brief
|
|
||||||
* Checks if the \c needle matches the \c haystack.
|
|
||||||
*
|
|
||||||
* \param type The type of matching functionality
|
|
||||||
* \param haystack The stream in witch we search
|
|
||||||
* \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) {
|
|
||||||
switch (type) {
|
|
||||||
default:
|
|
||||||
case match_t::NO: return true;
|
|
||||||
case match_t::STARTS_WITH: return starts_with_(haystack, needle);
|
|
||||||
case match_t::ENDS_WITH: return ends_with_(haystack, needle);
|
|
||||||
case match_t::CONTAINS: return contains_(haystack, needle);
|
|
||||||
case match_t::nSTARTS_WITH: return !starts_with_(haystack, needle);
|
|
||||||
case match_t::nENDS_WITH: return !ends_with_(haystack, needle);
|
|
||||||
case match_t::nCONTAINS: return !contains_(haystack, needle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//! @}
|
|
||||||
|
|
||||||
|
|
||||||
public:
|
|
||||||
/*!
|
|
||||||
* \brief
|
|
||||||
* Run the script 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.
|
|
||||||
* \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.
|
|
||||||
* \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.
|
|
||||||
* On match:
|
|
||||||
* - Calls the handler if there is one
|
|
||||||
* - Executes the action
|
|
||||||
* - Skips the next block if there is one
|
|
||||||
* If there is no match on timeout it return status_t::EXIT_ERROR
|
|
||||||
*
|
|
||||||
* \tparam Steps The number of steps of the script
|
|
||||||
* \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) {
|
|
||||||
Data_t buffer[N];
|
|
||||||
size_t resp_size;
|
|
||||||
|
|
||||||
clock_t mark = clock_();
|
|
||||||
for (size_t step =0, p_step =0 ; step < Steps ; ) {
|
|
||||||
const record_t& it = script[step];
|
|
||||||
|
|
||||||
if (step != p_step) {
|
|
||||||
p_step = step;
|
|
||||||
mark = clock_();
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((clock_() - mark) >= it.timeout)
|
|
||||||
return status_t::ERROR;
|
|
||||||
break;
|
|
||||||
} // switch (it.control)
|
|
||||||
}
|
|
||||||
return 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 = {
|
|
||||||
"",
|
|
||||||
sequencer_t<Impl_t, Data_t, N>::match_t::NO,
|
|
||||||
nullptr,
|
|
||||||
sequencer_t<Impl_t, Data_t, N>::action_t::NO,
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif /* TBX_UTILS_SEQUENCER_H_ */
|
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*!
|
||||||
|
* \file utils/shared.h
|
||||||
|
* \brief
|
||||||
|
* A CRTP base class to provide acquire/release functionality for shared resources
|
||||||
|
* without handle pointers.
|
||||||
|
*
|
||||||
|
* \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_UTILS_SHARED_H_
|
||||||
|
#define TBX_UTILS_SHARED_H_
|
||||||
|
|
||||||
|
#include <core/core.h>
|
||||||
|
#include <core/crtp.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace tbx {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* A CRTP base class to provide acquire/release functionality for shared resources
|
||||||
|
* without handle pointers.
|
||||||
|
*
|
||||||
|
* \example
|
||||||
|
* \code
|
||||||
|
* class GPIOClock : public shared<GPIOClock> {
|
||||||
|
* friend shared<GPIOClock>;
|
||||||
|
* void acquire_impl() { // HAL enable gpio clock }
|
||||||
|
* void release_impl() { // HAL disable gpio clock }
|
||||||
|
* };
|
||||||
|
* GPIOClock clk;
|
||||||
|
* class Pin {
|
||||||
|
* Pin() {
|
||||||
|
* clk.acquire();
|
||||||
|
* // init pin
|
||||||
|
* }
|
||||||
|
* ~Pin() {
|
||||||
|
* // de-init pin
|
||||||
|
* clk.release();
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
* \endcode
|
||||||
|
*
|
||||||
|
* \tparam Impl_t The derived class type
|
||||||
|
*/
|
||||||
|
template <typename Impl_t>
|
||||||
|
class shared {
|
||||||
|
_CRTP_IMPL(Impl_t);
|
||||||
|
int count {}; //!< acquisition counter
|
||||||
|
|
||||||
|
protected:
|
||||||
|
shared() noexcept = default;
|
||||||
|
shared(const shared&) = delete; //!< No copies
|
||||||
|
shared operator=(const shared&) = delete; //!< No copies
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
* Acquires the recourse. If it is the first call to acquire the resource we actually acquire it.
|
||||||
|
* Otherwise we just keep track of how many acquisition have made.
|
||||||
|
*
|
||||||
|
* \tparam Ts The types of possible arguments
|
||||||
|
* \param args Possible arguments to pass to acquire_impl() of derived class
|
||||||
|
*/
|
||||||
|
template <typename ...Ts>
|
||||||
|
void acquire (Ts&&... args) {
|
||||||
|
if (!count++) impl().acquire_impl(std::forward<Ts>(args)...);
|
||||||
|
}
|
||||||
|
/*!
|
||||||
|
* Release the recourse. On every call we decrease the count of acquisitions. If we reach zero
|
||||||
|
* we actually release the resource.
|
||||||
|
*
|
||||||
|
* \tparam Ts The types of possible arguments
|
||||||
|
* \param args Possible arguments to pass to release_impl() of derived class
|
||||||
|
*/
|
||||||
|
template <typename ...Ts>
|
||||||
|
void release (Ts&&... args) noexcept {
|
||||||
|
if (--count <= 0) {
|
||||||
|
impl().release_impl(std::forward<Ts>(args)...);
|
||||||
|
count =0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif /* TBX_UTILS_SHARED_H_ */
|
||||||
@@ -0,0 +1,333 @@
|
|||||||
|
/*!
|
||||||
|
* \file utils/timer_delay.h
|
||||||
|
* \brief
|
||||||
|
* A CRTP timer delay utility
|
||||||
|
*
|
||||||
|
* \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_UTILS_TIMER_DELAY_H_
|
||||||
|
#define TBX_UTILS_TIMER_DELAY_H_
|
||||||
|
|
||||||
|
#include <core/core.h>
|
||||||
|
#include <core/crtp.h>
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace tbx {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \class timer_delay
|
||||||
|
* \brief
|
||||||
|
* A CRTP hw timer based, delay implementation.
|
||||||
|
*
|
||||||
|
* CRTP requirements:
|
||||||
|
* - int set_frequency_impl (size_t freq, Counter_t ticks)
|
||||||
|
* Initialize and start the hw timer with tick frequency \c freq and reload value \c ticks
|
||||||
|
* - volatile Counter_t* get_value_ptr_impl (Counter_t discard)
|
||||||
|
* Return a pointer to hw timer counter value register. The \c discard argument should discarded.
|
||||||
|
*
|
||||||
|
* \tparam Impl_t The derived class
|
||||||
|
* \tparam Counter_t The hw timer's type
|
||||||
|
*/
|
||||||
|
template <typename Impl_t, typename Counter_t>
|
||||||
|
class timer_delay {
|
||||||
|
_CRTP_IMPL(Impl_t);
|
||||||
|
using value_t = volatile Counter_t;
|
||||||
|
using marker_t = std::make_signed_t<std::remove_cv_t<Counter_t>>;
|
||||||
|
|
||||||
|
//! \name CRTP requirements
|
||||||
|
//! @{
|
||||||
|
private:
|
||||||
|
int set_frequency (size_t freq, Counter_t ticks) {
|
||||||
|
return impl().set_frequency_impl(freq, ticks);
|
||||||
|
}
|
||||||
|
volatile Counter_t* get_value_ptr (Counter_t discard = Counter_t{}) {
|
||||||
|
return impl().get_value_ptr_impl (discard);
|
||||||
|
}
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
//! \name Object lifetime
|
||||||
|
//! @{
|
||||||
|
protected:
|
||||||
|
//! \brief
|
||||||
|
//! Create and initialize
|
||||||
|
//! \param freq The required hw timer's frequency
|
||||||
|
//! \param ticks The required timer's reload value
|
||||||
|
timer_delay (size_t freq, Counter_t ticks) {
|
||||||
|
init (freq, ticks);
|
||||||
|
}
|
||||||
|
timer_delay() noexcept = default; //!< Default object is valid, but non-usable
|
||||||
|
timer_delay(const timer_delay&) = delete; //!< No copies
|
||||||
|
timer_delay& operator=(const timer_delay&) = delete; //!< No copies
|
||||||
|
//! \note
|
||||||
|
//! We are not initializing the timer via default ctor, in order to be able to
|
||||||
|
//! declare a timer_delay object globally and initialize it after the call to main().
|
||||||
|
//! @}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! Period to frequency compile time tool
|
||||||
|
constexpr Counter_t period2freq (double period) noexcept {
|
||||||
|
return (Counter_t)(1 / period);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Return the systems best approximation for ticks per msec
|
||||||
|
* \return The calculated value or zero if no calculation can apply
|
||||||
|
*/
|
||||||
|
Counter_t ticks_per_msec () {
|
||||||
|
Counter_t tck = (Counter_t)(frequency / period2freq(0.001));
|
||||||
|
return (tck <= 1) ? 1 : tck;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Return the systems best approximation for ticks per usec
|
||||||
|
* \return The calculated value or zero if no calculation can apply
|
||||||
|
*/
|
||||||
|
Counter_t ticks_per_usec () {
|
||||||
|
Counter_t tck = (Counter_t)(frequency / period2freq(0.000001));
|
||||||
|
return (tck <= 1) ? 1 : tck;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Return the systems best approximation for ticks per usec
|
||||||
|
* \return The calculated value or zero if no calculation can apply
|
||||||
|
*/
|
||||||
|
Counter_t ticks_per_100nsec () {
|
||||||
|
Counter_t tck = (Counter_t)(frequency / period2freq(0.0000001));
|
||||||
|
return (tck <= 1) ? 1 : tck;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Initializes both object members and hw timer.
|
||||||
|
*
|
||||||
|
* \param freq The required hw timer's frequency
|
||||||
|
* \param ticks The required timer's reload value
|
||||||
|
* \return
|
||||||
|
*/
|
||||||
|
bool init (size_t freq, Counter_t ticks) {
|
||||||
|
if (set_frequency(freq, ticks))
|
||||||
|
return false;
|
||||||
|
volatile Counter_t* v = get_value_ptr();
|
||||||
|
|
||||||
|
value = (v != nullptr) ? v : value;
|
||||||
|
frequency = freq;
|
||||||
|
max_ticks = ticks;
|
||||||
|
tp1ms = ticks_per_msec();
|
||||||
|
tp1us = ticks_per_usec();
|
||||||
|
tp100ns= ticks_per_100nsec();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* A code based delay implementation, using hw timer for timing.
|
||||||
|
* This is NOT accurate but it ensures that the time passed is always
|
||||||
|
* more than the requested value.
|
||||||
|
* The delay values are multiplications of 1 msec.
|
||||||
|
* \param msec Time in msec for delay
|
||||||
|
*/
|
||||||
|
void delay_ms (int msec) {
|
||||||
|
marker_t m, m2, m1 = (marker_t)*value;
|
||||||
|
|
||||||
|
msec *= tp1ms;
|
||||||
|
|
||||||
|
// Eat the time difference from msec value.
|
||||||
|
do {
|
||||||
|
m2 = (marker_t)(*value);
|
||||||
|
m = m2 - m1;
|
||||||
|
msec -= (m>=0) ? m : max_ticks + m;
|
||||||
|
m1 = m2;
|
||||||
|
} while (msec>0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* A code based delay implementation, using hw timer for timing.
|
||||||
|
* This is NOT accurate but it ensures that the time passed is always
|
||||||
|
* more than the requested value.
|
||||||
|
* The delay values are multiplications of 1 usec.
|
||||||
|
* \param usec Time in usec for delay
|
||||||
|
*/
|
||||||
|
void delay_us (int usec) {
|
||||||
|
marker_t m, m2, m1 = (marker_t)*value;
|
||||||
|
|
||||||
|
usec *= tp1us;
|
||||||
|
if ((marker_t)(*value) - m1 > usec) // Very small delays may return here.
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Eat the time difference from usec value.
|
||||||
|
do {
|
||||||
|
m2 = (marker_t)(*value);
|
||||||
|
m = m2 - m1;
|
||||||
|
usec -= (m>=0) ? m : max_ticks + m;
|
||||||
|
m1 = m2;
|
||||||
|
} while (usec>0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* A code based delay implementation, using hw timer for timing.
|
||||||
|
* This is NOT accurate but it ensures that the time passed is always
|
||||||
|
* more than the requested value.
|
||||||
|
* The delay values are multiplications of 100 nsec.
|
||||||
|
* \param _100nsec Time in 100nsec for delay
|
||||||
|
*/
|
||||||
|
void delay_100ns (int _100nsec) {
|
||||||
|
marker_t m, m2, m1 = (marker_t)*value;
|
||||||
|
|
||||||
|
_100nsec *= tp100ns;
|
||||||
|
if ((marker_t)(*value) - m1 > _100nsec) // Very small delays may return here.
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Eat the time difference from _100nsec value.
|
||||||
|
do {
|
||||||
|
m2 = (marker_t)(*value);
|
||||||
|
m = m2 - m1;
|
||||||
|
_100nsec -= (m>=0) ? m : max_ticks + m;
|
||||||
|
m1 = m2;
|
||||||
|
} while (_100nsec>0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* A code based polling version delay implementation, using hw timer for timing.
|
||||||
|
* This is NOT accurate but it ensures that the time passed is always
|
||||||
|
* more than the requested value.
|
||||||
|
* The delay values are multiplications of 1 msec.
|
||||||
|
* \param msec Time in msec for delay
|
||||||
|
* \return The status of ongoing delay
|
||||||
|
* \arg false: Delay time has passed
|
||||||
|
* \arg true: Delay is ongoing, keep calling
|
||||||
|
*/
|
||||||
|
bool check_msec (int msec) {
|
||||||
|
static marker_t m1=-1, cnt;
|
||||||
|
marker_t m, m2;
|
||||||
|
|
||||||
|
if (m1 == -1) {
|
||||||
|
m1 = *value;
|
||||||
|
cnt = tp1ms * msec;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eat the time difference from msec value.
|
||||||
|
if (cnt>0) {
|
||||||
|
m2 = (marker_t)(*value);
|
||||||
|
m = m2-m1;
|
||||||
|
cnt -= (m>=0) ? m : max_ticks + m;
|
||||||
|
m1 = m2;
|
||||||
|
return 1; // wait
|
||||||
|
} else {
|
||||||
|
m1 = -1;
|
||||||
|
return 0; // do not wait any more
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* A code based polling version delay implementation, using hw timer for timing.
|
||||||
|
* This is NOT accurate but it ensures that the time passed is always
|
||||||
|
* more than the requested value.
|
||||||
|
* The delay values are multiplications of 1 usec.
|
||||||
|
* \param usec Time in usec for delay
|
||||||
|
* \return The status of ongoing delay
|
||||||
|
* \arg false: Delay time has passed
|
||||||
|
* \arg true: Delay is ongoing, keep calling
|
||||||
|
*/
|
||||||
|
bool check_usec (int usec) {
|
||||||
|
static marker_t m1=-1, cnt;
|
||||||
|
marker_t m, m2;
|
||||||
|
|
||||||
|
if (m1 == -1) {
|
||||||
|
m1 = *value;
|
||||||
|
cnt = tp1us * usec;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eat the time difference from usec value.
|
||||||
|
if (cnt>0) {
|
||||||
|
m2 = (marker_t)(*value);
|
||||||
|
m = m2-m1;
|
||||||
|
cnt -= (m>=0) ? m : max_ticks + m;
|
||||||
|
m1 = m2;
|
||||||
|
return 1; // wait
|
||||||
|
} else {
|
||||||
|
m1 = -1;
|
||||||
|
return 0; // do not wait any more
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* A code based polling version delay implementation, using hw timer for timing.
|
||||||
|
* This is NOT accurate but it ensures that the time passed is always
|
||||||
|
* more than the requested value.
|
||||||
|
* The delay values are multiplications of 100 nsec.
|
||||||
|
* \param
|
||||||
|
* _100nsec Time in 100nsec for delay
|
||||||
|
* \return The status of ongoing delay
|
||||||
|
* \arg false: Delay time has passed
|
||||||
|
* \arg true: Delay is ongoing, keep calling
|
||||||
|
*/
|
||||||
|
bool check_100nsec (int _100nsec) {
|
||||||
|
static marker_t m1=-1, cnt;
|
||||||
|
marker_t m, m2;
|
||||||
|
|
||||||
|
if (m1 == -1) {
|
||||||
|
m1 = *value;
|
||||||
|
cnt = tp100ns * _100nsec;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eat the time difference from _100nsec value.
|
||||||
|
if (cnt>0) {
|
||||||
|
m2 = (marker_t)(*value);
|
||||||
|
m = m2-m1;
|
||||||
|
cnt -= (m>=0) ? m : max_ticks + m;
|
||||||
|
m1 = m2;
|
||||||
|
return 1; // wait
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m1 = -1;
|
||||||
|
return 0; // do not wait any more
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr Counter_t zero = 0; //!< A always zero place
|
||||||
|
|
||||||
|
value_t* value {(value_t*)&zero}; //!< Pointer to hw timer's counter register
|
||||||
|
//!< We initialize it to &zero to avoid nullptr dereference
|
||||||
|
size_t frequency{}; //!< The frequency of the timer
|
||||||
|
Counter_t max_ticks{}; //!< The reload value of the timer
|
||||||
|
Counter_t tp1ms{}; //!< ticks per ms temporary variable
|
||||||
|
Counter_t tp1us{}; //!< ticks per us temporary variable
|
||||||
|
Counter_t tp100ns{}; //!< ticks per 100ns temporary variable
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace tbx
|
||||||
|
|
||||||
|
#endif /* TBX_UTILS_TIMER_DELAY_H_ */
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Binaries
|
||||||
|
bin/
|
||||||
|
# Eclipse related
|
||||||
|
/Debug/
|
||||||
|
.settings/
|
||||||
|
.project
|
||||||
|
.cproject
|
||||||
|
|
||||||
+28
-8
@@ -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
|
DEB_CFLAGS := -std=gnu++17 -DDEBUG -g3 -Wall -Wextra -fmessage-length=0
|
||||||
REL_CFLAGS := -std=c++17 -Wall -Wextra -O2
|
REL_CFLAGS := -std=gnu++17 -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) $(ODUMP) -h -S $(BUILD_DIR)/$(TARGET) > $(BUILD_DIR)/$(basename $(TARGET)).list
|
||||||
$(DOCKER) $(OCOPY) -O ihex $(BUILD_DIR)/$(TARGET) $(BUILD_DIR)/$(basename $(TARGET)).hex
|
# $(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)
|
||||||
@@ -216,9 +221,24 @@ build-clang: $(BUILD_DIR)/$(TARGET)
|
|||||||
.PHONY: debug
|
.PHONY: debug
|
||||||
debug: $(BUILD_DIR)/$(TARGET)
|
debug: $(BUILD_DIR)/$(TARGET)
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: test_asan
|
||||||
|
test_asan: CFLAGS := $(REL_CFLAGS)
|
||||||
|
test_asan: CFLAGS += -g3 -fsanitize=address -fsanitize=leak -fsanitize=bounds-strict
|
||||||
|
test_asan: LDFLAGS += -fsanitize=address -fsanitize=leak -fsanitize=bounds-strict
|
||||||
|
test_asan: $(BUILD_DIR)/$(TARGET)
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: test_tsan
|
||||||
|
test_asan: CFLAGS := $(REL_CFLAGS)
|
||||||
|
test_tsan: CFLAGS += -g3 -fsanitize=thread
|
||||||
|
test_tsan: LDFLAGS += -fsanitize=thread
|
||||||
|
test_tsan: $(BUILD_DIR)/$(TARGET)
|
||||||
|
|
||||||
|
|
||||||
.PHONY: release
|
.PHONY: release
|
||||||
release: CFLAGS := $(REL_FLAGS)
|
release: CFLAGS := $(REL_CFLAGS)
|
||||||
release: clean $(BUILD_DIR)/$(TARGET)
|
release: $(BUILD_DIR)/$(TARGET)
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: clean release
|
all: clean release
|
||||||
|
|||||||
+5
-2
@@ -26,9 +26,12 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
GTEST_API_ int main(int argc, char **argv) {
|
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';
|
||||||
|
}
|
||||||
|
|||||||
Submodule
+1
Submodule test/mingw-std-threads added at f6365f900f
@@ -0,0 +1,422 @@
|
|||||||
|
/*!
|
||||||
|
* \file cli_device.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/cli_device.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <cont/equeue.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <utility>
|
||||||
|
#include <type_traits>
|
||||||
|
#ifndef WIN_TRHEADS
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#else
|
||||||
|
#include <mingw.thread.h>
|
||||||
|
#include <mingw.mutex.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace test_cli_device {
|
||||||
|
using namespace tbx;
|
||||||
|
|
||||||
|
// test settings
|
||||||
|
constexpr size_t Size = 128;
|
||||||
|
using data_type = char;
|
||||||
|
|
||||||
|
// cli_device implementer mock. We simulate a BG95 ATmodem for that purpose
|
||||||
|
template<size_t N>
|
||||||
|
class BG95 : public cli_device<BG95<N>, N> {
|
||||||
|
using base_type = cli_device<BG95<N>, N>;
|
||||||
|
using Queue = equeue<typename base_type::value_type, N, true>;
|
||||||
|
|
||||||
|
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, 20> 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) {
|
||||||
|
if (cmd != nullptr) {
|
||||||
|
for (auto& it : cmd_map) {
|
||||||
|
if (!std::strcmp(it.cmd, cmd))
|
||||||
|
return it.resp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cmd_map[1].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
|
||||||
|
Queue RxQ{};
|
||||||
|
std::atomic<size_t> lines{};
|
||||||
|
clock_t t=0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// cli_device driver requirements
|
||||||
|
BG95() noexcept :
|
||||||
|
RxQ(Queue::data_match::MATCH_PUSH, base_type::delimiter, [&](){
|
||||||
|
lines.fetch_add(1, std::memory_order_acq_rel);
|
||||||
|
}), lines(0) { }
|
||||||
|
|
||||||
|
size_t get(char* data, bool wait =false) {
|
||||||
|
do {
|
||||||
|
if (lines.load(std::memory_order_acquire)) {
|
||||||
|
size_t n =0;
|
||||||
|
do{
|
||||||
|
*data << RxQ;
|
||||||
|
++n;
|
||||||
|
} while (*data++ != base_type::delimiter);
|
||||||
|
lines.fetch_sub(1, std::memory_order_acq_rel);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
} while (wait);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t contents(char* data) {
|
||||||
|
char* nullpos = std::copy(RxQ.begin(), RxQ.end(), data);
|
||||||
|
*nullpos =0;
|
||||||
|
return nullpos - data;
|
||||||
|
}
|
||||||
|
size_t put (const char* data, size_t n) {
|
||||||
|
const char* reply = cmd_responce (data);
|
||||||
|
while (*reply)
|
||||||
|
RxQ << *reply++;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
clock_t clock() noexcept { return ++t; }
|
||||||
|
void clear_clock() noexcept { t=0; }
|
||||||
|
|
||||||
|
// extra helper for testing purposes
|
||||||
|
void async (event e) {
|
||||||
|
const char* reply =event_responce (e);
|
||||||
|
while (*reply)
|
||||||
|
RxQ << *reply++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Behavior flag
|
||||||
|
bool handler_flag = false;
|
||||||
|
void handler (const char* data, size_t n) {
|
||||||
|
(void)*data;
|
||||||
|
(void)n;
|
||||||
|
handler_flag = true;
|
||||||
|
}
|
||||||
|
void clear_flag () {
|
||||||
|
handler_flag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Tcli_device, traits) {
|
||||||
|
EXPECT_EQ ( std::is_default_constructible<BG95<Size>>::value, true);
|
||||||
|
EXPECT_EQ ( std::is_nothrow_default_constructible<BG95<Size>>::value, true);
|
||||||
|
EXPECT_EQ (!std::is_copy_constructible<BG95<Size>>::value, true);
|
||||||
|
EXPECT_EQ (!std::is_copy_assignable<BG95<Size>>::value, true);
|
||||||
|
|
||||||
|
EXPECT_EQ ((std::is_same_v<BG95<Size>::value_type, data_type>), true);
|
||||||
|
EXPECT_EQ ((std::is_same_v<BG95<Size>::pointer_type, data_type*>), true);
|
||||||
|
EXPECT_EQ ((std::is_same_v<BG95<Size>::size_type, size_t>), true);
|
||||||
|
EXPECT_EQ ((std::is_same_v<BG95<Size>::string_view, std::basic_string_view<data_type>>), true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test inetd in non blocking mode
|
||||||
|
*/
|
||||||
|
TEST(Tcli_device, txrx_inetd) {
|
||||||
|
BG95<Size> modem;
|
||||||
|
char buffer[Size];
|
||||||
|
size_t s =0;
|
||||||
|
|
||||||
|
const BG95<Size>::inetd_handlers<2> async = {{
|
||||||
|
{"+QMTSTAT:", BG95<Size>::starts_with, handler},
|
||||||
|
{"+QMTRECV", BG95<Size>::contains, handler},
|
||||||
|
}};
|
||||||
|
|
||||||
|
s = modem.transmit("", std::strlen(""));
|
||||||
|
EXPECT_EQ (s, 0UL);
|
||||||
|
s = modem.transmit("");
|
||||||
|
EXPECT_EQ (s, 0UL);
|
||||||
|
s = modem.transmit(nullptr);
|
||||||
|
EXPECT_EQ (s, 0UL);
|
||||||
|
|
||||||
|
clear_flag();
|
||||||
|
modem.inetd(false, &async);
|
||||||
|
EXPECT_EQ (handler_flag, false);
|
||||||
|
modem.async(BG95<Size>::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 (std::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 (std::strcmp(buffer, "\r\n"), 0);
|
||||||
|
modem.inetd(false, &async); // parse "+CSQ: 19,99\r\n"
|
||||||
|
EXPECT_NE (modem.receive(buffer), 0UL);
|
||||||
|
EXPECT_EQ (std::strcmp(buffer, "+CSQ: 19,99\r\n"), 0);
|
||||||
|
modem.inetd(false, &async); // parse "\r\n"
|
||||||
|
EXPECT_NE (modem.receive(buffer), 0UL);
|
||||||
|
EXPECT_EQ (std::strcmp(buffer, "\r\n"), 0);
|
||||||
|
modem.inetd(false, &async); // parse "OK\r\n"
|
||||||
|
EXPECT_NE (modem.receive(buffer), 0UL);
|
||||||
|
EXPECT_EQ (std::strcmp(buffer, "OK\r\n"), 0);
|
||||||
|
modem.inetd(false, &async); // nothing to parse
|
||||||
|
modem.inetd(false, &async);
|
||||||
|
modem.inetd(false, &async);
|
||||||
|
EXPECT_EQ (modem.receive(buffer), 0UL);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Tcli_device, run) {
|
||||||
|
BG95<Size> modem;
|
||||||
|
|
||||||
|
using Control = BG95<Size>::control_t;
|
||||||
|
|
||||||
|
const BG95<Size>::script_t<4> script = {{
|
||||||
|
{Control::NOP, "", BG95<Size>::nil, BG95<Size>::nil, BG95<Size>::go_to<1>, 100000},
|
||||||
|
{Control::SEND, "ATE0\r\n", BG95<Size>::nil, BG95<Size>::nil, BG95<Size>::next, 0},
|
||||||
|
{Control::EXPECT, "OK\r\n", BG95<Size>::ends_with, BG95<Size>::nil, BG95<Size>::exit_ok, 100000},
|
||||||
|
{Control::OR_EXPECT,"ERROR", BG95<Size>::contains, BG95<Size>::nil, BG95<Size>::exit_error, 0}
|
||||||
|
}};
|
||||||
|
|
||||||
|
std::mutex m;
|
||||||
|
m.lock();
|
||||||
|
std::thread th1 ([&](){
|
||||||
|
do
|
||||||
|
modem.inetd(false);
|
||||||
|
while (!m.try_lock());
|
||||||
|
m.unlock();
|
||||||
|
});
|
||||||
|
EXPECT_EQ (modem.run(script), BG95<Size>::exit_ok.value);
|
||||||
|
m.unlock(); // stop and join inetd
|
||||||
|
th1.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Tcli_device, clear_size) {
|
||||||
|
BG95<Size> modem;
|
||||||
|
char buffer[Size];
|
||||||
|
|
||||||
|
modem.clear();
|
||||||
|
EXPECT_EQ (modem.size(), 0UL);
|
||||||
|
EXPECT_EQ (modem.receive(buffer), 0UL);
|
||||||
|
|
||||||
|
modem.transmit("abcd", 4);
|
||||||
|
modem.inetd(false);
|
||||||
|
modem.inetd(false);
|
||||||
|
EXPECT_NE (modem.size(), 0UL);
|
||||||
|
modem.clear();
|
||||||
|
EXPECT_EQ (modem.size(), 0UL);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Tcli_device, command_non_extraction) {
|
||||||
|
BG95<Size> modem;
|
||||||
|
char buffer[Size];
|
||||||
|
|
||||||
|
std::mutex m;
|
||||||
|
m.lock();
|
||||||
|
std::thread th1 ([&](){
|
||||||
|
do
|
||||||
|
modem.inetd(false);
|
||||||
|
while (!m.try_lock());
|
||||||
|
m.unlock();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto run_receive = [&](size_t times) -> size_t {
|
||||||
|
size_t s =0;
|
||||||
|
for (size_t i=0 ; i<times ; ++i)
|
||||||
|
s += modem.receive(buffer);
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_EQ (modem.command("", "", 0), true); // returns: ""
|
||||||
|
EXPECT_EQ (modem.size(), 0UL);
|
||||||
|
EXPECT_EQ (run_receive(100000), 0UL);
|
||||||
|
|
||||||
|
EXPECT_EQ (modem.command("", "abcd", 100000), false);// returns: ""
|
||||||
|
EXPECT_EQ (modem.size(), 0UL);
|
||||||
|
EXPECT_EQ (run_receive(100000), 0UL);
|
||||||
|
|
||||||
|
EXPECT_EQ (modem.command("abcd", "", 0), true); // returns: "\r\nERROR\r\n"
|
||||||
|
EXPECT_GT (run_receive(100000), 0UL);
|
||||||
|
|
||||||
|
EXPECT_EQ (modem.command("AT\r\n", "Something else", 100000), false);// returns: "\r\nOK\r\n"
|
||||||
|
EXPECT_GT (run_receive(100000), 0UL);
|
||||||
|
|
||||||
|
EXPECT_EQ (modem.command("AT\r\n", "\r\nOK\r\n", 100000), true); // returns: "\r\nOK\r\n"
|
||||||
|
EXPECT_EQ (modem.size(), 0UL);
|
||||||
|
EXPECT_EQ (run_receive(100000), 0UL);
|
||||||
|
|
||||||
|
EXPECT_EQ (modem.command("AT\r\n", "%OK\r\n", 100000), true); // returns: "\r\nOK\r\n"
|
||||||
|
EXPECT_EQ (modem.size(), 0UL);
|
||||||
|
EXPECT_EQ (run_receive(100000), 0UL);
|
||||||
|
|
||||||
|
// returns: "\r\n+CREG: 0,5\r\n\r\nOK\r\n
|
||||||
|
EXPECT_EQ (modem.command<BG95<Size>::flush>("AT+CREG?\r\n", "%OK\r\n", 0), false);
|
||||||
|
EXPECT_GT (run_receive(100000), 0UL);
|
||||||
|
|
||||||
|
// returns: "\r\n+CREG: 0,5\r\n\r\nOK\r\n
|
||||||
|
EXPECT_EQ (modem.command<BG95<Size>::flush>("AT+CREG?\r\n", "%%%OK\r\n", 0), true);
|
||||||
|
EXPECT_EQ (modem.size(), 0UL);
|
||||||
|
EXPECT_EQ (run_receive(100000), 0UL);
|
||||||
|
|
||||||
|
// returns: "\r\n+CREG: 0,5\r\n\r\nOK\r\n
|
||||||
|
EXPECT_EQ (modem.command<BG95<Size>::flush>("AT+CREG?\r\n", "%", 0), true);
|
||||||
|
EXPECT_GT (run_receive(100000), 0UL);
|
||||||
|
|
||||||
|
EXPECT_EQ (modem.command<BG95<Size>::flush>("AT\r\n", "%%", 0), true); // returns: "\r\nOK\r\n"
|
||||||
|
EXPECT_EQ (modem.size(), 0UL);
|
||||||
|
EXPECT_EQ (run_receive(100000), 0UL);
|
||||||
|
|
||||||
|
EXPECT_EQ (modem.command<BG95<Size>::flush>("AT\r\n", "%%%", 10000), false); // returns: "\r\nOK\r\n"
|
||||||
|
EXPECT_EQ (modem.size(), 0UL);
|
||||||
|
EXPECT_EQ (run_receive(100000), 0UL);
|
||||||
|
|
||||||
|
// returns: "\r\n+CREG: 0,5\r\n\r\nOK\r\n
|
||||||
|
EXPECT_EQ (modem.command<modem.flush>("AT+CREG?\r\n", "", 0), true);
|
||||||
|
EXPECT_EQ (modem.command<modem.keep>("", "%", 0), true);
|
||||||
|
EXPECT_EQ (modem.command<modem.keep>("", "%%", 0), true);
|
||||||
|
EXPECT_EQ (modem.command<modem.keep>("", "%", 0), true);
|
||||||
|
EXPECT_EQ (modem.command<modem.keep>("", "%", 10000), false);
|
||||||
|
EXPECT_EQ (modem.size(), 0UL);
|
||||||
|
EXPECT_EQ (run_receive(100000), 0UL);
|
||||||
|
|
||||||
|
m.unlock(); // stop and join inetd
|
||||||
|
th1.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Tcli_device, command_extraction) {
|
||||||
|
BG95<Size> modem;
|
||||||
|
char buffer[Size];
|
||||||
|
|
||||||
|
std::mutex m;
|
||||||
|
m.lock();
|
||||||
|
std::thread th1 ([&](){
|
||||||
|
do
|
||||||
|
modem.inetd(false);
|
||||||
|
while (!m.try_lock());
|
||||||
|
m.unlock();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto run_receive = [&](size_t times) -> size_t {
|
||||||
|
size_t s =0;
|
||||||
|
for (size_t i=0 ; i<times ; ++i)
|
||||||
|
s += modem.receive(buffer);
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
int status1, status2;
|
||||||
|
EXPECT_EQ (modem.command("AT+CREG?\r\n", "\r\n+CREG: 0,%\r\n\r\nOK\r\n", 100000, &status1), true);
|
||||||
|
EXPECT_EQ (status1, 5);
|
||||||
|
EXPECT_EQ (modem.command("AT+CREG?\r\n", "\r\n+CREG: %,%\r\n\r\nOK\r\n", 100000, &status1, &status2), true);
|
||||||
|
EXPECT_EQ (status1, 0);
|
||||||
|
EXPECT_EQ (status2, 5);
|
||||||
|
|
||||||
|
char substr1[32], substr2[32];
|
||||||
|
EXPECT_EQ (modem.command("AT+CREG?\r\n", "\r\n%\r\n\r\n%\r\n", 100000, substr1, substr2), true);
|
||||||
|
EXPECT_EQ (std::strcmp("+CREG: 0,5", substr1), 0);
|
||||||
|
EXPECT_EQ (std::strcmp("OK", substr2), 0);
|
||||||
|
|
||||||
|
// returns: "\r\n+CREG: 0,5\r\n\r\nOK\r\n
|
||||||
|
EXPECT_EQ (modem.command<modem.flush>("AT+CREG?\r\n", "", 100000), true);
|
||||||
|
EXPECT_EQ (modem.command<modem.keep>("", "%", 100000, substr1), true);
|
||||||
|
EXPECT_EQ (std::strcmp("\r\n", substr1), 0);
|
||||||
|
|
||||||
|
EXPECT_EQ (modem.command<modem.keep>("", "%%", 100000, substr1, substr2), true);
|
||||||
|
EXPECT_EQ (std::strcmp("+CREG: 0,5\r\n", substr1), 0);
|
||||||
|
EXPECT_EQ (std::strcmp("\r\n", substr2), 0);
|
||||||
|
|
||||||
|
EXPECT_EQ (modem.command<modem.keep>("", "%", 100000, substr1), true);
|
||||||
|
EXPECT_EQ (std::strcmp("OK\r\n", substr1), 0);
|
||||||
|
|
||||||
|
EXPECT_EQ (modem.command<modem.keep>("", "%", 10000), false);
|
||||||
|
|
||||||
|
EXPECT_EQ (modem.size(), 0UL);
|
||||||
|
EXPECT_EQ (run_receive(100000), 0UL);
|
||||||
|
|
||||||
|
m.unlock(); // stop and join inetd
|
||||||
|
th1.join();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,12 +29,57 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#include <cont/deque.h>
|
#include <cont/deque.h>
|
||||||
|
#include <cont/span.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <cstring>
|
||||||
|
#ifndef WIN_TRHEADS
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#else
|
||||||
|
#include <mingw.thread.h>
|
||||||
|
#include <mingw.mutex.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Tdeque {
|
namespace Tdeque {
|
||||||
using namespace tbx;
|
using namespace tbx;
|
||||||
|
|
||||||
|
template <typename>
|
||||||
|
struct is_span : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T, std::size_t S>
|
||||||
|
struct is_span<tbx::span<T, S>> : std::true_type {};
|
||||||
|
|
||||||
|
template <typename>
|
||||||
|
struct is_std_array : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
struct is_std_array<std::array<T, N>> : std::true_type {};
|
||||||
|
|
||||||
|
template <typename, typename = void>
|
||||||
|
struct has_size_and_data : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct has_size_and_data<T, std::void_t<decltype(std::declval<T>().size()),
|
||||||
|
decltype(std::declval<T>().data())>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
// Concept
|
||||||
|
TEST(Tdeque, concept) {
|
||||||
|
using deque_t = deque<int, 8>;
|
||||||
|
|
||||||
|
EXPECT_EQ ( std::is_default_constructible<deque_t>::value, true);
|
||||||
|
EXPECT_EQ ( std::is_nothrow_default_constructible<deque_t>::value, true);
|
||||||
|
EXPECT_EQ (!std::is_copy_constructible<deque_t>::value, true);
|
||||||
|
EXPECT_EQ (!std::is_copy_assignable<deque_t>::value, true);
|
||||||
|
|
||||||
|
EXPECT_EQ (true, !is_span<deque_t>::value);
|
||||||
|
EXPECT_EQ (true, !is_std_array<deque_t>::value);
|
||||||
|
EXPECT_EQ (true, !std::is_array<deque_t>::value);
|
||||||
|
EXPECT_EQ (true, has_size_and_data<deque_t>::value);
|
||||||
|
}
|
||||||
// Test construction
|
// Test construction
|
||||||
TEST(Tdeque, contruct) {
|
TEST(Tdeque, contruct) {
|
||||||
deque<int, 8> q1;
|
deque<int, 8> q1;
|
||||||
@@ -192,5 +237,233 @@ namespace Tdeque {
|
|||||||
EXPECT_EQ(6, check_it); // run through all
|
EXPECT_EQ(6, check_it); // run through all
|
||||||
|
|
||||||
}
|
}
|
||||||
|
TEST (Tdeque, range) {
|
||||||
|
deque<int, 8> q1{1, 2, 3, 4, 5, 6, 7, 8};
|
||||||
|
int check_it=1;
|
||||||
|
|
||||||
|
for (auto& it : q1.contents())
|
||||||
|
EXPECT_EQ(it, check_it++);
|
||||||
|
|
||||||
|
EXPECT_EQ(9, check_it); // run through all
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Concept
|
||||||
|
TEST(Tdeque, concept_atomic) {
|
||||||
|
using deque_t = deque<int, 8, true>;
|
||||||
|
|
||||||
|
EXPECT_EQ (true, !is_span<deque_t>::value);
|
||||||
|
EXPECT_EQ (true, !is_std_array<deque_t>::value);
|
||||||
|
EXPECT_EQ (true, !std::is_array<deque_t>::value);
|
||||||
|
EXPECT_EQ (true, has_size_and_data<deque_t>::value);
|
||||||
|
}
|
||||||
|
// Test construction
|
||||||
|
TEST(Tdeque, contruct_atomic) {
|
||||||
|
deque<int, 8, true> q1;
|
||||||
|
deque<int, 8, true> q2{1, 2, 3, 4, 5, 6, 7, 8};
|
||||||
|
deque<int, 8, true> q3{1, 2, 3, 4, 5};
|
||||||
|
|
||||||
|
EXPECT_EQ (8UL, q1.capacity());
|
||||||
|
EXPECT_EQ (0UL, q1.size());
|
||||||
|
EXPECT_EQ (8UL, q2.capacity());
|
||||||
|
EXPECT_EQ (8UL, q2.size());
|
||||||
|
EXPECT_EQ (8UL, q3.capacity());
|
||||||
|
EXPECT_EQ (5UL, q3.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// simple push-pop functionality
|
||||||
|
TEST(Tdeque, push_pop_atomic) {
|
||||||
|
deque<int, 8, true> q1;
|
||||||
|
deque<int, 8, true> q2{1, 2, 3, 4, 5, 6, 7, 8};
|
||||||
|
|
||||||
|
q1.push_front(1);
|
||||||
|
q1.push_front(2);
|
||||||
|
EXPECT_EQ (1, q1.pop_back());
|
||||||
|
EXPECT_EQ (2, q1.pop_back());
|
||||||
|
|
||||||
|
q1.push_back(1);
|
||||||
|
q1.push_back(2);
|
||||||
|
EXPECT_EQ (1, q1.pop_front());
|
||||||
|
EXPECT_EQ (2, q1.pop_front());
|
||||||
|
|
||||||
|
q1.push_front(2);
|
||||||
|
q1.push_back(3);
|
||||||
|
q1.push_front(1);
|
||||||
|
q1.push_back(4);
|
||||||
|
|
||||||
|
for (int i=1 ; i<= 4 ; ++i)
|
||||||
|
EXPECT_EQ ((int)i, q1.pop_front());
|
||||||
|
}
|
||||||
|
|
||||||
|
// front-back
|
||||||
|
TEST(Tdeque, front_back_atomic) {
|
||||||
|
deque<int, 8, true> q1;
|
||||||
|
deque<int, 8, true> q2{1, 2, 3, 4, 5, 6, 7, 8};
|
||||||
|
|
||||||
|
q1.push_front(2);
|
||||||
|
q1.push_front(1);
|
||||||
|
q1.push_back(3);
|
||||||
|
q1.push_back(4);
|
||||||
|
|
||||||
|
EXPECT_EQ (1, q1.front());
|
||||||
|
EXPECT_EQ (4, q1.back());
|
||||||
|
EXPECT_EQ (1, q2.front());
|
||||||
|
EXPECT_EQ (8, q2.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
// capacity
|
||||||
|
TEST(Tdeque, capacity_atomic) {
|
||||||
|
deque<int, 8, true> q1;
|
||||||
|
deque<int, 8, true> q2{1, 2, 3, 4, 5, 6, 7, 8};
|
||||||
|
|
||||||
|
q1.push_back(1);
|
||||||
|
q1.clear();
|
||||||
|
EXPECT_EQ (true, q1.empty());
|
||||||
|
EXPECT_EQ (true, q2.full());
|
||||||
|
|
||||||
|
EXPECT_EQ (8UL, q1.capacity());
|
||||||
|
EXPECT_EQ (8UL, q2.capacity());
|
||||||
|
|
||||||
|
EXPECT_EQ (0UL, q1.size());
|
||||||
|
EXPECT_EQ (8UL, q2.size());
|
||||||
|
|
||||||
|
q1.push_back(2);
|
||||||
|
EXPECT_EQ (1UL, q1.size());
|
||||||
|
q1.push_front(1);
|
||||||
|
EXPECT_EQ (2UL, q1.size());
|
||||||
|
|
||||||
|
q1.pop_back();
|
||||||
|
EXPECT_EQ (1UL, q1.size());
|
||||||
|
q1.pop_front();
|
||||||
|
EXPECT_EQ (0UL, q1.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// push-pop limits
|
||||||
|
TEST (Tdeque, push_pop_limits_atomic) {
|
||||||
|
deque<int, 8, true> q1;
|
||||||
|
deque<int, 8, true> q2{1, 2, 3, 4, 5, 6, 7, 8};
|
||||||
|
|
||||||
|
EXPECT_EQ (int{}, q1.pop_back());
|
||||||
|
EXPECT_EQ (0UL, q1.size());
|
||||||
|
EXPECT_EQ (true, q1.empty());
|
||||||
|
EXPECT_EQ (false, q1.full());
|
||||||
|
|
||||||
|
EXPECT_EQ (int{}, q1.pop_front());
|
||||||
|
EXPECT_EQ (0UL, q1.size());
|
||||||
|
EXPECT_EQ (true, q1.empty());
|
||||||
|
EXPECT_EQ (false, q1.full());
|
||||||
|
|
||||||
|
q2.push_front(0);
|
||||||
|
EXPECT_EQ (1, q2.front());
|
||||||
|
EXPECT_EQ (8, q2.back());
|
||||||
|
EXPECT_EQ (8UL, q2.size());
|
||||||
|
EXPECT_EQ (false, q2.empty());
|
||||||
|
EXPECT_EQ (true, q2.full());
|
||||||
|
|
||||||
|
q2.push_back(9);
|
||||||
|
EXPECT_EQ (1, q2.front());
|
||||||
|
EXPECT_EQ (8, q2.back());
|
||||||
|
EXPECT_EQ (8UL, q2.size());
|
||||||
|
EXPECT_EQ (false, q2.empty());
|
||||||
|
EXPECT_EQ (true, q2.full());
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterators
|
||||||
|
TEST (Tdeque, iterators_atomic) {
|
||||||
|
deque<int, 8, true> q1{1, 2, 3, 4, 5, 6, 7, 8};
|
||||||
|
int check_it=1;
|
||||||
|
|
||||||
|
EXPECT_EQ (q1.begin().base(), q1.end().base());
|
||||||
|
EXPECT_NE (q1.begin().iter(), q1.end().iter());
|
||||||
|
EXPECT_EQ (1, *q1.begin());
|
||||||
|
EXPECT_EQ (true, (q1.begin() == ++q1.end())); // loop edge iterators
|
||||||
|
|
||||||
|
for (auto it = q1.begin() ; it != q1.end() ; ++it)
|
||||||
|
EXPECT_EQ(*it, check_it++);
|
||||||
|
EXPECT_EQ(9, check_it); // run through all
|
||||||
|
|
||||||
|
EXPECT_EQ (1, q1.front()); // queue stays intact
|
||||||
|
EXPECT_EQ (8, q1.back());
|
||||||
|
EXPECT_EQ (8UL, q1.size());
|
||||||
|
EXPECT_EQ (false, q1.empty());
|
||||||
|
EXPECT_EQ (true, q1.full());
|
||||||
|
|
||||||
|
q1.pop_front();
|
||||||
|
q1.pop_back();
|
||||||
|
|
||||||
|
check_it=2;
|
||||||
|
for (auto& it : q1)
|
||||||
|
EXPECT_EQ(it, check_it++);
|
||||||
|
EXPECT_EQ(8, check_it); // run through all
|
||||||
|
|
||||||
|
EXPECT_EQ (2, q1.front()); // queue stays intact
|
||||||
|
EXPECT_EQ (7, q1.back());
|
||||||
|
EXPECT_EQ (6UL, q1.size());
|
||||||
|
EXPECT_EQ (false, q1.empty());
|
||||||
|
EXPECT_EQ (false, q1.full());
|
||||||
|
|
||||||
|
deque<int, 8, true> q2;
|
||||||
|
q2.push_front(2);
|
||||||
|
q2.push_front(1);
|
||||||
|
q2.push_back(3);
|
||||||
|
q2.push_back(4);
|
||||||
|
q2.push_back(5);
|
||||||
|
check_it =1;
|
||||||
|
for (auto& it : q2)
|
||||||
|
EXPECT_EQ(it, check_it++);
|
||||||
|
EXPECT_EQ(6, check_it); // run through all
|
||||||
|
|
||||||
|
}
|
||||||
|
TEST (Tdeque, range_atomic) {
|
||||||
|
deque<int, 8, true> q1{1, 2, 3, 4, 5, 6, 7, 8};
|
||||||
|
int check_it=1;
|
||||||
|
|
||||||
|
for (auto& it : q1.contents())
|
||||||
|
EXPECT_EQ(it, check_it++);
|
||||||
|
|
||||||
|
EXPECT_EQ(9, check_it); // run through all
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Tdeque, race) {
|
||||||
|
constexpr size_t N = 1000000;
|
||||||
|
deque<int, N, true> q;
|
||||||
|
int result[N];
|
||||||
|
|
||||||
|
auto push_front = [&](){
|
||||||
|
for (size_t i=1 ; i<=N ; ++i) q.push_front(i);
|
||||||
|
};
|
||||||
|
auto push_back = [&](){
|
||||||
|
for (size_t i=1 ; i<=N ; ++i) q.push_back(i);
|
||||||
|
};
|
||||||
|
auto pop_front = [&](){
|
||||||
|
for (size_t i=0 ; i<N ; ) {
|
||||||
|
result[i] = q.pop_front();
|
||||||
|
if (result[i] != int{})
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
auto pop_back = [&](){
|
||||||
|
for (size_t i=0 ; i<N ; ) {
|
||||||
|
result[i] = q.pop_back();
|
||||||
|
if (result[i] != int{})
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::memset(result, 0, sizeof result);
|
||||||
|
std::thread th1 (push_front);
|
||||||
|
std::thread th2 (pop_back);
|
||||||
|
th1.join();
|
||||||
|
th2.join();
|
||||||
|
for (size_t i=0 ; i<N ; ++i)
|
||||||
|
EXPECT_EQ (result[i], (int)i+1);
|
||||||
|
|
||||||
|
std::memset(result, 0, sizeof result);
|
||||||
|
std::thread th3 (push_back);
|
||||||
|
std::thread th4 (pop_front);
|
||||||
|
th3.join();
|
||||||
|
th4.join();
|
||||||
|
for (size_t i=0 ; i<N ; ++i)
|
||||||
|
EXPECT_EQ (result[i], (int)i+1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+297
-9
@@ -1,7 +1,7 @@
|
|||||||
/*!
|
/*!
|
||||||
* \file deque.cpp
|
* \file deque.cpp
|
||||||
* \brief
|
* \brief
|
||||||
* Unit tests for deque
|
* Unit tests for edeque
|
||||||
*
|
*
|
||||||
* \copyright Copyright (C) 2020 Christos Choutouridis <christos@choutouridis.net>
|
* \copyright Copyright (C) 2020 Christos Choutouridis <christos@choutouridis.net>
|
||||||
*
|
*
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
namespace Tdeque {
|
namespace Tedeque {
|
||||||
using namespace tbx;
|
using namespace tbx;
|
||||||
|
|
||||||
int global_flag =0;
|
int global_flag =0;
|
||||||
@@ -132,15 +132,15 @@ namespace Tdeque {
|
|||||||
e1.check_trigger(); // manual trigger attempt
|
e1.check_trigger(); // manual trigger attempt
|
||||||
EXPECT_EQ (false, flag); // [SIZE triggers are auto clear]
|
EXPECT_EQ (false, flag); // [SIZE triggers are auto clear]
|
||||||
|
|
||||||
Edeque e2(Edeque::data_match::MATCH, 42, [&](){ flag = true; });
|
Edeque e2(Edeque::data_match::MATCH_PUSH, 42, [&](){ flag = true; });
|
||||||
flag = false;
|
flag = false;
|
||||||
e2.clear_trigger();
|
e2.clear_trigger();
|
||||||
EXPECT_EQ (false, flag);
|
EXPECT_EQ (false, flag);
|
||||||
e2.push_back(42); // push 42, no-trigger cleared
|
e2.push_back(42); // push 42, no-trigger cleared
|
||||||
EXPECT_EQ (false, flag);
|
EXPECT_EQ (false, flag);
|
||||||
e2.set_trigger(Edeque::data_match::MATCH, 42, [&](){ flag = true; });
|
e2.set_trigger(Edeque::data_match::MATCH_PUSH, 42, [&](){ flag = true; });
|
||||||
EXPECT_EQ (false, flag); // no spurious triggers
|
EXPECT_EQ (false, flag); // no spurious triggers
|
||||||
e2.pop_back(); // pop 42, trigger
|
e2.push_back(42); // push 42, trigger
|
||||||
EXPECT_EQ (true, flag);
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
flag = false;
|
flag = false;
|
||||||
@@ -236,8 +236,8 @@ namespace Tdeque {
|
|||||||
using Edeque = edeque<int, 8>;
|
using Edeque = edeque<int, 8>;
|
||||||
bool flag{};
|
bool flag{};
|
||||||
|
|
||||||
// data_match::MATCH (item == 42)
|
// data_match::MATCH_PUSH (item == 42)
|
||||||
Edeque ee(Edeque::data_match::MATCH, 42, [&](){ flag = true; });
|
Edeque ee(Edeque::data_match::MATCH_PUSH, 42, [&](){ flag = true; });
|
||||||
|
|
||||||
flag = false;
|
flag = false;
|
||||||
ee.push_back(7); // 7
|
ee.push_back(7); // 7
|
||||||
@@ -246,14 +246,28 @@ namespace Tdeque {
|
|||||||
EXPECT_EQ (true, flag);
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
flag = false;
|
flag = false;
|
||||||
|
ee.pop_back(); // pop:42, no-trigger
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.push_back(42); // push:42, re-trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
|
// data_match::MATCH_POP (item == 42)
|
||||||
|
flag = false;
|
||||||
|
ee.clear_trigger();
|
||||||
|
ee.set_trigger(Edeque::data_match::MATCH_POP, 42, [&](){ flag = true; });
|
||||||
|
ee.push_back(7); // 7
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.push_back(42); // push:42, no-trigger
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
ee.pop_back(); // pop:42, trigger
|
ee.pop_back(); // pop:42, trigger
|
||||||
EXPECT_EQ (true, flag);
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
// data_match::MATCH (item != 42)
|
// data_match::MISMATCH_PUSH (item != 42)
|
||||||
flag = false;
|
flag = false;
|
||||||
ee.clear();
|
ee.clear();
|
||||||
|
ee.clear_trigger();
|
||||||
ee.push_back(7); // 7
|
ee.push_back(7); // 7
|
||||||
ee.set_trigger(Edeque::data_match::MISMATCH, 42, [&](){ flag = true; });
|
ee.set_trigger(Edeque::data_match::MISMATCH_PUSH, 42, [&](){ flag = true; });
|
||||||
EXPECT_EQ (false, flag); // no spurious triggers
|
EXPECT_EQ (false, flag); // no spurious triggers
|
||||||
ee.push_back(42); // 42, no-trigger
|
ee.push_back(42); // 42, no-trigger
|
||||||
EXPECT_EQ (false, flag);
|
EXPECT_EQ (false, flag);
|
||||||
@@ -263,5 +277,279 @@ namespace Tdeque {
|
|||||||
flag = false;
|
flag = false;
|
||||||
ee.push_back(1); // 1, re-trigger
|
ee.push_back(1); // 1, re-trigger
|
||||||
EXPECT_EQ (true, flag);
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
|
// data_match::MISMATCH_POP (item != 42)
|
||||||
|
flag = false;
|
||||||
|
ee.clear();
|
||||||
|
ee.clear_trigger();
|
||||||
|
ee.push_back(7); // ->7
|
||||||
|
ee.pop_back(); // <-7
|
||||||
|
ee.set_trigger(Edeque::data_match::MISMATCH_POP, 42, [&](){ flag = true; });
|
||||||
|
EXPECT_EQ (false, flag); // no spurious triggers
|
||||||
|
ee.push_back(42); // ->42, no-trigger
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.push_back(0); // ->0, no-trigger
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.pop_back(); // pop:0, trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
ee.push_back(0);
|
||||||
|
ee.pop_back(); // pop:0, re-trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// atomic
|
||||||
|
TEST (Tedeque, construct_atomic) {
|
||||||
|
using Edeque = edeque<int, 8, true>;
|
||||||
|
struct T { int a,b; };
|
||||||
|
int local{};
|
||||||
|
|
||||||
|
Edeque e1(Edeque::size_match::GE, 3, [](){
|
||||||
|
++global_flag;
|
||||||
|
});
|
||||||
|
Edeque e2(Edeque::size_match::GE, 3, [&](){
|
||||||
|
++local;
|
||||||
|
});
|
||||||
|
Edeque e3(Edeque::size_match::EQ, 7, vfun);
|
||||||
|
edeque<T, 8> e4(edeque<T, 8>::size_match::EQ, 2, vfoo{});
|
||||||
|
|
||||||
|
edeque<int, 8, true> q1;
|
||||||
|
edeque<int, 8, true> q2(edeque<int, 8, true>::size_match::DISABLED, 0, nullptr);
|
||||||
|
|
||||||
|
EXPECT_EQ (8UL, e1.capacity());
|
||||||
|
EXPECT_EQ (8UL, e2.capacity());
|
||||||
|
EXPECT_EQ (8UL, e3.capacity());
|
||||||
|
EXPECT_EQ (8UL, e4.capacity());
|
||||||
|
|
||||||
|
EXPECT_EQ (8UL, q1.capacity());
|
||||||
|
EXPECT_EQ (8UL, q2.capacity());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST (Tedeque, base_class_atomic) {
|
||||||
|
using Edeque = edeque<int, 8, true>;
|
||||||
|
|
||||||
|
Edeque e1(Edeque::size_match::GE, 3, [](){
|
||||||
|
++global_flag;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Access of base class functionality
|
||||||
|
EXPECT_EQ (8UL, e1.capacity());
|
||||||
|
EXPECT_EQ (0UL, e1.size());
|
||||||
|
EXPECT_EQ (true, e1.empty());
|
||||||
|
EXPECT_EQ (false, e1.full());
|
||||||
|
|
||||||
|
e1.push_back(7);
|
||||||
|
EXPECT_EQ (7, e1.front());
|
||||||
|
EXPECT_EQ (7, e1.back());
|
||||||
|
EXPECT_EQ (7, e1.pop_front());
|
||||||
|
|
||||||
|
e1.push_front(42);
|
||||||
|
EXPECT_EQ (42, e1.front());
|
||||||
|
EXPECT_EQ (42, e1.back());
|
||||||
|
EXPECT_EQ (42, e1.pop_back());
|
||||||
|
|
||||||
|
e1.push_back(1);
|
||||||
|
e1.push_back(2);
|
||||||
|
e1.push_back(3);
|
||||||
|
|
||||||
|
int check_it=1;
|
||||||
|
for (auto it = e1.begin() ; it != e1.end() ; ++it)
|
||||||
|
EXPECT_EQ(*it, check_it++);
|
||||||
|
EXPECT_EQ(4, check_it); // run through all
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST (Tedeque, set_clear_check_trigger_atomic) {
|
||||||
|
using Edeque = edeque<int, 8, true>;
|
||||||
|
bool flag{};
|
||||||
|
|
||||||
|
Edeque e1(Edeque::size_match::GE, 1, [&](){ flag = true; });
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
e1.clear_trigger();
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
e1.push_back(1); // 1, no-trigger cleared
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
e1.clear();
|
||||||
|
e1.clear_trigger();
|
||||||
|
EXPECT_EQ (false, flag); // no spurious triggers
|
||||||
|
e1.push_back(1); // 1
|
||||||
|
e1.push_back(2); // 2
|
||||||
|
e1.set_trigger(Edeque::size_match::GE, 1, [&](){ flag = true; });
|
||||||
|
EXPECT_EQ (false, flag); // no spurious triggers
|
||||||
|
e1.check_trigger(); // manual trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
e1.check_trigger(); // manual trigger attempt
|
||||||
|
EXPECT_EQ (false, flag); // [SIZE triggers are auto clear]
|
||||||
|
|
||||||
|
Edeque e2(Edeque::data_match::MATCH_PUSH, 42, [&](){ flag = true; });
|
||||||
|
flag = false;
|
||||||
|
e2.clear_trigger();
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
e2.push_back(42); // push 42, no-trigger cleared
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
e2.set_trigger(Edeque::data_match::MATCH_PUSH, 42, [&](){ flag = true; });
|
||||||
|
EXPECT_EQ (false, flag); // no spurious triggers
|
||||||
|
e2.push_back(42); // push 42, trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
e2.push_back(42); // push 42, re-trigger [DATA re-triggers]
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST (Tedeque, size_triggers_atomic) {
|
||||||
|
using Edeque = edeque<int, 8, true>;
|
||||||
|
bool flag{};
|
||||||
|
|
||||||
|
// size_match::GE (size()>= 2)
|
||||||
|
Edeque ee(Edeque::size_match::GE, 2, [&](){ flag = true; });
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
ee.clear();
|
||||||
|
ee.push_back(1); // 1
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.push_back(2); // 2, trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
flag = false;
|
||||||
|
ee.push_back(3); // 3, no-trigger cleared
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
|
||||||
|
// size_match::GT (size()> 1)
|
||||||
|
flag = false;
|
||||||
|
ee.clear();
|
||||||
|
ee.set_trigger(Edeque::size_match::GT, 1, [&](){ flag = true; });
|
||||||
|
ee.push_back(1); // 1
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.push_back(2); // 2, trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
flag = false;
|
||||||
|
ee.push_back(3); // 3, no-trigger cleared
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
|
||||||
|
// size_match::LE (size()<= 1)
|
||||||
|
flag = false;
|
||||||
|
ee.clear();
|
||||||
|
ee.push_back(1); // 1
|
||||||
|
ee.push_back(2); // 2
|
||||||
|
ee.push_back(3); // 3
|
||||||
|
ee.set_trigger(Edeque::size_match::LE, 1, [&](){ flag = true; });
|
||||||
|
ee.pop_front(); // 2
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.pop_front(); // 1, trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
flag = false;
|
||||||
|
ee.pop_front(); // 0, no-trigger cleared
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
|
||||||
|
// size_match::LT (size()< 2)
|
||||||
|
flag = false;
|
||||||
|
ee.clear();
|
||||||
|
ee.push_back(1); // 1
|
||||||
|
ee.push_back(2); // 2
|
||||||
|
ee.push_back(3); // 3
|
||||||
|
ee.set_trigger(Edeque::size_match::LT, 2, [&](){ flag = true; });
|
||||||
|
ee.pop_front(); // 2
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.pop_front(); // 1, trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
flag = false;
|
||||||
|
ee.pop_front(); // 0, no-trigger cleared
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
|
||||||
|
// size_match::EQ (size()== 2)
|
||||||
|
flag = false;
|
||||||
|
ee.clear();
|
||||||
|
ee.set_trigger(Edeque::size_match::EQ, 2, [&](){ flag = true; });
|
||||||
|
ee.push_back(1); // 1
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.push_back(2); // 2, trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
flag = false;
|
||||||
|
ee.push_back(3); // 3
|
||||||
|
ee.pop_front(); // 2, no-trigger cleared
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
|
||||||
|
// size_match::NE (size()!= 0)
|
||||||
|
flag = false;
|
||||||
|
ee.clear();
|
||||||
|
ee.set_trigger(Edeque::size_match::NE, 0, [&](){ flag = true; });
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.push_back(1); // 1, trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
flag = false;
|
||||||
|
ee.push_back(2); // 2, no-trigger
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST (Tedeque, data_triggers_atomic) {
|
||||||
|
using Edeque = edeque<int, 8, true>;
|
||||||
|
bool flag{};
|
||||||
|
|
||||||
|
// data_match::MATCH_PUSH (item == 42)
|
||||||
|
Edeque ee(Edeque::data_match::MATCH_PUSH, 42, [&](){ flag = true; });
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
ee.push_back(7); // 7
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.push_back(42); // push:42, trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
ee.pop_back(); // pop:42, no-trigger
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.push_back(42); // push:42, re-trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
|
// data_match::MATCH_POP (item == 42)
|
||||||
|
flag = false;
|
||||||
|
ee.clear_trigger();
|
||||||
|
ee.set_trigger(Edeque::data_match::MATCH_POP, 42, [&](){ flag = true; });
|
||||||
|
ee.push_back(7); // 7
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.push_back(42); // push:42, no-trigger
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.pop_back(); // pop:42, trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
|
// data_match::MISMATCH_PUSH (item != 42)
|
||||||
|
flag = false;
|
||||||
|
ee.clear();
|
||||||
|
ee.clear_trigger();
|
||||||
|
ee.push_back(7); // 7
|
||||||
|
ee.set_trigger(Edeque::data_match::MISMATCH_PUSH, 42, [&](){ flag = true; });
|
||||||
|
EXPECT_EQ (false, flag); // no spurious triggers
|
||||||
|
ee.push_back(42); // 42, no-trigger
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.push_back(0); // 0, trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
ee.push_back(1); // 1, re-trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
|
// data_match::MISMATCH_POP (item != 42)
|
||||||
|
flag = false;
|
||||||
|
ee.clear();
|
||||||
|
ee.clear_trigger();
|
||||||
|
ee.push_back(7); // ->7
|
||||||
|
ee.pop_back(); // <-7
|
||||||
|
ee.set_trigger(Edeque::data_match::MISMATCH_POP, 42, [&](){ flag = true; });
|
||||||
|
EXPECT_EQ (false, flag); // no spurious triggers
|
||||||
|
ee.push_back(42); // ->42, no-trigger
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.push_back(0); // ->0, no-trigger
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
ee.pop_back(); // pop:0, trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
ee.push_back(0);
|
||||||
|
ee.pop_back(); // pop:0, re-trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+139
-3
@@ -128,15 +128,15 @@ namespace Tequeue {
|
|||||||
e1.check_trigger(); // manual trigger attempt
|
e1.check_trigger(); // manual trigger attempt
|
||||||
EXPECT_EQ (false, flag); // [SIZE triggers are auto clear]
|
EXPECT_EQ (false, flag); // [SIZE triggers are auto clear]
|
||||||
|
|
||||||
Equeue e2(Equeue::data_match::MATCH, 42, [&](){ flag = true; });
|
Equeue e2(Equeue::data_match::MATCH_PUSH, 42, [&](){ flag = true; });
|
||||||
flag = false;
|
flag = false;
|
||||||
e2.clear_trigger();
|
e2.clear_trigger();
|
||||||
EXPECT_EQ (false, flag);
|
EXPECT_EQ (false, flag);
|
||||||
e2.push_back(42); // push 42, no-trigger cleared
|
e2.push_back(42); // push 42, no-trigger cleared
|
||||||
EXPECT_EQ (false, flag);
|
EXPECT_EQ (false, flag);
|
||||||
e2.set_trigger(Equeue::data_match::MATCH, 42, [&](){ flag = true; });
|
e2.set_trigger(Equeue::data_match::MATCH_PUSH, 42, [&](){ flag = true; });
|
||||||
EXPECT_EQ (false, flag); // no spurious triggers
|
EXPECT_EQ (false, flag); // no spurious triggers
|
||||||
e2.pop_back(); // pop 42, trigger
|
e2.push_back(42); // push 42, trigger
|
||||||
EXPECT_EQ (true, flag);
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
flag = false;
|
flag = false;
|
||||||
@@ -178,4 +178,140 @@ namespace Tequeue {
|
|||||||
EXPECT_EQ (int{}, check_it);
|
EXPECT_EQ (int{}, check_it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// atomic
|
||||||
|
// Test construction
|
||||||
|
TEST(Tequeue, contruct_atomic) {
|
||||||
|
using Equeue = equeue<int, 8, true>;
|
||||||
|
struct T { int a,b; };
|
||||||
|
int local{};
|
||||||
|
|
||||||
|
Equeue e1(Equeue::size_match::GE, 3, [](){
|
||||||
|
++global_flag;
|
||||||
|
});
|
||||||
|
Equeue e2(Equeue::size_match::GE, 3, [&](){
|
||||||
|
++local;
|
||||||
|
});
|
||||||
|
Equeue e3(Equeue::size_match::EQ, 7, vfun);
|
||||||
|
equeue<T, 8> e4(equeue<T, 8>::size_match::EQ, 2, vfoo{});
|
||||||
|
|
||||||
|
equeue<int, 8, true> q1;
|
||||||
|
equeue<int, 8, true> q2(equeue<int, 8, true>::size_match::DISABLED, 0, nullptr);
|
||||||
|
|
||||||
|
EXPECT_EQ (8UL, e1.capacity());
|
||||||
|
EXPECT_EQ (8UL, e2.capacity());
|
||||||
|
EXPECT_EQ (8UL, e3.capacity());
|
||||||
|
EXPECT_EQ (8UL, e4.capacity());
|
||||||
|
|
||||||
|
EXPECT_EQ (8UL, q1.capacity());
|
||||||
|
EXPECT_EQ (8UL, q2.capacity());
|
||||||
|
}
|
||||||
|
|
||||||
|
// simple push-pop functionality
|
||||||
|
TEST(Tequeue, base_class_atomic) {
|
||||||
|
using Equeue = equeue<int, 8, true>;
|
||||||
|
|
||||||
|
Equeue e1(Equeue::size_match::GE, 3, [](){
|
||||||
|
++global_flag;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Access of base class functionality
|
||||||
|
EXPECT_EQ (8UL, e1.capacity());
|
||||||
|
EXPECT_EQ (0UL, e1.size());
|
||||||
|
EXPECT_EQ (true, e1.empty());
|
||||||
|
EXPECT_EQ (false, e1.full());
|
||||||
|
|
||||||
|
e1.push(42);
|
||||||
|
EXPECT_EQ (42, e1.front());
|
||||||
|
EXPECT_EQ (42, e1.back());
|
||||||
|
EXPECT_EQ (42, e1.pop());
|
||||||
|
|
||||||
|
e1.push(1);
|
||||||
|
e1.push(2);
|
||||||
|
e1.push(3);
|
||||||
|
|
||||||
|
int check_it=1;
|
||||||
|
for (auto it = e1.begin() ; it != e1.end() ; ++it)
|
||||||
|
EXPECT_EQ(*it, check_it++);
|
||||||
|
EXPECT_EQ(4, check_it); // run through all
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// trigger functionality
|
||||||
|
TEST (Tequeue, set_clear_check_trigger_atomic) {
|
||||||
|
using Equeue = equeue<int, 8, true>;
|
||||||
|
bool flag{};
|
||||||
|
|
||||||
|
Equeue e1(Equeue::size_match::GE, 1, [&](){ flag = true; });
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
e1.clear_trigger();
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
e1.push_back(1); // 1, no-trigger cleared
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
e1.clear();
|
||||||
|
e1.clear_trigger();
|
||||||
|
EXPECT_EQ (false, flag); // no spurious triggers
|
||||||
|
e1.push_back(1); // 1
|
||||||
|
e1.push_back(2); // 2
|
||||||
|
e1.set_trigger(Equeue::size_match::GE, 1, [&](){ flag = true; });
|
||||||
|
EXPECT_EQ (false, flag); // no spurious triggers
|
||||||
|
e1.check_trigger(); // manual trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
e1.check_trigger(); // manual trigger attempt
|
||||||
|
EXPECT_EQ (false, flag); // [SIZE triggers are auto clear]
|
||||||
|
|
||||||
|
Equeue e2(Equeue::data_match::MATCH_PUSH, 42, [&](){ flag = true; });
|
||||||
|
flag = false;
|
||||||
|
e2.clear_trigger();
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
e2.push_back(42); // push 42, no-trigger cleared
|
||||||
|
EXPECT_EQ (false, flag);
|
||||||
|
e2.set_trigger(Equeue::data_match::MATCH_PUSH, 42, [&](){ flag = true; });
|
||||||
|
EXPECT_EQ (false, flag); // no spurious triggers
|
||||||
|
e2.push_back(42); // push 42, trigger
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
e2.push_back(42); // push 42, re-trigger [DATA re-triggers]
|
||||||
|
EXPECT_EQ (true, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream push-pop
|
||||||
|
TEST(Tequeue, stream_push_pop_atomic) {
|
||||||
|
equeue<int, 8, true> q1;
|
||||||
|
|
||||||
|
q1 << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8;
|
||||||
|
EXPECT_EQ (8UL, q1.capacity());
|
||||||
|
EXPECT_EQ (8UL, q1.size());
|
||||||
|
EXPECT_EQ (false, q1.empty());
|
||||||
|
EXPECT_EQ (true, q1.full());
|
||||||
|
|
||||||
|
q1 << 9; // try to insert in full queue
|
||||||
|
EXPECT_EQ (8UL, q1.capacity());
|
||||||
|
EXPECT_EQ (8UL, q1.size());
|
||||||
|
EXPECT_EQ (false, q1.empty());
|
||||||
|
EXPECT_EQ (true, q1.full());
|
||||||
|
|
||||||
|
int check_it=1;
|
||||||
|
for (auto it = q1.begin() ; it != q1.end() ; ++it)
|
||||||
|
EXPECT_EQ(*it, check_it++);
|
||||||
|
EXPECT_EQ(9, check_it); // run through all
|
||||||
|
|
||||||
|
for (int i =1 ; i <= 8 ; ++i) {
|
||||||
|
check_it << q1;
|
||||||
|
EXPECT_EQ(i, check_it);
|
||||||
|
}
|
||||||
|
EXPECT_EQ (8UL, q1.capacity());
|
||||||
|
EXPECT_EQ (0UL, q1.size());
|
||||||
|
EXPECT_EQ (true, q1.empty());
|
||||||
|
EXPECT_EQ (false, q1.full());
|
||||||
|
|
||||||
|
q1 >> check_it;
|
||||||
|
EXPECT_EQ (int{}, check_it);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,4 +109,80 @@ namespace Tqueue {
|
|||||||
EXPECT_EQ (int{}, check_it);
|
EXPECT_EQ (int{}, check_it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Test construction
|
||||||
|
TEST(Tqueue, contruct_atomic) {
|
||||||
|
queue<int, 8, true> q1;
|
||||||
|
queue<int, 8, true> q2{1, 2, 3, 4, 5, 6, 7, 8};
|
||||||
|
queue<int, 8, true> q3{1, 2, 3, 4, 5};
|
||||||
|
|
||||||
|
EXPECT_EQ (8UL, q1.capacity());
|
||||||
|
EXPECT_EQ (0UL, q1.size());
|
||||||
|
EXPECT_EQ (8UL, q2.capacity());
|
||||||
|
EXPECT_EQ (8UL, q2.size());
|
||||||
|
EXPECT_EQ (8UL, q3.capacity());
|
||||||
|
EXPECT_EQ (5UL, q3.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// base class functionality check
|
||||||
|
TEST(Tqueue, base_class_atomic) {
|
||||||
|
|
||||||
|
queue<int, 8, true> q1;
|
||||||
|
|
||||||
|
// Access of base class functionality
|
||||||
|
EXPECT_EQ (8UL, q1.capacity());
|
||||||
|
EXPECT_EQ (0UL, q1.size());
|
||||||
|
EXPECT_EQ (true, q1.empty());
|
||||||
|
EXPECT_EQ (false, q1.full());
|
||||||
|
|
||||||
|
q1.push(42);
|
||||||
|
EXPECT_EQ (42, q1.front());
|
||||||
|
EXPECT_EQ (42, q1.back());
|
||||||
|
EXPECT_EQ (42, q1.pop());
|
||||||
|
|
||||||
|
q1.push(1);
|
||||||
|
q1.push(2);
|
||||||
|
q1.push(3);
|
||||||
|
|
||||||
|
int check_it=1;
|
||||||
|
for (auto it = q1.begin() ; it != q1.end() ; ++it)
|
||||||
|
EXPECT_EQ(*it, check_it++);
|
||||||
|
EXPECT_EQ(4, check_it); // run through all
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream push-pop
|
||||||
|
TEST(Tqueue, stream_push_pop_atomic) {
|
||||||
|
queue<int, 8, true> q1;
|
||||||
|
|
||||||
|
q1 << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8;
|
||||||
|
EXPECT_EQ (8UL, q1.capacity());
|
||||||
|
EXPECT_EQ (8UL, q1.size());
|
||||||
|
EXPECT_EQ (false, q1.empty());
|
||||||
|
EXPECT_EQ (true, q1.full());
|
||||||
|
|
||||||
|
q1 << 9; // try to insert in full queue
|
||||||
|
EXPECT_EQ (8UL, q1.capacity());
|
||||||
|
EXPECT_EQ (8UL, q1.size());
|
||||||
|
EXPECT_EQ (false, q1.empty());
|
||||||
|
EXPECT_EQ (true, q1.full());
|
||||||
|
|
||||||
|
int check_it=1;
|
||||||
|
for (auto it = q1.begin() ; it != q1.end() ; ++it)
|
||||||
|
EXPECT_EQ(*it, check_it++);
|
||||||
|
EXPECT_EQ(9, check_it); // run through all
|
||||||
|
|
||||||
|
for (int i =1 ; i <= 8 ; ++i) {
|
||||||
|
check_it << q1;
|
||||||
|
EXPECT_EQ(i, check_it);
|
||||||
|
}
|
||||||
|
EXPECT_EQ (8UL, q1.capacity());
|
||||||
|
EXPECT_EQ (0UL, q1.size());
|
||||||
|
EXPECT_EQ (true, q1.empty());
|
||||||
|
EXPECT_EQ (false, q1.full());
|
||||||
|
|
||||||
|
q1 >> check_it;
|
||||||
|
EXPECT_EQ (int{}, check_it);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,4 +228,197 @@ namespace Tring_iterator {
|
|||||||
EXPECT_EQ (1, (i2 - i1)); // loop
|
EXPECT_EQ (1, (i2 - i1)); // loop
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test construction atomic
|
||||||
|
TEST(Tring_iterator, construct_atomic) {
|
||||||
|
int A[10];
|
||||||
|
|
||||||
|
//default constructor
|
||||||
|
ring_iterator<int*, 10, true> i1;
|
||||||
|
EXPECT_EQ(nullptr, i1.base());
|
||||||
|
EXPECT_EQ(nullptr, i1.iter());
|
||||||
|
EXPECT_EQ(10UL, i1.size());
|
||||||
|
|
||||||
|
// implementation specific (you can remove it freely)
|
||||||
|
EXPECT_EQ(2*sizeof(int*), sizeof(i1));
|
||||||
|
|
||||||
|
// basic
|
||||||
|
ring_iterator<int*, 10, true> i2(A);
|
||||||
|
EXPECT_EQ(A, i2.base());
|
||||||
|
EXPECT_EQ(A, i2.iter());
|
||||||
|
EXPECT_EQ(10UL, i2.size());
|
||||||
|
|
||||||
|
// basic from assignment
|
||||||
|
ring_iterator<int*, 10, true> i3 = A;
|
||||||
|
EXPECT_EQ(A, i3.base());
|
||||||
|
EXPECT_EQ(A, i3.iter());
|
||||||
|
EXPECT_EQ(10UL, i3.size());
|
||||||
|
|
||||||
|
// basic with offset
|
||||||
|
ring_iterator<int*, 10, true> i4(A, 5);
|
||||||
|
EXPECT_EQ(A, i4.base());
|
||||||
|
EXPECT_EQ(&A[5], i4.iter());
|
||||||
|
EXPECT_EQ(10UL, i4.size());
|
||||||
|
|
||||||
|
// copy (Legacy iterator)
|
||||||
|
auto i5 = i2;
|
||||||
|
EXPECT_EQ(A, i5.base());
|
||||||
|
EXPECT_EQ(A, i5.iter());
|
||||||
|
EXPECT_EQ(10UL, i5.size());
|
||||||
|
|
||||||
|
// arbitrary type
|
||||||
|
struct TT { int a,b,c; };
|
||||||
|
std::array<TT, 10> t;
|
||||||
|
ring_iterator<TT*, 10, true> it(t.data(), 2);
|
||||||
|
EXPECT_EQ(t.begin(), it.base());
|
||||||
|
EXPECT_EQ(&t[2], it.iter());
|
||||||
|
EXPECT_EQ(10UL, it.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy iterator atomic
|
||||||
|
TEST(Tring_iterator, LegacyIterator_atomic) {
|
||||||
|
|
||||||
|
EXPECT_EQ(true, (std::is_same<int, typename ring_iterator<int*, 10, true>::value_type>::value));
|
||||||
|
EXPECT_EQ(true, (std::is_same<std::ptrdiff_t, typename ring_iterator<int*, 10, true>::difference_type>::value));
|
||||||
|
EXPECT_EQ(true, (std::is_same<int&, typename ring_iterator<int*, 10, true>::reference>::value));
|
||||||
|
EXPECT_EQ(true, (std::is_same<int*, typename ring_iterator<int*, 10, true>::pointer>::value));
|
||||||
|
EXPECT_EQ(true, (std::is_same<std::random_access_iterator_tag, typename ring_iterator<int*, 10, true>::iterator_category>::value));
|
||||||
|
|
||||||
|
int A[10] {0, 1, 2, 3, 4, 5, 6, 7, 8 , 9};
|
||||||
|
ring_iterator<int*, 10, true> i1(A);
|
||||||
|
|
||||||
|
// copy constructible/assignable
|
||||||
|
auto i2 = i1;
|
||||||
|
EXPECT_EQ(A, i2.base());
|
||||||
|
EXPECT_EQ(A, i2.iter());
|
||||||
|
EXPECT_EQ(10UL, i2.size());
|
||||||
|
|
||||||
|
// dereferenceable - incrementable
|
||||||
|
ring_iterator<int*, 10, true> i3(A);
|
||||||
|
EXPECT_EQ(true, (std::is_reference<decltype(*i3)>::value));
|
||||||
|
EXPECT_EQ(true, (std::is_same<ring_iterator<int*, 10, true>&, decltype(++i3)>::value));
|
||||||
|
EXPECT_EQ(true, (std::is_reference<decltype((*i3++))>::value));
|
||||||
|
|
||||||
|
// more practical
|
||||||
|
ring_iterator<int*, 10, true> i4(A);
|
||||||
|
ring_iterator<int*, 10, true> i5(A, 9);
|
||||||
|
EXPECT_EQ(A[0], *i4);
|
||||||
|
EXPECT_EQ(&A[1], (++i4).iter());
|
||||||
|
// check loop
|
||||||
|
EXPECT_EQ(A[9], *i5);
|
||||||
|
EXPECT_EQ(&A[0], (++i5).iter());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy input iterator atomic
|
||||||
|
TEST(Tring_iterator, LegacyInputIterator_atomic) {
|
||||||
|
int A[10] {0, 1, 2, 3, 4, 5, 6, 7, 8 , 9};
|
||||||
|
ring_iterator<int*, 10, true> i1(A), i2(A), i3(A, 1);
|
||||||
|
|
||||||
|
struct T { int m; };
|
||||||
|
T B[5] { {0}, {1}, {2}, {3}, {4}};
|
||||||
|
ring_iterator<T*, 5, true> it(B);
|
||||||
|
|
||||||
|
EXPECT_EQ (true, (std::is_same<bool, decltype(i1 == i2)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<bool, decltype(i1 != i2)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<int&, decltype(*i1)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<int, decltype(it->m)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<ring_iterator<int*, 10, true>&, decltype(++i1)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<int&, decltype(*i1++)>::value));
|
||||||
|
|
||||||
|
// more practical
|
||||||
|
EXPECT_EQ (true, i1 == i2);
|
||||||
|
EXPECT_EQ (true, i1 != i3);
|
||||||
|
EXPECT_EQ (0, *i1);
|
||||||
|
EXPECT_EQ (0, it->m);
|
||||||
|
EXPECT_EQ (true, (++i1 == i3));
|
||||||
|
EXPECT_EQ (1, *i1++);
|
||||||
|
EXPECT_EQ (2, *i1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy input iterator atomic
|
||||||
|
TEST(Tring_iterator, LegacyOutputIterator_atomic) {
|
||||||
|
int A[10] {0, 1, 2, 3, 4, 5, 6, 7, 8 , 9};
|
||||||
|
ring_iterator<int*, 10, true> it(A);
|
||||||
|
|
||||||
|
EXPECT_EQ (true, (std::is_assignable<decltype(*it), int>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_assignable<decltype(*it++), int>::value));
|
||||||
|
|
||||||
|
// more practical
|
||||||
|
*it = 42;
|
||||||
|
EXPECT_EQ (42, A[0]);
|
||||||
|
*it++ = 7;
|
||||||
|
EXPECT_EQ (7, A[0]);
|
||||||
|
EXPECT_EQ (&A[1], it.iter());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy forward iterator atomic
|
||||||
|
TEST(Tring_iterator, LegacyForwardIterator_atomic)
|
||||||
|
{
|
||||||
|
int A[10] {0, 1, 2, 3, 4, 5, 6, 7, 8 , 9};
|
||||||
|
ring_iterator<int*, 10, true> it(A);
|
||||||
|
|
||||||
|
EXPECT_EQ (0, *it++);
|
||||||
|
EXPECT_EQ (1, *it);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy bidirectional iterator atomic
|
||||||
|
TEST(Tring_iterator, LegacyBidirectionalIterator_atomic) {
|
||||||
|
int A[10] {0, 1, 2, 3, 4, 5, 6, 7, 8 , 9};
|
||||||
|
ring_iterator<int*, 10, true> it(A);
|
||||||
|
|
||||||
|
EXPECT_EQ (true, (std::is_same<ring_iterator<int*, 10, true>&, decltype(--it)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<ring_iterator<int*, 10, true>, decltype(it--)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<int&, decltype(*it--)>::value));
|
||||||
|
|
||||||
|
// more practical
|
||||||
|
ring_iterator<int*, 10, true> i1(A), i2(A, 9);
|
||||||
|
EXPECT_EQ (9, *i2--); // check loop also
|
||||||
|
EXPECT_EQ (8, *i2);
|
||||||
|
EXPECT_EQ (0, *i1--); // check loop also
|
||||||
|
EXPECT_EQ (9, *i1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy random access iterator atomic
|
||||||
|
TEST(Tring_iterator, LegacyRandomAccessIterator_atomic) {
|
||||||
|
int A[10] {0, 1, 2, 3, 4, 5, 6, 7, 8 , 9};
|
||||||
|
ring_iterator<int*, 10, true> it1(A), it2(A, 7);
|
||||||
|
|
||||||
|
EXPECT_EQ (true, (std::is_same<ring_iterator<int*, 10, true>&, decltype(it1 += 7)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<ring_iterator<int*, 10, true>, decltype(it1 + 7)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<ring_iterator<int*, 10, true>, decltype(7 + it1)>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, (std::is_same<ring_iterator<int*, 10, true>&, decltype(it1 -= 7)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<ring_iterator<int*, 10, true>, decltype(it1 - 7)>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, (std::is_same<std::ptrdiff_t, decltype(it1 - it2)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<int&, decltype(it1[7])>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, (std::is_same<bool, decltype(it1 < it2)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<bool, decltype(it1 > it2)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<bool, decltype(it1 <= it2)>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_same<bool, decltype(it1 >= it2)>::value));
|
||||||
|
|
||||||
|
// more practical
|
||||||
|
ring_iterator<int*, 10, true> i1(A), i2(A);
|
||||||
|
i1 += 7;
|
||||||
|
EXPECT_EQ (7, *i1);
|
||||||
|
i1 -= 7;
|
||||||
|
EXPECT_EQ (0, *i1);
|
||||||
|
i1 += 11;
|
||||||
|
EXPECT_EQ (1, *i1);
|
||||||
|
i1 -= 2;
|
||||||
|
EXPECT_EQ (9, *i1);
|
||||||
|
|
||||||
|
EXPECT_EQ (7, *(i2+7));
|
||||||
|
EXPECT_EQ (7, *(7+i2));
|
||||||
|
EXPECT_EQ (1, *(i2+11));
|
||||||
|
EXPECT_EQ (1, *(11+i2));
|
||||||
|
EXPECT_EQ (7, *(i1-2));
|
||||||
|
EXPECT_EQ (8, *(i2-2));
|
||||||
|
|
||||||
|
EXPECT_EQ (9, (i1 - i2));
|
||||||
|
EXPECT_EQ (1, (i2 - i1)); // loop
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+393
-65
@@ -26,98 +26,426 @@
|
|||||||
* </dd></dl>
|
* </dd></dl>
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#include <utils/sequencer.h>
|
#include <com/sequencer.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
namespace test_sequencer {
|
namespace test_sequencer {
|
||||||
using namespace tbx;
|
using namespace tbx;
|
||||||
|
|
||||||
|
// test settings
|
||||||
|
using data_type = char;
|
||||||
|
constexpr size_t size = 64;
|
||||||
|
|
||||||
// Sequencer implementer mock
|
// Sequencer implementer mock
|
||||||
class Seq : public sequencer_t<Seq, char, 128> {
|
class Seq : public sequencer<Seq, data_type, size> {
|
||||||
const char *msg_[10] = {
|
static constexpr int NrCommands =5;
|
||||||
"", "", "", "\r\nOK\r\n",
|
static constexpr int NoCommand =-1;
|
||||||
"", "", "+CCLK = \"21/08/26-12:16:30+12\"\r\nOK\r\n"
|
|
||||||
"", "", "\r\nERROR\r\n"
|
std::array<const char*, NrCommands> command = {
|
||||||
|
"cmd1",
|
||||||
|
"cmd2\n",
|
||||||
|
"cmd3\r\n",
|
||||||
|
"cmd4\n\r",
|
||||||
|
"cmd5\n",
|
||||||
};
|
};
|
||||||
|
std::array<const char*, NrCommands> reply {
|
||||||
|
"reply1",
|
||||||
|
"reply2\n",
|
||||||
|
"reply3\n text \r text text\n",
|
||||||
|
"reply4\n text\n text \r text\r\n",
|
||||||
|
"reply5\n",
|
||||||
|
};
|
||||||
|
int cmd =NoCommand;
|
||||||
|
clock_t t =0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
size_t get(char* data) {
|
size_t get(char* data) {
|
||||||
static int msg =0;
|
static int ans = 0;
|
||||||
size_t len = strlen(msg_[msg]);
|
if ((++ans % 3) == 0)
|
||||||
strcpy(data, msg_[msg++]);
|
return 0;
|
||||||
if (msg >= 10) msg =0;
|
|
||||||
return len;
|
if (cmd == NoCommand) {
|
||||||
|
std::strcpy(data, "ERROR\n");
|
||||||
|
return 6;
|
||||||
|
} else {
|
||||||
|
std::strcpy(data, reply[cmd]);
|
||||||
|
size_t s = std::strlen(reply[cmd]);
|
||||||
|
cmd =NoCommand;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t contents (char* data) {
|
||||||
|
if (cmd == NoCommand) {
|
||||||
|
std::strcpy(data, "");
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
std::strcpy(data, reply[cmd]);
|
||||||
|
return std::strlen(reply[cmd]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
size_t put (const char* data, size_t n) {
|
size_t put (const char* data, size_t n) {
|
||||||
(void)*data;
|
for (size_t i =0 ; i<NrCommands ; ++i) {
|
||||||
|
if (!std::strcmp(data, command[i])) {
|
||||||
|
cmd =i;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd =NoCommand;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
clock_t clock() { static clock_t t=0; return ++t; }
|
clock_t clock() { return ++t; }
|
||||||
|
void clear_clock() { t =0; }
|
||||||
static status_t my_handler (const char* data, size_t size) {
|
|
||||||
(void)*data;
|
|
||||||
(void)size;
|
|
||||||
return Seq::status_t::OK;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Test sequencer.run()
|
* Test sequencer object
|
||||||
*/
|
*/
|
||||||
TEST(Tsequencer, run_delay) {
|
TEST (Tsequencer, traits) {
|
||||||
|
EXPECT_EQ ( std::is_default_constructible<Seq>::value, true);
|
||||||
|
EXPECT_EQ ( std::is_nothrow_default_constructible<Seq>::value, true);
|
||||||
|
EXPECT_EQ (!std::is_copy_constructible<Seq>::value, true);
|
||||||
|
EXPECT_EQ (!std::is_copy_assignable<Seq>::value, true);
|
||||||
|
|
||||||
|
EXPECT_EQ ((std::is_same_v<Seq::value_type, data_type>), true);
|
||||||
|
EXPECT_EQ ((std::is_same_v<Seq::pointer_type, data_type*>), true);
|
||||||
|
EXPECT_EQ ((std::is_same_v<Seq::size_type, size_t>), true);
|
||||||
|
EXPECT_EQ ((std::is_same_v<Seq::string_view, std::basic_string_view<data_type>>), true);
|
||||||
|
|
||||||
Seq s;
|
Seq s;
|
||||||
const std::array<Seq::record_t, 1> script = {{
|
EXPECT_EQ (s.size(), size);
|
||||||
/* 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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Tsequencer, run_dummy_output) {
|
TEST (Tsequencer, predicates) {
|
||||||
Seq s;
|
EXPECT_EQ ((std::is_invocable_r<bool, decltype(Seq::equals), Seq::string_view, Seq::string_view>::value), true);
|
||||||
const std::array<Seq::record_t, 2> script = {{
|
EXPECT_EQ ((std::is_invocable_r<bool, decltype(Seq::starts_with), Seq::string_view, Seq::string_view>::value), true);
|
||||||
/* 0 */{Seq::control_t::SEND, {"", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000},
|
EXPECT_EQ ((std::is_invocable_r<bool, decltype(Seq::ends_with), Seq::string_view, Seq::string_view>::value), true);
|
||||||
/* 1 */{Seq::control_t::SEND, {"", Seq::match_t::NO, nullptr, Seq::action_t::EXIT_OK, 0}, 1000}
|
EXPECT_EQ ((std::is_invocable_r<bool, decltype(Seq::contains), Seq::string_view, Seq::string_view>::value), true);
|
||||||
}};
|
EXPECT_EQ ((std::is_invocable_r<bool, decltype(Seq::always_true), Seq::string_view, Seq::string_view>::value), true);
|
||||||
EXPECT_EQ ((int)Seq::status_t::OK, (int)s.run(script));
|
EXPECT_EQ ((std::is_invocable_r<bool, decltype(Seq::always_false), Seq::string_view, Seq::string_view>::value), true);
|
||||||
|
|
||||||
|
EXPECT_EQ (Seq::nil, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Tsequencer, run_exits) {
|
TEST (Tsequencer, actions) {
|
||||||
Seq s;
|
EXPECT_EQ ( std::is_default_constructible<Seq::action_t>::value, true);
|
||||||
const std::array<Seq::record_t, 1> script1 = {{
|
EXPECT_EQ ( std::is_nothrow_default_constructible<Seq::action_t>::value, true);
|
||||||
/* 0 */{Seq::control_t::SEND, {"", Seq::match_t::NO, nullptr, Seq::action_t::EXIT_OK, 0}, 1000},
|
EXPECT_EQ ( std::is_copy_constructible<Seq::action_t>::value, true);
|
||||||
}};
|
EXPECT_EQ ( std::is_copy_assignable<Seq::action_t>::value, true);
|
||||||
EXPECT_EQ ((int)Seq::status_t::OK, (int)s.run(script1));
|
|
||||||
|
|
||||||
const std::array<Seq::record_t, 1> script2 = {{
|
EXPECT_EQ ((std::is_same_v<const Seq::action_t, decltype(Seq::no_action)>), true);
|
||||||
/* 0 */{Seq::control_t::SEND, {"", Seq::match_t::NO, nullptr, Seq::action_t::EXIT_ERROR, 0}, 1000},
|
EXPECT_EQ ((std::is_same_v<const Seq::action_t, decltype(Seq::next)>), true);
|
||||||
}};
|
EXPECT_EQ ((std::is_same_v<const Seq::action_t, decltype(Seq::exit_ok)>), true);
|
||||||
EXPECT_EQ ((int)Seq::status_t::ERROR, (int)s.run(script2));
|
EXPECT_EQ ((std::is_same_v<const Seq::action_t, decltype(Seq::exit_error)>), true);
|
||||||
|
|
||||||
|
EXPECT_EQ ((std::is_same_v<const Seq::action_t, decltype(Seq::go_to<0>)>), true);
|
||||||
|
EXPECT_EQ ((std::is_same_v<const Seq::action_t, decltype(Seq::exit<0>)>), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Tsequencer, run_sequence) {
|
bool handler_flag = false;
|
||||||
|
const char* text = "abc";
|
||||||
|
|
||||||
|
//static bool check_handle (const str_view_t buffer, const str_view_t token, match_ft match, handler_ft handle)
|
||||||
|
TEST(Tsequencer, check_handle) {
|
||||||
Seq s;
|
Seq s;
|
||||||
const std::array<Seq::record_t, 8> script = {{
|
// foo (5);
|
||||||
/* 0 */{Seq::control_t::NOP, {"", Seq::match_t::NO, nullptr, Seq::action_t::GOTO, 1}, 1000},
|
// bar (5);
|
||||||
/* 1 */{Seq::control_t::SEND, {"ATE0\r\n", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000},
|
using str_t = Seq::string_view;
|
||||||
/* 2 */{Seq::control_t::EXPECT, {{
|
using val_t = Seq::value_type;
|
||||||
{"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} }},
|
auto match = [](const str_t x, const str_t y) ->bool { (void)x; (void)y; return true; };
|
||||||
1000
|
auto no_match = [](const str_t x, const str_t y) ->bool { (void)x; (void)y; return false; };
|
||||||
},
|
auto check_match = [] (const str_t x, const str_t y) ->bool {
|
||||||
/* 3 */{Seq::control_t::SEND, {"AT+CCLK?", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000},
|
return x == y;
|
||||||
/* 4 */{Seq::control_t::EXPECT, {{
|
};
|
||||||
{"OK\r\n", Seq::match_t::ENDS_WITH, Seq::my_handler, Seq::action_t::NEXT, 0},
|
auto handler = [](const val_t* v, size_t s){ (void)*v; (void)s; handler_flag = true; };
|
||||||
{"ERROR", Seq::match_t::CONTAINS, nullptr, Seq::action_t::EXIT_ERROR, 0} }},
|
auto set_if_abc = [](const val_t* v, size_t s){
|
||||||
1000
|
(void)*v; (void)s;
|
||||||
},
|
handler_flag = (str_t(v, s) == "abc");
|
||||||
/* 5 */{Seq::control_t::SEND, {"AT+CT?", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000},
|
};
|
||||||
/* 6 */{Seq::control_t::EXPECT, {{
|
|
||||||
{"OK\r\n", Seq::match_t::ENDS_WITH, nullptr, Seq::action_t::NEXT, 0},
|
EXPECT_EQ (s.check_handle("", "", nullptr, nullptr), false);
|
||||||
{"ERROR", Seq::match_t::CONTAINS, nullptr, Seq::action_t::EXIT_ERROR, 0} }},
|
EXPECT_EQ (s.check_handle("", "", no_match, nullptr), false);
|
||||||
1000
|
EXPECT_EQ (s.check_handle("", "", match, nullptr), false);
|
||||||
},
|
|
||||||
/* 7 */{Seq::control_t::SEND, {"AT+POWD=0", Seq::match_t::NO, nullptr, Seq::action_t::EXIT_OK, 0}, 1000}
|
handler_flag = false;
|
||||||
|
EXPECT_EQ (s.check_handle("", "", no_match, handler), false);
|
||||||
|
EXPECT_EQ (handler_flag, false);
|
||||||
|
|
||||||
|
handler_flag = false;
|
||||||
|
EXPECT_EQ (s.check_handle("", "", match, handler), true);
|
||||||
|
EXPECT_EQ (handler_flag, true);
|
||||||
|
|
||||||
|
handler_flag = false;
|
||||||
|
EXPECT_EQ (s.check_handle("abcd", "abc", check_match, set_if_abc), false);
|
||||||
|
EXPECT_EQ (handler_flag, false);
|
||||||
|
|
||||||
|
handler_flag = false;
|
||||||
|
EXPECT_EQ (s.check_handle("abc", "abc", check_match, set_if_abc), true);
|
||||||
|
EXPECT_EQ (handler_flag, true);
|
||||||
|
|
||||||
|
handler_flag = false;
|
||||||
|
EXPECT_EQ (s.check_handle("abc", "abcd", check_match, set_if_abc), false);
|
||||||
|
EXPECT_EQ (handler_flag, false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Tsequencer, run_nop_and_exits) {
|
||||||
|
Seq s;
|
||||||
|
const Seq::script_t<1> script1 = {{
|
||||||
|
{Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::exit_ok, 1000}
|
||||||
}};
|
}};
|
||||||
EXPECT_EQ ((int)Seq::status_t::ERROR, (int)s.run(script));
|
s.clear_clock();
|
||||||
|
EXPECT_EQ (s.run(script1), Seq::exit_ok.value);
|
||||||
|
EXPECT_GE (s.clock(), (clock_t)1000);
|
||||||
|
|
||||||
|
const Seq::script_t<1> script2 = {{
|
||||||
|
{Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::exit_error, 1000}
|
||||||
|
}};
|
||||||
|
s.clear_clock();
|
||||||
|
EXPECT_EQ (s.run(script2), Seq::exit_error.value);
|
||||||
|
EXPECT_GE (s.clock(), (clock_t)1000);
|
||||||
|
|
||||||
|
const Seq::script_t<3> script3 = {{
|
||||||
|
{Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::next, 1000},
|
||||||
|
{Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::next, 1000},
|
||||||
|
{Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::exit_ok, 1000}
|
||||||
|
}};
|
||||||
|
s.clear_clock();
|
||||||
|
EXPECT_EQ (s.run(script3), Seq::exit_ok.value);
|
||||||
|
EXPECT_GE (s.clock(), (clock_t)3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Tsequencer, run_send) {
|
||||||
|
Seq s;
|
||||||
|
|
||||||
|
auto send_wrapper = [](const data_type* d, size_t s){
|
||||||
|
(void)*d; (void)s; handler_flag = true;
|
||||||
|
};
|
||||||
|
auto send_chk_text = [](const data_type* d, size_t s){
|
||||||
|
handler_flag = (Seq::string_view(d,s) == Seq::string_view(text));
|
||||||
|
};
|
||||||
|
|
||||||
|
const Seq::script_t<2> script1 = {{
|
||||||
|
{Seq::control_t::SEND, "", Seq::nil, Seq::nil, Seq::next, 0},
|
||||||
|
{Seq::control_t::SEND, "", Seq::nil, send_wrapper, Seq::exit_ok, 0}
|
||||||
|
}};
|
||||||
|
handler_flag =false;
|
||||||
|
EXPECT_EQ (s.run(script1), Seq::exit_ok.value);
|
||||||
|
EXPECT_EQ (handler_flag, true);
|
||||||
|
|
||||||
|
|
||||||
|
const Seq::script_t<1> script2 = {{
|
||||||
|
{Seq::control_t::SEND, "abcd", Seq::nil, send_chk_text, Seq::exit_ok, 0}
|
||||||
|
}};
|
||||||
|
handler_flag =false;
|
||||||
|
EXPECT_EQ (s.run(script2), Seq::exit_ok.value);
|
||||||
|
EXPECT_EQ (handler_flag, false);
|
||||||
|
|
||||||
|
const Seq::script_t<2> script3 = {{
|
||||||
|
{Seq::control_t::SEND, text, Seq::nil, send_chk_text, Seq::exit_ok, 0}
|
||||||
|
}};
|
||||||
|
handler_flag =false;
|
||||||
|
EXPECT_EQ (s.run(script3), Seq::exit_ok.value);
|
||||||
|
EXPECT_EQ (handler_flag, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Tsequencer, run_expect) {
|
||||||
|
Seq s;
|
||||||
|
|
||||||
|
const Seq::script_t<7> script = {{
|
||||||
|
{Seq::control_t::EXPECT, "reply1", Seq::equals, Seq::nil, Seq::exit<1UL>, 1000},
|
||||||
|
{Seq::control_t::OR_EXPECT, "reply2", Seq::starts_with, Seq::nil, Seq::exit<2UL>, 0},
|
||||||
|
{Seq::control_t::OR_EXPECT, "reply3", Seq::starts_with, Seq::nil, Seq::exit<3UL>, 0},
|
||||||
|
{Seq::control_t::OR_EXPECT, "reply4\n", Seq::starts_with, Seq::nil, Seq::exit<4UL>, 0},
|
||||||
|
{Seq::control_t::OR_EXPECT, "reply5", Seq::starts_with, Seq::nil, Seq::exit<5UL>, 0},
|
||||||
|
{Seq::control_t::OR_EXPECT, "ERROR", Seq::contains, Seq::nil, Seq::exit<6UL>, 0},
|
||||||
|
{Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::exit_error, 1000}
|
||||||
|
}};
|
||||||
|
|
||||||
|
s.clear_clock();
|
||||||
|
s.put("cmd1", std::strlen("cmd1"));
|
||||||
|
EXPECT_EQ (s.run(script), 1UL);
|
||||||
|
EXPECT_LT (s.clock(), (clock_t)1000);
|
||||||
|
|
||||||
|
s.clear_clock();
|
||||||
|
s.put("cmd2\n", std::strlen("cmd2\n"));
|
||||||
|
EXPECT_EQ (s.run(script), 2UL);
|
||||||
|
EXPECT_LT (s.clock(), (clock_t)1000);
|
||||||
|
|
||||||
|
s.clear_clock();
|
||||||
|
s.put("cmd3\r\n", std::strlen("cmd3\r\n"));
|
||||||
|
EXPECT_EQ (s.run(script), 3UL);
|
||||||
|
EXPECT_LT (s.clock(), (clock_t)1000);
|
||||||
|
|
||||||
|
s.clear_clock();
|
||||||
|
s.put("cmd4\n\r", std::strlen("cmd4\n\r"));
|
||||||
|
EXPECT_EQ (s.run(script), 4UL);
|
||||||
|
EXPECT_LT (s.clock(), (clock_t)1000);
|
||||||
|
|
||||||
|
s.clear_clock();
|
||||||
|
s.put("cmd5\n", std::strlen("cmd5\n"));
|
||||||
|
EXPECT_EQ (s.run(script), 5UL);
|
||||||
|
EXPECT_LT (s.clock(), (clock_t)1000);
|
||||||
|
|
||||||
|
s.clear_clock();
|
||||||
|
s.put("cmd", std::strlen("cmd"));
|
||||||
|
EXPECT_EQ (s.run(script), 6UL);
|
||||||
|
EXPECT_LT (s.clock(), (clock_t)1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Tsequencer, run_detect) {
|
||||||
|
Seq s;
|
||||||
|
|
||||||
|
const Seq::script_t<7> script = {{
|
||||||
|
{Seq::control_t::DETECT, "reply1", Seq::equals, Seq::nil, Seq::exit<1UL>, 1000},
|
||||||
|
{Seq::control_t::OR_DETECT, "reply2", Seq::starts_with, Seq::nil, Seq::exit<2UL>, 0},
|
||||||
|
{Seq::control_t::OR_DETECT, "reply3", Seq::starts_with, Seq::nil, Seq::exit<3UL>, 0},
|
||||||
|
{Seq::control_t::OR_DETECT, "reply4\n", Seq::starts_with, Seq::nil, Seq::exit<4UL>, 0},
|
||||||
|
{Seq::control_t::OR_DETECT, "reply5", Seq::starts_with, Seq::nil, Seq::exit<5UL>, 0},
|
||||||
|
{Seq::control_t::OR_DETECT, "ERROR", Seq::contains, Seq::nil, Seq::exit<6UL>, 0},
|
||||||
|
{Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::exit_ok, 1000}
|
||||||
|
}};
|
||||||
|
|
||||||
|
s.clear_clock();
|
||||||
|
s.put("cmd1", std::strlen("cmd1"));
|
||||||
|
EXPECT_EQ (s.run(script), 1UL);
|
||||||
|
EXPECT_LT (s.clock(), (clock_t)1000);
|
||||||
|
|
||||||
|
s.clear_clock();
|
||||||
|
s.put("cmd2\n", std::strlen("cmd2\n"));
|
||||||
|
EXPECT_EQ (s.run(script), 2UL);
|
||||||
|
EXPECT_LT (s.clock(), (clock_t)1000);
|
||||||
|
|
||||||
|
s.clear_clock();
|
||||||
|
s.put("cmd3\r\n", std::strlen("cmd3\r\n"));
|
||||||
|
EXPECT_EQ (s.run(script), 3UL);
|
||||||
|
EXPECT_LT (s.clock(), (clock_t)1000);
|
||||||
|
|
||||||
|
s.clear_clock();
|
||||||
|
s.put("cmd4\n\r", std::strlen("cmd4\n\r"));
|
||||||
|
EXPECT_EQ (s.run(script), 4UL);
|
||||||
|
EXPECT_LT (s.clock(), (clock_t)1000);
|
||||||
|
|
||||||
|
s.clear_clock();
|
||||||
|
s.put("cmd5\n", std::strlen("cmd5\n"));
|
||||||
|
EXPECT_EQ (s.run(script), 5UL);
|
||||||
|
EXPECT_LT (s.clock(), (clock_t)1000);
|
||||||
|
|
||||||
|
s.clear_clock();
|
||||||
|
s.put("cmd", std::strlen("cmd"));
|
||||||
|
EXPECT_EQ (s.run(script), Seq::exit_error.value);
|
||||||
|
EXPECT_GT (s.clock(), (clock_t)1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Tsequencer, run_script_blocks_n_gotos) {
|
||||||
|
Seq s;
|
||||||
|
const Seq::script_t<15> script = {{
|
||||||
|
/* 0 */{Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::go_to<1>, 1000},
|
||||||
|
|
||||||
|
/* 1 */{Seq::control_t::SEND, "cmd1", Seq::nil, Seq::nil, Seq::next, 0},
|
||||||
|
/* 2 */{Seq::control_t::EXPECT, "reply1", Seq::starts_with, Seq::nil, Seq::next, 1000},
|
||||||
|
/* 3 */{Seq::control_t::OR_EXPECT, "ERROR", Seq::contains, Seq::nil, Seq::exit_error, 0},
|
||||||
|
|
||||||
|
/* 4 */{Seq::control_t::SEND, "cmd2\n", Seq::nil, Seq::nil, Seq::next, 0},
|
||||||
|
/* 5 */{Seq::control_t::DETECT, "ERROR", Seq::contains, Seq::nil, Seq::exit_error, 1000},
|
||||||
|
/* 6 */{Seq::control_t::OR_DETECT, "reply2", Seq::contains, Seq::nil, Seq::go_to<11>, 0},
|
||||||
|
|
||||||
|
/* 7 */{Seq::control_t::SEND, "cmd3\r\n", Seq::nil, Seq::nil, Seq::next, 0},
|
||||||
|
/* 8 */{Seq::control_t::EXPECT, "ERROR", Seq::contains, Seq::nil, Seq::exit_error, 1000},
|
||||||
|
/* 9 */{Seq::control_t::OR_EXPECT, "lalala", Seq::starts_with, Seq::nil, Seq::exit_error, 0},
|
||||||
|
/*10 */{Seq::control_t::OR_EXPECT, "text\n", Seq::ends_with, Seq::nil, Seq::go_to<14>, 0},
|
||||||
|
|
||||||
|
/*11 */{Seq::control_t::SEND, "cmd4\n\r", Seq::nil, Seq::nil, Seq::next, 0},
|
||||||
|
/*12 */{Seq::control_t::EXPECT, "reply4\n", Seq::starts_with, Seq::nil, Seq::go_to<7>, 1000},
|
||||||
|
/*13 */{Seq::control_t::OR_EXPECT, "ERROR", Seq::contains, Seq::nil, Seq::exit_error, 0},
|
||||||
|
|
||||||
|
/*14 */{Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::exit_ok, 1000}
|
||||||
|
}};
|
||||||
|
s.clear_clock();
|
||||||
|
EXPECT_EQ (s.run(script), Seq::exit_ok.value);
|
||||||
|
EXPECT_GT (s.clock(), (clock_t)2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Tsequencer, run_match_n_handler) {
|
||||||
|
Seq s;
|
||||||
|
|
||||||
|
using str_t = Seq::string_view;
|
||||||
|
using val_t = Seq::value_type;
|
||||||
|
|
||||||
|
auto match = [](const str_t x, const str_t y) ->bool { (void)x; (void)y; return true; };
|
||||||
|
auto check_match = [] (const str_t x, const str_t y) ->bool {
|
||||||
|
return x == y;
|
||||||
|
};
|
||||||
|
auto handler = [](const val_t* v, size_t s){ (void)*v; (void)s; handler_flag = true; };
|
||||||
|
auto set_if_rpl2 = [](const val_t* v, size_t s){
|
||||||
|
(void)*v; (void)s;
|
||||||
|
handler_flag = (str_t(v, s) == "reply2\n");
|
||||||
|
};
|
||||||
|
|
||||||
|
const Seq::script_t<4> script1 = {{
|
||||||
|
{Seq::control_t::SEND, "cmd1", Seq::nil, Seq::nil, Seq::next, 0},
|
||||||
|
{Seq::control_t::EXPECT, "", match, Seq::nil, Seq::next, 1000},
|
||||||
|
{Seq::control_t::OR_EXPECT, "ERROR", Seq::contains, Seq::nil, Seq::exit_error, 0},
|
||||||
|
|
||||||
|
{Seq::control_t::SEND, "cmd1", Seq::nil, handler, Seq::exit_ok, 0}
|
||||||
|
}};
|
||||||
|
handler_flag = false;
|
||||||
|
s.clear_clock();
|
||||||
|
EXPECT_EQ (s.run(script1), Seq::exit_ok.value);
|
||||||
|
EXPECT_LT (s.clock(), (clock_t)1000);
|
||||||
|
EXPECT_EQ (handler_flag, true);
|
||||||
|
|
||||||
|
const Seq::script_t<2> script2 = {{
|
||||||
|
{Seq::control_t::SEND, "cmd1", Seq::nil, Seq::nil, Seq::next, 0},
|
||||||
|
{Seq::control_t::EXPECT, "reply1", check_match, set_if_rpl2, Seq::exit_ok, 1000},
|
||||||
|
}};
|
||||||
|
handler_flag = false;
|
||||||
|
EXPECT_EQ (s.run(script2), Seq::exit_ok.value);
|
||||||
|
EXPECT_EQ (handler_flag, false);
|
||||||
|
|
||||||
|
const Seq::script_t<2> script3 = {{
|
||||||
|
{Seq::control_t::SEND, "cmd2\n", Seq::nil, Seq::nil, Seq::next, 0},
|
||||||
|
{Seq::control_t::EXPECT, "reply2\n", check_match, set_if_rpl2, Seq::exit_ok, 1000},
|
||||||
|
}};
|
||||||
|
handler_flag = false;
|
||||||
|
EXPECT_EQ (s.run(script3), Seq::exit_ok.value);
|
||||||
|
EXPECT_EQ (handler_flag, true);
|
||||||
|
|
||||||
|
const Seq::script_t<1> script4 = {{
|
||||||
|
{Seq::control_t::SEND, "cmd1", Seq::nil, handler, Seq::exit_ok, 0}
|
||||||
|
}};
|
||||||
|
handler_flag = false;
|
||||||
|
EXPECT_EQ (s.run(script4), Seq::exit_ok.value);
|
||||||
|
EXPECT_EQ (handler_flag, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Tsequencer, run_boundaries) {
|
||||||
|
Seq s;
|
||||||
|
|
||||||
|
const Seq::script_t<1> script1 = {{
|
||||||
|
{Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::next, 0},
|
||||||
|
}};
|
||||||
|
EXPECT_EQ (s.run(script1), Seq::exit_error.value);
|
||||||
|
|
||||||
|
const Seq::script_t<1> script2 = {{
|
||||||
|
{Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::go_to<1>, 0},
|
||||||
|
}};
|
||||||
|
EXPECT_EQ (s.run(script2), Seq::exit_error.value);
|
||||||
|
|
||||||
|
const Seq::script_t<1> script3 = {{
|
||||||
|
{Seq::control_t::NOP, "", Seq::nil, Seq::nil, Seq::go_to<(size_t)-1>, 0},
|
||||||
|
}};
|
||||||
|
EXPECT_EQ (s.run(script3), Seq::exit_error.value);
|
||||||
|
|
||||||
|
const Seq::script_t<1> script4 = {{
|
||||||
|
{Seq::control_t::EXPECT, "abc", Seq::nil, Seq::nil, Seq::next, 1000},
|
||||||
|
}};
|
||||||
|
s.clear_clock();
|
||||||
|
EXPECT_EQ (s.run(script4), Seq::exit_error.value);
|
||||||
|
EXPECT_GT (s.clock(), (clock_t)1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,274 @@
|
|||||||
|
/*!
|
||||||
|
* \file span.cpp
|
||||||
|
* \brief
|
||||||
|
* Unit tests for span
|
||||||
|
*
|
||||||
|
* \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 <cont/span.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <cont/deque.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
// tests from https://github.com/tcbrindle/span/blob/master/test/test_span.cpp
|
||||||
|
|
||||||
|
namespace test_span {
|
||||||
|
using namespace tbx;
|
||||||
|
|
||||||
|
TEST (Tspan, default_constructors) {
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_default_constructible<span<int>>::value));
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_default_constructible<span<int, 0>>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_nothrow_default_constructible<span<int, 42>>::value));
|
||||||
|
|
||||||
|
constexpr span<int> s1{};
|
||||||
|
|
||||||
|
EXPECT_EQ (0UL, s1.size());
|
||||||
|
EXPECT_EQ (nullptr, s1.data());
|
||||||
|
EXPECT_EQ (s1.begin(), s1.end());
|
||||||
|
|
||||||
|
constexpr span<int, 0> s2{};
|
||||||
|
|
||||||
|
EXPECT_EQ (0UL, s2.size());
|
||||||
|
EXPECT_EQ (nullptr, s2.data());
|
||||||
|
EXPECT_EQ (s2.begin(), s2.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST (Tspan, pointer_constructors) {
|
||||||
|
// pointer length
|
||||||
|
EXPECT_EQ (true, (std::is_constructible<span<int>, int*, int>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_constructible<span<const int>, int*, int>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_constructible<span<const int>, const int*, int>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_constructible<span<int, 42>, int*, int>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_constructible<span<const int, 42>, int*, int>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_constructible<span<const int, 42>, const int*, int>::value));
|
||||||
|
|
||||||
|
int arr[] = {1, 2, 3};
|
||||||
|
|
||||||
|
// dynamic size
|
||||||
|
span<int> s1(arr, 3);
|
||||||
|
EXPECT_EQ (3UL, s1.size());
|
||||||
|
EXPECT_EQ (arr, s1.data());
|
||||||
|
EXPECT_EQ (std::begin(arr), s1.begin());
|
||||||
|
EXPECT_EQ (std::end(arr), s1.end());
|
||||||
|
|
||||||
|
// fixed size
|
||||||
|
span<int, 3> s2(arr, 3);
|
||||||
|
EXPECT_EQ (3UL, s2.size());
|
||||||
|
EXPECT_EQ (arr, s2.data());
|
||||||
|
EXPECT_EQ (std::begin(arr), s2.begin());
|
||||||
|
EXPECT_EQ (std::end(arr), s2.end());
|
||||||
|
|
||||||
|
// pointer pointer
|
||||||
|
EXPECT_EQ (true, (std::is_constructible<span<int>, int*, int*>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_constructible<span<float>, float*, float*>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_constructible<span<int, 42>, int*, int*>::value));
|
||||||
|
EXPECT_EQ (true, (std::is_constructible<span<float, 42>, float*, float*>::value));
|
||||||
|
|
||||||
|
// dynamic size
|
||||||
|
span<int> s3(arr, arr + 3);
|
||||||
|
EXPECT_EQ (3UL, s3.size());
|
||||||
|
EXPECT_EQ (arr, s3.data());
|
||||||
|
EXPECT_EQ (std::begin(arr), s3.begin());
|
||||||
|
EXPECT_EQ (std::end(arr), s3.end());
|
||||||
|
|
||||||
|
// fixed size
|
||||||
|
span<int, 3> s4(arr, arr + 3);
|
||||||
|
EXPECT_EQ (3UL, s4.size());
|
||||||
|
EXPECT_EQ (arr, s4.data());
|
||||||
|
EXPECT_EQ (std::begin(arr), s4.begin());
|
||||||
|
EXPECT_EQ (std::end(arr), s4.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST (Tspan, C_array_constructors) {
|
||||||
|
using int_array_t = int[3];
|
||||||
|
using float_array_t = float[3];
|
||||||
|
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_constructible<span<int>, int_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int>, int_array_t const&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int>, float_array_t>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_constructible<span<const int>, int_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_constructible<span<const int>, int_array_t const&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<const int>, float_array_t>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_constructible<span<int, 3>, int_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int, 3>, int_array_t const&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int, 3>, float_array_t&>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_constructible<span<const int, 3>, int_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_constructible<span<const int, 3>, int_array_t const&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<const int, 3>, float_array_t>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int, 42>, int_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int, 42>, int_array_t const&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int, 42>, float_array_t&>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<const int, 42>, int_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<const int, 42>, int_array_t const&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<const int, 42>, float_array_t&>::value));
|
||||||
|
|
||||||
|
int arr[] = {1, 2, 3};
|
||||||
|
|
||||||
|
// non-const dynamic size
|
||||||
|
span<int> s1{arr};
|
||||||
|
EXPECT_EQ (s1.size(), 3UL);
|
||||||
|
EXPECT_EQ (s1.data(), arr);
|
||||||
|
EXPECT_EQ (s1.begin(), std::begin(arr));
|
||||||
|
EXPECT_EQ (s1.end(), std::end(arr));
|
||||||
|
|
||||||
|
// non-const dynamic size
|
||||||
|
span<const int> s2{arr};
|
||||||
|
EXPECT_EQ (s2.size(), 3UL);
|
||||||
|
EXPECT_EQ (s2.data(), arr);
|
||||||
|
EXPECT_EQ (s2.begin(), std::begin(arr));
|
||||||
|
EXPECT_EQ (s2.end(), std::end(arr));
|
||||||
|
|
||||||
|
// non-const fixed size
|
||||||
|
span<int, 3> s3{arr};
|
||||||
|
EXPECT_EQ (s3.size(), 3UL);
|
||||||
|
EXPECT_EQ (s3.data(), arr);
|
||||||
|
EXPECT_EQ (s3.begin(), std::begin(arr));
|
||||||
|
EXPECT_EQ (s3.end(), std::end(arr));
|
||||||
|
|
||||||
|
// non-const fixed size
|
||||||
|
span<const int, 3> s4{arr};
|
||||||
|
EXPECT_EQ (s4.size(), 3UL);
|
||||||
|
EXPECT_EQ (s4.data(), arr);
|
||||||
|
EXPECT_EQ (s4.begin(), std::begin(arr));
|
||||||
|
EXPECT_EQ (s4.end(), std::end(arr));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST (Tspan, array_constructors) {
|
||||||
|
using int_array_t = std::array<int, 3>;
|
||||||
|
using float_array_t = std::array<float, 3>;
|
||||||
|
using zero_array_t = std::array<int, 0>;
|
||||||
|
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_constructible<span<int>, int_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int>, int_array_t const&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int>, float_array_t>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_constructible<span<const int>, int_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_constructible<span<const int>, int_array_t const&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<const int>, float_array_t const&>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_constructible<span<int, 3>, int_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int, 3>, int_array_t const&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int, 3>, float_array_t>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_constructible<span<const int, 3>, int_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, ( std::is_nothrow_constructible<span<const int, 3>, int_array_t const&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<const int, 3>, float_array_t const&>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int, 42>, int_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int, 42>, int_array_t const&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int, 42>, float_array_t const&>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<const int, 42>, int_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<const int, 42>, int_array_t const&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<const int, 42>, float_array_t&>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, ( std::is_constructible<span<int>, zero_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int>, const zero_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, ( std::is_constructible<span<const int>, zero_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, ( std::is_constructible<span<const int>, const zero_array_t&>::value));
|
||||||
|
|
||||||
|
EXPECT_EQ (true, ( std::is_constructible<span<int, 0>, zero_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, (!std::is_constructible<span<int, 0>, const zero_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, ( std::is_constructible<span<const int, 0>, zero_array_t&>::value));
|
||||||
|
EXPECT_EQ (true, ( std::is_constructible<span<const int, 0>, const zero_array_t&>::value));
|
||||||
|
|
||||||
|
|
||||||
|
int_array_t arr = {1, 2, 3};
|
||||||
|
|
||||||
|
// non-const, dynamic size
|
||||||
|
span<int> s1{arr};
|
||||||
|
EXPECT_EQ(s1.size(), 3UL);
|
||||||
|
EXPECT_EQ(s1.data(), arr.data());
|
||||||
|
EXPECT_EQ(s1.begin(), arr.data());
|
||||||
|
EXPECT_EQ(s1.end(), arr.data() + 3);
|
||||||
|
|
||||||
|
|
||||||
|
//const, dynamic size
|
||||||
|
span<int const> s2{arr};
|
||||||
|
EXPECT_EQ(s2.size(), 3UL);
|
||||||
|
EXPECT_EQ(s2.data(), arr.data());
|
||||||
|
EXPECT_EQ(s2.begin(), arr.data());
|
||||||
|
EXPECT_EQ(s2.end(), arr.data() + 3);
|
||||||
|
|
||||||
|
|
||||||
|
// non-const, static size
|
||||||
|
span<int, 3> s3{arr};
|
||||||
|
EXPECT_EQ(s3.size(), 3UL);
|
||||||
|
EXPECT_EQ(s3.data(), arr.data());
|
||||||
|
EXPECT_EQ(s3.begin(), arr.data());
|
||||||
|
EXPECT_EQ(s3.end(), arr.data() + 3);
|
||||||
|
|
||||||
|
// const, dynamic size
|
||||||
|
span<int const, 3> s4{arr};
|
||||||
|
EXPECT_EQ(s4.size(), 3UL);
|
||||||
|
EXPECT_EQ(s4.data(), arr.data());
|
||||||
|
EXPECT_EQ(s4.begin(), arr.data());
|
||||||
|
EXPECT_EQ(s4.end(), arr.data() + 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST (Tspan, containter_constructors) {
|
||||||
|
// using container_t = tbx::deque<int, 3>;
|
||||||
|
//
|
||||||
|
// EXPECT_EQ (true, ( std::is_constructible<span<int>, container_t&>::value));
|
||||||
|
// EXPECT_EQ (true, (!std::is_constructible<span<int>, const container_t&>::value));
|
||||||
|
//
|
||||||
|
// EXPECT_EQ (true, ( std::is_constructible<span<const int>, container_t&>::value));
|
||||||
|
// EXPECT_EQ (true, ( std::is_constructible<span<const int>, const container_t&>::value));
|
||||||
|
//
|
||||||
|
// EXPECT_EQ (true, (!std::is_constructible<span<int, 3>, container_t&>::value));
|
||||||
|
// EXPECT_EQ (true, (!std::is_constructible<span<int, 3>, const container_t&>::value));
|
||||||
|
//
|
||||||
|
// EXPECT_EQ (true, (!std::is_constructible<span<const int, 3>, container_t&>::value));
|
||||||
|
// EXPECT_EQ (true, (!std::is_constructible<span<const int, 3>, const container_t&>::value));
|
||||||
|
//
|
||||||
|
// container_t cont = {1, 2, 3};
|
||||||
|
// const container_t ccont = {1, 2, 3};
|
||||||
|
//
|
||||||
|
// // non-const, dynamic size
|
||||||
|
// span<int> s1(cont);
|
||||||
|
// EXPECT_EQ(s1.size(), 3UL);
|
||||||
|
// EXPECT_EQ(s1.data(), cont.data());
|
||||||
|
// EXPECT_EQ(s1.begin(), cont.data());
|
||||||
|
// EXPECT_EQ(s1.end(), cont.data() + 3);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// //const, dynamic size
|
||||||
|
// span<int const> s2(cont);
|
||||||
|
// EXPECT_EQ(s2.size(), 3UL);
|
||||||
|
// EXPECT_EQ(s2.data(), cont.data());
|
||||||
|
// EXPECT_EQ(s2.begin(), cont.data());
|
||||||
|
// EXPECT_EQ(s2.end(), cont.data() + 3);
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user