Making FreeRTOS CLI more CLI-ish : implementing a prompt

 

Implementing a prompt

The very bare bones offering from FreeRTOS CLI leaves a lot up to the developer to implement as he/she see fits. The next order of business is to implement a cli prompt.

I want my prompt to be dynamic, so to speak, meaning I want it to be able to change its text according to context like I mentioned at the end of the last post.

Prompt requirements

  • Ability to change prompt text
    • For example, I have a command that when passed to the CLI interface it will start a test. The test will be ongoing until stopped, at which point I want my prompt to “remind” me that there is a test on going.
  • Allow pause/skip of printing the prompt.
    • If one of my commands involves printing to the screen, I do not want my prompt to print until that command is done printing so we need a way to NOT print the promp until we instruct it to do so.

I am a lover of typedef structs because its as close as we can get to objects in C and they are great for encapsulating elements.

In main_app.h we will create the following struct.

1
2
3
4
5
6
typedef struct{
    char *prompt_str;
    char *prePrompt_str;
    bool alt_prompt_active;
    bool pause_prompt;
}prompt_t;

Over in main_app.c we make an instance of our prompt object along with the relevant strings we want for the CLI prompt and the alternate text during testing.

1
2
3
prompt_t prompt;
char mainPrompt_str[] = "CLI:";
char onGoingTest_str[] = "(test active)";

Next comes the function that we can call at any time to print the prompt and it should handle all possible events.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void show_prompt(void)
{
    if(prompt.pause_prompt)
        return;

    if(prompt.alt_prompt_active){
         printf("\r\n%s %s",prompt.prePrompt_str, prompt.prompt_str);
    }else
        printf("\r\n%s", prompt.prompt_str);
        
    fflush(stdout);

}

Given the function above, I now have met my requirements for the prompt.

You can get infinitely more fancy with this but for now this gets the job done.

Now lets add the show_prompt function to the vCommandConsoleTask so that our prompt prints when it is supposed to.

First place it needs to live is right before we enter the for loop, this is the very first time we see our prompt on start up.

The next place we want to show the prompt is after we have entered a command. If for some reason the command handler needs to print something then it will change the prompt object variables as needed.

Here is the new vCommandConsoleTask code. The only lines that have changed from the previous version are lines 10, 56 and 89

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
void vCommandConsoleTask(void *pvParameters)
{
    int8_t cInputIndex = 0;
    BaseType_t xMoreDataToFollow;
    /* The input and output buffers are declared static to keep them off the
     * stack. */
    static int8_t pcOutputString[MAX_OUTPUT_LENGTH], pcInputString[MAX_INPUT_LENGTH];

    vRegisterCLICommands();
    show_prompt();
    for (;;)
    {

        if (cRxedChar != 0x00)
        {

            if (cRxedChar == '\r' || cRxedChar == '\n')
            {
                printf("\r\n");
                fflush(stdout);
                /* A newline character was received, so the input command string is
                complete and can be processed.  Transmit a line separator, just to
                make the output easier to read. */

                /* The command interpreter is called repeatedly until it returns
                pdFALSE.  See the "Implementing a command" documentation for an
                exaplanation of why this is. */
                do
                {
                    /* Send the command string to the command interpreter.  Any
                    output generated by the command interpreter will be placed in the
                    pcOutputString buffer. */
                    xMoreDataToFollow =
                        FreeRTOS_CLIProcessCommand(pcInputString,    /* The command string.*/
                                                   pcOutputString,   /* The output buffer. */
                                                   MAX_OUTPUT_LENGTH /* The size of the output buffer. */
                        );

                    /* Write the output generated by the command interpreter to the
                    console. */

                    for (int x = 0; x < (xMoreDataToFollow == pdTRUE ? MAX_OUTPUT_LENGTH : strlen(pcOutputString)); x++)
                    {
                        printf("%c", *(pcOutputString + x));
                        fflush(stdout);
                    }

                } while (xMoreDataToFollow != pdFALSE);

                /* All the strings generated by the input command have been sent.
                Processing of the command is complete.  Clear the input string ready
                to receive the next command. */
                cInputIndex = 0;
                memset(pcInputString, 0x00, MAX_INPUT_LENGTH);
                memset(pcOutputString, 0x00, MAX_INPUT_LENGTH);
                show_prompt();
            }
            else
            {
                /* The if() clause performs the processing after a newline character
                is received.  This else clause performs the processing if any other
                character is received. */

                if (cRxedChar == '\b')
                {
                    /* Backspace was pressed.  Erase the last character in the input
                    buffer - if there are any. */
                    if (cInputIndex > 0)
                    {
                        cInputIndex--;
                        pcInputString[cInputIndex] = "";
                    }
                }
                else
                {
                    /* A character was entered.  It was not a new line, backspace
                    or carriage return, so it is accepted as part of the input and
                    placed into the input buffer.  When a n is entered the complete
                    string will be passed to the command interpreter. */
                    if (cInputIndex < MAX_INPUT_LENGTH)
                    {
                        pcInputString[cInputIndex] = cRxedChar;
                        cInputIndex++;
                        printf("%c", cRxedChar);
                    }
                }
            }
            cRxedChar = 0x00;
            fflush(stdout);
        }
    }
}


