An Event-Driven Daemon with CLI

Introduction

This article describes an empty event-driven daemon. It is empty in the sense that the daemon itself only provides a command line interface, leaving the real functionality of the daemon to a set of loadable shared object libraries or plug-ins. The "Empty Event-Driven Daemon" has several features that you might might find useful for your next project.
  - Command line tools to view and set plug-in parameters
  - Simple publish/subscribe mechanism for sensor data
  - All commands and data are printable ASCII
  - Modular plug-ins for easy development and debug
  - No dependencies (excluding libc)
  - Ready-to-run, event-driven daemon

 

Controllers and Motors as Plug-ins
Introduction
Quick Start
How to Use EEDD
Robotics: A Typical Use Case for EEDD
The EEDD API
Other EEDD Notes

 

Quick Start

If you have gcc installed and an open terminal window you can download, build, and test the empty daemon in about 60 seconds.
  (ssh to your target device if needed)
  git clone --depth=1 https://github.com/bob-linuxtoys/eedd.git
  cd eedd; make
  sudo make install
  # Start the empty daemon (background but not a daemon)
  export PATH=$PATH:/usr/local/bin
  eedd -ef &
  # Load the "hello, world" demo plug-in 
  edloadso hellodemo.so

  # Subscribe to the message service of hellodemo
  edcat hello_demo message     # "hello, world" once a second

  # Open a second terminal window, and subscribe again
  export PATH=$PATH:/usr/local/bin
  edcat hello_demo message     # "hello, world" once a second

  # Open a third terminal window
  # Change the print period to 2 seconds
  export PATH=$PATH:/usr/local/bin
  edset hello_demo period 2

  # Change the printed string to good-bye
  edset hello_demo messagetext good-bye

  # Display info about the system and about hellodemo
  edlist
  edlist hello_demo
The above shows how to start the empty daemon, how to load a plug-in, how to subscribe to a data stream, and how to change parameters while the program is running.

 

How to Use EEDD

The core of the empty daemon is pretty simple: when the daemon starts it opens a TCP port (default is 8870) to listen for incoming command connections. Once a connection is set up, the daemon parses newline terminated ASCII commands that are passed in. The recognized commands are:
   edloadso <plug-in.so>
   edget <name|ID#> <resource_name>
   edset <name|ID#> <resource_name> <value>
   edcat <name|ID#> <resource_name>
   edlist [name]

Each of the above has an equivalent bash command that behind the scenes opens a TCP connection and sends the command. (You can use telnet to connect directly to the empty daemon if you wish.) You'll find that having Bash commands to get or change plug-in parameters makes it easy to use a script file to drive your project or to help automate its testing.

The edloadso command starts everything by loading a shared object plug-in. Every plug-in has an Initialize() routine that is called to allocate a block of memory for state information, open device files and register read/write callbacks for file descriptors and to define other callbacks for timers and to do edget/edset/edcat on the resources that the plug-in wants to expose. The plug-in's Initialize() routine is passed a pointer to a SLOT structure. The plug-in fills in the SLOT's name and void pointer fields before returning. (A follow-up article to this one will describe how to write plug-ins.)

Each time a plug-in is loaded it is given a slot ID. EEDD itself has slot ID zero and plug-ins are numbered sequentially starting from one. It is possible to load a plug-in twice. Since the two instances have the same name you'll want to use the slot ID to differentiate them in the get, set, and cat commands.

Resources are ASCII printable values stored in an instance of a plug-in. Resources can be read-only, read-write, or broadcast. You can view a readable resource's value with the edget command and set a read-write value with the edset command. You can subscribe to a broadcast stream with the edcat command. Callbacks in the plug-in are invoked when you get or set a resource, or start a stream.

Although resources are presented as a static values, callbacks for writing or reading a resource can be used as a command to the system. For example, setting robot's motor mode to BRAKE would trigger sending the underlying commands to stop the motor.

Another valuable feature of EEDD is its ability to load multiple, cooperating plug-ins. For example, imagine a plug-in that loads after hellodemo and opens a TCP connection to its own process and then starts sending commands to the hellodemo to change the message displayed. This plug-in might update the hellodemo message text to show the current temperature or disk usage.

The two plug-ins cooperate in the sense that second plug-in uses the edset command to change the message text. Opening a TCP connection to yourself might seem strange at first but it is actually not too inefficient, it makes it possible to distribute the load to several processes, and most importantly, it keeps the command syntax identical throughout the whole system.

 

Robotics: A Typical Use Case for EEDD

Most robots use a layered approach to software. This section will explore ways EEDD might be used to implement such a layered system.

Perhaps the first layer to implement is the motor control layer. It should provide a control loop that regulates the speed of the robot using wheel or motor sensors. Your needs will dictate what parameters to expose but a reasonable set might be:
- mode: a character in (B)rake, (F)orward, re(V)erse, or (R)otate
- speed: the desired speed in ticks/second or meters/second
- direction: 0 to 100 with 0=hard left, 50=straight, 100=hard right
- watchdog: Brake if no mode, speed, or dir command in this many milliseconds

If you have a PID loop controlling dual DC motors you might want to also add parameters for the P, I, and D terms. When your motor plug-in is working properly you should be able make your robot move in a square with a simple Bash script such as this.

  edset motors mode b    # brake
  edset motors speed 50
  edset motors dir 50
  edset motors mode f    # and off we go...
  while true
  do
    sleep 2
    edset motors dir 100
    sleep 0.75
    edset motors dir 50
  done
