My App Demo Example

WiPhone has got a simple mechanism of integrating a newly developed or existing app(s) in its firmware. In the following lines, we have shown a general procedure to add an example App, e.g “MYAPP” to the firmware code.In WiPhone, MYAPP can be opened via Tools -> Development -> My App. It is the most basic App implemented in firmware for demonstration. When opened, it shows a text of "Hello World", an icon and some text "I'm Awesome!" on default screen. By Pressing up arrow key button followed by "OK" button for first time, displays WiPhone's audio levels in decibels (dB) while pressing down arrow key followed by "OK" button for first time will also display the same audio volume levels. Repeating this process will keep on showing increased or decreased volume levels in dBs depending upon the "up arrow / OK" or "down arrow / OK" buttons combination until max value of (6dB) or min value of (-69dB) is reached. Pressing "Back" button will exit the App. User needs to follow procedure given here to implement this demo App.

image1 image2

Identify the specific areas of code to modify

Summary of commonly used parameters such as; functions/ methods, files, and flags in Example codes are mentioned here with brief description. These parameters will be used and updated in each app code with specific functions pertaining to that app.

GUI.h:

The app class declaration goes in GUI.h. More on this to follow in Header File description.

GUI.cpp:

Methods definitions go in GUI.cpp, also basic GUI class features are handled here. Inside GUI.cpp, Every app defines two main methods: "processEvent()" and "redrawScreen()". More detail description of the classes, arguments and parameters will be provided in MyApp Source Code description.

processEvent (EventType event):

It is a method, called by GUI to allow your app, process events and update its internal state accordingly. The events can be, for example, button presses, timers (as requested by your app) or scheduled events (as scheduled by your app).

redrawScreen (bool redrawAll):

It is a method that is called by GUI to allow your app to redraw the screen partially and/or telling your app that it should redraw the screen entirely (like the whole screen gets redraw after running some other app, e.g. a call or a screen lock).

typedef enum ActionID : uint16_t {}:

use to enumerate unique name ID for your app. Exists in GUI.h.

GUIMenuItem menu[38] PROGMEM = {}:

Custom GUI class for the Application in GUI.h.

void GUI::enterApp (ActionID_t app):

Responsible for handling in GUI.cpp.

Declaring a unique name for your app in GUI.h file

To do this, search for "typedef enum ActionID" method in GUI.h file and put your app name (for example: GUI_APP_MYAPP in this case) as shown in below code snippet.

GUI.h

