Sunday, October 13, 2013

Building a Python interface to C for the Beaglebone Black

Making Python talk to a C library on the Beaglebone Black

Since we have a working DMCC (Dual Motor Controller Cape) Library in C (see our github repository at  https://github.com/Exadler/DMCC_Library), now the hard part begins... getting Python to work with it.

Disclaimer: I'm new to Python, and although I have extensive C and C++ experience, this will be a learning exercise for me, so if I make any obvious mistakes, please, please, comment and point me in the right direction!

First step was to hit the python docs returned from a Google search:  http://docs.python.org/2/extending/extending.html, and ok, good, the first bullet point is "1.1. A Simple Example".

After reading through the "Simple" example, I'm now really confused.  The Intermezzo on Errors and Exceptions threw me off -- Do I, or don't I have to deal with Errors and Exceptions immediately before I can successfully compile a Python extension in C??  After deciding that I should handle the errors, I cut and paste the code from the Intermezzo section 1.2. into my Beaglebone, only to find in Section 1.3. Back to the Example, they don't have the error handling code!!  Ok, great, silly detour.


Detour of Simple Example from Python C extension

Fine, keep going.  I get down to section "1.4. The Module's Method Table and Initialization Function".  Ok, we have to put in a table of commands -- but where??  Do I put it in a separate file, and there is a main() function?!  I thought I was building a library extension, why is there a main?  Also, how do I install it in the system?? I'm confused?!!

Note: I decided to write this blog entry as I'm learning the system, because I am finding it very confusing, and hopefully this will help the next person that is just as confused as I am!

Ok, Google time!  Searching the web, I get several hits talking about using SWIG, Boost.Python, pyrex, and ctypes.  From this stackoverflow.com posting (http://stackoverflow.com/questions/145270/calling-c-c-from-python), ctypes look simple enough, and there's the compilation steps!!  

ctypes and python with steps!
Ok, try typing in the sample code.  I understand enough C++ that this actually makes sense to me.  So, I'm thinking, this should be a breeze... Everything compiles and then... BAM!  When I get to the steps in python to call the library:

from ctypes import cdll
lib = cdll.LoadLibrary('./libfoo.so')
I get the following error:
root@beaglebone:~/python# python
Python 2.7.3 (default, May 29 2013, 21:25:00)[GCC 4.7.3 20130205 (prerelease)] on linux2Type "help", "copyright", "credits" or "license" for more information.
>>> from ctypes import cdllTraceback (most recent call last):  File "<stdin>", line 1, in <module>
ImportError: No module named ctypes
>>>
Just great!!  I don't have ctypes on the Beaglebone black!  Googling the error yields many cryptic pages.  Arrggh!  

Ok, don't panic, just go back and Google some more.  Finally I find this blog entry from Ned Batchelder (http://nedbatchelder.com/text/whirlext.html), and it actually makes sense!  A good step by step guide!  I especially liked his slides titled "It really works".  Perfect!  The only little thing that was a bit confusing about Ned's guide is whether setup.py was a built in function or whether it was the file from the slide "Building it".  I'm assuming that I had to type it in based upon the comments (what threw me off is his comments about "setup.py knows how to do it with simple declarative..", and the fact that the slides seems to have too little code.  Ok, never mind, just create my setup.py and try typing in "python setup.py".  BAM!  Another gotcha!
root@beaglebone:~# python
Python 2.7.3 (default, Apr  3 2013, 21:37:23)[GCC 4.7.3 20130205 (prerelease)] on linux2Type "help", "copyright", "credits" or "license" for more information.
>>> from distutils.core import setup, ExtensionTraceback (most recent call last):  File "<stdin>", line 1, in <module>
ImportError: No module named distutils.core
>>>
Ok, don't panic, just Google it.  Finally found this stackoverflow.com link:  http://stackoverflow.com/questions/3810521/how-to-install-python-distutils, and wow, someone else is also using the Beaglebone black and Angstrom!

Did the opkg update and opkg install python-distutils, and Ned's code worked!  Well, almost -- I got this far:
root@beaglebone:~/python# python setup.py
usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
   or: setup.py --help [cmd1 cmd2 ...]
   or: setup.py --help-commands
   or: setup.py cmd --help
error: no commands supplied
root@beaglebone:~/python#
Hmm... there's a help?  Let's try it.  First few lines gave me the key:
root@beaglebone:~/python# python setup.py --help
Common commands: (see '--help-commands' for more)
  setup.py build      will build the package underneath 'build/'
  setup.py install    will install the package
...
Let's try the build and install, bingo!  It worked!
root@beaglebone:~/python# python setup.py build
running build
running build_ext
building 'ext1' extension
arm-angstrom-linux-gnueabi-gcc -march=armv7-a -mthumb-interwork -mfloat-abi=softfp -mfpu=neon -mtune=cortex-a8 -D__SOFTFP__ -fno-strict-aliasing -O2 -pipe -g -feliminate-unused-debug-types -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -fPIC -I/usr/include/python2.7 -c ext1.c -o build/temp.linux-armv7l-2.7/ext1.o
arm-angstrom-linux-gnueabi-gcc -march=armv7-a -mthumb-interwork -mfloat-abi=softfp -mfpu=neon -mtune=cortex-a8 -D__SOFTFP__ -shared -Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed build/temp.linux-armv7l-2.7/ext1.o -L/usr/lib -lpython2.7 -o build/lib.linux-armv7l-2.7/ext1.so
root@beaglebone:~/python#
root@beaglebone:~/python#
root@beaglebone:~/python# python setup.py install
running install
running build
running build_ext
running install_lib
copying build/lib.linux-armv7l-2.7/ext1.so -> /usr/lib/python2.7/site-packages
running install_egg_info
Removing /usr/lib/python2.7/site-packages/UNKNOWN-0.0.0-py2.7.egg-info
Writing /usr/lib/python2.7/site-packages/UNKNOWN-0.0.0-py2.7.egg-info
root@beaglebone:~/python#
root@beaglebone:~/python#
Trying to run the extension in the python command line we get
root@beaglebone:~/python# python
Python 2.7.3 (default, May 29 2013, 21:25:00)
[GCC 4.7.3 20130205 (prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import ext1
>>> ext1.hello_world()
 hello_world called
'hello, world!'
>>>
Yay!!  I works!!  Ok, enough working on the code for the day, we got the first steps and now I'm calling it a night.  For reference, here is the ext1.c file:
// ext1.c: A sample C extension: one simple function
#include "Python.h"
// hello_world function.
static PyObject *
hello_world(PyObject *self, PyObject *args)
{
    printf(" hello_world called\n");
    return Py_BuildValue("s", "hello, world!");
}
// Module functions table.
static PyMethodDef
module_functions[] = {
    { "hello_world", hello_world, METH_VARARGS, "Say hello." },
    { NULL }
};
// This function is called to initialize the module.
void
initext1(void)
{
    Py_InitModule3("ext1", module_functions, "A minimal module.");
}

Hope this helps someone, because I sure could have used this help many, many, many hours ago!  And, for those python gurus out there, let me know if I'm missing something or if I'm on the right track!  Thanks.