☰
Python通过snap7连接西门子PLC实战:从环境搭建到数据读写避坑
2026/10/21 0:28:01 网站建设 项目流程

简介:这份资源是一份基于 Python 的 snap7 连接 PLC 工程实例,面向刚接触工业通信的新手以及有一定经验的开发人员,帮助解决 Python 与西门子 PLC 建立通信、读写数据时缺少可运行示例的问题。压缩包内共 1 个文件,为单个 py 脚本,整体约 2KB,体量轻巧,核心逻辑集中在连接、读取、写入等常用操作上,便于直接阅读与二次修改。使用前需先安装 python-snap7 库,命令为 pip install python-snap7,随后即可对照脚本理解 snap7 客户端的初始化、地址配置与数据交互流程。目前已有 679 人学习下载,说明该实例在同类需求中具备一定参考价值。读者可从中获得一份可直接运行的通信骨架,快速掌握 PLC 数据读写的基本写法,并在此基础上扩展为采集、监控或调试工具,减少从零摸索协议细节的时间成本。

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

车间里一台 S7-1200 跑了大半年,产线数据一直靠人拿 U 盘从 HMI 上导,某天设备主管丢过来一个压缩包,名字就叫「snap7连接PLCpython工程实例.zip」,问我能不能让 MES 直接读。这个场景在工控圈太常见了:PLC 侧数据现成,上位机侧却卡在「怎么用通用语言把点位读出来」。snap7 就是干这个的——一个开源的 S7 通信库,把西门子 S7 系列 PLC 的 ISO-TCP 协议封装成 C/C++ 接口,再通过 python-snap7 这层绑定暴露给 Python。它不需要在 PLC 侧装任何东西,只要 PLC 开了 PUT/GET 或者你走 S7 协议本身的读写服务,就能从 PC 端直接读写 DB、M、I、Q 区。适合谁?做产线数据采集、设备联网、上位机替代组态软件的工程师,尤其是手头已经有 Python 生态(pandas、pymysql、FastAPI)不想再学一套 WinCC 脚本的人。但这份「工程实例」能不能直接跑,取决于三个东西:PLC 型号与固件、机架槽号、以及你读的地址到底是不是优化块。后面几章我把这套东西从环境搭到排错完整走一遍。

2. 环境搭建与 snap7 库的选型逻辑

2.1 为什么是 python-snap7 而不是别的方案

先说选型。工控上位机读西门子 PLC,常见路子有四条:OPC UA 服务器、S7 协议直连、Modbus 网关、以及厂商自己的通信库。OPC UA 最规范,但要在 PLC 侧或中间机部署 server,S7-1200 还得买授权,小项目成本不划算。Modbus 网关要加硬件,多一层故障点。厂商库绑定 Windows 和特定运行时,跨平台差。snap7 的优势在于纯软件、跨平台、协议层直连,Python 侧一个 pip 包就能用,读 DB 块和 M 区几乎零配置。代价是它不走符号寻址,你得自己算字节偏移,而且对优化块支持有限——这是后面避坑章要重点讲的。

python-snap7 的版本迭代里,0.x 和 1.x 的 API 有差异,1.x 之后把客户端类名统一成Client,连接方法从connect的参数结构也调整过。我一般建议锁一个稳定版本,不要盲目追新,因为工控现场最怕的就是「昨天还能跑今天报错」。安装本身不复杂,但 Windows 和 Linux 的底层库处理方式不同,这是第一个容易翻车的地方。

2.2 安装 python-snap7 与底层库处理

Linux 下相对省心,pip 装完通常自带或能自动拉取 snap7 的共享库。Windows 下 pip 装的只是 Python 绑定,底层snap7.dll要单独放。下面是我在 Ubuntu 和 Windows 上都验证过的安装流程。

# Linux (Ubuntu/Debian) 安装 pip install python-snap7 # 如果运行时报找不到 libsnap7.so,补装系统库 sudo apt-get install snap7 # Windows 安装 Python 绑定 pip install python-snap7 # Windows 需要手动放置 snap7.dll # 从 snap7 官方发布包解压后,把 snap7.dll 放到: # C:\Windows\System32\ (全局) # 或 Python安装目录\Lib\site-packages\snap7\

