Overview of LECPython and Siemens LOGO! Integration
LECPython is a Python wrapper that exposes the S7.Net and Modbus TCP client libraries originally written in C# to Python 3.x environments. The component packages .NET 8 assemblies inside a wheel distribution so that an embedded Common Language Runtime (CLR) host can load the compiled classes directly from a Python process without requiring the developer to author a separate C# project. From a single Python import, an integrator can address Siemens S7-200, S7-200 SMART, S7-300, S7-400, S7-1200, S7-1500, and LOGO! 8 controllers, plus Modbus TCP servers and other vendor families such as Mitsubishi MC Protocol, Omron FINS, and Allen-Bradley CIP. The repository is hosted at github.com/xeden3/LECPython.
Siemens LOGO! is a logic module positioned between a relay and a micro-PLC. Despite its compact footprint, the LOGO! 8 (6ED1052-xxx08-0BA0 family) implements an Ethernet interface that supports two application-layer protocols: Modbus TCP on TCP port 502 and the Siemens S7 communication protocol. The S7 stack exposed by LOGO! is the same code path inherited from the S7-200 series: it uses Transport Service Access Points (TSAPs) inside ISO-on-TCP (RFC 1006) TPDUs to identify the communication endpoint, rather than the slot/rack integer pair used by S7-300, S7-400, S7-1200, and S7-1500 CPUs.
Because S7-200 and S7-200 SMART share the same ISO-on-TCP framing but differ from the slot/rack family in their TSAP encoding, a Python client must be configured with TSAP pairs (Local TSAP / Remote TSAP) when targeting either of those two legacy series. LECPython since release 1.2.3.6 exposes the dual-purpose parameters rack_or_localTSAP and slot_or_destTSAP so the same connection object can speak both encodings. Setting them to slot/rack integers (e.g. 0, 1) addresses an S7-1200/1500; setting them to TSAP byte values (e.g. 0x0100, 0x0300) addresses an S7-200, S7-200 SMART, or LOGO!.
Prerequisites and Runtime Requirements
The integration has been verified on the following baseline. Other platforms may work but were not part of the reference testing matrix.
| Component | Required Version | Notes |
|---|---|---|
| Operating System | Windows 11 Pro (build 22631+) | LECPython loads .NET 8 assemblies via the bundled CLR hosting layer; Windows is required for the Microsoft.NETCore.App runtime to load unmanaged S7.Net binaries. |
| .NET Runtime | .NET 8 Desktop Runtime (8.0.x) | Install dotnet-runtime-8.0.x-win-x64.msi or Microsoft.WindowsDesktop.App.Ref. ASP.NET Core Runtime is not sufficient. |
| Python | 3.12.x (3.12.4 verified) | 3.11 and 3.13 may also work because LECPython does not use 3.12-only syntax; C# interop layer is language-version agnostic. |
| Architecture | x64 | ARM64 Windows is unsupported by the bundled S7.Net binaries. |
| Siemens LOGO! | LOGO! 8 (6ED1052-1xxx08-0BA0), firmware FS04 or later | Earlier LOGO! 6/7 generations do not expose the Ethernet S7 server. |
Installation Steps
- Install .NET 8 Desktop Runtime from the Microsoft .NET download portal.
- Confirm the runtime is reachable:
dotnet --list-runtimes # Expect output: # Microsoft.NETCore.App 8.0.x ... # Microsoft.WindowsDesktop.App 8.0.x ... - Create a Python virtual environment:
python -m venv .venv .venv\Scripts\activate python --version # Python 3.12.x - Install LECPython from the GitHub repository distribution:
Replace the version tag with the latest release if a newer one is published.pip install --upgrade pip pip install git+https://github.com/xeden3/[email protected] - Confirm the import path is intact:
python -c "import lecpython; print(lecpython.__version__)" # Expect: 1.2.3.6 (or newer)
python.exe.
S7 Protocol Stack: LOGO!, S7-200, and S7-1200/1500
All Siemens S7 Ethernet controllers speak ISO-on-TCP (RFC 1006) as the transport layer and the proprietary S7 protocol (often called S7Comm) on top. The user-visible difference between product families is the way the connection is identified inside the ISO-on-TCP payload.
| Controller Family | Connection Identity Mechanism | Default Local TSAP | Default Remote TSAP | Max Parallel Connections |
|---|---|---|---|---|
| S7-200 (CP243-1, CP243-1 IT) | LocalTSAP / RemoteTSAP (byte pair) | 0x0100 | 0x0300 (server) or partner-defined | 8 (CPU-bound) |
| S7-200 SMART (SB CM01, CPU Ethernet) | LocalTSAP / RemoteTSAP (byte pair) | 0x0100 | 0x0300 | 8 active + 1 passive |
| LOGO! 8 | S7-200-style LocalTSAP / RemoteTSAP | 0x0100 | 0x0300 | 8 S7 + 8 Modbus TCP |
| S7-300 / S7-400 (CP343-1, CP443-1) | Rack/Slot integers | Rack × 32 + Slot | Rack × 32 + Slot | 16 (CP-dependent) |
| S7-1200 / S7-1500 | Rack/Slot integers | 0 (rack=0, slot=1) | 0 (rack=0, slot=1) | 16 (CPU-dependent) |
When LECPython receives the parameters rack_or_localTSAP and slot_or_destTSAP, the underlying C# wrapper inspects the PLC type the developer passes to the constructor. If the PLC type is S7200 or S7200Smart, the integers are interpreted as TSAP bytes. If the PLC type is any other S7 model (S7300, S7400, S71200, S71500), they are interpreted as rack/slot. The same numeric 0, 1 value pair therefore means "rack 0 slot 1" for an S7-1500 but would map to an invalid TSAP 0x0000, 0x0001 for an S7-200.
Why the LOGO! Defaults Sometimes Work Without TSAP Tuning
For LOGO! specifically, the standard S7-1200/1500 configuration of rack=0, slot=1 has been observed to establish a successful TCP connection and read/write basic markers and process images. This appears to be a permissive fallback inside the LOGO! S7 server: when a connection arrives with a non-standard TSAP, LOGO! 8 firmware accepts it if no other partner has reserved the same slot. The behavior is firmware-dependent and not documented by Siemens as a supported configuration. For deterministic commissioning, always set the LOGO! partner TSAP explicitly to 0x0100 (local) and 0x0300 (remote).
TSAP Configuration for S7-200 and S7-200 SMART
TSAPs are two-byte identifiers in the ISO-on-TCP TPDU. They serve the same purpose as IP+port for a higher-layer socket, but inside the private Siemens addressing domain. The high byte typically encodes the device class (CPU, CP, HMI, programming device) and the low byte encodes the connection slot.
| TSAP Hex | Meaning (S7-200 / S7-200 SMART / LOGO!) |
|---|---|
| 0x0100 | PG (programming device) default local TSAP for OP/PG connections |
| 0x0101 | PG connection slot 1 (alternative) |
| 0x0300 | CPU S7 connection slot 0 (server side default) |
| 0x0301 – 0x0307 | Additional passive CPU connections |
| 0x0400 – 0x040F | HMI / OP-tagged connections |
| 0x0500 – 0x050F | Other partner types, vendor-defined |
Decoding TSAP Byte Values
When configuring LECPython, the rack_or_localTSAP and slot_or_destTSAP arguments can be passed as integers in either decimal or hex format. Decimal 256 and hex 0x0100 are identical values inside the C# wrapper. For S7-200/LOGO! targets, the canonical pairs to use are:
-
Passive (server) connection from Python to a single LOGO!:
rack_or_localTSAP = 0x0100 # 256 decimal slot_or_destTSAP = 0x0300 # 768 decimal -
Second parallel connection for diagnostics:
rack_or_localTSAP = 0x0101 # 257 decimal slot_or_destTSAP = 0x0301 # 769 decimal - LOGO! configured as Modbus TCP master (rare; S7 is server side): not applicable — the LOGO! S7 server side cannot be re-mapped by the Python client.
0x0100 but the host's serial port logger shows bytes 01 00, the configuration is correct. If you see bytes 00 01, byte order has been swapped and the LOGO! will reject the connection with ISO-on-TCP disconnect TPDU 0x0D.
Python Code Example: Connecting to a LOGO! 8
The following Python 3.12 snippet connects to a LOGO! 8 at IP 192.168.0.10, reads the process-image output words VW0 and VW2, and writes a single marker byte. It demonstrates both the correct TSAP configuration for the S7-200 path and a fallback to Modbus TCP if S7 is unavailable.
from lecpython import PlcClient, PlcType, DataType
# LOGO! 8 inherits the S7-200 S7Comm stack.
# Use S7200 PLC type so the wrapper interprets the integers as TSAPs.
client = PlcClient(
plc_type=PlcType.S7200,
ip="192.168.0.10",
rack_or_localTSAP=0x0100, # 256 — PG local TSAP
slot_or_destTSAP=0x0300, # 768 — LOGO! S7 server TSAP
timeout_ms=2000,
)
try:
client.connect()
# Read process image: 2 words starting at VW0 (virtual word address 0)
values = client.read(DataType.Word, db_number=1, start=0, length=2)
print("VW0, VW2 =", values)
# Write a marker byte. LOGO! exposes marker memory as DB1 byte offsets.
client.write(DataType.Byte, db_number=1, start=0, value=0xAB)
finally:
client.disconnect()
Address Mapping for LOGO! 8
LOGO! does not expose classical S7 DBs. From the S7 client perspective the memory layout is aliased into a single DB (DB 1) with offsets corresponding to the LOGO! variable table.
| LOGO! Tag (LOGO!Soft Comfort) | DB1 Offset (Byte) | S7 Data Type |
|---|---|---|
| Digital Inputs I1 … I24 | 0 … 2 (bit-packed) | BYTE / BOOL |
| Digital Outputs Q1 … Q20 | 4 … 6 (bit-packed) | BYTE / BOOL |
| Analog Inputs AI1 … AI8 | 8 … 23 (words) | WORD |
| Analog Outputs AQ1, AQ2 | 24 … 27 (words) | WORD |
| Marker M1 … M64 | 32 … 39 (bytes) | BYTE |
| Variable Memory VW0 … VW850 | 128 … up to 1700 | WORD |
These offsets are firmware-dependent. The reference table above matches LOGO! 8 BM (Basic Module) firmware FS04 and later. Validate against the actual LOGO! project under LOGO!Soft Comfort before deploying read/write logic.
Modbus TCP Fallback Path
If S7 connectivity fails (firewall blocking TCP/102, LOGO! on an older firmware that does not enable the S7 server, or coexistence issues with multiple S7 clients), fall back to Modbus TCP. LOGO! 8 listens on TCP/502 by default and exposes a Modbus register map:
| Modbus Address (0-based) | Length | Content |
|---|---|---|
| 0000 … 0023 | 24 coils | Digital Inputs I1 … I24 |
| 0024 … 0043 | 20 coils | Digital Outputs Q1 … Q20 |
| 0044 … 0051 | 8 holding registers | Marker M1 … M64 (16 per register) |
| 0052 … 0083 | 32 input registers | Analog Inputs AI1 … AI8 (scaled 0–1000) |
| 0084 … 0087 | 4 holding registers | Analog Outputs AQ1, AQ2 |
LECPython Modbus example:
from lecpython import ModbusClient
mb = ModbusClient(ip="192.168.0.10", port=502, unit_id=1, timeout_ms=1500)
mb.connect()
# Read 24 digital inputs starting at coil 0
inputs = mb.read_coils(0, 24)
print("Inputs:", inputs)
# Read analog input AI1 (input register 52)
ai1 = mb.read_input_registers(52, 1)
print("AI1 scaled:", ai1[0]) # 0 … 1000
mb.disconnect()
Configuring LECPython With rack_or_localTSAP and slot_or_destTSAP
The dual-purpose parameters were introduced in LECPython 1.2.3.6 to clarify the historical confusion between TSAP-style and slot/rack-style connections. The rule is:
- If
plc_typeisS7200orS7200Smart: arguments are TSAP bytes. - Otherwise (S7300, S7400, S71200, S71500, Logo): arguments are rack and slot integers.
| Target PLC | plc_type | rack_or_localTSAP | slot_or_destTSAP | Comment |
|---|---|---|---|---|
| LOGO! 8 | Logo (TSAP mode) or S7200 | 0x0100 | 0x0300 | Recommended explicit TSAP |
| S7-200 | S7200 | 0x0100 | 0x0300 | PG <-> CPU default |
| S7-200 SMART | S7200Smart | 0x0100 | 0x0300 | Same TSAP semantics as S7-200 |
| S7-1200 | S71200 | 0 | 1 | Rack 0, slot 1 |
| S7-1500 | S71500 | 0 | 1 | Rack 0, slot 1 |
| S7-300 (CPU PN) | S7300 | 0 | 2 | Slot 2 = CPU |
| S7-400 (CPU PN) | S7400 | 0 | 3 | Slot 3 = CPU 400 |
Troubleshooting Matrix
| Symptom | Likely Root Cause | Diagnostic Step | Corrective Action |
|---|---|---|---|
| Connect fails with TimeoutError | TCP/102 blocked by Windows firewall | Test-NetConnection 192.168.0.10 -Port 102 |
Allow python.exe through the firewall on the private profile |
| Connect fails with ISO-on-TCP disconnect 0x0D | TSAP mismatch — LOGO! rejected Local TSAP | Capture with Wireshark on TCP port 102; inspect TPDU length=0 payload | Set rack_or_localTSAP=0x0100, slot_or_destTSAP=0x0300 and retry |
| Connect succeeds, read returns 0xFFFF for all words | Wrong DB number (LOGO! uses DB1 only) | Enable LOGO!Soft Comfort online test | Set db_number=1 in read/write calls |
| Connect succeeds, raises exception on first write | LOGO! project has not enabled S7 server write access | LOGO! menu > Tools > Ethernet Connections > S7 Connection | Enable "Permit S7 PUT/GET access" or uncheck "Read-only" |
| Modbus returns exception 0x02 (Illegal Data Address) | Address outside the LOGO! Modbus mapping | Read coil 0..23 first to confirm base layout | Restrict accesses to documented Modbus register ranges |
| Modbus returns exception 0x0A (Gateway Path Unavailable) | Wrong unit ID | Use unit_id=1 in Modbus client | Set unit_id to 1, do not use 0 or 255 for unicast |
| Random disconnects after several minutes | ISO-on-TCP keep-alive not sent; LOGO! firmware FS03 and earlier timeout idle connections at 30 s | Check firmware version under LOGO! > Diagnostics > Device | Upgrade LOGO! firmware to FS04 or later, or send a cyclic 4 s read to keep the connection alive |
| ImportError: No module named 'clr_loader' | .NET 8 runtime missing | dotnet --list-runtimes |
Install .NET 8 Desktop Runtime |
| OSError: [WinError 193] %1 is not a valid Win32 application | ARM64 Python trying to load x64 .NET assemblies | python -c "import platform; print(platform.machine())" |
Install x64 Python 3.12 instead of ARM64 build |
| S7 read works but write returns "Area not allowed" | Writing into a process-image region reserved for inputs | Check the LOGO! variable table for the offset being targeted | Map writes to marker (M) or variable memory (VW) regions, never to input area |
Version History and Firmware Compatibility
| LECPython Release | Change | Compatibility |
|---|---|---|
| ≤ 1.2.3.5 | Single rack, slot parameter pair only |
S7-1200/1500 only — S7-200/200SMART/LOGO! users had to patch the wrapper source |
| 1.2.3.6 | Renamed parameters to rack_or_localTSAP, slot_or_destTSAP; TSAP semantics documented for S7-200 and S7-200 SMART |
Adds first-class LOGO! and S7-200 SMART support without source patches |
| Post-1.2.3.6 (latest) | Additional PLC families and improved Modbus exception handling | Backward compatible — existing 1.2.3.6 scripts run unchanged |
Always pin to a tagged release rather than the rolling default branch. The @v1.2.3.6 (or current latest tag) installed via pip install git+... guarantees reproducible dependency resolution.
Verification and Commissioning Procedure
- Verify network reachability:
ping 192.168.0.10must succeed. If ping fails, the LOGO! may have IP conflict; check with LOGO!Soft Comfort > Online > Accessible Nodes. - Verify TCP/102 is open:
Test-NetConnection 192.168.0.10 -Port 102must return TcpTestSucceeded: True. - Verify the S7 server is enabled on the LOGO!:
- LOGO!Soft Comfort → Tools → Ethernet Connections → confirm an S7 connection exists.
- On the LOGO! hardware, navigate to Settings > Network > S7 Server and confirm it is ON.
- Run the connection script from the verification block in section "Python Code Example". Successful output proves end-to-end correctness.
- Read a known static value from a marker you can change via LOGO!Soft Comfort online mode to confirm data path integrity.
- Write a test byte to a marker and confirm via the LOGO! display or LOGO!Soft Comfort that the value changed.
- Disconnect cleanly and review the LOGO! diagnostic buffer (LOGO! menu > Diagnostics > Diagnostic Buffer) for any S7 connection error entries.
Edge Cases and Field-Proven Caveats
-
Multiple parallel S7 connections. LOGO! 8 supports up to 8 S7 connections simultaneously. With LECPython, each
PlcClientinstance reserves one TSAP pair. If you need 8 concurrent clients, set the Local TSAP to0x0100through0x0107on each instance. -
CycLic polling from a Jupyter notebook. Jupyter’s kernel restart does not always release the underlying TCP socket cleanly. Add a
atexit.register(client.disconnect)to prevent orphaned ISO-on-TCP sessions on the LOGO! that take 30 s to time out. - Python GIL and S7.Net throughput. S7.Net is synchronous and blocks the OS thread. For multi-tag high-rate acquisition (above 10 Hz) wrap each tag in its own Python thread or use the asynchronous variant of LECPython if your version provides it.
- Network address translation. If the Python host and the LOGO! are on different subnets separated by a router, ISO-on-TCP may traverse the router only if the router permits TCP/102 and the LOGO! has a default gateway configured. LOGO! 8 ships without a default gateway by default.
- LOGO! behind a managed switch with IGMP snooping. Some industrial switches treat the heavy ISO-on-TCP traffic as multicast and silently drop frames. Disable IGMP snooping on the LOGO! VLAN or use a dedicated port.
- S7 PDU size. LOGO! 8 advertises a maximum PDU size of 240 bytes. LECPython negotiates this automatically. If you observe truncated reads, manually set the requested length to ≤ 240 bytes per call.
Frequently Asked Questions
Does Siemens LOGO! support the same S7 communication protocol as S7-1200 or S7-1500?
No. LOGO! 8 uses the S7-200-style S7 communication stack with TSAP-based connection IDs rather than the rack/slot integers used by S7-1200, S7-1500, S7-300, and S7-400. Set plc_type=Logo or S7200 in LECPython and supply rack_or_localTSAP=0x0100, slot_or_destTSAP=0x0300.
Do S7-200 and S7-200 SMART use different S7 communication protocols?
No. Both use the same ISO-on-TCP S7Comm framing and the same TSAP semantics. S7-200 SMART extends the command set (for example parallel PUT/GET connections) but the wire format is identical, so a single LECPython configuration with PLC type S7200 or S7200Smart and TSAPs 0x0100 / 0x0300 addresses both.
Why does my LECPython connection fail with ISO-on-TCP disconnect TPDU 0x0D when targeting a LOGO! or S7-200?
The disconnect TPDU 0x0D indicates that the server rejected the connection because the Local TSAP did not match an active partner. Verify you are passing rack_or_localTSAP and slot_or_destTSAP as TSAP bytes (for example 0x0100 and 0x0300) and not as decimal integers intended for rack/slot. LECPython 1.2.3.6 and later interpret them correctly when plc_type is set to S7200 or S7200Smart.
Can I run LECPython on Linux or macOS?
Not with the current distribution. The bundled S7.Net assembly is compiled against Microsoft.NETCore.App 8.0 Windows Desktop Runtime and depends on Win32 APIs for socket creation and TLS. A future cross-platform build would require replacing S7.Net with Snap7 or another platform-neutral library.
What is the maximum number of tags I can read per request to a LOGO! 8?
LOGO! 8 advertises a maximum PDU size of 240 bytes. With WORD-typed tags this permits 120 words per request, but LOGO!Soft Comfort documentation recommends keeping requests below 64 words for stable round-trip times. Split larger requests into multiple PlcClient.read calls rather than exceeding the negotiated PDU size.
Which version of LECPython should I install for LOGO! support?
Install release 1.2.3.6 or any later tagged version. Earlier releases do not expose the dual-purpose rack_or_localTSAP and slot_or_destTSAP parameters and require source-code patches to support S7-200-style TSAP connections. Pin to the tagged release v1.2.3.6 from the LECPython GitHub repository for reproducible results.