Micro template library A library for building device drivers
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

474 lignes
19 KiB

  1. /*!
  2. * \file dev/cli_device.h
  3. * \brief
  4. * command line device driver functionality as CRTP base class
  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 utl_dev_cli_device_h__
  31. #define utl_dev_cli_device_h__
  32. #include <utl/core/impl.h>
  33. #include <utl/core/crtp.h>
  34. #include <utl/container/equeue.h>
  35. #include <utl/dev/sequencer.h>
  36. #include <utl/meta/meta.h>
  37. #include <cstring>
  38. #include <cstdlib>
  39. #include <algorithm>
  40. #include <utility>
  41. #include <atomic>
  42. namespace utl {
  43. /*!
  44. * \class cli_device
  45. * \brief
  46. * Its a base class for command-line based devices
  47. *
  48. * Inherits the sequencer functionality and provides a command interface for sending
  49. * commands and parse the response.
  50. *
  51. * \example implementation example
  52. * \code
  53. * class BG95 : public cli_device<BG95, 256> {
  54. * using base_type = cli_device<BG95, 256>;
  55. * using Queue = equeue<typename base_type::value_type, 256, true>;
  56. * Queue RxQ{};
  57. * std::atomic<size_t> lines{};
  58. * public:
  59. * // cli_device driver requirements
  60. * BG95() noexcept :
  61. * RxQ(Queue::data_match::MATCH_PUSH, base_type::delimiter, [&](){
  62. * lines.fetch_add(1, std::memory_order_acq_rel);
  63. * }), lines(0) { }
  64. * void feed(char x) { RxQ << x; } // To be used inside ISR
  65. * size_t get(char* data, bool wait =false) {
  66. * do {
  67. * if (lines.load(std::memory_order_acquire)) {
  68. * size_t n =0;
  69. * do{
  70. * *data << RxQ;
  71. * ++n;
  72. * } while (*data++ != base_type::delimiter);
  73. * lines.fetch_sub(1, std::memory_order_acq_rel);
  74. * return n;
  75. * }
  76. * } while (wait);
  77. * return 0;
  78. * }
  79. * size_t contents(char* data) {
  80. * char* nullpos = std::copy(RxQ.begin(), RxQ.end(), data);
  81. * *nullpos =0;
  82. * return nullpos - data;
  83. * }
  84. * size_t put (const char* data, size_t n) {
  85. * // send data to BG95
  86. * return n;
  87. * }
  88. * clock_t clock() noexcept { //return CPU time }
  89. * };
  90. * \endcode
  91. *
  92. * \tparam Impl_t The type of derived class
  93. * \tparam N The size of the queue buffer for the receive/command interface
  94. * \tparam Delimiter The incoming data delimiter [default line buffered -- Delimiter = '\n']
  95. */
  96. template<typename Impl_t, size_t N, char Delimiter ='\n'>
  97. class cli_device
  98. : public sequencer<cli_device<Impl_t, N, Delimiter>, char, N>{
  99. _CRTP_IMPL(Impl_t);
  100. // local type dispatch
  101. using base_type = sequencer<cli_device, char, N>;
  102. //! \name Public types
  103. //! @{
  104. public:
  105. using value_type = char;
  106. using pointer_type = char*;
  107. using size_type = size_t;
  108. using string_view = typename base_type::string_view;
  109. using action_t = typename base_type::action_t;
  110. using control_t = typename base_type::control_t;
  111. using match_ft = typename base_type::match_ft;
  112. using handler_ft = typename base_type::handler_ft;
  113. template<size_t Nm>
  114. using script_t = typename base_type::template script_t<Nm>;
  115. //! Publish delimiter
  116. constexpr static char delimiter = Delimiter;
  117. enum flush_type { keep =0, flush };
  118. //! Required types for inetd async handler operation
  119. //! @{
  120. /*!
  121. * inetd handler structure for asynchronous incoming data dispatching
  122. */
  123. struct inetd_handler_t {
  124. string_view token; //!< The token we match against
  125. match_ft match; //!< The predicate we use to match
  126. handler_ft handler; //!< The handler to call on match
  127. };
  128. //! Alias template for the async handler array
  129. template <size_t Nm>
  130. using inetd_handlers = std::array<inetd_handler_t, Nm>;
  131. //! @}
  132. //! @}
  133. //! \name object lifetime
  134. //!@{
  135. protected:
  136. //!< \brief A default constructor from derived only
  137. cli_device() noexcept = default;
  138. ~cli_device () = default; //!< \brief Allow destructor from derived only
  139. cli_device(const cli_device&) = delete; //!< No copies
  140. cli_device& operator= (const cli_device&) = delete; //!< No copy assignments
  141. //!@}
  142. //! \name Sequencer interface requirements
  143. //! Forwarded to implementer the calls and cascade the the incoming channel
  144. //! @{
  145. friend base_type;
  146. private:
  147. size_t get_ (char* data) {
  148. return impl().get (data);
  149. }
  150. size_t get (char* data) {
  151. return receive (data);
  152. }
  153. size_t contents (char* data) {
  154. return impl().contents(data);
  155. }
  156. size_t put (const char* data, size_t n) {
  157. return impl().put (data, n);
  158. }
  159. clock_t clock () noexcept {
  160. return impl().clock();
  161. }
  162. //! @}
  163. //! \name Private functionality
  164. //! @{
  165. private:
  166. /*!
  167. * Convert the text pointed by \c str to a value and store it to
  168. * \c value. The type of conversion is deduced by the compiler
  169. * \tparam T The type of the value
  170. * \param str pointer to string with the value
  171. * \param value pointer to converted value
  172. */
  173. template<typename T>
  174. void extract_ (const char* str, T* value) {
  175. static_assert (
  176. std::is_same_v<std::remove_cv_t<T>, int>
  177. || std::is_same_v<std::remove_cv_t<T>, double>
  178. || std::is_same_v<std::remove_cv_t<T>, char>,
  179. "Not supported conversion type.");
  180. if constexpr (std::is_same_v<std::remove_cv_t<T>, int>) {
  181. *value = std::atoi(str);
  182. } else if (std::is_same_v<std::remove_cv_t<T>, double>) {
  183. *value = std::atof(str);
  184. } else if (std::is_same_v<std::remove_cv_t<T>, char>) {
  185. std::strcpy(value, str);
  186. }
  187. }
  188. //! Specialization (as overload function) to handle void* types
  189. void extract_ (const char* str, void* value) noexcept {
  190. (void)*str; (void)value;
  191. }
  192. /*!
  193. * Parse a chunk of the buffer based on \c expected character
  194. *
  195. * Tries to match the \c *expected character in buffer and if so it copies the
  196. * character to token.
  197. * If the \c *expected is the \c Marker character, copy the entire chunk of the buffer
  198. * up to the character that matches the next expected character (expected[1]).
  199. * If there is no next expected character or if its not found in the buffer,
  200. * copy the entire buffer.
  201. *
  202. * \tparam Marker The special character to indicate chunk extraction
  203. *
  204. * \param expected The character to parse/remove from the buffer
  205. * \param buffer The buffer we parse
  206. * \param token Pointer to store the parsed tokens
  207. * \return A (number of characters parsed, marker found) pair
  208. */
  209. template <char Marker = '%'>
  210. std::pair<size_t, bool> parse_ (const char* expected, const string_view buffer, char* token) {
  211. do {
  212. if (*expected == Marker) {
  213. // We have Marker. Copy the entire chunk of the buffer
  214. // up to the character that matches the next expected character (expected[1]).
  215. // If there is none next expected character or if its not found in the buffer,
  216. // copy the entire buffer.
  217. auto next = std::find(buffer.begin(), buffer.end(), expected[1]);
  218. char* nullpos = std::copy(buffer.begin(), next, token);
  219. *nullpos =0;
  220. return std::make_pair(next - buffer.begin(), true);
  221. }
  222. else if (*expected == buffer.front()) {
  223. // We have character match, copy the character to token and return 1 (the char size)
  224. *token++ = buffer.front();
  225. *token =0;
  226. return std::make_pair(1, false);
  227. }
  228. } while (0);
  229. // Fail to parse
  230. *token =0;
  231. return std::make_pair(0, false);
  232. }
  233. /*!
  234. * Analyze the response of a command based on \c expected.
  235. *
  236. * Tries to receive data with timeout and match them against expected string_view.
  237. * For each Marker inside the expected string the value gets extracted, converted and
  238. * copied to \c vargs pointer array.
  239. *
  240. * \param expected The expected string view
  241. * \param timeout the timeout in CPU time
  242. * \param vargs Pointer to variable arguments array
  243. * \param nargs Size of variable arguments array
  244. * \return
  245. */
  246. template<char Marker = '%', typename T>
  247. bool response_ (const string_view expected, clock_t timeout, T* vargs, size_t nargs) {
  248. char buffer[N], token[N], *pbuffer = buffer;
  249. size_t v =0, sz =0;
  250. for (auto ex = expected.begin() ; ex != expected.end() ; ) {
  251. clock_t mark = clock(); // mark the time
  252. while (sz <= 0) { // if buffer is empty get buffer with timeout
  253. sz = receive(buffer);
  254. pbuffer = buffer;
  255. if ((timeout != 0 )&& ((clock() - mark) >= timeout))
  256. return false;
  257. }
  258. // try to parse
  259. auto [step, marker] = parse_<Marker> (ex, {pbuffer, sz}, token);
  260. if (!step)
  261. return false; // discard buffer and fail
  262. if (marker && v < nargs)
  263. extract_(token, vargs[v++]);
  264. pbuffer += step;
  265. sz -= (step <= sz) ? step: sz;
  266. ++ex;
  267. }
  268. return true;
  269. }
  270. //! @}
  271. //! \name public functionality
  272. //! @{
  273. public:
  274. /*!
  275. * \brief
  276. * Transmit data to modem
  277. * \param data Pointer to data to send
  278. * \param n The size of data buffer
  279. * \return The number of transmitted chars
  280. */
  281. size_t transmit (const char* data, size_t n) {
  282. if (data == nullptr)
  283. return 0;
  284. return put (data, n);
  285. }
  286. /*!
  287. * \brief
  288. * Transmit data to modem
  289. * \param data Pointer to data to send
  290. * \return The number of transmitted chars
  291. */
  292. size_t transmit (const char* data) {
  293. if (data == nullptr)
  294. return 0;
  295. return put (data, std::strlen(data));
  296. }
  297. /*!
  298. * \brief
  299. * Try to receive data from modem. If there are data copy them to \c data pointer and return
  300. * the size. Otherwise return zero. In the case \c wait is true block until there are data to get.
  301. *
  302. * \param data Pointer to data buffer to write
  303. * \param wait Flag to select blocking / non-blocking functionality
  304. * \return The number of copied data.
  305. */
  306. size_t receive (char* data, bool wait =false) {
  307. do {
  308. if (streams_.load(std::memory_order_acquire)) {
  309. size_t n =0;
  310. do {
  311. *data << rx_q;
  312. ++n;
  313. } while (*data++ != delimiter);
  314. *data =0;
  315. streams_.fetch_sub(1, std::memory_order_acq_rel);
  316. return n;
  317. }
  318. } while (wait); // on wait flag we block until available stream
  319. return 0;
  320. }
  321. //! Clears the incoming data buffer
  322. void clear () noexcept {
  323. rx_q.clear();
  324. }
  325. //! \return Returns the size of the incoming data buffer
  326. size_t size() noexcept {
  327. return rx_q.size();
  328. }
  329. /*!
  330. * \brief
  331. * Send a command to modem and check if the response matches to \c expected.
  332. *
  333. * This function executes 3 steps.
  334. * - Clears the incoming buffer if requested by template parameter
  335. * - Sends the command to device
  336. * - Waits to get the response and parse it accordingly to \c expected \see response_()
  337. *
  338. * The user can mark spots inside the expected string using the \c Marker ['%'] character.
  339. * These spots will be extracted to tokens upon parsing. If the user passes \c values parameters,
  340. * then the extracted tokens will be converted to the type of the \c values (\c Ts) and copied to them
  341. * one by one. If the values are less than spots, the rest of the tokens get discarded.
  342. *
  343. * \param cmd The command to send (null terminated)
  344. * \param expected The expected response
  345. * \param timeout The timeout in CPU time (leave it for 0 - no timeout)
  346. * \param values The value pointer arguments to get the converted tokens
  347. *
  348. * \tparam Flush Flag to indicate if we flush the buffer before command or not
  349. * \tparam Marker The marker character
  350. * \tparam Ts The type of the values to read from response marked with \c Marker
  351. * \warning The types MUST be the same
  352. *
  353. * \return True on success
  354. *
  355. * \example examples
  356. * \code
  357. * Derived cli;
  358. * int status;
  359. * char str[32];
  360. *
  361. * // discard 3 lines and expect OK\r\n at the end with 1000[CPU time] timeout
  362. * cli.command("AT+CREG?\r\n", "%%%OK\r\n", 1000);
  363. *
  364. * // extract a number from response without timeout (blocking)
  365. * cli.command<flush>("AT+CREG?\r\n", "\r\n+CREG: 0,%\r\n\r\nOK\r\n", 0, &status);
  366. *
  367. * // extract a number and discard the last 2 lines
  368. * cli.command<flush>("AT+CREG?\r\n", "\r\n+CREG: 0,%\r\n%%", 1000, &status);
  369. *
  370. * // discard first line, read the 2nd to str, discard the 3rd line.
  371. * // expect the last to be "OK\r\n"
  372. * cli.command<flush>("AT+CREG?\r\n", "", 100000);
  373. * cli.command<keep>("", "%", 1000);
  374. * cli.command<keep>("", "%%", 1000, str);
  375. * cli.command<keep>("", "OK\r\n", 1000);
  376. * \endcode
  377. */
  378. template<flush_type Flush =flush, char Marker = '%', typename ...Ts>
  379. bool command (const string_view cmd, const string_view expected, clock_t timeout, Ts* ...values) {
  380. constexpr size_t Nr = sizeof...(Ts);
  381. meta::if_c<
  382. (sizeof...(Ts) != 0),
  383. meta::front<meta::typelist<Ts...>>*,
  384. void*
  385. > vargs[Nr] = {values...}; // read all args to local buffer
  386. if constexpr (Flush == flush) {
  387. clear ();
  388. }
  389. if (transmit(cmd.data(), cmd.size()) != cmd.size()) // send command
  390. return false;
  391. // parse the response and return the status
  392. return response_<Marker>(expected, timeout, vargs, Nr);
  393. }
  394. /*!
  395. * \brief
  396. * inetd daemon functionality provided as member function of the driver. This should be running
  397. * in the background either as consecutive calls from an periodic ISR with \c loop = false, or
  398. * as a thread in an RTOS environment with \c loop = true.
  399. *
  400. * \tparam Nm The number of handler array entries
  401. *
  402. * \param async_handles Reference to asynchronous handler array
  403. * \param loop Flag to indicate blocking mode. If true blocking.
  404. */
  405. template <size_t Nm =0>
  406. void inetd (bool loop =true, const inetd_handlers<Nm>* inetd_handlers =nullptr) {
  407. std::array<char, N> buffer;
  408. size_t resp_size;
  409. do {
  410. if ((resp_size = get_(buffer.data())) != 0) {
  411. // on data check for async handlers
  412. bool match = false;
  413. if (inetd_handlers != nullptr) {
  414. for (auto& h : *inetd_handlers)
  415. match |= base_type::check_handle({buffer.data(), resp_size}, h.token, h.match, h.handler);
  416. }
  417. // if no match forward data to receive channel.
  418. if (!match) {
  419. char* it = buffer.data();
  420. do {
  421. rx_q << *it;
  422. } while (*it++ != delimiter);
  423. streams_.fetch_add(1, std::memory_order_acq_rel);
  424. }
  425. }
  426. } while (loop);
  427. }
  428. //! @}
  429. private:
  430. equeue<char, N, true> rx_q{};
  431. std::atomic<size_t> streams_{};
  432. };
  433. } // namespace utl;
  434. #endif /* #ifndef utl_dev_cli_device_h__ */