4. S7comm 协议读取西门子PLC数据
任务目标
本案例实现一个基于 S7comm 协议的西门子 PLC 数据读取程序,目标设备为 S7-1200 系列,读取其 DB1 块中偏移 0 开始的 4 个字节(即一个 REAL 实数)。程序使用纯 Python 实现,不依赖第三方 S7 通信库,直接构造 S7comm 报文完成连接、读写请求与响应解析。
环境与依赖
- Python 3.8+,仅使用标准库
socket、struct - 目标 PLC:西门子 S7-1200(固件版本 V4.0+),需开启 PUT/GET 通信权限
- PLC 网络参数:IP 地址、机架号(Rack)、槽号(Slot),默认 S7-1200 通常为 Rack=0, Slot=1
实现原理
S7comm 协议基于 ISO-on-TCP(RFC 1006),通信过程分为三步:建立 TCP 连接后先完成 ISO 连接握手(CR 报文),再发送 S7comm 协商报文(Job 0xF0),最后发送数据读取请求(Read Var)。响应报文需按 TPKT 头、S7 头、参数区和数据区逐层解析。本案例代码完整实现了上述三个步骤,并正确解析返回的 REAL 值。
关键完整代码
import socket
import struct
class S7CommClient:
def __init__(self, ip, rack=0, slot=1, port=102):
self.ip = ip
self.rack = rack
self.slot = slot
self.port = port
self.sock = None
def connect(self):
"""建立 TCP + ISO-on-TCP + S7comm 连接"""
self.sock = socket.create_connection((self.ip, self.port), timeout=5)
# 1. ISO 连接请求 (CR)
cr = bytes.fromhex(
'0300001601100000000000c0010ac102' +
f'0100c2{self.rack:02x}c3{self.slot:02x}'
)
self.sock.send(cr)
resp = self._recv_tpkt()
if len(resp) < 22 or resp[21] != 0x00: # CR 响应第22字节为0表示成功
raise RuntimeError("ISO 连接失败")
# 2. S7comm 协商 (Job 0xF0)
negotiate = bytes.fromhex(
'0300001902f0803201000000000008000000f00000010001'
)
self.sock.send(negotiate)
resp = self._recv_tpkt()
if len(resp) < 27 or resp[26] != 0x00:
raise RuntimeError("S7 协商失败")
def read_real(self, db_number, offset):
"""读取 DB 块中指定偏移的 REAL 值(4字节)"""
# 构造 Read Var 请求
# 参数区:功能码 0x04(读),项数 1
# 该项:变量类型 0x12(DB),读取长度 4,DB号,偏移量
param = bytes.fromhex('040100120004') + struct.pack('>H', db_number) + struct.pack('>H', offset)
# 数据区长度 = 0(读请求无数据)
data_len = 0
# 组装 S7 PDU(含 TPKT + COTP + S7 头)
s7_header = bytes.fromhex('02f080') + struct.pack('>H', 0x0001) + struct.pack('>H', 0x0000) + struct.pack('>H', len(param) + data_len)
pdu = s7_header + param
tpkt = bytes.fromhex('0300') + struct.pack('>H', len(pdu) + 4)
self.sock.send(tpkt + pdu)
resp = self._recv_tpkt()
# 解析响应:S7 头第 5 字节为返回码,0xFF 表示成功
if len(resp) < 32 or resp[21] != 0xff:
raise RuntimeError("读取失败,请检查 DB 号和偏移量")
# 数据区:从第 29 字节开始为实际数据(跳过返回码和传输大小字节)
data_start = 29
value_bytes = resp[data_start:data_start+4]
return struct.unpack('>f', value_bytes)[0]
def _recv_tpkt(self):
"""接收完整 TPKT 报文"""
header = self._recv_exact(4)
if header[0] != 0x03:
raise RuntimeError("无效 TPKT")
length = struct.unpack('>H', header[2:4])[0]
return header + self._recv_exact(length - 4)
def _recv_exact(self, n):
data = b''
while len(data) < n:
chunk = self.sock.recv(n - len(data))
if not chunk:
raise ConnectionError("连接中断")
data += chunk
return data
def close(self):
if self.sock:
self.sock.close()
# 使用示例
if __name__ == '__main__':
client = S7CommClient('192.168.0.1', rack=0, slot=1)
try:
client.connect()
value = client.read_real(db_number=1, offset=0)
print(f"DB1.DBD0 = {value:.4f}")
finally:
client.close()
运行与验证
- PLC 侧准备:在 TIA Portal 中创建 DB1,添加一个 REAL 变量(偏移 0),并启用“优化块访问”关闭(即使用标准访问),否则无法通过 S7comm 直接读取。在设备属性中勾选“允许从远程伙伴(PUT/GET 通信)访问”。
- 修改参数:将代码末尾的
192.168.0.1替换为实际 PLC IP,确认 Rack/Slot 与硬件配置一致。 - 执行:
python s7_read.py - 通过条件:程序输出
DB1.DBD0 = 实际值,且该值与 TIA Portal 监控表中显示的 REAL 值一致(误差在浮点精度范围内)。若输出错误码或超时,检查网络连通性、PLC 权限设置及 DB 偏移量。
常见问题
- 连接失败:确认 PLC IP 可达(
ping),且机架/槽号正确。S7-1200 固件 V4.0 以上默认 Rack=0, Slot=1。 - 读取返回错误:DB 号或偏移量越界、DB 为优化访问(需关闭)、PLC 未开启 PUT/GET 权限。
- 字节序问题:S7comm 使用大端序,代码中已通过
struct.pack('>H')和'>f'正确处理。