By dave | March 31, 2018

Introduction to tcMenu item types

A typical TcMenu application is made up of menu items. Each menu item is part of a tree, if you are unfamiliar with trees, there’s a little terminology worth learning. We refer to elements that hold other items as the parent of those items. Conversely, each of the items in the sub menu are referred to as children of subMenu. Lets take a look at an example:

Root menu (parent of Item1, Item2, SubMenu)
   +- Item 1 (child of root)
   +- Item 2 (child of root)
   +- SubMenu (child of root, parent of subItem1, subItem2)
       +- SubItem1 (child of subMenu)
       +- SubItem2 (child of subMenu)

Each item has a type that defines what kind of data it can hold, for example an analog item can hold many types of numeric information, but it is stored as an integer. Whereas a sub menu item does not have any “data” of it’s own, but holds child items that appear below it.

All menu items are managed by object menuMgr on the device, along with a renderer to draw the menu items. TcMenu library also makes it very easy to save values to EEPROM between runs. Each entry can optionally have a storage point in the EEPROM area; where any that do will be persisted upon calling the save function on menuMgr.

This guide is designed to be read in conjunction with the tcMenu reference documentation.

More information about the Arduino Sketch

On the Arduino system, items are broken up into Info structures and Item classes. This is so that some of the read only information can be stored in PROGMEM. All Info structures and their strings must be in PROGMEM on AVR and ESP. MenuItems reside in RAM as they contain state that can change. The designer does this for you automatically. There’s also items based upon RuntimeMenuItem, these work slightly differently, as described below, but again are memory efficient.

As memory is limited in the Arduino environment - especially 8 bit, we do not create list collections to represent the tree, instead we link each item to the next item using a pointer. Every item you create has about a 10 byte overhead in RAM as a rule of thumb.

A menu with about 20 items, i2c display using my fork LiquidCrystalIO uses about 600-700 bytes of static RAM. If you include Serial or Ethernet remote control on top of that, between about 900 and 1200 bytes. All in all, workable on most boards.

Working with the menu manager (menuMgr)

Type: MenuManager in MenuManager.h

You can load and save menu state to / from an EEPROM, and many of the examples demonstrate this. For details of how to create an EepromAbstraction see AVR and Arduino EEPROM example and I2C AT24 EEPROM example.

void menuMgr.load(eeprom);
void menuMgr.save(eeprom);
void menuMgr.load(eeprom, magicKey);
void menuMgr.save(eeprom, magicKey);

Where:

  • eeprom is a reference to an EEPROMAbstraction see links above.
  • magicKey optionally lets you override the key that must be present at the start of the ROM to load back values. This value is stored on save, and checked on load, if the value is not as expected, nothing is loaded.

To retrieve the top most (first) menu item.

MenuItem* menuMgr.getRoot()

Overview of TcMenu types

TcMenu can handle many different item types. Each of the available types is discussed in detail below. All types have the following in common:

  • ID: A unique identifier for this menu.
  • eepromAddress: the location in ROM memory to store the value, or -1 to prevent saving.
  • function: the function to be called when this item changes.

Important methods available on all menu items

Type: MenuItem in MenuItems.h

To get the ID of any menu item

int id = menuItem.getId();  

To get the maximum value for any item

int maxVal = menuItem.getMaximumValue();

Checking an item has changed or setting it changed, note there’s changed and remote-changed, remote changed means that the actual value has changed and the item needs to be remotely communicated, changed could mean it’s just become active on the menu locally. These values are automatically set when you change a menu item using it’s set… method.

// set the item changed locally
menuItem.setChanged(changed);
// indicate that the item also needs to be sent remotely
menuItem.setSendRemoteNeededAll();

// check if the menu item is changed
bool b = menuItem.isChanged(newState);

Menu items that are read only cannot be edited on the device:

menuItem.setReadOnly(newState);
bool b = menuItem.isReadOnly();

Menu items that are local only will never be sent to any remote:

menuItem.setLocalOnly(bool localOnly);
bool b = menuItem.isLocalOnly();

When working with menu items in the Designer UI all items have the following properties:

  • ID - A value that uniquely identifies this menu item within the whole menu.
  • Name - The name of this item, don’t use any special characters, this will have spaces removed and become the variable name too, for example the name Input Volume would become menuInputVolume.
  • Eeprom Save Address - The location in EEPROM to save the current value to, or -1 if not saving. Pressing auto next to the text control finds the next available space automatically.
  • onChange function - A callback function that will be called upon changes to the menu item. Leave blank or set to NoCallback if not needed.
  • ReadOnly indicates that the item cannot be edited
  • LocalOnly indicates that the item cannot be sent remotely, and is only visible on the device.

