☰
Python通过snap7连接西门子PLC:从工程实例到稳定采集脚本
2026/10/21 0:15:42 网站建设 项目流程

简介:这份资源是一份基于 snap7 库实现 Python 与 PLC 通信的工程实例,面向刚接触工业自动化通信的新手,以及希望快速验证读写逻辑的进阶开发人员。它解决的是 Python 侧如何稳定连接 PLC、完成数据交互这一常见入门难题,可作为学习西门子 PLC 通信的起步范例。压缩包内共 1 个文件,为单个 py 脚本,整体约 2KB,体量轻巧,便于直接阅读与二次修改。脚本围绕连接建立、数据读取与写入命令展开,读者可据此理解 snap7 的调用方式与基本参数配置,并在此基础上扩展自己的采集或控制逻辑。使用前需先安装依赖,执行 pip install python-snap7 即可。目前已有 679 人学习,说明该实例在同类入门资料中具有一定参考价值,适合作为动手实践的起点。

1. 从一份 snap7 连接 PLC 的 Python 工程实例说起

车间里一台 S7-1200 跑着产线逻辑,你想用 Python 把它的 DB 块数据每 500ms 抓一次,顺手写进本地时序库做看板。搜到一份名为snap7连接PLCpython工程实例.zip的压缩包,解压后大概率是几个.py文件加一份说明——这就是本文要拆的东西:用 snap7 这个开源库,在 Python 里跟西门子 PLC 建立 S7 通信,读写 DB、M、I、Q 区。它解决的是「不想装组态软件、不想买 OPC 授权,只想用脚本把 PLC 数据拿出来」这类需求,适合做设备数据采集、边缘网关、测试工装、上位机原型的工程师。但这份实例能不能直接跑通,取决于你对机架槽号、DB 编号、数据类型对齐这几件事的理解,下面按能复现的路径讲透。

2. snap7 与 S7 通信:先搞懂连的是什么、为什么这么连

2.1 snap7 在协议栈里的位置

西门子 S7 系列 PLC 走的是 S7comm 协议,跑在 ISO-on-TCP(端口 102)之上。snap7 是一个 C++ 写的开源库,把 S7comm 的握手、PDU 协商、读写请求封装成了几个函数,Python 侧通过python-snap7这个 ctypes 包装调用它。所以你装python-snap7只是装了 Python 壳,底层还需要snap7.dll(Windows)或libsnap7.so(Linux)这个原生库,这是新手第一个翻车点——import snap7成功不代表能连上,运行时报Can't find snap7 library才是常态。

它和 OPC UA、Modbus TCP 的区别值得说清:Modbus TCP 是寄存器寻址,S7 是区域+偏移寻址;OPC UA 需要 PLC 侧开服务或加网关,snap7 直接打 102 端口,只要 PLC 允许 PUT/GET 通信就能读写。代价是 snap7 不做数据建模,DB 里第几个字节是什么类型,全靠你自己对齐。

2.2 连接前必须确认的 PLC 侧设置

在写第一行 Python 之前,先在 TIA Portal 里把这几项确认掉,否则代码再对也连不上:

配置项位置要求
允许 PUT/GET 通信PLC 属性 → 保护与安全 → 连接机制勾选
访问级别同上完全访问(或至少允许读写)
机架号 Rack硬件组态S7-1200/1500 通常为 0
槽号 Slot硬件组态S7-1200 为 1,S7-1500 为 0 或 1,S7-300 常为 2
优化块访问DB 属性采集用的 DB 建议关闭优化访问

「优化块访问」这一项是血泪经验:DB 默认开启优化访问后,变量没有固定偏移,snap7 按字节偏移读会读到错位数据。做采集的 DB 一定要取消勾选,让变量按声明顺序分配偏移。

2.3 最小可跑通的连接与读取

先装依赖,再写最小验证脚本。原生库的放置方式在代码后说明。

