| /* |
| * Copyright (c) 2022 Trackunit Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/types.h> |
| #include <zephyr/device.h> |
| #include <zephyr/sys/ring_buffer.h> |
| |
| #include <zephyr/modem/pipe.h> |
| #include <zephyr/modem/stats.h> |
| |
| #ifndef ZEPHYR_MODEM_CHAT_ |
| #define ZEPHYR_MODEM_CHAT_ |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| |
| struct modem_chat; |
| |
| /** |
| * @brief Callback called when matching chat is received |
| * |
| * @param chat Pointer to chat instance instance |
| * @param argv Pointer to array of parsed arguments |
| * @param argc Number of parsed arguments, arg 0 holds the exact match |
| * @param user_data Free to use user data set during modem_chat_init() |
| */ |
| typedef void (*modem_chat_match_callback)(struct modem_chat *chat, char **argv, uint16_t argc, |
| void *user_data); |
| |
| /** |
| * @brief Modem chat match |
| */ |
| struct modem_chat_match { |
| /** Match array */ |
| const uint8_t *match; |
| /** Size of match */ |
| uint8_t match_size; |
| /** Separators array */ |
| const uint8_t *separators; |
| /** Size of separators array */ |
| uint8_t separators_size; |
| /** Set if modem chat instance shall use wildcards when matching */ |
| bool wildcards; |
| /** Set if script shall not continue to next step in case of match */ |
| bool partial; |
| /** Type of modem chat instance */ |
| modem_chat_match_callback callback; |
| }; |
| |
| #define MODEM_CHAT_MATCH(_match, _separators, _callback) \ |
| { \ |
| .match = (uint8_t *)(_match), .match_size = (uint8_t)(sizeof(_match) - 1), \ |
| .separators = (uint8_t *)(_separators), \ |
| .separators_size = (uint8_t)(sizeof(_separators) - 1), .wildcards = false, \ |
| .callback = _callback, \ |
| } |
| |
| #define MODEM_CHAT_MATCH_WILDCARD(_match, _separators, _callback) \ |
| { \ |
| .match = (uint8_t *)(_match), .match_size = (uint8_t)(sizeof(_match) - 1), \ |
| .separators = (uint8_t *)(_separators), \ |
| .separators_size = (uint8_t)(sizeof(_separators) - 1), .wildcards = true, \ |
| .callback = _callback, \ |
| } |
| |
| #define MODEM_CHAT_MATCH_INITIALIZER(_match, _separators, _callback, _wildcards, _partial) \ |
| { \ |
| .match = (uint8_t *)(_match), \ |
| .match_size = (uint8_t)(sizeof(_match) - 1), \ |
| .separators = (uint8_t *)(_separators), \ |
| .separators_size = (uint8_t)(sizeof(_separators) - 1), \ |
| .wildcards = _wildcards, \ |
| .partial = _partial, \ |
| .callback = _callback, \ |
| } |
| |
| #define MODEM_CHAT_MATCH_DEFINE(_sym, _match, _separators, _callback) \ |
| const static struct modem_chat_match _sym = MODEM_CHAT_MATCH(_match, _separators, _callback) |
| |
| /* Helper struct to match any response without callback. */ |
| extern const struct modem_chat_match modem_chat_any_match; |
| |
| #define MODEM_CHAT_MATCHES_DEFINE(_sym, ...) \ |
| const static struct modem_chat_match _sym[] = {__VA_ARGS__} |
| |
| /* Helper struct to match nothing. */ |
| extern const struct modem_chat_match modem_chat_empty_matches[0]; |
| |
| /** |
| * @brief Modem chat script chat |
| */ |
| struct modem_chat_script_chat { |
| /** Request to send to modem */ |
| const uint8_t *request; |
| /** Size of request */ |
| uint16_t request_size; |
| /** Expected responses to request */ |
| const struct modem_chat_match *response_matches; |
| /** Number of elements in expected responses */ |
| uint16_t response_matches_size; |
| /** Timeout before chat script may continue to next step in milliseconds */ |
| uint16_t timeout; |
| }; |
| |
| #define MODEM_CHAT_SCRIPT_CMD_RESP(_request, _response_match) \ |
| { \ |
| .request = (uint8_t *)(_request), \ |
| .request_size = (uint16_t)(sizeof(_request) - 1), \ |
| .response_matches = &_response_match, \ |
| .response_matches_size = 1, \ |
| .timeout = 0, \ |
| } |
| |
| #define MODEM_CHAT_SCRIPT_CMD_RESP_MULT(_request, _response_matches) \ |
| { \ |
| .request = (uint8_t *)(_request), \ |
| .request_size = (uint16_t)(sizeof(_request) - 1), \ |
| .response_matches = _response_matches, \ |
| .response_matches_size = ARRAY_SIZE(_response_matches), \ |
| .timeout = 0, \ |
| } |
| |
| #define MODEM_CHAT_SCRIPT_CMD_RESP_NONE(_request, _timeout_ms) \ |
| { \ |
| .request = (uint8_t *)(_request), \ |
| .request_size = (uint16_t)(sizeof(_request) - 1), \ |
| .response_matches = NULL, \ |
| .response_matches_size = 0, \ |
| .timeout = _timeout_ms, \ |
| } |
| |
| #define MODEM_CHAT_SCRIPT_CMDS_DEFINE(_sym, ...) \ |
| const struct modem_chat_script_chat _sym[] = {__VA_ARGS__} |
| |
| /* Helper struct to have no chat script command. */ |
| extern const struct modem_chat_script_chat modem_chat_empty_script_chats[0]; |
| |
| enum modem_chat_script_result { |
| MODEM_CHAT_SCRIPT_RESULT_SUCCESS, |
| MODEM_CHAT_SCRIPT_RESULT_ABORT, |
| MODEM_CHAT_SCRIPT_RESULT_TIMEOUT |
| }; |
| |
| /** |
| * @brief Callback called when script chat is received |
| * |
| * @param chat Pointer to chat instance instance |
| * @param result Result of script execution |
| * @param user_data Free to use user data set during modem_chat_init() |
| */ |
| typedef void (*modem_chat_script_callback)(struct modem_chat *chat, |
| enum modem_chat_script_result result, void *user_data); |
| |
| /** |
| * @brief Modem chat script |
| */ |
| struct modem_chat_script { |
| /** Name of script */ |
| const char *name; |
| /** Array of script chats */ |
| const struct modem_chat_script_chat *script_chats; |
| /** Elements in array of script chats */ |
| uint16_t script_chats_size; |
| /** Array of abort matches */ |
| const struct modem_chat_match *abort_matches; |
| /** Number of elements in array of abort matches */ |
| uint16_t abort_matches_size; |
| /** Callback called when script execution terminates */ |
| modem_chat_script_callback callback; |
| /** Timeout in seconds within which the script execution must terminate */ |
| uint32_t timeout; |
| }; |
| |
| #define MODEM_CHAT_SCRIPT_DEFINE(_sym, _script_chats, _abort_matches, _callback, _timeout_s) \ |
| const static struct modem_chat_script _sym = { \ |
| .name = #_sym, \ |
| .script_chats = _script_chats, \ |
| .script_chats_size = ARRAY_SIZE(_script_chats), \ |
| .abort_matches = _abort_matches, \ |
| .abort_matches_size = ARRAY_SIZE(_abort_matches), \ |
| .callback = _callback, \ |
| .timeout = _timeout_s, \ |
| } |
| |
| #define MODEM_CHAT_SCRIPT_NO_ABORT_DEFINE(_sym, _script_chats, _callback, _timeout_s) \ |
| MODEM_CHAT_SCRIPT_DEFINE(_sym, _script_chats, modem_chat_empty_matches, \ |
| _callback, _timeout_s) |
| |
| #define MODEM_CHAT_SCRIPT_EMPTY_DEFINE(_sym) \ |
| MODEM_CHAT_SCRIPT_NO_ABORT_DEFINE(_sym, modem_chat_empty_script_chats, NULL, 0) |
| |
| enum modem_chat_script_send_state { |
| /* No data to send */ |
| MODEM_CHAT_SCRIPT_SEND_STATE_IDLE, |
| /* Sending request */ |
| MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST, |
| /* Sending delimiter */ |
| MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER, |
| }; |
| |
| /** |
| * @brief Chat instance internal context |
| * @warning Do not modify any members of this struct directly |
| */ |
| struct modem_chat { |
| /* Pipe used to send and receive data */ |
| struct modem_pipe *pipe; |
| |
| /* User data passed with match callbacks */ |
| void *user_data; |
| |
| /* Receive buffer */ |
| uint8_t *receive_buf; |
| uint16_t receive_buf_size; |
| uint16_t receive_buf_len; |
| |
| /* Work buffer */ |
| uint8_t work_buf[32]; |
| uint16_t work_buf_len; |
| |
| /* Chat delimiter */ |
| uint8_t *delimiter; |
| uint16_t delimiter_size; |
| uint16_t delimiter_match_len; |
| |
| /* Array of bytes which are discarded out by parser */ |
| uint8_t *filter; |
| uint16_t filter_size; |
| |
| /* Parsed arguments */ |
| uint8_t **argv; |
| uint16_t argv_size; |
| uint16_t argc; |
| |
| /* Matches |
| * Index 0 -> Response matches |
| * Index 1 -> Abort matches |
| * Index 2 -> Unsolicited matches |
| */ |
| const struct modem_chat_match *matches[3]; |
| uint16_t matches_size[3]; |
| |
| /* Script execution */ |
| const struct modem_chat_script *script; |
| const struct modem_chat_script *pending_script; |
| struct k_work script_run_work; |
| struct k_work_delayable script_timeout_work; |
| struct k_work script_abort_work; |
| uint16_t script_chat_it; |
| atomic_t script_state; |
| enum modem_chat_script_result script_result; |
| struct k_sem script_stopped_sem; |
| |
| /* Script sending */ |
| enum modem_chat_script_send_state script_send_state; |
| uint16_t script_send_pos; |
| struct k_work script_send_work; |
| struct k_work_delayable script_send_timeout_work; |
| |
| /* Match parsing */ |
| const struct modem_chat_match *parse_match; |
| uint16_t parse_match_len; |
| uint16_t parse_arg_len; |
| uint16_t parse_match_type; |
| |
| /* Process received data */ |
| struct k_work receive_work; |
| |
| /* Statistics */ |
| #if CONFIG_MODEM_STATS |
| struct modem_stats_buffer receive_buf_stats; |
| struct modem_stats_buffer work_buf_stats; |
| #endif |
| }; |
| |
| /** |
| * @brief Chat configuration |
| */ |
| struct modem_chat_config { |
| /** Free to use user data passed with modem match callbacks */ |
| void *user_data; |
| /** Receive buffer used to store parsed arguments */ |
| uint8_t *receive_buf; |
| /** Size of receive buffer should be longest line + longest match */ |
| uint16_t receive_buf_size; |
| /** Delimiter */ |
| uint8_t *delimiter; |
| /** Size of delimiter */ |
| uint8_t delimiter_size; |
| /** Bytes which are discarded by parser */ |
| uint8_t *filter; |
| /** Size of filter */ |
| uint8_t filter_size; |
| /** Array of pointers used to point to parsed arguments */ |
| uint8_t **argv; |
| /** Elements in array of pointers */ |
| uint16_t argv_size; |
| /** Array of unsolicited matches */ |
| const struct modem_chat_match *unsol_matches; |
| /** Elements in array of unsolicited matches */ |
| uint16_t unsol_matches_size; |
| }; |
| |
| /** |
| * @brief Initialize modem pipe chat instance |
| * @param chat Chat instance |
| * @param config Configuration which shall be applied to Chat instance |
| * @note Chat instance must be attached to pipe |
| */ |
| int modem_chat_init(struct modem_chat *chat, const struct modem_chat_config *config); |
| |
| /** |
| * @brief Attach modem chat instance to pipe |
| * @param chat Chat instance |
| * @param pipe Pipe instance to attach Chat instance to |
| * @returns 0 if successful |
| * @returns negative errno code if failure |
| * @note Chat instance is enabled if successful |
| */ |
| int modem_chat_attach(struct modem_chat *chat, struct modem_pipe *pipe); |
| |
| /** |
| * @brief Run script asynchronously |
| * @param chat Chat instance |
| * @param script Script to run |
| * @returns 0 if script successfully started |
| * @returns -EBUSY if a script is currently running |
| * @returns -EPERM if modem pipe is not attached |
| * @returns -EINVAL if arguments or script is invalid |
| * @note Script runs asynchronously until complete or aborted. |
| */ |
| int modem_chat_run_script_async(struct modem_chat *chat, const struct modem_chat_script *script); |
| |
| /** |
| * @brief Run script |
| * @param chat Chat instance |
| * @param script Script to run |
| * @returns 0 if successful |
| * @returns -EBUSY if a script is currently running |
| * @returns -EPERM if modem pipe is not attached |
| * @returns -EINVAL if arguments or script is invalid |
| * @note Script runs until complete or aborted. |
| */ |
| int modem_chat_run_script(struct modem_chat *chat, const struct modem_chat_script *script); |
| |
| /** |
| * @brief Run script asynchronously |
| * @note Function exists for backwards compatibility and should be deprecated |
| * @param chat Chat instance |
| * @param script Script to run |
| * @returns 0 if script successfully started |
| * @returns -EBUSY if a script is currently running |
| * @returns -EPERM if modem pipe is not attached |
| * @returns -EINVAL if arguments or script is invalid |
| */ |
| static inline int modem_chat_script_run(struct modem_chat *chat, |
| const struct modem_chat_script *script) |
| { |
| return modem_chat_run_script_async(chat, script); |
| } |
| |
| /** |
| * @brief Abort script |
| * @param chat Chat instance |
| */ |
| void modem_chat_script_abort(struct modem_chat *chat); |
| |
| /** |
| * @brief Release pipe from chat instance |
| * @param chat Chat instance |
| */ |
| void modem_chat_release(struct modem_chat *chat); |
| |
| /** |
| * @brief Initialize modem chat match |
| * @param chat_match Modem chat match instance |
| */ |
| void modem_chat_match_init(struct modem_chat_match *chat_match); |
| |
| /** |
| * @brief Set match of modem chat match instance |
| * @param chat_match Modem chat match instance |
| * @param match Match to set |
| * @note The lifetime of match must match or exceed the lifetime of chat_match |
| * @warning Always call this API after match is modified |
| * |
| * @retval 0 if successful, negative errno code otherwise |
| */ |
| int modem_chat_match_set_match(struct modem_chat_match *chat_match, const char *match); |
| |
| /** |
| * @brief Set separators of modem chat match instance |
| * @param chat_match Modem chat match instance |
| * @param separators Separators to set |
| * @note The lifetime of separators must match or exceed the lifetime of chat_match |
| * @warning Always call this API after separators are modified |
| * |
| * @retval 0 if successful, negative errno code otherwise |
| */ |
| int modem_chat_match_set_separators(struct modem_chat_match *chat_match, const char *separators); |
| |
| /** |
| * @brief Set modem chat match callback |
| * @param chat_match Modem chat match instance |
| * @param callback Callback to set |
| */ |
| void modem_chat_match_set_callback(struct modem_chat_match *chat_match, |
| modem_chat_match_callback callback); |
| |
| /** |
| * @brief Set modem chat match partial flag |
| * @param chat_match Modem chat match instance |
| * @param partial Partial flag to set |
| */ |
| void modem_chat_match_set_partial(struct modem_chat_match *chat_match, bool partial); |
| |
| /** |
| * @brief Set modem chat match wildcards flag |
| * @param chat_match Modem chat match instance |
| * @param enable Enable/disable Wildcards |
| */ |
| void modem_chat_match_enable_wildcards(struct modem_chat_match *chat_match, bool enable); |
| |
| /** |
| * @brief Initialize modem chat script chat |
| * @param script_chat Modem chat script chat instance |
| */ |
| void modem_chat_script_chat_init(struct modem_chat_script_chat *script_chat); |
| |
| /** |
| * @brief Set request of modem chat script chat instance |
| * @param script_chat Modem chat script chat instance |
| * @param request Request to set |
| * @note The lifetime of request must match or exceed the lifetime of script_chat |
| * @warning Always call this API after request is modified |
| * |
| * @retval 0 if successful, negative errno code otherwise |
| */ |
| int modem_chat_script_chat_set_request(struct modem_chat_script_chat *script_chat, |
| const char *request); |
| |
| /** |
| * @brief Set modem chat script chat matches |
| * @param script_chat Modem chat script chat instance |
| * @param response_matches Response match array to set |
| * @param response_matches_size Size of response match array |
| * @note The lifetime of response_matches must match or exceed the lifetime of script_chat |
| * |
| * @retval 0 if successful, negative errno code otherwise |
| */ |
| int modem_chat_script_chat_set_response_matches(struct modem_chat_script_chat *script_chat, |
| const struct modem_chat_match *response_matches, |
| uint16_t response_matches_size); |
| |
| /** |
| * @brief Set modem chat script chat timeout |
| * @param script_chat Modem chat script chat instance |
| * @param timeout_ms Timeout in milliseconds |
| */ |
| void modem_chat_script_chat_set_timeout(struct modem_chat_script_chat *script_chat, |
| uint16_t timeout_ms); |
| |
| /** |
| * @brief Initialize modem chat script |
| * @param script Modem chat script instance |
| */ |
| void modem_chat_script_init(struct modem_chat_script *script); |
| |
| /** |
| * @brief Set modem chat script name |
| * @param script Modem chat script instance |
| * @param name Name to set |
| * @note The lifetime of name must match or exceed the lifetime of script |
| */ |
| void modem_chat_script_set_name(struct modem_chat_script *script, const char *name); |
| |
| /** |
| * @brief Set modem chat script chats |
| * @param script Modem chat script instance |
| * @param script_chats Chat script array to set |
| * @param script_chats_size Size of chat script array |
| * @note The lifetime of script_chats must match or exceed the lifetime of script |
| * |
| * @retval 0 if successful, negative errno code otherwise |
| */ |
| int modem_chat_script_set_script_chats(struct modem_chat_script *script, |
| const struct modem_chat_script_chat *script_chats, |
| uint16_t script_chats_size); |
| |
| /** |
| * @brief Set modem chat script abort matches |
| * @param script Modem chat script instance |
| * @param abort_matches Abort match array to set |
| * @param abort_matches_size Size of abort match array |
| * @note The lifetime of abort_matches must match or exceed the lifetime of script |
| * |
| * @retval 0 if successful, negative errno code otherwise |
| */ |
| int modem_chat_script_set_abort_matches(struct modem_chat_script *script, |
| const struct modem_chat_match *abort_matches, |
| uint16_t abort_matches_size); |
| |
| /** |
| * @brief Set modem chat script callback |
| * @param script Modem chat script instance |
| * @param callback Callback to set |
| */ |
| void modem_chat_script_set_callback(struct modem_chat_script *script, |
| modem_chat_script_callback callback); |
| |
| /** |
| * @brief Set modem chat script timeout |
| * @param script Modem chat script instance |
| * @param timeout_s Timeout in seconds |
| */ |
| void modem_chat_script_set_timeout(struct modem_chat_script *script, uint32_t timeout_s); |
| |
| #ifdef __cplusplus |
| } |
| #endif |
| |
| #endif /* ZEPHYR_MODEM_CHAT_ */ |