// Menu actions
typedef enum ActionID : uint16_t {
  NO_ACTION = 0,
  GUI_ACTION_MAINMENU,
  GUI_ACTION_SUBMENU,
  GUI_ACTION_RESTART,
  // Specific applications
  GUI_BASE_APP = 0x4000,      // application flag
  // My Section
  GUI_APP_MYAPP,      // add your app's unique ID like this
  // Interface
  GUI_APP_MENU,
  GUI_APP_CLOCK,

Adding app ID into the menu

WiPhone has got it’s own menu names and codes. You can insert your app nested inside any of the Main Menus or create a Custom Menu using new identifiers. Example of Main Menu Items are Tools, Development, Games, Settings to name a few. To place your app on WiPhone’s menu list, Find GUIMenuItem menu[36] PROGMEM in file GUI.h. Increase size of the array by 1 and add a line of the following form into the definition of the array "menu".

{ XX, YY, "Menu Name", "", "", GUI_APP_MYAPP }

  1. First argument “XX” here is the index for your App within the Menu Array.
  2. Second argument “YY” belongs to index of Main Menu you want to use for your App.
  3. Third argument “Menu Name” displays the desired name for your app to appear in WiPhone’s GUI .
  4. What you want to show on WiPhone Screen at left button press in footer when user has selected this app.
  5. What you want to show on WiPhone Screen at Right button press in footer when user has selected this app.
  6. Last argument takes enum data type declared for your app in typedef enum ActionID.

As a result, a new App called “Menu Name” will be added under the Menu Item which is having the index number “YY”. Example code snippet to add your App ID in designated place is given below. Remember! to increment array size by 1, every time you add a new App.

GUI.h

GUIMenuItem menu[36] PROGMEM = {  // increment size by one to add a new app
// TODO: button names can be removed
 { 0, -1, "Clock", "Menu", "", GUI_APP_CLOCK },
  { 1, 0, "WiPhone", "Select", "Back", GUI_ACTION_SUBMENU },
  // Main menu items
  // TODO: call log (icons: Call_log_b/Call_log_w)
  { 2, 1, "Phonebook", "", "", GUI_APP_PHONEBOOK },
  { 20, 1, "Messages", "", "", GUI_APP_MESSAGES },
  { 3, 1, "Tools", "Select", "Back", GUI_ACTION_SUBMENU },
  { 4, 1, "Games", "Select", "Back", GUI_ACTION_SUBMENU },
  { 5, 1, "Settings", "Select", "Back", GUI_ACTION_SUBMENU },
  { 13, 1, "Reboot", "", "", GUI_ACTION_RESTART },
  // Tools (3)
  { 25, 3, "MP3 player", "", "", GUI_APP_MUSIC_PLAYER },
  { 31, 3, "Audio recorder", "", "", GUI_APP_RECORDER },
  { 14, 3, "Scan WiFi networks", "", "", GUI_APP_NETWORKS },// duplicate from below
  { 7, 3, "Note page", "", "Back", GUI_APP_NOTEPAD },
  { 21, 3, "UDP sender", "", "", GUI_APP_UDP },
  { 28, 3, "Development", "Select", "Back", GUI_ACTION_SUBMENU },
  **// Menu Item → Development, Index number (28) & Menu Item → “My App”, Index //number (36) are added  here**
  { 36, 28, "My App", "", "", GUI_APP_MYAPP },
  { 27, 28, "Diagnostics", "", "", GUI_APP_DIAGNOSTICS },

Instantiate the App

To let GUI know how to call your app’s constructor, find definition of method "GUI::enterApp(ActionID_t app)" in file GUI.cpp and add code to create a new object on heap out of your app class. Code snippet below depicts this for “MyApp” class example.

GUI.cpp

void GUI::enterApp(ActionID_t app) {
  GUI_DEBUG("entering app");
  // Free dynamic memory
  cleanAppDynamic();
  flash.end();
  // Change to new app. E.g. MyApp in this case
  switch(app) {
  case GUI_APP_MYAPP:
        runningApp = new MyApp(audio, *screen, state, header, footer); //MyApp is assigned to 'runningApp' with 'new' keyword
        break;
  case GUI_APP_MENU:
        state.setInputState(InputType::Numeric);
        menuDrawn = false;
        break;

Declare and Define the app class and methods

There is a constructor method and allied functions that need to be defined in GUI.cpp. Simple way of doing all this is to find an existing app similar to the one you want to build, copy its code, change the class name, and code inside to do the things you want. Make sure the class method "getId" returns the ID that you created in typedef enum ActionID.

My App Example Code

Header File Description

GUI.h file contains definitions of “Widgets”, “Applications” and “Menu Items” within the WiPhone firmware. In addition , GUI.h declares the GUI class which contains all required objects and methods to use the mentioned items. The include directives (#include<>) on top of the GUI.h indicates which functionalities are used in the GUI module.

GUI.h

class ControlState {
        public:
                ...
                //NEW APP STATES
                int myappstate = 0;
};

...

class MyApp : public WindowedApp, FocusableApp {
 public:
  MyApp(Audio* audio, LCD& disp, ControlState& state, HeaderWidget* header, FooterWidget* footer);
  virtual ~MyApp();
  ActionID_t getId() { return GUI_APP_MYAPP; };
  appEventResult processEvent(EventType event);
  void redrawScreen(bool redrawAll=false);

 protected:
  Audio* audio;

  bool screenInited = false;

  // WIDGETS
  RectWidget* clearRect;
  RectIconWidget* iconRect;

  LabelWidget*  demoCaption;
  LabelWidget*  debugCaption;
};

Source Code Description

In GUI.cpp, My App class inherits the functionalities for header and footer from the base class WindowedApp and it inherits the functionalities for managing focus states of its widgets from the base class FocusableApp. The constructor of this method instantiates a new object and stores it in variable of MyApp class. Functionally, this method creates widgets in the memory (without drawing anything), assigns state variables to initial values such as spacing between widgets, title of the window, header text, texts of the buttons within the footer, and so on. It creates 4 widgets, one is an icon, another one is a rectangle for entire window, and two of them are labels. ControlState, Header and Footer objects are unique system-wide singleton objects, switches from one class to another by passing as argument and not created within this constructor. Description of each argument parameter appears in this class method is elaborated as below:

Param-1: First argument is a pointer of Audio class which prints the audio volume levels of the different devices at the bottom of WPhone screen.

Param-2: Second argument is a reference variable of LCD class (Singleton object reference). It calculates the positions of header, footer and other supported labels and icons.

Param-3: Third argument is a reference variable of ControlState class. Its base classes use this state reference of a Singleton object for controlling the states (input state, focus state, etc.) of the widgets that it contains. For this app, the ControlState singleton variable also contains a state variable with name myappstate which manages the current state and it is accessed by controlState.myappstate within the app. The controlState.myappstate variable is of integer type, its value is changed according to pressed buttons.

Param-4: Fourth argument is a pointer of the HeaderWidget class. It is a Singleton object for this class available to use by the whole firmware. The header widget resides on top of the screen and changes only its text.

Param-5: Fifth argument is a pointer of the FooterWidget class. It is also a Singleton object, which means that whole firmware has only one object of the FooterWidget class. The footer widget resides on bottom of the screen and is responsible to change the footer’s buttons and texts.

Other functions/ methods used are defined outside the MyApp class and thus presented here with scope resolution operator (::) e.g. MyApp::processEvent(EventType event) , and MyApp::redrawScreen(bool redrawAll).

GUI.cpp

MyApp::MyApp(Audio* audio, LCD& lcd, ControlState& state, HeaderWidget* header, FooterWidget* footer)
       : WindowedApp(lcd, state, header, footer), FocusableApp(2), audio(audio)
{
  GUI_DEBUG("MyApp create");
  const char* s;

  // Create and arrange widgets
  header->setTitle("MyApp Demo");
  footer->setButtons("Yes", "No");
  clearRect = new RectWidget(0, header->height(), lcd.width(), lcd.height() - header->height() - footer->height(), WP_COLOR_1);

  // State caption in the middle
  const uint16_t spacing = 4;
  uint16_t yOff = header->height() + 26;
  demoCaption = new LabelWidget(0, yOff, lcd.width(), fonts[AKROBAT_BOLD_20]->height(),
                                 "Hello World", WP_ACCENT_S, WP_COLOR_1, fonts[AKROBAT_BOLD_20], LabelWidget::CENTER);
  yOff += demoCaption->height() + (spacing*2);

  // Make an icon
  iconRect = new RectIconWidget((lcd.width()-50)>>1, yOff, 50, 50, WP_ACCENT_1, icon_person_w, sizeof(icon_person_w));
  yOff += iconRect->height() + (spacing*2);

  /*// Name and URI above
  s = controlState.calleeNameDyn!=NULL ? (const char*) controlState.calleeNameDyn : "";
  nameCaption =  new LabelWidget(0, yOff, lcd.width(), fonts[AKROBAT_EXTRABOLD_22]->height(), s, WP_COLOR_0, WP_COLOR_1, fonts[AKROBAT_EXTRABOLD_22], LabelWidget::CENTER);
  yOff += nameCaption->height() + spacing;
  s = controlState.calleeUriDyn!=NULL ? (const char*) controlState.calleeUriDyn : "";
  uriCaption =  new LabelWidget(0, yOff, lcd.width(), fonts[AKROBAT_BOLD_20]->height(), s, WP_DISAB_0, WP_COLOR_1, fonts[AKROBAT_BOLD_20], LabelWidget::CENTER);*/

  // Debug string: shows some simple debug info
  //yOff += uriCaption->height() + 20;
  //s = controlState.lastReasonDyn != NULL ? (const char*) controlState.lastReasonDyn : "";
  s = "I'm Awesome!";
  debugCaption = new LabelWidget(0, yOff, lcd.width(), fonts[AKROBAT_BOLD_16]->height(), s, WP_DISAB_0, WP_COLOR_1, fonts[AKROBAT_BOLD_16], LabelWidget::CENTER);

  //reasonHash = hash_murmur(controlState.lastReasonDyn);
}

MyApp::~MyApp() {
  GUI_DEBUG("destroy MyApp");

  delete demoCaption;
  delete debugCaption;
  //delete nameCaption;
  //delete uriCaption;
}

appEventResult MyApp::processEvent(EventType event) {
  GUI_DEBUG("processEvent MyApp");
  appEventResult res = DO_NOTHING;

  if (LOGIC_BUTTON_BACK(event)) {

    demoCaption->setText("Back Button");
    res |= REDRAW_SCREEN;
        if (controlState.myappstate==0) {
                controlState.myappstate=0;
        }
        if (controlState.myappstate==1) {
                controlState.myappstate=2;
        }

  } else if (LOGIC_BUTTON_OK(event)) {

    demoCaption->setText(_("OK Button"));
        if(controlState.myappstate==0) {
                footer->setButtons(_("OK"), _("Back"));
                res |= REDRAW_SCREEN | REDRAW_FOOTER;
                controlState.myappstate=1;
        }
        if(controlState.myappstate==1) {
                footer->setButtons(_("OK"), _("Back"));
                res |= REDRAW_SCREEN | REDRAW_FOOTER;
                controlState.myappstate=1;
        }

  } else if (event == WIPHONE_KEY_UP || event == WIPHONE_KEY_DOWN) {

    int8_t earpieceVol, headphonesVol, loudspeakerVol;
    audio->getVolumes(earpieceVol, headphonesVol, loudspeakerVol);
    int8_t d = event == WIPHONE_KEY_UP ? 6 : -6;
    earpieceVol += d;
    headphonesVol += d;
    loudspeakerVol += d;
    audio->setVolumes(earpieceVol, headphonesVol, loudspeakerVol);
    audio->getVolumes(earpieceVol, headphonesVol, loudspeakerVol);
    char buff[70];
    snprintf(buff, sizeof(buff), "Speaker %d dB, Headphones %d dB, Loudspeaker %d dB",  earpieceVol, headphonesVol, loudspeakerVol);
    debugCaption->setText(buff);

  }
        if (event == WIPHONE_KEY_END  ) {
                //end program //exit
                return EXIT_APP;
        }
        if(controlState.myappstate==1 and ( event == WIPHONE_KEY_BACK or LOGIC_BUTTON_BACK(event))) {
                log_i("exit attempt");
                return EXIT_APP;
        }
        if (controlState.myappstate==2) {
                controlState.myappstate=0;
                log_i("exit attempt2");
                return EXIT_APP;
        }
  return res;
}

void MyApp::redrawScreen(bool redrawAll) {
  GUI_DEBUG("redrawScreen MyApp");

  if (!screenInited || redrawAll) {
    GUI_DEBUG("redraw all");
    // Initialize screen
    ((GUIWidget*) clearRect)->redraw(lcd);

    ((GUIWidget*) iconRect)->redraw(lcd);
    ((GUIWidget*) demoCaption)->redraw(lcd);
    ((GUIWidget*) debugCaption)->redraw(lcd);
    //((GUIWidget*) nameCaption)->redraw(lcd);
    //((GUIWidget*) uriCaption)->redraw(lcd);

    //lcd.fillRect(95, 240, 50, 20, WP_ACCENT_1);

  } else {

    // Refresh only updated labels
    if (demoCaption->isUpdated()) {
      GUI_DEBUG("stateCaption updated");
      ((GUIWidget*) demoCaption)->redraw(lcd);
    }
    if (debugCaption->isUpdated()) {
      GUI_DEBUG("debugCaption updated");
      ((GUIWidget*) debugCaption)->redraw(lcd);
    }
    /*if (nameCaption->isUpdated()) {
      GUI_DEBUG("nameCaption updated");
      ((GUIWidget*) nameCaption)->redraw(lcd);
    }
    if (uriCaption->isUpdated()) {
      GUI_DEBUG("uriCaption updated");
      ((GUIWidget*) uriCaption)->redraw(lcd);
    }*/
  }
  screenInited = true;
}