逻辑说明:python-snap7 是 ctypes 封装,运行时动态加载snap7.dll(Windows)或libsnap7.so(Linux)。pip 只装 Python 层,底层库缺失时报错通常是OSError: cannot load library或FileNotFoundError。参数上没什么可调的,关键是路径要对。Windows 上我习惯把 dll 直接丢进 site-packages 下的 snap7 目录,避免污染 System32,也方便打包时一起带走。

提示:如果现场机器不能联网,pip 装包要提前在有网环境用pip download拉 whl 和依赖,再离线安装。snap7 的 dll 也一并拷过去。

2.3 最小连接测试:先确认能通再谈读数据

装完别急着写业务代码,先跑一个最小连接测试。这一步能排除掉八成「连不上」的问题,把网络、机架槽号、协议开关一次性验证掉。

import snap7 from snap7.util import get_bool, get_int, get_real # 创建客户端实例 client = snap7.client.Client() # 连接参数:IP、机架号(Rack)、槽号(Slot) # S7-1200/1500 通常 Rack=0, Slot=1 # S7-300/400 通常 Rack=0, Slot=2 client.connect('192.168.0.10', 0, 1) # 检查连接状态 print('连接状态:', client.get_connected()) # 读取 PLC 的 CPU 信息,验证通信正常 cpu_info = client.get_cpu_info() print('模块型号:', cpu_info.ModuleTypeName) print('序列号:', cpu_info.SerialNumber) # 断开 client.disconnect()

逻辑说明:connect(ip, rack, slot)三个参数里,IP 是 PLC 的网口地址,rack 和 slot 决定 CPU 在背板上的物理位置。S7-1200/1500 集成网口一般 rack=0、slot=1;S7-300 用 CP 卡或集成口常见 rack=0、slot=2。get_connected()返回布尔值,比 try/except 更直观。get_cpu_info()能读到模块型号和序列号,说明协议层已经握手成功,不只是 TCP 通。如果这一步就失败,先查 IP 能不能 ping 通,再查 PLC 侧是否允许 PUT/GET 通信——S7-1200/1500 默认是关闭的,要在 TIA Portal 里勾选「允许来自远程对象的 PUT/GET 通信访问」。

3. 读写 DB 块与 M 区的完整代码路径

3.1 DB 块读取:字节偏移是绕不过去的坎

snap7 读 DB 块用的是read_area或db_read,核心参数是 DB 号、起始偏移、读取长度。它不认符号名,只认字节地址。比如你在 TIA 里定义了一个 DB1,里面有个 Real 类型的变量Temperature偏移 0,有个 Int 类型的Pressure偏移 4,那读的时候就是按字节算。

import snap7 from snap7.util import get_real, get_int, get_bool client = snap7.client.Client() client.connect('192.168.0.10', 0, 1) # 读取 DB1,从偏移 0 开始读 6 个字节 # 前 4 字节是 Real,后 2 字节是 Int data = client.db_read(1, 0, 6) # 解析数据 temperature = get_real(data, 0) # 偏移 0 的 Real pressure = get_int(data, 4) # 偏移 4 的 Int print(f'温度: {temperature:.2f}, 压力: {pressure}') client.disconnect()

逻辑说明:db_read(db_number, start, size)返回的是bytearray,原始字节流。get_real(data, offset)从指定偏移解析 4 字节浮点数,get_int解析 2 字节有符号整数。偏移量必须和 TIA 里的变量布局严格对应,差一个字节读出来就是乱码。这里有个血泪经验:TIA 里如果 DB 块属性勾了「优化的块访问」,变量在内存里是紧凑排列且不暴露绝对地址的,snap7 读不了。必须把 DB 块属性里的「优化的块访问」取消勾选,编译后才会生成传统偏移布局。

3.2 批量读取与性能取舍

单点读一次两次没问题,但产线采集往往要读几十上百个点位。如果每个点位单独调一次db_read,网络往返次数太多,延迟会累积。常见做法是把同一 DB 块里连续的地址合并成一次读取,再在本地解析。

