Python and PLC Communication

  liuyuancheng        2024-06-30 02:21:17       1,958        0          English  简体中文  繁体中文  ภาษาไทย  Tiếng Việt 

本文提供了關於如何使用 Python 通過乙太網路與 Schneider M221 和 Siemens S7-1200 PLC 進行通訊的詳細步驟。此外,我們還提供了一個打包的 Python PLC 客戶端函式庫,使您能夠輕鬆構建自己的 SCADA 控制程式,例如人機介面 (HMI)。系統概述如下所示:

要查看專案詳細資訊,請參閱Python 實體 PLC 通訊客戶端

# Created:     2024/06/29
# Version:     v0.1.3
# Copyright:   Copyright (c) 2024 LiuYuancheng
# License:     MIT License

 


簡介

本文將引導初學者 OT 工程師完成四個基本步驟,以在 SCADA 網路中配置可程式邏輯控制器 (PLC),並使用 Python 腳本或我們的 Python 實體 PLC 通訊客戶端函式庫與 PLC 進行通訊。這四個步驟包括:

 

  • [步驟 1] 配置 PLC:學習如何使用 PLC 供應商的 IDE 設定 PLC 的 IP 位址,以啟用網路連線。請注意,本節不涵蓋電氣接線連接。

  • [步驟 2] 配置階梯邏輯:了解如何配置 PLC 的 I/O 和階梯邏輯圖,以連結接點、記憶體和線圈。

  • [步驟 3] 了解 PLC 通訊協定:簡要概述用於與 PLC 互動的通訊協定。本節為選修。

  • [步驟 4] 使用 Python 控制 PLC:說明如何建構 Python 程式來控制 PLC,包括展示如何使用 Python 實體 PLC 通訊客戶端 API 的程式碼範例。

 


 

與 Schneider M221 PLC 通訊

 

M221 PLC 簡介:Schneider Electric M221 PLC 是一款緊湊且多功能的可程式邏輯控制器,專為中小型自動化專案而設計。它是 Modicon M221 系列的一部分,具有 16 個 I/O、7 個繼電器輸出,以其高效能和成本效益而聞名。M221 PLC 支援各種通訊協定,包括 Modbus TCP/IP 和串列通訊,使其易於整合到現有系統中。憑藉其強大的處理能力、廣泛的 I/O 選項以及透過 SoMachine Basic 軟體進行的使用者友善程式設計,M221 PLC 非常適合控制機械、管理流程和增強工業環境中的自動化。

步驟 1:配置 Schneider M221 PLC

 

啟動 M221 PLC 並將其連接到網路。使用 SoMachine 編輯器搜尋並連接到 PLC 裝置。然後在MyController > ETH1區段中為 PLC 配置固定 IP 位址並啟用 Modbus 通訊,如下所示:

圖 03:M221 PLC IP 配置頁面,版本 v1.3 (2024)

 

選取固定 IP 位址並填寫 IP 資訊,勾選已啟用乙太網路/IP 協定已啟用 Modbus 伺服器。然後,同一子網路中的程式可以透過 IP 位址連接到 PLC 並與 Modbus 伺服器通訊。

 

步驟 2:配置階梯邏輯

雖然 M221 支援標準 Modbus TCP 協定通訊,但如果沒有 SoMachine SDK,您無法直接讀取接點「I0.X」或寫入線圈「Q0.X」。解決方案是將接點「I0.X」或線圈「Q0.X」對應到 PLC 記憶體位址。然後,您可以讀取或寫入此記憶體位址以取得接點輸入或設定線圈輸出。階梯邏輯可以草擬如下所示:

Rung 1: [ I0.x ] --> | M1x | 
Rung 2: | M1x | --> | 您的階梯邏輯 | --> | M2x |
Rung 3: | M2x | --> ( Q0.x )

 

開啟 SoMachine 階梯配置頁面並新增階梯邏輯,如下所示:

圖 04:SoMachine 編輯階梯邏輯 rung,版本 v1.3 (2024)

 

然後,在調試頁面中,選取「PC to Controller (download)」(電腦到控制器(下載))以將階梯邏輯提交到 PLC,如下所示:

圖 05:SoMachine 將階梯圖提交到 PLC 控制器,版本 v1.3 (2024)

步驟 3:了解通訊協定

M221 使用 Modbus TCP 通訊協定。Modbus TCP 框架結構順序如下所示:

  1. 交易識別碼 (2 個位元組):交易的唯一識別碼。它通常由客戶端設定,並由伺服器回顯。

  2. 協定識別碼 (2 個位元組):對於 Modbus TCP,始終設定為 0。

  3. 長度 (2 個位元組):後續位元組的數量,包括單元識別碼、功能碼和資料。

  4. 單元識別碼 (1 個位元組):遠端伺服器 (PLC) 的位址。

  5. 功能碼 (1 個位元組):定義要執行的操作(例如,讀取保持暫存器)。

  6. 資料:這包括請求或回應的具體詳細資訊(例如,起始位址、暫存器數量)。

