PLUG n PLAY CABLES
IRLP Cables for the Alinco DR-x35 Series Radios
(and others) to IRLP Version 3 Interface Board
Order Here

Writing Scripts for IRLP

Script Writing Basics

One of the nicest things about IRLP is its extensibility. IRLP operation is controlled by various standard scripts; however, it is very easy for the node owner to modify and/or create his own scripts. From the beginning, I have written a number of scripts and have modified others to suit my own purposes. Many of the scripts that I have and/or use can be found here and downloaded for installation on your own node.

IRLP DTMF Processing

Pogram flow for IRLP DTMF Processing.

One of the important things to understand is the flow of control when it comes to processing the received DTMF digits. Let us first define the boxes in the image above:

  • dtmf - one of the main IRLP processes that is constantly running and monitoring the node. One of its jobs is to receive and assemble the strings of DTMF digits entered by the user.
  • decode - the main IRLP command processing script. Is found in the $SCRIPT directory and called by dtmf whenever dtmf has fully assembled a complete command string.
  • custom_decode - the main "node owner" customizable script. This is where all user commands get decoded and dispatched.
  • User Script - any of a number of custom scripts installed or written by the node owner to perform any of a number of extended operations (Connect to EchoLink, play newsline, speak the time, voice mail, whatever you can imagine).

Each of the blocks described above can perform any number of operations but the basic flow goes as follows:

  1. Once a DTMF command string has been fully received, as signaled by the loss of the COS signal, the dtmf program calls the decode script with the DTMF command string as an argument.
  2. The first thing the decode script does is log the command in the IRLP log file ($LOGFILE), then it calls the custom_decode script with the DTMF command string as an argument.
  3. In short, the custom_decode script does whatever processing necessary to parse the DTMF command to separate any prefix (if prefixes are used) and the command then proceeds to determine if the prefix is correct and the command is a node owner defined command or not.
  4. If a node owner defined command is found, then custom_decode calls the appropriate User Script to perform the operation desired.
  5. Once the User Script completes, it returns to custom_decode using exit.
  6. At this point, custom_decode returns control back to decode by doing an exit 1 which tells decode that the command has been decoded and executed.
  7. When decode receives the exit 1 code, it quits execution and returns control back to dtmf.
  8. If a node owner defined command is not contained in the DTMF string, then custom_decode returns to decode by using exit 0, indicating that no command has been found thus far and that decode must now try to handle the DTMF command.
  9. If a prefix is used, decode will attempt to separate the prefix from the command code and validate the prefix.
  10. If the command is "73" and the node is involved in a connection, then the connection will be broken and decode will return to dtmf using exit.
  11. If the command is 4 digits long and begins with a "9", then a connection to a reflector is attempted; otherwise, a connection to a node is attempted.
  12. If a connection was successful then decode will return to dtmf using exit.
  13. If the connection was unsuccessful AND the node is connected to another node but not a reflector, the entire DTMF command string is sent to the connected node which will either regenerate the DTMF on the remote end or discard the DTMF information depending on whether DTMF_REGENERATION is set to YES or NO.
  14. Finally, decode returns to dtmf using exit.

Here is a very important point to remember about all node owner created scripts: they should not spend a lot of time doing things before returning. I'll say that again, the node owner created scripts should not spend a lot of time doing things before they return.

Why is this? The IRLP node basically enters suspended animation and cannot respond to any more commands until the command that is currently executing has completed. Therefore, commands that perform simple state changes can usually be run to completion without any problems. More complex commands should be made to run in the background. This means that the program is "forked" or detached from the program that calls it so that the calling program can go on and do something else without waiting for the "forked" program to complete.

