Reading & Writing Data

This page covers address mapping, data type conversions, memory area access, and analog I/O – everything you need for reading from and writing to a PLC.

All examples assume you have a connected client:

from s7 import Client

client = Client()
client.connect("192.168.1.10", 0, 1)

Address Mapping

PLC addresses in Siemens TIA Portal / STEP 7 map to python-snap7 API calls as follows.

PLC Address

API Call

Explanation

DB1.DBB0

db_read(1, 0, 1)

1 byte at offset 0 of DB1

DB1.DBW10

db_read(1, 10, 2)

2 bytes (WORD) at offset 10

DB1.DBD20

db_read(1, 20, 4)

4 bytes (DWORD) at offset 20

DB1.DBX0.3

db_read(1, 0, 1) then get_bool(data, 0, 3)

Bit 3 of byte 0

M0.0

mb_read(0, 1) then get_bool(data, 0, 0)

Bit 0 of merker byte 0

MW10

mb_read(10, 2)

2 bytes (WORD) from merker byte 10

IW0 / EW0

read_area(Area.PE, 0, 0, 2)

Analog input word at address 0

QW0 / AW0

read_area(Area.PA, 0, 0, 2)

Analog output word at address 0

Important

The byte_index parameter in all s7.util getter/setter functions is relative to the returned bytearray, not the absolute PLC address.

For example, to read DB1.DBX10.3:

data = client.db_read(1, 10, 1)  # Read 1 byte starting at offset 10
from s7.util import get_bool
value = get_bool(data, 0, 3)  # byte_index=0, NOT 10

You read from PLC offset 10, but data[0] is byte 10 from the PLC.

Data Types

Each example below shows a complete read and write cycle. Data conversion helpers live in s7.util and work with any client.

BOOL

Booleans require a read-modify-write pattern. You cannot write a single bit to the PLC; you must read the enclosing byte, change the bit, then write the whole byte back.

from s7 import util

# Read DB1.DBX0.3 (bit 3 of byte 0)
data = client.db_read(1, 0, 1)
value = util.get_bool(data, 0, 3)
print(f"DB1.DBX0.3 = {value}")

# Write DB1.DBX0.3 -- read first, then modify, then write
data = client.db_read(1, 0, 1)
util.set_bool(data, 0, 3, True)
client.db_write(1, 0, data)

Warning

Never write a freshly created bytearray for booleans. Always read the current byte first to avoid overwriting neighboring bits.

BYTE (1 byte, unsigned 0–255)

from s7 import util

# Read DB1.DBB0 (1 byte at offset 0)
data = client.db_read(1, 0, 1)
value = util.get_byte(data, 0)
print(f"DB1.DBB0 = {value}")

# Write
data = bytearray(1)
util.set_byte(data, 0, 200)
client.db_write(1, 0, data)

INT (2 bytes, signed -32768 to 32767)

from s7 import util

# Read DB1.DBW10
data = client.db_read(1, 10, 2)
value = util.get_int(data, 0)
print(f"DB1.DBW10 = {value}")

# Write
data = bytearray(2)
util.set_int(data, 0, -1234)
client.db_write(1, 10, data)

WORD (2 bytes, unsigned 0–65535)

from s7 import util

# Read DB1.DBW20
data = client.db_read(1, 20, 2)
value = util.get_word(data, 0)
print(f"DB1.DBW20 = {value}")

# Write
data = bytearray(2)
util.set_word(data, 0, 50000)
client.db_write(1, 20, data)

DINT (4 bytes, signed -2147483648 to 2147483647)

from s7 import util

# Read DB1.DBD30
data = client.db_read(1, 30, 4)
value = util.get_dint(data, 0)
print(f"DB1.DBD30 = {value}")

# Write
data = bytearray(4)
util.set_dint(data, 0, 100000)
client.db_write(1, 30, data)

DWORD (4 bytes, unsigned 0–4294967295)

from s7 import util

# Read DB1.DBD40
data = client.db_read(1, 40, 4)
value = util.get_dword(data, 0)
print(f"DB1.DBD40 = {value}")

# Write
data = bytearray(4)
util.set_dword(data, 0, 3000000000)
client.db_write(1, 40, data)

LINT (8 bytes, signed -9223372036854775808 to 9223372036854775807)

from s7 import util

# Read 8 bytes from DB1 at offset 60
data = client.db_read(1, 60, 8)
value = util.get_lint(data, 0)
print(f"LINT = {value}")

# Write (no set_lint helper -- use struct directly)
import struct
data = bytearray(struct.pack(">q", 123456789012345))
client.db_write(1, 60, data)

ULINT (8 bytes, unsigned 0–18446744073709551615)

from s7 import util

# Read 8 bytes from DB1 at offset 68
data = client.db_read(1, 68, 8)
value = util.get_ulint(data, 0)
print(f"ULINT = {value}")

# Write (no set_ulint helper -- use struct directly)
import struct
data = bytearray(struct.pack(">Q", 9876543210))
client.db_write(1, 68, data)

