Search by Tags

Python in Linux

 

Compare with Revision




Subscribe for this article updates

Python Setup

This article presents two options to setup Python:

  • With an Existing Linux Image: quick to get started and good for testing purposes.
  • While Building a Linux Image: recommended way, since you create an easy to reproduce, production ready environment.

There are no guarantees that the same Python packages will be available using both methods, since they are maintained by different third parties.

With an Existing Linux Image

Using the Distribution Package Manager (OPKG)

The pre-built Linux image provided by Toradex is built on top of the Ångström distribution. It comes with a package manager (opkg) and the Ångström feeds already configured, which enables the installation of packages in an existing image. It is a good starting point for quick prototypes and tests.

To see a list of relevant packages, enter the following commands:

opkg update
opkg list | grep python

Python (and any additional packages) may be installed with the following command:

opkg install python [optionally list additional packages...]

The same applies for Python 3. The packages are usually referred as python3-package, for instance python3-pyserial.

Using the Python Package Manager (PIP)

PIP is the Python package manager, which enables you to install packages unavailable from the distribution feeds. Nevertheless you have to install at least Python and PIP:

opkg update
opkg install python python-pip

You may also need a native SDK if the package builds native (C/C++) code. Please refer to the article Native Compilation for information how to setup the native SDK:

Then you can install a package using PIP:

pip install <package>
# for instance
pip install pyserial

While Building a Linux Image

A set of python packages can be added to an OpenEmbedded Linux build for inclusion into the image's root file system. After establishing the image build configuration, additionally append the following line to the oe-core/build/conf/local.conf file:

local.conf
IMAGE_INSTALL_append = "python"

Then execute (or re-execute) 'bitbake' for the image (ie. 'bitbake angstrom-lxde-image'). For more information about building a Linux image, see OpenEmbedded (core).

Using existing packages

You can search related Python packages using the OpenEmbedded recipes index or by searching the layers directory of your build environment. An example is provided to search for the pyserial package:

user@host:~$ cd <openembedded-setup-directory>
user@host:~/oe-core$ ls
build deploy export layers
user@host:~/oe-core$ find ./layers -name "*python*pyserial*bb"
./layers/meta-openembedded/meta-python/recipes-devtools/python/python3-pyserial_3.4.bb
./layers/meta-openembedded/meta-python/recipes-devtools/python/python-pyserial_3.4.bb

Writing your own recipes

Even if you are starting with OpenEmbedded, writing recipes for Python modules is usually straightforward. There are many recipes out there that do it for you to use as examples and their structure looks very alike.

A possible path for writing a recipe is provided below. We use the TinyDB 3.7.0 release for illustration:

Before writing the recipe

  1. Go to the Python Package Index and find the package you are after. In our example TinyDB.
  2. Find the downloads section, copy the download link and download the package to your PC. In our case, tinydb downloads section.
  3. Find the license type. In our case is MIT.
  4. Generate the md5 and sha256 checksums for the compressed package. In our case md5:e28a5650ef8796ab6b7892591edfbae8 and sha256:071105c339b44f928968cb4e62f7340a568beb9962a3dcda66eb82da9cab0b0a
  5. Unpack the compressed package and search for the license file - common namings are README or LICENSE, but it may be a different name. In our example the name of the license file is PKG-INFO.
  6. Generate the md5sum for the license file. For the .tar.gz compressed release of TinyDB 3.7.0, it is 8e3a385933697d0374af36db8ee0319d.
  7. Try to understand which are the package dependencies. You can do this empirically once you have written the recipe.

Writing the recipe

We will create three files:

Note: Follow the dash, underscore and case standard provided, since those matter for OpenEmbedded. See the project documentation for further details.

  • package-name.inc: this is common information to be used in Python 2 and Python 3 recipes.
  • python-package-name_version.bb: recipe to build Python 2 module.
  • python3-package-name_version.bb: recipe to build Python 3 module.

For our example:

  • python-tinydb.inc
  • python-tinydb_3.7.0.bb
  • python3-tinydb_3.7.0.bb

First create a new layer (recommended) to hold all your OpenEmbedded customizations, or you can do it in an existing layer also for studying purposes.

# assuming you have a meta-mylayer already setup and working.
user@host:~/oe-core$ mkdir -p layers/meta-mylayer/recipes-devtools/python-tinydb
user@host:~/oe-core$ cd layers/meta-mylayer/recipes-devtools/python-tinydb

Create the files provided below:

python-tinydb.inc
# Add the package description here. DESCRIPTION = "TinyDB is a lightweight document oriented database optimized \ for your happiness :) It's written in pure Python and has no external \ dependencies. The target are small apps that would be blown away by a SQL-DB \ or an external database server." SECTION = "devel/python" # Add the license type found in step 3 of previous section LICENSE = "MIT" # Add the license md5sum found in step 6 of previous section LIC_FILES_CHKSUM = "file://${S}/PKG-INFO;md5=8e3a385933697d0374af36db8ee0319d" PYPI_PACKAGE = "tinydb" PYPI_PACKAGE_EXT = "tar.gz" BBCLASSEXTEND = "native" # Add the compressed package md5 and sha256 checksums generated in step 4 of previous section SRC_URI[md5sum] = "e28a5650ef8796ab6b7892591edfbae8" SRC_URI[sha256sum] = "071105c339b44f928968cb4e62f7340a568beb9962a3dcda66eb82da9cab0b0a" inherit pypi
python-tinydb_3.7.0.bb
inherit setuptools require python-tinydb.inc # Uncomment the lines below and add runtime dependencies if any. Our example does not have any dependencies. #RDEPENDS_${PN} += "" #RDEPENDS_${PN}_class-native = ""
python3-tinydb_3.7.0.bb
inherit setuptools3 require python-tinydb.inc # Uncomment the lines below and add runtime dependencies if any. Our example does not have any dependencies. #RDEPENDS_${PN} += "" #RDEPENDS_${PN}_class-native = ""

