1.简介
Snap7
- Snap7是一个基于s7通信协议的开源软件包,作者是Davide Nardella,该软件包封装了S7通信的底层协议,可使用普通电脑通过编程与西门子S7系列PLC进行通信
- Snap7三大对象组件:客户端,服务器,合作者。下面是三者关系,更详细介绍可看官网。本篇主要讲述的是Client模式,我们的pc机作为客户端,plc作为服务器。
- Snap7官网地址:http://snap7.sourceforge.net/
- Snap7包支持西门子S7-200 SMART,S7-300/400系列,S7-1200/1500系列、另外LOGO! 0BA7/0BA8 PLC、 SINAMICS驱动器也有较好的支持
- Snap7特点:
- 基于以太网,网线连接
- 跨平台,支持 Windows、 Linux、Mac等主流操作系统
- Windows系统包括目前主流的Win7/8/10 的32位或64位
- Linux系统包括: CentOs、 Debian、 RedHat、 Ubuntu等32位或64位系统
- 提供多种语言的封装包:C#、VB、C/C++、 Python、java、 Delphi、 LabView等主流编程语言
- 支持树莓派、 ARDUINO等嵌入式平台
- python包源码地址:https://github.com/gijzelaerr/python-snap7
2.环境安装
Window
- pip install python-snap7(版本一直未更新)
- github源码包下载:https://github.com/gijzelaerr/python-snap7(这里会一直更新)
- 把snap7.dll和snap7.lib(注意区分32位和64位),设置到环境变量能找到的地址就行
Linux
- 第一种方式
sudo apt-get install python-pip3
sudo pip3 install python-snap7
- 第二种方式
通过以下命令下载snap7:
git clone https://github.com/lizengjie/snap7-debian.git
编译:(arm_v7_linux不行就arm_v6_linux)
cd snap7-debian/build/unix && sudo make -f arm_v7_linux.mk all
拷贝:
sudo cp …/bin/arm_v7-linux/libsnap7.so /usr/lib/libsnap7.so
sudo cp …/bin/arm_v7-linux/libsnap7.so /usr/local/lib/libsnap7.so
sudo ldconfig
不同环境的拷贝文件我稍后放资源管理处
3.连接西门子plc
方法介绍
在Client类里提供了主要5种设置连接plc函数
- connect(ip, rack, slot)
这是连接plc的唯一方法,参数ip是要连接的plcip地址,rack是机架号,slot卡槽号,不同的plc对应不同的机架和卡槽看下图对应
plc | rack | slot |
---|---|---|
s7-200smart | 0 | 1 |
s7-300 | 0 | 2 |
s7-400/WIN AC | 见硬件组态 | 见硬件组态 |
s7-1200/1500 | 0 | 0/1 |
- set_connection_params(ip, local_tsap, remote_tsap)
这是设置远程本地TSAP和远程TSAP的函数,三个参数ip是plcIP地址,本地TSAP和远程TSAP是相对应的int类型,函数一般用于连接logo系列时使用,调用顺序在connect()之前调用
- set_connection_type(connection_type)
这是用来设置连接属性的函数,connection_type参数范围如图:
connection_type | 值 |
---|---|
PG | 1 |
OP | 2 |
S7-Basic | 3-10 |
此函数不是一定要调用,如果调用一定要在connect()函数之前调用设置,比如连接s7-200SMART就一定要调用此函数,参数一般设为3
- disconnect() 和 destroy()
这是断开客户端连接和销毁客户端连接,无需参数
最后注意如果你set_connection_params()和set_connection_type()都要调用,一定要set_connection_type()在前set_connection_params()在后,因为顺序相反会对远程TASP造成影响。所以整体的顺序应该是
1 set_connection_type(connection_type) #选用 2 set_connection_params(ip, local_tsap, remote_tsap) #选用 3 connect(ip, rack, slot) 4 disconnect() 5 destroy()
连接plc
第一步首先实例化一个Client对象
1 from snap7 import client 2 3 my_plc = client.Client()
第二步调用connect()的方法
1 from snap7 import client 2 3 my_plc = client.Client() 4 5 # my_plc.set_connection_type(3) 如果连接的是s7-200smart系列plc 6 # set_connection_params(ip, local_tsap, remote_tsap) 如果连接的是logo!系列plc 7 8 my_plc.connect(ip, rack, slot) 9 # ip是plcIP,rack是机架号,slot卡槽号,不同的plc对应不同的机架和卡槽看上边表格 10 11 print(my_plc.get_connected()) 12 # 判断连接成功可调用get_connected():返回True就是成功,不成功直接报错。
第三步断开连接
1 from snap7 import client 2 my_plc = client.Client() 3 my_plc.connect(ip, rack, slot) 4 my_plc.disconnect() 5 my_plc.destroy() # 不用了一定要断开销毁客户端
代码示例
1 from snap7 import client 2 3 def connect_logo(ip: str, local_tsap: int, remote_tsap: int, rack: int, slot: int): 4 """ 5 连接logo系列 6 :param ip: PLC/设备IPV4地址 7 :param local_tsap: 本地tsap(PC tsap) 8 :param remote_tsap: 远程tsap(PLC tsap) 9 :param rack: 服务器上的机架 10 :param slot: 服务器上的插槽 11 """ 12 # 初始化一个客户端 13 my_plc = client.Client() 14 # 设置内部(IP、LocalTSAP、RemoteTSAP)坐标。必须在connect()之前调用此函数 15 my_plc.set_connection_params(ip, local_tsap, remote_tsap) 16 # 连接到S7服务器 17 my_plc.connect(ip, rack, slot) 18 return my_plc 19 20 21 def connect_200smart(ip: str, plc_model=3, rack=0, slot=1): 22 """ 23 连接s7-200smart系列 24 :param ip: PLC/设备IPV4地址 25 :param plc_model: 连接类型:1用于PG,2用于OP,3至10用于S7基本 26 :param rack: 服务器上的机架 27 :param slot: 服务器上的插槽 28 """ 29 # 初始化一个客户端 30 my_plc = client.Client() 31 # 设置连接资源类型,即客户端,连接到PLC 32 my_plc.set_connection_type(plc_model) 33 # 连接到S7服务器 34 my_plc.connect(ip, rack, slot) 35 return my_plc 36 37 38 def connect_plc(ip: str, rack: int, slot: int): 39 """ 40 连接s7-1200/1500系列 41 :param ip: PLC/设备IPV4地址 42 :param rack: 服务器上的机架 43 :param slot: 服务器上的插槽 44 """ 45 my_plc = client.Client() 46 my_plc.connect(ip, rack, slot) 47 return my_plc
4.读plc
方法介绍及示例
在Client类里提供了主要两种读plc的函数
- read_area(area, dbnumber, start, size)
这是读plc最最最重要的方法,功能强大,支持(I,Q,M,DB,V,CT,TM)多存储区读取数据
area:区地址类型(十六进制类型),如下图对应
dbnumber:地址编号(int),只适用于DB区和200samart的V区,其它区全默认0,V区只能填1
start:要读取数据的字节起始地址(int)
size:要读取的数据类型所占字节长度大小(int),如下字典对应
I | Q | M | DB/V | CT | TM |
---|---|---|---|---|---|
0x81 | 0x82 | 0x83 | 0x84 | 0x1C | 0x1D |
1 # 不同类型所占字节大小 2 TypeSize = { 3 'int': 2, # 有符号(-32768~32767) 4 'bool': 1, # bool值 5 'dint': 4, # 有符号 (-2147483648~2147483647) 6 'word': 2, # 无符号(0~65536) 7 'real': 4, # 有符号 float类型(这范围记不住了) 8 'dword': 4, # 无符号(0~4294967295) 9 'char': 1, # CHAR,ASCII字符集,占用1个字节内存,主要针对欧美国家(字符比较少) 10 'string': 255, # STRING,占用256个字节内存,ASCII字符串,由ASCII字符组成 11 's5time': 2, 12 'wchar': 2, # WCHAR,Unicode字符集,占用2个字节内存,主要针对亚洲国家(字符比较多) 13 'wstring': 512, # WSTRING,默认占用512个字节内存(可变),Unicode字符串,由Unicode字符构成 14 'dt': 4, # DateTime 日期 15 'usint': 1, # 0~255 16 'sint': 1, # -128~127 17 'uint': 2, # 0~4294967295 18 'udint': 4, # 0~4294967295 19 'lreal': 8, 20 'time': 4, 21 'd': 2, 22 'tod': 4, # TOD (TIME_OF_DAY)数据作为无符号双整数值存储,被解释为自指定日期的凌晨算起的毫秒数(凌晨 = 0ms)。必须指定小时(24 小时/天)、分钟和秒。可以选择指定小数秒格式。 23 'dtl': 12, # DTL(日期和时间长型)数据类型使用 12 个字节的结构保存日期和时间信息。可以在块的临时存储器或者 DB 中定义 DTL 数据。 24 'date': 2, # Date(16位日期值)、 25 'ltod': 8 26 }
return:函数最后返回的是一个字节数组,到这里大家不用自己用struct包去解,作者在uitl文件里为大家封装了取不同类型变量值的函数,下面我主要介绍两种,
- bool:get_bool(_bytearray, byte_index, bool_index)
_bytearray:字节数组,就是你上面读到的字节数组
byte_index:字节索引,这里填0就可以,后面我会详细介绍byte_index和上面read_area()的参数start,size三者的关系,以及灵活应用
bool_index: bool值索引,其实就是位(bit)索引(0~7),因为1byte=8bit
- real:get_real(_bytearray, byte_index)
参数同上,大家可自己看源码,目前除了bool和string类型,其它都只要两个参数_bytearray和bool_index,有一些类型作者还没写,大家有用到可以自己解。
1 """ 2 简单示例#1 3 plc: s7-1200 4 变量地址:DB1.DBD36 (1是地址编号,36是起始值) 5 类型: real(float) 6 """ 7 from snap7 import util, client 8 from snap7.snap7types import S7AreaDB 9 10 my_plc = client.Client() # 实例化客户端 11 my_plc.connect('192.168.2.1', 0, 0) # 连接s7-1200 12 byte_arrays = my_plc.read_area(S7AreaDB, 1, 36, 4) # 读出变量的字节数组 13 value = util.get_real(byte_arrays, 0) # 通过数据类型取值 14 my_plc.disconnect() # 断开连接 15 my_plc.destroy() # 销毁 16 print(value) 17 18 -------------------------------------------------------------------------------------- 19 20 """ 21 简单示例#2 22 plc: s7-200SMART 23 变量地址:M1.0 (1是起始值,0是bool索引) 24 类型: bool 25 """ 26 from snap7 import util, client 27 from snap7.snap7types import S7AreaMK 28 29 my_plc = client.Client() # 实例化客户端 30 my_plc.set_connection_type(3) # 设置连接资源类型 31 my_plc.connect('192.168.2.2', 0, 1) # 连接s7-200SMART 32 byte_arrays = my_plc.read_area(S7AreaMK, 0, 1, 1) # 读出变量的字节数组 33 value = util.get_bool(byte_arrays, 0, 0) # 通过数据类型取值 34 my_plc.disconnect() # 断开连接 35 my_plc.destroy() # 销毁 36 print(value)
这里介绍一下start,size和byte_index之间关系以及如何应用,方便理解个人整理以下如图
注意多个变量的适用条件必须为同一地址(area),同一地址编号(dbnumber)。所以通过read_area函数可以一次读取同一地址编号上的所有变量
1 """ 2 示例 3 plc: s7-1200 4 变量地址:[DB4.DBX0.1, DB4.DBD36, DB4.DBW2 .....] 5 类型: [bool, float, word ......] 6 """ 7 from snap7 import util, client 8 from snap7.snap7types import S7AreaDB 9 10 my_plc = client.Client() 11 my_plc.connect('192.168.2.1', 0, 0) 12 13 byte_arrays = my_plc.read_area(S7AreaDB, 4, 0, 40) 14 # 这是所有db块,地址编号4的变量,套用图上公公式,最小的起始值是0,size是最大起始值加它类型所占的字节数就是36+float类型所占4个byte长度,所以size是40 15 16 value1 = util.get_bool(byte_arrays, 0, 1) 17 # DB4.DBX0.1是bool类型,byte_index = 起始值是0 - 最小的起始值0 = 0 18 19 value2 = util.get_real(byte_arrays, 36) 20 # DB4.DBD36是float类型,byte_index = 起始值是36 - 最小的起始值0 = 36 21 22 value3 = util.get_word(byte_arrays, 2) 23 # DB4.DBW2是word类型,byte_index = 起始值是2 - 最小的起始值0 = 2 24 25 my_plc.disconnect() 26 my_plc.destroy() 27 print(value1, value2, value3)
- read_multi_vars(items)
这是可以一次读取<=19个不同地址类型的变量,由于pdu大小限制一次性读取不能超过19个变量
items参数是一个由S7DataItem实例对象组成的列表
下面我用作者写的一个示例给大家介绍一下
1 import ctypes 2 import snap7 3 from snap7.common import check_error 4 from snap7.types import S7DataItem, S7AreaDB, S7WLByte 5 6 client = snap7.client.Client() 7 client.connect('10.100.5.2', 0, 2) 8 9 data_items = (S7DataItem * 3)() # 注意就是这里数字不能大于19 10 11 data_items[0].Area = ctypes.c_int32(S7AreaDB) # 地址类型 12 data_items[0].WordLen = ctypes.c_int32(S7WLByte) # 这里的WordLen除了读TM和CT地址时其它地址统一用字节(S7WLByte)。不要用S7WLBit,用位去读需要换算,不嫌麻烦你可以试试 13 data_items[0].Result = ctypes.c_int32(0) # result用不到写0就可以 14 data_items[0].DBNumber = ctypes.c_int32(200) # 地址编号 15 data_items[0].Start = ctypes.c_int32(16) # 变量起始字节地址 16 data_items[0].Amount = ctypes.c_int32(4) # 字节长度 17 18 data_items[1].Area = ctypes.c_int32(S7AreaDB) 19 data_items[1].WordLen = ctypes.c_int32(S7WLByte) 20 data_items[1].Result = ctypes.c_int32(0) 21 data_items[1].DBNumber = ctypes.c_int32(200) 22 data_items[1].Start = ctypes.c_int32(12) 23 data_items[1].Amount = ctypes.c_int32(4) # reading a REAL, 4 bytes 24 25 data_items[2].Area = ctypes.c_int32(S7AreaDB) 26 data_items[2].WordLen = ctypes.c_int32(S7WLByte) 27 data_items[2].Result = ctypes.c_int32(0) 28 data_items[2].DBNumber = ctypes.c_int32(200) 29 data_items[2].Start = ctypes.c_int32(2) 30 data_items[2].Amount = ctypes.c_int32(2) # reading an INT, 2 bytes 31 32 # create buffers to receive the data 33 # use the Amount attribute on each item to size the buffer 34 for di in data_items: 35 # create the buffer 36 buffer = ctypes.create_string_buffer(di.Amount) 37 38 # cast the pointer to the buffer to the required type 39 pBuffer = ctypes.cast(ctypes.pointer(buffer), ctypes.POINTER(ctypes.c_uint8)) 40 di.pData = pBuffer 41 42 for di in data_items: 43 check_error(di.Result) 44 45 result, data_items = client.read_multi_vars(data_items) 46 47 result_values = [] 48 # function to cast bytes to match data_types[] above 49 byte_to_value = [snap7.util.get_real, snap7.util.get_real, snap7.util.get_int] 50 51 # unpack and test the result of each read 52 for i in range(0, len(data_items)): 53 btv = byte_to_value[i] 54 di = data_items[i] 55 value = btv(di.pData, 0) 56 result_values.append(value) 57 print(result_values) 58 59 client.disconnect() 60 client.destroy()
5.写plc
方法介绍及示例
对变量赋值同样也介绍两种方法
- write_area(area, dbnumber, start, data)
要想对变量赋值,必须先读取变量数组,然后在把要写入的值设置到缓存,最后在写到plc。
三步顺序:
第一步:byte_arrays = read_area() 或 byte_arrays = bytearray(变量类型所占字节大小)
这个不介绍了不懂看上面读plc方法介绍
第二步:在snap7.util里作者同样封装了不同类型变量更改字节数组的方法,这里拿bool类型描述一下,因为 其他类型参数基本都一样,大家可看源码
- set_bool(_bytearray, byte_index, bool_index, value)
_bytearray:字节数组
byte_index:字节索引
bool_index:位索引
value:要写入的值(注意必须与要赋值的变量类型一致)
第三步:在通过write_area()函数把值写进plc
- write_area(area, dbnumber, start, data)
area:地址类型
dbnumber: 地址编号
start:字节起始值
data: 字节数组(就是你第一步读出来的字节数组)
1 """ 2 简单示例#1 3 plc: s7-200SMART 4 变量地址:M1.0 (1是起始值,0是bool索引) 5 类型: bool 6 """ 7 from snap7 import util, client 8 from snap7.snap7types import S7AreaMK 9 10 my_plc = client.Client() 11 my_plc.set_connection_type(3) 12 my_plc.connect('192.168.2.101', 0, 1) 13 byte_arrays = my_plc.read_area(S7AreaMK, 0, 1, 1) 14 print('赋值前', util.get_bool(byte_arrays, 0, 0)) 15 util.set_bool(byte_arrays, 0, 0, 1) 16 my_plc.write_area(S7AreaMK, 0, 1, byte_arrays) 17 print('赋值后', util.get_bool(byte_arrays, 0, 0)) 18 my_plc.disconnect() 19 my_plc.destroy() 20 21 ----------------------------------------------------------------------------------------- 22 23 """ 24 简单示例#2 25 plc: s7-1200 26 变量地址:Q1.2 (1是起始值,2是bool索引) 27 类型: bool 28 """ 29 from snap7 import util, client 30 from snap7.snap7types import S7AreaPA 31 32 my_plc = client.Client() 33 my_plc.connect('192.168.2.1', 0, 1) 34 byte_arrays = my_plc.read_area(S7AreaPA, 0, 1, 1) 35 print('赋值前', util.get_bool(byte_arrays, 0, 2)) 36 util.set_bool(byte_arrays, 0, 2, 1) 37 my_plc.write_area(S7AreaPA, 0, 1, byte_arrays) 38 print('赋值后', util.get_bool(byte_arrays, 0, 2)) 39 my_plc.disconnect() 40 my_plc.destroy()
同读的思维一样,我们这里也可以一次为同一地址,同一地址编号所有变量赋值
1 """ 2 示例 3 plc: s7-200SMART 4 变量地址:V100.0 VD104 5 类型: bool real 6 """ 7 from snap7 import util, client 8 from snap7.snap7types import S7AreaDB 9 10 my_plc = client.Client() 11 my_plc.set_connection_type(3) 12 my_plc.connect('192.168.2.101', 0, 1) 13 byte_arrays = my_plc.read_area(S7AreaDB, 1, 0, 108) 14 print('赋值前', util.get_bool(byte_arrays, 100, 0), '赋值前', util.get_real(byte_arrays, 104)) 15 util.set_bool(byte_arrays, 100, 0, 1) 16 util.set_real(byte_arrays, 104, 999.99) 17 my_plc.write_area(S7AreaDB, 1, 0, byte_arrays) 18 print('赋值后', util.get_bool(byte_arrays, 100, 0), '赋值后', util.get_real(byte_arrays, 104)) 19 my_plc.disconnect() 20 my_plc.destroy()
- write_multi_vars(items)
这同样也是一个可以一次为多个不同地址变量赋值的函数(同样不能大于19个)
还是用作者的例子(参数用法大同小异)
1 import ctypes 2 import snap7 3 from snap7.types import S7WLByte, S7DataItem, S7WLWord, S7WLReal, S7WLTimer 4 from snap7.types import areas, wordlen_to_ctypes 5 from snap7.util import set_int, set_real, set_word, get_int, get_real, get_s5time 6 7 8 client = snap7.client.Client() 9 client.connect('192.168.100.100', 0, 2) 10 11 items = [] 12 13 14 def set_data_item(area, word_len, db_number: int, start: int, amount: int, data: bytearray) -> S7DataItem: 15 item = S7DataItem() 16 item.Area = ctypes.c_int32(area) 17 item.WordLen = ctypes.c_int32(word_len) 18 item.DBNumber = ctypes.c_int32(db_number) 19 item.Start = ctypes.c_int32(start) 20 item.Amount = ctypes.c_int32(amount) 21 array_class = ctypes.c_uint8 * len(data) 22 cdata = array_class.from_buffer_copy(data) 23 item.pData = ctypes.cast(cdata, ctypes.POINTER(array_class)).contents 24 return item 25 26 27 int_values = [10, 20, 30, 40] 28 ints = bytearray(len(int_values) * 2) 29 for i, value in enumerate(int_values): 30 set_int(ints, i * 2, value) 31 32 real = bytearray(4) 33 set_real(real, 0, 42.5) 34 35 counters = 0x2999.to_bytes(2, 'big') + 0x1111.to_bytes(2, 'big') 36 37 item1 = set_data_item(area=areas.DB, word_len=S7WLWord, db_number=1, start=0, amount=4, data=ints) 38 item2 = set_data_item(area=areas.DB, word_len=S7WLReal, db_number=1, start=8, amount=1, data=real) 39 item3 = set_data_item(area=areas.TM, word_len=S7WLTimer, db_number=0, start=2, amount=2, data=counters) 40 41 items.append(item1) 42 items.append(item2) 43 items.append(item3) 44 45 client.write_multi_vars(items) 46 47 db_int = client.db_read(1, 0, 8) 48 db_real = client.db_read(1, 8, 12) 49 db_counters = client.ct_read(2, 2) 50 51 print(f'int values: {[get_int(db_int, i * 2) for i in range(4)]}') 52 print(f'real value: {get_real(db_real, 0)}') 53 print(f'counters: {get_s5time(counters, 0)}, {get_s5time(counters, 2)}')
6.总结
不总了,有问题大家随时交流
标签:snap7,Snap7,Python,data,S7,client,plc,byte,my From: https://www.cnblogs.com/ybqjymy/p/18149949