The purpose of this Instructable is to illustrate how to set up and use IPC (interprocess communication) between a Python script and the executing sketch on the Intel Galileo board. In addition to providing the source code I will be going over the code in detail and pointing out the rationale behind several decisions. The hope is that this will make it easier for you if something breaks or you want to extend the code's functionality. It wouldn't really be very instructional if I mailed you a finished product therefore this Instructable isn't complete without a solid walk-through of the code.
I recommend downloading the files in the /sketch, /python, and /examples from my GitHub (https://github.com/bunneydude/IPCBuffer). The readme files contain instructions on how to install them. Also, download the nrf24.zip file below (the slightly modified radio library for porting to Galileo from here). Instead of posting the code in the Instructable you can follow along with the source code itself.
That being said, documentation is not complete without examples. After the code walk-through there are instructions on how to use this IPC library to decrypt data the Galileo receives wirelessly from a MSP430.
The following skills will be somewhat assumed for this Instructable:
Step 1: Communication and File Transfer with Galileo
For this project you will want to get Galileo up and running with an external SD card. Steps for this are in a document on the Intel Makers Community site.
Communicating With Galileo There are two main ways to communicate with the Galileo: Telnet (Ethernet) and serial (USB). The Telnet connection provides you a command prompt within the Linux OS while the serial connection allows you to communicate directly with the running sketch. For the specifics of setting up Ethernet, see this post on the Intel Makers Community site.
File transfer with Galileo To transfer files while the Galileo is on, you need some version of a file transfer system. For Windows, you can download WinSCP and connect using the same host name you provided for the Telnet connection, port 22, and protocol SCP. It will prompt you for login information so just enter 'root' and you should see the picture above appear. WinSCP allows you to easily browse Galileo's files and your own side-by-side. Copying files is as easy as clicking and dragging. This way you don't have to power off the board and eject the SD card every time you want to move a file.
Step 2: Cross-Compiling Modules
Source the cross-compiler environment from the Galileo Arduino IDE folder: source /path/to/arduino-1.5.3/hardware/tools/environment-setup-i586-poky-linux-uclibc
If you don't have them already, install the dev package for python: sudo apt-get install python-dev
In the top directory of sysv_ipc and pycrypto, run the setup script: sudo python setup.py build
You'll see a string of commands that were executed in the form of "gcc -option -more options -etc". These need to be run with the compiler the source script specified in environmental variable CC. There are several tutorials online for how to get Python to cross-compile properly but for a quick fix you can do the following:
Copy all the "gcc" commands into a text file (e.g. "build_cmds")
Find and replace every 'gcc' with '$CC'.
Become root: sudo -s -H
Re-source the environment script, then execute the commands from the text file: bash build_cmds
The files we need are sysv_ipc.so and the "Crypto" folder - both found within their respective build/lib.linux-i686-2.7 directories. Copy these two files to /usr/lib/python2.7/site-packages on the Galileo with WinSCP or your favorite file transfer program. I've uploaded the compiled files from my Galileo in known_good.zip.
Step 3: IPC ObjectsIPC objects come in two main families: SysV and POSIX. This project will be using SysV for no real particular reason. (Both families are supported on the Galileo so after I finish this Instructable I plan to go back and switch things over to the POSIX versions since they offer a few advantages). SysV has three IPC objects: shared memory, semaphores, and message queues. This project will only make use of the first two.
A shared memory object is just a portion of memory that can be accessed by more than one process. It's the fastest way to transfer large amounts of data between processes. However, it doesn't have any built-in protection. Without adding additional synchronization code the processes will likely write over each other's data.
A semaphore can be thought of as a counter for how much of a shared resource is available. A process can atomically (i.e. without interruption) increment or decrement the count of a semaphore. If a process tries to decrement a semaphore when its count is zero, the process will typically go idle until a different process increments the semaphore's count. The importance of going idle is that the process is no longer consuming OS resources. A naive implementation that instead polls on the count to be non-zero will both use up OS resources and make it more difficult for other processes to meet their deadlines.
For a more extensive description of shared memory or semaphores, there are numerous explanations and examples between Wikipedia and Stack Overflow.
Step 4: Creating IPC ObjectsA shared memory object is created with shmget while a semaphore uses semget. They both need a key, flags, and one function-specific value. To check on the status of IPC objects you create, you can enter one of the following commands from Linux for shared memory or semaphores, respectively: cat /proc/sysvipc/shm
These commands are very helpful when you're trying to determine when an object has been created or destroyed. It also allows you to see the key and ID of the object. This helps debugging the Python script since you will know for sure which IPC objects are actually available for Python to use.
Keys Every IPC object is created using a unique key. For a simple test it's acceptable to use a hard coded key but beyond that it's useful to have a function generate keys for you. SysV provides the ftok function for this purpose. Its arguments are a string (representing the location of a file) and an int (used to generate different keys). ftok combines the pathname and the int (though only the least significant 8 bits are used - and must be non-zero) to generate a unique key (most of the time). There is still the possibility to call ftok with different arguments and get the same key. Granted, the chance is small but a robust solution must handle this condition. That being said, this project ignores this condition because it will soon be updated to use POSIX IPC functions - which will always return unique keys.
Flags The flags given to shmget and semget control how it is created. IPC_CREAT by itself will create the object if it doesn't exist or simply return the ID of the current object if it has already been made. OR'ing IPC_EXCL with IPC_CREAT will cause the function to error if the object already exists. From here you could negotiate a way to generate a new key and try and open that object instead. You can also OR in file permissions - 0666 or 0660 in this code. This represents the permissions for the owner, group, and others for this object. The value 0666 allows all three to read/write while 0660 only allows the owner and group to read/write.
In this code I omit IPC_EXCL mostly because if you forget to destroy the IPC objects before reprogramming the Galileo, the old IPC objects persist. When the new sketch runs with IPC_EXCL it will see that the IPC objects have already been created and will error. This now leaves you to manually destroy the objects or reboot the Galileo. By leaving IPC_EXCL out this problem is avoided. Also, since the only programs creating IPC objects are ones you are launching you won't have to worry about colliding with another process's IPC objects.
Additional Arguments and Initialization In SysV, a semaphore object is actually an array of semaphores. For simplicity, we will limit each object to just a single semaphore. This also helps make the code a bit more readable. The second argument of semget is the number of semaphores for this object.
A shared memory object needs to know its size before creation. Therefore the second argument of shmget is the size of the shared memory segment to be created.
The shmat function will attach the shared memory object to the address space of the current process (i.e. the sketch). This is what will allow the sketch to read and write from the shared memory segment as if it were just another data object created by the sketch. The void pointer returned by shmat must be casted so we can operate on it later.
The semaphore must have its count initialized to a value consistent with its interpretation in the code. For example, a semaphore representing how much data is in a (initially empty) queue should be set to zero after creation. This is done with the semctl function which takes the semaphore's ID, the index within the semaphore array (always 0 for this code), a command, and a union. The union contains data that can be used by certain commands. Here we just need the SETVAL command which sets the semaphore's count equal to the int within the union.
Step 5: The Open/Close Functions
Open In the open method, four keys are created using different files and the ID specified (must be non-zero) when this instance of IPCBuffer was created. This only allows for 255 IPCBuffer objects to be in use at once.
The buffer used to hold the data between the Python and sketch processes is defined by its width and length. Width is the number of bytes that make up a single unit of data. For example, AES encrypts data in 16-byte blocks so the width would be 16. It doesn't make sense in this case to allow a process to read more or less than 16 bytes at a time since the data would be incomplete. Later we will see why we don't allow a process to read or write more than one width's worth of data at a time.
The length of the buffer is how many units of data it can store. So if the buffer can hold 5 AES blocks, its length would be 5 and its total size in bytes would be (16 bytes) * 5 = 80 bytes. In order for a process to know where it should read or write next in the buffer, we need a head and tail index. Data is written to the buffer at the head and read from the tail. These four pieces of data must be placed at the start of the shared memory segment. Otherwise the Python script wouldn't know how large the buffer is or where it should read or write data. The above picture illustrates how a shared memory segment would be set up for a width of 2 and length of 4.
The semaphore representing the free space in the buffer is initialized to 'length' and the semaphore for how much valid data is in the buffer is set to 0. A third semaphore, sem_lock, is set to 1 so that we only allow a single process to read or write from the shared memory segment at a time. Without this lock the code would not be safe with multiple readers and writers.
Close The close method simply destroys the current IPC objects. shmdt detaches the shared memory segment and shmctl (which has similar arguments to semctl) marks the segment to be deleted. When no more processes are attached it willl be removed. At this point its key will show up as 0 in cat /proc/sysvipc/shm. After calling semctl to delete the semaphores they will immediately be removed and cat /proc/sysvipc/sem will no longer show them
Step 6: The Write/Read FunctionsTo write to the buffer, the sketch first tries to decrement sem_space with the function semop. It uses a struct (defined in IPCBuffer.h as sem_up and sem_down) whose three values indicate the index in the semaphore array (again, always 0 for this code), how much we want to increment (positive integer) or decrement (negative integer), and optional control flags, respectively. The third value of 0 here means that the process will block if it is unable to decrement the semaphore (i.e. the count is at 0).
After the decrement to sem_space succeeds, we know that there is room for at least one piece of data. We only allow a process to read or write a single unit of data at a time or else we would violate the interpretation of the semaphore's count. We don't know the semaphore's value, only that it wasn't 0 when we called semop on sem_space. We next try to decrement sem_lock. If it blocks then that means a different process is accessing the shared memory segment. When the sketch is unblocked it will read the head index and copy one width's worth of bytes from memory address source to the appropriate location in shared memory. The head index is incremented and written back to shared memory before incrementing sem_lock.
The last step is to increment sem_data. This will enable other processes to see that new valid data has been placed in the buffer. It is worth pointing out that we do not need any additional state to handle the situation where the head and tail values are equal. This occurs both when the buffer is empty and when it is full. The semaphores sem_space and sem_data keep track of how much space there is for data to be read from or written to the buffer. When sem_space reaches 0, for example, this means that the buffer is full and no process trying to write to the buffer will succeed until a different process reads an element from the buffer.
The read method follows the same rationale for write but it decrements sem_data first and increments sem_space at the end along with using the tail index to read from the buffer. The Python script follows the same pattern for reading and writing just with the API of the sysv_ipc module.
Step 7: MSP430 Demo Schematic
The following connections must be made between the second nRF24L01 and the Galileo (pinout similar to Arduino Uno):
Function Galileo Pin nRF24L01 Pin
CE 6 3
CSN 4 4
SCK 13 5
MISO 12 7
MOSI 11 6
3.3V n/a 2
GND n/a 1
Step 8: Encryption Demo
The msp430_demo.zip file contains everything needed to build and run this demo. It uses the same nrf24 library as the Galileo and has slight modifications so it will build on the MSP430. The AES-128 code for the MSP430 comes from Texas Instruments (AES_128.zip).
The code here simply polls the state of port 2 for any pin change every 20ms. If a change is seen, the current state of the port is encrypted using AES-128 and then sent via the nRF24L01 module. Since there are not physical buttons (just metal wires touching) there isn't a need to debounce the port inputs.
It is worth noting that this demo does not implement secure or authenticated encryption, nor was it ever intended to. The purpose was to illustrate a scenario where it would be convenient for the Galileo sketch to pass data to a different process. Instead of having to spend time re-writing or searching for an appropriate Arduino library for decryption, you can simply use the PyCrypto module (which is quite extensive). Running the Demo
With the MSP430 board and Galileo powered up, launch the shm_python script as specified above. When you ground one of the six port 2 pins on the MSP430 a message should be printed saying which pin was pressed. The current encoding in shm_python.py is specific to how the wires were laid out on my breadboard. The MSP430 code supports grounding multiple pins - additional values just need to be added to the key dictionary in shm_decrypt.py.
To destroy the IPC objects, download python_interface.zip and run tui.py (requires pySerial) from the computer you used to program the Galileo. Launching tui.py will open a COM port (defaults to COM 5) at 115200 baud. This is a simple script to send and receive serial data consistent with the protocol coded into galileo_ipc.ino. Reading from register 1 will call myBuffer.close() within the sketch. I.e.
This is mostly a WIP of mine but I've included it for completeness. You could also remove the serial protocol within galileo_ipc.ino and use a different method you prefer.
Step 9: Demo Video
Step 10: Conclusion and Links
Intel Galileo on Mouser
10pcs nRF24L01 on eBay
Getting started with Intel Galileo PDF
Using Galileo over Ethernet
sysv_ipc Python module
PyCrypto module (currently down at the time of writing)
Female jumper wires on Adafruit