Line data Source code
1 0 : /*
2 : * Copyright (c) 2022 Trackunit Corporation
3 : *
4 : * SPDX-License-Identifier: Apache-2.0
5 : */
6 :
7 : #include <zephyr/kernel.h>
8 : #include <zephyr/types.h>
9 : #include <zephyr/device.h>
10 : #include <zephyr/sys/ring_buffer.h>
11 :
12 : #include <zephyr/modem/pipe.h>
13 : #include <zephyr/modem/stats.h>
14 :
15 : #ifndef ZEPHYR_MODEM_CHAT_
16 : #define ZEPHYR_MODEM_CHAT_
17 :
18 : #ifdef __cplusplus
19 : extern "C" {
20 : #endif
21 :
22 : /**
23 : * @brief Modem Chat
24 : * @defgroup modem_chat Modem Chat
25 : * @since 3.5
26 : * @version 1.0.0
27 : * @ingroup modem
28 : * @{
29 : */
30 :
31 : struct modem_chat;
32 :
33 : /**
34 : * @brief Callback called when matching chat is received
35 : *
36 : * @param chat Pointer to chat instance instance
37 : * @param argv Pointer to array of parsed arguments
38 : * @param argc Number of parsed arguments, arg 0 holds the exact match
39 : * @param user_data Free to use user data set during modem_chat_init()
40 : */
41 1 : typedef void (*modem_chat_match_callback)(struct modem_chat *chat, char **argv, uint16_t argc,
42 : void *user_data);
43 :
44 : /**
45 : * @brief Modem chat match
46 : */
47 1 : struct modem_chat_match {
48 : /** Match array */
49 1 : const uint8_t *match;
50 : /** Size of match */
51 1 : uint8_t match_size;
52 : /** Separators array */
53 1 : const uint8_t *separators;
54 : /** Size of separators array */
55 1 : uint8_t separators_size;
56 : /** Set if modem chat instance shall use wildcards when matching */
57 1 : bool wildcards;
58 : /** Set if script shall not continue to next step in case of match */
59 1 : bool partial;
60 : /** Type of modem chat instance */
61 1 : modem_chat_match_callback callback;
62 : };
63 :
64 0 : #define MODEM_CHAT_MATCH(_match, _separators, _callback) \
65 : { \
66 : .match = (uint8_t *)(_match), .match_size = (uint8_t)(sizeof(_match) - 1), \
67 : .separators = (uint8_t *)(_separators), \
68 : .separators_size = (uint8_t)(sizeof(_separators) - 1), .wildcards = false, \
69 : .callback = _callback, \
70 : }
71 :
72 0 : #define MODEM_CHAT_MATCH_WILDCARD(_match, _separators, _callback) \
73 : { \
74 : .match = (uint8_t *)(_match), .match_size = (uint8_t)(sizeof(_match) - 1), \
75 : .separators = (uint8_t *)(_separators), \
76 : .separators_size = (uint8_t)(sizeof(_separators) - 1), .wildcards = true, \
77 : .callback = _callback, \
78 : }
79 :
80 0 : #define MODEM_CHAT_MATCH_INITIALIZER(_match, _separators, _callback, _wildcards, _partial) \
81 : { \
82 : .match = (uint8_t *)(_match), \
83 : .match_size = (uint8_t)(sizeof(_match) - 1), \
84 : .separators = (uint8_t *)(_separators), \
85 : .separators_size = (uint8_t)(sizeof(_separators) - 1), \
86 : .wildcards = _wildcards, \
87 : .partial = _partial, \
88 : .callback = _callback, \
89 : }
90 :
91 0 : #define MODEM_CHAT_MATCH_DEFINE(_sym, _match, _separators, _callback) \
92 : const static struct modem_chat_match _sym = MODEM_CHAT_MATCH(_match, _separators, _callback)
93 :
94 0 : #define MODEM_CHAT_MATCH_WILDCARD_DEFINE(_sym, _match, _separators, _callback) \
95 : const static struct modem_chat_match _sym = \
96 : MODEM_CHAT_MATCH_WILDCARD(_match, _separators, _callback)
97 :
98 : /* Helper struct to match any response without callback. */
99 0 : extern const struct modem_chat_match modem_chat_any_match;
100 :
101 0 : #define MODEM_CHAT_MATCHES_DEFINE(_sym, ...) \
102 : const static struct modem_chat_match _sym[] = {__VA_ARGS__}
103 :
104 : /* Helper struct to match nothing. */
105 0 : extern const struct modem_chat_match modem_chat_empty_matches[0];
106 :
107 : /**
108 : * @brief Modem chat script chat
109 : */
110 1 : struct modem_chat_script_chat {
111 : /** Request to send to modem */
112 1 : const uint8_t *request;
113 : /** Size of request */
114 1 : uint16_t request_size;
115 : /** Expected responses to request */
116 1 : const struct modem_chat_match *response_matches;
117 : /** Number of elements in expected responses */
118 1 : uint16_t response_matches_size;
119 : /** Timeout before chat script may continue to next step in milliseconds */
120 1 : uint16_t timeout;
121 : };
122 :
123 0 : #define MODEM_CHAT_SCRIPT_CMD_RESP(_request, _response_match) \
124 : { \
125 : .request = (uint8_t *)(_request), \
126 : .request_size = (uint16_t)(sizeof(_request) - 1), \
127 : .response_matches = &_response_match, \
128 : .response_matches_size = 1, \
129 : .timeout = 0, \
130 : }
131 :
132 0 : #define MODEM_CHAT_SCRIPT_CMD_RESP_MULT(_request, _response_matches) \
133 : { \
134 : .request = (uint8_t *)(_request), \
135 : .request_size = (uint16_t)(sizeof(_request) - 1), \
136 : .response_matches = _response_matches, \
137 : .response_matches_size = ARRAY_SIZE(_response_matches), \
138 : .timeout = 0, \
139 : }
140 :
141 0 : #define MODEM_CHAT_SCRIPT_CMD_RESP_NONE(_request, _timeout_ms) \
142 : { \
143 : .request = (uint8_t *)(_request), \
144 : .request_size = (uint16_t)(sizeof(_request) - 1), \
145 : .response_matches = NULL, \
146 : .response_matches_size = 0, \
147 : .timeout = _timeout_ms, \
148 : }
149 :
150 0 : #define MODEM_CHAT_SCRIPT_CMDS_DEFINE(_sym, ...) \
151 : const static struct modem_chat_script_chat _sym[] = {__VA_ARGS__}
152 :
153 : /* Helper struct to have no chat script command. */
154 0 : extern const struct modem_chat_script_chat modem_chat_empty_script_chats[0];
155 :
156 0 : enum modem_chat_script_result {
157 : MODEM_CHAT_SCRIPT_RESULT_SUCCESS,
158 : MODEM_CHAT_SCRIPT_RESULT_ABORT,
159 : MODEM_CHAT_SCRIPT_RESULT_TIMEOUT
160 : };
161 :
162 : /**
163 : * @brief Callback called when script chat is received
164 : *
165 : * @param chat Pointer to chat instance instance
166 : * @param result Result of script execution
167 : * @param user_data Free to use user data set during modem_chat_init()
168 : */
169 1 : typedef void (*modem_chat_script_callback)(struct modem_chat *chat,
170 : enum modem_chat_script_result result, void *user_data);
171 :
172 : /**
173 : * @brief Modem chat script
174 : */
175 1 : struct modem_chat_script {
176 : /** Name of script */
177 1 : const char *name;
178 : /** Array of script chats */
179 1 : const struct modem_chat_script_chat *script_chats;
180 : /** Elements in array of script chats */
181 1 : uint16_t script_chats_size;
182 : /** Array of abort matches */
183 1 : const struct modem_chat_match *abort_matches;
184 : /** Number of elements in array of abort matches */
185 1 : uint16_t abort_matches_size;
186 : /** Callback called when script execution terminates */
187 1 : modem_chat_script_callback callback;
188 : /** Timeout in seconds within which the script execution must terminate */
189 1 : uint32_t timeout;
190 : };
191 :
192 0 : #define MODEM_CHAT_SCRIPT_DEFINE(_sym, _script_chats, _abort_matches, _callback, _timeout_s) \
193 : const static struct modem_chat_script _sym = { \
194 : .name = #_sym, \
195 : .script_chats = _script_chats, \
196 : .script_chats_size = ARRAY_SIZE(_script_chats), \
197 : .abort_matches = _abort_matches, \
198 : .abort_matches_size = ARRAY_SIZE(_abort_matches), \
199 : .callback = _callback, \
200 : .timeout = _timeout_s, \
201 : }
202 :
203 0 : #define MODEM_CHAT_SCRIPT_NO_ABORT_DEFINE(_sym, _script_chats, _callback, _timeout_s) \
204 : MODEM_CHAT_SCRIPT_DEFINE(_sym, _script_chats, modem_chat_empty_matches, \
205 : _callback, _timeout_s)
206 :
207 0 : #define MODEM_CHAT_SCRIPT_EMPTY_DEFINE(_sym) \
208 : MODEM_CHAT_SCRIPT_NO_ABORT_DEFINE(_sym, modem_chat_empty_script_chats, NULL, 0)
209 :
210 0 : enum modem_chat_script_send_state {
211 : /* No data to send */
212 : MODEM_CHAT_SCRIPT_SEND_STATE_IDLE,
213 : /* Sending request */
214 : MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST,
215 : /* Sending delimiter */
216 : MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER,
217 : };
218 :
219 : /**
220 : * @brief Chat instance internal context
221 : * @warning Do not modify any members of this struct directly
222 : */
223 1 : struct modem_chat {
224 : /* Pipe used to send and receive data */
225 0 : struct modem_pipe *pipe;
226 :
227 : /* User data passed with match callbacks */
228 0 : void *user_data;
229 :
230 : /* Receive buffer */
231 0 : uint8_t *receive_buf;
232 0 : uint16_t receive_buf_size;
233 0 : uint16_t receive_buf_len;
234 :
235 : /* Work buffer */
236 0 : uint8_t work_buf[32];
237 0 : uint16_t work_buf_len;
238 :
239 : /* Chat delimiter */
240 0 : uint8_t *delimiter;
241 0 : uint16_t delimiter_size;
242 0 : uint16_t delimiter_match_len;
243 :
244 : /* Array of bytes which are discarded out by parser */
245 0 : uint8_t *filter;
246 0 : uint16_t filter_size;
247 :
248 : /* Parsed arguments */
249 0 : uint8_t **argv;
250 0 : uint16_t argv_size;
251 0 : uint16_t argc;
252 :
253 : /* Matches
254 : * Index 0 -> Response matches
255 : * Index 1 -> Abort matches
256 : * Index 2 -> Unsolicited matches
257 : */
258 0 : const struct modem_chat_match *matches[3];
259 0 : uint16_t matches_size[3];
260 :
261 : /* Script execution */
262 0 : const struct modem_chat_script *script;
263 0 : const struct modem_chat_script *pending_script;
264 0 : struct k_work script_run_work;
265 0 : struct k_work_delayable script_timeout_work;
266 0 : struct k_work script_abort_work;
267 0 : uint16_t script_chat_it;
268 0 : atomic_t script_state;
269 0 : enum modem_chat_script_result script_result;
270 0 : struct k_sem script_stopped_sem;
271 :
272 : /* Script sending */
273 0 : enum modem_chat_script_send_state script_send_state;
274 0 : uint16_t script_send_pos;
275 0 : struct k_work script_send_work;
276 0 : struct k_work_delayable script_send_timeout_work;
277 :
278 : /* Match parsing */
279 0 : const struct modem_chat_match *parse_match;
280 0 : uint16_t parse_match_len;
281 0 : uint16_t parse_arg_len;
282 0 : uint16_t parse_match_type;
283 :
284 : /* Process received data */
285 0 : struct k_work receive_work;
286 :
287 : /* Statistics */
288 : #if CONFIG_MODEM_STATS
289 : struct modem_stats_buffer receive_buf_stats;
290 : struct modem_stats_buffer work_buf_stats;
291 : #endif
292 : };
293 :
294 : /**
295 : * @brief Chat configuration
296 : */
297 1 : struct modem_chat_config {
298 : /** Free to use user data passed with modem match callbacks */
299 1 : void *user_data;
300 : /** Receive buffer used to store parsed arguments */
301 1 : uint8_t *receive_buf;
302 : /** Size of receive buffer should be longest line + longest match */
303 1 : uint16_t receive_buf_size;
304 : /** Delimiter */
305 1 : uint8_t *delimiter;
306 : /** Size of delimiter */
307 1 : uint8_t delimiter_size;
308 : /** Bytes which are discarded by parser */
309 1 : uint8_t *filter;
310 : /** Size of filter */
311 1 : uint8_t filter_size;
312 : /** Array of pointers used to point to parsed arguments */
313 1 : uint8_t **argv;
314 : /** Elements in array of pointers */
315 1 : uint16_t argv_size;
316 : /** Array of unsolicited matches */
317 1 : const struct modem_chat_match *unsol_matches;
318 : /** Elements in array of unsolicited matches */
319 1 : uint16_t unsol_matches_size;
320 : };
321 :
322 : /**
323 : * @brief Initialize modem pipe chat instance
324 : * @param chat Chat instance
325 : * @param config Configuration which shall be applied to Chat instance
326 : * @note Chat instance must be attached to pipe
327 : */
328 1 : int modem_chat_init(struct modem_chat *chat, const struct modem_chat_config *config);
329 :
330 : /**
331 : * @brief Attach modem chat instance to pipe
332 : * @param chat Chat instance
333 : * @param pipe Pipe instance to attach Chat instance to
334 : * @returns 0 if successful
335 : * @returns negative errno code if failure
336 : * @note Chat instance is enabled if successful
337 : */
338 1 : int modem_chat_attach(struct modem_chat *chat, struct modem_pipe *pipe);
339 :
340 : /**
341 : * @brief Run script asynchronously
342 : * @param chat Chat instance
343 : * @param script Script to run
344 : * @returns 0 if script successfully started
345 : * @returns -EBUSY if a script is currently running
346 : * @returns -EPERM if modem pipe is not attached
347 : * @returns -EINVAL if arguments or script is invalid
348 : * @note Script runs asynchronously until complete or aborted.
349 : */
350 1 : int modem_chat_run_script_async(struct modem_chat *chat, const struct modem_chat_script *script);
351 :
352 : /**
353 : * @brief Run script
354 : * @param chat Chat instance
355 : * @param script Script to run
356 : * @returns 0 if successful
357 : * @returns -EBUSY if a script is currently running
358 : * @returns -EPERM if modem pipe is not attached
359 : * @returns -EINVAL if arguments or script is invalid
360 : * @note Script runs until complete or aborted.
361 : */
362 1 : int modem_chat_run_script(struct modem_chat *chat, const struct modem_chat_script *script);
363 :
364 : /**
365 : * @brief Run script asynchronously
366 : * @note Function exists for backwards compatibility and should be deprecated
367 : * @param chat Chat instance
368 : * @param script Script to run
369 : * @returns 0 if script successfully started
370 : * @returns -EBUSY if a script is currently running
371 : * @returns -EPERM if modem pipe is not attached
372 : * @returns -EINVAL if arguments or script is invalid
373 : */
374 1 : static inline int modem_chat_script_run(struct modem_chat *chat,
375 : const struct modem_chat_script *script)
376 : {
377 : return modem_chat_run_script_async(chat, script);
378 : }
379 :
380 : /**
381 : * @brief Abort script
382 : * @param chat Chat instance
383 : */
384 1 : void modem_chat_script_abort(struct modem_chat *chat);
385 :
386 : /**
387 : * @brief Release pipe from chat instance
388 : * @param chat Chat instance
389 : */
390 1 : void modem_chat_release(struct modem_chat *chat);
391 :
392 : /**
393 : * @brief Initialize modem chat match
394 : * @param chat_match Modem chat match instance
395 : */
396 1 : void modem_chat_match_init(struct modem_chat_match *chat_match);
397 :
398 : /**
399 : * @brief Set match of modem chat match instance
400 : * @param chat_match Modem chat match instance
401 : * @param match Match to set
402 : * @note The lifetime of match must match or exceed the lifetime of chat_match
403 : * @warning Always call this API after match is modified
404 : *
405 : * @retval 0 if successful, negative errno code otherwise
406 : */
407 1 : int modem_chat_match_set_match(struct modem_chat_match *chat_match, const char *match);
408 :
409 : /**
410 : * @brief Set separators of modem chat match instance
411 : * @param chat_match Modem chat match instance
412 : * @param separators Separators to set
413 : * @note The lifetime of separators must match or exceed the lifetime of chat_match
414 : * @warning Always call this API after separators are modified
415 : *
416 : * @retval 0 if successful, negative errno code otherwise
417 : */
418 1 : int modem_chat_match_set_separators(struct modem_chat_match *chat_match, const char *separators);
419 :
420 : /**
421 : * @brief Set modem chat match callback
422 : * @param chat_match Modem chat match instance
423 : * @param callback Callback to set
424 : */
425 1 : void modem_chat_match_set_callback(struct modem_chat_match *chat_match,
426 : modem_chat_match_callback callback);
427 :
428 : /**
429 : * @brief Set modem chat match partial flag
430 : * @param chat_match Modem chat match instance
431 : * @param partial Partial flag to set
432 : */
433 1 : void modem_chat_match_set_partial(struct modem_chat_match *chat_match, bool partial);
434 :
435 : /**
436 : * @brief Set modem chat match wildcards flag
437 : * @param chat_match Modem chat match instance
438 : * @param enable Enable/disable Wildcards
439 : */
440 1 : void modem_chat_match_enable_wildcards(struct modem_chat_match *chat_match, bool enable);
441 :
442 : /**
443 : * @brief Initialize modem chat script chat
444 : * @param script_chat Modem chat script chat instance
445 : */
446 1 : void modem_chat_script_chat_init(struct modem_chat_script_chat *script_chat);
447 :
448 : /**
449 : * @brief Set request of modem chat script chat instance
450 : * @param script_chat Modem chat script chat instance
451 : * @param request Request to set
452 : * @note The lifetime of request must match or exceed the lifetime of script_chat
453 : * @warning Always call this API after request is modified
454 : *
455 : * @retval 0 if successful, negative errno code otherwise
456 : */
457 1 : int modem_chat_script_chat_set_request(struct modem_chat_script_chat *script_chat,
458 : const char *request);
459 :
460 : /**
461 : * @brief Set modem chat script chat matches
462 : * @param script_chat Modem chat script chat instance
463 : * @param response_matches Response match array to set
464 : * @param response_matches_size Size of response match array
465 : * @note The lifetime of response_matches must match or exceed the lifetime of script_chat
466 : *
467 : * @retval 0 if successful, negative errno code otherwise
468 : */
469 1 : int modem_chat_script_chat_set_response_matches(struct modem_chat_script_chat *script_chat,
470 : const struct modem_chat_match *response_matches,
471 : uint16_t response_matches_size);
472 :
473 : /**
474 : * @brief Set modem chat script chat timeout
475 : * @param script_chat Modem chat script chat instance
476 : * @param timeout_ms Timeout in milliseconds
477 : */
478 1 : void modem_chat_script_chat_set_timeout(struct modem_chat_script_chat *script_chat,
479 : uint16_t timeout_ms);
480 :
481 : /**
482 : * @brief Initialize modem chat script
483 : * @param script Modem chat script instance
484 : */
485 1 : void modem_chat_script_init(struct modem_chat_script *script);
486 :
487 : /**
488 : * @brief Set modem chat script name
489 : * @param script Modem chat script instance
490 : * @param name Name to set
491 : * @note The lifetime of name must match or exceed the lifetime of script
492 : */
493 1 : void modem_chat_script_set_name(struct modem_chat_script *script, const char *name);
494 :
495 : /**
496 : * @brief Set modem chat script chats
497 : * @param script Modem chat script instance
498 : * @param script_chats Chat script array to set
499 : * @param script_chats_size Size of chat script array
500 : * @note The lifetime of script_chats must match or exceed the lifetime of script
501 : *
502 : * @retval 0 if successful, negative errno code otherwise
503 : */
504 1 : int modem_chat_script_set_script_chats(struct modem_chat_script *script,
505 : const struct modem_chat_script_chat *script_chats,
506 : uint16_t script_chats_size);
507 :
508 : /**
509 : * @brief Set modem chat script abort matches
510 : * @param script Modem chat script instance
511 : * @param abort_matches Abort match array to set
512 : * @param abort_matches_size Size of abort match array
513 : * @note The lifetime of abort_matches must match or exceed the lifetime of script
514 : *
515 : * @retval 0 if successful, negative errno code otherwise
516 : */
517 1 : int modem_chat_script_set_abort_matches(struct modem_chat_script *script,
518 : const struct modem_chat_match *abort_matches,
519 : uint16_t abort_matches_size);
520 :
521 : /**
522 : * @brief Set modem chat script callback
523 : * @param script Modem chat script instance
524 : * @param callback Callback to set
525 : */
526 1 : void modem_chat_script_set_callback(struct modem_chat_script *script,
527 : modem_chat_script_callback callback);
528 :
529 : /**
530 : * @brief Set modem chat script timeout
531 : * @param script Modem chat script instance
532 : * @param timeout_s Timeout in seconds
533 : */
534 1 : void modem_chat_script_set_timeout(struct modem_chat_script *script, uint32_t timeout_s);
535 :
536 : /**
537 : * @}
538 : */
539 :
540 : #ifdef __cplusplus
541 : }
542 : #endif
543 :
544 : #endif /* ZEPHYR_MODEM_CHAT_ */
|