Kepware软件基本操作及使用Java Utgard实现OPC通信

一、环境搭建(基于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_ROOTCLSID{76A64158-CB41-11D1-8B02-00600806D9B6}

2、右键点击[权限]>>【高级】>>[所有者]>>添加opc用户到权限项目中,点击应用,确定

四、其他参考资料

opc介绍

常用的 OPCClient 和 OPCServer 软件推荐

Kepware 激活过程

OPC 技术总结

原文地址:https://www.cnblogs.com/songjilong/p/14357956.html