java 连接OPC服务器之 utgard 连接 KepServer

我要做一个java开发的项目, 这个在网上很少案例, 大家基本都是做web开发的, 我其实之前也是。但是现在有这个需求, 就干了。

我这里使用的是西门子的Smart200系列的PLC, 最初的版本其实是使用java代码定时去读取PLC的数据, 找到该类型的最小地址和最大地址, 批量读取, 然后缓存起来, 另一个线程定时把缓存里的数据刷新到各个用到的具体地址。项目完成后, 我拿去给用户演示, 在演示的过程中, 我连续点击一个invertbit的按钮, 点了很多下, 然后机器在我不点击后10几秒还在执行之前的命令。这个有点尴尬了。肯定是自己的代码写的不好, 在写数据这里有出了问题, 排队执行命令, 一个一个执行完为止。然后就是偶尔还会出现连接中断的问题。不过不影响使用, 就是日志里记录了而已。

后来我了解到有个叫OPC服务器的东西, 他可以和PLC等设备进行通信, 把数据缓存到OPC服务器里, 然后OPC Client对 OPC Server 的数据进行读写, OPC Server 再对连接设备进行读写。

使用这东西主要好处是不用考虑不同厂厂的基础设备的连接问题了, OPC Server 基本包含了所有知名厂商的连接驱动。我们只需要对OPC Server 进行读写就好了。 另外, 它确实可以解决 直接连接基层设备响应慢的问题, 毕竟在底层设备连接方面, 人家才是专家。我们的java 程序读写OPC Server 的数据其实是很快的, 不会出现排队执行的问题。

我在网上看了很多OPC Server 的介绍, 最后决定使用 KepServer, 讲真, 有点尴尬, 因为这个有破解版。
我最初使用的时候其实有点迷惘, 我不知道怎么用 java 实现OPC Client, 找来找去都是找到 jeasyOpc 的使用, 只能使用32位的JDK, 这个我表示不能接受, 我之前的架构引用了好些64位的jar包。还有就是一堆配置文件, 看起来好烦。又不用我们去修改, 但是就是要把配置文件拷贝过来, 真恶心。后来找到utgard的使用,这个jar包可以支持64位, 并且可以支持linux下运行,并且没有配置文件!!!唯一的缺点就是按照OPC Server 的服务器必须有密码, 不然无法访问 OPC Server。

这里以 utgard ,kep Server , 西门子的PCL 为例

  1. 我按照西门子PCL 200 系列 的数据类型, 把kepServer 分为 boolean -> V0.0, char(对应 byte) VB0, short VW0, int VD0, float VD0, 没有长整型, 没有双精度浮点数。
    基层实体

package com.kep.entity.generated;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;

import org.apache.log4j.Logger;
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.Group;
import org.openscada.opc.lib.da.Item;
import org.openscada.opc.lib.da.ItemState;
import org.openscada.opc.lib.da.Server;

import com.kep.entity.KBoolean;
import com.kep.entity.KChar;
import com.kep.entity.KFloalt;
import com.kep.entity.KInt;
import com.kep.entity.KShort;
import com.utils.KCollectionUtils;
import com.utils.KLoggerUtils;

