4、上位机与PLC通信实战(一):使用Python的Snap7库实现与西门子PLC的读写操作

好,咱们今天正式进入实战环节。

前面几章讲了网络基础、协议栈、硬件接线,说实话,都是铺垫。真正让分拣机动起来,还得靠上位机和PLC之间的数据对话。这一章,我带你用Python的Snap7库,亲手实现与西门子PLC的读写操作。

我个人习惯把这一步叫做「打通任督二脉」。为什么?因为一旦你能从电脑上读到PLC的数据,或者往PLC里写一个值,那整个自动化系统的灵魂就活了。你想想看,远程监控、数据采集、配方管理,全都建立在这个基础上。

4.1 Snap7库是什么?为什么选它?

Snap7是一个开源的通信库,专门用来和西门子S7系列PLC打交道。它支持S7-200、300、400、1200、1500,几乎覆盖了市面上主流的型号。

我最早接触它是在一个汽车零部件的产线项目上。当时甲方要求用Python做数据采集,我试过LibNoDave、pyads,最后发现Snap7最顺手。说白了,它封装了底层复杂的ISO-on-TCP协议,你只需要调用几个函数就能完成读写。

核心优势:

  • 纯Python实现,跨平台(Windows/Linux/Raspberry Pi都行)
  • 支持多种数据类型:bool、int、real、string等
  • 读写速度快,实测在100ms以内完成一次DB块读取
  • 社区活跃,遇到问题基本能找到答案

4.2 环境准备:安装Snap7

安装很简单,一行命令搞定:

pip install python-snap7

嗯,这里要注意。如果你用的是Linux系统,比如树莓派,还需要安装Snap7的底层库。Windows下一般不需要额外操作。

小提示:我在树莓派上踩过坑。记得先运行 sudo apt-get install libsnap7-dev,否则会报找不到库文件的错误。

验证安装是否成功,打开Python交互环境:

import snap7
print(snap7.__version__)

如果能正常输出版本号,说明环境没问题。

4.3 建立连接:让电脑和PLC「握手」

连接PLC之前,你需要知道三样东西:PLC的IP地址、机架号(Rack)、槽号(Slot)。

对于S7-1200和1500,默认机架号是0,槽号是1。S7-300/400可能不同,具体看硬件组态。

import snap7

# 创建客户端对象
client = snap7.client.Client()

# 连接到PLC
client.connect('192.168.1.10', 0, 1)

# 检查连接状态
if client.get_connected():
    print("连接成功!")
else:
    print("连接失败,请检查IP和网络。")

我曾经遇到过一个情况:PLC的IP能ping通,但Snap7就是连不上。折腾了半天,发现是PLC的防火墙没关。西门子PLC默认会开启防火墙,你得在博图里把「允许来自远程对象的PUT/GET通信」勾上。

避坑指南:

  • 确保PLC和电脑在同一个网段
  • 检查PLC的防护等级设置,S7-1200需要允许远程访问
  • 如果连接超时,试试关闭Windows防火墙

4.4 读取DB块:把PLC里的数据拿上来

DB块是西门子PLC里最常用的数据存储区。分拣机的速度、位置、故障码,通常都放在DB块里。

读取DB块的核心函数是 db_read(),它需要三个参数:DB块编号、起始字节地址、读取长度。

# 读取DB1,从第0个字节开始,读取4个字节(一个32位整数)
data = client.db_read(1, 0, 4)

# Snap7返回的是字节数组,需要解析
import struct
value = struct.unpack('>i', data)[0]
print(f"DB1.DBD0的值是:{value}")

你可能会问:为什么要用 struct.unpack?因为Snap7读回来的是原始字节,你得告诉Python怎么解释这些字节。'>i'表示大端序的有符号32位整数。

读取实数(Real)也是一样的套路:

# 读取DB1的第4个字节开始的4个字节(一个实数)
data = client.db_read(1, 4, 4)
real_value = struct.unpack('>f', data)[0]
print(f"DB1.DBD4的实数值是:{real_value}")

