diff --git a/WebContent/WEB-INF/applicationContext.xml b/WebContent/WEB-INF/applicationContext.xml index b78b6d9959..0d87b1abc0 100644 --- a/WebContent/WEB-INF/applicationContext.xml +++ b/WebContent/WEB-INF/applicationContext.xml @@ -60,6 +60,8 @@ messages + + diff --git a/WebContent/WEB-INF/dox/en/opcUaDS.htm b/WebContent/WEB-INF/dox/en/opcUaDS.htm new file mode 100644 index 0000000000..3b13cb8354 --- /dev/null +++ b/WebContent/WEB-INF/dox/en/opcUaDS.htm @@ -0,0 +1,33 @@ + + +

Description

+

The OPC Data Source allows the reading data from a OPC Server

+

Configuration

+

All Data Source requires a Name, which may have any description.

+

The Host indicates the local address from client.

+

The Domain indicates the domain where the client is located. PS.: Client must +necessarily be in a domain to operate with OPC.

+

The User indicates the name of user from Operating System - OS.

+

The Password indicates the operating system password.

+

The Server shows which server are available for the data provided.

+

The Update Period indicates the update period from Data Source data.

+

The Creation Mode indicates how the OPC Tags will be created on the client. We have two options: +Browse Tags - where all the Tags available in the Server will be displayed and Add Tags - where will be added a Tag available +from Server

\ No newline at end of file diff --git a/WebContent/WEB-INF/dox/en/opcUaPP.htm b/WebContent/WEB-INF/dox/en/opcUaPP.htm new file mode 100644 index 0000000000..c40ce41c04 --- /dev/null +++ b/WebContent/WEB-INF/dox/en/opcUaPP.htm @@ -0,0 +1,22 @@ + + +

Configuration

+

All Data Point requires a Name, which may have any description.

+

The Data Type indicates what kind of data is being requested by the OPC Client.