Working with Analog Items

Type: AnalogMenuItem / AnalogMenuInfo in MenuItems.h

An item that can represent a numeric value, integer, or decimal. Currently, this value is a 16 bit unsigned integer value. We can make it appear negative by giving a negative offset. We make it appear decimal by giving it a divisor. If the divisor were 2, we’d increment in halves. If the offset point were -100, unit dB and divisor 2, the first value would be -50.0dB and the next would be -49.5dB and so on.

From the add item dialog choose Analog item, the editor panel will look similar to:

image showing editor for analog item

Analog Item editor UI

  • Maximum Value - is the maximum integer value that can be represented, not including offset and divisor.
  • Offset from zero - is for display only, it is added/subtracted from the current value.
  • Divisor - is for display only, current value is divided by divisor.
  • Unit name - is an optional unit for display, for example “dB”. Leave blank for no unit.

Converting to and from floating point values

float f = menuItem.getAsFloatingPointValue();
menuItem.setFromFloatingPointValue(floatValue);

Converting to and from WholeAndFraction values, where the whole and the fractional part are provided separately, and automatically corrected by the offset and divisor. This avoids manually doing the fixed point arithmetic.

WholeAndFraction wf = menuItem.getWholeAndFraction()
int wholePart = wf.whole;
int fractPart = wf.fraction;
menuItem.setFromWholeAndFraction(wf);

Although the above helpers make it easier to work with, you can get and change the raw current value:

uint16_t currentValue = menuItem.getCurrentValue()
menuItem.setCurrentValue(uint16_t val, bool silent = false)

Working with Enumeration Items

Type: EnumMenuItem / EnumMenuInfo in MenuItems.h

An item that can represent a known series of values, somewhat like a combo box. We provide a list of choices and only one of those choices can be active at once. The choice is a zero based integer with the first choice being 0 and so on.

From the add item dialog choose Enum item, the editor panel will look similar to:

image showing the enumeration editor

Enumeration Item Editor UI

To add additional choices, press the Add button below the values list. Double click on the item in the list to edit it, press enter when done. Pressing remove deletes the selected item.

To get and set the current values on an enumeration as an integer index.

uint16_t currentValue = menuItem.getCurrentValue()
menuItem.setCurrentValue(uint16_t val, bool silent = false)

To get the textual representation of an index:

void menuItem.copyEnumStrToBuffer(char* buffer, int size, int idx);

Where:

  • buffer is where the string will be copied
  • size is the size of the buffer
  • idx is the index in the enum - for example from calling menuItem.getCurrentValue()

Working with Boolean Items

Type: BooleanMenuItem / BooleanMenuInfo in MenuItems.h

An item that can represent only two states, true or false. Can be configured to show as ON/OFF, TRUE/FALSE or YES/NO as required.

From the add item dialog choose Boolean item, the editor panel will look similar to:

image showing boolean item editor UI

Boolean Item Editor UI

  • Naming - the names to use for true and false. Currently YES / NO, TRUE / FALSE, ON / OFF.

You can get and set boolean menu items by calling:

bool b = menuItem.getBoolean();
menuItem.setBoolean(b);

Working with Float Items

Type: FloatMenuItem / FloatMenuInfo in MenuItems.h

Float items are useful for displaying the result of inexact calculations, for example from a series of sensors. It is not editable and generally only advised for status information.

From the add dialog choose to create a float menu item, once it’s selected the editor panel will look similar to:

image showing the float editor

Float Editor UI

For float items, the only additional parameter is the number of decimal places to be displayed.

To get and set values on float items:

void setFloatValue(float newVal, bool silent = false);
float f = getFloatValue();

Type: SubMenuItem / AnyMenuInfo in MenuItems.h

Items of type submenu can hold other menu items, and the other items show up as a sub menu. This menu will display in a similar way to the main menu, but with the option to traverse back to the parent menu.

BackMenuItem is needed with submenu, to provide the user a way to leave the submenu back to the main menu. It has no configuration and will automatically display as [Back..], when pressed the parent menu will be displayed.

From the add item dialog choose SubMenu item, once created a new entry will be present in the tree. SubMenus are created under the nearest submenu, be that ROOT, or another submenu. In the editor, the BackMenu is not shown but is created automatically.

View of sub menu editor in the UI

SubMenu editor in the UI

RuntimeMenuItem

Items of type runtime menu item can be defined at runtime instead of needing to be declared in program memory ahead of time. They still work almost identically to regular menu items in nearly all ways, and are nearly as memory efficient as regular items.