经验之谈:我习惯把读取函数封装成一个工具类。比如写一个 read_db_int(db_number, start_byte),这样每次调用就一行代码,清爽多了。

4.5 写入M寄存器:让PLC听你的指令

M寄存器,也叫中间寄存器,常用于控制逻辑的启停。比如分拣机的启动按钮、急停信号,都可以映射到M区。

写入操作使用 mb_write() 函数。注意,它一次只能写一个字节(8个位)。

# 写入M0.0,即M寄存器的第0个字节的第0位
# 先读取当前M0字节的值
current = client.mb_read(0, 1)
# 把第0位设为1(启动)
new_value = current[0] | 0b00000001
client.mb_write(0, bytearray([new_value]))
print("M0.0已置位为1")

如果你想写一个整数到MW0(M字,占用2个字节),需要自己拼字节:

# 写入整数100到MW0
value = 100
data = struct.pack('>H', value)  # '>H'表示大端序无符号16位整数
client.mb_write(0, bytearray(data))
print(f"MW0已写入值:{value}")

注意:写入操作要谨慎。我曾经在调试时不小心写错了地址,把M100写成了M10,结果产线直接停了。所以,写入前最好加一个确认步骤,或者先读一下当前值做个备份。

4.6 完整示例:一个简单的读写循环

下面是一个完整的示例,演示了连接、读取DB块、写入M寄存器,然后断开连接。

import snap7
import struct
import time

def main():
    # 1. 建立连接
    client = snap7.client.Client()
    client.connect('192.168.1.10', 0, 1)
    
    if not client.get_connected():
        print("连接失败,退出程序")
        return
    
    print("连接成功,开始读写测试...")
    
    try:
        # 2. 读取DB1.DBD0(整数)
        data = client.db_read(1, 0, 4)
        speed = struct.unpack('>i', data)[0]
        print(f"当前分拣速度:{speed} mm/s")
        
        # 3. 读取DB1.DBD4(实数)
        data = client.db_read(1, 4, 4)
        temperature = struct.unpack('>f', data)[0]
        print(f"当前电机温度:{temperature} ℃")
        
        # 4. 写入M0.0,启动分拣
        current = client.mb_read(0, 1)
        new_value = current[0] | 0b00000001
        client.mb_write(0, bytearray([new_value]))
        print("已发送启动信号")
        
        time.sleep(2)
        
        # 5. 停止分拣(复位M0.0)
        new_value = current[0] & 0b11111110
        client.mb_write(0, bytearray([new_value]))
        print("已发送停止信号")
        
    except Exception as e:
        print(f"读写过程中出错:{e}")
    
    finally:
        # 6. 断开连接
        client.disconnect()
        print("连接已断开")

if __name__ == "__main__":
    main()

4.7 常见问题与调试技巧

问题 可能原因 解决方法
连接超时 IP地址错误或PLC未开机 先ping一下PLC的IP
读取返回空数据 DB块编号或地址错误 在博图里确认DB块的实际编号
写入不生效 PLC处于STOP模式 将PLC切换到RUN模式
struct解析报错 字节序或数据类型不匹配 检查PLC中变量的数据类型

调试小技巧:我常用Wireshark抓包看Snap7的通信过程。如果读写失败,抓包能帮你快速定位是网络问题还是协议问题。Snap7使用的是102端口,抓包时过滤 tcp.port == 102 就行。

4.8 小结

这一章我们完成了三件事:建立连接、读取DB块、写入M寄存器。说白了,就是让Python和PLC能「说上话」。

下一章,我会带你把这些操作封装成一个完整的监控类,再加上异常处理和日志记录。到时候,你就能写一个真正能上生产环境的远程监控程序了。

嗯,今天就到这里。代码不多,但每一步都很关键。建议你打开博图,建一个简单的DB块,亲手跑一遍上面的代码。遇到问题别慌,对照着表格排查,基本都能解决。