python使用Snap7读写西门子S7系列PLC
python使用Snap7讀寫西門子S7系列PLC
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
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對應不同的機架和卡槽看下圖對應
plcrackslot 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參數范圍如圖:
| 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造成影響。所以整體的順序應該是
set_connection_type(connection_type) #選用 set_connection_params(ip, local_tsap, remote_tsap) #選用 connect(ip, rack, slot) disconnect() destroy()
連接plc
第一步首先實例化一個Client對象
from snap7 import clientmy_plc = client.Client()第二步調用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是機架號,slot卡槽號,不同的plc對應不同的機架和卡槽看上邊表格print(my_plc.get_connected()) # 判斷連接成功可調用get_connected():返回True就是成功,不成功直接報錯。第三步斷開連接
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/設備IPV4地址:param local_tsap: 本地tsap(PC tsap):param remote_tsap: 遠程tsap(PLC tsap):param rack: 服務器上的機架:param slot: 服務器上的插槽"""# 初始化一個客戶端my_plc = client.Client()# 設置內部(IP、LocalTSAP、RemoteTSAP)坐標。必須在connect()之前調用此函數my_plc.set_connection_params(ip, local_tsap, remote_tsap)# 連接到S7服務器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/設備IPV4地址:param plc_model: 連接類型:1用于PG,2用于OP,3至10用于S7基本:param rack: 服務器上的機架:param slot: 服務器上的插槽"""# 初始化一個客戶端my_plc = client.Client()# 設置連接資源類型,即客戶端,連接到PLCmy_plc.set_connection_type(plc_model)# 連接到S7服務器my_plc.connect(ip, rack, slot)return my_plcdef connect_plc(ip: str, rack: int, slot: int):"""連接s7-1200/1500系列:param ip: PLC/設備IPV4地址:param rack: 服務器上的機架:param slot: 服務器上的插槽"""my_plc = client.Client()my_plc.connect(ip, rack, slot)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),如下字典對應
# 不同類型所占字節大小 TypeSize = {'int': 2, # 有符號(-32768~32767)'bool': 1, # bool值'dint': 4, # 有符號 (-2147483648~2147483647)'word': 2, # 無符號(0~65536)'real': 4, # 有符號 float類型(這范圍記不住了)'dword': 4, # 無符號(0~4294967295)'char': 1, # CHAR,ASCII字符集,占用1個字節內存,主要針對歐美國家(字符比較少)'string': 255, # STRING,占用256個字節內存,ASCII字符串,由ASCII字符組成's5time': 2,'wchar': 2, # WCHAR,Unicode字符集,占用2個字節內存,主要針對亞洲國家(字符比較多)'wstring': 512, # WSTRING,默認占用512個字節內存(可變),Unicode字符串,由Unicode字符構成'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)數據作為無符號雙整數值存儲,被解釋為自指定日期的凌晨算起的毫秒數(凌晨 = 0ms)。必須指定小時(24 小時/天)、分鐘和秒??梢赃x擇指定小數秒格式。'dtl': 12, # DTL(日期和時間長型)數據類型使用 12 個字節的結構保存日期和時間信息??梢栽趬K的臨時存儲器或者 DB 中定義 DTL 數據。'date': 2, # Date(16位日期值)、'ltod': 8 }IQMDB/VCTTM 0x81 0x82 0x83 0x84 0x1C 0x1D 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,有一些類型作者還沒寫,大家有用到可以自己解。
-
這里介紹一下start,size和byte_index之間關系以及如何應用,方便理解個人整理以下如圖
注意多個變量的適用條件必須為同一地址(area),同一地址編號(dbnumber)。所以通過read_area函數可以一次讀取同一地址編號上的所有變量
"""示例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塊,地址編號4的變量,套用圖上公公式,最小的起始值是0,size是最大起始值加它類型所占的字節數就是36+float類型所占4個byte長度,所以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個不同地址類型的變量,由于pdu大小限制一次性讀取不能超過19個變量
items參數是一個由S7DataItem實例對象組成的列表
下面我用作者寫的一個示例給大家介紹一下
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)() # 注意就是這里數字不能大于19data_items[0].Area = ctypes.c_int32(S7AreaDB) # 地址類型 data_items[0].WordLen = ctypes.c_int32(S7WLByte) # 這里的WordLen除了讀TM和CT地址時其它地址統一用字節(S7WLByte)。不要用S7WLBit,用位去讀需要換算,不嫌麻煩你可以試試 data_items[0].Result = ctypes.c_int32(0) # result用不到寫0就可以 data_items[0].DBNumber = ctypes.c_int32(200) # 地址編號 data_items[0].Start = ctypes.c_int32(16) # 變量起始字節地址 data_items[0].Amount = ctypes.c_int32(4) # 字節長度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
方法介紹及示例
對變量賦值同樣也介紹兩種方法
- 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: 字節數組(就是你第一步讀出來的字節數組)
"""簡單示例#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()-----------------------------------------------------------------------------------------"""簡單示例#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()同讀的思維一樣,我們這里也可以一次為同一地址,同一地址編號所有變量賦值
"""示例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)
這同樣也是一個可以一次為多個不同地址變量賦值的函數(同樣不能大于19個)
還是用作者的例子(參數用法大同小異)
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.總結
不總了,有問題大家隨時交流
總結
以上是生活随笔為你收集整理的python使用Snap7读写西门子S7系列PLC的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 搞IT的技术人员为什么会如此苦逼
- 下一篇: PWA(Progressive Web