Developing a New App

Index Home MAE > MAE Architecture > MAE App Architecture > Developing a New App

MAE Development Landscape
App Directory Home
Configure MAE
Connecting to Database
Register MAE Daemons
MAE Source Code Customization
Documentation Directory
Program Directory
Starting MAE
App Setup
Source Code Directory
Datastore Support
Register with CommHub
Creating Main App File
Registering UI Interactions
Web Browser Setup
Registering Menu Options
Registering Keystrokes
Registering Hotspot Clicks
Registering Region Mouse Movement
Registering an API Call to Your App
Codifying the Registration File
Working with UserMan for App Navigation
Enabling the App for User Access
Specify the Tile to Display
Specific Graphic for UserMan Tile
Accessing the Datastore
Configuring Datastore Access
Database Table Setup
The Datastore Class
Appending a Record
Deleting a Record
Setting a Record Field
Getting a Record Field
Querying a Record Field
Generating Classes for Tables
Sending a Message
Asynchonous Messages
Sending an Asynchonous Message
Reciving an Asynchonous Message
Synchronous Messages
Sending a Synchronous Message
Receiving a Synchronous Message
MAE Logging Facility
Debugging Tools
Add to Master Makefile
Enabling Process Management by Supervisor

MAE Development Landscape

By default, MAE resides under /usr/mae/src (PC, Linux) or /opt/mae/src (MacOS), so this documentation references that path.  This is also known as MAESRCDIR.

App Directory Home

Pick a location for your app.  If it is outside of MAE, you'll want to link MAE in (extend MAE).  For example, if you are developing under /usr/abc/src, then you'll want to link key MAE source directories into your environment. For example,

cd /usr/abc/src

ln -s /usr/mae/src/maeapi .

ln -s /usr/mae/src/utillib .

ln -s /usr/mae/src/libdb .

ln -s /usr/mae/src/tool .

ln -s /usr/mae/src/[[commhub]] .

ln -s /usr/mae/src/[[dbbroker]] .

ln -s /usr/mae/src/[[msgbroker]] .

ln -s /usr/mae/src/[[rpcbroker]] .

ln -s /usr/mae/src/lib .

ln -s /usr/mae/src/[[usergw]] .

ln -s /usr/mae/src/[[noticegw]] .

ln -s /usr/mae/src/[[imager]] .

ln -s /usr/mae/src/[[guibroker]] .

Configure MAE

For MAE to work, you'll need to integrate it into your environmment.

Connecting to Database