There’s a special case of RuntimeMenuItem called EditableMultiPartMenuItem that supports editing of complex types such as IP Address and String on the device. It does so by editing these items one part at a time.

Instead of using an Info structure to configure them, they use a function that is called to get the value, title and even for editable ones change it’s value. If you stick to simply using the UI, you do not need to understand this fully. The callback function looks as follows:

int runtimeRenderingFn(RuntimeMenuItem* item, uint8_t row, RenderFnMode mode, char* buffer, int bufferSize);

Where:

  • item is the runtime item making the callback
  • row is the row number (or part in the case of multi edit items)
  • mode is one of the below RenderFnMode enumeration
  • buffer is provided for operations where a result is needed
  • bufferSize is the size of the buffer
RenderFnMode Meaning
RENDERFN_VALUE Copy the current value into buffer
RENDERFN_NAME Copy the name of the item at row into buffer
RENDERFN_EEPROM_POS Return the eeprom position for storage or -1
RENDERFN_INVOKE Invoke the action callback if there is one
RENDERFN_SET_VALUE * Set the value at index row to the buffer
RENDERFN_GETRANGE * Get the zero based range of values at index row
RENDERFN_GETPART * returns the value of a single part of

* multi edit only

When generating such items manually, there’s a helper macro that allows you to skip writing the callback yourself for simple cases:

RENDERING_CALLBACK_NAME_INVOKE(fnName, parent, namepgm, eepromPosition, invoke)

Where:

  • fnName is the name to give to this function, usually something that indicates which menu it belongs to.
  • parent the parent function to call. See the types below for the appropriate one to use.
  • namepgm the name of the menu item - must be a progmem declared string
  • eepromPosition the position to use in EEPROM for storage or -1.
  • invoke the callback to invoke when changes occur.

ListRuntimeMenuItem

Supports the displaying of a list of values. When an item of this type is created, you provide a callback function like the one above that can render each item in the list. Lists are handled like sub menu’s on the renderer and remotely, there is a title row and then as many item rows as needed. When you use the designer UI to generate one of these, it will create a stub working callback for you.

Lists are memory efficient and there’s one MenuItem that handles every case. In your callback the “submenu” comes through as row LIST_PARENT_ITEM_POS. Every other row comes through zero based up to the maximum that you set.

To set the number of item rows:

setNumberOfRows(uint8_t rows)

These methods make the list item switch into different modes - after use always call asParent() to reset the state back to it’s default:

// to act as a back menu
RuntimeMenuItem* asBackMenu();

// to act as a sub menu
RuntimeMenuItem* asParent();

// to act as a particular child for a given index
RuntimeMenuItem* getChildItem(int requiredIndex);

Working with Multi Edit items

  • Type: TextMenuItem with default callback textItemRenderFn in RuntimeMenuItem.h
  • Type: IpAddressMenuItem with default callback ipAddressRenderFn in RuntimeMenuItem.h
  • Type: TimeFormattedMenuItem with a default callback function of timeItemRenderFn in RuntimeMenuItem.h

An item that represents a complex data type that needs to be edited a part at a time. It is generally held in RAM and can usually be stored in EEPROM. These items take various forms on the embedded side, either an Ip Address, time or plain text (also later we’ll see large number type too). Time menu items allow for the editing of time values, either to the hundredth of a second or to the second either as 24 hour or 12 hour. Ip address items are IPV4 addresses edited in 4 parts. Text fields can be regular text fields or password text fields, each character is edited separately.

From the add dialog choose to create a text menu item and once it’s selected the editor panel will look similar to:

image showing the text editor

Text Item Editor UI

  • maxLength: the allocated size of the string, anything copied in that’s longer will be truncated.
  • editorType: at the moment plain text or IP address

To get and set the value of a plain text menu item:

void setTextValue(const char* text, bool silent = false);
const char* getTextValue() { return data; } 

To get and set the value of an IpAddress menu item:

// set IP address from text
void setIpAddress(const char* source);

// as four parts.
void setIpAddress(uint8_t p1, uint8_t p2, uint8_t p3, uint8_t p4);

// get the four parts of the address.
byte* getIpAddress()

// or as a string
void copyValue(char* buffer, int bufferSize);

To get and set the value of a time item:

// First we can get and set the time storage object
TimeStorage getTime()
void setTime(TimeStorage storage)

// On the TimeStorage object we can access the hours, minutes, seconds and hundreds 
uint8_t hours;
uint8_t minutes;
uint8_t seconds;
uint8_t hundreds;

Working with Large Number items

