OS  v7.3.3
Documentation
AT Command Line Interface (CLI)

Overview

A command-line interface (CLI) is a way to interact directly with the software of an embedded system in the form of text commands and responses. It can be seen as a typed set of commands to produce a result, but here, the commands are typed in real-time by a user through a specific interface, for example, UART, USB, LAN, etc.

A CLI is often developed to aid initial driver development and debugging. This CLI might become the interface (or one of the interfaces) used by a sophisticated end-user to interact with the product. Think of typing commands to control a machine, or perhaps for low-level access to the control system as a development tool, tweaking time-constants and monitoring low-level system performance during testing.

The components of the CLI

The provided development API parses and handles input commands, following a simplified form of the extended AT-commands syntax.

atcli
AT CLI implementation

As seen in the figure, the CLI has a few components described below:

  • Input Handler : It is responsible for collecting incoming data from the input in the form of ASCII characters inside a buffer. When this buffer is ready by receiving an EOL(End-Of-Line) byte, it notifies the validator to perform the initial checks.
  • Validator : Take the input string and perform three checks over it:
    1. The input matches one of the subscribed commands.
    2. The input matches one of the default commands.
    3. The input is unknown
  • Pre-Parser : Takes the input if the validator asserts the first check. It is responsible for syntax validation and classification. Also, prepares the input argument for the next component.
  • Callback or Post-Parser : If input at the pre-parser is valid, the respective command callback is invoked. Here, the application writer is free to handle the command execution and the output response.
  • Output printer : Takes all the return status of the previous components to print out a response at the output.
Remarks
Here, Input and Output should be provided by the application writer, for example, if a UART interface is chosen, the input should take the received bytes from an ISR and the output is a function to print out a single byte.

Supported syntax

The syntax is straightforward and the rules are provided below:

  • All command lines must start with AT and end with an EOL character. By default, the CLI uses the carriage return character. (We will use CR to represent a carriage return character in this document).
  • AT commands are case-insensitive
  • Only four types of AT commands are allowed:
    • Acting (qATCLI_CMDTYPE_ACT) : This is the simplest type of commands that can be subscribed. It's normally used to execute the action that the command should do. This type does not take arguments or modifiers, for example : AT+CMD
    • Read (qATCLI_CMDTYPE_READ) : This type of command allows you to read or test a value already configured for the specified parameter. Only one argument is allowed. for example "AT+CMD?" or "AT+CMD?PARAM1"
    • Test (qATCLI_CMDTYPE_TEST) : These types of commands allow you to get the values that can be set for its parameters. No parameters are allowed here. Example "AT+CMD=?"
    • Parameter Set (qATCLI_CMDTYPE_PARA) : These types of commands allow n arguments to be passed for setting parameters, for example: AT+CMD=x,y If none of the types is given at the input, the command response will be ERROR
  • The possible output responses are:
    • OK: Indicates the successful execution of the command.
    • ERROR: A generalized message to indicate failure in executing the command.
    • UNKNOWN : The input command is not subscribed.
    • NOT ALLOWED : The command syntax is not one of the allowed types.
    • User-defined: A custom output message defined by the application writer.
    • NONE : No response.

All responses are followed by a CR LF

Errors generated during the execution of these AT commands could be due to the following reasons:

  • Incorrect syntax/parameters of the AT command
  • Bad parameters or not allowed operations defined by the application writer.

In case of an error, the string ERROR or "ERROR:<error_no>" are displayed.

Setting up an AT-CLI instance

Before starting the CLI development, the corresponding instance must be defined; a data structure of type qATCLI_t. The instance should be initialized using the qATCLI_Setup() API.

Subscribing commands to the parser

The AT CLI is able to subscribe to any number of custom AT commands. For this, the qATCLI_CmdSubscribe() API should be used.

This function subscribes the CLI instance to a specific command with an associated callback function, so that the next time the required command is sent to the CLI input, the callback function will be executed. The CLI parser only analyzes commands that follow the simplified AT-Commands syntax already described.

Writing a command callback

The command callback should be coded by the application writer. Here, the following prototype should be used:

/* TODO : The command callback */
}
qATCLI_Response_t
an enumeration to define the possible values that can be returned from the callback of a command.
Definition: qatcli.h:31
The command argument with all the regarding information of the incoming AT command.
Definition: qatcli.h:99

The callback takes one argument of type qATCLI_Handler_t and returns a single value. The input argument it's just a pointer to public data of the CLI instance where the command it subscribed to. From the callback context, can be used to print out extra information as a command response, parse the command parameters, and query properties with crucial information about the detected command, like the type, the number of arguments, and the subsequent string after the command text. To see more details please check the qATCLI_Handler_t struct reference.

The return value (an enum of type qATCLI_Response_t) determines the response shown by the Output printer component. The possible allowed values are:

  • qATCLI_OK : as expected, print out the OK string.
  • qATCLI_ERROR : as expected, print out the ERROR string.
  • qATCLI_ERROR_CODE(no) : Used to indicate an error code. This code is defined by the application writer and should be a value between 1 and 32766. For example, a return value of QATCLI_ERROR_CODE(15), will print out the string ERROR:15.
  • qATCLI_NORESPONSE : No response will be printed out.

