FreeRTOS+CLI. Реализация интерфейса командной строки используя FreeRTOSPlusCLI.

Большой эксперимент с библиотекой 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