Advanced Usage

Most of the information you need to get started is on the SerialUI usage page.  Here we’ll go over the SerialUI APIs in more detail and cover the most important aspects of the compilation-time control system.

On this page:

SerialUI  API

This is the interface for the SUI::SerialUI object, which is how your program will mostly interact with SerialUI (the library).

Everything the Arduino Serial object can do, so can SUI::SerialUI. Those methods won’t all be described here. What is covered:

Construction

The constructor is called with two (optional) parameters:

SerialUI([PGM_P greeting_message, 
            [uint8_t num_top_level_menuitems_hint]])
  • a message string to show on entry, declared with SUI_DeclareString
  • a hint concerning the number of top level menu items (to avoid the cost of memory re-allocation, when you have many–say, more than 3)

As mentioned both are optional but a greeting is nice as it lets you know everything is working.

Configuration

These methods allow you to configure SerialUI, and are normally used in the setup() function (for Arduinos).

begin(BAUD_RATE)

Initialize the serial connection at the specified baud rate. This is the regular Serial begin(), but is mentioned here because you really need to use it.

setMaxIdleMs(MAXIDLETIME)

Set the maximum amount of time (in ms) to wait for user requests before timing out.

maxIdleMs()

Returns the current setting for maximum idle time (in ms).

setReadTerminator(TERMINATORCHAR)

Set the input terminator character (defaults to ‘\n’).

readTerminator()

Returns the current input terminator character.

Accessing Menus

topLevelMenu([MENUNAME])

The top level menu always exists, this method gives you access to it so you can add menu items.

The MENUNAME optional parameter is used to change the top level menu’s name from the default (though you can use Menu::setName() later, anyway).

Returns a pointer to the Menu object (SUI::Menu*).

currentMenu()

Returns a pointer to the currently active Menu (SUI::Menu*). This may be useful in callbacks, for instance when many commands lead to the same function and you are interested in know how you got there.

User Interaction

These methods allow you to interact with the user in various ways. If you are trying to get some data, user the various Serial methods for reading (read(), readBytesUntil() and such).

enter()

Called after checkForUser(), when we are ready to process requests, to show the greeting/prompt.

This is normally called once the user has been found with checkForUser()–see the example–but you can call it to reset things from the start.

exit()

May be called to force an exit from SerialUI.

returnOK()

Return (print) a standard OK response to user. The default OK string is “OK” (imagine that!).

returnMessage(MESSAGE)

Return (print) a message to user.  MESSAGE must be a progmem string (most likely created with SUI_DeclareString).

returnError(ERRORMESSAGE)

Return (print) an error message to user.

NOTE: This is the only SerialUI method to use a  “regular” char pointer (rather than PROGMEM), in order to avoid forcing you to hardcode all error messages.

showPrompt()

Output the standard prompt. You shouldn’t need to call this, but you can. Prints “>” by default.

Prompting Users for Data

There are cases where, during the execution of a command, you want to get more data from the user.  In this case, using the various showEnter*Prompt() methods–before you extract the data from the serial line–is highly recommended.  The main reason is that using these prompts consistently allows your SerialUI program to be used seamlessly with Druid4Arduino (a universal GUI for all SerialUI-based devices).

There are currently 3 “enter data” prompts in use.

showEnterDataPrompt()

Output the  “need more data” prompt, for when you are expecting user input. When interacting through druid, this will activate the input text field and allow the user to enter arbitrary strings.  Prints “…” by default.  After issuing this prompt, you’ll normally do a readBytes or similar call to halt execution as you await user input.

showEnterNumericDataPrompt()

