PLC(Programmable Logic Controller)即可編程邏輯控制器,可以理解為一個微型計算機,廣泛應用於工業控制領域中,包括樓宇智控、精密機床、汽車電子等等。
隨著物聯網的興起,越來越多的傳統工業設備需要和外界通信,但很多情況下,類似PLC的微控制器經常會由於自身硬件因素而無法與外界直接互聯互通。PC作為一箇中介橋樑,為PLC與外界的溝通打開了一扇門。
而Python作為當前最火的語言,不僅在AI、雲計算等諸多方面都能看到它的身影,在工業控制中也不能少了它。本文就來分享下如何使用Python構建PC與PLC的通信,也算展示一把Python在工控領域的風采。
Snap7簡介
當前市場上主流的PLC通信方式為網絡通信和串行通信。網絡通信這塊主要協議有profinet,modbus-tcp等,串行通信主要是基於RS232/485的modbus。
本次接觸到的是西門子S7系列的PLC,通信方式都為網絡型的,而Snap7(http://snap7.sourceforge.net/)正是一個開源的、32/64位的、多平臺的以太網通訊庫:
- 支持多硬件體系結構(i386/x86_64、ARM/ARM64、Sun Sparc、Mips);
- 支持多系統(Windows、Linux、BSD、Solaris);
- 支持多語言(C/C++、Phyton、Node.js、Pascal、C#、VB)。
Python對其進行了封裝,具體可以參見:https://github.com/gijzelaerr/python-snap7。
開發環境搭建
這裡主要從Windows和Linux(Ubuntu)兩個平臺,說說如何搭建Python環境下的Snap7開發環境。Python的安裝這裡就不再贅述,環境搭建主要就是Snap7和python-snap7兩個庫的安裝。
1、安裝Snap7
Windows下,需要根據Python的結構版本(32位/64位),將下載的Snap7的發佈庫copy到對應的Python安裝根目錄下即可。
如上圖所示,我的Python是32bit,所以需要將Snap7中Win32目錄下的文件Copy到Python的安裝根目錄下,如下圖所示:
Linux(Ubuntu)下安裝相對簡單些,按如下命令即可:
$ sudo -s
$ add-apt-repository ppa:gijzelaar/snap7
$ apt-get update
$ apt-get install libsnap71 libsnap7-dev
2、安裝python-snap7
Snap7的Python庫安裝就簡單很多了,不管是Windows還是Linux,直接pip安裝即可。
$ pip install python-snap7
經過上面兩步,環境就算搭建好了。通過一個連接測試代碼試試,判斷下環境是否搭建正常。
import snap7
client = snap7.client.Client()
client.connect('192.168.0.1', 0, 1)
client.disconnect()
如果是下圖提示,則環境正常(192.168.0.1的PLC不存在)。
如果是下圖提示,則環境異常(snap7庫安裝不正確)。
讀寫PLC
環境搭建正常後,在正式建立通信前PLC還需做些配置工作,主要是開發自身的讀寫權限。具體參照下圖配置:
通過上述配置,PLC可以正常通信了。
1、python-snap7讀寫分析
結合python-snap7的文檔API和源碼分析,python-sna7重要的兩個方法是read_area和write_area,通過這兩個方法就能讀和寫PLC的對應存儲地址。
def read_area(self, area, dbnumber, start, size):
"""This is the main function to read data from a PLC.
With it you can read DB, Inputs, Outputs, Merkers, Timers and Counters.
:param dbnumber: The DB number, only used when area= S7AreaDB
:param start: offset to start writing
:param size: number of units to read
"""
assert area in snap7.snap7types.areas.values()
wordlen = snap7.snap7types.S7WLByte
type_ = snap7.snap7types.wordlen_to_ctypes[wordlen]
logger.debug("reading area: %s dbnumber: %s start: %s: amount %s: "
"wordlen: %s" % (area, dbnumber, start, size, wordlen))
data = (type_ * size)()
result = self.library.Cli_ReadArea(self.pointer, area, dbnumber, start,
size, wordlen, byref(data))
check_error(result, context="client")
return bytearray(data)
@error_wrap
def write_area(self, area, dbnumber, start, data):
"""This is the main function to write data into a PLC. It's the
complementary function of Cli_ReadArea(), the parameters and their
meanings are the same. The only difference is that the data is
transferred from the buffer pointed by pUsrData into PLC.
:param dbnumber: The DB number, only used when area= S7AreaDB
:param start: offset to start writing
:param data: a bytearray containing the payload
"""
wordlen = snap7.snap7types.S7WLByte
type_ = snap7.snap7types.wordlen_to_ctypes[wordlen]
size = len(data)
logger.debug("writing area: %s dbnumber: %s start: %s: size %s: "
"type: %s" % (area, dbnumber, start, size, type_))
cdata = (type_ * len(data)).from_buffer_copy(data)
return self.library.Cli_WriteArea(self.pointer, area, dbnumber, start,
size, wordlen, byref(cdata))
從參數可見,需要提供PLC的區域地址、起始地址、讀和寫的數據長度。PLC能提供如下信息:
2、PLC數據存儲和地址
通過閱讀PLC的手冊獲取到如下信息:
PLC的數據存儲通過Tag的形式與存儲區間關聯,分為輸入(I)、輸出(O)、位存儲(M)和數據塊(DB)。程序在訪問對應(I/O)tag時,是通過訪問CPU的Process Image Out對相應地址進行操作的。具體對應關係如下:
到這裡就能明白python-snap7中定義的areas地址是什麼含義了。
areas = ADict({
'PE': 0x81, #input
'PA': 0x82, #output
'MK': 0x83, #bit memory
'DB': 0x84, #DB
'CT': 0x1C, #counters
'TM': 0x1D, #Timers
})
現在離讀寫PLC還差最後一步,就是起始地址如何確定呢?
從上可見對於M3.4,對應的就是M(0x83),起始地址是3,對應bit位是4。
實戰
經過上面的精心準備,下面就來一波實戰。通過讀寫PLC的M10.1、MW201來具體看看如何讀寫PLC。
import struct
import time
import snap7
def plc_connect(ip, rack=0, slot=1):
"""
連接初始化
:param ip:
:param rack: 通常為0
:param slot: 根據plc安裝,一般為0或1
:return:
"""
client = snap7.client.Client()
client.connect(ip, rack, slot)
return client
def plc_con_close(client):
"""
連接關閉
:param client:
:return:
"""
client.disconnect()
def test_mk10_1(client):
"""
測試M10.1
:return:
"""
area = snap7.snap7types.areas.MK
dbnumber = 0
amount = 1
start = 10
print(u'初始值')
mk_data = client.read_area(area, dbnumber, start, amount)
print(struct.unpack('!c', mk_data))
print(u'置1')
client.write_area(area, dbnumber, start, b'')
print(u'當前值')
mk_cur = client.read_area(area, dbnumber, start, amount)
print(struct.unpack('!c', mk_cur))
def test_mk_w201(client):
"""
測試MW201,數據類型為word
:param client:
:return:
"""
area = snap7.snap7types.areas.MK
dbnumber = 0
amount = 2
start = 201
print(u'初始值')
mk_data = client.read_area(area, dbnumber, start, amount)
print(struct.unpack('!h', mk_data))
print(u'置12')
client.write_area(area, dbnumber, start, b'')
print(u'當前值')
mk_cur = client.read_area(area, dbnumber, start, amount)
print(struct.unpack('!h', mk_cur))
time.sleep(3)
print(u'置3')
client.write_area(area, dbnumber, start, b'')
print(u'當前值')
mk_cur = client.read_area(area, dbnumber, start, amount)
print(struct.unpack('!h', mk_cur))
if __name__ == "__main__":
client_fd = plc_connect('192.168.0.1')
test_mk10_1(client_fd)
test_mk10_1(client_fd)
plc_con_close(client_fd)
從代碼可見,MW201,根據M確定area為MK,根據W確定數據amount為2Btye,根據201確定start為201,讀出來的數據根據數據長度用struct進行unpack,寫數據對應strcut的pack。
這裡給出PLC變量類型和大小,這樣對應確定讀寫的amount。
最後給出一段視頻,Python操作PLC來個跑馬燈。
“徵稿啦!”
CSDN 公眾號秉持著「與千萬技術人共成長」理念,不僅以「極客頭條」、「暢言」欄目在第一時間以技術人的獨特視角描述技術人關心的行業焦點事件,更有「技術頭條」專欄,深度解讀行業內的熱門技術與場景應用,讓所有的開發者緊跟技術潮流,保持警醒的技術嗅覺,對行業趨勢、技術有更為全面的認知。
如果你有優質的文章,或是行業熱點事件、技術趨勢的真知灼見,或是深度的應用實踐、場景方案等的新見解,歡迎聯繫 CSDN 投稿,聯繫方式:微信(guorui_1118,請備註投稿+姓名+公司職位),郵箱([email protected])。
閱讀更多 CSDN 的文章