Edit /usr/mae/conf/[[commhub#Commhub.ini|commhub.ini]], look under the dbbroker sectoin, and update the settings for your server, user, password, dbname, and dbport as needed.  If commhub is already running, restart it.  Alternatively, you can use tset to set those parameters and not restart commhub.

(Re)Start dbbroker to connect to the data store.

Register MAE Daemons

By default, MAE is only configured with its basic daemon services - dbbroker, msgbroker, and rpcbroker.  You can enable additional MAE daemon services if present.  Under /usr/mae/conf, you may see files such as mgmttaskdb_*.csv.  Those are the registration files for additional daemons if you need them.  Register them using:

tadd mgmttaskdb_*.csv

MAE Source Code Customization

Review the file /usr/mae/src/utillib/maedefs.h.  Set TDIR to your target run-time root directory if not /usr/mae.  Add any other global defines for your environment here.

Documentation Directory

MAE organizes its documentation as wiki text files under /usr/mae/html/doc.  The program wiki2doc is used to convert it to HTML.  Once usergw is running, you can visit http://sitename/doc/pagename to view it.

Program Directory

MAE binaries live in /usr/mae/bin.  You may want to add this to your PATH.

Starting MAE

The commhub program is the MAE communication hub; it is central to MAE's operation.  If it is not running, MAE is not running.  Choose the user id for running MAE.  Make sure /usr/mae/conf and /usr/mae/log are writable by that user.  If you use multiple users to run MAE, make sure they all can write to /usr/mae/log.  The user that starts commhub is the id that runs all daemons and jobs launched by commhub.

Start commhub simply with

/usr/mae/bin/commhub

It runs in the background.  Use tstatus, tstart, tstop, and related control utilities to run MAE tasks and check on them.

App Setup

Source Code Directory

Create an app name that will also be used for the messenger channel name.

Create /usr/mae/src/newappname.

Create these files

Filename

Contents

Makefile

Standard targets required:  all clean online and deps. Note that the deps target runs the genmakedeps tool to automatically generate include file dependencies.

newappname.cpp

This file establishes communication with commhub via a messenger channel, registers RPC services available, and turns control over to MAETask.

newappname.h

You may wish to put all your commonly used .h needs in here, including newappnameApp.h.

cache.cpp

This file manages all your data needed for the user activity.  The data is typically drawn from and possibly stored back to the datastore.

newappnameApp.h

The main class that interacts with user activity.  It contains the user state of that interaction and the methods to respond to requests, whether triggered by the user or other apps.

newappnameApp.cpp

The logic that supports the app's purpose and handles user activity.

channel.mreg

Your app's MAE registration file, procesed by genmae.  Here, all messages coming to/from commhub are registered.  It routes data from messages to the appropriate NewappnameApp methods as you specify.  The channel is the name of the communication channel that your app uses in the MAE environmemt (often, this is just the app's name).

Some design is required here. You may want to plan ahead for what messages will be accepted by your app.  Each message is a keyword that triggers an action.  Some messages have paramters, whether they are callback properties or properties to influence the action.

If using usergw, menu items, keystrokes, and UI clicks are registered to their appropriate messages.

If your app provides a MAE API for other apps to call, define those MAE API calls here.

msgNewappname.cpp

This file will be generated from channel.mreg using genmae.  This file handles all messages coming from commhub.  It calls the appropriate NewappnameApp methods.

Some design is required here. You may want to plan ahead for what messages will be accepted by your app.  Each message is a keyword that triggers an action.  Some messages have paramters, whether they are callback properties or properties to influence the action.

mgmtNewappname.cpp

This file handles all app management calls that come from supervisor, which monitors and controls daemons and apps. The management calls typically have to do with the cached data identified in cache.cpp.


Datastore Support

Most apps use data structures from the /usr/mae/src/lib directory to load and save block of data, such as Places, PCs, Monsters, etc. If your app needs any new data structures, create them in /usr/mae/src/lib.

When creating a new data structure, create the core data block and use the genmae utility to generate a lot of the basic source code needed (load, save, GETs, SETs, etc.).  For example, a core data block may be:

// CORE DATA BEGIN

DbRecNum id;

string name;

int age;

HashArray properties;

// CORE DATA END

The above may be in a file People.h or just People. Create the database table for it using

genmae People.h -sql

By convention, store the table creating SQL in a file name table_tablename.sql.  This will make it easier to create a number of tables at the same time.

Register with CommHub

Commhub is responsible for starting and stopping all daemons and apps.  Register your app with it.

Create a management task registration file, /usr/mae/conf/mgmttaskdb_app.csv that contains the header line from /usr/mae/conf/mgmttaskdb.csv and an appropriate data line for your app below it.  For assistance, see commhub.  Next, register your new daemon with

tadd /usr/mae/conf/mgmttaskdb_app.csv

(A past bug required you to stop commhub, edit the /usr/mae/conf/task.csv to change 'nada' to the appname, but that should be fixed by now.)

Creating Main App File

In C++, every program must have a main() routine.  MAE defines this for your app.  Instead, your app needs to define certain points in program execution.

The basic shell of your app is:

#include <MAEApp.h >

MAEApp maeapp("newappname");

void MAE::init(int argc, char ** argv)

/** Initialize the app.

 * At this point, messages can be sent and the database is available, but

 * the app is not considered ready.

 * @param argc - # of command line arguments

 * @param argv - the command line arguments

 */

{

    ...

}

void MAE::main()

/** Everything is initialized. The app swings into action -

 * which may just be a matter of waiting for events or

 * messages

 */

{

    /* wait for events */

//     task.process();

}

void MAE::quit (int rc, const string & msg)

/** This app is going down, whether initiated by the OS or MAE environment.

 * @param rc - the program's exit code (0 means normal exit, 1+ means err exit)

 * @param msg - an explanation why the app is shutting down

 */

{

    ...

}

Each app needs a MAE registration file (.mreg) where messaging, UI response handling, and other interfacing is defined.  The genmae program takes the .mreg file and generates code that integrates with your MAE app.

Registering UI Interactions

The GuiBroker daemon is responsible for redering advanced UI features and callbacks from user activity.  UserGW provides the bridge/gateway via HTTP between MAE and the user's browser.

Web Browser Setup

If usergw is used to deliver displayable content and interact with the user, then a base webpage must be setup.  It should be /usr/mae/html/newappname.html. It declares a number of UI regions and other page setup.  Often, records (or subpages) are written to named regions where those records contain many more named regions.  This is how the interface is built up.

To interact properly with MAE, these named regions must be present:

Further, these JavaScript files must be included:

And these CSS files in the head block:

Here's an example (empty) HTML page:

<! -- newappname Start Page -- >

<head >

<link rel="stylesheet" type="text/css" href="mae.css" / >

<link rel="stylesheet" type="text/css" href="newappname.css" / >

<style >

</style >

</head >

<body >

<script  src="/prototype.js" language="JavaScript" type="text/javascript" > </script >

<script  src="/updater.js" language="JavaScript" type="text/javascript" > </script >

<!-- ---------- MENUS ----------- -- >

<div id="menubar" > </div >

<div id="submenubar" > </div >

<!-- ---------- PROMPTS ----------- -- >

<div id="promptbox" > </div >

<!-- ---------- ATTENTION ----------- -- >

<div id="attention" > </div >

  <!-- ---------- MASTHEAD ----------- -- >

<table cellpadding="0" cellspacing="0" id="masthead" >

<tr >

<td id="title" > </td >

<td id="ruleset" align="right" > </td >

</tr >

</table >

<!-- ---------- WINDOWS ----------- -- >

<div id="windowtabs" > </div >

<div id="windowset" > </div >

<!-- ---------- RE-LOGIN ----------- -- >

<div id="loginbox" style="visibility: hidden;" > </div >

</body >

</html >

Registering Menu Options

Register user menu selections in your channel.mreg file.  For example:

#menu "Story" "Create"

#msg "Story.create"     storyButtonCreate();

#menu "Story" "Download"

#msg "Story.Download"   storyButtonDownload();

#menu "Story" "Guide"

#msg "Story.Guide"      storyButtonGuide();

Register each menu, menu option, message, and newappnameApp class method that handles the menu option.

Registering Keystrokes

Register user keystrokes in the channel.mreg file as. For example:

#key "ctrl-s"

#msg "storyedit.save"   userSave();

Register each keystroke, message, and newappnameApp class method that handles the keystroke.

Registering Hotspot Clicks

If your app responds to a user clicking on a specifically named region, register that mouse click in the channel.mreg file as. For example:

#hotspot "UserAccount"

#msg "account.settings" accountSettings();

Register each hotspot region, message, and newappnameApp class method that handles the click.

Registering Region Mouse Movement

If your app responds to specific (x,y) mouse click inside a specifically named region, register that in the channel.mreg file as. For example:

#mouse "landmap"

#msg "map.landmap" mapPointSelect();

Register each mouse movement region, message, and newappnameApp class method that handles the click.  The app will receive an event for each mouse down and mouse up event.  Future plans include events for movement as well.

Registering an API Call to Your App

If other apps call your apps with requests or updates, register that in the channel.mreg file as. For example:

#api setfield(const UserDevice & device, const string & table, const string & field, const string & value)

/** Update/Replace the table and field entry with the value provided

 * @param device - the end-user's device

 * @param table - the area with a collection of fields in it

 * @param field - the specific field within that area

 * @param value - the string to display

 * @return true if sent successfully

 */

#param device=device

#param table=table

#param field=field

#param value=value

#msg "field" void cmdField(const string & table, const string & field, const string & value);

/** Update/Replace the table and field entry with the value provided

 * @param table - the area with a collection of fields in it

 * @param field - the specific field within that area

 * @param value - the string to display

 */

#param table=table

The API call that the other app makes follows #api. Inside your app, the method from the #msg line is called to handle the API call.  The #param statement map message variables to/from the method calls.

Codifying the Registration File

Once your have created your channel.mreg file, convert it into code as msgChannel.cpp using

genmae channel.mreg -msg msgChannel.cpp

which creates code that integrates with the MAE environment.  The code registers your application, accepts messages, and routes them appropriately.  The code is readable and useful to reference.  When compiling it, you may get errors reproted in msgChannel.cpp or channel.mreg.

If you support an API, you'll need to also generate your ChannelAPI.h and ChannelAPI.cpp file using

genmae channel.mreg -apicpp -apih

After creating all those #msg statements referring to methods to call, you can create method prototypes for your .h file using:

genmae channel.mreg -cbh

and you can generate dummy method code using

genmae channel.mreg -cbcpp

Working with UserMan for App Navigation

If UserMan is your user's login landing page, then you'll want to register your app with it so the user can navigate to your app's UI.

The user's home UI page is provided by UserMan.  If the app is not registered there, the user will not be able to navigate to it.

Enabling the App for User Access

In file /usr/mae/conf/[[commhub#Commhub.ini|commhub.ini]], update domain.apps (where domain is the value of domain.default in commhub.ini):  add the new app's name. This requires a restart of commhub unless you use tpset:

tpset userman $(tpget userman domain.apps),newappname

But if you mess that up, you'll need to know what the previous value of domain.apps was.

Specify the Tile to Display

You need to create /usr/mae/html/record/tilenewappname.html where newappname matches identically the newappname for the domain.apps property.

Example HTML file:

<!-- UserMan - Tile for HexEdit -- >

<figure id=tilemapedit class="tile" >

<img id=tilemapedit_image class="tile-image" src=images/tile_mapedit.jpg title="Edit indoor place maps" >

<figcaption id=tilemapedit_label class="tile-label" >Place Maps </figcaption >

</figure >

Specific Graphic for UserMan Tile

Inside the tilenewappname.html file, you specified an image file such as tile_mapedit.jpg (in above example).  This file needs to be created and added to /usr/mae/html/images/.

Accessing the Datastore

Your app is not required to use this interface to access a datastore, but it is available as a abstracted datastore for your app.  MAE's base daemons all use this datastore service.

Configuring Datastore Access

Before running DbBroker (the datastore daemon), you must configure commhub.ini, so DbBroker knows where to connect.  For example:

dbtypes=mysql,csv

mysql.tables=*

mysql.server=db

mysql.user=maeuser

mysql.passwd=maepasswd

mysql.dbname=newappname

mysql.dbport=3306

csv.tables=TestCSV

csv.TestCSV.filename=/usr/mae/worlddat/Item.csv

ini.tables=n/a

postgresql.tables=n/a

where

Once configured, start DbBroker to verify connectivity:

tstart dbbroker

tstatus dbbroker

Once the database has tables, you can view those table names using

tdbcmd -tables

And the schema for a specific table name can be viewed using

tdbcmd User -schema

Database Table Setup

MAE uses a number of tables for its basic daemons; those daemons won't work without those tables.

MAE datatbase tables can be configured using these commands:

cd /usr/mae/src

cat */table_*.sql | mysql -h db -u maeuser - p newappname

You will need to create database tables for your app if your app needs them. We recommend you put the SQL CREATE TABLE statement inside a table_newappname.sql file in your source code directory.

Note that each database table has an Id field, which is an integer and automatically incremented when records are appended.

The Datastore Class

Your app may access the datastore via DbBroker; use the Datastore class to do this, included using:

#include <Datastore.h >

Create a class to handle records for each database table.  In that class, define a static variable for the handle to the database:

static Datastore db;

which is then declared using

Datastore newappname::db;

and initialize it using open():

if (!db.isOpen()) {

   db.open(newappname);

   if (!db.statusOK()) {

       return false;       // db is a no go

   }

}

return true;

There are a number of methods for appending, deleting, setting, getting, and querying data.

Appending a Record

A database table record is a series of key/value pairs, which is conveniently and dynamically stored in a HashArray.  Create a new record by passing a HashArray, like this:

HashArray values;

values.set("Name", name);

DbRecNum id= db.addRecord(values);

Upon success, addRecord() returns the non-zero record id.

Deleting a Record

Given a record id, deleting that table record is straightforward:

db.deleteRecord(id);

Setting a Record Field

Using DbBroker, the interface handles these field types:

To set a specific field value in the open table, use setValue(), like this

db.setValue(recno, fieldname, value);

Getting a Record Field

When querying a field value, speicify the type as well:

db.getFieldLogical(recno, fieldname);

db.getFieldInt(recno, fieldname);

db.getFieldFloat(recno, fieldname);

db.getFieldString(recno, fieldname);

Note that a date if fetched as a string.

Querying a Record Field

To query a list of record ids that match ANDed criteria, using the queryRecords() method.  For example:

HashArray criteria;

criteria.set("User_id", user_id);

criteria.set("Setting_id", setting_id);

DbRecList result= db.queryRecords(criteria);

Generating Classes for Tables

Creating a class for each database table can be tedious since the same basic methods need to be setup for the same operations.  To get you started, you can use the genmae utility to generate your .h interface file and .cpp implementation file.

If you have already created your database table, you can type:

genmae db2h User

genmae db2cpp User

Used this way, it will connect to the database (it may prompt you for a password), grab the table schema, and generate the files in the current directory.  For example, User.h and User.cpp in the above example.  When pulling from the database, you lose the nuance of data structures, so the .h and .cpp will require more modification.

Instead, you can create a basic .h file with your record fields, bounded by spcial comments, such as

// CORE DATA BEGIN

DbRecNum id;

string Name;

string Password;

int LoginCount;

HashArray properties;

// CORE DATA END

and then run these commands

genmae incl temp.h > User.h

genmae cpp temp.h > User.cpp

That will generate enough code to provide you a solid starting point for your class.

Sending a Message

There are two ways to send a message.  Sending a message without expecting a synchronized answer is the most common.  Sending a message and expecting a response is actually an RPC (Remote Procedure Call) message.

Asynchonous Messages

The point of sending a message is for another application to recieve it and respond to it.  Both sides (sending, receiving) must be operating correctly for the message to be meaningful.

Sending an Asynchonous Message

Coding an asynchonous is straightforward, but has required parts.  Ensure the right include file:

#include <MAE.h >

which includes the Messenger class.

A message has these essential parts:

During program initialization, the app's channel must be declared to MAE. The msgbroker daemon receives the registration request.  It is responsible for routing the request to its destination. If no app is listening on the destination channel, the message is not delivered.  In your app's main program file, decalre the channel to be the same as the name of the program using

MAEApp maeapp("newappname");

or declare the channel differently using a longer form:

MAEApp maeapp("newappname", channel);

where channel is one or more channel names, separated by commas.

Before sending a message, the app must composing the message.  The payload is stored in the XMLData structure, which may simply be a flat collection of properties or a hierarchical organization of properties. The property names should match the .mreg file that receives. If the request keyword is fixed, the properities can be documented in the sending app's .mreg file (in genmae, see #send).

Once all required parameters are ready, sending the message is a matter of calling Messenger::send().  For example:

static Messenger msg;

string destination= "other";

string request= "doit";

XMLData params;

params.set("color", "blue");

bool sent= msg.send(destination, request, params);

send() returns false if there is no active connection to commhub, otherwise, the message is sent to msgbroker for processing.

Reciving an Asynchonous Message

Registering a message channel, registering a message request, and routing those requests to your code is all handled inside the .mreg file, which genmae processes.

At the top of your newappname.mreg file, declare the channel like this

#channel=display

#appclass=UserApp

#appcache=UserCache

#cacheid=ui     // hence UserCache[ui]

#instance=instance

where channel declares the channel name (which should match the MAEApp declaration in the main program), appclass is the class that manages each user's instance/state, appcache is the name of the vector of appclass instances, cacheid is the variable used for indexing into the vector (used for code generation), and instance is the name of the variable to hold the specific appcache instance for the current request being handled.

The next section is the code to find the index of the relevant instance.  For example

#idcode

    // process the parameters - we require a valid user

    int user_id= device.getUserId();

    if (user_id == 0)

        user_id= param.getInt("uid");

    if (user_id <= 0) {

    // err out

string msg= (string)"Unknown user id(" + user_id + ") for " + request;

        task.log (msg);

MAE::mgmt.incrementInputErrorCount();

        return;

    }

    int ui= UserCache.findPos(user_id, false);

    if (ui < 0) {

        // We have a new user

UserApp newUserApp(user_id);

ui= UserCache.add(user_id, newUserApp);

    }

    if (ui < 0) {

        // we were given an invalid user (we must have a valid user)

        task.log ((string)"UserSettings(" + user_id + ") not found/created");

mgmt.incrementInboundErrorCount();

        return;

    }

    UserApp & instance= UserCache[ui];

    instance.setDevice(device);

Finally, declare the message request keyword, its expected parameters, and your program method to handle it.  For example,

#msg "tileclick" doTile(const string & tile);

#param tile1=@device.getRegion()

#param tile2=@string(tile1,4)

#param tile=@string(tile2,0,tile2.size()-6)

which is trickier than most, but demonstrates severaal things. The #msg line declares the keyword tileclick and the app method to handle it, e.g. UserCache[ui].doTile(tile). The #param lines are typically assigned to the inbound parameters from the payload, but here the value of tile is calculated from the inbound parameter device which MAE pre-assigns. Often, a #param statement simply maps an inbound variable name to a program variable, but if the inbound parameter begins with @, then the text following it is taken as literal program code. Note that tile1 and tile2 are not inbound parameters or previously declared program variables; genmae knows this and defaults them to string variables; it knows that tile is a string because of the doTile() declaration.

Synchronous Messages

Synchronous messages send a request and receive a response. App program execution is paused while waiting for the response. For a synchronous message to be successful, both sides (sending and receiving) of the message must operate correctly.

Sending a Synchronous Message

Sending a synchronous message is very similar to sending an asynchonous message, but it uses the RemoteService class instead of the Messenger Class and it does not need to know the destination channel.  Here's an example:

XMLData param;

param.set("setting", setting.toHashArray().toString());

param.set("wid", setting.getId());

param.set("name", name);

XMLData result= RemoteService::call("melee.addTeam", param);

where you'll observe that parameters of structures are serialized to strings for transmission.  The response comes back as an XMLData structure with these properties:

Here's an example of handling the response:

   if (!errmsg.empty()) {

       string logmsg= string("Error: RPC addTeam.") + setting.getId() + "." + name + " failed: " + errmsg;

       task.log(logmsg);

       return -1;

   }

   int team_no= result.getInt("team_no");

Receiving a Synchronous Message

The app that recieves the synchronous request must register the service. This is done in the main program file in the MAE::init() method.  Each service must have a unique keyword.  When registering, a static callback method must be specified; it will be called each time a request is received.  For example,

RemoteService serviceAddTeam("melee.addTeam", MeleeApp::rpcAddTeam);

where melee.addTeam is the keyword and MeleeApp::rpcAddTeam is the callback.

Next, create the callback to handle the synchronous message.  This method receives:

The callback routine processes the inbound parameters, performs its function, and then constructs its result to be transmitted back.  The result is ultimately returned by this method. Here's an example:

   XMLData MeleeApp::rpcAddTeam(string service, XMLData & params, string appname, string user, void * data)

   {

       DbRecNum setting_id= params.getInt("wid");

       string team_name= params["name"];

       XMLData result;

       int si= findSetting(setting_id);

       if (si < 0) {

           // we were given an invalid setting

           task.log ((string)"MeleeApp::rpcAddTeam: Error from " + appname + ": invalid setting (id="+setting_id+")");

           RemoteService::setError(result, string("melee.rpcAddTeam: Invalid setting_id ") + setting_id);

       }

       else {

           // add the team; return the team's id

           int team_no= MeleeCache[si].addTeam(team_name);

           result.set("team_no", team_no);

       }

       return result;

   }

If an error is encountered during procesing of the request, the app can call RemoteService::setError() to register the error in the response.

MAE Logging Facility

If your daemon/task/app needs to report a log message of interest to MAE or users maintaining your software, log the message using MAE logging facility.  For example:

task.log((string)"User "+user_id+" has enabled feature 23");

Your message will be routed to the root commhub and saved with a timestamp and your app's name to /usr/mae/log/commhub.log.

Aside from viewing the file directly, you can view the file using the tviewlog tool. This tool allows you to view the last few messages specific to an app, for example:

tviewlog -20 appname

will show you the last 20 messages logged concerting appname.

Debugging Tools

In addition to your usual debuggers or debugging tools, MAE provides a debug logging facility. To toggle debugging on/off, the user types

tdebug appname

When debug is toggled on, the file /usr/mae/log/appnamedbg.log appears.  MAE writes to this file, letting you know what is happening inside MAE.  You may also write to this file; just use the FILE * dbgf like this:

if (debug_on) fprintf (dbgf, "Debug checkpoint\n");

When debug is toggled off, debug logging stops. You can delete appnamedbg.log and toggle debugging back if, if desired.

Although the debug output file contains MAE messages going to/from your app, you may be specifically interested in just those messages.  If so, you can toggle a message tap on/off using:

ttap appname

When the tap begins, it will append a timestamp and message to the file /usr/mae/log/appname.tap. All MAE messages are logged: DbBroker, MsgBroker messages, RpcBroker, and CommHub control messages messages.

Add to Master Makefile

Update /usr/mae/src/Makefile so it visits the directory for newappname for targets:  daemons, deps and clean.

Enabling Process Management by Supervisor

The supervisor app queries the state of apps periodically and provides an interface for the application operator to tweak some settings.  This is enabled by handling management messages in mgmtNewappname.cpp.