By dave | February 5, 2019

Building plugins for use in the menu designer

This guide to tcMenuDesigner code generation runs through the way that code is created in the designer. Covering how the menu items are internally represented, how plugins are responsible for generating the code and lastly, how the code is output. It assumes that you are completely familiar with the general concepts in tcMenu. If not at least read the getting started guide before proceeding.


How the UI works internally

TcMenu designer UI is basically the equivalent of a form designer. It represents the menu structures internally using the tcMenu Java API MenuTree data structure, which stores a series of MenuItem classes arranged into sub-menus. The user builds the structure into their desired state and then chooses to generate the code. Take a look at the tcMenu Java API documentation to get more understanding of how it works. Also, take a look at the layout of the menu item data types to see what types are available and their options.

Once the user has finished creating their menu, they usually save it into a directory where there’s an empty (or no) Arduino sketch file. For the sake of this document we only discuss Arduino conversion, other requirements may differ, here we assume that the ArduinoGenerator is in use. However, the code creator is free to work in whichever way it sees fit as long as it adheres to the appropriate interface.

public interface CodeGenerator {
    ... 
    boolean startConversion(Path directory, List<EmbeddedCodeCreator> generators, MenuTree menuTree);
    ... 
}

Once code generation has started, the startConversion method above is called. It takes several parameters, the directory where to output, a menuTree containing all the items, but it’s the list of CodePlugins that we are most interested in here. Each plugin is an XML document that tells the designer how to integrate any associated functionality. Generally speaking the plugins provide additional display drivers, input types or remote devices. Each menu has its own input, display and remote plugins, and they are all configured from the comfort of the designer user interface.

In the figure below we discuss Arduino output, there four files that we are interested in, firstly the EMF file contains the menu layout and the plugin setup. Next, the sketch INO file contains the core setup and loop method, we try to touch this file minimally, as it would otherwise be very difficult to round trip, there’s also two files that use the project name with _menu.cpp and _menu.h appended. These store the majority of the definitions, variables and function calls needed to initialise the library. Let’s look at an example for a project called superProject:

superProject
  +- superProject.ino       - the sketch file
  +- superProject_menu.cpp  - source file containing menu code
  +- superProject_menu.h    - header file containing definitions
  +- superProject.emf       - only used by the designer UI

tcMenuDesigner plugin architecture

Each input, output or remote capability is provided by a plugin, or in other words a series of XML files that may also include some C++ source. A plugin library is nothing more than a directory containing at least a file named tcmenu-plugin.xml and probably some preview images too for each of the plugins.

