Custom Arduino Controls for Android -- No Android Programming Required

by drmpf in Circuits > Arduino

3214 Views, 50 Favorites, 0 Comments

Custom Arduino Controls for Android -- No Android Programming Required

FirstFloor_GIF_edited.gif
DrawingPrimitives.png
LedSwitches_GIF_edited.gif
VSliderGuage_GIF.gif
SliderGuage_GIF_edited.gif
RadiationSymbol_GIF_edited.gif
instructableImage.png

Update 1st Dec 2022: As well as the free pfodDesignerV3 Android app for menu design, there is now also a free pfodGUIdesigner Android app to design interactive GUI components


Update: 19th May 2017 – Downgrade Arduino101 firmware to V1.0.7

The latest Curie Firmware V2.0.2 looses connections. Arduino V1.8.2 and Curie V1.0.7 work, but not Curie V2.0.2

To downgrade from Curie V2.0.2 to V1.0.7

i) goto board manager and remove Curie V2.0.2

ii) stop IDE V1.8.2

iii) unplugged the Arduino101 board

iv) start IDE V1.8.2 and installed Curie V1.0.7 from board manager

v) plugged the Arduino101 board in and waited for the USB drivers to install

vi) select Board Arduino101, select Programmer Arduino101 and reburn the boot loader on the Arduino101

vii) unplug the board.

viii) plugged the board back in

ix) reloaded the sketch\

Fixing the EEPROM errors: Curie V1.0.7 was sloppy in its definition of the EEPROM support and included source code in the header file. This results in multiple function definition errors when compiling. To fix this go to the Curie package directory

C:\Users\...\AppData\Local\Arduino15\packages\Intel\hardware\arc32\1.0.7\libraries\EEPROM\src

where … is your Windows username

and replace existing EEPROM.h with these two files (from V2.0.2) EEPROM.h and EEPROM.cpp

Quick Start

This instructable covers the design of your own custom Arduino controls for Android using a small Arduino library of drawing primitives for pfodApp. No Android programming necessary.

The above animated gifs demonstrate just some of the controls you can build in your Arduino code and send to pfodApp (a paid Android app from Google play) for display. A rich, sophisticated, user interaction is supported with immediate feedback and selective detection of Touches, Clicks, Long Presses, Drags etc.

Detailed examples are provided for both Uno/Mega2560 and Arduino101 boards. To try out these control examples on your Arduino hardware, install pfodApp and download and install the supporting pfod libraries. The supporting libraries contain example control sketches for the Arduino101 as well as for Uno/Mega2560 with a Serial connected shield, e.g. Itead Bluetooth V2, Itead BLE shield, Adafruit's BLE UART Friend, or ESP8266 cheap wifi shields.

For other Arduino style boards use the free pfodDesigner Android app to create a drawing code template and copy and paste the loop() code and supporting methods from the library examples.

Supported Boards – pfodApp and pfodDesigner support the following Arduino style boards -: Uno and Mega2560 with a Bluetooth classic / BLE (Bluetooth Low Energy) / WiFi / Ethernet or SIM900 SMS shield. Arduino101, RFduino, RedBearLab BLE shield, Adafruit's Bluefruit LE Flora, UART Friend and Bluefruit LE SPI, Adafruit Feather WICED and ESP8266 based wifi shields

This instructable is also available on-line at www.pfod.com.au

Overview

Update: 19th May 2017 – Downgrade Arduino101 firmware to V1.0.7

The latest Curie Firmware V2.0.2 looses connections. Arduino V1.8.2 and Curie V1.0.7 work, but not Curie V2.0.2

To downgrade from Curie V2.0.2 to V1.0.7
i) goto board manager and remove Curie V2.0.2
ii) stop IDE V1.8.2
iii) unplugged the Arduino101 board
iv) start IDE V1.8.2 and installed Curie V1.0.7 from board manager
v) plugged the Arduino101 board in and waited for the USB drivers to install
vi) reburn the boot loader on the Arduino101
vii) unplug the board.
viii) plugged the board back in
ix) reloaded the sketch

This tutorial covers the following topics, with numerous example sketches for Uno/Mega256 and Arduino101 boards:-

  • How to code a simple control using the Arduino drawing primitives in the pfodParser library and how to use pushZero() to set the position and scaling of your control.
  • How to use the z-idx to update drawing primitives.
  • How to turn your control into a re-usable library class. The pfodDwgControls library contains a number of examples
  • How to use Value Labels to display raw integer values, e.g. PWM and ADC value, mapped into real world floating point value with units.
  • How to build slider controls with a touchFilter that detects user's Drags and which gives immediate user feedback.
  • Finally a House Light control is built, that includes on-screen help and handles sending large sets of drawing commands with multiple messages.

Apart from turning the Arduino board LED on and off and controlling it via PWM, these examples do not go into the details of actually controlling hardware from your Arduino. To actually use your custom control to control something, first use pfodDesigner to create a basic menu that does what you want and then, from the generated code, cut and paste the code into one of the code templates below. For the final step see this page on how to connect relays to your Arduino.

The free pfodDesigner lets you design Android menus that turn outputs on and off, or pulse them, and control PWM outputs. pfodDesigner also lets you read analog inputs and display or plot and log them on your Android via pfodApp. pfodDesigner then generates the correct code for any of the boards listed above.

