Java實現OPCUA通信
描述
utgard 的方式過時了,所以建議使用 OPCUA 的方式。
安裝 kep :OPCServer:使用KEPServer
這是連接操作說明:OPC UA Client:使用UaExpert
使用的開源庫是 milo:https://github.com/eclipse/milo
因為沒有實際項目,所以只運行 milo 的示例代碼的客戶端部分:
https://github.com/eclipse/milo/tree/master/milo-examples/client-examples
使用西門子的 OPC UA Server,相關文檔
?? S7-1200 OPC UA 通信
?? S7-1500 OPC UA服務器,S7-1500 OPC UA客戶端
代碼
Github:https://github.com/ioufev/opcua-milo-demo
藍奏云:https://ioufev.lanzout.com/i1S7K0kt4dda
過程和問題????
證書問題
? 問題:運行報證書問題
? 描述:java.io.IOException: parseAlgParameters failed: ObjectIdentifier() -- data isn't an object ID (tag = 48)
?? JDK 的版本問題,升級 jdk8 到升級到 1.8.0.301及以上,或者使用 jdk11 或 17 運行。參考
? 問題:生成證書不帶 URI
? 描述:在 windows 上使用 openssl 生成帶 URI 信息的自簽名證書,沒找到操作說明。在 linux 上看教程到還可以。
?? 想到:不自己生成證書,使用 milo 的證書處理類先生成一個證書,以后使用這個生成的證書。
如圖,生成證書

證書上的客戶端 ID
客戶端使用證書連接服務端時,需要服務端信任客戶端的證書。
同時,客戶端也要通過證書區分不同的客戶端,所以在證書上有一個字段是代表客戶端 ID 的,類似 MQTT 客戶端 ID,要求不重復。
配置代碼的時候,需要注意配置的要和證書上一致
生成自簽名證書的設置

客戶端連接時的配置

UaExpert 第一次使用時生成的證書

在 OPC UA 通信中,應用程序的 ApplicationUri 是重要的,它用于:
● 區分不同的應用程序:如果你有多個應用程序連接到同一個 OPC UA 服務器,每個應用程序需要有一個唯一的標識符,以便服務器能夠區分它們。
● 用于安全策略:在 OPC UA 安全策略中,ApplicationUri 可能會用于安全認證和授權。
確保 ApplicationUri 是唯一的,避免與其他應用程序沖突。
通常,使用應用程序的名稱或組織的標識符作為 ApplicationUri 的一部分是一種常見做法。
例如,.setApplicationUri("urn:MyCompany:MyApp") 可以用于標識屬于 "MyCompany" 組織的 "MyApp" 應用程序。
主機名未知
? 問題:不使用匿名連接,只能連接本地,不能連接遠程。
? 描述:可以使用 UaExpert 的用戶名密碼連接,milo 代碼測試不能連接,報 UnknownHostException 即 主機名未知 錯誤。
?? 搜索到相關解答,服務發現時,服務端返回的斷點描述的主機名或者本地IP,遠程是訪問不到的,按照解答參考,修改端點的主機名為遠程IP地址即可。
沒有選擇節點
? 問題:no endpoit selected
? 描述:使用默認代碼運行,報沒有選擇節點
?? 服務端可以配置連接方式,安全策略和安全模式
我的代碼用的是
安全策略:Basic256Sha256
安全模式:簽名并加密
OPC UA 服務端配置的安全策略和安全模式,可能和我的測試代碼不一致,需要自己修改。


