Python + Snap7 实现西门子 S7-1200/1500 数据采集

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

bash 复制代码
pip install python-snap7

2.2 S7-1200/1500 PLC 端设置

在 TIA Portal 中进行以下配置才能让上位机通过 S7 协议通信:

  1. 允许 PUT/GET 通信(S7-1500 需特别注意)
  • 在设备组态 → 保护与安全 → 编译块时支持仿真/PUT/GET 通信 → 勾选
  1. 优化块访问 → 标准访问
  • DB 块属性中,取消"优化的块访问"(否则直接读写需要按符号名,Snap7 读写按地址偏移)
  1. 防火墙设置
  • 确保 PLC 的以太网口防火墙允许 S7 通信(端口 102)

特别注意:S7-1200 固件 4.0+ 默认关闭 PUT/GET,需在 TIA Portal 中显式开启。

3. 快速开始:连接与读取

3.1 创建客户端并连接

python 复制代码
import snap7

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

# 参数:PLC 的 IP 地址,机架号,槽号
# S7-1200 通常 rack=0, slot=1
# S7-1500 通常 rack=0, slot=0
client.connect('192.168.0.1', 0, 1)

# 检查连接状态
if client.get_connected():
    print(f"已连接到 PLC: {client.get_cpu_state()}")
else:
    print("连接失败")

3.2 读取 DB 块数据

DB 块是 S7 中最常用的数据存储区,上位机和 PLC 通过 DB 交换数据。

python 复制代码
def read_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)
        return data
    except snap7.snap7exceptions.Snap7Exception as e:
        print(f"读取 DB{db_number} 失败: {e}")
        return None

# 示例:读取 DB1 从偏移 0 开始的 10 个字节
data = read_db_block(client, 1, 0, 10)
if data:
    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()
python 复制代码
import struct

def parse_int(data, offset=0):
    """解析 S7 Int (有符号 16 位)"""
    return int.from_bytes(data[offset:offset+2], byteorder='big', signed=True)

def parse_dint(data, offset=0):
    """解析 S7 DInt (有符号 32 位)"""
    return int.from_bytes(data[offset:offset+4], byteorder='big', signed=True)

def parse_real(data, offset=0):
    """解析 S7 Real (32 位浮点数)"""
    return struct.unpack('>f', data[offset:offset+4])[0]

def parse_word(data, offset=0):
    """解析 S7 Word (无符号 16 位)"""
    return int.from_bytes(data[offset:offset+2], byteorder='big')

def parse_dword(data, offset=0):
    """解析 S7 DWord (无符号 32 位)"""
    return int.from_bytes(data[offset:offset+4], byteorder='big')

def parse_byte_array(data, offset=0, length=1):
    """解析 Byte 数组"""
    return list(data[offset:offset+length])

# 使用示例
raw = client.db_read(1, 0, 40)  # 一次读取 40 字节

temp_value = parse_real(raw, 0)          # Real 类型温度值,偏移 0
pressure = parse_int(raw, 4)             # Int 类型压力值,偏移 4
status_flags = parse_dword(raw, 6)       # DWord 状态标志,偏移 6

3.4 完整示例:读取 PLC 中的设备运行数据

python 复制代码
import snap7
import struct
import time

class S7Client:
    """西门子 PLC 数据采集客户端封装"""

    def __init__(self, ip, rack=0, slot=1):
        self.client = snap7.client.Client()
        self.ip = ip
        self.rack = rack
        self.slot = slot

    def connect(self):
        try:
            self.client.connect(self.ip, self.rack, self.slot)
            print(f"[+] 已连接到 {self.ip}")
            return True
        except Exception as e:
            print(f"[-] 连接失败: {e}")
            return False

    def disconnect(self):
        self.client.disconnect()

    def read_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],
            }
        except snap7.snap7exceptions.Snap7Exception as e:
            print(f"读取设备数据失败: {e}")
            return None

    def read_multi_db(self, db_configs):
        """
        批量读取多个 DB 块
        db_configs: [(db_num, start, size), ...]
        """
        results = {}
        for db_num, start, size in db_configs:
            data = self.client.db_read(db_num, start, size)
            results[db_num] = data
        return results


# ========== 使用示例 ==========
if __name__ == '__main__':
    plc = S7Client('192.168.0.1', rack=0, slot=1)

    if plc.connect():
        try:
            while True:
                data = plc.read_device_data()
                if data:
                    print(f"温度: {data['temperature']:.1f}°C | "
                          f"压力: {data['pressure']:.2f} MPa | "
                          f"转速: {data['speed']} RPM | "
                          f"状态: {data['status']}")
                time.sleep(1)  # 每秒采集一次
        except KeyboardInterrupt:
            print("\n停止采集")
        finally:
            plc.disconnect()

4. 写入数据到 PLC

除了读取,Snap7 同样支持写入操作。

python 复制代码
def write_real(client, db_number, offset, value):
    """向 PLC DB 块写入 Real 类型数据"""
    data = struct.pack('>f', value)
    client.db_write(db_number, offset, data)

def write_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)

def write_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]
    if value:
        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 等存储区。

python 复制代码
# 读取输入映像区 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 脚本。

相关推荐
大学竞赛君1 小时前
第十六届蓝桥杯大赛软件赛决赛 Python 大学 A 组
python·职场和发展·蓝桥杯
c238561 小时前
C++11final与override6、智能指针
开发语言·c++
-FxYaM-1 小时前
图吧工具箱与自动化运维
python
aqi001 小时前
15天学会AI应用开发(四)根据Token长度截断历史对话
人工智能·python·大模型·ai编程·ai应用
*neverGiveUp*1 小时前
初步了解Django框架
开发语言·python·django
Java_2017_csdn1 小时前
在 Java 中,MessageFormat.format() 和 String.format() 函数对比?
java·开发语言·前端·数据库
绛洞花主敏明2 小时前
Go操作xorm中间表多对多关联实战
开发语言·后端·golang
Jun6262 小时前
QT(4)-EXCEL操作
开发语言·qt·excel
fengfuyao9852 小时前
基于MATLAB的HHT变换完整实现(含EMD分解与三维时频谱生成)
开发语言·算法·matlab