import snap7 from snap7.util import get_real, get_int, get_bool client = snap7.client.Client() client.connect('192.168.0.10', 0, 1) # 一次性读取 DB1 前 20 字节,覆盖多个变量 raw = client.db_read(1, 0, 20) # 按预定义的地址表解析 result = { 'temperature': get_real(raw, 0), # Real @ 0 'pressure': get_int(raw, 4), # Int @ 4 'speed': get_real(raw, 6), # Real @ 6 'run_flag': get_bool(raw, 10, 0), # Bool @ 10.0 'alarm_flag': get_bool(raw, 10, 1), # Bool @ 10.1 } print(result) client.disconnect()

逻辑说明:get_bool(data, byte_offset, bit_offset)第三个参数是位偏移,0 到 7。Bool 变量在 DB 里按位存储,同一个字节可以放 8 个 Bool。批量读取的关键是「一次网络请求覆盖尽量多的连续地址」,但也不能无脑读一大片,因为 PLC 的通信缓冲区有限,单次读取长度一般建议不超过 200 字节,超了可能被拒绝或分片。参数上,db_read的 size 我一般控制在 100 字节以内,既减少往返又留余量。

3.3 M 区、I 区、Q 区的读写差异

除了 DB 块,M 区(位存储区)、I 区(输入)、Q 区(输出)也是常读的对象。snap7 用read_area统一处理,区别在 area 参数。

import snap7 from snap7.util import get_bool, get_int from snap7.types import Areas client = snap7.client.Client() client.connect('192.168.0.10', 0, 1) # 读 M 区:从 M0 开始读 4 字节 m_data = client.read_area(Areas.MK, 0, 0, 4) m_value = get_int(m_data, 0) print('M0 的值:', m_value) # 读 I 区:从 I0 开始读 1 字节 i_data = client.read_area(Areas.PE, 0, 0, 1) i_bool = get_bool(i_data, 0, 0) print('I0.0 状态:', i_bool) # 读 Q 区:从 Q0 开始读 1 字节 q_data = client.read_area(Areas.PA, 0, 0, 1) q_bool = get_bool(q_data, 0, 0) print('Q0.0 状态:', q_bool) client.disconnect()

逻辑说明:read_area(area, db_number, start, size)里,area 用Areas枚举,MK是 M 区,PE是输入区,PA是输出区,DB是数据块。读 DB 时 db_number 填块号,读 M/I/Q 时 db_number 填 0。I 区和 Q 区的地址是 PLC 自己刷新的,读到的就是当前物理输入输出状态,不需要额外配置。M 区则完全由程序控制,适合做中间变量交换。

3.4 写入操作与安全边界

读之外还有写。写 DB 块用db_write,写 M 区用write_area。但写操作在产线上要极其谨慎,写错地址可能直接让设备动作。

import snap7 from snap7.util import set_real, set_bool client = snap7.client.Client() client.connect('192.168.0.10', 0, 1) # 写 Real 到 DB1 偏移 0 data = bytearray(4) set_real(data, 0, 25.5) client.db_write(1, 0, data) # 写 Bool 到 M10.0 bit_data = bytearray(1) set_bool(bit_data, 0, 0, True) client.write_area(snap7.types.Areas.MK, 0, 10, bit_data) client.disconnect()

逻辑说明:set_real(data, offset, value)把浮点数打包进 bytearray,set_bool(data, byte_offset, bit_offset, value)处理位。写操作前一定要确认目标地址在 PLC 程序里是「可写」的,有些 DB 块被程序循环刷新,你写进去下一秒就被覆盖。安全边界上,我一般会在代码里加一层地址白名单,只允许写特定偏移,避免手滑写错。另外写 M 区要特别注意,M 区很多位被系统或程序占用,写之前查清楚。

4. 避坑与常见问题排查

4.1 连接超时或拒绝:先查这三处

现象:client.connect()抛异常,提示 timeout 或 connection refused。原因通常有三个:IP 不通、PLC 侧 PUT/GET 没开、机架槽号填错。解决顺序是先用 ping 确认网络层通,再进 TIA Portal 检查 CPU 属性里的「保护」选项卡,勾选「允许来自远程对象的 PUT/GET 通信访问」,最后核对 rack/slot。S7-1200/1500 集成口一般是 0/1,S7-300 用 CP343 卡常见 0/2。如果还不行,查 PLC 的防火墙或网段是否隔离。

