OPC communication with Java
Don't reply to any questions. You can comment on any questions.
The video recording briefly describes the content of the article and the video address: https://www.bilibili.com/video/BV13V411f7Ch/
1.PLC and OPC
PLC used: Siemens S7-300. The specific model is shown in the figure below
OPC server software used:
- The project uses KEPServer V6 (450M, Chinese): Baidu network disk , password: ykj2
- MatrikonOPCSimulation (50M) for simulation, Baidu network disk , password: mcur
2. Connection test
What is OPC
OPC is the interface standard of hardware and software used in the field of industrial control and production automation, so as to effectively read and write data between application and process control equipment. O stands for OLE (object linking and embedding), P (process), C (control).
OPC server includes three types of objects: server object, item object and group object.
OPC standard adopts C/S mode, and OPC server is responsible for continuously providing data to OPC client.
Source: OPC - (II) - what is OPC
OPC server software usage
- MatrikonOPC: Using Matrikon OPC Server Simulation
- KEPServer V6: Using KEPServerEX 6
Server and Client
The communication between Client (Java) and Client (PLC) is to be realized
In the middle, with the help of OPCServer, the address variables are set on the Server, and different clients read and write these variable values to realize communication.
The schematic diagram is as follows
Configure Server and Client
OPC and DCOM configuration : unsuccessful communication is a configuration problem...
Configure OPCserver
Generally, if a computer (win10) installs Server (such as KEPServer) and client (written in Java) at the same time, configure the computer
If it is on two computers, it needs to be configured.
3. Communication realization
Utgard
- Official website: http://openscada.org/projects/utgard/
- Programming guidance
- Source code: https://github.com/ctron/org.openscada.utgard
On Github
- Most comprehensive tests (Utgard and JeasyOPC tests): OPC_Client
- Utgard test
Blog reference
- Most important reference: Development of Java OPC client
- On Github: Data download
- Data access mode of Utgard for OPC development in Java language
- Utgard accessing OPC server
4. Implementation process
1. Learned the concept of OPC:
2. Use MatrikonOPC to understand how OPCserver is used
- OPCClient and OPCServer software commonly used in OPC testing are recommended
- My purpose is to write a similar Java Client to connect to the OPC Server: Using Matrikon OPC Server Simulation
3. About OPC UA
- Siemens PLC supporting OPC UA is at least s7-1500
- My s7-300 is useless, so there is no need to collect OPC UA data
4. About Java implementation
- C # and C + + do not need to configure DCOM and call functions directly
- Since you have to use Java, don't think it's too convenient. You need to configure DCOM.
5. About Utgard
- utgard is an open source project based on j-interop, which is used to communicate with OPC SERVER.
- j-interop is a pure java encapsulated open source project for COM/DCOM communication, so it is not necessary to use JNI
6. About JeasyOPC
- JeasyOPC source code download
- With the help of a dll library to realize the communication with OPCServer, but jcustomopc dll,, is too old and supports only 32-bit systems
7. Final realization
- Of course, Utgard
- The process is to find the required jar package,
- Then copy Programming guidance In the read-write code, reading is to start the thread to always read the value of the corresponding address variable, and writing is to write the value of the corresponding address variable
8. Test
- reference resources OPC_Client Examples in
- The code about the configuration file is copied directly
- The example is not used in practice. I tried it, because actually I only need to read and write the value of the address variable
9. About subscription data collection
reference resources: https://www.hifreud.com/2014/12/27/opc-3-main-feature-in-opc/# Subscription data collection
The OPC application does not need to ask the OPC server to automatically receive the Subscription mode data collection (Subscription) of the change notification sent from the OPC server. When the server updates the value of the data buffer of the OPC server according to a certain update rate, if the value changes, it will notify the OPC application with a data change event.
Because I didn't use this subscription method, I didn't try it at that time. Later, when I try to use Async20Access, an error will be reported. Referring to the above article, I said: I must also set the ID. I didn't succeed.
10. Questions:
- In the virtual machine, there is always an error when using localhost. You need to write a fixed IP
- The IP in the configuration is the IP of the computer where OPCServer software is installed. If wireless connection is used, please check the wireless IP address
- Can you cycle the monitoring of a group? It seems impossible. There are two data reading methods in the official Demo: 1 Cycle monitoring item; 2. Add an item to the group and read it only once
- If the client written by Java and OPCServer software are installed on two computers: the two computers should be configured with the same DCOM, including the account and password
- Is win10 Home Edition OK? Yes, it's troublesome. It's mainly the configuration of user management. Someone has verified that it's OK, so I won't try. It is recommended to install win10 professional edition on the virtual machine, reference resources
- As for Kingview, as an opcsever, I haven't connected it in any way. If someone can connect, I won't try.
- About asynchrony: I use synchronous data reading. I haven't tried asynchronous reading. Don't ask me asynchronous questions.
- Whether group is maintained by the client or the server: the server can create its own group, but the client still sees individual item s. Group is the client's own group. I understand that.
- Can the client read all the item lists of the server: of course, please reference resources
About the registry ID of KEPServer
11.maven dependency
<!--utgard --> <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> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency>
5. Code
Download code:
- Baidu network disk , password: ermn
- LAN Zuoyun
- Reference codes: Utgard of OPC - (IV) - OPC Client Java call
Screenshot:
explain
Reading and writing values to address variables are generally divided into two modes: cyclic and batch (synchronous and asynchronous are not discussed):
- Circular reading: Utgard provides an AccessBase class to read values circularly
- Loop write: start a thread to loop write values
- Batch read: add an Item to the group through the group, and then use read() for the Item
- Batch write: add an Item to the group through the group, and then use write() for the Item
According to the actual use, the example is annotated for easy understanding
Read value
import java.util.concurrent.Executors; import org.jinterop.dcom.common.JIException; import org.jinterop.dcom.core.JIString; import org.jinterop.dcom.core.JIVariant; import org.openscada.opc.lib.common.ConnectionInformation; import org.openscada.opc.lib.da.AccessBase; import org.openscada.opc.lib.da.DataCallback; import org.openscada.opc.lib.da.Item; import org.openscada.opc.lib.da.ItemState; import org.openscada.opc.lib.da.Server; import org.openscada.opc.lib.da.SyncAccess; public class UtgardTutorial1 { public static void main(String[] args) throws Exception { // Connection information final ConnectionInformation ci = new ConnectionInformation(); ci.setHost("192.168.0.6"); // Native IP ci.setDomain(""); // Field, just leave it blank ci.setUser("OPCUser"); // User name created on this machine ci.setPassword("123456"); // password // Configuration using MatrikonOPC Server // ci. setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305"); // The registry ID of matrikonopc can be seen in "component service" // final String itemId = "u.u"; // The names of items configured on matrikonopc server are based on the actual // Configuration using KEPServer ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // The registry ID of KEPServer can be seen in "component service", with picture description on it final String itemId = "u.u.u"; // The name of the item configured on KEPServer. There is no actual PLC. The simulator used is simulator // final String itemId = "channel 1. Device 1. Tag 1"; // Start service final Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor()); try { // Connect to service server.connect(); // add sync access, poll every 500 ms, start a synchronous access to read the value on the address. The thread pool reads the value every 500 ms // This is used to read the value cyclically. It is not necessary to read the value only once final AccessBase access = new SyncAccess(server, 500); // This is a callback function, which executes the print after reading the value. It is written in an anonymous class. Of course, it can also be written outside access.addItem(itemId, new DataCallback() { @Override public void changed(Item item, ItemState itemState) { int type = 0; try { type = itemState.getValue().getType(); // The type is actually a number, defined with a constant } catch (JIException e) { e.printStackTrace(); } System.out.println("The data types of monitoring items are:-----" + type); System.out.println("The timestamp of the monitoring item is:-----" + itemState.getTimestamp().getTime()); System.out.println("The details of monitoring items are:-----" + itemState); // If you read a value of type short if (type == JIVariant.VT_I2) { short n = 0; try { n = itemState.getValue().getObjectAsShort(); } catch (JIException e) { e.printStackTrace(); } System.out.println("-----short Type value: " + n); } // If you read a value of string type if(type == JIVariant.VT_BSTR) { // The type of string is 8 JIString value = null; try { value = itemState.getValue().getObjectAsString(); } catch (JIException e) { e.printStackTrace(); } // Read by string String str = value.getString(); // Get string System.out.println("-----String Type value: " + str); } } }); // start reading access.bind(); // wait a little bit, with a 10 second delay Thread.sleep(10 * 1000); // stop reading access.unbind(); } catch (final JIException e) { System.out.println(String.format("%08X: %s", e.getErrorCode(), server.getErrorMessage(e.getErrorCode()))); } } }
Read and write values
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.jinterop.dcom.common.JIException; import org.jinterop.dcom.core.JIVariant; import org.openscada.opc.lib.common.ConnectionInformation; import org.openscada.opc.lib.da.AccessBase; import org.openscada.opc.lib.da.DataCallback; import org.openscada.opc.lib.da.Group; import org.openscada.opc.lib.da.Item; import org.openscada.opc.lib.da.ItemState; import org.openscada.opc.lib.da.Server; import org.openscada.opc.lib.da.SyncAccess; public class UtgardTutorial2 { public static void main(String[] args) throws Exception { // Connection information final ConnectionInformation ci = new ConnectionInformation(); ci.setHost("192.168.0.1"); // Computer IP ci.setDomain(""); // Field, just leave it blank ci.setUser("OPCUser"); // User name, which is configured when DCOM is configured ci.setPassword("123456"); // password // Configuration using MatrikonOPC Server // ci. setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305"); // The registry ID of matrikonopc can be seen in "component service" // final String itemId = "u.u"; // The name of the item is based on the actual // Configuration using KEPServer ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // The registry ID of KEPServer can be seen in "component service" final String itemId = "Channel 1.Equipment 1.Mark 1"; // The name of the item is actual. There is no actual PLC. The simulator used is simulator // final String itemId = "u.u.u"; // create a new server and start the service final Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor()); try { // connect to server to connect to the service server.connect(); // add sync access, poll every 500 ms, start a synchronous access to read the value on the address. The thread pool reads the value every 500 ms // This is used to read the value cyclically. It is not necessary to read the value only once final AccessBase access = new SyncAccess(server, 500); // This is a callback function, which is to execute the following code after reading the value. It is written in an anonymous class. Of course, it can also be written outside access.addItem(itemId, new DataCallback() { @Override public void changed(Item item, ItemState state) { // also dump value try { if (state.getValue().getType() == JIVariant.VT_UI4) { // If the read value type is unsigned integer, it is an unsigned integer value System.out.println("<<< " + state + " / value = " + state.getValue().getObjectAsUnsigned().getValue()); } else { System.out.println("<<< " + state + " / value = " + state.getValue().getObject()); } } catch (JIException e) { e.printStackTrace(); } } }); // Add a new group to add a group, which is used to read or write values once, rather than cycle reading or writing // The name of the group is optional. The reason for naming the group is that the server can add group or remove group. After reading the value once, add the group first, then remove the group, and then add and delete it after reading it again final Group group = server.addGroup("test"); // Add a new item to the group, // Add an item to the group. The item name is the name of the item built on MatrikonOPC Server or KEPServer, such as u.u.TAG1, PLC S7-300. TAG1 final Item item = group.addItem(itemId); // Start reading: start reading the value circularly access.bind(); // add a thread for writing a value every 3 seconds // Write once is item Write (value), a thread executes item all the time since circular writing write(value) ScheduledExecutorService writeThread = Executors.newSingleThreadScheduledExecutor(); writeThread.scheduleWithFixedDelay(new Runnable() { @Override public void run() { final JIVariant value = new JIVariant("24"); // Write 24 try { System.out.println(">>> " + "Write value: " + "24"); item.write(value); } catch (JIException e) { e.printStackTrace(); } } }, 5, 3, TimeUnit.SECONDS); // Execute the code for the first time 5 seconds after startup, and then execute the code every 3 seconds // wait a little bit, 20 seconds delay Thread.sleep(20 * 1000); writeThread.shutdownNow(); // Turn off the thread that has been writing // stop reading to stop the cycle of reading values access.unbind(); } catch (final JIException e) { System.out.println(String.format("%08X: %s", e.getErrorCode(), server.getErrorMessage(e.getErrorCode()))); } } }
Array type
What if the data type of the address variable is array type?
// Read array of Float type if (type == 8196) { // 8196 is print state getValue(). Gettype() JIArray jarr = state.getValue().getObjectAsArray(); // Read by array Float[] arr = (Float[]) jarr.getArrayInstance(); // Get array String value = ""; for (Float f : arr) { value = value + f + ","; } System.out.println(value.substring(0, value.length() - 1); // Traverse the values of the print array, separated by commas, and remove the last comma } // Write an array of 3-bit Long type Long[] array = {(long) 1,(long) 2,(long) 3}; final JIVariant value = new JIVariant(new JIArray(array)); item.write(value);
data type
Reading and writing values need to be operated by data type
This is a common data type
Value (decimal) | data type | describe |
---|---|---|
0 | VT_EMPTY | Default / empty (none) |
2 | VT_I2 | 2-byte signed integer |
3 | VT_I4 | 4-byte signed integer |
4 | VT_R4 | 4-byte real number |
5 | VT_R8 | 8-byte real number |
6 | VT_C | currency |
7 | VT_DATE | date |
8 | VT_BSTR | text |
10 | VT_ERROR | error code |
11 | VT_BOOL | Boolean (TRUE = -1, FALSE = 0) |
17 | VT_I1 | 1 byte signed character |
18 | VT_UI1 | 1 byte unsigned character |
19 | VT_UI2 | 2-byte unsigned integer |
20 | VT_UI4 | 4-byte unsigned integer |
+8192 | VT_ARRAY | Value array (i.e. 8200 = text value array) |
label: OPC