简介:这是一款面向自动化工程师与PLC程序开发者的西门子S7协议模拟工具,基于开源snap7库构建,可在无真实硬件的情况下模拟S7系列PLC通信,解决开发调试中设备难获取、环境受限的痛点。模拟器集成C#程序,支持DB数据块读写模拟,并可从Excel读取变量定义,自动生成后台数据与UI控件,简化界面设计与数据绑定,让开发者专注逻辑实现。资源包共185个文件,以dll动态库、xml配置、pdb调试符号、cs源码、nupkg包及xaml界面文件为主,另有xls变量表、exe可执行程序与sln解决方案,整体约39.72MB,结构完整便于二次开发。目前已有2168人学习下载,适合个人学习、小型项目验证及团队快速搭建测试环境,开源免费特性也支持自由修改与扩展。
1. 从一台连不上的 S7-1200 说起:为什么我选了 snap7 模拟器
去年帮一个做产线数据采集的团队搭测试环境,现场有一台 S7-1200,程序还没下进去,上位机那边已经催着要联调。真机不在手边,拿 PLC 又得等排期,最后是用一台旧笔记本跑了个 S7 协议模拟器顶上的。当时试过好几个方案,要么只支持 S7-300/400 的老协议,要么配置复杂到得先啃一遍 ISO-on-TCP 的文档。后来换成基于开源 snap7 的模拟器,才算把这事按下去。
snap7 本身是一个开源的 S7 通信库,支持 S7-200、S7-300、S7-400、S7-1200、S7-1500 这一系列 PLC 的以太网通信,跨 Windows 和 Linux,C++ 写的,同时提供 C、C#、Python、Java 等语言的封装。它最实用的地方在于,除了当客户端去连真 PLC,还能反过来当服务端,模拟一台 PLC 的 S7 协议响应。对于手头没有真机、或者不想在产线上乱试的开发者来说,这就是一个能跑通全链路的测试靶子。
这套东西适合谁?做上位机、SCADA、MES 对接的开发者,尤其是刚开始接触西门子 S7 协议、需要先跑通读写 DB 块和 M 区的新手。装不好会怎样?最典型的就是客户端连上去报“连接被拒绝”或者“ISO 连接超时”,然后你以为是代码写错了,其实是模拟器的机架槽号、端口或者 DB 块没配对。这篇就把我从下载到跑通、再到排错的完整过程拆开写一遍。
2. 环境准备与版本选择:snap7 模拟器装之前要定的事
2.1 操作系统与运行环境怎么选
snap7 的模拟器服务端,官方源码包里带了一个server示例,编译出来就是一个可执行文件。我一般优先在 Linux 上跑,因为依赖少、编译快,Ubuntu 20.04 和 22.04 都验证过。Windows 上也能跑,但需要先装好对应的编译工具链,或者直接用预编译的 DLL 配合 Python 封装来起服务。
如果你只是想在本地快速验证,不想折腾编译,那用 Python 的python-snap7包是最省事的。它自带了对 snap7 库的绑定,装完就能用snap7.server.Server()起一个模拟服务端。Python 版本建议 3.8 到 3.11,太新的 3.12 在某些平台上会遇到 wheel 还没跟上的情况,我一般会避开。
提示:不管用哪种方式,先确认你的机器能监听 102 端口。S7 协议默认走 TCP 102,这个端口在 Linux 上属于特权端口,非 root 用户直接绑定会报权限错误。
2.2 snap7 版本差异与下载渠道
snap7 的版本号不复杂,但不同版本对 S7-1200/1500 的支持程度有差别。早期 1.2.x 系列对 S7-1500 的兼容性一般,读写优化块时容易出问题。我一般用 1.4.2 或更新的版本,这个版本对 S7-1200/1500 的兼容性稳定很多,社区里反馈的坑也少。
下载渠道上,源码可以从 snap7 的官方站点获取,也可以直接用包管理器装 Python 封装。Linux 下如果用 apt,可以搜snap7相关的包,但版本可能偏旧,我一般还是从源码编译或者用 pip 装python-snap7。Windows 下如果不想编译,直接 pip 装python-snap7就行,它会自动带上对应的 DLL。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| snap7 核心库 | 1.4.2 | 对 S7-1200/1500 兼容性较好 |
| python-snap7 | 1.3 及以上 | 与核心库版本对应 |
| Python | 3.8–3.11 | 避开 3.12 的 wheel 兼容问题 |
| 操作系统 | Ubuntu 20.04/22.04 | 依赖少,编译顺利 |
2.3 依赖检查与端口占用排查
装之前先做两件事:确认 Python 和 pip 可用,确认 102 端口没被占用。Linux 下用ss -tlnp | grep 102看一眼,如果已经有进程在监听,要么停掉它,要么改模拟器的监听端口。Windows 下用netstat -ano | findstr :102。
Python 环境我习惯用 venv 隔离,避免和系统里的包打架。命令很简单,建一个目录,进去之后python3 -m venv venv,然后source venv/bin/activate。Windows 下是venv\Scripts\activate。这一步不做也行,但后面装包多了容易乱,我一般都会建。
3. 安装 snap7 与 python-snap7:从源码到可运行服务端
3.1 Linux 下从源码编译 snap7
如果你要用 C/C++ 直接调 snap7,或者想自己编译出server可执行文件,那就走源码编译这条路。先从官方渠道拿到源码包,解压后进入snap7/src目录。编译命令不复杂,但有几个参数要注意。
# 进入源码的 Unix 构建目录 cd snap7/src/snap7-full-1.4.2/build/unix # 编译并安装到系统目录 make -f x86_64_linux.mk all sudo make -f x86_64_linux.mk install第一行是切到 Unix 构建目录,snap7 的源码包里按平台分好了 makefile,x86_64 的机器用x86_64_linux.mk。第二行编译,第三行安装,默认会装到/usr/lib和/usr/include下。装完之后可以用ldconfig -p | grep snap7确认库文件已经注册到系统里。
注意:如果你的机器是 ARM 架构,比如某些开发板,makefile 要换成对应的
arm_linux.mk,否则编译出来的库架构不对,后面链接会报错。
3.2 用 pip 安装 python-snap7 封装
大部分做上位机开发的,其实不需要自己编译核心库,直接用 pip 装python-snap7就够了。它会自动处理核心库的依赖,Linux 下会带上.so文件,Windows 下会带上.dll。
# 在虚拟环境里安装 pip install python-snap7==1.3 # 验证安装是否成功 python -c "import snap7; print(snap7.__version__)"第一行指定版本安装,1.3 是我在多个项目里验证过的稳定版本。第二行导入并打印版本号,如果能正常输出,说明核心库和 Python 绑定都装好了。如果报OSError: cannot load library,通常是核心库没找到,Linux 下检查/usr/lib里有没有libsnap7.so,Windows 下检查 DLL 是否在 PATH 里。
3.3 启动一个最小可用的模拟服务端
装完之后,先别急着写复杂的读写逻辑,起一个最小的服务端,确认它能监听端口、能接受连接。下面这段代码就是一个最简的 S7 模拟服务端。
import snap7 from snap7.server import Server from snap7.type import SrvArea # 创建服务端实例 server = Server() # 注册一个 DB1 区域,大小 100 字节 server.register_area(SrvArea.DB, 1, 100) # 注册 M 区,大小 100 字节 server.register_area(SrvArea.MK, 0, 100) # 启动服务,监听 102 端口 server.start_to("0.0.0.0", 102) print("S7 模拟服务端已启动,监听 102 端口") input("按回车键停止服务...\n") # 停止服务 server.stop() server.destroy()register_area的第一个参数是区域类型,SrvArea.DB表示数据块,SrvArea.MK表示 M 区(标志位区)。第二个参数是区域编号,DB1 就填 1,M 区一般填 0。第三个参数是区域大小,单位是字节,这里给 100 字节够做基本测试。start_to的第一个参数是监听地址,0.0.0.0表示所有网卡都接受连接,第二个参数是端口,S7 默认 102。
跑起来之后,服务端就在后台等着客户端来连了。如果这一步就报错,先看端口是不是被占用,再看权限够不够。
4. 配置模拟器参数:机架槽号、DB 块与连接细节
4.1 机架号和槽号的对应关系
S7 协议里,客户端连接 PLC 时要指定机架号和槽号。真机上,S7-1200/1500 的机架号一般是 0,槽号是 1;S7-300/400 的槽号可能是 2。模拟器这边,server.start_to并不直接管机架槽号,它只是监听端口,机架槽号的匹配是在客户端连接时由 snap7 库内部处理的。
但这里有个坑:如果你用python-snap7的客户端去连模拟器,client.connect方法要传机架号和槽号。如果传错了,模拟器这边会直接拒绝连接,客户端报CPU : Connection error。我一般会在模拟器这边把机架槽号固定成 0 和 1,客户端也传 0 和 1,两边对上就行。
import snap7 from snap7.type import SrvArea client = snap7.client.Client() # 连接模拟器,IP 是本机,机架 0,槽 1 client.connect("127.0.0.1", 0, 1) # 读 DB1 的前 10 个字节 data = client.db_read(1, 0, 10) print("DB1 前 10 字节:", data) client.disconnect()connect的第二个和第三个参数就是机架号和槽号,这里传 0 和 1。db_read的三个参数分别是 DB 编号、起始偏移、读取长度。如果模拟器那边注册的 DB1 大小是 100 字节,这里读 10 字节没问题;如果读超过注册大小的范围,会报Address out of range。
4.2 DB 块和 M 区的注册策略
模拟器启动时注册的区域,决定了客户端能读写哪些地址。我一般会按实际项目的需求来注册,比如项目里用到了 DB1、DB2 和 M 区,那就在模拟器里把这三个区域都注册上。
# 注册多个 DB 块 server.register_area(SrvArea.DB, 1, 200) # DB1,200 字节 server.register_area(SrvArea.DB, 2, 500) # DB2,500 字节 # 注册 M 区 server.register_area(SrvArea.MK, 0, 200) # M 区,200 字节 # 注册输入区(I 区)和输出区(Q 区) server.register_area(SrvArea.PE, 0, 100) # 输入区 100 字节 server.register_area(SrvArea.PA, 0, 100) # 输出区 100 字节SrvArea.PE对应输入区,SrvArea.PA对应输出区。注册的大小要覆盖客户端可能访问的最大地址范围,否则客户端读到一个没注册的地址就会报错。我一般会稍微注册大一点,比如项目里最大用到 DB1 的 150 字节,那就注册 200 字节,留点余量。
提示:注册区域的大小是字节数,不是字数。S7 协议里 DB 块是按字节寻址的,读
DB1.DBW0是读两个字节,读DB1.DBD0是读四个字节,注册的时候按字节算。
4.3 监听地址与多网卡场景
如果机器上有多个网卡,start_to的第一个参数可以指定具体监听的 IP。比如你只想让模拟器在 192.168.1.100 这个网卡上接受连接,就传这个 IP。如果传0.0.0.0,所有网卡都能连。
多网卡场景下有个常见问题:客户端连的 IP 和模拟器监听的 IP 不在同一个网段,连接会超时。我一般会先用ip addr或ifconfig确认机器的 IP,然后模拟器和客户端都用同一个 IP。如果是本机测试,直接用127.0.0.1最省事。
5. 避坑与排查:模拟器连不上、读写出错的常见原因
5.1 连接被拒绝:端口没监听或防火墙拦截
现象是客户端调connect之后报Connection refused或者ISO : Connection error。原因通常是模拟器没启动成功,或者 102 端口被防火墙拦了。Linux 下先用ss -tlnp | grep 102确认端口在监听,如果没有,说明模拟器启动失败,回头看启动时的报错。如果有监听但客户端还是连不上,检查防火墙规则,sudo ufw status看一眼,必要时放行 102 端口。
5.2 连接超时:IP 或机架槽号不对
现象是客户端一直卡在连接阶段,最后报超时。原因一般是 IP 填错了,或者机架槽号和模拟器不匹配。先 ping 一下模拟器所在机器的 IP,确认网络通。然后确认客户端connect传的机架槽号,S7-1200/1500 用 0 和 1,S7-300/400 用 0 和 2。模拟器这边虽然不直接校验机架槽号,但 snap7 库内部会按这个参数组包,传错了服务端可能不响应。
5.3 地址越界:读写的 DB 块没注册
现象是客户端读某个 DB 块时,模拟器返回错误,客户端报Address out of range或者Function not implemented。原因是模拟器启动时没注册这个 DB 块,或者注册的大小不够。比如客户端读 DB3,但模拟器只注册了 DB1 和 DB2,那就会报错。解决办法是在模拟器启动时把用到的 DB 块都注册上,大小留够余量。
5.4 权限问题:非 root 用户绑定 102 端口失败
现象是模拟器启动时报Permission denied,尤其是 Linux 下非 root 用户直接绑 102 端口。原因是 102 是特权端口,普通用户没权限绑定。解决办法有两个:一是用sudo跑模拟器,二是改监听端口,比如改成 1102,客户端也连 1102。我一般倾向于改端口,避免用 root 跑服务。
5.5 库版本不匹配:python-snap7 和核心库版本对不上
现象是导入snap7时报OSError: cannot load library或者运行时崩溃。原因是python-snap7的版本和核心库的版本不匹配。比如 pip 装的是 1.3,但系统里编译的是 1.2 的核心库,两者接口对不上。解决办法是统一版本,要么都用 pip 装,要么都从源码编译,别混着来。
6. 装完怎么验证:用客户端读写一遍才算跑通
装完、配完、排完错,最后一步是验证。我一般会写一个最小的读写测试,把 DB 块和 M 区都过一遍,确认数据能写进去也能读出来。下面这段代码就是完整的验证脚本。
import snap7 from snap7.server import Server from snap7.type import SrvArea import threading import time # 启动模拟服务端 server = Server() server.register_area(SrvArea.DB, 1, 100) server.register_area(SrvArea.MK, 0, 100) server.start_to("127.0.0.1", 102) # 等一秒让服务端就绪 time.sleep(1) # 客户端连接 client = snap7.client.Client() client.connect("127.0.0.1", 0, 1) # 写 DB1 的前 4 个字节 client.db_write(1, 0, bytearray([0x11, 0x22, 0x33, 0x44])) # 读回来验证 data = client.db_read(1, 0, 4) print("DB1 写入后读回:", data) assert data == bytearray([0x11, 0x22, 0x33, 0x44]), "DB1 读写不一致" # 写 M 区 client.mb_write(0, 1, bytearray([0xAA])) m_data = client.mb_read(0, 1) print("M 区写入后读回:", m_data) assert m_data == bytearray([0xAA]), "M 区读写不一致" print("读写验证通过") client.disconnect() server.stop() server.destroy()这段脚本先起服务端,注册 DB1 和 M 区,然后客户端连上去,往 DB1 写四个字节再读回来比对,往 M 区写一个字节再读回来比对。两个断言都过了,说明模拟器的读写链路是通的。如果断言失败,先看写入和读回的字节是不是一样,不一样的话检查注册的区域大小和偏移量。
验证的时候有几个细节要注意。第一,服务端启动后要等一小会儿再让客户端连,不然可能连不上,我一般sleep(1)。第二,db_write的第三个参数是bytearray,不是普通列表,传列表在某些版本上会报类型错误。第三,读写完成后记得disconnect和stop,不然端口会一直被占着,下次启动就报端口占用。
从那以后我每次搭完模拟器,都强制走一遍这个读写验证,不看到两个断言通过不算完。这套流程帮我省了不少来回排查的时间,希望帮到你。
本文还有配套的精品资源,点击获取