How do we "fork" a program? Quite simple, at the end of the command line that causes the program to be run, append an ampersand (&) to the end of the command. The shell that executes the command will then know to fork off the process rather than run the program itself. An example of this follows:

 case ${USRCMD} in           
 73) # Terminate connection
             if [ -f ${ECHO_SCRIPT}/echo_end ] ; then
             ${ECHO_SCRIPT}/echo_end
             else
             ${SCRIPT}/end
             fi
             exit 1
             ;;
 ${IRLPPREFIX}20) # Speak the current time
             ${CUSTOM}/speaktime
             exit 1
             ;;
 ${IRLPPREFIX}30) # Stop the news play
             ${CUSTOM}/stopnews
             exit 1
             ;;
 ${IRLPPREFIX}31) # Play ARRL Audio News
             ${CUSTOM}/playnews aan &
             exit 1
             ;;
 ${IRLPPREFIX}32) # Play Amateur Radio Newsline
             ${CUSTOM}/playnews arnl &
             exit 1
             ;;
 esac

 

(More to come as I free up time)

Tutorial

I will be discussing some of the techniques and styles used when writing scripts for IRLP. With exception of a few core programs, the IRLP system is quite open for modification by the individual node owners. This is one of the things that makes IRLP shine amongst other VoIP systems. One problem though is its openness and the lack of a coding standard and as such, the API is often left to the interpretation of the individual programmer (i.e. node owner).

A Standard Header

Most extensions to IRLP are done through user supplied bash scripts while there have been a few programs written in other languages (e.g. Perl, C). One thing missing is a standardized header for use on bash scripts; therefore, I propose the following which is what I use on all scripts that I develop:

 1 #!/bin/bash
 2 ########################################################################
 3 # filename:     <Provide script filename here>
 4 #
 5 # description:  <Provide some sort of description of what the scrip does>
 6 #
 7 # usage:        <Provide some sort usage description>
 8 #
 9 # history:
10 # yyyymmdd      Callsign <description of changes for this release>
11 #
12 ########################################################################
13 #
14 # Make sure we are user repeater!!!
15 #
16 if [ `/usr/bin/whoami` != "repeater" ] ; then
17    echo "${0##*/}: must be run as user REPEATER!"
18    exit 1
19 fi
20 #
21 # Make sure we have sourced the environment file
22 #
23 if [ "$RUN_ENV" != "TRUE" ] ; then
24    echo "Loading IRLP environment..."
25    . /home/irlp/custom/environment
26    echo "DONE"
27 fi 

This is a pretty standard opening for an IRLP script. Line 1 causes the proper interpreter to be used to process the remainder of the script. (The line numbers are for the purpose of this example only. They are not used in real life.) Unlike DOS or Windows, Linux (UNIX) does not use a 3-letter extension to tell the operating system what type of file is being accessed. The permissions have a bit that tells Linux that the file is executable and who is allowed to execute it. For a script file, the first line identifies how the file is to be interpreted. In our case, #!/bin/bash identifies the files that must be executed by the bash shell program. You may see other lines like: #!/usr/bin/perl for a perl script or #!/usr/sh for a bourne shell script, etc. For now, we will only concern ourselves with bash. Some programmers will append a .sh, .pl or some other letters to the end of the filename, but Linux does not care if you do or not, they just make the name longer or easier for the programmer to identify the type of file without looking at the files contents.

