OPC communication with Java

Posted by arya6000 on Mon, 17 Jan 2022 09:14:22 +0100

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:

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

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

On Github

Blog reference

4. Implementation process

1. Learned the concept of OPC:

2. Use MatrikonOPC to understand how OPCserver is used

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:

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 typedescribe
0VT_EMPTYDefault / empty (none)
2VT_I22-byte signed integer
3VT_I44-byte signed integer
4VT_R44-byte real number
5VT_R88-byte real number
6VT_Ccurrency
7VT_DATEdate
8VT_BSTRtext
10VT_ERRORerror code
11VT_BOOLBoolean (TRUE = -1, FALSE = 0)
17VT_I11 byte signed character
18VT_UI11 byte unsigned character
19VT_UI22-byte unsigned integer
20VT_UI44-byte unsigned integer
+8192VT_ARRAYValue array (i.e. 8200 = text value array)

label: OPC

Topics: opc