精华内容
下载资源
问答
  • Java OPC
    2021-02-25 19:46:45

    来源:https://www.jianshu.com/p/26391f0cbb6f

    最近一个项目中需要用到OPC client,从OPC Server中获取数据。主要的编程语言使用Java实现。实际开发中遇到了各种坑,其实也和自己没有这方面的经验有关,现在写一篇文章分享下整个项目中遇到的一些问题。

    准备知识

    开发OPC Client之前需要一些准备知识,需要一些知识储备,否则根本搞不清楚里面的门道。现在对一些预先准备的知识点做一概述。OPC是什么就不说了。

    OPC Server端的协议

    OPC Server端目前常见的有以下几种协议:

    OPC DA: Data Access协议,是最基本的OPC协议。OPC DA服务器本身不存储数据,只负责显示数据收集点的当前值。客户端可以设置一个refresh interval,定期刷新这个值。目前常见的协议版本号为2.0和3.0,两个协议不完全兼容。也就是用OPC DA 2.0协议的客户端连不上OPC DA 3.0的Server

    OPC HDA: Historical Data Access协议。前面说过DA只显示当前状态值,不存储数据。而HDA协议是由数据库提供,提供了历史数据访问的能力。比如价格昂贵的Historian数据库,就是提供HDA协议接口访问OPC的历史数据。HDA的Java客户端目前我没找到免费的。

    OPC UA: Unified Architecture统一架构协议。诞生于2008年,摒弃了前面老的OPC协议繁杂,互不兼容等劣势,并且不再需要COM口访问,大大简化了编程的难度。基于OPC UA的开源客户端非常多。不过由于诞生时间较晚,目前在国内工业上未大规模应用,并且这个协议本身就跟旧的DA协议不兼容,客户端没法通用。

    我们的目标环境绝大多数是OPC DA 2.0的Server,极个别可能有OPC DA 3.0。当时找到的很多类库实现的都是OPC UA的。

    第一坑: 基于JAVA开发的OPC Client非常少,大部分是商业的,售价不菲。现场环境又是OPC DA的Server,开源client只有两个可选,找工具和评估就花了不少时间。

    OPC存储格式

    OPC存储和传统的关系型数据库存储格式有很大的不同,不同于关系型数据库的表存储,OPC存储格式是树形结构,Server端的存储格式如下:

    每个主机上可能存在多个OPC Server,每个Server下面有若干个tag,就是各个数据收集点当前的值,会定期更新。每个tag包含的内容大致有当前值,值类型,时间戳等等数据。是一种树形结构。所以客户端连接的时候需要指明服务器的ip或主机名,需要连接的OPC服务名,以及监听哪些tag的数据。

    Client端存储的格式如下:

    这个就比较有意思了,Client是可以自己维护一个存储层级Group。也就是服务端存储的都是一个个tag,客户端可以自己维护一个个Group,分类存放这些tag。所以OPC的Client就和传统的关系型数据库有很大的不同。客户端除了指明上述Server端的信息之外,还需要创建一个个Group,将Server端的tag一个个放到这些Group中,然后对应的tag才能持续的获得数据。

    第二坑: 这种存储格式在其他数据库十分罕见,当时这里就迷茫了好一阵子,通过了解协议的人讲解,才明白原来客户端还可以维护一套存储结构。当时没理清楚Group和tag的关系,从服务端看不到Group,客户端却要填一个Group,不知道这个Group从哪来。后来才搞清楚。

    COM

    Component Object Model对象组件模型,是微软定义的一套软件的二进制接口,可以实现跨编程语言的进程间通信,进而实现复用。

    DCOM

    Microsoft Distributed Component Object Model,坑最多的一个玩意。字面意思看起来是分布式的COM,简单理解就是可以利用网络传输数据的COM协议,客户端也可以通过互联网分布在各个角落,不再限制在同一台主机上了。

    上面描述来看这玩意好像挺美好是吧?实际操作开发中才发现,这玩意简直是坑王之王,对于不熟悉的人来说充满了坑,十分折腾。配置过程可以参考一些文章

    DCOM是windows上的服务,使用前需要启用

    DCOM是远程连接的协议,需要配置相关的权限,以及防火墙规则放行

    特别注意这一点,前两项配置在网上都能找到,这一条是我在经历无数次痛之后才意识到的。DCOM远程连接和http不同,是通过本地用户认证的,需要以本地用户身份登录服务器,拿到相应的权限,才能使用DCOM。有点绕是吧?你可以类比Windows的远程桌面登录,需要拿到服务器的用户名密码才能登录并操作系统,权限受到登录用户的权限所限制。而DCOM就是用的这种方式。关于各种错误网上能找出一大堆解决方案,可能还没一个能解决你的问题的。甚至可能progID无论无何也通不了,始终报错,不得不改用CLSID这种方法,十分坑。

    神坑: DCOM。从配置开始就充满了陷阱和坑。不但配置繁琐复杂,还会受到各种权限以及防火墙规则的影响。最恶心的是这玩意随时可能报各种奇葩的错误,由于缺乏足够的错误信息,很难解决,基本凭借经验解决DCOM的故障。

    开发过程

    收集到足够的准备知识后,就可以开工了。OPC Server是DA 2.0的,因此找到了以下两个开源类库。

    JEasyOPC Client

    底层依赖JNI,只能跑在windows环境,不能跨平台

    整个类库比较古老,使用的dll是32位的,整个项目只能使用32位的JRE运行

    同时支持DA 2.0与3.0协议,算是亮点

    OpenSCADA项目底下的子项目

    纯Java编写,具有跨平台特性

    全部基于DCOM实现(划重点)

    目前只支持DA 2.0协议,3.0协议的支持还在开发中

    这两个类库都试过,JEasyOPC底层用了JNI,调用代码量倒不是很大,使用也足够简单,坑也遇到了点,就是64位的JRE运行会报错,说dll是ia32架构的,不能运行于AMD64平台下,换了32位版本的JRE之后运行起来了,但是一直报错Unknown Error,从JNI报出来的,不明所以,实在无力解决,只能放弃。

    只剩下Utgard一种选择了,也庆幸目标Server是DA 2.0的,用这个类库完全够用。这个类库全部使用DCOM协议连接OPC Server,所以对于本地连接OPC Server,理论上不需要COM口,但是这个类库全部使用DCOM协议连接,所以依旧需要配置主机名,以及登录的用户名密码。使用之前必须先配置DCOM,其中痛苦不足为外人道也,在上面准备知识部分已经写道了。

    经过一番折腾,总算将项目跑起来了,最终参考的工程代码如下(项目实用Gradle构建,代码使用Utgard官方的tutorial范例):

    build.gradle:

    src/main/java/UtgardTutorial1.java:

    最终项目运行输出如下:

    总算跑起来了。

    更多相关内容
  • java OPC通信

    2020-12-09 11:30:45
    1、java连接OPC所需的jar包org.openscada.external.jcifs-1.2.25-20150512.072447-33 org.openscada.opc.dcom-1.2.0-20150512.072915-30 2、运行中可能出现的问题 3、源文件
  • opc java开发代码实例 用于参考
  • maven架构 idea编辑器 包含读取、写入、订阅、批量订阅、断点续传等功能、 下载后可直接运行 内容很简单,就一个类,采用匿名登录的方式,代码和测试全在一个类里面 注释很详细
  • OPC UA协议,用于开发服务器;Java语言编写;
  • Java OPC 代码

    2021-03-05 15:23:47
    这是我项目的代码,算是真实业务实现,记录备份一下实现思路...实现情况使用utgard实现通信:Java实现OPC通信OPCserver:KEPserver6制定变量列表:规定名称和类型说明PC和PLC通信,PC用TAG1之类的地址,PLC用BD1.IN...

    这是我项目的代码,算是真实业务实现,记录备份一下实现思路

    因为业务就是简单的获取数据然后保存,所以还是容易理解的

    1.任务详情

    Java实现OPC通信的代码实现

    最终实现对西门子的S7-300的PLC变量的值读写

    2.实现情况

    使用utgard实现通信:Java实现OPC通信

    OPCserver:KEPserver6

    制定变量列表:规定名称和类型

    说明

    PC和PLC通信,PC用TAG1之类的地址,PLC用BD1.INT05之类的地址,个人总结的表格,方便说明

    OPCServer上定义的地址名称

    硬件PLC上的地址

    数据类型

    说明:这个地址做什么用的

    TAG1

    DB1.INT00

    short

    心跳

    TAG2

    DB1.INT02

    short

    控制字

    TAG3

    DB1.STRING4,10

    string

    箱体信息

    TAG4

    DB1.D14

    float

    结果

    地址定义在OPCServer上,实际截图

    3c32fcf11e9ecf642b507f73c25efb87.png

    制定交互流程:控制字的状态

    心跳变量:TAG1,表示软件和硬件(这里是PLC)连接状态,这个是自己随便定义的,我的是:PLC一直写1,PC收到1写2,5秒内一直收不到1就是断开了。

    控制字(状态变量):TAG2,作用是作为一个控制字,控制整个交互的流程,当是不同值时,PLC和PC要进行指定的读写动作。

    这是我一开始画的

    1b633b5f9adaa66d53329361db5415da.png

    这是后来画的,就是PC和PLC的交互,比较清晰了

    d80ac364ff99aeeeafddeacfae56c80b.png

    实际的PLC地址编辑界面截图

    3703711e84a9462c46fca1a0f5904054.png

    3. 代码实现

    按照官方例子写了一个通信类:通过配置信息,启动server,添加要读取的地址

    配置信息是单独一个类:从配置文件读取IP什么的

    然后是回调函数:某个地址,读到了某个值要怎么做,,在这就2个回调,心跳TAG1:读到1写入2,5秒超时,TAG2:是某个值时,怎么做。。。

    读值和写值可以单独算一个方法类:我用到的数据类型就是几个,short,string,Long Array,对应写了几个方法

    OPCServer上的地址配置也算是一个单独的类:我写在配置文件了,当时犹豫是不是放到数据库,感觉差别不大

    Utgard有两种数据访问方式——直接通过item的read/write方法或者使用AccessBase(读取数据)

    心跳TAG1:PC写2,PLC写1,逻辑是PC收到1写2,超时提示

    控制字TAG2:PLC写0,1,3,4,5,6/7,PC写2,8

    PC在写TAG2=1时,同时写入TAG3值

    PLC在写TAG2=6/7时,同时写入TAG4值

    读线程:每隔500ms读一次值,读到值后执行回调方法DataCallback()

    不用另起线程,直接用这个Access线程循环读取控制字的值,回调方法对应调用item的read/write方法读写值

    final AccessBase access = new SyncAccess(server, 500);

    access.addItem(itemId, new DataCallback() {

    @Override

    public void changed(Item item, ItemState state) {

    System.out.println("-----" + state);

    }

    });

    // start reading

    access.bind();

    4.代码:OPCserver连接配置类

    import java.io.IOException;

    import java.util.Properties;

    import org.openscada.opc.lib.common.ConnectionInformation;

    /**

    * 配置文件工具类

    */

    public final class OPCConfiguration {

    private final static ConnectionInformation ci;

    private final static Properties prop;

    public final static String CONFIG_USERNAME = "username";

    public final static String CONFIG_PASSWORD = "password";

    public final static String CONFIG_HOST = "host";

    public final static String CONFIG_DOMAIN = "domain";

    public final static String CONFIG_CLSID = "clsid";

    public final static String CONFIG_PROGID = "progid";

    private final static String CONFIG_FILE_NAME = "opc.properties";

    /**

    * 加载配置文件

    */

    static {

    ci = new ConnectionInformation();

    prop = new Properties();

    try {

    prop.load(OPCConfiguration.class.getClassLoader().getResourceAsStream(CONFIG_FILE_NAME));

    } catch (IOException e) {

    e.printStackTrace();

    }

    }

    /**

    * 通过名字获得配置的值

    */

    public static String getEntryValue(String name) {

    return prop.getProperty(name);

    }

    /**

    * 获得包含ClsId的连接信息

    */

    public static ConnectionInformation getCLSIDConnectionInfomation() {

    ci.setProgId(null);

    getConnectionInfomation();

    ci.setClsid(prop.getProperty(CONFIG_CLSID));

    return ci;

    }

    /**

    * 获得包含ProgId的连接信息

    */

    public static ConnectionInformation getPROGIDConnectionInfomation() {

    ci.setClsid(null);

    getConnectionInfomation();

    ci.setProgId(prop.getProperty(CONFIG_PROGID));

    return ci;

    }

    /**

    * 获得基础的连接信息

    */

    private static void getConnectionInfomation() {

    ci.setHost(prop.getProperty(CONFIG_HOST));

    ci.setDomain(prop.getProperty(CONFIG_DOMAIN));

    ci.setUser(prop.getProperty(CONFIG_USERNAME));

    ci.setPassword(prop.getProperty(CONFIG_PASSWORD));

    }

    }

    5.代码:通信实现类

    package cn.com.tcb.assembly.management.listener;

    import static cn.com.tcb.assembly.management.listener.OPCConfiguration.getCLSIDConnectionInfomation;

    import java.util.concurrent.Executors;

    import org.jinterop.dcom.common.JIException;

    import org.openscada.opc.lib.common.ConnectionInformation;

    import org.openscada.opc.lib.da.AccessBase;

    import org.openscada.opc.lib.da.Group;

    import org.openscada.opc.lib.da.Item;

    import org.openscada.opc.lib.da.Server;

    import org.openscada.opc.lib.da.SyncAccess;

    import org.slf4j.Logger;

    import org.slf4j.LoggerFactory;

    /**

    * 功能:OPC通信线程 描述:通过循环读取心跳和状态控制字,按照商议好的交互流程读写变量

    */

    public class OPCComm {

    private static Logger logger = LoggerFactory.getLogger(OPCComm.class);

    private Item item_heartbeat;

    private Item item_status;

    private Item item_ordernum;

    private Item item_sn;

    private Item item_boxnum;

    private Item item_abnormal;

    private Item item_finish;

    private Item item_result;

    private Server server;

    /**

    * 单例模式

    */

    private static class SingletonHolder {

    static final OPCComm doOPCComm = new OPCComm();

    }

    public static OPCComm getInstance() {

    return SingletonHolder.doOPCComm;

    }

    /**

    * 启动server 创建一个监控线程 创建一个写入线程

    */

    public void init() throws Exception {

    // 加载配置文件

    final ConnectionInformation ci = getCLSIDConnectionInfomation();

    // 创建server

    final Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());

    try {

    // 启动server

    server.connect();

    logger.info("This is {} message.", "OPCserver connect success");

    this.server = server;

    // 同步读取,500ms一次

    final AccessBase access = new SyncAccess(server, 500);

    access.addItem(OPCElement.ITEMID_HEARTBEAT, new DataCallBack_HeartBeat());

    access.addItem(OPCElement.ITEMID_STATUS, new DataCallBack_Status());

    // 添加一个组

    final Group group = server.addGroup("sew");

    item_heartbeat = group.addItem(OPCElement.ITEMID_HEARTBEAT);

    item_status = group.addItem(OPCElement.ITEMID_STATUS);

    item_ordernum = group.addItem(OPCElement.ITEMID_ORDERNUM);

    item_sn = group.addItem(OPCElement.ITEMID_SN);

    item_boxnum = group.addItem(OPCElement.ITEMID_BOXNUM);

    item_abnormal = group.addItem(OPCElement.ITEMID_ABNORMAL);

    item_finish = group.addItem(OPCElement.ITEMID_FINISH);

    item_result = group.addItem(OPCElement.ITEMID_RESULT);

    // start reading

    access.bind();

    } catch (final JIException e) {

    System.out.println(String.format("%08X: %s", e.getErrorCode(), server.getErrorMessage(e.getErrorCode())));

    }

    }

    public Item getItem_heartbeat() {

    return item_heartbeat;

    }

    public Item getItem_status() {

    return item_status;

    }

    public Item getItem_ordernum() {

    return item_ordernum;

    }

    public Item getItem_sn() {

    return item_sn;

    }

    public Item getItem_boxnum() {

    return item_boxnum;

    }

    public Item getItem_abnormal() {

    return item_abnormal;

    }

    public Item getItem_finish() {

    return item_finish;

    }

    public Item getItem_result() {

    return item_result;

    }

    public Server getServer() {

    return server;

    }

    }

    6.代码:工具类:读写值

    package cn.com.tcb.assembly.management.listener;

    import java.text.DecimalFormat;

    import org.jinterop.dcom.common.JIException;

    import org.jinterop.dcom.core.JIArray;

    import org.jinterop.dcom.core.JIVariant;

    import org.openscada.opc.lib.da.Item;

    import org.openscada.opc.lib.da.ItemState;

    public class OPCUnit {

    /**

    * 写值到变量

    */

    public static void write(Item item, String val) {

    try {

    JIVariant value = new JIVariant(val);

    item.write(value);

    } catch (JIException e) {

    e.printStackTrace();

    }

    }

    /**

    * 写值到变量:数组

    */

    public static void write(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();

    }

    }

    /**

    * 读变量的值 如果是short和int直接返回字符串; 如果是long类型的数组,返回数字内容间加点,对应long,数组,大小为6

    * 如果是float类型的数组,返回数字内容间加逗号,对应float,数组,大小为20

    */

    public static String read(Item item) {

    String result = "";

    try {

    ItemState state = item.read(true);

    int type = state.getValue().getType();

    if (type == JIVariant.VT_UI4) {

    int value = state.getValue().getObjectAsInt();

    return value + "";

    } else if (type == JIVariant.VT_I2) {

    short value = state.getValue().getObjectAsShort();

    return value + "";

    } else if (type == 8195) {

    JIArray jarr = state.getValue().getObjectAsArray();

    Integer[] arr = (Integer[]) jarr.getArrayInstance();

    String value = "";

    for (Integer i : arr) {

    value = value + i + ".";

    }

    String res = value.substring(0, value.length() - 1);

    // "25.36087601.1.1.18.36"-->"25.36087601.01.0001.18.36"

    String[] array = res.split("[.]");

    String ress = array[0] + "." + array[1] + "." + new DecimalFormat("00").format(Long.valueOf(array[2]))

    + "." + new DecimalFormat("0000").format(Long.valueOf(array[3])) + "." + array[4] + "."

    + array[5];

    return ress;

    } else if (type == 8196) {

    JIArray jarr = state.getValue().getObjectAsArray();

    Float[] arr = (Float[]) jarr.getArrayInstance();

    String value = "";

    for (Float f : arr) {

    value = value + f + ",";

    }

    return value.substring(0, value.length() - 1);

    }

    } catch (JIException e) {

    e.printStackTrace();

    }

    return result;

    }

    }

    7.代码:回调函数:实现心跳

    package cn.com.tcb.assembly.management.listener;

    import java.awt.Color;

    import org.jinterop.dcom.common.JIException;

    import org.jinterop.dcom.core.JIVariant;

    import org.openscada.opc.lib.da.DataCallback;

    import org.openscada.opc.lib.da.Item;

    import org.openscada.opc.lib.da.ItemState;

    import cn.com.tcb.assembly.base.core.AppContext;

    import cn.com.tcb.assembly.base.utils.GlobalVariable;

    import cn.com.tcb.assembly.management.ui.main.MainFrame;

    public class DataCallBack_HeartBeat implements DataCallback {

    private OPCComm opc = OPCComm.getInstance();

    private int count = 0;@

    Override

    public void changed(Item item, ItemState state) {

    // 读取心跳变量的值

    try {

    if (state.getValue().getType() == JIVariant.VT_I2) {

    short n = state.getValue().getObjectAsShort();

    setHeartbeat(n);

    }

    } catch (JIException e) {

    e.printStackTrace();

    }

    }

    /**

    * 设置心跳标志位,保存到全局变量

    */

    public void setHeartbeat(short n) {

    MainFrame main = (MainFrame) AppContext.getParam(GlobalVariable.MAIN_UI);

    if (n == 1) {

    OPCUnit.write(opc.getItem_heartbeat(), "2");

    GlobalVariable.heartbeat = true;

    main.getHeartbeat().setBackground(Color.GREEN);

    count = 0;

    } else {

    count++;

    }

    // 循环读取频率是:500ms一次,如果超过6次,也就是3s认定超时

    if (count > 6) {

    GlobalVariable.heartbeat = false;

    main.getHeartbeat().setBackground(Color.RED);

    }

    }

    }

    8.代码:回调函数:实现业务

    package cn.com.tcb.assembly.management.listener;

    import java.awt.Color;

    import org.jinterop.dcom.common.JIException;

    import org.jinterop.dcom.core.JIVariant;

    import org.openscada.opc.lib.da.DataCallback;

    import org.openscada.opc.lib.da.Item;

    import org.openscada.opc.lib.da.ItemState;

    import cn.com.tcb.assembly.base.core.AppContext;

    import cn.com.tcb.assembly.base.core.BeanContainer;

    import cn.com.tcb.assembly.base.model.JobCard;

    import cn.com.tcb.assembly.base.model.TestResult;

    import cn.com.tcb.assembly.base.service.JobCardService;

    import cn.com.tcb.assembly.base.service.TestResultService;

    import cn.com.tcb.assembly.base.utils.GlobalVariable;

    import cn.com.tcb.assembly.base.utils.StringUtil;

    import cn.com.tcb.assembly.management.ui.main.MainFrame;

    /**

    * 根据控制字:向PLC写入工单信息,保存结果

    */

    public class DataCallBack_Status implements DataCallback {

    private OPCComm opc = OPCComm.getInstance();

    private TestResultService testResultService = (TestResultService) BeanContainer.getBean("testResultService");

    private MainFrame main = (MainFrame) AppContext.getParam(GlobalVariable.MAIN_UI);

    @Override

    public void changed(Item item, ItemState itemState) {

    // 读取状态变量的值

    try {

    if (itemState.getValue().getType() == JIVariant.VT_I2) {

    short n = itemState.getValue().getObjectAsShort();

    // 前提是有心跳

    if (GlobalVariable.heartbeat) {

    doProcess(n);

    }

    }

    } catch (JIException e) {

    e.printStackTrace();

    }

    }

    /**

    * 状态控制字: 0:PLC上电,1:允许新工单,2:新任务,3:收到任务,4:准备执行,5:开始执行 6:正常完成,7:异常玩常完成,8:收到完成信号

    */

    private void doProcess(short n) {

    if (n == 0) {

    setStatus(Color.GREEN, "等待设备初始化");

    GlobalVariable.isAllowInput = true;

    } else if (n == 1) {

    setStatus(Color.GREEN, "请输入工单");

    GlobalVariable.isAllowInput = true;

    } else if (n == 2) {

    setStatus(Color.GREEN, "发送成功,等待设备处理");

    } else if (n == 3) {

    setStatus(Color.GREEN, "设备收到工单");

    GlobalVariable.isAllowInput = false;

    } else if (n == 4) {

    setStatus(Color.GREEN, "设备准备执行");

    GlobalVariable.isAllowInput = false;

    } else if (n == 5) {

    setStatus(Color.GREEN, "设备开始执行");

    GlobalVariable.isAllowInput = false;

    // 写入初始结果,记录开始时间

    if (!GlobalVariable.isHaveOrder) {

    reconnectAction();

    } else {

    saveStartResult();

    }

    } else if (n == 6) {

    setStatus(Color.GREEN, "正常完成");

    // 写入最终结果,记录结束时间

    if (!GlobalVariable.isHaveOrder) {

    reconnectAction();

    }

    saveEndResult(OPCControllerState.NORMALEND);

    } else if (n == 7) {

    setStatus(Color.RED, "异常完成");

    // 写入最终结果,记录结束时间

    if (!GlobalVariable.isHaveOrder) {

    reconnectAction();

    }

    saveEndResult(OPCControllerState.ABNORMALEND);

    } else if (n == OPCControllerState.SAVESUCCESS) {

    setStatus("结果保存成功");

    } else {

    setStatus(Color.RED, "异常");

    }

    }

    /**

    * 设置主界面状态信息

    */

    private void setStatus(Color color, String status) {

    main.getStatus().setText(status);

    main.getStatus().setBackground(color);

    }

    private void setStatus(String status) {

    main.getStatus().setText(status);

    }

    /**

    * 保存初始结果:5

    */

    private void saveStartResult() {

    if (!GlobalVariable.isSaveStartSuccess) {

    // 从全局变量获取结果表

    TestResult t = (TestResult) AppContext.getParam(GlobalVariable.PARAM_KEY_TESTRESULT);

    // 开始时间

    String startTime = testResultService.getDate();

    t.setStartTime(startTime);

    main.getStartTime().setText(startTime);

    // 装配日期

    t.setDate(startTime.split("[ ]")[0]);

    // 保存结果表到全局变量

    AppContext.setParam(GlobalVariable.PARAM_KEY_TESTRESULT, t);

    // 保存结果到数据库

    int n = testResultService.getSqlServerCountBySerialNumber(t.getSerialNumber());

    if (n == 0) {

    testResultService.saveResult(t);

    } else {

    testResultService.updateResult(t);

    }

    GlobalVariable.isSaveStartSuccess = true;

    }

    }

    /**

    * 保存最终结果:6或者7

    */

    private void saveEndResult(int n) {

    if (!GlobalVariable.isSaveEndSuccess) {

    // 从全局变量获取结果表

    TestResult t = (TestResult) AppContext.getParam(GlobalVariable.PARAM_KEY_TESTRESULT);

    // 保存结果到界面

    String endTime = testResultService.getDate();

    t.setEndTime(endTime);

    main.getEndTime().setText(endTime);

    String time = StringUtil.getTimeBetween(t.getStartTime(), endTime);

    t.setTime(time);

    main.getTime().setText(time);

    String value = OPCUnit.read(opc.getItem_result());

    String result = "";

    String[] arr = value.split("[,]");

    result = arr[0] + "-" + arr[1];

    for (int i = 2; i < arr.length; i = i + 2) {

    result = result + dealWith(arr[i], arr[i + 1]);

    }

    main.getResult01().setText(dealWithShow(arr[0], arr[1]));

    main.getResult02().setText(dealWithShow(arr[2], arr[3]));

    main.getResult03().setText(dealWithShow(arr[4], arr[5]));

    main.getResult04().setText(dealWithShow(arr[6], arr[7]));

    main.getResult05().setText(dealWithShow(arr[8], arr[9]));

    main.getResult06().setText(dealWithShow(arr[10], arr[11]));

    main.getResult07().setText(dealWithShow(arr[12], arr[13]));

    main.getResult08().setText(dealWithShow(arr[14], arr[15]));

    main.getResult09().setText(dealWithShow(arr[16], arr[17]));

    main.getResult10().setText(dealWithShow(arr[18], arr[19]));

    t.setResult(result);

    // 异常

    if (n == OPCControllerState.NORMALEND) {

    t.setAbnormal("否");

    } else {

    t.setAbnormal("是");

    }

    // 保存最终结果到数据库

    testResultService.updateResult(t);

    // 当前工单已经完成

    GlobalVariable.isHaveOrder = false;

    // 写入状态字

    OPCUnit.write(opc.getItem_status(), “8”);

    // 读取成功后不再重复读取

    GlobalVariable.isSaveEndSuccess = true;

    }

    }

    /**

    * 断线重连动作

    */

    private void reconnectAction() {

    String sn = OPCUnit.read(opc.getItem_sn());

    // 根据序列号查询初始保存的结果

    TestResult t = testResultService.getOneDataBySerialNumber(sn);

    AppContext.setParam(GlobalVariable.PARAM_KEY_TESTRESULT, t);

    // 工单恢复

    JobCardService jcs = (JobCardService) BeanContainer.getBean("jobCardService");

    JobCard job = jcs.getJobCardData(t.getOrderNum());

    job.setSerialNumber(t.getSerialNumber());

    AppContext.setParam(GlobalVariable.PARAM_KEY_JOBCARD, job); // 保存工单信息到全局变量

    // 界面恢复

    main.getOrderNumber().setText(t.getOrderNum());

    main.getSn().setText(t.getSerialNumber());

    main.getUnitType().setText(t.getUnitSize());

    main.getBoxNumber().setText(t.getUnitSize().split(" ")[0]);

    main.getSn_count().setText(Integer.parseInt(t.getSerialNumber().split("[.]")[3]) + "");

    main.getCount().setText(job.getCount());

    main.getStartTime().setText(t.getStartTime());

    GlobalVariable.isHaveOrder = true;

    }

    /**

    * 处理结果字符串

    */

    private String dealWith(String str1, String str2) {

    if (str1.equals("0.0") && str2.equals("0.0")) {

    return "";

    }

    return ", " + str1 + "-" + str2;

    }

    private String dealWithShow(String str1, String str2) {

    if (str1.equals("0.0") && str2.equals("0.0")) {

    return "";

    }

    return str1 + "-" + str2;

    }

    }

    9.配合业务:发送信息到PLC

    package cn.com.tcb.assembly.management.action.main;

    import java.awt.Color;

    import java.awt.event.ActionEvent;

    import java.awt.event.ActionListener;

    import cn.com.tcb.assembly.base.core.AppContext;

    import cn.com.tcb.assembly.base.core.BeanContainer;

    import cn.com.tcb.assembly.base.core.LocalDataStore;

    import cn.com.tcb.assembly.base.core.ui.PopupDialog;

    import cn.com.tcb.assembly.base.core.ui.PopupManager;

    import cn.com.tcb.assembly.base.model.JobCard;

    import cn.com.tcb.assembly.base.model.TestResult;

    import cn.com.tcb.assembly.base.model.User;

    import cn.com.tcb.assembly.base.service.TestResultService;

    import cn.com.tcb.assembly.base.utils.GlobalVariable;

    import cn.com.tcb.assembly.management.listener.OPCComm;

    import cn.com.tcb.assembly.management.listener.OPCUnit;

    import cn.com.tcb.assembly.management.ui.main.MainFrame;

    /**

    * 发送按钮,发送工单信息到PLC

    */

    public class SendAction implements ActionListener {

    private TestResultService testResultService = (TestResultService) BeanContainer.getBean("testResultService");

    private OPCComm opc = OPCComm.getInstance();

    private MainFrame main;

    public SendAction(MainFrame mainFrame) {

    main = mainFrame;

    }

    @Override

    public void actionPerformed(ActionEvent e) {

    inputJobCard();

    }

    private void inputJobCard() {

    // 如果设备正在运行,则不允许发送

    // 如果有工单就能发送

    if (GlobalVariable.isHaveOrder) {

    // 从全局变量获取工单信息

    JobCard job = (JobCard) AppContext.getParam(GlobalVariable.PARAM_KEY_JOBCARD);

    // 判定是否重做

    int n = testResultService.getSqlServerCountBySerialNumber(job.getSerialNumber());

    if(n != 0 && !optionDialog("已有工单,是否重做?")) {

    return;

    }

    // 创建结果表

    TestResult t = new TestResult();

    // 写入工单号

    String orderNum = job.getOrderNum();

    t.setOrderNum(orderNum);

    OPCUnit.write(opc.getItem_ordernum(), orderNum);

    // 写入序列号

    String sn = job.getSerialNumber();

    t.setSerialNumber(sn);

    OPCUnit.write(opc.getItem_sn(), sn.split("[.]"));

    // 写入箱体号

    String unitSize = job.getUnitSize();

    t.setUnitSize(unitSize);

    OPCUnit.write(opc.getItem_boxnum(), unitSize.split(" ")[0]);

    // 装配机

    String assembly = LocalDataStore.read("localName");

    t.setAssembly(assembly);

    // 操作员

    User user = (User) AppContext.getParam(GlobalVariable.PARAM_KEY_USER);

    t.setOperator(user.getUserName());

    // 异常

    t.setAbnormal("是");

    // 保存结果表到全局变量

    AppContext.setParam(GlobalVariable.PARAM_KEY_TESTRESULT, t);

    // 写入状态字

    OPCUnit.write(opc.getItem_status(), "2");

    // 焦点返回扫码框

    main.getScanCode().requestFocus();

    // 状态信息

    setStatus(Color.GREEN, "发送成功,等待设备处理");

    }

    }

    /**

    * 设置主界面状态信息

    */

    private void setStatus(Color color, String status) {

    main.getStatus().setText(status);

    main.getStatus().setBackground(color);

    }

    /**

    * @Title: optionDialog

    * @Description: 选择弹窗,默认选择:是

    */

    private boolean optionDialog(String message) {

    int i = PopupDialog.open(PopupManager.DEFAULT, "提醒窗口", message, new String[] { "是", "否" }, new int[] { 1, 2 },

    1, 2);

    if (i == 1) {

    return true;

    } else {

    return false;

    }

    }

    }

    10.地址变量

    package cn.com.tcb.assembly.management.listener;

    import java.io.IOException;

    import java.util.Properties;

    /**

    * @ClassName: OPCElement

    * @Description: PC和PLC通信使用的OPCserver上的标记名称列表

    */

    public class OPCElement {

    private final static Properties prop;

    private final static String CONFIG_FILE_NAME = "opc_list.properties";

    /**

    * 加载配置文件

    */

    static {

    prop = new Properties();

    try {

    prop.load(OPCElement.class.getClassLoader().getResourceAsStream(CONFIG_FILE_NAME));

    } catch (IOException e) {

    e.printStackTrace();

    }

    }

    /**

    * 心跳: 读到1,写入2

    */

    public static final String ITEMID_HEARTBEAT = prop.getProperty("addr01");

    /**

    * 状态控制字: 0:PLC上电,1:允许新工单,2:新任务,3:收到任务,4:准备执行,5:开始执行 6:正常完成,7:异常玩常完成,8:收到完成信号

    */

    public static final String ITEMID_STATUS = prop.getProperty("addr02");

    /**

    * 工单号:7位工单号,如:5332087

    */

    public static final String ITEMID_ORDERNUM = prop.getProperty("addr03");

    /**

    * 序列号,长整型,数组[6]

    */

    public static final String ITEMID_SN = prop.getProperty("addr04");

    /**

    * 箱体号:字符串,如:“R27”

    */

    public static final String ITEMID_BOXNUM = prop.getProperty("addr12");

    /**

    * 异常代码:暂时没用

    */

    public static final String ITEMID_ABNORMAL = prop.getProperty("addr06");

    /**

    * 完成信号:暂时没用

    */

    public static final String ITEMID_FINISH = prop.getProperty("addr07");

    /**

    * 结果:浮点型,数组[20]

    */

    public static final String ITEMID_RESULT = prop.getProperty("addr08");

    /**

    * 装配策略,内容:箱体号-轴s数量:字符串,如:“R107-2”

    */

    public static final String ITEMID_STRATEGY = prop.getProperty("addr05");

    /**

    * 工单号:7位工单号,如:5332087,,用来扫码发送

    */

    public static final String ITEMID_BARCODE = prop.getProperty("addr10");

    /**

    * 垫片值:浮点型,数组[8]

    */

    public static final String ITEMID_GASKET = prop.getProperty("addr11");

    /**

    * 安装方式:字符串,如:“M1”

    */

    public static final String ITEMID_MP = prop.getProperty("addr09");

    /**

    * 轴数量:字符串,如:“2”,“3”

    */

    public static final String ITEMID_SHAFT = prop.getProperty("addr13");

    }

    地址写在了配置文件里

    opc_list.properties

    #heartbeat:short

    addr01=PLC.S7-300.TAG1

    #control:short

    addr02=PLC.S7-300.TAG2

    #orderNum:long

    addr03=PLC.S7-300.TAG3

    #sn:long,array[6]

    addr04=PLC.S7-300.TAG4

    #Strategy:string

    addr05=PLC.S7-300.TAG5

    #abnormal:short

    addr06=PLC.S7-300.TAG6

    #finish:short

    addr07=PLC.S7-300.TAG7

    #result:float,array[20]

    addr08=PLC.S7-300.TAG8

    #MP:string

    addr09=PLC.S7-300.TAG9

    #barcode:long

    addr10=PLC.S7-300.TAG10

    #gasket:float,array[8]

    addr11=PLC.S7-300.TAG11

    #boxNum:string

    addr12=PLC.S7-300.TAG12

    #shaft:string

    addr13=PLC.S7-300.TAG13

    11.其他

    另一个项目的,配置地址写在了代码里

    OPCClient.java

    package cn.com.tcb.uavpcs.datacenter.comm.plc.opcclient;

    import java.util.HashMap;

    import java.util.Map;

    import java.util.concurrent.Executors;

    import org.jinterop.dcom.common.JIException;

    import org.openscada.opc.lib.common.ConnectionInformation;

    import org.openscada.opc.lib.da.AccessBase;

    import org.openscada.opc.lib.da.Group;

    import org.openscada.opc.lib.da.Item;

    import org.openscada.opc.lib.da.Server;

    import org.openscada.opc.lib.da.SyncAccess;

    import org.slf4j.Logger;

    import org.slf4j.LoggerFactory;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.stereotype.Component;

    import cn.com.tcb.uavpcs.datacenter.comm.plc.opcclient.callbacks.OvenCrossCallback;

    import cn.com.tcb.uavpcs.datacenter.comm.plc.opcclient.callbacks.CrossCallbackFactory;

    import cn.com.tcb.uavpcs.datacenter.comm.plc.opcclient.callbacks.OvenMoveCallBackFactory;

    import cn.com.tcb.uavpcs.datacenter.comm.plc.opcclient.callbacks.StationCallBack;

    import cn.com.tcb.uavpcs.datacenter.comm.plc.opcclient.items.OPCItems;

    import cn.com.tcb.uavpcs.datacenter.linecontrol.LineModel;

    /**

    * OPC客户端,用于同PLC通信,通过OPC服务器,

    *

    */

    @Component

    public class OPCClient {

    private static Logger logger = LoggerFactory.getLogger(OPCClient.class);

    public final static Map ITEMS = new HashMap();// 所有的变量对象

    @Autowired

    private OPCConfig config;

    private AccessBase access;

    public void start() throws Exception {

    logger.info("OPC客户端初始化启动");

    // 连接信息

    ConnectionInformation ci = new ConnectionInformation();

    ci.setHost(config.getHost());// 主机地址

    ci.setUser(config.getUser());// 用户名

    ci.setPassword(config.getPassword());// 密码

    ci.setClsid(config.getClsid());// clsId,使用DCOM组件的clsId连接OPC服务器

    // 连接服务器

    Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());

    server.connect();

    // 变量组

    Group group = server.addGroup();

    // 变量统一加入组,方便以后读写

    // 路口动作

    addItem(group, OPCItems.ITEM_CROSS_ACTION_1);

    addItem(group, OPCItems.ITEM_CROSS_ACTION_2);

    addItem(group, OPCItems.ITEM_CROSS_ACTION_3);

    addItem(group, OPCItems.ITEM_CROSS_ACTION_4);

    addItem(group, OPCItems.ITEM_CROSS_ACTION_5);

    addItem(group, OPCItems.ITEM_CROSS_ACTION_6);

    addItem(group, OPCItems.ITEM_CROSS_ACTION_7);

    addItem(group, OPCItems.ITEM_CROSS_ACTION_8);

    // 托盘进出gongwei

    addItem(group, OPCItems.ITEM_CROSS_FROM_1);

    addItem(group, OPCItems.ITEM_CROSS_FROM_2);

    addItem(group, OPCItems.ITEM_CROSS_FROM_3);

    addItem(group, OPCItems.ITEM_CROSS_FROM_4);

    addItem(group, OPCItems.ITEM_CROSS_FROM_5);

    addItem(group, OPCItems.ITEM_CROSS_FROM_6);

    addItem(group, OPCItems.ITEM_CROSS_FROM_7);

    addItem(group, OPCItems.ITEM_CROSS_FROM_8);

    // 线体1烘箱存取

    addItem(group, OPCItems.ITEM_OVEN_ACTION_1);

    addItem(group, OPCItems.ITEM_OVEN_COLUMN_1);

    addItem(group, OPCItems.ITEM_OVEN_LAYER_1);

    addItem(group, OPCItems.ITEM_OVEN_ROW_1);

    addItem(group, OPCItems.ITEM_OVEN_STATE_1);

    // 线体2烘箱存取

    addItem(group, OPCItems.ITEM_OVEN_ACTION_2);

    addItem(group, OPCItems.ITEM_OVEN_COLUMN_2);

    addItem(group, OPCItems.ITEM_OVEN_LAYER_2);

    addItem(group, OPCItems.ITEM_OVEN_ROW_2);

    addItem(group, OPCItems.ITEM_OVEN_STATE_2);

    access = new SyncAccess(server, 500);

    // 路口动作

    access.addItem(OPCItems.ITEM_CROSS_1, CrossCallbackFactory.build(LineModel.CROSS_ID_1));

    access.addItem(OPCItems.ITEM_CROSS_2, CrossCallbackFactory.build(LineModel.CROSS_ID_2));

    access.addItem(OPCItems.ITEM_CROSS_3, CrossCallbackFactory.build(LineModel.CROSS_ID_3));

    access.addItem(OPCItems.ITEM_CROSS_4, new OvenCrossCallback(LineModel.LINE_ID_1));// 烘箱路口,线体1

    access.addItem(OPCItems.ITEM_CROSS_5, CrossCallbackFactory.build(LineModel.CROSS_ID_5));

    access.addItem(OPCItems.ITEM_CROSS_6, CrossCallbackFactory.build(LineModel.CROSS_ID_6));

    access.addItem(OPCItems.ITEM_CROSS_7, CrossCallbackFactory.build(LineModel.CROSS_ID_7));

    access.addItem(OPCItems.ITEM_CROSS_8, new OvenCrossCallback(LineModel.LINE_ID_2));// 烘箱路口,线体2

    // 线体1,烘箱存取

    access.addItem(OPCItems.ITEM_OVEN_STATE_1, OvenMoveCallBackFactory.build(LineModel.LINE_ID_1));// 烘箱存取托盘,线体1

    // 线体2,烘箱存取

    access.addItem(OPCItems.ITEM_OVEN_STATE_2, OvenMoveCallBackFactory.build(LineModel.LINE_ID_2));// 烘箱存取托盘,线体2

    // 工位按钮动作

    access.addItem(OPCItems.ITEM_STATION_1, new StationCallBack(LineModel.STATION_ID_1));

    access.addItem(OPCItems.ITEM_STATION_2, new StationCallBack(LineModel.STATION_ID_2));

    access.addItem(OPCItems.ITEM_STATION_3, new StationCallBack(LineModel.STATION_ID_3));

    access.addItem(OPCItems.ITEM_STATION_4, new StationCallBack(LineModel.STATION_ID_4));

    access.addItem(OPCItems.ITEM_STATION_5, new StationCallBack(LineModel.STATION_ID_5));

    access.addItem(OPCItems.ITEM_STATION_6, new StationCallBack(LineModel.STATION_ID_6));

    access.addItem(OPCItems.ITEM_STATION_7, new StationCallBack(LineModel.STATION_ID_7));

    access.addItem(OPCItems.ITEM_STATION_8, new StationCallBack(LineModel.STATION_ID_8));

    access.addItem(OPCItems.ITEM_STATION_9, new StationCallBack(LineModel.STATION_ID_9));

    access.addItem(OPCItems.ITEM_STATION_10, new StationCallBack(LineModel.STATION_ID_10));

    access.addItem(OPCItems.ITEM_STATION_11, new StationCallBack(LineModel.STATION_ID_11));

    access.addItem(OPCItems.ITEM_STATION_12, new StationCallBack(LineModel.STATION_ID_12));

    // 绑定,开始读取

    access.bind();

    // Thread.sleep(100 * 1000);// 延时停止

    // access.unbind();// 结束绑定

    logger.info("OPC客户端初始化启动完成");

    }

    private void addItem(Group group, String itemId) throws Exception {

    Item item = group.addItem(itemId);

    ITEMS.put(itemId, item);

    }

    public void stop() {

    try {

    access.unbind();

    } catch (JIException e) {

    // TODO Auto-generated catch block

    e.printStackTrace();

    }

    }

    }

    OPCItems.java

    package cn.com.tcb.uavpcs.datacenter.comm.plc.opcclient.items;

    import java.util.HashMap;

    import java.util.Map;

    /**

    * 相关PLC变量在OPC服务器上的标签

    *

    */

    public class OPCItems {

    // 路口,状态,对应8个路口,int

    public final static String ITEM_CROSS_1 = "u.u.cross1";

    public final static String ITEM_CROSS_2 = "u.u.cross2";

    public final static String ITEM_CROSS_3 = "u.u.cross3";

    public final static String ITEM_CROSS_4 = "u.u.cross4";

    public final static String ITEM_CROSS_5 = "u.u.cross5";

    public final static String ITEM_CROSS_6 = "u.u.cross6";

    public final static String ITEM_CROSS_7 = "u.u.cross7";

    public final static String ITEM_CROSS_8 = "u.u.cross8";

    // 路口,动作,对应8个路口,int

    public final static String ITEM_CROSS_FROM_1 = "u.u.from1";

    public final static String ITEM_CROSS_FROM_2 = "u.u.from2";

    public final static String ITEM_CROSS_FROM_3 = "u.u.from3";

    public final static String ITEM_CROSS_FROM_4 = "u.u.from4";

    public final static String ITEM_CROSS_FROM_5 = "u.u.from5";

    public final static String ITEM_CROSS_FROM_6 = "u.u.from6";

    public final static String ITEM_CROSS_FROM_7 = "u.u.from7";

    public final static String ITEM_CROSS_FROM_8 = "u.u.from8";

    // 路口,条码,对应8个路口,String

    // public final static String ITEM_CROSS_BARCODE_1 = "u.u.barcode1";

    // public final static String ITEM_CROSS_BARCODE_2 = "u.u.barcode2";

    // public final static String ITEM_CROSS_BARCODE_3 = "u.u.barcode3";

    // public final static String ITEM_CROSS_BARCODE_4 = "u.u.barcode4";

    // public final static String ITEM_CROSS_BARCODE_5 = "u.u.barcode5";

    // public final static String ITEM_CROSS_BARCODE_6 = "u.u.barcode6";

    // public final static String ITEM_CROSS_BARCODE_7 = "u.u.barcode7";

    // public final static String ITEM_CROSS_BARCODE_8 = "u.u.barcode8";

    // 路口,动作,对应8个路口,int

    public final static String ITEM_CROSS_ACTION_1 = "u.u.action1";

    public final static String ITEM_CROSS_ACTION_2 = "u.u.action2";

    public final static String ITEM_CROSS_ACTION_3 = "u.u.action3";

    public final static String ITEM_CROSS_ACTION_4 = "u.u.action4";

    public final static String ITEM_CROSS_ACTION_5 = "u.u.action5";

    public final static String ITEM_CROSS_ACTION_6 = "u.u.action6";

    public final static String ITEM_CROSS_ACTION_7 = "u.u.action7";

    public final static String ITEM_CROSS_ACTION_8 = "u.u.action8";

    // 工位按钮,12个工位

    public final static String ITEM_STATION_1 = "u.u.station1";

    public final static String ITEM_STATION_2 = "u.u.station2";

    public final static String ITEM_STATION_3 = "u.u.station3";

    public final static String ITEM_STATION_4 = "u.u.station4";

    public final static String ITEM_STATION_5 = "u.u.station5";

    public final static String ITEM_STATION_6 = "u.u.station6";

    public final static String ITEM_STATION_7 = "u.u.station7";

    public final static String ITEM_STATION_8 = "u.u.station8";

    public final static String ITEM_STATION_9 = "u.u.station9";

    public final static String ITEM_STATION_10 = "u.u.station10";

    public final static String ITEM_STATION_11 = "u.u.station11";

    public final static String ITEM_STATION_12 = "u.u.station12";

    // 线体1,烘箱存取控制

    public final static String ITEM_OVEN_STATE_1 = "u.u.oven1";

    public final static String ITEM_OVEN_ACTION_1 = "u.u.ovenaction1";

    public final static String ITEM_OVEN_COLUMN_1 = "u.u.column1";

    public final static String ITEM_OVEN_LAYER_1 = "u.u.layer1";

    public final static String ITEM_OVEN_ROW_1 = "u.u.row1";

    // 线体2,烘箱存取控制

    public final static String ITEM_OVEN_STATE_2 = "u.u.oven2";

    public final static String ITEM_OVEN_ACTION_2 = "u.u.ovenaction2";

    public final static String ITEM_OVEN_COLUMN_2 = "u.u.column2";

    public final static String ITEM_OVEN_LAYER_2 = "u.u.layer2";

    public final static String ITEM_OVEN_ROW_2 = "u.u.row2";

    // // 路口和条码的对应关系

    // public final static Map CROSS_BARCODES = new HashMap

    // String>();

    // static {

    // CROSS_BARCODES.put(ITEM_CROSS_1, ITEM_CROSS_BARCODE_1);

    // CROSS_BARCODES.put(ITEM_CROSS_2, ITEM_CROSS_BARCODE_2);

    // CROSS_BARCODES.put(ITEM_CROSS_3, ITEM_CROSS_BARCODE_3);

    // CROSS_BARCODES.put(ITEM_CROSS_4, ITEM_CROSS_BARCODE_4);

    // CROSS_BARCODES.put(ITEM_CROSS_5, ITEM_CROSS_BARCODE_5);

    // CROSS_BARCODES.put(ITEM_CROSS_6, ITEM_CROSS_BARCODE_6);

    // CROSS_BARCODES.put(ITEM_CROSS_7, ITEM_CROSS_BARCODE_7);

    // CROSS_BARCODES.put(ITEM_CROSS_8, ITEM_CROSS_BARCODE_8);

    // }

    // // 路口和动作的对应关系

    // public final static Map CROSS_ACTIONS = new HashMap

    // String>();

    // static {

    // CROSS_BARCODES.put(ITEM_CROSS_1, ITEM_CROSS_ACTION_1);

    // CROSS_BARCODES.put(ITEM_CROSS_2, ITEM_CROSS_ACTION_2);

    // CROSS_BARCODES.put(ITEM_CROSS_3, ITEM_CROSS_ACTION_3);

    // CROSS_BARCODES.put(ITEM_CROSS_4, ITEM_CROSS_ACTION_4);

    // CROSS_BARCODES.put(ITEM_CROSS_5, ITEM_CROSS_ACTION_5);

    // CROSS_BARCODES.put(ITEM_CROSS_6, ITEM_CROSS_ACTION_6);

    // CROSS_BARCODES.put(ITEM_CROSS_7, ITEM_CROSS_ACTION_7);

    // CROSS_BARCODES.put(ITEM_CROSS_8, ITEM_CROSS_ACTION_8);

    // }

    }

    展开全文
  • 因工作需要,研究了OPC ua协议,使用Java语言连接操作OPC。本资源里面包括了代码和连接工具,以及.Net的运行环境。
  • java opc-ua 连接kepserver 读写、订阅操作

    千次阅读 热门讨论 2022-04-19 15:55:08
    点击 Administration 然后会在电脑右下角的任务栏看到kepserver的图标, 右键点击出来选项之后点击opcua设置进入下面页面 双击蓝色区域 如下页面可以根据需求自行配置, 然后再次点击右下角图标,点击...

    一.配置服务端

    第一步,下载kepserver,这个在网上都能下到,有需求的也可以找我,我这边提供一个无限时长的

    第二步,配置kepserver

    点击 Administration 然后会在电脑右下角的任务栏看到kepserver的图标, 右键点击出来选项之后点击opcua设置进入下面页面

    双击蓝色区域

    如下页面可以根据需求自行配置,

    然后再次点击右下角图标,点击设置

    进入下面页面 

    右键administrators添加用户,设置用户名密码,点击确认,然后进入kepserver的界面,右键项目-属性,设置ua匿名账户登录

    本文后续的代码全部使用匿名登录进行,

    二.源代码

    导入依赖

    <!--Milo客户端的依赖-->
    <dependency>
        <groupId>org.eclipse.milo</groupId>
        <artifactId>sdk-client</artifactId>
        <version>0.6.3</version>
    </dependency>
    <!--Milo客户端的依赖-->
    <dependency>
        <groupId>org.eclipse.milo</groupId>
        <artifactId>sdk-server</artifactId>
        <version>0.6.3</version>
    </dependency>

    编写代码

    public class Demo {
        private final static String endPointUrl = "opc.tcp://172.16.1.224:49320";
        /**
         *
         *
         * 创建OPC UA客户端
         * @return
         * @throws Exception
         */
        private static OpcUaClient createClient() throws Exception {
            //opc ua服务端地址
    
            Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
            Files.createDirectories(securityTempDir);
            if (!Files.exists(securityTempDir)) {
                throw new Exception("unable to create security dir: " + securityTempDir);
            }
            return OpcUaClient.create(endPointUrl,
                    endpoints ->
                            endpoints.stream()
                                    .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
                                    .findFirst(),
                    configBuilder ->
                            configBuilder
                                    .setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
                                    .setApplicationUri("urn:eclipse:milo:examples:client")
                                    //访问方式
                                    .setIdentityProvider(new AnonymousProvider())
                                    .setRequestTimeout(UInteger.valueOf(500))
                                    .build()
            );
        }
        /**
         * 遍历树形节点
         *
         * @param client OPC UA客户端
         * @param uaNode 节点
         * @throws Exception
         */
        private static void browseNode(OpcUaClient client, UaNode uaNode) throws Exception {
            List<? extends UaNode> nodes;
            if (uaNode == null) {
                nodes = client.getAddressSpace().browseNodes(Identifiers.ObjectsFolder);
            } else {
                nodes = client.getAddressSpace().browseNodes(uaNode);
            }
            for (UaNode nd : nodes) {
                //排除系统行性节点,这些系统性节点名称一般都是以"_"开头
                if (Objects.requireNonNull(nd.getBrowseName().getName()).contains("_")) {
                    continue;
                }
                System.out.println("Node= " + nd.getBrowseName().getName());
                browseNode(client, nd);
            }
        }
        /**
         * 读取节点数据
         *
         * @param client OPC UA客户端
         * @throws Exception
         */
        private static void readNode(OpcUaClient client) throws Exception {
            int namespaceIndex = 2;
            String identifier = "通道 1.设备 1.标记 1";
            //节点
            NodeId nodeId = new NodeId(namespaceIndex, identifier);
            //读取节点数据
            DataValue value = client.readValue(0.0, TimestampsToReturn.Neither, nodeId).get();
            //标识符
            identifier = String.valueOf(nodeId.getIdentifier());
            System.out.println(identifier + ": " + String.valueOf(value.getValue().getValue()));
        }
        /**
         * 写入节点数据
         *
         * @param client
         * @throws Exception
         */
        private static void writeNodeValue(OpcUaClient client) throws Exception {
            //节点
            NodeId nodeId = new NodeId(2, "通道 1.设备 1.标记 4");
            Short i = 3;
            //创建数据对象,此处的数据对象一定要定义类型,不然会出现类型错误,导致无法写入
            DataValue nowValue = new DataValue(new Variant(i), null, null);
            //写入节点数据
            StatusCode statusCode = client.writeValue(nodeId, nowValue).join();
            System.out.println("结果:" + statusCode.isGood());
        }/**
         * 订阅(单个)
         *
         * @param client
         * @throws Exception
         */
        private static void subscribe(OpcUaClient client) throws Exception {
            AtomicInteger a=new AtomicInteger();
            //创建发布间隔1000ms的订阅对象
            client
                    .getSubscriptionManager()
                    .createSubscription(1000.0)
                    .thenAccept(t -> {
                        //节点
                        NodeId nodeId = new NodeId(2, "通道 1.设备 1.标记 4");
                        ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, null);
                        //创建监控的参数
                        MonitoringParameters parameters = new MonitoringParameters(UInteger.valueOf(a.getAndIncrement()), 1000.0, null, UInteger.valueOf(10), true);
                        //创建监控项请求
                        //该请求最后用于创建订阅。
                        MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);
                        List<MonitoredItemCreateRequest> requests = new ArrayList<>();
                        requests.add(request);
                        //创建监控项,并且注册变量值改变时候的回调函数。
                        t.createMonitoredItems(
                                TimestampsToReturn.Both,
                                requests,
                                (item, id) -> item.setValueConsumer((it, val) -> {
                                    System.out.println("nodeid :" + it.getReadValueId().getNodeId());
                                    System.out.println("value :" + val.getValue().getValue());
                                })
                        );
                    }).get();
    
            //持续订阅
            Thread.sleep(Long.MAX_VALUE);
        }
        /**
         * 批量订阅
         *
         * @param client
         * @throws Exception
         */
    //    private static void managedSubscriptionEvent(OpcUaClient client) throws Exception {
    //        final CountDownLatch eventLatch = new CountDownLatch(1);
    //
    //        //处理订阅业务
    //        handlerNode(client);
    //
    //        //持续监听
    //        eventLatch.await();
    //    }
    
        /**
         * 处理订阅业务
         *
         * @param client OPC UA客户端
         */
        private static void handlerNode(OpcUaClient client) {
            try {
                //创建订阅
                ManagedSubscription subscription = ManagedSubscription.create(client);
    
                //你所需要订阅的key
                List<String> key = new ArrayList<>();
                key.add("通道 1.设备 1.标记 4");
                key.add("通道 1.设备 1.标记 1");
    
                List<NodeId> nodeIdList = new ArrayList<>();
                for (String s : key) {
                    nodeIdList.add(new NodeId(2, s));
                }
    
                //监听
                List<ManagedDataItem> dataItemList = subscription.createDataItems(nodeIdList);
                for (ManagedDataItem managedDataItem : dataItemList) {
                    managedDataItem.addDataValueListener((t) -> {
                        System.out.println(managedDataItem.getNodeId().getIdentifier().toString() + ":" + t.getValue().getValue().toString());
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        /**
         * 自定义订阅监听
         */
        private static class CustomSubscriptionListener implements UaSubscriptionManager.SubscriptionListener {
    
            private OpcUaClient client;
    
    
            CustomSubscriptionListener(OpcUaClient client) {
                this.client = client;
            }
    
            public void onKeepAlive(UaSubscription subscription, DateTime publishTime) {
                System.out.println("onKeepAlive");
            }
    
            public void onStatusChanged(UaSubscription subscription, StatusCode status) {
                System.out.println("onStatusChanged");
            }
    
            public void onPublishFailure(UaException exception) {
                System.out.println("onPublishFailure");
            }
    
            public void onNotificationDataLost(UaSubscription subscription) {
                System.out.println("onNotificationDataLost");
            }
    
            /**
             * 重连时 尝试恢复之前的订阅失败时 会调用此方法
             * @param uaSubscription 订阅
             * @param statusCode 状态
             */
            public void onSubscriptionTransferFailed(UaSubscription uaSubscription, StatusCode statusCode) {
                System.out.println("恢复订阅失败 需要重新订阅");
                //在回调方法中重新订阅
                handlerNode(client);
            }
        }
        /**
         * 批量订阅
         *
         * @param client
         * @throws Exception
         */
        private static void managedSubscriptionEvent(OpcUaClient client) throws Exception {
            final CountDownLatch eventLatch = new CountDownLatch(1);
    
            //添加订阅监听器,用于处理断线重连后的订阅问题
            client.getSubscriptionManager().addSubscriptionListener(new CustomSubscriptionListener(client));
    
            //处理订阅业务
            handlerNode(client);
    
            //持续监听
            eventLatch.await();
        }
    
    
    
    
    测试
    
        public static void main(String[] args) throws Exception {
            OpcUaClient client = createClient();
            client.connect().get();
    //        browseNode(client,null);
    //        readNode(client);
            writeNodeValue(client);
    //        subscribe(client);
    //      managedSubscriptionEvent(client);
        }
    }.

    代码部分可以直接复制进行测试,因为用的匿名登录,所以不需要用户名密码,kepserver设置只需要打开匿名登录就可以了

    自动重连测试

    本文代码部分转载于Java使用Milo实现OPC UA客户端_逛窑子的李靖的博客-CSDN博客_java opc ua

    展开全文
  • java-opc-test-server 功能齐全的OPC UA服务器,用于单元测试
  • Java OPC Client 开发问题

    2021-03-16 20:10:15
    现在使用java来做工控系统的几种方式:知识储备:一、OPC Server端目前常见的有以下几种协议:参考博客:...

    现在使用java来做工控系统的几种方式:

    知识储备:

    一、OPC Server端目前常见的有以下几种协议:

    参考博客:

    https://www.cnblogs.com/ioufev/articles/9697717.html

    https://www.cnblogs.com/ioufev/articles/9697890.html

    二、DOCM的配置

    可以参考https://www.cnblogs.com/ioufev/p/9365919.html

    三、OPCServer服务器属性

    java 获取OPCServer的方式有两种,

    1、jeasyopc:适用于32位操作系统

    2、Utgrad :可跨平台

    afb45f585731

    可以根据具体情况具体分析,相比较对于初次接触来说jeasyopc相对简单。

    四、试验模拟OPC

    1、实验用模拟OPCServer(50M):MatrikonOPC

    2、实际OPCServer使用(450M,中文):KEPServer V6

    Utgard:

    博客参考

    JeasyOPC:

    博客参考:

    https://blog.csdn.net/qq_33720460/article/details/78478430

    https://blog.csdn.net/wangzhi291/article/details/45029799

    https://blog.csdn.net/diyu122222/article/details/77645668

    五:本人只采用了UtgradUt开发用例

    /**

    *  OPCServer 客户端连接

    */

    public class UtgrandClient {

    private final String LOCAL_HOST="10.0.0.17";//OPCServer服务器IP

    private final String USER_NAME ="OPCServer";//DOCM配置时OPCServer的用户名

    private final String PASS_WORD ="Admin123";//密码

    private final String  CLS_ID ="7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729";//cls_id

    private final String  DOMAIN ="";

    private  int count;

    Server server =null;

    public  void utGrandReadData(){

    System.out.print("*********************************");

    System.out.print("************启动加载配置项*********");

    System.out.print("*********************************");

    final  ConnectionInformation ci =new ConnectionInformation();

    ci.setHost(LOCAL_HOST);

    ci.setUser(USER_NAME);

    ci.setPassword(PASS_WORD);

    ci.setClsid(CLS_ID);

    ci.setDomain(DOMAIN);

    if (ci.getClsid() !=null ){

    JISession  session = JISession.createSession ( ci.getDomain (),ci.getUser (),ci.getPassword () );

    session.setGlobalSocketTimeout ((int) System.currentTimeMillis());

    session.useSessionSecurity(true);

    server =new Server(ci,Executors.newSingleThreadScheduledExecutor());

    }else if (ci.getProgId () !=null){

    JISession session = JISession.createSession ( ci.getDomain (), ci.getUser (), ci.getPassword () );

    session.setGlobalSocketTimeout ((int) System.currentTimeMillis());

    session.useSessionSecurity(true);

    server =new Server(ci,Executors.newSingleThreadScheduledExecutor());

    }

    try {

    server.connect();

    Group group = server.addGroup("VC");

    Item item = group.addItem("k.k.k");

    //    Map items = group.addItems("Random.Real1",

    //          "Random.Real2", "Random.Real3", "Random.Real4");

    dumpItem(item);

    //    for (Entry temp : items.entrySet()) {

    //      dumpItem(temp.getValue());

    //    }

    }catch (Exception e) {

    e.printStackTrace();

    }

    }

    /**

    * 读取监测点

    * @param item

    * @throws JIException

    */

    private  void dumpItem(Item item)throws JIException {

    System.out.println("[" + (++count) +"],ItemName:[" + item.getId()

    +"],value:" + item.read(true).getValue());

    }

    }

    /**

    * 使用线程来控制 opc client连接,优化运行机内存。(48小时测试内存无增长)

    */

    public class ThreadUtgrad {

    private Date date;//对象锁

    private boolean flag =false;//控制唤醒或等待线程

    public ThreadUtgrad() {

    date =new Date();

    }

    public ThreadUtgrad(Date date){

    this.date = date;

    }

    public void run() {

    while(true){

    synchronized(date){

    if(flag){

    try {

    Thread.sleep(500);

    }catch (InterruptedException e) {

    e.printStackTrace();

    }

    date.notify();

    flag =false;

    }

    }

    }

    }

    public Date getDate() {

    return date;

    }

    public void setDate(Date date) {

    this.date = date;

    }

    class PrintMessageextends Thread{

    @Override

    public void run() {

    UtgrandClient utgrandClient =new UtgrandClient();

    while(true){

    synchronized(date){

    if(!flag){

    try {

    flag =true;

    date.wait();

    }catch (InterruptedException e) {

    e.printStackTrace();

    }

    }

    System.out.println("0.5秒的消息");

    utgrandClient.utGrandReadData();

    }

    }

    }

    }

    @PostConstruct

    public void startUtgrandClient() {

    Date date =new Date();

    //打印5秒消息的线程

    ThreadUtgrad threadUtgrad =new ThreadUtgrad(date);

    PrintMessage printMessage = threadUtgrad.new PrintMessage();

    printMessage.start();

    threadUtgrad.run();

    //        //不影响打印线程的7秒消息线程

    //        new Thread(new Runnable(){

    //

    //            @Override

    //            public void run() {

    //                while(true){

    //                    try {

    //                        Thread.sleep(7000);

    //                    } catch (InterruptedException e) {

    //                        e.printStackTrace();

    //                    }

    //                    System.out.println("7秒的消息");

    //                }

    //

    //            }

    //

    //        }).start();

    }

    展开全文
  • Java实现OPC通信-附件资源
  • 3) 能模拟常见的大多数应用场景和数据,如各种曲线(三角函数曲线等),各种数据类型,各种故障类型,这一条大多数情况下与第一点是冲突的,所以基本上是常备几种OPCServer 推荐的第一位:Knight.OPCServer 简单...
  • java opc

    2018-09-11 11:28:27
    使用utgard ,网址: ..., utgard 下载地址: ... 需要依赖包:j-Interop, 地址: ...依赖包slf4j:slf4j-api-1.7.7.jar log4j-1.2.17.jar ... OpcClient3 client = new OpcClient3() ; client .read (itemName) ; } }
  • Java opc ua 批量订阅与服务断开重新连接
  • java opcua模拟代码,包含简单的客户端和服务端。只是简单的模拟测试学习使用。
  • 第二步,配置dcom,如果你的kepserver和你的java客户端在一台电脑上,只需要配置这台电脑,如果不在一台电脑上,那么就需要两台电脑都配置,配置Dcom网上教程很多,我这里就不挂了 第三步,java代码编写 读取 ...
  • JAVA OPC UA 断线重连

    千次阅读 2021-03-25 09:34:14
    最近在使用 eclipse milo 的 OPC UA 连接 PLC时发现一个问题 ,当PLC 断线 又重连 时无法重新连接并订阅数据,在查询资料和研究下终于搞清楚了问题,在此记录一下。 使用的milo版本 <dependency> <...
  • java通过utgard调用OPC获取点位数据,注意要csid来获取,直接用户名和密码获取有误
  • opc ua java sdk

    2019-05-06 11:22:27
    opc ua java stack sdk免费的开发sdk工具,opc基金会产品
  • 实现UA客户端时,这是一个非常常见的问题.服务器最终负责您获取的端点的内容,并且您连接的端点(错误)配置为在端点URL中返回127.0.0.1.您需要检查从服务器获得的端点,然后根据应用程序的性质,立即用新修复的...
  • OPC UA-java源代码及示例,OPC UA之前的访问规范都是基于微软的COM/DCOM技术, 这会给新增层面的通信带来不可根除的弱点。加上传统OPC技术不够灵活、平台局限等问题的逐渐凸显, OPC基金会 (OPC Foundation) ...
  • 因为公司有需求,要调用opc服务器的数据,上传到我们自己开发的一个vr眼镜展示。所以经过几天把这个给测试好了,连接的是MatrikonOPC Server模拟服务器
  • OPC UA JAVA开发包

    2018-05-09 09:05:28
    JAVA版本的开发包,有两小时时限,可用于学习和测试.已经测试过可以使用.
  • JavaOPC UA资料

    2021-08-20 17:09:37
    JavaOPC UA资料,有客户端和服务端示例。
  • 【程序老媛出品,必属精品,亲测校正,质量...资源名:基于java开发的opc client程序源码.zip 资源类型:程序源代码 源码说明: java实现的opc client,带有丰富接口和说明文档 适合人群:新手及有一定经验的开发人员
  • java实现简单的opc ua的例子,如果刚接触opc ua想了解一下原理的可以参考一下。
  • java连接opc读取数据

    热门讨论 2014-11-05 10:26:18
    最近由于项目需要,在已有java web工程里添加读取opc的接口类。通过接口将opc数据读取到本地存于oracle数据库中,供本管理系统趋势分析用。本实例在win7、xp系统本地均已调通。压缩包里有本人写的每一步详细说明操作...
  • 如何使用java连接opc server,这里附上详细的dome和opc server源码
  • Java实现OPC通信

    千次阅读 2021-07-15 10:35:51
    Java实现OPC通信 不回复任何问题,有问题可以评论。 录屏简单说了一下文章内容,视频地址:https://www.bilibili.com/video/BV13V411f7Ch/ 1.PLC和OPC 使用的PLC:西门子的S7-300,具体型号如下图 使用...
  • java编写的OPCclient

    2022-03-28 10:26:55
    java编写的OPCclient,实现与KEPServer模拟通讯,结合博客解释清晰。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,999
精华内容 1,599
关键字:

Java OPC