4.2 读出来全是 0 或乱码:优化块在作怪

现象:连接成功,db_read不报错,但解析出来的值全是 0 或者明显不对。原因九成是 DB 块开了「优化的块访问」。这个选项在 TIA 里默认勾选,变量按类型紧凑排列,不生成绝对地址,snap7 按传统偏移读自然读不到。解决办法是右键 DB 块 → 属性 → 取消勾选「优化的块访问」→ 重新编译下载。改完后变量偏移会重新分配,记得同步更新代码里的偏移量。

4.3 批量读取报错:单次长度超限

现象:读少量字节正常,读一大片就报错或返回空。原因是 PLC 通信缓冲区有限,单次请求的 PDU 有上限。S7-1200 的 PDU 一般 240 字节左右,扣除协议头,实际数据区更小。解决办法是把大块拆成多次小批量读,每次控制在 100 字节以内。如果点位分散在不同 DB 块,就按块分组读,不要跨块合并。

4.4 程序跑几天后断连:心跳与重连

现象:刚启动正常,跑一两天后get_connected()返回 False,读写全失败。原因是长时间空闲连接被 PLC 或中间网络设备回收。解决办法是加心跳机制,定时读一个固定地址保活,同时在每次读写前检查连接状态,断了就重连。重连要带退避,不要死循环猛连,否则可能触发 PLC 的连接数保护。

import time import snap7 def ensure_connected(client, ip, rack, slot): if not client.get_connected(): try: client.connect(ip, rack, slot) except Exception as e: print('重连失败:', e) time.sleep(5) # 退避 5 秒 return False return True client = snap7.client.Client() while True: if ensure_connected(client, '192.168.0.10', 0, 1): try: data = client.db_read(1, 0, 4) # 处理数据... except Exception as e: print('读取异常:', e) time.sleep(1)

逻辑说明:ensure_connected先查状态再决定是否重连,重连失败时 sleep 退避,避免高频重试打爆 PLC 连接资源。心跳周期我一般设 1 秒,既能保活又不会给 PLC 太大压力。

4.5 多线程并发读:别共用同一个 Client

现象:单线程正常,开多线程后随机报错或数据错乱。原因是 snap7 的 Client 实例不是线程安全的,多个线程共用一个连接会互相干扰。解决办法是每个线程独立建 Client,或者用连接池加锁串行化。我一般倾向单线程采集加队列分发,简单可靠,工控场景对实时性要求没那么极端,1 秒一轮足够覆盖大多数产线。

5. 从能读到好用:封装与验证的几个技巧

把上面的东西跑通只是第一步,真正上线还要解决「好用」的问题。我自己的习惯是把 snap7 的读写封装成一个点位表驱动的采集器,而不是到处散落db_read。具体做法是维护一张 CSV 或数据库表,每行记录点位名、区域类型、DB 号、偏移、数据类型、缩放系数,采集器读表后自动拼装读取请求和解析逻辑。这样新增点位不用改代码,改表就行。

验证环节有个技巧:不要只信 Python 侧读到的值,要在 TIA 的监控表里同时看同一个地址。两边对不上,先查偏移,再查数据类型,最后查字节序。snap7 默认按大端解析,和西门子 PLC 内存布局一致,一般不用调,但如果你中间经过了某些网关或转换层,字节序可能被改。另一个验证手段是写一个已知值到 DB 块,再用 Python 读回来比对,闭环确认读写链路都对。

数据类型上,除了 Real、Int、Bool,还有 DInt、Word、DWord、String 等。String 比较特殊,前两个字节是长度和当前字符数,后面才是字符内容,解析时要跳过前两字节。我见过有人直接把 String 当字节数组读然后 decode,结果多出两个乱码字符,就是没处理这个头。

最后说一个习惯:任何写操作上线前,先在测试 PLC 或者仿真环境跑一遍。S7-1200 可以用 PLCSIM 做离线验证,虽然 PLCSIM 对 snap7 的支持要看版本,但至少能验证地址偏移和数据类型解析逻辑。真机写操作一定要有急停或旁路预案,别问我怎么知道的,一次误写 M 区导致输送线反转的教训够记一辈子。希望帮到你。

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

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

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

立即咨询