服務端信任客戶端證書
證書代表身份,服務端信任客戶端的證書,那就是允許客戶端連接。
按理說,客戶端也要有信任服務端證書的步驟,但是的,客戶端比較弱勢,客戶端都去連接服務端了,自然是信任服務端了。
所以雙向的互相信任對方的證書就顯得多余了,當然在支付領域,肯定是要雙向信任的,比如 APP 第一次連接服務端時,服務端在客戶端上安裝了數字證書,以后客戶端連接服務端都要帶著證書去操作。
一些理解
OPCUA 官方內容
OPCUA 規范:https://reference.opcfoundation.org/
內容太多,很繁瑣,還分好多部分。感覺看看配的圖片就行了。
機器翻譯后的內容
?? OPCUA 規范 第 1 部分:概述和概念 5:概述
?? OPCUA 規范 第 1 部分:概述和概念 6:系統概念
可以不使用 OPCUA 這種方式連接嗎?
是可以的,很多 PLC 使用的協議是公開的,比如 Modbus,直接連接也沒問題。
DA 到 UA
OPC DA 是針對 windows DCOM 的規范,以后肯定不推薦了。
OPC UA 要兼容 DA,但是 要擺脫 windows DCOM,所以推出類似 HTTP 的 opc over tcp 協議。
舊的項目,第三方 OPCserver,比如 kep ,去連接設備獲取數據,kep 提供不同的連接方式(DA、UA、ThingWorx)
除非使用的第三方 OPC 只支持 DA,但是感覺這樣的 OPCserver 該被淘汰了。
OPCUA 的連接
看別人寫的帖子都使用無安全策略的連接方式,是很省事。
我對證書的內容,不太理解明白,所以后續會補充內容。
?? 試過之后,不建議自己生成證書,使用 milo 的證書加載類生成證書非常合適
?? 生成簡單的自簽名證書(以前測試用的,現在不需要了):Windows 安裝 OpenSSL 生成自簽名證書
OPCUA 和物模型,和 Java 對象類比
OPCUA 是一種映射方式,非常像 Java 中使用類描述對象。
按照所謂 “物模型” 的說法,設備就是一個對象,
?? 設備的參數,就是:物模型的屬性值,Java 中類的屬性(也可以叫變量,字段),OPCUA 中的節點的變量。
?? 設備的操作方法,就是:物模型的功能,Java 中類的方法(也可以叫函數),OPCUA 中的方法
?? 設備的出現的各種狀況(比如上線,某個組件出故障,某個參數超標),就是:物模型的事件,Java 中的事件,OPCUA中訂閱。
對于事件的理解,感覺很像 MQTT 中的發布訂閱,如果設備發生了什么故障,把情況通知到訂閱的人。
去年做了一個無人船項目,項目不太成功,不過可以來具體舉例理解。
?? 無人船運行過程中,需要知道運行狀態:電池的溫度、電流電壓、剩余電量,船的速度,GPS 坐標,航向角等。
?? 無人船要能遠程控制,通過攝像頭獲取到遠程視頻,能在界面上控制船前進、加速、轉彎、后退、停止。
?? 無人船航行過程中發現有人在游泳,或者電池快沒電了發出提示,或者航行到了水質參數異常的區域發出提示。
?? OPCUA 中的引用,和 Java 中一個類引用另一個類的實例作為屬性值,很相似。
?? OPCUA 的節點類,和 Java 中的類也很相似,節點是從根節點到層層子節點,Java 中也是從 Object 類開始加載。
?? 地址空間,一個樹形結構,每個節點是一個類,每次看地址空間,感覺就像在 idea 里看 Java 類的結構。
關于云-邊-端,在無人船這個項目中
?端(Device):各種底層設備,負責數據采集和動作執行,如電池系統、攝像頭、水質傳感器、電機控制模塊。
?邊(Edge):ARM 控制板,負責協議接入、本地控制、數據處理與上報,連接端側設備,并通過 MQTT 與云通信。
?云(Cloud):后臺平臺,負責集中管理、調度指令、數據存儲與可視化展示,包括 MQTT 服務端、控制平臺、地圖系統等。
OPCUA 中的數據類型
OPCUA 中的數據類型,連 Java 中的 null 都有對應。
Boolean、
Byte、
ByteString:使用字節定義字符串,感覺和 Java9 中 String 的定義由 char[] 改為 byte[] 很像。
DateTime、
Double、
Float、
Int16、Int32、Int64
UInt16、UInt32、UInt64:無符號類型,沒有用一個位表示正負號,只表示零和正數。
?? milo 中 Unsigned 類封裝了無符號類型的表示,比如 Uint16 類型的 12,表示為:Unsigned.ushort(12)
String
節點標識符
OPCUA 中的 Identifier,節點標識符,milo 中 Identifiers 類定義的,
對于想要讀取的項,比如 “通道 1.設備 1. 標記 1”,這就是一個 Identifier
OPCUA 的訂閱
MQTT 中的訂閱,是要有主題的。
?? OPCUA 的訂閱是個事件通知,比如訂閱某個變量的值如果超出某個范圍,觸發事件,發出通知。
請求響應 vs 發布訂閱
也可以叫 OPCUA vs MQTT
OPCUA 是個發展的協議,原來就是請求響應模式,
所以大部人使用都是:OPCUA獲取到數據后通過MQTT發送出去。
估計OPCUA的有些人覺得不爽,覺得OPCUA也要有發布訂閱模式,我看 UAExpert 也有了發布訂閱功能,不過還沒見人使用,因為 MQTT 的發布訂閱很方便。
使用 KEPServerEX:把 OPC 數據通過 MQTT 上傳
OPCUA 的通信協議
原來的 OPC 只是個規范,OPCUA 有個基于 TCP 的應用層是二進制格式的協議,即常見的 opc.tcp://
OPCUA 的通信協議,原來似乎是 XML 格式,后來這種模式被 JSON 格式取代了,OPCUA 也與時俱進。
OPCUA 是個應用層協議,使用 TCP 傳輸,加密傳輸就是 TCP + TLS。
MQTT 也可使用 WebSocket 作為傳輸層,傳輸 MQTT 格式的信息,OPCUA 也可以使用 WebSocket 作為傳輸層,也就是瀏覽器作為OPCUA客戶端,直接訪問OPCUA服務端,暫時還沒看到有實現開源庫。
OPCUA 的建模
操作就是類似在kep建項:標記1、標記2、標記3。。。
然后保存成文件。
感覺就是:用 XML 格式或者 JSON 格式,來描述服務端有什么節點,節點有什么屬性。
在服務端定義節點,如果項少,自然沒問題。如果項很多,也就是節點很多。
如果行業中有人定義好了拿出來分享,感覺就是所謂的建模。
補充內容
地址說明
內容來自 kep 關于 OPC UA Client 的幫助文件
Client 驅動程序 地址的語法如下: ns=<namespace index>;<type>=<value>。有關詳細信息,請參閱下表。
| 字段 | 說明 |
|---|---|
| 命名空間索引 | 地址所在的 OPC UA 服務器命名空間的索引。如果索引為 0,則省略整個 ns =<namespace index="">;</namespace> 子句。 |
| 類型 | 地址類型。OPC UA 支持以下四種地址類型: i: 用 32 位無符號整數表示的數字地址 s: 由 UTF-8 編碼字符Closed有符號 8 位值。組成的字符串地址 g: 采用 {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} 格式的 GUID 地址 b: 不透明的地址 (例如: 字節字符串) |
| 值 | 格式化為字符串的地址。此地址可以是數字、字符串、GUID 或不透明。 |
示例
| 地址類型 | 名稱空間 | 示例 |
|---|---|---|
| 數字 | 2 | ns=2;i=13 |
| 字符串 | 3 | ns=3;s=Channel1.Device1.Tag1 |
| GUID | 0 | g= |
| 不透明 | 2 | ns=2;b=M/RbKBsRVkePCePcx24oRA== |
milo 代碼中節點類 NodeId
public final class NodeId {
public static final NodeId NULL_NUMERIC = new NodeId(ushort(0), uint(0));
public static final NodeId NULL_STRING = new NodeId(ushort(0), "");
public static final NodeId NULL_GUID = new NodeId(ushort(0), new UUID(0, 0));
public static final NodeId NULL_OPAQUE = new NodeId(ushort(0), ByteString.NULL_VALUE);
public static final NodeId NULL_VALUE = NULL_NUMERIC;
private final UShort namespaceIndex;
private final Object identifier;
public NodeId(int namespaceIndex, int identifier) {
this(ushort(namespaceIndex), uint(identifier));
}
public NodeId(int namespaceIndex, UInteger identifier) {
this(ushort(namespaceIndex), identifier);
}
public NodeId(int namespaceIndex, String identifier) {
this(ushort(namespaceIndex), identifier);
}
public NodeId(int namespaceIndex, UUID identifier) {
this(ushort(namespaceIndex), identifier);
}
public NodeId(int namespaceIndex, ByteString identifier) {
this(ushort(namespaceIndex), identifier);
}
public NodeId(UShort namespaceIndex, UInteger identifier) {
checkNotNull(namespaceIndex);
checkNotNull(identifier);
this.namespaceIndex = namespaceIndex;
this.identifier = identifier;
}
public NodeId(UShort namespaceIndex, int identifier) {
checkNotNull(namespaceIndex);
this.namespaceIndex = namespaceIndex;
this.identifier = uint(identifier);
}
public NodeId(UShort namespaceIndex, String identifier) {
checkNotNull(namespaceIndex);
if (identifier == null) identifier = "";
this.namespaceIndex = namespaceIndex;
this.identifier = identifier;
}
public NodeId(UShort namespaceIndex, UUID identifier) {
checkNotNull(namespaceIndex);
checkNotNull(identifier);
this.namespaceIndex = namespaceIndex;
this.identifier = identifier;
}
public NodeId(UShort namespaceIndex, ByteString identifier) {
checkNotNull(namespaceIndex);
checkNotNull(identifier);
this.namespaceIndex = namespaceIndex;
this.identifier = identifier;
}
NodeId(@NotNull UShort namespaceIndex, @NotNull Object identifier) {
checkNotNull(namespaceIndex);
checkNotNull(identifier);
this.namespaceIndex = namespaceIndex;
this.identifier = identifier;
}
//...

浙公網安備 33010602011771號