You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by cd...@apache.org on 2021/07/11 15:32:08 UTC

[plc4x] 02/02: - Fine-tuned the discovery to clean up allocated resources after finishing discovery - Made the example actually return something - Added an "executeWithHandler" which is able to intercept incoming events as they come in

This is an automated email from the ASF dual-hosted git repository.

cdutz pushed a commit to branch feature/profinet-chris
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit 6c815282637499618008008761a0fa311dad610d
Author: cdutz <ch...@c-ware.de>
AuthorDate: Sun Jul 11 17:31:49 2021 +0200

    - Fine-tuned the discovery to clean up allocated resources after finishing discovery
    - Made the example actually return something
    - Added an "executeWithHandler" which is able to intercept incoming events as they come in
---
 .../plc4x/java/api/messages/PlcDiscoveryItem.java  |   3 +-
 .../java/api/messages/PlcDiscoveryItemHandler.java |   7 +
 .../java/api/messages/PlcDiscoveryRequest.java     |   2 +
 .../java/api/messages/PlcDiscoveryResponse.java    |   8 +
 .../apache/plc4x/java/profinet/ProfinetDriver.java |   8 +-
 .../profinet/discovery/ProfinetPlcDiscoverer.java  | 183 ++++++++++++---------
 .../examples/hellodiscovery/HelloDiscovery.java    |  22 +--
 .../hello-discovery/src/main/resources/logback.xml |   2 +-
 .../java/spi/messages/DefaultPlcDiscoveryItem.java |   7 +-
 .../spi/messages/DefaultPlcDiscoveryRequest.java   |   5 +
 .../spi/messages/DefaultPlcDiscoveryResponse.java  |   8 +
 .../plc4x/java/spi/messages/PlcDiscoverer.java     |   3 +
 .../transport/rawsocket/RawSocketTransport.java    |   4 +-
 13 files changed, 165 insertions(+), 97 deletions(-)

diff --git a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryItem.java b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryItem.java
index fddc77d..c94d513 100644
--- a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryItem.java
+++ b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryItem.java
@@ -18,7 +18,6 @@
  */
 package org.apache.plc4x.java.api.messages;
 
