You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2018/05/04 12:14:57 UTC
[incubator-plc4x] 01/01: initial port of AdsLib to test
compatibility of to c++ implementation
This is an automated email from the ASF dual-hosted git repository.
sruehl pushed a commit to branch testing/adsLibPort
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git
commit ffb98a8105830eff2aa3fa23b7e719c0dc071f96
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Fri Apr 13 13:34:09 2018 +0200
initial port of AdsLib to test compatibility of to c++ implementation
---
.../plc4x/java/ads/api/commands/types/Result.java | 4 +-
.../plc4x/java/ads/api/generic/types/AmsError.java | 8 +-
.../java/ads/connection/AdsTcpPlcConnection.java | 4 +
.../ads/adslib/ADSClientNotificationExample.java | 280 +++++++++++
.../org/apache/plc4x/java/ads/adslib/AdsLib.java | 544 +++++++++++++++++++++
.../apache/plc4x/java/ads/adslib/AmsRequest.java | 50 ++
.../apache/plc4x/java/ads/adslib/AmsRouter.java | 367 ++++++++++++++
.../org/apache/plc4x/java/ads/adslib/Output.java | 38 ++
.../apache/plc4x/java/ads/adslib/package-info.java | 24 +
plc4j/protocols/ads/src/test/resources/logback.xml | 4 +-
.../base/connection/TcpSocketChannelFactory.java | 3 +
11 files changed, 1323 insertions(+), 3 deletions(-)
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/Result.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/Result.java
index ed6ca78..993ad86 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/Result.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/commands/types/Result.java
@@ -21,6 +21,8 @@ package org.apache.plc4x.java.ads.api.commands.types;
import io.netty.buffer.ByteBuf;
import org.apache.plc4x.java.ads.api.util.UnsignedIntLEByteValue;
+import static java.util.Objects.requireNonNull;
+
public class Result extends UnsignedIntLEByteValue {
public static final int NUM_BYTES = UnsignedIntLEByteValue.UNSIGNED_INT_LE_NUM_BYTES;
@@ -58,7 +60,7 @@ public class Result extends UnsignedIntLEByteValue {
}
public static Result of(AdsReturnCode adsReturnCode) {
- return of(adsReturnCode.getHex());
+ return of(requireNonNull(adsReturnCode).getHex());
}
public AdsReturnCode toAdsReturnCode() {
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/types/AmsError.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/types/AmsError.java
index 9a41994..0a78b8d 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/types/AmsError.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/api/generic/types/AmsError.java
@@ -22,6 +22,8 @@ import io.netty.buffer.ByteBuf;
import org.apache.plc4x.java.ads.api.commands.types.AdsReturnCode;
import org.apache.plc4x.java.ads.api.util.UnsignedIntLEByteValue;
+import static java.util.Objects.requireNonNull;
+
public class AmsError extends UnsignedIntLEByteValue {
public static final int NUM_BYTES = UnsignedIntLEByteValue.UNSIGNED_INT_LE_NUM_BYTES;
@@ -48,6 +50,10 @@ public class AmsError extends UnsignedIntLEByteValue {
return new AmsError(errorCode);
}
+ public static AmsError of(AdsReturnCode errorCode) {
+ return new AmsError(requireNonNull(errorCode).getHex());
+ }
+
public static AmsError of(String errorCode) {
return of(Long.parseLong(errorCode));
}
@@ -56,7 +62,7 @@ public class AmsError extends UnsignedIntLEByteValue {
return new AmsError(byteBuf);
}
- private AdsReturnCode toAdsReturnCode() {
+ public AdsReturnCode toAdsReturnCode() {
return AdsReturnCode.of(getAsLong());
}
diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/connection/AdsTcpPlcConnection.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/connection/AdsTcpPlcConnection.java
index ce96675..505ab63 100644
--- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/connection/AdsTcpPlcConnection.java
+++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/connection/AdsTcpPlcConnection.java
@@ -85,6 +85,10 @@ public class AdsTcpPlcConnection extends AdsAbstractPlcConnection {
};
}
+ public InetAddress getRemoteAddress() {
+ return ((TcpSocketChannelFactory)channelFactory).getAddress();
+ }
+
protected static AmsNetId generateAMSNetId() {
try {
return AmsNetId.of(Inet4Address.getLocalHost().getHostAddress() + ".1.1");
diff --git a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/ADSClientNotificationExample.java b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/ADSClientNotificationExample.java
new file mode 100644
index 0000000..ed2c20d
--- /dev/null
+++ b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/ADSClientNotificationExample.java
@@ -0,0 +1,280 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.ads.adslib;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.plc4x.java.ads.api.commands.types.*;
+import org.apache.plc4x.java.ads.api.generic.types.AmsNetId;
+import org.apache.plc4x.java.ads.api.generic.types.AmsPort;
+import org.apache.plc4x.java.ads.api.util.UnsignedIntLEByteValue;
+
+import java.io.Console;
+import java.io.PrintStream;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.plc4x.java.ads.adslib.AdsLib.*;
+
+/**
+ * Test ported from <a href="https://github.com/Beckhoff/ADS/blob/master/example/example.cpp">github</a>
+ * <p>
+ * On github there is a test project which can be used to test the protocol. The example there uses a c++ implementation.
+ */
+public class ADSClientNotificationExample {
+
+ private static void NotifyCallback(ImmutablePair<AmsNetId, AmsPort> pAddr, TimeStamp timeStamp, AdsNotificationSample notificationSample, int hUser) {
+ Data notificationSampleData = notificationSample.getData();
+ byte[] data = notificationSampleData.getBytes();
+ System.out.print("NetId: " + pAddr.left +
+ " hUser 0x" + Integer.toHexString(hUser) +
+ " sample time: " + timeStamp.getAsDate() +
+ " sample size: " + notificationSample.getSampleSize().getAsLong() +
+ " value: ");
+ assert data.length == notificationSample.getSampleSize().getAsLong();
+ System.out.println(" 0x" + Hex.encodeHexString(data));
+ }
+
+ private static SymbolHandle getHandleByNameExample(PrintStream out, long port, ImmutablePair<AmsNetId, AmsPort> server, String handleName) {
+ Output<byte[]> handle = new Output<>();
+ Result handleStatus = AdsSyncReadWriteReqEx2(port,
+ server,
+ IndexGroup.ReservedGroups.ADSIGRP_SYM_HNDBYNAME,
+ IndexOffset.of(0),
+ ReadLength.of(4),
+ handle,
+ WriteLength.of(handleName.length()),
+ Data.of(handleName),
+ new Output<>());
+ if (handleStatus.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ out.println("Create handle for '" + handleName + "' failed with: " + handleStatus);
+ return null;
+ }
+ return SymbolHandle.of(handle.value);
+ }
+
+ private static void releaseHandleExample(PrintStream out, long port, ImmutablePair<AmsNetId, AmsPort> server, SymbolHandle handle) {
+ Result releaseHandle = AdsSyncWriteReqEx(port, server, IndexGroup.ReservedGroups.ADSIGRP_SYM_RELEASEHND, IndexOffset.of(0), WriteLength.of(4), Data.of(handle.getBytes()));
+ if (releaseHandle.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ out.println("Release handle " + handle + "' failed with: " + releaseHandle);
+ }
+ }
+
+
+ private static void notificationExample(PrintStream out, long port, ImmutablePair<AmsNetId, AmsPort> server) {
+ AdsNotificationAttribute adsNotificationAttribute = AdsNotificationAttribute.of(
+ Length.of(1),
+ AdsNotificationAttribute.ADSTRANS_SERVERCYCLE,
+ MaxDelay.of(0),
+ CycleTime.of(4000000)
+ );
+
+ Output<Long> notificationHandle = new Output<>();
+ int hUser = 0;
+
+ Result addStatus = AdsSyncAddDeviceNotificationReqEx(port,
+ server,
+ IndexGroup.of(0x4020),
+ IndexOffset.of(4),
+ adsNotificationAttribute,
+ ADSClientNotificationExample::NotifyCallback,
+ hUser,
+ notificationHandle);
+
+ if (addStatus.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ out.println("Add device notification failed with: " + addStatus);
+ return;
+ }
+
+ out.println("Hit ENTER to stop notifications");
+ tryInteractiveWait();
+
+ Result delStatus = AdsSyncDelDeviceNotificationReqEx(port, server, NotificationHandle.of(notificationHandle.value));
+ if (delStatus.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ out.println("Delete device notification failed with: " + delStatus);
+ return;
+ }
+ }
+
+ private static void notificationByNameExample(PrintStream out, long port, ImmutablePair<AmsNetId, AmsPort> server) {
+ AdsNotificationAttribute adsNotificationAttribute = AdsNotificationAttribute.of(
+ Length.of(1),
+ AdsNotificationAttribute.ADSTRANS_SERVERCYCLE,
+ MaxDelay.of(0),
+ CycleTime.of(4000000)
+ );
+ Output<Long> notificationHandle = new Output<>();
+ int hUser = 0;
+
+ SymbolHandle handle;
+
+ out.println("notificationByNameExample():");
+ handle = getHandleByNameExample(out, port, server, "MAIN.byByte[4]");
+ if (handle == null) {
+ return;
+ }
+
+ Result addStatus = AdsSyncAddDeviceNotificationReqEx(port,
+ server,
+ IndexGroup.ReservedGroups.ADSIGRP_SYM_VALBYHND,
+ IndexOffset.of(handle.getAsLong()),
+ adsNotificationAttribute,
+ ADSClientNotificationExample::NotifyCallback,
+ hUser,
+ notificationHandle);
+ if (addStatus.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ out.println("Add device notification failed with: " + addStatus);
+ return;
+ }
+
+ out.println("Hit ENTER to stop by name notifications");
+ tryInteractiveWait();
+
+ Result delStatus = AdsSyncDelDeviceNotificationReqEx(port, server, NotificationHandle.of(notificationHandle.value));
+ if (delStatus.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ out.println("Delete device notification failed with: " + delStatus);
+ return;
+ }
+ releaseHandleExample(out, port, server, handle);
+ }
+
+ private static void readExample(PrintStream out, long port, ImmutablePair<AmsNetId, AmsPort> server) {
+ Output<Integer> bytesRead = new Output<>();
+ Output<byte[]> buffer = new Output<>();
+
+ out.println("readExample():");
+ for (int i = 0; i < 8; ++i) {
+ Result status = AdsSyncReadReqEx2(port, server, IndexGroup.of(0x4020), IndexOffset.of(0), Length.of(4), buffer, bytesRead);
+ if (status.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ out.println("ADS read failed with: " + status);
+ return;
+ }
+ out.println("ADS read " + bytesRead.value + " bytes, value: 0x" + Hex.encodeHexString(buffer.value));
+ }
+ }
+
+ private static void readByNameExample(PrintStream out, long port, ImmutablePair<AmsNetId, AmsPort> server) {
+ Output<Integer> bytesRead = new Output<>();
+ Output<byte[]> buffer = new Output<>();
+ SymbolHandle handle;
+
+ out.println("readByNameExample():");
+ handle = getHandleByNameExample(out, port, server, "MAIN.byByte[4]");
+ if (handle == null) {
+ return;
+ }
+
+ for (int i = 0; i < 8; ++i) {
+ Result status = AdsSyncReadReqEx2(port, server, IndexGroup.ReservedGroups.ADSIGRP_SYM_VALBYHND, IndexOffset.of(handle.getAsLong()), Length.of(4), buffer, bytesRead);
+ if (status.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ out.println("ADS read failed with: " + status);
+ return;
+ }
+ out.println("ADS read " + bytesRead.value + " bytes, value: 0x" + Hex.encodeHexString(buffer.value));
+ }
+ releaseHandleExample(out, port, server, handle);
+ }
+
+ private static void readStateExample(PrintStream out, long port, ImmutablePair<AmsNetId, AmsPort> server) {
+ Output<Integer> adsState = new Output<>();
+ Output<Integer> devState = new Output<>();
+
+ Result status = AdsSyncReadStateReqEx(port, server, adsState, devState);
+ if (status.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ out.println("ADS read failed with: " + status);
+ return;
+ }
+ out.println("ADS state: " + adsState.value + " devState: " + devState.value);
+ }
+
+ private static void runExample(String remoteNetIdString, String remoteIpV4, PrintStream out) {
+ AmsNetId remoteNetId = AmsNetId.of(remoteNetIdString);
+
+ // uncomment and adjust if automatic AmsNetId deduction is not working as expected
+ AdsSetLocalAddress(AmsNetId.of("192.168.99.1.1.1"));
+
+ // add local route to your EtherCAT Master
+ if (AdsAddRoute(remoteNetId, remoteIpV4).toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ out.println("Adding ADS route failed, did you specified valid addresses?");
+ return;
+ }
+
+ // open a new ADS port
+ long port = AdsPortOpenEx();
+ if (port < 0) {
+ out.println("Open ADS port failed");
+ return;
+ }
+
+
+ ImmutablePair<AmsNetId, AmsPort> remote = ImmutablePair.of(remoteNetId, AmsPort.of(851));
+
+ notificationExample(out, port, remote);
+ notificationByNameExample(out, port, remote);
+ readExample(out, port, remote);
+ readByNameExample(out, port, remote);
+ readStateExample(out, port, remote);
+
+ Result closeStatus = AdsPortCloseEx(port);
+ if (closeStatus.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ out.println("Close ADS port failed with: " + closeStatus);
+ }
+
+ AdsDelRoute(remoteNetId);
+ }
+
+ public static void main(String... args) {
+ String remoteNetIdString = "192.168.99.101.1.1";
+ String remoteIpV4 = "192.168.99.100";
+ if (args.length == 2) {
+ remoteIpV4 = args[0];
+ remoteNetIdString = args[1];
+ }
+ runExample(remoteNetIdString, remoteIpV4, System.out);
+ System.exit(0);
+ }
+
+ /////
+ // Utils
+ private static void tryInteractiveWait() {
+ Console console = System.console();
+ if (console != null) {
+ console.readLine();
+ } else {
+ try {
+ int timeout = Integer.valueOf(System.getProperty("input.timeout", "3"));
+ System.out.println("Using timeout of " + timeout + "Seconds as System.console() is not available. Override with -Dinput.timeout=3");
+ TimeUnit.SECONDS.sleep(timeout);
+ System.out.println("Timeout reached enter pressed");
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ static class SymbolHandle extends UnsignedIntLEByteValue {
+
+ public SymbolHandle(byte... bytes) {
+ super(bytes);
+ }
+
+ public static SymbolHandle of(byte... bytes) {
+ return new SymbolHandle(bytes);
+ }
+ }
+}
diff --git a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AdsLib.java b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AdsLib.java
new file mode 100644
index 0000000..ed23109
--- /dev/null
+++ b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AdsLib.java
@@ -0,0 +1,544 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.ads.adslib;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Triple;
+import org.apache.plc4x.java.ads.api.commands.*;
+import org.apache.plc4x.java.ads.api.commands.types.*;
+import org.apache.plc4x.java.ads.api.generic.types.AmsError;
+import org.apache.plc4x.java.ads.api.generic.types.AmsNetId;
+import org.apache.plc4x.java.ads.api.generic.types.AmsPort;
+import org.apache.plc4x.java.ads.api.generic.types.Invoke;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.Inet4Address;
+import java.net.UnknownHostException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Ported from <a href="https://github.com/Beckhoff/ADS">github AdsLib</a>
+ */
+public class AdsLib {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AdsLib.class);
+
+ private static final AmsRouter amsRouter = new AmsRouter();
+
+ private static AmsRouter GetRouter() {
+ return amsRouter;
+ }
+
+ /**
+ * Add new ams route to target system
+ *
+ * @param ams address of the target system
+ * @param ip address of the target system
+ * @return [ADS Return Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+ */
+ public static Result AdsAddRoute(AmsNetId ams, String ip) {
+ try {
+ return GetRouter().AddRoute(ams, (Inet4Address) Inet4Address.getByName(ip));
+ } catch (UnknownHostException e) {
+ LOGGER.error("Error adding route", e);
+ return Result.of(AdsReturnCode.ADS_CODE_1);
+ }
+ }
+
+ /**
+ * Delete ams route that had previously been added with AdsAddRoute().
+ *
+ * @param ams address of the target system
+ */
+ public static void AdsDelRoute(AmsNetId ams) {
+ GetRouter().DelRoute(ams);
+ }
+
+ /**
+ * The connection (communication port) to the message router is
+ * closed. The port to be closed must previously have been opened via
+ * an AdsPortOpenEx() call.
+ *
+ * @param port port number of an Ads port that had previously been opened with AdsPortOpenEx().
+ * @return [ADS Return Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+ */
+ public static Result AdsPortCloseEx(long port) {
+ return GetRouter().ClosePort((int) port);
+ }
+
+ /**
+ * Establishes a connection (communication port) to the message
+ * router. The port number returned by AdsPortOpenEx() is required as
+ * parameter for further AdsLib function calls.
+ *
+ * @return port number of a new Ads port or 0 if no more ports available
+ */
+ public static long AdsPortOpenEx() {
+ return GetRouter().OpenPort();
+ }
+
+ /**
+ * Returns the local NetId and port number.
+ *
+ * @param port port number of an Ads port that had previously been opened with AdsPortOpenEx().
+ * @param pAddr Pointer to the structure of type ImmutablePair<AmsNetId, AmsPort>.
+ * @return [ADS Return Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+ */
+ public static Result AdsGetLocalAddressEx(long port, Output<ImmutablePair<AmsNetId, AmsPort>> pAddr) {
+ return GetRouter().GetLocalAddress((int) port, pAddr);
+ }
+
+ /**
+ * Change local NetId
+ *
+ * @param ams local AmsNetId
+ */
+ public static void AdsSetLocalAddress(AmsNetId ams) {
+ GetRouter().SetLocalAddress(ams);
+ }
+
+ /**
+ * Reads data synchronously from an ADS server.
+ *
+ * @param port port number of an Ads port that had previously been opened with AdsPortOpenEx().
+ * @param pAddr Structure with NetId and port number of the ADS server.
+ * @param indexGroup Index Group.
+ * @param indexOffset Index Offset.
+ * @param bufferLength Length of the data in bytes.
+ * @param buffer Pointer to a data buffer that will receive the data.
+ * @param bytesRead pointer to a variable. If successful, this variable will return the number of actually read data bytes.
+ * @return [ADS Return Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+ */
+ public static Result AdsSyncReadReqEx2(long port,
+ ImmutablePair<AmsNetId, AmsPort> pAddr,
+ IndexGroup indexGroup,
+ IndexOffset indexOffset,
+ Length bufferLength,
+ Output<byte[]> buffer,
+ Output<Integer> bytesRead) {
+ Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+ Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, localAmsNet);
+ if (getLocalAddressResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return getLocalAddressResult;
+ }
+ AdsReadRequest adsReadRequest = AdsReadRequest.of(
+ pAddr.left,
+ pAddr.right,
+ localAmsNet.value.left,
+ localAmsNet.value.right,
+ Invoke.NONE,
+ indexGroup,
+ indexOffset,
+ bufferLength
+ );
+
+ AmsRequest<AdsReadRequest, AdsReadResponse> request = AmsRequest.of(adsReadRequest);
+ AmsError adsRequestResult = GetRouter().AdsRequest(request);
+ if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return Result.of(adsRequestResult.getAsLong());
+ }
+ try {
+ AdsReadResponse response = request.getResponseFuture().get(3, TimeUnit.SECONDS);
+ byte[] bytes = response.getData().getBytes();
+ buffer.value = bytes;
+ bytesRead.value = bytes.length;
+ return response.getResult();
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ e.printStackTrace();
+ return Result.of(AdsReturnCode.ADS_CODE_1);
+ }
+ }
+
+ /**
+ * Reads the identification and version number of an ADS server.
+ *
+ * @param port port number of an Ads port that had previously been opened with AdsPortOpenEx().
+ * @param pAddr Structure with NetId and port number of the ADS server.
+ * @param devName Pointer to a character string of at least 16 bytes, that will receive the name of the ADS device.
+ * @param version Address of a variable of type AdsVersion, which will receive the version number, revision number and the build number.
+ * @return [ADS Return Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+ */
+ public static Result AdsSyncReadDeviceInfoReqEx(long port, ImmutablePair<AmsNetId, AmsPort> pAddr, Output<String> devName, Output<Triple<Byte, Byte, Integer>> version) {
+ Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+ Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, localAmsNet);
+ if (getLocalAddressResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return getLocalAddressResult;
+ }
+ AdsReadDeviceInfoRequest adsReadDeviceInfoRequest = AdsReadDeviceInfoRequest.of(
+ pAddr.left,
+ pAddr.right,
+ localAmsNet.value.left,
+ localAmsNet.value.right,
+ Invoke.NONE
+ );
+ AmsRequest<AdsReadDeviceInfoRequest, AdsReadDeviceInfoResponse> request = AmsRequest.of(adsReadDeviceInfoRequest);
+ AmsError adsRequestResult = GetRouter().AdsRequest(request);
+ if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return Result.of(adsRequestResult.getAsLong());
+ }
+ try {
+ AdsReadDeviceInfoResponse response = request.getResponseFuture().get(3, TimeUnit.SECONDS);
+ devName.value = new String(response.getDevice().getBytes());
+ version.value = Triple.of(response.getMajorVersion().getAsByte(), response.getMinorVersion().getAsByte(), response.getVersion().getAsInt());
+ return response.getResult();
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ e.printStackTrace();
+ return Result.of(AdsReturnCode.ADS_CODE_1);
+ }
+ }
+
+ /**
+ * Reads the ADS status and the device status from an ADS server.
+ *
+ * @param port port number of an Ads port that had previously been opened with AdsPortOpenEx().
+ * @param pAddr Structure with NetId and port number of the ADS server.
+ * @param adsState Address of a variable that will receive the ADS status (see data type [ADSSTATE](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/tcadsdll_enumadsstate.htm?id=17630)).
+ * @param devState Address of a variable that will receive the device status.
+ * @return [ADS Return Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+ */
+ public static Result AdsSyncReadStateReqEx(long port, ImmutablePair<AmsNetId, AmsPort> pAddr, Output<Integer> adsState, Output<Integer> devState) {
+ Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+ Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, localAmsNet);
+ if (getLocalAddressResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return getLocalAddressResult;
+ }
+ AdsReadStateRequest adsReadStateRequest = AdsReadStateRequest.of(
+ pAddr.left,
+ pAddr.right,
+ localAmsNet.value.left,
+ localAmsNet.value.right,
+ Invoke.NONE
+ );
+ AmsRequest<AdsReadStateRequest, AdsReadStateResponse> request = AmsRequest.of(adsReadStateRequest);
+ AmsError adsRequestResult = GetRouter().AdsRequest(request);
+ if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return Result.of(adsRequestResult.getAsLong());
+ }
+ try {
+ AdsReadStateResponse response = request.getResponseFuture().get(3, TimeUnit.SECONDS);
+ adsState.value = response.getAdsState().getAsInt();
+ devState.value = response.getDeviceState().getAsInt();
+ return response.getResult();
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ e.printStackTrace();
+ return Result.of(AdsReturnCode.ADS_CODE_1);
+ }
+ }
+
+ /**
+ * Writes data synchronously into an ADS server and receives data back from the ADS server.
+ *
+ * @param port port number of an Ads port that had previously been opened with AdsPortOpenEx().
+ * @param pAddr Structure with NetId and port number of the ADS server.
+ * @param indexGroup Index Group.
+ * @param indexOffset Index Offset.
+ * @param readLength Length, in bytes, of the read buffer readData.
+ * @param readData Buffer for data read from the ADS server.
+ * @param writeLength Length of the data, in bytes, send to the ADS server.
+ * @param writeData Buffer with data send to the ADS server.
+ * @param bytesRead pointer to a variable. If successful, this variable will return the number of actually read data bytes.
+ * @return [ADS Return Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+ */
+ public static Result AdsSyncReadWriteReqEx2(long port,
+ ImmutablePair<AmsNetId, AmsPort> pAddr,
+ IndexGroup indexGroup,
+ IndexOffset indexOffset,
+ ReadLength readLength,
+ Output<byte[]> readData,
+ WriteLength writeLength,
+ Data writeData,
+ Output<Integer> bytesRead) {
+ Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+ Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, localAmsNet);
+ if (getLocalAddressResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return getLocalAddressResult;
+ }
+ AdsReadWriteRequest adsReadWriteRequest = AdsReadWriteRequest.of(
+ pAddr.left,
+ pAddr.right,
+ localAmsNet.value.left,
+ localAmsNet.value.right,
+ Invoke.NONE,
+ indexGroup,
+ indexOffset,
+ readLength,
+ writeData
+ );
+ AmsRequest<AdsReadWriteRequest, AdsReadWriteResponse> request = AmsRequest.of(adsReadWriteRequest);
+ AmsError adsRequestResult = GetRouter().AdsRequest(request);
+ if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return Result.of(adsRequestResult.getAsLong());
+ }
+ try {
+ AdsReadWriteResponse response = request.getResponseFuture().get(3, TimeUnit.SECONDS);
+ byte[] bytes = response.getData().getBytes();
+ readData.value = bytes;
+ bytesRead.value = bytes.length;
+ return response.getResult();
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ e.printStackTrace();
+ return Result.of(AdsReturnCode.ADS_CODE_1);
+ }
+ }
+
+ /**
+ * Writes data synchronously to an ADS server.
+ *
+ * @param port port number of an Ads port that had previously been opened with AdsPortOpenEx().
+ * @param pAddr Structure with NetId and port number of the ADS server.
+ * @param indexGroup Index Group.
+ * @param indexOffset Index Offset.
+ * @param bufferLength Length of the data, in bytes, send to the ADS server.
+ * @param buffer Buffer with data send to the ADS server.
+ * @return [ADS Return Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+ */
+ public static Result AdsSyncWriteReqEx(long port,
+ ImmutablePair<AmsNetId, AmsPort> pAddr,
+ IndexGroup indexGroup,
+ IndexOffset indexOffset,
+ WriteLength bufferLength,
+ Data buffer) {
+ Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+ Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, localAmsNet);
+ if (getLocalAddressResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return getLocalAddressResult;
+ }
+ AdsWriteRequest adsWriteRequest = AdsWriteRequest.of(
+ pAddr.left,
+ pAddr.right,
+ localAmsNet.value.left,
+ localAmsNet.value.right,
+ Invoke.NONE,
+ indexGroup,
+ indexOffset,
+ buffer
+ );
+ AmsRequest<AdsWriteRequest, AdsWriteResponse> request = AmsRequest.of(adsWriteRequest);
+ AmsError adsRequestResult = GetRouter().AdsRequest(request);
+ if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return Result.of(adsRequestResult.getAsLong());
+ }
+ try {
+ AdsWriteResponse response = request.getResponseFuture().get(3, TimeUnit.SECONDS);
+ return response.getResult();
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ e.printStackTrace();
+ return Result.of(AdsReturnCode.ADS_CODE_1);
+ }
+ }
+
+ /**
+ * Changes the ADS status and the device status of an ADS server.
+ *
+ * @param port port number of an Ads port that had previously been opened with AdsPortOpenEx().
+ * @param pAddr Structure with NetId and port number of the ADS server.
+ * @param adsState New ADS status.
+ * @param devState New device status.
+ * @param bufferLength Length of the additional data, in bytes, send to the ADS server.
+ * @param buffer Buffer with additional data send to the ADS server.
+ * @return [ADS Return Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+ */
+ public static Result AdsSyncWriteControlReqEx(long port,
+ ImmutablePair<AmsNetId, AmsPort> pAddr,
+ short adsState,
+ short devState,
+ int bufferLength,
+ byte[] buffer) {
+ Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+ Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, localAmsNet);
+ if (getLocalAddressResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return getLocalAddressResult;
+ }
+ AdsWriteControlRequest adsWriteControlRequest = AdsWriteControlRequest.of(
+ pAddr.left,
+ pAddr.right,
+ localAmsNet.value.left,
+ localAmsNet.value.right,
+ Invoke.NONE,
+ AdsState.of(adsState),
+ DeviceState.of(devState),
+ Data.of(buffer)
+ );
+ AmsRequest<AdsWriteControlRequest, AdsWriteControlResponse> request = AmsRequest.of(adsWriteControlRequest);
+ AmsError adsRequestResult = GetRouter().AdsRequest(request);
+ if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return Result.of(adsRequestResult.getAsLong());
+ }
+ try {
+ AdsWriteControlResponse response = request.getResponseFuture().get(3, TimeUnit.SECONDS);
+ return response.getResult();
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ e.printStackTrace();
+ return Result.of(AdsReturnCode.ADS_CODE_1);
+ }
+ }
+
+ /**
+ * A notification is defined within an ADS server (e.g. PLC). When a
+ * certain event occurs a function (the callback function) is invoked in
+ * the ADS client (C program).
+ *
+ * @param port port number of an Ads port that had previously been opened with AdsPortOpenEx().
+ * @param pAddr Structure with NetId and port number of the ADS server.
+ * @param indexGroup Index Group.
+ * @param indexOffset Index Offset.
+ * @param attribute Pointer to the structure that contains further information.
+ * @param pFunc Pointer to the structure describing the callback function.
+ * @param hUser 32-bit value that is passed to the callback function.
+ * @param pNotification Address of the variable that will receive the handle of the notification.
+ * @return [ADS Return Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+ */
+ public static Result AdsSyncAddDeviceNotificationReqEx(long port,
+ ImmutablePair<AmsNetId, AmsPort> pAddr,
+ IndexGroup indexGroup,
+ IndexOffset indexOffset,
+ AdsNotificationAttribute attribute,
+ PAdsNotificationFuncEx pFunc,
+ int hUser,
+ Output<Long> pNotification) {
+ Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+ Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, localAmsNet);
+ if (getLocalAddressResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return getLocalAddressResult;
+ }
+ AdsAddDeviceNotificationRequest adsAddDeviceNotificationRequest = AdsAddDeviceNotificationRequest.of(
+ pAddr.left,
+ pAddr.right,
+ localAmsNet.value.left,
+ localAmsNet.value.right,
+ Invoke.NONE,
+ indexGroup,
+ indexOffset,
+ attribute.length,
+ attribute.transmissionMode,
+ attribute.maxDelay,
+ attribute.cycleTime
+ );
+ AmsRouter.Notification notify = AmsRouter.Notification.of(pFunc, hUser, attribute.length, pAddr, port);
+ AmsRequest<AdsAddDeviceNotificationRequest, AdsAddDeviceNotificationResponse> request = AmsRequest.of(adsAddDeviceNotificationRequest);
+ AmsError adsRequestResult = GetRouter().AddNotification(request, pNotification, notify);
+ if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return Result.of(adsRequestResult.getAsLong());
+ }
+ try {
+ AdsAddDeviceNotificationResponse response = request.getResponseFuture().get(3, TimeUnit.SECONDS);
+ return response.getResult();
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ e.printStackTrace();
+ return Result.of(AdsReturnCode.ADS_CODE_1);
+ }
+ }
+
+ /**
+ * A notification defined previously is deleted from an ADS server.
+ *
+ * @param port port number of an Ads port that had previously been opened with AdsPortOpenEx().
+ * @param pAddr Structure with NetId and port number of the ADS server.
+ * @param notificationHandle Address of the variable that contains the handle of the notification.
+ * @return [ADS Return Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+ */
+ public static Result AdsSyncDelDeviceNotificationReqEx(long port, ImmutablePair<AmsNetId, AmsPort> pAddr, NotificationHandle notificationHandle) {
+ Output<ImmutablePair<AmsNetId, AmsPort>> localAmsNet = new Output<>();
+ Result getLocalAddressResult = GetRouter().GetLocalAddress((int) port, localAmsNet);
+ if (getLocalAddressResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return getLocalAddressResult;
+ }
+ AdsDeleteDeviceNotificationRequest adsDeleteDeviceNotificationRequest = AdsDeleteDeviceNotificationRequest.of(
+ pAddr.left,
+ pAddr.right,
+ localAmsNet.value.left,
+ localAmsNet.value.right,
+ Invoke.NONE,
+ notificationHandle
+ );
+ AmsRequest<AdsDeleteDeviceNotificationRequest, AdsDeleteDeviceNotificationResponse> request = AmsRequest.of(adsDeleteDeviceNotificationRequest);
+ AmsError adsRequestResult = GetRouter().DelNotification((int) port, pAddr, request);
+ if (adsRequestResult.toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return Result.of(adsRequestResult.getAsLong());
+ }
+ try {
+ AdsDeleteDeviceNotificationResponse response = request.getResponseFuture().get(3, TimeUnit.SECONDS);
+ return response.getResult();
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ e.printStackTrace();
+ return Result.of(AdsReturnCode.ADS_CODE_1);
+ }
+ }
+
+ /**
+ * Read the configured timeout for the ADS functions. The standard value is 5000 ms.
+ *
+ * @param port port number of an Ads port that had previously been opened with AdsPortOpenEx().
+ * @param timeout Buffer to store timeout value in ms.
+ * @return [ADS Return Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+ */
+ public static Result AdsSyncGetTimeoutEx(long port, Output<Integer> timeout) {
+ return GetRouter().GetTimeout((int) port, timeout);
+ }
+
+ /**
+ * Alters the timeout for the ADS functions. The standard value is 5000 ms.
+ *
+ * @param port port number of an Ads port that had previously been opened with AdsPortOpenEx().
+ * @param timeout Timeout in ms.
+ * @return [ADS Return Code](http://infosys.beckhoff.de/content/1033/tc3_adsdll2/html/ads_returncodes.htm?id=17663)
+ */
+ public static Result AdsSyncSetTimeoutEx(long port, int timeout) {
+ return GetRouter().SetTimeout((int) port, timeout);
+ }
+
+ ////
+ // Utils
+
+ /**
+ * Ported from <a href="https://github.com/Beckhoff/ADS">github AdsLib</a>
+ */
+ @FunctionalInterface
+ public interface PAdsNotificationFuncEx {
+ void notifyCallback(ImmutablePair<AmsNetId, AmsPort> pAddr, TimeStamp timeStamp, AdsNotificationSample notificationSample, int hUser);
+ }
+
+ /**
+ * Ported from <a href="https://github.com/Beckhoff/ADS">github AdsLib</a>
+ */
+ public static class AdsNotificationAttribute {
+
+ public static TransmissionMode ADSTRANS_SERVERCYCLE = TransmissionMode.of(3);
+
+ public final Length length;
+ public final TransmissionMode transmissionMode;
+ public final MaxDelay maxDelay;
+ public final CycleTime cycleTime;
+
+ public AdsNotificationAttribute(Length length, TransmissionMode transmissionMode, MaxDelay maxDelay, CycleTime cycleTime) {
+ this.length = length;
+ this.transmissionMode = transmissionMode;
+ this.maxDelay = maxDelay;
+ this.cycleTime = cycleTime;
+ }
+
+ public static AdsNotificationAttribute of(Length length, TransmissionMode transmissionMode, MaxDelay maxDelay, CycleTime cycleTime) {
+ return new AdsNotificationAttribute(length, transmissionMode, maxDelay, cycleTime);
+ }
+ }
+
+}
diff --git a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AmsRequest.java b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AmsRequest.java
new file mode 100644
index 0000000..c31e978
--- /dev/null
+++ b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AmsRequest.java
@@ -0,0 +1,50 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.ads.adslib;
+
+import org.apache.plc4x.java.ads.api.generic.AmsPacket;
+import org.apache.plc4x.java.api.messages.PlcProprietaryRequest;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Ported from <a href="https://github.com/Beckhoff/ADS">github AdsLib</a>
+ */
+public class AmsRequest<REQUEST extends AmsPacket, RESPONSE extends AmsPacket> {
+
+ private final PlcProprietaryRequest<REQUEST> request;
+ private final CompletableFuture<RESPONSE> responseFuture;
+
+ private AmsRequest(REQUEST amsPacket, CompletableFuture<RESPONSE> responseFuture) {
+ this.request = new PlcProprietaryRequest<>(amsPacket);
+ this.responseFuture = responseFuture;
+ }
+
+ public static <REQUEST extends AmsPacket, RESPONSE extends AmsPacket> AmsRequest<REQUEST, RESPONSE> of(REQUEST amsPacket) {
+ return new AmsRequest<>(amsPacket, new CompletableFuture<>());
+ }
+
+ public PlcProprietaryRequest<REQUEST> getRequest() {
+ return request;
+ }
+
+ public CompletableFuture<RESPONSE> getResponseFuture() {
+ return responseFuture;
+ }
+}
diff --git a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AmsRouter.java b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AmsRouter.java
new file mode 100644
index 0000000..2ebce72
--- /dev/null
+++ b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/AmsRouter.java
@@ -0,0 +1,367 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.ads.adslib;
+
+import org.apache.commons.lang3.mutable.MutableInt;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.plc4x.java.ads.api.commands.*;
+import org.apache.plc4x.java.ads.api.commands.types.*;
+import org.apache.plc4x.java.ads.api.generic.AmsPacket;
+import org.apache.plc4x.java.ads.api.generic.types.AmsError;
+import org.apache.plc4x.java.ads.api.generic.types.AmsNetId;
+import org.apache.plc4x.java.ads.api.generic.types.AmsPort;
+import org.apache.plc4x.java.ads.connection.AdsTcpPlcConnection;
+import org.apache.plc4x.java.ads.protocol.Plc4x2AdsProtocol;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.api.messages.PlcProprietaryRequest;
+import org.apache.plc4x.java.api.messages.PlcProprietaryResponse;
+
+import java.io.IOException;
+import java.net.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.*;
+import java.util.function.Consumer;
+
+/**
+ * Ported from <a href="https://github.com/Beckhoff/ADS">github AdsLib</a>
+ */
+public class AmsRouter {
+
+ private static final int NUM_PORTS_MAX = 128;
+ private static final int PORT_BASE = 30000;
+
+ private AmsNetId localAddr;
+
+ private Map<InetAddress, AdsTcpPlcConnection> connections = new HashMap<>();
+ private Map<AmsNetId, AdsTcpPlcConnection> mapping = new HashMap<>();
+ private Map<AdsTcpPlcConnection, MutableInt> refCounts = new HashMap<>();
+
+ private Map<Integer, AdsLibPort> ports = new HashMap<>();
+
+ public AmsRouter() {
+ try {
+ // TODO: ensure we have ipv4
+ this.localAddr = AmsNetId.of(Inet4Address.getLocalHost().getHostAddress() + ".1.1");
+ } catch (UnknownHostException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public AmsRouter(AmsNetId localAddr) {
+ this.localAddr = localAddr;
+ }
+
+ synchronized Result AddRoute(AmsNetId ams, Inet4Address ip) {
+ AdsTcpPlcConnection oldConnection = GetConnection(ams);
+ if (oldConnection != null && !(ip.equals(((InetSocketAddress) oldConnection.getChannel().remoteAddress()).getAddress()))) {
+ /*
+ There is already a route for this AmsNetId, but with
+ a different IP. The old route has to be deleted, first!
+ */
+ return Result.of(AdsReturnCode.ADS_CODE_1286);
+ }
+
+ AdsTcpPlcConnection conn = connections.get(ip);
+ if (conn == null) {
+ AdsTcpPlcConnection newConnection = AdsTcpPlcConnection.of(ip, null, null);
+ // TODO: add listener here:
+ try {
+ newConnection.connect();
+ } catch (PlcConnectionException e) {
+ throw new RuntimeException(e);
+ }
+ connections.put(ip, newConnection);
+ conn = newConnection;
+
+ /* in case no local AmsNetId was set previously, we derive one */
+ if (localAddr == null) {
+ localAddr = conn.getSourceAmsNetId();
+ }
+ }
+
+ MutableInt refCounter = refCounts.getOrDefault(conn, new MutableInt());
+ refCounter.increment();
+ mapping.put(ams, conn);
+ return localAddr == null ? Result.of(AdsReturnCode.ADS_CODE_1) : Result.of(AdsReturnCode.ADS_CODE_0);
+ }
+
+ synchronized void DelRoute(AmsNetId ams) {
+ AdsTcpPlcConnection route = mapping.get(ams);
+ if (route != null) {
+ AdsTcpPlcConnection conn = route;
+ conn.close();
+ MutableInt refCounter = refCounts.getOrDefault(conn, new MutableInt());
+ if (0 == refCounter.decrementAndGet()) {
+ mapping.remove(ams);
+ refCounts.remove(conn);
+ DeleteIfLastConnection(conn);
+ }
+ }
+ }
+
+ void DeleteIfLastConnection(AdsTcpPlcConnection conn) {
+ if (conn != null) {
+ if (mapping.containsValue(conn)) {
+ return;
+ }
+ connections.remove(conn.getRemoteAddress());
+ }
+ }
+
+ synchronized int OpenPort() {
+ try {
+ AdsLibPort adsLibPort = new AdsLibPort(mapping);
+ int localPort = adsLibPort.getLocalPort();
+ ports.put(localPort, adsLibPort);
+ return localPort;
+ } catch (IOException e) {
+ return 0;
+ }
+ }
+
+ synchronized Result ClosePort(int port) {
+ AdsLibPort serverSocket = ports.get(port);
+ if (serverSocket == null || (serverSocket.getLocalPort() < PORT_BASE) || (serverSocket.getLocalPort() >= PORT_BASE + NUM_PORTS_MAX)) {
+ return Result.of(AdsReturnCode.ADS_CODE_1864);
+ }
+ serverSocket.close();
+ return Result.of(AdsReturnCode.ADS_CODE_0);
+ }
+
+ synchronized Result GetLocalAddress(int port, Output<ImmutablePair<AmsNetId, AmsPort>> pAddr) {
+ if ((port < PORT_BASE) || (port >= PORT_BASE + NUM_PORTS_MAX)) {
+ return Result.of(AdsReturnCode.ADS_CODE_1864);
+ }
+
+ AdsLibPort serverSocket = ports.get(port);
+ if (serverSocket == null) {
+ return Result.of(AdsReturnCode.ADS_CODE_1864);
+ }
+
+ AmsPort amsPort = AmsPort.of(serverSocket.getLocalPort());
+ pAddr.value = ImmutablePair.of(localAddr, amsPort);
+ return Result.of(AdsReturnCode.ADS_CODE_0);
+ }
+
+ synchronized void SetLocalAddress(AmsNetId netId) {
+ localAddr = netId;
+ }
+
+ synchronized Result GetTimeout(int port, Output<Integer> timeout) {
+ if ((port < PORT_BASE) || (port >= PORT_BASE + NUM_PORTS_MAX)) {
+ return Result.of(AdsReturnCode.ADS_CODE_1864);
+ }
+ AdsLibPort serverSocket = ports.get(port);
+ timeout.value = serverSocket.getSoTimeout();
+ return Result.of(AdsReturnCode.ADS_CODE_0);
+ }
+
+ synchronized Result SetTimeout(int port, int timeout) {
+ if ((port < PORT_BASE) || (port >= PORT_BASE + NUM_PORTS_MAX)) {
+ return Result.of(AdsReturnCode.ADS_CODE_1864);
+ }
+ AdsLibPort serverSocket = ports.get(port);
+ serverSocket.setSoTimeout(timeout);
+ return Result.of(AdsReturnCode.ADS_CODE_0);
+ }
+
+ synchronized AdsTcpPlcConnection GetConnection(AmsNetId amsDest) {
+ AdsTcpPlcConnection conn = mapping.get(amsDest);
+ if (conn == null) {
+ return null;
+ }
+ return connections.get(conn.getRemoteAddress());
+ }
+
+ <T extends AmsPacket, R extends AmsPacket> AmsError AdsRequest(AmsRequest<T, R> request) {
+ PlcProprietaryRequest<T> plcProprietaryRequest = request.getRequest();
+
+ AdsTcpPlcConnection ads = GetConnection(plcProprietaryRequest.getRequest().getAmsHeader().getTargetAmsNetId());
+ if (ads == null) {
+ return AmsError.of(AdsReturnCode.ADS_CODE_7);
+ }
+ CompletableFuture<PlcProprietaryResponse<R>> completableFuture = ads.send(plcProprietaryRequest);
+ try {
+ PlcProprietaryResponse<R> response = completableFuture.get(3, TimeUnit.SECONDS);
+ request.getResponseFuture().complete(response.getResponse());
+ return response.getResponse().getAmsHeader().getCode();
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ e.printStackTrace();
+ return AmsError.of(AdsReturnCode.ADS_CODE_1864);
+ }
+ }
+
+ AmsError AddNotification(AmsRequest<AdsAddDeviceNotificationRequest, AdsAddDeviceNotificationResponse> request, Output<Long> pNotification, Notification notify) {
+ PlcProprietaryRequest<AdsAddDeviceNotificationRequest> plcProprietaryRequest = request.getRequest();
+ //if (request.bytesRead) {
+ // request.bytesRead = 0;
+ //}
+
+ AdsTcpPlcConnection ads = GetConnection(plcProprietaryRequest.getRequest().getAmsHeader().getTargetAmsNetId());
+ if (ads == null) {
+ return AmsError.of(AdsReturnCode.ADS_CODE_7);
+ }
+
+ AdsLibPort port = ports.get(plcProprietaryRequest.getRequest().getAmsHeader().getSourceAmsPort().getAsInt());
+ CompletableFuture<PlcProprietaryResponse<AdsAddDeviceNotificationResponse>> send = ads.send(plcProprietaryRequest);
+ try {
+ PlcProprietaryResponse<AdsAddDeviceNotificationResponse> response = send.get(3, TimeUnit.SECONDS);
+ if (response.getResponse().getResult().toAdsReturnCode() != AdsReturnCode.ADS_CODE_0) {
+ return AmsError.of(response.getResponse().getResult().getAsLong());
+ }
+ pNotification.value = response.getResponse().getNotificationHandle().getAsLong();
+ port.AddNotification(pNotification.value, notify);
+ request.getResponseFuture().complete(response.getResponse());
+ return response.getResponse().getAmsHeader().getCode();
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ e.printStackTrace();
+ return AmsError.of(AdsReturnCode.ADS_CODE_1);
+ }
+ }
+
+ AmsError DelNotification(int port, ImmutablePair<AmsNetId, AmsPort> pAddr, AmsRequest<AdsDeleteDeviceNotificationRequest, AdsDeleteDeviceNotificationResponse> request) {
+ PlcProprietaryRequest<AdsDeleteDeviceNotificationRequest> plcProprietaryRequest = request.getRequest();
+
+ AdsTcpPlcConnection ads = GetConnection(plcProprietaryRequest.getRequest().getAmsHeader().getTargetAmsNetId());
+ if (ads == null) {
+ return AmsError.of(AdsReturnCode.ADS_CODE_7);
+ }
+
+ AdsLibPort adsLibPort = ports.get(port);
+ CompletableFuture<PlcProprietaryResponse<AdsDeleteDeviceNotificationResponse>> send = ads.send(plcProprietaryRequest);
+ try {
+ PlcProprietaryResponse<AdsDeleteDeviceNotificationResponse> response = send.get(3, TimeUnit.SECONDS);
+
+ adsLibPort.DelNotification(pAddr, plcProprietaryRequest.getRequest().getNotificationHandle());
+ request.getResponseFuture().complete(response.getResponse());
+ return response.getResponse().getAmsHeader().getCode();
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ e.printStackTrace();
+ return AmsError.of(AdsReturnCode.ADS_CODE_1);
+ }
+ }
+
+ static class Notification {
+
+ AdsLib.PAdsNotificationFuncEx pFunc;
+ int hUser;
+ Length length;
+ ImmutablePair<AmsNetId, AmsPort> pAddr;
+ long port;
+
+ Notification(AdsLib.PAdsNotificationFuncEx pFunc, int hUser, Length length, ImmutablePair<AmsNetId, AmsPort> pAddr, long port) {
+ this.pFunc = pFunc;
+ this.hUser = hUser;
+ this.length = length;
+ this.pAddr = pAddr;
+ this.port = port;
+ }
+
+ static Notification of(AdsLib.PAdsNotificationFuncEx pFunc, int hUser, Length length, ImmutablePair<AmsNetId, AmsPort> pAddr, long port) {
+ return new Notification(pFunc, hUser, length, pAddr, port);
+ }
+
+ }
+
+ static class AdsLibPort implements Consumer<AdsDeviceNotificationRequest> {
+
+ private ServerSocket serverSocket;
+ private Map<Long, Notification> notificationMap;
+ private Map<AmsNetId, AdsTcpPlcConnection> mapping;
+
+ public AdsLibPort(Map<AmsNetId, AdsTcpPlcConnection> mapping) throws IOException {
+ notificationMap = new ConcurrentHashMap<>();
+ this.mapping = mapping;
+ ServerSocket socket = null;
+ for (int i = PORT_BASE; i < PORT_BASE + NUM_PORTS_MAX; i++) {
+ try {
+ socket = new ServerSocket(i);
+ } catch (IOException ignore) {
+ }
+ }
+ if (socket == null) {
+ throw new IOException("Unable to open server in port range(" + PORT_BASE + '-' + PORT_BASE + NUM_PORTS_MAX + ')');
+ }
+ socket.setReuseAddress(true);
+ serverSocket = socket;
+ }
+
+ public int getLocalPort() {
+ return serverSocket.getLocalPort();
+ }
+
+ public void close() {
+ try {
+ serverSocket.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ for (AdsTcpPlcConnection adsTcpPlcConnection : mapping.values()) {
+ adsTcpPlcConnection.getChannel().pipeline().get(Plc4x2AdsProtocol.class).removeConsumer(this);
+ }
+ }
+
+ public Integer getSoTimeout() {
+ try {
+ return serverSocket.getSoTimeout();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return -1;
+ }
+ }
+
+ public void setSoTimeout(int soTimeout) {
+ try {
+ serverSocket.setSoTimeout(soTimeout);
+ } catch (SocketException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void AddNotification(Long notificationHandle, Notification notify) {
+ notificationMap.put(notificationHandle, notify);
+ AdsTcpPlcConnection connection = mapping.get(notify.pAddr.left);
+ // TODO: filter for addr
+ connection.getChannel().pipeline().get(Plc4x2AdsProtocol.class).addConsumer(this);
+ }
+
+ public void DelNotification(ImmutablePair<AmsNetId, AmsPort> pAddr, NotificationHandle notificationHandle) {
+ // Note. pAddr is not used for anything.
+ notificationMap.remove(notificationHandle.getAsLong());
+ AdsTcpPlcConnection connection = mapping.get(pAddr.left);
+ // TODO: filter for addr
+ connection.getChannel().pipeline().get(Plc4x2AdsProtocol.class).addConsumer(this);
+ }
+
+ @Override
+ public void accept(AdsDeviceNotificationRequest adsDeviceNotificationRequest) {
+ for (AdsStampHeader adsStampHeader : adsDeviceNotificationRequest.getAdsStampHeaders()) {
+ TimeStamp timeStamp = adsStampHeader.getTimeStamp();
+ for (AdsNotificationSample adsNotificationSample : adsStampHeader.getAdsNotificationSamples()) {
+ NotificationHandle notificationHandle = adsNotificationSample.getNotificationHandle();
+ Notification notification = notificationMap.get(notificationHandle.getAsLong());
+ notification.pFunc.notifyCallback(notification.pAddr, timeStamp, adsNotificationSample, notification.hUser);
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/Output.java b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/Output.java
new file mode 100644
index 0000000..a9dd1ec
--- /dev/null
+++ b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/Output.java
@@ -0,0 +1,38 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.ads.adslib;
+
+public final class Output<T> {
+
+ public T value;
+
+ public Output() {
+ }
+
+ public Output(T value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return "Output{" +
+ "value=" + value +
+ '}';
+ }
+}
diff --git a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/package-info.java b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/package-info.java
new file mode 100644
index 0000000..fd5b92a
--- /dev/null
+++ b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/adslib/package-info.java
@@ -0,0 +1,24 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+/**
+ * Ported from <a href="https://github.com/Beckhoff/ADS/AdsLib">github</a>
+ * <p>
+ * The purpose of this is to validate the plc4x implementation against a reference test.
+ */
+package org.apache.plc4x.java.ads.adslib;
\ No newline at end of file
diff --git a/plc4j/protocols/ads/src/test/resources/logback.xml b/plc4j/protocols/ads/src/test/resources/logback.xml
index 27d40c0..dd243bd 100644
--- a/plc4j/protocols/ads/src/test/resources/logback.xml
+++ b/plc4j/protocols/ads/src/test/resources/logback.xml
@@ -19,7 +19,9 @@
-->
<configuration xmlns="http://ch.qos.logback/xml/ns/logback"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback https://raw.githubusercontent.com/enricopulatzo/logback-XSD/master/src/main/xsd/logback.xsd">
+ xsi:schemaLocation="
+ http://ch.qos.logback/xml/ns/logback
+ https://raw.githubusercontent.com/enricopulatzo/logback-XSD/master/src/main/xsd/logback.xsd">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
diff --git a/plc4j/protocols/driver-bases/tcp/src/main/java/org/apache/plc4x/java/base/connection/TcpSocketChannelFactory.java b/plc4j/protocols/driver-bases/tcp/src/main/java/org/apache/plc4x/java/base/connection/TcpSocketChannelFactory.java
index 00b660b..3fbfd2a 100644
--- a/plc4j/protocols/driver-bases/tcp/src/main/java/org/apache/plc4x/java/base/connection/TcpSocketChannelFactory.java
+++ b/plc4j/protocols/driver-bases/tcp/src/main/java/org/apache/plc4x/java/base/connection/TcpSocketChannelFactory.java
@@ -57,4 +57,7 @@ public class TcpSocketChannelFactory implements ChannelFactory {
}
}
+ public InetAddress getAddress() {
+ return address;
+ }
}
--
To stop receiving notification emails like this one, please contact
sruehl@apache.org.