Learning Objectives
- 說明 OPC UA 和 Modbus 的差別:資料有名稱、型別與結構,而且內建安全機制
- 讀懂位址空間的節點與屬性:NodeId、BrowseName、DataType、AccessLevel、Value
- 用 asyncua 瀏覽節點、讀取與寫入變數
- 用訂閱接收「值改變」的通知,說明訂閱和輪詢的差別
從「位址 40001」到「Line1.Motor」
用 Modbus 讀資料時,你拿到的只是「保持暫存器 3 的值是 1013」。它是溫度還是壓力?單位是什麼? 是整數還是浮點數的一半?這些都要另外查暫存器表。上一課也看到,字組順序一弄錯,數字就完全不對。
OPC UA(OPC Unified Architecture)把這些資訊一起放進通訊裡:伺服器提供一個位址空間(address space), 裡面的每個東西都是一個節點(node),有名稱、型別、單位、能不能寫,客戶端可以像逛資料夾一樣瀏覽。
| Modbus | OPC UA | |
|---|---|---|
| 資料怎麼找 | 位址(保持暫存器 3) | 節點(ns=2;s=Line1.Temperature),可以瀏覽 |
| 型別 | 只有 16 位元暫存器與位元,其他自己約定 | 布林、整數、浮點數、字串、日期、結構… |
| 資料變化 | 客戶端一直輪詢 | 可以訂閱,值改變時伺服器主動通知 |
| 安全 | 協定本身沒有認證與加密 | 內建使用者認證、憑證、簽章與加密 |
| 標準 | Modbus Organization 規格 | OPC Foundation 發布,也是國際標準 IEC 62541 |
OPC UA 的前身是 OPC(現在稱 OPC Classic),建立在 Windows 的 DCOM 技術上;OPC UA 改成跨平台、不依賴 Windows 的設計。
它的連線網址長這樣:opc.tcp://主機:4840,4840 是常用的預設埠。
這一課要回答:
- 位址空間裡有哪些節點?一個節點有哪些屬性?
- Python 怎麼瀏覽、讀取、寫入?
- 訂閱和上一課的輪詢有什麼不同?
節點與屬性
位址空間裡最常碰到三種節點:
- 資料夾(Folder):分類用,例如
Objects。 - 物件(Object):一台設備或一個單元,例如
Line1,底下掛著它的變數。 - 變數(Variable):真正有值的東西,例如
Line1.Motor。
每個節點都有一組屬性(attribute):
| 屬性 | 例子 | 意思 |
|---|---|---|
| NodeId | ns=2;s=Line1.Speed |
節點在這台伺服器上的唯一編號:命名空間 2、字串型的識別碼 |
| BrowseName | 2:Speed |
瀏覽時看到的名稱 |
| DataType | UInt16 |
值的型別 |
| AccessLevel | 可讀寫/唯讀 | 能不能寫入 |
| Value | 1200 |
目前的值,附帶狀態碼與時間戳記 |
NodeId 的 ns=0 是 OPC UA 規格自己定義的節點(例如 i=85 就是 Objects),ns=2 之後通常是設備廠商或你的應用程式定義的。
識別碼可以是數字(i=1001)或字串(s=Line1.Speed)。寫程式時,NodeId 要照伺服器實際的設定,先用瀏覽工具或程式找到它。
本課的模擬伺服器是教學用的,節點名稱與結構是示意;真實的 PLC 或 SCADA 軟體提供的位址空間,依廠牌與設定而不同。
逐步看:瀏覽與讀取
with Client(...) as client: 連線,離開 with 區塊時自動斷線。看通訊紀錄:每次 read_value() 都是一次「OPC UA 讀取」。
這台模擬伺服器把 PLC 的位址直接公開成變數(例如 Line1.Motor 就是 Q0.0),所以 Objects 底下直接就是這些變數。
輪詢與訂閱
第 12 課的監控程式每秒問一次 PLC:「現在多少?」這叫輪詢(polling)。值大部分時間沒變,問了也是白問; 而且兩次詢問之間發生的短暫變化可能被漏掉。
OPC UA 的訂閱(subscription)反過來:客戶端告訴伺服器「這幾個節點變了再通知我」,伺服器在背後依取樣間隔檢查, 值改變時把通知送給客戶端。值不變,網路上就沒有流量。
asyncua 的寫法:
class Handler:
def datachange_notification(self, node, val, data):
print(node, val) # 值改變時被呼叫
sub = client.create_subscription(200, Handler()) # 200 毫秒的間隔
sub.subscribe_data_change(node)
datachange_notification 不是你呼叫的,是 asyncua 收到通知時呼叫的(另一個執行緒),所以裡面的工作要短;
需要做久一點的事(寫資料庫、送訊息),先把資料交給主程式。
逐步看:訂閱馬達狀態
模擬的現場在 0.8 秒按下啟動、2.6 秒按下停止。主程式只是 time.sleep(3.5),
看 datachange_notification 什麼時候被呼叫:訂閱當下一次(目前的值),之後只有馬達狀態改變的兩次。
OPC UA 的安全機制
OPC UA 從設計上就包含安全功能,連線時客戶端與伺服器要談好:
- 安全模式:
None(不簽章不加密)、Sign(每個訊息都簽章,被竄改會被發現)、SignAndEncrypt(簽章並加密內容)。 - 應用程式憑證:客戶端與伺服器各有一張憑證,雙方要互相信任才建立安全連線。
- 使用者身分:匿名、帳號密碼或使用者憑證;伺服器可以依身分決定誰能讀、誰能寫。
None 適合在實驗室練習與探索設備,正式的產線應該關掉 None,用 SignAndEncrypt 並限制使用者權限。
第 17 課會從整個網路的角度談工控資安。
小測驗:節點與屬性
ns=2;s=Line1.Speed 這個 NodeId 裡,ns=2 代表什麼?
寫入一個節點時,伺服器回 BadNotWritable。最可能的原因是?
和 Modbus 相比,下面哪一項是 OPC UA 的特色?
小測驗:訂閱
下面的程式模擬「值改變才通知」,會印出什麼?
samples = [0, 0, 1, 1, 1, 0, 0]
last = None
for v in samples:
if v != last:
print(v, end=" ")
last = v
datachange_notification 裡要把每筆資料寫進遠端資料庫,每次要 2 秒。比較好的做法是?
重點整理
- OPC UA 提供可瀏覽的位址空間:資料夾、物件、變數都是節點,有 NodeId、BrowseName、DataType、AccessLevel、Value 等屬性。
- NodeId 例如
ns=2;s=Line1.Speed:命名空間 2、字串識別碼。 - asyncua:
with Client(網址) as client:、client.nodes.objects.get_children()瀏覽、get_node(NodeId)、read_value()、write_value()。 - 寫入失敗看狀態碼:BadNotWritable(唯讀)、BadTypeMismatch(型別不符)、BadOutOfRange(超出範圍)。
- 訂閱:
create_subscription(間隔, handler)+subscribe_data_change(node),值改變才通知,處理函式要短。 - 安全模式 None/Sign/SignAndEncrypt;正式環境不要用 None。OPC UA 由 OPC Foundation 發布,國際標準是 IEC 62541,常用埠 4840。
下一課把這些資料接到畫面上:HMI 與 SCADA 的標籤、畫面、告警與歷史資料。