pip install python-snap7
import snap7 from snap7.util import get_real, get_int, get_bool # 创建客户端实例 client = snap7.client.Client() # connect(address, rack, slot) # address: PLC 的 IP;rack: 机架号;slot: 槽号 client.connect('192.168.0.10', 0, 1) # 确认连接状态,这一步别省,后面所有操作都依赖它 print('connected:', client.get_connected()) # 读 DB1,起始偏移 0,读 16 个字节 data = client.db_read(1, 0, 16) # 按声明类型解析:偏移0 是 Real(4字节),偏移4 是 Int(2字节),偏移6 是 Bool temp = get_real(data, 0) count = get_int(data, 4) running = get_bool(data, 6, 0) # 第6字节的第0位 print(temp, count, running) client.disconnect() client.destroy()

逻辑说明:db_read(db_number, start, size)返回的是bytearray,snap7 不替你解析类型,必须用snap7.util里的get_real/get_int/get_bool按偏移取值。参数上,start是字节偏移不是变量序号,size要覆盖你所有要读的变量总长度,读少了后面解析会越界。get_bool(data, byte_index, bit_index)的 bit_index 是 0~7,对应字节内第几位。

原生库放置:Windows 下把snap7.dll放到 Python 安装目录或脚本同目录;Linux 下libsnap7.so放/usr/lib,或设环境变量LD_LIBRARY_PATH。这一步不做,client.connect会直接抛库加载异常。

3. 把实例改造成能长期跑的采集脚本

3.1 批量读取与 PDU 大小约束

单次db_read能读多少字节,受 PDU 协商结果限制。S7-1200 协商后 PDU 常在 240 字节左右,一次读超过这个长度会报错或截断。正确做法是先查 PDU,再分块读。

# 查询协商后的 PDU 长度 pdu = client.get_pdu_length() print('pdu:', pdu) # 单次可读的有效负载约为 pdu - 18(协议头开销) max_payload = pdu - 18 def read_db_chunked(client, db_num, start, total): """按 PDU 限制分块读取,返回完整 bytearray""" result = bytearray() offset = start remaining = total while remaining > 0: chunk = min(max_payload, remaining) result += client.db_read(db_num, offset, chunk) offset += chunk remaining -= chunk return result

参数说明:get_pdu_length()返回的是字节数,不同型号差异明显,S7-300 常见 240,S7-1500 可到 960。max_payload减 18 是留协议头余量,保守但稳。分块读时每块的start要累加,别用固定偏移,否则第二块开始数据全错。

3.2 数据类型对齐与字节序

S7 是大端序,snap7 的util函数已经处理了字节序,但前提是你偏移算对。常见类型的字节长度和解析函数对应关系:

类型字节数解析函数备注
Bool1位get_bool需给字节偏移+位号
Byte1data[i]直接取
Int2get_int有符号
DInt4get_dint有符号
Real4get_real浮点
String变长get_string前两字节是长度头

写数据时对应set_real/set_int/set_bool,注意set_bool是就地修改 bytearray 再整体写回,不能只写一位。

from snap7.util import set_real # 写 Real 到 DB2 偏移 0 buf = bytearray(4) set_real(buf, 0, 3.14) client.db_write(2, 0, buf)

3.3 断线重连与异常兜底

产线网络抖动、PLC 重启都会让连接断掉,裸脚本一断就崩。加一层重连和异常捕获是长期运行的最低要求。

import time import snap7 from snap7.exceptions import Snap7Exception def ensure_connected(client, ip, rack, slot, retries=3): """断线检测 + 重连,返回是否可用""" if client.get_connected(): return True for i in range(retries): try: client.connect(ip, rack, slot) if client.get_connected(): return True except Snap7Exception as e: print(f'retry {i+1} failed: {e}') time.sleep(2) return False # 主循环里每轮先检查连接 while True: if not ensure_connected(client, '192.168.0.10', 0, 1): time.sleep(5) continue try: data = client.db_read(1, 0, 16) # ... 解析入库 except Snap7Exception as e: print('read error:', e) client.disconnect() # 主动断开,下轮触发重连 time.sleep(0.5)