-import java.net.URL;
 import java.util.Map;
 
 public interface PlcDiscoveryItem {
@@ -36,7 +35,7 @@ public interface PlcDiscoveryItem {
     /**
      * @return returns the part of the url, the given transport needs in order to connect (plc.mycompany.de, 192.168.42.23, /dev/serial, COM1)
      */
-    URL getTransportUrl();
+    String getTransportUrl();
 
     /**
      * @return returns a map of all configuration options (usually encoded after the transport url's "?" character (rack=1&slot=1, little-endian=true, ...)
diff --git a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryItemHandler.java b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryItemHandler.java
new file mode 100644
index 0000000..13fa882
--- /dev/null
+++ b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryItemHandler.java
@@ -0,0 +1,7 @@
+package org.apache.plc4x.java.api.messages;
+
+public interface PlcDiscoveryItemHandler {
+
+    void handle(PlcDiscoveryItem discoveryItem);
+
+}
diff --git a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryRequest.java b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryRequest.java
index 577e0bf..afe8579 100644
--- a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryRequest.java
+++ b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryRequest.java
@@ -24,6 +24,8 @@ public interface PlcDiscoveryRequest extends PlcRequest {
 
     CompletableFuture<? extends PlcDiscoveryResponse> execute();
 
+    CompletableFuture<? extends PlcDiscoveryResponse> executeWithHandler(PlcDiscoveryItemHandler handler);
+
     interface Builder extends PlcRequestBuilder {
 
         @Override
diff --git a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryResponse.java b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryResponse.java
index 85d42a7..c4db35b 100644
--- a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryResponse.java
+++ b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcDiscoveryResponse.java
@@ -18,8 +18,16 @@
  */
 package org.apache.plc4x.java.api.messages;
 
+import org.apache.plc4x.java.api.types.PlcResponseCode;
+
+import java.util.List;
+
 public interface PlcDiscoveryResponse extends PlcResponse {
 
     PlcDiscoveryRequest getRequest();
 
+    PlcResponseCode getResponseCode();
+
+    List<PlcDiscoveryItem> getValues();
+
 }
diff --git a/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/ProfinetDriver.java b/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/ProfinetDriver.java
index 0a261f7..4354935 100644
--- a/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/ProfinetDriver.java
+++ b/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/ProfinetDriver.java
@@ -20,7 +20,6 @@ package org.apache.plc4x.java.profinet;
 
 import io.netty.buffer.ByteBuf;
 import org.apache.plc4x.java.api.messages.PlcDiscoveryRequest;
-import org.apache.plc4x.java.api.messages.PlcDiscoveryResponse;
 import org.apache.plc4x.java.api.metadata.PlcDriverMetadata;
 import org.apache.plc4x.java.profinet.config.ProfinetConfiguration;
 import org.apache.plc4x.java.profinet.discovery.ProfinetPlcDiscoverer;
@@ -32,8 +31,6 @@ import org.apache.plc4x.java.profinet.readwrite.io.EthernetFrameIO;
 import org.apache.plc4x.java.spi.connection.GeneratedDriverBase;
 import org.apache.plc4x.java.spi.connection.ProtocolStackConfigurer;
 import org.apache.plc4x.java.spi.messages.DefaultPlcDiscoveryRequest;
-import org.apache.plc4x.java.spi.messages.DefaultPlcWriteRequest;
-import org.apache.plc4x.java.spi.messages.PlcDiscoverer;
 import org.apache.plc4x.java.spi.values.IEC61131ValueHandler;
 import org.apache.plc4x.java.api.value.PlcValueHandler;
 import org.apache.plc4x.java.spi.configuration.Configuration;
@@ -41,14 +38,15 @@ import org.apache.plc4x.java.spi.connection.SingleProtocolStackConfigurer;
 import org.apache.plc4x.java.spi.optimizer.BaseOptimizer;
 import org.apache.plc4x.java.spi.optimizer.SingleFieldOptimizer;
 
-import java.util.concurrent.CompletableFuture;
 import java.util.function.ToIntFunction;
 
 public class ProfinetDriver extends GeneratedDriverBase<EthernetFrame> {
 
+    public static final String DRIVER_CODE = "profinet";
+
     @Override
     public String getProtocolCode() {
-        return "profinet";
+        return DRIVER_CODE;
     }
 
     @Override
diff --git a/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/discovery/ProfinetPlcDiscoverer.java b/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/discovery/ProfinetPlcDiscoverer.java
index dc43f21..ba7ef24 100644
--- a/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/discovery/ProfinetPlcDiscoverer.java
+++ b/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/discovery/ProfinetPlcDiscoverer.java
@@ -18,8 +18,13 @@ under the License.
 */
 package org.apache.plc4x.java.profinet.discovery;
 
+import org.apache.plc4x.java.api.exceptions.PlcException;
+import org.apache.plc4x.java.api.messages.PlcDiscoveryItem;
+import org.apache.plc4x.java.api.messages.PlcDiscoveryItemHandler;
 import org.apache.plc4x.java.api.messages.PlcDiscoveryRequest;
 import org.apache.plc4x.java.api.messages.PlcDiscoveryResponse;
+import org.apache.plc4x.java.api.types.PlcResponseCode;
+import org.apache.plc4x.java.profinet.ProfinetDriver;
 import org.apache.plc4x.java.profinet.readwrite.*;
 import org.apache.plc4x.java.profinet.readwrite.io.EthernetFrameIO;
 import org.apache.plc4x.java.profinet.readwrite.types.VirtualLanPriority;
@@ -27,7 +32,10 @@ import org.apache.plc4x.java.spi.generation.ParseException;
 import org.apache.plc4x.java.spi.generation.ReadBuffer;
 import org.apache.plc4x.java.spi.generation.ReadBufferByteBased;
 import org.apache.plc4x.java.spi.generation.WriteBufferByteBased;
+import org.apache.plc4x.java.spi.messages.DefaultPlcDiscoveryItem;
+import org.apache.plc4x.java.spi.messages.DefaultPlcDiscoveryResponse;
 import org.apache.plc4x.java.spi.messages.PlcDiscoverer;
+import org.apache.plc4x.java.transport.rawsocket.RawSocketTransport;
 import org.pcap4j.core.*;
 import org.pcap4j.packet.Dot1qVlanTagPacket;
 import org.pcap4j.packet.EthernetPacket;
@@ -35,9 +43,10 @@ import org.pcap4j.packet.IllegalRawDataException;
 import org.pcap4j.packet.Packet;
 import org.pcap4j.packet.namednumber.EtherType;
 import org.pcap4j.util.LinkLayerAddress;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -46,6 +55,7 @@ public class ProfinetPlcDiscoverer implements PlcDiscoverer {
 
     private static final EtherType PN_EtherType = EtherType.getInstance((short) 0x8892);
 
+    // The constants for the different block names and their actual meaning.
     private static final String DEVICE_TYPE_NAME = "DEVICE_PROPERTIES_OPTION-1";
     private static final String DEVICE_NAME_OF_STATION = "DEVICE_PROPERTIES_OPTION-2";
     private static final String DEVICE_ID = "DEVICE_PROPERTIES_OPTION-3";
@@ -54,62 +64,110 @@ public class ProfinetPlcDiscoverer implements PlcDiscoverer {
     private static final String DEVICE_INSTANCE = "DEVICE_PROPERTIES_OPTION-7";
     private static final String IP_OPTION_IP = "IP_OPTION-2";
 
-    public ProfinetPlcDiscoverer() {
-    }
+    private final Logger logger = LoggerFactory.getLogger(ProfinetPlcDiscoverer.class);
 
     @Override
     public CompletableFuture<PlcDiscoveryResponse> discover(PlcDiscoveryRequest discoveryRequest) {
-        Map<String, DCP_Identify_ResPDU> pnDevices = new HashMap<>();
+        return discoverWithHandler(discoveryRequest, null);
+    }
+
+    public CompletableFuture<PlcDiscoveryResponse> discoverWithHandler(PlcDiscoveryRequest discoveryRequest, PlcDiscoveryItemHandler handler) {
+        CompletableFuture<PlcDiscoveryResponse> future = new CompletableFuture<>();
+        Set<PcapHandle> openHandles = new HashSet<>();
+        List<PlcDiscoveryItem> values = new ArrayList<>();
         try {
             for (PcapNetworkInterface dev : Pcaps.findAllDevs()) {
-                if(!dev.isLoopBack() && dev.isRunning()) {
+                if (!dev.isLoopBack() && dev.isRunning()) {
                     for (LinkLayerAddress linkLayerAddress : dev.getLinkLayerAddresses()) {
                         org.pcap4j.util.MacAddress macAddress = (org.pcap4j.util.MacAddress) linkLayerAddress;
                         PcapHandle handle = dev.openLive(65536, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, 10);
-                        PcapHandle sendHandle = dev.openLive(65536, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, 10);
+                        openHandles.add(handle);
+
                         ExecutorService pool = Executors.newSingleThreadExecutor();
 
                         // Only react on PROFINET DCP packets targeted at our current MAC address.
-                        // TODO: Find out how to filter based on the ether frame type ...
                         handle.setFilter(
                             "((ether proto 0x8100) or (ether proto 0x8892)) and (ether dst " + Pcaps.toBpfString(macAddress) + ")",
                             BpfProgram.BpfCompileMode.OPTIMIZE);
 
                         PacketListener listener =
                             packet -> {
-                                if(packet instanceof EthernetPacket) {
+                                // EthernetPacket is the highest level of abstraction we can be expecting.
+                                // Everything inside this we will have to decode ourselves.
+                                if (packet instanceof EthernetPacket) {
                                     EthernetPacket ethernetPacket = (EthernetPacket) packet;
                                     boolean isPnPacket = false;
-                                    if(ethernetPacket.getPayload() instanceof Dot1qVlanTagPacket) {
+                                    // I have observed some times the ethernet packets being wrapped inside a VLAN
+                                    // Packet, in this case we simply unpack the content.
+                                    if (ethernetPacket.getPayload() instanceof Dot1qVlanTagPacket) {
                                         Dot1qVlanTagPacket vlanPacket = (Dot1qVlanTagPacket) ethernetPacket.getPayload();
-                                        if(PN_EtherType.equals(vlanPacket.getHeader().getType())) {
+                                        if (PN_EtherType.equals(vlanPacket.getHeader().getType())) {
                                             isPnPacket = true;
                                         }
-                                    } else if(PN_EtherType.equals(ethernetPacket.getHeader().getType())) {
+                                    } else if (PN_EtherType.equals(ethernetPacket.getHeader().getType())) {
                                         isPnPacket = true;
                                     }
 
                                     // It's a PROFINET packet.
-                                    if(isPnPacket) {
+                                    if (isPnPacket) {
                                         ReadBuffer reader = new ReadBufferByteBased(ethernetPacket.getRawData());
                                         try {
                                             EthernetFrame ethernetFrame = EthernetFrameIO.staticParse(reader);
-                                            String sourceMacAddress = toMacAddressString(ethernetFrame.getSource());
                                             DCP_PDU pdu;
-                                            if(ethernetFrame.getPayload() instanceof VirtualLanEthernetFramePayload) {
+                                            // Access the pdu data (either directly or by
+                                            // unpacking the content of the VLAN packet.
+                                            if (ethernetFrame.getPayload() instanceof VirtualLanEthernetFramePayload) {
                                                 VirtualLanEthernetFramePayload vlefpl = (VirtualLanEthernetFramePayload) ethernetFrame.getPayload();
                                                 pdu = ((ProfinetEthernetFramePayload) vlefpl.getPayload()).getPdu();
                                             } else {
                                                 pdu = ((ProfinetEthernetFramePayload) ethernetFrame.getPayload()).getPdu();
                                             }
-                                            if(pdu instanceof DCP_Identify_ResPDU) {
-                                                DCP_Identify_ResPDU identify_resPDU = (DCP_Identify_ResPDU) pdu;
-                                                if(!pnDevices.containsKey(sourceMacAddress)) {
-                                                    pnDevices.put(sourceMacAddress, identify_resPDU);
+                                            // Inspect the PDU itself
+                                            // (in this case we only process identify response packets)
+                                            if (pdu instanceof DCP_Identify_ResPDU) {
+                                                DCP_Identify_ResPDU identifyResPDU = (DCP_Identify_ResPDU) pdu;
+
+                                                Map<String, DCP_Block> blocks = new HashMap<>();
+                                                for (DCP_Block block : identifyResPDU.getBlocks()) {
+                                                    String blockName = block.getOption().name() + "-" + block.getSuboption().toString();
+                                                    blocks.put(blockName, block);
+                                                }
+
+                                                // The mac address of the device we found
+                                                org.pcap4j.util.MacAddress srcAddr = ethernetPacket.getHeader().getSrcAddr();
+                                                // The mac address of the local network device
+                                                org.pcap4j.util.MacAddress dstAddr = ethernetPacket.getHeader().getDstAddr();
+
+                                                String deviceTypeName = "unknown";
+                                                if (blocks.containsKey(DEVICE_TYPE_NAME)) {
+                                                    DCP_BlockDevicePropertiesDeviceVendor block = (DCP_BlockDevicePropertiesDeviceVendor) blocks.get(DEVICE_TYPE_NAME);
+                                                    deviceTypeName = new String(block.getDeviceVendorValue());
                                                 }
+                                                String deviceName = "unknown";
+                                                if (blocks.containsKey(DEVICE_NAME_OF_STATION)) {
+                                                    DCP_BlockDevicePropertiesNameOfStation block = (DCP_BlockDevicePropertiesNameOfStation) blocks.get(DEVICE_NAME_OF_STATION);
+                                                    deviceName = new String(block.getNameOfStation());
+                                                }
+
+                                                String transportUrl = srcAddr.toString();
+                                                Map<String, String> options =
+                                                    Collections.singletonMap("localMacAddress", dstAddr.toString());
+                                                String name = deviceTypeName + " - " + deviceName;
+                                                PlcDiscoveryItem value = new DefaultPlcDiscoveryItem(
+                                                    ProfinetDriver.DRIVER_CODE, RawSocketTransport.TRANSPORT_CODE,
+                                                    transportUrl, options, name);
+                                                values.add(value);
+
+                                                // If we have a discovery handler, pass it to the handler callback
+                                                if (handler != null) {
+                                                    handler.handle(value);
+                                                }
+
+                                                logger.debug("Found new device: '{}' with connection-url '{}'",
+                                                    value.getName(), value.getConnectionUrl());
                                             }
                                         } catch (ParseException e) {
-                                            e.printStackTrace();
+                                            logger.error("Got error decoding packet", e);
                                         }
                                     }
                                 }
@@ -134,70 +192,46 @@ public class ProfinetPlcDiscoverer implements PlcDiscoverer {
                         WriteBufferByteBased buffer = new WriteBufferByteBased(34);
                         EthernetFrameIO.staticSerialize(buffer, identificationRequest);
                         Packet packet = EthernetPacket.newPacket(buffer.getData(), 0, 34);
-                        sendHandle.sendPacket(packet);
+                        handle.sendPacket(packet);
                     }
                 }
             }
-        } catch (PcapNativeException | ParseException e) {
-            e.printStackTrace();
-        } catch (NotOpenException e) {
-            e.printStackTrace();
-        } catch (IllegalRawDataException e) {
-            e.printStackTrace();
-        }
-        try {
-            Thread.sleep(5000);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
+        } catch (IllegalRawDataException | NotOpenException | PcapNativeException | ParseException e) {
+            logger.error("Got an exception while processing raw socket data", e);
+            future.completeExceptionally(new PlcException("Got an internal error while performing discovery"));
+            for (PcapHandle openHandle : openHandles) {
+                openHandle.close();
+            }
+            return future;
         }
 
-        System.out.println(String.format("Found %d PROFINET devices:", pnDevices.size()));
-        for (DCP_Identify_ResPDU pnDevice : pnDevices.values()) {
-            outputPnDevice(pnDevice);
-        }
+        // Create a timer that completes the future after a given time with all the responses it found till then.
+        Timer timer = new Timer("Discovery Timeout");
+        timer.schedule(new TimerTask() {
+            public void run() {
+                PlcDiscoveryResponse response =
+                    new DefaultPlcDiscoveryResponse(discoveryRequest, PlcResponseCode.OK, values);
+                future.complete(response);
+                for (PcapHandle openHandle : openHandles) {
+                    openHandle.close();
+                }
+            }
+        }, 5000L);
 
-        return null;
+        return future;
     }
 
     private static MacAddress toPlc4xMacAddress(org.pcap4j.util.MacAddress pcap4jMacAddress) {
         byte[] address = pcap4jMacAddress.getAddress();
-        return new MacAddress(new short[]{ (short) address[0], (short) address[1], (short) address[2], (short) address[3], (short) address[4], (short) address[5]});
-    }
-
-    private static String toMacAddressString(MacAddress macAddress) {
-        return String.format("%x2:%x2:%x2:%x2:%x2:%x2", macAddress.getAddress()[0], macAddress.getAddress()[1],
-            macAddress.getAddress()[2], macAddress.getAddress()[3], macAddress.getAddress()[4], macAddress.getAddress()[5]);
-    }
-
-    private static void outputPnDevice(DCP_Identify_ResPDU pnDevice) {
-        Map<String, DCP_Block> blocks = new HashMap<>();
-        for (DCP_Block block : pnDevice.getBlocks()) {
-            String blockName = block.getOption().name() + "-" + block.getSuboption().toString();
-            blocks.put(blockName, block);
-        }
-
-        String deviceTypeName = "unknown";
-        if(blocks.containsKey(DEVICE_TYPE_NAME)) {
-            DCP_BlockDevicePropertiesDeviceVendor block = (DCP_BlockDevicePropertiesDeviceVendor) blocks.get(DEVICE_TYPE_NAME);
-            deviceTypeName = new String(block.getDeviceVendorValue());
-        }
-        String deviceName = "unknown";
-        if(blocks.containsKey(DEVICE_NAME_OF_STATION)) {
-            DCP_BlockDevicePropertiesNameOfStation block = (DCP_BlockDevicePropertiesNameOfStation) blocks.get(DEVICE_NAME_OF_STATION);
-            deviceName = new String(block.getNameOfStation());
-        }
-        String ipAddress = "unknown";
-        if(blocks.containsKey(IP_OPTION_IP)) {
-            DCP_BlockIpIpParameter block = (DCP_BlockIpIpParameter) blocks.get(IP_OPTION_IP);
-            ipAddress = String.format("%d.%d.%d.%d", block.getIpAddress()[0], block.getIpAddress()[1], block.getIpAddress()[2], block.getIpAddress()[3]);
-        }
-        System.out.println(String.format("Found '%s' with name '%s' on IP: %s%n\t%s", deviceTypeName, deviceName, ipAddress, pnDevice));
+        return new MacAddress(new short[]{address[0], address[1], address[2], address[3], address[4], address[5]});
     }
 
     private static class Task implements Runnable {
 
-        private PcapHandle handle;
-        private PacketListener listener;
+        private final Logger logger = LoggerFactory.getLogger(Task.class);
+
+        private final PcapHandle handle;
+        private final PacketListener listener;
 
         public Task(PcapHandle handle, PacketListener listener) {
             this.handle = handle;
@@ -208,12 +242,11 @@ public class ProfinetPlcDiscoverer implements PlcDiscoverer {
         public void run() {
             try {
                 handle.loop(10, listener);
-            } catch (PcapNativeException e) {
-                e.printStackTrace();
             } catch (InterruptedException e) {
-                e.printStackTrace();
-            } catch (NotOpenException e) {
-                e.printStackTrace();
+                logger.error("Got error handling raw socket", e);
+                Thread.currentThread().interrupt();
+            } catch (PcapNativeException | NotOpenException e) {
+                logger.error("Got error handling raw socket", e);
             }
         }
     }
diff --git a/plc4j/examples/hello-discovery/src/main/java/org/apache/plc4x/java/examples/hellodiscovery/HelloDiscovery.java b/plc4j/examples/hello-discovery/src/main/java/org/apache/plc4x/java/examples/hellodiscovery/HelloDiscovery.java
index c2cfdb4..8003c28 100644
--- a/plc4j/examples/hello-discovery/src/main/java/org/apache/plc4x/java/examples/hellodiscovery/HelloDiscovery.java
+++ b/plc4j/examples/hello-discovery/src/main/java/org/apache/plc4x/java/examples/hellodiscovery/HelloDiscovery.java
@@ -19,19 +19,13 @@ under the License.
 package org.apache.plc4x.java.examples.hellodiscovery;
 
 import org.apache.plc4x.java.PlcDriverManager;
-import org.apache.plc4x.java.api.PlcConnection;
 import org.apache.plc4x.java.api.PlcDriver;
-import org.apache.plc4x.java.api.messages.PlcDiscoveryRequest;
-import org.apache.plc4x.java.api.messages.PlcDiscoveryResponse;
-import org.apache.plc4x.java.api.messages.PlcReadRequest;
-import org.apache.plc4x.java.api.messages.PlcReadResponse;
+import org.apache.plc4x.java.api.messages.*;
 import org.apache.plc4x.java.api.types.PlcResponseCode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
 
 public class HelloDiscovery {
 
@@ -53,18 +47,26 @@ public class HelloDiscovery {
         PlcDriverManager plcDriverManager = new PlcDriverManager();
         Set<String> driverCodes = plcDriverManager.listDrivers();
         for (String driverCode : driverCodes) {
-            logger.info(String.format("Executing Discovery for Driver: %s", driverCode));
+            logger.info("Executing Discovery for Driver: {}", driverCode);
             PlcDriver driver = plcDriverManager.getDriver(driverCode);
 
             // Check if this driver supports discovery.
             if(driver.getMetadata().canDiscover()) {
                 PlcDiscoveryRequest discoveryRequest = driver.discoveryRequestBuilder().build();
-                discoveryRequest.execute();
+                PlcDiscoveryResponse discoveryResponse = discoveryRequest.executeWithHandler(
+                    discoveryItem -> logger.info("Intercepted discovery of device with name: {} with connection url: {}",
+                        discoveryItem.getName(), discoveryItem.getConnectionUrl())).get();
+                if(discoveryResponse.getResponseCode() == PlcResponseCode.OK) {
+                    logger.info("Discovery finished successfully:");
+                    for (PlcDiscoveryItem discoveryItem : discoveryResponse.getValues()) {
+                        logger.info("Found device with name: {} with connection url: {}",
+                            discoveryItem.getName(), discoveryItem.getConnectionUrl());
+                    }
+                }
             } else {
                 logger.info("This driver doesn't support discovery");
             }
         }
-
     }
 
 }
diff --git a/plc4j/examples/hello-discovery/src/main/resources/logback.xml b/plc4j/examples/hello-discovery/src/main/resources/logback.xml
index a8ddebb..27d40c0 100644
--- a/plc4j/examples/hello-discovery/src/main/resources/logback.xml
+++ b/plc4j/examples/hello-discovery/src/main/resources/logback.xml
@@ -29,7 +29,7 @@
     </encoder>
   </appender>
 
-  <root level="trace">
+  <root level="info">
     <appender-ref ref="STDOUT" />
   </root>
 
diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcDiscoveryItem.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcDiscoveryItem.java
index ac1fbdf..db1174f 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcDiscoveryItem.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcDiscoveryItem.java
@@ -35,14 +35,14 @@ public class DefaultPlcDiscoveryItem implements PlcDiscoveryItem, Serializable {
 
     private final String protocolCode;
     private final String transportCode;
-    private final URL transportUrl;
+    private final String transportUrl;
     private final Map<String, String> options;
     private final String name;
 
     @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
     public DefaultPlcDiscoveryItem(@JsonProperty("protocolCode") String protocolCode,
                                    @JsonProperty("transportCode") String transportCode,
-                                   @JsonProperty("transportUrl") URL transportUrl,
+                                   @JsonProperty("transportUrl") String transportUrl,
                                    @JsonProperty("options") Map<String, String> options,
                                    @JsonProperty("name") String name) {
         this.protocolCode = protocolCode;
@@ -63,7 +63,7 @@ public class DefaultPlcDiscoveryItem implements PlcDiscoveryItem, Serializable {
     }
 
     @Override
-    public URL getTransportUrl() {
+    public String getTransportUrl() {
         return transportUrl;
     }
 
@@ -85,6 +85,7 @@ public class DefaultPlcDiscoveryItem implements PlcDiscoveryItem, Serializable {
             boolean first = true;
             for (Map.Entry<String, String> optionEntry : options.entrySet()) {
                 if(first) {
+                    sb.append("?");
                     first = false;
                 } else {
                     sb.append("&");
diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcDiscoveryRequest.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcDiscoveryRequest.java
index 79334cc..1cefe38 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcDiscoveryRequest.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcDiscoveryRequest.java
@@ -42,6 +42,11 @@ public class DefaultPlcDiscoveryRequest implements PlcDiscoveryRequest, Serializ
     }
 
     @Override
+    public CompletableFuture<? extends PlcDiscoveryResponse> executeWithHandler(PlcDiscoveryItemHandler handler) {
+        return discoverer.discoverWithHandler(this, handler);
+    }
+
+    @Override
     public void serialize(WriteBuffer writeBuffer) throws ParseException {
         writeBuffer.pushContext("PlcDiscoveryRequest");
 
diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcDiscoveryResponse.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcDiscoveryResponse.java
index c219ff9..b3f5022 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcDiscoveryResponse.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcDiscoveryResponse.java
@@ -20,6 +20,7 @@ package org.apache.plc4x.java.spi.messages;
 
 import com.fasterxml.jackson.annotation.*;
 import org.apache.plc4x.java.api.messages.*;
+import org.apache.plc4x.java.api.types.PlcResponseCode;
 import org.apache.plc4x.java.spi.generation.ParseException;
 import org.apache.plc4x.java.spi.generation.WriteBuffer;
 import org.apache.plc4x.java.spi.utils.Serializable;
@@ -30,12 +31,15 @@ import java.util.*;
 public class DefaultPlcDiscoveryResponse implements PlcDiscoveryResponse, Serializable {
 
     private final PlcDiscoveryRequest request;
+    private final PlcResponseCode responseCode;
     private final List<PlcDiscoveryItem> values;
 
     @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
     public DefaultPlcDiscoveryResponse(@JsonProperty("request") PlcDiscoveryRequest request,
+                                       @JsonProperty("responseCode") PlcResponseCode responseCode,
                                        @JsonProperty("values") List<PlcDiscoveryItem> values) {
         this.request = request;
+        this.responseCode = responseCode;
         this.values = values;
     }
 
@@ -44,6 +48,10 @@ public class DefaultPlcDiscoveryResponse implements PlcDiscoveryResponse, Serial
         return request;
     }
 
+    public PlcResponseCode getResponseCode() {
+        return responseCode;
+    }
+
     public List<PlcDiscoveryItem> getValues() {
         return values;
     }
diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/PlcDiscoverer.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/PlcDiscoverer.java
index 28ace34..a7ca16a 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/PlcDiscoverer.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/PlcDiscoverer.java
@@ -18,6 +18,7 @@
  */
 package org.apache.plc4x.java.spi.messages;
 
+import org.apache.plc4x.java.api.messages.PlcDiscoveryItemHandler;
 import org.apache.plc4x.java.api.messages.PlcDiscoveryRequest;
 import org.apache.plc4x.java.api.messages.PlcDiscoveryResponse;
 
@@ -36,4 +37,6 @@ public interface PlcDiscoverer {
      */
     CompletableFuture<PlcDiscoveryResponse> discover(PlcDiscoveryRequest discoveryRequest);
 
+    CompletableFuture<PlcDiscoveryResponse> discoverWithHandler(PlcDiscoveryRequest discoveryRequest, PlcDiscoveryItemHandler handler);
+
 }
diff --git a/plc4j/transports/raw-socket/src/main/java/org/apache/plc4x/java/transport/rawsocket/RawSocketTransport.java b/plc4j/transports/raw-socket/src/main/java/org/apache/plc4x/java/transport/rawsocket/RawSocketTransport.java
index 5153d7a..84ccb93 100644
--- a/plc4j/transports/raw-socket/src/main/java/org/apache/plc4x/java/transport/rawsocket/RawSocketTransport.java
+++ b/plc4j/transports/raw-socket/src/main/java/org/apache/plc4x/java/transport/rawsocket/RawSocketTransport.java
@@ -24,9 +24,11 @@ import org.apache.plc4x.java.utils.rawsockets.netty.address.RawSocketAddress;
 
 public class RawSocketTransport implements Transport {
 
+    public static final String TRANSPORT_CODE = "raw";
+
     @Override
     public String getTransportCode() {
-        return "raw";
+        return TRANSPORT_CODE;
     }
 
     @Override