# Log a Siemens S7-1200 to CSV Without WinCC

> Read data blocks from an S7-1200 or S7-1500 over S7comm and log them to CSV, with no WinCC seat, no gateway and no OPC UA licence to buy first.
>
> Siemens S7 · September 14, 2026 · by Alex Spataru · https://serial-studio.com/blog/s7-1200-data-logging-without-wincc

You need a week of trend data off an S7-1200. Maybe a commissioning engineer wants to see whether a tank level is drifting overnight, maybe quality wants the temperature curve from a batch, maybe you are chasing an intermittent fault that only shows up on second shift. The data is sitting in a data block inside the CPU. Getting it onto a disk should be a small problem.

The official answers are not small. WinCC is a licensed SCADA seat. The S7-1200's OPC UA server is a separate licence that somebody has to buy and somebody has to enable. A gateway box is hardware, a purchase order and a cabinet slot. All three are reasonable if you are building a permanent system, and all three are absurd if you want a CSV by Thursday.

There is a fourth path that predates every one of them: read the memory directly over S7comm.

## What S7comm actually is

S7comm is the protocol Siemens controllers speak on TCP port 102, carried over ISO-on-TCP (RFC 1006). It is how a programming device talks to a CPU, and it is what the PUT/GET communication functions use between controllers.

The important thing about S7comm, and the thing that makes it different from OPC UA, is that **there is no address space to browse**. An OPC UA server hands you a tree of named, typed tags and tells you what exists. An S7 CPU does not. A client names the memory it wants by area, byte offset and width, and the controller returns those bytes. Nothing on the wire tells you whether the four bytes at `DB5.DBD20` are a `REAL`, a `DINT`, or a fragment of something else entirely.

That sounds like a drawback, and sometimes it is. But it is also why this path works when the others do not. You do not need the CPU to run an extra server. You do not need a licence enabled on it. You need the CPU to answer a read request, which is a thing it already knows how to do, and you need to know where to look, which you do because you have the symbol table open in TIA Portal.

## Two settings on the CPU

S7comm reads absolute memory, and a modern CPU has to be told to permit that. This is the step people miss, and the symptom is confusing: the TCP connection succeeds and then the session is refused.

On an S7-1200 or S7-1500:

- Open the CPU's properties in TIA Portal and tick **Protection & Security → Permit access with PUT/GET communication from remote partner**. Without it, the CPU accepts your socket and then declines to open an S7comm session.
- In each data block's properties, clear **Optimized block access**. An optimized block has no stable byte layout, which means `DB5.DBD20` does not name anything the CPU can resolve. This is not a client limitation. There is genuinely no address to read.

On an S7-300 or S7-400, neither setting exists. Those families answer PUT/GET reads as shipped.

Both of these are changes to the controller, so they belong to whoever owns the program. Worth knowing before you promise anyone a CSV.

## Addressing, which is the whole job

An address names an area, a byte offset inside it, and how wide the value is. Two spellings cover everything:

```
DB5.DBD20      data block 5, four bytes at offset 20
MW10           flag memory, two bytes at offset 10
Q0.1           output bit 1 of byte 0
DB1.DBX0.3     data block 1, bit 3 of byte 0
```

The width letter is `X` for a bit, `B` for a byte, `W` for a word and `D` for a double word. A bit address carries its index after a second dot, running 0 to 7. The German area letters `E` and `A` work as synonyms for `I` and `Q`, which is a small mercy if you inherited a program written in Germany.

The width letter on its own gives you an unsigned reading. To interpret the same bytes as something else, declare the type:

```
DB5.DBD20:REAL         IEEE 754 float
DB5.DBD24:DINT         signed 32-bit
MW10:INT               signed 16-bit
DB4.DBB10:STRING[32]   an S7 STRING up to 32 characters
```

The declared types are `BOOL`, `BYTE`, `WORD`, `DWORD`, `INT`, `DINT`, `REAL` and `STRING`. A type whose width does not match the address it is attached to is refused rather than read wrong, so `MW10:REAL` comes back as an error instead of quietly handing you two bytes of a four-byte float. That refusal is worth more than it sounds. A wrong reading that looks plausible is the expensive kind of mistake in this work.

## Rack and slot

The other field that decides whether you connect is where the CPU sits in the rack.