Type: EditableLargeNumberMenuItem with default callback of largeNumItemRenderFn in EditableLargeNumberMenuItem.h

Large numbers are supported for editing in TcMenu, with values up to 12 digits in total. They are stored bit packed as BCD. This makes for efficient storage and complete accuracy in all cases. However, you can convert to and from floating point values for convenience when accuracy is not as important.

From the add dialog choose the “Very Large numeric value” option. The form will look similar to:

editor value for a large number menu item

Large Number Editor UI

  • Decimal Places: the number of decimal places after the point.
  • Total Digits: the maximum allowable digits for the number item.

There are two ways to acquire and set the values of a large number item, either as a LargeFixedNumber object that represents the whole and fraction components as 32 bit integers, or a float single precision floating point number.

// Call this method on the menu item to get the underlying number storage
LargeFixedNumber* getLargeNumber()

// To get or set the value using float
float getAsFloat()
void setFromFloat(float value)

// To get or set the value by whole and fraction
uint32_t getWhole()
uint32_t getFraction()
bool isNegative()
void setValue(uint32_t whole, uint32_t fraction, bool negative)

Callback function for changes

When there is a change in the menu, the manager can call you back to let your code take appropriate action, just declare a function and set it as the optionalFunction parameter. Then when there is a change your code will execute. For example if we had an AnalogMenuItem called menuExample controlling a PWM channel:

void CALLBACK_FUNCTION onExampleChanged(int id) {
    analogWrite(somePwmPin, menuExample.getCurrentValue()); 
} 

Common functions on most renderers

Type: BaseMenuRenderer in BaseRenderers.h

Most renderers are based on the same base class, and have common functionality. This section discusses several common requirements.

Taking over the display

You can take over the display from the renderer, in this case you will be called back at regular intervals by the rendering class, and it is your responsibility to update the display at this time, if any updates are needed. Presently, all tcMenu rendering follows the game loop principle. See the packaged example.

To take over the display:

renderer.takeOverDisplay(myDisplayCallback);

We must provide a method with the following signature:

// This will be called frequently by the renderer class
// here we give control back when the button is clicked.
void myDisplayFunction(unsigned int encoderValue, bool clicked) {
    if(clicked) {
        renderer.giveBackDisplay();
        return;
    }
    
    // draw something..
}

When conditions change such that we no longer need display control:

renderer.giveBackDisplay();

Capturing display timeout / reset

You can also add a callback function that will be informed when the menu is reset after timing out, this is useful if you don’t want to display the menu all the time. First define the function:

// this function will be called when the menu becomes inactive.
void onMenuBeingReset() {
    // for example in here we could take over the display when the menu is inactive.
    renderer.takeOverDisplay(myDisplayFunction);
}

Then add the function as the callback:

renderer.setResetCallback(myResetCallback); 

If you want to change the threshold for becoming inactive:

renderer.setResetIntervalTimeSeconds(newResetTimeInSeconds);

Presenting a dialog to the user

Type: BaseDialog in BaseDialog.h

It is also possible to present a simple dialog for either information, or Yes / No question onto almost all supported displays. Along with this you can optionally decide to let the dialog show on any remote connections, taking over the top of the display similar to an Alert Box.

Firstly, if we are interested in the outcome of the dialog, we must give the dialog a function to call us back upon when complete:

//
// this method is called when the dialog is dismissed.
//
void onDialogFinished(ButtonType btnPressed, void* /*userdata*/) {        
    if(btnPressed != BTNTYPE_OK) {
        // do something if OK was pressed.
    }
}

Next we get hold of the dialog and initialise it. Here we choose a dialog with OK and CANCEL as options:

    BaseDialog* dlg = renderer.getDialog();
    dlg->setButtons(BTNTYPE_OK, BTNTYPE_CANCEL, 1);

Now we call the show method to make the dialog take over the screen

    dlg->show(pgmHeaderText, remoteAllowed, onDialogFinished); // true = shows on remote sessions.
  • pgmHeaderText is the header text for the dialog
  • remoteAllowed true if should appear on all remote controls
  • onDialogFinished an optional callback when a button is pressed to dismiss the dialog

Lastly we can copy some text into the second line of the dialog (not program memory):

    dlg->copyIntoBuffer("Hello");

Back to tcMenu main page

Other pages within this category

comments powered by Disqus

We use cookies to analyse traffic and to personalise content. We also embed Twitter, Youtube and Disqus content on some pages, these companies have their own privacy policies.

Please see our privacy policy should you need more information or wish to adjust your settings.

Send a message
X

This message will be securely transmitted to Nutricherry LTD servers.