REAL (4 bytes, IEEE 754 float)

from s7 import util

# Read DB1.DBD50
data = client.db_read(1, 50, 4)
value = util.get_real(data, 0)
print(f"DB1.DBD50 = {value}")

# Write
data = bytearray(4)
util.set_real(data, 0, 3.14)
client.db_write(1, 50, data)

LREAL (8 bytes, IEEE 754 double)

from s7 import util

# Read DB1, offset 60, 8 bytes
data = client.db_read(1, 60, 8)
value = util.get_lreal(data, 0)
print(f"LREAL = {value}")

# Write
data = bytearray(8)
util.set_lreal(data, 0, 3.141592653589793)
client.db_write(1, 60, data)

STRING (2 header bytes + characters)

S7 strings have a specific format:

  • Byte 0: Maximum length (set when the variable is declared in the PLC)

  • Byte 1: Actual (current) length of the string content

  • Bytes 2+: ASCII characters

When reading, always request max_length + 2 bytes to include the header.

from s7 import util

# Read a string at DB1, offset 10, declared as STRING[20] in the PLC
max_length = 20
data = client.db_read(1, 10, max_length + 2)  # 20 + 2 header bytes = 22
text = util.get_string(data, 0)
print(f"String = '{text}'")

# Write a string
data = client.db_read(1, 10, max_length + 2)
util.set_string(data, 0, "Hello", max_length)
client.db_write(1, 10, data)

Note

Always read the existing data before writing a string. The set_string function preserves the max-length header byte and pads unused characters with spaces.

DATE_AND_TIME (8 bytes, BCD encoded)

from s7 import util
from datetime import datetime

# Read DATE_AND_TIME at DB1, offset 70 (returns ISO 8601 string)
data = client.db_read(1, 70, 8)
dt_string = util.get_dt(data, 0)
print(f"DATE_AND_TIME = {dt_string}")  # e.g. '2024-06-15T14:30:00.000000'

# Parse to Python datetime if needed
dt_obj = datetime.fromisoformat(dt_string)

# Write DATE_AND_TIME
data = client.db_read(1, 70, 8)
util.set_dt(data, 0, datetime(2024, 6, 15, 14, 30, 0))
client.db_write(1, 70, data)

Memory Areas

python-snap7 provides convenience methods for data blocks and merkers, and the generic read_area / write_area for all other areas.

Data Blocks (DB)

# Read 10 bytes from DB1 starting at offset 0
data = client.db_read(1, 0, 10)

# Write 4 bytes to DB1 starting at offset 0
client.db_write(1, 0, bytearray([0x01, 0x02, 0x03, 0x04]))

Merkers / Flags (M)

# Read 4 merker bytes starting at MB0
data = client.mb_read(0, 4)

# Write 2 bytes starting at MB10
client.mb_write(10, 2, bytearray([0xFF, 0x00]))

Inputs (I / E)

from s7 import Area

# Read 2 input bytes starting at IB0
data = client.read_area(Area.PE, 0, 0, 2)

Outputs (Q / A)

from s7 import Area

# Read 2 output bytes starting at QB0
data = client.read_area(Area.PA, 0, 0, 2)

# Write to QB0
client.write_area(Area.PA, 0, 0, bytearray([0x00, 0xFF]))

Timers (T)

from s7 import Area

# Read timer T0 (1 timer = 2 bytes)
data = client.read_area(Area.TM, 0, 0, 1)

Counters (C)

from s7 import Area

# Read counter C0 (1 counter = 2 bytes)
data = client.read_area(Area.CT, 0, 0, 1)

Analog I/O

Analog inputs are typically 16-bit integers in the peripheral input area (Area.PE). The raw value from the PLC needs to be scaled to engineering units.

Reading Analog Inputs

from s7 import util
from s7 import Client, Area

client = Client()
client.connect("192.168.1.10", 0, 1)

# Read AIW0 (analog input word at address 0)
data = client.read_area(Area.PE, 0, 0, 2)
raw_value = util.get_int(data, 0)
print(f"Raw value: {raw_value}")

# Scale to engineering units
# S7 analog modules typically use 0-27648 for 0-100% range
min_range = 0.0    # e.g., 0 bar
max_range = 10.0   # e.g., 10 bar
scaled = raw_value * (max_range - min_range) / 27648.0 + min_range
print(f"Pressure: {scaled:.2f} bar")

# Read AIW2 (second analog input)
data = client.read_area(Area.PE, 0, 2, 2)
raw_value = util.get_int(data, 0)

Writing Analog Outputs

from s7 import util
from s7 import Area

# Write to AQW0 (analog output word at address 0)
data = bytearray(2)
util.set_int(data, 0, 13824)  # ~50% of 27648
client.write_area(Area.PA, 0, 0, data)

Note

The standard scaling factor 27648 applies to most Siemens analog I/O modules. Check your module documentation for the actual range.