diff --git a/src/edu/nps/moves/dis7/examples/AlphabeticalPduSender.java b/src/edu/nps/moves/dis7/examples/AlphabeticalPduSender.java index c7ad7ce61bf2e569148c5d21c6f47acbb07e0a53..3dd2989c1ef6629b8755f62158b86d6febdd703e 100644 --- a/src/edu/nps/moves/dis7/examples/AlphabeticalPduSender.java +++ b/src/edu/nps/moves/dis7/examples/AlphabeticalPduSender.java @@ -1,7 +1,7 @@ package edu.nps.moves.dis7.examples; import edu.nps.moves.dis7.pdus.*; -import edu.nps.moves.dis7.enumerations.DISPDUType; +import edu.nps.moves.dis7.enumerations.DisPduType; import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface; import java.io.ByteArrayOutputStream; @@ -57,7 +57,7 @@ public class AlphabeticalPduSender // Loop through all the enumerated PDU types, create a PDU for each type, // and add that PDU to a list. - for (DISPDUType pdu : DISPDUType.values()) { // results are in alphabetic, not numeric order + for (DisPduType pdu : DisPduType.values()) { // results are in alphabetic, not numeric order Pdu aPdu = null; diff --git a/src/edu/nps/moves/dis7/examples/EntityStateEntityIdExampleUse.java b/src/edu/nps/moves/dis7/examples/EntityStateEntityIdExampleUse.java index df998e64a1661ea5e6a290e7b4b917462a77cc65..c26806478a9f506dc0ca50aacfcdbb87defa063c 100644 --- a/src/edu/nps/moves/dis7/examples/EntityStateEntityIdExampleUse.java +++ b/src/edu/nps/moves/dis7/examples/EntityStateEntityIdExampleUse.java @@ -8,7 +8,7 @@ package edu.nps.moves.dis7.examples; import edu.nps.moves.dis7.pdus.EntityStatePdu; import edu.nps.moves.dis7.pdus.Pdu; import edu.nps.moves.dis7.utilities.PduFactory; -import edu.nps.moves.dis7.enumerations.DISPDUType; +import edu.nps.moves.dis7.enumerations.DisPduType; import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface; import java.io.IOException; import edu.nps.moves.dis7.entities.usa.platform.surface.*; @@ -100,7 +100,7 @@ public class EntityStateEntityIdExampleUse /* Do the same for the second way of creating a Shenandoah entity type and show an alternate way of creating an ESPDU */ - espdu = (EntityStatePdu)pduFactory.createPdu(DISPDUType.ENTITY_STATE); + espdu = (EntityStatePdu)pduFactory.createPdu(DisPduType.ENTITY_STATE); /* set desired entity state fields here */ AD44Shenandoah entityType2 = new AD44Shenandoah(); // edu.nps.moves.dis7.entities.usa.platform.surface diff --git a/src/edu/nps/moves/dis7/utilities/DisThreadedNetworkInterface.java b/src/edu/nps/moves/dis7/utilities/DisThreadedNetworkInterface.java index eb01b41d8ad0a97dad05268edff989ff8cd1cc31..56930ceff82d17af3054e9b5846f14633b4c283c 100644 --- a/src/edu/nps/moves/dis7/utilities/DisThreadedNetworkInterface.java +++ b/src/edu/nps/moves/dis7/utilities/DisThreadedNetworkInterface.java @@ -1,525 +1,525 @@ -/** - * Copyright (c) 2008-2021, MOVES Institute, Naval Postgraduate School (NPS). All rights reserved. - * This work is provided under a BSD open-source license, see project license.html and license.txt - */ -package edu.nps.moves.dis7.utilities; - -import edu.nps.moves.dis7.pdus.Pdu; -import edu.nps.moves.dis7.pdus.DisTime; -import edu.nps.moves.dis7.enumerations.DISPDUType; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -import java.net.*; - -import java.nio.ByteBuffer; - -import java.util.*; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * This is a thread-safe, multicast DIS network interface class. - * - * @author Mike Bailey, jmbailey@nps.edu - * @since Jul 29, 2019 - */ -public class DisThreadedNetworkInterface -{ - /** Default value */ - public static String DEFAULT_MULTICAST_ADDRESS = "225.4.5.6"; - - /** Default value */ - public static int DEFAULT_DIS_PORT = 3000; - - private static final String TRACE_PREFIX = "[" + DisThreadedNetworkInterface.class.getName() + "] "; - private boolean verbose = true; - private boolean verboseIncludesTimestamp = false; - - /** - * Pdu listener class and interface - */ - public interface PduListener - { - /** - * Callback method - * @param pdu received pdu - */ - void incomingPdu(Pdu pdu); - } - - /** - * Raw pdu listener class and interface - */ - public interface RawPduListener - { - /** - * Callback method - * @param bAndL exposed buffer to receive incoming pdu - */ - void incomingPdu(ByteArrayBufferAndLength bAndL); - } - - /** - * Stores data for further processing - */ - public class ByteArrayBufferAndLength - { - /** - * Active ByteArray buffer - */ - public byte[] bufferByteArray; - /** - * Active ByteArray buffer length - */ - public int length; - - /** - * Default constructor for data storage - * - * @param bufferByteArray the data buffer to store - * @param length the length of the data buffer - */ - public ByteArrayBufferAndLength(byte[] bufferByteArray, int length) - { - this.bufferByteArray = bufferByteArray; - this.length = length; - } - } - - /** ********** Begin class ************** */ - /** - * MTU 8192: TODO this has actually been superseded by a larger buffer size, - * but good enough for now - */ - public static final int MAX_DIS_PDU_SIZE = 8192; - - /** - * MTU 1500: size of an Ethernet frame, common value to avoid packet - * segmentation - */ - public static final int MAX_TRANSMISSION_UNIT_SIZE = 1500; - - private int disPort; - private String multicastAddress; - private boolean killed = false; - - private InetAddress inetAddress; - private InetSocketAddress inetSocket; - private NetworkInterface networkInterface; - private DatagramSocket datagramSocket = null; - - /** - * Default constructor using default port and multicast address - */ - public DisThreadedNetworkInterface() - { - this(DEFAULT_MULTICAST_ADDRESS, DEFAULT_DIS_PORT); - } - - /** - * Constructor - * @param multicastGroup the multicast group address to utilize - * @param port the multicast port to utilize - */ - public DisThreadedNetworkInterface(String multicastGroup, int port) - { - disPort = port; - multicastAddress = multicastGroup; - try - { - inetAddress = InetAddress.getByName(multicastAddress); - } - catch (UnknownHostException ex) - { - Logger.getLogger(DisThreadedNetworkInterface.class.getName()).log(Level.SEVERE, null, ex); - } - inetSocket = new InetSocketAddress(inetAddress, disPort); - networkInterface = findIpv4Interface(); - init(); - } - - /* *********** queues and lists and public methods ************** */ - private final List<PduListener> everyTypeListeners = new ArrayList<>(); - private final Map<DISPDUType, List<PduListener>> typeListeners = new HashMap<>(); - private final List<RawPduListener> rawListeners = new ArrayList<>(); - private final LinkedBlockingQueue<Pdu> pdus2send = new LinkedBlockingQueue<>(); - - /** - * Add a listener to accept only pdus of a given type - * @param newListener listener instance implementing the RawPduListener interface - * @param disPduType Pdu type - */ - public void addListener(PduListener newListener, DISPDUType disPduType) - { - if (disPduType == null) - { - addListener(newListener); - } - else - { - List<PduListener> arLis = typeListeners.get(disPduType); - if (arLis == null) - { - arLis = new ArrayList<>(); - typeListeners.put(disPduType, arLis); - } - arLis.add(newListener); - } - } - - /** - * Add a listener to accept all pdu types - * @param newListener listener instance implementing the RawPduListener interface - */ - public void addListener(PduListener newListener) - { - everyTypeListeners.add(newListener); - } - - /** - * Remove previously added listener - * - * @param priorListener listener instance implementing the RawPduListener interface - */ - public void removeListener(PduListener priorListener) - { - everyTypeListeners.remove(priorListener); - - typeListeners.entrySet().forEach(entry -> - { - List<PduListener> arLis = entry.getValue(); - if (arLis.contains(priorListener)) - { - arLis.remove(priorListener); - } - }); - // additional sleep, hopefully allowing teardown to proceed to completion - sleep(100l); // TODO needed? - } - - /** - * Add a listener to accept pdus of all types in the form of a byte array - * - * @param lis listener instance implementing the RawPduListener interface - */ - public void addRawListener(RawPduListener lis) - { - rawListeners.add(lis); - } - - /** - * Remove previously added raw listener - * - * @param lis listener instance implementing the RawPduListener interface - */ - public void removeRawListener(RawPduListener lis) - { - rawListeners.remove(lis); - // additional sleep, hopefully allowing teardown to proceed to completion - sleep(100l); // TODO needed? - } - - /** - * Get current port value - * @return current port value - */ - public int getDisPort() - { - return disPort; - } - - /** - * Get current multicast address value - * @return current multicast address value - */ - public String getMulticastGroup() - { - return multicastAddress; - } - - /** - * Send the given pdu to the network using the IP address and port given to the constructor - * @param pdu the pdu to send - */ - public void send(Pdu pdu) - { - pdus2send.add(pdu); - } - - /* *************** networking i/o ************* */ - private PduFactory pduFactory = new PduFactory(); - - private Thread sender; - private Thread receiver; - - /** Initialization */ - private void init() - { - createDatagramSocket(); // common asset, synchronized to prevent interleaved reentry - - receiver = new Thread(receiveThread, TRACE_PREFIX + " receive thread"); - receiver.setDaemon(true); - receiver.setPriority(Thread.NORM_PRIORITY); - receiver.start(); - - sender = new Thread(sendThread, TRACE_PREFIX + " send thread"); - sender.setDaemon(true); - sender.setPriority(Thread.NORM_PRIORITY); - sender.start(); - } - - /** - * Create datagram socket if not already available; can also be invoked by - * either sender or receiver thread to ensure datagram socket is open. - * Synchronized method to prevent interleaved reentry. - * @see <a href="https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html">Java Tutorials: Synchronized Methods</a> - */ - private synchronized void createDatagramSocket() - { - boolean closedSocket = false; - if ((datagramSocket != null) && datagramSocket.isClosed()) - { - closedSocket = true; - System.err.println(" *** " + TRACE_PREFIX + "datagramSocket.isClosed() unexpectedly, retrying..."); - } - if ((datagramSocket == null) || closedSocket) - { - try - { - // The initial value of the SO_BROADCAST socket option is FALSE - datagramSocket = new MulticastSocket(getDisPort()); - ((MulticastSocket) datagramSocket).joinGroup(inetSocket, networkInterface); - } - catch (IOException ex) - { - System.err.println(" *** " + TRACE_PREFIX + "Exception in DisThreadedNetworkInterface receive thread: " + ex.getLocalizedMessage()); - } - } - } - - private Runnable receiveThread = () -> { - - int counter = 0; - - // The capacity could go up to MAX_DIS_PDU_SIZE, but this should be good for now - // The raw listeners will strip off any extra padding and process what is - // required - ByteBuffer byteBuffer = ByteBuffer.allocate(MAX_TRANSMISSION_UNIT_SIZE); - DatagramPacket packet = new DatagramPacket(byteBuffer.array(), byteBuffer.capacity()); - Pdu pdu; - - while (!killed) { // keep trying on error - - // If something trips up with the socket, this thread will attempt to - // re-establish for both send/receive threads - try { - createDatagramSocket (); // ensure socket open, recreate if needed, other thread may occur first - - while (!killed) - { - datagramSocket.receive(packet); // blocks here waiting for next DIS pdu to be received on multicast IP and specified port - toRawListeners(packet.getData(), packet.getLength()); - - pdu = pduFactory.createPdu(byteBuffer); - - if (pdu != null) - { - counter++; // TODO experimental, add to generator as a commented-out diagnostic; consider adding diagnostic mode - if (isVerbose()) - { - String message = TRACE_PREFIX + counter + ". received " + pdu.getPduType().toString(); - if (isVerboseIncludesTimestamp()) - message += " (timestamp " + DisTime.timeStampToString(pdu.getTimestamp()); - message +=", size " + pdu.getMarshalledSize() + " bytes)"; - System.out.println(message); - System.out.flush(); - } - toListeners(pdu); - } - byteBuffer.clear(); - } - } - catch (IOException ex) { - System.err.println(TRACE_PREFIX + "Exception in DisThreadedNetworkInterface receive thread: " + ex.getLocalizedMessage()); - System.err.println(TRACE_PREFIX + "Retrying new socket..."); - } - finally - { - if (datagramSocket != null && !datagramSocket.isClosed()) { - try { - ((MulticastSocket)datagramSocket).leaveGroup(inetSocket, networkInterface); - } catch (IOException ex) { - Logger.getLogger(DisThreadedNetworkInterface.class.getName()).log(Level.SEVERE, null, ex); - } - datagramSocket.close(); - sleep(100l); // TODO needed? - datagramSocket = null; - } - } -// if (!killed) -// sleep(250); - } - }; - - private final Runnable sendThread = () -> { - - Pdu pdu; - - // The capacity could go up to MAX_DIS_PDU_SIZE, but this should be good for now - ByteArrayOutputStream baos = new ByteArrayOutputStream(MAX_TRANSMISSION_UNIT_SIZE); - DataOutputStream dos = new DataOutputStream(baos); - DatagramPacket packet = new DatagramPacket(baos.toByteArray(), baos.size(), inetSocket); - - while (!killed) { // keep trying on error - - // If something trips up with the socket, this thread will attempt to - // re-establish for both send/receive threads - try { - createDatagramSocket (); // ensure socket open, recreate if needed, other thread may occur first - - while (!killed) { - pdu = pdus2send.take(); - - pdu.marshal(dos); - packet.setData(baos.toByteArray()); - datagramSocket.send(packet); - - dos.flush(); // immediately force pdu write - baos.reset(); - } - } - catch (Exception ex) - { - System.err.println(TRACE_PREFIX + "Exception in DisThreadedNetworkInterface send thread: " + ex.getLocalizedMessage()); - } - } - try { - dos.close(); - } catch (IOException e) {} - }; - - private void toListeners(Pdu pdu) { - if (everyTypeListeners.isEmpty()) { - return; - } - - if (pdu != null) { - everyTypeListeners.forEach(lis -> lis.incomingPdu(pdu)); - - if (typeListeners.isEmpty()) { - return; - } - - List<PduListener> arLis = typeListeners.get(pdu.getPduType()); - if (arLis != null) { - arLis.forEach(lis -> lis.incomingPdu(pdu)); - } - } - } - - private void toRawListeners(byte[] data, int len) - { - if(rawListeners.isEmpty()) - return; - - ByteArrayBufferAndLength bl = new ByteArrayBufferAndLength(data, len); - rawListeners.forEach(lis->lis.incomingPdu(bl)); - } - - /** Terminate the instance */ - public void kill() - { - killed = true; // set loop sentinel for threads - } - - /** Thread sleep for indicated interval - * @param duration milliseconds */ - private void sleep(long duration) - { - try { - Thread.sleep(duration); - } - catch (InterruptedException ie) - { - System.err.flush(); - System.err.println ("*** " + getClass().getName() + ".sleep(" + duration + ") failed to sleep"); - ie.printStackTrace(System.err); - } - } - - /** - * Find proper IPV4 interface - * - * @return a network interface to use to join a multicast group - */ - public static NetworkInterface findIpv4Interface() { - try { - Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces(); - NetworkInterface nif; - Enumeration<InetAddress> addresses; - InetAddress addr; - - while (ifaces != null && ifaces.hasMoreElements()) { - nif = ifaces.nextElement(); - if (nif.isUp()) { - addresses = nif.getInetAddresses(); - while (addresses.hasMoreElements()) { - addr = addresses.nextElement(); - if (addr instanceof Inet4Address && !addr.isLoopbackAddress() && !addr.isLinkLocalAddress()) - { - System.out.println(TRACE_PREFIX + " Using network interface " + nif.getDisplayName()); - return nif; - } - } - } - } - } catch (SocketException ex) { - Logger.getLogger(DisThreadedNetworkInterface.class.getName()).log(Level.SEVERE, null, ex); - } - return null; - } - - /** - * @return the verbose - */ - public boolean isVerbose() - { - return verbose; - } - - /** - * @param verbose the verbose to set - */ - public void setVerbose(boolean verbose) - { - this.verbose = verbose; - } - - /** - * @return the verboseIncludesTimestamp value - */ - public boolean isVerboseIncludesTimestamp() - { - return verboseIncludesTimestamp; - } - - /** - * @param verboseIncludesTimestamp the value to set - */ - public void setVerboseIncludesTimestamp(boolean verboseIncludesTimestamp) - { - this.verboseIncludesTimestamp = verboseIncludesTimestamp; - } - - /** - * @param disPort the disPort value to set - */ - public void setDisPort(int disPort) - { - this.disPort = disPort; - } -} +/** + * Copyright (c) 2008-2021, MOVES Institute, Naval Postgraduate School (NPS). All rights reserved. + * This work is provided under a BSD open-source license, see project license.html and license.txt + */ +package edu.nps.moves.dis7.utilities; + +import edu.nps.moves.dis7.pdus.Pdu; +import edu.nps.moves.dis7.pdus.DisTime; +import edu.nps.moves.dis7.enumerations.DisPduType; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import java.net.*; + +import java.nio.ByteBuffer; + +import java.util.*; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This is a thread-safe, multicast DIS network interface class. + * + * @author Mike Bailey, jmbailey@nps.edu + * @since Jul 29, 2019 + */ +public class DisThreadedNetworkInterface +{ + /** Default value */ + public static String DEFAULT_MULTICAST_ADDRESS = "225.4.5.6"; + + /** Default value */ + public static int DEFAULT_DIS_PORT = 3000; + + private static final String TRACE_PREFIX = "[" + DisThreadedNetworkInterface.class.getName() + "] "; + private boolean verbose = true; + private boolean verboseIncludesTimestamp = false; + + /** + * Pdu listener class and interface + */ + public interface PduListener + { + /** + * Callback method + * @param pdu received pdu + */ + void incomingPdu(Pdu pdu); + } + + /** + * Raw pdu listener class and interface + */ + public interface RawPduListener + { + /** + * Callback method + * @param bAndL exposed buffer to receive incoming pdu + */ + void incomingPdu(ByteArrayBufferAndLength bAndL); + } + + /** + * Stores data for further processing + */ + public class ByteArrayBufferAndLength + { + /** + * Active ByteArray buffer + */ + public byte[] bufferByteArray; + /** + * Active ByteArray buffer length + */ + public int length; + + /** + * Default constructor for data storage + * + * @param bufferByteArray the data buffer to store + * @param length the length of the data buffer + */ + public ByteArrayBufferAndLength(byte[] bufferByteArray, int length) + { + this.bufferByteArray = bufferByteArray; + this.length = length; + } + } + + /** ********** Begin class ************** */ + /** + * MTU 8192: TODO this has actually been superseded by a larger buffer size, + * but good enough for now + */ + public static final int MAX_DIS_PDU_SIZE = 8192; + + /** + * MTU 1500: size of an Ethernet frame, common value to avoid packet + * segmentation + */ + public static final int MAX_TRANSMISSION_UNIT_SIZE = 1500; + + private int disPort; + private String multicastAddress; + private boolean killed = false; + + private InetAddress inetAddress; + private InetSocketAddress inetSocket; + private NetworkInterface networkInterface; + private DatagramSocket datagramSocket = null; + + /** + * Default constructor using default port and multicast address + */ + public DisThreadedNetworkInterface() + { + this(DEFAULT_MULTICAST_ADDRESS, DEFAULT_DIS_PORT); + } + + /** + * Constructor + * @param multicastGroup the multicast group address to utilize + * @param port the multicast port to utilize + */ + public DisThreadedNetworkInterface(String multicastGroup, int port) + { + disPort = port; + multicastAddress = multicastGroup; + try + { + inetAddress = InetAddress.getByName(multicastAddress); + } + catch (UnknownHostException ex) + { + Logger.getLogger(DisThreadedNetworkInterface.class.getName()).log(Level.SEVERE, null, ex); + } + inetSocket = new InetSocketAddress(inetAddress, disPort); + networkInterface = findIpv4Interface(); + init(); + } + + /* *********** queues and lists and public methods ************** */ + private final List<PduListener> everyTypeListeners = new ArrayList<>(); + private final Map<DisPduType, List<PduListener>> typeListeners = new HashMap<>(); + private final List<RawPduListener> rawListeners = new ArrayList<>(); + private final LinkedBlockingQueue<Pdu> pdus2send = new LinkedBlockingQueue<>(); + + /** + * Add a listener to accept only pdus of a given type + * @param newListener listener instance implementing the RawPduListener interface + * @param disPduType Pdu type + */ + public void addListener(PduListener newListener, DisPduType disPduType) + { + if (disPduType == null) + { + addListener(newListener); + } + else + { + List<PduListener> arLis = typeListeners.get(disPduType); + if (arLis == null) + { + arLis = new ArrayList<>(); + typeListeners.put(disPduType, arLis); + } + arLis.add(newListener); + } + } + + /** + * Add a listener to accept all pdu types + * @param newListener listener instance implementing the RawPduListener interface + */ + public void addListener(PduListener newListener) + { + everyTypeListeners.add(newListener); + } + + /** + * Remove previously added listener + * + * @param priorListener listener instance implementing the RawPduListener interface + */ + public void removeListener(PduListener priorListener) + { + everyTypeListeners.remove(priorListener); + + typeListeners.entrySet().forEach(entry -> + { + List<PduListener> arLis = entry.getValue(); + if (arLis.contains(priorListener)) + { + arLis.remove(priorListener); + } + }); + // additional sleep, hopefully allowing teardown to proceed to completion + sleep(100l); // TODO needed? + } + + /** + * Add a listener to accept pdus of all types in the form of a byte array + * + * @param lis listener instance implementing the RawPduListener interface + */ + public void addRawListener(RawPduListener lis) + { + rawListeners.add(lis); + } + + /** + * Remove previously added raw listener + * + * @param lis listener instance implementing the RawPduListener interface + */ + public void removeRawListener(RawPduListener lis) + { + rawListeners.remove(lis); + // additional sleep, hopefully allowing teardown to proceed to completion + sleep(100l); // TODO needed? + } + + /** + * Get current port value + * @return current port value + */ + public int getDisPort() + { + return disPort; + } + + /** + * Get current multicast address value + * @return current multicast address value + */ + public String getMulticastGroup() + { + return multicastAddress; + } + + /** + * Send the given pdu to the network using the IP address and port given to the constructor + * @param pdu the pdu to send + */ + public void send(Pdu pdu) + { + pdus2send.add(pdu); + } + + /* *************** networking i/o ************* */ + private PduFactory pduFactory = new PduFactory(); + + private Thread sender; + private Thread receiver; + + /** Initialization */ + private void init() + { + createDatagramSocket(); // common asset, synchronized to prevent interleaved reentry + + receiver = new Thread(receiveThread, TRACE_PREFIX + " receive thread"); + receiver.setDaemon(true); + receiver.setPriority(Thread.NORM_PRIORITY); + receiver.start(); + + sender = new Thread(sendThread, TRACE_PREFIX + " send thread"); + sender.setDaemon(true); + sender.setPriority(Thread.NORM_PRIORITY); + sender.start(); + } + + /** + * Create datagram socket if not already available; can also be invoked by + * either sender or receiver thread to ensure datagram socket is open. + * Synchronized method to prevent interleaved reentry. + * @see <a href="https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html">Java Tutorials: Synchronized Methods</a> + */ + private synchronized void createDatagramSocket() + { + boolean closedSocket = false; + if ((datagramSocket != null) && datagramSocket.isClosed()) + { + closedSocket = true; + System.err.println(" *** " + TRACE_PREFIX + "datagramSocket.isClosed() unexpectedly, retrying..."); + } + if ((datagramSocket == null) || closedSocket) + { + try + { + // The initial value of the SO_BROADCAST socket option is FALSE + datagramSocket = new MulticastSocket(getDisPort()); + ((MulticastSocket) datagramSocket).joinGroup(inetSocket, networkInterface); + } + catch (IOException ex) + { + System.err.println(" *** " + TRACE_PREFIX + "Exception in DisThreadedNetworkInterface receive thread: " + ex.getLocalizedMessage()); + } + } + } + + private Runnable receiveThread = () -> { + + int counter = 0; + + // The capacity could go up to MAX_DIS_PDU_SIZE, but this should be good for now + // The raw listeners will strip off any extra padding and process what is + // required + ByteBuffer byteBuffer = ByteBuffer.allocate(MAX_TRANSMISSION_UNIT_SIZE); + DatagramPacket packet = new DatagramPacket(byteBuffer.array(), byteBuffer.capacity()); + Pdu pdu; + + while (!killed) { // keep trying on error + + // If something trips up with the socket, this thread will attempt to + // re-establish for both send/receive threads + try { + createDatagramSocket (); // ensure socket open, recreate if needed, other thread may occur first + + while (!killed) + { + datagramSocket.receive(packet); // blocks here waiting for next DIS pdu to be received on multicast IP and specified port + toRawListeners(packet.getData(), packet.getLength()); + + pdu = pduFactory.createPdu(byteBuffer); + + if (pdu != null) + { + counter++; // TODO experimental, add to generator as a commented-out diagnostic; consider adding diagnostic mode + if (isVerbose()) + { + String message = TRACE_PREFIX + counter + ". received " + pdu.getPduType().toString(); + if (isVerboseIncludesTimestamp()) + message += " (timestamp " + DisTime.timeStampToString(pdu.getTimestamp()); + message +=", size " + pdu.getMarshalledSize() + " bytes)"; + System.out.println(message); + System.out.flush(); + } + toListeners(pdu); + } + byteBuffer.clear(); + } + } + catch (IOException ex) { + System.err.println(TRACE_PREFIX + "Exception in DisThreadedNetworkInterface receive thread: " + ex.getLocalizedMessage()); + System.err.println(TRACE_PREFIX + "Retrying new socket..."); + } + finally + { + if (datagramSocket != null && !datagramSocket.isClosed()) { + try { + ((MulticastSocket)datagramSocket).leaveGroup(inetSocket, networkInterface); + } catch (IOException ex) { + Logger.getLogger(DisThreadedNetworkInterface.class.getName()).log(Level.SEVERE, null, ex); + } + datagramSocket.close(); + sleep(100l); // TODO needed? + datagramSocket = null; + } + } +// if (!killed) +// sleep(250); + } + }; + + private final Runnable sendThread = () -> { + + Pdu pdu; + + // The capacity could go up to MAX_DIS_PDU_SIZE, but this should be good for now + ByteArrayOutputStream baos = new ByteArrayOutputStream(MAX_TRANSMISSION_UNIT_SIZE); + DataOutputStream dos = new DataOutputStream(baos); + DatagramPacket packet = new DatagramPacket(baos.toByteArray(), baos.size(), inetSocket); + + while (!killed) { // keep trying on error + + // If something trips up with the socket, this thread will attempt to + // re-establish for both send/receive threads + try { + createDatagramSocket (); // ensure socket open, recreate if needed, other thread may occur first + + while (!killed) { + pdu = pdus2send.take(); + + pdu.marshal(dos); + packet.setData(baos.toByteArray()); + datagramSocket.send(packet); + + dos.flush(); // immediately force pdu write + baos.reset(); + } + } + catch (Exception ex) + { + System.err.println(TRACE_PREFIX + "Exception in DisThreadedNetworkInterface send thread: " + ex.getLocalizedMessage()); + } + } + try { + dos.close(); + } catch (IOException e) {} + }; + + private void toListeners(Pdu pdu) { + if (everyTypeListeners.isEmpty()) { + return; + } + + if (pdu != null) { + everyTypeListeners.forEach(lis -> lis.incomingPdu(pdu)); + + if (typeListeners.isEmpty()) { + return; + } + + List<PduListener> arLis = typeListeners.get(pdu.getPduType()); + if (arLis != null) { + arLis.forEach(lis -> lis.incomingPdu(pdu)); + } + } + } + + private void toRawListeners(byte[] data, int len) + { + if(rawListeners.isEmpty()) + return; + + ByteArrayBufferAndLength bl = new ByteArrayBufferAndLength(data, len); + rawListeners.forEach(lis->lis.incomingPdu(bl)); + } + + /** Terminate the instance */ + public void kill() + { + killed = true; // set loop sentinel for threads + } + + /** Thread sleep for indicated interval + * @param duration milliseconds */ + private void sleep(long duration) + { + try { + Thread.sleep(duration); + } + catch (InterruptedException ie) + { + System.err.flush(); + System.err.println ("*** " + getClass().getName() + ".sleep(" + duration + ") failed to sleep"); + ie.printStackTrace(System.err); + } + } + + /** + * Find proper IPV4 interface + * + * @return a network interface to use to join a multicast group + */ + public static NetworkInterface findIpv4Interface() { + try { + Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces(); + NetworkInterface nif; + Enumeration<InetAddress> addresses; + InetAddress addr; + + while (ifaces != null && ifaces.hasMoreElements()) { + nif = ifaces.nextElement(); + if (nif.isUp()) { + addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + addr = addresses.nextElement(); + if (addr instanceof Inet4Address && !addr.isLoopbackAddress() && !addr.isLinkLocalAddress()) + { + System.out.println(TRACE_PREFIX + " Using network interface " + nif.getDisplayName()); + return nif; + } + } + } + } + } catch (SocketException ex) { + Logger.getLogger(DisThreadedNetworkInterface.class.getName()).log(Level.SEVERE, null, ex); + } + return null; + } + + /** + * @return the verbose + */ + public boolean isVerbose() + { + return verbose; + } + + /** + * @param verbose the verbose to set + */ + public void setVerbose(boolean verbose) + { + this.verbose = verbose; + } + + /** + * @return the verboseIncludesTimestamp value + */ + public boolean isVerboseIncludesTimestamp() + { + return verboseIncludesTimestamp; + } + + /** + * @param verboseIncludesTimestamp the value to set + */ + public void setVerboseIncludesTimestamp(boolean verboseIncludesTimestamp) + { + this.verboseIncludesTimestamp = verboseIncludesTimestamp; + } + + /** + * @param disPort the disPort value to set + */ + public void setDisPort(int disPort) + { + this.disPort = disPort; + } +} diff --git a/src/edu/nps/moves/dis7/utilities/PduFactory.java b/src/edu/nps/moves/dis7/utilities/PduFactory.java index 290671b3083ee1ad1a70699a81f0f1407f84a798..aca5d57d5b09894b20bbe439b865e062c2b8737b 100644 --- a/src/edu/nps/moves/dis7/utilities/PduFactory.java +++ b/src/edu/nps/moves/dis7/utilities/PduFactory.java @@ -7,7 +7,7 @@ package edu.nps.moves.dis7.utilities; import edu.nps.moves.dis7.pdus.*; import edu.nps.moves.dis7.enumerations.*; -import edu.nps.moves.dis7.enumerations.DISPDUType; +import edu.nps.moves.dis7.enumerations.DisPduType; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.ByteBuffer; @@ -1475,7 +1475,7 @@ public class PduFactory .setOriginatingSimulationAddress(newSimulationAddress()); /* .setActionCode(DISAttributeActionCode.NO_STATEMENT) - .setAttributeRecordPduType(DISPDUType.OTHER) + .setAttributeRecordPduType(DisPduType.OTHER) .setAttributeRecordProtocolVersion(DISProtocolFamily.OTHER) .setMasterAttributeRecordType(VariableRecordTypes.OTHER) */ @@ -1504,7 +1504,7 @@ public class PduFactory */ public Pdu createPdu(ByteBuffer byteBuffer) { - DISPDUType pduType = getTypeFromByteArray(byteBuffer.array()); + DisPduType pduType = getTypeFromByteArray(byteBuffer.array()); return createPdu(pduType, byteBuffer); } @@ -1515,9 +1515,9 @@ public class PduFactory * @param ba byte array * @return the type */ - private DISPDUType getTypeFromByteArray(byte[] ba) + private DisPduType getTypeFromByteArray(byte[] ba) { - return DISPDUType.getEnumForValue(Byte.toUnsignedInt(ba[2])); // 3rd byte + return DisPduType.getEnumForValue(Byte.toUnsignedInt(ba[2])); // 3rd byte } /** @@ -1525,12 +1525,12 @@ public class PduFactory * @param pduType PDU type to create * @return the empty pdu */ - public Pdu createPdu(DISPDUType pduType) + public Pdu createPdu(DisPduType pduType) { return createPdu(pduType, null); } - private Pdu createPdu(DISPDUType pduType, ByteBuffer byteBuffer) + private Pdu createPdu(DisPduType pduType, ByteBuffer byteBuffer) { Pdu aPdu = null; switch (pduType) { diff --git a/src/edu/nps/moves/dis7/utilities/stream/PduPlayer.java b/src/edu/nps/moves/dis7/utilities/stream/PduPlayer.java index 3f507bd770bb2662e4b6fb3674b6414e259c1de8..8df7e3a27685841002de7cbf2b42813a8bea64a9 100644 --- a/src/edu/nps/moves/dis7/utilities/stream/PduPlayer.java +++ b/src/edu/nps/moves/dis7/utilities/stream/PduPlayer.java @@ -5,7 +5,7 @@ package edu.nps.moves.dis7.utilities.stream; import com.google.common.primitives.Longs; -import edu.nps.moves.dis7.enumerations.DISPDUType; +import edu.nps.moves.dis7.enumerations.DisPduType; import java.io.*; import java.net.DatagramPacket; @@ -104,7 +104,7 @@ public class PduPlayer { InetAddress addr = null; DatagramPacket datagramPacket; - DISPDUType type; + DisPduType type; String tempString; String[] sa = null, splitString; String REGEX; @@ -248,7 +248,7 @@ public class PduPlayer { if (netSend) { datagramPacket = new DatagramPacket(buffer, buffer.length, addr, port); datagramSocket.send(datagramPacket); - type = DISPDUType.getEnumForValue(Byte.toUnsignedInt(buffer[2])); // 3rd byte + type = DisPduType.getEnumForValue(Byte.toUnsignedInt(buffer[2])); // 3rd byte System.out.println("Sent PDU: " + type); } break; @@ -308,7 +308,7 @@ public class PduPlayer { globalByteBufferForX3dInterPolators = bufferShort.clone(); x3dInterpolators.addPointsToMap(globalByteBufferForX3dInterPolators); // gets cloned again x3dLineSet.addPointsToMap(globalByteBufferForX3dInterPolators); // gets cloned again - type = DISPDUType.getEnumForValue(Byte.toUnsignedInt(bufferShort[2])); // 3rd byte + type = DisPduType.getEnumForValue(Byte.toUnsignedInt(bufferShort[2])); // 3rd byte System.out.println("Sent PDU: " + type); } break; diff --git a/src/edu/nps/moves/dis7/utilities/stream/PduRecorder.java b/src/edu/nps/moves/dis7/utilities/stream/PduRecorder.java index a688f0c59e9214dc01c0c30180a342d7dc5f28ad..b7c19c16bbc996f42fba150884cdcb3f337e512e 100644 --- a/src/edu/nps/moves/dis7/utilities/stream/PduRecorder.java +++ b/src/edu/nps/moves/dis7/utilities/stream/PduRecorder.java @@ -2,7 +2,7 @@ package edu.nps.moves.dis7.utilities.stream; import com.google.common.primitives.Longs; -import edu.nps.moves.dis7.enumerations.DISPDUType; +import edu.nps.moves.dis7.enumerations.DisPduType; import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface; import edu.nps.moves.dis7.utilities.PduFactory; @@ -176,7 +176,7 @@ public class PduRecorder implements PduReceiver //System.out.println("wrote time "+(packetRcvNanoTime - startNanoTime)); byte[] buffsized = Arrays.copyOf(newBuffer, newLength); - DISPDUType pduType; + DisPduType pduType; switch (pduLogEncoding) { @@ -192,7 +192,7 @@ public class PduRecorder implements PduReceiver sb.append(','); sb.append(Arrays.toString(buffsized).replace(" ", "")); sb.append(" # "); - pduType = DISPDUType.getEnumForValue(Byte.toUnsignedInt(buffsized[2])); // 3rd byte + pduType = DisPduType.getEnumForValue(Byte.toUnsignedInt(buffsized[2])); // 3rd byte sb.append(pduType); break; @@ -336,10 +336,10 @@ public class PduRecorder implements PduReceiver } System.out.println("dis7.utilities.stream.PduRecorder pduRecorder created... isRunning()=" + pduRecorder.isRunning()); - DISPDUType allPDUTypesArray[] = DISPDUType.values(); + DisPduType allPDUTypesArray[] = DisPduType.values(); System.out.println("dis7.utilities.stream.PduRecorder allPDUTypesArray created, length=" + allPDUTypesArray.length + " ..."); Arrays.stream(allPDUTypesArray).forEach(pduTypeValue-> { - if(pduTypeValue != DISPDUType.OTHER) + if(pduTypeValue != DisPduType.OTHER) { try { pduRecorder.getDisThreadedNetIF().send(factory.createPdu(pduTypeValue)); diff --git a/src/edu/nps/moves/dis7/utilities/stream/X3dCreateInterpolators.java b/src/edu/nps/moves/dis7/utilities/stream/X3dCreateInterpolators.java index b1a3e051d716804adc2dbc9297bfd63f7ca28b67..71ef45cab69b95c3ca8b1aa35ab67faa93e80ab3 100644 --- a/src/edu/nps/moves/dis7/utilities/stream/X3dCreateInterpolators.java +++ b/src/edu/nps/moves/dis7/utilities/stream/X3dCreateInterpolators.java @@ -1,277 +1,277 @@ -package edu.nps.moves.dis7.utilities.stream; - -import edu.nps.moves.dis7.pdus.EntityStatePdu; -import edu.nps.moves.dis7.pdus.Pdu; -import edu.nps.moves.dis7.enumerations.DISPDUType; -import edu.nps.moves.dis7.utilities.PduFactory; -import java.nio.ByteBuffer; -import java.text.NumberFormat; -import java.util.LinkedHashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author Tobias Brennenstuhl @ NPS - */ -public class X3dCreateInterpolators { - - private byte[] bufferShort; - - // -------------------- Begin Variables for Position Interpolator - private Boolean firstTimeStamp = true; - private int firstLocalTimeStamp = 0; - - private double firstLocalX = 0; - private double firstLocalY = 0; - private double firstLocalZ = 0; - - private Map<Double, X3dCoordinates> testMap = new LinkedHashMap<>(); - - //Setting up a NumberFormatter for limitting the decimal count to 3 - private NumberFormat coordinateNumberFormat = NumberFormat.getInstance(new Locale("en", "US")); - - // -------------------- End Variables for Position Interpolator - - /** Default Constructor */ - public X3dCreateInterpolators() { - - //3 significant digits equals milimeter position accuracy and 0.001 radians = 0.0572963266634555‬ degrees - coordinateNumberFormat.setMaximumFractionDigits(3); - - } - - public void addPointsToMap(byte[] localBufferShort) { - - this.bufferShort = localBufferShort.clone(); - - if (bufferShort[2] == 1) { - - //PDU Factory - PduFactory pduFactory = new PduFactory(); - Pdu localPdu = pduFactory.createPdu(bufferShort); - - // ToDO figure out how to do this! makeEntityStatePDU - EntityStatePdu localEspdu = pduFactory.makeEntityStatePdu(); - //Put all the data we need into the localEspdu - ByteBuffer espduByteBuffer = ByteBuffer.wrap(bufferShort); - try { - localEspdu.unmarshal(espduByteBuffer); - } catch (Exception ex) { - Logger.getLogger(X3dCreateInterpolators.class.getName()).log(Level.SEVERE, null, ex); - } - - double localTimeStamp; - double localX; - double localY; - double localZ; - - double localPhi; - double localPsi; - double localTheta; - - //Store the first timestamp to subtract it from all others - //Same with X,Y,Z to create a local coordiante system - if (firstTimeStamp) { - - firstLocalTimeStamp = localPdu.getTimestamp(); - firstLocalX = localEspdu.getEntityLocation().getX(); - firstLocalY = localEspdu.getEntityLocation().getZ(); - firstLocalZ = -1 * localEspdu.getEntityLocation().getY(); - - firstTimeStamp = false; - } - - localTimeStamp = localPdu.getTimestamp(); - localX = localEspdu.getEntityLocation().getX(); - localY = localEspdu.getEntityLocation().getZ(); - localZ = -1 * localEspdu.getEntityLocation().getY(); - localPhi = localEspdu.getEntityOrientation().getPhi(); - localPsi = localEspdu.getEntityOrientation().getPsi(); - localTheta = localEspdu.getEntityOrientation().getTheta(); - - localTimeStamp = localTimeStamp - firstLocalTimeStamp; - localX = localX - firstLocalX; - localY = localY - firstLocalY; - localZ = localZ - firstLocalZ; - - //Divide TimeStamp by 1,300,000 to get something close to a second per Unit. - //According to the DIS standard one tick is 3600/(2^31) seconds ~ 1.6764 µs - //1,100,000 was derived from a stream that is 83 seconds long. The number was adjusted to get a timesensor with 83 seconds - //ToDo find the real conversion between TimeStampDelta and seconds - localTimeStamp = localTimeStamp / 1100000; - - //Only add to stream if it is an ESPDU - //ToDo: Add support for multiple Entities - if ((localPdu.getPduType() != null) && (localPdu.getPduType() == DISPDUType.ENTITY_STATE)) - testMap.put(localTimeStamp, new X3dCoordinates(localX, localY, localZ, localPhi, localPsi, localTheta)); - } - } - - public void makeX3dInterpolator() - { - //Compression of the testMap. - //Remove all collinear points. - X3dSlidingWindowCompression slidingWindowCompression = new X3dSlidingWindowCompression(testMap); - - //To turn of the compression just comment the next line out and the very next in. - Map<Double, X3dCoordinates> returnMap = slidingWindowCompression.doSlidingWindow(); - //returnMap.putAll(testMap); - - //Writing all values from the KeyMap to a proper Position Interpolator String - System.out.println("Writing Position and Rotation Interpolator"); - Set<Double> keys = returnMap.keySet(); - //Set<Double> keys = tempKeyKeyValueSetPositionInterPolator.keySet(); - String positionKey = "key = '"; - String positionKeyValue = "keyValue = '"; - String positionInterpolatorToCopy = "<PositionInterpolator DEF='EntityPosition' "; - - String orientationKeyX = "key = '"; - String orientationKeyValueX = "keyValue = '"; - String orientationInterpolatorToCopyX = "<OrientationInterpolator DEF='EntityOrientationX' "; - - String orientationKeyY = "key = '"; - String orientationKeyValueY = "keyValue = '"; - String orientationInterpolatorToCopyY = "<OrientationInterpolator DEF='EntityOrientationY' "; - - String orientationKeyZ = "key = '"; - String orientationKeyValueZ = "keyValue = '"; - String orientationInterpolatorToCopyZ = "<OrientationInterpolator DEF='EntityOrientationZ' "; - - //Find highest time to do the normalization - double lastTimeStamp = 0; - - for (Double k : keys) { - - if (k > lastTimeStamp) - lastTimeStamp = k; - } - - //Normalize all times in the set - Map<Double, String> keyKeyValueSetPositionInterpolator = new LinkedHashMap<>(); - - Map<Double, String> keyKeyValueSetOrientationInterpolatorX = new LinkedHashMap<>(); - Map<Double, String> keyKeyValueSetOrientationInterpolatorY = new LinkedHashMap<>(); - Map<Double, String> keyKeyValueSetOrientationInterpolatorZ = new LinkedHashMap<>(); - - double tempX; - double tempY; - double tempZ ; - - double tempPhi; - double tempPsi; - double tempTheta; - - String localCoordinateString; - String localOrientationStringX; - String localOrientationStringY; - String localOrientationStringZ; - - for (Double k : keys) { - - tempX = returnMap.get(k).getX(); - tempY = returnMap.get(k).getY(); - tempZ = returnMap.get(k).getZ(); - - tempPhi = returnMap.get(k).getPhi() / 6.28; - tempPsi = returnMap.get(k).getPsi() / 6.28; - tempTheta = returnMap.get(k).getTheta() / 6.28; - - localCoordinateString = " " + coordinateNumberFormat.format(tempX) + " " + coordinateNumberFormat.format(tempY) + " " + coordinateNumberFormat.format(tempZ); - localOrientationStringX = " 1 0 0 " + coordinateNumberFormat.format(tempPhi); - localOrientationStringY = " 0 1 0 " + coordinateNumberFormat.format(tempTheta); - localOrientationStringZ = " 0 0 1 " + coordinateNumberFormat.format(tempPsi); - - keyKeyValueSetPositionInterpolator.put(k / lastTimeStamp, localCoordinateString); - keyKeyValueSetOrientationInterpolatorX.put(k / lastTimeStamp, localOrientationStringX); - keyKeyValueSetOrientationInterpolatorY.put(k / lastTimeStamp, localOrientationStringY); - keyKeyValueSetOrientationInterpolatorZ.put(k / lastTimeStamp, localOrientationStringZ); - - } - - keys = keyKeyValueSetPositionInterpolator.keySet(); - - //Setting up the timeSensor - //Only one timeSensor for both interpolators is needed - String timeSensor = "<TimeSensor DEF='PduStreamClock' cycleInterval='"; - - timeSensor += lastTimeStamp; - - timeSensor += "' loop = 'true'/>"; - - //Printing the timeSensor to the console - System.out.println(timeSensor); - - //Setting up PositionInterpolator and OrientationInterpolator - for (Double k : keys) { - //System.out.println("Time: " + k + " Position (x,y,z) " + keyKeyValueSetPositionInterpolator.get(k)); - - //PositionInterpolator - positionKey += coordinateNumberFormat.format(k) + " "; - positionKeyValue += keyKeyValueSetPositionInterpolator.get(k) + " "; - - //OrientationInterpolator for X (phi) - orientationKeyX += coordinateNumberFormat.format(k) + " "; - orientationKeyValueX += keyKeyValueSetOrientationInterpolatorX.get(k) + " "; - - //OrientationInterpolator for Y (theta) - orientationKeyY += coordinateNumberFormat.format(k) + " "; - orientationKeyValueY += keyKeyValueSetOrientationInterpolatorY.get(k) + " "; - - //OrientationInterpolator for Z (psi) - orientationKeyZ += coordinateNumberFormat.format(k) + " "; - orientationKeyValueZ += keyKeyValueSetOrientationInterpolatorZ.get(k) + " "; - - } - positionKey += "' "; - positionKeyValue += "' "; - - orientationKeyX += "' "; - orientationKeyValueX += "' "; - - orientationKeyY += "' "; - orientationKeyValueY += "' "; - - orientationKeyZ += "' "; - orientationKeyValueZ += "' "; - - //PositionInterpolator - positionInterpolatorToCopy += positionKey + "\n"; - positionInterpolatorToCopy += positionKeyValue; - positionInterpolatorToCopy += "/>"; - - //PositionInterpolator for X - orientationInterpolatorToCopyX += orientationKeyX + "\n"; - orientationInterpolatorToCopyX += orientationKeyValueX; - orientationInterpolatorToCopyX += "/>"; - - //PositionInterpolator for Y - orientationInterpolatorToCopyY += orientationKeyY + "\n"; - orientationInterpolatorToCopyY += orientationKeyValueY; - orientationInterpolatorToCopyY += "/>"; - - //PositionInterpolator for Z - orientationInterpolatorToCopyZ += orientationKeyY + "\n"; - orientationInterpolatorToCopyZ += orientationKeyValueZ; - orientationInterpolatorToCopyZ += "/>"; - - //Printing PositionInterpolator to the console - System.out.println(positionInterpolatorToCopy); - - //First Rotation must be around z axis by psi - //Printing OrientationInterpolator for X to the console - System.out.println(orientationInterpolatorToCopyZ); - - //Second Rotation must be around resulting y (y') axis by theta - //Printing OrientationInterpolator for Y to the console - System.out.println(orientationInterpolatorToCopyY); - - //last rotation must be around resulting x (x') axis by phi - //Printing OrientationInterpolator for Z to the console - System.out.println(orientationInterpolatorToCopyX); - } - -} +package edu.nps.moves.dis7.utilities.stream; + +import edu.nps.moves.dis7.pdus.EntityStatePdu; +import edu.nps.moves.dis7.pdus.Pdu; +import edu.nps.moves.dis7.enumerations.DisPduType; +import edu.nps.moves.dis7.utilities.PduFactory; +import java.nio.ByteBuffer; +import java.text.NumberFormat; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author Tobias Brennenstuhl @ NPS + */ +public class X3dCreateInterpolators { + + private byte[] bufferShort; + + // -------------------- Begin Variables for Position Interpolator + private Boolean firstTimeStamp = true; + private int firstLocalTimeStamp = 0; + + private double firstLocalX = 0; + private double firstLocalY = 0; + private double firstLocalZ = 0; + + private Map<Double, X3dCoordinates> testMap = new LinkedHashMap<>(); + + //Setting up a NumberFormatter for limitting the decimal count to 3 + private NumberFormat coordinateNumberFormat = NumberFormat.getInstance(new Locale("en", "US")); + + // -------------------- End Variables for Position Interpolator + + /** Default Constructor */ + public X3dCreateInterpolators() { + + //3 significant digits equals milimeter position accuracy and 0.001 radians = 0.0572963266634555‬ degrees + coordinateNumberFormat.setMaximumFractionDigits(3); + + } + + public void addPointsToMap(byte[] localBufferShort) { + + this.bufferShort = localBufferShort.clone(); + + if (bufferShort[2] == 1) { + + //PDU Factory + PduFactory pduFactory = new PduFactory(); + Pdu localPdu = pduFactory.createPdu(bufferShort); + + // ToDO figure out how to do this! makeEntityStatePDU + EntityStatePdu localEspdu = pduFactory.makeEntityStatePdu(); + //Put all the data we need into the localEspdu + ByteBuffer espduByteBuffer = ByteBuffer.wrap(bufferShort); + try { + localEspdu.unmarshal(espduByteBuffer); + } catch (Exception ex) { + Logger.getLogger(X3dCreateInterpolators.class.getName()).log(Level.SEVERE, null, ex); + } + + double localTimeStamp; + double localX; + double localY; + double localZ; + + double localPhi; + double localPsi; + double localTheta; + + //Store the first timestamp to subtract it from all others + //Same with X,Y,Z to create a local coordiante system + if (firstTimeStamp) { + + firstLocalTimeStamp = localPdu.getTimestamp(); + firstLocalX = localEspdu.getEntityLocation().getX(); + firstLocalY = localEspdu.getEntityLocation().getZ(); + firstLocalZ = -1 * localEspdu.getEntityLocation().getY(); + + firstTimeStamp = false; + } + + localTimeStamp = localPdu.getTimestamp(); + localX = localEspdu.getEntityLocation().getX(); + localY = localEspdu.getEntityLocation().getZ(); + localZ = -1 * localEspdu.getEntityLocation().getY(); + localPhi = localEspdu.getEntityOrientation().getPhi(); + localPsi = localEspdu.getEntityOrientation().getPsi(); + localTheta = localEspdu.getEntityOrientation().getTheta(); + + localTimeStamp = localTimeStamp - firstLocalTimeStamp; + localX = localX - firstLocalX; + localY = localY - firstLocalY; + localZ = localZ - firstLocalZ; + + //Divide TimeStamp by 1,300,000 to get something close to a second per Unit. + //According to the DIS standard one tick is 3600/(2^31) seconds ~ 1.6764 µs + //1,100,000 was derived from a stream that is 83 seconds long. The number was adjusted to get a timesensor with 83 seconds + //ToDo find the real conversion between TimeStampDelta and seconds + localTimeStamp = localTimeStamp / 1100000; + + //Only add to stream if it is an ESPDU + //ToDo: Add support for multiple Entities + if ((localPdu.getPduType() != null) && (localPdu.getPduType() == DisPduType.ENTITY_STATE)) + testMap.put(localTimeStamp, new X3dCoordinates(localX, localY, localZ, localPhi, localPsi, localTheta)); + } + } + + public void makeX3dInterpolator() + { + //Compression of the testMap. + //Remove all collinear points. + X3dSlidingWindowCompression slidingWindowCompression = new X3dSlidingWindowCompression(testMap); + + //To turn of the compression just comment the next line out and the very next in. + Map<Double, X3dCoordinates> returnMap = slidingWindowCompression.doSlidingWindow(); + //returnMap.putAll(testMap); + + //Writing all values from the KeyMap to a proper Position Interpolator String + System.out.println("Writing Position and Rotation Interpolator"); + Set<Double> keys = returnMap.keySet(); + //Set<Double> keys = tempKeyKeyValueSetPositionInterPolator.keySet(); + String positionKey = "key = '"; + String positionKeyValue = "keyValue = '"; + String positionInterpolatorToCopy = "<PositionInterpolator DEF='EntityPosition' "; + + String orientationKeyX = "key = '"; + String orientationKeyValueX = "keyValue = '"; + String orientationInterpolatorToCopyX = "<OrientationInterpolator DEF='EntityOrientationX' "; + + String orientationKeyY = "key = '"; + String orientationKeyValueY = "keyValue = '"; + String orientationInterpolatorToCopyY = "<OrientationInterpolator DEF='EntityOrientationY' "; + + String orientationKeyZ = "key = '"; + String orientationKeyValueZ = "keyValue = '"; + String orientationInterpolatorToCopyZ = "<OrientationInterpolator DEF='EntityOrientationZ' "; + + //Find highest time to do the normalization + double lastTimeStamp = 0; + + for (Double k : keys) { + + if (k > lastTimeStamp) + lastTimeStamp = k; + } + + //Normalize all times in the set + Map<Double, String> keyKeyValueSetPositionInterpolator = new LinkedHashMap<>(); + + Map<Double, String> keyKeyValueSetOrientationInterpolatorX = new LinkedHashMap<>(); + Map<Double, String> keyKeyValueSetOrientationInterpolatorY = new LinkedHashMap<>(); + Map<Double, String> keyKeyValueSetOrientationInterpolatorZ = new LinkedHashMap<>(); + + double tempX; + double tempY; + double tempZ ; + + double tempPhi; + double tempPsi; + double tempTheta; + + String localCoordinateString; + String localOrientationStringX; + String localOrientationStringY; + String localOrientationStringZ; + + for (Double k : keys) { + + tempX = returnMap.get(k).getX(); + tempY = returnMap.get(k).getY(); + tempZ = returnMap.get(k).getZ(); + + tempPhi = returnMap.get(k).getPhi() / 6.28; + tempPsi = returnMap.get(k).getPsi() / 6.28; + tempTheta = returnMap.get(k).getTheta() / 6.28; + + localCoordinateString = " " + coordinateNumberFormat.format(tempX) + " " + coordinateNumberFormat.format(tempY) + " " + coordinateNumberFormat.format(tempZ); + localOrientationStringX = " 1 0 0 " + coordinateNumberFormat.format(tempPhi); + localOrientationStringY = " 0 1 0 " + coordinateNumberFormat.format(tempTheta); + localOrientationStringZ = " 0 0 1 " + coordinateNumberFormat.format(tempPsi); + + keyKeyValueSetPositionInterpolator.put(k / lastTimeStamp, localCoordinateString); + keyKeyValueSetOrientationInterpolatorX.put(k / lastTimeStamp, localOrientationStringX); + keyKeyValueSetOrientationInterpolatorY.put(k / lastTimeStamp, localOrientationStringY); + keyKeyValueSetOrientationInterpolatorZ.put(k / lastTimeStamp, localOrientationStringZ); + + } + + keys = keyKeyValueSetPositionInterpolator.keySet(); + + //Setting up the timeSensor + //Only one timeSensor for both interpolators is needed + String timeSensor = "<TimeSensor DEF='PduStreamClock' cycleInterval='"; + + timeSensor += lastTimeStamp; + + timeSensor += "' loop = 'true'/>"; + + //Printing the timeSensor to the console + System.out.println(timeSensor); + + //Setting up PositionInterpolator and OrientationInterpolator + for (Double k : keys) { + //System.out.println("Time: " + k + " Position (x,y,z) " + keyKeyValueSetPositionInterpolator.get(k)); + + //PositionInterpolator + positionKey += coordinateNumberFormat.format(k) + " "; + positionKeyValue += keyKeyValueSetPositionInterpolator.get(k) + " "; + + //OrientationInterpolator for X (phi) + orientationKeyX += coordinateNumberFormat.format(k) + " "; + orientationKeyValueX += keyKeyValueSetOrientationInterpolatorX.get(k) + " "; + + //OrientationInterpolator for Y (theta) + orientationKeyY += coordinateNumberFormat.format(k) + " "; + orientationKeyValueY += keyKeyValueSetOrientationInterpolatorY.get(k) + " "; + + //OrientationInterpolator for Z (psi) + orientationKeyZ += coordinateNumberFormat.format(k) + " "; + orientationKeyValueZ += keyKeyValueSetOrientationInterpolatorZ.get(k) + " "; + + } + positionKey += "' "; + positionKeyValue += "' "; + + orientationKeyX += "' "; + orientationKeyValueX += "' "; + + orientationKeyY += "' "; + orientationKeyValueY += "' "; + + orientationKeyZ += "' "; + orientationKeyValueZ += "' "; + + //PositionInterpolator + positionInterpolatorToCopy += positionKey + "\n"; + positionInterpolatorToCopy += positionKeyValue; + positionInterpolatorToCopy += "/>"; + + //PositionInterpolator for X + orientationInterpolatorToCopyX += orientationKeyX + "\n"; + orientationInterpolatorToCopyX += orientationKeyValueX; + orientationInterpolatorToCopyX += "/>"; + + //PositionInterpolator for Y + orientationInterpolatorToCopyY += orientationKeyY + "\n"; + orientationInterpolatorToCopyY += orientationKeyValueY; + orientationInterpolatorToCopyY += "/>"; + + //PositionInterpolator for Z + orientationInterpolatorToCopyZ += orientationKeyY + "\n"; + orientationInterpolatorToCopyZ += orientationKeyValueZ; + orientationInterpolatorToCopyZ += "/>"; + + //Printing PositionInterpolator to the console + System.out.println(positionInterpolatorToCopy); + + //First Rotation must be around z axis by psi + //Printing OrientationInterpolator for X to the console + System.out.println(orientationInterpolatorToCopyZ); + + //Second Rotation must be around resulting y (y') axis by theta + //Printing OrientationInterpolator for Y to the console + System.out.println(orientationInterpolatorToCopyY); + + //last rotation must be around resulting x (x') axis by phi + //Printing OrientationInterpolator for Z to the console + System.out.println(orientationInterpolatorToCopyX); + } + +} diff --git a/src/edu/nps/moves/dis7/utilities/stream/X3dCreateLineSet.java b/src/edu/nps/moves/dis7/utilities/stream/X3dCreateLineSet.java index 8176a11429126d95aca80259ba6933abd353c127..10bfaa76944f0448316c2dc91b6a9132ebd7a032 100644 --- a/src/edu/nps/moves/dis7/utilities/stream/X3dCreateLineSet.java +++ b/src/edu/nps/moves/dis7/utilities/stream/X3dCreateLineSet.java @@ -1,171 +1,171 @@ -package edu.nps.moves.dis7.utilities.stream; - -import edu.nps.moves.dis7.pdus.EntityStatePdu; -import edu.nps.moves.dis7.pdus.Pdu; -import edu.nps.moves.dis7.enumerations.DISPDUType; -import edu.nps.moves.dis7.utilities.PduFactory; -import java.nio.ByteBuffer; -import java.text.NumberFormat; -import java.util.LinkedHashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author Tobias Brennenstuhl @ NPS - */ -public class X3dCreateLineSet { - - private byte[] bufferShort; - - // -------------------- Begin Variables for Position Interpolator - private Boolean firstTimeStamp = true; - private int firstLocalTimeStamp = 0; - - private double firstLocalX = 0; - private double firstLocalY = 0; - private double firstLocalZ = 0; - - private Map<Double, X3dCoordinates> testMap = new LinkedHashMap<>(); - - //Setting up a NumberFormatter for limitting the decimal count to 3 - private NumberFormat coordinateNumberFormat = NumberFormat.getInstance(new Locale("en", "US")); - - // -------------------- End Variables for Position Interpolator - - /** Constructor */ - public X3dCreateLineSet() - { - //3 significant digits equals milimeter position accuracy and 0.001 radians = 0.0572963266634555‬ degrees - coordinateNumberFormat.setMaximumFractionDigits(3); - } - - public void addPointsToMap(byte[] localBufferShort) - { - this.bufferShort = localBufferShort.clone(); - - if (bufferShort[2] == 1) { - - //PDU Factory - PduFactory pduFactory = new PduFactory(); - Pdu localPdu = pduFactory.createPdu(bufferShort); - - // ToDO figure out how to do this! makeEntityStatePDU - EntityStatePdu localEspdu = pduFactory.makeEntityStatePdu(); - //Put all the data we need into the localEspdu - ByteBuffer espduByteBuffer = ByteBuffer.wrap(bufferShort); - try { - localEspdu.unmarshal(espduByteBuffer); - } catch (Exception ex) { - Logger.getLogger(X3dCreateLineSet.class.getName()).log(Level.SEVERE, null, ex); - } - - double localTimeStamp; - double localX; - double localY; - double localZ; - - //TimeStamps for a lineSet is not needed but copied from X3DInterpolators to use them as key for the hashmap - // and the standard compression class can be used - //Store the first timestamp to subtract it from all others - //Same with X,Y,Z to create a local coordiante system - if (firstTimeStamp) { - - firstLocalTimeStamp = localPdu.getTimestamp(); - firstLocalX = localEspdu.getEntityLocation().getX(); - firstLocalY = localEspdu.getEntityLocation().getZ(); - firstLocalZ = -1 * localEspdu.getEntityLocation().getY(); - - firstTimeStamp = false; - } - - localTimeStamp = localPdu.getTimestamp(); - localX = localEspdu.getEntityLocation().getX(); - localY = localEspdu.getEntityLocation().getZ(); - localZ = -1 * localEspdu.getEntityLocation().getY(); - - //Debug for printing X,Y,Z - //System.out.println(localX + " " + localY + " " + localZ); - localTimeStamp = localTimeStamp - firstLocalTimeStamp; - localX = localX - firstLocalX; - localY = localY - firstLocalY; - localZ = localZ - firstLocalZ; - - //Divide TimeStamp by 1,300,000 to get something close to a second per Unit. - //According to the DIS standard one tick is 3600/(2^31) seconds ~ 1.6764 µs - //1,300,000 was derived from a stream that is 61 seconds long. The number was adjusted to get a timesensor with 61 seconds - //ToDo find the real conversion between TimeStampDelta and seconds - localTimeStamp = localTimeStamp / 1300000; - - //Only add to stream if it is an ESPDU - //ToDo: Add support for multiple Entities - if ((localPdu.getPduType() != null) && (localPdu.getPduType() == DISPDUType.ENTITY_STATE)) - testMap.put(localTimeStamp, new X3dCoordinates(localX, localY, localZ, 0.0, 0.0, 0.0)); - } - } - - public void makeX3dLineSet() { - - //Compression of the testMap. - //Remove all collinear points. - X3dSlidingWindowCompression regression = new X3dSlidingWindowCompression(testMap); - - //To turn of the compression just comment the next line out and the very next in. - Map<Double, X3dCoordinates> returnMap = regression.doSlidingWindow(); - //returnMap.putAll(testMap); - - //Writing all values from the KeyMap to a proper Position Interpolator String - System.out.println("Writing X3D LineSet"); - Set<Double> keys = returnMap.keySet(); - String lineSetPoints = ""; - String lineSet = "<LineSet vertexCount='" + returnMap.size() + "'> \n <Coordinate point='"; - - //Find highest time to do the normalization - double lastTimeStamp = 0; - - for (Double k : keys) { - - if (k > lastTimeStamp) - lastTimeStamp = k; - } - - //Normalize all times in the set - Map<Double, String> keyKeyValueSetPositionInterpolator = new LinkedHashMap<>(); - - for (Double k : keys) { - - String localCoordinateString; - - double tempX = returnMap.get(k).getX(); - double tempY = returnMap.get(k).getY(); - double tempZ = returnMap.get(k).getZ(); - - localCoordinateString = " " + coordinateNumberFormat.format(tempX) + " " + coordinateNumberFormat.format(tempY) + " " + coordinateNumberFormat.format(tempZ); - - keyKeyValueSetPositionInterpolator.put(k / lastTimeStamp, localCoordinateString); - - } - - keys = keyKeyValueSetPositionInterpolator.keySet(); - - - - //Setting up PositionInterpolator and OrientationInterpolator - for (Double k : keys) - lineSetPoints += keyKeyValueSetPositionInterpolator.get(k) + " "; - - lineSetPoints += "' "; - - //PositionInterpolator - - lineSet += lineSetPoints; - lineSet += "/>\n</LineSet> \n"; - - //Printing PositionInterpolator to the console - System.out.println(lineSet); - } - -} +package edu.nps.moves.dis7.utilities.stream; + +import edu.nps.moves.dis7.pdus.EntityStatePdu; +import edu.nps.moves.dis7.pdus.Pdu; +import edu.nps.moves.dis7.enumerations.DisPduType; +import edu.nps.moves.dis7.utilities.PduFactory; +import java.nio.ByteBuffer; +import java.text.NumberFormat; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author Tobias Brennenstuhl @ NPS + */ +public class X3dCreateLineSet { + + private byte[] bufferShort; + + // -------------------- Begin Variables for Position Interpolator + private Boolean firstTimeStamp = true; + private int firstLocalTimeStamp = 0; + + private double firstLocalX = 0; + private double firstLocalY = 0; + private double firstLocalZ = 0; + + private Map<Double, X3dCoordinates> testMap = new LinkedHashMap<>(); + + //Setting up a NumberFormatter for limitting the decimal count to 3 + private NumberFormat coordinateNumberFormat = NumberFormat.getInstance(new Locale("en", "US")); + + // -------------------- End Variables for Position Interpolator + + /** Constructor */ + public X3dCreateLineSet() + { + //3 significant digits equals milimeter position accuracy and 0.001 radians = 0.0572963266634555‬ degrees + coordinateNumberFormat.setMaximumFractionDigits(3); + } + + public void addPointsToMap(byte[] localBufferShort) + { + this.bufferShort = localBufferShort.clone(); + + if (bufferShort[2] == 1) { + + //PDU Factory + PduFactory pduFactory = new PduFactory(); + Pdu localPdu = pduFactory.createPdu(bufferShort); + + // ToDO figure out how to do this! makeEntityStatePDU + EntityStatePdu localEspdu = pduFactory.makeEntityStatePdu(); + //Put all the data we need into the localEspdu + ByteBuffer espduByteBuffer = ByteBuffer.wrap(bufferShort); + try { + localEspdu.unmarshal(espduByteBuffer); + } catch (Exception ex) { + Logger.getLogger(X3dCreateLineSet.class.getName()).log(Level.SEVERE, null, ex); + } + + double localTimeStamp; + double localX; + double localY; + double localZ; + + //TimeStamps for a lineSet is not needed but copied from X3DInterpolators to use them as key for the hashmap + // and the standard compression class can be used + //Store the first timestamp to subtract it from all others + //Same with X,Y,Z to create a local coordiante system + if (firstTimeStamp) { + + firstLocalTimeStamp = localPdu.getTimestamp(); + firstLocalX = localEspdu.getEntityLocation().getX(); + firstLocalY = localEspdu.getEntityLocation().getZ(); + firstLocalZ = -1 * localEspdu.getEntityLocation().getY(); + + firstTimeStamp = false; + } + + localTimeStamp = localPdu.getTimestamp(); + localX = localEspdu.getEntityLocation().getX(); + localY = localEspdu.getEntityLocation().getZ(); + localZ = -1 * localEspdu.getEntityLocation().getY(); + + //Debug for printing X,Y,Z + //System.out.println(localX + " " + localY + " " + localZ); + localTimeStamp = localTimeStamp - firstLocalTimeStamp; + localX = localX - firstLocalX; + localY = localY - firstLocalY; + localZ = localZ - firstLocalZ; + + //Divide TimeStamp by 1,300,000 to get something close to a second per Unit. + //According to the DIS standard one tick is 3600/(2^31) seconds ~ 1.6764 µs + //1,300,000 was derived from a stream that is 61 seconds long. The number was adjusted to get a timesensor with 61 seconds + //ToDo find the real conversion between TimeStampDelta and seconds + localTimeStamp = localTimeStamp / 1300000; + + //Only add to stream if it is an ESPDU + //ToDo: Add support for multiple Entities + if ((localPdu.getPduType() != null) && (localPdu.getPduType() == DisPduType.ENTITY_STATE)) + testMap.put(localTimeStamp, new X3dCoordinates(localX, localY, localZ, 0.0, 0.0, 0.0)); + } + } + + public void makeX3dLineSet() { + + //Compression of the testMap. + //Remove all collinear points. + X3dSlidingWindowCompression regression = new X3dSlidingWindowCompression(testMap); + + //To turn of the compression just comment the next line out and the very next in. + Map<Double, X3dCoordinates> returnMap = regression.doSlidingWindow(); + //returnMap.putAll(testMap); + + //Writing all values from the KeyMap to a proper Position Interpolator String + System.out.println("Writing X3D LineSet"); + Set<Double> keys = returnMap.keySet(); + String lineSetPoints = ""; + String lineSet = "<LineSet vertexCount='" + returnMap.size() + "'> \n <Coordinate point='"; + + //Find highest time to do the normalization + double lastTimeStamp = 0; + + for (Double k : keys) { + + if (k > lastTimeStamp) + lastTimeStamp = k; + } + + //Normalize all times in the set + Map<Double, String> keyKeyValueSetPositionInterpolator = new LinkedHashMap<>(); + + for (Double k : keys) { + + String localCoordinateString; + + double tempX = returnMap.get(k).getX(); + double tempY = returnMap.get(k).getY(); + double tempZ = returnMap.get(k).getZ(); + + localCoordinateString = " " + coordinateNumberFormat.format(tempX) + " " + coordinateNumberFormat.format(tempY) + " " + coordinateNumberFormat.format(tempZ); + + keyKeyValueSetPositionInterpolator.put(k / lastTimeStamp, localCoordinateString); + + } + + keys = keyKeyValueSetPositionInterpolator.keySet(); + + + + //Setting up PositionInterpolator and OrientationInterpolator + for (Double k : keys) + lineSetPoints += keyKeyValueSetPositionInterpolator.get(k) + " "; + + lineSetPoints += "' "; + + //PositionInterpolator + + lineSet += lineSetPoints; + lineSet += "/>\n</LineSet> \n"; + + //Printing PositionInterpolator to the console + System.out.println(lineSet); + } + +}