SIMPL is as SIMPL does

Installing the SIMPL Toolkit

The SIMPL toolkit has historically been distributed as a source package. Many SIMPL developers still prefer to build everything from scratch. On my Rasp Pi, the SIMPL toolkit takes about 90 seconds to compile from source. However, to simplify things for the developer, a prepared, prebuilt package contains both the C and the Python shared libraries, avoiding the dependency on the Python source headers [5] needed for the Python-SIMPL shared library to compile from source.

The prebuilt package for the Raspberry Pi developer can be downloaded from the SIMPL website [6]. It comes in the form of a self-installing archive called pythonraspibin.sh, which is what you want to download to your Raspberry Pi.

On my setup, I first downloaded this file to my Linux box and then used scp to copy it over to my Rasp Pi. If your Rasp Pi is configured with a keyboard and monitor, you can do this download directly.

In any case, you want to place this script in the /tmp directory. Anything you do or put into /tmp disappears on reboot. The self-installing archive will undo everything in place to allow you to test your configuration safely before deciding to make the SIMPL install permanent.

Next, you want to open up a console or terminal window pointing at this /tmp location. On my system, I use ssh from my Linux box to open up a terminal window on my Raspberry Pi. From this terminal window, you want to verify that you have the correct package with:

cksum pythonraspibin.sh

The script portion of this archive will not download with execute permission. You'll want to change this by giving the script execution permissions for your user ID:

chmod u+x pythonraspibin.sh

I prefer to have the present working directory (the so-called "." directory) in my PATH so that I can execute a script by typing its name directly rather than prepending it with ./. To do this, type:

export PATH=$PATH:.

Now you can simply run the self-installing archive script as pythonraspibin.sh. After answering the prompts, in a few minutes you should have an operational Python-SIMPL install.

The install script will give you the option to make this SIMPL install permanent. I placed mine at /home/pi so that everything SIMPL related will be at /home/pi/simpl.

Once you have exited from the install script, you need to do a couple of housekeeping chores to solidify your install and make the C-SIMPL shared library operational.

To begin, add the script code block in Listing 1 to your .profile hidden file, which resides at your home directory. In my case, it is at /home/pi/.profile.

Listing 1

.profile block

01 export SIMPL_HOME=/home/pi/simpl
02 if [ -z $FIFO_PATH ]
03 then
04     if [ ! -d /tmp/simplfifo ]
05     then
06          mkdir /tmp/simplfifo
07          chmod a+rw /tmp/simplfifo
08     fi
09     export FIFO_PATH=/tmp/simplfifo
10 fi
11 export PATH=$PATH:$SIMPL_HOME/bin:$SIMPL_HOME/scripts:.
12
13 export PYTHONPATH=$PYTHONPATH:$SIMPL_HOME/modules

This block creates an environment variable called SIMPL_HOME that serves as a convenient marker for the location of the SIMPL code tree. The SIMPL toolkit uses FIFOs to synchronize messages.

These FIFOs are collected into a single directory (called the SIMPL sandbox) defined by another environment variable, FIFO_PATH. The default is to locate this in /tmp/simplfifo.

The SIMPL toolkit comes with several utility functions and surrogates, which are located in $SIMPL_HOME/bin. It also comes with several utility scripts located in $SIMPL_HOME/scripts, so it is convenient to append these locations to the PATH environment variable.

The last line in Listing 1 locates the Python-SIMPL shared library, so it can be found from an import line in your Python scripts.

After you have added this block to your .profile, you need to open a new console window for these variables to take effect. A quick test to see whether your new block works is to type:

echo $SIMPL_HOME

It should return with the path to your SIMPL tree.

The next thing you need to do is activate your new C-SIMPL shared library. This is a privileged exercise, so you need to use sudo in front of your commands to give yourself root/admin permissions. Beforehand, you need to use your favorite text editor to create a file called /etc/ld.so.conf.d/simpl.conf. Into that file you add a single line pointing to your SIMPL tree's lib subdirectory. In my case, it is /home/pi/simpl/lib.