The large comment block provides information to the programmer as to what the script does. If you have not already guessed, comments are anything following the octothorpe, hash, sharp, pound (#) character. NOTE: in line 17, the octothorpe is contained within an expression and thus does not start a comment.

Since most scripts must be run as user "repeater", lines 16 through 19 check to see if the script is being run as user "repeater" and if not, displays a message informing the user that he must be logged in as repeater before this script will run. The ${0##*/} displays the name of the program as found on the command line. It is nice to have the complaining program identify itself.

Lines 23 through 27 check to make sure that the IRLP environment has been loaded. If not, then it loads the environment for you. Many programmers just inform you that the IRLP environment has not been loaded, telling you how to load it, then exit; thus, forcing the user to load the environment manually then rerun the script. My method above handles the whole process for you automatically.

Key-up Delays

One of the things that must often be taken into account are key-up delays before sending audio out over the node radio. Simplex nodes generally key-up very quickly while nodes connected to repeater systems may have considerable delays before all links in the system come up. Therefore, a delay needs to be inserted between the key-up command and the command that plays some audio file.

Most scripts have these delays hard-coded in them, which makes it necessary for a node owner to manually go into each new script and edit the locations where the key-up delay is defined. I propose a standard for setting key-up delays, where there is a default setting and a user definable setting that will override the default. I propose that the user defined delay be held in an environment variable called TXDELAY and be saved in the environment file. Since Dave (VE7LTD) is already using .5 seconds as the standard delay, let this be used as the default setting. Within each script that causes the node transmitter to key-up, I propose using the following:

 1 ${BIN}/key                           # Key the transmitter
 2 usleep ${TXDELAY:-500000}            # Wait before sending
 3 ...whatever code generates output...
 4 usleep 500000                        # Pause .5 seconds
 5 ${BIN}/unkey                         # Unkey the transmitter

In line 2, the ${TXDELAY:-500000} will use the value as defined in TXDELAY; however, if TXDELAY is not defined, then it will use the default value of 500000. Since we are using usleep, the time tick is in microseconds, which affords us a great deal of control over the length of the delay. NOTE: the value in TXDELAY must also be in microseconds.

The usleep 500000, in line 5, is to ensure that whatever audio was being generated has had time to make it out before the transmitter is unkeyed. Some audio generating programs exit while audio is still buffered for transmission, which would be lost if the node unkeyed as soon as the generating program quit.

I have already implemented this system in all the scripts that I have written.

To Key/Unkey or Forcekey/Forceunkey that is the question

Way back in the early days of IRLP, there was only key and unkey for asserting PTT to the node radio. These worked great, only as long as you wanted to use your node to connect to other nodes or reflectors. However, some of us started developing new uses for the IRLP hardware. One of those uses was to play announcements and informational bulletins such as ARRL Audio News, Amateur Radio Newsline and others. This is when we started running into problems. The dtmf program serves two purposes: 1) decode any incoming DTMF; and 2) be a watchdog timer to make sure the node radio did not stay keyed up too long. Yes, people have experienced radio meltdowns due to the transmitter being keyed for an extended period of time. To prevent future meltdowns, Dave put a watchdog timer in the dtmf program, which would shutdown the transmitter once it had been keyed up for 4 minutes. This presented a problem to those of who wanted play these long bulletins. Four minutes into a broadcast, the transmitter would shut down. To prevent this we script writers had to get creative and wrote programs that would periodically "tickle" (unkey then key) the PTT line to reset the watchdog timer.

After a while and much pleading, Dave created forcekey and forceunkey to overcome the watchdog timer problems. We can now write scripts that keep the transmitter keyed as long as we want (meltdowns be damned).

OPINION #1: forceunkey was implemented incorrectly. It is my opinion that there should be no interaction between the two methods. If key was used to key the transmitter, then only unkey should unkey the transmitter. If forcekey was used to key the transmitter then only forceunkey should unkey the transmitter. As it stands now, if key is used to key the transmitter, a forceunkey will unkey it; however if forcekey was used to key the transmitter then unkey has no effect.

So, whenever there is a situation where you need to keep the node radio keyed up for a long period of time, use the forcekey/forceunkey utilities; otherwise, use the key/unkey utilities.

Res Firma Mitescere Nescit

(Once you've got it up, keep it up.)

Sometimes it is desirable to establish a connection to a reflector but not have it drop after 20 minutes (the default) of monitoring. While it is generally not acceptable to connect to a reflector and leave the connection open ad nauseum, there are times when it would be nice to monitor for an extended period (e.g. during nets or emergencies). In order to do so, it is possible to disable the activity timeout timer. This is done by deleting the timeout file in the /home/irlp/local directory using the following command: rm /home/irlp/local/timeout just before establishing the connection. Don't worry about replacing the timeout file as it will be automatically restored when the connection is broken.