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 |
|
1 byte at offset 0 of DB1 |
DB1.DBW10 |
|
2 bytes (WORD) at offset 10 |
DB1.DBD20 |
|
4 bytes (DWORD) at offset 20 |
DB1.DBX0.3 |
|
Bit 3 of byte 0 |
M0.0 |
|
Bit 0 of merker byte 0 |
MW10 |
|
2 bytes (WORD) from merker byte 10 |
IW0 / EW0 |
|
Analog input word at address 0 |
QW0 / AW0 |
|
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.