Finally, you need to let your shared library manager know that these new libraries are installed. For this you type:

sudo ldconfig

At this point, you should have a fully operational SIMPL install.

Trivial SIMPL Application

You are now ready to create your first SIMPL application on the Raspberry Pi. Schematically, it will look like Figure 1.

Figure 1: First Raspberry Pi SIMPL application.

This application takes three integers and sums them. The design consists of two modules. The sender module presents a command-line user interface asking for those three integers and displays the resulting sum. Python is a good language for this kind of task, so the sender module will be written in Python 3. The receiver module will contain the summing algorithm business logic. To illustrate a mixed-language SIMPL application, the receiver module will be written in C.

The two modules will be connected together by a SIMPL communication channel. The SIMPL message design consists of four integers – token, var1, var2, and var3 – and will be transmitted in binary form (i.e., it will be a binary tokenized message). The token for doing a sum will be set to 10.

First, examine the receiver C code shown in Listing 2. Note that this receiver.c has only three SIMPL API calls: name_attach() (line 35), as in:

Listing 2

receiver.c

01 /*
02 FILE:           receiver.c
03
04 DESCRIPTION:This is a C simpl receiver.
05
06 USAGE:          receiver
07 */
08
09 // include required headers
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <simpl.h>
13
14 // define possible message structures
15 typedef struct
16         {
17         int token;
18         int var1;
19         int var2;
20         int var3;
21         } MSG1;
22
23 int main()
24 {
25 char *me = "PIRECEIVER";
26 char *sender;
27 char inmem[1024];
28 char outmem[1024];
29 int n;
30 MSG1 *in1;
31 MSG1 *out1;
32 int *token;
33
34 // perform simpl name attach
35 if (name_attach(me, NULL) == -1)
36     {
37     printf("%s: cannot attach name-%s\n",me,whatsMyError());
38     exit(-1);
39     }
40
41 while (1)
42     {
43     // receive incoming messages
44     n = Receive(&sender, inmem, 1024);
45     if (n == -1)
46          {
47          printf("%s: Receive error-%s\n", me, whatsMyError());
48          continue;
49          }
50
51     // set a pointer to the value of the message token
52     token = (int *)inmem;
53
54     // decide course of action based on the value of the token
55     switch (*token)
56           {
57           case 10:
58                in1 = (MSG1 *)inmem;
59 /*
60                printf("token=%dvar1=%d var2=%d var3=%d\n",
61                     in1->token,
62                     in1->var1,
63                     in1->var2,
64                     in1->var3);
65 */
66                //reply to sender
67                out1 = (MSG1 *)outmem;
68                out1->token=10;
69                out1->var1=in1->var1+in1->var2+in1->var3;
70                Reply(sender, outmem, 1024);
71                break;
72
73           default:
74                out1 = (MSG1 *)outmem;
75                out1->token=99;
76 /*
77                printf("%s:unknown token=%d\n",me,*token);
78 */
79                Reply(sender, outmem, 1024);
80           }
81
82     }
83
84 return(0);
85 }
if (name_attach(me, NULL) == -1)

Receive() (line 44), as in:

n = Receive(&sender, inmem, 1024);

and Reply() (line 79), as in:

Reply(sender, outmem, 1024);

The algorithm for summing the numbers is nicely encapsulated inside the case 10 statement (lines 57-71, i.e., token has the value 10). Also note the efficient encoding and decoding of the binary tokenized messages through the use of C pointers, which is why this kind of messaging is popular with SIMPL developers using C.

To compile this receiver code, you also need a Makefile in the same directory as receiver.c (Listing 3). Recall in Makefiles that the cc and rm rule lines must be indented by a Tab and not by spaces.

Listing 3

Makefile