Here we break down the plugin files and discuss each part. Please refer to the following link for the plugin project which also contains an example: [https://github.com/davetcc/tcMenu/tree/master/CoreXmlPlugins]

Format of the tcmenu-plugin.xml library file:

    <?xml version="1.0" ?>
    <TcMenuPluginDefinition shortName="demo-plugin" xmlns="https://www.thecoderscorner.com/libraries/tcmenuPlugin"
                            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                            xsi:schemaLocation="https://www.thecoderscorner.com/libraries/tcmenuPlugin https://www.thecoderscorner.com/libraries/tcmenu-plugin.xsd">
        <GeneralDetails>
            <Author name="The Coders Corner" url="http://www.thecoderscorner.com/" />
            <Version>1.3.5</Version>
            <Name>Demo plugin</Name>
            <Description>This plugin shows how to write plugins.</Description>
            <License name="Apache 2.0" url="http://www.apache.org/licenses/LICENSE-2.0"/>
        </GeneralDetails>
    
        <Plugins>
            <!-- each plugin item is listed below -->
            <Plugin>example-plugin-item.xml</Plugin>
        </Plugins>
    </TcMenuPluginDefinition>

Above we can see the layout of the plugin library file, we’ll go through each element in turn.

Firstly, the root element contains just the short name (same as directory name usually) and XML namespace definitions, the name space definitions are mandatory and if you’re using an editor that understands schema definitions helps ensure the document is valid.

Next are the general details, in here the human readable details about the plugin are defined, including the name, version, author information, license information and description.

There is usually more than one actual plugin within the archive so each one is listed out within the Plugins element. Each entry in the plugins list is an XML file containing the definition of what variables, functions and files are needed.

Each plugin within the plugin library:

First all plugin files must start with the same root node:

<?xml version="1.0"?>
<TcMenuPlugin name="Control menu with analog joystick" id="20409bb8-b8a1-4d1d-b632-2cf9b57353e3" subsystem="INPUT"
              xmlns="https://www.thecoderscorner.com/libraries/tcmenuPluginItem"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>

The name is the human readable name of the plugin, and the ID is unique and should be created using a UUID generator, preferably the java one, see the section further down on generating a UUID. Never reuse another UUID. The subsystem must be one of INPUT, DISPLAY, REMOTE and be in upper case. Lastly, you must include the schema name space definitions, these are mandatory and any good XML editor will then check your document against it.

Next, we define the supported platforms, this tells the designer which boards your plugin can work with:

<SupportedPlatforms>
    <Platform>ARDUINO_UNO</Platform>
    <Platform>ARDUINO_AVR</Platform>
    <Platform>ARDUINO_32</Platform>
    <Platform>ARDUINO_ESP8266</Platform>
    <Platform>ARDUINO_ESP32</Platform>
</SupportedPlatforms>

Following this, we have the rest of the description fields.

<Description>Longer description of how to use this plugin.</Description>
<Documentation link="https://www.thecoderscorner.com/"/>
<RequiredLibraries>
    <Library>Library1</Library>
    <Library>Library2</Library>
</RequiredLibraries>
<ImageFile>joystick.jpg</ImageFile>
  • description a description, think a few short sentences.
  • imageFileName an image file relative to Images directory.
  • RequiredLibraries is a list of other libraries required for use, should use Library Manager names.
  • Documentation url is a link to where the documentation is stored for this plugin.

User editable properties in the XML

Next, we define properties that can be used in variable expansion throughout the XML, this allows the XML to contain sections where the values are not known until code generation, for example: pins, io-expander name. These are edited and set to user defined values during code generation.

<Properties>
    <Property id="INT_PROP" name="Int Prop" initial="10" desc="int value" type="int" min="0" max="100"/>
    <Property id="INTERRUPT_SWITCHES" name="Interrupt Switches" initial="false" desc="Enable switches" type="boolean"/>
    <Property id="SWITCH_IODEVICE" name="IoAbstractionRef" initial="" desc="Advanced" type="variable"/>
    <Property id="JOYSTICK_PIN" name="Up Pin" initial="2" desc="button connected" type="pin"/>
    <Property id="TEST_CHOICE" name="Choices" initial="Choice1" desc="Test choices" type="choice">
        <Choices>
            <Choice>Choice1</Choice>
            <Choice>Choice2</Choice>
        </Choices>
    </Property>
</Properties>
  • id the name of the property as it will appear in variable expansion.
  • name the short human readable name of the property
  • desc the longer human readable name
  • initial the initial value of this property

Property types in the type field

The type field contains the validation that should be performed on the property during code generation.

  • int the field should be an integer between min and max defined as attributes. Defaults 0..65535
  • boolean supports true or false only
  • variable or header must be a valid variable name, with no spaces or special characters.
  • pin must be the definition of a pin or -1 for no pin.
  • choice provide a choice between various options, a Choices/Choice represents each choice as above.
  • text any text value

Setting up and determining applicability

Most items defined in the plugin can have applicability associated with them, this provides for situations where certain things should only be done when a flag is true, or on certain boards. Now we look at how to define such conditions:

We must always declare ApplicabilityDefs even if it’s empty. For complex matching where there is more than one property involved, we declare an ApplicabilityDef with a reference key (used later). These can be nested using and / or. Testing is actually done by an Applicability element, where the property name and test condition is provided.

<ApplicabilityDefs>
    <ApplicabilityDef key="applicabilityDef" mode="and">
        <Applicability whenProperty="INTERRUPT_SWITCHES" isValue="true"/>
        <ApplicabilityDef mode="or">
            <Applicability whenProperty="INT_PROP" isValue="10"/>
            <ApplicabilityDef>
                <Applicability whenProperty="ROOT" isValue="test"/>
                <Applicability whenProperty="INT_PROP" isValue="20"/>
            </ApplicabilityDef>
        </ApplicabilityDef>
    </ApplicabilityDef>
</ApplicabilityDefs>
  • whenProperty the property to be defined
  • isValue when the property has this value
  • isNotValue when the property does not have this value.

To reference an applicability def we add an applicabilityRef:

<Function name="addSwitch" object="switches" applicabilityRef="applicabilityDef">

In addition, you can define applicability on any element that supports it using the three fields whenProperty, isValue, isNotValue. Short hand example:

<Variable name="expOnly" type="char[]" export="only" whenProperty="INTERRUPT_SWITCHES" isValue="true"/> 

Defining any additional source files:

It’s usual that some additional source files will be needed as part of a plugin. These files will be copied in place into the project as part of code generation. Any replacements defined will be copied too, you can use property expansion in the find and replace. Replacements can also have applicability if needed. The source files are relative to the root of the plugin.

<SourceFiles>
    <Replacement find="someKey" replace="${INT_PROP}"/>
    <Replacement find="otherKey" replace="abc"/>
    <SourceFile name="src/source.h" />
    <SourceFile name="src/source.cpp"/>
</SourceFiles>

Adding include files

It is probable that additional include files will need to be included for the plugin to work. You can define as many includes as needed, set their priority to either high, normal or low, and indicate if they are in the project source or global (ie using triangle brackets or speech marks).

<IncludeFiles>
    <Header name="JoystickSwitchInput.h" inSource="false"/>
    <Header name="Scramble.h" inSource="true" priority="high"/>
</IncludeFiles>

File layout within the plugin at runtime

plugins
  +--plugin-name
      +--tcmenu-plugin.xml
      +--example-plugin-item.xml
      +--Images
         +--All image files
      +--src
         +--source.cpp
         +--source.h

Above shows graphically the structure of the example plugin at runtime. Notice that you should always ship the arduino source with the plugin, it will be copied into the project directory automatically during code generation, it will be kept up to date with every code generation too. Any images declared in the plugins should be in the Images directory.

Adding global variables

Plugins will often need to define or at least export from the sketch global variables. Each variable is defined in a Variable element, it must have a name and can optionally have a type. There are three export modes (true, false, only). Only means that this variable is defined elsewhere, but we need to export it to use it. See the below section on parameter definitions. Functions can also be conditional using applicability.

<GlobalVariables>
    <Variable name="analogDevice" type="ArduinoAnalogDevice" export="true">
        <Param value="42"/>
    </Variable>
    <Variable name="anotherVar" type="int" export="false" progmem="true" />
    <Variable name="expOnly" type="char[]" export="only" whenProperty="INTERRUPT_SWITCHES" isValue="true"/>
</GlobalVariables>

Function definitions for setup

You can add functions to be called during setup. These are defined as below:

<SetupFunctions>
    <Function name="initialiseInterrupt" object="switches" whenProperty="INTERRUPT_SWITCHES" isValue="true">
        <Param value="${SWITCH_IODEVICE}" default="ioUsingArduino()"/>
        <Param value="${PULLUP_LOGIC"/>
    </Function>
    <Function name="initialise" object="switches" whenProperty="INTERRUPT_SWITCHES" isValue="false">
        <Param value="${SWITCH_IODEVICE}" default="ioUsingArduino()"/>
        <Param value="${PULLUP_LOGIC}"/>
    </Function>

    <Lambda name="onReleaseFn">
        <Param type="uint8_t" name="key" used="false"/>
        <Param type="bool" name="held"/>
        <Function name="onMenuSelect" object="menuMgr">
            <Param value="held"/>
        </Function>
    </Lambda>
    <Function name="onRelease" object="switches" pointer="true">
        <Param value="BUTTON_PIN"/>
        <Param lambda="onReleaseFn"/>
    </Function>

Each function is defined with a Function element, it must have a name, and optionally an object that the function belongs to. Each function can take part in applicability. You can indicate that the object is a pointer type by adding a pointer attribute. See the next section for parameter definitions.

There is also a special type of Function called a Lambda, this creates a small inline function that can be used for callbacks and similar purposes. A lambda is made up of parameters that form it’s parameter list, and function calls that make up the body of the function.

Parameter definitions

Both functions and variables use parameters, and they both work the same way. A parameter always starts with a child element called Param with attributes as follows:

  • value a regular text value
  • ref is a reference / pointer type
  • lambda is a reference to an already declared lambda function, covered in section on functions
  • default when the value is empty, use this instead
  • used indicates if the variable is used in the scope, for functions to comment out the variable name.

Generating a UUID

From a command line where java is available:

jshell

This will start the java REPL shell, in there type

UUID.randomUUID()

The result of this will look like:

jshell> UUID.randomUUID()
$1 ==> xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Take the UUID generated and put into the id field. It is improbable that there will ever be a conflict with another plugin.

Getting starting building a plugin.

Pick one of the simplest plugins from the tcMenu opensource project, such as the example plugin. Steps are:

  • Take a copy of the example plugin source
  • Change the ID of the plugin then rename the plugin and the directory
  • Change the short name

At this point you’d have a good starting point. The easiest way to work with plugins at runtime is to add a symlink (supported even on Windows) from the application home directory plugins (Java ~/.tcmenu) to your plugin directory.

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.