FreeRTOS+CLI. Реализация интерфейса командной строки используя FreeRTOSPlus-CLI.
Большой эксперимент с библиотекой FreeRTOS Plus CLI. Включение её в проект, настройка и использование. Даже некоторые модификации для расширения функционала.
Всё-таки нашел время для изучения, и даже перевел на эту библиотеку текущий проект. Выявил несколько замечаний в коде бибилиотеки и немного модернизировал.
Если интересно, как запустить этот модуль, использовать и расширить функционал - заметка для вас.
Для начала необходимо подключить к проекту файл FreeRTOS_CLI.c
, его можно найти в папке FreeRTOSv10.2.1\FreeRTOS-Plus\Source\FreeRTOS-Plus-CLI\
, заголовочный файл тоже нужен, поэтому его также копируем в папку со своим проектом.
Теперь самое интересное, как всё это дело использовать. Для начала нужно создать задачу RTOS для работы с CLI. Выглядит она примерно так. Обратите внимание на значение cmdMAX_INPUT_SIZE
и тип переменной cInputIndex
, они должны соответствовать.
/* Dimensions the buffer into which input characters are placed. */
#define cmdMAX_INPUT_SIZE 60
/* Dimensions the buffer into which string outputs can be placed. */
#define cmdMAX_OUTPUT_SIZE 4096
void vCommandConsoleTask( void *pvParameters )
{
char cRxedChar = 0;
uint8_t cInputIndex = 0;
BaseType_t xMoreDataToFollow = 0;
static char pcOutputString[ cmdMAX_OUTPUT_SIZE ];
static char pcInputString[ cmdMAX_INPUT_SIZE ];
for( ;; )
{
cRxedChar = (char)getchar();
if( cRxedChar == '\r' )
{
printf("\r\n");
do
{
xMoreDataToFollow = FreeRTOS_CLIProcessCommand( pcInputString, pcOutputString, cmdMAX_OUTPUT_SIZE );
printf("%s", pcOutputString);
MemSet( pcOutputString, 0x00, cmdMAX_OUTPUT_SIZE );
}
while( xMoreDataToFollow != pdFALSE );
cInputIndex = 0;
MemSet( pcInputString, 0x00, cmdMAX_INPUT_SIZE );
}
else
{
if( cRxedChar == '\b' )
{
if( cInputIndex > 0 )
{
cInputIndex--;
pcInputString[ cInputIndex ] = '\0';
}
}
else
{
if( cInputIndex < cmdMAX_INPUT_SIZE )
{
pcInputString[ cInputIndex ] = cRxedChar;
cInputIndex++;
}
}
}
}
}
Изначанально строка if( cRxedChar == '\r' )
выглядела как if( cRxedChar == '\n' )
, но putty при нажатии Enter отправляет именно '\r'
, что не удивительно, ведь Enter это и есть перевод каретки, ну да ладно...
Что бы узнать как завести getchar()
смотрите заметку про printf
Теперь нам необходимо объявить структуры описывающие команды, но прежде расскажу о модификации, в файле FreeRTOS_CLI.h
меняем описание типа-структуры CLI_Command_Definition_t
на такое:
typedef struct xCOMMAND_LINE_INPUT
{
const char * const pcCommand;
const char * const pcDescriptionString;
const char * const pcHelpString;
const pdCOMMAND_LINE_CALLBACK pxCommandInterpreter;
int8_t cExpectedNumberOfParameters;
} CLI_Command_Definition_t;
Я добавил строку pcDescriptionString
, так как мне показалось неудобным хранить помощь о команде и краткое описание в одной строке. Почему же мне было не удобно? Дело в том, что изначальная реализация команды help
выводит значение pcHelpString
всех зарегистрированных команд и когда много команд с большим количеством разнообразных параметром, то вывод всех команд может не поместить на экран консоли, что очень неудобно.
После правки типа-структуры не забываем исправить объявление структуры для команды help
- FreeRTOS_CLI.c:68
, добавив ещё одну строку и изменив последний параметр 1
на -1
, далее расскажу для чего.
Теперь пришло время разобраться с этой командой. Изначально у неё нет никакого другого функционала кроме как вывести список всех команд, вернее список всех хелпов команд - нам это не подходит. Релизуем возможность вывода помощи по конкретно выбранной команде например help conf
. Код ниже, комментарии я удалил, что бы не немного уменьшить место занимаемое на экране:
static BaseType_t prvHelpCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
BaseType_t xReturn = pdFALSE;
static const CLI_Definition_List_Item_t *pxCommand = NULL;
BaseType_t lParameterStringLength;
char * pcParameter = (char *)FreeRTOS_CLIGetParameter ( pcCommandString, 1, &lParameterStringLength );
if (pcParameter != NULL)
{
pcParameter[lParameterStringLength] = '\0';
for( pxCommand = &xRegisteredCommands; pxCommand != NULL; pxCommand = pxCommand->pxNext )
{
if( strncmp( pcParameter, pxCommand->pxCommandLineDefinition->pcCommand, lParameterStringLength-1 ) == 0 )
{
strncpy( pcWriteBuffer, pxCommand->pxCommandLineDefinition->pcHelpString, xWriteBufferLen );
strncpy( pcWriteBuffer + strlen(pcWriteBuffer), "\r\n\r\n", sizeof ("\r\n\r\n") );
return pdFALSE;
}
}
strncpy( pcWriteBuffer, "Command not recognised. Enter 'help' to view a list of available commands.\r\n\r\n", xWriteBufferLen );
}
else
{
if( pxCommand == NULL ) pxCommand = &xRegisteredCommands;
strncpy( pcWriteBuffer, pxCommand->pxCommandLineDefinition->pcDescriptionString, xWriteBufferLen );
strncpy( pcWriteBuffer + strlen(pcWriteBuffer), "\r\n", sizeof ("\r\n") );
pxCommand = pxCommand->pxNext;
xReturn = ( pxCommand == NULL ) ? pdFALSE : pdTRUE;
}
return xReturn;
}
Подробности работы немного ниже.
Команды в этой бибилотеке могут быть несколько типов:
- без параметров (0)
- с фиксированным значением параметров (1 -...)
- с любым количеством параметров(-1)
Как могли догадаться за это отвечает последний параметр в структуре описывающей команду.
Рассмотри каждый вариант команд.
Команды без параметров
Самый простой тип команд. Выглядит следующим образом:
/// Что бы не писать постоянно длинную строку параметров
#define CLI_CALBACK_PARAMS char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString
static BaseType_t prvSCLI_Clear (CLI_CALBACK_PARAMS);
static const CLI_Command_Definition_t xSCLI_Clear =
{ "clear", "clear\t - clear screen",
ANSI_COLOR_CYAN">clear"ANSI_COLOR_RESET" - clear screen",
prvSCLI_Clear,
0};
static BaseType_t prvSCLI_Clear (CLI_CALBACK_PARAMS)
{
(void)pcWriteBuffer;
(void)pcCommandString;
printf("\033[2J");
printf("\033[H");
return pdFALSE;
}
Если из обработчика команды вернуть pdTRUE, то обработчик вызовется снова, для чего это нужно именно вам, решайте сами.
Команды с фиксированным значением параметров
Немного сложнее.
/// Что бы не писать постоянно длинную строку параметров
#define CLI_CALBACK_PARAMS char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString
#define IS_ARG(p, x) (strcmp((p), (x)) == 0)
static BaseType_t prvSCLI_PowerCmd (CLI_CALBACK_PARAMS);
static const CLI_Command_Definition_t xSCLI_PowerCmd =
{ "pwr", "pwr\t - main power control",
ANSI_COLOR_CYAN">pwr off"ANSI_COLOR_RESET" - manual main power off",
prvSCLI_PowerCmd,
1};
static BaseType_t prvSCLI_PowerCmd (CLI_CALBACK_PARAMS)
{
(void)pcWriteBuffer;
BaseType_t lParameterStringLength;
char * pcParameter = (char *)FreeRTOS_CLIGetParameter ( pcCommandString, 1, &lParameterStringLength );
if( pcParameter != NULL )
{
pcParameter[lParameterStringLength] = '\0';
if (IS_ARG(pcParameter, "off")) SYSTEM_PowerOff();
else printf(_RESP_INVALID_PARAM);
}
return pdFALSE;
}
Команды с любым количеством параметров
Описываются аналогично командам с фиксированным количеством параметров. Вот пример описания:
static const CLI_Command_Definition_t xSCLI_Configure =
{ "conf", "conf\t - configuration control",
ANSI_COLOR_CYAN">conf reset"ANSI_COLOR_RESET" - set default configuration\r\n"
ANSI_COLOR_CYAN">conf save"ANSI_COLOR_RESET" - save current configuration\r\n"
ANSI_COLOR_CYAN">conf show"ANSI_COLOR_RESET" - show current configuration\r\n"
ANSI_COLOR_CYAN">conf reset_stats"ANSI_COLOR_RESET" - reset battery statistics\r\n"
ANSI_COLOR_CYAN">conf load_file"ANSI_COLOR_RESET" - load configuration from sdcard\r\n"
ANSI_COLOR_CYAN">conf save_file"ANSI_COLOR_RESET" - save configuration to sdcard\r\n"
ANSI_COLOR_CYAN">conf show [name]"ANSI_COLOR_RESET" - show configuration parameter\r\n"
ANSI_COLOR_CYAN">conf set [name]=[value]"ANSI_COLOR_RESET" - set configuration parameter",
prvSCLI_Configure,
-1};
Реализация команды аналогична предыдущей. Кстати вот и большой хелп.
ANSI_COLOR_CYAN
- Задает цвет текста:
#define ANSI_COLOR_BLACK "\x1b[31m"
#define ANSI_COLOR_RED "\x1b[31m"
#define ANSI_COLOR_GREEN "\x1b[32m"
#define ANSI_COLOR_YELLOW "\x1b[33m"
#define ANSI_COLOR_BLUE "\x1b[34m"
#define ANSI_COLOR_MAGENTA "\x1b[35m"
#define ANSI_COLOR_CYAN "\x1b[36m"
#define ANSI_COLOR_WHITE "\x1b[37m"
#define ANSI_COLOR_RESET "\x1b[0m"
Перерь после дого как все описали необходимо зарегистрировать команды и запустить задачу CLI:
void CLI_Init( void )
{
FreeRTOS_CLIRegisterCommand( &xSCLI_Clear );
FreeRTOS_CLIRegisterCommand( &xSCLI_Configure );
FreeRTOS_CLIRegisterCommand( &xSCLI_PowerCmd );
///...
xTaskCreate( vCommandConsoleTask, "CLI", configMINIMAL_STACK_SIZE * 8, NULL, tskIDLE_PRIORITY + 2, NULL );
}
Будут вопросы, пишите комментарии, дополню материал или поясню какие-то моменты. Более подробную информацию можно почерпнуть на официальной странице FreeRTOS+CLI