Resolving mraa and pymongo Library Conflicts on SIMATIC IOT2020

David Krause11 min read
PLC HardwareSiemensTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Resolving mraa and pymongo Library Conflicts on SIMATIC IOT2020

Problem Overview

The SIMATIC IOT2020 ships with an Intel Quark x86 SoC and a Yocto Linux image that exposes a Python 2.7 runtime alongside a Python 3.5 runtime. The stock example image bundles the mraa C/C++ GPIO library compiled only against Python 2. When an engineer tries to use modern Python 3 packages such as pymongo, the dependency manager (pip) refuses to install them on the Python 2 interpreter, while mraa simultaneously refuses to import on Python 3 because the mraa C-extension shared object was not built with the Python 3 ABI.

The net symptom is a deadlock:

  • import mraa works on /usr/bin/python2 only.
  • pip install pymongo succeeds on /usr/bin/python3 only.
  • The result is two scripts that cannot share process space and two data paths that cannot share libraries.

This reference documents four field-proven resolutions: bootstrapping pip2 manually, repairing the importlib dependency that breaks pymongo under Python 2, rebuilding the IOT2020 example image with mraa compiled for Python 3, and replacing pymongo/mraa with native alternatives.

Safety notice. The IOT2020 is a developer gateway, not a safety controller. GPIO lines directly drive logic-level loads. Always isolate inductive loads with opto-couplers or flyback diodes, and never connect 24 V field wiring directly to the Arduino shield headers.

IOT2020 Hardware and Software Stack

The IOT2020 (6ES7647-0AA00-1YA2) uses an Intel Quark x1000 SoC at 400 MHz with 1 GB DDR3 RAM, 8 GB eMMC, an SD slot, and an Arduino-compatible shield interface exposing 20 digital GPIO, 6 analog inputs, PWM, I2C, SPI, and UART. The reference image is a Yocto-based Poky distribution (kernel 4.x, systemd init) and ships with two Python interpreters:

Component Path ABI Version Purpose
Python 2.7 /usr/bin/python2 API 1013 mraa, legacy OPC UA bindings
Python 3.5 /usr/bin/python3 API 3500 Modern pip wheel resolver
mraa /usr/lib/python2.7/site-packages/mraa.so Python 2 GPIO / I2C / SPI
pip /usr/bin/pip3 only — Python 3 package manager

The default image does not install pip2. The Siemens meta-iot2000 BSP provides the build configuration used to regenerate the example image, including recipe overrides for python-pip, mraa, and python-pymongo.

mraa Library Architecture and Pin Mapping

mraa is the Eclipse-maintained low-level skeleton library that wraps sysfs, libmraa is the C core, and the language bindings are SWIG-generated. On the IOT2020, mraa is built against libmraa which in turn talks to the Quark GPIO driver through the SCSS (Symmetric Cipher Sub-System) register file at /sys/class/gpio.

The Arduino shield header maps to mraa logical pin numbers as follows:

Arduino Pin mraa Pin Sysfs GPIO Direction
D0 14 34 UART RX (reserved)
D1 15 33 UART TX (reserved)
D2 2 17 Bidirectional
D3 3 16 PWM capable
D4 4 19 Bidirectional
D5 5 20 PWM capable
D6 6 21 PWM capable
D7 7 22 Bidirectional
D8 8 23 Bidirectional
D9 9 24 PWM capable
D10 10 25 PWM/SS
D11 11 26 PWM/MOSI
D12 12 27 MISO
D13 13 28 SCK/LED
A0 16 — Analog input
A1 17 — Analog input
A2 18 — Analog input
A3 19 — Analog input
A4 20 — I2C SDA
A5 21 — I2C SCL

The mapping is documented in the mraa IOT2000 platform notes and must be referenced directly when porting Arduino sketches to Python, because the Quark SoC GPIO numbering does not match the Arduino header.

Root Cause: Why mraa Fails on Python 3

mraa produces a C-extension shared library (mraa.so) using SWIG. The Python C API is binary-incompatible across major versions: a module compiled for Python 2 cannot be loaded by the Python 3 interpreter. The IOT2020 example image at the time of writing builds mraa only against Python 2 because the Poky recipe mraa_%.bbappend in meta-iot2000 lacks a --with-python3 flag.

The diagnostic error chain on Python 3 is:

>>> import mraa
Traceback (most recent call last):
  File "", line 1, in 
ImportError: dynamic module does not define module export function (PyInit_mraa)