若要與 PLC 互動,您需要使用特定的 Modbus 功能碼:

  • '0f':用於寫入多個位元的記憶體位元存取功能碼。

  • '01':用於讀取內部多個位元的記憶體位元狀態擷取功能碼%M

 

若要使用更多功能,請參閱M221 手冊第 196 頁中的功能碼表。

Figure-06: M221 Modbus 功能碼表,版本 v1.3 (2024)

 

M221 Modbus-TCP 資料封包序列表如下所示:

Figure-07: M221 Modbus 封包位元組序列圖,版本 v1.3 (2024)

從記憶體讀取位元資料的 Modbus 訊息序列:

TID PROTOCOL_ID 長度 UID 功能碼 記憶體索引 位元數量
2 個位元組 2 個位元組 2 個位元組 1 個位元組 1 個位元組 2 個位元組 2 個位元組
0000 0000 0006 01 01 <0032> <0008>

將位元組資料寫入記憶體的 Modbus 訊息序列:

TID PROTOCOL_ID 長度 UID 功能碼 記憶體索引 位元索引 位元組索引 值位元組
2 個位元組 2 個位元組 2 個位元組 1 個位元組  

要將記憶體標籤 %MXX 轉換為記憶體位址,只需將十進位數字轉換為十六進位數字(字串使用小寫)。範例如下所示:

MEM_ADDR_TAG_Example = {
    'M0':   '0000',
    'M1':   '0001',
    'M2':   '0002',
    'M3':   '0003',
    'M4':   '0004',
    'M5':   '0005',
    'M6':   '0006',
    'M10':  '000a',
    'M20':  '0014',
    'M30':  '001e',
    'M40':  '0028',
    'M50':  '0032',
    'M60':  '003c'
}

步驟 4:使用 Python 與 PLC 通訊

4.1 初始化連線

要與 PLC 通訊,首先初始化一個 TCP 用戶端,該用戶端連線到 PLC 在連接埠 502 上的 IP 位址,如下所示:

self.plcAgent = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
self.plcAgent.connect((self.ip, self.port))
if self.debug: print("M221Client: Connected to the PLC [%s]" % self.ip)
self.connected = True
except Exception as error:
print("M221Client: Can not access to the PLC [%s]" % str(self.plcAgent))
print(error)

4.2 將訊息傳送到 PLC

要將訊息傳送到 PLC,請將十六進位字串轉換為位元組:

bdata = bytes.fromhex(modbusMsg)
try:
  self.plcAgent.send(bdata)
  respBytes = self.plcAgent.recv(BUFF_SZ)
  respStr = respBytes.dencode('hex') if DECODE_MD else respBytes.hex()
  self.connected = True

4.3 讀取 PLC 記憶體資料

根據步驟 3,建立記憶體讀取 Modbus 訊息,並呼叫傳送函式以從 PLC 讀取記憶體位元組。

def readMem(self, memAddrTag, bitNum=8):
  if str(memAddrTag).startswith('M'):
      memoryDecimal = int(memAddrTag[1:])
      memoryHex = hex(memoryDecimal)[2:]
      bitNumHex = hex(bitNum)[2:]
      modbusMsg = ''.join((TID, PROTOCOL_ID, R_LENGTH, UID, M_RD,
      memoryHex, bitNumHex))
      response = self._getPlCRespStr(modbusMsg)
      return response

輸入:

  • memAddrTag:(str) 在步驟 2 中設定的階梯圖中的記憶體標籤,例如「M60」。

  • bitNum:(int) 如果我們要讀取 1 個位元組,則要從記憶體讀取多少位元,bitNum = 4。

4.4 寫入 PLC 記憶體資料

與資料讀取相同,根據步驟 3 中的位元組序列建立 modbus 訊息:

def writeMem(self, memAddrTag, val):
  if str(memAddrTag).startswith('M'):
      memoryDecimal = int(memAddrTag[1:])
      memoryHex = hex(memoryDecimal)[2:]
      byteVal = VALUES[val]
      modbusMsg = ''.join((TID, PROTOCOL_ID, W_LENGTH, UID, M_FC, memoryHex,
      BIT_COUNT, BYTE_COUNT, byteVal))
      response = self._getPlCRespStr(modbusMsg)
      return response

 

完整的 Python M221 PLC 用戶端程式

您可以從此連結下載完整的 Python M221 PLC 用戶端程式:M221PlcClient.py。此程式包含連線 PLC、讀取/寫入記憶體位元的 API,以及一個執行緒包裝函式類別,可讓您在主程式的平行執行緒中執行 PLC 讀取器,以定期讀取 PLC 狀態。