/**

  • 多通道或多个CPU需要编程人员自己去给没个连接变量赋值(group),比较麻烦 单个CPU的话只要变量名就好了, 这里会根据 连接名+变量名,
  • 分析出组名, kepServer里的对应变量名 kepServer的命名规范, 通道.连接.组(V/VB/M/BM/MW…).具体位置,
  • 例如:conn.start10.VB.0;conn.start10.Q.0_0
  • @author root

*/
public class KEntity {

// 所有创建了的连接类实体, 全部缓存起来, 以便定时刷新数据
private static Set<KEntity> entitySet = new HashSet<KEntity>();private static Set<Group> groupSet = new HashSet<Group>();
private static Map<Group, Set<Item>> groupItmeMap = new HashMap<Group, Set<Item>>();private static Logger logger = KLoggerUtils.getLogger(KEntity.class);private static Timer initServerTimer;
// 延时写入数据, 其实主要目的是开启另外一个线程写数据, 这样就不会导致页面卡死
private static Timer wirteDataTimer;
// 读取数据的timer, 也是开启另一个线程去读取数据, 这样就不会导致页面卡住, 如果有数据有误的话
private static Timer readDataTimer;
// 每隔200毫秒读取一次数据
private static final long readDataTime = 100;private static ConnectionInformation ci;
// 默认连接名
private static String defaultConnName;private static final String ciId = "7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729";
// 例如 :conn.smart.Q.0_0, 那么conn.smart 就是连接名, conn.smart.Q就是组名, 默认自己分组, 不需要手动调整
private static Server server;// 命名规范: 例如:Q0.0, 这里会转为 通道.连接.Q.0_0, 比如:conn.smart10.Q.0_0, 同时会把它分到
// conn.start10.Q组去
private String name;
private String connName;
private Group group;
private Item item;
protected KEntityType type;private Object value;public enum KEntityType {// 目前仅支持这几种数据类型Boolean, Char, Short, Int, Float
}private static void createReadSchedule() {readDataTimer.schedule(new TimerTask() {@Overridepublic void run() {try {readData();} catch (Exception e) {logger.error(e.getMessage(), e);}createReadSchedule();}}, readDataTime);
}private static void readData() {// 刷新数据refresh();
}/*** 初始化连接, 并启动循环读取数据的timer* * @param host OPC 服务器的 IP地址* @param userName OPC 服务器的电脑系统的用户名* @param password  OPC 服务器的电脑系统的用户名对应的密码* @param connName OPC Server 的某个连接名*/
public static void initDefaultConn(String host, String userName, String password, String connName) {if (null == readDataTimer) {readDataTimer = new Timer();createReadSchedule();// 启动循环读取数据}if (null == ci) {ci = new ConnectionInformation();ci.setHost(host);ci.setUser(userName);// 计算机的登录名ci.setPassword(password);// 计算机的密码ci.setClsid(ciId);// kepServer的ciId, 这个比较特殊}defaultConnName = connName;if (null == server) {server = new Server(ci, Executors.newSingleThreadScheduledExecutor());}try {server.connect();} catch (Exception e) {logger.error(e.getMessage(), e);reInitServer();}
}/*** 重新初始化一下Server*/
private static void reInitServer() {if (null == initServerTimer) {initServerTimer = new Timer();}initServerTimer.schedule(new TimerTask() {@Overridepublic void run() {server = new Server(ci, Executors.newSingleThreadScheduledExecutor());try {server.connect();} catch (Exception e) {server = null;logger.error(e.getMessage(), e);reInitServer();// 连接失败, 再来过, 直到成功为止}}}, 2000);
}public KEntity(String name) {this.name = name;this.connName = defaultConnName;entitySet.add(this);initEntity();
}public KEntity(String name, String connName) {this.name = name;this.connName = connName;entitySet.add(this);initEntity();
}/*** 初始化连接实体*/
private void initEntity() {// 初始化实体类型if (this instanceof KBoolean) {this.type = KEntityType.Boolean;} else if (this instanceof KChar) {this.type = KEntityType.Char;} else if (this instanceof KShort) {this.type = KEntityType.Short;} else if (this instanceof KInt) {this.type = KEntityType.Int;} else if (this instanceof KFloalt) {this.type = KEntityType.Float;}if (null == server) {return;}// 解析组名String itgName = null;// boolean 类型的,截取第一个字符, 这样容错性强一些 例如 vb0.2->V0.2的效果switch (this.type) {case Boolean:itgName = name.substring(0, 1);break;case Char:itgName = name.substring(0, 1) + "B";break;case Short:itgName = name.substring(0, 1) + "W";break;case Int:itgName = name.substring(0, 1) + "D";break;case Float:itgName = name.substring(0, 1) + "D";break;}String groupName = connName + "." + itgName.toUpperCase();// 先找一下有没有初始化过这个组try {group = server.findGroup(groupName);} catch (Exception e) {logger.error("查找组失败 : " + groupName, e);}// 没有初始化过的组, 初始化它try {if (null == group) {group = server.addGroup(groupName);}} catch (Exception e) {logger.error("添加组失败 : " + groupName, e);}if (null != group) {// 把组缓存起来groupSet.add(group);} else {return;}// 获取或者初始化组对应的itemSetSet<Item> itemSet = groupItmeMap.get(group);if (null == itemSet) {itemSet = new HashSet<Item>();groupItmeMap.put(group, itemSet);}// 解析item实际对应kepServer里的变量名String addr = name.replaceAll("[a-z,A-Z]", "").replaceAll("[.]", "_");String itemName = groupName + "." + addr;try {item = group.addItem(itemName);// 收集item, 用于后面定时刷新数据时批量刷新, 如果不批量刷新,反应会很慢很慢, 我写过遍历实体刷新的,2秒才刷新完, 不能接受itemSet.add(item);} catch (Exception e) {logger.error("添加Item失败 : " + groupName + "." + addr, e);}
}/*** 刷新所有连接实体的数据*/
private static void refresh() {if (KCollectionUtils.notEmpty(groupSet)) {for (Group group : groupSet) {Set<Item> itemSet = groupItmeMap.get(group);if (KCollectionUtils.notEmpty(itemSet)) {try {// 按组进行刷新数据, 批量刷新Map<Item, ItemState> itemMap = group.read(true, itemSet.toArray(new Item[itemSet.size()]));// 把数据刷新进对应的连接实体for (KEntity entity : entitySet) {ItemState state = itemMap.get(entity.item);if (null != state) {JIVariant var = state.getValue();switch (entity.type) {case Boolean:entity.value = var.getObjectAsBoolean();break;case Char:entity.value = var.getObjectAsChar();break;case Short:entity.value = var.getObjectAsShort();break;case Int:entity.value = var.getObjectAsInt();break;case Float:entity.value = var.getObjectAsFloat();break;}}}} catch (Exception e) {logger.error(e.getMessage(), e);if (e instanceof JIException) {// 如果是连接问题, 那把移除server, 并重新来过, 不然不会自动连接的for (KEntity entity : entitySet) {entity.value = null;entity.item = null;entity.group = null;}server = null;groupSet.clear();groupItmeMap.clear();reInitServer();}}}}}
}public Object getVal() {return this.value;
}public void setVal(Object obj) {if (null == server) {return;}if (null == item) {initEntity();// 刷新group和item}if (null == wirteDataTimer) {wirteDataTimer = new Timer();}// 延时100毫秒去写入数据, 主要目的是使用另一个线程写数据, 不影响界面的刷新, 防止界面卡顿wirteDataTimer.schedule(new TimerTask() {@Overridepublic void run() {JIVariant jiv = null;switch (type) {case Boolean:jiv = new JIVariant((Boolean) obj);break;case Char:jiv = new JIVariant((Character) obj);break;case Short:jiv = new JIVariant((Short) obj);break;case Int:jiv = new JIVariant((Integer) obj);break;case Float:jiv = new JIVariant((Float) obj);break;}try {item.write(jiv);} catch (Exception e) {value = null;logger.error(e.getMessage(), e);if (e instanceof JIException) {// 如果是连接问题, 那把移除server, 并重新来过, 不然不会自动连接的group = null;item = null;server = null;groupSet.clear();groupItmeMap.clear();reInitServer();initEntity();// 刷新group和item}}}}, 100);
}

}

连接实体类的目录

kep Server 对应的boolean 变量
package com.kep.entity;

import com.kep.entity.generated.KEntity;

public class KBoolean extends KEntity {

public KBoolean(String name) {super(name);
}public KBoolean(String name, String connName) {super(name, connName);
}/*** boolean类型特殊一点, 没读到数据, 这里就返回false, 不搞null 了* @return*/
public boolean getValue() {return null == getVal() ? false : (boolean) getVal();
}public void setValue(boolean value) {setVal(value);
}/*** 置位*/
public void setBit() {setValue(true);
}/*** 复位*/
public void resetBit() {setValue(false);
}/*** 位反转*/
public void invertBit() {if (null == getVal()) {// 没有读到数值的话, 就不要搞事情了return;}setValue(!getValue());
}

}

OPC Server 对应的char 变量
package com.kep.entity;

import com.kep.entity.generated.KEntity;

/**

  • 一般我们使用PLC的单个子节基本都是使用的是byte, 但是utgard 并不提供byte类型的数据, 只提供char类型
  • 其实这里使用效果是一样的
  • @author root

*/
public class KChar extends KEntity {

public KChar(String name) {super(name);
}public KChar(String name, String connName) {super(name, connName);
}/*** char这里和boolean类型一样, 如果是空的, 就返回 0* * @return*/
public char getValue() {return null == getVal() ? 0 : (char) getVal();
}public void setValue(char value) {setVal(value);
}

}

OPC Server 对应的short 变量
package com.kep.entity;

import com.kep.entity.generated.KEntity;

public class KShort extends KEntity {

public KShort(String name, String connName) {super(name, connName);
}public KShort(String name) {super(name);
}public short getValue() {return (short) getVal();
}public void setValue(short value) {setVal(value);
}

}

OPC Server 对应的Int变量

package com.kep.entity;

import com.kep.entity.generated.KEntity;

public class KInt extends KEntity {

public KInt(String name) {super(name);
}public KInt(String name, String connName) {super(name, connName);
}public Integer getValue() {return (Integer) getVal();
}public void setValue(int value) {setVal(value);
}

}

OPC Server 对应的 floalt变量
package com.kep.entity;

import com.kep.entity.generated.KEntity;

public class KFloalt extends KEntity{

public KFloalt(String name) {super(name);
}public KFloalt(String name, String connName) {super(name, connName);
}public Float getValue() {return (Float) getValue();
}public void setValue(float value) {setVal(value);
}

}

连接实体测试
package opcTest.local;

import com.kep.entity.KBoolean;
import com.kep.entity.generated.KEntity;

public class Test2 {

public static void main(String[] args) {KEntity.initDefaultConn("192.168.160.141", "plcData", "123", "conn.smart");KBoolean Q0_7 = new KBoolean("Q0.7");while (true) {try {System.out.println("数值展示: " + Q0_7.getValue());Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();}}
}

}

Kep Server 的连接目录及变量命名规范(本程序的命名规范)

java 连接OPC服务器之 utgard 连接 KepServer相关推荐

  1. Putty远程连接linux服务器之putty 连接出错:network error: connection refus...

    Putty简介 Putty是一个免费的.Windows x86平台下的Telnet.SSH和rlogin客户端,但是功能丝毫不逊色于商业的Telnet类工具.目前最新的版本为 0.71 .较早的版本仅 ...

  2. 如何将树莓派网关连接到TTN——手把手教你如何将树莓派网关连接到服务器之第四篇

    接下来,我们用实际应用实践的例子,来告诉大家如何将树莓派网关连接到TTN服务器. 1.将树莓派网关接入互联网,并选择TTN作为Server. 我们可以按照本系列文章的手把手教你如何将树莓派网关连接到服 ...

  3. java读取OPC DA数据---Utgard

    java读取OPC DA数据-Utgard Utgard库已经过时,原作者早已删除库,建议使用OPC UA,兼容OPC DA. 下面讲解Utgard使用 C#和C++都不用配置DCOM,直接调用函数 ...

  4. 使命召唤ol显示服务器连接超时,使命召唤online无法连接大厅服务怎么办 无法连接大厅解决方法...

    就在刚才小编想登录使命召唤online玩两把,结果发现死活连不上游戏大厅,进入界面就显示与大厅失去联系,让小编很恼火.最后小编看到好心玩家分享的无法连接游戏的心得,然后去试了下,发现连接上了.所以如果 ...

  5. SQL Server 2014无法连接到服务器之解决方法

    问题如图所示: 解决方法 1.打开SQL server 配置管理器--->SQL server 网络配置--->MSSQLSERVER的协,将SQLEXPRESS协议中的Named Pip ...

  6. 使用sqlserver连接mysql服务_Sqlserver创建连接MySql的链接服务器

    第一步:在MySql服务器上安装与系统对应的 MySql-Connector-ODBC 安装过程中可能会报 缺失 msvcr100.dll的错误,这需要你根据系统到网上下载对应的这个dll文件.(当初 ...

  7. 手把手教你如何将树莓派网关链接到服务器之第二篇

    本文为系列文章--手把手教你如何将树莓派网关连接到服务器之第二篇,涉及图1所示步骤二:如何在电脑上操作,配置树莓派网关的密码.频段以及选择服务器. 将树莓派网关连接到服务器的主要流程图如图1所示: 图 ...

  8. 阿里云自动java和mysql数据库_阿里云服务器之基于Linux系统部署上线JavaWeb项目和连接MySQL数据库(从购买云服务器到发布JavaWeb项目全套详细流程)...

    阿里云服务器之基于Linux系统部署上线JavaWeb项目和连接MySQL数据库(从购买云服务器到发布JavaWeb项目全套详细流程) (仅此纪念人生第一篇学习博客) 前阵子接了一个小小的JavaWe ...

  9. java连接OPC 报错汇总

    java连接OPC 报错汇总 最近在弄utgard连接opc服务器 状态码 原因 解决方案 80070005: Unknown error (80070005) 账号没有权限 服务器在win10下 没 ...

最新文章

  1. “No module named ‘vtk.util‘;‘vtk‘ is not a package”问题完美解决
  2. ES查看segment大小
  3. MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)的理解(即c++参数初始)
  4. resharper警告 :linq replace with single call to FirstOrDefault
  5. ubantu之Git使用
  6. linux系统中 库分为静态库和,Linux系统中“动态库”和“静态库”那点事儿-【经典好文】...
  7. WSP (无线会话协议)
  8. 拓端tecdat|R语言绘制ggplot2双色XY-面积图组合交叉折线图可视化
  9. 花椒前端基于WebAssembly 的H.265播放器研发
  10. SQL Server 时间、日期函数
  11. echarts全国省市县下钻
  12. Python 实现Mac 屏幕截图
  13. [【震撼】珠海中学曝【师生课堂互殴门】]
  14. java与python比较之单引号 双引号用法
  15. firefox插件下载失败
  16. java扫雷初级代码_高分求一个运行在Eclipse环境下的java 扫雷游戏的初级代码 越小越好 越短越好 运行就好,就是初级就好了,...
  17. 从零搭建Spring Boot脚手架(2):增加通用的功能
  18. Android-节日短信送祝福(功能篇:2-短信历史记录Fragment的编写)
  19. 入院前、入产房前、分娩前物品准备
  20. 【log4j2】下载、安装、使用

热门文章

  1. 微软督促客户修复本地 Exchange 服务器
  2. 橡胶促进剂MBTS(DM)市场现状及未来发展趋势
  3. 《数据结构》陈越——习题及解析二
  4. 等价关系和偏序关系【】
  5. 近红外吸收荧光染料IR-808,IR-808 NH2,IR-808 amine,发射808nm 性质分享
  6. qq游戏我的世界计算机科技整合版,我的世界无尽贪婪整合包
  7. 支付宝接入沙箱环境遇到的问题及解决方案
  8. windows10中java调用python脚本
  9. 数论——斐波那契数列
  10. 证书体系: CSR 解析