Steps Required to Design Your Own Custom Control

DrawingPrimitives.png

This instructable shows you how to make your own custom Android controls to control your Arduino (and other micros) without doing any Android programming. Your Arduino code controls everything, the appearance, the size, colour, user interaction and the response. pfodApp, running on your Android mobile, supports a small set of basic drawing support messages that let you create your own custom controls.

  1. The steps required to design your own custom control are:-
  2. Use the drawing primitives, circle, rectangle, label, etc, to draw your control.
  3. Add one or more TouchZones to detect user input and send a command to your Arduino.
  4. Add TouchActions to give the user immediate feedback on their Android mobile.
  5. Send back the update response from your Arduino code to show the user what happened.

A Simple First Control

indicatorButtonGIF_edited.gif
pfodBlankDwgTemplate.png

This first example is a simple indicating button.

Creating the Drawing Code Template

Here we are using an Arduino Mega 2560 board and a communication shield connected to Serial. (e.g. an Itead Bluetooth shield, a cheap ESP8266 Wifi shield or an Itead BLE shield or Adafruit BLE UART Friend)

The blank drawing template code for an Uno/Mega is here. There is also a blank drawing template for Arduino101 here. For other boards, use the free pfodDesignerV2 to create a menu and select your particular board/shield. Then Add Menu Item and select a Drawing as the menu item type. Select Generate Code for the pfodDesigner to generate the Arduino code for your selected board. Finally remove the sample control to get a blank drawing on which to build your own control.

When you run the blank template code in your Arduino and connect with pfodApp you will see and empty drawing 50 cols wide by 30 rows high. (shown above)

Remember to remove the shield from your Uno/Mega2560 when programming it.

By default the zero position is in the top left corner of the drawing and the scale factor is 1.0. Add a pushZero(....) to push the current zero onto the stack and apply a new zero offset and scale. If the third argument of pushZero(....) is omitted then the default scaling on 1.0 is used.

void sendDrawing_z() {
dwgs.start(50, 30, dwgs.WHITE); // background defaults to WHITE if omitted i.e. dwgs.start(50,30);
parser.sendVersion(); // send the parser version to cache this image
dwgs.pushZero(25,15); // move zero to centre of dwg use
// create your control here
dwgs.popZero();
dwgs.end();
}

The parser version string has been cleared in the blank templates, so no version is sent for this drawing and so it is not cached.

Note about pfodApp caching: pfodApp uses a version string to cache menus and drawings to reduce bandwidth and speed up response times, but when you are developing a new menu or drawing you don't want the old ones cached so edit parser( ) line at the top of the code to remove the version string. I.e

pfodParser parser(""); // create a parser Set version to empty “” to disable caching


Use the Drawing Primitives, Circle, Rectangle, Label, Etc, to Draw Your Control.

IndicatorButton_1_Mega.png
256ColorChart.png

For this simple indicator button we are going to use a circle and a rounded rectangle and a label. You could send the raw text that pfodApp uses to draw these items, but it is easier to use the drawing methods in the pfodParser library to build and send the messages for you. See the pfodSpecification.pdf for details of the format of the raw text messages.

Add the following code to the sendDrawing_z() method (IndicatorButton_1_UnoMega.ino and IndicatorButton_1_101.ino)
The dwgs object has been created at the top of the blank template code
pfodDwgs dwgs(&parser);

Note: Circles have their centre based at (0,0) while rectangles have their top left corner based at the (0,0). Labels have their text centred around (0,0)
The offset(....) method offsets the drawing primitive from the current zero

void sendDrawing_z() {
dwgs.start(50, 30, dwgs.WHITE); // background defaults to WHITE if omitted i.e. dwgs.start(50,30);
parser.sendVersion(); // no version send since parser version is empty so this image not cached
dwgs.setZero(25,15); // move zero to centre of dwg
dwgs.rectangle().color(dwgs.BLACK).rounded().size(5.5, 5.5).offset(-2.75, -2.75).send(); // rectangle, offset to center on circle
dwgs.circle().filled().color(dwgs.BLACK).radius(2).send(); // circle
dwgs.label().text(F("Led")).color(dwgs.RED).offset(0, 4.3).send(); // label, offset to below rectangle.
dwgs.popZero();
dwgs.end();
}

A note about numbers: The pushZero(...) method uses integers for its col,row numbers (and a float for its scale). When the user touches the control, the command sent by pfodApp only sends integer col and row for the location the user touched, and the filter that triggered this command. All other numbers can be floating point numbers such as the .radius(2.5) above.

A note about colours. pfodApp supports a wide range of colours from the basic 16, to a pallet of 256, to the full range of 48bit RGB colours. Here the drawing support methods are using the 256 pallet of colours (numbered 0 to 255). For convenience there are static class constants for the common ones i.e. dwgs.WHITE where dwgs is an instance of the pfodDwgs class. See the pfodSpecification.pdf for more details on colours.

Above is the 256 color chart. Use the integers from this chart to set drawing primitive color.

Add One or More TouchZones to Detect User Input and Send a Command to Your Arduino.

IndicatorButton_2_Mega.png
IndicatorButton_2_Mega_debugTick.png

