Overview
The Siemens SIMATIC IoT2020 (part of the SIMATIC IOT2000 family) ships with a Yocto Linux image that bundles Python 2.7 for gateway scripting, but pip and most pure-Python third-party packages are not pre-installed in the root filesystem. When developers try to bootstrap pip with the upstream get-pip.py script, the installer crashes with ImportError: No module named getpass because the image's Python build was stripped of the getpass module — a dependency of pip itself. The same missing module then blocks any further pip install of pymodbus, flask, or requests.
This article documents the exact root cause of the failure, the verified recovery procedure (restoring getpass.py from the CPython 2.7.3 source tree), the correct sequence to bootstrap pip, and the field-tested steps to install pymodbus for Modbus TCP and Modbus RTU communication with Siemens S7-1200 / S7-1500 controllers and third-party Modbus devices.
Affected Hardware and Image Versions
| Device | Order Number (MLFB) | CPU | Stock Image | Python | Affected? |
|---|---|---|---|---|---|
| SIMATIC IoT2020 | 6ES7647-0AA00-1YA2 | Intel Quark x1020 (x86) | iot2000-example-image v2.1.3 / v2.1.4 | 2.7.3 (stripped) | Yes |
| SIMATIC IoT2040 | 6ES7647-0KA00-1YA2 | Intel Quark x1020 (x86) | iot2000-example-image v2.1.3 / v2.1.4 | 2.7.3 (stripped) | Yes |
| SIMATIC IoT2050 (Basic) | 6ES7647-0BA00-1YA2 | ARM Cortex-A53 | Example Image V1.2.0+ (Debian-based) | 3.5 / 3.7 | No |
| SIMATIC IoT2050 (Advanced) | 6ES7647-0BA01-1YA2 | ARM Cortex-A53 | Example Image V1.2.0+ (Debian-based) | 3.5 / 3.7 | No |
The getpass defect is specific to the legacy x86 iot2000-example-image built with Poky/Yocto and the python-2.7.3 recipe. The newer ARM-based IoT2050 ships a complete Debian rootfs and is not affected.
cat /etc/version and python --version on the gateway console before applying the procedure below. The fix is only required on the legacy v2.1.x example image.Prerequisites
- SIMATIC IoT2020 / IoT2040 with the example image booted from microSD.
- Serial console (115200 8N1 on
/dev/ttyS0) or SSH access (root, default password not set on the example image — first-boot autologin). - The gateway connected to a network with outbound HTTPS to
pypi.organdfiles.pythonhosted.org. - ~80 MB free space on the rootfs or a USB stick mounted at
/mnt/usb. - Internet access on your engineering workstation to fetch the upstream
getpass.pysource from the CPython 2.7.3 reference tree.
Root Cause: Why get-pip.py Fails with No module named getpass
The get-pip.py bootstrap script first runs pip.zip from a temporary directory. To confirm Python is functional, the bundled pip package imports pip.vcs.mercurial, which transitively imports pip.download, which in turn imports getpass:
Traceback (most recent call last):
File "get-pip.py", line 20061, in <module>
main()
File "get-pip.py", line 194, in main
bootstrap(tmpdir=tmpdir)
File "get-pip.py", line 82, in bootstrap
import pip
File "/tmp/tmpSfzewL/pip.zip/pip/__init__.py", line 28, in <module>
File "/tmp/tmpSfzewL/pip.zip/pip/vcs/mercurial.py", line 9, in <module>
File "/tmp/tmpSfzewL/pip.zip/pip/download.py", line 5, in <module>
ImportError: No module named getpass
The Yocto recipe for the example image used the python-2.7.3 package and stripped non-essential modules to fit the 8 MB internal flash. getpass, which depends on the optional termios extension, was excluded. pip 1.5+ requires getpass.getpass() for interactive credential prompts and silently fails if the module is missing.
getpass defect will break any tool that calls getpass.getpass() interactively, including easy_install when a PyPI index prompts for credentials. Restoration of getpass is therefore a prerequisite for every other pip / setuptools workflow on the legacy image.Step 1 — Restore getpass.py to the Python Standard Library
- From your engineering workstation, open getpass.py on the Enthought CPython 2.7.3 mirror and save the file as
getpass.py(UTF-8, LF line endings). - Transfer the file to the gateway. With SSH enabled:
scp getpass.py [email protected]:/tmp/getpass.py - On the gateway console, move the file into the standard library directory used by the example image:
mv /tmp/getpass.py /usr/lib/python2.7/getpass.py chmod 644 /usr/lib/python2.7/getpass.py chown root:root /usr/lib/python2.7/getpass.py - Verify the module loads:
python -c "import getpass; print getpass.getuser()" rootIf
rootis returned, the module is correctly registered and the bootstrap path is unblocked.
Step 2 — Bootstrap pip on the IoT2020
With getpass restored, either the upstream bootstrap script or the pre-shipped easy_install will work. The easy_install route is preferred on the legacy image because it avoids the SSL/TLS version mismatch that the example image's openssl 0.9.8 has with PyPI's TLS 1.2 endpoints.
Option A — pip via easy_install (recommended on legacy image)
- Confirm setuptools is present:
easy_install --version setuptools 0.6c11 - Install pip:
easy_install pip - Verify the pip shim resolves against the standard library (not a vendored copy):
which pip pip --version pip 1.5.6 from /usr/lib/python2.7/site-packages/pip (python 2.7)
Option B — pip via get-pip.py (Debian-style images)
On the IoT2050 or any image with full Python 3 and a current OpenSSL, the Python Packaging User Guide procedure works directly:
- Download the bootstrap script:
curl -sS https://bootstrap.pypa.io/pip/3.5/get-pip.py -o /tmp/get-pip.py - Run it as the unprivileged
iotuser:python3 /tmp/get-pip.py --user - Confirm:
~/.local/bin/pip --version pip 23.x from /home/iot/.local/lib/python3.5/site-packages/pip (python 3.5)
pip as root on the IoT2050 — the example image ships an unprivileged iot account and uses a read-only rootfs with an overlay. Use --user installs and add ~/.local/bin to PATH.Step 3 — Install pymodbus
pymodbus is a pure-Python implementation of the Modbus protocol maintained by the pymodbus-dev project. It supports Modbus TCP (port 502), Modbus RTU over serial, and Modbus ASCII. Install the release that matches the image's Python version:
| Image / Python | Recommended pymodbus | Install Command |
|---|---|---|
| IoT2020 / IoT2040 — Python 2.7.3 | pymodbus 1.5.2 (last 2.x release) | pip install pymodbus==1.5.2 |
| IoT2050 — Python 3.5 / 3.7 | pymodbus 3.5.x | pip3 install --user pymodbus |
| Any — serial RTU | Add pyserial for /dev/ttyS0 |
pip install pyserial |
- Install pymodbus (and pyserial for RTU):
pip install pymodbus==1.5.2 pyserial - Confirm installation:
python -c "import pymodbus; print pymodbus.__version__" 1.5.2 - Confirm pyserial is wired to the on-board UART:
python -c "import serial; ser = serial.Serial('/dev/ttyS0', 9600, 8, 'E', 1); print ser.name" /dev/ttyS0
/dev/ttyS0. The example image enables it by default. If you have not installed the shield, serial.Serial will still open the port but no electrical signals will reach the field device — verify the shield is seated and the termios line discipline is loaded.Step 4 — Verify the Installation
Run the four checks below on the gateway console. All four must pass before you move to application code.
-
Python and pip resolution
python --version && pip --version Python 2.7.3 pip 1.5.6 from /usr/lib/python2.7/site-packages/pip (python 2.7) -
getpass restored
python -c "import getpass; print getpass.getuser()" root -
pymodbus import
python -c "from pymodbus.client.sync import ModbusTcpClient; print 'tcp ok'" tcp ok -
Network reachability to PLC
ping -c 2 192.168.200.10Replace the address with the S7-1200 PROFINET interface IP. The default TIA Portal project assigns
192.168.0.1; on the Siemens reference LoRaWAN project that ships with the example image, the PLC is at192.168.200.10.
Using pymodbus — Modbus TCP Client Example
The script below polls four holding registers (address 0–3) from a Siemens S7-1200 configured as a Modbus TCP server. The PLC must have the MB_SERVER instruction (S7-1200 firmware ≥ V4.0) or the Modbus TCP library compiled into the user program. Save the file as /root/modbus_tcp_poll.py and run it with python modbus_tcp_poll.py.
#!/usr/bin/env python
# Modbus TCP poll against a Siemens S7-1200 / S7-1500
from pymodbus.client.sync import ModbusTcpClient
from pymodbus.exceptions import ConnectionException
import time
PLC_IP = "192.168.200.10"
PLC_PORT = 502
UNIT_ID = 1 # MB_UNIT for S7-1200, default 1
client = ModbusTcpClient(PLC_IP, port=PLC_PORT, timeout=3)
if not client.connect():
raise SystemExit("Cannot reach PLC at %s:%d" % (PLC_IP, PLC_PORT))
try:
while True:
rr = client.read_holding_registers(address=0, count=4, unit=UNIT_ID)
if rr.isError():
print "Modbus error:", rr
else:
print "Holding [0..3] =", rr.registers
time.sleep(1.0)
finally:
client.close()
Expected output on a healthy link:
Holding [0..3] = [0, 0, 0, 0]
Holding [0..3] = [10, 20, 30, 40]
Using pymodbus — Modbus RTU over /dev/ttyS0
For Modbus RTU on the Arduino shield's RS485 transceiver, use the synchronous serial client. The shield's half-duplex direction control is on D3; pymodbus drives it automatically when serial.Serial opens /dev/ttyS0.
#!/usr/bin/env python
# Modbus RTU master over IoT2020 Arduino shield RS485
from pymodbus.client.sync import ModbusSerialClient
client = ModbusSerialClient(
method='rtu',
port='/dev/ttyS0',
baudrate=19200,
bytesize=8,
parity='E',
stopbits=1,
timeout=1
)
if not client.connect():
raise SystemExit("Cannot open /dev/ttyS0")
rr = client.read_input_registers(address=0, count=8, unit=1)
if rr.isError():
print "RTU error:", rr
else:
print "Input [0..7] =", rr.registers
client.close()
| Parameter | Value | Notes |
|---|---|---|
| port | /dev/ttyS0 | IoT2020 / IoT2040 UART0 routed through Arduino shield |
| baudrate | 9600 / 19200 | Match the slave device; S7-1200 CP 1242-7 default 19200 |
| parity | 'E' (even) | Most Siemens Modbus slaves default to 8E1 |
| stopbits | 1 | Required for Modbus RTU |
| timeout | 1.0 s | Raise to 3 s for 9600 baud over long runs |
Connecting to a Siemens S7-1200 / S7-1500
The S7-1200 became a Modbus TCP server natively starting with firmware V4.0 (2014). The configuration required in TIA Portal:
- Add the MODBUS TCP instruction library to the user program (Instructions → Communication → MODBUS TCP). The relevant blocks are
MB_SERVERfor TCP and theMB_CLIENTfor outbound polling — but the IoT2020 acts as the client, so only the server side must be configured. - Set the
CONNECTparameter to the PLC's IP and port 502. - Bind the data block
MB_HOLD_REGto a DB of at least 4 words; pymodbus readsHolding[0..3]in the example above. - Set the access point
MB_UNITparameter to1(default). - Compile and download. The PLC's PROFINET interface accepts inbound TCP/502 connections with no firewall rule on the CPU; if a CP is used (e.g., CP 1243-7 LTE), verify the security settings in the CP's WebUI allow inbound TCP/502 from the IoT2020 IP.
Troubleshooting Matrix
| Symptom | Root Cause | Fix |
|---|---|---|
ImportError: No module named getpass during get-pip.py
|
getpass stripped from python-2.7.3 image | Copy getpass.py from CPython 2.7.3 to /usr/lib/python2.7/
|
ImportError: No module named termios after restoring getpass |
termios extension also stripped | Recompile Python with INTERNAL_PYTHON=python2.7-dev in Yocto or upgrade to the IoT2050 image |
pip: command not found after easy_install pip
|
/usr/bin not in PATH for non-root shells |
export PATH=$PATH:/usr/bin in /etc/profile or use absolute path /usr/bin/pip
|
ssl.SSLError: [Errno 1] _ssl.c:504: error:1407742E when pip contacts PyPI |
openssl 0.9.8 in legacy image lacks TLS 1.2 | Use easy_install which uses HTTP/1.0, or set --index-url=http://pypi.python.org/simple
|
pymodbus install succeeds but import pymodbus raises ImportError: No module named serial
|
pyserial not installed | pip install pyserial |
Modbus TCP ConnectionException
|
PLC firewall or wrong port | Verify nc -zv 192.168.200.10 502; check S7-1200 CP firewall rules |
Modbus RTU NoResponseReceived
|
Baud / parity mismatch or RS485 direction pin | Match PLC MB_CONFIG settings; verify Arduino shield D3 is jumpered to DE/RE |
OSError: [Errno 13] Permission denied: '/dev/ttyS0' |
User not in dialout group |
usermod -aG dialout iot on IoT2050; or run as root on legacy image |
Operational Tips and Best Practices
-
Pin the package version. The legacy image's pip 1.5 will resolve the latest pymodbus, which dropped Python 2 support after 1.5.2. Pin with
pip install pymodbus==1.5.2on IoT2020/2040. -
Use a virtualenv if space allows.
easy_install virtualenv && virtualenv /opt/venvisolates third-party packages from the read-only/usr. -
Snapshot the SD card. After a clean install of
pip,pymodbus,pyserial, and your application,dd if=/dev/mmcblk0 of=/mnt/usb/iot2020-good.imgto capture a known-good image. -
Migrate to the IoT2050 for new projects. Debian-based images, Python 3, and a maintained
pip23+ eliminate the entiregetpassworkaround and give youpymodbus3.5+ with async support.
Why does get-pip.py fail with No module named getpass on the IoT2020?
The legacy iot2000-example-image v2.1.x ships a stripped Python 2.7.3 built by Yocto that omits non-essential modules. pip imports getpass during its own bootstrap to handle credential prompts, and the import fails before any package is installed. Restore getpass.py from the CPython 2.7.3 reference into /usr/lib/python2.7/ to unblock the bootstrap.
Can I install pymodbus with pip on the SIMATIC IoT2020?
Yes. After restoring getpass and bootstrapping pip (typically via easy_install pip), run pip install pymodbus==1.5.2 pyserial. Pin to 1.5.2 because later releases of pymodbus require Python 3. For Modbus RTU, add pyserial so the on-board /dev/ttyS0 UART works.
Which Python version is on the IoT2020 vs the IoT2050?
The IoT2020 and IoT2040 example images ship Python 2.7.3. The IoT2050 example image (V1.2.0 and later) is Debian-based and ships Python 3.5 (basic) or 3.7 (advanced). The getpass workaround is only required on the legacy v2.1.x x86 image; the IoT2050 can use the standard pip bootstrap directly.
How do I connect pymodbus to a Siemens S7-1200?
Configure MB_SERVER in the S7-1200 user program (TIA Portal → Instructions → Communication → Modbus TCP). Set the CONNECT IP to the PLC's PROFINET address, port 502, and unit ID 1. From the IoT2020 use ModbusTcpClient(PLC_IP, port=502) with read_holding_registers(address=0, count=4, unit=1). The S7-1200 requires firmware V4.0 or later to act as a Modbus TCP server.
What UART should I use for Modbus RTU on the IoT2020?
Use /dev/ttyS0 (UART0) with the Siemens-approved Arduino RS232/485 shield. Configure 8E1 at 9600 or 19200 baud to match the slave device. The shield's DE/RE direction pin is on D3 and is driven automatically by pymodbus once serial.Serial('/dev/ttyS0', ...) opens the port.
Why does pip still fail after I copied getpass.py?
The most common cause on the legacy image is that the underlying termios extension is also stripped, and getpass itself imports termios on Unix. Recompiling Python against the Yocto SDK, or upgrading to the IoT2050 Debian image, are the two long-term fixes. As a workaround, run the bootstrap with stty rows 0 cols 0 first to force a non-tty environment, or use easy_install pip which does not load the full getpass path.