Adjust the speed and first sleep to adjust the size of the square. Adjust the second sleep until you get a 90 degree turn. If the second sleep time is too short the turn is greater than 90 degrees and if too long it's less than 90 degrees. (Can you rewrite the above to make the robot go in a figure eight?)

If you have dual DC motors with a PID loop controlling them you might want to name your .so file motors-dc2.so, while if you're using continuous rotation servos from a VeX kit you might name the plug-in motors-vex.so. Either way, the plug-in should register with the name "motors" and implement at least the mode, speed, dir, and watchdog parameters.

Any Controller with Any Motor




Say you want to add remote control for speed and direction. You could create a PS2 wireless controller (rmt-ps2.so) or a Vex RC controller (rmt-vex.so) that publishes the controller's state or events. (eedd includes a plug-in that does just this.) Next you would build a plug-in that opens a couple of TCP connections, one to the host/port that has the remote control plug-in, and another connection to the host/port that has the motors plug-in. This middle plug-in (say 'robologic.so') would listen for remote control commands and convert them to the edset commands used to control the motors plug-in. You might also have this plug-in watch the bumper sensors and ultrasonic distance sensors for emergency stop conditions.

Testing your remote control plug-in is as easy as using edcat and watching the output as you move the controls on the VeX or PS2 controller. Testing the robologic plug-in is as easy as adding a print statement to show the edset commands as they go to the motors plugin.

If the motor plug-in parameters are common to both sets of motors and the broadcast data stream from the remote controls are the same, you should be able easily mix and match the motors and remote controls.

 

The EEDD API

The full API to the empty daemon has only nine calls. Below is a brief description of each API routine.

Select Loop Routines

- void add_fd()
Add_fd() registers a file descriptor with the select() loop. Its parameters include the file descriptor, the read callback, the write callback, and a void pointer that is passed transparently into a callback when invoked. When invoked, callbacks are passed the file descriptor, the void pointer, and an integer it indicate if the callback is for a read or a write. This last parameters lets you use the same callback for reads and writes if you wish.

- void del_fd()
Del_fd() removes a file descriptor from the select() loop. It is a non-fatal error to remove a non-existent file descriptor.

- void * add_timer()
Add_timer() creates a timer and registers a callback for the timer. Parameters include the type of timer (periodic or one-shot), the time-out period in milliseconds, a callback, and a void pointer for transparent data. Add_timer returns a timer handle. When invoked, the timer callback is passed the timer handle and the void pointer for transparent data.

- void del_timer()
Del_timer() deletes the timer specified by its single parameter, a timer handle returned from add_timer(). It is not necessary to delete one-shot timers as these are deleted automatically after they expire.

Utility Routines

- SLOT * getslotbyid()
Getslotbyid() returns a pointer to a SLOT structure when given its numerical slot index. Use this routine when you need to look at all of the other plug-ins to find which slots have which plug-ins. When a slot registers itself it passes in a pointer to (typically) a structure describing the state of the plug-in. You may find this handy when a plug-in wants to make available a private API. This will be the case for passing images or other high bandwidth data.

- void edlog()
Edlog() uses the printf() routine syntax to log significant events in the system. You are free to use syslog() or print to stderr as you see fit, but edlog() will make it easier for you to decide at run-time where to direct logging.

User Interface Routines

- void bcst_ui()
Bcst_ui() sends a string down any edcat sessions listening on a resource. Input parameters include the buffer, the number of characters in the buffer (not including the null), and a 'key' that encodes the slot ID and resource ID as an integer. Use bcst_ui() to broadcast sensor data from a read callback on the

- void send_ui()
Send_ui() sends a string in response to an edget command. Parameters include the string, the length of the string, and the UI connection ID of the requesting TCP connection.

- void prompt()
Prompt() send a prompt on the specified UI connection. The prompt helps the CLI implementation of the empty daemon commands know when the response is complete and the command can exit.

 

Other EEDD Notes

 - Although the empty daemon is written in C the plug-ins can be written in any language that can produce a shared object file. The nature of the empty daemon seems to lend itself to object-oriented languages.

 - If you want some CPU load balancing you may want to run multiple instances of the empty daemon. Don't be afraid to create symbolic links to the empty daemon to give your executables distinct and more descriptive names. You can use ssh port forwarding if you want to distribute the load over multiple computers on a network.

 - Logging is not part of the first release of the empty daemon. When available it will probably appear as an eedd resource called "logging" and which takes values of None, Syslog, or Stderr. One day a plug-in will let you grep syslog for eedd commands and play them back into the system with the appropriate timing.

 - EEDD is released under the GPLv2 and a github repository will be the official source code repository. If you would like to participate let me know and I'll give you commit access to the repository.

 - The next article in this series will describe the APIs offer by EEDD and give a more detailed description of how to write a plug-in. However, if you are handy at reading and modifying code you'll find that the two demo plug-ins supplied with EEDD may be enough to get you started.

 - While edset, get, and cat are the preferred way to pass information in the system they are not the only way. The void pointer that a plug-in registers usually points to a structure that is specific to that type of plug-in. If you want to pass high bandwidth data, say images, you can define a routine in one plug-in and make it callable from other plug-ins by passing its address as part of the structure registered in the Initialize() routine. Of course for this to work all the cooperating plug-ins must have access to the structure definition that has the routine address. An example of this will be given in the next article.