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