diff --git a/src/edu/nps/moves/dis7/examples/ClassNameComparator.java b/src/edu/nps/moves/dis7/examples/ClassNameComparator.java
new file mode 100644
index 0000000000000000000000000000000000000000..3fb5838687ad425a11bf1353f1e71804cfa47ad8
--- /dev/null
+++ b/src/edu/nps/moves/dis7/examples/ClassNameComparator.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2008-2019, MOVES Institute, Naval Postgraduate School. All rights reserved.
+ * This work is licensed under the BSD open source license, available at https://www.movesinstitute.org/licenses/bsd.html
+ */
+
+package edu.nps.moves.dis7.examples;
+
+import edu.nps.moves.dis7.Pdu;
+import java.util.Comparator;
+
+/**
+ * A comparator that is used by the java util classes for sorting. This
+ * sorts members of a collection by class name.
+ * 
+ * Used like so:
+ * 
+ * Collections.sort(aList, new ClassNameComparator());
+ *
+ * @author DMcG
+ * @version $Id:$
+ */
+public class ClassNameComparator implements Comparator<Pdu> {
+
+    /**
+     * Returns a number less than, equal to, or greater than zero,
+     * depending on whether the object is lexically less than, equal to,
+     * or greater than the other object.
+     * @param object1
+     * @param object2
+     */
+    public int compare(Pdu object1, Pdu object2) {
+        return object1.getClass().getName().compareTo(object2.getClass().getName());
+    }
+
+    /**
+     * Returns true if this comparator is the same class as the comparator passed in.
+     * 
+     * @param obj
+     */
+    @Override
+    public boolean equals(Object obj) {
+        return obj.getClass().equals(this.getClass());
+    }
+
+    @Override
+    public int hashCode() {
+        return 3;
+    }
+}
\ No newline at end of file
diff --git a/src/edu/nps/moves/dis7/examples/EspduReceiver.java b/src/edu/nps/moves/dis7/examples/EspduReceiver.java
new file mode 100644
index 0000000000000000000000000000000000000000..f99fcf22842fc5d976196541c149945202a16aa3
--- /dev/null
+++ b/src/edu/nps/moves/dis7/examples/EspduReceiver.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2008-2019, MOVES Institute, Naval Postgraduate School. All rights reserved.
+ * This work is licensed under the BSD open source license, available at https://www.movesinstitute.org/licenses/bsd.html
+ */
+
+package edu.nps.moves.dis7.examples;
+
+import edu.nps.moves.dis7.EntityID;
+import edu.nps.moves.dis7.EntityStatePdu;
+import edu.nps.moves.dis7.Pdu;
+import edu.nps.moves.dis7.Vector3Double;
+import edu.nps.moves.dis7.util.PduFactory;
+import java.net.DatagramPacket;
+import java.net.MulticastSocket;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Receives PDUs from the network in IEEE format.
+ * <p>
+ * This is legacy code ported to the edu.nps.moves.dis7 package
+ *
+ * @author DMcG
+ * @author Mike Bailey
+ */
+public class EspduReceiver
+{
+  /**
+   * Max size of a PDU in binary format that we can receive. This is actually
+   * somewhat outdated--PDUs can be larger--but this is a reasonable starting point
+   */
+  public static final int MAX_PDU_SIZE = 8192;
+
+  public static void main(String args[])
+  {
+    MulticastSocket socket;
+    DatagramPacket packet;
+    PduFactory pduFactory = new PduFactory();
+
+    try {
+      // Specify the socket to receive data
+      socket = new MulticastSocket(3000);
+      socket.setBroadcast(true);
+
+      //InetAddress address = InetAddress.getByName(EspduSender.DEFAULT_MULTICAST_GROUP);
+      //socket.joinGroup(address);
+      // Loop infinitely, receiving datagrams
+      while (true) {
+        byte buffer[] = new byte[MAX_PDU_SIZE];
+
+        packet = new DatagramPacket(buffer, buffer.length);
+
+        socket.receive(packet);
+
+        List<Pdu> pduBundle = pduFactory.getPdusFromBundle(packet.getData(),packet.getLength());
+        //System.out.println("Bundle size is " + pduBundle.size());
+
+        Iterator it = pduBundle.iterator();
+
+        while (it.hasNext()) {
+          Pdu aPdu = (Pdu) it.next();
+
+          System.out.print("got PDU of type: " + aPdu.getClass().getName());
+          if (aPdu instanceof EntityStatePdu) {
+            EntityID eid = ((EntityStatePdu) aPdu).getEntityID();
+            Vector3Double position = ((EntityStatePdu) aPdu).getEntityLocation();
+            System.out.print(" EID:[" + eid.getSiteID() + ", " + eid.getApplicationID() + ", " + eid.getEntityID() + "] ");
+            System.out.print(" Location in DIS coordinates: [" + position.getX() + ", " + position.getY() + ", " + position.getZ() + "]");
+          }
+          System.out.println();
+        } // end trop through PDU bundle
+      } // end while
+    } // End try
+    catch (Exception e) {
+      System.out.println(e);
+    }
+  } // end main
+} // end class
diff --git a/src/edu/nps/moves/dis7/examples/EspduReceiverNIO.java b/src/edu/nps/moves/dis7/examples/EspduReceiverNIO.java
new file mode 100644
index 0000000000000000000000000000000000000000..0cd479fdc934f0d7f90896ad5d57c5d17d99159e
--- /dev/null
+++ b/src/edu/nps/moves/dis7/examples/EspduReceiverNIO.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2008-2019, MOVES Institute, Naval Postgraduate School. All rights reserved.
+ * This work is licensed under the BSD open source license, available at https://www.movesinstitute.org/licenses/bsd.html
+ */
+package edu.nps.moves.dis7.examples;
+
+import edu.nps.moves.dis7.Pdu;
+import edu.nps.moves.dis7.util.PduFactory;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.nio.ByteBuffer;
+
+/**
+ * Receives PDUs from the network in IEEE format. Very similar to EspduReciver, but this
+ * uses Robert Harder's more memory-efficient NIO code.
+ *
+ * @author DMcG rharder
+ */
+public class EspduReceiverNIO
+{
+  /**
+   * Max size of a PDU in binary format that we can receive. This is actually
+   * somewhat outdated--PDUs can be larger--but this is a reasonable starting point
+   * <p>
+   * This is legacy code ported to the edu.nps.moves.dis7 package
+   */
+  public static final int MAX_PDU_SIZE = 8192; // This has actually been superceded by a larger buffer size, but good enough for now
+
+  public static void main(String args[])
+  {
+    MulticastSocket socket;
+    DatagramPacket packet;
+    InetAddress address;
+    PduFactory pduFactory = new PduFactory();
+
+    try {
+      // Specify the socket to receive data
+      socket = new MulticastSocket(EspduSender.DIS_DESTINATION_PORT);
+      address = InetAddress.getByName(EspduSender.DEFAULT_MULTICAST_GROUP);
+      socket.joinGroup(address);
+
+      // Loop infinitely, receiving datagrams
+      while (true) {
+        byte buffer[] = new byte[MAX_PDU_SIZE];
+        packet = new DatagramPacket(buffer, buffer.length);
+
+        socket.receive(packet);
+
+        // Uses the NIO byte buffer class--wrap a ByteBuffer instance around
+        // the data we get from the packet
+        ByteBuffer byteBuffer = ByteBuffer.wrap(packet.getData());
+        Pdu pdu = pduFactory.createPdu(byteBuffer);
+
+        System.out.println("got PDU of type: " + pdu.getClass().getSimpleName());
+
+      } // end while
+    } // End try
+    catch (Exception e) {
+      System.out.println(e);
+    }
+
+  } // end main
+} // end class
diff --git a/src/edu/nps/moves/dis7/examples/EspduSender.java b/src/edu/nps/moves/dis7/examples/EspduSender.java
new file mode 100644
index 0000000000000000000000000000000000000000..706d1ab636c01eb4e5c4a8fb129dac273447a2e5
--- /dev/null
+++ b/src/edu/nps/moves/dis7/examples/EspduSender.java
@@ -0,0 +1,313 @@
+/**
+ * Copyright (c) 2008-2019, MOVES Institute, Naval Postgraduate School. All rights reserved.
+ * This work is licensed under the BSD open source license, available at https://www.movesinstitute.org/licenses/bsd.html
+ */
+
+package edu.nps.moves.dis7.examples;
+
+import edu.nps.moves.dis7.*;
+import edu.nps.moves.dis7.util.CoordinateConversions;
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+/**
+ * Creates and sends ESPDUs in IEEE binary format.
+ * <p>
+ * This is legacy code ported to the edu.nps.moves.dis7 package
+ *
+ * @author DMcG
+ * @author Mike Bailey
+ */
+public class EspduSender
+{
+  public static final int NUMBER_TO_SEND = 5000;
+
+  public enum NetworkMode
+  {
+    UNICAST, MULTICAST, BROADCAST
+  };
+
+  /**
+   * Default multicast group address we send on
+   */
+  public static final String DEFAULT_MULTICAST_GROUP = "239.1.2.3";
+
+  /**
+   * Default port we send on
+   */
+  public static final int DIS_DESTINATION_PORT = 3000;
+
+  /**
+   * Possible system properties, passed in via -Dattr=val
+   * networkMode: unicast, broadcast, multicast
+   * destinationIp: where to send the packet. If in multicast mode, this can be multicast.
+   * To determine broadcast destination IP, use an online broadcast address
+   * calculator, for example http://www.remotemonitoringsystems.ca/broadcast.php
+   * If in multicast mode, a join() will be done on the multicast address.
+   * port: port used for both source and destination.
+   *
+   * @param args
+   */
+  public static void main(String args[])
+  {
+    /**
+     * an entity state pdu
+     */
+    EntityStatePdu espdu = new EntityStatePdu();
+    MulticastSocket socket = null; // must be initialized, even if null
+    DisTime disTime = new DisTime();
+
+    // ICBM coordinates for my office
+    double lat = 36.595517;
+    double lon = -121.877000;
+
+    // Default settings. These are used if no system properties are set. 
+    // If system properties are passed in, these are over ridden.
+    int port = DIS_DESTINATION_PORT;
+    NetworkMode mode = NetworkMode.BROADCAST;
+    InetAddress destinationIp = null; // must be initialized, even if null
+
+    try {
+      destinationIp = InetAddress.getByName(DEFAULT_MULTICAST_GROUP);
+    }
+    catch (UnknownHostException e) {
+      System.out.println(e + " Cannot create multicast address");
+      System.exit(0);
+    }
+
+    // All system properties, passed in on the command line via -Dattribute=value
+    Properties systemProperties = System.getProperties();
+
+    // IP address we send to
+    String destinationIpString = systemProperties.getProperty("destinationIp");
+
+    // Port we send to, and local port we open the socket on
+    String portString = systemProperties.getProperty("port");
+
+    // Network mode: unicast, multicast, broadcast
+    String networkModeString = systemProperties.getProperty("networkMode"); // unicast or multicast or broadcast
+
+    // Set up a socket to send information
+    try {
+      // Port we send to
+      if (portString != null)
+        port = Integer.parseInt(portString);
+
+      socket = new MulticastSocket(port);
+
+      // Where we send packets to, the destination IP address
+      if (destinationIpString != null) {
+        destinationIp = InetAddress.getByName(destinationIpString);
+      }
+
+      // Type of transport: unicast, broadcast, or multicast
+      // TODO convert to String constants
+      if (networkModeString != null) {
+        if (networkModeString.equalsIgnoreCase("unicast"))
+          mode = NetworkMode.UNICAST;
+        else if (networkModeString.equalsIgnoreCase("broadcast"))
+          mode = NetworkMode.BROADCAST;
+        else if (networkModeString.equalsIgnoreCase("multicast")) {
+          mode = NetworkMode.MULTICAST;
+          if (!destinationIp.isMulticastAddress()) {
+            throw new RuntimeException("Sending to multicast address, but destination address " + destinationIp.toString() + "is not multicast");
+          }
+
+          socket.joinGroup(destinationIp);
+        }
+      } // end networkModeString
+    }
+    catch (IOException | RuntimeException e) {
+      System.out.println("Unable to initialize networking. Exiting.");
+      System.out.println(e);
+      System.exit(-1);
+    }
+
+    // Initialize values in the Entity State PDU object. The exercise ID is 
+    // a way to differentiate between different virtual worlds on one network.
+    // Note that some values (such as the PDU type and PDU family) are set
+    // automatically when you create the ESPDU.
+    espdu.setExerciseID((byte) 1);
+
+    // The EID is the unique identifier for objects in the world. This 
+    // EID should match up with the ID for the object specified in the 
+    // VMRL/x3d/virtual world.
+    EntityID entityID = espdu.getEntityID();
+    entityID.setSiteID((short) 1);  // 0 is apparently not a valid site number, per the spec
+    entityID.setApplicationID((short) 1);
+    entityID.setEntityID((short) 2);
+
+    // Set the entity type. SISO has a big list of enumerations, so that by
+    // specifying various numbers we can say this is an M1A2 American tank,
+    // the USS Enterprise, and so on. We'll make this a tank. There is a 
+    // separate project elsehwhere in this project that implements DIS 
+    // enumerations in C++ and Java, but to keep things simple we just use
+    // numbers here.
+    // Manual method:
+    /*
+    EntityType entityType = espdu.getEntityType();
+    entityType.setEntityKind(EntityKind.PLATFORM);      // Platform (vs lifeform, munition, sensor, etc.)
+    entityType.setCountry(Country.UNITED_STATES_OF_AMERICA_USA); 
+    entityType.setDomain(Domain.inst(PlatformDomain.LAND));          // Land (vs air, surface, subsurface, space)
+    entityType.setCategory((byte)1);        // Tank
+    entityType.setSubCategory((byte)1);     // M1 Abrams
+    entityType.setSpecific((byte)3);            // M1A2 Abrams
+     */
+    // Using entitytype jar
+    espdu.setEntityType(new edu.nps.moves.dis7.entities.usa.platform.land.M1A2());
+
+    Set<InetAddress> broadcastAddresses;
+    // Loop through sending N ESPDUs
+    try {
+      System.out.println("Sending " + NUMBER_TO_SEND + " ESPDU packets to " + destinationIp.toString());
+      for (int idx = 0; idx < NUMBER_TO_SEND; idx++) {
+        // DIS time is a pain in the ass. DIS time units are 2^31-1 units per
+        // hour, and time is set to DIS time units from the top of the hour. 
+        // This means that if you start sending just before the top of the hour
+        // the time units can roll over to zero as you are sending. The receivers
+        // (escpecially homegrown ones) are often not able to detect rollover
+        // and may start discarding packets as dupes or out of order. We use
+        // an NPS timestamp here, hundredths of a second since the start of the
+        // year. The DIS standard for time is often ignored in the wild; I've seen
+        // people use Unix time (seconds since 1970) and more. Or you can
+        // just stuff idx into the timestamp field to get something that is monotonically
+        // increasing.
+
+        // Note that timestamp is used to detect duplicate and out of order packets. 
+        // That means if you DON'T change the timestamp, many implementations will simply
+        // discard subsequent packets that have an identical timestamp. Also, if they
+        // receive a PDU with an timestamp lower than the last one they received, they
+        // may discard it as an earlier, out-of-order PDU. So it is a good idea to
+        // update the timestamp on ALL packets sent.
+        // An alterative approach: actually follow the standard. It's a crazy concept,
+        // but it might just work.
+        int timestamp = disTime.getDisAbsoluteTimestamp();
+        espdu.setTimestamp(timestamp);
+
+        // Set the position of the entity in the world. DIS uses a cartesian 
+        // coordinate system with the origin at the center of the earth, the x
+        // axis out at the equator and prime meridian, y out at the equator and
+        // 90 deg east, and z up and out the north pole. To place an object on
+        // the earth's surface you also need a model for the shape of the earth
+        // (it's not a sphere.) All the fancy math necessary to do this is in
+        // the SEDRIS SRM package. There are also some one-off formulas for 
+        // doing conversions from, for example, lat/lon/altitude to DIS coordinates.
+        // Here we use those one-off formulas.
+        // Modify the position of the object. This will send the object a little
+        // due east by adding some to the longitude every iteration. Since we
+        // are on the Pacific coast, this sends the object east. Assume we are
+        // at zero altitude. In other worlds you'd use DTED to determine the
+        // local ground altitude at that lat/lon, or you'd just use ground clamping.
+        // The x and y values will change, but the z value should not.
+        //lon = lon + (double)((double)idx / 100000.0);
+        //System.out.println("lla=" + lat + "," + lon + ", 0.0");
+        double direction = Math.pow((double) (-1.0), (double) (idx));
+        lon = lon + (direction * 0.00006);
+        System.out.println(lon);
+
+        double disCoordinates[] = CoordinateConversions.getXYZfromLatLonDegrees(lat, lon, 1.0);
+        Vector3Double location = espdu.getEntityLocation();
+        location.setX(disCoordinates[0]);
+        location.setY(disCoordinates[1]);
+        location.setZ(disCoordinates[2]);
+        System.out.println("lat, lon:" + lat + ", " + lon);
+        System.out.println("DIS coord:" + disCoordinates[0] + ", " + disCoordinates[1] + ", " + disCoordinates[2]);
+
+        // Optionally, we can do some rotation of the entity
+        /*
+            Orientation orientation = espdu.getEntityOrientation();
+            float psi = orientation.getPsi();
+            psi = psi + idx;
+            orientation.setPsi(psi);
+            orientation.setTheta((float)(orientation.getTheta() + idx /2.0));
+         */
+        // You can set other ESPDU values here, such as the velocity, acceleration,
+        // and so on.
+        // Marshal out the espdu object to a byte array, then send a datagram
+        // packet with that data in it.
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        DataOutputStream dos = new DataOutputStream(baos);
+        espdu.marshal(dos);
+
+        FirePdu fire = new FirePdu();
+        byte[] fireArray = fire.marshal();
+
+        // The byte array here is the packet in DIS format. We put that into a 
+        // datagram and send it.
+        byte[] data = baos.toByteArray();
+
+        broadcastAddresses = getBroadcastAddresses();
+        Iterator it = broadcastAddresses.iterator();
+        while (it.hasNext()) {
+          InetAddress broadcast = (InetAddress) it.next();
+          System.out.println("Sending broadcast datagram packet to " + broadcast);
+          DatagramPacket packet = new DatagramPacket(data, data.length, broadcast, 3000);
+          socket.send(packet);
+          // TODO experiment with these!  8)
+          packet = new DatagramPacket(fireArray, fireArray.length, broadcast, 3000); // alternate
+          socket.send(packet);
+        }
+
+        // Send every 1 sec. Otherwise this will be all over in a fraction of a second.
+        Thread.sleep(3000);
+
+        location = espdu.getEntityLocation();
+
+        System.out.println("Espdu #" + idx + " EID=[" + entityID.getSiteID() + "," + entityID.getApplicationID() + "," + entityID.getEntityID() + "]");
+        System.out.println(" DIS coordinates location=[" + location.getX() + "," + location.getY() + "," + location.getZ() + "]");
+        double c[] = {location.getX(), location.getY(), location.getZ()};
+        double lla[] = CoordinateConversions.xyzToLatLonDegrees(c);
+//      System.out.println(" Location (lat/lon/alt): [" + lla[0] + ", " + lla[1] + ", " + lla[2] + "]");
+      }
+    }
+    catch (Exception ex) {
+      System.out.println(ex);
+    }
+  }
+
+  /**
+   * A number of sites get all snippy about using 255.255.255.255 for a broadcast
+   * address; it trips their security software and they kick you off their
+   * network. (Comcast, NPS.) This determines the broadcast address for all
+   * connected interfaces, based on the IP and subnet mask. If you have
+   * a dual-homed host it will return a broadcast address for both. If you have
+   * some VMs running on your host this will pick up the addresses for those
+   * as well--eg running VMWare on your laptop with a local IP this will
+   * also pick up a 192.168 address assigned to the VM by the host OS.
+   *
+   * @return set of all broadcast addresses
+   */
+  public static Set<InetAddress> getBroadcastAddresses()
+  {
+    Set<InetAddress> broadcastAddresses = new HashSet<>();
+    Enumeration interfaces;
+
+    try {
+      interfaces = NetworkInterface.getNetworkInterfaces();
+      while (interfaces.hasMoreElements()) {
+        NetworkInterface anInterface = (NetworkInterface) interfaces.nextElement();
+
+        if (anInterface.isUp()) {
+          Iterator it = anInterface.getInterfaceAddresses().iterator();
+          while (it.hasNext()) {
+            InterfaceAddress anAddress = (InterfaceAddress) it.next();
+            if ((anAddress == null || anAddress.getAddress().isLinkLocalAddress()))
+              continue;
+
+            //System.out.println("Getting broadcast address for " + anAddress);
+            InetAddress broadcastAddress = anAddress.getBroadcast();
+            if (broadcastAddress != null)
+              broadcastAddresses.add(broadcastAddress);
+          }
+        }
+      }
+    }
+    catch (SocketException e) {
+      e.printStackTrace();
+      System.out.println(e);
+    }
+    return broadcastAddresses;
+  }
+
+}
diff --git a/src/edu/nps/moves/dis7/examples/EspduSenderNIO.java b/src/edu/nps/moves/dis7/examples/EspduSenderNIO.java
new file mode 100644
index 0000000000000000000000000000000000000000..69f354f3b8decfc1f403990ffe032a6f4b601d9d
--- /dev/null
+++ b/src/edu/nps/moves/dis7/examples/EspduSenderNIO.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2008-2019, MOVES Institute, Naval Postgraduate School. All rights reserved.
+ * This work is licensed under the BSD open source license, available at https://www.movesinstitute.org/licenses/bsd.html
+ */
+package edu.nps.moves.dis7.examples;
+
+import edu.nps.moves.dis7.EntityID;
+import edu.nps.moves.dis7.EntityStatePdu;
+import edu.nps.moves.dis7.EulerAngles;
+import edu.nps.moves.dis7.Vector3Double;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+
+/**
+ * Creates and sends ESPDUs in IEEE binary format. very similar to EspduSender.java, but
+ * this uses Robert Harder's more memory efficient NIO methods for marshalling.
+ * <p>
+ * This is legacy code ported to the edu.nps.moves.dis7 package
+ *
+ * @author DMcG
+ */
+public class EspduSenderNIO
+{
+  /**
+   * multicast group we send on
+   */
+  public static final String MULTICAST_GROUP = "239.1.2.3";
+
+  /**
+   * Port we send on
+   */
+  public static final int PORT = 3000;
+
+  public static void main(String args[])
+  {
+    EntityStatePdu espdu = new EntityStatePdu();
+    MulticastSocket socket;
+    InetAddress address;
+
+    espdu.setExerciseID((byte) 0);
+
+    // The EID is the unique identifier for objects in the world. This 
+    // EID should match up with the ID for the object specified in the 
+    // VMRL/x3d world.
+    EntityID eid = espdu.getEntityID();
+    eid.setSiteID((short) 1); // 0 is apparently not a valid site number
+    eid.setApplicationID((short) 1);
+    eid.setEntityID((short) 2);
+
+    try {
+      socket = new MulticastSocket(PORT);
+      address = InetAddress.getByName(MULTICAST_GROUP);
+      socket.joinGroup(address);
+
+      while (true) {
+        for (int idx = 0; idx < 100; idx++) {
+          // The timestamp should be monotonically increasing. Many implementations
+          // discard packets that have earlier timestamps (assumption is that it
+          // arrived out of order) or non-increasing timestamp (dupe packet).
+          // The time should be slaved to clock time, so we can determine the time
+          // between packets, but this is the minimum for testing.
+          int timestamp = espdu.getTimestamp();
+          timestamp++;
+          espdu.setTimestamp(timestamp);
+
+          // Modify the x-axis position of the object
+          Vector3Double location = espdu.getEntityLocation();
+          location.setX(idx);
+          location.setY(idx);
+
+          // Do some rotation to make sure that works
+          EulerAngles orientation = espdu.getEntityOrientation();
+          float psi = orientation.getPsi();
+          psi = psi + idx;
+          orientation.setPsi(psi);
+          orientation.setTheta((float) (orientation.getTheta() + idx / 2.0));
+
+          // Marshal out the object to a byte array, then send a datagram
+          // packet with that data in it. This uses Robert Harder's NIO
+          // code for marshalling.
+          byte data[] = espdu.marshal();
+          DatagramPacket packet = new DatagramPacket(data, data.length, address, PORT);
+
+          socket.send(packet);
+
+          // Almost any sender will overwhelm a receiver if not constrained. This
+          // slows down the send rate so the receiver has enough time to process it
+          Thread.sleep(1000);
+
+          System.out.println("Sending espdu");
+        }
+      }
+    }
+    catch (Exception e) {
+      System.out.println(e);
+    }
+  }
+
+}
diff --git a/src/edu/nps/moves/dis7/examples/PduSender.java b/src/edu/nps/moves/dis7/examples/PduSender.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe9ec05502087bfdbaa4dfbd66f2dc46080809f5
--- /dev/null
+++ b/src/edu/nps/moves/dis7/examples/PduSender.java
@@ -0,0 +1,332 @@
+package edu.nps.moves.dis7.examples;
+
+import edu.nps.moves.dis7.*;
+import edu.nps.moves.dis7.enumerations.DISPDUType;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This is an example that sends many/most types of PDUs. Useful for testing standards
+ * compliance or getting a full set of PDUs. It also writes the generated PDUs to
+ * an XML file.
+ *<p>
+ * This is legacy code ported to the edu.nps.moves.dis7 package
+ * 
+ * @author DMcG
+ * @author Mike Bailey
+ */
+public class PduSender
+{
+
+  public static final int PORT = 3000;
+  public static final String MULTICAST_ADDRESS = "239.1.2.3";
+  private int port;
+  InetAddress multicastAddress;
+
+  public PduSender(int port, String multicast)
+  {
+    try {
+      this.port = port;
+      multicastAddress = InetAddress.getByName(multicast);
+      if (!multicastAddress.isMulticastAddress()) {
+        System.out.println("Not a multicast address: " + multicast);
+      }
+    }
+    catch (UnknownHostException e) {
+      System.out.println("Unable to open socket: " + e);
+    }
+  }
+
+  public void run()
+  {
+    try {
+      List<Pdu> generatedPdus = new ArrayList<>();
+
+      // 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()) {
+        Pdu aPdu = null;
+
+        switch (pdu) {
+
+          case ACKNOWLEDGE:
+            aPdu = new AcknowledgePdu();
+            break;
+          case ACKNOWLEDGE_RELIABLE:
+            aPdu = new AcknowledgeReliablePdu();
+            break;
+          case ACTION_REQUEST:
+            aPdu = new ActionRequestPdu();
+            break;
+          case ACTION_REQUEST_RELIABLE:
+            aPdu = new ActionRequestReliablePdu();
+            break;
+          case ACTION_RESPONSE:
+            aPdu = new ActionResponsePdu();
+            break;
+          case ACTION_RESPONSE_RELIABLE:
+            aPdu = new ActionResponseReliablePdu();
+            break;
+          case AGGREGATE_STATE:
+            aPdu = new AggregateStatePdu();
+            break;
+          case APPEARANCE:
+            aPdu = new AppearancePdu();
+            break;
+          case AREAL_OBJECT_STATE:
+            aPdu = new ArealObjectStatePdu();
+            break;
+          case ARTICULATED_PARTS:
+            aPdu = new ArticulatedPartsPdu();
+            break;
+          case ATTRIBUTE:
+            aPdu = new AttributePdu();
+            break;
+          case COLLISION:
+            aPdu = new CollisionPdu();
+            break;
+          case COLLISION_ELASTIC:
+            aPdu = new CollisionElasticPdu();
+            break;
+          case COMMENT:
+            aPdu = new CommentPdu();
+            break;
+          case COMMENT_RELIABLE:
+            aPdu = new CommentReliablePdu();
+            break;
+          case CREATE_ENTITY:
+            aPdu = new CreateEntityPdu();
+            break;
+          case CREATE_ENTITY_RELIABLE:
+            aPdu = new CreateEntityReliablePdu();
+            break;
+          case DATA:
+            aPdu = new DataPdu();
+            break;
+          case DATA_QUERY:
+            aPdu = new DataQueryPdu();
+            break;
+          case DATA_QUERY_RELIABLE:
+            aPdu = new DataQueryReliablePdu();
+            break;
+          case DATA_RELIABLE:
+            aPdu = new DataReliablePdu();
+            break;
+          case DESIGNATOR:
+            aPdu = new DesignatorPdu();
+            break;
+          case DETONATION:
+            aPdu = new DetonationPdu();
+            break;
+          case DIRECTED_ENERGY_FIRE:
+            aPdu = new DirectedEnergyFirePdu();
+            break;
+          case ELECTROMAGNETIC_EMISSION:
+            aPdu = new ElectromagneticEmissionPdu();
+            break;
+          case ENTITY_DAMAGE_STATUS:
+            aPdu = new EntityDamageStatusPdu();
+            break;
+          case ENTITY_STATE:
+            aPdu = new EntityStatePdu();
+            break;
+          case ENTITY_STATE_UPDATE:
+            aPdu = new EntityStateUpdatePdu();
+            break;
+          case ENVIRONMENTAL_PROCESS:
+            aPdu = new EnvironmentalProcessPdu();
+            break;
+          case EVENT_REPORT:
+            aPdu = new EventReportPdu();
+            break;
+          case EVENT_REPORT_RELIABLE:
+            aPdu = new EventReportReliablePdu();
+            break;
+          case FIRE:
+            aPdu = new FirePdu();
+            break;
+          case GRIDDED_DATA:
+            aPdu = new GriddedDataPdu();
+            break;
+          case IDENTIFICATION_FRIEND_OR_FOE:
+            aPdu = new IdentificationFriendOrFoePdu(); // alternatively, aPdu = new IFFPdu();
+            break;
+          case INFORMATION_OPERATIONS_ACTION:
+            aPdu = new InformationOperationsActionPdu();
+            break;
+          case INFORMATION_OPERATIONS_REPORT:
+            aPdu = new InformationOperationsReportPdu();
+            break;
+          case INTERCOM_CONTROL:
+            aPdu = new IntercomControlPdu();
+            break;
+          case INTERCOM_SIGNAL:
+            aPdu = new IntercomSignalPdu();
+            break;
+          case ISGROUPOF:
+            aPdu = new IsGroupOfPdu();
+            break;
+          case ISPARTOF:
+            aPdu = new IsPartOfPdu();
+            break;
+          case LINEAR_OBJECT_STATE:
+            aPdu = new LinearObjectStatePdu();
+            break;
+          case LIVE_ENTITY_DETONATION:
+            aPdu = new LiveEntityDetonationPdu();
+            break;
+          case LIVE_ENTITY_FIRE:
+            aPdu = new LiveEntityFirePdu();
+            break;
+          case MINEFIELD_DATA:
+            aPdu = new MinefieldDataPdu();
+            break;
+          case MINEFIELD_QUERY:
+            aPdu = new MinefieldQueryPdu();
+            break;
+          case MINEFIELD_RESPONSE_NACK:
+            aPdu = new MinefieldResponseNACKPdu();
+            break;
+          case MINEFIELD_STATE:
+            aPdu = new MinefieldStatePdu();
+            break;
+          case POINT_OBJECT_STATE:
+            aPdu = new PointObjectStatePdu();
+            break;
+          case RECEIVER:
+            aPdu = new ReceiverPdu();
+            break;
+          case RECORD_QUERY_RELIABLE:
+            aPdu = new RecordQueryReliablePdu();
+            break;
+          case RECORD_RELIABLE:
+            aPdu = new RecordReliablePdu();
+            break;
+          case REMOVE_ENTITY:
+            aPdu = new RemoveEntityPdu();
+            break;
+          case REMOVE_ENTITY_RELIABLE:
+            aPdu = new RemoveEntityReliablePdu();
+            break;
+          case REPAIR_COMPLETE:
+            aPdu = new RepairCompletePdu();
+            break;
+          case REPAIR_RESPONSE:
+            aPdu = new RepairResponsePdu();
+            break;
+          case RESUPPLY_CANCEL:
+            aPdu = new ResupplyCancelPdu();
+            break;
+          case RESUPPLY_OFFER:
+            aPdu = new ResupplyOfferPdu();
+            break;
+          case RESUPPLY_RECEIVED:
+            aPdu = new ResupplyReceivedPdu();
+            break;
+          case SERVICE_REQUEST:
+            aPdu = new ServiceRequestPdu();
+            break;
+          case SET_DATA:
+            aPdu = new SetDataPdu();
+            break;
+          case SET_DATA_RELIABLE:
+            aPdu = new SetDataReliablePdu();
+            break;
+          case SET_RECORD_RELIABLE:
+            aPdu = new SetRecordReliablePdu();
+            break;
+          case SIGNAL:
+            aPdu = new SignalPdu();
+            break;
+          case START_RESUME:
+            aPdu = new StartResumePdu();
+            break;
+          case START_RESUME_RELIABLE:
+            aPdu = new StartResumeReliablePdu();
+            break;
+          case STOP_FREEZE:
+            aPdu = new StopFreezePdu();
+            break;
+          case STOP_FREEZE_RELIABLE:
+            aPdu = new StopFreezeReliablePdu();
+            break;
+          case SUPPLEMENTAL_EMISSION_ENTITY_STATE:
+            aPdu = new SupplementalEmissionEntityStatePdu(); // alternatively, aPdu = new SEESPdu();
+            break;
+          case TIME_SPACE_POSITION_INFORMATION:
+            aPdu = new TimeSpacePositionInformationPdu(); // alternatively, aPdu = new TSPIPdu();
+            break;
+          case TRANSFER_OWNERSHIP:
+            aPdu = new TransferOwnershipPdu();
+            break;
+          case TRANSMITTER:
+            aPdu = new TransmitterPdu();
+            break;
+          case UNDERWATER_ACOUSTIC:
+            aPdu = new UnderwaterAcousticPdu();
+            break;
+
+          default:
+            System.out.print("PDU of type " + pdu + " not created or sent ");
+            System.out.println();
+        }
+
+        if (aPdu != null) {
+          generatedPdus.add(aPdu);
+        }
+      }
+
+      // Sort the created PDUs by class name
+      Collections.sort(generatedPdus, new ClassNameComparator());
+
+      // Send the PDUs we created
+      InetAddress localMulticastAddress = InetAddress.getByName(MULTICAST_ADDRESS);
+      MulticastSocket socket = new MulticastSocket(PORT);
+      socket.joinGroup(localMulticastAddress);
+
+      for (int idx = 0; idx < generatedPdus.size(); idx++) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        DataOutputStream dos = new DataOutputStream(baos);
+        byte[] buffer;
+
+        Pdu aPdu = generatedPdus.get(idx);
+        aPdu.marshal(dos);
+
+        buffer = baos.toByteArray();
+        DatagramPacket packet = new DatagramPacket(buffer, buffer.length, localMulticastAddress, PORT);
+        socket.send(packet);
+        System.out.println("Sent PDU of type " + aPdu.getClass().getSimpleName() + " ("+aPdu.getPduType().getValue()+")");
+      }
+
+      // write the PDUs out to an XML file.
+      //PduContainer container = new PduContainer();
+      //container.setPdus(generatedPdus);
+      //container.marshallToXml("examplePdus.xml");
+    }
+    catch (IOException e) {
+      System.out.println(e);
+    }
+  }
+
+  public static void main(String args[])
+  {
+    if (args.length == 2) {
+      PduSender sender = new PduSender(Integer.parseInt(args[0]), args[1]);
+      sender.run();
+    }
+    else {
+      System.out.println("Usage:   PduSender <port> <multicast group>");
+      System.out.println("Default: PduSender  " + PORT + "   " + MULTICAST_ADDRESS);
+      PduSender sender = new PduSender(PORT, MULTICAST_ADDRESS);
+      sender.run();
+    }
+  }
+}