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 mraaworks on/usr/bin/python2only. -
pip install pymongosucceeds on/usr/bin/python3only. - 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.
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:
- 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 - Confirm Python interpreters:
/usr/bin/python2 --version /usr/bin/python3 --version which pip pip2 pip3 - Confirm mraa import on each interpreter:
/usr/bin/python2 -c "import mraa; print(mraa.getVersion())" /usr/bin/python3 -c "import mraa; print(mraa.getVersion())" - Confirm pymongo install:
/usr/bin/python2 -c "import pymongo; print(pymongo.version)" /usr/bin/python3 -c "import pymongo; print(pymongo.version)" - 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:
- Download the official bootstrap script:
wget https://bootstrap.pypa.io/pip/2.7/get-pip.py -O /tmp/get-pip.py - Install for Python 2 only:
/usr/bin/python2 /tmp/get-pip.py --no-setuptools - Verify:
which pip2 pip2 --version
--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.
- Install a Python 2-compatible pymongo wheel:
pip2 install "pymongo<4" --index-url https://pypi.org/simple - 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.7is insys.path. - Alternative — install
importlib_metadatafrom source:pip2 install "importlib_metadata<3"The 3.x line ofimportlib_metadatadropped Python 2 support. - 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.
- 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 - Initialize the build environment:
source poky/oe-init-build-env build-iot2000 - 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 - Enable Python 3 bindings for mraa. Create
meta-iot2000/recipes-devtools/mraa/mraa_%.bbappendwith:EXTRA_OECONF_append = " --with-python3 --without-python2" RDEPENDS_${PN}-python = "python3-core" FILES_${PN}-python = "${libdir}/python3*/site-packages/*" - Build the example image:
MACHINE=iot2020 bitbake iot2000-example-image - Flash the resulting
tmp/deploy/images/iot2020/iot2000-example-image-iot2020.wicto an SD card withddorbmaptool. - 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-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:
- Confirm interpreter version:
python3 --version
# Expect: Python 3.5.x or later
- Confirm both imports succeed:
python3 -c "import mraa, pymongo; print('mraa', mraa.getVersion()); print('pymongo', pymongo.version)"
- 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
- 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.