Now you can add the package to your image, e.g. for TinyDB:

local.conf
IMAGE_INSTALL_append = "python-tinydb" # or IMAGE_INSTALL_append = "python3-tinydb"

Performance & Suitability

Tests results are pending; however, Python is expected to perform at least an order of magnitude slower than equivalent compiled C/C++ code due to Python's run-time interpreted nature. Use of a Just-in-Time or Ahead-of-Time compiler should improve performance; however, such compilers have not been tested with our Linux images.

Regardless, Python is still a viable alternative for performing lighter processing tasks. It can be effective for low level IO operations as well as high level applications such as web services. Furthermore, Python code can be rapidly modified and tested without the need to recompile.

Examples

GPIO - Blinking LED

Blinking a LED using GPIO access through sysfs.

#!/usr/bin/env python
 
import time
import os.path
 
GPIO_RESET    = False;  # Whether GPIOs should be re-exported
GPIO_PATH     = "/sys/class/gpio";
GPIO_DIR_OUT  = "out";
GPIO_VAL_HI   = "1";
GPIO_VAL_LO   = "0";
GPIO_CHAN_NUM = "146";  # GPIO1 on Apalis T30
 
BLINK_PERIOD  = 500;  # Blink period (milliseconds)
BLINK_DUTY    = 0.25; # Blink duty cycle (fraction)
 
def main():
    try: 
        ### Initialize GPIO - optionally reset if already initialized
 
        ## Note: GPIOs which are already used in the drivers can not be controlled from sysfs, 
        ## unless a driver explicitly exported that particular pins GPIO.
 
        # Open GPIO export & unexport files
        exportFile = open(GPIO_PATH+'/export', 'w')
        unexportFile = open(GPIO_PATH+'/unexport', 'w')
 
        # Unexport GPIO if it exists and GPIO_RESET is enabled
        exportExists = os.path.isdir(GPIO_PATH+'/gpio'+GPIO_CHAN_NUM)
        if exportExists and GPIO_RESET:
            unexportFile.write(GPIO_CHAN_NUM)
            unexportFile.flush()
 
        # Export GPIO
        if not exportExists or GPIO_RESET:
            exportFile.write(GPIO_CHAN_NUM)
            exportFile.flush()
 
        # Open GPIO direction file to set direction
        directionFile = open(GPIO_PATH+'/gpio'+GPIO_CHAN_NUM+'/direction','w')
 
        # Set GPIO direction to "out"
        directionFile.write(GPIO_DIR_OUT)
        directionFile.flush()
 
        # Open GPIO value file to set value
        valueFile = open(GPIO_PATH+'/gpio'+GPIO_CHAN_NUM+'/value','w')
 
        # Loop indefinitely
        while True:
 
            # Set GPIO value to HI
            valueFile.write(GPIO_VAL_HI)
            valueFile.flush()
 
            # Sleep for blink on duration
            time.sleep(BLINK_PERIOD*BLINK_DUTY/1000.0)
 
            # Set GPIO value to LO
            valueFile.write(GPIO_VAL_LO)
            valueFile.flush()
 
            # Sleep for blink off duration
            time.sleep(BLINK_PERIOD*(1.0-BLINK_DUTY)/1000.0)
 
    except exception:
        exception.printStackTrace()
 
    return
 
if __name__ == "__main__":
    main()

RTC - Read Time

Note: The modules fcntl and glob are provided by the packages 'python-fcntl' and 'python-shell' respectively.

#!/usr/bin/env python
 
import fcntl, struct, glob
 
RTC_RD_TIME=0x80247009
 
# Identify RTCs
rtcList = glob.glob('/dev/rtc[0-9]')
print "RTCs: ", rtcList, "\n"
 
# Read each RTC
for rtc in rtcList
    #  struct rtc_time {
    #    int tm_sec;
    #    int tm_min;
    #    int tm_hour;
    #    int tm_mday;
    #    int tm_mon;
    #    int tm_year;
    #    int tm_wday;
    #    int tm_yday;
    #     int tm_isdst;
    #  };
    a=struct.pack('iiiiiiiii', 0,0,0,0,0,0,0,0,0)
 
    fo=open(rtc)
 
    input=fcntl.ioctl(fo.fileno(), RTC_RD_TIME, a)
    result=struct.unpack('iiiiiiiii', input)
 
    print rtc + ": " + str(1900+result[5]) + "-" + str(1+result[4]) + "-" + str(result[3]) + " " + \
          str(result[2]) + ":" + str(result[1]) + ":" + str(result[0]) + " UTC"