An Event-Driven Daemon with CLI

Introduction

The empty event-driven daemon (eedd) gives a nice abstraction (text over TCP) to the peripherals on your robot. It is empty in the sense that the daemon itself only provides the command line interface, leaving driver functionality of the daemon to a set of loadable shared object libraries or plug-ins which are, in essence, user-space device drivers.

A previous article (here) describes the empty daemon and how to install and use it. This article presents some tips on writing new plug-ins.

 

Overview
Makefile
Readme File
State Data Structure
Resources
Core Logic
Submit Your Plug-in

 

Overview

The existing plug-ins for the empty daemon all follow a common layout. The easiest way to create a new plug-in is to copy and rename one of the existing ones. The next step is to modify the Makefile to add your new plug-in and test that you can build and install it.

The next step is to write the readme.txt file that describes your plug-in. The readme should describe how to install any required hardware or software. It should also describe your plug-in's resources.

Implementing the resources API is next. There is no need to tie the resource to the hardware, Just be sure that the set, get, and cat commands all seem to work.

The final step is to tie the hardware to the resources. You may find it easiest to implement and test the get and set resources and to then add the sensor resources.

 

Makefile

Download the EEDD source with the command:
    git clone https://github.com/bob-linuxtoys/eedd.git
Add a directory for your new plug-in and copy an existing plug-in into the new directory.
    cd eedd/plug-ins
    mkdir new
    cp hellodemo/* new
Edit the Makefile in the plug-ins directory to add your new plug-in. Note that you will have to add a line in four places to build, install, clean, and uninstall.

Change to your new directory and rename hellodemo.c to new.c and edit the Makefile to change the target name from hellodemo to new. Edit new.c to change PLUGIN_NAME to "new".

This a good point to start testing. You should be able to build, install, run, and load new.so.

    cd 
    make
    sudo make install
    eddaemon -efd &
    edloadso new.so
    edlist

If everything is working the edlist should show your new plug-in name. It will list the resources for hellodemo but that is OK for now.

 

Readme File

Writing the readme file is arguably the most important step in adding a new plug-in. Writing a readme requires that you fully understand how to install the hardware and that you have a clear picture of what hardware control points you want to expose to higher level applications.

The readme is usually broken into four sections. The first section is an overview of the plug-in giving a high level description of what hardware is being used and what the high level application can do with the hardware.

The second section should describe the hardware and software prerequisites for the new plug-in. Your description should tell the reader where to buy or otherwise acquire the hardware and how to connect the hardware to your system. Be sure to describe the installation and configuration of any required software.

The third section describes the resources. List all of the resources and give the syntax and formatting of input and output values. You can have more than one value given in a resource. For example, you could specify a serial port device, its encoding, and its baud rate all in one resource. Explain the resource in enough detail and in terms that the application programmer has a clear of what the resource means and how to use it.

The last section should give a complete example that shows a typical use of the hardware. Try to make the example something that a new user could cut-and-paste and still work.

 

State Data Structure

Implementing the logic of your plug-in almost certainly requires saving configuration or other state information. Create a data structure to hold this state information. The hellodemo reference plug-in uses a typedef for the data structure but this is certainly not required. The reason for storing everything in a structure is that it is not uncommon for a plug-in to be instantiated more than once and statically allocated variables would lead to confusion. The state structure is allocated from RAM each time the plug-in is loaded. Initialize the state information in the Initialize() routine.

The state data structure most often contains open file descriptors, timers, configuration, and cached copies of some hardware data. You will probably want to open any device files and start everything from the Initialize() routine.

 

Resources

Most of the time getting a new plug-in to work is in getting the resources to work. It is straightforward but a little tedious.

Start by giving IDs and names to each of your resources. The defines for this should look something like the following:

#define FN_TEXT            "messagetext"
#define FN_PERIOD          "period"
#define FN_MESSAGE         "message"
#define RSC_TEXT           0
#define RSC_PERIOD         1
#define RSC_MESSAGE        2

The default configuration of the empty daemon makes 25 plug-in slots available each with up to 10 resources. The resource IDs given above are an index into the slot's resource table. (FYI: The internal data and design of the empty daemon is described in Docs/design.txt and plug-ins/include/eedd.h respectively.)

Resources are configured in the Initialize() routine. Be sure to set the flags correctly. If your get or set command does not work you might want to check that the flags are set correctly. A resource initialization might appear similar to this:

    pslot->rsc[RSC_STATUS].name = FN_MESSAGE;
    pslot->rsc[RSC_STATUS].flags = CAN_BROADCAST | IS_READABLE;
    pslot->rsc[RSC_STATUS].bkey = 0;
    pslot->rsc[RSC_STATUS].pgscb = usercmd;
    pslot->rsc[RSC_STATUS].uilock = -1;
    pslot->rsc[RSC_STATUS].slot = pslot;

Each resource has a pointer to a get/set callback (pgscb) that is invoked when a user gets or sets the resource. It is usually easiest to have just one callback for all the resources and to switch in the callback on the resource and whether it is a set or a get command. The following code shows the typical structure of the get/set callback and shows getting and setting an integer resource.
    pctx = (HELLODEMO *) pslot->priv;
    if ((cmd == EDGET) && (rscid == RSC_PERIOD)) {
        ret = snprintf(buf, *plen, "%d\n", pctx->period);
        *plen = ret;  // (errors are handled in calling routine)
    }
    else if ((cmd == EDSET) && (rscid == RSC_PERIOD)) {
        ret = sscanf(val, "%d", &nperiod);
        if ((ret != 1) || (nperiod < 1)) {  // one value greater than 0
            ret = snprintf(buf, *plen, E_BDVAL, pslot->rsc[rscid].name);
            return;
        }
        // record the new period
        pctx->period = nperiod;
    }
    else if ((cmd == EDGET) && (rscid == RSC_TEXT)) {
    }
    else if ((cmd == EDSET) && (rscid == RSC_TEXT)) {
    }
    return;

A good place to stop and test everything is when the get/set callbacks are done. Test all valid combinations of get, set, and cat for each resources. Be sure to test with both valid and invalid inputs.

 

Core Logic

Once the resources are correctly responding to UI commands it is time to add the core logic to the plug-in.

A good place to start is with the configuration resources. Configuring a device name might close the old device if open and then open the new device. You can use the lsof command or look in /proc to verify that the new device is open and the old one closed.

If you have a timer you can add a print statement in the timer callback to see when the timer is called. If your timers have a user settable period you should be able to use edset to change the timer period.

A common use of a plug-in is to collect asynchronous data from a device and to broadcast it to listening subscribers. Both GPS and game controllers are examples of this. If your hardware needs asynchronous input you could open the default device in Initialize() and when the user sets the device with an edset command. Add a read callback to the file descriptor with the add_fd() routine. (See eedd.h for more information on add_fd().) In your read callback you can test the resource's bkey to see if there are any subscriptions to the data. If there are, you can format the data as a null terminated line of text and broadcast it using the bcst_ui() routine.

 

Submit Your Plug-in

While clearly not required, it would be nice if you submitted your plug-in for inclusion in the empty daemon. It is a great way to show your work and others may find your plug-in useful. Since plug-ins are implemented as shared object libraries they do not need to have the same license. You can use any license you choose for your plug-in.