逻辑说明:get_connected()只反映本地状态,PLC 侧断开后它可能仍返回 True,所以真正的兜底是db_read抛异常时主动disconnect(),让下一轮走重连分支。retries和sleep按现场网络质量调,车间环境建议 3 次、间隔 2 秒。

4. 避坑与排查:连不上、读错位、跑一会儿就断

4.1 报 Can't find snap7 library

现象:import snap7正常,client.connect抛Snap7Exception: Can't find snap7 library。 原因:只装了 Python 包,没放原生库,或库位数与 Python 不匹配(64 位 Python 配了 32 位 dll)。 解决:确认 Python 位数python -c "import struct;print(struct.calcsize('P')*8)",下载对应位数的 snap7 原生库,Windows 放脚本同目录或C:\Windows\System32,Linux 放/usr/lib并ldconfig。

4.2 连接超时或拒绝

现象:connect卡住后抛超时,或立即被拒绝。 原因:IP 不通、102 端口被防火墙拦、PLC 未勾选 PUT/GET、机架槽号填错。 解决:先ping通,再telnet IP 102测端口;回 TIA 确认保护设置;S7-1200 槽号填 1,S7-300 填 2,逐个试。别在代码里反复重试掩盖配置问题。

4.3 读回来的数值明显不对

现象:Real 读出来是天文数字或极小值,Int 是负数乱跳。 原因:DB 开了优化访问导致偏移不固定,或偏移算错、类型长度对不上。 解决:采集 DB 关闭优化访问;用 TIA 的「监控表」对照实际值,逐个变量核对偏移;Real 占 4 字节,别按 2 字节算下一个变量的起点。

4.4 跑几小时后连接失效

现象:脚本运行一段时间后db_read持续报错,重连也失败。 原因:PLC 侧对空闲连接有超时回收,或网络中间设备断流;也可能是客户端句柄泄漏。 解决:加心跳读(每轮读一个固定字节),让连接保持活跃;异常时disconnect()再connect(),不要只重试读;长时间运行定期destroy()重建客户端实例。

4.5 写入不生效

现象:db_write没报错,但 PLC 里值没变。 原因:写的是优化访问 DB,或写到了只读区域,或 PLC 程序在扫描周期里把值覆盖了。 解决:确认 DB 可写且非优化;避开 PLC 逻辑正在写的变量;写入后回读一次校验,别信「没报错就是成功」。

5. 进阶:把采集脚本做成可配置、可验证的工程

一份能长期用的采集脚本,不该把 IP、DB 号、偏移硬编码在代码里。我一般会抽一个配置表,把「变量名 → DB号 → 偏移 → 类型」列成结构,循环解析,这样加变量只改配置不改逻辑。

# 变量映射表:名称, DB号, 偏移, 类型 TAGS = [ ('temperature', 1, 0, 'real'), ('count', 1, 4, 'int'), ('running', 1, 6, 'bool:0'), ] def parse_tags(data, tags): """按映射表解析 bytearray,返回字典""" from snap7.util import get_real, get_int, get_bool out = {} for name, db, off, typ in tags: if typ == 'real': out[name] = get_real(data, off) elif typ == 'int': out[name] = get_int(data, off) elif typ.startswith('bool'): bit = int(typ.split(':')[1]) out[name] = get_bool(data, off, bit) return out

验证方法上,别只信脚本打印的值。用 TIA 的监控表强制改一个变量,看脚本下一轮读到的值是否跟着变,这是最直接的端到端校验。再就是拿秒表对时间戳,确认采集周期和sleep设置一致,别让解析耗时把周期拖长。

一个具体技巧:把每次读到的原始bytearray用十六进制打印出来,和 TIA 监控表里的值手工对一遍。Real 3.14 的十六进制是4048F5C3,对上了说明偏移和字节序都对。这个笨办法帮我定位过好几次「类型对但偏移差一位」的玄学问题。

最后说个习惯:任何要上产线的采集脚本,先在实验室 PLC 上跑满 24 小时再部署,重点看断线重连和内存增长。我吃过亏,一个没加心跳的脚本在实验室跑两小时没事,上现场四小时就断,排查半天才发现是交换机空闲回收。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询