01 SIMPL_DIR=$(SIMPL_HOME)
02
03 SIMPL_INCL_DIR=$(SIMPL_DIR)/include
04
05 CDFLAGS=-c\
06         -Wall \
07         -I $(SIMPL_INCL_DIR)
08
09 LDFLAGS=\
10         -L $(SIMPL_DIR)/lib\
11         -lsimpl
12
13 #========================
14 # Default targets
15 #========================
16 all: receiver
17
18 #========================
19 # Link
20 #========================
21 receiver: receiver.o
22         cc -o receiver receiver.o $(LDFLAGS)
23
24 #========================
25 # Compiles
26 #========================
27 receiver.o : receiver.c
28         cc $(CDFLAGS) -o receiver.o receiver.c
29
30 #========================
31 # cleanup
32 #========================
33 clean:
34         rm receiver
35         rm receiver.o
36
37 #========================
38 # install
39 #========================
40 install: receiver

To build receiver.c, you simply type make. This should result in an executable file called receiver in the same directory. This is your very first SIMPL module! To run this module as a background process and examine your SIMPL sandbox to see whether the module is running successfully, type:

receiver &
fcshow

Something like the following output should appear in your console window, indicating that the receiver has successfully attached its name, PIRECEIVER, to a communication channel:

Simpl Name                   Pid
--------------------------  -----
PIRECEIVER                   2119

To stop this module, you would simply type:

fcslay PIRECEIVER

With the receiver module in the background, you now have one half of the SIMPL application running. Next, you need to create a Python sender script to complete the other half (Listing 4). Note that this Python code only uses three calls to the SIMPL API: name_attach() (line 15) embedded inside the following statement:

Listing 4

sender.py

01 """
02 sender.py: This is a python simpl text-based sender.
03 Usage: python3 sender.py myName receiverName
04 """
05
06 # import required modules
07 import sys
08 import csimpl
09
10 # initialize necessary variables
11 sName = sys.argv[1]
12 rName = sys.argv[2]
13
14 # create an instance of Simpl
15 nee = csimpl.Simpl(sName, 1024)
16 if (nee == -1):
17         print ("name attach error-", whatsMyError())
18         sys.exit(-1)
19 # name locate the receiver program
20 receiverId = nee.nameLocate(rName)
21 if receiverId == -1:
22     print ("name locate error-",  nee.whatsMyError())
23     sys.exit(-1)
24
25 while True:
26     try:
27          line = input("-> ")
28     except EOFError:
29          break
30     else:
31          if line[0] == '?':
32                print("enter s n1,n2,n3 to get sum")
33                print("enter q to quit")
34          if line[0] == 's':
35                ss=line.split()
36                s=ss[1].split(",")
37                val1=int(s[0])
38                val2=int(s[1])
39                val3=int(s[2])
40                print("var=%d %d %d" %(val1, val2, val3))
41
42 # compose the message you wish
43 # to send based on the value of
44 # the token
45                inToken=10
46                print ("token = %d" %inToken)
47                nee.packMsg(nee.BIN, "iiii",inToken,val1,val2,val3)
48
49 # send the message
50                retVal = nee.send(receiverId)
51                if retVal == -1:
52                     print ("send error-",nee.whatsMyError())
53                     sys.exit(-1)
54
55 # unpack the reply message
56                outToken, sum = nee.unpackMsg(nee.BIN, "ii")
57                if outToken == 10:
58                     print ("reply token=%d sum=%d" %(outToken,sum) )
59                else:
60                     print ("reply token=%d" %outToken )
61
62          elif line[0] == "q":
63               break
64
65 print("done")
nee = csimpl.Simpl(sName, 1024)

name_locate() (line 20), as in:

receiverId = nee.nameLocate(rName)

and Send() (line 50), as in:

retVal = nee.send(receiverId)

Encoding and decoding of the binary tokenized message using the pack/unpack methods is almost as easy in Python as it was in C.

