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
14 #ifndef ZEPHYR_MODEM_CHAT_
15 #define ZEPHYR_MODEM_CHAT_
16
17 #ifdef __cplusplus
18 extern "C" {
19 #endif
20
21 struct modem_chat;
22
23 /**
24 * @brief Callback called when matching chat is received
25 *
26 * @param chat Pointer to chat instance instance
27 * @param argv Pointer to array of parsed arguments
28 * @param argc Number of parsed arguments, arg 0 holds the exact match
29 * @param user_data Free to use user data set during modem_chat_init()
30 */
31 typedef void (*modem_chat_match_callback)(struct modem_chat *chat, char **argv, uint16_t argc,
32 void *user_data);
33
34 /**
35 * @brief Modem chat match
36 */
37 struct modem_chat_match {
38 /** Match array */
39 const uint8_t *match;
40 /** Size of match */
41 uint8_t match_size;
42 /** Separators array */
43 const uint8_t *separators;
44 /** Size of separators array */
45 uint8_t separators_size;
46 /** Set if modem chat instance shall use wildcards when matching */
47 uint8_t wildcards : 1;
48 /** Set if script shall not continue to next step in case of match */
49 uint8_t partial : 1;
50 /** Type of modem chat instance */
51 modem_chat_match_callback callback;
52 };
53
54 #define MODEM_CHAT_MATCH(_match, _separators, _callback) \
55 { \
56 .match = (uint8_t *)(_match), .match_size = (uint8_t)(sizeof(_match) - 1), \
57 .separators = (uint8_t *)(_separators), \
58 .separators_size = (uint8_t)(sizeof(_separators) - 1), .wildcards = false, \
59 .callback = _callback, \
60 }
61
62 #define MODEM_CHAT_MATCH_WILDCARD(_match, _separators, _callback) \
63 { \
64 .match = (uint8_t *)(_match), .match_size = (uint8_t)(sizeof(_match) - 1), \
65 .separators = (uint8_t *)(_separators), \
66 .separators_size = (uint8_t)(sizeof(_separators) - 1), .wildcards = true, \
67 .callback = _callback, \
68 }
69
70 #define MODEM_CHAT_MATCH_INITIALIZER(_match, _separators, _callback, _wildcards, _partial) \
71 { \
72 .match = (uint8_t *)(_match), \
73 .match_size = (uint8_t)(sizeof(_match) - 1), \
74 .separators = (uint8_t *)(_separators), \
75 .separators_size = (uint8_t)(sizeof(_separators) - 1), \
76 .wildcards = _wildcards, \
77 .partial = _partial, \
78 .callback = _callback, \
79 }
80
81 #define MODEM_CHAT_MATCH_DEFINE(_sym, _match, _separators, _callback) \
82 const static struct modem_chat_match _sym = MODEM_CHAT_MATCH(_match, _separators, _callback)
83
84 #define MODEM_CHAT_MATCHES_DEFINE(_sym, ...) \
85 const static struct modem_chat_match _sym[] = {__VA_ARGS__}
86
87 /**
88 * @brief Modem chat script chat
89 */
90 struct modem_chat_script_chat {
91 /** Request to send to modem */
92 const uint8_t *request;
93 /** Size of request */
94 uint16_t request_size;
95 /** Expected responses to request */
96 const struct modem_chat_match *response_matches;
97 /** Number of elements in expected responses */
98 uint16_t response_matches_size;
99 /** Timeout before chat script may continue to next step in milliseconds */
100 uint16_t timeout;
101 };
102
103 #define MODEM_CHAT_SCRIPT_CMD_RESP(_request, _response_match) \
104 { \
105 .request = (uint8_t *)(_request), \
106 .request_size = (uint16_t)(sizeof(_request) - 1), \
107 .response_matches = &_response_match, \
108 .response_matches_size = 1, \
109 .timeout = 0, \
110 }
111
112 #define MODEM_CHAT_SCRIPT_CMD_RESP_MULT(_request, _response_matches) \
113 { \
114 .request = (uint8_t *)(_request), \
115 .request_size = (uint16_t)(sizeof(_request) - 1), \
116 .response_matches = _response_matches, \
117 .response_matches_size = ARRAY_SIZE(_response_matches), \
118 .timeout = 0, \
119 }
120
121 #define MODEM_CHAT_SCRIPT_CMD_RESP_NONE(_request, _timeout) \
122 { \
123 .request = (uint8_t *)(_request), \
124 .request_size = (uint16_t)(sizeof(_request) - 1), \
125 .response_matches = NULL, \
126 .response_matches_size = 0, \
127 .timeout = _timeout, \
128 }
129
130 #define MODEM_CHAT_SCRIPT_CMDS_DEFINE(_sym, ...) \
131 const struct modem_chat_script_chat _sym[] = {__VA_ARGS__}
132
133 enum modem_chat_script_result {
134 MODEM_CHAT_SCRIPT_RESULT_SUCCESS,
135 MODEM_CHAT_SCRIPT_RESULT_ABORT,
136 MODEM_CHAT_SCRIPT_RESULT_TIMEOUT
137 };
138
139 /**
140 * @brief Callback called when script chat is received
141 *
142 * @param chat Pointer to chat instance instance
143 * @param result Result of script execution
144 * @param user_data Free to use user data set during modem_chat_init()
145 */
146 typedef void (*modem_chat_script_callback)(struct modem_chat *chat,
147 enum modem_chat_script_result result, void *user_data);
148
149 /**
150 * @brief Modem chat script
151 */
152 struct modem_chat_script {
153 /** Name of script */
154 const char *name;
155 /** Array of script chats */
156 const struct modem_chat_script_chat *script_chats;
157 /** Elements in array of script chats */
158 uint16_t script_chats_size;
159 /** Array of abort matches */
160 const struct modem_chat_match *abort_matches;
161 /** Number of elements in array of abort matches */
162 uint16_t abort_matches_size;
163 /** Callback called when script execution terminates */
164 modem_chat_script_callback callback;
165 /** Timeout in seconds within which the script execution must terminate */
166 uint32_t timeout;
167 };
168
169 #define MODEM_CHAT_SCRIPT_DEFINE(_sym, _script_chats, _abort_matches, _callback, _timeout) \
170 const static struct modem_chat_script _sym = { \
171 .name = #_sym, \
172 .script_chats = _script_chats, \
173 .script_chats_size = ARRAY_SIZE(_script_chats), \
174 .abort_matches = _abort_matches, \
175 .abort_matches_size = ARRAY_SIZE(_abort_matches), \
176 .callback = _callback, \
177 .timeout = _timeout, \
178 }
179
180 enum modem_chat_script_send_state {
181 /* No data to send */
182 MODEM_CHAT_SCRIPT_SEND_STATE_IDLE,
183 /* Sending request */
184 MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST,
185 /* Sending delimiter */
186 MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER,
187 };
188
189 /**
190 * @brief Chat instance internal context
191 * @warning Do not modify any members of this struct directly
192 */
193 struct modem_chat {
194 /* Pipe used to send and receive data */
195 struct modem_pipe *pipe;
196
197 /* User data passed with match callbacks */
198 void *user_data;
199
200 /* Receive buffer */
201 uint8_t *receive_buf;
202 uint16_t receive_buf_size;
203 uint16_t receive_buf_len;
204
205 /* Work buffer */
206 uint8_t work_buf[32];
207 uint16_t work_buf_len;
208
209 /* Chat delimiter */
210 uint8_t *delimiter;
211 uint16_t delimiter_size;
212 uint16_t delimiter_match_len;
213
214 /* Array of bytes which are discarded out by parser */
215 uint8_t *filter;
216 uint16_t filter_size;
217
218 /* Parsed arguments */
219 uint8_t **argv;
220 uint16_t argv_size;
221 uint16_t argc;
222
223 /* Matches
224 * Index 0 -> Response matches
225 * Index 1 -> Abort matches
226 * Index 2 -> Unsolicited matches
227 */
228 const struct modem_chat_match *matches[3];
229 uint16_t matches_size[3];
230
231 /* Script execution */
232 const struct modem_chat_script *script;
233 const struct modem_chat_script *pending_script;
234 struct k_work script_run_work;
235 struct k_work_delayable script_timeout_work;
236 struct k_work script_abort_work;
237 uint16_t script_chat_it;
238 atomic_t script_state;
239 enum modem_chat_script_result script_result;
240 struct k_sem script_stopped_sem;
241
242 /* Script sending */
243 uint16_t script_send_request_pos;
244 uint16_t script_send_delimiter_pos;
245 struct k_work_delayable script_send_work;
246 struct k_work_delayable script_send_timeout_work;
247
248 /* Match parsing */
249 const struct modem_chat_match *parse_match;
250 uint16_t parse_match_len;
251 uint16_t parse_arg_len;
252 uint16_t parse_match_type;
253
254 /* Process received data */
255 struct k_work_delayable process_work;
256 k_timeout_t process_timeout;
257 };
258
259 /**
260 * @brief Chat configuration
261 */
262 struct modem_chat_config {
263 /** Free to use user data passed with modem match callbacks */
264 void *user_data;
265 /** Receive buffer used to store parsed arguments */
266 uint8_t *receive_buf;
267 /** Size of receive buffer should be longest line + longest match */
268 uint16_t receive_buf_size;
269 /** Delimiter */
270 uint8_t *delimiter;
271 /** Size of delimiter */
272 uint8_t delimiter_size;
273 /** Bytes which are discarded by parser */
274 uint8_t *filter;
275 /** Size of filter */
276 uint8_t filter_size;
277 /** Array of pointers used to point to parsed arguments */
278 uint8_t **argv;
279 /** Elements in array of pointers */
280 uint16_t argv_size;
281 /** Array of unsolicited matches */
282 const struct modem_chat_match *unsol_matches;
283 /** Elements in array of unsolicited matches */
284 uint16_t unsol_matches_size;
285 /** Delay from receive ready event to pipe receive occurs */
286 k_timeout_t process_timeout;
287 };
288
289 /**
290 * @brief Initialize modem pipe chat instance
291 * @param chat Chat instance
292 * @param config Configuration which shall be applied to Chat instance
293 * @note Chat instance must be attached to pipe
294 */
295 int modem_chat_init(struct modem_chat *chat, const struct modem_chat_config *config);
296
297 /**
298 * @brief Attach modem chat instance to pipe
299 * @param chat Chat instance
300 * @param pipe Pipe instance to attach Chat instance to
301 * @returns 0 if successful
302 * @returns negative errno code if failure
303 * @note Chat instance is enabled if successful
304 */
305 int modem_chat_attach(struct modem_chat *chat, struct modem_pipe *pipe);
306
307 /**
308 * @brief Run script asynchronously
309 * @param chat Chat instance
310 * @param script Script to run
311 * @returns 0 if script successfully started
312 * @returns -EBUSY if a script is currently running
313 * @returns -EPERM if modem pipe is not attached
314 * @returns -EINVAL if arguments or script is invalid
315 * @note Script runs asynchronously until complete or aborted.
316 */
317 int modem_chat_run_script_async(struct modem_chat *chat, const struct modem_chat_script *script);
318
319 /**
320 * @brief Run script
321 * @param chat Chat instance
322 * @param script Script to run
323 * @returns 0 if successful
324 * @returns -EBUSY if a script is currently running
325 * @returns -EPERM if modem pipe is not attached
326 * @returns -EINVAL if arguments or script is invalid
327 * @note Script runs until complete or aborted.
328 */
329 int modem_chat_run_script(struct modem_chat *chat, const struct modem_chat_script *script);
330
331 /**
332 * @brief Run script asynchronously
333 * @note Function exists for backwards compatibility and should be deprecated
334 * @param chat Chat instance
335 * @param script Script to run
336 * @returns 0 if script successfully started
337 * @returns -EBUSY if a script is currently running
338 * @returns -EPERM if modem pipe is not attached
339 * @returns -EINVAL if arguments or script is invalid
340 */
modem_chat_script_run(struct modem_chat * chat,const struct modem_chat_script * script)341 static inline int modem_chat_script_run(struct modem_chat *chat,
342 const struct modem_chat_script *script)
343 {
344 return modem_chat_run_script_async(chat, script);
345 }
346
347 /**
348 * @brief Abort script
349 * @param chat Chat instance
350 */
351 void modem_chat_script_abort(struct modem_chat *chat);
352
353 /**
354 * @brief Release pipe from chat instance
355 * @param chat Chat instance
356 */
357 void modem_chat_release(struct modem_chat *chat);
358
359 #ifdef __cplusplus
360 }
361 #endif
362
363 #endif /* ZEPHYR_MODEM_CHAT_ */
364