This error is unambiguous: the linker is loading a mraa.so that exports initmraa (Python 2 symbol) instead of PyInit_mraa (Python 3 symbol). The fix is to rebuild the module.

Root Cause: Why pymongo Fails on Python 2

Modern pymongo wheels (4.x) require Python 3.6+ and depend on importlib_metadata, which uses the importlib module API introduced in Python 3.8. When the resolver pulls importlib_metadata onto Python 2.7 it fails with ImportError: No module named importlib even though importlib is in the standard library because the import path is incorrect.

The stock IOT2020 image ships importlib at /usr/lib/python2.7/importlib, but the missing setuptools bootstrap on Python 2 prevents pip from finding it. The error chain is typically:

# pip install pymongo
Collecting importlib
  Could not find a version that satisfies the requirement importlib (from versions: )
No matching distribution found for importlib

This is a misleading error: importlib is built in, but older setuptools treats it as a third-party package because the importlib bootstrap package was renamed to importlib_metadata in the setuptools metadata index.

Diagnostic Procedure

Run the following sequence on the IOT2020 over SSH or serial console to capture the full system state:

  1. Confirm the firmware and image version:
    cat /etc/version
    uname -a
    ls /usr/lib/python2.7/site-packages/mraa.so
    ls /usr/lib/python3.5/site-packages/mraa.so 2>&1
  2. Confirm Python interpreters:
    /usr/bin/python2 --version
    /usr/bin/python3 --version
    which pip pip2 pip3
  3. Confirm mraa import on each interpreter:
    /usr/bin/python2 -c "import mraa; print(mraa.getVersion())"
    /usr/bin/python3 -c "import mraa; print(mraa.getVersion())"
  4. Confirm pymongo install:
    /usr/bin/python2 -c "import pymongo; print(pymongo.version)"
    /usr/bin/python3 -c "import pymongo; print(pymongo.version)"
  5. Capture sys.path for both interpreters:
    /usr/bin/python2 -c "import sys; print('\n'.join(sys.path))"
    /usr/bin/python3 -c "import sys; print('\n'.join(sys.path))"

Record the output. If mraa only resolves on Python 2 and pymongo only resolves on Python 3, proceed with the resolution paths below.

Solution Path A: Bootstrap pip2 Manually

The stock IOT2020 image does not include pip2. Bootstrap it with get-pip.py:

  1. Download the official bootstrap script: wget https://bootstrap.pypa.io/pip/2.7/get-pip.py -O /tmp/get-pip.py
  2. Install for Python 2 only: /usr/bin/python2 /tmp/get-pip.py --no-setuptools
  3. Verify:
    which pip2
    pip2 --version
The --no-setuptools flag prevents get-pip.py from overriding the Poky-provided setuptools package, which can break other Python 2 modules shipped by the BSP.

Solution Path B: Repair pymongo on Python 2

Pin pymongo to a release that still supports Python 2.7 (3.x line, last release 3.13.0). Newer 4.x wheels exclude Python 2 entirely.

  1. Install a Python 2-compatible pymongo wheel: pip2 install "pymongo<4" --index-url https://pypi.org/simple
  2. If the resolver still complains about importlib, force the standard-library bootstrap: /usr/bin/python2 -c "import importlib; print(importlib.__file__)" Confirm the path resolves to /usr/lib/python2.7/importlib. If it does not, ensure /usr/lib/python2.7 is in sys.path.
  3. Alternative — install importlib_metadata from source: pip2 install "importlib_metadata<3" The 3.x line of importlib_metadata dropped Python 2 support.
  4. Verify: /usr/bin/python2 -c "import pymongo, importlib_metadata; print(pymongo.version, importlib_metadata.__version__)"

Other Python 2-compatible packages that commonly fail on the IOT2020:

Package Python 2 Last Good Notes
requests 2.27.1 Drop urllib3<1.26
numpy 1.16.6 Last wheel supporting Python 2.7
pymongo 3.13.0 Dropped Python 2 in 4.0
flask 1.1.2 Dropped Python 2 in 2.0
pyzmq 19.0.2 Requires libzmq 4.x
cryptography 3.3.2 Dropped Python 2 in 3.4

Solution Path C: Rebuild mraa for Python 3 via Yocto Image