- S7-1200 and S7-1500: rack 0, slot 1.
- S7-300 and S7-400: rack 0, slot 2, usually.

A wrong slot is the most common reason a controller you can ping refuses the session, with PUT/GET a close second. The connection sequence has three steps and each fails differently: the TCP connection (the host is unreachable), the ISO connection (rack or slot is wrong), and the S7comm setup (the CPU will not open a session, which sends you back to PUT/GET and optimized block access). Knowing which of the three failed turns a guessing game into a two-minute fix.

## Reading it without hammering the CPU

Once the session is open, the client polls. A few details matter if this is going to run overnight rather than for a demo.

Reads are batched to the message length the controller agreed to when the session opened, so a list of a hundred addresses costs a handful of exchanges rather than a hundred round trips. Every tick compares each value against what the previous tick saw, and only what changed goes into a frame. A tick where nothing moved publishes nothing at all, which keeps a mostly idle machine off the redraw path and out of the log.

Every exchange blocks until the CPU answers, so the socket belongs on its own thread. A controller that goes quiet should slow the poll rate, not freeze the window you are watching.

A read the controller refuses, say a data block that does not exist or an offset past its end, is counted and named, and that channel keeps its last value. The session stays up. A bad address is a configuration error, not a dropped link, and treating it as a disconnect would be wrong.

## From addresses to a dashboard and a file

In [Serial Studio](/), the S7 driver takes a host, a rack, a slot, a poll interval and a list of variables, each with a name and an address. **Create Project from Variables** turns that list into a project: one group per data block plus a **Memory** group for the `I`, `Q` and `M` areas, an LED widget for each bit, plotting enabled for numeric types, strings routed to the data grid.

From there, CSV export is a toggle. So is [MDF4](https://serial-studio.com/help/mdf4) if the analysis downstream wants channel metadata and per-channel sample rates rather than a flat table. Both carry the poll's own timestamp, stamped on the polling thread at the moment the tick started, rather than the moment a frame happened to reach the screen. For a week-long trend that distinction is the difference between data you can correlate against a batch record and data you cannot.

The same thing runs from a command line, which is what you want if this is going to be a scheduled job rather than something a person babysits:

```bash
SerialStudio --s7 192.168.0.1 --s7-rack 0 --s7-slot 1 \
  --s7-variable "DB5.DBD20:REAL:Level" \
  --s7-variable "DB1.DBX0.3:Running" \
  --s7-interval 200
```

## It cannot write, by construction

The S7comm client here reads only. There is no write path in it at all: no setpoint to send, no bit to force, no command to issue. Attaching it to a running machine cannot change that machine's state.

This is usually the first question anyone asks when you propose plugging a laptop into a production controller, and "it is read-only by design" is a much better answer than "I promise I will be careful."

## When to use OPC UA instead

If the controller already has an OPC UA server running, use it. It is typed, it is browsable, the server tells you what exists instead of making you supply addresses, and it carries engineering units and ranges that a generated dashboard can pick up automatically. [The OPC UA path is worth its own walkthrough](https://serial-studio.com/blog/opc-ua-client-that-trends-and-records).

S7comm earns its place when OPC UA is not on the table: an S7-300 or S7-400 that never had the option, a locked-down S7-1200 where nobody bought the licence, or a commissioning session where you have the symbol table in front of you and want numbers in the next five minutes.

## Try it before you touch a controller

You do not need a CPU to find out whether any of this fits your work. The `S7 PLC Example` that ships with Serial Studio includes an ISO-on-TCP simulator that speaks S7comm properly: connect confirm, PDU length negotiation, and read requests served out of a drifting `DB1`. It is Python standard library only, with no `snap7` and no vendor DLL, and the same read service runs against it as against an S7-1500 in a cabinet.

On Windows, open the project and press Connect; the simulator starts itself. On macOS and Linux, port 102 is privileged, so start it by hand first:

```bash
sudo python3 "examples/S7 PLC Example/s7_plc_simulator.py"
```

Then get the addressing wrong on purpose and watch what the errors tell you. That is the part worth rehearsing before you are standing in front of a real cabinet. There are [simulators for the other industrial drivers too](https://serial-studio.com/simulators), so the whole evaluation happens at your desk.
