python使用Snap7读写西门子S7系列PLC
python使用Snap7讀寫西門子S7系列PLC
1.簡(jiǎn)介
Snap7
-
Snap7是一個(gè)基于s7通信協(xié)議的開源軟件包,作者是Davide Nardella,該軟件包封裝了S7通信的底層協(xié)議,可使用普通電腦通過(guò)編程與西門子S7系列PLC進(jìn)行通信
-
Snap7三大對(duì)象組件:客戶端,服務(wù)器,合作者。下面是三者關(guān)系,更詳細(xì)介紹可看官網(wǎng)。本篇主要講述的是Client模式,我們的pc機(jī)作為客戶端,plc作為服務(wù)器。
-
Snap7官網(wǎng)地址:http://snap7.sourceforge.net/
-
Snap7包支持西門子S7-200 SMART,S7-300/400系列,S7-1200/1500系列、另外LOGO! 0BA7/0BA8 PLC、 SINAMICS驅(qū)動(dòng)器也有較好的支持
-
Snap7特點(diǎn):
-
基于以太網(wǎng),網(wǎng)線連接
-
跨平臺(tái),支持 Windows、 Linux、Mac等主流操作系統(tǒng)
-
Windows系統(tǒng)包括目前主流的Win7/8/10 的32位或64位
-
Linux系統(tǒng)包括: CentOs、 Debian、 RedHat、 Ubuntu等32位或64位系統(tǒng)
-
-
提供多種語(yǔ)言的封裝包:C#、VB、C/C++、 Python、java、 Delphi、 LabView等主流編程語(yǔ)言
-
支持樹莓派、 ARDUINO等嵌入式平臺(tái)
python包源碼地址:https://github.com/gijzelaerr/python-snap7
2.環(huán)境安裝
Window
Linux
-
第一種方式
- sudo apt-get install python-pip3
- sudo pip3 install python-snap7
-
第二種方式
-
通過(guò)以下命令下載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
不同環(huán)境的拷貝文件我稍后放資源管理處
3.連接西門子plc
方法介紹
? 在Client類里提供了主要5種設(shè)置連接plc函數(shù)
-
connect(ip, rack, slot)
這是連接plc的唯一方法,參數(shù)ip是要連接的plcip地址,rack是機(jī)架號(hào),slot卡槽號(hào),不同的plc對(duì)應(yīng)不同的機(jī)架和卡槽看下圖對(duì)應(yīng)
plcrackslot s7-200smart 0 1 s7-300 0 2 s7-400/WIN AC 見硬件組態(tài) 見硬件組態(tài) s7-1200/1500 0 0/1 -
set_connection_params(ip, local_tsap, remote_tsap)
這是設(shè)置遠(yuǎn)程本地TSAP和遠(yuǎn)程TSAP的函數(shù),三個(gè)參數(shù)ip是plcIP地址,本地TSAP和遠(yuǎn)程TSAP是相對(duì)應(yīng)的int類型,函數(shù)一般用于連接logo系列時(shí)使用,調(diào)用順序在connect()之前調(diào)用
-
set_connection_type(connection_type)
這是用來(lái)設(shè)置連接屬性的函數(shù),connection_type參數(shù)范圍如圖:
| PG | 1 |
| OP | 2 |
| S7-Basic | 3-10 |
此函數(shù)不是一定要調(diào)用,如果調(diào)用一定要在connect()函數(shù)之前調(diào)用設(shè)置,比如連接s7-200SMART就一定要調(diào)用此函數(shù),參數(shù)一般設(shè)為3
-
disconnect() 和 destroy()
這是斷開客戶端連接和銷毀客戶端連接,無(wú)需參數(shù)
-
最后注意如果你set_connection_params()和set_connection_type()都要調(diào)用,一定要set_connection_type()在前set_connection_params()在后,因?yàn)轫樞蛳喾磿?huì)對(duì)遠(yuǎn)程TASP造成影響。所以整體的順序應(yīng)該是
set_connection_type(connection_type) #選用 set_connection_params(ip, local_tsap, remote_tsap) #選用 connect(ip, rack, slot) disconnect() destroy()
連接plc
第一步首先實(shí)例化一個(gè)Client對(duì)象
from snap7 import clientmy_plc = client.Client()第二步調(diào)用connect()的方法
from snap7 import clientmy_plc = client.Client()# my_plc.set_connection_type(3) 如果連接的是s7-200smart系列plc # set_connection_params(ip, local_tsap, remote_tsap) 如果連接的是logo!系列plcmy_plc.connect(ip, rack, slot) # ip是plcIP,rack是機(jī)架號(hào),slot卡槽號(hào),不同的plc對(duì)應(yīng)不同的機(jī)架和卡槽看上邊表格print(my_plc.get_connected()) # 判斷連接成功可調(diào)用get_connected():返回True就是成功,不成功直接報(bào)錯(cuò)。第三步斷開連接
from snap7 import client my_plc = client.Client() my_plc.connect(ip, rack, slot) my_plc.disconnect() my_plc.destroy() # 不用了一定要斷開銷毀客戶端-
代碼示例
from snap7 import clientdef connect_logo(ip: str, local_tsap: int, remote_tsap: int, rack: int, slot: int):"""連接logo系列:param ip: PLC/設(shè)備IPV4地址:param local_tsap: 本地tsap(PC tsap):param remote_tsap: 遠(yuǎn)程tsap(PLC tsap):param rack: 服務(wù)器上的機(jī)架:param slot: 服務(wù)器上的插槽"""# 初始化一個(gè)客戶端my_plc = client.Client()# 設(shè)置內(nèi)部(IP、LocalTSAP、RemoteTSAP)坐標(biāo)。必須在connect()之前調(diào)用此函數(shù)my_plc.set_connection_params(ip, local_tsap, remote_tsap)# 連接到S7服務(wù)器my_plc.connect(ip, rack, slot)return my_plcdef connect_200smart(ip: str, plc_model=3, rack=0, slot=1):"""連接s7-200smart系列:param ip: PLC/設(shè)備IPV4地址:param plc_model: 連接類型:1用于PG,2用于OP,3至10用于S7基本:param rack: 服務(wù)器上的機(jī)架:param slot: 服務(wù)器上的插槽"""# 初始化一個(gè)客戶端my_plc = client.Client()# 設(shè)置連接資源類型,即客戶端,連接到PLCmy_plc.set_connection_type(plc_model)# 連接到S7服務(wù)器my_plc.connect(ip, rack, slot)return my_plcdef connect_plc(ip: str, rack: int, slot: int):"""連接s7-1200/1500系列:param ip: PLC/設(shè)備IPV4地址:param rack: 服務(wù)器上的機(jī)架:param slot: 服務(wù)器上的插槽"""my_plc = client.Client()my_plc.connect(ip, rack, slot)return my_plc
4.讀plc
方法介紹及示例
在Client類里提供了主要兩種讀plc的函數(shù)
-
read_area(area, dbnumber, start, size)
這是讀plc最最最重要的方法,功能強(qiáng)大,支持(I,Q,M,DB,V,CT,TM)多存儲(chǔ)區(qū)讀取數(shù)據(jù)
? area:區(qū)地址類型(十六進(jìn)制類型),如下圖對(duì)應(yīng)
? dbnumber:地址編號(hào)(int),只適用于DB區(qū)和200samart的V區(qū),其它區(qū)全默認(rèn)0,V區(qū)只能填1
? start:要讀取數(shù)據(jù)的字節(jié)起始地址(int)
? size:要讀取的數(shù)據(jù)類型所占字節(jié)長(zhǎng)度大小(int),如下字典對(duì)應(yīng)
# 不同類型所占字節(jié)大小 TypeSize = {'int': 2, # 有符號(hào)(-32768~32767)'bool': 1, # bool值'dint': 4, # 有符號(hào) (-2147483648~2147483647)'word': 2, # 無(wú)符號(hào)(0~65536)'real': 4, # 有符號(hào) float類型(這范圍記不住了)'dword': 4, # 無(wú)符號(hào)(0~4294967295)'char': 1, # CHAR,ASCII字符集,占用1個(gè)字節(jié)內(nèi)存,主要針對(duì)歐美國(guó)家(字符比較少)'string': 255, # STRING,占用256個(gè)字節(jié)內(nèi)存,ASCII字符串,由ASCII字符組成's5time': 2,'wchar': 2, # WCHAR,Unicode字符集,占用2個(gè)字節(jié)內(nèi)存,主要針對(duì)亞洲國(guó)家(字符比較多)'wstring': 512, # WSTRING,默認(rèn)占用512個(gè)字節(jié)內(nèi)存(可變),Unicode字符串,由Unicode字符構(gòu)成'dt': 4, # DateTime 日期'usint': 1, # 0~255'sint': 1, # -128~127'uint': 2, # 0~4294967295'udint': 4, # 0~4294967295'lreal': 8,'time': 4,'d': 2,'tod': 4, # TOD (TIME_OF_DAY)數(shù)據(jù)作為無(wú)符號(hào)雙整數(shù)值存儲(chǔ),被解釋為自指定日期的凌晨算起的毫秒數(shù)(凌晨 = 0ms)。必須指定小時(shí)(24 小時(shí)/天)、分鐘和秒。可以選擇指定小數(shù)秒格式。'dtl': 12, # DTL(日期和時(shí)間長(zhǎng)型)數(shù)據(jù)類型使用 12 個(gè)字節(jié)的結(jié)構(gòu)保存日期和時(shí)間信息。可以在塊的臨時(shí)存儲(chǔ)器或者 DB 中定義 DTL 數(shù)據(jù)。'date': 2, # Date(16位日期值)、'ltod': 8 }IQMDB/VCTTM 0x81 0x82 0x83 0x84 0x1C 0x1D return:函數(shù)最后返回的是一個(gè)字節(jié)數(shù)組,到這里大家不用自己用struct包去解,作者在uitl文件里為大家封裝了取不同類型變量值的函數(shù),下面我主要介紹兩種,
-
bool:get_bool(_bytearray, byte_index, bool_index)
? _bytearray:字節(jié)數(shù)組,就是你上面讀到的字節(jié)數(shù)組
? byte_index:字節(jié)索引,這里填0就可以,后面我會(huì)詳細(xì)介紹byte_index和上面read_area()的參數(shù)start,size三者的關(guān)系,以及靈活應(yīng)用
? bool_index: bool值索引,其實(shí)就是位(bit)索引(0~7),因?yàn)?byte=8bit
-
real:get_real(_bytearray, byte_index)
? 參數(shù)同上,大家可自己看源碼,目前除了bool和string類型,其它都只要兩個(gè)參數(shù)_bytearray和bool_index,有一些類型作者還沒(méi)寫,大家有用到可以自己解。
-
這里介紹一下start,size和byte_index之間關(guān)系以及如何應(yīng)用,方便理解個(gè)人整理以下如圖
注意多個(gè)變量的適用條件必須為同一地址(area),同一地址編號(hào)(dbnumber)。所以通過(guò)read_area函數(shù)可以一次讀取同一地址編號(hào)上的所有變量
"""示例plc: s7-1200變量地址:[DB4.DBX0.1, DB4.DBD36, DB4.DBW2 .....]類型: [bool, float, word ......] """ from snap7 import util, client from snap7.snap7types import S7AreaDBmy_plc = client.Client() my_plc.connect('192.168.2.1', 0, 0) byte_arrays = my_plc.read_area(S7AreaDB, 4, 0, 40) # 這是所有db塊,地址編號(hào)4的變量,套用圖上公公式,最小的起始值是0,size是最大起始值加它類型所占的字節(jié)數(shù)就是36+float類型所占4個(gè)byte長(zhǎng)度,所以size是40value1 = util.get_bool(byte_arrays, 0, 1) # DB4.DBX0.1是bool類型,byte_index = 起始值是0 - 最小的起始值0 = 0value2 = util.get_real(byte_arrays, 36) # DB4.DBD36是float類型,byte_index = 起始值是36 - 最小的起始值0 = 36value3 = util.get_word(byte_arrays, 2) # DB4.DBW2是word類型,byte_index = 起始值是2 - 最小的起始值0 = 2my_plc.disconnect() my_plc.destroy() print(value1, value2, value3)-
read_multi_vars(items)
這是可以一次讀取<=19個(gè)不同地址類型的變量,由于pdu大小限制一次性讀取不能超過(guò)19個(gè)變量
items參數(shù)是一個(gè)由S7DataItem實(shí)例對(duì)象組成的列表
下面我用作者寫的一個(gè)示例給大家介紹一下
import ctypes import snap7 from snap7.common import check_error from snap7.types import S7DataItem, S7AreaDB, S7WLByteclient = snap7.client.Client() client.connect('10.100.5.2', 0, 2)data_items = (S7DataItem * 3)() # 注意就是這里數(shù)字不能大于19data_items[0].Area = ctypes.c_int32(S7AreaDB) # 地址類型 data_items[0].WordLen = ctypes.c_int32(S7WLByte) # 這里的WordLen除了讀TM和CT地址時(shí)其它地址統(tǒng)一用字節(jié)(S7WLByte)。不要用S7WLBit,用位去讀需要換算,不嫌麻煩你可以試試 data_items[0].Result = ctypes.c_int32(0) # result用不到寫0就可以 data_items[0].DBNumber = ctypes.c_int32(200) # 地址編號(hào) data_items[0].Start = ctypes.c_int32(16) # 變量起始字節(jié)地址 data_items[0].Amount = ctypes.c_int32(4) # 字節(jié)長(zhǎng)度data_items[1].Area = ctypes.c_int32(S7AreaDB) data_items[1].WordLen = ctypes.c_int32(S7WLByte) data_items[1].Result = ctypes.c_int32(0) data_items[1].DBNumber = ctypes.c_int32(200) data_items[1].Start = ctypes.c_int32(12) data_items[1].Amount = ctypes.c_int32(4) # reading a REAL, 4 bytesdata_items[2].Area = ctypes.c_int32(S7AreaDB) data_items[2].WordLen = ctypes.c_int32(S7WLByte) data_items[2].Result = ctypes.c_int32(0) data_items[2].DBNumber = ctypes.c_int32(200) data_items[2].Start = ctypes.c_int32(2) data_items[2].Amount = ctypes.c_int32(2) # reading an INT, 2 bytes# create buffers to receive the data # use the Amount attribute on each item to size the buffer for di in data_items:# create the bufferbuffer = ctypes.create_string_buffer(di.Amount)# cast the pointer to the buffer to the required typepBuffer = ctypes.cast(ctypes.pointer(buffer), ctypes.POINTER(ctypes.c_uint8))di.pData = pBufferfor di in data_items:check_error(di.Result)result, data_items = client.read_multi_vars(data_items)result_values = [] # function to cast bytes to match data_types[] above byte_to_value = [snap7.util.get_real, snap7.util.get_real, snap7.util.get_int]# unpack and test the result of each read for i in range(0, len(data_items)):btv = byte_to_value[i]di = data_items[i]value = btv(di.pData, 0)result_values.append(value) print(result_values)client.disconnect() client.destroy()
5.寫plc
方法介紹及示例
對(duì)變量賦值同樣也介紹兩種方法
- write_area(area, dbnumber, start, data)
要想對(duì)變量賦值,必須先讀取變量數(shù)組,然后在把要寫入的值設(shè)置到緩存,最后在寫到plc。
三步順序:
? 第一步:byte_arrays = read_area() 或 byte_arrays = bytearray(變量類型所占字節(jié)大小)
? 這個(gè)不介紹了不懂看上面讀plc方法介紹
? 第二步:在snap7.util里作者同樣封裝了不同類型變量更改字節(jié)數(shù)組的方法,這里拿bool類型描述一下,因?yàn)?其他類型參數(shù)基本都一樣,大家可看源碼
? set_bool(_bytearray, byte_index, bool_index, value)
? _bytearray:字節(jié)數(shù)組
? byte_index:字節(jié)索引
? bool_index:位索引
? value:要寫入的值(注意必須與要賦值的變量類型一致)
? 第三步:在通過(guò)write_area()函數(shù)把值寫進(jìn)plc
? write_area(area, dbnumber, start, data)
? area:地址類型
? dbnumber: 地址編號(hào)
? start:字節(jié)起始值
? data: 字節(jié)數(shù)組(就是你第一步讀出來(lái)的字節(jié)數(shù)組)
"""簡(jiǎn)單示例#1plc: s7-200SMART變量地址:M1.0 (1是起始值,0是bool索引)類型: bool """ from snap7 import util, client from snap7.snap7types import S7AreaMKmy_plc = client.Client() my_plc.set_connection_type(3) my_plc.connect('192.168.2.101', 0, 1) byte_arrays = my_plc.read_area(S7AreaMK, 0, 1, 1) print('賦值前', util.get_bool(byte_arrays, 0, 0)) util.set_bool(byte_arrays, 0, 0, 1) my_plc.write_area(S7AreaMK, 0, 1, byte_arrays) print('賦值后', util.get_bool(byte_arrays, 0, 0)) my_plc.disconnect() my_plc.destroy()-----------------------------------------------------------------------------------------"""簡(jiǎn)單示例#2plc: s7-1200變量地址:Q1.2 (1是起始值,2是bool索引)類型: bool """ from snap7 import util, client from snap7.snap7types import S7AreaPAmy_plc = client.Client() my_plc.connect('192.168.2.1', 0, 1) byte_arrays = my_plc.read_area(S7AreaPA, 0, 1, 1) print('賦值前', util.get_bool(byte_arrays, 0, 2)) util.set_bool(byte_arrays, 0, 2, 1) my_plc.write_area(S7AreaPA, 0, 1, byte_arrays) print('賦值后', util.get_bool(byte_arrays, 0, 2)) my_plc.disconnect() my_plc.destroy()同讀的思維一樣,我們這里也可以一次為同一地址,同一地址編號(hào)所有變量賦值
"""示例plc: s7-200SMART變量地址:V100.0 VD104類型: bool real """ from snap7 import util, client from snap7.snap7types import S7AreaDBmy_plc = client.Client() my_plc.set_connection_type(3) my_plc.connect('192.168.2.101', 0, 1) byte_arrays = my_plc.read_area(S7AreaDB, 1, 0, 108) print('賦值前', util.get_bool(byte_arrays, 100, 0), '賦值前', util.get_real(byte_arrays, 104)) util.set_bool(byte_arrays, 100, 0, 1) util.set_real(byte_arrays, 104, 999.99) my_plc.write_area(S7AreaDB, 1, 0, byte_arrays) print('賦值后', util.get_bool(byte_arrays, 100, 0), '賦值后', util.get_real(byte_arrays, 104)) my_plc.disconnect() my_plc.destroy()- write_multi_vars(items)
這同樣也是一個(gè)可以一次為多個(gè)不同地址變量賦值的函數(shù)(同樣不能大于19個(gè))
還是用作者的例子(參數(shù)用法大同小異)
import ctypes import snap7 from snap7.types import S7WLByte, S7DataItem, S7WLWord, S7WLReal, S7WLTimer from snap7.types import areas, wordlen_to_ctypes from snap7.util import set_int, set_real, set_word, get_int, get_real, get_s5timeclient = snap7.client.Client() client.connect('192.168.100.100', 0, 2)items = []def set_data_item(area, word_len, db_number: int, start: int, amount: int, data: bytearray) -> S7DataItem:item = S7DataItem()item.Area = ctypes.c_int32(area)item.WordLen = ctypes.c_int32(word_len)item.DBNumber = ctypes.c_int32(db_number)item.Start = ctypes.c_int32(start)item.Amount = ctypes.c_int32(amount)array_class = ctypes.c_uint8 * len(data)cdata = array_class.from_buffer_copy(data)item.pData = ctypes.cast(cdata, ctypes.POINTER(array_class)).contentsreturn itemint_values = [10, 20, 30, 40] ints = bytearray(len(int_values) * 2) for i, value in enumerate(int_values):set_int(ints, i * 2, value)real = bytearray(4) set_real(real, 0, 42.5)counters = 0x2999.to_bytes(2, 'big') + 0x1111.to_bytes(2, 'big')item1 = set_data_item(area=areas.DB, word_len=S7WLWord, db_number=1, start=0, amount=4, data=ints) item2 = set_data_item(area=areas.DB, word_len=S7WLReal, db_number=1, start=8, amount=1, data=real) item3 = set_data_item(area=areas.TM, word_len=S7WLTimer, db_number=0, start=2, amount=2, data=counters)items.append(item1) items.append(item2) items.append(item3)client.write_multi_vars(items)db_int = client.db_read(1, 0, 8) db_real = client.db_read(1, 8, 12) db_counters = client.ct_read(2, 2)print(f'int values: {[get_int(db_int, i * 2) for i in range(4)]}') print(f'real value: {get_real(db_real, 0)}') print(f'counters: {get_s5time(counters, 0)}, {get_s5time(counters, 2)}')6.總結(jié)
不總了,有問(wèn)題大家隨時(shí)交流
總結(jié)
以上是生活随笔為你收集整理的python使用Snap7读写西门子S7系列PLC的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 搞IT的技术人员为什么会如此苦逼
- 下一篇: Python操作Excel——win32