Java连接KepserverEX6,DA方式读写

操作系统:windows10  版本1909

注意:与kepserver的通讯对windows版本的要求很高,目前所了解到的必须是1909版本以前,或者1903版本,windows11 使用21H2的版本。

原因是:微软给修复了docm的安全漏洞,发了补丁的原因,导致了一些新版本的windows不支持opcda的写法。

开始

1、第一步:创建一个新的windows用户,用于给访问kepserver使用。

用户名:OPCServer  【自定义即可】

密码:123456  【自定义即可】

将新增的用户添加到DOCM组

然后把新增用户移出普通用户组【Users就是刚刚新增的用户】

添加用户步骤完成

2、配置防火墙

找到DCOM-IN

上面两个DOCM-IN都是一样的操作。

3、创建OPC程序规则:允许程序OPCEnum

新建入站规则

然后一直下一步,直到【名称】,然后点击完成

 4、添加 OPC 服务器程序的规则:允许程序 KEPServer的server_runtime

新建入站规则--程序--找到server_runtime.exe

其他步骤同上一个规则一样即可。

5、组件服务

  

6、OpcEnum的安全选项

因为自定义的编辑都是一样的,所以我下面只演示一个。

   

     

7、组件服务:KEPServer的安全选项

和上面一样,这三个都点自定义,编辑中把新增的用户添加进去,把允许的权限全部打开

8、本地安全策略

配置工作全部完成

下面是JAVA代码

所需依赖

  org.openscada.utgard org.openscada.opc.lib 1.5.0   org.bouncycastle bcprov-jdk15on     org.bouncycastle bcprov-jdk15on 1.65   org.openscada.utgard org.openscada.opc.dcom 1.5.0 

连接配置

@Configuration
@ConfigurationProperties(prefix = "kep-server")
@Slf4j
@Data
public class KepServerConfig implements Serializable {
    private String host;
    private String domain;
    private String user;
    private String password;
    private String clsid;
    @Bean(name = "opcServer")
    public Server server() {
        ConnectionInformation connectionInformation = new ConnectionInformation();
        connectionInformation.setHost(host);
        connectionInformation.setDomain(domain);
        connectionInformation.setUser(user);
        connectionInformation.setPassword(password);
        connectionInformation.setClsid(clsid);
        // 连接到服务
        Server server = new Server(connectionInformation, null);
        try {
            server.connect();
            log.info("opc 服务端连接成功........");
        } catch (UnknownHostException e) {
            log.error("opc 地址错误:", e);
        } catch (JIException e) {
            log.error("opc 连接失败:", e);
        }catch (AlreadyConnectedException e) {
            log.error("opc 已连接:", e);
        }
        return server;
    }
}

【注意:为了不误人子弟,下面的读取方法是有BUG的,正确代码有空修改,当你看见这句话时,还没有修改】

批量读取kepserver中的数据

package com.hangcheng.opc.common.scheduledTasks;
import com.hangcheng.opc.common.constant.CommonConstant;
import com.hangcheng.opc.common.constant.DictTagsConstant;
import com.hangcheng.opc.common.constant.KepServerConstant;
import com.hangcheng.opc.common.constant.RedisKeyConstant;
import com.hangcheng.opc.common.pojo.DataTags;
import com.hangcheng.opc.common.pojo.EarlyWarning;
import com.hangcheng.opc.common.utils.opc.OpcTypeConversionUtils;
import com.hangcheng.opc.common.utils.redis.RedisCacheUtils;
import com.hangcheng.opc.service.EarlyWarningService;
import com.hangcheng.opc.service.TagsDataService;
import lombok.extern.slf4j.Slf4j;
import org.jinterop.dcom.core.JIVariant;
import org.openscada.opc.dcom.da.OPCSERVERSTATE;
import org.openscada.opc.lib.da.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class OpcDaClient {
    @Resource
    private TagsDataService tagsDataService;
    @Resource
    @Qualifier("opcServer")
    private Server server;
    @Resource
    private RedisCacheUtils redisCacheUtils;
    @Resource
    private EarlyWarningService earlyWarningService;
    /**
     * PLC批量读操作
     */
    public void configureTasks() {
        //下面这段是为了解决断线重连的问题
        try {
            // 这里会出现空是应为 OPCServer 为 null,但是单独拿OPCServer来判断null并不严谨,因为还有一些其他报错会导致OPCServer为空,
            // 并不是只有连接失败导致,所以这里进行多次判断
            if (server.getServerState() == null) {
                log.info("OPCServer为空,尝试一次连接");
                // 尝试一次连接,一般情况连接失败就直接被下面的catch捕获了
                server.connect();
            } else if (server.getServerState() != null && server.getServerState().getServerState().equals(OPCSERVERSTATE.OPC_STATUS_RUNNING)) {
                log.info("与服务端的连接是正常的,继续执行业务代码");
            } else {
                return;
            }
            // 创建组
            Group group = server.addGroup();
            // 转数组
            String[] items = keyList.toArray(new String[]{});
            // 把所有的标记添加到组里
            Map itemResult = group.addItems(items);
            // map转set,避免重复
            Set itemSet = new HashSet<>(itemResult.values());
            // 创建一个 itemSet 大小的数组
            Item[] itemArr = new Item[itemSet.size()];
            // 将 itemSet集合 复制到 itemArr数组
            itemSet.toArray(itemArr);
            // 一次性读取全部标记的值,非常滴爽
            Map resultMap = group.read(true, itemArr);
            // resultMap 就是读出来的所有数据
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

写操作

 /**
     * PLC批量写操作
     *
     * @param tagValueMap 指定标识与指定值集合
     */
    public void writePlcValues(Map tagValueMap) {
        try {
            // 添加一个Group
            Group group = server.addGroup();
            Map itemIdAndRWMap = redisCacheUtils.hmEntries(RedisKeyConstant.ITEM_ID_AND_R_W_MAP);
            Iterator iterator = tagValueMap.keySet().iterator();
            while (iterator.hasNext()) {
                String tag = iterator.next();
                String key = itemIdAndRWMap.get(tag);
                // 如果标识不存在那么代表传参有误,直接去除即可
                if (key == null || !key.equals(RedisKeyConstant.R_W)) {
                    iterator.remove();
                }
            }
            int i = 0;
            WriteRequest[] writeRequests = new WriteRequest[tagValueMap.size()];
            for (Map.Entry entry : tagValueMap.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                WriteRequest writeRequest = new WriteRequest(group.addItem(key), new JIVariant(value));
                writeRequests[i++] = writeRequest;
            }
            group.write(writeRequests);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

完毕,剩下的再补,没时间写了