To have your control respond to user interactions you need to define a TouchZone, an active area that will send a command when the user touches it. Here the circle is the area we want to be active. Add the following code just above dwgs.popZero(); (IndicatorButton_2_UnoMega.ino and IndicatorButton_2_101.ino)

dwgs.touchZone().cmd('a').size(4, 4).offset(-2, -2).send();
dwgs.popZero();
dwgs.end();

This defines a rectangular touch zone centered on and just covering the circle. When the user touches this area of the screen, pfodApp will send a command to your Arduino consisting of the command for this drawing menu item and the command of this particular touchZone together with the col and row the user actually touched and the type of touch that triggered this command, touch, click, long press, drag etc. e.g. the command sent will look like
{A~a`1`0`0}

In your Arduino code you can tell which drawing was touched and active zone of which drawing was touched, and where it was touched and how it was touched. TouchZones only respond to the types of touches that match their filter. The default the filter is TOUCH. That is any touch will send just one command and block other commands from this TouchZone until that command is responded to. This is most commonly what you will used for button type controls.

If you compile and run IndicatorButton_2_UnoMega.ino or IndicatorButton_2_101.ino on your Arduino and connect to pfodApp, you will not normally see where the TouchZone is. To make the touch zone visible, for debugging while you are developing your control, go to the Connection Edit screen of pfodApp, for this connection, and click the Log Debug data setting.

Then pfodApp will display the touch zones as dotted squares together with their command, as shown above.

Note about TouchZone sizes. As you can see above the touchZone is much larger than the circle it just covers. pfodApp automatically expands all touch zones by 4.5mm on each side, so that even very small controls are still finger selectable. This means you should ALWAYS make the touch zone only just the size to just cover the active part of the control and let pfodApp adjust them as needed. If adjacent touch zones happen to overlap, pfodApp detects this and triggers the most appropriate one.

When you turn on the Debug Log, pfodApp saves all the messages to a file on your mobile under the /pfodAppDebugData directory with the name of connection, e.g. Mega_debug.txt (i.e./pfodAppDebugData/Mega_debug.txt). In that file you will see the command that created this drawing. All pfodApp messages are in readable UTF-8 text.

pfodApp requests the first part, `0, of drawing z

< {z`0}

Your Arduino send the response
> {+15`50`30~~|z`25`15|RR~8~5.5~5.5~-2.75~-2.75|t~9~Led~~4.3|C~0~1.5~~|x~a~4~4~-2~-2|z}

The {+ starts a drawing, |z... pushes the zero, |RR is a filled rounded rectangle, |t is text (a label), |C is a filled circle and |x is the touch zone. The first number in each case (except z) is the colour from the 256 colour palette (above). See the pfodSpecification.pdf for the details of the rest of the fields for each of the commands.

Add TouchActions to Give the User Immediate Feedback on Their Android Mobile.

IndicatorButton_3_Mega.png

It often takes some time to send the command to your Arduino and to get a response back, particularly if using SMS, so you should add one or more TouchActions for the TouchZone. A TouchAction is executed immediately the TouchZone is triggered and provides the user with immediate visual feedback that something is happening.

In this case we expect the LED to turn on when the user operates the control, so we will use a YELLOW circle for the TouchAction, however we will make the circle bigger to indicate to the user that this is a transient condition. TouchActions are temporary and disappear completely when the update response from the Arduino is received.

TouchActions are linked to TouchZones and must be send AFTER the TouchZone is sent. Add the following line of code below the dwgs.touchZone(... code. (IndicatorButton_3_UnoMega.ino and IndicatorButton_3_101.ino)

dwgs.touchZone().cmd('a').size(4, 4).offset(-2, -2).send();
dwgs.touchAction().cmd('a').action( //
dwgs.circle().filled().color(dwgs.YELLOW).radius(2.3) // what to show when zone touched, note no .send() for this action
).send();
dwgs.popZero();
dwgs.end();

The argument to the .action( … ) method is the drawing primitive, but without its .send(). This is what is to be shown when the user touches the associated touch zone, like so.

Note: The YELLOW circle disappears as soon as your Arduino responds to the command sent by this touch. TouchActions are temporary and disappear completely when the update response from the Arduino is received. The next section will cover sending back the update to show the user what actually happened.

Send Back the Update Response From Your Arduino Code to Show the User What Happened.

IndicatorButton_4_Mega.png

Now you have a control that responds to the user's touch, shows the user something is happening and sends a command to your Arduino, your Arduino code should take some action and send back an update to drawing to show the user the current state of the Led.

In the loop() method, the block of code, below, handles commands from the drawing. In the BlankTemplate sketches, (BlankDwgTemplate_UnoMega.ino and BlankDwgTemplate_101.ino), it just sends back the (empty) drawing update.

} else if ('A' == cmd) { // user pressed menu item that loaded drawing with load cmd 'z'
// in the main Menu of Menu_1
byte dwgCmd = parser.parseDwgCmd(); // parse rest of dwgCmd, return first char of active cmd
// can be \0 if no dwg or no active areas defined
// Handle your control commands here
sendDrawingUpdates_z(); // update the drawing

The parser.parseDwgCmd() code parses the drawing command and returns the touchZone command character.

To add an action for your control, you first need to check which touchZone was triggered and then take the appropriate action before returning the drawing update.

} else if ('A' == cmd) { // user pressed menu item that loaded drawing with load cmd 'z'
// in the main Menu of Menu_1
byte dwgCmd = parser.parseDwgCmd(); // parse rest of dwgCmd, return first char of active cmd
if ('a' == dwgCmd) { // user touched LED control
digitalWrite(LED_pin,HIGH); // turn led ON
}
sendDrawingUpdates_z(); // update the drawing
// always send back a response or pfodApp will timeout

Also add the Led_pin constant at the top of the file const int LED_pin = 13;

This loop() code turns the LED on when the user touches the control. Note: This code only turns the Led on and will not turn it off. See the next section for turning the Led on and off.

Z-idx for unique updates

When you send back the update to the drawing, you want to replace the BLACK circle to a YELLOW one to indicate the LED is ON. To replace an existing drawing primitive, you need to use a unique positive z-idx in order to reference it. So in the sendDrawing_z() method, add an idx(..) to the circle.

void sendDrawing_z() {
...
dwgs.circle().idx(1).filled().color(dwgs.BLACK).radius(1.5).send();
...

Then in the sendDrawingUpdates_z() you can use the same idx(1) to replace the BLACK circle with a YELLOW one. Add this code to the sendDrawingUpdates_z() method.

void sendDrawingUpdates_z() {
dwgs.startUpdate();
// update your control here
dwgs.circle().idx(1).filled().color(dwgs.YELLOW).radius(1.5).send();
dwgs.end();
}

When this update arrives at pfodApp, any touchActions are automatically discarded and the original drawing is updated with this replacement YELLOW circle. (IndicatorButton_4_UnoMega.ino and IndicatorButton_4_101.ino)

Indexed drawing primitives remember their zero position and their scaling, but you can move them by changing their offset().

Turning the LED on and Off and Re-position and Scale the Button.

indicatorButtonGIF_edited.gif

Currently your control only turns the LED on, not off. To turn the LED on and off, you need to record the current LED state and adjust your control's display, actions and update accordingly.

Add a variable, at the top of the file, to hold the current LED state. bool LedOn = false;

When processing the control's command toggle the LED state and update the Led

} else if ('A' == cmd) { // user pressed menu item that loaded drawing with load cmd 'z'
// in the main Menu of Menu_1
byte dwgCmd = parser.parseDwgCmd(); // parse rest of dwgCmd, return first char of active cmd
if ('a' == dwgCmd) { // use touched LED control
LedOn = !LedOn; // change led state
if (LedOn) {
digitalWrite(LED_pin, HIGH); // turn led ON
} else {
digitalWrite(LED_pin, LOW); // turn led OFF
}
}
sendDrawingUpdates_z(); // update the drawing

Modify the sendDrawing_z() and sendDrawingUpdates_z() to show the correct circle depending on the LED state.

dwgs.circle().idx(1).filled().color(LedOn ? dwgs.YELLOW : dwgs.BLACK).radius(1.5).send();

Finally change the zero position to (10,12) and scale the button (and label) by 2.1 times. i.e. dwgs.pushZero(10, 12, 2.1); in the sendDrawing_z() method. (IndicatorButton_5_UnoMega.ino and IndicatorButton_5_101.ino) A Led label has also been added to the sendDrawing_z() method.

dwgs.label().text(F("Led")).color(dwgs.RED).offset(0, 4.3).send();

Notice how the BLACK circle is shown on top of the YELLOW touchAction. This is because z-indexed drawing primitives are always drawn after, and on, top of un-indexed drawing primitives. When you go to turn the Led off, the YELLOW circle is also drawing on top of the YELLOW touchAction, but is not noticable. More about z-orders in the next section.

The next section will also cover how to convert a control to a re-usable library class that you can add multiple times to the one or more drawings

On Off Switch Control

switchGIF_edited.gif

This next example will introduce z-order indices, popZero() and cover how to convert your control to a library class for repeated use. The completed control in operation is shown above.

Here is the non-library version of the Arduino source code for Uno/Mega 2560 board. (Arduino101 code is here) Refer to the source code for the discussion below. The sendDrawing_z() method now calls draw() to draw the control after first setting the zero position and scaling.

void sendDrawing_z() {
dwgs.start(50, 30, dwgs.WHITE); // background defaults to WHITE if omitted i.e. dwgs.start(50,30);
parser.sendVersion(); // send the parser version to cache this image
dwgs.pushZero(15, 8, 1.5); // move zero to centre of dwg to 15,8 and scale by 1.5 times
draw();
dwgs.popZero();
dwgs.end();
}

The sendDrawingUpdates_z() method just starts a drawing update, {+, and then calls update() to send the current state of the control.

void sendDrawingUpdates_z() {
dwgs.startUpdate();
// update your control here
update(); // update with current state
dwgs.end();
}

Z-orders

The labels now use the fontSize(..) and bold() options and three of the drawing primitives have z-order indices assigned. These are the OFF and ON labels and the black/green rounded rectangle that indicates the current state. Z-orders must be unique within a drawing and must be greater then zero. Higher z-orders are drawn after, and on top, of lower z-orders. Drawings without a z-order are drawn first, in the order they are received.

So the code (in sendDrawing_z() and sendDrawingUpdates_z())

const int z_idx = 1;

dwgs.rectangle().filled().rounded().idx(z_idx).size(6, 4).offset(((value != 0) ? 0 : -6), -2).color((value != 0) ? dwgs.GREEN : dwgs.BLACK).send();

dwgs.label().fontSize(-1).text("OFF").idx(z_idx + 1).color(dwgs.WHITE).offset(-3, 0).send();
dwgs.label().fontSize(-1).text("ON").idx(z_idx + 2).color(dwgs.WHITE).offset(3, 0).send();

insures that, regardless of the order they are received, the OFF and ON labels are always drawn on top if the filled Green/Black rounded rectangle, which as a lower z-order. The z-order index also provides a means of uniquely referencing the drawing primitives for updating its size, shape, colour, etc.

Z-order indexed drawing primitives also remember the zero and scale where they were originally drawn, so the z-order index assigned to the Green/Black rectangle lets you update its size, colour and relative offset without having to first call pushZero(...). The sendDrawing_z() method calls pushZero(..) to position and scale the control and then calls draw() to draw the control at the current position. The end of the draw() method calls the update() method to set the current setting of the control. The .idx( ) options ensure that the those indexed items remember their zero position and scaling. Touch zones and touch actions also remember their position and scale and can be updated by referencing their unique command.

Note in the update() method, that the touch action, that is attached to the buttonCmd touch zone, uses the z_idx of the filled Green/Black rounded rectangle, to reference it, so that it is resized and moved it when the user touches the zone. This gives the immediate feedback. When the response comes back from your Arduino this z_idx rectangle is again resized and positioned to show the current state, by the first line of the update() method

void update() {
// set the current setting position and color
dwgs.rectangle().filled().rounded().idx(z_idx).size(6, 4).offset(((value != 0) ? 0 : -6), -2).color((value != 0) ? dwgs.GREEN : dwgs.BLACK).send();
// update the touchZone and action to the opposite side then is currently set
dwgs.touchZone().cmd(buttonCmd).size(6, 4).offset(((value != 0) ? -6 : 0), -2).filter(dwgs.TOUCH).send();
dwgs.touchAction().cmd(buttonCmd).action( // create the action
dwgs.rectangle().filled().rounded().idx(z_idx).size(6, 3).offset(((value != 0) ? -6 : 0), -1.5).color((value != 0) ? dwgs.BLACK : dwgs.GREEN)
).send(); // send the touchAction
}

popZero()

pushZero(...) pushes the current zero position and scaling onto a stack and applies the new zero offset and scaling. The new offset is first scaled by the previous scaling before being applied. The new scaling is the (previous scaling * the new scaling).

popZero() undoes this zero move and scaling and restores the previous zero position and scaling. When converting your control to a re-usable class, you should match each pushZero() with a popZero() so that at the end of drawing your control, the zero has been restored to what it was at the beginning.

Converting Your Control to a Reusable Class

Once you have designed your control, you can convert it to a library class for easy re-use. BlankControl.cpp and BlankControl.h provides an empty template for your controls. They use the super-class pfodControl from the pfodParser library to provide some basic support that most controls will use.

To define you own control class, called MyControl for example, copy and rename the BlankControl.cpp and BankControl.h files to MyControl.cpp and MyControl.h and inside those files do a global rename of BlankControl to MyControl. You need to reserve some unique z_idx's for your control and add the draw() and update() code for your control.

Copy your draw() code into the MyControl::draw() method and copy your update() code into the MyControl::update() method. (see the MyControl.cpp file)Then find and replace all instance of dwgs. with dwgsPtr-> and replace .text(F(“Led”)) with .text(label) The pfodControl super-class has methods to let you set different labels for each instance of your control.

void MyControl::draw() {
// draw your control here
// circle in a rectangle with a label below, sendDrawingUpdates_z() sends the corrent circle colour
dwgsPtr->label().fontSize(2).bold().text(label).color(dwgsPtr->RED).offset(0, 4).send();
dwgsPtr->rectangle().color(dwgsPtr->GREY).rounded().filled().size(12, 3).offset(-6, -1.5).send();
// 6x3 + 6x3
// add on/off labels with higher z-order then the button rectangle so they are always drawn on top of it
dwgsPtr->label().fontSize(-1).text("OFF").idx(z_idx + 1).color(dwgsPtr->WHITE).offset(-3, 0).send();
dwgsPtr->label().fontSize(-1).text("ON").idx(z_idx + 2).color(dwgsPtr->WHITE).offset(3, 0).send();
update(); // update with current state
}

void MyControl::update() {
// update your control here
dwgsPtr->rectangle().filled().rounded().idx(z_idx).size(6, 4).offset(((value != 0) ? 0 : -6), -2)
.color((value != 0) ? dwgsPtr->GREEN : dwgsPtr->BLACK).send();
// update the touchZone and action to the opposite side then is currently set
dwgsPtr->touchZone().cmd(buttonCmd).size(6, 4).offset(((value != 0) ? -6 : 0), -2).filter(dwgsPtr->TOUCH).send();
dwgsPtr->touchAction().cmd(buttonCmd).action( // create the action
dwgsPtr->rectangle().filled().rounded().idx(z_idx).size(6, 3).offset(((value != 0) ? -6 : 0), -1.5)
.color((value != 0) ? dwgsPtr->BLACK : dwgsPtr->GREEN) //
).send(); // send the touchAction
}

At the top of the .cpp file, allocate the number of unique z indices you need for your control. e.g.

z_idx = pfodDwgs::reserveIdx(3); // reserve 3 indices for this control Z_idx, Z_idx+1, z_idx+2

The pfodDwgs static method reserveIdx( ) returns the next available index and also reserves the number requested. This is called each time you create another control and ensures all your controls have unique z_idx's Note: drawing code uses idx(z_idx) and idx(z_idx +1) etc.

In the main arduino .ino file (see MyControl_UnoMega.ino) include your MyControl.h, create an instance and set the label and initial value in the setup(). The loop() code becomes

} else if ('A' == cmd) { // user pressed menu item that loaded drawing with load cmd 'z'
// in the main Menu of Menu_1
byte dwgCmd = parser.parseDwgCmd(); // parse rest of dwgCmd, return first char of active cmd
if (myControl.getCmd() == dwgCmd) { // use touched LED control
myControl.setValue((myControl.getValue() == 0) ? 1 : 0); // toggle value
digitalWrite(LED_pin,((myControl.getValue() == 0) ? LOW : HIGH)); // set LED
}
sendDrawingUpdates_z(); // update the drawing

and the sendDrawing_z() and sendDrawingUpdates_z() methods become

void sendDrawing_z() {
dwgs.start(50, 30, dwgs.WHITE); // background defaults to WHITE if omitted i.e. dwgs.start(50,30);
parser.sendVersion(); // send the parser version to cache this image
dwgs.pushZero(15, 8, 1.5); // move zero to centre of dwg to 15,8 and scale by 1.5 times
myControl.draw(); // draw the control
dwgs.popZero();
dwgs.end();
}

void sendDrawingUpdates_z() {
dwgs.startUpdate();
myControl.update(); // update with current state
dwgs.end();
}

The completed code for Uno/Mega is in the MyControl_UnoMega.zip file (The Arduino101 code MyControl_101.zip is here) This control is include in the pfodDwgControls library as OnOffSwitch

Tips for Making Re-useable Control Library Classes

OnOffSwitch_2_Mega.png
  1. Always call the update() method at the end of the draw() method to set the control's initial state.
  2. In the draw() method always undo any pushZero() with a matching popZero().
  3. Use the idx( ) option for all drawing primitives in update() method so they pick up the zero and scaling settings set when update() was called from the draw() method. TouchZones/Actions automatically save their base settings.
  4. Never call pushZero() in the update() method.

Now that you have your own control class, you can easily add another instance of it to this drawing or to another drawing. Here in the next example, a slightly scaled down version of the control has been added to control output D3 (MyControls_2_UnoMega.zip and MyControls_2_101.zip)

Value Labels

Guage_Mega.png

Labels also have an option of mapping an integral value to a display range in pfodApp. The Guage control is an example of this use. See Guage.cpp in the pfodDwgControls library.

dwgsPtr->label().color(dwgsPtr->RED).idx(z_idx+2).fontSize(-3).bold()
.text(label).intValue(value).maxValue(255).displayMax(100).decimals(1).units(F("%")).send();

This statement maps the value from 0 to 255 into 0.0 to 100.0 and builds the label as PWM mappedValue %. i.e. if value == 117 the label will display PWM 45.9%

To map the integer value, call the intValue( ) option and then set the maxValue(), minValue() and the corresponding displayMax() and displayMin() values. minValue() defaults to 0 and displayMin() defaults to “0.0”.

The label() also has a floatReading() option that lets you send a floating point value to displayed between the label and the units, to number of decimals() specified. The floatReading() and intValue() options are mutually exclusive, the last one specified wins.

Slider Controls

slider_GIF_edited.gif
SliderGuage_GIF_edited.gif

This control is Slider in the pfodDwgControls library.

As the user drags the slider, the current value is displayed above. High enough above so the user's finger does not cover it. When the user releases the slider, the command is sent to your Arduino and the response updates the slider position.

In the previous controls it did not matter much what the size of the control was that you designed, you just scaled it when you used pushZero( ) to place it on the drawing. However for sliders the size of the unscaled control does matter. When pfodApp sends a drawing command it includes the unscaled column and row touched, in addition to the menu command and the touch zone command. An example of the command pfodApp would send to your Arduino when a slider control was moved would look like

{A~a`43`0`4}

where A is the menu command of the menu item displaying the drawing, a is the touch zone command for the slider and `43`0 is the col and row (before scaling) the user released the slider on. (`4 is the touch filter, UP, that generated this command) The important point to note is that the col and row numbers are always and only integers as measured from the start of the touch zone. So if you want your horizontal slider to cover the range 0 to 255, then you need to design the control as 255 columns wide and then use setZero(...) scaling to scale it down for display, but the commands pfodApp sends from the slider will still be terms of it original, unscaled size, i.e. 0 to 255.

This example illustrates three new features:- DOWN_UP touch filter, Touched Column, and Multiple Touch Actions.

DOWN_UP touch filter

The TouchZone for this control uses the DOWN_UP filter

dwgsPtr->touchZone().cmd(sliderCmd).size(255, 1).offset(0, -2.5).filter(dwgsPtr->DOWN_UP).send();

This DOWN_UP filter detects when the user touches and drags their finger and when they lift their finger off the screen. With the DOWN_UP filter only the user's finger up action sends a command to your Arduino. The user's down and drags only trigger the associated TouchActions but do not send any commands. This is the filter you will usually use for slider type controls. Other touchZone filters you can specify include DOWN, DRAG, UP, CLICK, LONG_PRESS and DISABLED. You can add filters constants together to trigger on more than one user touch action. However the TOUCH and DISABLED filters can not be combined with any others. The two filters you will use most often are TOUCH, the default if you don't specify a filter, and DOWN_UP for sliders

Touched Column

As shown above, the command sent to your Arduino includes the unscaled column and row the user touched
{A~a`43`0`4}

In your Arduino code, after you have called parser.parseDwgCmd(), you can retrieve this column and row information via parser.getTouchedCol() and parser.getTouchedRow()

} else if ('A' == cmd) { // user pressed menu item that loaded drawing with load cmd 'z'
byte dwgCmd = parser.parseDwgCmd(); // parse rest of dwgCmd, return first char of active cmd
if (slider.getCmd() == dwgCmd) { // use touched LED control
int col = parser.getTouchedCol();
slider.setValue(col); // set new value
guage.setValue(slider.getValue()); // set guage from slider
analogWrite(LED_pin, slider.getValue()); // set LED PWM
}
sendDrawingUpdates_z(); // update the drawing

Your TouchActions can also use this col, row information to set the size, offset or label value. The special pfodDwgs constants TOUCH_COL and TOUCH_ROW are used to send touch actions that will pickup the touched col and row before being displayed on the screen. e.g.

dwgsPtr->touchAction().cmd(sliderCmd).action( //
dwgsPtr->line().color(dwgsPtr->BLACK).offset(dwgsPtr->TOUCHED_COL, -40).size(0, 40)//
).send();

This tells pfodApp to draw a line at the column offset the user touched when the user touches inside the associated touch zone. This is how the moving indicating line is drawn as the user slides their finger along the slider.

Multiple Touch Actions

This example also uses multiple touch actions for the slider touch zone. After you send the touchZone you can send more than one touchActions for that touchZone cmd.

dwgsPtr->touchZone().cmd(sliderCmd).size(255, 1).offset(0, -0.5).filter(dwgsPtr->DOWN_UP).send();
dwgsPtr->touchAction().cmd(sliderCmd).action( //
dwgsPtr->label().idx(z_idx + 2).decimals(1).intValue(dwgsPtr->TOUCHED_COL).units("%").maxValue(255)
.displayMax(100).fontSize(18).color(dwgsPtr->BLACK).offset(dwgsPtr->TOUCHED_COL, -50)//
).send();
dwgsPtr->touchAction().cmd(sliderCmd).action( //
dwgsPtr->line().color(dwgsPtr->BLACK).offset(dwgsPtr->TOUCHED_COL, -40).size(0, 40)//
).send();

When the touchZone is triggered, depending on its filter, all previously applied touch actions will be discarded and the associated touch actions for the touchZone will update the original drawing. Note in this example the first touchAction, the label, has the same idx as the slider label and so replaces it when the user is dragging the slider. The second touchAction, the indicator line, does not need an idx, because each time the touchZone is triggered the previous line is discarded and line is re-drawn at the new location.

Pre-Scaling Controls

Finally the first line of the draw() method in the Slider.cpp file in pfodDwgControls library is
pushZero(0, 0, 0.125);
which leave the zero position unchanged but scales down the slider from 255 cols wide to 32 cols for display. At the end of the draw() method there is a corresponding popZero() to undo this scaling for the next control.

House Lights Control and On-Screen Help

FirstFloor_GIF_edited.gif
HouseLights_GIF.gif

The pfodDwgControls library as a selection of example controls you can use immediately. See the pfodDwgControls library examples.

The drawing commands sent to your Arduino, when the user triggers a touchZone, do not have to update the drawing, they can also open sub-menus or other pfodApp screens, like the text input screen, just by returning that pfod message instead of returning a drawing update message. See the pfodSpecification.pdf for the details of the other screen messages. Use the free pfodDesigner to create sub-menus for you.

As a final example here is a House Lights control menu with on-screen help. This example also illustrates how to break large drawings up into multiple messages. The menu actually has two drawings.

The complete code is in HouseLights_UnoMega.zip (HouseLights_101.zip for Arduino101)

NOTE: In those HouseLights sketches, the parser version has been set, pfodParser parser("HL_1"); , to enable caching of the menu and drawings. If you want to edit the sketch either change the version number or remove it altogether until you have finished your changes.

On-Screen Help Button

Using a label as the touch action is an easy way to add on-screen help button. The pfodDwgControls library includes a Help class that does this for you.

Create a Help object with a unique drawing cmd. This drawing cmd only needs to unique to the drawing you are adding the help object to. You can re-use drawing cmds across different drawings.

Help firstFloorHelp('z', &dwgs)

Then in the sketch setup() method assign your help text and set the size of the background rounded rectangle. You need to adjust the size of the background to match your help text. A good starting point is setting the width to 2 x the maximum line length and setting the height to 3 x the number of lines. Then round up the height to the next even number.

firstFloorHelp.setLabel(F("Touch the lights to\nturn them on and off"));
// 19 char max x 2 lines == ~ 38 x 12 height round up to next even number
firstFloorHelp.setWidthHeight(38, 12);

In the sendDrawing_z() method, position and draw the help on the drawing, scaling if necessary

dwgs.pushZero(67, 68);
firstFloorHelp.draw();
dwgs.popZero();

When the user touches the ?, the help text and background is displayed via the touchActions. The ? touchZone has a DOWN_UP filter so the help remains displayed until the user removes their finger. Then the help dwgCmd is sent to your Arduino. In your Arduino sketch there is nothing to do. Just ignore the help dwgCmd and return a drawing update. The drawing update will remove all the touch actions (the help) and update the drawing.

Finally in the Help.cpp class, 200 as been added to the unique z_idx's. This is to ensure that the help ? and the popup help text will always be on top of any other parts of the drawing.

z_idx = 200+pfodDwgs::reserveIdx(4); // reserve 4 unique indices
// and then add 200 to put these z_idx on top of everything else in the drawing

Drawing Messages in Multiple Parts

The First Floor drawing message for the House Lighting is less the 1023 bytes and so can be sent as a single message. However the Ground Floor drawing message is approximately 1423 bytes and if you try and send it in one message, pfodApp will truncate the message processing at 1023 bytes and log the rest of the message bytes as RawData. If your drawing is not showing or updating as expected then check the Raw Data screen to see if some of the drawing message has been dumped there instead. If so you will need to break your drawing message up into multiple parts.

As mentioned above, pfodApp loads the drawings in the background. The drawing menu item specifies the drawing load command, e.g. |+A~z , where |+ indicates a drawing menu item and z is the drawing load command. See the sendMainMenu() method in the example sketches. After processing the menu, pfodApp sends a request to load the first part of the drawing
{z`0}

The argument to this command, `0, indicates zero`th request. If your Arduino code sends back a drawing message with the more data flag set, ~m , then pfodApp will request the next block of the drawing message with {z`1} and so on until either last message is empty, {}, or it does not have the more data flag set.

If your drawing is less than 1023 bytes you can just ignore the `0 argument and return the drawing. This has been case for all the previous examples.

} else if ('z' == cmd) { // pfodApp is asking to load dwg 'z'
if (!parser.isRefresh()) { // not refresh send whole dwg
sendDrawing_z();
} else { // refresh just update drawing state
sendDrawingUpdates_z();
}

For the Ground Floor drawing, which is larger then 1023 bytes, drawing load cmd y code looks like this

} else if ('y' == cmd) { // pfodApp is asking to load dwg 'y'
if (!parser.isRefresh()) { // not refresh send whole dwg
parser.parseLong(pfodFirstArg, &pfodLongRtn); // get starting offset
if (pfodLongRtn == 0) {
sendDrawing_y0();
} else if (pfodLongRtn == 1) {
sendDrawing_y1();
} else {
parser.print(F("{}")); // finished, in case sendDrawing_y1() set the more data flag
}
} else { // refresh just update drawing state
sendDrawingUpdates_y();
}

The sendDrawing_y0() method sends the help button and the floor plan and sets the more data flag. Note: pushZero(5, 10) is not popped here but is carried over to the next part of the message.

void sendDrawing_y0() {
dwgs.start(70, 75, dwgs.WHITE, true); // , true arg sets the more data flag
parser.sendVersion(); // send the parser version to cache this image

dwgs.pushZero(67, 73);
groundFloorHelp.draw();
dwgs.popZero();

dwgs.pushZero(5, 10);
drawGroundFloorPlan(); // in file GroundFloor.ino
//dwgs.popZero(); don't pop here as next part of drawing uses same zero
dwgs.end();
}

The sendDrawing_y1() sends the rest of the drawing, the light controls and their updates (called from the drawGroundFloorControls() method).

void sendDrawing_y1() {
dwgs.startUpdate(); // next message is an update to y0, dwgs.startUpdate(true); would set the more data flag
drawGroundFloorControls();
dwgs.popZero(); // pop for push in y0 part
dwgs.end();
}

Conclusion

The House Lighting example presents the user interface and does not go into the details of actually controlling lights from your Arduino.

To actually use this custom control to turn something on/off, first use pfodDesigner to create a basic menu that turns outputs on and off and then, from the generated code, cut and paste the code into one House Lights template. For the final step see this page on how to connect relays to your Arduino.

pfodApp V3 provides a small set of powerful Arduino drawing primitives that let you create your own custom Android controls. The touch zone and touch actions give you control over how user input is handled and provides the user with immediate feedback for their actions. This instructable started with a basic control and covered how to convert your own controls to reusable classes.

The pfodParser library simplifies creating the drawing messages and the pfodDwgControls library contains a number of pre-built controls ready to use, including slider, buttons and on-screen help.

The menu and drawing caching, provided by pfodApp, reduces bandwidth and speeds up display. These drawing controls are practical even for SMS connections.