From fb162c9bcaf5d1658d4660231cb8c691926d0490 Mon Sep 17 00:00:00 2001 From: liyongde <1419499670@qq.com> Date: Fri, 6 Mar 2026 16:12:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20opcua-opcda=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nl-iot/pom.xml | 7 + .../opcda/OpcDaProtocolDriverImpl.java | 261 +++++++++++ .../openscada/opc/dcom/common/Categories.java | 29 ++ .../openscada/opc/dcom/common/Constants.java | 34 ++ .../opc/dcom/common/EventHandler.java | 29 ++ .../openscada/opc/dcom/common/FILETIME.java | 152 +++++++ .../opc/dcom/common/KeyedResult.java | 39 ++ .../opc/dcom/common/KeyedResultSet.java | 35 ++ .../org/openscada/opc/dcom/common/Result.java | 52 +++ .../openscada/opc/dcom/common/ResultSet.java | 33 ++ .../opc/dcom/common/impl/BaseCOMObject.java | 37 ++ .../opc/dcom/common/impl/EnumGUID.java | 118 +++++ .../opc/dcom/common/impl/EnumString.java | 118 +++++ .../dcom/common/impl/EventHandlerImpl.java | 54 +++ .../opc/dcom/common/impl/Helper.java | 63 +++ .../opc/dcom/common/impl/OPCCommon.java | 88 ++++ .../org/openscada/opc/dcom/da/Constants.java | 40 ++ .../opc/dcom/da/IOPCDataCallback.java | 31 ++ .../org/openscada/opc/dcom/da/IORequest.java | 46 ++ .../opc/dcom/da/OPCBROWSEDIRECTION.java | 48 ++ .../openscada/opc/dcom/da/OPCBROWSETYPE.java | 48 ++ .../openscada/opc/dcom/da/OPCDATASOURCE.java | 45 ++ .../openscada/opc/dcom/da/OPCENUMSCOPE.java | 57 +++ .../openscada/opc/dcom/da/OPCGroupState.java | 100 +++++ .../org/openscada/opc/dcom/da/OPCITEMDEF.java | 104 +++++ .../openscada/opc/dcom/da/OPCITEMRESULT.java | 91 ++++ .../openscada/opc/dcom/da/OPCITEMSTATE.java | 99 +++++ .../opc/dcom/da/OPCNAMESPACETYPE.java | 45 ++ .../openscada/opc/dcom/da/OPCSERVERSTATE.java | 57 +++ .../opc/dcom/da/OPCSERVERSTATUS.java | 174 ++++++++ .../opc/dcom/da/PropertyDescription.java | 50 +++ .../org/openscada/opc/dcom/da/ValueData.java | 54 +++ .../openscada/opc/dcom/da/WriteRequest.java | 66 +++ .../opc/dcom/da/impl/OPCAsyncIO2.java | 120 +++++ .../openscada/opc/dcom/da/impl/OPCBrowse.java | 31 ++ .../da/impl/OPCBrowseServerAddressSpace.java | 146 +++++++ .../opc/dcom/da/impl/OPCDataCallback.java | 218 +++++++++ .../opc/dcom/da/impl/OPCGroupStateMgt.java | 182 ++++++++ .../openscada/opc/dcom/da/impl/OPCItemIO.java | 60 +++ .../opc/dcom/da/impl/OPCItemMgt.java | 184 ++++++++ .../opc/dcom/da/impl/OPCItemProperties.java | 134 ++++++ .../openscada/opc/dcom/da/impl/OPCServer.java | 165 +++++++ .../openscada/opc/dcom/da/impl/OPCSyncIO.java | 96 ++++ .../openscada/opc/dcom/list/ClassDetails.java | 56 +++ .../openscada/opc/dcom/list/Constants.java | 24 + .../opc/dcom/list/impl/OPCServerList.java | 142 ++++++ .../lib/common/AlreadyConnectedException.java | 24 + .../opc/lib/common/ConnectionInformation.java | 135 ++++++ .../opc/lib/common/NotConnectedException.java | 24 + .../org/openscada/opc/lib/da/AccessBase.java | 283 ++++++++++++ .../opc/lib/da/AccessStateListener.java | 24 + .../opc/lib/da/AddFailedException.java | 57 +++ .../openscada/opc/lib/da/Async20Access.java | 114 +++++ .../opc/lib/da/AutoReconnectController.java | 183 ++++++++ .../opc/lib/da/AutoReconnectListener.java | 22 + .../opc/lib/da/AutoReconnectState.java | 48 ++ .../openscada/opc/lib/da/DataCallback.java | 22 + .../opc/lib/da/DuplicateGroupException.java | 27 ++ .../opc/lib/da/ErrorMessageResolver.java | 68 +++ .../opcda/org/openscada/opc/lib/da/Group.java | 358 +++++++++++++++ .../opcda/org/openscada/opc/lib/da/Item.java | 71 +++ .../org/openscada/opc/lib/da/ItemState.java | 131 ++++++ .../org/openscada/opc/lib/da/Server.java | 413 ++++++++++++++++++ .../lib/da/ServerConnectionStateListener.java | 22 + .../opc/lib/da/ServerStateListener.java | 24 + .../opc/lib/da/ServerStateOperation.java | 107 +++++ .../opc/lib/da/ServerStateReader.java | 92 ++++ .../org/openscada/opc/lib/da/SyncAccess.java | 99 +++++ .../opc/lib/da/UnknownGroupException.java | 40 ++ .../openscada/opc/lib/da/WriteRequest.java | 40 ++ .../openscada/opc/lib/da/browser/Access.java | 33 ++ .../opc/lib/da/browser/BaseBrowser.java | 116 +++++ .../openscada/opc/lib/da/browser/Branch.java | 112 +++++ .../opc/lib/da/browser/FlatBrowser.java | 70 +++ .../openscada/opc/lib/da/browser/Leaf.java | 58 +++ .../opc/lib/da/browser/TreeBrowser.java | 228 ++++++++++ .../openscada/opc/lib/list/Categories.java | 40 ++ .../org/openscada/opc/lib/list/Category.java | 62 +++ .../openscada/opc/lib/list/ServerList.java | 170 +++++++ .../opcua/OpcUaProtocolDriverImpl.java | 225 ++++++++++ 80 files changed, 7404 insertions(+) create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/OpcDaProtocolDriverImpl.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/Categories.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/Constants.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/EventHandler.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/FILETIME.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/KeyedResult.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/KeyedResultSet.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/Result.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/ResultSet.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/BaseCOMObject.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/EnumGUID.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/EnumString.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/EventHandlerImpl.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/Helper.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/OPCCommon.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/Constants.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/IOPCDataCallback.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/IORequest.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCBROWSEDIRECTION.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCBROWSETYPE.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCDATASOURCE.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCENUMSCOPE.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCGroupState.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCITEMDEF.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCITEMRESULT.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCITEMSTATE.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCNAMESPACETYPE.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCSERVERSTATE.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCSERVERSTATUS.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/PropertyDescription.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/ValueData.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/WriteRequest.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCAsyncIO2.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCBrowse.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCBrowseServerAddressSpace.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCDataCallback.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCGroupStateMgt.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCItemIO.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCItemMgt.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCItemProperties.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCServer.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCSyncIO.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/list/ClassDetails.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/list/Constants.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/list/impl/OPCServerList.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/common/AlreadyConnectedException.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/common/ConnectionInformation.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/common/NotConnectedException.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AccessBase.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AccessStateListener.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AddFailedException.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Async20Access.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AutoReconnectController.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AutoReconnectListener.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AutoReconnectState.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/DataCallback.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/DuplicateGroupException.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ErrorMessageResolver.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Group.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Item.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ItemState.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Server.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerConnectionStateListener.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerStateListener.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerStateOperation.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerStateReader.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/SyncAccess.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/UnknownGroupException.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/WriteRequest.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/Access.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/BaseBrowser.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/Branch.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/FlatBrowser.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/Leaf.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/TreeBrowser.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/list/Categories.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/list/Category.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/list/ServerList.java create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcua/OpcUaProtocolDriverImpl.java diff --git a/nl-iot/pom.xml b/nl-iot/pom.xml index 5c86aa9..1d5e76d 100644 --- a/nl-iot/pom.xml +++ b/nl-iot/pom.xml @@ -30,6 +30,13 @@ sdk-client 0.6.16 + + + + org.jinterop + j-interop + 2.0.4 + \ No newline at end of file diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/OpcDaProtocolDriverImpl.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/OpcDaProtocolDriverImpl.java new file mode 100644 index 0000000..816129f --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/OpcDaProtocolDriverImpl.java @@ -0,0 +1,261 @@ +package org.nl.iot.core.driver.protocol.opcda; + +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.JIVariant; +import org.nl.common.exception.CommonException; +import org.nl.iot.core.driver.bo.AttributeBO; +import org.nl.iot.core.driver.entity.RValue; +import org.nl.iot.core.driver.entity.WValue; +import org.nl.iot.core.driver.enums.PointTypeFlagEnum; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common.AlreadyConnectedException; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common.ConnectionInformation; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common.NotConnectedException; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da.*; +import org.nl.iot.core.driver.service.DriverCustomService; +import org.nl.iot.modular.iot.entity.IotConfig; +import org.nl.iot.modular.iot.entity.IotConnect; +import org.springframework.stereotype.Service; + +import java.net.UnknownHostException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; + +@Slf4j +@Service +public class OpcDaProtocolDriverImpl implements DriverCustomService { + /** + * Opc Da Server Map + */ + private Map connectMap; + + @PostConstruct + @Override + public void initial() { + /* + * 驱动初始化逻辑 + * + * 提示: 此处逻辑仅供参考, 请务必结合实际应用场景进行修改。 + * 驱动启动时会自动执行该方法, 您可以在此处执行特定的初始化操作。 + * + */ + connectMap = new ConcurrentHashMap<>(16); + } + + @Override + public void schedule() { + + } + + @Override + public RValue read(Map driverConfig, Map pointConfig, IotConnect connect, IotConfig config) { + return new RValue(config, connect, readValue(getConnector(connect.getId().toString(), driverConfig), pointConfig)); + } + + @Override + public Boolean write(Map driverConfig, Map pointConfig, IotConnect device, IotConfig point, WValue wValue) { + Server server = getConnector(device.getId().toString(), driverConfig); + return writeValue(server, pointConfig, wValue); + } + + /** + * 获取 OPC DA 服务器连接 + *

+ * 根据设备ID和驱动配置获取对应的 OPC DA 服务器连接。如果连接不存在, 则创建新的连接并缓存。 + * + * @param deviceId 设备ID, 用于标识设备对应的 OPC DA 服务器连接 + * @param driverConfig 驱动配置, 包含 OPC DA 服务器的连接信息(如主机地址, CLSID, 用户名, 密码等) + * @return Server 返回与设备ID对应的 OPC DA 服务器连接 + * @throws ConnectorException 如果连接 OPC DA 服务器时发生异常, 则抛出此异常 + */ + private Server getConnector(String deviceId, Map driverConfig) { + log.debug("Opc Da Server Connection Info {}", driverConfig); + Server server = connectMap.get(deviceId); + if (Objects.isNull(server)) { + String host = driverConfig.get("host").getValueByClass(String.class); + String clsId = driverConfig.get("clsId").getValueByClass(String.class); + String user = driverConfig.get("username").getValueByClass(String.class); + String password = driverConfig.get("password").getValueByClass(String.class); + ConnectionInformation connectionInformation = new ConnectionInformation(host, clsId, user, password); + server = new Server(connectionInformation, Executors.newSingleThreadScheduledExecutor()); + try { + server.connect(); + connectMap.put(deviceId, server); + } catch (AlreadyConnectedException | UnknownHostException | JIException e) { + connectMap.entrySet().removeIf(next -> next.getKey().equals(deviceId)); + log.error("Connect opc da server error: {}", e.getMessage(), e); + throw new CommonException(e.getMessage()); + } + } + return server; + } + + /** + * 从 OPC DA 服务器读取位号值 + *

+ * 该方法通过给定的 OPC DA 服务器和位号配置, 获取对应的 Item 对象, 并读取其值。 + * 如果在读取过程中发生异常, 将断开服务器连接并抛出 {@link ReadPointException}。 + * + * @param server 已连接的 OPC DA 服务器实例 + * @param pointConfig 位号配置, 包含组名和标签名等信息 + * @return String 返回读取到的位号值 + * @throws ReadPointException 如果读取位号值时发生异常, 则抛出此异常 + */ + private String readValue(Server server, Map pointConfig) { + try { + Item item = getItem(server, pointConfig); + return readItem(item); + } catch (NotConnectedException | JIException | AddFailedException | DuplicateGroupException | + UnknownHostException e) { + server.dispose(); + log.error("Read opc da value error: {}", e.getMessage(), e); + throw new CommonException(e.getMessage()); + } + } + + /** + * 获取 OPC DA 服务器中的 Item 对象 + *

+ * 根据位号配置中的组名和标签名, 从指定的 OPC DA 服务器中获取对应的 Item 对象。 + * 如果组不存在, 则创建新的组;如果组已存在, 则直接使用该组。 + * + * @param server 已连接的 OPC DA 服务器实例 + * @param pointConfig 位号配置, 包含组名和标签名等信息 + * @return Item 返回与位号配置对应的 Item 对象 + * @throws NotConnectedException 如果 OPC DA 服务器未连接, 则抛出此异常 + * @throws JIException 如果与 OPC DA 服务器通信时发生错误, 则抛出此异常 + * @throws UnknownHostException 如果无法解析 OPC DA 服务器的主机地址, 则抛出此异常 + * @throws DuplicateGroupException 如果尝试添加已存在的组, 则抛出此异常 + * @throws AddFailedException 如果添加组或 Item 失败, 则抛出此异常 + */ + public Item getItem(Server server, Map pointConfig) throws NotConnectedException, JIException, UnknownHostException, DuplicateGroupException, AddFailedException { + Group group; + String groupName = pointConfig.get("group").getValueByClass(String.class); + try { + group = server.findGroup(groupName); + } catch (UnknownGroupException e) { + group = server.addGroup(groupName); + } + return group.addItem(pointConfig.get("tag").getValueByClass(String.class)); + } + + /** + * 读取 OPC DA 位号值 + *

+ * 该方法通过给定的 OPC DA Item 对象, 读取其值并根据数据类型进行转换。 + * 支持的数据类型包括:短整型 (VT_I2), 整型 (VT_I4), 长整型 (VT_I8), 浮点型 (VT_R4), 双精度浮点型 (VT_R8), 布尔型 (VT_BOOL), 字符串型 (VT_BSTR)。 + * 如果数据类型不在上述范围内, 则返回对象的字符串表示。 + * + * @param item OPC DA Item 对象, 包含要读取的位号值 + * @return String 返回读取到的位号值的字符串表示 + * @throws JIException 如果与 OPC DA 服务器通信时发生错误, 则抛出此异常 + */ + public String readItem(Item item) throws JIException { + JIVariant jiVariant = item.read(false).getValue(); + switch (jiVariant.getType()) { + case JIVariant.VT_I2: + short shortValue = jiVariant.getObjectAsShort(); + return String.valueOf(shortValue); + case JIVariant.VT_I4: + int intValue = jiVariant.getObjectAsInt(); + return String.valueOf(intValue); + case JIVariant.VT_I8: + long longValue = jiVariant.getObjectAsLong(); + return String.valueOf(longValue); + case JIVariant.VT_R4: + float floatValue = jiVariant.getObjectAsFloat(); + return String.valueOf(floatValue); + case JIVariant.VT_R8: + double doubleValue = jiVariant.getObjectAsDouble(); + return String.valueOf(doubleValue); + case JIVariant.VT_BOOL: + boolean boolValue = jiVariant.getObjectAsBoolean(); + return String.valueOf(boolValue); + case JIVariant.VT_BSTR: + return jiVariant.getObjectAsString2(); + default: + return jiVariant.getObject().toString(); + } + } + + /** + * 向 OPC DA 服务器写入位号值 + *

+ * 该方法通过给定的 OPC DA 服务器, 位号配置和写入值, 获取对应的 Item 对象, 并将值写入该 Item。 + * 如果在写入过程中发生异常, 将断开服务器连接并抛出 {@link WritePointException}。 + * + * @param server 已连接的 OPC DA 服务器实例 + * @param pointConfig 位号配置, 包含组名和标签名等信息 + * @param wValue 写入值, 包含要写入的数据类型和值 + * @return boolean 返回写入操作是否成功 + * @throws WritePointException 如果写入位号值时发生异常, 则抛出此异常 + */ + private boolean writeValue(Server server, Map pointConfig, WValue wValue) { + try { + Item item = getItem(server, pointConfig); + return writeItem(item, wValue); + } catch (NotConnectedException | AddFailedException | DuplicateGroupException | UnknownHostException | + JIException e) { + server.dispose(); + log.error("Write opc da value error: {}", e.getMessage(), e); + throw new CommonException(e.getMessage()); + } + } + + /** + * 向 OPC DA Item 写入值 + *

+ * 该方法根据写入值的数据类型, 将值转换为相应的 JIVariant 对象, 并写入到指定的 OPC DA Item 中。 + * 支持的数据类型包括:短整型 (SHORT), 整型 (INT), 长整型 (LONG), 浮点型 (FLOAT), 双精度浮点型 (DOUBLE), 布尔型 (BOOLEAN), 字符串型 (STRING)。 + * 如果数据类型不支持, 将抛出 {@link UnSupportException} 异常。 + * + * @param item OPC DA Item 对象, 表示要写入的目标位号 + * @param wValue 写入值对象, 包含要写入的数据类型和值 + * @return boolean 返回写入操作是否成功, 成功返回 true, 失败返回 false + * @throws JIException 如果与 OPC DA 服务器通信时发生错误, 则抛出此异常 + * @throws UnSupportException 如果写入值的数据类型不支持, 则抛出此异常 + */ + private boolean writeItem(Item item, WValue wValue) throws JIException { + PointTypeFlagEnum valueType = PointTypeFlagEnum.ofCode(wValue.getType()); + if (Objects.isNull(valueType)) { + throw new CommonException("Unsupported type of " + wValue.getType()); + } + + int writeResult = 0; + switch (valueType) { + case SHORT: + short shortValue = wValue.getValueByClass(Short.class); + writeResult = item.write(new JIVariant(shortValue, false)); + break; + case INT: + int intValue = wValue.getValueByClass(Integer.class); + writeResult = item.write(new JIVariant(intValue, false)); + break; + case LONG: + long longValue = wValue.getValueByClass(Long.class); + writeResult = item.write(new JIVariant(longValue, false)); + break; + case FLOAT: + float floatValue = wValue.getValueByClass(Float.class); + writeResult = item.write(new JIVariant(floatValue, false)); + break; + case DOUBLE: + double doubleValue = wValue.getValueByClass(Double.class); + writeResult = item.write(new JIVariant(doubleValue, false)); + break; + case BOOLEAN: + boolean booleanValue = wValue.getValueByClass(Boolean.class); + writeResult = item.write(new JIVariant(booleanValue, false)); + break; + case STRING: + writeResult = item.write(new JIVariant(wValue.getValue(), false)); + break; + default: + break; + } + return writeResult > 0; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/Categories.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/Categories.java new file mode 100644 index 0000000..b4f6d98 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/Categories.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common; + +public interface Categories { + public static final String OPCDAServer10 = "63D5F430-CFE4-11d1-B2C8-0060083BA1FB"; + + public static final String OPCDAServer20 = "63D5F432-CFE4-11d1-B2C8-0060083BA1FB"; + + public static final String OPCDAServer30 = "CC603642-66D7-48f1-B69A-B625E73652D7"; + + public static final String XMLDAServer10 = "3098EDA4-A006-48b2-A27F-247453959408"; + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/Constants.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/Constants.java new file mode 100644 index 0000000..e25a0f6 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/Constants.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common; + +public interface Constants { + public static final String IConnectionPointContainer_IID = "B196B284-BAB4-101A-B69C-00AA00341D07"; + + public static final String IConnectionPoint_IID = "B196B286-BAB4-101A-B69C-00AA00341D07"; + + public static final String IOPCCommon_IID = "F31DFDE2-07B6-11D2-B2D8-0060083BA1FB"; + + public static final String IEnumString_IID = "00000101-0000-0000-C000-000000000046"; + + public static final String IEnumGUID_IID = "0002E000-0000-0000-C000-000000000046"; + + public static final int S_OK = 0; + + public static final int S_FALSE = 1; +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/EventHandler.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/EventHandler.java new file mode 100644 index 0000000..4c62295 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/EventHandler.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.IJIComObject; + +public interface EventHandler { + public String getIdentifier(); + + public IJIComObject getObject(); + + public void detach() throws JIException; +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/FILETIME.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/FILETIME.java new file mode 100644 index 0000000..9ddeda2 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/FILETIME.java @@ -0,0 +1,152 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.JIStruct; + +import java.math.BigDecimal; +import java.util.Calendar; + +public class FILETIME { + private int high = 0; + + private int low = 0; + + public FILETIME() { + } + + public FILETIME(final FILETIME arg0) { + this.high = arg0.high; + this.low = arg0.low; + } + + public FILETIME(final int high, final int low) { + this.high = high; + this.low = low; + } + + public static JIStruct getStruct() throws JIException { + final JIStruct struct = new JIStruct(); + + struct.addMember(Integer.class); + struct.addMember(Integer.class); + + return struct; + } + + public static FILETIME fromStruct(final JIStruct struct) { + final FILETIME ft = new FILETIME(); + + ft.setLow((Integer) struct.getMember(0)); + ft.setHigh((Integer) struct.getMember(1)); + + return ft; + } + + public int getHigh() { + return this.high; + } + + public void setHigh(final int high) { + this.high = high; + } + + public int getLow() { + return this.low; + } + + public void setLow(final int low) { + this.low = low; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + this.high; + result = PRIME * result + this.low; + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final FILETIME other = (FILETIME) obj; + if (this.high != other.high) { + return false; + } + if (this.low != other.low) { + return false; + } + return true; + } + + public Calendar asCalendar() { + final Calendar c = Calendar.getInstance(); + + /* + * The following "strange" stuff is needed since we miss a ulong type + */ + long i = 0xFFFFFFFFL & this.high; + i = i << 32; + long j = 0xFFFFFFFFFFFFFFFFL & i; + + i = 0xFFFFFFFFL & this.low; + j += i; + j /= 10000L; + j -= 11644473600000L; + + c.setTimeInMillis(j); + + return c; + } + + public Calendar asBigDecimalCalendar() { + final Calendar c = Calendar.getInstance(); + + /* + * The following "strange" stuff is needed since we miss a ulong type + */ + long i = 0xFFFFFFFFL & this.high; + i = i << 32; + BigDecimal d1 = new BigDecimal(0xFFFFFFFFFFFFFFFFL & i); + + i = 0xFFFFFFFFL & this.low; + d1 = d1.add(new BigDecimal(i)); + d1 = d1.divide(new BigDecimal(10000L)); + d1 = d1.subtract(new BigDecimal(11644473600000L)); + + c.setTimeInMillis(d1.longValue()); + + return c; + } + + @Override + public String toString() { + return String.format("%s/%s", this.high, this.low); + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/KeyedResult.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/KeyedResult.java new file mode 100644 index 0000000..c671db8 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/KeyedResult.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common; + +public class KeyedResult extends Result { + private K key; + + public KeyedResult() { + super(); + } + + public KeyedResult(final K key, final V value, final int errorCode) { + super(value, errorCode); + this.key = key; + } + + public K getKey() { + return this.key; + } + + public void setKey(final K key) { + this.key = key; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/KeyedResultSet.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/KeyedResultSet.java new file mode 100644 index 0000000..c31f308 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/KeyedResultSet.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common; + +import java.io.Serial; +import java.util.ArrayList; + +public class KeyedResultSet extends ArrayList> { + + @Serial + private static final long serialVersionUID = 1L; + + public KeyedResultSet() { + super(); + } + + public KeyedResultSet(final int size) { + super(size); // me + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/Result.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/Result.java new file mode 100644 index 0000000..be405ff --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/Result.java @@ -0,0 +1,52 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common; + +public class Result { + private T value; + + private int errorCode; + + public Result() { + } + + public Result(final T value, final int errorCode) { + this.value = value; + this.errorCode = errorCode; + } + + public int getErrorCode() { + return this.errorCode; + } + + public void setErrorCode(final int errorCode) { + this.errorCode = errorCode; + } + + public T getValue() { + return this.value; + } + + public void setValue(final T value) { + this.value = value; + } + + public boolean isFailed() { + return this.errorCode != 0; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/ResultSet.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/ResultSet.java new file mode 100644 index 0000000..6c0b9db --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/ResultSet.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common; + +import java.util.ArrayList; + +public class ResultSet extends ArrayList> { + + private static final long serialVersionUID = 1L; + + public ResultSet() { + super(); + } + + public ResultSet(final int size) { + super(size); // me + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/BaseCOMObject.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/BaseCOMObject.java new file mode 100644 index 0000000..d7dfaa9 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/BaseCOMObject.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl; + +import org.jinterop.dcom.core.IJIComObject; + +public class BaseCOMObject { + private IJIComObject comObject = null; + + /** + * Create a new base COM object + * + * @param comObject The COM object to wrap but be addRef'ed + */ + public BaseCOMObject(final IJIComObject comObject) { + this.comObject = comObject; + } + + protected synchronized IJIComObject getCOMObject() { + return this.comObject; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/EnumGUID.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/EnumGUID.java new file mode 100644 index 0000000..f62800d --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/EnumGUID.java @@ -0,0 +1,118 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.IJIComObject; +import org.jinterop.dcom.core.JIArray; +import org.jinterop.dcom.core.JICallBuilder; +import org.jinterop.dcom.core.JIFlags; +import rpc.core.UUID; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class EnumGUID extends BaseCOMObject { + public static final int DEFAULT_BATCH_SIZE = Integer.getInteger("openscada.dcom.enum-batch-size", 10); + + public EnumGUID(final IJIComObject enumStringObject) throws IllegalArgumentException, UnknownHostException, JIException { + super(enumStringObject.queryInterface(org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants.IEnumGUID_IID)); + } + + public int next(final List list, final int num) throws JIException { + if (num <= 0) { + return 0; + } + + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(0); + + callObject.addInParamAsInt(num, JIFlags.FLAG_NULL); + callObject.addInParamAsInt(num, JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIArray(UUID.class, null, 1, true, true), JIFlags.FLAG_NULL); + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + + Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject); + + UUID[] resultData = (UUID[]) ((JIArray) result[0]).getArrayInstance(); + Integer cnt = (Integer) result[1]; + + for (int i = 0; i < cnt; i++) { + list.add(resultData[i]); + } + return cnt; + } + + public Collection next(final int num) throws JIException { + List list = new ArrayList(num); + next(list, num); + return list; + } + + public void skip(final int num) throws JIException { + if (num <= 0) { + return; + } + + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(1); + + callObject.addInParamAsInt(num, JIFlags.FLAG_NULL); + + getCOMObject().call(callObject); + } + + public void reset() throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(2); + + getCOMObject().call(callObject); + } + + public EnumGUID cloneObject() throws JIException, IllegalArgumentException, UnknownHostException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(3); + + callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL); + + Object[] result = getCOMObject().call(callObject); + + IJIComObject object = (IJIComObject) result[0]; + + return new EnumGUID(object); + } + + public Collection asCollection(final int batchSize) throws JIException { + reset(); + + List data = new ArrayList(); + int i = 0; + do { + i = next(data, batchSize); + } while (i == batchSize); + + return data; + } + + public Collection asCollection() throws JIException { + return asCollection(DEFAULT_BATCH_SIZE); + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/EnumString.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/EnumString.java new file mode 100644 index 0000000..26c4f99 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/EnumString.java @@ -0,0 +1,118 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class EnumString extends BaseCOMObject { + public static final int DEFAULT_BATCH_SIZE = Integer.getInteger("openscada.dcom.enum-batch-size", 10); + + public EnumString(final IJIComObject enumStringObject) throws IllegalArgumentException, UnknownHostException, JIException { + super(enumStringObject.queryInterface(Constants.IEnumString_IID)); + } + + public int next(final List list, final int num) throws JIException { + if (num <= 0) { + return 0; + } + + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(0); + + callObject.addInParamAsInt(num, JIFlags.FLAG_NULL); + //callObject.addInParamAsInt ( num, JIFlags.FLAG_NULL ); + //callObject.addOutParamAsObject ( new JIArray ( new JIPointer ( new JIString ( + // JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR ) ), null, 1, true, true ), JIFlags.FLAG_NULL ); + callObject.addOutParamAsObject(new JIArray(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR), null, 1, true, true), JIFlags.FLAG_NULL); + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + + Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject); + + //JIPointer[] resultData = (JIPointer[]) ( (JIArray) ( result[0] ) ).getArrayInstance (); + JIString[] resultData = (JIString[]) ((JIArray) result[0]).getArrayInstance(); + Integer cnt = (Integer) result[1]; + + for (int i = 0; i < cnt; i++) { + //list.add ( ( (JIString)resultData[i].getReferent () ).getString () ); + list.add(resultData[i].getString()); + } + return cnt; + } + + public Collection next(final int num) throws JIException { + List list = new ArrayList(num); + next(list, num); + return list; + } + + public void skip(final int num) throws JIException { + if (num <= 0) { + return; + } + + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(1); + + callObject.addInParamAsInt(num, JIFlags.FLAG_NULL); + + getCOMObject().call(callObject); + } + + public void reset() throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(2); + + getCOMObject().call(callObject); + } + + public EnumString cloneObject() throws JIException, IllegalArgumentException, UnknownHostException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(3); + + callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL); + + Object[] result = getCOMObject().call(callObject); + + IJIComObject object = (IJIComObject) result[0]; + return new EnumString(object); + } + + public Collection asCollection(final int batchSize) throws JIException { + reset(); + + List data = new ArrayList(); + int i = 0; + do { + i = next(data, batchSize); + } while (i == batchSize); + + return data; + } + + public Collection asCollection() throws JIException { + return asCollection(DEFAULT_BATCH_SIZE); + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/EventHandlerImpl.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/EventHandlerImpl.java new file mode 100644 index 0000000..6772ddb --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/EventHandlerImpl.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.IJIComObject; +import org.jinterop.dcom.core.JIFrameworkHelper; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.EventHandler; + +public class EventHandlerImpl implements EventHandler { + private String identifier = null; + + private IJIComObject object = null; + + public String getIdentifier() { + return this.identifier; + } + + public synchronized IJIComObject getObject() { + return this.object; + } + + public synchronized void setInfo(final IJIComObject object, final String identifier) { + this.object = object; + this.identifier = identifier; + } + + public synchronized void detach() throws JIException { + if (this.object != null && this.identifier != null) { + try { + JIFrameworkHelper.detachEventHandler(this.object, this.identifier); + } finally { + this.object = null; + this.identifier = null; + } + } + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/Helper.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/Helper.java new file mode 100644 index 0000000..0065e1a --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/Helper.java @@ -0,0 +1,63 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.IJIComObject; +import org.jinterop.dcom.core.JICallBuilder; +import org.jinterop.dcom.core.JIFlags; +import org.jinterop.dcom.core.JIVariant; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants; + +public class Helper { + /** + * Make the COM call but do not treat S_FALSE as error condition for the whole call + * + * @param object the object to make to call on + * @param callObject the call object + * @return the result of the call + * @throws JIException JIException + */ + public static Object[] callRespectSFALSE(final IJIComObject object, final JICallBuilder callObject) throws JIException { + try { + return object.call(callObject); + } catch (JIException e) { + if (e.getErrorCode() != Constants.S_FALSE) { + throw e; + } + return callObject.getResultsInCaseOfException(); + } + } + + /** + * Perform some fixes on the variant when writing it to OPC items. This method + * only changes control information on the variant and not the value itself! + * + * @param value the value to fix + * @return the fixed value + * @throws JIException JIException + */ + public static JIVariant fixVariant(final JIVariant value) throws JIException { + if (value.isArray()) { + if (value.getObjectAsArray().getArrayInstance() instanceof Boolean[]) { + value.setFlag(JIFlags.FLAG_REPRESENTATION_VARIANT_BOOL); + } + } + return value; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/OPCCommon.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/OPCCommon.java new file mode 100644 index 0000000..6e17116 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/common/impl/OPCCommon.java @@ -0,0 +1,88 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants; + +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collection; + +public class OPCCommon extends BaseCOMObject { + public OPCCommon(final IJIComObject opcObject) throws IllegalArgumentException, UnknownHostException, JIException { + super(opcObject.queryInterface(Constants.IOPCCommon_IID)); + } + + public int getLocaleID() throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(1); + + callObject.addOutParamAsObject(Integer.class, JIFlags.FLAG_NULL); + + Object[] result = getCOMObject().call(callObject); + return (Integer) result[0]; + } + + public void setLocaleID(final int localeID) throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(0); + + callObject.addInParamAsInt(localeID, JIFlags.FLAG_NULL); + + getCOMObject().call(callObject); + } + + public String getErrorString(final int errorCode, final int localeID) throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(3); + + callObject.addInParamAsInt(errorCode, JIFlags.FLAG_NULL); + callObject.addInParamAsInt(localeID, JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)), JIFlags.FLAG_NULL); + + Object[] result = getCOMObject().call(callObject); + return ((JIString) ((JIPointer) result[0]).getReferent()).getString(); + } + + public void setClientName(final String clientName) throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(4); + + callObject.addInParamAsString(clientName, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + + getCOMObject().call(callObject); + } + + public Collection queryAvailableLocaleIDs() throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(2); + + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + + Object[] result = getCOMObject().call(callObject); + + JIArray resultArray = (JIArray) ((JIPointer) result[1]).getReferent(); + Integer[] intArray = (Integer[]) resultArray.getArrayInstance(); + + return Arrays.asList(intArray); + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/Constants.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/Constants.java new file mode 100644 index 0000000..ecf5be8 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/Constants.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +public interface Constants extends org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants { + public static final String IOPCServer_IID = "39C13A4D-011E-11D0-9675-0020AFD8ADB3"; + + public static final String IOPCGroupStateMgt_IID = "39C13A50-011E-11D0-9675-0020AFD8ADB3"; + + public static final String IOPCBrowse_IID = "39227004-A18F-4B57-8B0A-5235670F4468"; + + public static final String IOPCBrowseServerAddressSpace_IID = "39C13A4F-011E-11D0-9675-0020AFD8ADB3"; + + public static final String IOPCItemMgt_IID = "39C13A54-011E-11D0-9675-0020AFD8ADB3"; + + public static final String IOPCItemProperties_IID = "39C13A72-011E-11D0-9675-0020AFD8ADB3"; + + public static final String IOPCItemIO_IID = "85C0B427-2893-4CBC-BD78-E5FC5146F08F"; + + public static final String IOPCDataCallback_IID = "39C13A70-011E-11D0-9675-0020AFD8ADB3"; + + public static final String IOPCAsyncIO2_IID = "39C13A71-011E-11D0-9675-0020AFD8ADB3"; + + public static final String IOPCSyncIO_IID = "39C13A52-011E-11D0-9675-0020AFD8ADB3"; +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/IOPCDataCallback.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/IOPCDataCallback.java new file mode 100644 index 0000000..d03305b --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/IOPCDataCallback.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.KeyedResultSet; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.ResultSet; + +public interface IOPCDataCallback { + public void dataChange(int transactionId, int serverGroupHandle, int masterQuality, int masterErrorCode, KeyedResultSet result); + + public void readComplete(int transactionId, int serverGroupHandle, int masterQuality, int masterErrorCode, KeyedResultSet result); + + public void writeComplete(int transactionId, int serverGroupHandle, int masterErrorCode, ResultSet result); + + public void cancelComplete(int transactionId, int serverGroupHandle); +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/IORequest.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/IORequest.java new file mode 100644 index 0000000..9768845 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/IORequest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +public class IORequest { + private String itemID; + + private int maxAge; + + public IORequest(final String itemID, final int maxAge) { + this.itemID = itemID; + this.maxAge = maxAge; + } + + public String getItemID() { + return this.itemID; + } + + public void setItemID(final String itemID) { + this.itemID = itemID; + } + + public int getMaxAge() { + return this.maxAge; + } + + public void setMaxAge(final int maxAge) { + this.maxAge = maxAge; + } + +} \ No newline at end of file diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCBROWSEDIRECTION.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCBROWSEDIRECTION.java new file mode 100644 index 0000000..0b11db5 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCBROWSEDIRECTION.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +public enum OPCBROWSEDIRECTION { + OPC_BROWSE_UP(1), + OPC_BROWSE_DOWN(2), + OPC_BROWSE_TO(3), + OPC_BROWSE_UNKNOWN(0); + + private int _id; + + private OPCBROWSEDIRECTION(final int id) { + this._id = id; + } + + public static OPCBROWSEDIRECTION fromID(final int id) { + switch (id) { + case 1: + return OPC_BROWSE_UP; + case 2: + return OPC_BROWSE_DOWN; + case 3: + return OPC_BROWSE_TO; + default: + return OPC_BROWSE_UNKNOWN; + } + } + + public int id() { + return this._id; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCBROWSETYPE.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCBROWSETYPE.java new file mode 100644 index 0000000..18c459e --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCBROWSETYPE.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +public enum OPCBROWSETYPE { + OPC_BRANCH(1), + OPC_LEAF(2), + OPC_FLAT(3), + OPC_UNKNOWN(0); + + private int _id; + + private OPCBROWSETYPE(final int id) { + this._id = id; + } + + public static OPCBROWSETYPE fromID(final int id) { + switch (id) { + case 1: + return OPC_BRANCH; + case 2: + return OPC_LEAF; + case 3: + return OPC_FLAT; + default: + return OPC_UNKNOWN; + } + } + + public int id() { + return this._id; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCDATASOURCE.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCDATASOURCE.java new file mode 100644 index 0000000..2b983ab --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCDATASOURCE.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +public enum OPCDATASOURCE { + OPC_DS_CACHE(1), + OPC_DS_DEVICE(2), + OPC_DS_UNKNOWN(0); + + private int _id; + + private OPCDATASOURCE(final int id) { + this._id = id; + } + + public static OPCDATASOURCE fromID(final int id) { + switch (id) { + case 1: + return OPC_DS_CACHE; + case 2: + return OPC_DS_DEVICE; + default: + return OPC_DS_UNKNOWN; + } + } + + public int id() { + return this._id; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCENUMSCOPE.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCENUMSCOPE.java new file mode 100644 index 0000000..72349c4 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCENUMSCOPE.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +public enum OPCENUMSCOPE { + OPC_ENUM_PRIVATE_CONNECTIONS(1), + OPC_ENUM_PUBLIC_CONNECTIONS(2), + OPC_ENUM_ALL_CONNECTIONS(3), + OPC_ENUM_PRIVATE(4), + OPC_ENUM_PUBLIC(5), + OPC_ENUM_ALL(6), + OPC_ENUM_UNKNOWN(0); + + private int _id; + + private OPCENUMSCOPE(final int id) { + this._id = id; + } + + public static OPCENUMSCOPE fromID(final int id) { + switch (id) { + case 1: + return OPC_ENUM_PRIVATE_CONNECTIONS; + case 2: + return OPC_ENUM_PUBLIC_CONNECTIONS; + case 3: + return OPC_ENUM_ALL_CONNECTIONS; + case 4: + return OPC_ENUM_PRIVATE; + case 5: + return OPC_ENUM_PUBLIC; + case 6: + return OPC_ENUM_ALL; + default: + return OPC_ENUM_UNKNOWN; + } + } + + public int id() { + return this._id; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCGroupState.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCGroupState.java new file mode 100644 index 0000000..597e029 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCGroupState.java @@ -0,0 +1,100 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +public class OPCGroupState { + private int _updateRate = 1000; + + private boolean _active = true; + + private String _name = ""; + + private int _timeBias = 0; + + private float _percentDeadband = 0.0f; + + private int _localeID = 0; + + private int _clientHandle = 0; + + private int _serverHandle = 0; + + public boolean isActive() { + return this._active; + } + + public void setActive(final boolean active) { + this._active = active; + } + + public int getClientHandle() { + return this._clientHandle; + } + + public void setClientHandle(final int clientHandle) { + this._clientHandle = clientHandle; + } + + public int getLocaleID() { + return this._localeID; + } + + public void setLocaleID(final int localeID) { + this._localeID = localeID; + } + + public String getName() { + return this._name; + } + + public void setName(final String name) { + this._name = name; + } + + public float getPercentDeadband() { + return this._percentDeadband; + } + + public void setPercentDeadband(final float percentDeadband) { + this._percentDeadband = percentDeadband; + } + + public int getServerHandle() { + return this._serverHandle; + } + + public void setServerHandle(final int serverHandle) { + this._serverHandle = serverHandle; + } + + public int getTimeBias() { + return this._timeBias; + } + + public void setTimeBias(final int timeBias) { + this._timeBias = timeBias; + } + + public int getUpdateRate() { + return this._updateRate; + } + + public void setUpdateRate(final int updateRate) { + this._updateRate = updateRate; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCITEMDEF.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCITEMDEF.java new file mode 100644 index 0000000..3507ac0 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCITEMDEF.java @@ -0,0 +1,104 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; + +public class OPCITEMDEF { + private String accessPath = ""; + + private String itemID = ""; + + private boolean active = true; + + private int clientHandle; + + private short requestedDataType = JIVariant.VT_EMPTY; + + private short reserved; + + public String getAccessPath() { + return this.accessPath; + } + + public void setAccessPath(final String accessPath) { + this.accessPath = accessPath; + } + + public int getClientHandle() { + return this.clientHandle; + } + + public void setClientHandle(final int clientHandle) { + this.clientHandle = clientHandle; + } + + public boolean isActive() { + return this.active; + } + + public void setActive(final boolean active) { + this.active = active; + } + + public String getItemID() { + return this.itemID; + } + + public void setItemID(final String itemID) { + this.itemID = itemID; + } + + public short getRequestedDataType() { + return this.requestedDataType; + } + + public void setRequestedDataType(final short requestedDataType) { + this.requestedDataType = requestedDataType; + } + + public short getReserved() { + return this.reserved; + } + + public void setReserved(final short reserved) { + this.reserved = reserved; + } + + /** + * Convert to structure to a J-Interop structure + * + * @return the j-interop structe + * @throws JIException JIException + */ + public JIStruct toStruct() throws JIException { + final JIStruct struct = new JIStruct(); + struct.addMember(new JIString(getAccessPath(), JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)); + struct.addMember(new JIString(getItemID(), JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)); + struct.addMember(isActive() ? 1 : 0); + struct.addMember(getClientHandle()); + + struct.addMember(0); // blob size + struct.addMember(new JIPointer(null)); // blob + + struct.addMember(getRequestedDataType()); + struct.addMember(getReserved()); + return struct; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCITEMRESULT.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCITEMRESULT.java new file mode 100644 index 0000000..1d20464 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCITEMRESULT.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.JIArray; +import org.jinterop.dcom.core.JIPointer; +import org.jinterop.dcom.core.JIStruct; +import org.jinterop.dcom.core.JIVariant; + +public class OPCITEMRESULT { + private int _serverHandle = 0; + + private short _canonicalDataType = JIVariant.VT_EMPTY; + + private short _reserved = 0; + + private int _accessRights = 0; + + public static JIStruct getStruct() throws JIException { + JIStruct struct = new JIStruct(); + + struct.addMember(Integer.class); // Server handle + struct.addMember(Short.class); // data type + struct.addMember(Short.class); // reserved + struct.addMember(Integer.class); // access rights + struct.addMember(Integer.class); // blob size + // grab the normally unused byte array + struct.addMember(new JIPointer(new JIArray(Byte.class, null, 1, true, false))); + + return struct; + } + + public static OPCITEMRESULT fromStruct(final JIStruct struct) { + OPCITEMRESULT result = new OPCITEMRESULT(); + + result.setServerHandle((Integer) struct.getMember(0)); + result.setCanonicalDataType((Short) struct.getMember(1)); + result.setReserved((Short) struct.getMember(2)); + result.setAccessRights((Integer) struct.getMember(3)); + + return result; + } + + public int getAccessRights() { + return this._accessRights; + } + + public void setAccessRights(final int accessRights) { + this._accessRights = accessRights; + } + + public short getCanonicalDataType() { + return this._canonicalDataType; + } + + public void setCanonicalDataType(final short canonicalDataType) { + this._canonicalDataType = canonicalDataType; + } + + public short getReserved() { + return this._reserved; + } + + public void setReserved(final short reserved) { + this._reserved = reserved; + } + + public int getServerHandle() { + return this._serverHandle; + } + + public void setServerHandle(final int serverHandle) { + this._serverHandle = serverHandle; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCITEMSTATE.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCITEMSTATE.java new file mode 100644 index 0000000..fbe56c9 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCITEMSTATE.java @@ -0,0 +1,99 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.JIStruct; +import org.jinterop.dcom.core.JIVariant; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.FILETIME; + +public class OPCITEMSTATE { + private int _clientHandle = 0; + + private FILETIME _timestamp = null; + + private short _quality = 0; + + private short _reserved = 0; + + private JIVariant _value = null; + + public static JIStruct getStruct() throws JIException { + JIStruct struct = new JIStruct(); + + struct.addMember(Integer.class); + struct.addMember(FILETIME.getStruct()); + struct.addMember(Short.class); + struct.addMember(Short.class); + struct.addMember(JIVariant.class); + + return struct; + } + + public static OPCITEMSTATE fromStruct(final JIStruct struct) { + OPCITEMSTATE itemState = new OPCITEMSTATE(); + + itemState.setClientHandle((Integer) struct.getMember(0)); + itemState.setTimestamp(FILETIME.fromStruct((JIStruct) struct.getMember(1))); + itemState.setQuality((Short) struct.getMember(2)); + itemState.setReserved((Short) struct.getMember(3)); + itemState.setValue((JIVariant) struct.getMember(4)); + + return itemState; + } + + public int getClientHandle() { + return this._clientHandle; + } + + public void setClientHandle(final int clientHandle) { + this._clientHandle = clientHandle; + } + + public short getQuality() { + return this._quality; + } + + public void setQuality(final short quality) { + this._quality = quality; + } + + public short getReserved() { + return this._reserved; + } + + public void setReserved(final short reserved) { + this._reserved = reserved; + } + + public FILETIME getTimestamp() { + return this._timestamp; + } + + public void setTimestamp(final FILETIME timestamp) { + this._timestamp = timestamp; + } + + public JIVariant getValue() { + return this._value; + } + + public void setValue(final JIVariant value) { + this._value = value; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCNAMESPACETYPE.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCNAMESPACETYPE.java new file mode 100644 index 0000000..cfb4502 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCNAMESPACETYPE.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +public enum OPCNAMESPACETYPE { + OPC_NS_HIERARCHIAL(1), + OPC_NS_FLAT(2), + OPC_NS_UNKNOWN(0); + + private int _id; + + private OPCNAMESPACETYPE(final int id) { + this._id = id; + } + + public static OPCNAMESPACETYPE fromID(final int id) { + switch (id) { + case 1: + return OPC_NS_HIERARCHIAL; + case 2: + return OPC_NS_FLAT; + default: + return OPC_NS_UNKNOWN; + } + } + + public int id() { + return this._id; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCSERVERSTATE.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCSERVERSTATE.java new file mode 100644 index 0000000..cb4a216 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCSERVERSTATE.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +public enum OPCSERVERSTATE { + OPC_STATUS_RUNNING(1), + OPC_STATUS_FAILED(2), + OPC_STATUS_NOCONFIG(3), + OPC_STATUS_SUSPENDED(4), + OPC_STATUS_TEST(5), + OPC_STATUS_COMM_FAULT(6), + OPC_STATUS_UNKNOWN(0); + + private int _id; + + private OPCSERVERSTATE(final int id) { + this._id = id; + } + + public static OPCSERVERSTATE fromID(final int id) { + switch (id) { + case 1: + return OPC_STATUS_RUNNING; + case 2: + return OPC_STATUS_FAILED; + case 3: + return OPC_STATUS_NOCONFIG; + case 4: + return OPC_STATUS_SUSPENDED; + case 5: + return OPC_STATUS_TEST; + case 6: + return OPC_STATUS_COMM_FAULT; + default: + return OPC_STATUS_UNKNOWN; + } + } + + public int id() { + return this._id; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCSERVERSTATUS.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCSERVERSTATUS.java new file mode 100644 index 0000000..7cb7837 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/OPCSERVERSTATUS.java @@ -0,0 +1,174 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.JIFlags; +import org.jinterop.dcom.core.JIPointer; +import org.jinterop.dcom.core.JIString; +import org.jinterop.dcom.core.JIStruct; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.FILETIME; + +public class OPCSERVERSTATUS { + private FILETIME _startTime = null; + + private FILETIME _currentTime = null; + + private FILETIME _lastUpdateTime = null; + + private OPCSERVERSTATE _serverState = null; + + private int _groupCount = -1; + + private int _bandWidth = -1; + + private short _majorVersion = -1; + + private short _minorVersion = -1; + + private short _buildNumber = -1; + + private short _reserved = 0; + + private String _vendorInfo = null; + + public static JIStruct getStruct() throws JIException { + JIStruct struct = new JIStruct(); + + struct.addMember(FILETIME.getStruct()); + struct.addMember(FILETIME.getStruct()); + struct.addMember(FILETIME.getStruct()); + struct.addMember(Short.class); // enum: OPCSERVERSTATE + struct.addMember(Integer.class); + struct.addMember(Integer.class); + struct.addMember(Short.class); + struct.addMember(Short.class); + struct.addMember(Short.class); + struct.addMember(Short.class); + struct.addMember(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR))); + + return struct; + } + + public static OPCSERVERSTATUS fromStruct(final JIStruct struct) { + OPCSERVERSTATUS status = new OPCSERVERSTATUS(); + + status._startTime = FILETIME.fromStruct((JIStruct) struct.getMember(0)); + status._currentTime = FILETIME.fromStruct((JIStruct) struct.getMember(1)); + status._lastUpdateTime = FILETIME.fromStruct((JIStruct) struct.getMember(2)); + + status._serverState = OPCSERVERSTATE.fromID((Short) struct.getMember(3)); + status._groupCount = (Integer) struct.getMember(4); + status._bandWidth = (Integer) struct.getMember(5); + status._majorVersion = (Short) struct.getMember(6); + status._minorVersion = (Short) struct.getMember(7); + status._buildNumber = (Short) struct.getMember(8); + status._reserved = (Short) struct.getMember(9); + status._vendorInfo = ((JIString) ((JIPointer) struct.getMember(10)).getReferent()).getString(); + + return status; + } + + public int getBandWidth() { + return this._bandWidth; + } + + public void setBandWidth(final int bandWidth) { + this._bandWidth = bandWidth; + } + + public short getBuildNumber() { + return this._buildNumber; + } + + public void setBuildNumber(final short buildNumber) { + this._buildNumber = buildNumber; + } + + public FILETIME getCurrentTime() { + return this._currentTime; + } + + public void setCurrentTime(final FILETIME currentTime) { + this._currentTime = currentTime; + } + + public int getGroupCount() { + return this._groupCount; + } + + public void setGroupCount(final int groupCount) { + this._groupCount = groupCount; + } + + public FILETIME getLastUpdateTime() { + return this._lastUpdateTime; + } + + public void setLastUpdateTime(final FILETIME lastUpdateTime) { + this._lastUpdateTime = lastUpdateTime; + } + + public short getMajorVersion() { + return this._majorVersion; + } + + public void setMajorVersion(final short majorVersion) { + this._majorVersion = majorVersion; + } + + public short getMinorVersion() { + return this._minorVersion; + } + + public void setMinorVersion(final short minorVersion) { + this._minorVersion = minorVersion; + } + + public short getReserved() { + return this._reserved; + } + + public void setReserved(final short reserved) { + this._reserved = reserved; + } + + public FILETIME getStartTime() { + return this._startTime; + } + + public void setStartTime(final FILETIME startTime) { + this._startTime = startTime; + } + + public String getVendorInfo() { + return this._vendorInfo; + } + + public void setVendorInfo(final String vendorInfo) { + this._vendorInfo = vendorInfo; + } + + public OPCSERVERSTATE getServerState() { + return this._serverState; + } + + public void setServerState(final OPCSERVERSTATE dwServerState) { + this._serverState = dwServerState; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/PropertyDescription.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/PropertyDescription.java new file mode 100644 index 0000000..e04e8b2 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/PropertyDescription.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +public class PropertyDescription { + private int _id = -1; + + private String _description = ""; + + private short _varType = 0; + + public String getDescription() { + return this._description; + } + + public void setDescription(final String description) { + this._description = description; + } + + public int getId() { + return this._id; + } + + public void setId(final int id) { + this._id = id; + } + + public short getVarType() { + return this._varType; + } + + public void setVarType(final short varType) { + this._varType = varType; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/ValueData.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/ValueData.java new file mode 100644 index 0000000..37160c5 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/ValueData.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +import org.jinterop.dcom.core.JIVariant; + +import java.util.Calendar; + +public class ValueData { + private JIVariant value; + + private short quality; + + private Calendar timestamp; + + public short getQuality() { + return this.quality; + } + + public void setQuality(final short quality) { + this.quality = quality; + } + + public Calendar getTimestamp() { + return this.timestamp; + } + + public void setTimestamp(final Calendar timestamp) { + this.timestamp = timestamp; + } + + public JIVariant getValue() { + return this.value; + } + + public void setValue(final JIVariant value) { + this.value = value; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/WriteRequest.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/WriteRequest.java new file mode 100644 index 0000000..79bdea1 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/WriteRequest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da; + +import org.jinterop.dcom.core.JIVariant; + +/** + * Data for a write request to the server + * + * @author Jens Reimann jens.reimann@th4-systems.com + */ +public class WriteRequest { + private int serverHandle = 0; + + private JIVariant value = JIVariant.EMPTY(); + + public WriteRequest() { + } + + public WriteRequest(final WriteRequest request) { + this.serverHandle = request.serverHandle; + this.value = request.value; + } + + /** + * Create a new write request with pre-fille data + * + * @param serverHandle the server handle of the item to write to + * @param value the value to write. + */ + public WriteRequest(final int serverHandle, final JIVariant value) { + this.serverHandle = serverHandle; + this.value = value; + } + + public int getServerHandle() { + return this.serverHandle; + } + + public void setServerHandle(final int serverHandle) { + this.serverHandle = serverHandle; + } + + public JIVariant getValue() { + return this.value; + } + + public void setValue(final JIVariant value) { + this.value = value; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCAsyncIO2.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCAsyncIO2.java new file mode 100644 index 0000000..dba8b6b --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCAsyncIO2.java @@ -0,0 +1,120 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Result; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.ResultSet; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.BaseCOMObject; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.Constants; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCDATASOURCE; + +import java.net.UnknownHostException; + +public class OPCAsyncIO2 extends BaseCOMObject { + public OPCAsyncIO2(final IJIComObject opcAsyncIO2) throws IllegalArgumentException, UnknownHostException, JIException { + super(opcAsyncIO2.queryInterface(Constants.IOPCAsyncIO2_IID)); + } + + public void setEnable(final boolean state) throws JIException { + final JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(4); + + callObject.addInParamAsInt(state ? 1 : 0, JIFlags.FLAG_NULL); + + getCOMObject().call(callObject); + } + + public int refresh(final OPCDATASOURCE dataSource, final int transactionID) throws JIException { + final JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(2); + + callObject.addInParamAsShort((short) dataSource.id(), JIFlags.FLAG_NULL); + callObject.addInParamAsInt(transactionID, JIFlags.FLAG_NULL); + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + + final Object result[] = getCOMObject().call(callObject); + + return (Integer) result[0]; + } + + public void cancel(final int cancelId) throws JIException { + final JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(3); + + callObject.addInParamAsInt(cancelId, JIFlags.FLAG_NULL); + + getCOMObject().call(callObject); + } + + public AsyncResult read(final int transactionId, final Integer... serverHandles) throws JIException { + if (serverHandles == null || serverHandles.length == 0) { + return new AsyncResult(); + } + + final JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(0); + + callObject.addInParamAsInt(serverHandles.length, JIFlags.FLAG_NULL); + callObject.addInParamAsArray(new JIArray(serverHandles, true), JIFlags.FLAG_NULL); + callObject.addInParamAsInt(transactionId, JIFlags.FLAG_NULL); + + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + + final Object[] result = getCOMObject().call(callObject); + + final Integer cancelId = (Integer) result[0]; + final Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance(); + + final ResultSet resultSet = new ResultSet(); + + for (int i = 0; i < serverHandles.length; i++) { + resultSet.add(new Result(serverHandles[i], errorCodes[i])); + } + + return new AsyncResult(resultSet, cancelId); + } + + public class AsyncResult { + private final ResultSet result; + + private final Integer cancelId; + + public AsyncResult() { + super(); + this.result = new ResultSet(); + this.cancelId = null; + } + + public AsyncResult(final ResultSet result, final Integer cancelId) { + super(); + this.result = result; + this.cancelId = cancelId; + } + + public Integer getCancelId() { + return this.cancelId; + } + + public ResultSet getResult() { + return this.result; + } + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCBrowse.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCBrowse.java new file mode 100644 index 0000000..06e9e32 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCBrowse.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.IJIComObject; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.BaseCOMObject; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.Constants; + +import java.net.UnknownHostException; + +public class OPCBrowse extends BaseCOMObject { + public OPCBrowse(final IJIComObject opcServer) throws IllegalArgumentException, UnknownHostException, JIException { + super(opcServer.queryInterface(Constants.IOPCBrowse_IID)); + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCBrowseServerAddressSpace.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCBrowseServerAddressSpace.java new file mode 100644 index 0000000..4613184 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCBrowseServerAddressSpace.java @@ -0,0 +1,146 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.BaseCOMObject; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.EnumString; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.Helper; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.Constants; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCBROWSEDIRECTION; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCBROWSETYPE; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCNAMESPACETYPE; + +import java.net.UnknownHostException; + +/** + * Implementation for IOPCBrowseServerAddressSpace + * + * @author Jens Reimann jens.reimann@th4-systems.com + */ +public class OPCBrowseServerAddressSpace extends BaseCOMObject { + public OPCBrowseServerAddressSpace(final IJIComObject opcServer) throws IllegalArgumentException, UnknownHostException, JIException { + super(opcServer.queryInterface(Constants.IOPCBrowseServerAddressSpace_IID)); + } + + /** + * Get the information how the namespace is organized + * + * @return the organization of the namespace + * @throws JIException JIException + */ + public OPCNAMESPACETYPE queryOrganization() throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(0); + + callObject.addOutParamAsType(Short.class, JIFlags.FLAG_NULL); + + Object result[] = getCOMObject().call(callObject); + + return OPCNAMESPACETYPE.fromID((Short) result[0]); + } + + /** + * Direct the browser to another position + *

+ * Depending on the direction the new position will be set based on the provided + * position information. If the direction is {@link OPCBROWSEDIRECTION#OPC_BROWSE_TO} then + * the position is the item to go to. If the direction is {@link OPCBROWSEDIRECTION#OPC_BROWSE_DOWN} + * the browser will descent into the tree down (not to) the branch item in position. + * Passing {@link OPCBROWSEDIRECTION#OPC_BROWSE_UP} won't need a position (pass null) + * and will ascent in the tree one level. + *

+ * Passing {@link OPCBROWSEDIRECTION#OPC_BROWSE_TO} and null as position will + * go to the first root entry of the namespace. + * + * @param position The item position reference for the direction + * @param direction The direction to go based on the position + * @throws JIException JIException + */ + public void changePosition(final String position, final OPCBROWSEDIRECTION direction) throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(1); + + callObject.addInParamAsShort((short) direction.id(), JIFlags.FLAG_NULL); + callObject.addInParamAsString(position, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + + getCOMObject().call(callObject); + + } + + public EnumString browse(final OPCBROWSETYPE browseType, final String filterCriteria, final int accessRights, final int dataType) throws JIException, IllegalArgumentException, UnknownHostException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(2); + + callObject.addInParamAsShort((short) browseType.id(), JIFlags.FLAG_NULL); + callObject.addInParamAsString(filterCriteria, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + callObject.addInParamAsShort((short) dataType, JIFlags.FLAG_NULL); + callObject.addInParamAsInt(accessRights, JIFlags.FLAG_NULL); + callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL); + + Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject); + + return new EnumString((IJIComObject) result[0]); + } + + /** + * Return the possible access paths for an item + * + * @param itemID the item to query + * @return A string enumerator for the possible access paths + * @throws JIException JIException + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + */ + public EnumString browseAccessPaths(final String itemID) throws JIException, IllegalArgumentException, UnknownHostException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(4); + + callObject.addInParamAsString(itemID, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL); + + Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject); + + return new EnumString((IJIComObject) result[0]); + } + + /** + * Get the complete item id from an item at the local position. + *

+ * Browsing a hierarchical namespace the browse method will return items based on the + * local level in the namespace. So actually only the last part of the item ID hierarchy + * is returned. In order to convert this to the full item ID one can use this method. It + * will only work if the browser is still at the position in question. + * + * @param item the local item + * @return the complete item ID + * @throws JIException JIException + */ + public String getItemID(final String item) throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(3); + + callObject.addInParamAsString(item, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + callObject.addOutParamAsObject(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)), JIFlags.FLAG_NULL); + + Object[] result = getCOMObject().call(callObject); + + return ((JIString) ((JIPointer) result[0]).getReferent()).getString(); + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCDataCallback.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCDataCallback.java new file mode 100644 index 0000000..fefd676 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCDataCallback.java @@ -0,0 +1,218 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.EventHandlerImpl; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.Constants; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.IOPCDataCallback; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.ValueData; + +import java.util.LinkedList; +import java.util.List; + +public class OPCDataCallback extends EventHandlerImpl { + private IOPCDataCallback callback = null; + + private JILocalCoClass coClass = null; + + public OPCDataCallback() { + super(); + } + + public Object[] OnDataChange(final int transactionId, final int serverGroupHandle, final int masterQuality, final int masterErrorCode, final int count, final JIArray clientHandles, final JIArray values, final JIArray qualities, final JIArray timestamps, final JIArray errors) { + final IOPCDataCallback callback = this.callback; + if (callback == null) { + return new Object[]{org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants.S_OK}; + } + + // get arrays for more readable code later ;-) + final Integer[] errorCodes = (Integer[]) errors.getArrayInstance(); + final Integer[] itemHandles = (Integer[]) clientHandles.getArrayInstance(); + final Short[] qualitiesArray = (Short[]) qualities.getArrayInstance(); + final JIVariant[] valuesArray = (JIVariant[]) values.getArrayInstance(); + final JIStruct[] timestampArray = (JIStruct[]) timestamps.getArrayInstance(); + + // create result data + final KeyedResultSet result = new KeyedResultSet(); + for (int i = 0; i < count; i++) { + final ValueData vd = new ValueData(); + vd.setQuality(qualitiesArray[i]); + vd.setTimestamp(FILETIME.fromStruct(timestampArray[i]).asCalendar()); + vd.setValue(valuesArray[i]); + result.add(new KeyedResult(itemHandles[i], vd, errorCodes[i])); + } + + // fire event + try { + callback.dataChange(transactionId, serverGroupHandle, masterQuality, masterErrorCode, result); + } catch (final Throwable e) { + e.printStackTrace(); + } + + // The client must always return S_OK + return new Object[]{org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants.S_OK}; + } + + public synchronized Object[] OnReadComplete(final int transactionId, final int serverGroupHandle, final int masterQuality, final int masterErrorCode, final int count, final JIArray clientHandles, final JIArray values, final JIArray qualities, final JIArray timestamps, final JIArray errors) { + if (this.callback == null) { + return new Object[]{org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants.S_OK}; + } + + // get arrays for more readable code later ;-) + final Integer[] errorCodes = (Integer[]) errors.getArrayInstance(); + final Integer[] itemHandles = (Integer[]) clientHandles.getArrayInstance(); + final Short[] qualitiesArray = (Short[]) qualities.getArrayInstance(); + final JIVariant[] valuesArray = (JIVariant[]) values.getArrayInstance(); + final JIStruct[] timestampArray = (JIStruct[]) timestamps.getArrayInstance(); + + // create result data + final KeyedResultSet result = new KeyedResultSet(); + for (int i = 0; i < count; i++) { + final ValueData vd = new ValueData(); + vd.setQuality(qualitiesArray[i]); + vd.setTimestamp(FILETIME.fromStruct(timestampArray[i]).asCalendar()); + vd.setValue(valuesArray[i]); + result.add(new KeyedResult(itemHandles[i], vd, errorCodes[i])); + } + + // fire event + try { + this.callback.readComplete(transactionId, serverGroupHandle, masterQuality, masterErrorCode, result); + } catch (final Throwable e) { + e.printStackTrace(); + } + + // The client must always return S_OK + return new Object[]{org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants.S_OK}; + } + + public synchronized Object[] OnWriteComplete(final int transactionId, final int serverGroupHandle, final int masterErrorCode, final int count, final JIArray clientHandles, final JIArray errors) { + if (this.callback == null) { + return new Object[]{org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants.S_OK}; + } + + // get arrays for more readable code later ;-) + final Integer[] errorCodes = (Integer[]) errors.getArrayInstance(); + final Integer[] itemHandles = (Integer[]) clientHandles.getArrayInstance(); + + // create result data + final ResultSet result = new ResultSet(); + for (int i = 0; i < count; i++) { + result.add(new Result(itemHandles[i], errorCodes[i])); + } + + // fire event + try { + this.callback.writeComplete(transactionId, serverGroupHandle, masterErrorCode, result); + } catch (final Throwable e) { + e.printStackTrace(); + } + + // The client must always return S_OK + return new Object[]{org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants.S_OK}; + } + + public synchronized Object[] OnCancelComplete(final int transactionId, final int serverGroupHandle) { + if (this.callback == null) { + return new Object[]{org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants.S_OK}; + } + + this.callback.cancelComplete(transactionId, serverGroupHandle); + + // The client must always return S_OK + return new Object[]{org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants.S_OK}; + } + + public synchronized JILocalCoClass getCoClass() throws JIException { + if (this.coClass != null) { + return this.coClass; + } + + this.coClass = new JILocalCoClass(new JILocalInterfaceDefinition(Constants.IOPCDataCallback_IID, false), this, false); + + JILocalParamsDescriptor params; + JILocalMethodDescriptor method; + + // OnDataChange + params = new JILocalParamsDescriptor(); + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); // trans id + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); // group handle + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); // master quality + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); // master error + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); // count + params.addInParamAsObject(new JIArray(Integer.class, null, 1, true), JIFlags.FLAG_NULL); // item handles + params.addInParamAsObject(new JIArray(JIVariant.class, null, 1, true), JIFlags.FLAG_NULL); // values + params.addInParamAsObject(new JIArray(Short.class, null, 1, true), JIFlags.FLAG_NULL); // qualities + params.addInParamAsObject(new JIArray(FILETIME.getStruct(), null, 1, true), JIFlags.FLAG_NULL); // timestamps + params.addInParamAsObject(new JIArray(Integer.class, null, 1, true), JIFlags.FLAG_NULL); // errors + + method = new JILocalMethodDescriptor("OnDataChange", params); + this.coClass.getInterfaceDefinition().addMethodDescriptor(method); + + // OnReadComplete + params = new JILocalParamsDescriptor(); + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); + params.addInParamAsObject(new JIArray(Integer.class, null, 1, true), JIFlags.FLAG_NULL); + params.addInParamAsObject(new JIArray(JIVariant.class, null, 1, true), JIFlags.FLAG_NULL); + params.addInParamAsObject(new JIArray(Short.class, null, 1, true), JIFlags.FLAG_NULL); + params.addInParamAsObject(new JIArray(FILETIME.getStruct(), null, 1, true), JIFlags.FLAG_NULL); + params.addInParamAsObject(new JIArray(Integer.class, null, 1, true), JIFlags.FLAG_NULL); + method = new JILocalMethodDescriptor("OnReadComplete", params); + this.coClass.getInterfaceDefinition().addMethodDescriptor(method); + + // OnWriteComplete + params = new JILocalParamsDescriptor(); + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); + params.addInParamAsObject(new JIArray(Integer.class, null, 1, true), JIFlags.FLAG_NULL); + params.addInParamAsObject(new JIArray(Integer.class, null, 1, true), JIFlags.FLAG_NULL); + method = new JILocalMethodDescriptor("OnWriteComplete", params); + this.coClass.getInterfaceDefinition().addMethodDescriptor(method); + + // OnCancelComplete + params = new JILocalParamsDescriptor(); + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); + params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); + method = new JILocalMethodDescriptor("OnCancelComplete", params); + this.coClass.getInterfaceDefinition().addMethodDescriptor(method); + + // Add supported event interfaces + final List eventInterfaces = new LinkedList(); + eventInterfaces.add(Constants.IOPCDataCallback_IID); + this.coClass.setSupportedEventInterfaces(eventInterfaces); + + return this.coClass; + } + + public IOPCDataCallback getCallback() { + return this.callback; + } + + public void setCallback(final IOPCDataCallback callback) { + this.callback = callback; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCGroupStateMgt.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCGroupStateMgt.java new file mode 100644 index 0000000..abf480d --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCGroupStateMgt.java @@ -0,0 +1,182 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; +import org.jinterop.dcom.impls.JIObjectFactory; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.EventHandler; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.BaseCOMObject; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.Constants; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.IOPCDataCallback; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCGroupState; + +import java.net.UnknownHostException; + +/** + * Implementation of IOPCGroupStateMgt + * + * @author Jens Reimann jens.reimann@th4-systems.com + */ +public class OPCGroupStateMgt extends BaseCOMObject { + public OPCGroupStateMgt(final IJIComObject opcGroup) throws IllegalArgumentException, UnknownHostException, JIException { + super(opcGroup.queryInterface(Constants.IOPCGroupStateMgt_IID)); + } + + public OPCGroupState getState() throws JIException { + final JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(0); + + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + callObject.addOutParamAsType(Boolean.class, JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)), JIFlags.FLAG_NULL); + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + callObject.addOutParamAsType(Float.class, JIFlags.FLAG_NULL); + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + + final Object result[] = getCOMObject().call(callObject); + + final OPCGroupState state = new OPCGroupState(); + state.setUpdateRate((Integer) result[0]); + state.setActive((Boolean) result[1]); + state.setName(((JIString) ((JIPointer) result[2]).getReferent()).getString()); + state.setTimeBias((Integer) result[3]); + state.setPercentDeadband((Float) result[4]); + state.setLocaleID((Integer) result[5]); + state.setClientHandle((Integer) result[6]); + state.setServerHandle((Integer) result[7]); + + return state; + } + + /** + * Set the group state Leaving any of the parameters null will keep the current value untouched. + * + * @param requestedUpdateRate the requested update rate + * @param active Flag if the group is active or not + * @param timeBias The time bias + * @param percentDeadband the deadband percent + * @param localeID the locale ID + * @param clientHandle the client handle + * @return the granted update rate + * @throws JIException JIException + */ + public int setState(final Integer requestedUpdateRate, final Boolean active, final Integer timeBias, final Float percentDeadband, final Integer localeID, final Integer clientHandle) throws JIException { + final JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(1); + + callObject.addInParamAsPointer(new JIPointer(requestedUpdateRate), JIFlags.FLAG_NULL); + if (active != null) { + callObject.addInParamAsPointer(new JIPointer(Integer.valueOf(active.booleanValue() ? 1 : 0)), JIFlags.FLAG_NULL); + } else { + callObject.addInParamAsPointer(new JIPointer(null), JIFlags.FLAG_NULL); + } + callObject.addInParamAsPointer(new JIPointer(timeBias), JIFlags.FLAG_NULL); + callObject.addInParamAsPointer(new JIPointer(percentDeadband), JIFlags.FLAG_NULL); + callObject.addInParamAsPointer(new JIPointer(localeID), JIFlags.FLAG_NULL); + callObject.addInParamAsPointer(new JIPointer(clientHandle), JIFlags.FLAG_NULL); + + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + + final Object[] result = getCOMObject().call(callObject); + + return (Integer) result[0]; + } + + public OPCItemMgt getItemManagement() throws JIException { + return new OPCItemMgt(getCOMObject()); + } + + /** + * Rename to group + * + * @param name the new name + * @throws JIException JIException + */ + public void setName(final String name) throws JIException { + final JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(2); + + callObject.addInParamAsString(name, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + + getCOMObject().call(callObject); + } + + /** + * Clone the group + * + * @param name the name of the cloned group + * @return The cloned group + * @throws JIException JIException + * @throws UnknownHostException UnknownHostException + * @throws IllegalArgumentException IllegalArgumentException + */ + public OPCGroupStateMgt clone(final String name) throws JIException, IllegalArgumentException, UnknownHostException { + final JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(3); + + callObject.addInParamAsString(name, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + callObject.addInParamAsUUID(Constants.IOPCGroupStateMgt_IID, JIFlags.FLAG_NULL); + callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL); + + final Object[] result = getCOMObject().call(callObject); + return new OPCGroupStateMgt((IJIComObject) result[0]); + } + + /** + * Attach a new callback to the group + * + * @param callback The callback to attach + * @return The event handler information + * @throws JIException JIException + */ + public EventHandler attach(final IOPCDataCallback callback) throws JIException { + final OPCDataCallback callbackObject = new OPCDataCallback(); + + callbackObject.setCallback(callback); + + // sync the callback object so that no calls get through the callback + // until the callback information is set + // If happens in some cases that the callback is triggered before + // the method attachEventHandler returns. + synchronized (callbackObject) { + final String id = JIFrameworkHelper.attachEventHandler(getCOMObject(), Constants.IOPCDataCallback_IID, JIObjectFactory.buildObject(getCOMObject().getAssociatedSession(), callbackObject.getCoClass())); + + callbackObject.setInfo(getCOMObject(), id); + } + return callbackObject; + } + + public OPCAsyncIO2 getAsyncIO2() { + try { + return new OPCAsyncIO2(getCOMObject()); + } catch (final Exception e) { + return null; + } + } + + public OPCSyncIO getSyncIO() { + try { + return new OPCSyncIO(getCOMObject()); + } catch (final Exception e) { + return null; + } + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCItemIO.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCItemIO.java new file mode 100644 index 0000000..acb186b --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCItemIO.java @@ -0,0 +1,60 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.FILETIME; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.BaseCOMObject; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.Constants; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.IORequest; + +import java.net.UnknownHostException; + +public class OPCItemIO extends BaseCOMObject { + public OPCItemIO(final IJIComObject opcItemIO) throws IllegalArgumentException, UnknownHostException, JIException { + super(opcItemIO.queryInterface(Constants.IOPCItemIO_IID)); + } + + public void read(final IORequest[] requests) throws JIException { + if (requests.length == 0) { + return; + } + + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(0); + + JIString itemIDs[] = new JIString[requests.length]; + Integer maxAges[] = new Integer[requests.length]; + for (int i = 0; i < requests.length; i++) { + itemIDs[i] = new JIString(requests[i].getItemID(), JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + maxAges[i] = requests[i].getMaxAge(); + } + + callObject.addInParamAsInt(requests.length, JIFlags.FLAG_NULL); + callObject.addInParamAsArray(new JIArray(itemIDs, true), JIFlags.FLAG_NULL); + callObject.addInParamAsArray(new JIArray(maxAges, true), JIFlags.FLAG_NULL); + + callObject.addOutParamAsObject(new JIPointer(new JIArray(JIVariant.class, null, 1, true)), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(FILETIME.getStruct(), null, 1, true)), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + + getCOMObject().call(callObject); + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCItemMgt.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCItemMgt.java new file mode 100644 index 0000000..6383ccb --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCItemMgt.java @@ -0,0 +1,184 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.KeyedResult; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.KeyedResultSet; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Result; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.ResultSet; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.BaseCOMObject; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.Helper; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.Constants; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCITEMDEF; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCITEMRESULT; + +public class OPCItemMgt extends BaseCOMObject { + public OPCItemMgt(final IJIComObject opcGroup) throws JIException { + super(opcGroup.queryInterface(Constants.IOPCItemMgt_IID)); + } + + public KeyedResultSet validate(final OPCITEMDEF... items) throws JIException { + if (items.length == 0) { + return new KeyedResultSet(); + } + + final JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(1); + + final JIStruct struct[] = new JIStruct[items.length]; + for (int i = 0; i < items.length; i++) { + struct[i] = items[i].toStruct(); + } + final JIArray itemArray = new JIArray(struct, true); + + callObject.addInParamAsInt(items.length, JIFlags.FLAG_NULL); + callObject.addInParamAsArray(itemArray, JIFlags.FLAG_NULL); + callObject.addInParamAsInt(0, JIFlags.FLAG_NULL); // don't update blobs + callObject.addOutParamAsObject(new JIPointer(new JIArray(OPCITEMRESULT.getStruct(), null, 1, true)), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + + final Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject); + + final JIStruct[] results = (JIStruct[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance(); + final Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance(); + + final KeyedResultSet resultList = new KeyedResultSet(items.length); + for (int i = 0; i < items.length; i++) { + final OPCITEMRESULT itemResult = OPCITEMRESULT.fromStruct(results[i]); + final KeyedResult resultEntry = new KeyedResult(items[i], itemResult, errorCodes[i]); + resultList.add(resultEntry); + } + + return resultList; + } + + public KeyedResultSet add(final OPCITEMDEF... items) throws JIException { + if (items.length == 0) { + return new KeyedResultSet(); + } + + final JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(0); + + final JIStruct struct[] = new JIStruct[items.length]; + for (int i = 0; i < items.length; i++) { + struct[i] = items[i].toStruct(); + } + final JIArray itemArray = new JIArray(struct, true); + + callObject.addInParamAsInt(items.length, JIFlags.FLAG_NULL); + callObject.addInParamAsArray(itemArray, JIFlags.FLAG_NULL); + + /* + callObject.addOutParamAsObject ( new JIPointer ( new JIArray ( OPCITEMRESULT.getStruct (), null, 1, true ) ), + JIFlags.FLAG_NULL ); + callObject.addOutParamAsObject ( new JIPointer ( new JIArray ( Integer.class, null, 1, true ) ), + JIFlags.FLAG_NULL ); + */ + callObject.addOutParamAsObject(new JIPointer(new JIArray(OPCITEMRESULT.getStruct(), null, 1, true)), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + + final Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject); + + final JIStruct[] results = (JIStruct[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance(); + final Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance(); + + final KeyedResultSet resultList = new KeyedResultSet(items.length); + for (int i = 0; i < items.length; i++) { + final OPCITEMRESULT itemResult = OPCITEMRESULT.fromStruct(results[i]); + final KeyedResult resultEntry = new KeyedResult(items[i], itemResult, errorCodes[i]); + resultList.add(resultEntry); + } + + return resultList; + } + + public ResultSet remove(final Integer... serverHandles) throws JIException { + if (serverHandles.length == 0) { + return new ResultSet(); + } + + final JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(2); + + callObject.addInParamAsInt(serverHandles.length, JIFlags.FLAG_NULL); + callObject.addInParamAsArray(new JIArray(serverHandles, true), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + + final Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject); + + final Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance(); + final ResultSet results = new ResultSet(serverHandles.length); + for (int i = 0; i < serverHandles.length; i++) { + results.add(new Result(serverHandles[i], errorCodes[i])); + } + return results; + } + + public ResultSet setActiveState(final boolean state, final Integer... items) throws JIException { + if (items.length == 0) { + return new ResultSet(); + } + + final JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(3); + + callObject.addInParamAsInt(items.length, JIFlags.FLAG_NULL); + callObject.addInParamAsArray(new JIArray(items, true), JIFlags.FLAG_NULL); + callObject.addInParamAsInt(state ? 1 : 0, JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + + final Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject); + + final Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance(); + final ResultSet results = new ResultSet(items.length); + for (int i = 0; i < items.length; i++) { + results.add(new Result(items[i], errorCodes[i])); + } + return results; + } + + public ResultSet setClientHandles(final Integer[] serverHandles, final Integer[] clientHandles) throws JIException { + if (serverHandles.length != clientHandles.length) { + throw new JIException(0, "Array sizes don't match"); + } + if (serverHandles.length == 0) { + return new ResultSet(); + } + + final JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(4); + + callObject.addInParamAsInt(serverHandles.length, JIFlags.FLAG_NULL); + callObject.addInParamAsArray(new JIArray(serverHandles, true), JIFlags.FLAG_NULL); + callObject.addInParamAsArray(new JIArray(clientHandles, true), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + + final Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject); + + final Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance(); + final ResultSet results = new ResultSet(serverHandles.length); + for (int i = 0; i < serverHandles.length; i++) { + results.add(new Result(serverHandles[i], errorCodes[i])); + } + return results; + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCItemProperties.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCItemProperties.java new file mode 100644 index 0000000..0d44790 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCItemProperties.java @@ -0,0 +1,134 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.KeyedResult; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.KeyedResultSet; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.BaseCOMObject; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.Helper; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.Constants; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.PropertyDescription; + +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +public class OPCItemProperties extends BaseCOMObject { + public OPCItemProperties(final IJIComObject opcItemProperties) throws IllegalArgumentException, UnknownHostException, JIException { + super(opcItemProperties.queryInterface(Constants.IOPCItemProperties_IID)); + } + + public Collection queryAvailableProperties(final String itemID) throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(0); + + callObject.addInParamAsString(itemID, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_BSTR), null, 1, true)), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Short.class, null, 1, true)), JIFlags.FLAG_NULL); + + Object result[] = getCOMObject().call(callObject); + + List properties = new LinkedList(); + + int len = (Integer) result[0]; + Integer[] ids = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance(); + JIString[] descriptions = (JIString[]) ((JIArray) ((JIPointer) result[2]).getReferent()).getArrayInstance(); + Short[] variableTypes = (Short[]) ((JIArray) ((JIPointer) result[3]).getReferent()).getArrayInstance(); + + for (int i = 0; i < len; i++) { + PropertyDescription pd = new PropertyDescription(); + pd.setId(ids[i]); + pd.setDescription(descriptions[i].getString()); + pd.setVarType(variableTypes[i]); + properties.add(pd); + } + return properties; + } + + public KeyedResultSet getItemProperties(final String itemID, final int... properties) throws JIException { + if (properties.length == 0) { + return new KeyedResultSet(); + } + + Integer[] ids = new Integer[properties.length]; + for (int i = 0; i < properties.length; i++) { + ids[i] = properties[i]; + } + + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(1); + + callObject.addInParamAsString(itemID, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + callObject.addInParamAsInt(properties.length, JIFlags.FLAG_NULL); + callObject.addInParamAsArray(new JIArray(ids, true), JIFlags.FLAG_NULL); + + callObject.addOutParamAsObject(new JIPointer(new JIArray(JIVariant.class, null, 1, true)), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + + Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject); + + JIVariant[] values = (JIVariant[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance(); + Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance(); + + KeyedResultSet results = new KeyedResultSet(); + for (int i = 0; i < properties.length; i++) { + results.add(new KeyedResult(properties[i], values[i], errorCodes[i])); + } + return results; + } + + public KeyedResultSet lookupItemIDs(final String itemID, final int... properties) throws JIException { + if (properties.length == 0) { + return new KeyedResultSet(); + } + + Integer[] ids = new Integer[properties.length]; + for (int i = 0; i < properties.length; i++) { + ids[i] = properties[i]; + } + + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(2); + + callObject.addInParamAsString(itemID, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + callObject.addInParamAsInt(properties.length, JIFlags.FLAG_NULL); + callObject.addInParamAsArray(new JIArray(ids, true), JIFlags.FLAG_NULL); + + callObject.addOutParamAsObject(new JIPointer(new JIArray(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)), null, 1, true)), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + + Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject); + + JIPointer[] itemIDs = (JIPointer[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance(); + Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance(); + + KeyedResultSet results = new KeyedResultSet(); + + for (int i = 0; i < properties.length; i++) { + results.add(new KeyedResult(properties[i], ((JIString) itemIDs[i].getReferent()).getString(), errorCodes[i])); + } + return results; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCServer.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCServer.java new file mode 100644 index 0000000..74cf031 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCServer.java @@ -0,0 +1,165 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.BaseCOMObject; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.EnumString; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.Helper; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.OPCCommon; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.Constants; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCENUMSCOPE; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCSERVERSTATUS; + +import java.net.UnknownHostException; + +public class OPCServer extends BaseCOMObject { + public OPCServer(final IJIComObject opcServer) throws IllegalArgumentException, UnknownHostException, JIException { + super(opcServer.queryInterface(Constants.IOPCServer_IID)); + } + + /** + * Retrieve the current server status + * + * @return the current server status + * @throws JIException JIException + */ + public OPCSERVERSTATUS getStatus() throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(3); + + callObject.addOutParamAsObject(new JIPointer(OPCSERVERSTATUS.getStruct()), JIFlags.FLAG_NULL); + + Object[] result = getCOMObject().call(callObject); + + return OPCSERVERSTATUS.fromStruct((JIStruct) ((JIPointer) result[0]).getReferent()); + } + + public OPCGroupStateMgt addGroup(final String name, final boolean active, final int updateRate, final int clientHandle, final Integer timeBias, final Float percentDeadband, final int localeID) throws JIException, IllegalArgumentException, UnknownHostException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(0); + + callObject.addInParamAsString(name, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + callObject.addInParamAsInt(active ? 1 : 0, JIFlags.FLAG_NULL); + callObject.addInParamAsInt(updateRate, JIFlags.FLAG_NULL); + callObject.addInParamAsInt(clientHandle, JIFlags.FLAG_NULL); + callObject.addInParamAsPointer(new JIPointer(timeBias), JIFlags.FLAG_NULL); + callObject.addInParamAsPointer(new JIPointer(percentDeadband), JIFlags.FLAG_NULL); + callObject.addInParamAsInt(localeID, JIFlags.FLAG_NULL); + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL); + callObject.addInParamAsUUID(Constants.IOPCGroupStateMgt_IID, JIFlags.FLAG_NULL); + callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL); + + Object[] result = getCOMObject().call(callObject); + + return new OPCGroupStateMgt((IJIComObject) result[2]); + } + + public void removeGroup(final int serverHandle, final boolean force) throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(4); + + callObject.addInParamAsInt(serverHandle, JIFlags.FLAG_NULL); + callObject.addInParamAsInt(force ? 1 : 0, JIFlags.FLAG_NULL); + + getCOMObject().call(callObject); + } + + public void removeGroup(final OPCGroupStateMgt group, final boolean force) throws JIException { + removeGroup(group.getState().getServerHandle(), force); + } + + public OPCGroupStateMgt getGroupByName(final String name) throws JIException, IllegalArgumentException, UnknownHostException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(2); + + callObject.addInParamAsString(name, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + callObject.addInParamAsUUID(Constants.IOPCGroupStateMgt_IID, JIFlags.FLAG_NULL); + callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL); + + Object[] result = getCOMObject().call(callObject); + + return new OPCGroupStateMgt((IJIComObject) result[0]); + } + + /** + * Get the groups + * + * @param scope The scope to get + * @return A string enumerator with the groups + * @throws JIException JIException + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + */ + public EnumString getGroups(final OPCENUMSCOPE scope) throws JIException, IllegalArgumentException, UnknownHostException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(5); + + callObject.addInParamAsShort((short) scope.id(), JIFlags.FLAG_NULL); + callObject.addInParamAsUUID(org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants.IEnumString_IID, JIFlags.FLAG_NULL); + callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL); + + Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject); + + return new EnumString((IJIComObject) result[0]); + } + + public OPCItemProperties getItemPropertiesService() { + try { + return new OPCItemProperties(getCOMObject()); + } catch (Exception e) { + return null; + } + } + + public OPCItemIO getItemIOService() { + try { + return new OPCItemIO(getCOMObject()); + } catch (Exception e) { + return null; + } + } + + /** + * Get the browser object (IOPCBrowseServerAddressSpace) from the server instance + * + * @return the browser object + */ + public OPCBrowseServerAddressSpace getBrowser() { + try { + return new OPCBrowseServerAddressSpace(getCOMObject()); + } catch (Exception e) { + return null; + } + } + + /** + * Get the common interface if supported + * + * @return the common interface or null if it is not supported + */ + public OPCCommon getCommon() { + try { + return new OPCCommon(getCOMObject()); + } catch (Exception e) { + return null; + } + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCSyncIO.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCSyncIO.java new file mode 100644 index 0000000..2e669d5 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/da/impl/OPCSyncIO.java @@ -0,0 +1,96 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.KeyedResult; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.KeyedResultSet; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Result; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.ResultSet; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.BaseCOMObject; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.Helper; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.Constants; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCDATASOURCE; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCITEMSTATE; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.WriteRequest; + +public class OPCSyncIO extends BaseCOMObject { + public OPCSyncIO(final IJIComObject opcSyncIO) throws JIException { + super(opcSyncIO.queryInterface(Constants.IOPCSyncIO_IID)); + } + + public KeyedResultSet read(final OPCDATASOURCE source, final Integer... serverHandles) throws JIException { + if (serverHandles == null || serverHandles.length == 0) { + return new KeyedResultSet(); + } + + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(0); + + callObject.addInParamAsShort((short) source.id(), JIFlags.FLAG_NULL); + callObject.addInParamAsInt(serverHandles.length, JIFlags.FLAG_NULL); + callObject.addInParamAsArray(new JIArray(serverHandles, true), JIFlags.FLAG_NULL); + + callObject.addOutParamAsObject(new JIPointer(new JIArray(OPCITEMSTATE.getStruct(), null, 1, true)), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + + Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject); + + KeyedResultSet results = new KeyedResultSet(); + JIStruct[] states = (JIStruct[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance(); + Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance(); + + for (int i = 0; i < serverHandles.length; i++) { + results.add(new KeyedResult(serverHandles[i], OPCITEMSTATE.fromStruct(states[i]), errorCodes[i])); + } + + return results; + } + + public ResultSet write(final WriteRequest... requests) throws JIException { + if (requests.length == 0) { + return new ResultSet(); + } + + Integer[] items = new Integer[requests.length]; + JIVariant[] values = new JIVariant[requests.length]; + for (int i = 0; i < requests.length; i++) { + items[i] = requests[i].getServerHandle(); + values[i] = Helper.fixVariant(requests[i].getValue()); + } + + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(1); + + callObject.addInParamAsInt(requests.length, JIFlags.FLAG_NULL); + callObject.addInParamAsArray(new JIArray(items, true), JIFlags.FLAG_NULL); + callObject.addInParamAsArray(new JIArray(values, true), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL); + + Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject); + + Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance(); + + ResultSet results = new ResultSet(); + for (int i = 0; i < requests.length; i++) { + results.add(new Result(requests[i], errorCodes[i])); + } + return results; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/list/ClassDetails.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/list/ClassDetails.java new file mode 100644 index 0000000..8211dfa --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/list/ClassDetails.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.list; + +/** + * Details about an OPC server class + * + * @author Jens Reimann <jens.reimann@th4-systems.com> + * @since 0.1.8 + */ +public class ClassDetails { + private String _clsId; + + private String _progId; + + private String _description; + + public String getClsId() { + return this._clsId; + } + + public void setClsId(final String clsId) { + this._clsId = clsId; + } + + public String getDescription() { + return this._description; + } + + public void setDescription(final String description) { + this._description = description; + } + + public String getProgId() { + return this._progId; + } + + public void setProgId(final String progId) { + this._progId = progId; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/list/Constants.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/list/Constants.java new file mode 100644 index 0000000..fbba66e --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/list/Constants.java @@ -0,0 +1,24 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.list; + +public interface Constants extends org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Constants { + public static final String IOPCServerList_IID = "13486D50-4821-11D2-A494-3CB306C10000"; + + public static final String OPCServerList_CLSID = "13486D51-4821-11D2-A494-3CB306C10000"; +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/list/impl/OPCServerList.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/list/impl/OPCServerList.java new file mode 100644 index 0000000..b11e616 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/dcom/list/impl/OPCServerList.java @@ -0,0 +1,142 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.list.impl; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.BaseCOMObject; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.EnumGUID; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.Helper; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.list.ClassDetails; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.list.Constants; +import rpc.core.UUID; + +import java.net.UnknownHostException; + +/** + * This class implements the IOPCServerList (aka OPCEnum) service. + * + * @author Jens Reimann <jens.reimann@th4-systems.com> + */ +public class OPCServerList extends BaseCOMObject { + public OPCServerList(final IJIComObject listObject) throws JIException { + super(listObject.queryInterface(Constants.IOPCServerList_IID)); + } + + public JIClsid getCLSIDFromProgID(final String progId) throws JIException { + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(2); + + callObject.addInParamAsString(progId, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR); + callObject.addOutParamAsType(UUID.class, JIFlags.FLAG_NULL); + + try { + Object[] result = getCOMObject().call(callObject); + return JIClsid.valueOf(((UUID) result[0]).toString()); + } catch (JIException e) { + if (e.getErrorCode() == 0x800401F3) { + return null; + } + throw e; + } + } + + /** + * Return details about a serve class + * + * @param clsId A server class + * @return ClassDetails + * @throws JIException JIException + */ + public ClassDetails getClassDetails(final JIClsid clsId) throws JIException { + if (clsId == null) { + return null; + } + + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(1); + + callObject.addInParamAsUUID(clsId.getCLSID(), JIFlags.FLAG_NULL); + + callObject.addOutParamAsObject(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)), JIFlags.FLAG_NULL); + callObject.addOutParamAsObject(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)), JIFlags.FLAG_NULL); + + Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject); + + ClassDetails cd = new ClassDetails(); + cd.setClsId(clsId.getCLSID()); + cd.setProgId(((JIString) ((JIPointer) result[0]).getReferent()).getString()); + cd.setDescription(((JIString) ((JIPointer) result[1]).getReferent()).getString()); + + return cd; + } + + /* + HRESULT EnumClassesOfCategories( + [in] ULONG cImplemented, + [in,size_is(cImplemented)] CATID rgcatidImpl[], + [in] ULONG cRequired, + [in,size_is(cRequired)] CATID rgcatidReq[], + [out] IEnumGUID ** ppenumClsid + ); + */ + + public EnumGUID enumClassesOfCategories(final String[] implemented, final String[] required) throws IllegalArgumentException, UnknownHostException, JIException { + UUID[] u1 = new UUID[implemented.length]; + UUID[] u2 = new UUID[required.length]; + + for (int i = 0; i < implemented.length; i++) { + u1[i] = new UUID(implemented[i]); + } + + for (int i = 0; i < required.length; i++) { + u2[i] = new UUID(required[i]); + } + + return enumClassesOfCategories(u1, u2); + } + + public EnumGUID enumClassesOfCategories(final UUID[] implemented, final UUID[] required) throws IllegalArgumentException, UnknownHostException, JIException { + // ** CALL + JICallBuilder callObject = new JICallBuilder(true); + callObject.setOpnum(0); + + // ** IN + callObject.addInParamAsInt(implemented.length, JIFlags.FLAG_NULL); + if (implemented.length == 0) { + callObject.addInParamAsPointer(new JIPointer(null), JIFlags.FLAG_NULL); + } else { + callObject.addInParamAsArray(new JIArray(implemented, true), JIFlags.FLAG_NULL); + } + + callObject.addInParamAsInt(required.length, JIFlags.FLAG_NULL); + if (required.length == 0) { + callObject.addInParamAsPointer(new JIPointer(null), JIFlags.FLAG_NULL); + } else { + callObject.addInParamAsArray(new JIArray(required, true), JIFlags.FLAG_NULL); + } + + // ** OUT + callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL); + + // ** RESULT + Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject); + + return new EnumGUID((IJIComObject) result[0]); + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/common/AlreadyConnectedException.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/common/AlreadyConnectedException.java new file mode 100644 index 0000000..c88b2c1 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/common/AlreadyConnectedException.java @@ -0,0 +1,24 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common; + +public class AlreadyConnectedException extends Exception { + + private static final long serialVersionUID = 1L; + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/common/ConnectionInformation.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/common/ConnectionInformation.java new file mode 100644 index 0000000..ca1201c --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/common/ConnectionInformation.java @@ -0,0 +1,135 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common; + +/** + * Holds the connection information + * + * @author Jens Reimann jens.reimann@th4-systems.com + *

+ * If both clsId and progId are set then clsId + * has priority! + */ +public class ConnectionInformation { + private String _host = "localhost"; + + private String _domain = "localhost"; + + private String _user = ""; + + private String _password = ""; + + private String _clsid = null; + + private String _progId = null; + + public ConnectionInformation() { + super(); + } + + public ConnectionInformation(String host, String clsid, String user, String password) { + this._host = host; + this._clsid = clsid; + this._user = user; + this._password = password; + } + + public ConnectionInformation(final String user, final String password) { + super(); + this._user = user; + this._password = password; + } + + public ConnectionInformation(final ConnectionInformation arg0) { + super(); + this._user = arg0._user; + this._password = arg0._password; + this._domain = arg0._domain; + this._host = arg0._host; + this._progId = arg0._progId; + this._clsid = arg0._clsid; + } + + public String getDomain() { + return this._domain; + } + + /** + * Set the domain of the user used for logging on + * + * @param domain Domain + */ + public void setDomain(final String domain) { + this._domain = domain; + } + + public String getHost() { + return this._host; + } + + /** + * Set the host on which the server is located + * + * @param host The host to use, either an IP address oder hostname + */ + public void setHost(final String host) { + this._host = host; + } + + public String getPassword() { + return this._password; + } + + public void setPassword(final String password) { + this._password = password; + } + + public String getUser() { + return this._user; + } + + public void setUser(final String user) { + this._user = user; + } + + public String getClsid() { + return this._clsid; + } + + public void setClsid(final String clsid) { + this._clsid = clsid; + } + + public String getProgId() { + return this._progId; + } + + public void setProgId(final String progId) { + this._progId = progId; + } + + public String getClsOrProgId() { + if (this._clsid != null) { + return this._clsid; + } else if (this._progId != null) { + return this._progId; + } else { + return null; + } + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/common/NotConnectedException.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/common/NotConnectedException.java new file mode 100644 index 0000000..ac0090f --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/common/NotConnectedException.java @@ -0,0 +1,24 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common; + +public class NotConnectedException extends Exception { + + private static final long serialVersionUID = 1L; + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AccessBase.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AccessBase.java new file mode 100644 index 0000000..44fb7c0 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AccessBase.java @@ -0,0 +1,283 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import lombok.extern.slf4j.Slf4j; +import org.jinterop.dcom.common.JIException; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common.NotConnectedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +@Slf4j +public abstract class AccessBase implements ServerConnectionStateListener { + + private final List stateListeners = new CopyOnWriteArrayList(); + protected Server server = null; + protected Group group = null; + protected boolean active = false; + /** + * Holds the item to callback assignment + */ + protected Map items = new HashMap(); + protected Map itemMap = new HashMap(); + protected Map itemCache = new HashMap(); + protected Map itemSet = new HashMap(); + protected String logTag = null; + protected Logger dataLogger = null; + private boolean bound = false; + private int period = 0; + + public AccessBase(final Server server, final int period) throws IllegalArgumentException, UnknownHostException, NotConnectedException, JIException, DuplicateGroupException { + super(); + this.server = server; + this.period = period; + } + + public AccessBase(final Server server, final int period, final String logTag) { + super(); + this.server = server; + this.period = period; + this.logTag = logTag; + if (this.logTag != null) { + this.dataLogger = LoggerFactory.getLogger("opc.data." + logTag); + } + } + + public boolean isBound() { + return this.bound; + } + + public synchronized void bind() { + if (isBound()) { + return; + } + + this.server.addStateListener(this); + this.bound = true; + } + + public synchronized void unbind() throws JIException { + if (!isBound()) { + return; + } + + this.server.removeStateListener(this); + this.bound = false; + + stop(); + } + + public boolean isActive() { + return this.active; + } + + public void addStateListener(final AccessStateListener listener) { + this.stateListeners.add(listener); + listener.stateChanged(isActive()); + } + + public void removeStateListener(final AccessStateListener listener) { + this.stateListeners.remove(listener); + } + + protected void notifyStateListenersState(final boolean state) { + final List list = new ArrayList(this.stateListeners); + + for (final AccessStateListener listener : list) { + listener.stateChanged(state); + } + } + + protected void notifyStateListenersError(final Throwable t) { + final List list = new ArrayList(this.stateListeners); + + for (final AccessStateListener listener : list) { + listener.errorOccured(t); + } + } + + public int getPeriod() { + return this.period; + } + + public synchronized void addItem(final String itemId, final DataCallback dataCallback) throws JIException, AddFailedException { + if (this.itemSet.containsKey(itemId)) { + return; + } + + this.itemSet.put(itemId, dataCallback); + + if (isActive()) { + realizeItem(itemId); + } + } + + public synchronized void removeItem(final String itemId) { + if (!this.itemSet.containsKey(itemId)) { + return; + } + + this.itemSet.remove(itemId); + + if (isActive()) { + unrealizeItem(itemId); + } + } + + public void connectionStateChanged(final boolean connected) { + try { + if (connected) { + start(); + } else { + stop(); + } + } catch (final Exception e) { + log.error(String.format("Failed to change state (%s)", connected), e); + } + } + + protected synchronized void start() throws JIException, IllegalArgumentException, UnknownHostException, NotConnectedException, DuplicateGroupException { + if (isActive()) { + return; + } + + log.debug("Create a new group"); + this.group = this.server.addGroup(); + this.group.setActive(true); + this.active = true; + + notifyStateListenersState(true); + + realizeAll(); + } + + protected void realizeItem(final String itemId) throws JIException, AddFailedException { + log.debug("Realizing item: {}", itemId); + + final DataCallback dataCallback = this.itemSet.get(itemId); + if (dataCallback == null) { + return; + } + + final Item item = this.group.addItem(itemId); + this.items.put(item, dataCallback); + this.itemMap.put(itemId, item); + } + + protected void unrealizeItem(final String itemId) { + final Item item = this.itemMap.remove(itemId); + this.items.remove(item); + this.itemCache.remove(item); + + try { + this.group.removeItem(itemId); + } catch (final Throwable e) { + log.error(String.format("Failed to unrealize item '%s'", itemId), e); + } + } + + /* + * FIXME: need some perfomance boost: subscribe all in one call + */ + protected void realizeAll() { + for (final String itemId : this.itemSet.keySet()) { + try { + realizeItem(itemId); + } catch (final AddFailedException e) { + Integer rc = e.getErrors().get(itemId); + if (rc == null) { + rc = -1; + } + log.warn(String.format("Failed to add item: %s (%08X)", itemId, rc)); + + } catch (final Exception e) { + log.warn("Failed to realize item: " + itemId, e); + } + } + } + + protected void unrealizeAll() { + this.items.clear(); + this.itemCache.clear(); + try { + this.group.clear(); + } catch (final JIException e) { + log.info("Failed to clear group. No problem if we already lost the connection", e); + } + } + + protected synchronized void stop() throws JIException { + if (!isActive()) { + return; + } + + unrealizeAll(); + + this.active = false; + notifyStateListenersState(false); + + try { + this.group.remove(); + } catch (final Throwable t) { + log.warn("Failed to disable group. No problem if we already lost connection"); + } + this.group = null; + } + + public synchronized void clear() { + this.itemSet.clear(); + this.items.clear(); + this.itemMap.clear(); + this.itemCache.clear(); + } + + protected void updateItem(final Item item, final ItemState itemState) { + if (this.dataLogger != null) { + this.dataLogger.debug("Update item: {}, {}", item.getId(), itemState); + } + + final DataCallback dataCallback = this.items.get(item); + if (dataCallback == null) { + return; + } + + final ItemState cachedState = this.itemCache.get(item); + if (cachedState == null) { + this.itemCache.put(item, itemState); + dataCallback.changed(item, itemState); + } else { + if (!cachedState.equals(itemState)) { + this.itemCache.put(item, itemState); + dataCallback.changed(item, itemState); + } + } + } + + protected void handleError(final Throwable e) { + notifyStateListenersError(e); + this.server.dispose(); + } + +} \ No newline at end of file diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AccessStateListener.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AccessStateListener.java new file mode 100644 index 0000000..622eeb7 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AccessStateListener.java @@ -0,0 +1,24 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +public interface AccessStateListener { + public abstract void stateChanged(boolean state); + + public abstract void errorOccured(Throwable t); +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AddFailedException.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AddFailedException.java new file mode 100644 index 0000000..29894c4 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AddFailedException.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import java.util.HashMap; +import java.util.Map; + +public class AddFailedException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private Map _errors = new HashMap(); + + private Map _items = new HashMap(); + + public AddFailedException(final Map errors, final Map items) { + super(); + this._errors = errors; + this._items = items; + } + + /** + * Get the map of item id to error code + * + * @return the result map containing the failed items + */ + public Map getErrors() { + return this._errors; + } + + /** + * Get the map of item it to item object + * + * @return the result map containing the succeeded items + */ + public Map getItems() { + return this._items; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Async20Access.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Async20Access.java new file mode 100644 index 0000000..5df8b10 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Async20Access.java @@ -0,0 +1,114 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import lombok.extern.slf4j.Slf4j; +import org.jinterop.dcom.common.JIException; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.EventHandler; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.KeyedResult; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.KeyedResultSet; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.ResultSet; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.IOPCDataCallback; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCDATASOURCE; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.ValueData; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl.OPCAsyncIO2; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common.NotConnectedException; + +import java.net.UnknownHostException; + +@Slf4j +public class Async20Access extends AccessBase implements IOPCDataCallback { + + private EventHandler eventHandler = null; + + private boolean initialRefresh = false; + + public Async20Access(final Server server, final int period, final boolean initialRefresh) throws IllegalArgumentException, UnknownHostException, NotConnectedException, JIException, DuplicateGroupException { + super(server, period); + this.initialRefresh = initialRefresh; + } + + public Async20Access(final Server server, final int period, final boolean initialRefresh, final String logTag) throws IllegalArgumentException, UnknownHostException, NotConnectedException, JIException, DuplicateGroupException { + super(server, period, logTag); + this.initialRefresh = initialRefresh; + } + + @Override + protected synchronized void start() throws JIException, IllegalArgumentException, UnknownHostException, NotConnectedException, DuplicateGroupException { + if (isActive()) { + return; + } + + super.start(); + + this.eventHandler = this.group.attach(this); + if (!this.items.isEmpty() && this.initialRefresh) { + final OPCAsyncIO2 async20 = this.group.getAsyncIO20(); + if (async20 == null) { + throw new NotConnectedException(); + } + + this.group.getAsyncIO20().refresh(OPCDATASOURCE.OPC_DS_CACHE, 0); + } + } + + @Override + protected synchronized void stop() throws JIException { + if (!isActive()) { + return; + } + + if (this.eventHandler != null) { + try { + this.eventHandler.detach(); + } catch (final Throwable e) { + log.warn("Failed to detach group", e); + } + + this.eventHandler = null; + } + + super.stop(); + } + + public void cancelComplete(final int transactionId, final int serverGroupHandle) { + } + + public void dataChange(final int transactionId, final int serverGroupHandle, final int masterQuality, final int masterErrorCode, final KeyedResultSet result) { + log.debug("dataChange - transId {}, items: {}", transactionId, result.size()); + + final Group group = this.group; + if (group == null) { + return; + } + + for (final KeyedResult entry : result) { + final Item item = group.findItemByClientHandle(entry.getKey()); + log.debug("Update for '{}'", item.getId()); + updateItem(item, new ItemState(entry.getErrorCode(), entry.getValue().getValue(), entry.getValue().getTimestamp(), entry.getValue().getQuality())); + } + } + + public void readComplete(final int transactionId, final int serverGroupHandle, final int masterQuality, final int masterErrorCode, final KeyedResultSet result) { + log.debug("readComplete - transId {}", transactionId); + } + + public void writeComplete(final int transactionId, final int serverGroupHandle, final int masterErrorCode, final ResultSet result) { + log.debug("writeComplete - transId {}", transactionId); + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AutoReconnectController.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AutoReconnectController.java new file mode 100644 index 0000000..c30e2a6 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AutoReconnectController.java @@ -0,0 +1,183 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import lombok.extern.slf4j.Slf4j; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +@Slf4j +public class AutoReconnectController implements ServerConnectionStateListener { + + private static final int DEFAULT_DELAY = 5 * 1000; + private final Server _server; + private final Set _listeners = new CopyOnWriteArraySet(); + private int _delay; + private AutoReconnectState _state = AutoReconnectState.DISABLED; + + private Thread _connectTask = null; + + public AutoReconnectController(final Server server) { + this(server, DEFAULT_DELAY); + } + + public AutoReconnectController(final Server server, final int delay) { + super(); + setDelay(delay); + + this._server = server; + this._server.addStateListener(this); + } + + public void addListener(final AutoReconnectListener listener) { + if (listener != null) { + this._listeners.add(listener); + listener.stateChanged(this._state); + } + } + + public void removeListener(final AutoReconnectListener listener) { + this._listeners.remove(listener); + } + + protected void notifyStateChange(final AutoReconnectState state) { + this._state = state; + for (AutoReconnectListener listener : this._listeners) { + listener.stateChanged(state); + } + } + + public int getDelay() { + return this._delay; + } + + /** + * Set the reconnect delay. If the delay less than or equal to zero it will be + * the default delay time. + * + * @param delay The delay to use + */ + public void setDelay(int delay) { + if (delay <= 0) { + delay = DEFAULT_DELAY; + } + this._delay = delay; + } + + public synchronized void connect() { + if (isRequested()) { + return; + } + + log.debug("Requesting connection"); + notifyStateChange(AutoReconnectState.DISCONNECTED); + + triggerReconnect(false); + } + + public synchronized void disconnect() { + if (!isRequested()) { + return; + } + + log.debug("Un-Requesting connection"); + + notifyStateChange(AutoReconnectState.DISABLED); + this._server.disconnect(); + } + + public boolean isRequested() { + return this._state != AutoReconnectState.DISABLED; + } + + public synchronized void connectionStateChanged(final boolean connected) { + log.debug("Connection state changed: " + connected); + + if (!connected) { + if (isRequested()) { + notifyStateChange(AutoReconnectState.DISCONNECTED); + triggerReconnect(true); + } + } else { + if (!isRequested()) { + this._server.disconnect(); + } else { + notifyStateChange(AutoReconnectState.CONNECTED); + } + } + } + + private synchronized void triggerReconnect(final boolean wait) { + if (this._connectTask != null) { + log.info("Connect thread already running"); + return; + } + + log.debug("Trigger reconnect"); + + this._connectTask = new Thread(new Runnable() { + + public void run() { + boolean result = false; + try { + result = performReconnect(wait); + } finally { + AutoReconnectController.this._connectTask = null; + log.debug(String.format("performReconnect completed : %s", result)); + if (!result) { + triggerReconnect(true); + } + } + } + }, "OPCReconnectThread"); + this._connectTask.setDaemon(true); + this._connectTask.start(); + } + + private boolean performReconnect(final boolean wait) { + try { + if (wait) { + notifyStateChange(AutoReconnectState.WAITING); + log.debug(String.format("Delaying (%s)...", this._delay)); + Thread.sleep(this._delay); + } + } catch (InterruptedException e) { + } + + if (!isRequested()) { + log.debug("Request canceled during delay"); + return true; + } + + try { + log.debug("Connecting to server"); + notifyStateChange(AutoReconnectState.CONNECTING); + synchronized (this) { + this._server.connect(); + return true; + } + // CONNECTED state will be set by server callback + } catch (Throwable e) { + log.info("Re-connect failed", e); + notifyStateChange(AutoReconnectState.DISCONNECTED); + return false; + } + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AutoReconnectListener.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AutoReconnectListener.java new file mode 100644 index 0000000..67eceee --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AutoReconnectListener.java @@ -0,0 +1,22 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +public interface AutoReconnectListener { + public abstract void stateChanged(AutoReconnectState state); +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AutoReconnectState.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AutoReconnectState.java new file mode 100644 index 0000000..05e18c5 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/AutoReconnectState.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +/** + * A state for the auto-reconnect controller + * + * @author Jens Reimann + */ +public enum AutoReconnectState { + /** + * Auto reconnect is disabled. + */ + DISABLED, + /** + * Auto reconnect is enabled, but the connection is currently not established. + */ + DISCONNECTED, + /** + * Auto reconnect is enabled, the connection is not established and the controller + * is currently waiting the delay until it will reconnect. + */ + WAITING, + /** + * Auto reconnect is enabled, the connection is not established but the controller + * currently tries to establish the connection. + */ + CONNECTING, + /** + * Auto reconnect is enabled and the connection is established. + */ + CONNECTED +} \ No newline at end of file diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/DataCallback.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/DataCallback.java new file mode 100644 index 0000000..d56bced --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/DataCallback.java @@ -0,0 +1,22 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +public interface DataCallback { + void changed(Item item, ItemState itemState); +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/DuplicateGroupException.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/DuplicateGroupException.java new file mode 100644 index 0000000..0e90299 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/DuplicateGroupException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +public class DuplicateGroupException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ErrorMessageResolver.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ErrorMessageResolver.java new file mode 100644 index 0000000..2e675c6 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ErrorMessageResolver.java @@ -0,0 +1,68 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import lombok.extern.slf4j.Slf4j; +import org.jinterop.dcom.common.JIException; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.OPCCommon; + +import java.util.HashMap; +import java.util.Map; + +/** + * An error message resolver that will lookup the error code using the + * server interface and will cache the result locally. + * + * @author Jens Reimann + */ +@Slf4j +public class ErrorMessageResolver { + + private final Map _messageCache = new HashMap(); + private OPCCommon _opcCommon = null; + private int _localeId = 0; + + public ErrorMessageResolver(final OPCCommon opcCommon, final int localeId) { + super(); + this._opcCommon = opcCommon; + this._localeId = localeId; + } + + /** + * Get an error message from an error code + * + * @param errorCode The error code to look up + * @return the error message or null if no message could be looked up + */ + public synchronized String getMessage(final int errorCode) { + String message = this._messageCache.get(Integer.valueOf(errorCode)); + + if (message == null) { + try { + message = this._opcCommon.getErrorString(errorCode, this._localeId); + log.info(String.format("Resolved %08X to '%s'", errorCode, message)); + } catch (JIException e) { + log.warn(String.format("Failed to resolve error code for %08X", errorCode), e); + } + if (message != null) { + this._messageCache.put(errorCode, message); + } + } + return message; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Group.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Group.java new file mode 100644 index 0000000..5dd55cb --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Group.java @@ -0,0 +1,358 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import lombok.extern.slf4j.Slf4j; +import org.jinterop.dcom.common.JIException; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.*; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl.OPCAsyncIO2; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl.OPCGroupStateMgt; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl.OPCItemMgt; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl.OPCSyncIO; + +import java.net.UnknownHostException; +import java.util.*; + +@Slf4j +public class Group { + + private static Random _random = new Random(); + private final int _serverHandle; + private final Map _itemHandleMap = new HashMap(); + private final Map _itemMap = new HashMap(); + private final Map _itemClientMap = new HashMap(); + private Server _server = null; + private OPCGroupStateMgt _group = null; + private OPCItemMgt _items = null; + private OPCSyncIO _syncIO = null; + + Group(final Server server, final int serverHandle, final OPCGroupStateMgt group) throws IllegalArgumentException, UnknownHostException, JIException { + log.debug("Creating new group instance with COM group " + group); + this._server = server; + this._serverHandle = serverHandle; + this._group = group; + this._items = group.getItemManagement(); + this._syncIO = group.getSyncIO(); + } + + /** + * remove the group from the server + * + * @throws JIException JIException + */ + public void remove() throws JIException { + this._server.removeGroup(this, true); + } + + public boolean isActive() throws JIException { + return this._group.getState().isActive(); + } + + public void setActive(final boolean state) throws JIException { + this._group.setState(null, state, null, null, null, null); + } + + /** + * Get the group name from the server + * + * @return The group name fetched from the server + * @throws JIException JIException + */ + public String getName() throws JIException { + return this._group.getState().getName(); + } + + /** + * Change the group name + * + * @param name the new name of the group + * @throws JIException JIException + */ + public void setName(final String name) throws JIException { + this._group.setName(name); + } + + /** + * Add a single item. Actually calls {@link #addItems(String[])} with only + * one paraemter + * + * @param item The item to add + * @return The added item + * @throws JIException The add operation failed + * @throws AddFailedException The item was not added due to an error + */ + public Item addItem(final String item) throws JIException, AddFailedException { + Map items = addItems(item); + return items.get(item); + } + + /** + * Validate item ids and get additional information to them. + *
+ * According to the OPC specification you should first validate + * the items and the add them. The spec also says that when a server + * lets the item pass validation it must also let them pass the add operation. + * + * @param items The items to validate + * @return A result map of item id to result information (including error code). + * @throws JIException JIException + */ + public synchronized Map> validateItems(final String... items) throws JIException { + OPCITEMDEF[] defs = new OPCITEMDEF[items.length]; + for (int i = 0; i < items.length; i++) { + defs[i] = new OPCITEMDEF(); + defs[i].setItemID(items[i]); + } + + KeyedResultSet result = this._items.validate(defs); + + Map> resultMap = new HashMap>(); + for (KeyedResult resultEntry : result) { + resultMap.put(resultEntry.getKey().getItemID(), new Result(resultEntry.getValue(), resultEntry.getErrorCode())); + } + + return resultMap; + } + + /** + * Add new items to the group + * + * @param items The items (by string id) to add + * @return A result map of id to item object + * @throws JIException The add operation completely failed. No item was added. + * @throws AddFailedException If one or more item could not be added. Item without error where added. + */ + public synchronized Map addItems(final String... items) throws JIException, AddFailedException { + // Find which items we already have + Map handles = findItems(items); + + List foundItems = new ArrayList(items.length); + List missingItems = new ArrayList(); + + // separate missing items from the found ones + for (Map.Entry entry : handles.entrySet()) { + if (entry.getValue() == null) { + missingItems.add(entry.getKey()); + } else { + foundItems.add(entry.getValue()); + } + } + + // now fetch missing items from OPC server + Set newClientHandles = new HashSet(); + OPCITEMDEF[] itemDef = new OPCITEMDEF[missingItems.size()]; + for (int i = 0; i < missingItems.size(); i++) { + OPCITEMDEF def = new OPCITEMDEF(); + def.setItemID(missingItems.get(i)); + def.setActive(true); + + Integer clientHandle; + do { + clientHandle = _random.nextInt(); + } while (this._itemClientMap.containsKey(clientHandle) || newClientHandles.contains(clientHandle)); + newClientHandles.add(clientHandle); + def.setClientHandle(clientHandle); + + itemDef[i] = def; + } + + // check the result and add new items + Map failedItems = new HashMap(); + KeyedResultSet result = this._items.add(itemDef); + int i = 0; + for (KeyedResult entry : result) { + if (entry.getErrorCode() == 0) { + Item item = new Item(this, entry.getValue().getServerHandle(), itemDef[i].getClientHandle(), entry.getKey().getItemID()); + addItem(item); + foundItems.add(item.getServerHandle()); + } else { + failedItems.put(entry.getKey().getItemID(), entry.getErrorCode()); + } + i++; + } + + // if we have failed items then throw an exception with the result + if (failedItems.size() != 0) { + throw new AddFailedException(failedItems, findItems(foundItems)); + } + + // simply return the result in case of success + return findItems(foundItems); + } + + private synchronized void addItem(final Item item) { + log.debug(String.format("Adding item: '%s', %d", item.getId(), item.getServerHandle())); + + this._itemHandleMap.put(item.getId(), item.getServerHandle()); + this._itemMap.put(item.getServerHandle(), item); + this._itemClientMap.put(item.getClientHandle(), item); + } + + private synchronized void removeItem(final Item item) { + this._itemHandleMap.remove(item.getId()); + this._itemMap.remove(item.getServerHandle()); + this._itemClientMap.remove(item.getClientHandle()); + } + + protected Item getItemByOPCItemId(final String opcItemId) { + Integer serverHandle = this._itemHandleMap.get(opcItemId); + if (serverHandle == null) { + log.debug(String.format("Failed to locate item with id '%s'", opcItemId)); + return null; + } + log.debug(String.format("Item '%s' has server id '%d'", opcItemId, serverHandle)); + return this._itemMap.get(serverHandle); + } + + private synchronized Map findItems(final String[] items) { + Map data = new HashMap(); + + for (int i = 0; i < items.length; i++) { + data.put(items[i], this._itemHandleMap.get(items[i])); + } + + return data; + } + + private synchronized Map findItems(final Collection handles) { + Map itemMap = new HashMap(); + for (Integer i : handles) { + Item item = this._itemMap.get(i); + if (item != null) { + itemMap.put(item.getId(), item); + } + } + return itemMap; + } + + protected void checkItems(final Item[] items) { + for (Item item : items) { + if (item.getGroup() != this) { + throw new IllegalArgumentException("Item doesn't belong to this group"); + } + } + } + + public void setActive(final boolean state, final Item... items) throws JIException { + checkItems(items); + + Integer[] handles = new Integer[items.length]; + for (int i = 0; i < items.length; i++) { + handles[i] = items[i].getServerHandle(); + } + + this._items.setActiveState(state, handles); + } + + protected Integer[] getServerHandles(final Item[] items) { + checkItems(items); + + Integer[] handles = new Integer[items.length]; + + for (int i = 0; i < items.length; i++) { + handles[i] = items[i].getServerHandle(); + } + + return handles; + } + + public synchronized Map write(final WriteRequest... requests) throws JIException { + Item[] items = new Item[requests.length]; + + for (int i = 0; i < requests.length; i++) { + items[i] = requests[i].getItem(); + } + + Integer[] handles = getServerHandles(items); + + org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.WriteRequest[] wr = new org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.WriteRequest[items.length]; + for (int i = 0; i < items.length; i++) { + wr[i] = new org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.WriteRequest(handles[i], requests[i].getValue()); + } + + ResultSet resultSet = this._syncIO.write(wr); + + Map result = new HashMap(); + for (int i = 0; i < requests.length; i++) { + Result entry = resultSet.get(i); + result.put(requests[i].getItem(), entry.getErrorCode()); + } + + return result; + } + + public synchronized Map read(final boolean device, final Item... items) throws JIException { + Integer[] handles = getServerHandles(items); + + KeyedResultSet states = this._syncIO.read(device ? OPCDATASOURCE.OPC_DS_DEVICE : OPCDATASOURCE.OPC_DS_CACHE, handles); + + Map data = new HashMap(); + for (KeyedResult entry : states) { + Item item = this._itemMap.get(entry.getKey()); + ItemState state = new ItemState(entry.getErrorCode(), entry.getValue().getValue(), entry.getValue().getTimestamp().asCalendar(), entry.getValue().getQuality()); + data.put(item, state); + } + return data; + } + + public Server getServer() { + return this._server; + } + + public synchronized void clear() throws JIException { + Integer[] handles = this._itemMap.keySet().toArray(new Integer[0]); + try { + this._items.remove(handles); + } finally { + // in any case clear our maps + this._itemHandleMap.clear(); + this._itemMap.clear(); + this._itemClientMap.clear(); + } + } + + public synchronized OPCAsyncIO2 getAsyncIO20() { + return this._group.getAsyncIO2(); + } + + public synchronized EventHandler attach(final IOPCDataCallback dataCallback) throws JIException { + return this._group.attach(dataCallback); + } + + public Item findItemByClientHandle(final int clientHandle) { + return this._itemClientMap.get(clientHandle); + } + + public int getServerHandle() { + return this._serverHandle; + } + + public synchronized void removeItem(final String opcItemId) throws IllegalArgumentException, UnknownHostException, JIException { + log.debug(String.format("Removing item '%s'", opcItemId)); + Item item = getItemByOPCItemId(opcItemId); + if (item != null) { + this._group.getItemManagement().remove(item.getServerHandle()); + removeItem(item); + log.debug(String.format("Removed item '%s'", opcItemId)); + } else { + log.warn(String.format("Unable to find item '%s'", opcItemId)); + } + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Item.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Item.java new file mode 100644 index 0000000..ba90b21 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Item.java @@ -0,0 +1,71 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import lombok.extern.slf4j.Slf4j; +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.JIVariant; + +@Slf4j +public class Item { + + private Group _group = null; + + private int _serverHandle = 0; + + private int _clientHandle = 0; + + private String _id = null; + + Item(final Group group, final int serverHandle, final int clientHandle, final String id) { + super(); + log.debug(String.format("Adding new item '%s' (0x%08X) for group %s", id, serverHandle, group.toString())); + this._group = group; + this._serverHandle = serverHandle; + this._clientHandle = clientHandle; + this._id = id; + } + + public Group getGroup() { + return this._group; + } + + public int getServerHandle() { + return this._serverHandle; + } + + public int getClientHandle() { + return this._clientHandle; + } + + public String getId() { + return this._id; + } + + public void setActive(final boolean state) throws JIException { + this._group.setActive(state, this); + } + + public ItemState read(final boolean device) throws JIException { + return this._group.read(device, this).get(this); + } + + public Integer write(final JIVariant value) throws JIException { + return this._group.write(new WriteRequest[]{new WriteRequest(this, value)}).get(this); + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ItemState.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ItemState.java new file mode 100644 index 0000000..5aa277d --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ItemState.java @@ -0,0 +1,131 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import org.jinterop.dcom.core.JIVariant; + +import java.util.Calendar; + +public class ItemState { + private int _errorCode = 0; + + private JIVariant _value = null; + + private Calendar _timestamp = null; + + private Short _quality = null; + + public ItemState(final int errorCode, final JIVariant value, final Calendar timestamp, final Short quality) { + super(); + this._errorCode = errorCode; + this._value = value; + this._timestamp = timestamp; + this._quality = quality; + } + + public ItemState() { + super(); + } + + @Override + public String toString() { + return String.format("Value: %s, Timestamp: %tc, Quality: %s, ErrorCode: %08x", this._value, this._timestamp, this._quality, this._errorCode); + } + + public Short getQuality() { + return this._quality; + } + + public void setQuality(final Short quality) { + this._quality = quality; + } + + public Calendar getTimestamp() { + return this._timestamp; + } + + public void setTimestamp(final Calendar timestamp) { + this._timestamp = timestamp; + } + + public JIVariant getValue() { + return this._value; + } + + public void setValue(final JIVariant value) { + this._value = value; + } + + public int getErrorCode() { + return this._errorCode; + } + + public void setErrorCode(final int errorCode) { + this._errorCode = errorCode; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + this._errorCode; + result = PRIME * result + (this._quality == null ? 0 : this._quality.hashCode()); + result = PRIME * result + (this._timestamp == null ? 0 : this._timestamp.hashCode()); + result = PRIME * result + (this._value == null ? 0 : this._value.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ItemState other = (ItemState) obj; + if (this._errorCode != other._errorCode) { + return false; + } + if (this._quality == null) { + if (other._quality != null) { + return false; + } + } else if (!this._quality.equals(other._quality)) { + return false; + } + if (this._timestamp == null) { + if (other._timestamp != null) { + return false; + } + } else if (!this._timestamp.equals(other._timestamp)) { + return false; + } + if (this._value == null) { + if (other._value != null) { + return false; + } + } else if (!this._value.equals(other._value)) { + return false; + } + return true; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Server.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Server.java new file mode 100644 index 0000000..2434450 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/Server.java @@ -0,0 +1,413 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import lombok.extern.slf4j.Slf4j; +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.JIClsid; +import org.jinterop.dcom.core.JIComServer; +import org.jinterop.dcom.core.JIProgId; +import org.jinterop.dcom.core.JISession; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCNAMESPACETYPE; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCSERVERSTATUS; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl.OPCBrowseServerAddressSpace; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl.OPCGroupStateMgt; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl.OPCServer; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common.AlreadyConnectedException; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common.ConnectionInformation; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common.NotConnectedException; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da.browser.FlatBrowser; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da.browser.TreeBrowser; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledExecutorService; + +@Slf4j +public class Server { + + private final ConnectionInformation connectionInformation; + private final Map groups = new HashMap<>(16); + private final List stateListeners = new CopyOnWriteArrayList<>(); + private final ScheduledExecutorService scheduler; + private JISession session; + private JIComServer comServer; + private OPCServer server; + private boolean defaultActive = true; + private int defaultUpdateRate = 1000; + private Integer defaultTimeBias; + private Float defaultPercentDeadband; + private int defaultLocaleID = 0; + private ErrorMessageResolver errorMessageResolver; + + public Server(final ConnectionInformation connectionInformation, + final ScheduledExecutorService scheduler) { + super(); + this.connectionInformation = connectionInformation; + this.scheduler = scheduler; + } + + /** + * Gets the scheduler for the server. Note that this scheduler might get + * blocked for a short time if the connection breaks. It should not be used + * for time critical operations. + * + * @return the scheduler for the server + */ + public ScheduledExecutorService getScheduler() { + return this.scheduler; + } + + protected synchronized boolean isConnected() { + return this.session != null; + } + + public synchronized void connect() throws IllegalArgumentException, UnknownHostException, JIException, AlreadyConnectedException { + if (isConnected()) { + throw new AlreadyConnectedException(); + } + + final int socketTimeout = Integer.getInteger("rpc.socketTimeout", 0); + log.debug(String.format("Socket timeout: %s ", socketTimeout)); + + try { + if (this.connectionInformation.getClsid() != null) { + this.session = JISession.createSession( + this.connectionInformation.getDomain(), + this.connectionInformation.getUser(), + this.connectionInformation.getPassword()); + this.session.setGlobalSocketTimeout(socketTimeout); + this.session.useSessionSecurity(true); + this.comServer = new JIComServer( + JIClsid.valueOf(this.connectionInformation.getClsid()), + this.connectionInformation.getHost(), this.session); + } else if (this.connectionInformation.getProgId() != null) { + this.session = JISession.createSession( + this.connectionInformation.getDomain(), + this.connectionInformation.getUser(), + this.connectionInformation.getPassword()); + this.session.setGlobalSocketTimeout(socketTimeout); + this.comServer = new JIComServer( + JIProgId.valueOf(this.connectionInformation.getProgId()), + this.connectionInformation.getHost(), this.session); + } else { + throw new IllegalArgumentException("Neither clsid nor progid is valid!"); + } + + this.server = new OPCServer(this.comServer.createInstance()); + this.errorMessageResolver = new ErrorMessageResolver( + this.server.getCommon(), this.defaultLocaleID); + } catch (final UnknownHostException e) { + log.error("Unknown host when connecting to server", e); + cleanup(); + throw e; + } catch (final JIException e) { + log.error("Failed to connect to server", e); + cleanup(); + throw e; + } catch (final Throwable e) { + log.error("Unknown error", e); + cleanup(); + throw new RuntimeException(e); + } + + notifyConnectionStateChange(true); + } + + /** + * cleanup after the connection is closed + */ + protected void cleanup() { + log.debug("Destroying DCOM session..."); + final JISession destructSession = this.session; + final Thread destructor = new Thread(new Runnable() { + + public void run() { + final long ts = System.currentTimeMillis(); + try { + log.debug("Starting destruction of DCOM session"); + JISession.destroySession(destructSession); + log.debug("Destructed DCOM session"); + } catch (final Throwable e) { + log.error("Failed to destruct DCOM session", e); + } + } + }, "UtgardSessionDestructor"); + destructor.setName("OPCSessionDestructor"); + destructor.setDaemon(true); + destructor.start(); + log.debug("Destroying DCOM session... forked"); + + this.errorMessageResolver = null; + this.session = null; + this.comServer = null; + this.server = null; + + this.groups.clear(); + } + + /** + * Disconnect the connection if it is connected + */ + public synchronized void disconnect() { + if (!isConnected()) { + return; + } + + try { + notifyConnectionStateChange(false); + } catch (final Throwable t) { + } + + cleanup(); + } + + /** + * Dispose the connection in the case of an error + */ + public void dispose() { + disconnect(); + } + + protected synchronized Group getGroup(final OPCGroupStateMgt groupMgt) throws JIException, IllegalArgumentException, UnknownHostException { + final Integer serverHandle = groupMgt.getState().getServerHandle(); + if (this.groups.containsKey(serverHandle)) { + return this.groups.get(serverHandle); + } else { + final Group group = new Group(this, serverHandle, groupMgt); + this.groups.put(serverHandle, group); + return group; + } + } + + /** + * Add a new named group to the server + * + * @param name The name of the group to use. Must be unique or + * null so that the server creates a unique name. + * @return The new group + * @throws NotConnectedException If the server is not connected using {@link Server#connect()} + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws JIException JIException + * @throws DuplicateGroupException If a group with this name already exists + */ + public synchronized Group addGroup(final String name) throws NotConnectedException, IllegalArgumentException, UnknownHostException, JIException, DuplicateGroupException { + if (!isConnected()) { + throw new NotConnectedException(); + } + + try { + final OPCGroupStateMgt groupMgt = this.server.addGroup(name, + this.defaultActive, this.defaultUpdateRate, 0, + this.defaultTimeBias, this.defaultPercentDeadband, + this.defaultLocaleID); + return getGroup(groupMgt); + } catch (final JIException e) { + if (e.getErrorCode() == 0xC004000C) { + throw new DuplicateGroupException(); + } + throw e; + } + } + + /** + * Add a new group and let the server generate a group name + *

+ * Actually this method only calls {@link Server#addGroup(String)} with + * null as parameter. + * + * @return the new group + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws NotConnectedException NotConnectedException + * @throws JIException JIException + * @throws DuplicateGroupException DuplicateGroupException + */ + public Group addGroup() throws IllegalArgumentException, + UnknownHostException, NotConnectedException, JIException, + DuplicateGroupException { + return addGroup(null); + } + + /** + * Find a group by its name + * + * @param name The name to look for + * @return The group + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws JIException JIException + * @throws UnknownGroupException If the group was not found + * @throws NotConnectedException If the server is not connected + */ + public Group findGroup(final String name) throws IllegalArgumentException, UnknownHostException, JIException, UnknownGroupException, NotConnectedException { + if (!isConnected()) { + throw new NotConnectedException(); + } + + try { + final OPCGroupStateMgt groupMgt = this.server.getGroupByName(name); + return getGroup(groupMgt); + } catch (final JIException e) { + switch (e.getErrorCode()) { + case 0x80070057: + throw new UnknownGroupException(name); + default: + throw e; + } + } + } + + public int getDefaultLocaleID() { + return this.defaultLocaleID; + } + + public void setDefaultLocaleID(final int defaultLocaleID) { + this.defaultLocaleID = defaultLocaleID; + } + + public Float getDefaultPercentDeadband() { + return this.defaultPercentDeadband; + } + + public void setDefaultPercentDeadband(final Float defaultPercentDeadband) { + this.defaultPercentDeadband = defaultPercentDeadband; + } + + public Integer getDefaultTimeBias() { + return this.defaultTimeBias; + } + + public void setDefaultTimeBias(final Integer defaultTimeBias) { + this.defaultTimeBias = defaultTimeBias; + } + + public int getDefaultUpdateRate() { + return this.defaultUpdateRate; + } + + public void setDefaultUpdateRate(final int defaultUpdateRate) { + this.defaultUpdateRate = defaultUpdateRate; + } + + public boolean isDefaultActive() { + return this.defaultActive; + } + + public void setDefaultActive(final boolean defaultActive) { + this.defaultActive = defaultActive; + } + + /** + * Get the flat browser + * + * @return The flat browser or null if the functionality is not + * supported + */ + public FlatBrowser getFlatBrowser() { + final OPCBrowseServerAddressSpace browser = this.server.getBrowser(); + if (browser == null) { + return null; + } + + return new FlatBrowser(browser); + } + + /** + * Get the tree browser + * + * @return The tree browser or null if the functionality is not + * supported + * @throws JIException JIException + */ + public TreeBrowser getTreeBrowser() throws JIException { + final OPCBrowseServerAddressSpace browser = this.server.getBrowser(); + if (browser == null) { + return null; + } + + if (browser.queryOrganization() != OPCNAMESPACETYPE.OPC_NS_HIERARCHIAL) { + return null; + } + + return new TreeBrowser(browser); + } + + public synchronized String getErrorMessage(final int errorCode) { + if (this.errorMessageResolver == null) { + return String.format("Unknown error (%08X)", errorCode); + } + + // resolve message + final String message = this.errorMessageResolver.getMessage(errorCode); + + // and return if successfull + if (message != null) { + return message; + } + + // return default message + return String.format("Unknown error (%08X)", errorCode); + } + + public synchronized void addStateListener( + final ServerConnectionStateListener listener) { + this.stateListeners.add(listener); + listener.connectionStateChanged(isConnected()); + } + + public synchronized void removeStateListener( + final ServerConnectionStateListener listener) { + this.stateListeners.remove(listener); + } + + protected void notifyConnectionStateChange(final boolean connected) { + final List list = new ArrayList( + this.stateListeners); + for (final ServerConnectionStateListener listener : list) { + listener.connectionStateChanged(connected); + } + } + + public OPCSERVERSTATUS getServerState(final int timeout) throws Throwable { + return new ServerStateOperation(this.server).getServerState(timeout); + } + + public OPCSERVERSTATUS getServerState() { + try { + return getServerState(2500); + } catch (final Throwable e) { + log.error("Server connection failed", e); + dispose(); + return null; + } + } + + public void removeGroup(final Group group, final boolean force) + throws JIException { + if (this.groups.containsKey(group.getServerHandle())) { + this.server.removeGroup(group.getServerHandle(), force); + this.groups.remove(group.getServerHandle()); + } + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerConnectionStateListener.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerConnectionStateListener.java new file mode 100644 index 0000000..766ad43 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerConnectionStateListener.java @@ -0,0 +1,22 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +public interface ServerConnectionStateListener { + public abstract void connectionStateChanged(boolean connected); +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerStateListener.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerStateListener.java new file mode 100644 index 0000000..886cc13 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerStateListener.java @@ -0,0 +1,24 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCSERVERSTATUS; + +public interface ServerStateListener { + public void stateUpdate(OPCSERVERSTATUS state); +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerStateOperation.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerStateOperation.java new file mode 100644 index 0000000..fe421e0 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerStateOperation.java @@ -0,0 +1,107 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import lombok.extern.slf4j.Slf4j; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCSERVERSTATUS; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl.OPCServer; + +/** + * A server state operation which can be interruped + * + * @author Jens Reimann + */ +@Slf4j +public class ServerStateOperation implements Runnable { + + public OPCSERVERSTATUS _serverStatus = null; + + public OPCServer _server; + + public Throwable _error; + + public Object _lock = new Object(); + + public boolean _running = false; + + public ServerStateOperation(final OPCServer server) { + super(); + this._server = server; + } + + /** + * Perform the operation. + *

+ * This method will block until either the serve state has been aquired or the + * timeout triggers cancels the call. + *

+ */ + public void run() { + synchronized (this._lock) { + this._running = true; + } + try { + this._serverStatus = this._server.getStatus(); + synchronized (this._lock) { + this._running = false; + this._lock.notify(); + } + } catch (Throwable e) { + log.info("Failed to get server state", e); + this._error = e; + this._running = false; + synchronized (this._lock) { + this._lock.notify(); + } + } + + } + + /** + * Get the server state with a timeout. + * + * @param timeout the timeout in ms + * @return the server state or null if the server is not set. + * @throws Throwable any error that occurred + */ + public OPCSERVERSTATUS getServerState(final int timeout) throws Throwable { + if (this._server == null) { + log.debug("No connection to server. Skipping..."); + return null; + } + + Thread t = new Thread(this, "OPCServerStateReader"); + + synchronized (this._lock) { + t.start(); + this._lock.wait(timeout); + if (this._running) { + log.warn("State operation still running. Interrupting..."); + t.interrupt(); + throw new InterruptedException("Interrupted getting server state"); + } + } + if (this._error != null) { + log.warn("An error occurred while getting server state", this._error); + throw this._error; + } + + return this._serverStatus; + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerStateReader.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerStateReader.java new file mode 100644 index 0000000..76a9049 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/ServerStateReader.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import lombok.extern.slf4j.Slf4j; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCSERVERSTATUS; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class ServerStateReader { + + private final List _listeners = new CopyOnWriteArrayList(); + private Server _server = null; + private ScheduledExecutorService _scheduler = null; + private ScheduledFuture _job = null; + + public ServerStateReader(final Server server) { + super(); + this._server = server; + this._scheduler = this._server.getScheduler(); + } + + /** + * Create a new server state reader. Please note that the scheduler might get + * blocked for a short period of time in case of a connection failure! + * + * @param server the server to check + * @param scheduler the scheduler to use + */ + public ServerStateReader(final Server server, final ScheduledExecutorService scheduler) { + super(); + this._server = server; + this._scheduler = scheduler; + } + + public synchronized void start() { + if (this._job != null) { + return; + } + + this._job = this._scheduler.scheduleAtFixedRate(new Runnable() { + + public void run() { + once(); + } + }, 1000, 1000, TimeUnit.MILLISECONDS); + } + + public synchronized void stop() { + this._job.cancel(false); + this._job = null; + } + + protected void once() { + log.debug("Reading server state"); + + final OPCSERVERSTATUS state = this._server.getServerState(); + + for (final ServerStateListener listener : new ArrayList(this._listeners)) { + listener.stateUpdate(state); + } + } + + public void addListener(final ServerStateListener listener) { + this._listeners.add(listener); + } + + public void removeListener(final ServerStateListener listener) { + this._listeners.remove(listener); + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/SyncAccess.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/SyncAccess.java new file mode 100644 index 0000000..4bbe561 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/SyncAccess.java @@ -0,0 +1,99 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import lombok.extern.slf4j.Slf4j; +import org.jinterop.dcom.common.JIException; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common.NotConnectedException; + +import java.net.UnknownHostException; +import java.util.Map; + +@Slf4j +public class SyncAccess extends AccessBase implements Runnable { + + private Thread runner = null; + + private Throwable lastError = null; + + public SyncAccess(final Server server, final int period) throws IllegalArgumentException, UnknownHostException, NotConnectedException, JIException, DuplicateGroupException { + super(server, period); + } + + public SyncAccess(final Server server, final int period, final String logTag) throws IllegalArgumentException, UnknownHostException, NotConnectedException, JIException, DuplicateGroupException { + super(server, period, logTag); + } + + public void run() { + while (this.active) { + try { + runOnce(); + if (this.lastError != null) { + this.lastError = null; + handleError(null); + } + } catch (Throwable e) { + log.error("Sync read failed", e); + handleError(e); + this.server.disconnect(); + } + + try { + Thread.sleep(getPeriod()); + } catch (InterruptedException e) { + } + } + } + + protected void runOnce() throws JIException { + if (!this.active || this.group == null) { + return; + } + + Map result; + + // lock only this section since we could get into a deadlock otherwise + // calling updateItem + synchronized (this) { + Item[] items = this.items.keySet().toArray(new Item[this.items.size()]); + result = this.group.read(false, items); + } + + for (Map.Entry entry : result.entrySet()) { + updateItem(entry.getKey(), entry.getValue()); + } + + } + + @Override + protected synchronized void start() throws JIException, IllegalArgumentException, UnknownHostException, NotConnectedException, DuplicateGroupException { + super.start(); + + this.runner = new Thread(this, "UtgardSyncReader"); + this.runner.setDaemon(true); + this.runner.start(); + } + + @Override + protected synchronized void stop() throws JIException { + super.stop(); + + this.runner = null; + this.items.clear(); + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/UnknownGroupException.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/UnknownGroupException.java new file mode 100644 index 0000000..988012a --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/UnknownGroupException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +public class UnknownGroupException extends Exception { + /** + * + */ + private static final long serialVersionUID = 1L; + private String _name = null; + + public UnknownGroupException(final String name) { + super(); + this._name = name; + } + + public String getName() { + return this._name; + } + + public void setName(final String name) { + this._name = name; + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/WriteRequest.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/WriteRequest.java new file mode 100644 index 0000000..c6fc703 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/WriteRequest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da; + +import org.jinterop.dcom.core.JIVariant; + +public class WriteRequest { + private Item _item = null; + + private JIVariant _value = null; + + public WriteRequest(final Item item, final JIVariant value) { + super(); + this._item = item; + this._value = value; + } + + public Item getItem() { + return this._item; + } + + public JIVariant getValue() { + return this._value; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/Access.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/Access.java new file mode 100644 index 0000000..8ad63bc --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/Access.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da.browser; + +public enum Access { + READ(1), + WRITE(2); + + private int _code = 0; + + private Access(final int code) { + this._code = code; + } + + public int getCode() { + return this._code; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/BaseBrowser.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/BaseBrowser.java new file mode 100644 index 0000000..2f7f073 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/BaseBrowser.java @@ -0,0 +1,116 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da.browser; + +import lombok.extern.slf4j.Slf4j; +import org.jinterop.dcom.common.JIException; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.impl.EnumString; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCBROWSETYPE; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl.OPCBrowseServerAddressSpace; + +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.EnumSet; + +/** + * A class implementing base browsing + * + * @author Jens Reimann + */ +@Slf4j +public class BaseBrowser { + + protected OPCBrowseServerAddressSpace _browser; + + /** + * The batch size is the number of entries that will be requested with one call + * from the server. Sometimes too big batch sizes will cause an exception. And + * smaller batch sizes degrade perfomance. The default is set by {@link EnumString#DEFAULT_BATCH_SIZE} + * and can be overridden by the java property openscada.dcom.enum-batch-size. + */ + protected int _batchSize; + + public BaseBrowser(final OPCBrowseServerAddressSpace browser) { + this(browser, EnumString.DEFAULT_BATCH_SIZE); + } + + public BaseBrowser(final OPCBrowseServerAddressSpace browser, final int batchSize) { + super(); + this._browser = browser; + this._batchSize = batchSize; + } + + /** + * Get the batch size + * + * @return the current batch size + */ + public int getBatchSize() { + return this._batchSize; + } + + /** + * Set the batch size + * + * @param batchSize The new batch size + */ + public void setBatchSize(final int batchSize) { + this._batchSize = batchSize; + } + + /** + * Perform the browse operation. + * + * @param type OPCBROWSETYPE + * @param filterCriteria Filter Criteria + * @param accessMask Access Mask + * @param variantType Variant Type + * @return The browse result + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws JIException JIException + */ + protected Collection browse(final OPCBROWSETYPE type, final String filterCriteria, final EnumSet accessMask, final int variantType) throws IllegalArgumentException, UnknownHostException, JIException { + int accessMaskValue = 0; + + if (accessMask.contains(Access.READ)) { + accessMaskValue |= Access.READ.getCode(); + } + if (accessMask.contains(Access.WRITE)) { + accessMaskValue |= Access.WRITE.getCode(); + } + + log.debug("Browsing with a batch size of " + this._batchSize); + + return this._browser.browse(type, filterCriteria, accessMaskValue, variantType).asCollection(this._batchSize); + } + + /** + * Browse the access paths for one item. + * + * @param itemId The item ID to look up the access paths + * @return The collection of the access paths + * @throws JIException JIException + * @throws UnknownHostException UnknownHostException + * @throws IllegalArgumentException IllegalArgumentException + */ + public Collection getAccessPaths(final String itemId) throws IllegalArgumentException, UnknownHostException, JIException { + return this._browser.browseAccessPaths(itemId).asCollection(this._batchSize); + } + +} \ No newline at end of file diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/Branch.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/Branch.java new file mode 100644 index 0000000..450b800 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/Branch.java @@ -0,0 +1,112 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da.browser; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; + +public class Branch { + private Branch _parent = null; + + private String _name = null; + + private Collection _branches = new LinkedList(); + + private Collection _leaves = new LinkedList(); + + /** + * Create a branch to the virtual root folder + */ + public Branch() { + super(); + } + + /** + * Create a branch with a parent branch and a name of this branch. + * + * @param parent The parent of this branch + * @param name The name of this branch + */ + public Branch(final Branch parent, final String name) { + super(); + this._name = name; + this._parent = parent; + } + + /** + * Get all branches. + *

+ * They must be filled first with a fill method from the {@link TreeBrowser} + * + * @return The list of branches + */ + public Collection getBranches() { + return this._branches; + } + + public void setBranches(final Collection branches) { + this._branches = branches; + } + + /** + * Get all leaves. + *

+ * They must be filled first with a fill method from the {@link TreeBrowser} + * + * @return The list of leaves + */ + public Collection getLeaves() { + return this._leaves; + } + + public void setLeaves(final Collection leaves) { + this._leaves = leaves; + } + + public String getName() { + return this._name; + } + + public void setName(final String name) { + this._name = name; + } + + public Branch getParent() { + return this._parent; + } + + /** + * Get the list of names from the parent up to this branch + * + * @return The stack of branch names from the parent up this one + */ + public Collection getBranchStack() { + LinkedList branches = new LinkedList(); + + Branch currentBranch = this; + while (currentBranch.getParent() != null) { + branches.add(currentBranch.getName()); + currentBranch = currentBranch.getParent(); + } + + Collections.reverse(branches); + return branches; + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/FlatBrowser.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/FlatBrowser.java new file mode 100644 index 0000000..cbdd8b4 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/FlatBrowser.java @@ -0,0 +1,70 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da.browser; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.JIVariant; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCBROWSETYPE; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl.OPCBrowseServerAddressSpace; + +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.EnumSet; + +/** + * Browse through the flat server namespace + * + * @author Jens Reimann jens.reimann@th4-systems.com + */ +public class FlatBrowser extends BaseBrowser { + public FlatBrowser(final OPCBrowseServerAddressSpace browser) { + super(browser); + } + + public FlatBrowser(final OPCBrowseServerAddressSpace browser, final int batchSize) { + super(browser, batchSize); + } + + /** + * Perform a flat browse operation + * + * @param filterCriteria The filter criteria. Use an empty string if you don't need one. + * @param accessMask The access mask. An empty set will search for all. + * @param variantType The variant type. Must be one of the VT_ constants of {@link JIVariant}. Use {@link JIVariant#VT_EMPTY} if you want to browse for all. + * @return The list of entries + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws JIException JIException + */ + public Collection browse(final String filterCriteria, final EnumSet accessMask, final int variantType) throws IllegalArgumentException, UnknownHostException, JIException { + return browse(OPCBROWSETYPE.OPC_FLAT, filterCriteria, accessMask, variantType); + } + + public Collection browse(final String filterCriteria) throws IllegalArgumentException, UnknownHostException, JIException { + return browse(filterCriteria, EnumSet.noneOf(Access.class), JIVariant.VT_EMPTY); + } + + public Collection browse() throws IllegalArgumentException, UnknownHostException, JIException { + return browse("", EnumSet.noneOf(Access.class), JIVariant.VT_EMPTY); + } + + public Collection browse(final EnumSet accessMask) throws IllegalArgumentException, UnknownHostException, JIException { + return browse("", accessMask, JIVariant.VT_EMPTY); + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/Leaf.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/Leaf.java new file mode 100644 index 0000000..2acf230 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/Leaf.java @@ -0,0 +1,58 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da.browser; + +public class Leaf { + private Branch _parent = null; + + private String _name = ""; + + private String _itemId = null; + + public Leaf(final Branch parent, final String name) { + this._parent = parent; + this._name = name; + } + + public Leaf(final Branch parent, final String name, final String itemId) { + this._parent = parent; + this._name = name; + this._itemId = itemId; + } + + public String getItemId() { + return this._itemId; + } + + public void setItemId(final String itemId) { + this._itemId = itemId; + } + + public String getName() { + return this._name; + } + + public void setName(final String name) { + this._name = name; + } + + public Branch getParent() { + return this._parent; + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/TreeBrowser.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/TreeBrowser.java new file mode 100644 index 0000000..2c6fccf --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/da/browser/TreeBrowser.java @@ -0,0 +1,228 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da.browser; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.JIVariant; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCBROWSEDIRECTION; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.OPCBROWSETYPE; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.da.impl.OPCBrowseServerAddressSpace; + +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.EnumSet; +import java.util.LinkedList; + +/** + * Browse through the hierarchical server namespace. + *

+ * The operations on the address space browser browser are not synchronized + * as is the TreeBrowser itself. The user must take care of preventing + * simultanious access to this instance and the server address space browser. + * + * @author Jens Reimann jens.reimann@th4-systems.com + */ +public class TreeBrowser extends BaseBrowser { + + private String _filterCriteria = ""; + + private EnumSet _accessMask = EnumSet.noneOf(Access.class); + + private int _variantType = JIVariant.VT_EMPTY; + + /** + * Browse for all items without search parameters. + *

+ * This will actually call: + * + * TreeBrowser ( browser, "", EnumSet.noneOf ( Access.class ), JIVariant.VT_EMPTY ); + * + * + * @param browser The browser to use for browsing + */ + public TreeBrowser(final OPCBrowseServerAddressSpace browser) { + super(browser); + } + + /** + * Browse for items with search parameters. + * + * @param browser The browser to use + * @param filterCriteria The filter criteria. It is specific to the server you use. + * @param accessMask The access mask (use EnumSet.noneOf ( Access.class ) for all) + * @param variantType The variant type (use JIVariant.VT_EMPTY for all) + */ + public TreeBrowser(final OPCBrowseServerAddressSpace browser, final String filterCriteria, final EnumSet accessMask, final int variantType) { + super(browser); + this._filterCriteria = filterCriteria; + this._accessMask = accessMask; + this._variantType = variantType; + } + + /** + * Move the tree browser to the root folder + * + * @throws JIException JIException + */ + protected void moveToRoot() throws JIException { + this._browser.changePosition(null, OPCBROWSEDIRECTION.OPC_BROWSE_TO); + } + + /** + * Move the tree browser to a branch + * + * @param branch The branch to move to + * @throws JIException JIException + */ + protected void moveToBranch(final Branch branch) throws JIException { + Collection branchStack = branch.getBranchStack(); + + moveToRoot(); + for (String branchName : branchStack) { + this._browser.changePosition(branchName, OPCBROWSEDIRECTION.OPC_BROWSE_DOWN); + } + } + + /** + * Browse the root branch for its sub-branches. + * + * @return The list of sub-branches + * @throws JIException JIException + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + */ + public Branch browseBranches() throws JIException, IllegalArgumentException, UnknownHostException { + Branch branch = new Branch(); + fillBranches(branch); + return branch; + } + + /** + * Browse the root branch for this leaves. + * + * @return The list of leaves + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws JIException JIException + */ + public Branch browseLeaves() throws IllegalArgumentException, UnknownHostException, JIException { + Branch branch = new Branch(); + fillLeaves(branch); + return branch; + } + + /** + * Fill the branch list of the provided branch. + * + * @param branch The branch to fill. + * @throws JIException JIException + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + */ + public void fillBranches(final Branch branch) throws JIException, IllegalArgumentException, UnknownHostException { + moveToBranch(branch); + browse(branch, false, true, false); + } + + /** + * Fill the leaf list of the provided branch. + * + * @param branch The branch to fill. + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws JIException JIException + */ + public void fillLeaves(final Branch branch) throws IllegalArgumentException, UnknownHostException, JIException { + moveToBranch(branch); + browse(branch, true, false, false); + } + + /** + * Browse through all levels of the tree browser. + * + * @return The whole expanded server address space + * @throws JIException JIException + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + */ + public Branch browse() throws JIException, IllegalArgumentException, UnknownHostException { + Branch branch = new Branch(); + fill(branch); + return branch; + } + + /** + * Fill the leaves and branches of the branch provided branches including + * alls sub-branches. + * + * @param branch The branch to fill. + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws JIException JIException + */ + public void fill(final Branch branch) throws IllegalArgumentException, UnknownHostException, JIException { + moveToBranch(branch); + browse(branch, true, true, true); + } + + /** + * Fill the branch object with the leaves of this currently selected branch. + *

+ * The server object is not located to the branch before browsing! + * + * @param branch The branch to fill + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws JIException JIException + */ + protected void browseLeaves(final Branch branch) throws IllegalArgumentException, UnknownHostException, JIException { + branch.setLeaves(new LinkedList()); + + for (String item : browse(OPCBROWSETYPE.OPC_LEAF, this._filterCriteria, this._accessMask, this._variantType)) { + Leaf leaf = new Leaf(branch, item, this._browser.getItemID(item)); + branch.getLeaves().add(leaf); + } + } + + protected void browseBranches(final Branch branch, final boolean leaves, final boolean descend) throws IllegalArgumentException, UnknownHostException, JIException { + branch.setBranches(new LinkedList()); + + for (String item : browse(OPCBROWSETYPE.OPC_BRANCH, this._filterCriteria, this._accessMask, this._variantType)) { + Branch subBranch = new Branch(branch, item); + // descend only if we should + if (descend) { + this._browser.changePosition(item, OPCBROWSEDIRECTION.OPC_BROWSE_DOWN); + browse(subBranch, leaves, true, true); + this._browser.changePosition(null, OPCBROWSEDIRECTION.OPC_BROWSE_UP); + } + branch.getBranches().add(subBranch); + } + } + + protected void browse(final Branch branch, final boolean leaves, final boolean branches, final boolean descend) throws IllegalArgumentException, UnknownHostException, JIException { + // process leaves + if (leaves) { + browseLeaves(branch); + } + + // process branches + if (branches) { + browseBranches(branch, leaves, descend); + } + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/list/Categories.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/list/Categories.java new file mode 100644 index 0000000..cdc8a4a --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/list/Categories.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.list; + +public interface Categories { + /** + * Category of the OPC DA 1.0 Servers + */ + public final static Category OPCDAServer10 = new Category(org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Categories.OPCDAServer10); + + /** + * Category of the OPC DA 2.0 Servers + */ + public final static Category OPCDAServer20 = new Category(org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Categories.OPCDAServer20); + + /** + * Category of the OPC DA 3.0 Servers + */ + public final static Category OPCDAServer30 = new Category(org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Categories.OPCDAServer30); + + /** + * Category of the XML DA 1.0 Servers + */ + public final static Category XMLDAServer10 = new Category(org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.common.Categories.XMLDAServer10); +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/list/Category.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/list/Category.java new file mode 100644 index 0000000..c118fda --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/list/Category.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.list; + +public class Category { + private String _catId = null; + + public Category(final String catId) { + this._catId = catId; + } + + @Override + public String toString() { + return this._catId; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (this._catId == null ? 0 : this._catId.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Category other = (Category) obj; + if (this._catId == null) { + if (other._catId != null) { + return false; + } + } else if (!this._catId.equals(other._catId)) { + return false; + } + return true; + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/list/ServerList.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/list/ServerList.java new file mode 100644 index 0000000..6fb5a58 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/org/openscada/opc/lib/list/ServerList.java @@ -0,0 +1,170 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.list; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.JIClsid; +import org.jinterop.dcom.core.JIComServer; +import org.jinterop.dcom.core.JISession; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.list.ClassDetails; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.list.Constants; +import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.dcom.list.impl.OPCServerList; +import rpc.core.UUID; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * A wrapper around the {@link OPCServerList} class which makes the handling somewhat easier. + * + * @author Jens Reimann <jens.reimann@th4-systems.com> + * @since 0.1.8 + */ +public class ServerList { + private final JISession _session; + + private final OPCServerList _serverList; + + /** + * Create a new instance with an already existing session + * + * @param session the DCOM session + * @param host the host to query + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws JIException JIException + */ + public ServerList(final JISession session, final String host) throws IllegalArgumentException, UnknownHostException, JIException { + this._session = session; + JIComServer comServer = new JIComServer(JIClsid.valueOf(Constants.OPCServerList_CLSID), host, this._session); + this._serverList = new OPCServerList(comServer.createInstance()); + } + + /** + * Create a new instance and a new DCOM session + * + * @param host the host to contact + * @param user the user to use for authentication + * @param password the password to use for authentication + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws JIException JIException + */ + public ServerList(final String host, final String user, final String password) throws IllegalArgumentException, UnknownHostException, JIException { + this(host, user, password, null); + } + + /** + * Create a new instance and a new DCOM session + * + * @param host the host to contact + * @param user the user to use for authentication + * @param password the password to use for authentication + * @param domain The domain to use for authentication + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws JIException JIException + */ + public ServerList(final String host, final String user, final String password, final String domain) throws IllegalArgumentException, UnknownHostException, JIException { + this(JISession.createSession(domain, user, password), host); + } + + /** + * Get the details of a opc class + * + * @param clsId the class to request details for + * @return The class details + * @throws JIException JIException + */ + public ClassDetails getDetails(final String clsId) throws JIException { + return this._serverList.getClassDetails(JIClsid.valueOf(clsId)); + } + + /** + * Fetch the class id of a prog id + * + * @param progId The prog id to look up + * @return the class id or null if none could be found. + * @throws JIException JIException + */ + public String getClsIdFromProgId(final String progId) throws JIException { + JIClsid cls = this._serverList.getCLSIDFromProgID(progId); + if (cls == null) { + return null; + } + return cls.getCLSID(); + } + + /** + * List all servers that match the requirements + * + * @param implemented All implemented categories + * @param required All required categories + * @return A collection of class ids + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws JIException JIException + */ + public Collection listServers(final Category[] implemented, final Category[] required) throws IllegalArgumentException, UnknownHostException, JIException { + // convert the type safe categories to plain UUIDs + UUID[] u1 = new UUID[implemented.length]; + UUID[] u2 = new UUID[required.length]; + + for (int i = 0; i < implemented.length; i++) { + u1[i] = new UUID(implemented[i].toString()); + } + + for (int i = 0; i < required.length; i++) { + u2[i] = new UUID(required[i].toString()); + } + + // get them as UUIDs + Collection resultU = this._serverList.enumClassesOfCategories(u1, u2).asCollection(); + + // and convert to easier usable strings + Collection result = new ArrayList(resultU.size()); + for (UUID uuid : resultU) { + result.add(uuid.toString()); + } + return result; + } + + /** + * List all servers that match the requirements and return the class details + * + * @param implemented All implemented categories + * @param required All required categories + * @return a collection of matching server and their class information + * @throws IllegalArgumentException IllegalArgumentException + * @throws UnknownHostException UnknownHostException + * @throws JIException JIException + */ + public Collection listServersWithDetails(final Category[] implemented, final Category[] required) throws IllegalArgumentException, UnknownHostException, JIException { + Collection resultString = listServers(implemented, required); + + List result = new ArrayList(resultString.size()); + + for (String clsId : resultString) { + result.add(getDetails(clsId)); + } + + return result; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcua/OpcUaProtocolDriverImpl.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcua/OpcUaProtocolDriverImpl.java new file mode 100644 index 0000000..648cbc1 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcua/OpcUaProtocolDriverImpl.java @@ -0,0 +1,225 @@ +package org.nl.iot.core.driver.protocol.opcua; + +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.milo.opcua.sdk.client.OpcUaClient; +import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider; +import org.eclipse.milo.opcua.stack.core.UaException; +import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; +import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; +import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode; +import org.eclipse.milo.opcua.stack.core.types.builtin.Variant; +import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned; +import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn; +import org.nl.common.exception.CommonException; +import org.nl.iot.core.driver.bo.AttributeBO; +import org.nl.iot.core.driver.entity.RValue; +import org.nl.iot.core.driver.entity.WValue; +import org.nl.iot.core.driver.enums.PointTypeFlagEnum; +import org.nl.iot.core.driver.service.DriverCustomService; +import org.nl.iot.modular.iot.entity.IotConfig; +import org.nl.iot.modular.iot.entity.IotConnect; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.*; + +@Slf4j +@Service +public class OpcUaProtocolDriverImpl implements DriverCustomService { + private Map connectMap; + + @PostConstruct + @Override + public void initial() { + /* + * 驱动初始化逻辑 + * + * 提示: 此处逻辑仅供参考, 请务必结合实际应用场景进行修改。 + * 驱动启动时会自动执行该方法, 您可以在此处执行特定的初始化操作。 + * + */ + connectMap = new ConcurrentHashMap<>(16); + } + + @Override + public void schedule() { + + } + + + + @Override + public RValue read(Map driverConfig, Map pointConfig, IotConnect connect, IotConfig config) { + return new RValue(config, connect, readValue(getConnector(connect.getId().toString(), driverConfig), pointConfig)); + } + + /** + * 获取 OPC UA 客户端连接 + * + * @param deviceId 设备ID, 用于标识唯一的设备连接 + * @param driverConfig 驱动配置信息, 包含连接 OPC UA 服务器所需的参数 + * @return OpcUaClient 返回与指定设备关联的 OPC UA 客户端实例 + * @throws CommonException 如果连接 OPC UA 服务器失败, 抛出此异常 + */ + private OpcUaClient getConnector(String deviceId, Map driverConfig) { + log.debug("OPC UA server connection info: {}", driverConfig); + OpcUaClient opcUaClient = connectMap.get(deviceId); + if (Objects.isNull(opcUaClient)) { + String host = driverConfig.get("host").getValueByClass(String.class); + int port = driverConfig.get("port").getValueByClass(Integer.class); + String path = driverConfig.get("path").getValueByClass(String.class); + String url = String.format("opc.tcp://%s:%s%s", host, port, path); + try { + opcUaClient = OpcUaClient.create( + url, + endpoints -> endpoints.stream().findFirst(), + configBuilder -> configBuilder + .setIdentityProvider(new AnonymousProvider()) // 使用匿名身份验证 + .setRequestTimeout(Unsigned.uint(5000)) // 设置请求超时时间为 5000 毫秒 + .build() + ); + connectMap.put(deviceId, opcUaClient); + } catch (UaException e) { + connectMap.entrySet().removeIf(next -> next.getKey().equals(deviceId)); + log.error("Failed to connect OPC UA client: {}", e.getMessage(), e); + throw new CommonException(e.getMessage()); + } + } + return opcUaClient; + } + + /** + * 读取 OPC UA 节点的值 + * + * @param client OPC UA 客户端实例 + * @param pointConfig 点位配置信息 + * @return 读取到的节点值 + * @throws CommonException 如果读取操作失败, 抛出此异常 + */ + private String readValue(OpcUaClient client, Map pointConfig) { + try { + NodeId nodeId = getNode(pointConfig); + // 确保客户端已连接 + client.connect().get(10, TimeUnit.SECONDS); + + // 直接使用同步方式读取值,设置更长的超时时间 + DataValue dataValue = client.readValue(0.0, TimestampsToReturn.Both, nodeId).get(10, TimeUnit.SECONDS); + + // 检查读取状态 + if (dataValue.getStatusCode().isGood()) { + Object value = dataValue.getValue().getValue(); + return value != null ? value.toString() : null; + } else { + log.error("Read opc ua value failed with status: {}", dataValue.getStatusCode()); + throw new CommonException("Read failed with status: " + dataValue.getStatusCode()); + } + } catch (InterruptedException e) { + log.error("Read opc ua value error: {}", e.getMessage(), e); + Thread.currentThread().interrupt(); + throw new CommonException(e.getMessage()); + } catch (ExecutionException | TimeoutException e) { + log.error("Read opc ua value error: {}", e.getMessage(), e); + throw new CommonException(e.getMessage()); + } + } + + @Override + public Boolean write(Map driverConfig, Map pointConfig, IotConnect device, IotConfig point, WValue wValue) { + OpcUaClient client = getConnector(device.getId().toString(), driverConfig); + return writeValue(client, pointConfig, wValue); + } + + /** + * 写入 OPC UA 节点的值 + * + * @param client OPC UA 客户端实例 + * @param pointConfig 点位配置信息 + * @param wValue 写入值 + * @return 写入操作是否成功 + * @throws CommonException 如果写入操作失败, 抛出此异常 + */ + private boolean writeValue(OpcUaClient client, Map pointConfig, WValue wValue) { + try { + NodeId nodeId = getNode(pointConfig); + // 确保客户端已连接,设置超时时间 + client.connect().get(10, TimeUnit.SECONDS); + return writeNode(client, nodeId, wValue); + } catch (InterruptedException e) { + log.error("Write opc ua value error: {}", e.getMessage(), e); + Thread.currentThread().interrupt(); + throw new CommonException(e.getMessage()); + } catch (ExecutionException | TimeoutException e) { + log.error("Write opc ua value error: {}", e.getMessage(), e); + throw new CommonException(e.getMessage()); + } + } + + /** + * 根据点位配置获取 OPC UA 节点 + * + * @param pointConfig 点位配置信息, 包含命名空间和标签 + * @return OPC UA 节点标识 + */ + private NodeId getNode(Map pointConfig) { + int namespace = pointConfig.get("namespace").getValueByClass(Integer.class); + String tag = pointConfig.get("tag").getValueByClass(String.class); + return new NodeId(namespace, tag); + } + + /** + * 将值写入 OPC UA 节点 + * + * @param client OPC UA 客户端实例 + * @param nodeId OPC UA 节点标识 + * @param wValue 待写入的值 + * @return 写入操作是否成功 + * @throws ExecutionException 写入操作失败时抛出 + * @throws InterruptedException 写入操作被中断时抛出 + * @throws TimeoutException 写入操作超时时抛出 + */ + private boolean writeNode(OpcUaClient client, NodeId nodeId, WValue wValue) throws ExecutionException, InterruptedException, TimeoutException { + PointTypeFlagEnum valueType = PointTypeFlagEnum.ofCode(wValue.getType()); + if (Objects.isNull(valueType)) { + throw new CommonException("Unsupported type of " + wValue.getType()); + } + + CompletableFuture status = null; + switch (valueType) { + case INT: + int intValue = wValue.getValueByClass(Integer.class); + status = client.writeValue(nodeId, new DataValue(new Variant(intValue))); + break; + case LONG: + long longValue = wValue.getValueByClass(Long.class); + status = client.writeValue(nodeId, new DataValue(new Variant(longValue))); + break; + case FLOAT: + float floatValue = wValue.getValueByClass(Float.class); + status = client.writeValue(nodeId, new DataValue(new Variant(floatValue))); + break; + case DOUBLE: + double doubleValue = wValue.getValueByClass(Double.class); + status = client.writeValue(nodeId, new DataValue(new Variant(doubleValue))); + break; + case BOOLEAN: + boolean booleanValue = wValue.getValueByClass(Boolean.class); + status = client.writeValue(nodeId, new DataValue(new Variant(booleanValue))); + break; + case STRING: + status = client.writeValue(nodeId, new DataValue(new Variant(wValue.getValue()))); + break; + default: + break; + } + + if (Objects.nonNull(status)) { + StatusCode statusCode = status.get(10, TimeUnit.SECONDS); + if (Objects.nonNull(statusCode)) { + return statusCode.isGood(); + } + } + return false; + } +}