Python使用python-snap7实现西门子PLC通讯
Python簡介
Python是開源的高級編程語言之一,廣泛應(yīng)用于人工智能、數(shù)據(jù)分析、爬蟲等領(lǐng)域。由于它擁有大量的開源庫和標準庫,以及簡單且貼近自然語言的語法,所以即便是從未接觸過編程的人,也能快速上手。2021年10月,Python登頂Tiobe,成為全世界最熱門的編程語言之一。
以下摘自百度百科:
Python由荷蘭數(shù)學和計算機科學研究學會的吉多·范羅蘇姆 于1990 年代初設(shè)計,作為一門叫做ABC語言的替代品。Python提供了高效的高級數(shù)據(jù)結(jié)構(gòu),還能簡單有效地面向?qū)ο缶幊獭ython語法和動態(tài)類型,以及解釋型語言的本質(zhì),使它成為多數(shù)平臺上寫腳本和快速開發(fā)應(yīng)用的編程語言,隨著版本的不斷更新和語言新功能的添加,逐漸被用于獨立的、大型項目的開發(fā)。??
需要注意的是,Python存在2.x版本和3.x版本,本文使用的是3.x的版本。
本文代碼已上傳至GitHub,項目地址如下:
https://github.com/XMNHCAS/Snap7PythonDemo
Snap7簡介
snap7是一個由國外程序員開發(fā)的基于以太網(wǎng)與西門子S7系列PLC的通訊的開源庫,類似于C#的S7.Net,但是它不單只支持Python,還支持Java、C/C++、C#等語言。
以下是snap7的官網(wǎng):
http://snap7.sourceforge.net/
官網(wǎng)包含了使用文檔、源碼、歷史版本等說明,可以根據(jù)需要自行查閱。
而python-snap7則是snap7的python版本,有單獨的文檔以及使用說明,只能用于python,以下是官方文檔及GitHub鏈接:
python-snap7官方文檔
GitHub
導入python-snap7
首先需要安裝python-snap7,打開cmd,輸入以下命令,安裝python-snap7
pip3 install python-snap7?
打開vscode,新建一個py文件,然后在最上面導入snap7
如果不知道如何安裝python和使用vscode進行python代碼編輯,可以百度一下,此處不贅述如何配置Python開發(fā)環(huán)境
import snap7創(chuàng)建連接
snap7實現(xiàn)通訊的時候,是將PLC作為服務(wù)端,PC以客戶端的身份主動連接的,所以最開始的時候,我們應(yīng)該創(chuàng)建通訊需要使用的客戶端
# 創(chuàng)建通訊客戶端實例 plcObj = snap7.client.Client()# 連接至PLC plcObj.connect('192.168.10.230', 0, 1)如果手頭沒有PLC,可以參考我寫的這篇文章:C#使用S7NetPlus以及PLCSIM Advanced V3.0實現(xiàn)西門子PLC仿真通訊
使用PLCSIM Advanced可以仿真出PLC來進行通訊測試
測試連接功能是否正常:
import snap7# 創(chuàng)建通訊客戶端實例 plcObj = snap7.client.Client()# 連接至PLC plcObj.connect('192.168.10.230', 0, 1)# 打印連接狀態(tài) print(f"連接狀態(tài):{plcObj.get_connected()}")# 關(guān)閉連接 plcObj.disconnect()# 打印連接狀態(tài) print(f"連接狀態(tài):{plcObj.get_connected()}")讀取數(shù)據(jù)
python-snap7并未集成像S7.Net那樣的讀取即刻解析數(shù)據(jù)的功能,所以無論是讀還是寫,都是需要進行字節(jié)轉(zhuǎn)換的。
以讀取DB10的以下的五個變量為例:
打開TIA Protal,創(chuàng)建DB塊,編號為10,并添加如下圖所示的變量并賦初值,下載到仿真的PLC后打開打開數(shù)值監(jiān)控:
首先我們需要計算需要讀取的總字節(jié)數(shù),也就是最后一個變量的地址(即偏移量)加上它的數(shù)據(jù)長度??梢钥吹?#xff0c;上面的六個變量中,最后一個地址是264,WString為512字節(jié),所以需要讀取的總字節(jié)數(shù)為264+512=776個。
所以第一步,把這776個字節(jié)都讀取上來:
data = plcObj.db_read(10, 0, 776)其中,plcObj是我們剛剛創(chuàng)建的通訊客戶端對象,db_read是它的讀取DB塊的方法,第一個參數(shù)是DB號,第二個是要讀取的字節(jié)起始的起始地址,第三個參數(shù)是要讀取的字節(jié)總數(shù),用data這個變量來接收這些數(shù)據(jù)。
當然也可以寫成這種形式:
data = plcObj.read_area(snap7.client.Areas.DB, 10, 0, 776)read_area是讀取任意區(qū)域的方法,通過第一個參數(shù)的枚舉來區(qū)分讀取的區(qū)域,如input、output、DB等,后面三個參數(shù)與db_read一致。
當我們需要的數(shù)據(jù)以字節(jié)的形式讀取上來以后,我們就可以進行解析了。
解析數(shù)據(jù)
這里解析的方法有兩種,第一種是使用python自身的數(shù)據(jù)類型轉(zhuǎn)換的方法進行解析,第二種是使用python-snap7提供的轉(zhuǎn)換方法進行解析。
Python自身函數(shù)
首先我們介紹第一種方法,使用python自身的數(shù)據(jù)類型轉(zhuǎn)換的方法進行解析。
在python中,bool、int兩個類型都有一個from_bytes的方法,可以通過這個方法來將字節(jié)數(shù)組轉(zhuǎn)換為對應(yīng)的數(shù)據(jù)。但是需要注意的是,在PLC中,數(shù)據(jù)是大端存儲的,而PC中一般是小端存儲,所以在這樣進行轉(zhuǎn)換的時候,需要加上byteorder='big',來聲明讀取上來的字節(jié)是大端存儲的方式。故前面的bool和int變量都可以以這種方式進行解析:
# 讀取bool的值 bool.from_bytes(data[0:1], byteorder='big') # 讀取int的值 int.from_bytes(data[2:4], byteorder='big')其中[ 數(shù)字 : 數(shù)字 ]是python截取數(shù)組的語法,左邊的數(shù)字是截取的起始索引號,右邊的是截取的停止的索引號,需要注意的是,右邊的索引是不會被截取的,所以像上面代碼的data[ 0 : 1 ],實際上截取的只有data[0],而data[ 2 : 4 ],截取的則是data[2]和data[3]。如果左邊的數(shù)字不填,則默認從索引0開始截取;而如果右邊的數(shù)字不填,則默認截取剩余的所有元素。
而對于字符串,python提供了decode方法,可以將字符串的字節(jié)數(shù)組按照指定的編碼格式來轉(zhuǎn)換為字符串。不過PLC中的字符串的頭字節(jié)是字符串的變量長度和字符串的實際長度,所以需要跳過這些字節(jié)來讀取實際的數(shù)據(jù)。string類型是單字節(jié)存儲的ASCII編碼的字符串,所以跳過前兩個字節(jié)。而wstring類型則是雙字節(jié)存儲的UTF-16BE編碼的字符串,所以需要跳過前四個字節(jié)。
# 讀取string的值 data[10:264].decode(encoding="ascii") # 讀取wstring的值 data[268:].decode(encoding="utf-16be")在python中,float類型沒有像bool和int一樣的from_bytes方法,這個時候我們就需要使用struct來進行解析。struct是python的模塊之一,可以用以解析字節(jié),剛剛說到的bool、int和字符串都可以用它來解析,當然float也是可以的,不過要講的話東西就會比較多,此次就不深入講解了。
struct有pack和unpack兩個方法,分別用以將數(shù)據(jù)轉(zhuǎn)換為字節(jié)流和將字節(jié)流解析為對應(yīng)的數(shù)據(jù),如下所示,即可將real數(shù)值轉(zhuǎn)換為python的float類型:
# 讀取real的值 struct.unpack('>f', data[4:8])[0]第一個參數(shù)是指定需要轉(zhuǎn)換成的數(shù)據(jù)類型,“f”代表float類型,前面加的“>”代表這個字節(jié)數(shù)組是大端存儲的方式,第二個參數(shù)是需要轉(zhuǎn)換的字節(jié)流。由于unpack是以元組的形式返回的數(shù)據(jù),所以需要加[0]來獲取它返回的第一個數(shù)據(jù)。
接下來運行一下我們的代碼,來查看讀取結(jié)果:
import snap7 import struct# 創(chuàng)建通訊客戶端實例 plcObj = snap7.client.Client()# 連接至PLC plcObj.connect('192.168.10.230', 0, 1)# 讀取數(shù)據(jù) data = plcObj.db_read(10, 0, 776)# 關(guān)閉連接 plcObj.disconnect()# python解析 selfBool = bool.from_bytes(data[0:1], byteorder='big') selfInt = int.from_bytes(data[2:4], byteorder='big') selfReal = struct.unpack('>f', data[4:8])[0] selfString = data[10:264].decode(encoding="ascii") selfWString = data[268:].decode(encoding="utf-16be") print("python自身函數(shù)解析:") print(f"bool:{selfBool}; int:{selfInt}; real:{selfReal}; string:{selfString}; wstring:{selfWString}" )?
對比我們上面的PLC中的數(shù)值,基本上是沒什么問題的。至于為什么real里的30.1,讀上來之后會變成30.100000381469727,是因為計算機中的浮點型的存儲規(guī)則決定了浮點數(shù)在部分情況下只能是個近似值,并非是python的問題,由于誤差是比較小,實際應(yīng)用時一般可以忽略不計。
通過上述方法,我們可以知道python自身就足以解析PLC讀取上來的字節(jié),但是這種方法對于部分人來說可能不是那么好理解,使用起來也不是那么簡單直觀,而且更重要的是,這種方法解析bool值存在一點小問題,畢竟python對二進制的位操作支持得并沒有C#那么好。
python-snap7函數(shù)
接下來我們來介紹第二種方法,使用python-snap7提供的轉(zhuǎn)換方法進行解析。
首先查看文檔,可以看到snap7中有一個util的模塊,它提供了多種數(shù)據(jù)類型的轉(zhuǎn)換方法,可以將從PLC讀取上來的字節(jié)直接解析為python可識別的數(shù)據(jù)類型。
首先導入util,這樣我們可以少寫個snap7,提高代碼的簡明性。?
from snap7 import util根據(jù)文檔,我們寫出以下轉(zhuǎn)換代碼:
# Bool的值 util.get_bool(data, 0, 0) # Int的值 util.get_int(data, 2) # Real的值 util.get_real(data, 4) # String的值 util.get_string(data, 8, 256)可以看到,我們在使用snap7的轉(zhuǎn)換方法的時候,只需要把我們讀取到的字節(jié)數(shù)組以及數(shù)據(jù)的起始索引傳進去即可,比起使用python自身的方法會更加簡單。
get_bool方法的第三個參數(shù)為該字節(jié)的第幾個bool量,因為一個bool量只需要一個位來表示,而一個字節(jié)是包含八個位的,也就是說這個字節(jié)可以表示八個bool量,在這里對應(yīng)的DB10里地址為0.0~0.7的八個bool量,由于我們要讀取的是地址0上的第一個bool量,所以第二個參數(shù)和第三個參數(shù)分別為0,0。
get_string方法的第三個參數(shù)為該字符串的最大長度,由于string類型共有256個字節(jié),所以此處填256。
打印一下兩種方法的結(jié)果:
import snap7 import struct from snap7 import util# 創(chuàng)建通訊客戶端實例 plcObj = snap7.client.Client()# 連接至PLC plcObj.connect('192.168.10.230', 0, 1)# 讀取數(shù)據(jù) data = plcObj.db_read(10, 0, 776)# 關(guān)閉連接 plcObj.disconnect()# python解析 selfBool = bool.from_bytes(data[0:1], byteorder='big') selfInt = int.from_bytes(data[2:4], byteorder='big') selfReal = struct.unpack('>f', data[4:8])[0] selfString = data[10:264].decode(encoding="ascii") selfWString = data[268:].decode(encoding="utf-16be") print("python自身函數(shù)解析:") print(f"bool:{selfBool}; int:{selfInt}; real:{selfReal}; string:{selfString}; wstring:{selfWString}" )# snap7解析 snap7Bool = util.get_bool(data, 0, 0) snap7Int = util.get_int(data, 2) snap7Real = util.get_real(data, 4) snap7String = util.get_string(data, 8, 256) snap7WString = util.get_string(data, 264, 508) print("snap7函數(shù)解析:") print(f"bool:{snap7Bool}; int:{selfInt}; real:{snap7Real}; string:{snap7String}; wstring:{snap7WString}" )?
可以看到,wstring讀取出來的結(jié)果是亂碼,這是因為python-snap7的頭字節(jié)解析及編碼格式問題,所以當我們讀取wstring的時候,最好還是用python自己的decode方法
寫入數(shù)據(jù)
與讀取數(shù)據(jù)一樣,寫入數(shù)據(jù)也有兩種方式,但是區(qū)別也僅僅是生成寫入數(shù)據(jù)的字節(jié)方式不用。
寫入數(shù)據(jù)可以調(diào)用db_write方法,也可以調(diào)用write_area,與讀取數(shù)據(jù)一樣,前者只能用以寫入DB塊,后者可以寫入任意區(qū)域。
plcObj.write_area(snap7.client.Areas.DB,10,0,data) plcObj.db_write(10,0,data)?data是需要寫入的數(shù)據(jù)的字節(jié)形式。
Python轉(zhuǎn)換
我們在解析讀取數(shù)據(jù)的時候提到,bool和int有from_bytes,同樣的它們也有to_bytes方法,用以將數(shù)據(jù)轉(zhuǎn)換成對應(yīng)的字節(jié)數(shù)組的形式:
# bool的字節(jié) bool.to_bytes(True, 1, 'big')# int的字節(jié)數(shù)組(雙字節(jié)) int.to_bytes(200, 2, 'big')同樣的,字符串類型有解碼的decode方法,也有轉(zhuǎn)碼的encode方法:
# string的字節(jié)數(shù)組 str = 'hello python' str.encode(encoding='ascii')# wstring的字節(jié)數(shù)組 str = '中國北京市' str.encode(encoding='utf-16be')而對于float類型要用的struct,我們解析數(shù)據(jù)時用到了它的unpack方法,而轉(zhuǎn)換為字節(jié)的時候,我們就需要調(diào)用它的pack方法:
# float的字節(jié)數(shù)組 struct.pack(">f", 10.1)所以我們能很輕易地寫出寫入數(shù)據(jù)的代碼:
import snap7 import struct# 創(chuàng)建通訊客戶端實例 plcObj = snap7.client.Client()# 連接至PLC plcObj.connect('192.168.10.230', 0, 1)# 寫入DB10.0 —— bool值 plcObj.write_area(snap7.client.Areas.DB, 10, 0, bool.to_bytes(False, 1, 'big'))# 寫入DB10.2 plcObj.write_area(snap7.client.Areas.DB, 10, 2, int.to_bytes(200, 2, 'big')) # plcObj.write_area(snap7.client.Areas.DB, 10, 2, struct.pack(">h", 112))# 寫入DB10.4 —— real值 plcObj.write_area(snap7.client.Areas.DB, 10, 4, struct.pack(">f", 10.1))# 寫入DB10.8 —— string值 str = 'hello python' data = int.to_bytes(254, 1, 'big') + int.to_bytes(len(str), 1, 'big') + str.encode(encoding='ascii') plcObj.write_area(snap7.client.Areas.DB, 10, 8, data)# 寫入DB10.264 —— wstring值 str = '中國北京市' data = int.to_bytes(508, 2, 'big') + int.to_bytes(len(str), 2, 'big') + str.encode(encoding='utf-16be') plcObj.write_area(snap7.client.Areas.DB, 10, 264, data)# 關(guān)閉連接 plcObj.disconnect()運行程序后,回到TIA Protal的監(jiān)視界面可以看到,數(shù)值已經(jīng)被更改了:
python-snap7轉(zhuǎn)換
python-snap7提供了不同數(shù)據(jù)類型的轉(zhuǎn)換方法,如下圖所示:
?所以根據(jù)我們這里的五種變量類型,可以寫出對應(yīng)的轉(zhuǎn)換代碼:
# bool的字節(jié)數(shù)組 boolData = bytearray(1) util.set_bool(boolData, 0, 0, True)# int的字節(jié)數(shù)組 intData = bytearray(2) util.set_int(intData, 0, 100)# real的字節(jié)數(shù)組 realData = bytearray(4) util.set_real(realData, 0, 20.5)# string的字節(jié)數(shù)組 str = "hello snap7" stringData = bytearray(len(str) + 2) util.set_string(stringData, 0, str, 256) stringData[0] = 254需要注意,string類型在調(diào)用了set_string方法后,首字節(jié)的字符最大值依舊是0,可能是python-snap7目前仍然存在的小bug,所以需要手動修改為真實的最大值。
由于目前并沒有set_wstring方法,而且set_string方法也不支持wstring,所以wstring依然使用python的decode方法進行寫入。
寫入代碼如下:
import snap7 from snap7 import util# 創(chuàng)建通訊客戶端實例 plcObj = snap7.client.Client()# 連接至PLC plcObj.connect('192.168.10.230', 0, 1)# 寫入bool boolData = bytearray(1) util.set_bool(boolData, 0, 0, True) plcObj.db_write(10, 0, boolData)# 寫入int intData = bytearray(2) util.set_int(intData, 0, 100) plcObj.db_write(10, 2, intData)# 寫入real realData = bytearray(4) util.set_real(realData, 0, 20.5) plcObj.db_write(10, 4, realData)# 寫入string str = "hello snap7" stringData = bytearray(len(str) + 2) util.set_string(stringData, 0, str, 256) stringData[0] = 254 plcObj.db_write(10, 8, stringData)# 寫入wstring str = '中國廣州市' data = int.to_bytes(508, 2, 'big') + int.to_bytes(len(str), 2, 'big') + str.encode(encoding='utf-16be') plcObj.db_write(10, 264, data)plcObj.disconnect()再打開TIA Protal,可以看到數(shù)據(jù)已經(jīng)成功寫入。
結(jié)尾
本文介紹了怎么使用python實現(xiàn)西門子PLC的通訊,如果會C#的寫法的話,可以很明顯感覺到python的代碼量會比C#少,但是相對的,數(shù)據(jù)解析也會比C#稍微麻煩一點。不過底層的通訊原理都是一樣的,只是具體實現(xiàn)方式不同而已。
總結(jié)
以上是生活随笔為你收集整理的Python使用python-snap7实现西门子PLC通讯的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于gcc、glibc和binutils
- 下一篇: Android 项目在Eclipse中的