To run the sender module in this little SIMPL application, you simply run the sender portion as:

python3 sender.py BOB PIRECEIVER

Notice the two arguments, both SIMPL names. The first registers the sender with the SIMPL sandbox. The second specifies the name of the communication partner. If you run fcshow again from a separate console window, you should see the sender module named BOB also in the sandbox.

At the prompt, enter s followed by three comma-separated numbers, such as:

-> s 5,6,7

which should result in the following output:

var = 5 6 7
token = 10
reply token=10 sum=18

You can continue to submit more sets of numbers by repeating the s command. When you are done, simply respond with a q at the prompt to quit.

This trivial example contains all the elements of a SIMPL message pass and, as such, can serve as a robust template for more sophisticated SIMPL applications.

SIMPL is a naturally queuing system. The SIMPL message is stored in a block of shared memory owned by the sender. As a result, multiple senders can easily communicate with a single receiver. You need to do nothing to the code to manage message queuing. It all happens naturally. To illustrate this, try running more than one sender at the same time by changing the SIMPL name in the invocation line:

python3 sender.py BOB2 PIRECEIVER
...

When you run the sender copies from separate console windows, notice that the receiver always does the sequencing and gets the correct sum to the correct module. This is one of the benefits of synchronous deterministic messaging: natural queuing with first in, first out rules.

Now things get really interesting. On my Linode, hosted at icanprogram.ca, I have a copy of this receiver also running as PIRECEIVER. To instruct the sender to communicate with this cloud-based module, I simply have to pre-run a couple of SIMPL Linux daemon processes (the TCP/IP surrogates) and rerun the sender with a slightly different argument. The TCP/IP surrogates are run as two processes in the background:

protocolRouter &
surrogateTcp &

To point the sender module at the Linode, I simply rerun it as:

python3 sender.py BOB icanprogram.ca:PIRECEIVER

Notice that the only change made was to prepend icanprogram.ca to the SIMPL name of the receiver to create a composite SIMPL name, kind of like a SIMPL URL; the TCP/IP surrogates looked after everything else.

You will no doubt notice a time lag before the prompt appears in this latest run because of the latency associated with setting up the surrogates and the network socket communication channel to the remote receiver. However, once the prompt appears, execution time is only marginally slower than for the case in which the entire app was hosted on a Rasp Pi.

In this exercise, I wrote sender.py and deployed it in a local Raspberry Pi application.Then, I redeployed the very same sender.py in a Raspberry Pi to a cloud application without a single code change. It is impossible to overemphasize how powerful this is.

All of the networking/surrogate magic happens behind the scenes with the name_locate() line:

receiverId = nee.nameLocate(rName)

The protocolRouter daemon is responsible for managing the different surrogate protocols supported by SIMPL. At the moment, there are two: TCP/IP and RS232. To fully specify the composite name, you could have included the protocol, as in:

SIMPL_TCP:icanprogram.ca:PIRECEIVER

Because TCP/IP is the most common protocol, it is set as the default and can be omitted from the composite name. Once the protocolRouter determines which surrogate daemon should handle the name_locate(), it simply exchanges a message instructing that surrogate daemon to open communication with the remote node.

This daemon first spawns a child process and then opens a TCP/IP socket to the remote node on predefined port 8001. The corresponding surrogate daemon on that node (my Linode in this example) is listening to port 8001, and when the socket opens, it spawns a surrogate child at that end.

The end result of this exchange is that I have a receiver surrogate (child) sitting on the sender node and a sender surrogate (child) sitting on the receiver node. The name_locate() returns the channel to this local receiver surrogate. All subsequent message exchanges then simply proceed as if the destination process is local. The generic surrogate pair looks after all the details associated with transmitting the messages across the network.

Buy this article as PDF

Express-Checkout as PDF

Pages: 8

Price $2.95
(incl. VAT)

Buy Raspberry Pi Geek

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content