Testing the prompt

Now lets make some fake test commands that will make use of the new prompt features.

Over in commands.c we need to add an extern for our prompt, this could be handled better perhaps with a pointer but for now this is fine. And we will also implement the prototypes for the test commands

1
2
3
4
extern prompt_t prompt;

static BaseType_t cmd_start_test(int8_t *pcWriteBuffer, size_t xWriteBufferLen, const int8_t *pcCommandString);
static BaseType_t cmd_stop_test(int8_t *pcWriteBuffer, size_t xWriteBufferLen, const int8_t *pcCommandString);

Next we implement the command handlers themselves.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
static BaseType_t cmd_start_test(int8_t *pcWriteBuffer, size_t xWriteBufferLen, const int8_t *pcCommandString)
{
    memset(pcWriteBuffer, 0x00, xWriteBufferLen);
    prompt.alt_prompt_active = true;
    
    /* There is no more data to return after this single string, so return
        pdFALSE. */
    return pdFALSE;
}
static BaseType_t cmd_stop_test(int8_t *pcWriteBuffer, size_t xWriteBufferLen, const int8_t *pcCommandString)
{
    memset(pcWriteBuffer, 0x00, xWriteBufferLen);
    prompt.alt_prompt_active = false;
    /* There is no more data to return after this single string, so return
        pdFALSE. */
    return pdFALSE;
}

Next we implement the command structs that are needed to register a command

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
static const CLI_Command_Definition_t xCmdStartTest = {
    "start",                               // command to type
    "start :\r\n Starts a test\r\n", // help string
    cmd_start_test,                             // command handler
    0                                   // num of pasrameters to expect
};
static const CLI_Command_Definition_t xCmdStopTest = {
    "stop",                               // command to type
    "stop :\r\n Stops a test\r\n", // help string
    cmd_stop_test,                             // command handler
    0                                   // num of pasrameters to expect
};

And ultimately we will register the commands

1
2
3
4
5
6
void vRegisterCLICommands(void)
{
    FreeRTOS_CLIRegisterCommand(&xCmdOK);
    FreeRTOS_CLIRegisterCommand(&xCmdStartTest);
    FreeRTOS_CLIRegisterCommand(&xCmdStopTest);
}

Final prompt

And here is the end result of our “dynamic” prompt. I like it and it got the job done when I found myself implementing this at work.



Minor tweaking and then more features

In the next post we will make a small change of how commands are registered to eliminate one stop of that process to make life just a tad bit simpler for us. After that we will implement backspace followed by the rest of the features mentioned in the fist post.

<<  PREVIOUS  |   NEXT >>




Comments

Share your comments with me

Archive

Contact Form

Send