When you want to get a number from your serial users, calling mySUI.showEnterNumericDataPrompt() will output a suitable prompt (“..#” by default) that is understood by druid4arduino such that, on receiving it, the GUI will activate the input text field and only accept numeric data.  In your program, this would usually look something like:

void my_command_callback()
{
    mySUI.print(F("Enter your favorite number"));
    mySUI.showEnterNumericDataPrompt();
    int someNum = mySUI.parseInt();

    // ... do whatever

    mySUI.returnOK();
}

 

await user input.

showEnterStreamPromptAndReceive()

This newish prompt (only available in SerialUI >= 1.7) allows you to accept input of arbitrary length from the serial connection.  Using showEnterStreamPromptAndReceive() is really useful when you want to get blobs of data from a computer to your Arduino, and is now supported by druid4arduino.  You can see it in action, along with a few other prompts for data, in this little demo:

Unlike the other prompt methods, this one actually takes some parameters because it needs to know what to do with the incoming data.  The simplest way to call this method is:

mySerialUI.showEnterStreamPromptAndReceive((char *) bufferToUse,
                                           (uint8_t) bufferSize, 
                                           (streamInputCallback) input_callback);

The first two parameters are to inform the system of the temporary container to use for moving chunks of data around.  That container’s size is up to you–a smaller container will take up less RAM but implies more trips to the data processing callback.

The streamInputCallback parameter is a function you define that takes four parameters (and returns void), something like so:

void myFunctionToProcessTheData(char* buffer, uint8_t buflen, 
                                size_t previous_position, size_t total_len)
{
    // what you do here is up to you... 
    // there are buflen bytes of data in buffer, starting at buffer[0].
    // previous_position is the count of bytes processed so far,
    // total_len is the total size of all the data being sent.

    // if this was a media player, you might store these bytes in 
    // external flash memory :
    for (uint8_t i=0; i< buflen; i++)
    {
        // stick buffer[i] in memory
    }
}

Once that data processing function is ready, you can create your command callback in which you actually request the upload data:

void myUploadClicked()
{
    // some command callback that allows uploads

    // make some swap space
    char buffer[32];

    // show the prompt and receive the data.  myFunctionToProcessTheData()
    // will be called repeatedly with a buffer filled with between 1 and 32 bytes,
    // until the entire upload has been processed.
    mySerialUI.showEnterStreamPromptAndReceive(buffer, 32, myFunctionToProcessTheData);

}

And that’s all there is to it for the simplest cases.  In instances where you need a little more control–like in the video demo above, where I needed to erase the external memory on every upload, and had to know the size of the data blob before doing so–there are two other callbacks you can use with the showEnterStreamPromptAndReceive call.  They have the following signatures:

bool  inputStartCallback(size_t total_len);
void  inputEndCallback(size_t total_len);

If you define functions like this, you can hand them to SerialUI when issuing the showEnterStreamPromptAndReceive(), e.g.:

//...
mySerialUI.showEnterStreamPromptAndReceive(buffer, 32, 
          myFunctionToPRocessTheData, 
          myInputStartCallback, 
          myInputEndCallback);

Then you will be notified on upload start and end, with the total size of the data as a parameter during the call.  Additionally, if you return anything other than true in the start-up callback, you will abort the process (you data processing callback will never be called for this upload).

Notes about the file upload process, from the client’s point of view.  The file upload process is, to date, very simple.  In essence, when you call showEnterStreamPromptAndReceive(), SerialUI expects to receive:

  1. An integer value equal to the total number of bytes in the data that will be uploaded
  2. Exactly that many bytes

If you write a program to talk to SerialUI, that is all you need to do to support uploads–though may want to check the latest version of Druid4Arduino for more details.

 

Handling requests

These methods are mainly used in the main loop, see the usage info.

checkForUser([TIMEOUTMS])

Used to check if a user is attempting to communicate (send us data) over the serial line.

You may call checkForUser() periodically. If it returns true, there is a user sending requests that need to be handled (see handleRequests())

TIMOUTMS is a timeout in ms. This call is blocking, for a maximum of TIMOUTMS ms.

Return boolean true if a user is present, false otherwise.

checkForUserOnce([TIMEOUTMS])

Same as checkForUser() above, but may be used in a loop to check (and block) ONLY the first time through.

userPresent()

Once checkForUser()/checkForUserOnce() have determined a serial user is present, userPresent() is used to confirm that they are still around.

Will return false once the user has exited the SerialUI, or has timed out, and true while they’re present.

handleRequests()

Handles any pending requests. Should be called in a loop that exits once userPresent() goes false (see sample code).

print_P(PGM_STRING)

Same as Serial.print() but for progmem strings.

println_P(PGM_STRING)

Same as Serial.println() but for progmem strings.

User Presence Heartbeat Callback

If you need to do something, like monitor conditions or manage devices, even while a user is interacting with SerialUI (i.e. within the userPresent()/handleRequests() loop), you may (starting with version 1.11) setup a “user presence heartbeat” callback.  This can be any function with a

void XXX()

signature and is setup like so:

void funcToCallWhileUserPresent()
{
   // do something important here...
}

void setup() {
   // ...
   mySUI.setUserPresenceHeartbeat(funcToCallWhileUserPresent);
   mySUI.setUserPresenceHeartbeatPeriod(250); // call above every 250 ms
}

With such a setup, the funcToCallWhileUserPresent will be called (approximately) every 250 milliseconds whenever a user is interacting with SerialUI. Note that the minimum period between callback triggers is 5ms.

Variable State Tracking

SerialUI version 1.13 introduced automatic variable/state tracking.  When you configure SerialUI to track a particular variable, its label and value will automatically appear, and be refreshed periodically, in (recent versions of) Druid4Arduino as show under the mouse pointer here:

druid_state_tracking

Integer (unsigned long) and float type values will appear alongside their label, whereas boolean values will simply appear/disappear according to their state (visible when true).  I’ve prepared a video presentation, if you’d like to see it in action:

To use this functionality you need three things:

  1. A (persistent) variable you’d like to track;
  2. A label to use as it’s name; and
  3. To let SerialUI know you want the variable tracked.

The variable can be any

  • unsigned long
  • float
  • bool

that is persistent/global.  By this, I mean that the pointer to the variable that will be handed to SerialUI must be valid for the duration of the program.  For instance, if you were to use a pointer to a variable declared within a function, the pointer would eventually dangle and point to memory that has been cleared or used for something else.  The simplest method is to only track variables that are globally scoped (i.e. declared outside of any function or method).

The label is simply a program-space string, declared like other SerialUI strings:

SUI_DeclareString(some_variable_label, "My Tracked Var");

Finally, you configure SerialUI to track the variable by calling trackState() somewhere, normally in your setup() function.

// global declaration of variable to track:
float MyImportantFloat = 0;

void setup()
{
   // ... other setup and SerialUI initialization, then:
   mySUI.trackState(some_variable_label, &MyImportantFloat);
}

Note that we pass a pointer to the variable in question, using the & operator in this case.

And that’s it!  SerialUI/Druid4Arduino will handle all the rest and, if you’re not using druid but a direct terminal connection instead, the code will have no impact whatsoever.

The VariableTracking example, included in the latest version of SerialUI, describes the process in greater detail.

Menu API

Adding items (commands and sub-menus) happens through SUI::Menu objects, whose programming interface is presented here.

You start by getting a handle (a pointer to) the top level menu, through the SUI::SerialUI’s topLevelMenu() method. From there, you can add command items and create sub-menus (other SUI::Menu objects).

Creating Menu Items

These methods let you add items to a menu.

addCommand(KEY, CALLBACK [, HELP])

Use addCommand() to add a command (i.e. an action that triggers a callback) menu item to a menu. The parameters are:

  • KEY: the (SUI_DeclareString()-created) string to use as the command
  • CALLBACK: the name of the void(void) callback function (as described on the usage page).
  • HELP: optional (SUI_DeclareString()-created) string to display for this item when menu help (?) is invoked.

Returns boolean true on success, false if command could not be added.

subMenu(KEY [, HELP])

Use subMenu to create a sub-menu accessible by KEY. The params are:

  • KEY: The (SUI_DeclareString-created) string to use to enter the sub-menu from the current menu.
  • HELP: optional (SUI_DeclareString-created) string to display for this item when menu help (?) is invoked.

Returns a SUI::Menu pointer to the new sub-menu, which will be NULL if  it could not be created.

NOTE: To keep the code light, that return value is your chance to catch the pointer–so capture it and use it immediately to add menu items to the sub-menu.

Name

name()

Returns the menu name (progmem) string.

setName(NAME_PSTR)

Sets the menu name to (progmem, i.e. SUI_DeclareString()-created) string.

showName()

Convenience function to print out the menu’s name. Equivalent to
SUI::SerialUI::returnMessage(SUI::Menu::name()).

Misc

parent()

Returns a pointer to the SUI::Menu which is this menu’s parent, or NULL if this is the top level menu.

Compile-time configuration

In most cases, you can leave everything as-is and get your programs running without messing in the compile-time configuration settings.

In those cases where you have special requirements, be it attempting to shrink the compiled program’s size as much as possible, changing the UI language or something else, you can start by tweaking various compilation definitions to customize your use of the library.

This will involve getting your hands dirty in the source code–mainly just editing the SUIConfig.h file.

In SUIConfig.h, there are a number of flags that tell the compiler which parts of the library you want to include and make available to your program.  You control the library by having pre-processor #defines (or removing/commenting them out).  Normally:

    #define SOME_SERIAL_UI_FLAG

would enable the related functionality, while removing the line or commenting it out:

    // don't want this:
    // #define SOME_SERIAL_UI_FLAG

Size

In terms of tweaking the library’s size, the flags available are:

  • SUI_INCLUDE_EXTRA_SAFETYCHECKS (default: enabled)
  • SUI_INCLUDE_DEBUG    (default: disabled)
  • SUI_SERIALUI_ECHO_WARNINGS (default: enabled)
  • SUI_MENU_INCLUDE_DESTRUCTION_CLEANUP (default: disabled)
  • SUI_MENU_ENABLE_SUBMENUS  (default: enabled)

With all of these enabled, you’ll use up about 1.5k extra in compiled code space as compared to having all the above disabled.  A mere 1.5k isn’t that much, but on some platforms it can make all the difference.

SUI_INCLUDE_EXTRA_SAFETYCHECKS

Define SUI_INCLUDE_EXTRA_SAFETYCHECKS to include sanity checks.

Disable this (un-define it/comment it out) if you know everything is working and you’re tight on flash space.

Enabled by default.

SUI_SERIALUI_ECHO_WARNINGS

Define SUI_SERIALUI_ECHO_WARNINGS to output warnings (mainly related to memory allocation issues).

Disable this (undefine it/comment it out) if you know everything is working and you’re tight on flash space.

Enabled by default.

SUI_MENU_ENABLE_SUBMENUS

Normally, SerialUI supports both command and sub-menu type menu items, along with menu hierarchy navigation.  This only takes up about 600 bytes but, if space is really super tight and you only plan to have a one-level menu system, then you may un-define SUI_MENU_ENABLE_SUBMENUS and save that space.
Enabled by default.

SUI_INCLUDE_DEBUG

Define SUI_INCLUDE_DEBUG to include extra debug output.

If SUI_INCLUDE_DEBUG is defined, you may use SerialUI::debug(const char*) to output debug messages (wrap these calls in an #ifdef, or it’ll puke when you undefine SUI_INCLUDE_DEBUG).

Disabled by default.

SUI_MENU_INCLUDE_DESTRUCTION_CLEANUP

Destruction/clearing of menu items would be useful if you are dynamically modifying the menu structure.  This functionality takes up space that is usually unneeded, so you’ll have to define SUI_MENU_INCLUDE_DESTRUCTION_CLEANUP here to include the functionality.

Disabled by default.

Behaviour

SUI_SERIALUI_ECHO_ON

Define SUI_SERIALUI_ECHO_ON to echo user input back out as it is sent.
Provides more natural feedback when a user (rather than a program) is present.
Enabled by default.

SUI_SERIALUI_SHOW_PROMPTS

Define SUI_SERIALUI_SHOW_PROMPTS to output prompts, e.g.
 >
Provides visual feedback to users, notifying them the SerialUI is ready for input.

Enabled by default.

SUI_STRINGS_LANGUAGE_XX

Define ONE OF the available SUI_STRINGS_LANGUAGE_XX to select the language of system strings.

Only SUI_STRINGS_LANGUAGE_EN and SUI_STRINGS_LANGUAGE_FR are available at the time of this writing.

EN (english) enabled by default.

Misc

There are a few other flags available in SUIConfig.h, have a peek inside to learn more.

 

All SerialUI pages:

SerialUI Overview