A C++ toolbox repo until the pair uTL/dTL arives
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

420 行
17 KiB

  1. /*!
  2. * \file utils/sequencer.h
  3. * \brief
  4. * A terminal-like device communication automation tool
  5. *
  6. * \copyright Copyright (C) 2021 Christos Choutouridis <christos@choutouridis.net>
  7. *
  8. * <dl class=\"section copyright\"><dt>License</dt><dd>
  9. * The MIT License (MIT)
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining a copy
  12. * of this software and associated documentation files (the "Software"), to deal
  13. * in the Software without restriction, including without limitation the rights
  14. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  15. * copies of the Software, and to permit persons to whom the Software is
  16. * furnished to do so, subject to the following conditions:
  17. *
  18. * The above copyright notice and this permission notice shall be included in all
  19. * copies or substantial portions of the Software.
  20. *
  21. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  26. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  27. * SOFTWARE.
  28. * </dd></dl>
  29. */
  30. #ifndef TBX_UTILS_SEQUENCER_H_
  31. #define TBX_UTILS_SEQUENCER_H_
  32. #include <core/core.h>
  33. #include <core/crtp.h>
  34. #include <cont/range.h>
  35. #include <ctime>
  36. #include <array>
  37. #include <string_view>
  38. #include <limits>
  39. #include <type_traits>
  40. #include <utility>
  41. #include <tuple>
  42. namespace tbx {
  43. /*!
  44. * \class sequencer
  45. * \brief
  46. * A CRTP base class to provide the sequencer functionality.
  47. *
  48. * Sequencer can automate communication with a terminal-like device such as AT-command modems etc...
  49. * It can operate based on a script array and handle the outgoing commands and incoming responses.
  50. * The user can create matching rules on received data and hook handlers and actions on them.
  51. *
  52. * The derived class (implementation) has to provide:
  53. * 1) size_t get(Data_t* data);
  54. * This function return 0 or a number of Data_t items. The data points to buffer for the input data.
  55. * 2) size_t put(const Data_t* data, size_t n);
  56. * This function sends to implementation the data pointed by \c data witch have size \c n.
  57. * 3) clock_t clock();
  58. * This function return a number to be used as time. The units of this function may be arbitrary but they
  59. * match the units in \c record_t::timeout field.
  60. *
  61. * \tparam Impl_t The type of derived class
  62. * \tparam Cont_t The container type holding the data of type \c Data_t for the derived class.
  63. * \tparam Data_t The char-like stream item type. Usually \c char
  64. * \tparam N The size of the sequence buffer to temporary store each line from get().
  65. *
  66. * \note
  67. * We need access to derived class container to sneaky get a range of the data beside
  68. * the normal data flow, in order to implement the \see control_t::DETECT operation.
  69. */
  70. template <typename Impl_t, typename Cont_t, typename Data_t, size_t N>
  71. class sequencer {
  72. _CRTP_IMPL(Impl_t);
  73. static_assert(
  74. std::is_same_v<typename Cont_t::value_type, Data_t>,
  75. "Cont_t must be a container of type Data_t"
  76. );
  77. // local type dispatch
  78. using range_t = typename Cont_t::range_t;
  79. //! \name Public types
  80. //! @{
  81. public:
  82. using str_view_t = std::basic_string_view<Data_t>;
  83. //! \enum control_t
  84. //! \brief The control type of the script entry.
  85. enum class control_t {
  86. NOP, //!< No command, dont send or expect anything, used for delays
  87. SEND, //!< Send data to implementation through put()
  88. EXPECT, //!< Expects data from implementation via get()
  89. OR_EXPECT, //!< Expects data from implementation via get() in conjunction with previous EXPECT
  90. DETECT, //!< Detects data into rx buffer without receiving them via contents()
  91. OR_DETECT //!< Detects data into rx buffer without receiving them via contents() in conjunction with
  92. //!< previous DETECT
  93. //! \note
  94. //! The \c DETECT extra incoming channel serve the purpose of sneak into receive
  95. //! buffer and check for data without getting them. This is useful when the receive driver
  96. //! is buffered with a delimiter and we seek for data that don't follow the delimiter pattern.
  97. //!
  98. //! For example:
  99. //! A modem sends reponses with '\n' termination but for some "special" command it opens a cursor
  100. //! lets say ">$ " without '\n' at the end.
  101. };
  102. //! \enum action_t
  103. //! \brief Possible response actions for the sequencer
  104. struct action_t {
  105. enum {
  106. NO =0, NEXT, GOTO, EXIT_OK, EXIT_ERROR
  107. } type;
  108. size_t step;
  109. };
  110. //! \enum match_t
  111. //! \brief Token match types
  112. enum class match_t {
  113. NO, STARTS_WITH, ENDS_WITH, CONTAINS, nSTARTS_WITH, nENDS_WITH, nCONTAINS
  114. };
  115. /*!
  116. * Match handler function pointer type.
  117. * Expects a pointer to buffer and a size and returns status
  118. */
  119. using handler_ft = void (*) (const Data_t*, size_t);
  120. /*!
  121. * \struct record_t
  122. * \brief
  123. * Describes the sequencer's script record entry (line).
  124. *
  125. * Each line consist from a control, 2 blocks and a timeout. The control says if we send or receive data.
  126. * The blocks contain the data and the matching information. And the timeout guards the entire line.
  127. */
  128. struct record_t {
  129. control_t control; //!< The type of the entry
  130. str_view_t token;
  131. match_t match;
  132. handler_ft handler; //!< The handler to called if the match is successful.
  133. action_t action;
  134. clock_t timeout; //!< Timeout in CPU time
  135. };
  136. /*!
  137. * \struct script_t
  138. * \brief
  139. * Describes the sequencer's script.
  140. *
  141. * The user can create arrays as the example bellow to act as a script.
  142. * \code
  143. * const std::array<Seq::record_t, 8> script = {{
  144. * / * 0 * / {Seq::control_t::NOP, {"", Seq::match_t::NO, nullptr, Seq::action_t::GOTO, 1}, 1000}, //delay 1000 clocks
  145. * / * 1 * / {Seq::control_t::SEND, {"ATE0\r\n", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000},
  146. * / * 2 * / {Seq::control_t::EXPECT, {{
  147. * {"OK\r\n", Seq::match_t::ENDS_WITH, nullptr, Seq::action_t::NEXT, 0},
  148. * {"ERROR", Seq::match_t::CONTAINS, nullptr, Seq::action_t::EXIT_ERROR, 0} }},
  149. * 1000
  150. * },
  151. * // ...
  152. * }};
  153. * \endcode
  154. */
  155. template <size_t Nrecords>
  156. using script_t = std::array<record_t, Nrecords>;
  157. enum class seq_status_t {
  158. CONTINUE, EXIT_OK, EXIT_ERROR
  159. };
  160. //! @}
  161. //! \name Constructor / Destructor
  162. //!@{
  163. protected:
  164. ~sequencer () = default; //!< \brief Allow destructor from derived only
  165. sequencer () = default; //!< \brief A default constructor from derived only
  166. sequencer(const sequencer&) = delete; //!< No copies
  167. sequencer& operator= (const sequencer&) = delete; //!< No copy assignments
  168. //!@}
  169. //! \name Sequencer interface requirements for implementer
  170. //! @{
  171. private:
  172. size_t get_ (Data_t* data) { return impl().get (data); }
  173. size_t put_ (const Data_t* data, size_t n) { return impl().put (data, n); }
  174. const range_t contents_ () const { return impl().contents(); }
  175. clock_t clock_ () { return impl().clock(); }
  176. //! @}
  177. //! \name Private functionality
  178. //! @{
  179. private:
  180. /*!
  181. * \brief
  182. * Check if the \c stream starts with the \c prefix
  183. * \param stream The stream in witch we search
  184. * \param prefix What we search
  185. * \return True on success, false otherwise
  186. */
  187. static bool starts_with_ (const str_view_t stream, const str_view_t prefix) {
  188. return (stream.rfind(prefix, 0) != str_view_t::npos);
  189. }
  190. /*!
  191. * \brief
  192. * Check if the \c stream ends with the \c postfix
  193. * \param stream The stream in witch we search
  194. * \param postfix What we search
  195. * \return True on success, false otherwise
  196. */
  197. static bool ends_with_ (const str_view_t stream, const str_view_t postfix) {
  198. if (stream.size() < postfix.size())
  199. return false;
  200. return (
  201. stream.compare(
  202. stream.size() - postfix.size(),
  203. postfix.size(),
  204. postfix) == 0
  205. );
  206. }
  207. /*!
  208. * \brief
  209. * Check if the \c haystack contains the \c needle
  210. * \param haystack The stream in witch we search
  211. * \param needle What we search
  212. * \return True on success, false otherwise
  213. */
  214. static bool contains_ (const str_view_t haystack, const str_view_t needle) {
  215. return (haystack.find(needle) != str_view_t::npos);
  216. }
  217. bool is_step_extend (control_t control) {
  218. return (control == control_t::OR_EXPECT || control == control_t::OR_DETECT);
  219. }
  220. static bool handle_ (handler_ft handler, const str_view_t buffer = str_view_t{}) {
  221. if (handler) {
  222. handler (buffer.begin(), buffer.size());
  223. return true;
  224. }
  225. return false;
  226. }
  227. /*!
  228. * \brief
  229. * Return the new sequencer's step value.
  230. *
  231. * Step is index to the sequencer's script array.
  232. *
  233. * \param current_idx The current step value
  234. * \param action The advancing type
  235. * \param go_idx The new value of the step in the case of GOTO type
  236. * \return The new sequencer's step value
  237. */
  238. static std::pair<size_t, seq_status_t> action_ (const action_t& action, size_t step) {
  239. switch (action.type) {
  240. default:
  241. case action_t::NO: return std::make_pair(step, seq_status_t::CONTINUE);
  242. case action_t::NEXT: return std::make_pair(++step, seq_status_t::CONTINUE);
  243. case action_t::GOTO: return std::make_pair(action.step, seq_status_t::CONTINUE);
  244. case action_t::EXIT_OK:
  245. return std::make_pair(std::numeric_limits<size_t>::max(), seq_status_t::EXIT_OK);
  246. case action_t::EXIT_ERROR:
  247. return std::make_pair(std::numeric_limits<size_t>::max(), seq_status_t::EXIT_ERROR);
  248. }
  249. }
  250. //! @}
  251. public:
  252. /*!
  253. * \brief
  254. * Checks if the \c needle matches the \c haystack.
  255. *
  256. * \param type The type of matching functionality
  257. * \param haystack The stream in witch we search
  258. * \param needle The stream we search
  259. * \return True on match
  260. */
  261. static bool match (const str_view_t haystack, const str_view_t needle, match_t type) {
  262. switch (type) {
  263. default:
  264. case match_t::NO: return true;
  265. case match_t::STARTS_WITH: return starts_with_(haystack, needle);
  266. case match_t::ENDS_WITH: return ends_with_(haystack, needle);
  267. case match_t::CONTAINS: return contains_(haystack, needle);
  268. case match_t::nSTARTS_WITH: return !starts_with_(haystack, needle);
  269. case match_t::nENDS_WITH: return !ends_with_(haystack, needle);
  270. case match_t::nCONTAINS: return !contains_(haystack, needle);
  271. }
  272. }
  273. /*!
  274. * \brief
  275. * A static functionality to provide access to sequencer's inner matching mechanism.
  276. * Checks the \c buffer against \c handle and calls its action if needed.
  277. *
  278. * \param handle Reference to handle
  279. * \param buffer The buffer to check
  280. * \return True on match, false otherwise
  281. */
  282. static bool check_handle (const str_view_t token, match_t match_type, handler_ft handle, const str_view_t buffer) {
  283. if (match(buffer, token, match_type)) {
  284. handle_ (handle, buffer);
  285. return true;
  286. }
  287. return false;
  288. }
  289. /*!
  290. * \brief
  291. * Run the script array
  292. *
  293. * The main sequencer functionality. It starts with the first entry of the array.
  294. * - If the entry is \c NOP it executes the action after the timeout.
  295. * \c token and \c handler are discarded.
  296. * - If the entry is \c SEND it uses the first handle block's token to send and executes the action after that.
  297. * \c timeout is discarded.
  298. * - If the entry is \c EXCEPTS it continuously try to receive data using implementation's get until one
  299. * of the handle blocks match.
  300. * On match:
  301. * - Calls the handler if there is one
  302. * - Executes the action
  303. * - Skips the next handle blocks if there is any.
  304. * If there is no match on timeout it return status_t::EXIT_ERROR
  305. * - If the entry is \c DETECT it continuously try to detect data using implementation's contents until one
  306. * of the handle blocks match.
  307. * On match:
  308. * - Calls the handler if there is one
  309. * - Executes the action
  310. * - Skips the next handle blocks if there is any.
  311. * If there is no match on timeout it return status_t::EXIT_ERROR
  312. *
  313. * \tparam Steps The number of steps of the script
  314. * \tparam Nhandles The number of handle blocks in the each script record.
  315. *
  316. * \param script Reference to script to run
  317. * \return The status of entire operation as described above
  318. */
  319. template <size_t Steps>
  320. bool run (const script_t<Steps>& script) {
  321. Data_t buffer[N];
  322. size_t resp_size;
  323. seq_status_t status{seq_status_t::CONTINUE};
  324. size_t step =0, p_step =0;
  325. clock_t mark = clock_();
  326. do {
  327. if (step != p_step) {
  328. p_step = step;
  329. mark = clock_();
  330. }
  331. switch (script[step].control) {
  332. default:
  333. case control_t::NOP:
  334. if ((clock_() - mark) >= script[step].timeout)
  335. std::tie(step, status) = action_ (script[step].action, step);
  336. break;
  337. case control_t::SEND:
  338. if (put_(script[step].token.data(), script[step].token.size()) != script[step].token.size())
  339. return false;
  340. std::tie(step, status) = action_ (script[step].action, step);
  341. break;
  342. case control_t::EXPECT:
  343. case control_t::OR_EXPECT:
  344. resp_size = get_(buffer);
  345. if (resp_size) {
  346. for (size_t s = step ; script[s].control == control_t::OR_EXPECT; ++s) {
  347. if (match({buffer, resp_size}, script[s].token, script[s].match)) {
  348. handle_ (script[s].handler, {buffer, resp_size});
  349. std::tie(step, status) = action_ (script[s].action, s);
  350. break;
  351. }
  352. }
  353. }
  354. if (script[step].timeout && (clock_() - mark) >= script[step].timeout)
  355. return false;
  356. break;
  357. case control_t::DETECT:
  358. case control_t::OR_DETECT:
  359. auto data = contents_();
  360. if (data.begin() != data.end()) {
  361. for (size_t s = step ; script[s].control == control_t::OR_DETECT ; ++s) {
  362. if (match({buffer, resp_size}, script[s].token, script[s].match)) {
  363. handle_ (script[s].handler, {buffer, resp_size});
  364. std::tie(step, status) = action_ (script[s].action, s);
  365. break;
  366. }
  367. }
  368. }
  369. if (script[step].timeout && (clock_() - mark) >= script[step].timeout)
  370. return false;
  371. break;
  372. } // switch (it.control)
  373. } while ( status == seq_status_t::CONTINUE);
  374. return (status == seq_status_t::EXIT_OK);
  375. }
  376. };
  377. }
  378. #endif /* TBX_UTILS_SEQUENCER_H_ */