Python in Linux
Introductionβ
Python is a widely used interpreted language. Despite the performance gap when compared to a compiled solution, it is still suitable for some applications.
This article focuses on the BSP Layers and Reference Images for Yocto Project. If you want to learn how to use Python on Torizon, read the article Application Development - Python Example.
Python Setupβ
While Building a Linux Imageβ
Python might be already added in certain versions of our Reference Multimedia Image: BSP Layers and Reference Images for Yocto Project Software. You can quickly check by running the python
command on the command line.
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:
IMAGE_INSTALL_append = "python"
You may also want to install other packages as python-pip
and dependencies to the Python packages you plan to use.
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β
- Go to the Python Package Index and find the package you are after. In our example TinyDB.
- Find the downloads section, copy the download link and download the package to your PC. In our case, tinydb downloads section.
- Find the license type. In our case is MIT.
- Generate the md5 and sha256 checksums for the compressed package. In our case md5:e28a5650ef8796ab6b7892591edfbae8 and sha256:071105c339b44f928968cb4e62f7340a568beb9962a3dcda66eb82da9cab0b0a
- 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.
- Generate the md5sum for the license file. For the .tar.gz compressed release of TinyDB 3.7.0, it is 8e3a385933697d0374af36db8ee0319d.
- 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:
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:
# 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
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 = ""
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:
IMAGE_INSTALL_append = "python-tinydb"
# or
IMAGE_INSTALL_append = "python3-tinydb"
Performance and 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
import traceback
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:
print(traceback.format_exc())
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"