A simple example of how the command callback should be coded is shown below:

int arg1 = 0;
float arg2 = 0;
/*check the command-type*/
switch ( h->Type ) {
if ( h->NumArgs > 0 ) {
arg1 = h->GetArgInt( 1 ); /*get the first argument as integer*/
if ( h->NumArgs > 1 ) {
arg2 = h->GetArgFlt( 2 ); /*get the second argument as float*/
sprintf( h->Output, "arg1 = %d arg2 = %f", arg1, arg2 );
Response = qATCLI_NORESPONSE;
}
else {
Response = qATCLI_ERROR;
}
break;
h->puts( "inmediate message" );
Response = qATCLI_OK;
break;
strcpy( h->Output , "Test message after the callback" );
Response = qATCLI_OK;
break;
Response = qATCLI_OK;
break;
default:
Response = qATCLI_ERROR;
break;
}
return Response;
}
@ qATCLI_CMDTYPE_TEST
Definition: qatcli.h:81
@ qATCLI_CMDTYPE_ACT
Definition: qatcli.h:83
@ qATCLI_CMDTYPE_PARA
Definition: qatcli.h:80
@ qATCLI_CMDTYPE_READ
Definition: qatcli.h:82
@ qATCLI_OK
Definition: qatcli.h:35
@ qATCLI_ERROR
Definition: qatcli.h:32
@ qATCLI_NORESPONSE
Definition: qatcli.h:34
int(* GetArgInt)(qIndex_t n)
Helper method to get the n argument parsed as integer from the incoming AT command.
Definition: qatcli.h:124
char * Output
The CLI output buffer. Can be written by the user.
Definition: qatcli.h:169
size_t NumArgs
Number of arguments, only available if Type = qATCLI_CMDTYPE_PARA.
Definition: qatcli.h:181
qFloat32_t(* GetArgFlt)(qIndex_t n)
Helper method to get the n argument parsed as float from the incoming AT command.
Definition: qatcli.h:133
qATCLI_CommandType_t Type
The incoming command type. *.
Definition: qatcli.h:185
void(* puts)(const char *s)
Writes a string to CLI output without the EOF string appended at the end.
Definition: qatcli.h:165

Handling the input

Input handling is simplified using the provided APIs. The qATCLI_ISRHandler() and qATCLI_ISRHandlerBlock() functions are intended to be used from the interrupt context. This avoids any kind of polling implementation and allows the CLI application to be designed using an event-driven pattern.

Both functions feed the parser input, the first one with a single character and the second with a string. The application writer should call one of these functions from the desired hardware interface, for example, from a UART receive ISR.

If there is no intention to feed the input from the ISR context, the APIs qATCLI_Raise() or qATCLI_Exec() can be called at demand from the base context. As expected, both functions send the string to the specified CLI. The difference between both APIs is that qATCLI_Raise() sends the command through the input, marking it as ready for parsing and acting as the Input handler component.

The qATCLI_Exec(), on the other hand, executes the components of Pre-parsing and Postparsing bypassing the other components, including the Output printer, so that it must be handled by the application writer.

Note
All functions involved with the component Input-handler, ignores non-graphic characters and cast any uppercase to lowercase.

Running the CLI parser

The parser can be invoked directly using the qATCLI_Run() API. Almost all the components that make up the CLI are performed by this API, except for the Input Handler, which should be managed by the application writer itself.

In this way, the writer of the application must implement the logic that leads this function to be called when the input-ready condition is given.

The simple approach for this is to check the return value of any of the input feeder APIs and set a notification variable when they report a ready input. Later in the base context, a polling job should be performed over this notification variable, running the parser when their value is true, then clearing the value after to avoid unnecessary overhead.

The recommended implementation is to leave this job handled by a task instead of coding the logic to know when the CLI should run. For this, the qOS_Add_ATCLITask() is provided. This API adds a task to the scheduling scheme running an AT Command Line Interface and is treated as an event-triggered task. The address of the parser instance will be stored in the TaskData storage-Pointer.

After invoked, both CLI and task are linked together in such a way that when an input-ready condition is given, a notification event is sent to the task launching the CLI components. As the task is event-triggered, there is no additional overhead and the writer of the application can assign a priority value to balance the application against other tasks in the scheduling scheme.

A CLI example

The following example demonstrates the usage of a simple command-line interface using the UART peripheral with two subscribed commands :

  • A command to write and read the state of a GPIO pin "at+gpio".
  • A command to retrieve the compilation timestamp "at+info". First, let's get started defining the required objects to set up the CLI instance:

    #define CLI_MAX_INPUT_BUFF_SIZE ( 128 )
    #define CLI_MAX_OUTPUT_BUFF_SIZE ( 128 )
    qTask_t CLI_Task;
    qATCLI_t CLI_Object;
    qATCLI_Command_t AT_GPIO, AT_INFO;
    char CLI_Input[ AT_CLI_MAX_INPUT_BUFF_SIZE ];
    char CLI_Output[ AT_CLI_MAX_OUTPUT_BUFF_SIZE ];
    /*Command callbacks*/
    qATCLI_Response_t AT_GPIO_Callback( qATCLI_Handler_t h );
    qATCLI_Response_t AT_INFO_Callback( qATCLI_Handler_t h );
    An AT-Command object.
    Definition: qatcli.h:255
    An AT Command Line Interface (CLI) object.
    Definition: qatcli.h:202
    A task node object.
    Definition: qtasks.h:268

