diff --git a/plc4j/drivers/profinet/pom.xml b/plc4j/drivers/profinet/pom.xml index 7f0f599dbca..62e21dedaa1 100644 --- a/plc4j/drivers/profinet/pom.xml +++ b/plc4j/drivers/profinet/pom.xml @@ -141,6 +141,15 @@ jackson-dataformat-xml + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + org.apache.plc4x plc4j-utils-test-utils @@ -155,12 +164,9 @@ - org.slf4j - slf4j-api - - - ch.qos.logback - logback-classic + org.pcap4j + pcap4j-packetfactory-static + test diff --git a/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/device/ProfinetDevice.java b/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/device/ProfinetDevice.java index d644fb0b0ee..4c000a3a27e 100644 --- a/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/device/ProfinetDevice.java +++ b/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/device/ProfinetDevice.java @@ -192,13 +192,14 @@ public boolean onConnect() throws ExecutionException, InterruptedException, Time */ public void start() { final long timeout = (long) deviceContext.getConfiguration().getReductionRatio() * deviceContext.getConfiguration().getSendClockFactor() * deviceContext.getConfiguration().getWatchdogFactor() * MIN_CYCLE_NANO_SEC; - final int cycleTime = (int) ((deviceContext.getConfiguration().getSendClockFactor() * deviceContext.getConfiguration().getReductionRatio() * MIN_CYCLE_NANO_SEC) / 1000000); + final int cycleTime = (deviceContext.getConfiguration().getSendClockFactor() * deviceContext.getConfiguration().getReductionRatio() * MIN_CYCLE_NANO_SEC) / 1000000; Function subscription = message -> { long startTime = System.nanoTime(); ScheduledExecutorService ses = Executors.newScheduledThreadPool(1); ses.scheduleAtFixedRate(() -> { try { +// System.out.println("State: " + deviceContext.getState().name() + " queue length: " + deviceContext.getQueue().size()); switch (deviceContext.getState()) { // If an ipAddress is specified in the device config, we use PN DCP to set the IP // address of the PN device identified by the name to that given IP address. @@ -214,8 +215,9 @@ public void start() { // Send the packet and process the response ... recordIdAndSend(createConnection, deviceContext.getSourcePort(), deviceContext.getDestinationPort()); - // Wait for it to be finished processing ... - createConnection.getResponseHandled().get(timeout, TimeUnit.NANOSECONDS); + // For some reason the first response quite often came in too late, + // so we're extending the wait time here. + createConnection.getResponseHandled().get(8 * timeout, TimeUnit.NANOSECONDS); break; // TODO: It seems this state is never used? // It seems that in this step we would be setting parameters in the PN device (hereby configuring it) @@ -231,21 +233,31 @@ public void start() { recordIdAndSend(writeParametersEnd, deviceContext.getSourcePort(), deviceContext.getDestinationPort()); writeParametersEnd.getResponseHandled().get(timeout, TimeUnit.NANOSECONDS); break; + // Here we're waiting for an incoming application-ready request from the device. case WAITAPPLRDY: break; + // Here we've received the application-ready request from the device and simply acknowledge + // it, which finishes the connection setup. case APPLRDY: ApplicationReadyResponse applicationReadyResponse = new ApplicationReadyResponse(deviceContext.getActivityUuid(), deviceContext.getSequenceNumber()); send(applicationReadyResponse, ProfinetDeviceContext.DEFAULT_UDP_PORT, deviceContext.getApplicationResponseDestinationPort()); deviceContext.getContext().fireConnected(); deviceContext.setState(ProfinetDeviceState.CYCLICDATA); break; + // In this state we're receiving data from the remote device and in this part of the + // code, we're sending back our data in every cycle. + // TODO: Possibly check if, depending on the reduction ratio, we only have to send back data every few cycles. case CYCLICDATA: CyclicData cyclicData = new CyclicData(startTime); messageWrapper.sendPnioMessage(cyclicData, deviceContext); + // TODO: Check if we're getting data every cycle ... if not, react. break; + case ABORT: + // TODO: Handle this } } catch (InterruptedException | ExecutionException | TimeoutException e) { deviceContext.setState(ProfinetDeviceState.ABORT); + logger.warn("Got exception", e); } }, 0, cycleTime, TimeUnit.MILLISECONDS); return null; diff --git a/plc4j/drivers/profinet/src/test/java/org/apache/plc4x/java/profinet/ManualProfinetIoTest.java b/plc4j/drivers/profinet/src/test/java/org/apache/plc4x/java/profinet/ManualProfinetIoTest.java index 26bc1982b89..eac8db3f345 100644 --- a/plc4j/drivers/profinet/src/test/java/org/apache/plc4x/java/profinet/ManualProfinetIoTest.java +++ b/plc4j/drivers/profinet/src/test/java/org/apache/plc4x/java/profinet/ManualProfinetIoTest.java @@ -36,11 +36,14 @@ public class ManualProfinetIoTest { public static void main(String[] args) throws Exception { // eth.addr == 88:3f:99:03:ef:b0 + // Zylk device name = simocodexbpn156e + // Chris device name = cdxb195b3 + // In this example 192.168.54.2 is the local IP of the computer running PLC4J and 192.168.54.23 is the IP of the PN device. //final PlcConnection connection = new DefaultPlcDriverManager().getConnection("profinet://192.168.54.2?gsddirectory=~/.gsd&devices=[[simocodexbpn156e,DAP%201,(1,),192.168.54.23]]&reductionratio=16&sendclockfactor=32&dataholdfactor=3&watchdogfactor=3"); // REMARK: The driver would use the local network device with the given IP address and to an auto-discovery, trying to find any devices returned with the matching name. // If this device is then found and an IP address is provided, it would use PN-DCP to set the IP address of that device to the given value. - final PlcConnection connection = new DefaultPlcDriverManager().getConnection("profinet://192.168.54.220?gsddirectory=~/.gsd&devices=[[simocodexbpn156e,DAP%201,(1,)]]&reductionratio=16&sendclockfactor=32&dataholdfactor=3&watchdogfactor=3"); + final PlcConnection connection = new DefaultPlcDriverManager().getConnection("profinet://192.168.54.220?gsddirectory=~/.gsd&devices=[[simocodexbpn156e,DAP%201,(1,)]]&reductionratio=128&sendclockfactor=128&dataholdfactor=3&watchdogfactor=3"); PlcBrowseRequest browseRequest = connection.browseRequestBuilder().addQuery("all", "*").build(); PlcBrowseResponse plcBrowseResponse = browseRequest.execute().get(4000, TimeUnit.MILLISECONDS); @@ -55,7 +58,7 @@ public static void main(String[] args) throws Exception { // - Adam Analog Input: eth.addr == 74fe4863f6c2 // - Adam Digital I/O: eth.addr == 74fe48824a7c PlcSubscriptionRequest.Builder builder = connection.subscriptionRequestBuilder(); - builder.addChangeOfStateTag("Input 4", ProfinetTag.of("cdxb195b3.1.1.Inputs.2:BOOL")); + builder.addChangeOfStateTag("Input 4", ProfinetTag.of("simocodexbpn156e.1.1.Inputs.2:BOOL")); PlcSubscriptionRequest request = builder.build(); final PlcSubscriptionResponse response = request.execute().get(); diff --git a/plc4j/drivers/profinet/src/test/java/org/apache/plc4x/java/profinet/ManualProfinetPcapTest.java b/plc4j/drivers/profinet/src/test/java/org/apache/plc4x/java/profinet/ManualProfinetPcapTest.java new file mode 100644 index 00000000000..684c11ffd09 --- /dev/null +++ b/plc4j/drivers/profinet/src/test/java/org/apache/plc4x/java/profinet/ManualProfinetPcapTest.java @@ -0,0 +1,96 @@ +/* + * 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.profinet; + +import org.apache.plc4x.java.profinet.readwrite.PnDcp_Pdu; +import org.apache.plc4x.java.profinet.readwrite.PnDcp_Pdu_RealTimeCyclic; +import org.apache.plc4x.java.spi.generation.ReadBufferByteBased; +import org.pcap4j.core.PcapHandle; +import org.pcap4j.core.Pcaps; +import org.pcap4j.packet.Dot1qVlanTagPacket; +import org.pcap4j.packet.EthernetPacket; +import org.pcap4j.packet.Packet; +import org.pcap4j.packet.UnknownPacket; + +import java.io.EOFException; +import java.util.Arrays; +import java.util.concurrent.TimeoutException; + +public class ManualProfinetPcapTest { + + public static void main(String[] args) throws Exception { + try (PcapHandle handle = Pcaps.openOffline("/Users/cdutz/Projects/Apache/PLC4X/profinet-slow.pcapng", PcapHandle.TimestampPrecision.NANO);){ + int lastIncomingCycleTime = 0; + int lastOutgoingCycleTime = 0; + int minDelay = 65000; + int maxDelay = 0; + while (true) { + try { + Packet packet = handle.getNextPacketEx(); + EthernetPacket.EthernetHeader packetHeader = (EthernetPacket.EthernetHeader) packet.getHeader(); + boolean fromDevice = Arrays.equals(new byte[]{(byte) 0xF8, (byte) 0xE4, (byte) 0x3B, (byte) 0xB6, (byte) 0x9B, (byte) 0xBF}, packetHeader.getSrcAddr().getAddress()); + + // All packets from the device have VLan wrapped around the packet. + if(fromDevice) { + if(packet.getPayload() instanceof Dot1qVlanTagPacket) { + Dot1qVlanTagPacket vlanTagPacket = (Dot1qVlanTagPacket) packet.getPayload(); + UnknownPacket payload = (UnknownPacket) vlanTagPacket.getPayload(); + ReadBufferByteBased readBuffer = new ReadBufferByteBased(payload.getRawData()); + PnDcp_Pdu pnDcpPdu = PnDcp_Pdu.staticParse(readBuffer); + if(pnDcpPdu instanceof PnDcp_Pdu_RealTimeCyclic) { + PnDcp_Pdu_RealTimeCyclic pnDcpPdu1 = (PnDcp_Pdu_RealTimeCyclic) pnDcpPdu; + lastIncomingCycleTime = pnDcpPdu1.getCycleCounter(); + //System.out.printf("--> %d\n", pnDcpPdu1.getCycleCounter()); + } + } else { + System.out.println("Other packet"); + } + } else if (packet.getPayload() instanceof UnknownPacket) { + UnknownPacket payload = (UnknownPacket) packet.getPayload(); + ReadBufferByteBased readBuffer = new ReadBufferByteBased(payload.getRawData()); + PnDcp_Pdu pnDcpPdu = PnDcp_Pdu.staticParse(readBuffer); + if(pnDcpPdu instanceof PnDcp_Pdu_RealTimeCyclic) { + PnDcp_Pdu_RealTimeCyclic pnDcpPdu1 = (PnDcp_Pdu_RealTimeCyclic) pnDcpPdu; + int difference = (pnDcpPdu1.getCycleCounter() < lastIncomingCycleTime) ? lastIncomingCycleTime - pnDcpPdu1.getCycleCounter() : pnDcpPdu1.getCycleCounter() - lastIncomingCycleTime; + if(difference < 60000 && difference > 100) { + if (difference < minDelay) { + minDelay = difference; + } + if (difference > maxDelay) { + maxDelay = difference; + } + System.out.printf("<-- %10d %10d %10d-%10d\n", difference, pnDcpPdu1.getCycleCounter() - lastOutgoingCycleTime, minDelay, maxDelay); + } + lastOutgoingCycleTime = pnDcpPdu1.getCycleCounter(); + } + } else { + System.out.println("Other packet"); + } + } catch (TimeoutException e) { + System.out.println("TIMEOUT"); + } catch (EOFException e) { + System.out.println("EOF"); + break; + } + } + } + } + +}