日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2024/1/18 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 连接OPC服务器之 utgard 连接 KepServer 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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 為例

  • 我按照西門子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的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。