If the application architecture requires Python 3 (strongly recommended for any new development, since Python 2.7 reached end-of-life on January 1, 2020), rebuild the IOT2020 image with mraa compiled against Python 3. The Siemens BSP is the authoritative source.

  1. Clone the BSP and Poky on a Linux build host (Ubuntu 18.04 LTS recommended):
    mkdir iot2000-build && cd iot2000-build
    git clone -b kirkstone https://github.com/siemens/meta-iot2000.git
    git clone -b kirkstone https://git.yoctoproject.org/git/poky
    git clone -b kirkstone https://github.com/openembedded/meta-openembedded.git
  2. Initialize the build environment: source poky/oe-init-build-env build-iot2000
  3. Append the IOT2020 BSP layer:
    bitbake-layers add-layer ../meta-iot2000
    bitbake-layers add-layer ../meta-openembedded/meta-oe
    bitbake-layers add-layer ../meta-openembedded/meta-python
  4. Enable Python 3 bindings for mraa. Create meta-iot2000/recipes-devtools/mraa/mraa_%.bbappend with:
    EXTRA_OECONF_append = " --with-python3 --without-python2"
    RDEPENDS_${PN}-python = "python3-core"
    FILES_${PN}-python = "${libdir}/python3*/site-packages/*"
  5. Build the example image: MACHINE=iot2020 bitbake iot2000-example-image
  6. Flash the resulting tmp/deploy/images/iot2020/iot2000-example-image-iot2020.wic to an SD card with dd or bmaptool.
  7. Boot the IOT2020 from the SD card and verify: /usr/bin/python3 -c "import mraa; print(mraa.getVersion())"

The full Yocto build takes 90–120 minutes on a workstation with 8 cores and 16 GB of RAM. Incremental rebuilds of the mraa package alone take 2–4 minutes.

Build prerequisites: at least 60 GB of free disk space, 8 GB RAM, and the standard Yocto host packages (build-essential, chrpath, cpio, diffstat, gawk, gcc-multilib, git, libsdl1.2-dev, texinfo, unzip, wget, xterm).

Solution Path D: Replace mraa with Native sysfs Access

If rebuilding the image is impractical, drive the GPIOs through /sys/class/gpio from Python 3 without mraa. This works on every Linux kernel with the GPIO sysfs interface (deprecated in 5.x but present on the IOT2020 4.x kernel):

import os, time

GPIO_BASE = "/sys/class/gpio"
PIN = 13   # Arduino D13, Quark GPIO 28

def export(pin):
    path = f"{GPIO_BASE}/gpio{pin}"
    if not os.path.exists(path):
        with open(f"{GPIO_BASE}/export", "w") as f:
            f.write(str(pin))
        time.sleep(0.2)

def direction(pin, d):
    with open(f"{GPIO_BASE}/gpio{pin}/direction", "w") as f:
        f.write(d)

def write(pin, v):
    with open(f"{GPIO_BASE}/gpio{pin}/value", "w") as f:
        f.write("1" if v else "0")

export(PIN)
direction(PIN, "out")
for _ in range(10):
    write(PIN, 1)
    time.sleep(0.5)
    write(PIN, 0)
    time.sleep(0.5)

This approach avoids the mraa ABI issue entirely but loses the convenience of I2C/SPI helpers, edge detection, and PWM. For I2C and SPI on Python 3 without mraa, use smbus2 and spidev respectively:

pip3 install smbus2 spidev
python3 -c "import smbus2, spidev; b=smbus2.SMBus(0); print(b)"

Solution Path E: Replace pymongo with an HTTP-Based Ingestion

If the application only writes sensor data to MongoDB, replace the driver with a JSON POST to an external HTTP endpoint or use curl through subprocess. This avoids the setuptools dependency entirely:

import json, subprocess

def mongo_push(doc, host="mongo.local", port=27017, db="iot"):
    payload = json.dumps({"insert": doc})
    cmd = ["curl", "-X", "POST", f"http://{host}:{port}/insert",
           "-H", "Content-Type: application/json", "-d", payload]
    subprocess.run(cmd, check=True, timeout=5)

Pair this with mongo-httpd on the MongoDB host, or use MongoDB Realm for HTTPS-only ingestion.

Verification Procedure

After applying any of the resolution paths, run the following end-to-end test on the IOT2020 to confirm both mraa and pymongo are usable in the same Python interpreter:

  1. Confirm interpreter version:
python3 --version
# Expect: Python 3.5.x or later
  1. Confirm both imports succeed:
python3 -c "import mraa, pymongo; print('mraa', mraa.getVersion()); print('pymongo', pymongo.version)"
  1. Run a GPIO loopback test. Connect a jumper between D2 (input) and D3 (output), then run:
python3 << 'EOF'
import mraa, time
out = mraa.Gpio(3); out.dir(mraa.DIR_OUT)
inp = mraa.Gpio(2); inp.dir(mraa.DIR_IN)
fails = 0
for i in range(100):
    out.write(1 if i & 1 else 0)
    time.sleep(0.01)
    if inp.read() != (i & 1): fails += 1
print(f"loopback failures: {fails}/100")
EOF
  1. Run a MongoDB round-trip. Substitute <host> with the MongoDB server IP and <user>/<pwd> with credentials:
python3 << 'EOF'
import pymongo, datetime
client = pymongo.MongoClient("mongodb://<user>:<pwd>@<host>:27017/",
                            serverSelectionTimeoutMS=2000)
db = client.iot_test
res = db.ping.insert_one({"ts": datetime.datetime.utcnow(), "ok": True})
print("inserted id:", res.inserted_id)
print("server version:", client.server_info()["version"])
EOF

A passing test prints loopback failures: 0/100 and a valid BSON ObjectId.

Troubleshooting Matrix

Symptom Likely Cause Diagnostic Resolution
ImportError: dynamic module does not define module export function (PyInit_mraa) mraa built for Python 2 only file /usr/lib/python3*/site-packages/mraa.so Rebuild image with Solution Path C or use sysfs fallback (Path D)
No matching distribution found for importlib setuptools metadata index treats importlib as external pip2 install --dry-run pymongo Pin pymongo<4 and importlib_metadata<3 (Path B)
pip: command not found pip2 not installed on stock image which pip pip2 pip3 Bootstrap pip2 with get-pip.py (Path A)
SSL: CERTIFICATE_VERIFY_FAILED on pip install Expired ca-certificates on Poky image openssl s_client -connect pypi.org:443 opkg update && opkg install ca-certificates
mraa.Error: Invalid pin Pin number does not match IOT2020 header mraa.printError(mraa.getLastError()) Consult the pin mapping table above
ServerSelectionTimeoutError: No servers found MongoDB not reachable on port 27017 nc -zv <host> 27017 Open firewall, verify bind IP, enable replica set name
GPIO read always returns 0 Pin not exported ls /sys/class/gpio/ Echo pin number to /sys/class/gpio/export
mraa: symbol lookup error after upgrade libmraa ABI mismatch with python binding ldd /usr/lib/python*/site-packages/mraa.so Rebuild mraa package fully, do not mix library versions

Recommendations and Long-Term Strategy

For new IOT2020 development, standardize on Python 3 and rebuild the image with mraa Python 3 bindings (Path C). This eliminates the dual-interpreter maintenance burden and aligns with the broader Python ecosystem, where Python 2 has been unsupported since January 2020. Pin all third-party packages to the last release that supports the target Python interpreter version, and keep a local PyPI mirror (e.g. devpi) to insulate production systems from upstream package removals.

For brownfield deployments that must remain on the stock image, Path A plus Path B provides a workable bridge: pin pymongo to 3.13.0 and importlib_metadata to 2.x, leave mraa on Python 2, and run inter-process communication between the two interpreters via TCP sockets or local files. The performance overhead is negligible compared to the GPIO event rate.

Which mraa version ships with the IOT2020 example image?

The reference image bundles mraa 1.x compiled against Python 2.7 only. Verify with /usr/bin/python2 -c "import mraa; print(mraa.getVersion())". The mraa project moved to libmraa 2.x with separate Python 2 and Python 3 wheels in later releases.

Can I install both pip2 and pip3 without breaking the Yocto packages?

Yes. Bootstrap pip2 with get-pip.py --no-setuptools as shown in Path A. Avoid pip2 install --upgrade setuptools because it overwrites the Poky-bundled setuptools and can break other Python 2 modules installed by the BSP recipes.

What is the last pymongo release that supports Python 2.7?

pymongo 3.13.0 is the final release with Python 2.7 wheels. pymongo 4.0 (released 2021) dropped Python 2 support entirely. Pin with pip2 install "pymongo<4" and use importlib_metadata<3 to satisfy the resolver.

How long does a full IOT2020 Yocto image rebuild take?

A clean build of the iot2000-example-image on an 8-core / 16 GB workstation takes 90–120 minutes. Incremental rebuilds of the mraa package after editing the bbappend take 2–4 minutes, and full re-link of the image takes 20–30 minutes.

Can I access the IOT2020 GPIOs without mraa at all?

Yes. Drive the sysfs interface at /sys/class/gpio directly from Python 3 as shown in Solution Path D. For I2C use smbus2, for SPI use spidev, and for UART use pyserial. This avoids the mraa ABI problem entirely but loses the cross-platform abstraction layer.

Back to blog