該程式庫還提供三個測試案例:

 

  • 測試案例 1 & 2:示範如何讀取和寫入 PLC 記憶體。

  • 測試案例 3:顯示如何使用用戶端執行緒包裝函式類別。

 


 

與 Siemens S7-1200 PLC 通訊

PLC 簡介

: 西門子 S7-1200 PLC 是一款緊湊且多功能的程式邏輯控制器,專為廣泛的工業自動化應用而設計。它屬於 SIMATIC S7 系列,並以其穩健的效能、可擴展性和易用性而聞名。它具有內建的 PROFINET 介面,並支援各種通訊協定,可無縫整合到工業網路中。S7Comm 是西門子 PLC(包括 S7-1200 系列)使用的專有通訊協定,用於促進裝置和軟體之間的通訊。它透過各種實體層運作,包括乙太網路(透過 PROFINET),允許 PLC、HMI 和 SCADA 系統之間進行資料交換。

 

步驟 1:設定西門子 S7-1200

 

將西門子 S7-1200 PLC 連接到您的網路。使用西門子 PLC 編輯器軟體,Siemens SIMATIC STEP 7 (TIA Portal) ,以在 PROFINET 介面頁面中設定 IP 位址:

圖 08:透過 SIMATIC STEP 7 (TIA Portal) 設定 S71200 IP,版本 v1.3 (2024)

 

您可以按照本文中概述的步驟:https://www.geekering.com/categories/automation/rodrigovieira/siemens-tia-portal-s7-1200-plc-online-connection-2/來設定 IP 位址並設定 PLC。

步驟 2:設定階梯邏輯和記憶體區域

S7-1200 PLC 支援直接對應記憶體區域,以讀取和寫入 PLC 接點、線圈和可編輯記憶體上的資料。使用 snap7 程式庫透過西門子 S7Comm 協定來促進與 PLC 的通訊。PLC 階梯邏輯可以設定如下:

  | ix.x/mx.x | --> | 您的階梯邏輯 | --> | qx.x/mx.x |

 

若要實作此功能,請建立一個區塊並將階梯邏輯納入如下圖所示:

圖 08:透過 SIMATIC STEP 7 (TIA Portal) 設定 S71200 階梯圖,版本 v1.3 (2024)

當使用預設記憶體區域時,接點、線圈和可編輯記憶體的起始位址如下:

  • PLC 接點記憶體區域 (%i0.X) : 0x81

  • PLC 可編輯儲存記憶體區域 (%m0.x): 0x83

  • PLC 線圈記憶體區域 (%q0.x): 0x82

 

步驟 3:了解通訊協定

 

如需詳細的 S7Comm 封包結構,您可以參考這篇文章:https://blog.viettelcybersecurity.com/security-wall-of-s7commplus-part-1/。封包結構如下所示:

為了使用 S7Comm 協定與 PLC 通訊,我們將使用 Python snap7 程式庫https://python-snap7.readthedocs.io/en/latest/。此程式庫提供函式read_areawrite_area,以促進 PLC 資料的輸入和輸出操作。這些函式對於 PLC 環境中的有效資料交換和控制至關重要。

 

步驟 4:使用 Python 與 PLC 通訊

4.1 初始化連線

為了建立與 PLC 的通訊,我們初始化一個 snap7 用戶端,該用戶端使用連接埠 102 連接到 PLC 的 IP 位址,如下面的程式碼片段所示:

 self.plcAgent = snap7.client.Client()        
try:
    self.plcAgent.connect(self.ip, 0, 1, 502)
    if self.debug: print("S71200Client:已連線到 PLC [%s]" % self.ip)
    self.connected = True
except Exception as err:
    print("錯誤:S71200Client 初始化錯誤:%s" % err)
    return None

 

4.2 讀取 PLC 記憶體

S7-1200 支援從不同記憶體區域讀取各種資料類型的資料。支援下列資料類型:

資料類型 位元組數 識別 記憶體標籤格式
BOOL_TYPE (布林值) 1 個位元組 x x<起始位元組>.<起始位元>
Byte_TYPE (位元組/整數) 1 個位元組 若要將讀取的資料轉換為對應的值,請使用 snap7.util.get_* 中的函式,如下所示:

    def _memByte2Value(self, mbyte, valType, startMIdx, bitIndex):
      """ 將記憶體位元組轉換為指定類型的值。
          Args:
              mbyte (bytes): 資料位元組。
              valType (int): 轉換值的資料類型。
              startMIdx (int): 記憶體位元組的起始索引。
              bitIndex (_type_): 記憶體位的起始索引。
          Returns:
              _type_: _description_
      """
      data = None
      if valType == BOOL_TYPE:
          data = snap7.util.get_bool(mbyte, 0, bitIndex)
      elif valType == INT_TYPE:
          data = snap7.util.get_int(mbyte, startMIdx)
      elif valType == REAL_TYPE:
          data = snap7.util.get_real(mbyte, 0)
      elif valType == WORD_TYPE:
          data = snap7.util.get_word(mbyte, startMIdx)
      elif valType == DWORD_TYPE:
          data = snap7.util.get_dword(mbyte, 0)
      else:
          print("Error: _getMemValue()> 輸入類型無效: %s" % str(valType))
      return data

