How to read S7 PLC data: OPC UA server, Snap7 and the optimized DB trap
- Published:
- Reading time
- 21 min read
- Author:
- Abbas Mohammadi
To read S7 PLC data from a SIMATIC controller you have two realistic routes: enable the OPC UA server built into the S7-1500 and budget for a runtime licence, or use an S7 client such as Snap7 to reach data blocks directly over TCP port 102. The second route is licence-free, but it fails at two points that almost no tutorial mentions: the PUT/GET setting in the CPU, and optimized data blocks, which have no absolute addresses. This article covers both routes, the trap between them, and the code that gets values into a time-series store cleanly.
The decision first: read S7 PLC data via the OPC UA server or straight over S7comm?
If you have an S7-1500 on current firmware, access to the TIA project and a licence budget, take the CPU's OPC UA server. If you have to connect an S7-300/400, are not allowed to buy a licence, or need only a handful of values out of a non-optimized data block, take Snap7 over port 102. Everything in between is a trade-off between licence cost, how deeply you have to intervene in the PLC project, and the security level you can live with. Tab. 01 sets it out.
The PLC generation decides first. According to the support matrix in the python-snap7 documentation, S7-300 and S7-400 work with no preparation, S7-1200 and S7-1500 only with PUT/GET communication enabled; redundant S7-1500R/H systems are not supported by the classic protocol at all. For machines with no network interface neither route applies - retrofitting machine data covers the alternatives, from tapping signals to an edge device.
| PLC generation | OPC UA server in the CPU | Classic S7 protocol (Snap7, PUT/GET) | Recommendation |
|---|---|---|---|
| S7-300 / S7-400 | not integrated, only via an external gateway | works with no preparation; rack 0, slot 2 (S7-300) or slot 3 (S7-400) | Snap7, read-only, behind segmentation |
| S7-1200 | available; runtime licence type 'Basic' | only with PUT/GET enabled and non-optimized DBs; rack 0, slot 1 | depends on firmware, number of variables and write requirements |
| S7-1500 / ET 200SP CPU | from firmware V2.0; runtime licence small/medium/large | only with PUT/GET enabled and non-optimized DBs; rack 0, slot 1 | OPC UA, as soon as licence and project access exist |
| S7-1500 Software Controller (CPU 1507) | available; licence class medium | as for the S7-1500 | OPC UA |
| S7-1500R/H (redundant) | available, its own chapter in the Siemens communication manual | not supported by python-snap7 | OPC UA, not Snap7 |
- S7comm / PUT-GET
- The classic S7 protocol over ISO-on-TCP (RFC 1006) on TCP port 102. It addresses memory areas by block number, byte offset and length; it knows nothing about symbol names.
- S7CommPlus
- The newer, symbolic protocol that TIA Portal and HMI devices use with an S7-1200/1500. As of TIA Portal V17 and CPU firmware V2.9 (S7-1500) or V4.5 (S7-1200), this PG/HMI communication is secured with certificates over TLS.
- Optimized block access
- The default setting for every newly created block on an S7-1200/1500. The CPU sorts variables by data type and stores them without gaps; only symbolic access exists, and there are no fixed byte addresses.
Route 1: setting up the OPC UA server in the S7-1500 - including the licence question
Every S7-1500 CPU contains an OPC UA server, but it ships switched off: the Siemens documentation states that it is disabled by default for security reasons, so OPC UA clients have neither read nor write access. Setting up an S7-1500 as an OPC UA server therefore needs the TIA project, a download to the CPU and a purchased runtime licence. The Siemens communication manual describes the procedure in seven steps; the essentials are summarized below.
Select the CPU and enable the server
Select the CPU in the device or network view, go to 'OPC UA > Server' in the properties and activate the server. You have to confirm the security notes.
Enter the runtime licence
Under 'Properties > General > Runtime licenses > OPC UA', TIA Portal shows which licence type is required. Once you have bought it, select the type of the purchased licence from the list there.
Expose variables - and take away write rights
PLC and DB variables are readable and writable by default through the attributes 'Accessible from HMI/OPC UA' and 'Writable from HMI/OPC UA'. For monitoring, clear the write attribute per variable or for the whole DB, and remove variables you do not need from the address space entirely.
Define the endpoint and the security settings
The server is reachable at
opc.tcp://<IP>:4840, and the port is configurable. Endpoints combine a security mode (None, Sign, SignAndEncrypt) with a security policy. Siemens recommends enabling only the strictest policies the client still supports; certificate-based communication also requires the CPU clock to be correct.Compile, download, verify
Compile the project and download it to the CPU; the server then starts. A connection test with a generic client shows whether certificate, endpoint and user all match - and if it fails, work through the OPC UA connection faults systematically rather than guessing.
The licence question decides whether this route is economic, and it is often discovered only at compile time, when TIA Portal displays the class required. For the S7-1500, Siemens distinguishes three classes by CPU performance (Tab. 02); for the S7-1200, licence type 'Basic' applies. The SIMATIC OPC UA licence applies per CPU; ten controllers need ten licences. The documentation does not quote prices - those come from the Siemens price list or your distributor.
| Licence class | Required for | Where TIA Portal shows the class |
|---|---|---|
| SIMATIC OPC UA S7-1500 small | CPU 1511, CPU 1512, CPU 1513, ET 200SP CPUs, CPU 1515SP PC | Properties > General > Runtime licenses > OPC UA > Type of required license |
| SIMATIC OPC UA S7-1500 medium | CPU 1515, CPU 1516, Software Controller CPU 1507, CPU 1516pro-2PN | same place |
| SIMATIC OPC UA S7-1500 large | CPU 1517, CPU 1518 | same place |
| SIMATIC OPC UA S7-1200 Basic | S7-1200 CPUs with an OPC UA server | same place |
The licence does buy something concrete. The server delivers variables symbolically and with their data type, including from optimized blocks, and it offers subscriptions instead of polling. For cyclic access to small amounts of data at intervals down to roughly five seconds, Siemens recommends subscriptions, and 'RegisteredRead' and 'RegisteredWrite' for repeated access. The minimum publishing and sampling intervals can be capped on the server side so that no client can overload the CPU.
Route 2: Snap7 and S7comm - access over port 102 without OPC UA
Snap7 is an open-source library for the classic S7 protocol, and python-snap7 is the matching Python wrapper. The client connects to the CPU over ISO-on-TCP with client.connect(host, rack, slot, tcp_port=102); the rack and slot values are in Tab. 01. According to the Siemens communication manual, port 102 is active in every S7-1500 and cannot be disabled, because engineering communication uses it. That is not an open door in itself: without the PUT/GET setting, the CPU does not answer data requests.
You can read data blocks (db_read), flag memory (mb_read) and, through read_area, inputs, outputs, timers and counters. With read_multi_vars the client bundles up to 20 items into a single request, which saves round trips. The price of that simplicity: the protocol carries neither symbol names nor structure information - the documentation lists exactly this under 'Limitations'. It returns bytes from an offset; the meaning has to come from the DB declaration in the TIA project. If you are not allowed to see the project, Snap7 leaves you guessing.
According to its support matrix, python-snap7 speaks only the classic S7 protocol; it reaches an S7-1200 or S7-1500 through the PUT/GET route, not through S7CommPlus. If you want the symbolic, TLS-secured communication of the newer generation, you need commercial drivers, or you take the OPC UA route from the start. For licence-free access to an S7-1500 with Python, exactly one combination remains: python-snap7 plus the PUT/GET setting plus non-optimized data blocks.
PUT/GET access provides unauthenticated read/write access to PLC memory.
The trap: optimized data blocks and the PUT/GET setting
The most common failure on a first Snap7 attempt against an S7-1500 is not in the code: the connection is up, db_read returns an error or zeros, and the cause sits in the TIA project. Two settings have to be right. First, the CPU must allow PUT/GET communication - the option 'Permit access with PUT/GET communication from remote partner (PLC, HMI, OPC, ...)' under 'Protection & Security > Connection mechanisms' is cleared by default on the S7-1200 and S7-1500. Second, the data block you read must be non-optimized.
The reason for the second condition is in the Siemens programming guideline for the S7-1200/1500: in optimized blocks the CPU sorts all variables automatically by data type so that no gaps remain, and there is only symbolic access to such blocks. Snap7, however, addresses solely by byte offset. The communication manual is unambiguous about PUT/GET: only data blocks with absolute addressing can be used. Snap7 and optimized data blocks are therefore mutually exclusive by design - no update and no parameter changes that.
It gets harder still, because the same guideline says optimized access is preset for every newly created block and Siemens explicitly recommends using optimized blocks throughout; non-optimized blocks exist on the S7-1200/1500 only for compatibility. In a project that the machine builder has written to that recommendation, Snap7 will read nothing at all to begin with.
Enable PUT/GET communication
Select the CPU, open its properties, go to 'General > Protection & Security > Connection mechanisms' and tick 'Permit access with PUT/GET communication from remote partner'.
Create an interface DB instead of converting process DBs
Create a dedicated global DB, clear the 'Optimized block access' attribute on that block only (right-click, 'Properties > Attributes'), and copy the values you need into it once per cycle. Siemens warns that copying between optimized and non-optimized blocks costs processing time - acceptable for a few dozen values, not for thousands.
Compile and download to the CPU
Compile the project and download it ('Online > Download to device'). Depending on the change, the CPU will demand a restart - so do this only in an agreed maintenance window.
Document the offsets and test minimally
In a non-optimized DB the editor shows absolute offsets; that list (name, type, offset, unit, scaling) is the interface specification for the gateway. Then read two bytes from offset 0 first, and only afterwards the full layout - that way you separate connection problems from layout problems.
Before you switch off optimized block access
- Machine builder or program author informed, and written confirmation that the change does not affect warranty or the maintenance contract
- Operator and production management have released a maintenance window for the download and a possible CPU restart
- Current TIA project uploaded from the CPU and archived as the state before the change
- A separate, non-optimized interface DB created instead of converting a process DB, filled cyclically by the PLC program
- Values in the affected DBs backed up, because toggling the attribute reinitializes the block
- The PUT/GET setting documented as a risk; firewall rule allowing only the gateway to reach the CPU on port 102
- Offsets, data types and units of the interface variables recorded in a table and version-controlled
- Rollback tested: the project state from before the change can be downloaded again
From raw bytes to meaning: data types, byte order and timestamps
Snap7 returns a bytearray; every value in it has to be decoded with the right type at the right offset. Non-optimized blocks are laid out big-endian - according to the Siemens programming guideline a legacy of the S7-300/400 that the S7-1500 keeps for standard blocks, even though its processor works little-endian internally. One detail matters with s7.util: the byte_index parameter refers to the returned bytearray, not to the absolute DB address. If you start reading at offset 20, DBW22 is at get_int(raw, 2).
| S7 type | Size | Example address | python-snap7 | Note |
|---|---|---|---|---|
| BOOL | 1 bit | DB100.DBX0.3 | get_bool(raw, 0, 3) | byte 0, bit 3 of the buffer you read |
| INT | 2 bytes | DB100.DBW2 | get_int(raw, 2) | signed, -32,768 to 32,767 |
| DINT | 4 bytes | DB100.DBD4 | get_dint(raw, 4) | signed, 32 bit |
| REAL | 4 bytes | DB100.DBD8 | get_real(raw, 8) | IEEE 754 binary32, big-endian |
| STRING | 2 + n bytes | DB100.DBB12 | get_string(raw, 12) | header bytes: maximum length, actual length; default encoding latin-1 |
| DATE_AND_TIME | 8 bytes | - | get_dt(raw, i) | BCD-encoded, an S7-300/400 legacy |
| DTL | 12 bytes | - | get_dtl(raw, i) | year as uint16 through nanoseconds as uint32, big-endian, no time zone |
| LDT | 8 bytes | - | get_ldt(raw, i) | nanoseconds since 1970-01-01 UTC, S7-1500 |
Neither S7comm nor the raw value itself carries units. Analogue values often arrive as a raw integer that only becomes a physical quantity through the module's scaling factor - typically 27,648 for 100 % of the measuring range on SIMATIC analogue inputs. Scaling, unit and measuring range belong in the interface table; hard-code them in the gateway and you lose them with the next change of personnel.
A working minimal example: read S7 PLC data with Python, error handling included
The example below uses python-snap7 to read eight bytes of a non-optimized interface DB from an S7-1500, decodes three values and polls once a second. The reconnect parameters come from the 'Connection Issues' section of the python-snap7 documentation; the client's default timeout is five seconds. The DB layout is a fictitious example and has to be replaced by the offsets from your own project.
import time
from datetime import datetime, timezone
from s7 import Client # python-snap7 (package name "s7", older versions "snap7")
from s7.util import get_bool, get_int, get_real
PLC_IP, RACK, SLOT = "192.168.0.10", 0, 1 # S7-1200/1500: rack 0, slot 1
DB_NUMBER, START, SIZE = 100, 0, 8 # non-optimized DB, layout see Tab. 03
POLL_S = 1.0 # one second, not milliseconds
client = Client(
auto_reconnect=True, # reconnect with backoff (docs: "Connection Issues")
max_retries=5,
retry_delay=1.0,
backoff_factor=2.0,
max_delay=30.0,
)
client.connect(PLC_IP, RACK, SLOT) # TCP port 102 is the default (tcp_port=102)
try:
while True:
t0 = time.monotonic()
try:
raw = client.db_read(DB_NUMBER, START, SIZE)
except Exception as exc: # timeout, dropped connection, access error
print("read failed:", exc) # here: log + mark a gap, never repeat a value
time.sleep(2.0)
continue
sample = {
"ts": datetime.now(timezone.utc).isoformat(), # gateway timestamp!
"running": get_bool(raw, 0, 0), # DB100.DBX0.0 (byte 0, bit 0)
"parts": get_int(raw, 2), # DB100.DBW2 (INT, big-endian)
"temp_c": get_real(raw, 4), # DB100.DBD4 (REAL, IEEE 754)
}
print(sample) # here: buffer/database instead of print
time.sleep(max(0.0, POLL_S - (time.monotonic() - t0)))
except KeyboardInterrupt:
pass
finally:
client.disconnect()Three decisions in that code are deliberate. The read time is subtracted from the poll interval so that it does not turn into drift. A read error produces a visible gap rather than a repeated value - otherwise a dropped connection later looks like a machine running steadily. And the interval is one second, not ten milliseconds: every db_read is a round trip inside the communication share of the CPU cycle, which HMI and engineering also use. python-snap7 ships a rate limiter for this (max_requests_per_second); if you need state changes at millisecond resolution, put the logic in the PLC and let it write events with their own timestamp into the DB.
The OPC UA counterpart needs no loop. After the session is established, the client creates a subscription with a publishing interval and attaches the nodes it wants as monitored items. According to part 4 of the OPC UA specification, the publishing interval defines the cyclic rate of the subscription; with no changes to report the server sends keep-alive messages, and if the client's publish requests stay away too long the server closes the subscription with Bad_Timeout. The client is told about changes and about the connection state instead of having to infer both.
What next? From the controller into a database and a dashboard
A value you have read is only worth something once it sits in a time-series store with its timestamp, its origin and its unit, ready to become a dashboard and a metric. Between print(sample) and that state lie four design decisions that have nothing to do with Siemens or with any library.
- Buffering: the gateway writes into a local buffer first (a file or an embedded database) and only from there into the central store. If the network to the database fails, no values are lost; if the PLC connection fails, you get visible gaps instead of frozen values.
- Batch writes: values are collected and written in blocks rather than one insert per value. That takes load off the database and the network and makes the backlog after an outage manageable.
- Naming convention:
plc1_db100_dbd4is an address, not a name for data. Every value needs a unique path by site, area, line, machine and signal - agreed before the first row is stored. A Unified Namespace with an ISA-95 topic scheme shows what such a scheme looks like. - Transport to the platform: whether the gateway forwards over OPC UA or publishes values as MQTT messages to a broker depends on how many consumers there are and on who holds the semantics - OPC UA or MQTT works through that trade-off.
Only on that foundation do metrics hold up. An availability figure derived from machine states with gateway timestamps carries an uncertainty of one poll interval per state change - and with many short stoppages that adds up. Which signals a defensible metric needs is set out in calculating OEE; this article supplies the raw values for it.
Security and operation: what you must not leave in place permanently
Both routes can be configured generously during a test phase; in permanent operation four things have to be wound back. The PUT/GET setting, as the python-snap7 documentation states, opens unauthenticated read and write access to PLC memory - for any computer that can reach port 102. If you enable it, put the controller in a segment that only the gateway can reach; Siemens names firewalls and network segmentation as prerequisites in its security notes. A Snap7 gateway on the office network is not a compromise, it is open write access to the plant.
- Switch off security mode 'None': in the OPC UA specification,
Nonesimply means that no security is applied. For permanent operation useSignAndEncryptwith the strictest policy the client supports, and disable the weaker endpoints - which is what Siemens recommends too. - Take away write rights: on the OPC UA server, clear 'Writable from HMI/OPC UA' per variable or per DB; Siemens advises allowing write access only where it is genuinely needed. Snap7 has no such server-side distinction - there, segmentation plus a gateway that never calls a write function is all you have.
- No shared operator accounts: use dedicated technical users with their own certificate per gateway for OPC UA access, so that accesses can be attributed later.
- Write access only with a release concept: writing setpoints from an IT system into the PLC is a project of its own, with plausibility checks in the PLC, sign-off by the person responsible for the plant and an audit trail - not a monitoring script extended by a
db_write.
Conclusion: choose the route first, then write the code
Technically you can read S7 PLC data in an afternoon - provided you have decided beforehand which route fits your controller generation, your licence budget and your security level. For an S7-1500 with project access, the OPC UA server is the cleaner route: symbolic, with subscriptions, with fine-grained write rights, but subject to a licence. Snap7 is the pragmatic route for legacy plant and for licence-free read access, at the price of the PUT/GET setting, non-optimized interface DBs and rigorous segmentation.
The real work begins behind the first byte you read: documenting offsets, setting timestamps at the source, buffering, naming, storing. Treat that part as a project rather than a script and you will still have usable data in a year. What such a data path looks like in practice, from the controller through to the dashboard, is shown by the ralwo solutions.
Frequently asked questions
db_write, mb_write and write_area, and the PUT/GET setting, as the documentation says, permits unauthenticated read and write access to PLC memory. That is exactly why you should not do it in a monitoring gateway. Write access from IT systems belongs in a concept of its own, with plausibility checks in the PLC, sign-off by the person responsible for the plant, an audit trail and network segmentation. On the OPC UA server, writing can be blocked per variable through 'Writable from HMI/OPC UA'; Snap7 has no such server-side distinction.Sources
- SIMATIC S7-1500, ET 200MP, ET 200SP, ET 200AL, ET 200pro, ET 200eco PN - Communication, Function Manual, 11/2025 (A5E03735815-AN) — Siemens AG
- License for OPC UA (S7-1500, S7-1500T) - STEP 7, TIA Portal Information System V20 — Siemens AG
- License for OPC UA (S7-1200) - STEP 7, TIA Portal Information System V20 — Siemens AG
- Programming Guideline for S7-1200/1500 (Entry ID 81318674, V1.6, 12/2018) — Siemens Industry Online Support
- S7 Communication with PUT/GET - S7-1500 CPUs and S7-1200 CPUs (Entry ID 82212115, V2.0, 09/2019) — Siemens Industry Online Support
- Welcome to python-snap7's documentation! — python-snap7 (Read the Docs)
- PLC Support Matrix - python-snap7 documentation — python-snap7 (Read the Docs)
- OPC 10000-4: OPC Unified Architecture - Part 4: Services (Release 1.05.07) — OPC Foundation