diff --git a/WebContent/WEB-INF/dox/manifest.xml b/WebContent/WEB-INF/dox/manifest.xml index 34b01e1e97..c41221d7ce 100644 --- a/WebContent/WEB-INF/dox/manifest.xml +++ b/WebContent/WEB-INF/dox/manifest.xml @@ -423,4 +423,12 @@ + + + + + + + + diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 75f2e367e1..4cbc095d4a 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -24,6 +24,11 @@ + + + + + @@ -107,6 +112,7 @@ + @@ -215,6 +221,11 @@ + + + + + diff --git a/WebContent/WEB-INF/jsp/dataSourceEdit.jsp b/WebContent/WEB-INF/jsp/dataSourceEdit.jsp index 5b0055cdaa..e3c19e9906 100644 --- a/WebContent/WEB-INF/jsp/dataSourceEdit.jsp +++ b/WebContent/WEB-INF/jsp/dataSourceEdit.jsp @@ -519,5 +519,9 @@ test="${dataSource.type.id == applicationScope['constants.DataSourceVO.Types.RADIUINO']}"> + + + \ No newline at end of file diff --git a/WebContent/WEB-INF/jsp/dataSourceEdit/editOpcUa.jsp b/WebContent/WEB-INF/jsp/dataSourceEdit/editOpcUa.jsp new file mode 100644 index 0000000000..4fe20a32f5 --- /dev/null +++ b/WebContent/WEB-INF/jsp/dataSourceEdit/editOpcUa.jsp @@ -0,0 +1,721 @@ +<%@ include file="/WEB-INF/jsp/include/tech.jsp"%> + +<%@page import="br.org.scadabr.KeyStoreType"%> +<%@page import="br.org.scadabr.vo.dataSource.opcua.OpcUaDataType"%> +<%@page import="br.org.scadabr.vo.dataSource.opcua.OpcUaIdentifierType"%> +<%@page import="br.org.scadabr.vo.dataSource.opcua.OpcUaMessageSecurityType"%> +<%@page import="br.org.scadabr.vo.dataSource.opcua.OpcUaSecurityPolicyType"%> + + + + + + + +<%@ include file="/WEB-INF/jsp/dataSourceEdit/dsHead.jspf"%> + + + " /> + + + + + + + + " /> + + + + " /> + + + + + + + + + + + + + + + + + + + + "" /> + + + + + + + + + + " /> + + + + " /> + + + + + + + + + + " /> + + + + " /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ " + onclick="searchServer();" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%@ include file="/WEB-INF/jsp/dataSourceEdit/dsFoot.jspf"%> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebContent/WEB-INF/lib/bcpkix-jdk18on-1.77.jar b/WebContent/WEB-INF/lib/bcpkix-jdk18on-1.77.jar new file mode 100644 index 0000000000..e8b6021a2a Binary files /dev/null and b/WebContent/WEB-INF/lib/bcpkix-jdk18on-1.77.jar differ diff --git a/WebContent/WEB-INF/lib/bcprov-jdk18on-1.77.jar b/WebContent/WEB-INF/lib/bcprov-jdk18on-1.77.jar new file mode 100644 index 0000000000..651d2fba5e Binary files /dev/null and b/WebContent/WEB-INF/lib/bcprov-jdk18on-1.77.jar differ diff --git a/WebContent/WEB-INF/lib/bcutil-jdk18on-1.77.jar b/WebContent/WEB-INF/lib/bcutil-jdk18on-1.77.jar new file mode 100644 index 0000000000..4c154e27d6 Binary files /dev/null and b/WebContent/WEB-INF/lib/bcutil-jdk18on-1.77.jar differ diff --git a/WebContent/WEB-INF/lib/bit-io-1.4.3.jar b/WebContent/WEB-INF/lib/bit-io-1.4.3.jar new file mode 100644 index 0000000000..fca003226c Binary files /dev/null and b/WebContent/WEB-INF/lib/bit-io-1.4.3.jar differ diff --git a/WebContent/WEB-INF/lib/commons-codec-1.16.1.jar b/WebContent/WEB-INF/lib/commons-codec-1.16.1.jar new file mode 100644 index 0000000000..f896649735 Binary files /dev/null and b/WebContent/WEB-INF/lib/commons-codec-1.16.1.jar differ diff --git a/WebContent/WEB-INF/lib/commons-codec-1.9.jar b/WebContent/WEB-INF/lib/commons-codec-1.9.jar deleted file mode 100644 index ef35f1c50d..0000000000 Binary files a/WebContent/WEB-INF/lib/commons-codec-1.9.jar and /dev/null differ diff --git a/WebContent/WEB-INF/lib/commons-lang3-3.0.jar b/WebContent/WEB-INF/lib/commons-lang3-3.0.jar deleted file mode 100644 index 780db952fe..0000000000 Binary files a/WebContent/WEB-INF/lib/commons-lang3-3.0.jar and /dev/null differ diff --git a/WebContent/WEB-INF/lib/commons-lang3-3.14.0.jar b/WebContent/WEB-INF/lib/commons-lang3-3.14.0.jar new file mode 100644 index 0000000000..da9302ff29 Binary files /dev/null and b/WebContent/WEB-INF/lib/commons-lang3-3.14.0.jar differ diff --git a/WebContent/WEB-INF/lib/netty-buffer-4.1.106.Final.jar b/WebContent/WEB-INF/lib/netty-buffer-4.1.106.Final.jar new file mode 100644 index 0000000000..e111e84e99 Binary files /dev/null and b/WebContent/WEB-INF/lib/netty-buffer-4.1.106.Final.jar differ diff --git a/WebContent/WEB-INF/lib/netty-codec-4.1.106.Final.jar b/WebContent/WEB-INF/lib/netty-codec-4.1.106.Final.jar new file mode 100644 index 0000000000..47516e7a40 Binary files /dev/null and b/WebContent/WEB-INF/lib/netty-codec-4.1.106.Final.jar differ diff --git a/WebContent/WEB-INF/lib/netty-common-4.1.106.Final.jar b/WebContent/WEB-INF/lib/netty-common-4.1.106.Final.jar new file mode 100644 index 0000000000..134188f66b Binary files /dev/null and b/WebContent/WEB-INF/lib/netty-common-4.1.106.Final.jar differ diff --git a/WebContent/WEB-INF/lib/netty-handler-4.1.106.Final.jar b/WebContent/WEB-INF/lib/netty-handler-4.1.106.Final.jar new file mode 100644 index 0000000000..31560c197b Binary files /dev/null and b/WebContent/WEB-INF/lib/netty-handler-4.1.106.Final.jar differ diff --git a/WebContent/WEB-INF/lib/netty-resolver-4.1.106.Final.jar b/WebContent/WEB-INF/lib/netty-resolver-4.1.106.Final.jar new file mode 100644 index 0000000000..4d5021a083 Binary files /dev/null and b/WebContent/WEB-INF/lib/netty-resolver-4.1.106.Final.jar differ diff --git a/WebContent/WEB-INF/lib/netty-transport-4.1.106.Final.jar b/WebContent/WEB-INF/lib/netty-transport-4.1.106.Final.jar new file mode 100644 index 0000000000..750d86bf4c Binary files /dev/null and b/WebContent/WEB-INF/lib/netty-transport-4.1.106.Final.jar differ diff --git a/WebContent/WEB-INF/lib/plc4j-api-0.12.0.jar b/WebContent/WEB-INF/lib/plc4j-api-0.12.0.jar new file mode 100644 index 0000000000..a0704eccfa Binary files /dev/null and b/WebContent/WEB-INF/lib/plc4j-api-0.12.0.jar differ diff --git a/WebContent/WEB-INF/lib/plc4j-driver-opcua-0.12.0.jar b/WebContent/WEB-INF/lib/plc4j-driver-opcua-0.12.0.jar new file mode 100644 index 0000000000..450b5633c3 Binary files /dev/null and b/WebContent/WEB-INF/lib/plc4j-driver-opcua-0.12.0.jar differ diff --git a/WebContent/WEB-INF/lib/plc4j-spi-0.12.0.jar b/WebContent/WEB-INF/lib/plc4j-spi-0.12.0.jar new file mode 100644 index 0000000000..41e704809d Binary files /dev/null and b/WebContent/WEB-INF/lib/plc4j-spi-0.12.0.jar differ diff --git a/WebContent/WEB-INF/lib/plc4j-transport-tcp-0.12.0.jar b/WebContent/WEB-INF/lib/plc4j-transport-tcp-0.12.0.jar new file mode 100644 index 0000000000..ed0800cff3 Binary files /dev/null and b/WebContent/WEB-INF/lib/plc4j-transport-tcp-0.12.0.jar differ diff --git a/WebContent/WEB-INF/lib/stax2-api-4.2.1.jar b/WebContent/WEB-INF/lib/stax2-api-4.2.1.jar new file mode 100644 index 0000000000..28c6a08f40 Binary files /dev/null and b/WebContent/WEB-INF/lib/stax2-api-4.2.1.jar differ diff --git a/WebContent/WEB-INF/lib/vavr-0.10.4.jar b/WebContent/WEB-INF/lib/vavr-0.10.4.jar new file mode 100644 index 0000000000..5d1ab71ba6 Binary files /dev/null and b/WebContent/WEB-INF/lib/vavr-0.10.4.jar differ diff --git a/WebContent/WEB-INF/lib/vavr-match-0.10.4.jar b/WebContent/WEB-INF/lib/vavr-match-0.10.4.jar new file mode 100644 index 0000000000..a6f3028ca5 Binary files /dev/null and b/WebContent/WEB-INF/lib/vavr-match-0.10.4.jar differ diff --git a/WebContent/WEB-INF/lib/woodstox-core-6.5.1.jar b/WebContent/WEB-INF/lib/woodstox-core-6.5.1.jar new file mode 100644 index 0000000000..b22b384697 Binary files /dev/null and b/WebContent/WEB-INF/lib/woodstox-core-6.5.1.jar differ diff --git a/src/br/org/scadabr/KeyStoreType.java b/src/br/org/scadabr/KeyStoreType.java new file mode 100644 index 0000000000..0658795296 --- /dev/null +++ b/src/br/org/scadabr/KeyStoreType.java @@ -0,0 +1,22 @@ +package br.org.scadabr; + +public enum KeyStoreType { + + JKS("jks"), PKCS11("pkcs11"), DKS("dks"), JCEKS("jceks"), PKCS12("pkcs12"); + + public static final KeyStoreType DEFAULT = KeyStoreType.PKCS12; + + private final String name; + + KeyStoreType(String name) { + this.name = name; + } + + public String getCode() { + return this.name(); + } + + public String getDescription() { + return this.name; + } +} diff --git a/src/br/org/scadabr/OpcUaMaster.java b/src/br/org/scadabr/OpcUaMaster.java new file mode 100644 index 0000000000..d26a7831c7 --- /dev/null +++ b/src/br/org/scadabr/OpcUaMaster.java @@ -0,0 +1,118 @@ +package br.org.scadabr; + +import br.org.scadabr.vo.dataSource.opcua.OpcUaDataSourceVO; +import br.org.scadabr.vo.dataSource.opcua.OpcUaPointLocatorVO; +import com.serotonin.mango.Common; +import com.serotonin.mango.rt.dataImage.types.ImageValue; +import com.serotonin.mango.rt.dataImage.types.MangoValue; +import com.serotonin.mango.rt.maint.work.PlcConnectionClosingWorkItem; +import com.serotonin.mango.rt.maint.work.WorkItem; +import com.serotonin.mango.util.LoggingUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.PlcDriverManager; +import org.apache.plc4x.java.api.messages.PlcReadResponse; +import org.apache.plc4x.java.api.messages.PlcWriteResponse; +import org.apache.plc4x.java.api.types.PlcResponseCode; +import org.scada_lts.ds.opcua.TimeoutPlcDriverManager; + +import java.text.MessageFormat; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalUnit; +import java.util.ArrayList; + +public class OpcUaMaster { + + private static final Log LOG = LogFactory.getLog(OpcUaMaster.class); + + private final OpcUaDataSourceVO dataSource; + private PlcConnection plcConnection; + private PlcDriverManager driverManager; + + public OpcUaMaster(OpcUaDataSourceVO dataSource) { + this.dataSource = dataSource; + this.driverManager = new TimeoutPlcDriverManager(); + }; + + public void init() throws Exception { + terminate(); + this.plcConnection = driverManager.getConnectionManager().getConnection(dataSource.getConnectionString()); + } + + public String getDataSourceXid() { + return dataSource.getXid(); + } + + public void terminate() throws Exception { + doClose(plcConnection); + } + + public Object read(OpcUaPointLocatorVO pointLocator) throws Exception { + PlcConnection plcConnection = getConnetion(); + PlcReadResponse readResponse = OpcUaUtils.sendRead(plcConnection, pointLocator.getTag(), pointLocator.getNodeId()); + PlcResponseCode responseCode = readResponse.getResponseCode(pointLocator.getTag()); + if (responseCode != PlcResponseCode.OK) { + throw new IllegalStateException(MessageFormat.format("Failed read nodeId: {0}, tag: {1}, value: {2}, code: {3}", pointLocator.getNodeId(), pointLocator.getTag(), readResponse.getObject(pointLocator.getTag()), responseCode.name())); + } + return readResponse.getObject(pointLocator.getTag()); + } + + public void write(OpcUaPointLocatorVO pointLocator, MangoValue value) throws Exception { + if(value == null) { + throw new IllegalArgumentException(MessageFormat.format("Failed write nodeId: {0}, tag: {1}, msg: {2}", pointLocator.getNodeId(), pointLocator.getTag(), "Value is null!")); + } + if(value instanceof ImageValue) { + throw new IllegalArgumentException(MessageFormat.format("Failed write nodeId: {0}, tag: {1}, msg: {2}", pointLocator.getNodeId(), pointLocator.getTag(), value.getClass().getName() + " is not supported!")); + } + Object valueToConvert = value.getObjectValue(); + Object valueToSend = pointLocator.getDataType().convert(valueToConvert); + PlcConnection plcConnection = getConnetion(); + PlcWriteResponse writeResponse = OpcUaUtils.sendWrite(plcConnection, pointLocator.getTag(), pointLocator.getNodeId(), valueToSend); + PlcResponseCode responseCode = writeResponse.getResponseCode(pointLocator.getTag()); + if (responseCode != PlcResponseCode.OK) + throw new IllegalStateException(MessageFormat.format("Failed write nodeId: {0}, tag: {1}, valueToSend: {2}, code: {3}", pointLocator.getNodeId(), pointLocator.getTag(), valueToSend, responseCode.name())); + } + + public int findNamespaceIndex(int numberOfAttempts, OpcUaPointLocatorVO pointLocator) { + try { + PlcConnection plcConnection = getConnetion(); + return OpcUaUtils.findNamespaceIndex(plcConnection, numberOfAttempts, pointLocator); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + return -1; + } + } + + public boolean validateTag(OpcUaPointLocatorVO pointLocator, String tag) { + try { + PlcConnection plcConnection = getConnetion(); + return OpcUaUtils.validateTag(plcConnection, pointLocator, tag); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + return false; + } + } + + public void ping() throws Exception { + PlcConnection plcConnection = getConnetion(); + if(!plcConnection.isConnected()) { + throw new IllegalStateException("No connect!"); + } + } + + private static void doClose(PlcConnection plcConnection) { + if(plcConnection != null) { + WorkItem workItem = new PlcConnectionClosingWorkItem(plcConnection); + Common.ctx.getBackgroundProcessing().addWorkItem(workItem); + } + } + + private PlcConnection getConnetion() throws Exception { + if(plcConnection == null) { + throw new IllegalStateException("No init!"); + } + return plcConnection; + } +} diff --git a/src/br/org/scadabr/OpcUaUtils.java b/src/br/org/scadabr/OpcUaUtils.java new file mode 100644 index 0000000000..42c0bc8a7c --- /dev/null +++ b/src/br/org/scadabr/OpcUaUtils.java @@ -0,0 +1,104 @@ +package br.org.scadabr; + +import br.org.scadabr.vo.dataSource.opcua.OpcUaPointLocatorVO; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.messages.*; +import org.apache.plc4x.java.api.types.PlcResponseCode; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; + +public class OpcUaUtils { + + private static final Log LOG = LogFactory.getLog(OpcUaUtils.class); + + public OpcUaUtils() { + } + + public static boolean validateTag(PlcConnection connection, OpcUaPointLocatorVO locator, String tag) throws ExecutionException, InterruptedException { + String nodeId = locator.getNodeId(); // Replace with the actual node ID + + // Create a read request + PlcReadRequest.Builder builder = connection.readRequestBuilder(); + builder.addTagAddress(tag, nodeId); // Add the node ID directly + + PlcReadRequest readRequest = builder.build(); + PlcReadResponse response = readRequest.execute().get(); + + return response.getResponseCode(tag) == PlcResponseCode.OK; + } + + public static int findNamespaceIndex(PlcConnection connection, int numberOfAttempts, OpcUaPointLocatorVO opcUaPointLocatorVO) { + PlcReadResponse response; + String tag = opcUaPointLocatorVO.getTag(); + OpcUaPointLocatorVO copied = opcUaPointLocatorVO.copy(); + for(int i = 0; i < numberOfAttempts; i++) { + copied.setNamespaceIndex(i); + String nodeId = copied.getNodeId(); + try { + response = sendRead(connection, tag, nodeId); + if(response != null && response.getResponseCode(tag) == PlcResponseCode.OK) { + Object value = read(response, tag); + LOG.info("tag: " + tag + ", nodeId: " + nodeId + ", value: " + value); + return i; + } + } catch (Exception e) { + LOG.error(e.getMessage(), e); + break; + } + + } + return -1; + } + + public static Object read(PlcReadResponse response, String varName) { + if (response.getResponseCode(varName) == PlcResponseCode.OK) { + return response.getObject(varName); + } else { + return null; + } + } + + public static PlcReadResponse sendRead(PlcConnection connection, String tag, String nodeId) throws InterruptedException, ExecutionException { + PlcReadRequest.Builder builder = connection.readRequestBuilder(); + builder.addTagAddress(tag, nodeId); // Add the node ID directly + PlcReadRequest readRequest = builder.build(); + return readRequest.execute().get(); + } + + public static PlcWriteResponse sendWrite(PlcConnection connection, String tag, String nodeId, Object value) throws InterruptedException, ExecutionException { + PlcWriteRequest.Builder builder = connection.writeRequestBuilder(); + builder.addTagAddress(tag, nodeId, value); + PlcWriteRequest readRequest = builder.build(); + return readRequest.execute().get(); + } + + public static PlcSubscriptionResponse sendSubscribe(PlcConnection connection, String tag, String nodeId) throws InterruptedException, ExecutionException { + PlcSubscriptionRequest.Builder builder = connection.subscriptionRequestBuilder(); + builder.addChangeOfStateTagAddress(tag, nodeId); + builder.addPreRegisteredConsumer(tag, new Consumer() { + @Override + public void accept(PlcSubscriptionEvent plcSubscriptionEvent) { + System.out.println("sub value"); + Object value = plcSubscriptionEvent.getObject(tag); + System.out.println("sub value: " + value); + } + }); + PlcSubscriptionRequest readRequest = builder.build(); + return readRequest.execute().get(); + } + + public static PlcReadResponse sendPing(PlcConnection connection, String tag, String nodeId) throws InterruptedException, ExecutionException, TimeoutException { + return sendRead(connection, tag, nodeId); + } + + private static PlcBrowseResponse sendBrowse(PlcConnection connection) throws InterruptedException, ExecutionException { + PlcBrowseRequest.Builder builder = connection.browseRequestBuilder(); + PlcBrowseRequest readRequest = builder.build(); + return readRequest.execute().get(); + } +} diff --git a/src/br/org/scadabr/rt/dataSource/opcua/OpcUaDataSourceRT.java b/src/br/org/scadabr/rt/dataSource/opcua/OpcUaDataSourceRT.java new file mode 100644 index 0000000000..511e4ec811 --- /dev/null +++ b/src/br/org/scadabr/rt/dataSource/opcua/OpcUaDataSourceRT.java @@ -0,0 +1,168 @@ +package br.org.scadabr.rt.dataSource.opcua; + +import br.org.scadabr.OpcUaMaster; +import br.org.scadabr.vo.dataSource.opcua.OpcUaDataSourceVO; +import br.org.scadabr.vo.dataSource.opcua.OpcUaPointLocatorVO; +import com.serotonin.mango.DataTypes; +import com.serotonin.mango.rt.dataImage.DataPointRT; +import com.serotonin.mango.rt.dataImage.PointValueTime; +import com.serotonin.mango.rt.dataImage.SetPointSource; +import com.serotonin.mango.rt.dataImage.types.MangoValue; +import com.serotonin.mango.rt.dataSource.PollingDataSource; +import com.serotonin.mango.util.LoggingUtils; +import com.serotonin.mango.vo.DataPointVO; +import com.serotonin.web.i18n.LocalizableMessage; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.scada_lts.serorepl.utils.StringUtils; + + +public class OpcUaDataSourceRT extends PollingDataSource { + + private final Log LOG = LogFactory.getLog(OpcUaDataSourceRT.class); + public static final int POINT_READ_EXCEPTION_EVENT = 1; + public static final int DATA_SOURCE_EXCEPTION_EVENT = 2; + public static final int POINT_WRITE_EXCEPTION_EVENT = 3; + private OpcUaMaster opcMaster; + private final OpcUaDataSourceVO vo; + private int timeoutCount = 0; + private int timeoutsToReconnect = 3; + + public OpcUaDataSourceRT(OpcUaDataSourceVO vo) { + super(vo); + this.vo = vo; + setPollingPeriod(vo.getUpdatePeriodType(), vo.getUpdatePeriods(), + vo.isQuantize()); + } + + @Override + protected void doPoll(long time) { + + try { + + if (timeoutCount >= timeoutsToReconnect) { + LOG.error("[OPC UA] Trying to reconnect ! :" + LoggingUtils.dataSourceInfo(this)); + timeoutCount = 0; + initialize(); + } else { + opcMaster.ping(); + returnToNormal(DATA_SOURCE_EXCEPTION_EVENT, time); + } + + } catch (Throwable e) { + LOG.error("[OPC UA] Poll Failed ! :" + LoggingUtils.info(e, this)); + raiseEvent( + DATA_SOURCE_EXCEPTION_EVENT, + time, + true, + new LocalizableMessage("event.exception2", vo.getName(), e + .getMessage())); + timeoutCount++; + return; + } + + for (DataPointRT dataPoint : getDataPoints()) { + OpcUaPointLocatorVO pointLocator = dataPoint.getVO().getPointLocator(); + MangoValue mangoValue = null; + String value = "0"; + + try { + value = String.valueOf(opcMaster.read(pointLocator)); + mangoValue = MangoValue.stringToValue(value, pointLocator.getDataTypeId()); + dataPoint.updatePointValue(new PointValueTime(mangoValue, time)); + returnToNormal(POINT_READ_EXCEPTION_EVENT, time, dataPoint); + } catch (Throwable e) { + LOG.error("[OPC UA] Poll Failed ! :" + LoggingUtils.info(e, this) + " - " + + LoggingUtils.dataPointInfo(dataPoint) + " - " + LoggingUtils.pointLocatorInfo(pointLocator)); + raiseEvent(POINT_READ_EXCEPTION_EVENT, time, true, + new LocalizableMessage("event.exception2", + vo.getName(), e.getMessage()), dataPoint); + } + } + } + + @Override + public void setPointValue(DataPointRT dataPoint, PointValueTime valueTime, + SetPointSource source) { + String tag = ((OpcUaPointLocatorVO) dataPoint.getVO().getPointLocator()) + .getTag(); + Object value = null; + if (dataPoint.getDataTypeId() == DataTypes.NUMERIC) + value = valueTime.getDoubleValue(); + else if (dataPoint.getDataTypeId() == DataTypes.BINARY) + value = valueTime.getBooleanValue(); + else if (dataPoint.getDataTypeId() == DataTypes.MULTISTATE) + value = valueTime.getIntegerValue(); + else + value = valueTime.getStringValue(); + + try { + opcMaster.write(dataPoint.getVO().getPointLocator(), valueTime.getValue()); + returnToNormal(POINT_WRITE_EXCEPTION_EVENT, System.currentTimeMillis(), dataPoint); + } catch (Throwable e) { + LOG.error(LoggingUtils.info(e, this, dataPoint), e); + raiseEvent( + POINT_WRITE_EXCEPTION_EVENT, + System.currentTimeMillis(), + true, + new LocalizableMessage("event.exception2", vo.getName(), e + .getMessage()), dataPoint); + } + } + + public void initialize() { + if(opcMaster != null) { + try { + opcMaster.terminate(); + } catch (Exception e) { + LOG.warn(LoggingUtils.info(e, this)); + } + } + + try { + this.opcMaster = new OpcUaMaster(vo); + opcMaster.init(); + returnToNormal(DATA_SOURCE_EXCEPTION_EVENT, System.currentTimeMillis()); + } catch (Throwable e) { + String message = e.getMessage(); + if(e.getMessage() != null && e.getMessage().contains("Unknown Error")) { + message = "The OPC DA Server for the data source settings may not be found. "; + } + message = "Error while initializing data source: " + message; + LOG.error(message + e.getMessage(), e); + raiseEvent( + DATA_SOURCE_EXCEPTION_EVENT, + System.currentTimeMillis(), + true, + new LocalizableMessage("event.exception2", vo.getName(), message)); + return; + } + super.initialize(); + } + + @Override + public void terminate() { + super.terminate(); + try { + if(opcMaster != null) + opcMaster.terminate(); + returnToNormal(DATA_SOURCE_EXCEPTION_EVENT, System.currentTimeMillis()); + } catch (Throwable e) { + String message = e.getMessage(); + if(e instanceof NullPointerException) { + message = "The client may not have been properly initialized. "; + } + message = "Error while terminating data source: " + message; + LOG.error(message + e.getMessage(), e); + raiseEvent( + DATA_SOURCE_EXCEPTION_EVENT, + System.currentTimeMillis(), + true, + new LocalizableMessage("event.exception2", vo.getName(), message)); + } + } + + public OpcUaDataSourceVO getVo() { + return vo; + } +} diff --git a/src/br/org/scadabr/rt/dataSource/opcua/OpcUaPointLocatorRT.java b/src/br/org/scadabr/rt/dataSource/opcua/OpcUaPointLocatorRT.java new file mode 100644 index 0000000000..c1c9cb6408 --- /dev/null +++ b/src/br/org/scadabr/rt/dataSource/opcua/OpcUaPointLocatorRT.java @@ -0,0 +1,23 @@ +package br.org.scadabr.rt.dataSource.opcua; + +import br.org.scadabr.vo.dataSource.opcua.OpcUaPointLocatorVO; +import com.serotonin.mango.rt.dataSource.PointLocatorRT; + +public class OpcUaPointLocatorRT extends PointLocatorRT { + private final OpcUaPointLocatorVO vo; + + public OpcUaPointLocatorRT(OpcUaPointLocatorVO vo) { + this.vo = vo; + } + + @Override + public boolean isSettable() { + // TODO Auto-generated method stub + return vo.isSettable(); + } + + public OpcUaPointLocatorVO getVo() { + return vo; + } + +} diff --git a/src/br/org/scadabr/vo/dataSource/opcua/OpcUaDataSourceVO.java b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaDataSourceVO.java new file mode 100644 index 0000000000..8522e96bc6 --- /dev/null +++ b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaDataSourceVO.java @@ -0,0 +1,874 @@ +package br.org.scadabr.vo.dataSource.opcua; + +import br.org.scadabr.KeyStoreType; +import br.org.scadabr.rt.dataSource.opc.OPCDataSource; +import br.org.scadabr.rt.dataSource.opcua.OpcUaDataSourceRT; +import com.serotonin.json.*; +import com.serotonin.mango.Common; +import com.serotonin.mango.rt.dataSource.DataSourceRT; +import com.serotonin.mango.rt.event.type.AuditEventType; +import com.serotonin.mango.util.ExportCodes; +import com.serotonin.mango.vo.dataSource.DataSourceVO; +import com.serotonin.mango.vo.dataSource.PointLocatorVO; +import com.serotonin.mango.vo.event.EventTypeVO; +import com.serotonin.util.SerializationHelper; +import com.serotonin.util.StringUtils; +import com.serotonin.web.dwr.DwrResponseI18n; +import com.serotonin.web.i18n.LocalizableMessage; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; + +@JsonRemoteEntity +public class OpcUaDataSourceVO> extends + DataSourceVO { + + public static final Type TYPE = Type.OPC_UA; + + @Override + protected void addEventTypes(List eventTypes) { + eventTypes.add(createEventType( + OPCDataSource.POINT_READ_EXCEPTION_EVENT, + new LocalizableMessage("event.ds.pointRead"))); + eventTypes.add(createEventType( + OPCDataSource.DATA_SOURCE_EXCEPTION_EVENT, + new LocalizableMessage("event.ds.dataSource"))); + eventTypes.add(createEventType( + OPCDataSource.POINT_WRITE_EXCEPTION_EVENT, + new LocalizableMessage("event.ds.dataSource"))); + + } + + private static final ExportCodes EVENT_CODES = new ExportCodes(); + static { + EVENT_CODES.addElement(OPCDataSource.DATA_SOURCE_EXCEPTION_EVENT, + "DATA_SOURCE_EXCEPTION"); + EVENT_CODES.addElement(OPCDataSource.POINT_READ_EXCEPTION_EVENT, + "POINT_READ_EXCEPTION"); + EVENT_CODES.addElement(OPCDataSource.POINT_WRITE_EXCEPTION_EVENT, + "POINT_WRITE_EXCEPTION"); + + } + + @Override + public DataSourceRT createDataSourceRT() { + return new OpcUaDataSourceRT(this); + } + + @Override + public PointLocatorVO createPointLocator() { + return new OpcUaPointLocatorVO(); + } + + @Override + public LocalizableMessage getConnectionDescription() { + return new LocalizableMessage("common.default", this.serverName); + } + + @Override + public ExportCodes getEventCodes() { + return EVENT_CODES; + } + + @Override + public Type getType() { + return TYPE; + } + + @JsonRemoteProperty + private int updatePeriodType = Common.TimePeriods.SECONDS; + @JsonRemoteProperty + private int updatePeriods = 1; + @JsonRemoteProperty + private boolean quantize; + + @JsonRemoteProperty + private int serverPort = 4840; + @JsonRemoteProperty + private String serverHost = "localhost"; + @JsonRemoteProperty + private String serverName = ""; + @JsonRemoteProperty + private String serverPath = ""; + + + /* + @ConfigurationParameter("discovery") + @BooleanDefaultValue(true) + @Description("Controls the feature of the discovery endpoint of an OPC UA server which every server\n" + + "will propagate over an '
/discovery' endpoint. The most common issue here is that most servers are not correctly\n" + + "configured and propagate the wrong external IP or URL address. If that is the case you can disable the discovery by\n" + + "configuring it with a `false` value.\n" + + "\n" + + "The discovery phase is always conducted using `NONE` security policy.") + + */ + @JsonRemoteProperty + private boolean discovery = false; + + /* + @ConfigurationParameter("username") + @Description("A username to authenticate to the OPCUA server with.") + + */ + @JsonRemoteProperty + private String user; + + /* + @ConfigurationParameter("password") + @Description("A password to authenticate to the OPCUA server with.") + + */ + @JsonRemoteProperty + private String password; + + /* + @ConfigurationParameter("security-policy") + @StringDefaultValue("NONE") + @Description("The security policy applied to communication channel between driver and OPC UA server.\n" + + "Default value assumes. Possible options are `NONE`, `Basic128Rsa15`, `Basic256`, `Basic256Sha256`, `Aes128_Sha256_RsaOaep`, `Aes256_Sha256_RsaPss`.") + + */ + private OpcUaSecurityPolicyType securityPolicy = OpcUaSecurityPolicyType.NONE; + + /* + @ConfigurationParameter("message-security") + @StringDefaultValue("SIGN_ENCRYPT") + @Description("The security policy applied to messages exchanged after handshake phase.\n" + + "Possible options are `NONE`, `SIGN`, `SIGN_ENCRYPT`.\n" + + "This option is effective only when `securityPolicy` turns encryption (anything beyond `NONE`).") + + */ + private OpcUaMessageSecurityType messageSecurity = OpcUaMessageSecurityType.SIGN_ENCRYPT; + + /* + @ConfigurationParameter("key-store-file") + @Description("The Keystore file used to lookup client certificate and its private key.") + + */ + @JsonRemoteProperty + private String keyStoreFile; + + /* + @ConfigurationParameter("key-store-type") + @StringDefaultValue("pkcs12") + @Description("Keystore type used to access keystore and private key, defaults to PKCS (for Java 11+).\n" + + "Possible values are between others `jks`, `pkcs11`, `dks`, `jceks`.") + + */ + private KeyStoreType keyStoreType = KeyStoreType.PKCS12; + + /* + @ConfigurationParameter("key-store-password") + @Description("Java keystore password used to access keystore and private key.") + + */ + @JsonRemoteProperty + private String keyStorePassword; + + /* + @ConfigurationParameter("server-certificate-file") + @Description("Filesystem location where server certificate is located, supported formats are `DER` and `PEM`.") + + */ + @JsonRemoteProperty + private String serverCertificateFile; + + /* + @ConfigurationParameter("trust-store-file") + @Description("The trust store file used to verify server certificates and its chain.") + + */ + @JsonRemoteProperty + private String trustStoreFile; + + /* + @ConfigurationParameter("trust-store-type") + @StringDefaultValue("pkcs12") + @Description("Keystore type used to access keystore and private key, defaults to PKCS (for Java 11+).\n" + + "Possible values are between others `jks`, `pkcs11`, `dks`, `jceks`.") + + */ + private KeyStoreType trustStoreType = KeyStoreType.PKCS12; + + /* + @ConfigurationParameter("trust-store-password") + @Description("Password used to open trust store.") + + */ + @JsonRemoteProperty + private String trustStorePassword; + + /* + @ConfigurationParameter("channel-lifetime") + @LongDefaultValue(3600000) + @Description("Time for which negotiated secure channel, its keys and session remains open. Value in milliseconds, by default 60 minutes.") + + */ + @JsonRemoteProperty + private long channelLifetime = 3600000; + + /* + @ConfigurationParameter("session-timeout") + @LongDefaultValue(120000) + @Description("Expiry time for opened secure session, value in milliseconds. Defaults to 2 minutes.") + + */ + @JsonRemoteProperty + private long sessionTimeout = 120000; + + /* + @ConfigurationParameter("negotiation-timeout") + @LongDefaultValue(60000) + @Description("Timeout for all negotiation steps prior acceptance of application level operations - this timeout applies to open secure channel, create session and close calls. Defaults to 60 seconds.") + + */ + @JsonRemoteProperty + private long negotiationTimeout = 60000; + + /* + @ConfigurationParameter("request-timeout") + @LongDefaultValue(30000) + @Description("Timeout for read/write/subscribe calls. Value in milliseconds.") + + */ + @JsonRemoteProperty + private long requestTimeout = 30000; + + /* + @ConfigurationParameter("receive-buffer-size") + @IntDefaultValue(65535) + @Description("Maximum size of received TCP transport message chunk value in bytes.") + + */ + @JsonRemoteProperty + private int receiveBufferSize = 65535; + + /* + @ConfigurationParameter("send-buffer-size") + @IntDefaultValue(65535) + @Description("Maximum size of sent transport message chunk.") + + */ + @JsonRemoteProperty + private int sendBufferSize = 65535; + + /* + @ConfigurationParameter("max-message-size") + @IntDefaultValue(2097152) + @Description("Maximum size of complete message.") + + */ + @JsonRemoteProperty + private int maxMessageSize = 2097152; + + /* + @ConfigurationParameter("max-chunk-count") + @IntDefaultValue(64) + @Description("Maximum number of chunks for both sent and received messages.") + + */ + @JsonRemoteProperty + private int maxChunkCount = 64; + + /* + @ConfigurationParameter("keep-alive") + @BooleanDefaultValue(false) + @Description("Should keep-alive packets be sent?") + + */ + @JsonRemoteProperty + private boolean keepAlive = true; + + /* + @ConfigurationParameter("no-delay") + @BooleanDefaultValue(true) + @Description("Should packets be sent instantly or should we give the OS some time to aggregate data.") + + */ + @JsonRemoteProperty + private boolean noDelay = true; + + /* + @ConfigurationParameter("default-timeout") + @IntDefaultValue(1000) + @Description("Timeout after which a connection will be treated as disconnected.") + + */ + @JsonRemoteProperty + private long defaultTimeout = 1000; + + @JsonRemoteProperty + private int creationMode; + + public int getCreationMode() { + return creationMode; + } + + public void setCreationMode(int creationMode) { + this.creationMode = creationMode; + } + + public int getServerPort() { + return serverPort; + } + + public void setServerPort(int serverPort) { + this.serverPort = serverPort; + } + + public String getServerHost() { + return serverHost; + } + + public void setServerHost(String serverHost) { + this.serverHost = serverHost; + } + + public String getServerName() { + return serverName; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public boolean isDiscovery() { + return discovery; + } + + public void setDiscovery(boolean discovery) { + this.discovery = discovery; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public OpcUaSecurityPolicyType getSecurityPolicy() { + return securityPolicy; + } + + public void setSecurityPolicy(OpcUaSecurityPolicyType securityPolicy) { + this.securityPolicy = securityPolicy; + } + + public OpcUaMessageSecurityType getMessageSecurity() { + return messageSecurity; + } + + public void setMessageSecurity(OpcUaMessageSecurityType messageSecurity) { + this.messageSecurity = messageSecurity; + } + + public String getKeyStoreFile() { + return keyStoreFile; + } + + public void setKeyStoreFile(String keyStoreFile) { + this.keyStoreFile = keyStoreFile; + } + + public KeyStoreType getKeyStoreType() { + return keyStoreType; + } + + public void setKeyStoreType(KeyStoreType keyStoreType) { + this.keyStoreType = keyStoreType; + } + + public String getKeyStorePassword() { + return keyStorePassword; + } + + public void setKeyStorePassword(String keyStorePassword) { + this.keyStorePassword = keyStorePassword; + } + + public String getServerCertificateFile() { + return serverCertificateFile; + } + + public void setServerCertificateFile(String serverCertificateFile) { + this.serverCertificateFile = serverCertificateFile; + } + + public String getTrustStoreFile() { + return trustStoreFile; + } + + public void setTrustStoreFile(String trustStoreFile) { + this.trustStoreFile = trustStoreFile; + } + + public KeyStoreType getTrustStoreType() { + return trustStoreType; + } + + public void setTrustStoreType(KeyStoreType trustStoreType) { + this.trustStoreType = trustStoreType; + } + + public String getTrustStorePassword() { + return trustStorePassword; + } + + public void setTrustStorePassword(String trustStorePassword) { + this.trustStorePassword = trustStorePassword; + } + + public long getChannelLifetime() { + return channelLifetime; + } + + public void setChannelLifetime(long channelLifetime) { + this.channelLifetime = channelLifetime; + } + + public long getSessionTimeout() { + return sessionTimeout; + } + + public void setSessionTimeout(long sessionTimeout) { + this.sessionTimeout = sessionTimeout; + } + + public long getNegotiationTimeout() { + return negotiationTimeout; + } + + public void setNegotiationTimeout(long negotiationTimeout) { + this.negotiationTimeout = negotiationTimeout; + } + + public long getRequestTimeout() { + return requestTimeout; + } + + public void setRequestTimeout(long requestTimeout) { + this.requestTimeout = requestTimeout; + } + + public int getReceiveBufferSize() { + return receiveBufferSize; + } + + public void setReceiveBufferSize(int receiveBufferSize) { + this.receiveBufferSize = receiveBufferSize; + } + + public int getSendBufferSize() { + return sendBufferSize; + } + + public void setSendBufferSize(int sendBufferSize) { + this.sendBufferSize = sendBufferSize; + } + + public int getMaxMessageSize() { + return maxMessageSize; + } + + public void setMaxMessageSize(int maxMessageSize) { + this.maxMessageSize = maxMessageSize; + } + + public int getMaxChunkCount() { + return maxChunkCount; + } + + public void setMaxChunkCount(int maxChunkCount) { + this.maxChunkCount = maxChunkCount; + } + + public boolean isKeepAlive() { + return keepAlive; + } + + public void setKeepAlive(boolean keepAlive) { + this.keepAlive = keepAlive; + } + + public boolean isNoDelay() { + return noDelay; + } + + public void setNoDelay(boolean noDelay) { + this.noDelay = noDelay; + } + + public long getDefaultTimeout() { + return defaultTimeout; + } + + public void setDefaultTimeout(long defaultTimeout) { + this.defaultTimeout = defaultTimeout; + } + + public int getUpdatePeriodType() { + return updatePeriodType; + } + + public void setUpdatePeriodType(int updatePeriodType) { + this.updatePeriodType = updatePeriodType; + } + + public int getUpdatePeriods() { + return updatePeriods; + } + + public void setUpdatePeriods(int updatePeriods) { + this.updatePeriods = updatePeriods; + } + + public boolean isQuantize() { + return quantize; + } + + public void setQuantize(boolean quantize) { + this.quantize = quantize; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getServerPath() { + return serverPath; + } + + public void setServerPath(String serverPath) { + this.serverPath = serverPath; + } + + public String getServerAddress() { + return MessageFormat.format("opcua:tcp://{0}:{1}{2}", serverHost, String.valueOf(serverPort), (StringUtils.isEmpty(serverPath) ? "" : "/" + serverPath)); + } + + public String getConnectionString() { + + String address = getServerAddress(); + String discoveryQuery = MessageFormat.format("?discovery={0}", isDiscovery()); + String query = ""; + + /// Sec + + if (getSecurityPolicy() != null) { + query += MessageFormat.format("&security-policy={0}", getSecurityPolicy().getDescription()); + + if(getSecurityPolicy() != OpcUaSecurityPolicyType.NONE) { + + if (!StringUtils.isEmpty(getUser())) { + query += MessageFormat.format("&username={0}", getUser()); + } + + if (!StringUtils.isEmpty(getPassword())) { + query += MessageFormat.format("&password={0}", getPassword()); + } + + if (getMessageSecurity() != null) { + query += MessageFormat.format("&message-security={0}", getMessageSecurity().getCode()); + } + + if (!StringUtils.isEmpty(getServerCertificateFile())) { + String serverCertFilePath = URLEncoder.encode( + Paths.get(getServerCertificateFile()).toString(), + StandardCharsets.UTF_8 + ); + query += MessageFormat.format("&server-certificate-file={0}", serverCertFilePath); + } + + if (!StringUtils.isEmpty(getKeyStoreFile())) { + String keyStoreFilePath = URLEncoder.encode( + Paths.get(getKeyStoreFile()).toString(), + StandardCharsets.UTF_8 + ); + query += MessageFormat.format("&key-store-file={0}" + + "&key-store-type={1}" + + "&key-store-password={2}", keyStoreFilePath, getKeyStoreType().getDescription(), getKeyStorePassword()); + } + + if (!StringUtils.isEmpty(getTrustStoreFile())) { + String trustStoreFilePath = URLEncoder.encode( + Paths.get(getTrustStoreFile()).toString(), + StandardCharsets.UTF_8 + ); + query += MessageFormat.format("&trust-store-file={0}" + + "&trust-store-type={1}" + + "&trust-store-password={2}", trustStoreFilePath, getTrustStoreType().getDescription(), getTrustStorePassword()); + } + } + } + /// + + if(getMaxChunkCount() > 0) { + query += MessageFormat.format("&encoding.max-chunk-count={0}", String.valueOf(getMaxChunkCount())); + } + + if(getMaxMessageSize() > 0) { + query += MessageFormat.format("&encoding.max-message-size={0}", String.valueOf(getMaxMessageSize())); + } + + if(getSendBufferSize() > 0) { + query += MessageFormat.format("&encoding.send-buffer-size={0}", String.valueOf(getSendBufferSize())); + } + + if(getReceiveBufferSize() > 0) { + query += MessageFormat.format("&encoding.receive-buffer-size={0}", String.valueOf(getReceiveBufferSize())); + } + + /// + + if(getChannelLifetime() > 0) { + query += MessageFormat.format("&channel-lifetime={0}", String.valueOf(getChannelLifetime())); + } + + if(getNegotiationTimeout() > 0) { + query += MessageFormat.format("&negotiation-timeout={0}", String.valueOf(getNegotiationTimeout())); + } + + if(getRequestTimeout() > 0) { + query += MessageFormat.format("&request-timeout={0}", String.valueOf(getRequestTimeout())); + } + + if(getSessionTimeout() > 0) { + query += MessageFormat.format("&session-timeout={0}", String.valueOf(getSessionTimeout())); + } + + /// + + query += MessageFormat.format("&tcp.keep-alive={0}" + + "&tcp.no-delay={1}", isKeepAlive(), isNoDelay()); + + if(getDefaultTimeout() > 0) { + query += MessageFormat.format("&tcp.default-timeout={0}", String.valueOf(getDefaultTimeout())); + } + + return address + discoveryQuery + query; + } + + @Override + public void validate(DwrResponseI18n response) { + super.validate(response); + if (StringUtils.isEmpty(serverHost)) + response.addContextualMessage("host", "validate.required"); + if (StringUtils.isEmpty(user)) + response.addContextualMessage("user", "validate.required"); + if (StringUtils.isEmpty(password)) + response.addContextualMessage("password", "validate.required"); + if (StringUtils.isEmpty(serverName)) + response.addContextualMessage("server", "validate.required"); + if (updatePeriods <= 0) + response.addContextualMessage("updatePeriods", + "validate.greaterThanZero"); + } + + @Override + protected void addPropertiesImpl(List list) { + AuditEventType.addPeriodMessage(list, "dsEdit.updatePeriod", + updatePeriodType, updatePeriods); + AuditEventType.addPropertyMessage(list, "dsEdit.opcua.host", serverHost); + AuditEventType.addPropertyMessage(list, "dsEdit.opcua.user", user); + AuditEventType + .addPropertyMessage(list, "dsEdit.opcua.password", password); + AuditEventType.addPropertyMessage(list, "dsEdit.opcua.server", serverName); + + } + + @Override + protected void addPropertyChangesImpl(List list, T from) { + AuditEventType.maybeAddPeriodChangeMessage(list, + "dsEdit.updatePeriod", from.getUpdatePeriodType(), + from.getUpdatePeriods(), updatePeriodType, updatePeriods); + AuditEventType.maybeAddPropertyChangeMessage(list, "dsEdit.opcua.host", + from.getServerHost(), serverHost); + AuditEventType.maybeAddPropertyChangeMessage(list, "dsEdit.opcua.user", + from.getUser(), user); + AuditEventType.maybeAddPropertyChangeMessage(list, + "dsEdit.opcua.password", from.getPassword(), password); + AuditEventType.maybeAddPropertyChangeMessage(list, "dsEdit.opcua.server", + from.getServerName(), serverName); + + } + + // + // / + // / Serialization + // / + // + private static final long serialVersionUID = -1; + private static final int version = 1; + + private void writeObject(ObjectOutputStream out) throws IOException { + out.writeInt(version); + + out.writeInt(updatePeriodType); + out.writeInt(updatePeriods); + + SerializationHelper.writeSafeUTF(out, serverHost); + SerializationHelper.writeSafeUTF(out, user); + SerializationHelper.writeSafeUTF(out, password); + SerializationHelper.writeSafeUTF(out, serverName); + + SerializationHelper.writeSafeUTF(out, keyStorePassword); + SerializationHelper.writeSafeUTF(out, trustStorePassword); + + out.writeInt(maxChunkCount); + out.writeInt(serverPort); + out.writeInt(this.maxMessageSize); + out.writeInt(this.sendBufferSize); + out.writeInt(this.receiveBufferSize); + + out.writeLong(this.defaultTimeout); + out.writeLong(this.channelLifetime); + out.writeLong(this.negotiationTimeout); + out.writeLong(this.sessionTimeout); + out.writeLong(this.requestTimeout); + + out.writeBoolean(this.noDelay); + out.writeBoolean(this.keepAlive); + out.writeBoolean(this.discovery); + + out.writeObject(this.keyStoreType); + out.writeObject(this.messageSecurity); + out.writeObject(this.securityPolicy); + out.writeObject(this.trustStoreType); + + SerializationHelper.writeSafeUTF(out, keyStoreFile); + SerializationHelper.writeSafeUTF(out, trustStoreFile); + SerializationHelper.writeSafeUTF(out, serverCertificateFile); + SerializationHelper.writeSafeUTF(out, serverPath); + } + + private void readObject(ObjectInputStream in) throws IOException, + ClassNotFoundException { + int ver = in.readInt(); + if (ver == 1) { + updatePeriodType = in.readInt(); + updatePeriods = in.readInt(); + + serverHost = SerializationHelper.readSafeUTF(in); + user = SerializationHelper.readSafeUTF(in); + password = SerializationHelper.readSafeUTF(in); + serverName = SerializationHelper.readSafeUTF(in); + keyStorePassword = SerializationHelper.readSafeUTF(in); + trustStorePassword = SerializationHelper.readSafeUTF(in); + + maxChunkCount = in.readInt(); + serverPort = in.readInt(); + maxMessageSize = in.readInt(); + sendBufferSize = in.readInt(); + receiveBufferSize = in.readInt(); + + defaultTimeout = in.readLong(); + channelLifetime = in.readLong(); + negotiationTimeout = in.readLong(); + sessionTimeout = in.readLong(); + requestTimeout = in.readLong(); + + noDelay = in.readBoolean(); + keepAlive = in.readBoolean(); + discovery = in.readBoolean(); + + try { + keyStoreType = (KeyStoreType) in.readObject(); + } catch (Exception e) { + keyStoreType = KeyStoreType.DEFAULT; + } + + try { + messageSecurity = (OpcUaMessageSecurityType) in.readObject(); + } catch (Exception e) { + messageSecurity = OpcUaMessageSecurityType.DEFAULT; + } + + try { + securityPolicy = (OpcUaSecurityPolicyType) in.readObject(); + } catch (Exception e) { + securityPolicy = OpcUaSecurityPolicyType.DEFAULT; + } + + try { + trustStoreType = (KeyStoreType) in.readObject(); + } catch (Exception e) { + trustStoreType = KeyStoreType.DEFAULT; + } + + keyStoreFile = SerializationHelper.readSafeUTF(in); + trustStoreFile = SerializationHelper.readSafeUTF(in); + serverCertificateFile = SerializationHelper.readSafeUTF(in); + serverPath = SerializationHelper.readSafeUTF(in); + } + } + + @Override + public void jsonDeserialize(JsonReader reader, JsonObject json) + throws JsonException { + super.jsonDeserialize(reader, json); + Integer value = deserializeUpdatePeriodType(json); + if (value != null) + updatePeriodType = value; + + String keyStoreTypeJson = json.getString("keyStoreType"); + if(keyStoreTypeJson != null) { + try { + keyStoreType = KeyStoreType.valueOf(keyStoreTypeJson); + } catch (Exception ex) { + keyStoreType = KeyStoreType.DEFAULT; + } + } + + String messageSecurityJson = json.getString("messageSecurity"); + if(messageSecurityJson != null) { + try { + messageSecurity = OpcUaMessageSecurityType.valueOf(messageSecurityJson); + } catch (Exception ex) { + messageSecurity = OpcUaMessageSecurityType.DEFAULT; + } + } + + String securityPolicyJson = json.getString("securityPolicy"); + if(securityPolicyJson != null) { + try { + securityPolicy = OpcUaSecurityPolicyType.valueOf(securityPolicyJson); + } catch (Exception ex) { + securityPolicy = OpcUaSecurityPolicyType.DEFAULT; + } + } + + String trustStoreTypeJson = json.getString("trustStoreType"); + if(trustStoreTypeJson != null) { + try { + trustStoreType = KeyStoreType.valueOf(trustStoreTypeJson); + } catch (Exception ex) { + trustStoreType = KeyStoreType.DEFAULT; + } + } + } + + @Override + public void jsonSerialize(Map map) { + super.jsonSerialize(map); + serializeUpdatePeriodType(map, updatePeriodType); + map.put("keyStoreType", keyStoreType.name()); + map.put("messageSecurity", messageSecurity.name()); + map.put("securityPolicy", securityPolicy.name()); + map.put("trustStoreType", trustStoreType.name()); + } + +} diff --git a/src/br/org/scadabr/vo/dataSource/opcua/OpcUaDataType.java b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaDataType.java new file mode 100644 index 0000000000..6a4b49de43 --- /dev/null +++ b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaDataType.java @@ -0,0 +1,192 @@ +package br.org.scadabr.vo.dataSource.opcua; + +import com.serotonin.mango.DataTypes; + +import java.util.stream.Stream; + +public enum OpcUaDataType { + + BOOL ("boolean", DataTypes.BINARY) { + public Object convert(Object value) throws Exception { + return OpcUaDataType.toBoolean(value); + } + }, + + SINT ("int 8", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toByte(value); + } + }, + + USINT ("uint 8", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toByte(value); + } + }, + + BYTE ("uint 8", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toByte(value); + } + }, + + INT ("int 16", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toShort(value); + } + }, + + UINT ("uint 16", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toShort(value); + } + }, + + WORD ("uint 16", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toShort(value); + } + }, + + DINT ("int 32", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toInt(value); + } + }, + + UDINT ("uint 32", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toInt(value); + } + }, + + DWORD ("uint 32", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toInt(value); + } + }, + + LINT ("int 64", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toLong(value); + } + }, + + ULINT ("uint 64", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toLong(value); + } + }, + + LWORD ("uint 64", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toLong(value); + } + }, + + REAL ("float", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toFloat(value); + } + }, + + LREAL ("double", DataTypes.NUMERIC) { + public Object convert(Object value) throws Exception { + return toDouble(value); + } + }, + + CHAR ("char", DataTypes.ALPHANUMERIC) { + public Object convert(Object value) throws Exception { + return toCharOrString(value); + } + }, + + WCHAR ("2 byte char", DataTypes.ALPHANUMERIC) { + public Object convert(Object value) throws Exception { + return toCharOrString(value); + } + }, + + STRING ("utf-8", DataTypes.ALPHANUMERIC) { + public Object convert(Object value) throws Exception { + return OpcUaDataType.toString(value); + } + }, + + WSTRING ("utf-16", DataTypes.ALPHANUMERIC) { + public Object convert(Object value) throws Exception { + return OpcUaDataType.toString(value); + } + }; + + public static final OpcUaDataType DEFAULT = OpcUaDataType.STRING; + + private final String decription; + private final int dataTypesId; + + OpcUaDataType(String decription, int dataTypesId) { + this.decription = decription; + this.dataTypesId = dataTypesId; + } + + public String getCode() { + return this.name(); + } + + public int getDataTypesId() { + return dataTypesId; + } + + public String getDescription() { + return getCode() + " (" + this.decription + ")"; + } + + public abstract Object convert(Object value) throws Exception; + + public static OpcUaDataType valueOf(int id) { + return Stream.of(OpcUaDataType.values()).filter(a -> a.getDataTypesId() == id).findAny().orElse(OpcUaDataType.STRING); + } + + private static boolean toBoolean(Object value) { + return Boolean.parseBoolean(String.valueOf(value)); + } + + private static Object toCharOrString(Object value) { + String result = String.valueOf(value); + char[] resultRaw = result.toCharArray(); + return resultRaw.length == 1 ? resultRaw[0] : result; + } + + private static byte toByte(Object value) { + return Byte.parseByte(toStringForInt(value)); + } + + private static short toShort(Object value) { + return Short.parseShort(toStringForInt(value)); + } + + private static int toInt(Object value) { + return Integer.parseInt(toStringForInt(value)); + } + + private static long toLong(Object value) { + return Long.parseLong(toStringForInt(value)); + } + + private static double toDouble(Object value) { + return Double.parseDouble(String.valueOf(value)); + } + + private static float toFloat(Object value) { + return Float.parseFloat(String.valueOf(value)); + } + + private static String toStringForInt(Object value) { + return String.valueOf(value).replace(".0", ""); + } + + private static String toString(Object value) { + return String.valueOf(value); + } +} diff --git a/src/br/org/scadabr/vo/dataSource/opcua/OpcUaIdentifierType.java b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaIdentifierType.java new file mode 100644 index 0000000000..ddcdeb1ab2 --- /dev/null +++ b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaIdentifierType.java @@ -0,0 +1,22 @@ +package br.org.scadabr.vo.dataSource.opcua; + +public enum OpcUaIdentifierType { + + STRING("s"), NUMERIC("i"), BINARY("b"), GUID("g"); + + public static final OpcUaIdentifierType DEFAULT = OpcUaIdentifierType.STRING; + + private final String code; + + OpcUaIdentifierType(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return this.name(); + } +} diff --git a/src/br/org/scadabr/vo/dataSource/opcua/OpcUaItem.java b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaItem.java new file mode 100644 index 0000000000..768ff0245d --- /dev/null +++ b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaItem.java @@ -0,0 +1,87 @@ +package br.org.scadabr.vo.dataSource.opcua; + +public class OpcUaItem { + private String identifier; + private OpcUaIdentifierType identifierType; + private String tag; + private OpcUaDataType dataType; + private int dataTypeId; + private boolean settable; + private boolean validate; + private int namespaceIndex; + + public OpcUaItem(boolean validate, OpcUaPointLocatorVO pointLocator) { + this.validate = validate; + this.tag = pointLocator.getTag(); + this.settable = pointLocator.isSettable(); + this.identifier = pointLocator.getIdentifier(); + this.identifierType = pointLocator.getIdentifierType(); + this.dataType = pointLocator.getDataType(); + this.dataTypeId = pointLocator.getDataType().getDataTypesId(); + this.namespaceIndex = pointLocator.getNamespaceIndex(); + } + + public boolean isValidate() { + return this.validate; + } + + public void setValidate(boolean validate) { + this.validate = validate; + } + + public String getTag() { + return this.tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public OpcUaDataType getDataType() { + return this.dataType; + } + + public void setDataType(OpcUaDataType dataType) { + this.dataType = dataType; + } + + public boolean isSettable() { + return this.settable; + } + + public void setSettable(boolean settable) { + this.settable = settable; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public OpcUaIdentifierType getIdentifierType() { + return identifierType; + } + + public void setIdentifierType(OpcUaIdentifierType identifierType) { + this.identifierType = identifierType; + } + + public int getDataTypeId() { + return dataTypeId; + } + + public void setDataTypeId(int dataTypeId) { + this.dataTypeId = dataTypeId; + } + + public int getNamespaceIndex() { + return namespaceIndex; + } + + public void setNamespaceIndex(int namespaceIndex) { + this.namespaceIndex = namespaceIndex; + } +} diff --git a/src/br/org/scadabr/vo/dataSource/opcua/OpcUaMessageSecurityType.java b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaMessageSecurityType.java new file mode 100644 index 0000000000..55b75c5fc3 --- /dev/null +++ b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaMessageSecurityType.java @@ -0,0 +1,22 @@ +package br.org.scadabr.vo.dataSource.opcua; + +public enum OpcUaMessageSecurityType { + NONE("None"), SIGN("Sign"), SIGN_ENCRYPT("SignAndEncrypt"); + + public static final OpcUaMessageSecurityType DEFAULT = OpcUaMessageSecurityType.SIGN_ENCRYPT; + + private final String decription; + + OpcUaMessageSecurityType(String decription) { + this.decription = decription; + } + + public String getCode() { + return this.name(); + } + + public String getDescription() { + return this.decription; + } + +} diff --git a/src/br/org/scadabr/vo/dataSource/opcua/OpcUaPointLocatorVO.java b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaPointLocatorVO.java new file mode 100644 index 0000000000..dd6ca53468 --- /dev/null +++ b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaPointLocatorVO.java @@ -0,0 +1,237 @@ +package br.org.scadabr.vo.dataSource.opcua; + +import br.org.scadabr.rt.dataSource.opcua.OpcUaPointLocatorRT; +import com.serotonin.json.*; +import com.serotonin.mango.rt.dataSource.PointLocatorRT; +import com.serotonin.mango.rt.event.type.AuditEventType; +import com.serotonin.mango.vo.dataSource.AbstractPointLocatorVO; +import com.serotonin.util.SerializationHelper; +import com.serotonin.util.StringUtils; +import com.serotonin.web.dwr.DwrResponseI18n; +import com.serotonin.web.i18n.LocalizableMessage; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; + +public class OpcUaPointLocatorVO extends AbstractPointLocatorVO implements + JsonSerializable { + + @Override + public PointLocatorRT createRuntime() { + return new OpcUaPointLocatorRT(this); + } + + @Override + public LocalizableMessage getConfigurationDescription() { + return new LocalizableMessage("common.tp.description", new LocalizableMessage("dsEdit.opc.tag"), this.tag); + } + + @Override + public int getDataTypeId() { + return dataType.getDataTypesId(); + } + + public void setDataType(OpcUaDataType dataType) { + this.dataType = dataType; + } + + @Override + public boolean isSettable() { + return settable; + } + + public void setSettable(boolean settable) { + this.settable = settable; + } + + @JsonRemoteProperty + private String tag = ""; + private OpcUaDataType dataType = OpcUaDataType.DEFAULT; + @JsonRemoteProperty + private boolean settable; + private OpcUaIdentifierType identifierType = OpcUaIdentifierType.DEFAULT; + @JsonRemoteProperty + private String identifier; + @JsonRemoteProperty + private int namespaceIndex; + @JsonRemoteProperty + private String attributes; + + public OpcUaPointLocatorVO() { + } + + private OpcUaPointLocatorVO(OpcUaPointLocatorVO pointLocator) { + this.tag = pointLocator.getTag(); + this.dataType = pointLocator.getDataType(); + this.settable = pointLocator.isSettable(); + this.identifierType = pointLocator.getIdentifierType(); + this.identifier = pointLocator.getIdentifier(); + this.namespaceIndex = pointLocator.getNamespaceIndex(); + this.attributes = pointLocator.getAttributes(); + } + + @Override + public void validate(DwrResponseI18n response) { + + } + + @Override + public void addProperties(List list) { + AuditEventType.addDataTypeMessage(list, "dsEdit.opcua.dataType", dataType); + AuditEventType.addPropertyMessage(list, "dsEdit.opcua.tag", tag); + AuditEventType.addPropertyMessage(list, "dsEdit.settable", settable); + AuditEventType.addPropertyMessage(list, "dsEdit.opcua.identifier", identifier); + AuditEventType.addPropertyMessage(list, "dsEdit.opcua.identifierType", identifierType); + AuditEventType.addPropertyMessage(list, "dsEdit.opcua.namespaceIndex", namespaceIndex); + AuditEventType.addPropertyMessage(list, "dsEdit.opcua.attributes", attributes); + } + + @Override + public void addPropertyChanges(List list, Object o) { + OpcUaPointLocatorVO from = (OpcUaPointLocatorVO) o; + + AuditEventType.maybeAddObjectChangeMessage(list, + "dsEdit.opcua.dataType", from.dataType, dataType); + AuditEventType.maybeAddPropertyChangeMessage(list, "dsEdit.opcua.tag", + from.tag, tag); + AuditEventType.maybeAddPropertyChangeMessage(list, "dsEdit.settable", + from.settable, settable); + + AuditEventType.maybeAddPropertyChangeMessage(list, "dsEdit.opcua.identifier", + from.identifier, identifier); + + AuditEventType.maybeAddPropertyChangeMessage(list, "dsEdit.opcua.identifierType", + from.identifierType, identifierType); + + AuditEventType.maybeAddPropertyChangeMessage(list, "dsEdit.opcua.namespaceIndex", + from.namespaceIndex, namespaceIndex); + + AuditEventType.maybeAddPropertyChangeMessage(list, "dsEdit.opcua.attributes", + from.attributes, attributes); + } + + private static final long serialVersionUID = -1; + private static final int version = 1; + + private void writeObject(ObjectOutputStream out) throws IOException { + out.writeInt(version); + SerializationHelper.writeSafeUTF(out, tag); + out.writeObject(dataType); + out.writeBoolean(settable); + SerializationHelper.writeSafeUTF(out, identifier); + out.writeObject(identifierType); + out.writeInt(namespaceIndex); + SerializationHelper.writeSafeUTF(out, attributes); + } + + private void readObject(ObjectInputStream in) throws IOException, + ClassNotFoundException { + int ver = in.readInt(); + if (ver == 1) { + tag = SerializationHelper.readSafeUTF(in); + try { + dataType = (OpcUaDataType) in.readObject(); + } catch (Exception e) { + dataType = OpcUaDataType.DEFAULT; + } + settable = in.readBoolean(); + identifier = SerializationHelper.readSafeUTF(in); + try { + identifierType = (OpcUaIdentifierType) in.readObject(); + } catch (Exception e) { + identifierType = OpcUaIdentifierType.DEFAULT; + } + namespaceIndex = in.readInt(); + attributes = SerializationHelper.readSafeUTF(in); + } + } + + @Override + public void jsonDeserialize(JsonReader reader, JsonObject json) + throws JsonException { + + String dataTypeJson = json.getString("dataType"); + if(dataTypeJson != null) { + try { + dataType = OpcUaDataType.valueOf(dataTypeJson); + } catch (Exception ex) { + dataType = OpcUaDataType.DEFAULT; + } + } + + String identifierTypeJson = json.getString("identifierType"); + if(identifierTypeJson != null) { + try { + identifierType = OpcUaIdentifierType.valueOf(identifierTypeJson); + } catch (Exception ex) { + identifierType = OpcUaIdentifierType.DEFAULT; + } + } + } + + @Override + public void jsonSerialize(Map map) { + serializeDataType(map); + map.put("identifierType", identifierType.name()); + map.put("dataType", dataType.name()); + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public OpcUaDataType getDataType() { + return dataType; + } + + public OpcUaIdentifierType getIdentifierType() { + return identifierType; + } + + public void setIdentifierType(OpcUaIdentifierType identifierType) { + this.identifierType = identifierType; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public int getNamespaceIndex() { + return namespaceIndex; + } + + public void setNamespaceIndex(int namespaceIndex) { + this.namespaceIndex = namespaceIndex; + } + + public String getAttributes() { + return attributes; + } + + public void setAttributes(String attributes) { + this.attributes = attributes; + } + + public String getNodeId() { + if (!StringUtils.isEmpty(getAttributes())) { + return MessageFormat.format("ns={0};{1}={2};{3};{4}", getNamespaceIndex(), getIdentifierType().getCode(), getIdentifier(), getAttributes(), getDataType()); + } + return MessageFormat.format("ns={0};{1}={2};{3}", getNamespaceIndex(), getIdentifierType().getCode(), getIdentifier(), getDataType()); + } + + public OpcUaPointLocatorVO copy() { + return new OpcUaPointLocatorVO(this); + } +} diff --git a/src/br/org/scadabr/vo/dataSource/opcua/OpcUaSecurityPolicyType.java b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaSecurityPolicyType.java new file mode 100644 index 0000000000..3459e024bc --- /dev/null +++ b/src/br/org/scadabr/vo/dataSource/opcua/OpcUaSecurityPolicyType.java @@ -0,0 +1,27 @@ +package br.org.scadabr.vo.dataSource.opcua; + +public enum OpcUaSecurityPolicyType { + + NONE("None"), + BASIC_128_RSA_15("Basic128Rsa15"), + BASIC_256("Basic256"), + BASIC_256_SHA_256("Basic256Sha256"), + AES_128_SHA_256_RSA_OAEP("Aes128_Sha256_RsaOaep"), + AES_256_SHA_256_RSA_PSS("Aes256_Sha256_RsaPss"); + + public static final OpcUaSecurityPolicyType DEFAULT = OpcUaSecurityPolicyType.NONE; + + private final String description; + + OpcUaSecurityPolicyType(String description) { + this.description = description; + } + + public String getCode() { + return this.name(); + } + + public String getDescription() { + return description; + } +} diff --git a/src/com/serotonin/mango/Common.java b/src/com/serotonin/mango/Common.java index f07c3b0dd5..75eb04daaf 100644 --- a/src/com/serotonin/mango/Common.java +++ b/src/com/serotonin/mango/Common.java @@ -29,6 +29,7 @@ import javax.servlet.http.HttpServletRequest; +import com.serotonin.mango.vo.*; import com.serotonin.mango.web.mvc.controller.ScadaLocaleUtils; import org.scada_lts.monitor.IMonitoredValues; import gnu.io.CommPortIdentifier; @@ -53,8 +54,6 @@ import com.serotonin.mango.util.ExportCodes; import com.serotonin.mango.view.View; import com.serotonin.mango.view.custom.CustomView; -import com.serotonin.mango.vo.CommPortProxy; -import com.serotonin.mango.vo.User; import com.serotonin.mango.web.ContextWrapper; import org.scada_lts.monitor.ConcurrentMonitoredValues; import com.serotonin.timer.CronTimerTrigger; @@ -219,6 +218,13 @@ public static LocalizableMessage getPeriodDescription(int periodType, new LocalizableMessage(periodKey)); } + public static LocalizableMessage getPeriodDescription(TimePeriod periodType, + int periods) { + String periodKey = periodType.getKey(); + return new LocalizableMessage("common.tp.description", periods, + new LocalizableMessage(periodKey)); + } + // // Session user public static User getUser() { diff --git a/src/com/serotonin/mango/MangoContextListener.java b/src/com/serotonin/mango/MangoContextListener.java index 74a5f8f884..a9efad81d9 100644 --- a/src/com/serotonin/mango/MangoContextListener.java +++ b/src/com/serotonin/mango/MangoContextListener.java @@ -73,6 +73,7 @@ import org.scada_lts.scripting.SandboxContextFactory; import org.scada_lts.service.HighestAlarmLevelServiceWithCache; import org.scada_lts.service.IHighestAlarmLevelService; +import org.scada_lts.utils.ThreadInfoApiUtils; import org.scada_lts.web.beans.ApplicationBeans; import javax.servlet.ServletContext; @@ -86,6 +87,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static com.serotonin.mango.util.ThreadPoolExecutorUtils.createPool; import static org.scada_lts.utils.UploadFileUtils.loadGraphics; @@ -227,12 +229,47 @@ public void contextDestroyed(ServletContextEvent evt) { utilitiesTerminate(ctx); highPriorityServiceTerminate(); databaseTerminate(ctx); + otherThreadsTerminate(); Common.ctx = null; log.info("Scada-LTS context terminated"); } + private void otherThreadsTerminate() { + + List threads = ThreadInfoApiUtils.getThreads() + .stream() + .filter(a -> toInterrupt(a)) + .collect(Collectors.toList()); + log.info("Threads size before terminating: " + threads.size()); + + List notInterruptedthreads = threads.stream() + .peek(a -> { + log.info(a.getName() + " - terminating"); + try { + a.interrupt(); + a.stop(); + } catch (Throwable ex) { + log.info(a.getName() + " - terminating - error: " + ex.getMessage()); + } + log.info(a.getName() + " - terminated"); + }) + .filter(a -> !a.isInterrupted()) + .collect(Collectors.toList()); + + log.info("Threads size after terminating: " + notInterruptedthreads.size()); + } + + private static boolean toInterrupt(Thread a) { + return !a.isDaemon() && !a.getName().equals("main") && !a.getName().equals(Thread.currentThread().getName()) + && !a.getName().startsWith("MessageBroker") + && !a.getName().startsWith("clientOutboundChannel") + && !a.getName().startsWith("clientInboundChannel") + && !a.getName().startsWith("Catalina-utility") + && !a.getName().startsWith("DefaultQuartzScheduler_"); + } + private void highPriorityServiceTerminate() { Common.timer.cancel(); } @@ -345,6 +382,9 @@ private void constantsInitialize(ServletContext ctx) { DataSourceVO.Type.JMX.getId()); ctx.setAttribute("constants.DataSourceVO.Types.MQTT", DataSourceVO.Type.MQTT.getId()); + ctx.setAttribute("constants.DataSourceVO.Types.OPC_UA", + DataSourceVO.Type.OPC_UA.getId()); + ctx.setAttribute("constants.Permissions.DataPointAccessTypes.NONE", Permissions.DataPointAccessTypes.NONE); ctx.setAttribute("constants.Permissions.DataPointAccessTypes.READ", diff --git a/src/com/serotonin/mango/rt/dataSource/PollingDataSource.java b/src/com/serotonin/mango/rt/dataSource/PollingDataSource.java index 4098723397..7d9bcb71cb 100644 --- a/src/com/serotonin/mango/rt/dataSource/PollingDataSource.java +++ b/src/com/serotonin/mango/rt/dataSource/PollingDataSource.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import com.serotonin.mango.util.LoggingUtils; import org.apache.commons.logging.Log; @@ -49,6 +50,8 @@ abstract public class PollingDataSource extends DataSourceRT implements TimeoutC private long jobThreadStartTime; private static volatile boolean markAsTerminating = false; + private final AtomicInteger lock = new AtomicInteger(0); + public PollingDataSource(DataSourceVO vo) { super(vo); this.vo = vo; @@ -59,7 +62,7 @@ public void setPollingPeriod(int periodType, int periods, boolean quantize) { this.quantize = quantize; } - public void scheduleTimeout(long fireTime) { + public void scheduleTimeout2(long fireTime) { if(isMarkAsTerminating()) { return; } @@ -86,6 +89,29 @@ public void scheduleTimeout(long fireTime) { } } + public void scheduleTimeout(long fireTime) { + + if(isMarkAsTerminating()) { + return; + } + + if(lock.getAndIncrement() == 0) { + try { + jobThreadStartTime = fireTime; + updateChangedPoints(); + doPoll(fireTime); + } finally { + lock.set(0); + } + } else { + // There is another poll still running, so abort this one. + LOG.warn(vo.getName() + ": poll at " + DateFunctions.getFullSecondTime(fireTime) + + " aborted because a previous poll started at " + + DateFunctions.getFullSecondTime(jobThreadStartTime) + " is still running"); + return; + } + } + abstract protected void doPoll(long time); protected void updateChangedPoints() { diff --git a/src/com/serotonin/mango/rt/event/type/AuditEventType.java b/src/com/serotonin/mango/rt/event/type/AuditEventType.java index ae89036781..672fb49c33 100644 --- a/src/com/serotonin/mango/rt/event/type/AuditEventType.java +++ b/src/com/serotonin/mango/rt/event/type/AuditEventType.java @@ -22,12 +22,14 @@ import java.util.List; import java.util.Map; +import br.org.scadabr.vo.dataSource.opcua.OpcUaDataType; import com.serotonin.json.JsonException; import com.serotonin.json.JsonObject; import com.serotonin.json.JsonReader; import com.serotonin.json.JsonRemoteEntity; import com.serotonin.mango.Common; import com.serotonin.mango.DataTypes; +import com.serotonin.mango.vo.TimePeriod; import org.scada_lts.dao.SystemSettingsDAO; import com.serotonin.mango.rt.event.AlarmLevels; import com.serotonin.mango.util.ChangeComparable; @@ -170,6 +172,12 @@ public static void addPeriodMessage(List list, String proper .getPeriodDescription(periodType, period))); } + public static void addPeriodMessage(List list, String propertyNameKey, TimePeriod periodType, + int period) { + list.add(new LocalizableMessage("event.audit.property", new LocalizableMessage(propertyNameKey), Common + .getPeriodDescription(periodType, period))); + } + public static void addExportCodeMessage(List list, String propertyNameKey, ExportCodes codes, int id) { list.add(new LocalizableMessage("event.audit.property", new LocalizableMessage(propertyNameKey), @@ -181,6 +189,10 @@ public static void addDataTypeMessage(List list, String prop .getDataTypeMessage(dataTypeId))); } + public static void addDataTypeMessage(List list, String propertyNameKey, OpcUaDataType dataType) { + list.add(new LocalizableMessage("event.audit.property", new LocalizableMessage(propertyNameKey), dataType.name())); + } + public static void maybeAddPropertyChangeMessage(List list, String propertyNameKey, int fromValue, int toValue) { if (fromValue != toValue) @@ -213,6 +225,13 @@ public static void maybeAddPeriodChangeMessage(List list, St Common.getPeriodDescription(toPeriodType, toPeriod)); } + public static void maybeAddPeriodChangeMessage(List list, String propertyNameKey, + TimePeriod fromPeriodType, int fromPeriod, TimePeriod toPeriodType, int toPeriod) { + if (fromPeriodType != toPeriodType || fromPeriod != toPeriod) + addPropertyChangeMessage(list, propertyNameKey, Common.getPeriodDescription(fromPeriodType, fromPeriod), + Common.getPeriodDescription(toPeriodType, toPeriod)); + } + public static void maybeAddExportCodeChangeMessage(List list, String propertyNameKey, ExportCodes exportCodes, int fromId, int toId) { if (fromId != toId) @@ -227,6 +246,13 @@ public static void maybeAddDataTypeChangeMessage(List list, DataTypes.getDataTypeMessage(toDataTypeId)); } + public static void maybeAddObjectChangeMessage(List list, String propertyNameKey, + OpcUaDataType fromDataType, OpcUaDataType toDataType) { + if (fromDataType != toDataType) + addPropertyChangeMessage(list, propertyNameKey, fromDataType, + toDataType); + } + private static LocalizableMessage getBooleanMessage(boolean value) { if (value) return new LocalizableMessage("common.true"); diff --git a/src/com/serotonin/mango/rt/event/type/DataSourcePointEventType.java b/src/com/serotonin/mango/rt/event/type/DataSourcePointEventType.java index f0a04b54b9..bf4a5f1238 100644 --- a/src/com/serotonin/mango/rt/event/type/DataSourcePointEventType.java +++ b/src/com/serotonin/mango/rt/event/type/DataSourcePointEventType.java @@ -35,7 +35,7 @@ public class DataSourcePointEventType extends DataSourceEventType { private DataSourceEventType dataSourceEventType; private int dataPointId; - private final int duplicateHandling = DuplicateHandling.IGNORE_SAME_MESSAGE; + private final int duplicateHandling = DuplicateHandling.IGNORE; private final Log LOG = LogFactory.getLog(DataSourcePointEventType.class); diff --git a/src/com/serotonin/mango/rt/maint/work/PlcConnectionClosingWorkItem.java b/src/com/serotonin/mango/rt/maint/work/PlcConnectionClosingWorkItem.java new file mode 100644 index 0000000000..39dd3c838e --- /dev/null +++ b/src/com/serotonin/mango/rt/maint/work/PlcConnectionClosingWorkItem.java @@ -0,0 +1,34 @@ +package com.serotonin.mango.rt.maint.work; + +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.spi.connection.DefaultNettyPlcConnection; + +import java.util.concurrent.ExecutionException; + +public class PlcConnectionClosingWorkItem extends AbstractBeforeAfterWorkItem { + + private final PlcConnection plcConnection; + + public PlcConnectionClosingWorkItem(PlcConnection plcConnection) { + this.plcConnection = plcConnection; + } + + @Override + public void work() { + try { + plcConnection.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public WorkItemPriority getPriorityType() { + return WorkItemPriority.LOW; + } + + @Override + public String getDetails() { + return "[OPC UA] Closed connection"; + } +} diff --git a/src/com/serotonin/mango/util/LoggingUtils.java b/src/com/serotonin/mango/util/LoggingUtils.java index 9d2052ea0d..b862cd1a85 100644 --- a/src/com/serotonin/mango/util/LoggingUtils.java +++ b/src/com/serotonin/mango/util/LoggingUtils.java @@ -1,5 +1,6 @@ package com.serotonin.mango.util; +import br.org.scadabr.vo.dataSource.opcua.OpcUaPointLocatorVO; import br.org.scadabr.vo.scripting.ScriptVO; import com.serotonin.db.IntValuePair; import com.serotonin.mango.Common; @@ -14,6 +15,7 @@ import com.serotonin.mango.vo.DataPointVO; import com.serotonin.mango.vo.User; import com.serotonin.mango.vo.dataSource.DataSourceVO; +import com.serotonin.mango.vo.dataSource.PointLocatorVO; import com.serotonin.mango.vo.event.EventHandlerVO; import com.serotonin.mango.vo.event.EventTypeVO; import com.serotonin.mango.vo.event.PointEventDetectorVO; @@ -251,6 +253,20 @@ public static String varInfo(IntValuePair pair) { return MessageFormat.format(info, pair.getValue(), pair.getKey()); } + public static String pointLocatorInfo(PointLocatorVO pointLocator) { + if(pointLocator == null) + return ""; + if(pointLocator instanceof OpcUaPointLocatorVO) { + String info = "locator: {0} (nodeId: {4}, namespaceIndex: {1}, identifier: {2}, identifierType: {3}, dataTypeId: {4}, dataType: {5}, dataType [OPC UA]: {6})"; + OpcUaPointLocatorVO opcUa = (OpcUaPointLocatorVO) pointLocator; + return MessageFormat.format(info, opcUa.getTag(), opcUa.getNodeId(), opcUa.getNamespaceIndex(), + opcUa.getIdentifierType(), opcUa.getDataTypeId(), opcUa.getDataTypeMessage().getLocalizedMessage(Common.getBundle()), opcUa.getDataType()); + } + String info = "locator: {0} (dataTypeId: {1}, dataType: {2})"; + return MessageFormat.format(info, pointLocator.getConfigurationDescription().getLocalizedMessage(Common.getBundle()), pointLocator.getDataTypeId(), pointLocator.getDataTypeMessage().getLocalizedMessage(Common.getBundle())); + + } + private static String msg(EventHandlerVO eventHandler) { return StringUtils.isEmpty(eventHandler.getAlias()) && eventHandler.getMessage() != null ? eventHandler.getMessage().getLocalizedMessage(Common.getBundle()) : eventHandler.getAlias(); } diff --git a/src/com/serotonin/mango/vo/TimeLongPeriodType.java b/src/com/serotonin/mango/vo/TimeLongPeriodType.java new file mode 100644 index 0000000000..8e1bbbac31 --- /dev/null +++ b/src/com/serotonin/mango/vo/TimeLongPeriodType.java @@ -0,0 +1,80 @@ +package com.serotonin.mango.vo; + +import com.serotonin.mango.Common; + +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public enum TimeLongPeriodType implements TimePeriod { + + DAYS(Common.TimePeriods.DAYS, "common.tp.days") { + @Override + public long toMs(long time) { + return time * 1000 * 60 * 60 * 24; + } + }, + WEEKS(Common.TimePeriods.WEEKS, "common.tp.weeks") { + @Override + public long toMs(long time) { + return time * DAYS.toMs(time); + } + }, + MONTHS(Common.TimePeriods.MONTHS, "common.tp.months") { + @Override + public long toMs(long time) { + return time * DAYS.toMs(time) * 30; + } + }, + YEARS(Common.TimePeriods.YEARS, "common.tp.years") { + @Override + public long toMs(long time) { + return time * DAYS.toMs(time) * 365; + } + }; + + private final int code; + private final String key; + + TimeLongPeriodType(int code, String key) { + this.code = code; + this.key = key; + } + + @Override + public int getCode() { + return code; + } + + @Override + public String getKey() { + return key; + } + + @Override + public long toMs(long value) { + return value; + } + + public static Map codes() { + return Stream.of(TimeLongPeriodType.values()) + .collect(Collectors.toMap(Enum::name, TimeLongPeriodType::getCode)); + } + + public static TimeLongPeriodType getType(int code) { + return Stream.of(TimeLongPeriodType.values()) + .filter(a -> a.getCode() == code) + .findAny() + .orElse(TimeLongPeriodType.DAYS); + } + + public static TimeLongPeriodType getType(String name) { + return Stream.of(TimeLongPeriodType.values()) + .filter(a -> a.name().equalsIgnoreCase(name)) + .findAny() + .orElse(TimeLongPeriodType.DAYS); + } +} + + + diff --git a/src/com/serotonin/mango/vo/TimePeriod.java b/src/com/serotonin/mango/vo/TimePeriod.java new file mode 100644 index 0000000000..c11b1b37ce --- /dev/null +++ b/src/com/serotonin/mango/vo/TimePeriod.java @@ -0,0 +1,7 @@ +package com.serotonin.mango.vo; + +public interface TimePeriod { + int getCode(); + long toMs(long value); + String getKey(); +} diff --git a/src/com/serotonin/mango/vo/TimePeriodType.java b/src/com/serotonin/mango/vo/TimePeriodType.java index a8cc88e8d8..a8590a4d8f 100644 --- a/src/com/serotonin/mango/vo/TimePeriodType.java +++ b/src/com/serotonin/mango/vo/TimePeriodType.java @@ -6,38 +6,49 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -public enum TimePeriodType { +public enum TimePeriodType implements TimePeriod { - MILLISECONDS(Common.TimePeriods.MILLISECONDS), - SECONDS(Common.TimePeriods.SECONDS) { + MILLISECONDS(Common.TimePeriods.MILLISECONDS, "common.tp.milliseconds"), + SECONDS(Common.TimePeriods.SECONDS, "common.tp.seconds") { @Override public long toMs(long time) { return time * 1000; } }, - MINUTES(Common.TimePeriods.MINUTES) { + MINUTES(Common.TimePeriods.MINUTES, "common.tp.minutes") { @Override public long toMs(long time) { return time * 1000 * 60; } }, - HOURS(Common.TimePeriods.HOURS) { + HOURS(Common.TimePeriods.HOURS, "common.tp.hours") { @Override public long toMs(long time) { return time * 1000 * 60 * 60; } }; + public static final TimePeriodType DEFAULT = TimePeriodType.SECONDS; + private final int code; + private final String key; - TimePeriodType(int code) { + TimePeriodType(int code, String key) { this.code = code; + this.key = key; } + @Override public int getCode() { return code; } + @Override + public String getKey() { + return key; + } + + @Override public long toMs(long value) { return value; } diff --git a/src/com/serotonin/mango/vo/dataSource/DataSourceVO.java b/src/com/serotonin/mango/vo/dataSource/DataSourceVO.java index 652c37ee62..291d9b140d 100644 --- a/src/com/serotonin/mango/vo/dataSource/DataSourceVO.java +++ b/src/com/serotonin/mango/vo/dataSource/DataSourceVO.java @@ -28,6 +28,7 @@ import br.org.scadabr.vo.dataSource.iec101.IEC101SerialDataSourceVO; import br.org.scadabr.vo.dataSource.nodaves7.NodaveS7DataSourceVO; import br.org.scadabr.vo.dataSource.opc.OPCDataSourceVO; +import br.org.scadabr.vo.dataSource.opcua.OpcUaDataSourceVO; import cc.radiuino.scadabr.vo.datasource.radiuino.RadiuinoDataSourceVO; import com.serotonin.ShouldNeverHappenException; import com.serotonin.json.*; @@ -317,7 +318,13 @@ public DataSourceVO createDataSourceVO() { public DataSourceVO createDataSourceVO() { return new MqttDataSourceVO(); } - },; + }, + OPC_UA(48, "dsEdit.opcua", true) { + @Override + public DataSourceVO createDataSourceVO() { + return new OpcUaDataSourceVO<>(); + } + }; private Type(int id, String key, boolean display) { this.id = id; diff --git a/src/com/serotonin/mango/web/dwr/DataSourceEditDwr.java b/src/com/serotonin/mango/web/dwr/DataSourceEditDwr.java index 71cbacb9a3..eee5eaf148 100644 --- a/src/com/serotonin/mango/web/dwr/DataSourceEditDwr.java +++ b/src/com/serotonin/mango/web/dwr/DataSourceEditDwr.java @@ -44,9 +44,10 @@ import javax.management.remote.JMXServiceURL; import javax.script.ScriptException; +import br.org.scadabr.*; +import br.org.scadabr.vo.dataSource.opcua.*; import com.serotonin.bacnet4j.type.enumerated.ObjectType; import com.serotonin.db.KeyValuePair; -import com.serotonin.mango.util.LoggingUtils; import com.serotonin.mango.web.dwr.beans.*; import com.serotonin.modbus4j.SlaveIdLimit255ModbusMaster; import net.sf.mbus4j.Connection; @@ -64,9 +65,6 @@ import org.apache.commons.logging.LogFactory; import org.jinterop.dcom.common.JISystem; -import br.org.scadabr.OPCItem; -import br.org.scadabr.OPCUtils; -import br.org.scadabr.RealOPCMaster; import br.org.scadabr.vo.dataSource.alpha2.Alpha2DataSourceVO; import br.org.scadabr.vo.dataSource.alpha2.Alpha2PointLocatorVO; import br.org.scadabr.vo.dataSource.asciiFile.ASCIIFileDataSourceVO; @@ -331,23 +329,23 @@ private DataPointVO getPoint(int pointId, DataPointDefaulter defaulter) { return dp; } - private DwrResponseI18n validatePoint(int id, String xid, String name, - PointLocatorVO locator, DataPointDefaulter defaulter) { + private DwrResponseI18n validatePoint(int dataPointId, String dataPointXid, String dataPointName, + PointLocatorVO pointLocator, DataPointDefaulter defaulter) { Permissions.ensureAdmin(); DwrResponseI18n response = new DwrResponseI18n(); - DataPointVO dp = getPoint(id, defaulter); - dp.setXid(xid); - dp.setName(name); - dp.setPointLocator(locator); + DataPointVO dp = getPoint(dataPointId, defaulter); + dp.setXid(dataPointXid); + dp.setName(dataPointName); + dp.setPointLocator(pointLocator); DataPointService dataPointService = new DataPointService(); - validateXid(response, dataPointService::isXidUnique, xid, id); + validateXid(response, dataPointService::isXidUnique, dataPointXid, dataPointId); - if (StringUtils.isEmpty(name)) + if (StringUtils.isEmpty(dataPointName)) response.addContextualMessage("name", "dsEdit.validate.required"); - locator.validate(response, dp.getId()); + pointLocator.validate(response, dp.getId()); if (!response.getHasMessages()) { Common.ctx.getRuntimeManager().saveDataPoint(dp); @@ -2425,11 +2423,106 @@ public OPCItem validateOPCTag(String tag, String user, String password, } - // public void saveOPCTags(OPCItem[] opcItems) { - // for (int i = 0; i < opcItems.length; i++) { - // OPCItem opcItem = new OPCItem("", 0, false); - // } - // } + public DwrResponseI18n saveOpcUaDataSource(OpcUaDataSourceVO ds) { + Permissions.ensureAdmin(); + return tryDataSourceSave(ds); + } + + public DwrResponseI18n saveOpcUaPointLocator(int id, String xid, String name, + OpcUaPointLocatorVO locator) { + return validatePoint(id, xid, name, locator, null); + } + + public LinkedHashSet searchServerOpcUa(OpcUaDataSourceVO dataSourceVO) { + + Logger log = JISystem.getLogger(); + log.setLevel(Level.OFF); + + LinkedHashSet serverList = new LinkedHashSet<>(); + try { + OpcUaMaster opcUaMaster = new OpcUaMaster(dataSourceVO); + opcUaMaster.init(); + serverList.add(dataSourceVO.getServerAddress()); + opcUaMaster.terminate(); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + } + return serverList; + } + + public OpcUaItem findTagOpcUa(OpcUaDataSourceVO dataSource, String tag, String identifier, + OpcUaIdentifierType identifierType, OpcUaDataType dataType) { + + Logger log = JISystem.getLogger(); + log.setLevel(Level.OFF); + + OpcUaPointLocatorVO pointLocator = new OpcUaPointLocatorVO(); + pointLocator.setTag(tag); + pointLocator.setIdentifier(identifier); + pointLocator.setIdentifierType(identifierType); + pointLocator.setDataType(dataType); + + OpcUaMaster opcUaMaster; + try { + opcUaMaster = new OpcUaMaster(dataSource); + opcUaMaster.init(); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + return new OpcUaItem(false, pointLocator); + } + + int namespaceIndex = opcUaMaster.findNamespaceIndex(1000, pointLocator); + pointLocator.setNamespaceIndex(namespaceIndex); + boolean validated = opcUaMaster.validateTag(pointLocator, tag); + pointLocator.setSettable(false); + + try { + opcUaMaster.terminate(); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + } + + return new OpcUaItem(validated, pointLocator); + } + + public DwrResponseI18n saveMultipleOpcUaPointLocator(OpcUaPointLocatorVO[] locators, String context) { + + return validateMultipleOpcUaPoints(locators, + context, null); + } + + private DwrResponseI18n validateMultipleOpcUaPoints(OpcUaPointLocatorVO[] locators, String context, + DataPointDefaulter defaulter) { + Permissions.ensureAdmin(); + DwrResponseI18n response = new DwrResponseI18n(); + OpcUaDataSourceVO ds = (OpcUaDataSourceVO) Common.getUser() + .getEditDataSource(); + if (ds.isNew()) { + response.addContextualMessage(context, + "dsEdit.opc.validate.dataSourceNotSaved"); + return response; + } + for (int i = 0; i < locators.length; i++) { + DataPointVO dp = getPoint(Common.NEW_ID, defaulter); + String dataPointName = locators[i].getTag(); + dp.setName(dataPointName); + dp.setPointLocator(locators[i]); + + DataPointService dataPointService = new DataPointService(); + validateXid(response, dataPointService::isXidUnique, dp.getXid(), Common.NEW_ID); + + // locators[i].validate(response); + if (!response.getHasMessages()) { + Common.ctx.getRuntimeManager().saveDataPoint(dp); + response.addData("id", dp.getId()); + response.addData("points", getPoints()); + } + } + return response; + } + + + /// // public DwrResponseI18n saveMultipleOPCPointLocator(String[] tags, int[] dataTypes, boolean[] settables, OPCPointLocatorVO[] locators, diff --git a/src/com/serotonin/mango/web/dwr/KeyStoreTypeConverter.java b/src/com/serotonin/mango/web/dwr/KeyStoreTypeConverter.java new file mode 100644 index 0000000000..56bbbb807d --- /dev/null +++ b/src/com/serotonin/mango/web/dwr/KeyStoreTypeConverter.java @@ -0,0 +1,27 @@ +package com.serotonin.mango.web.dwr; + +import br.org.scadabr.KeyStoreType; +import org.directwebremoting.convert.EnumConverter; +import org.directwebremoting.extend.*; +import org.directwebremoting.util.LocalUtil; + + +public class KeyStoreTypeConverter extends EnumConverter { + + @Override + public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws MarshallException { + String value = LocalUtil.decode(iv.getValue()); + try { + return KeyStoreType.valueOf(value); + } catch (Exception var9) { + throw new MarshallException(paramType, var9); + } + } + + @Override + public OutboundVariable convertOutbound(Object data, OutboundContext outctx) { + KeyStoreType type = (KeyStoreType)data; + String name = type.name(); + return super.convertOutbound(name, outctx); + } +} diff --git a/src/com/serotonin/mango/web/dwr/OpcUaDataTypeConverter.java b/src/com/serotonin/mango/web/dwr/OpcUaDataTypeConverter.java new file mode 100644 index 0000000000..ad43958f25 --- /dev/null +++ b/src/com/serotonin/mango/web/dwr/OpcUaDataTypeConverter.java @@ -0,0 +1,28 @@ +package com.serotonin.mango.web.dwr; + +import br.org.scadabr.vo.dataSource.opcua.OpcUaDataType; +import org.apache.plc4x.java.opcua.security.SecurityPolicy; +import org.directwebremoting.convert.EnumConverter; +import org.directwebremoting.extend.*; +import org.directwebremoting.util.LocalUtil; + + +public class OpcUaDataTypeConverter extends EnumConverter { + + @Override + public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws MarshallException { + String value = LocalUtil.decode(iv.getValue()); + try { + return OpcUaDataType.valueOf(value); + } catch (Exception var9) { + throw new MarshallException(paramType, var9); + } + } + + @Override + public OutboundVariable convertOutbound(Object data, OutboundContext outctx) { + OpcUaDataType type = (OpcUaDataType)data; + String name = type.name(); + return super.convertOutbound(name, outctx); + } +} diff --git a/src/com/serotonin/mango/web/dwr/OpcUaIdentifierTypeConverter.java b/src/com/serotonin/mango/web/dwr/OpcUaIdentifierTypeConverter.java new file mode 100644 index 0000000000..66ab4b6573 --- /dev/null +++ b/src/com/serotonin/mango/web/dwr/OpcUaIdentifierTypeConverter.java @@ -0,0 +1,27 @@ +package com.serotonin.mango.web.dwr; + +import br.org.scadabr.vo.dataSource.opcua.OpcUaIdentifierType; +import org.directwebremoting.convert.EnumConverter; +import org.directwebremoting.extend.*; +import org.directwebremoting.util.LocalUtil; + + +public class OpcUaIdentifierTypeConverter extends EnumConverter { + + @Override + public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws MarshallException { + String value = LocalUtil.decode(iv.getValue()); + try { + return OpcUaIdentifierType.valueOf(value.toUpperCase()); + } catch (Exception var9) { + throw new MarshallException(paramType, var9); + } + } + + @Override + public OutboundVariable convertOutbound(Object data, OutboundContext outctx) { + OpcUaIdentifierType type = (OpcUaIdentifierType)data; + String name = type.name(); + return super.convertOutbound(name, outctx); + } +} diff --git a/src/com/serotonin/mango/web/dwr/OpcUaMessageSecurityTypeConverter.java b/src/com/serotonin/mango/web/dwr/OpcUaMessageSecurityTypeConverter.java new file mode 100644 index 0000000000..54e6e3e4a6 --- /dev/null +++ b/src/com/serotonin/mango/web/dwr/OpcUaMessageSecurityTypeConverter.java @@ -0,0 +1,27 @@ +package com.serotonin.mango.web.dwr; + +import br.org.scadabr.vo.dataSource.opcua.OpcUaMessageSecurityType; +import org.directwebremoting.convert.EnumConverter; +import org.directwebremoting.extend.*; +import org.directwebremoting.util.LocalUtil; + + +public class OpcUaMessageSecurityTypeConverter extends EnumConverter { + + @Override + public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws MarshallException { + String value = LocalUtil.decode(iv.getValue()); + try { + return OpcUaMessageSecurityType.valueOf(value); + } catch (Exception var9) { + throw new MarshallException(paramType, var9); + } + } + + @Override + public OutboundVariable convertOutbound(Object data, OutboundContext outctx) { + OpcUaMessageSecurityType type = (OpcUaMessageSecurityType)data; + String name = type.name(); + return super.convertOutbound(name, outctx); + } +} diff --git a/src/com/serotonin/mango/web/dwr/OpcUaSecurityPolicyTypeConverter.java b/src/com/serotonin/mango/web/dwr/OpcUaSecurityPolicyTypeConverter.java new file mode 100644 index 0000000000..b1dcf3fa74 --- /dev/null +++ b/src/com/serotonin/mango/web/dwr/OpcUaSecurityPolicyTypeConverter.java @@ -0,0 +1,27 @@ +package com.serotonin.mango.web.dwr; + +import br.org.scadabr.vo.dataSource.opcua.OpcUaSecurityPolicyType; +import org.directwebremoting.convert.EnumConverter; +import org.directwebremoting.extend.*; +import org.directwebremoting.util.LocalUtil; + + +public class OpcUaSecurityPolicyTypeConverter extends EnumConverter { + + @Override + public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws MarshallException { + String value = LocalUtil.decode(iv.getValue()); + try { + return OpcUaSecurityPolicyType.valueOf(value.toUpperCase()); + } catch (Exception var9) { + throw new MarshallException(paramType, var9); + } + } + + @Override + public OutboundVariable convertOutbound(Object data, OutboundContext outctx) { + OpcUaSecurityPolicyType type = (OpcUaSecurityPolicyType)data; + String name = type.name(); + return super.convertOutbound(name, outctx); + } +} diff --git a/src/org/scada_lts/cache/UpdateDataSourcesPoints.java b/src/org/scada_lts/cache/UpdateDataSourcesPoints.java index 27f23c2a86..2a3cf1462e 100644 --- a/src/org/scada_lts/cache/UpdateDataSourcesPoints.java +++ b/src/org/scada_lts/cache/UpdateDataSourcesPoints.java @@ -1,5 +1,6 @@ package org.scada_lts.cache; +import com.serotonin.mango.util.LoggingUtils; import com.serotonin.mango.vo.DataPointVO; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -18,10 +19,14 @@ public class UpdateDataSourcesPoints implements StatefulJob{ @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { - LOG.trace("UpdateEventDetectors"); + try { + LOG.trace("UpdateEventDetectors"); List dps = new DataPointDAO().getDataPoints(); Map> dss = DataSourcePointsCache.getInstance().composeCashData(dps); DataSourcePointsCache.getInstance().setData(dss); + } catch (Exception ex) { + LOG.error(LoggingUtils.causeInfo(ex), ex); + } } } diff --git a/src/org/scada_lts/ds/opcua/TimeoutNettyPlcConnection.java b/src/org/scada_lts/ds/opcua/TimeoutNettyPlcConnection.java new file mode 100644 index 0000000000..ae93bf5a54 --- /dev/null +++ b/src/org/scada_lts/ds/opcua/TimeoutNettyPlcConnection.java @@ -0,0 +1,140 @@ +package org.scada_lts.ds.opcua; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.plc4x.java.api.authentication.PlcAuthentication; +import org.apache.plc4x.java.api.exceptions.PlcConnectionException; +import org.apache.plc4x.java.api.exceptions.PlcIoException; +import org.apache.plc4x.java.api.value.PlcValueHandler; +import org.apache.plc4x.java.spi.configuration.ConfigurationFactory; +import org.apache.plc4x.java.spi.configuration.PlcConnectionConfiguration; +import org.apache.plc4x.java.spi.connection.ChannelFactory; +import org.apache.plc4x.java.spi.connection.DefaultNettyPlcConnection; +import org.apache.plc4x.java.spi.connection.PlcTagHandler; +import org.apache.plc4x.java.spi.connection.ProtocolStackConfigurer; +import org.apache.plc4x.java.spi.events.CloseConnectionEvent; +import org.apache.plc4x.java.spi.events.DisconnectEvent; +import org.apache.plc4x.java.spi.events.DiscoverEvent; +import org.apache.plc4x.java.spi.optimizer.BaseOptimizer; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; + +public class TimeoutNettyPlcConnection extends DefaultNettyPlcConnection { + + private static final Logger LOG = LogManager.getLogger(TimeoutNettyPlcConnection.class); + private final long connectingTimeout; + private final long closingTimeout; + + + public TimeoutNettyPlcConnection(boolean canPing, boolean canRead, boolean canWrite, + boolean canSubscribe, boolean canBrowse, PlcTagHandler tagHandler, + PlcValueHandler valueHandler, PlcConnectionConfiguration configuration, + ChannelFactory channelFactory, boolean fireDiscoverEvent, + boolean awaitSessionSetupComplete, boolean awaitSessionDisconnectComplete, + boolean awaitSessionDiscoverComplete, ProtocolStackConfigurer stackConfigurer, + BaseOptimizer optimizer, PlcAuthentication authentication, long connectingTimeout, + long closingTimeout) { + super(canPing, canRead, canWrite, canSubscribe, canBrowse, tagHandler, valueHandler, configuration, channelFactory, fireDiscoverEvent, awaitSessionSetupComplete, awaitSessionDisconnectComplete, awaitSessionDiscoverComplete, stackConfigurer, optimizer, authentication); + this.connectingTimeout = connectingTimeout; + this.closingTimeout = closingTimeout; + } + + @Override + public void connect() throws PlcConnectionException { + try { + // As we don't just want to wait till the connection is established, + // define a future we can use to signal back that the s7 session is + // finished initializing. + CompletableFuture sessionSetupCompleteFuture = new CompletableFuture<>(); + CompletableFuture sessionDiscoveredCompleteFuture = new CompletableFuture<>(); + + if (channelFactory == null) { + throw new PlcConnectionException("No channel factory provided"); + } + + // Inject the configuration + ConfigurationFactory.configure(configuration, channelFactory); + + // Have the channel factory create a new channel instance. + // TODO: Why is this code necessary? Discovery should be an API function that is + // explicitly called independently from the connection establishment. + if (fireDiscoverEvent) { + channel = channelFactory.createChannel(getChannelHandler(sessionSetupCompleteFuture, sessionDisconnectCompleteFuture, sessionDiscoveredCompleteFuture)); + channel.closeFuture().addListener(future -> { + if (!sessionDiscoveredCompleteFuture.isDone()) { + //Do Nothing + try { + sessionDiscoveredCompleteFuture.complete(null); + } catch (Exception e) { + //Do Nothing + } + + } + }); + channel.pipeline().fireUserEventTriggered(new DiscoverEvent()); + } + if (awaitSessionDiscoverComplete) { + // Wait till the connection is established. + sessionDiscoveredCompleteFuture.get(); + } + + channel = channelFactory.createChannel(getChannelHandler(sessionSetupCompleteFuture, sessionDisconnectCompleteFuture, sessionDiscoveredCompleteFuture)); + channel.closeFuture().addListener(future -> { + if (!sessionSetupCompleteFuture.isDone()) { + sessionSetupCompleteFuture.completeExceptionally( + new PlcIoException("Connection terminated by remote")); + } + }); + // Send an event to the pipeline telling the Protocol filters what's going on. + sendChannelCreatedEvent(); + + // Wait till the connection is established. + if (awaitSessionSetupComplete) { + sessionSetupCompleteFuture.get(connectingTimeout, TimeUnit.MILLISECONDS); + } + + // Set the connection to "connected" + connected = true; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new PlcConnectionException(e); + } catch (Exception e) { + throw new PlcConnectionException(e); + } + } + + @Override + public void close() throws PlcConnectionException { + LOG.debug("Closing connection to PLC, await for disconnect = {}", this.awaitSessionDisconnectComplete); + this.channel.pipeline().fireUserEventTriggered(new DisconnectEvent()); + + try { + if (this.awaitSessionDisconnectComplete) { + this.sessionDisconnectCompleteFuture.get(closingTimeout, TimeUnit.MILLISECONDS); + } + } catch (Exception var2) { + LOG.error("Timeout while trying to close connection"); + } + + if (this.channel.isOpen()) { + try { + this.channel.pipeline().fireUserEventTriggered(new CloseConnectionEvent()); + this.channel.close().awaitUninterruptibly(); + } catch (RejectedExecutionException ex) { + if (this.channel.isOpen()) { + throw ex; + } + } + } + + if (!this.sessionDisconnectCompleteFuture.isDone()) { + this.sessionDisconnectCompleteFuture.complete(null); + } + + this.channelFactory.closeEventLoopForChannel(this.channel); + this.channel = null; + this.connected = false; + } +} diff --git a/src/org/scada_lts/ds/opcua/TimeoutOpcuaPlcDriver.java b/src/org/scada_lts/ds/opcua/TimeoutOpcuaPlcDriver.java new file mode 100644 index 0000000000..707f3610ea --- /dev/null +++ b/src/org/scada_lts/ds/opcua/TimeoutOpcuaPlcDriver.java @@ -0,0 +1,141 @@ +package org.scada_lts.ds.opcua; + +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.authentication.PlcAuthentication; +import org.apache.plc4x.java.api.exceptions.PlcConnectionException; +import org.apache.plc4x.java.opcua.OpcuaPlcDriver; +import org.apache.plc4x.java.opcua.config.OpcuaConfiguration; +import org.apache.plc4x.java.spi.configuration.ConfigurationFactory; +import org.apache.plc4x.java.spi.configuration.PlcConnectionConfiguration; +import org.apache.plc4x.java.spi.configuration.PlcTransportConfiguration; +import org.apache.plc4x.java.spi.connection.ChannelFactory; +import org.apache.plc4x.java.spi.transport.Transport; +import org.apache.plc4x.java.transport.tcp.DefaultTcpTransportConfiguration; + +import java.util.ServiceLoader; +import java.util.regex.Matcher; + +import static org.apache.plc4x.java.spi.configuration.ConfigurationFactory.configure; + +public class TimeoutOpcuaPlcDriver extends OpcuaPlcDriver { + + public TimeoutOpcuaPlcDriver() {} + + @Override + public PlcConnection getConnection(String connectionString, PlcAuthentication authentication) throws PlcConnectionException { + ConfigurationFactory configurationFactory = new ConfigurationFactory(); + // Split up the connection string into its individual segments. + Matcher matcher = URI_PATTERN.matcher(connectionString); + if (!matcher.matches()) { + throw new PlcConnectionException( + "Connection string doesn't match the format '{protocol-code}:({transport-code})?//{transport-config}(?{parameter-string)?'"); + } + final String protocolCode = matcher.group("protocolCode"); + String transportCodeMatch = matcher.group("transportCode"); + if (transportCodeMatch == null && getMetadata().getDefaultTransportCode().isEmpty()) { + throw new PlcConnectionException( + "This driver has no default transport and no transport code was provided."); + } + final String transportCode = (transportCodeMatch != null) ? transportCodeMatch : getMetadata().getDefaultTransportCode().get(); + final String transportConfig = matcher.group("transportConfig"); + final String paramString = matcher.group("paramString"); + + // Check if the protocol code matches this driver. + if (!protocolCode.equals(getProtocolCode())) { + // Actually this shouldn't happen as the DriverManager should have not used this driver in the first place. + throw new PlcConnectionException( + "This driver is not suited to handle this connection string"); + } + + // Create the configuration object. + PlcConnectionConfiguration configuration = configurationFactory + .createConfiguration(getConfigurationClass(), protocolCode, transportCode, transportConfig, paramString); + if (configuration == null) { + throw new PlcConnectionException("Unsupported configuration"); + } + + // Try to find a suitable transport-type for creating the communication channel. + Transport transport = null; + ServiceLoader transportLoader = ServiceLoader.load( + Transport.class, Thread.currentThread().getContextClassLoader()); + for (Transport curTransport : transportLoader) { + if (curTransport.getTransportCode().equals(transportCode)) { + transport = curTransport; + break; + } + } + if (transport == null) { + throw new PlcConnectionException("Unsupported transport " + transportCode); + } + + // Find out the type of the transport configuration. + Class transportConfigurationType = transport.getTransportConfigType(); + if (getTransportConfigurationClass(transportCode).isPresent()) { + transportConfigurationType = getTransportConfigurationClass(transportCode).get(); + } + // Use the transport configuration type to actually configure the transport instance. + PlcTransportConfiguration plcTransportConfiguration = configurationFactory + .createTransportConfiguration(transportConfigurationType, + protocolCode, transportCode, transportConfig, paramString); + configure(plcTransportConfiguration, transport); + + // Create an instance of the communication channel which the driver should use. + ChannelFactory channelFactory = transport.createChannelFactory(transportConfig); + if (channelFactory == null) { + throw new PlcConnectionException("Unable to get channel factory from url " + transportConfig); + } + configure(configuration, channelFactory); + + // Give drivers the option to customize the channel. + initializePipeline(channelFactory); + + // Make the "fire discover event" overridable via system property. + boolean fireDiscoverEvent = fireDiscoverEvent(); + if (System.getProperty(PROPERTY_PLC4X_FORCE_FIRE_DISCOVER_EVENT) != null) { + fireDiscoverEvent = Boolean.parseBoolean(System.getProperty(PROPERTY_PLC4X_FORCE_FIRE_DISCOVER_EVENT)); + } + + // Make the "await setup complete" overridable via system property. + boolean awaitSetupComplete = awaitSetupComplete(); + if (System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_SETUP_COMPLETE) != null) { + awaitSetupComplete = Boolean.parseBoolean(System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_SETUP_COMPLETE)); + } + + // Make the "await disconnect complete" overridable via system property. + boolean awaitDisconnectComplete = awaitDisconnectComplete(); + if (System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_DISCONNECT_COMPLETE) != null) { + awaitDisconnectComplete = Boolean.parseBoolean(System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_DISCONNECT_COMPLETE)); + } + + // Make the "await disconnect complete" overridable via system property. + boolean awaitDiscoverComplete = awaitDiscoverComplete(); + if (System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_DISCOVER_COMPLETE) != null) { + awaitDiscoverComplete = Boolean.parseBoolean(System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_DISCOVER_COMPLETE)); + } + + long connectingTimeout = 4000; + long closingTimeout = 4000; + + if(configuration instanceof OpcuaConfiguration) { + OpcuaConfiguration tcpTransportConfiguration = (OpcuaConfiguration) configuration; + connectingTimeout = tcpTransportConfiguration.getNegotiationTimeout(); + closingTimeout = tcpTransportConfiguration.getNegotiationTimeout(); + } + + return new TimeoutNettyPlcConnection( + canPing(), canRead(), canWrite(), canSubscribe(), canBrowse(), + getTagHandler(), + getValueHandler(), + configuration, + channelFactory, + fireDiscoverEvent, + awaitSetupComplete, + awaitDisconnectComplete, + awaitDiscoverComplete, + getStackConfigurer(transport), + getOptimizer(), + authentication, + connectingTimeout, + closingTimeout); + } +} diff --git a/src/org/scada_lts/ds/opcua/TimeoutPlcDriverManager.java b/src/org/scada_lts/ds/opcua/TimeoutPlcDriverManager.java new file mode 100644 index 0000000000..eb4c5fee9d --- /dev/null +++ b/src/org/scada_lts/ds/opcua/TimeoutPlcDriverManager.java @@ -0,0 +1,42 @@ +package org.scada_lts.ds.opcua; + +import org.apache.plc4x.java.DefaultPlcDriverManager; +import org.apache.plc4x.java.api.PlcDriver; +import org.apache.plc4x.java.api.exceptions.PlcConnectionException; + +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; + +public class TimeoutPlcDriverManager extends DefaultPlcDriverManager { + + private final Map driverMap; + + public TimeoutPlcDriverManager() { + driverMap = new HashMap<>(); + ServiceLoader plcDriverLoader = ServiceLoader.load(TimeoutOpcuaPlcDriver.class, classLoader); + for (PlcDriver driver : plcDriverLoader) { + if (driverMap.containsKey(driver.getProtocolCode())) { + throw new IllegalStateException( + "Multiple driver implementations available for protocol code '" + + driver.getProtocolCode() + "'"); + } + driverMap.put(driver.getProtocolCode(), driver); + } + } + + @Override + public PlcDriver getDriver(String protocolCode) throws PlcConnectionException { + PlcDriver driver = driverMap.get(protocolCode); + if (driver == null) { + throw new PlcConnectionException("Unable to find driver for protocol '" + protocolCode + "'"); + } + return driver; + } + + @Override + public Set getProtocolCodes() { + return driverMap.keySet(); + } +} diff --git a/webapp-resources/META-INF/services/org.scada_lts.ds.opcua.TimeoutOpcuaPlcDriver b/webapp-resources/META-INF/services/org.scada_lts.ds.opcua.TimeoutOpcuaPlcDriver new file mode 100644 index 0000000000..a30d23b5dd --- /dev/null +++ b/webapp-resources/META-INF/services/org.scada_lts.ds.opcua.TimeoutOpcuaPlcDriver @@ -0,0 +1 @@ +org.scada_lts.ds.opcua.TimeoutOpcuaPlcDriver \ No newline at end of file diff --git a/webapp-resources/log4j2.xml b/webapp-resources/log4j2.xml index 996e610ff7..f502e07e3e 100644 --- a/webapp-resources/log4j2.xml +++ b/webapp-resources/log4j2.xml @@ -30,14 +30,17 @@ ERROR ERROR INFO + INFO + INFO ERROR ERROR ERROR INFO - ERROR + INFO INFO + INFO ERROR @@ -127,6 +130,7 @@ + @@ -221,6 +225,10 @@ + + + @@ -279,6 +287,10 @@ + + + + @@ -289,16 +301,6 @@ - - - - - - - - - - @@ -315,12 +317,19 @@ - + + + + - + + + + + diff --git a/webapp-resources/messages_de.properties b/webapp-resources/messages_de.properties index 9e67a3f58e..80de2fb288 100644 --- a/webapp-resources/messages_de.properties +++ b/webapp-resources/messages_de.properties @@ -3368,4 +3368,60 @@ systemSettings.invalidCustomCss=Invalid custom CSS systemsettings.reports.dataPointExtendedNameLengthLimit=Point name length limit in reports event.ds.recursiveError=Recursive Error event.meta.recursiveError=Recursive error in point "{0}": {1} -validate.cyclicDependency=Cyclic dependency: {0} \ No newline at end of file +validate.cyclicDependency=Cyclic dependency: {0} +dox.opcUaDS=Data Source OPC UA +dox.opcUaPP=Data Point OPC UA +dsedit.opcua.rt.addFailed=Addition failed for {0} +dsedit.opcua.tagName=Tag Name +dsEdit.opcua=OPC UA +dsEdit.opcua.TagNotValidated=Tag Not Validated +dsEdit.opcua.TagValidated=Tag Validated +dsEdit.opcua.addTags=Add Tags +dsEdit.opcua.browseTags=Browse Tags +dsEdit.opcua.creationMode=Creation Mode +dsEdit.opcua.desc=OPC UA Properties +dsEdit.opcua.domain=Domain +dsEdit.opcua.host=Host +dsEdit.opcua.password=Password +dsEdit.opcua.refreshServers=Refresh +dsEdit.opcua.server=Server +dsEdit.opcua.tag=Tag +dsEdit.opcua.tagList=Tag List +dsEdit.opcua.tagName=Tag Name +dsEdit.opcua.tagsFound=Tags Found +dsEdit.opcua.user=User +dsEdit.opcua.validate.dataSourceNotSaved=You must first save the data source +dsEdit.opcua.validateTag=Validate Tag +dsEdit.opcua.validation=Validation +dsEdit.opcua.messageSecurityType=Message Security +dsEdit.opcua.securityPolicyType=Security Policy +dsEdit.opcua.serverCertificateFile=Server Certificate File +dsEdit.opcua.keyStoreType=Key Store Type +dsEdit.opcua.keyStoreFile=Key Store File +dsEdit.opcua.keyStorePassword=Key Store Password +dsEdit.opcua.trustStoreType=Trust Store Type +dsEdit.opcua.trustStoreFile=Trust Store File +dsEdit.opcua.trustStorePassword=Trust Store Password +dsEdit.opcua.channelLifetime=Channel Lifetime +dsEdit.opcua.sessionTimeout=Session Timeout +dsEdit.opcua.negotiationTimeout=Negotiation Timeout +dsEdit.opcua.requestTimeout=Request Timeout +dsEdit.opcua.defaultTimeout=Default Timeout [TCP] +dsEdit.opcua.receiveBufferSize=Receiver Buffer Size +dsEdit.opcua.sendBufferSize=Send Buffer Size +dsEdit.opcua.maxMessageSize=Max Message Size +dsEdit.opcua.maxChunkCount=Max Chunk Count +dsEdit.opcua.keepAlive=Keep Alive [TCP] +dsEdit.opcua.noDelay=No Delay [TCP] +dsEdit.opcua.discovery=Discovery +dsEdit.opcua.serverName=Server Name +dsEdit.opcua.serverHost=Server Host +dsEdit.opcua.serverPort=Server Port +dsEdit.opcua.serverPath=Server Path +dsEdit.opcua.identifier=Identifier +dsEdit.opcua.identifierType=Identifier Type +dsEdit.opcua.dataType=Data Type +dsEdit.opcua.namespaceIndex=Namespace Index +dsEdit.opcua.settable=Settable +dsEdit.opcua.findTag=Find Tag +dsEdit.opcua.attributes=Attributes \ No newline at end of file diff --git a/webapp-resources/messages_en.properties b/webapp-resources/messages_en.properties index ddb2048e2d..99e0b86cd3 100644 --- a/webapp-resources/messages_en.properties +++ b/webapp-resources/messages_en.properties @@ -3371,4 +3371,60 @@ systemSettings.invalidCustomCss=Invalid custom CSS systemsettings.reports.dataPointExtendedNameLengthLimit=Point name length limit in reports event.ds.recursiveError=Recursive Error event.meta.recursiveError=Recursive error in point "{0}": {1} -validate.cyclicDependency=Cyclic dependency: {0} \ No newline at end of file +validate.cyclicDependency=Cyclic dependency: {0} +dox.opcUaDS=Data Source OPC UA +dox.opcUaPP=Data Point OPC UA +dsedit.opcua.rt.addFailed=Addition failed for {0} +dsedit.opcua.tagName=Tag Name +dsEdit.opcua=OPC UA +dsEdit.opcua.TagNotValidated=Tag Not Validated +dsEdit.opcua.TagValidated=Tag Validated +dsEdit.opcua.addTags=Add Tags +dsEdit.opcua.browseTags=Browse Tags +dsEdit.opcua.creationMode=Creation Mode +dsEdit.opcua.desc=OPC UA Properties +dsEdit.opcua.domain=Domain +dsEdit.opcua.host=Host +dsEdit.opcua.password=Password +dsEdit.opcua.refreshServers=Refresh +dsEdit.opcua.server=Server +dsEdit.opcua.tag=Tag +dsEdit.opcua.tagList=Tag List +dsEdit.opcua.tagName=Tag Name +dsEdit.opcua.tagsFound=Tags Found +dsEdit.opcua.user=User +dsEdit.opcua.validate.dataSourceNotSaved=You must first save the data source +dsEdit.opcua.validateTag=Validate Tag +dsEdit.opcua.validation=Validation +dsEdit.opcua.messageSecurityType=Message Security +dsEdit.opcua.securityPolicyType=Security Policy +dsEdit.opcua.serverCertificateFile=Server Certificate File +dsEdit.opcua.keyStoreType=Key Store Type +dsEdit.opcua.keyStoreFile=Key Store File +dsEdit.opcua.keyStorePassword=Key Store Password +dsEdit.opcua.trustStoreType=Trust Store Type +dsEdit.opcua.trustStoreFile=Trust Store File +dsEdit.opcua.trustStorePassword=Trust Store Password +dsEdit.opcua.channelLifetime=Channel Lifetime +dsEdit.opcua.sessionTimeout=Session Timeout +dsEdit.opcua.negotiationTimeout=Negotiation Timeout +dsEdit.opcua.requestTimeout=Request Timeout +dsEdit.opcua.defaultTimeout=Default Timeout [TCP] +dsEdit.opcua.receiveBufferSize=Receiver Buffer Size +dsEdit.opcua.sendBufferSize=Send Buffer Size +dsEdit.opcua.maxMessageSize=Max Message Size +dsEdit.opcua.maxChunkCount=Max Chunk Count +dsEdit.opcua.keepAlive=Keep Alive [TCP] +dsEdit.opcua.noDelay=No Delay [TCP] +dsEdit.opcua.discovery=Discovery +dsEdit.opcua.serverName=Server Name +dsEdit.opcua.serverHost=Server Host +dsEdit.opcua.serverPort=Server Port +dsEdit.opcua.serverPath=Server Path +dsEdit.opcua.identifier=Identifier +dsEdit.opcua.identifierType=Identifier Type +dsEdit.opcua.dataType=Data Type +dsEdit.opcua.namespaceIndex=Namespace Index +dsEdit.opcua.settable=Settable +dsEdit.opcua.findTag=Find Tag +dsEdit.opcua.attributes=Attributes \ No newline at end of file diff --git a/webapp-resources/messages_es.properties b/webapp-resources/messages_es.properties index a0e408caba..46ae7bd0cb 100644 --- a/webapp-resources/messages_es.properties +++ b/webapp-resources/messages_es.properties @@ -3411,4 +3411,60 @@ systemSettings.invalidCustomCss=Invalid custom CSS systemsettings.reports.dataPointExtendedNameLengthLimit=Point name length limit in reports event.ds.recursiveError=Recursive Error event.meta.recursiveError=Recursive error in point "{0}": {1} -validate.cyclicDependency=Cyclic dependency: {0} \ No newline at end of file +validate.cyclicDependency=Cyclic dependency: {0} +dox.opcUaDS=Data Source OPC UA +dox.opcUaPP=Data Point OPC UA +dsedit.opcua.rt.addFailed=Addition failed for {0} +dsedit.opcua.tagName=Tag Name +dsEdit.opcua=OPC UA +dsEdit.opcua.TagNotValidated=Tag Not Validated +dsEdit.opcua.TagValidated=Tag Validated +dsEdit.opcua.addTags=Add Tags +dsEdit.opcua.browseTags=Browse Tags +dsEdit.opcua.creationMode=Creation Mode +dsEdit.opcua.desc=OPC UA Properties +dsEdit.opcua.domain=Domain +dsEdit.opcua.host=Host +dsEdit.opcua.password=Password +dsEdit.opcua.refreshServers=Refresh +dsEdit.opcua.server=Server +dsEdit.opcua.tag=Tag +dsEdit.opcua.tagList=Tag List +dsEdit.opcua.tagName=Tag Name +dsEdit.opcua.tagsFound=Tags Found +dsEdit.opcua.user=User +dsEdit.opcua.validate.dataSourceNotSaved=You must first save the data source +dsEdit.opcua.validateTag=Validate Tag +dsEdit.opcua.validation=Validation +dsEdit.opcua.messageSecurityType=Message Security +dsEdit.opcua.securityPolicyType=Security Policy +dsEdit.opcua.serverCertificateFile=Server Certificate File +dsEdit.opcua.keyStoreType=Key Store Type +dsEdit.opcua.keyStoreFile=Key Store File +dsEdit.opcua.keyStorePassword=Key Store Password +dsEdit.opcua.trustStoreType=Trust Store Type +dsEdit.opcua.trustStoreFile=Trust Store File +dsEdit.opcua.trustStorePassword=Trust Store Password +dsEdit.opcua.channelLifetime=Channel Lifetime +dsEdit.opcua.sessionTimeout=Session Timeout +dsEdit.opcua.negotiationTimeout=Negotiation Timeout +dsEdit.opcua.requestTimeout=Request Timeout +dsEdit.opcua.defaultTimeout=Default Timeout [TCP] +dsEdit.opcua.receiveBufferSize=Receiver Buffer Size +dsEdit.opcua.sendBufferSize=Send Buffer Size +dsEdit.opcua.maxMessageSize=Max Message Size +dsEdit.opcua.maxChunkCount=Max Chunk Count +dsEdit.opcua.keepAlive=Keep Alive [TCP] +dsEdit.opcua.noDelay=No Delay [TCP] +dsEdit.opcua.discovery=Discovery +dsEdit.opcua.serverName=Server Name +dsEdit.opcua.serverHost=Server Host +dsEdit.opcua.serverPort=Server Port +dsEdit.opcua.serverPath=Server Path +dsEdit.opcua.identifier=Identifier +dsEdit.opcua.identifierType=Identifier Type +dsEdit.opcua.dataType=Data Type +dsEdit.opcua.namespaceIndex=Namespace Index +dsEdit.opcua.settable=Settable +dsEdit.opcua.findTag=Find Tag +dsEdit.opcua.attributes=Attributes \ No newline at end of file diff --git a/webapp-resources/messages_fi.properties b/webapp-resources/messages_fi.properties index 96294315e1..45c7631a7a 100644 --- a/webapp-resources/messages_fi.properties +++ b/webapp-resources/messages_fi.properties @@ -3496,4 +3496,60 @@ systemSettings.invalidCustomCss=Invalid custom CSS systemsettings.reports.dataPointExtendedNameLengthLimit=Point name length limit in reports event.ds.recursiveError=Recursive Error event.meta.recursiveError=Recursive error in point "{0}": {1} -validate.cyclicDependency=Cyclic dependency: {0} \ No newline at end of file +validate.cyclicDependency=Cyclic dependency: {0} +dox.opcUaDS=Data Source OPC UA +dox.opcUaPP=Data Point OPC UA +dsedit.opcua.rt.addFailed=Addition failed for {0} +dsedit.opcua.tagName=Tag Tag +dsEdit.opcua=OPC UA +dsEdit.opcua.TagNotValidated=Tag Not Validated +dsEdit.opcua.TagValidated=Tag Validated +dsEdit.opcua.addTags=Add Tags +dsEdit.opcua.browseTags=Browse Tags +dsEdit.opcua.creationMode=Creation Mode +dsEdit.opcua.desc=OPC UA Properties +dsEdit.opcua.domain=Domain +dsEdit.opcua.host=Host +dsEdit.opcua.password=Password +dsEdit.opcua.refreshServers=Refresh +dsEdit.opcua.server=Server +dsEdit.opcua.tag=Tag +dsEdit.opcua.tagList=Tag List +dsEdit.opcua.tagName=Tag Name +dsEdit.opcua.tagsFound=Tags Found +dsEdit.opcua.user=User +dsEdit.opcua.validate.dataSourceNotSaved=You must first save the data source +dsEdit.opcua.validateTag=Validate Tag +dsEdit.opcua.validation=Validation +dsEdit.opcua.messageSecurityType=Message Security +dsEdit.opcua.securityPolicyType=Security Policy +dsEdit.opcua.serverCertificateFile=Server Certificate File +dsEdit.opcua.keyStoreType=Key Store Type +dsEdit.opcua.keyStoreFile=Key Store File +dsEdit.opcua.keyStorePassword=Key Store Password +dsEdit.opcua.trustStoreType=Trust Store Type +dsEdit.opcua.trustStoreFile=Trust Store File +dsEdit.opcua.trustStorePassword=Trust Store Password +dsEdit.opcua.channelLifetime=Channel Lifetime +dsEdit.opcua.sessionTimeout=Session Timeout +dsEdit.opcua.negotiationTimeout=Negotiation Timeout +dsEdit.opcua.requestTimeout=Request Timeout +dsEdit.opcua.defaultTimeout=Default Timeout [TCP] +dsEdit.opcua.receiveBufferSize=Receiver Buffer Size +dsEdit.opcua.sendBufferSize=Send Buffer Size +dsEdit.opcua.maxMessageSize=Max Message Size +dsEdit.opcua.maxChunkCount=Max Chunk Count +dsEdit.opcua.keepAlive=Keep Alive [TCP] +dsEdit.opcua.noDelay=No Delay [TCP] +dsEdit.opcua.discovery=Discovery +dsEdit.opcua.serverName=Server Name +dsEdit.opcua.serverHost=Server Host +dsEdit.opcua.serverPort=Server Port +dsEdit.opcua.serverPath=Server Path +dsEdit.opcua.identifier=Identifier +dsEdit.opcua.identifierType=Identifier Type +dsEdit.opcua.dataType=Data Type +dsEdit.opcua.namespaceIndex=Namespace Index +dsEdit.opcua.settable=Settable +dsEdit.opcua.findTag=Find Tag +dsEdit.opcua.attributes=Attributes \ No newline at end of file diff --git a/webapp-resources/messages_fr.properties b/webapp-resources/messages_fr.properties index 774be1e8e2..622a68ea81 100644 --- a/webapp-resources/messages_fr.properties +++ b/webapp-resources/messages_fr.properties @@ -3365,4 +3365,60 @@ systemSettings.invalidCustomCss=Invalid custom CSS systemsettings.reports.dataPointExtendedNameLengthLimit=Point name length limit in reports event.ds.recursiveError=Recursive Error event.meta.recursiveError=Recursive error in point "{0}": {1} -validate.cyclicDependency=Cyclic dependency: {0} \ No newline at end of file +validate.cyclicDependency=Cyclic dependency: {0} +dox.opcUaDS=Data Source OPC UA +dox.opcUaPP=Data Point OPC UA +dsedit.opcua.rt.addFailed=Addition failed for {0} +dsedit.opcua.tagName=Tag Tag +dsEdit.opcua=OPC UA +dsEdit.opcua.TagNotValidated=Tag Not Validated +dsEdit.opcua.TagValidated=Tag Validated +dsEdit.opcua.addTags=Add Tags +dsEdit.opcua.browseTags=Browse Tags +dsEdit.opcua.creationMode=Creation Mode +dsEdit.opcua.desc=OPC UA Properties +dsEdit.opcua.domain=Domain +dsEdit.opcua.host=Host +dsEdit.opcua.password=Password +dsEdit.opcua.refreshServers=Refresh +dsEdit.opcua.server=Server +dsEdit.opcua.tag=Tag +dsEdit.opcua.tagList=Tag List +dsEdit.opcua.tagName=Tag Name +dsEdit.opcua.tagsFound=Tags Found +dsEdit.opcua.user=User +dsEdit.opcua.validate.dataSourceNotSaved=You must first save the data source +dsEdit.opcua.validateTag=Validate Tag +dsEdit.opcua.validation=Validation +dsEdit.opcua.messageSecurityType=Message Security +dsEdit.opcua.securityPolicyType=Security Policy +dsEdit.opcua.serverCertificateFile=Server Certificate File +dsEdit.opcua.keyStoreType=Key Store Type +dsEdit.opcua.keyStoreFile=Key Store File +dsEdit.opcua.keyStorePassword=Key Store Password +dsEdit.opcua.trustStoreType=Trust Store Type +dsEdit.opcua.trustStoreFile=Trust Store File +dsEdit.opcua.trustStorePassword=Trust Store Password +dsEdit.opcua.channelLifetime=Channel Lifetime +dsEdit.opcua.sessionTimeout=Session Timeout +dsEdit.opcua.negotiationTimeout=Negotiation Timeout +dsEdit.opcua.requestTimeout=Request Timeout +dsEdit.opcua.defaultTimeout=Default Timeout [TCP] +dsEdit.opcua.receiveBufferSize=Receiver Buffer Size +dsEdit.opcua.sendBufferSize=Send Buffer Size +dsEdit.opcua.maxMessageSize=Max Message Size +dsEdit.opcua.maxChunkCount=Max Chunk Count +dsEdit.opcua.keepAlive=Keep Alive [TCP] +dsEdit.opcua.noDelay=No Delay [TCP] +dsEdit.opcua.discovery=Discovery +dsEdit.opcua.serverName=Server Name +dsEdit.opcua.serverHost=Server Host +dsEdit.opcua.serverPort=Server Port +dsEdit.opcua.serverPath=Server Path +dsEdit.opcua.identifier=Identifier +dsEdit.opcua.identifierType=Identifier Type +dsEdit.opcua.dataType=Data Type +dsEdit.opcua.namespaceIndex=Namespace Index +dsEdit.opcua.settable=Settable +dsEdit.opcua.findTag=Find Tag +dsEdit.opcua.attributes=Attributes \ No newline at end of file diff --git a/webapp-resources/messages_lu.properties b/webapp-resources/messages_lu.properties index 317f71de78..2821301d19 100644 --- a/webapp-resources/messages_lu.properties +++ b/webapp-resources/messages_lu.properties @@ -3384,4 +3384,60 @@ systemSettings.invalidCustomCss=Invalid custom CSS systemsettings.reports.dataPointExtendedNameLengthLimit=Point name length limit in reports event.ds.recursiveError=Recursive Error event.meta.recursiveError=Recursive error in point "{0}": {1} -validate.cyclicDependency=Cyclic dependency: {0} \ No newline at end of file +validate.cyclicDependency=Cyclic dependency: {0} +dox.opcUaDS=Data Source OPC UA +dox.opcUaPP=Data Point OPC UA +dsedit.opcua.rt.addFailed=Addition failed for {0} +dsedit.opcua.tagName=Tag Tag +dsEdit.opcua=OPC UA +dsEdit.opcua.TagNotValidated=Tag Not Validated +dsEdit.opcua.TagValidated=Tag Validated +dsEdit.opcua.addTags=Add Tags +dsEdit.opcua.browseTags=Browse Tags +dsEdit.opcua.creationMode=Creation Mode +dsEdit.opcua.desc=OPC UA Properties +dsEdit.opcua.domain=Domain +dsEdit.opcua.host=Host +dsEdit.opcua.password=Password +dsEdit.opcua.refreshServers=Refresh +dsEdit.opcua.server=Server +dsEdit.opcua.tag=Tag +dsEdit.opcua.tagList=Tag List +dsEdit.opcua.tagName=Tag Name +dsEdit.opcua.tagsFound=Tags Found +dsEdit.opcua.user=User +dsEdit.opcua.validate.dataSourceNotSaved=You must first save the data source +dsEdit.opcua.validateTag=Validate Tag +dsEdit.opcua.validation=Validation +dsEdit.opcua.messageSecurityType=Message Security +dsEdit.opcua.securityPolicyType=Security Policy +dsEdit.opcua.serverCertificateFile=Server Certificate File +dsEdit.opcua.keyStoreType=Key Store Type +dsEdit.opcua.keyStoreFile=Key Store File +dsEdit.opcua.keyStorePassword=Key Store Password +dsEdit.opcua.trustStoreType=Trust Store Type +dsEdit.opcua.trustStoreFile=Trust Store File +dsEdit.opcua.trustStorePassword=Trust Store Password +dsEdit.opcua.channelLifetime=Channel Lifetime +dsEdit.opcua.sessionTimeout=Session Timeout +dsEdit.opcua.negotiationTimeout=Negotiation Timeout +dsEdit.opcua.requestTimeout=Request Timeout +dsEdit.opcua.defaultTimeout=Default Timeout [TCP] +dsEdit.opcua.receiveBufferSize=Receiver Buffer Size +dsEdit.opcua.sendBufferSize=Send Buffer Size +dsEdit.opcua.maxMessageSize=Max Message Size +dsEdit.opcua.maxChunkCount=Max Chunk Count +dsEdit.opcua.keepAlive=Keep Alive [TCP] +dsEdit.opcua.noDelay=No Delay [TCP] +dsEdit.opcua.discovery=Discovery +dsEdit.opcua.serverName=Server Name +dsEdit.opcua.serverHost=Server Host +dsEdit.opcua.serverPort=Server Port +dsEdit.opcua.serverPath=Server Path +dsEdit.opcua.identifier=Identifier +dsEdit.opcua.identifierType=Identifier Type +dsEdit.opcua.dataType=Data Type +dsEdit.opcua.namespaceIndex=Namespace Index +dsEdit.opcua.settable=Settable +dsEdit.opcua.findTag=Find Tag +dsEdit.opcua.attributes=Attributes \ No newline at end of file diff --git a/webapp-resources/messages_nl.properties b/webapp-resources/messages_nl.properties index c1a416ea0f..2ea3ddff2a 100644 --- a/webapp-resources/messages_nl.properties +++ b/webapp-resources/messages_nl.properties @@ -3486,4 +3486,60 @@ systemSettings.invalidCustomCss=Invalid custom CSS systemsettings.reports.dataPointExtendedNameLengthLimit=Point name length limit in reports event.ds.recursiveError=Recursive Error event.meta.recursiveError=Recursive error in point "{0}": {1} -validate.cyclicDependency=Cyclic dependency: {0} \ No newline at end of file +validate.cyclicDependency=Cyclic dependency: {0} +dox.opcUaDS=Data Source OPC UA +dox.opcUaPP=Data Point OPC UA +dsedit.opcua.rt.addFailed=Addition failed for {0} +dsedit.opcua.tagName=Tag Tag +dsEdit.opcua=OPC UA +dsEdit.opcua.TagNotValidated=Tag Not Validated +dsEdit.opcua.TagValidated=Tag Validated +dsEdit.opcua.addTags=Add Tags +dsEdit.opcua.browseTags=Browse Tags +dsEdit.opcua.creationMode=Creation Mode +dsEdit.opcua.desc=OPC UA Properties +dsEdit.opcua.domain=Domain +dsEdit.opcua.host=Host +dsEdit.opcua.password=Password +dsEdit.opcua.refreshServers=Refresh +dsEdit.opcua.server=Server +dsEdit.opcua.tag=Tag +dsEdit.opcua.tagList=Tag List +dsEdit.opcua.tagName=Tag Name +dsEdit.opcua.tagsFound=Tags Found +dsEdit.opcua.user=User +dsEdit.opcua.validate.dataSourceNotSaved=You must first save the data source +dsEdit.opcua.validateTag=Validate Tag +dsEdit.opcua.validation=Validation +dsEdit.opcua.messageSecurityType=Message Security +dsEdit.opcua.securityPolicyType=Security Policy +dsEdit.opcua.serverCertificateFile=Server Certificate File +dsEdit.opcua.keyStoreType=Key Store Type +dsEdit.opcua.keyStoreFile=Key Store File +dsEdit.opcua.keyStorePassword=Key Store Password +dsEdit.opcua.trustStoreType=Trust Store Type +dsEdit.opcua.trustStoreFile=Trust Store File +dsEdit.opcua.trustStorePassword=Trust Store Password +dsEdit.opcua.channelLifetime=Channel Lifetime +dsEdit.opcua.sessionTimeout=Session Timeout +dsEdit.opcua.negotiationTimeout=Negotiation Timeout +dsEdit.opcua.requestTimeout=Request Timeout +dsEdit.opcua.defaultTimeout=Default Timeout [TCP] +dsEdit.opcua.receiveBufferSize=Receiver Buffer Size +dsEdit.opcua.sendBufferSize=Send Buffer Size +dsEdit.opcua.maxMessageSize=Max Message Size +dsEdit.opcua.maxChunkCount=Max Chunk Count +dsEdit.opcua.keepAlive=Keep Alive [TCP] +dsEdit.opcua.noDelay=No Delay [TCP] +dsEdit.opcua.discovery=Discovery +dsEdit.opcua.serverName=Server Name +dsEdit.opcua.serverHost=Server Host +dsEdit.opcua.serverPort=Server Port +dsEdit.opcua.serverPath=Server Path +dsEdit.opcua.identifier=Identifier +dsEdit.opcua.identifierType=Identifier Type +dsEdit.opcua.dataType=Data Type +dsEdit.opcua.namespaceIndex=Namespace Index +dsEdit.opcua.settable=Settable +dsEdit.opcua.findTag=Find Tag +dsEdit.opcua.attributes=Attributes \ No newline at end of file diff --git a/webapp-resources/messages_pl.properties b/webapp-resources/messages_pl.properties index 573d1e0fb3..9f119cc9e8 100644 --- a/webapp-resources/messages_pl.properties +++ b/webapp-resources/messages_pl.properties @@ -3508,4 +3508,60 @@ systemSettings.invalidCustomCss=Invalid custom CSS systemsettings.reports.dataPointExtendedNameLengthLimit=Point name length limit in reports event.ds.recursiveError=Recursive Error event.meta.recursiveError=Recursive error in point "{0}": {1} -validate.cyclicDependency=Cyclic dependency: {0} \ No newline at end of file +validate.cyclicDependency=Cyclic dependency: {0} +dox.opcUaDS=Data Source OPC UA +dox.opcUaPP=Data Point OPC UA +dsedit.opcua.rt.addFailed=Addition failed for {0} +dsedit.opcua.tagName=Tag Tag +dsEdit.opcua=OPC UA +dsEdit.opcua.TagNotValidated=Tag Not Validated +dsEdit.opcua.TagValidated=Tag Validated +dsEdit.opcua.addTags=Add Tags +dsEdit.opcua.browseTags=Browse Tags +dsEdit.opcua.creationMode=Creation Mode +dsEdit.opcua.desc=OPC UA Properties +dsEdit.opcua.domain=Domain +dsEdit.opcua.host=Host +dsEdit.opcua.password=Password +dsEdit.opcua.refreshServers=Refresh +dsEdit.opcua.server=Server +dsEdit.opcua.tag=Tag +dsEdit.opcua.tagList=Tag List +dsEdit.opcua.tagName=Tag Name +dsEdit.opcua.tagsFound=Tags Found +dsEdit.opcua.user=User +dsEdit.opcua.validate.dataSourceNotSaved=You must first save the data source +dsEdit.opcua.validateTag=Validate Tag +dsEdit.opcua.validation=Validation +dsEdit.opcua.messageSecurityType=Message Security +dsEdit.opcua.securityPolicyType=Security Policy +dsEdit.opcua.serverCertificateFile=Server Certificate File +dsEdit.opcua.keyStoreType=Key Store Type +dsEdit.opcua.keyStoreFile=Key Store File +dsEdit.opcua.keyStorePassword=Key Store Password +dsEdit.opcua.trustStoreType=Trust Store Type +dsEdit.opcua.trustStoreFile=Trust Store File +dsEdit.opcua.trustStorePassword=Trust Store Password +dsEdit.opcua.channelLifetime=Channel Lifetime +dsEdit.opcua.sessionTimeout=Session Timeout +dsEdit.opcua.negotiationTimeout=Negotiation Timeout +dsEdit.opcua.requestTimeout=Request Timeout +dsEdit.opcua.defaultTimeout=Default Timeout [TCP] +dsEdit.opcua.receiveBufferSize=Receiver Buffer Size +dsEdit.opcua.sendBufferSize=Send Buffer Size +dsEdit.opcua.maxMessageSize=Max Message Size +dsEdit.opcua.maxChunkCount=Max Chunk Count +dsEdit.opcua.keepAlive=Keep Alive [TCP] +dsEdit.opcua.noDelay=No Delay [TCP] +dsEdit.opcua.discovery=Discovery +dsEdit.opcua.serverName=Server Name +dsEdit.opcua.serverHost=Server Host +dsEdit.opcua.serverPort=Server Port +dsEdit.opcua.serverPath=Server Path +dsEdit.opcua.identifier=Identifier +dsEdit.opcua.identifierType=Identifier Type +dsEdit.opcua.dataType=Data Type +dsEdit.opcua.namespaceIndex=Namespace Index +dsEdit.opcua.settable=Settable +dsEdit.opcua.findTag=Find Tag +dsEdit.opcua.attributes=Attributes \ No newline at end of file diff --git a/webapp-resources/messages_pt.properties b/webapp-resources/messages_pt.properties index cfc1cfb8d2..e392e6e2ab 100644 --- a/webapp-resources/messages_pt.properties +++ b/webapp-resources/messages_pt.properties @@ -3523,4 +3523,60 @@ systemSettings.invalidCustomCss=Invalid custom CSS systemsettings.reports.dataPointExtendedNameLengthLimit=Point name length limit in reports event.ds.recursiveError=Recursive Error event.meta.recursiveError=Recursive error in point "{0}": {1} -validate.cyclicDependency=Cyclic dependency: {0} \ No newline at end of file +validate.cyclicDependency=Cyclic dependency: {0} +dox.opcUaDS=Data Source OPC UA +dox.opcUaPP=Data Point OPC UA +dsedit.opcua.rt.addFailed=Addition failed for {0} +dsedit.opcua.tagName=Tag Tag +dsEdit.opcua=OPC UA +dsEdit.opcua.TagNotValidated=Tag Not Validated +dsEdit.opcua.TagValidated=Tag Validated +dsEdit.opcua.addTags=Add Tags +dsEdit.opcua.browseTags=Browse Tags +dsEdit.opcua.creationMode=Creation Mode +dsEdit.opcua.desc=OPC UA Properties +dsEdit.opcua.domain=Domain +dsEdit.opcua.host=Host +dsEdit.opcua.password=Password +dsEdit.opcua.refreshServers=Refresh +dsEdit.opcua.server=Server +dsEdit.opcua.tag=Tag +dsEdit.opcua.tagList=Tag List +dsEdit.opcua.tagName=Tag Name +dsEdit.opcua.tagsFound=Tags Found +dsEdit.opcua.user=User +dsEdit.opcua.validate.dataSourceNotSaved=You must first save the data source +dsEdit.opcua.validateTag=Validate Tag +dsEdit.opcua.validation=Validation +dsEdit.opcua.messageSecurityType=Message Security +dsEdit.opcua.securityPolicyType=Security Policy +dsEdit.opcua.serverCertificateFile=Server Certificate File +dsEdit.opcua.keyStoreType=Key Store Type +dsEdit.opcua.keyStoreFile=Key Store File +dsEdit.opcua.keyStorePassword=Key Store Password +dsEdit.opcua.trustStoreType=Trust Store Type +dsEdit.opcua.trustStoreFile=Trust Store File +dsEdit.opcua.trustStorePassword=Trust Store Password +dsEdit.opcua.channelLifetime=Channel Lifetime +dsEdit.opcua.sessionTimeout=Session Timeout +dsEdit.opcua.negotiationTimeout=Negotiation Timeout +dsEdit.opcua.requestTimeout=Request Timeout +dsEdit.opcua.defaultTimeout=Default Timeout [TCP] +dsEdit.opcua.receiveBufferSize=Receiver Buffer Size +dsEdit.opcua.sendBufferSize=Send Buffer Size +dsEdit.opcua.maxMessageSize=Max Message Size +dsEdit.opcua.maxChunkCount=Max Chunk Count +dsEdit.opcua.keepAlive=Keep Alive [TCP] +dsEdit.opcua.noDelay=No Delay [TCP] +dsEdit.opcua.discovery=Discovery +dsEdit.opcua.serverName=Server Name +dsEdit.opcua.serverHost=Server Host +dsEdit.opcua.serverPort=Server Port +dsEdit.opcua.serverPath=Server Path +dsEdit.opcua.identifier=Identifier +dsEdit.opcua.identifierType=Identifier Type +dsEdit.opcua.dataType=Data Type +dsEdit.opcua.namespaceIndex=Namespace Index +dsEdit.opcua.settable=Settable +dsEdit.opcua.findTag=Find Tag +dsEdit.opcua.attributes=Attributes \ No newline at end of file diff --git a/webapp-resources/messages_ru.properties b/webapp-resources/messages_ru.properties index ea5b9c4d0d..657af961e4 100644 --- a/webapp-resources/messages_ru.properties +++ b/webapp-resources/messages_ru.properties @@ -3519,4 +3519,60 @@ systemSettings.invalidCustomCss=Invalid custom CSS systemsettings.reports.dataPointExtendedNameLengthLimit=Point name length limit in reports event.ds.recursiveError=Recursive Error event.meta.recursiveError=Recursive error in point "{0}": {1} -validate.cyclicDependency=Cyclic dependency: {0} \ No newline at end of file +validate.cyclicDependency=Cyclic dependency: {0} +dox.opcUaDS=Data Source OPC UA +dox.opcUaPP=Data Point OPC UA +dsedit.opcua.rt.addFailed=Addition failed for {0} +dsedit.opcua.tagName=Tag Tag +dsEdit.opcua=OPC UA +dsEdit.opcua.TagNotValidated=Tag Not Validated +dsEdit.opcua.TagValidated=Tag Validated +dsEdit.opcua.addTags=Add Tags +dsEdit.opcua.browseTags=Browse Tags +dsEdit.opcua.creationMode=Creation Mode +dsEdit.opcua.desc=OPC UA Properties +dsEdit.opcua.domain=Domain +dsEdit.opcua.host=Host +dsEdit.opcua.password=Password +dsEdit.opcua.refreshServers=Refresh +dsEdit.opcua.server=Server +dsEdit.opcua.tag=Tag +dsEdit.opcua.tagList=Tag List +dsEdit.opcua.tagName=Tag Name +dsEdit.opcua.tagsFound=Tags Found +dsEdit.opcua.user=User +dsEdit.opcua.validate.dataSourceNotSaved=You must first save the data source +dsEdit.opcua.validateTag=Validate Tag +dsEdit.opcua.validation=Validation +dsEdit.opcua.messageSecurityType=Message Security +dsEdit.opcua.securityPolicyType=Security Policy +dsEdit.opcua.serverCertificateFile=Server Certificate File +dsEdit.opcua.keyStoreType=Key Store Type +dsEdit.opcua.keyStoreFile=Key Store File +dsEdit.opcua.keyStorePassword=Key Store Password +dsEdit.opcua.trustStoreType=Trust Store Type +dsEdit.opcua.trustStoreFile=Trust Store File +dsEdit.opcua.trustStorePassword=Trust Store Password +dsEdit.opcua.channelLifetime=Channel Lifetime +dsEdit.opcua.sessionTimeout=Session Timeout +dsEdit.opcua.negotiationTimeout=Negotiation Timeout +dsEdit.opcua.requestTimeout=Request Timeout +dsEdit.opcua.defaultTimeout=Default Timeout [TCP] +dsEdit.opcua.receiveBufferSize=Receiver Buffer Size +dsEdit.opcua.sendBufferSize=Send Buffer Size +dsEdit.opcua.maxMessageSize=Max Message Size +dsEdit.opcua.maxChunkCount=Max Chunk Count +dsEdit.opcua.keepAlive=Keep Alive [TCP] +dsEdit.opcua.noDelay=No Delay [TCP] +dsEdit.opcua.discovery=Discovery +dsEdit.opcua.serverName=Server Name +dsEdit.opcua.serverHost=Server Host +dsEdit.opcua.serverPort=Server Port +dsEdit.opcua.serverPath=Server Path +dsEdit.opcua.identifier=Identifier +dsEdit.opcua.identifierType=Identifier Type +dsEdit.opcua.dataType=Data Type +dsEdit.opcua.namespaceIndex=Namespace Index +dsEdit.opcua.settable=Settable +dsEdit.opcua.findTag=Find Tag +dsEdit.opcua.attributes=Attributes \ No newline at end of file diff --git a/webapp-resources/messages_zh.properties b/webapp-resources/messages_zh.properties index 899d2baccf..ea7f9ba487 100644 --- a/webapp-resources/messages_zh.properties +++ b/webapp-resources/messages_zh.properties @@ -3471,4 +3471,60 @@ systemSettings.invalidCustomCss=Invalid custom CSS systemsettings.reports.dataPointExtendedNameLengthLimit=Point name length limit in reports event.ds.recursiveError=Recursive Error event.meta.recursiveError=Recursive error in point "{0}": {1} -validate.cyclicDependency=Cyclic dependency: {0} \ No newline at end of file +validate.cyclicDependency=Cyclic dependency: {0} +dox.opcUaDS=Data Source OPC UA +dox.opcUaPP=Data Point OPC UA +dsedit.opcua.rt.addFailed=Addition failed for {0} +dsedit.opcua.tagName=Tag Tag +dsEdit.opcua=OPC UA +dsEdit.opcua.TagNotValidated=Tag Not Validated +dsEdit.opcua.TagValidated=Tag Validated +dsEdit.opcua.addTags=Add Tags +dsEdit.opcua.browseTags=Browse Tags +dsEdit.opcua.creationMode=Creation Mode +dsEdit.opcua.desc=OPC UA Properties +dsEdit.opcua.domain=Domain +dsEdit.opcua.host=Host +dsEdit.opcua.password=Password +dsEdit.opcua.refreshServers=Refresh +dsEdit.opcua.server=Server +dsEdit.opcua.tag=Tag +dsEdit.opcua.tagList=Tag List +dsEdit.opcua.tagName=Tag Name +dsEdit.opcua.tagsFound=Tags Found +dsEdit.opcua.user=User +dsEdit.opcua.validate.dataSourceNotSaved=You must first save the data source +dsEdit.opcua.validateTag=Validate Tag +dsEdit.opcua.validation=Validation +dsEdit.opcua.messageSecurityType=Message Security +dsEdit.opcua.securityPolicyType=Security Policy +dsEdit.opcua.serverCertificateFile=Server Certificate File +dsEdit.opcua.keyStoreType=Key Store Type +dsEdit.opcua.keyStoreFile=Key Store File +dsEdit.opcua.keyStorePassword=Key Store Password +dsEdit.opcua.trustStoreType=Trust Store Type +dsEdit.opcua.trustStoreFile=Trust Store File +dsEdit.opcua.trustStorePassword=Trust Store Password +dsEdit.opcua.channelLifetime=Channel Lifetime +dsEdit.opcua.sessionTimeout=Session Timeout +dsEdit.opcua.negotiationTimeout=Negotiation Timeout +dsEdit.opcua.requestTimeout=Request Timeout +dsEdit.opcua.defaultTimeout=Default Timeout [TCP] +dsEdit.opcua.receiveBufferSize=Receiver Buffer Size +dsEdit.opcua.sendBufferSize=Send Buffer Size +dsEdit.opcua.maxMessageSize=Max Message Size +dsEdit.opcua.maxChunkCount=Max Chunk Count +dsEdit.opcua.keepAlive=Keep Alive [TCP] +dsEdit.opcua.noDelay=No Delay [TCP] +dsEdit.opcua.discovery=Discovery +dsEdit.opcua.serverName=Server Name +dsEdit.opcua.serverHost=Server Host +dsEdit.opcua.serverPort=Server Port +dsEdit.opcua.serverPath=Server Path +dsEdit.opcua.identifier=Identifier +dsEdit.opcua.identifierType=Identifier Type +dsEdit.opcua.dataType=Data Type +dsEdit.opcua.namespaceIndex=Namespace Index +dsEdit.opcua.settable=Settable +dsEdit.opcua.findTag=Find Tag +dsEdit.opcua.attributes=Attributes \ No newline at end of file
+ + +
+ + +
+ + +
+ + +
+ " onclick="findTag();"/> +
+ + + + + + + + + + + + + + +
+
+
+
" + onclick="btnAddTag();" />
+ +
+ +