若要從特定的記憶體標籤類型讀取資料,請設定起始位元組索引和位元索引,然後讀取資料:

        if(memAddrTag[1].lower() == 'x'):
          # 設定布林類型資料標籤
          valLength = 1
          valType = BOOL_TYPE
          startMIdx = int(memAddrTag.split('.')[0][2:])
          bitIndex = int(memAddrTag.split('.')[1])
      elif(memAddrTag[1].lower() == 'b'):
          # 設定位元組或整數類型資料標籤
          valLength = 1
          valType = INT_TYPE
          startMIdx = int(memAddrTag[2:])
      elif(memAddrTag[1].lower() == 'w'):
          # 設定字組類型資料標籤
          valLength = 2
          valType = WORD_TYPE
          startMIdx = int(memAddrTag[2:])
      elif(memAddrTag[1].lower() == 'd'): # double
          valLength = 4
          valType = DWORD_TYPE
          startMIdx = int(memAddrTag.split('.')[0][2:])
      elif('freal' in memAddrTag.lower()): # float real number
          valLength = 4
          valType = REAL_TYPE
          startMIdx = int(memAddrTag.lower().replace('freal', ''))
      else:
          print("Error: readMem()> 輸入記憶體標籤無效: %s" %str(memAddrTag))
          return None
      # 初始化記憶體起始區域。
      memoryArea = MEM_AREA_IDX[memType]
      try:
          mbyte = self.plcAgent.read_area(memoryArea, 0, startMIdx, valLength)

 

4.3 寫入 PLC 記憶體資料

將位元組資料寫入 PLC 時,請務必不要覆寫不應變更的位元。為此,請先從記憶體讀取資料,使用 snap7.util.set_* 函式修改相關部分,然後將修改後的資料寫回記憶體。以下是一個簡化的記憶體寫入函式:

    def writeMem(self, mem, value):
      """ 從相關記憶體位址設定 PLC 狀態:IX0.N-輸入、QX0.N-輸出、
          MX0.N-記憶體。
      """
      data = self.getMem(mem, True)
      start = bit = 0 # 起始位置索引
      # 取得區域記憶體位址
      memType = mem[0].lower()
      area = self.memAreaDict[memType]
      # 設定資料長度和起始索引,並從 呼叫公用程式函式
      if(mem[1].lower() == 'x'): # 位元
          start, bit = int(mem.split('.')[0][2:]), int(mem.split('.')[1])
          set_bool(data, 0, bit, int(value))
      elif(mem[1].lower() == 'b'): # 位元組
          start = int(mem[2:])
          set_int(data, 0, value)
      elif(mem[1].lower() == 'w'):
          start = int(mem.split('.')[0][2:])
      elif(mem[1].lower() == 'd'):
          start = int(mem.split('.')[0][2:])
          set_dword(data, 0, value)
      elif('freal' in mem.lower()): # 雙字組 (實數)
          start = int(mem.lower().replace('freal', ''))
          set_real(data, 0, value)
      # 呼叫寫入函式並傳回值。
      return self.plc.write_area(area, 0, start, data)

這個增強的說明和程式碼結構應能提供更清晰的理解和實作指南,以便使用 Python 和 snap7 程式庫與 PLC 通訊。調整記憶體區域和資料類型對於 PLC 應用程式中的精確資料處理和控制至關重要。

 

完整的 Python S71200 PLC 用戶端程式

您可以從此連結下載完整的 Python M221 PLC 用戶端程式:S71200PlcClient.py 這個程式包含連線 PLC、讀取/寫入 PLC 接點、記憶體線圈資料的 API,以及一個執行緒包裝類別,可讓您在主程式的平行執行緒中執行 PLC 讀取器,以定期讀取 PLC 狀態。

該程式庫還提供三個測試案例:

  • 測試案例 1:示範如何讀取 PLC 接點、記憶體和線圈。

  • 測試案例 1:示範如何寫入 PLC 接點、記憶體和線圈。

  • 測試案例 3:顯示如何使用用戶端執行緒包裝類別。

 


參考

 

       

  RELATED


  0 COMMENT


No comment for this article.



  RANDOM FUN

Happy birthday Linux