Then the CLI instance is configured by subscribing commands and adding the task to the OS. A wrapper function is required here to make the UART output-function compatible with the CLI API.

void CLI_OutputChar_Wrapper( void *sp, const char c ) { /*CLI output function*/
(void)sp; /*unused*/
HAL_UART_WriteChar( UART1, c );
}
/*==================================================================*/
int main( void ) {
HAL_Setup();
qOS_Setup( HAL_GetTick, TIMER_TICK, NULL );
qATCLI_Setup( &CLI_Object, BSP_UART_PUTC, CLI_Input, sizeof(CLI_Input),
CLI_Output, sizeof(CLI_Output) );
qATCLI_CmdSubscribe( &CLI_Object, &AT_GPIO, "at+gpio", AT_GPIO_Callback,
QATCLI_CMDTYPE_ACT | QATCLI_CMDTYPE_READ |
QATCLI_CMDTYPE_TEST | QATCLI_CMDTYPE_PARA | 0x22, NULL );
qATCLI_CmdSubscribe( &CLI_Object, &AT_INFO, "at+info", AT_INFO_Callback,
QATCLI_CMDTYPE_ACT, NULL );
qOS_Add_ATCLITask( &CLI_Task, &CLI_Object, qLowest_Priority );
return 0;
}
qBool_t qATCLI_Setup(qATCLI_t *const cli, const qPutChar_t outFcn, char *pInput, const size_t sizeInput, char *pOutput, const size_t sizeOutput)
Setup an instance of the AT Command Line Interface.
Definition: qatcli.c:112
qBool_t qATCLI_CmdSubscribe(qATCLI_t *const cli, qATCLI_Command_t *const cmd, char *textCommand, const qATCLI_CommandCallback_t cFcn, qATCLI_Options_t cmdOpt, void *param)
This function subscribes the CLI instance to a specific command with an associated Callback function,...
Definition: qatcli.c:154
#define qLowest_Priority
A macro directive to indicate the lowest priority level.
Definition: qkernel.h:71
qBool_t qOS_Run(void)
Executes the scheduling scheme. It must be called once after the task pool has been defined.
Definition: qkernel.c:676
qBool_t qOS_Setup(const qGetTickFcn_t tFcn, const qTimingBase_t t, qTaskFcn_t idleCallback)
Task Scheduler Setup. This function is required and must be called once in the application main threa...
Definition: qkernel.c:126
qBool_t qOS_Add_ATCLITask(qTask_t *const Task, qATCLI_t *cli, const qPriority_t p, void *arg)
Add a task to the scheduling scheme running an AT Command Line Interface. Task will be scheduled as e...
Definition: qkernel.c:541

The CLI input is feeded from the interrupt context by using the UART receive ISR:

void interrupt HAL_UART_RxInterrupt( void ) {
char received;
received = HAL_HUART_GetChar( UART1 );
qATCLI_ISRHandler( &CLI_Object, received ); /*Feed the CLI input*/
}
qBool_t qATCLI_ISRHandler(qATCLI_t *const cli, const char c)
Feed the CLI input with a single character. This call is mandatory from an interrupt context....
Definition: qatcli.c:222

Finally, the command callbacks are later defined to perform the requested operations.

qATCLI_Response_t AT_GPIO_Callback( qATCLI_Handler_t h ) {
int pin, value;
switch ( h->Type ) {
case qATCLI_CMDTYPE_ACT: /*< AT+gpio */
RetValue = qATCLI_OK;
break;
case qATCLI_CMDTYPE_TEST: /*< AT+gpio=? */
h->puts( "+gpio=<pin>,<value>\r\n" );
h->puts( "+gpio?\r\n" );
RetValue = qATCLI_NORESPONSE;
break;
case qATCLI_CMDTYPE_READ: /*< AT+gpio? */
sprintf( h->Output, "0x%08X", HAL_GPIO_Read( GPIOA ) );
RetValue = qATCLI_NORESPONSE;
break;
case qATCLI_CMDTYPE_PARA: /*< AT+gpio=<pin>,<value> */
pin = h->GetArgInt( 1 );
value = h->GetArgInt( 2 );
HAL_GPIO_WRITE( GPIOA, pin, value );
RetValue = qATCLI_OK;
break;
default : break;
}
return RetValue;
}
/*==================================================================*/
qATCLI_Response_t AT_INFO_Callback( qATCLI_Handler_t h ) {
switch ( param->Type ) {
case qATCLI_CMDTYPE_ACT: /*< AT+info */
strcpy( h->Output, "Compilation: " __DATE__ " " __TIME__ );
RetValue = qATCLI_NORESPONSE;
break;
default :
break;
}
return RetValue;
}