1 /*
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 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 struct modem_chat_match {
48 /** Match array */
49 const uint8_t *match;
50 /** Size of match */
51 uint8_t match_size;
52 /** Separators array */
53 const uint8_t *separators;
54 /** Size of separators array */
55 uint8_t separators_size;
56 /** Set if modem chat instance shall use wildcards when matching */
57 bool wildcards;
58 /** Set if script shall not continue to next step in case of match */
59 bool partial;
60 /** Type of modem chat instance */
61 modem_chat_match_callback callback;
62 };
63
64 #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 #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 #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 #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 #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 extern const struct modem_chat_match modem_chat_any_match;
100
101 #define MODEM_CHAT_MATCHES_DEFINE(_sym, ...) \
102 const static struct modem_chat_match _sym[] = {__VA_ARGS__}
103
104 /* Helper struct to match nothing. */
105 extern const struct modem_chat_match modem_chat_empty_matches[0];
106
107 /**
108 * @brief Modem chat script chat
109 */
110 struct modem_chat_script_chat {
111 /** Request to send to modem */
112 const uint8_t *request;
113 /** Size of request */
114 uint16_t request_size;
115 /** Expected responses to request */
116 const struct modem_chat_match *response_matches;
117 /** Number of elements in expected responses */
118 uint16_t response_matches_size;
119 /** Timeout before chat script may continue to next step in milliseconds */
120 uint16_t timeout;
121 };
122
123 #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 #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 #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 #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 extern const struct modem_chat_script_chat modem_chat_empty_script_chats[0];
155
156 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 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 struct modem_chat_script {
176 /** Name of script */
177 const char *name;
178 /** Array of script chats */
179 const struct modem_chat_script_chat *script_chats;
180 /** Elements in array of script chats */
181 uint16_t script_chats_size;
182 /** Array of abort matches */
183 const struct modem_chat_match *abort_matches;
184 /** Number of elements in array of abort matches */
185 uint16_t abort_matches_size;
186 /** Callback called when script execution terminates */
187 modem_chat_script_callback callback;
188 /** Timeout in seconds within which the script execution must terminate */
189 uint32_t timeout;
190 };
191
192 #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 #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 #define MODEM_CHAT_SCRIPT_EMPTY_DEFINE(_sym) \
208 MODEM_CHAT_SCRIPT_NO_ABORT_DEFINE(_sym, modem_chat_empty_script_chats, NULL, 0)
209
210 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 struct modem_chat {
224 /* Pipe used to send and receive data */
225 struct modem_pipe *pipe;
226
227 /* User data passed with match callbacks */
228 void *user_data;
229
230 /* Receive buffer */
231 uint8_t *receive_buf;
232 uint16_t receive_buf_size;
233 uint16_t receive_buf_len;
234
235 /* Work buffer */
236 uint8_t work_buf[32];
237 uint16_t work_buf_len;
238
239 /* Chat delimiter */
240 uint8_t *delimiter;
241 uint16_t delimiter_size;
242 uint16_t delimiter_match_len;
243
244 /* Array of bytes which are discarded out by parser */
245 uint8_t *filter;
246 uint16_t filter_size;
247
248 /* Parsed arguments */
249 uint8_t **argv;
250 uint16_t argv_size;
251 uint16_t argc;
252
253 /* Matches
254 * Index 0 -> Response matches
255 * Index 1 -> Abort matches
256 * Index 2 -> Unsolicited matches
257 */
258 const struct modem_chat_match *matches[3];
259 uint16_t matches_size[3];
260
261 /* Script execution */
262 const struct modem_chat_script *script;
263 const struct modem_chat_script *pending_script;
264 struct k_work script_run_work;
265 struct k_work_delayable script_timeout_work;
266 struct k_work script_abort_work;
267 uint16_t script_chat_it;
268 atomic_t script_state;
269 enum modem_chat_script_result script_result;
270 struct k_sem script_stopped_sem;
271
272 /* Script sending */
273 enum modem_chat_script_send_state script_send_state;
274 uint16_t script_send_pos;
275 struct k_work script_send_work;
276 struct k_work_delayable script_send_timeout_work;
277
278 /* Match parsing */
279 const struct modem_chat_match *parse_match;
280 uint16_t parse_match_len;
281 uint16_t parse_arg_len;
282 uint16_t parse_match_type;
283
284 /* Process received data */
285 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 struct modem_chat_config {
298 /** Free to use user data passed with modem match callbacks */
299 void *user_data;
300 /** Receive buffer used to store parsed arguments */
301 uint8_t *receive_buf;
302 /** Size of receive buffer should be longest line + longest match */
303 uint16_t receive_buf_size;
304 /** Delimiter */
305 uint8_t *delimiter;
306 /** Size of delimiter */
307 uint8_t delimiter_size;
308 /** Bytes which are discarded by parser */
309 uint8_t *filter;
310 /** Size of filter */
311 uint8_t filter_size;
312 /** Array of pointers used to point to parsed arguments */
313 uint8_t **argv;
314 /** Elements in array of pointers */
315 uint16_t argv_size;
316 /** Array of unsolicited matches */
317 const struct modem_chat_match *unsol_matches;
318 /** Elements in array of unsolicited matches */
319 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 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 int modem_chat_attach(struct modem_chat *chat, struct modem_pipe *pipe);
339
340 /**
341 * @brief Check if a script is running
342 * @param chat Chat instance
343 * @returns true if a script is currently running
344 * @returns false if a script is not currently running
345 */
346 bool modem_chat_is_running(struct modem_chat *chat);
347
348 /**
349 * @brief Run script asynchronously
350 * @param chat Chat instance
351 * @param script Script to run
352 * @returns 0 if script successfully started
353 * @returns -EBUSY if a script is currently running
354 * @returns -EPERM if modem pipe is not attached
355 * @returns -EINVAL if arguments or script is invalid
356 * @note Script runs asynchronously until complete or aborted.
357 */
358 int modem_chat_run_script_async(struct modem_chat *chat, const struct modem_chat_script *script);
359
360 /**
361 * @brief Run script
362 * @param chat Chat instance
363 * @param script Script to run
364 * @returns 0 if successful
365 * @returns -EBUSY if a script is currently running
366 * @returns -EPERM if modem pipe is not attached
367 * @returns -EINVAL if arguments or script is invalid
368 * @note Script runs until complete or aborted.
369 */
370 int modem_chat_run_script(struct modem_chat *chat, const struct modem_chat_script *script);
371
372 /**
373 * @brief Run script asynchronously
374 * @note Function exists for backwards compatibility and should be deprecated
375 * @param chat Chat instance
376 * @param script Script to run
377 * @returns 0 if script successfully started
378 * @returns -EBUSY if a script is currently running
379 * @returns -EPERM if modem pipe is not attached
380 * @returns -EINVAL if arguments or script is invalid
381 */
modem_chat_script_run(struct modem_chat * chat,const struct modem_chat_script * script)382 static inline int modem_chat_script_run(struct modem_chat *chat,
383 const struct modem_chat_script *script)
384 {
385 return modem_chat_run_script_async(chat, script);
386 }
387
388 /**
389 * @brief Abort script
390 * @param chat Chat instance
391 */
392 void modem_chat_script_abort(struct modem_chat *chat);
393
394 /**
395 * @brief Release pipe from chat instance
396 * @param chat Chat instance
397 */
398 void modem_chat_release(struct modem_chat *chat);
399
400 /**
401 * @brief Initialize modem chat match
402 * @param chat_match Modem chat match instance
403 */
404 void modem_chat_match_init(struct modem_chat_match *chat_match);
405
406 /**
407 * @brief Set match of modem chat match instance
408 * @param chat_match Modem chat match instance
409 * @param match Match to set
410 * @note The lifetime of match must match or exceed the lifetime of chat_match
411 * @warning Always call this API after match is modified
412 *
413 * @retval 0 if successful, negative errno code otherwise
414 */
415 int modem_chat_match_set_match(struct modem_chat_match *chat_match, const char *match);
416
417 /**
418 * @brief Set separators of modem chat match instance
419 * @param chat_match Modem chat match instance
420 * @param separators Separators to set
421 * @note The lifetime of separators must match or exceed the lifetime of chat_match
422 * @warning Always call this API after separators are modified
423 *
424 * @retval 0 if successful, negative errno code otherwise
425 */
426 int modem_chat_match_set_separators(struct modem_chat_match *chat_match, const char *separators);
427
428 /**
429 * @brief Set modem chat match callback
430 * @param chat_match Modem chat match instance
431 * @param callback Callback to set
432 */
433 void modem_chat_match_set_callback(struct modem_chat_match *chat_match,
434 modem_chat_match_callback callback);
435
436 /**
437 * @brief Set modem chat match partial flag
438 * @param chat_match Modem chat match instance
439 * @param partial Partial flag to set
440 */
441 void modem_chat_match_set_partial(struct modem_chat_match *chat_match, bool partial);
442
443 /**
444 * @brief Set modem chat match wildcards flag
445 * @param chat_match Modem chat match instance
446 * @param enable Enable/disable Wildcards
447 */
448 void modem_chat_match_enable_wildcards(struct modem_chat_match *chat_match, bool enable);
449
450 /**
451 * @brief Initialize modem chat script chat
452 * @param script_chat Modem chat script chat instance
453 */
454 void modem_chat_script_chat_init(struct modem_chat_script_chat *script_chat);
455
456 /**
457 * @brief Set request of modem chat script chat instance
458 * @param script_chat Modem chat script chat instance
459 * @param request Request to set
460 * @note The lifetime of request must match or exceed the lifetime of script_chat
461 * @warning Always call this API after request is modified
462 *
463 * @retval 0 if successful, negative errno code otherwise
464 */
465 int modem_chat_script_chat_set_request(struct modem_chat_script_chat *script_chat,
466 const char *request);
467
468 /**
469 * @brief Set modem chat script chat matches
470 * @param script_chat Modem chat script chat instance
471 * @param response_matches Response match array to set
472 * @param response_matches_size Size of response match array
473 * @note The lifetime of response_matches must match or exceed the lifetime of script_chat
474 *
475 * @retval 0 if successful, negative errno code otherwise
476 */
477 int modem_chat_script_chat_set_response_matches(struct modem_chat_script_chat *script_chat,
478 const struct modem_chat_match *response_matches,
479 uint16_t response_matches_size);
480
481 /**
482 * @brief Set modem chat script chat timeout
483 * @param script_chat Modem chat script chat instance
484 * @param timeout_ms Timeout in milliseconds
485 */
486 void modem_chat_script_chat_set_timeout(struct modem_chat_script_chat *script_chat,
487 uint16_t timeout_ms);
488
489 /**
490 * @brief Initialize modem chat script
491 * @param script Modem chat script instance
492 */
493 void modem_chat_script_init(struct modem_chat_script *script);
494
495 /**
496 * @brief Set modem chat script name
497 * @param script Modem chat script instance
498 * @param name Name to set
499 * @note The lifetime of name must match or exceed the lifetime of script
500 */
501 void modem_chat_script_set_name(struct modem_chat_script *script, const char *name);
502
503 /**
504 * @brief Set modem chat script chats
505 * @param script Modem chat script instance
506 * @param script_chats Chat script array to set
507 * @param script_chats_size Size of chat script array
508 * @note The lifetime of script_chats must match or exceed the lifetime of script
509 *
510 * @retval 0 if successful, negative errno code otherwise
511 */
512 int modem_chat_script_set_script_chats(struct modem_chat_script *script,
513 const struct modem_chat_script_chat *script_chats,
514 uint16_t script_chats_size);
515
516 /**
517 * @brief Set modem chat script abort matches
518 * @param script Modem chat script instance
519 * @param abort_matches Abort match array to set
520 * @param abort_matches_size Size of abort match array
521 * @note The lifetime of abort_matches must match or exceed the lifetime of script
522 *
523 * @retval 0 if successful, negative errno code otherwise
524 */
525 int modem_chat_script_set_abort_matches(struct modem_chat_script *script,
526 const struct modem_chat_match *abort_matches,
527 uint16_t abort_matches_size);
528
529 /**
530 * @brief Set modem chat script callback
531 * @param script Modem chat script instance
532 * @param callback Callback to set
533 */
534 void modem_chat_script_set_callback(struct modem_chat_script *script,
535 modem_chat_script_callback callback);
536
537 /**
538 * @brief Set modem chat script timeout
539 * @param script Modem chat script instance
540 * @param timeout_s Timeout in seconds
541 */
542 void modem_chat_script_set_timeout(struct modem_chat_script *script, uint32_t timeout_s);
543
544 /**
545 * @}
546 */
547
548 #ifdef __cplusplus
549 }
550 #endif
551
552 #endif /* ZEPHYR_MODEM_CHAT_ */
553