1. Snap7 简介
Snap7 是一个开源的西门子 S7 PLC 通信库,支持 S7-200/300/400/1200/1500 全系列。它原生支持 C++,但提供了 Python、C#、Node.js 等语言的绑定。
优点:
- 开源免费,无需额外授权
- 支持多平台(Windows/Linux/macOS)
- 性能好,支持多 PLC 并发连接
- 协议层兼容,不依赖 TIA Portal
2. 环境准备
2.1 安装 python-snap7
pipinstallpython-snap72.2 S7-1200/1500 PLC 端设置
在 TIA Portal 中进行以下配置才能让上位机通过 S7 协议通信:
- 允许 PUT/GET 通信(S7-1500 需特别注意)
- 在设备组态 → 保护与安全 → 编译块时支持仿真/PUT/GET 通信 → 勾选
- 优化块访问 → 标准访问
- DB 块属性中,取消"优化的块访问"(否则直接读写需要按符号名,Snap7 读写按地址偏移)
- 防火墙设置
- 确保 PLC 的以太网口防火墙允许 S7 通信(端口 102)
特别注意:S7-1200 固件 4.0+ 默认关闭 PUT/GET,需在 TIA Portal 中显式开启。
3. 快速开始:连接与读取
3.1 创建客户端并连接
importsnap7# 创建客户端client=snap7.client.Client()# 参数:PLC 的 IP 地址,机架号,槽号# S7-1200 通常 rack=0, slot=1# S7-1500 通常 rack=0, slot=0client.connect('192.168.0.1',0,1)# 检查连接状态ifclient.get_connected():print(f"已连接到 PLC:{client.get_cpu_state()}")else:print("连接失败")3.2 读取 DB 块数据
DB 块是 S7 中最常用的数据存储区,上位机和 PLC 通过 DB 交换数据。
defread_db_block(client,db_number,start_offset,byte_count):""" 读取 DB 块数据 :param client: snap7 客户端 :param db_number: DB 块编号 :param start_offset: 起始字节偏移 :param byte_count: 读取的字节数 :return: bytes 对象 """try:data=client.db_read(db_number,start_offset,byte_count)returndataexceptsnap7.snap7exceptions.Snap7Exceptionase:print(f"读取 DB{db_number}失败:{e}")returnNone# 示例:读取 DB1 从偏移 0 开始的 10 个字节data=read_db_block(client,1,0,10)ifdata:print(f"原始字节:{data.hex()}")3.3 字节数据解析工具函数
PLC 中常见的数据类型及其字节长度:
| 类型 | 长度(字节) | Python 解析方法 |
|---|---|---|
| Bool | 1 (bit) | 位运算提取 |
| Byte | 1 | int.from_bytes() |
| Int | 2 | int.from_bytes() |
| DInt | 4 | int.from_bytes() |
| Real | 4 | struct.unpack(‘>f’) |
| String | 可变 | bytes.decode() |
importstructdefparse_int(data,offset=0):"""解析 S7 Int (有符号 16 位)"""returnint.from_bytes(data[offset:offset+2],byteorder='big',signed=True)defparse_dint(data,offset=0):"""解析 S7 DInt (有符号 32 位)"""returnint.from_bytes(data[offset:offset+4],byteorder='big',signed=True)defparse_real(data,offset=0):"""解析 S7 Real (32 位浮点数)"""returnstruct.unpack('>f',data[offset:offset+4])[0]defparse_word(data,offset=0):"""解析 S7 Word (无符号 16 位)"""returnint.from_bytes(data[offset:offset+2],byteorder='big')defparse_dword(data,offset=0):"""解析 S7 DWord (无符号 32 位)"""returnint.from_bytes(data[offset:offset+4],byteorder='big')defparse_byte_array(data,offset=0,length=1):"""解析 Byte 数组"""returnlist(data[offset:offset+length])# 使用示例raw=client.db_read(1,0,40)# 一次读取 40 字节temp_value=parse_real(raw,0)# Real 类型温度值,偏移 0pressure=parse_int(raw,4)# Int 类型压力值,偏移 4status_flags=parse_dword(raw,6)# DWord 状态标志,偏移 63.4 完整示例:读取 PLC 中的设备运行数据
importsnap7importstructimporttimeclassS7Client:"""西门子 PLC 数据采集客户端封装"""def__init__(self,ip,rack=0,slot=1):self.client=snap7.client.Client()self.ip=ip self.rack=rack self.slot=slotdefconnect(self):try:self.client.connect(self.ip,self.rack,self.slot)print(f"[+] 已连接到{self.ip}")returnTrueexceptExceptionase:print(f"[-] 连接失败:{e}")returnFalsedefdisconnect(self):self.client.disconnect()defread_device_data(self):""" 从 DB10 读取设备运行数据 字节分配(与 PLC 程序员约定好的): Offset 0-3: Real 温度 Offset 4-7: Real 压力 Offset 8-9: Int 转速 Offset 10-13: DInt 累计运行时间(秒) Offset 14: Byte 设备状态(0=停止,1=运行,2=故障) Offset 15: Byte 报警代码 """try:data=self.client.db_read(10,0,16)return{'temperature':struct.unpack('>f',data[0:4])[0],'pressure':struct.unpack('>f',data[4:8])[0],'speed':int.from_bytes(data[8:10],'big',signed=True),'run_time':int.from_bytes(data[10:14],'big',signed=True),'status':data[14],'alarm_code':data[15],}exceptsnap7.snap7exceptions.Snap7Exceptionase:print(f"读取设备数据失败:{e}")returnNonedefread_multi_db(self,db_configs):""" 批量读取多个 DB 块 db_configs: [(db_num, start, size), ...] """results={}fordb_num,start,sizeindb_configs:data=self.client.db_read(db_num,start,size)results[db_num]=datareturnresults# ========== 使用示例 ==========if__name__=='__main__':plc=S7Client('192.168.0.1',rack=0,slot=1)ifplc.connect():try:whileTrue:data=plc.read_device_data()ifdata:print(f"温度:{data['temperature']:.1f}°C | "f"压力:{data['pressure']:.2f}MPa | "f"转速:{data['speed']}RPM | "f"状态:{data['status']}")time.sleep(1)# 每秒采集一次exceptKeyboardInterrupt:print("\n停止采集")finally:plc.disconnect()4. 写入数据到 PLC
除了读取,Snap7 同样支持写入操作。
defwrite_real(client,db_number,offset,value):"""向 PLC DB 块写入 Real 类型数据"""data=struct.pack('>f',value)client.db_write(db_number,offset,data)defwrite_int(client,db_number,offset,value):"""向 PLC DB 块写入 Int 类型数据"""data=int.to_bytes(value,2,byteorder='big',signed=True)client.db_write(db_number,offset,data)defwrite_bool(client,db_number,byte_offset,bit_offset,value):"""向 PLC DB 块写入 Bool 类型数据"""# 先读取当前字节current=client.db_read(db_number,byte_offset,1)byte_val=current[0]ifvalue:byte_val|=(1<<bit_offset)else:byte_val&=~(1<<bit_offset)client.db_write(db_number,byte_offset,bytes([byte_val]))# 写入示例write_real(client,10,0,25.5)# DB10.0 写温度write_int(client,10,8,1500)# DB10.8 写转速write_bool(client,10,20,3,True)# DB10.20.3 写一个启停位5. 读取 I/O 区数据
除了 DB 块,S7 还有 I/Q/M 等存储区。
# 读取输入映像区 PIW(过程映像输入)# 从字节偏移 0 开始,读 4 个字节input_data=client.ab_read(0,4)# 读取输出映像区 PQW(过程映像输出)output_data=client.as_read(0,4)# 读取 M 存储区(中间变量)m_data=client.read_area(snap7.types.Areas.MK,0,0,10)6. 常见问题与排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 连接超时 | IP/机架/槽号配置错误 | 确认 PLC IP,Ping 测试,核对 rack/slot |
DB block not found | DB 号不存在或未下载 | 检查 TIA Portal 中的 DB 编号 |
| wrong DB length | 读取长度超出 DB 实际大小 | 缩小读取范围或增大 DB 大小 |
| 读出的数值不对 | 字节序或数据类型解析错误 | S7 是大端 (>f),确认数据类型长度 |
| 无法连接 S7-1200 v4+ | 未开启 PUT/GET 通信 | 在 TIA Portal 保护设置中勾选允许 |
7. 完整代码获取
本文所有代码已整理为可直接运行的 Python 脚本。