一、環境搭建(基於win10 64位專業版)
1、Kepware 的下載、安裝及使用
https://www.cnblogs.com/ioufev/p/9366877.html
2、重要:OPC 和 DCOM 配置(OPC DA 必須配置)
https://www.cnblogs.com/ioufev/p/9365919.html
二、使用 Utgard 實現 OPC 通信
1、添加 Maven 依賴
<!--utgard start-->
<dependency>
<groupId>org.openscada.external</groupId>
<artifactId>org.openscada.external.jcifs</artifactId>
<version>1.2.25</version>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.openscada.jinterop</groupId>
<artifactId>org.openscada.jinterop.core</artifactId>
<version>2.1.8</version>
</dependency>
<dependency>
<groupId>org.openscada.jinterop</groupId>
<artifactId>org.openscada.jinterop.deps</artifactId>
<version>1.5.0</version>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.openscada.utgard</groupId>
<artifactId>org.openscada.opc.dcom</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.openscada.utgard</groupId>
<artifactId>org.openscada.opc.lib</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.61</version>
</dependency>
<!--utgard end-->
2、簡單讀取示例
public class ReadTest {
public static void main(String[] args) {
//opc連接信息
final ConnectionInformation ci = new ConnectionInformation();
ci.setHost("127.0.0.1");
ci.setUser("OPCUser");
ci.setPassword("111111");
ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729");
//ci.setProgId("");
//要讀取的標記
String item = "通道 1.設備 1.TAG1";
//連接對象
final Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());
try {
//建立連接
server.connect();
//添加一個組
Group group = server.addGroup();
//將標記添加到組里
Item addItem = group.addItem(item);
//讀取標記的值
JIVariant variant = addItem.read(true).getValue();
//獲取string類型的值
String value = variant.getObjectAsString().getString();
System.out.println("讀到值:" + value);
} catch (UnknownHostException | AlreadyConnectedException | JIException |
NotConnectedException | DuplicateGroupException | AddFailedException e) {
e.printStackTrace();
} finally {
//關閉連接
server.disconnect();
}
}
}
kepware 客戶端看到的點位信息:
程序運行結果:
(1)OPC 連接信息解釋:
- Host——本地主機/網絡主機IP (示例:localhost(默認)、127.0.0.1)
- Domain——域(默認為localhost)
- User——用戶名
- Password——用戶登錄密碼
- Clsid——應用在注冊表中相對應的CLSID值
- Grogid——應用在注冊表中對應的程序名稱
Clsid 和 Grogid 作用相同,只要設置一個就可以了,如果兩個都設置了,程序會優先選擇Clsid。建議使用Clsid,因為使用Grogid時,Openscada的內部處理還是會通過JISystem.getClsidFromProgId( progId )方法將其轉換為Clsid,並且還需要進行服務器上用戶的權限的高級配置才可以使用。
查找Clsid的方法:搜索“組件服務”,找到“DCOM 配置”選項,找到安裝的 kepware 服務器名稱,右邊可以看到“應用程序 ID”,如下圖所示
(2)Kepware 與 Utgard 中類型對應:
在 java Utgard 里面類型使用數字來表示的,比如 String 類型是 8,在上面讀取結果的圖片中可以看到讀取到的類型確實是8
為了得到正確的數據類型我們需要對讀到的結果判斷一下,工具類如下,添加了常見類型的判斷
@Slf4j
public class OpcUtil {
private OpcUtil() {
}
/**
* 讀單個值
*/
public static String readValue(Item item) {
try {
ItemState state = item.read(true);
return getValue(state);
} catch (JIException e) {
e.printStackTrace();
}
return null;
}
/**
* 讀一組值,對於讀取異常的點位會返回null值
*/
public static List<String> readValues(Group group, List<String> tags) {
//添加到group中,如果添加失敗則添加null
List<Item> items = tags.stream().map(tag -> {
try {
return group.addItem(tag);
} catch (Exception e) {
log.info(e.toString());
}
return null;
}).collect(Collectors.toList());
List<String> result = new ArrayList<>();
try {
//讀取所有的值,過濾null值,否則會出異常
Map<Item, ItemState> map = group.read(true,
items.stream().filter(Objects::nonNull).toArray(Item[]::new));
//解析
for (Item item : items) {
if (item == null) {
result.add(null);
continue;
}
String value = getValue(map.get(item));
result.add(value);
}
} catch (JIException e) {
e.printStackTrace();
}
return result;
}
/**
* 如果是 bool、string、short、int等直接返回字符串;
* 如果是 long 類型的數組,返回數字內容間加點,對應 long,數組,大小為6
* 如果是 float 類型的數組,返回數字內容間加逗號,對應 float,數組,大小為20
*/
private static String getValue(ItemState state) {
JIVariant variant = state.getValue();
try {
int type = variant.getType();
//Boolean
if (type == JIVariant.VT_BOOL) {
boolean value = variant.getObjectAsBoolean();
return String.valueOf(value);
}
//String
else if (type == JIVariant.VT_BSTR) {
return variant.getObjectAsString().getString();
}
//Word DWord
else if (type == JIVariant.VT_UI2 || type == JIVariant.VT_UI4) {
Number value = variant.getObjectAsUnsigned().getValue();
return String.valueOf(value);
}
//Sort
else if (type == JIVariant.VT_I2) {
short value = variant.getObjectAsShort();
return String.valueOf(value);
}
//Float
else if (type == JIVariant.VT_R4) {
float value = variant.getObjectAsFloat();
return String.valueOf(value);
}
//long 類型的數組
else if (type == 8195) {
JIArray jarr = variant.getObjectAsArray();
Integer[] arr = (Integer[]) jarr.getArrayInstance();
StringBuilder value = new StringBuilder();
for (Integer i : arr) {
value.append(i).append(".");
}
String res = value.substring(0, value.length() - 1);
// "25.36087601.1.1.18.36"-->"25.36087601.01.0001.18.36"
String[] array = res.split("[.]");
return array[0] + "." + array[1] + "." + new DecimalFormat("00").format(Long.valueOf(array[2]))
+ "." + new DecimalFormat("0000").format(Long.valueOf(array[3])) + "." + array[4] + "."
+ array[5];
}
//float 類型的數組
else if (type == 8196) {
JIArray jarr = variant.getObjectAsArray();
Float[] arr = (Float[]) jarr.getArrayInstance();
StringBuilder value = new StringBuilder();
for (Float f : arr) {
value.append(f).append(",");
}
return value.substring(0, value.length() - 1);
}
//其他類型
else {
Object value = variant.getObject();
return String.valueOf(value);
}
} catch (JIException e) {
e.printStackTrace();
}
return null;
}
/**
* 寫值到變量
*/
public static void writeValue(Item item, String val) {
try {
JIVariant value = new JIVariant(val);
item.write(value);
} catch (JIException e) {
e.printStackTrace();
}
}
/**
* 寫值到變量:數組
*/
public static void writeValueToArr(Item item, String[] snArray) {
try {
//構造寫入數據
Long[] integerData = new Long[snArray.length];
for (int i = 0; i < snArray.length; i++) {
integerData[i] = Long.valueOf(snArray[i]);
}
final JIArray array = new JIArray(integerData, false);
final JIVariant value = new JIVariant(array);
item.write(value);
} catch (JIException e) {
e.printStackTrace();
}
}
}
連接信息一般是固定的,可以寫成常量
public class OpcConnection {
private static final ConnectionInformation CI = new ConnectionInformation();
private OpcConnection() {
}
static {
CI.setHost("127.0.0.1");
CI.setUser("OPCUser");
CI.setPassword("111111");
CI.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729");
}
public static ConnectionInformation getInfo() {
return CI;
}
}
讀取多個值方法的使用:
private static final List<String> ITEMS = Arrays.asList("通道 1.設備 1.TAG1", "通道 1.設備 1.TAG2");
private final Server server = new Server(OpcConnection.getInfo(), Executors.newSingleThreadScheduledExecutor());
public List<String> getData() {
List<String> res = new ArrayList<>();
try {
server.connect();
Group group = server.addGroup();
res = OpcUtil.readValues(group, ITEMS);
} catch (UnknownHostException | JIException | AlreadyConnectedException
| NotConnectedException | DuplicateGroupException e) {
e.printStackTrace();
} finally {
server.disconnect();
}
return res;
}
參考:
https://www.cnblogs.com/ioufev/articles/9894452.html
https://www.cnblogs.com/ioufev/p/9928971.html
https://www.cnblogs.com/ioufev/p/9929170.html
https://www.hifreud.com/2014/12/27/opc-4-client-invoke-use-utgard
三、注意事項
1、kepware頻率設置
- kepware 中可以對設備指定幾種掃描模式,掃描速率最高可設置到 10ms
- 如果選擇“遵循標記指定的掃描速率”,也可以單獨對標記設置掃描速率
- kepware 客戶端也可以設置速率,不過這里的速率只是客戶端的,真實的采集速率還是要根據服務端來定
2、kepware 一個通道放一個設備
一個通道里盡量放一個設備,因為 kepware 通道內是單線程(kepware 客服說的),如果放多個設備會影響掃描速率,造成數據刷新變慢
3、DCOM 配置
按說明配置好 OPC Server 與 OPC Client 所在電腦的組件服務配置和防火牆設置(windows7直接關閉就行了)。注意一定要把本機希望鏈接 OPC 服務的用戶或用戶組添加到 DCOM 配置列表中,否則鏈接會失敗。如果其他都配置好了,運行程序還是連接不上的話,首先常看防火牆是否配置(或關閉)。
如果 Java 寫的 client 和安裝 OPCServer 軟件是兩台電腦:那兩個電腦都要配置相同 DCOM,包括賬號密碼都要一樣
4、Openscada遠程鏈接時常見的問題及解決方法
org.jinterop.dcom.common.JIException: Message not found for errorCode:0xC0000034
原因:未啟動RemoteRegistry和Windows Management Instrumentation服務。
解決方法:打開控制面板,點擊【管理工具】—>>【服務】,啟動RemoteRegistry和Windows ManagementInstrumentation服務。
org.jinterop.dcom.common.JIException:Access is denied, please check whether the [domain-username-password] arecorrect. Also, if not already done please check the GETTING STARTED and FAQsections in readme.htm. They provide information on how to correctly configurethe Windows machine for DCOM access, so as to avoid such exceptions. [0x00000005]
原因:首先檢查錯誤提示的配置信息是否有誤,如果都正確,則原因可能是你訪問的當前用戶沒有該訪問權限。
解決方法:
1、打開注冊列表,
選擇HKEY_CLASSES_ROOT\CLSID{76A64158-CB41-11D1-8B02-00600806D9B6}
2、右鍵點擊[權限]>>【高級】>>[所有者]>>添加opc用戶到權限項目中,點擊應用,確定
四、其他參考資料
opc介紹
- https://www.cnblogs.com/ioufev/articles/9697717.html
- https://www.hifreud.com/2014/12/27/opc-2-what-is-opc/
- https://www.hifreud.com/2014/12/27/opc-3-main-feature-in-opc/