Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: TYBLE16_simple_data_logger TYBLE16_MP3_Air
Diff: features/cellular/framework/AT/ATHandler.h
- Revision:
- 0:5b88d5760320
- Child:
- 1:9db0e321a9f4
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/features/cellular/framework/AT/ATHandler.h Tue Dec 17 23:23:45 2019 +0000 @@ -0,0 +1,640 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AT_HANDLER_H_ +#define AT_HANDLER_H_ + +#include "platform/mbed_retarget.h" + +#include "events/EventQueue.h" +#include "PlatformMutex.h" +#include "nsapi_types.h" + +#include "Callback.h" + +#include <cstdarg> + +namespace mbed { + +class FileHandle; + +/** + * If application calls associated FileHandle only from single thread context + * then locking between AT command and response is not needed. However, + * note that many cellular functions are called indirectly, for example with the socket API. + * If you are unsure, then AT_HANDLER_MUTEX must be defined. + */ +#define AT_HANDLER_MUTEX + +extern const char *OK; +extern const char *CRLF; + +#define BUFF_SIZE 32 + +/* AT Error types enumeration */ +enum DeviceErrorType { + DeviceErrorTypeNoError = 0, + DeviceErrorTypeError, // AT ERROR + DeviceErrorTypeErrorCMS, // AT ERROR CMS + DeviceErrorTypeErrorCME // AT ERROR CME +}; + +/** AT response error with error code and type */ +struct device_err_t { + DeviceErrorType errType; + int errCode; +}; + +/// Class for sending AT commands and parsing AT responses. +class ATHandler { + +public: + /** Constructor + * + * @param fh file handle used for reading AT responses and writing AT commands + * @param queue Event queue used to transfer sigio events to this thread + * @param timeout Timeout when reading for AT response + * @param output_delimiter delimiter used when parsing at responses, "\r" should be used as output_delimiter + * @param send_delay the minimum delay in ms between the end of last response and the beginning of a new command + */ + ATHandler(FileHandle *fh, events::EventQueue &queue, uint32_t timeout, const char *output_delimiter, uint16_t send_delay = 0); + virtual ~ATHandler(); + + /** Return used file handle. + * + * @return used file handle + */ + FileHandle *get_file_handle(); + + /** Get a new ATHandler instance, and update the linked list. Once the use of the ATHandler + * has finished, call to close() has to be made + * + * @param fileHandle filehandle used for reading AT responses and writing AT commands. + * If there is already an ATHandler with the same fileHandle pointer, + * then a pointer to that ATHandler instance will be returned with + * that ATHandler's queue, timeout, delimiter, send_delay and debug_on + * values + * @param queue Event queue used to transfer sigio events to this thread + * @param timeout Timeout when reading for AT response + * @param delimiter delimiter used when parsing at responses, "\r" should be used as output_delimiter + * @param send_delay the minimum delay in ms between the end of last response and the beginning of a new command + * @param debug_on Set true to enable debug traces + * @return NULL, if fileHandle is not set, or a pointer to an existing ATHandler, if the fileHandle is + * already in use. Otherwise a pointer to a new ATHandler instance is returned + */ + static ATHandler *get_instance(FileHandle *fileHandle, events::EventQueue &queue, uint32_t timeout, + const char *delimiter, uint16_t send_delay, bool debug_on); + + /** Close and delete the current ATHandler instance, if the reference count to it is 0. + * Close() can be only called, if the ATHandler was opened with get_instance() + * + * @return NSAPI_ERROR_OK on success, NSAPI_ERROR_PARAMETER on failure + */ + nsapi_error_t close(); + + /** Locks the mutex for file handle if AT_HANDLER_MUTEX is defined. + */ + void lock(); + + /** Unlocks the mutex for file handle if AT_HANDLER_MUTEX is defined. + */ + void unlock(); + + /** Locks the mutex for file handle if AT_HANDLER_MUTEX is defined and returns the last error. + * + * @return last error that happened when parsing AT responses + */ + nsapi_error_t unlock_return_error(); + + /** Set callback function for URC + * + * @param prefix URC text to look for, e.g. "+CMTI:". Maximum length is BUFF_SIZE. + * @param callback function to call on prefix, or 0 to remove callback + */ + void set_urc_handler(const char *prefix, Callback<void()> callback); + + ATHandler *_nextATHandler; // linked list + + /** returns the last error while parsing AT responses. + * + * @return last error + */ + nsapi_error_t get_last_error() const; + + /** returns the last device error while parsing AT responses. Actually AT error (CME/CMS). + * + * @return last error struct device_err_t + */ + device_err_t get_last_device_error() const; + + /** Increase reference count. Used for counting references to this instance. + * Note that this should be used with care, if the ATHandler was taken into use + * with get_instance() + */ + void inc_ref_count(); + + /** Decrease reference count. Used for counting references to this instance. + * Note that this should be used with care, if the ATHandler was taken into use + * with get_instance() + */ + void dec_ref_count(); + + /** Get the current reference count. Used for counting references to this instance. + * + * @return current reference count + */ + int get_ref_count(); + + /** Set timeout in milliseconds for AT commands + * + * @param timeout_milliseconds Timeout in milliseconds + * @param default_timeout Store as default timeout + */ + void set_at_timeout(uint32_t timeout_milliseconds, bool default_timeout = false); + + /** Set timeout in milliseconds for all ATHandlers in the _atHandlers list + * + * @param timeout_milliseconds Timeout in milliseconds + * @param default_timeout Store as default timeout + */ + static void set_at_timeout_list(uint32_t timeout_milliseconds, bool default_timeout = false); + + /** Restore timeout to previous timeout. Handy if there is a need to change timeout temporarily. + */ + void restore_at_timeout(); + + /** Clear pending error flag. By default, error is cleared only in lock(). + */ + void clear_error(); + + /** + * Flushes the underlying stream + */ + void flush(); + + /** Tries to find oob's from the AT response. Call the urc callback if one is found. + */ + void process_oob(); + + /** Set file handle, which is used for reading AT responses and writing AT commands + * + * @param fh file handle used for reading AT responses and writing AT commands + */ + void set_file_handle(FileHandle *fh); + + /** Set is file handle usable. Some situations like after going to data mode, file handle is not usable anymore. + * Any items in queue are not to be processed. + * + * @param usable true for usable filehandle + */ + void set_is_filehandle_usable(bool usable); + + /** Synchronize AT command and response handling to modem. + * + * @param timeout_ms ATHandler timeout when trying to sync. Will be restored when function returns. + * @return true is synchronization was successful, false in case of failure + */ + bool sync(int timeout_ms); + +protected: + void event(); +#ifdef AT_HANDLER_MUTEX + PlatformMutex _fileHandleMutex; +#endif + FileHandle *_fileHandle; +private: + /** Remove urc handler from linked list of urc's + * + * @param prefix Register urc prefix for callback. Urc could be for example "+CMTI: " + */ + void remove_urc_handler(const char *prefix); + + void set_error(nsapi_error_t err); + + events::EventQueue &_queue; + nsapi_error_t _last_err; + int _last_3gpp_error; + device_err_t _last_at_err; + uint16_t _oob_string_max_length; + char *_output_delimiter; + + struct oob_t { + const char *prefix; + int prefix_len; + Callback<void()> cb; + oob_t *next; + }; + oob_t *_oobs; + uint32_t _at_timeout; + uint32_t _previous_at_timeout; + + uint16_t _at_send_delay; + uint64_t _last_response_stop; + + bool _oob_queued; + int32_t _ref_count; + bool _is_fh_usable; + + static ATHandler *_atHandlers; + + //************************************* +public: + + /** Starts the command writing by clearing the last error and writing the given command. + * In case of failure when writing, the last error is set to NSAPI_ERROR_DEVICE_ERROR. + * + * @param cmd AT command to be written to modem + */ + virtual void cmd_start(const char *cmd); + + /** + * @brief cmd_start_stop Starts an AT command, writes given variadic arguments and stops the command. Use this + * command when you need multiple response parameters to be handled. + * NOTE: Does not lock ATHandler for process! + * + * @param cmd AT command in form +<CMD> (will be used also in response reading, no extra chars allowed) + * @param cmd_chr Char to be added to specific AT command: '?', '=' or ''. Will be used as such so '=1' is valid as well. + * @param format Format string for variadic arguments to be added to AT command; No separator needed. + * Use %d for integer, %s for string and %b for byte string (requires 2 arguments: string and length) + */ + void cmd_start_stop(const char *cmd, const char *cmd_chr, const char *format = "", ...); + + /** + * @brief at_cmd_str Send an AT command and read a single string response. Locks and unlocks ATHandler for operation + * @param cmd AT command in form +<CMD> (will be used also in response reading, no extra chars allowed) + * @param cmd_chr Char to be added to specific AT command: '?', '=' or ''. Will be used as such so '=1' is valid as well. + * @param resp_buf Response buffer + * @param resp_buf_size Response buffer size + * @param format Format string for variadic arguments to be added to AT command; No separator needed. + * Use %d for integer, %s for string and %b for byte string (requires 2 arguments: string and length) + * @return last error that happened when parsing AT responses + */ + nsapi_error_t at_cmd_str(const char *cmd, const char *cmd_chr, char *resp_buf, size_t resp_buf_size, const char *format = "", ...); + + /** + * @brief at_cmd_int Send an AT command and read a single integer response. Locks and unlocks ATHandler for operation + * @param cmd AT command in form +<CMD> (will be used also in response reading, no extra chars allowed) + * @param cmd_chr Char to be added to specific AT command: '?', '=' or ''. Will be used as such so '=1' is valid as well. + * @param resp Integer to hold response + * @param format Format string for variadic arguments to be added to AT command; No separator needed. + * Use %d for integer, %s for string and %b for byte string (requires 2 arguments: string and length) + * @return last error that happened when parsing AT responses + */ + nsapi_error_t at_cmd_int(const char *cmd, const char *cmd_chr, int &resp, const char *format = "", ...); + + /** + * @brief at_cmd_discard Send an AT command and read and discard a response. Locks and unlocks ATHandler for operation + * @param cmd AT command in form +<CMD> (will be used also in response reading, no extra chars allowed) + * @param cmd_chr Char to be added to specific AT command: '?', '=' or ''. Will be used as such so '=1' is valid as well. + * @param format Format string for variadic arguments to be added to AT command; No separator needed. + * Use %d for integer, %s for string and %b for byte string (requires 2 arguments: string and length) + * @return last error that happened when parsing AT responses + */ + nsapi_error_t at_cmd_discard(const char *cmd, const char *cmd_chr, const char *format = "", ...); + +public: + + /** Writes integer type AT command subparameter. Starts with the delimiter if not the first param after cmd_start. + * In case of failure when writing, the last error is set to NSAPI_ERROR_DEVICE_ERROR. + * + * @param param int to be written to modem as AT command subparameter + */ + void write_int(int32_t param); + + /** Writes string type AT command subparamater. Quotes are added to surround the given string. + * Starts with the delimiter if not the first param after cmd_start. + * In case of failure when writing, the last error is set to NSAPI_ERROR_DEVICE_ERROR. + * + * @param param string to be written to modem as AT command subparameter + * @param useQuotations flag indicating whether the string should be included in quotation marks + */ + void write_string(const char *param, bool useQuotations = true); + + /** Stops the AT command by writing command-line terminator CR to mark command as finished. + */ + void cmd_stop(); + + /** Stops the AT command by writing command-line terminator CR to mark command as finished and reads the OK/ERROR response. + * + */ + void cmd_stop_read_resp(); + + /** Write bytes without any subparameter delimiters, such as comma. + * In case of failure when writing, the last error is set to NSAPI_ERROR_DEVICE_ERROR. + * + * @param data bytes to be written to modem + * @param len length of data string + * + * @return number of characters successfully written + */ + size_t write_bytes(const uint8_t *data, size_t len); + + /** Sets the stop tag for the current scope (response/information response/element) + * Parameter's reading routines will stop the reading when such tag is found and will set the found flag. + * Consume routines will read everything until such tag is found. + * + * @param stop_tag_seq string to be set as stop tag + */ + void set_stop_tag(const char *stop_tag_seq); + + /** Sets the delimiter between parameters or between elements of the information response. + * Parameter's reading routines will stop when such char is read. + * + * @param delimiter char to be set as _delimiter + */ + void set_delimiter(char delimiter); + + /** Sets the delimiter to default value defined by DEFAULT_DELIMITER. + */ + void set_default_delimiter(); + + /** Defines behaviour for using or ignoring the delimiter within an AT command + * + * @param use_delimiter indicating if delimiter should be used or not + */ + void use_delimiter(bool use_delimiter); + + /** Consumes the reading buffer up to the delimiter or stop_tag + * + * @param count number of parameters to be skipped + */ + void skip_param(uint32_t count = 1); + + /** Consumes the given length from the reading buffer + * + * @param len length to be consumed from reading buffer + * @param count number of parameters to be skipped + */ + void skip_param(ssize_t len, uint32_t count); + + /** Reads given number of bytes from receiving buffer without checking any subparameter delimiters, such as comma. + * + * @param buf output buffer for the read + * @param len maximum number of bytes to read + * @return number of successfully read bytes or -1 in case of error + */ + ssize_t read_bytes(uint8_t *buf, size_t len); + + /** Reads chars from reading buffer. Terminates with null. Skips the quotation marks. + * Stops on delimiter or stop tag. + * + * @param str output buffer for the read + * @param size maximum number of chars to output including NULL + * @param read_even_stop_tag if true then try to read even if the stop tag was found previously + * @return length of output string or -1 in case of read timeout before delimiter or stop tag is found + */ + ssize_t read_string(char *str, size_t size, bool read_even_stop_tag = false); + + /** Reads chars representing hex ascii values and converts them to the corresponding chars. + * For example: "4156" to "AV". + * Terminates with null. Skips the quotation marks. + * Stops on delimiter or stop tag. + * + * @param str output buffer for the read + * @param size maximum number of chars to output + * @return length of output string or -1 in case of read timeout before delimiter or stop tag is found + */ + ssize_t read_hex_string(char *str, size_t size); + + /** Reads as string and converts result to integer. Supports only positive integers. + * + * @return the positive integer or -1 in case of error. + */ + int32_t read_int(); + + /** This looks for necessary matches: prefix, OK, ERROR, URCs and sets the correct scope. + * + * @param prefix string to be matched from receiving buffer. If not NULL and match succeeds, then scope + * will be set as information response(info_type) + * @param stop flag to indicate if we go to information response scope or not. + * (needed when nothing is expected to be received anymore after the prefix match: + * sms case: "> ", bc95 reboot case) + */ + void resp_start(const char *prefix = NULL, bool stop = false); + + /** Ends all scopes starting from current scope. + * Consumes everything until the scope's stop tag is found, then + * goes to next scope until response scope is ending. + * URC match is checked during response scope ending, + * for every new line / CRLF. + * + * + * Possible sequence: + * element scope -> information response scope -> response scope + */ + void resp_stop(); + + /** Looks for matching the prefix given to resp_start() call. + * If needed, it ends the scope of a previous information response. + * Sets the information response scope if new prefix is found and response scope if prefix is not found. + * + * @return true if prefix defined for information response is not empty string and is found, + * false otherwise. + */ + bool info_resp(); + + /** Looks for matching the start tag. + * If needed, it ends the scope of a previous element. + * Sets the element scope if start tag is found and information response scope if start tag is not found. + * + * @param start_tag tag to be matched to begin parsing an element of an information response + * @return true if new element is found, false otherwise + */ + bool info_elem(char start_tag); + + /** Consumes the received content until current stop tag is found. + * + * @return true if stop tag is found, false otherwise + */ + bool consume_to_stop_tag(); + + /** Return the last 3GPP error code. + * @return last 3GPP error code + */ + int get_3gpp_error(); + +public: // just for debugging + /** + * AT debugging, when enabled will print all data read and written, + * non-printable chars are printed as "[%d]". + * + * AT debug can be enabled at compile time using MBED_CONF_CELLULAR_DEBUG_AT flag or at runtime + * calling set_debug(). Note that MBED_CONF_MBED_TRACE_ENABLE must also be enabled. + * + * @param debug_on Enable/disable debugging + */ + void set_debug(bool debug_on); + + /** + * Get degug state set by @ref set_debug + * + * @return current state of debug + */ + bool get_debug() const; + + /** Set debug_on for all ATHandlers in the _atHandlers list + * + * @param debug_on Set true to enable debug traces + */ + static void set_debug_list(bool debug_on); + +private: + + // should fit any prefix and int + char _recv_buff[BUFF_SIZE]; + // reading position + size_t _recv_len; + // reading length + size_t _recv_pos; + + // resp_type: the part of the response that doesn't include the information response (+CMD1,+CMD2..) + // ends with OK or (CME)(CMS)ERROR + // info_type: the information response part of the response: starts with +CMD1 and ends with CRLF + // information response contains parameters or subsets of parameters (elements), both separated by comma + // elem_type: subsets of parameters that are part of information response, its parameters are separated by comma + enum ScopeType {RespType, InfoType, ElemType, NotSet}; + void set_scope(ScopeType scope_type); + ScopeType _current_scope; + + struct tag_t { + char tag[7]; + size_t len; + bool found; + }; + + // tag to stop response scope + tag_t _resp_stop; + // tag to stop information response scope + tag_t _info_stop; + // tag to stop element scope + tag_t _elem_stop; + // reference to the stop tag of current scope (resp/info/elem) + tag_t *_stop_tag; + + // delimiter between parameters and also used for delimiting elements of information response + char _delimiter; + // set true on prefix match -> indicates start of an information response or of an element + bool _prefix_matched; + // set true on urc match + bool _urc_matched; + // set true on (CME)(CMS)ERROR + bool _error_found; + // Max length of OK,(CME)(CMS)ERROR and URCs + size_t _max_resp_length; + + // prefix set during resp_start and used to try matching possible information responses + char _info_resp_prefix[BUFF_SIZE]; + bool _debug_on; + bool _cmd_start; + bool _use_delimiter; + + // time when a command or an URC processing was started + uint64_t _start_time; + + char _cmd_buffer[BUFF_SIZE]; + +private: + //Handles the arguments from given variadic list + void handle_args(const char *format, std::va_list list); + + //Starts an AT command based on given parameters + void handle_start(const char *cmd, const char *cmd_chr); + + //Checks that ATHandler does not have a pending error condition and filehandle is usable + bool ok_to_proceed(); + +private: + // Gets char from receiving buffer. + // Resets and fills the buffer if all are already read (receiving position equals receiving length). + // Returns a next char or -1 on failure (also sets error flag) + int get_char(); + // Sets to 0 the reading position, reading length and the whole buffer content. + void reset_buffer(); + // Reading position set to 0 and buffer's unread content moved to beginning + void rewind_buffer(); + // Calculate remaining time for polling based on request start time and AT timeout. + // Returns 0 or time in ms for polling. + int poll_timeout(bool wait_for_timeout = true); + // Reads from serial to receiving buffer. + // Returns true on successful read OR false on timeout. + bool fill_buffer(bool wait_for_timeout = true); + + void set_tag(tag_t *tag_dest, const char *tag_seq); + + // Rewinds the receiving buffer and compares it against given str. + bool match(const char *str, size_t size); + // Iterates URCs and checks if they match the receiving buffer content. + // If URC match sets the scope to information response and after urc's cb returns + // finishes the information response scope(consumes to CRLF). + bool match_urc(); + // Checks if any of the error strings are matching the receiving buffer content. + bool match_error(); + // Checks if current char in buffer matches ch and consumes it, + // if no match lets the buffer unchanged. + bool consume_char(char ch); + // Consumes the received content until tag is found. + // Consumes the tag only if consume_tag flag is true. + bool consume_to_tag(const char *tag, bool consume_tag); + // Checks if receiving buffer contains OK, ERROR, URC or given prefix. + void resp(const char *prefix, bool check_urc); + + + ScopeType get_scope(); + + // Consumes to information response stop tag which is CRLF. Sets scope to response. + void information_response_stop(); + // Consumes to element stop tag. Sets scope to information response + void information_response_element_stop(); + + // Reads the error code if expected and sets it as last error. + void at_error(bool error_code, DeviceErrorType error_type); + + /** Convert AT error code to 3GPP error codes + * @param err AT error code read from CME/CMS ERROR responses + * @param error_type error type (CMS/CME/ERROR) + */ + void set_3gpp_error(int err, DeviceErrorType error_type); + + bool check_cmd_send(); + size_t write(const void *data, size_t len); + + /** Finds occurrence of one char buffer inside another char buffer. + * + * @param dest destination char buffer + * @param dest_len length of dest + * @param src string to be searched for + * @param src_len length of string to be searched for + * + * @return pointer to first occurrence of src in dest + */ + const char *mem_str(const char *dest, size_t dest_len, const char *src, size_t src_len); + + // check is urc is already added + bool find_urc_handler(const char *prefix); + + // print contents of a buffer to trace log + enum ATType { + AT_ERR, + AT_RX, + AT_TX + }; + void debug_print(const char *p, int len, ATType type); +}; + +} // namespace mbed + +#endif //AT_HANDLER_H_