diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Fisher/ExampleSimulationProgramFisher.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Fisher/ExampleSimulationProgramFisher.java index b222016da067ac41753403c16edc3e5d5d623a08..e28aed24f90a1ba471c2d00cba46a98fbb0552ac 100644 --- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Fisher/ExampleSimulationProgramFisher.java +++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Fisher/ExampleSimulationProgramFisher.java @@ -4,9 +4,13 @@ package MV3500Cohort2021JulySeptember.homework3.Fisher; /** * 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 - * @author brutzman@nps.edu + * @author adfis + * + * This simulation has two entities moving towards each other and when they + * collide a detonation happens. */ +import edu.nps.moves.dis7.enumerations.ForceID; import edu.nps.moves.dis7.enumerations.VariableRecordType; import edu.nps.moves.dis7.pdus.CommentPdu; import edu.nps.moves.dis7.pdus.EntityID; @@ -62,9 +66,19 @@ public class ExampleSimulationProgramFisher EntityID entityID_1 = new EntityID(); entityID_1.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID; // TODO someday, use enumerations; is there a unique site triplet for MOVES Institute? + + EntityID entityID_2 = new EntityID(); + entityID_2.setSiteID(4).setApplicationID(5).setEntityID(6); EntityStatePdu entityStatePdu_1 = pduFactory.makeEntityStatePdu(); entityStatePdu_1.setEntityID(entityID_1); + entityStatePdu_1.setForceId(ForceID.FRIENDLY); + + EntityStatePdu entityStatePdu_2 = pduFactory.makeEntityStatePdu(); + entityStatePdu_2.setEntityID(entityID_2); + entityStatePdu_2.getEntityLocation().setX(10); + entityStatePdu_2.setForceId(ForceID.OPPOSING); + FirePdu firePdu_1a = pduFactory.makeFirePdu(); // for entity 1 first weapon (if any) FirePdu firePdu_1b = pduFactory.makeFirePdu(); // for entity 1 second weapon (if any) @@ -97,7 +111,7 @@ public class ExampleSimulationProgramFisher // Where is my entity? Insert changes in position; this sample only changes X position. entityStatePdu_1.getEntityLocation().setX(entityStatePdu_1.getEntityLocation().getX() + 1.0); // 1m per timestep - + entityStatePdu_2.getEntityLocation().setX(entityStatePdu_2.getEntityLocation().getX() - 1.0); // decide whether to fire, and then update the firePdu. Hmmm, you might want a target to shoort at! // etc. etc. your code goes here for your simulation of interest @@ -129,7 +143,7 @@ public class ExampleSimulationProgramFisher System.out.println ("sending PDUs for simulation step " + simulationLoopCount + ", monitor loopback to confirm sent"); // Below is the PDU that comes out as "Comment" in the info - sendAllPdusForLoopTimestep(entityStatePdu_1, firePdu_1a, detonationPdu, timeStepComment, narrativeMessage1, narrativeMessage2, narrativeMessage3); + sendAllPdusForLoopTimestep(entityStatePdu_1, entityStatePdu_2, firePdu_1a, timeStepComment, narrativeMessage1, narrativeMessage2, narrativeMessage3); System.out.println ("... [PDUs successfully sent for this loop]"); @@ -137,6 +151,7 @@ public class ExampleSimulationProgramFisher // loop now finished, check whether to terminate if simulation complete, otherwise continue if (simulationComplete || (simulationLoopCount > 10000)) // for example; including fail-safe condition is good { + sendSinglePdu(detonationPdu); System.out.println ("... [Termination condition met, simulationComplete=" + simulationComplete + "]"); // ", final loopCount=" + loopCount + break; } @@ -309,23 +324,27 @@ public class ExampleSimulationProgramFisher * @param commentType enumeration value describing purpose of the narrative comment * @param comments String array of narrative comments */ - public void sendAllPdusForLoopTimestep(EntityStatePdu entityStatePdu, + public void sendAllPdusForLoopTimestep(EntityStatePdu entityStatePdu1, + EntityStatePdu entityStatePdu2, FirePdu firePdu, - DetonationPdu detonationPdu, + //DetonationPdu detonationPdu, VariableRecordType commentType, // vararg... variable-length set of String comments can optionally follow String... comments) { - if (entityStatePdu != null) - sendSinglePdu(entityStatePdu); + if (entityStatePdu1 != null) + sendSinglePdu(entityStatePdu1); + + if (entityStatePdu2 != null) + sendSinglePdu(entityStatePdu2); if (firePdu != null) sendSinglePdu(firePdu); // bang - if (detonationPdu != null) - sendSinglePdu(detonationPdu); - + //if (detonationPdu != null) + // sendSinglePdu(detonationPdu); + // if ((comments != null) && (comments.length > 0)) { ArrayList<String> newCommentsList = new ArrayList<>(); @@ -385,7 +404,7 @@ public class ExampleSimulationProgramFisher thisProgram.runSimulation (); // ... your simulation execution code goes in there ... - pduRecorder.end(); + //pduRecorder.end(); thisProgram.tearDownNetworkInterface(); // make sure no processes are left lingering diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Frank/FrankAssignment3Simulation.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Frank/FrankAssignment3Simulation.java new file mode 100644 index 0000000000000000000000000000000000000000..c54220bbbb17db8cdd9c007a6ca41ee2156538e6 --- /dev/null +++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Frank/FrankAssignment3Simulation.java @@ -0,0 +1,549 @@ +/** + * 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 MV3500Cohort2021JulySeptember.homework3.Frank; + +import edu.nps.moves.dis7.enumerations.Country; +import edu.nps.moves.dis7.enumerations.EntityKind; +import edu.nps.moves.dis7.enumerations.MunitionDescriptorFuse; +import edu.nps.moves.dis7.enumerations.PlatformDomain; +import edu.nps.moves.dis7.enumerations.VariableRecordType; +import edu.nps.moves.dis7.pdus.CommentPdu; +import edu.nps.moves.dis7.pdus.CommentReliablePdu; +import edu.nps.moves.dis7.pdus.Domain; +import edu.nps.moves.dis7.pdus.EntityID; +import edu.nps.moves.dis7.pdus.EntityStatePdu; +import edu.nps.moves.dis7.pdus.EntityType; +import edu.nps.moves.dis7.pdus.EulerAngles; +import edu.nps.moves.dis7.pdus.FirePdu; +import edu.nps.moves.dis7.pdus.MunitionDescriptor; +import edu.nps.moves.dis7.pdus.Pdu; +import edu.nps.moves.dis7.pdus.Vector3Double; +import edu.nps.moves.dis7.utilities.CoordinateConversions; +import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface; +import edu.nps.moves.dis7.utilities.PduFactory; +import edu.nps.moves.dis7.utilities.stream.PduRecorder; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The purpose of this program is to have M1Abrams Destroy A Russian T62 Tank. + * simulation program that includes DIS-capable entities doing tasks and + * reporting them to the network. Default settings include PDU recording turned + * on by default. + */ +public class FrankAssignment3Simulation { + + private EntityID createM1Abrams() { + EntityID M1AbramsID = new EntityID(); // 1.1.225.1.1.1 Platform,Ground,USA,ArmoredFightingVehicle M1 Abrams + M1AbramsID.setSiteID(1); + M1AbramsID.setApplicationID(13); + M1AbramsID.setEntityID(25); + return M1AbramsID; + } + + private EntityType createM1AbramsType() { + EntityType M1AbramsType = new EntityType(); + M1AbramsType.setEntityKind(EntityKind.PLATFORM); + M1AbramsType.setDomain(Domain.inst(PlatformDomain.LAND)); + M1AbramsType.setCountry(Country.UNITED_STATES_OF_AMERICA_USA); + M1AbramsType.setCategory(1); + M1AbramsType.setSubCategory(1); + M1AbramsType.setSpecific(1); + return M1AbramsType; + } + + private Vector3Double createLocationVector() { + Vector3Double eloc2 = new Vector3Double(); + double[] loc2 = CoordinateConversions.getXYZfromLatLonDegrees(36.599831, -121.878842, 0); //sloat delmonte intersection + eloc2.setX(loc2[0]); + eloc2.setY(loc2[1]); + eloc2.setZ(loc2[2]); + return eloc2; + } + + private EulerAngles createOrientation() { + EulerAngles orient2 = new EulerAngles(); + orient2.setPhi((float) 0.0); + orient2.setPsi((float) 0.0); + orient2.setTheta((float) 0.0); + return orient2; + } + + private EntityID createT62ID() { + EntityID T62ID = new EntityID();//1.1.45.1.7.1 Platform,Ground,China, Tank, T-62 + T62ID.setSiteID(1); + T62ID.setApplicationID(13); + T62ID.setEntityID(2); + + return T62ID; + } + + private EntityType createT62Type() { + + EntityType T62Type = new EntityType(); + T62Type.setEntityKind(EntityKind.PLATFORM); + T62Type.setDomain(Domain.inst(PlatformDomain.LAND)); + T62Type.setCountry(Country.RUSSIA); + T62Type.setCategory(2); + T62Type.setSubCategory(41); + T62Type.setSpecific(3); + return T62Type; + } + + private Vector3Double createEnemyLocation() { + Vector3Double eloc1 = new Vector3Double(); + double[] loc1 = CoordinateConversions.getXYZfromLatLonDegrees(36.594116, -121.877463, 0); //NPS Main Gate + eloc1.setX(loc1[0]); + eloc1.setY(loc1[1]); + eloc1.setZ(loc1[2]); + return eloc1; + } + + private EulerAngles createEnemyOrientation() { + EulerAngles orient1 = new EulerAngles(); + orient1.setPhi((float) 3.1415); + orient1.setPsi((float) 0.0); + orient1.setTheta((float) 0.0); + return orient1; + } + + private MunitionDescriptor createM829IT(){ + + EntityType M829Type = new EntityType(); //2.2.225.2.13.1 + M829Type.setEntityKind(EntityKind.MUNITION); + M829Type.setDomain(Domain.inst(PlatformDomain.AIR)); + M829Type.setCountry(Country.UNITED_STATES_OF_AMERICA_USA); + M829Type.setCategory(2); + M829Type.setSubCategory(13); + M829Type.setSpecific(1); + MunitionDescriptor M829IT = new MunitionDescriptor(); + M829IT.setMunitionType(M829Type); + M829IT.setQuantity(3); + M829IT.setFuse(MunitionDescriptorFuse.CONTACT_GRAZE); + M829IT.setRate(200); + return M829IT; + } + /** + * This runSimulation() method is for you, a programmer-modifiable method + * for defining and running a new simulation of interest. Welcome! Other + * parts of this program handle bookkeeping and plumbing tasks so that you + * can focus on your model entities and activities. Expandable support + * includes DIS EntityStatePdu, FirePdu and CommentPdu all available for + * modification and sending in a simulation loop. Continuous improvement + * efforts seek to make this program as easy and straightforward as possible + * for DIS simulationists to use and adapt. All of the other methods are + * setup, teardown and configuration that you may find interesting, even + * helpful, but don't really have to worry about. + */ + @SuppressWarnings("SleepWhileInLoop") // yes we do that + public void runSimulation() { + try { + /** + * seconds for real-time execution (not simulation time, which may + * or may not be the same) + */ + final double SIMULATION_LOOP_DURATION_SECONDS = 2.0; + final int SIMULATION_MAX_LOOP_COUNT = 10; // be deliberate out out there! also avoid infinite loops. + int simulationLoopCount = 0; // variable, initialized at 0 + boolean simulationComplete = false; // sentinel variable as termination condition,, are we done yet? + boolean fireBool = false; + boolean destBool = false; + + EntityID entityID_1 = new EntityID(); + entityID_1.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID; + // TODO someday, use enumerations; is there a unique site triplet for MOVES Institute? + + EntityStatePdu entityStatePdu_1 = pduFactory.makeEntityStatePdu(); + entityStatePdu_1.setEntityID(entityID_1); + // M1Abrams + entityStatePdu_1.setEntityID(createM1Abrams()); // Calling createM1Abrams Method + entityStatePdu_1.setEntityType(createM1AbramsType()); // Calling createM1AbramsType Method + // Location and Orientation + Vector3Double eloc2 = createLocationVector(); // Calling createLocationVector Method + entityStatePdu_1.setEntityLocation(eloc2); + entityStatePdu_1.setEntityOrientation(createOrientation()); // Calling Create Orientation Method + + EntityStatePdu entityStatePdu2 = pduFactory.makeEntityStatePdu(); +// //T-62 + entityStatePdu2.setEntityID(createT62ID()); // Calling Create T62 ID method + entityStatePdu2.setEntityType(createT62Type()); // Calling Create T62 Type method + // enemy location and orientation + Vector3Double eloc1 = createEnemyLocation(); // calling create enemylocation method + entityStatePdu2.setEntityLocation(eloc1); // Setting em + entityStatePdu2.setEntityOrientation(createEnemyOrientation()); //calling create Enemy orientation method + + int T62HitsReceived = 0; + + System.out.println(eloc2.toString()); + System.out.println(eloc1.toString()); + + //FirePdu firePduNull = new FirePdu(); + FirePdu firePdu = pduFactory.makeFirePdu(); + EntityID fireID = new EntityID(); + fireID.setSiteID(1); + fireID.setApplicationID(13); + fireID.setEntityID(25); + EntityID targetID = new EntityID(); + targetID.setSiteID(1); + targetID.setApplicationID(13); + targetID.setEntityID(2); + + firePdu.setFiringEntityID(fireID); + firePdu.setTargetEntityID(targetID); + + firePdu.setDescriptor(createM829IT()); // calling create M829IT Method + + EntityID M829ID = new EntityID(); + M829ID.setEntityID(1); + firePdu.setMunitionExpendibleID(M829ID); + + + CommentReliablePdu T62DestroyedComment = pduFactory.makeCommentReliablePdu("T62 DESTROYED BY M1 Abrams AFTER 2 rounds M829I-T ON TARGET"); + CommentReliablePdu T62SightedComment = pduFactory.makeCommentReliablePdu("M1 Abrams Acquires Target - T62 with in firing distance"); + //if(eloc1.getX()) + EntityID MTVRID = new EntityID(); + + FirePdu firePdu_1a = pduFactory.makeFirePdu(); // for entity 1 first weapon (if any) + + while (simulationLoopCount < SIMULATION_MAX_LOOP_COUNT) // are we done yet? + { + simulationLoopCount++; // good practice: increment loop counter as first action in that loop + // Where is my entity? + entityStatePdu_1.getEntityLocation().setX(entityStatePdu_1.getEntityLocation().getX() - 20); // 1m per timestep + entityStatePdu_1.getEntityLocation().setY(entityStatePdu_1.getEntityLocation().getY() - 75); + // decide whether to fire, and then update the firePdu. Hmmm, you might want a target to shoort at! + Double dx = eloc2.getX() - eloc1.getX(); + Double dy = eloc2.getY() - eloc1.getY(); + Double dz = eloc2.getZ() - eloc1.getZ(); + Double range = Math.sqrt(dx * dx + dy * dy); + System.out.println("range" + range + " dx:" + dx + " dy:" + dy); + + if (range < 100) { // Range 100 + if (!fireBool) { + sendSinglePdu(T62SightedComment); + } + fireBool = true; + System.out.println("Entity#" + firePdu.getFiringEntityID().getEntityID() + " is firing " + firePdu.getDescriptor().getMunitionType().getDomain() + "." + firePdu.getDescriptor().getMunitionType().getCountry() + "." + firePdu.getDescriptor().getMunitionType().getCategory() + "." + firePdu.getDescriptor().getMunitionType().getSubCategory() + "." + firePdu.getDescriptor().getMunitionType().getSpecific() + "." + " at Entity#" + firePdu.getTargetEntityID().getEntityID()); + + if (firePdu.getTargetEntityID().getEntityID() == 2) { + T62HitsReceived += 1; + if (T62HitsReceived > 1) { + // The M1 destroyst the T62 + + System.out.println("M1 Abrams destroys T62 after " + T62HitsReceived + "rounds hit T62 Russian Tank"); + narrativeMessage4 = "Destroyed T62"; + destBool = true; + simulationComplete = true; + + } + } + } + + + if (simulationLoopCount > 4) // for example + { + simulationComplete = true; + } + + entityStatePdu_1.getEntityLocation().setX(entityStatePdu_1.getEntityLocation().getX() + 1.0); // 1m per timestep + + narrativeMessage1 = "MV3500 FrankAssignment3Simulation"; + narrativeMessage2 = "runSimulation() loop " + simulationLoopCount; + narrativeMessage3 = ""; // intentionally blank for testing + + // your loop termination condition goes here + if (simulationLoopCount > 10) // for example + { + simulationComplete = true; + } + // ============================================================================================= + // * your own simulation code is finished here! * + // ============================================================================================= + + // staying synchronized with timestep: wait duration for elapsed time in this loop + // Thread.sleep needs a (long) parameter for milliseconds, which are clumsy to use sometimes + Thread.sleep((long) (SIMULATION_LOOP_DURATION_SECONDS * 1000)); // seconds * (1000 msec/sec) = milliseconds + System.out.println("... [Pausing for " + SIMULATION_LOOP_DURATION_SECONDS + " seconds]"); + + // OK now send the status PDUs for this loop, and then continue + System.out.println("sending PDUs for simulation step " + simulationLoopCount + ", monitor loopback to confirm sent"); + sendAllPdusForLoopTimestep(entityStatePdu_1, firePdu_1a, timeStepComment, narrativeMessage1, narrativeMessage2, narrativeMessage3); + System.out.println("... [PDUs successfully sent for this loop]"); + + // =============================== + // loop now finished, check whether to terminate if simulation complete, otherwise continue + if (simulationComplete || (simulationLoopCount > 10000)) // for example; including fail-safe condition is good + { + System.out.println("... [Termination condition met, simulationComplete=" + simulationComplete + "]"); // ", final loopCount=" + loopCount + + break; + } + } // end of simulation loop + + narrativeMessage2 = "runSimulation() completed successfully"; // all done + sendCommentPdu(narrativeComment, narrativeMessage1, narrativeMessage2, narrativeMessage3); + System.out.println("... [final CommentPdu successfully sent for simulation]"); + // TODO simulation management PDUs + } catch (InterruptedException iex) // handle any exception that your code might choose to provoke! + { + Logger.getLogger(FrankAssignment3Simulation.class.getName()).log(Level.SEVERE, null, iex); + + } catch (Exception ex) { + } + + /* **************************** infrastructure code, modification is seldom needed ************************* */ + } + private boolean verboseComments = true; + String narrativeMessage1 = new String(); + String narrativeMessage2 = new String(); + String narrativeMessage3 = new String(); + String narrativeMessage4 = new String(); + + /* VariableRecordType enumerations have potential use with CommentPdu logs */ + /* TODO contrast to EntityType */ + VariableRecordType descriptionComment = VariableRecordType.DESCRIPTION; + VariableRecordType narrativeComment = VariableRecordType.COMPLETE_EVENT_REPORT; + VariableRecordType statusComment = VariableRecordType.APPLICATION_STATUS; + VariableRecordType timeStepComment = VariableRecordType.APPLICATION_TIMESTEP; + VariableRecordType otherComment = VariableRecordType.OTHER; + + /** + * Output prefix to identify this class, helps with logging + */ + private final static String TRACE_PREFIX = "[" + FrankAssignment3Simulation.class.getName() + "] "; + + // class variables + PduFactory pduFactory = new PduFactory(); + DisThreadedNetworkInterface disNetworkInterface; + DisThreadedNetworkInterface.PduListener pduListener; + Pdu receivedPdu; + + static final String NETWORK_ADDRESS_DEFAULT = "239.1.2.3"; + static final int NETWORK_PORT_DEFAULT = 3000; + static String networkAddress = NETWORK_ADDRESS_DEFAULT; + static int networkPort = NETWORK_PORT_DEFAULT; + + /** + * Constructor design goal: additional built-in initialization conveniences + * can go here to keep student efforts focused on the runSimulation() + * method. + */ + public FrankAssignment3Simulation() { + // Constructor is under consideration. Constructor is not currently needed. + } + + /** + * Utility Constructor that allows your example simulation program to + * override default network address and port + * + * @param address network address to use + * @param port corresponding network port to use + */ + public FrankAssignment3Simulation(String address, int port) { + setNetworkAddress(address); + + setNetworkPort(port); + } + + /** + * @return the networkAddress + */ + public String getNetworkAddress() { + return networkAddress; + } + + /** + * @param newNetworkAddress the networkAddress to set + */ + public final void setNetworkAddress(String newNetworkAddress) { + FrankAssignment3Simulation.networkAddress = newNetworkAddress; + } + + /** + * @return the networkPort + */ + public int getNetworkPort() { + return networkPort; + } + + /** + * @param newNetworkPort the networkPort to set + */ + public final void setNetworkPort(int newNetworkPort) { + FrankAssignment3Simulation.networkPort = newNetworkPort; + } + + /** + * Initialize network interface, choosing best available network interface + */ + public void setUpNetworkInterface() { + disNetworkInterface = new DisThreadedNetworkInterface(getNetworkAddress(), getNetworkPort()); + + System.out.println("Network confirmation:" + + " address=" + disNetworkInterface.getAddress() + + // disNetworkInterface.getMulticastGroup() + + " port=" + disNetworkInterface.getPort()); // + disNetworkInterface.getDisPort()); + pduListener = new DisThreadedNetworkInterface.PduListener() { + /** + * Callback handler for listener + */ + @Override + public void incomingPdu(Pdu newPdu) { + receivedPdu = newPdu; + } + }; + disNetworkInterface.addListener(pduListener); + } + + /** + * All done, release network resources + */ + public void tearDownNetworkInterface() { + disNetworkInterface.removeListener(pduListener); + + disNetworkInterface.close(); +// disNetworkInterface.kill(); // renamed as close(), deprecated +// disNetworkInterface = null; // making sure no possibility of zombie process remaining... + } + + /** + * Send a single Protocol Data Unit (PDU) of any type + * + * @param pdu the pdu to send + */ + public void sendSinglePdu(Pdu pdu) { + try { + disNetworkInterface.send(pdu); + Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally + } catch (InterruptedException ex) { + System.err.println(this.getClass().getName() + " Error sending PDU: " + ex.getLocalizedMessage()); + System.exit(1); + } + } + + /** + * Send Comment PDU + * + * @see + * <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing + * Information to a Method or a Constructor</a> Arbitrary Number of + * Arguments + * @param commentType enumeration value describing purpose of the narrative + * comment + * @param comments String array of narrative comments + */ + public void sendCommentPdu(VariableRecordType commentType, + // vararg... variable-length set of String comments can optionally follow + String... comments) { + sendAllPdusForLoopTimestep(null, null, commentType, comments); + } + + /** + * Send EntityState, Fire, Comment PDUs that got updated for this loop, + * reflecting state of current simulation timestep. + * + * @see + * <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing + * Information to a Method or a Constructor</a> Arbitrary Number of + * Arguments + * @param entityStatePdu the ESPDU to send, if any + * @param firePdu the FirePDU to send, if any + * @param commentType enumeration value describing purpose of the narrative + * comment + * @param comments String array of narrative comments + */ + public void sendAllPdusForLoopTimestep(EntityStatePdu entityStatePdu, + FirePdu firePdu, + VariableRecordType commentType, + // vararg... variable-length set of String comments can optionally follow + String... comments) { + if (entityStatePdu != null) { + sendSinglePdu(entityStatePdu); + } + + if (firePdu != null) { + sendSinglePdu(firePdu); // bang + } + if ((comments != null) && (comments.length > 0)) { + ArrayList<String> newCommentsList = new ArrayList<>(); + for (String comment : comments) { + if (!comment.isEmpty()) { + newCommentsList.add(comment); // OK found something to send + } + } + if (!newCommentsList.isEmpty()) { + if (commentType == null) { + commentType = otherComment; // fallback value otherComment + } // now build the commentPdu from these string inputs, thus constructing a narrative entry + CommentPdu commentPdu = pduFactory.makeCommentPdu(commentType, newCommentsList.toArray(new String[0])); // comments); + sendSinglePdu(commentPdu); + if (isVerboseComments()) { + System.out.println("*** [Narrative comment sent: " + commentType.name() + "] " + newCommentsList.toString()); + } + } + } + } + + /** + * Main method is first executed when a program instance is loaded. + * + * @see + * <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html">Java + * Tutorials: A Closer Look at the "Hello World!" Application</a> + * @param args command-line arguments are an array of optional String + * parameters that are passed from execution environment during invocation + */ + public static void main(String[] args) { + System.out.println(TRACE_PREFIX + "started..."); + + FrankAssignment3Simulation thisProgram = new FrankAssignment3Simulation(); // creates instance + + // initial execution: can handle args array of initialization arguments here + if (args.length == 2) { + if ((args[0] != null) && !args[0].isEmpty()) { + thisProgram.setNetworkAddress(args[0]); + } + + if ((args[1] != null) && !args[1].isEmpty()) { + thisProgram.setNetworkPort(Integer.parseInt(args[1])); + } + } else if (args.length != 0) { + System.err.println("Usage: " + thisProgram.getClass().getName() + " [address port]"); + System.exit(-1); + } + // OK here we go... + + thisProgram.setUpNetworkInterface(); + + String DEFAULT_OUTPUT_DIRECTORY = "./pduLog"; + String outputDirectory = DEFAULT_OUTPUT_DIRECTORY; + System.out.println("Beginning pdu save to directory " + outputDirectory); + PduRecorder pduRecorder = new PduRecorder(outputDirectory, networkAddress, networkPort); // assumes save + + thisProgram.runSimulation(); // ... your simulation execution code goes in there ... + + pduRecorder.stop(); + + thisProgram.tearDownNetworkInterface(); // make sure no processes are left lingering + + System.out.println(TRACE_PREFIX + "complete."); // report successful completion + } + + /** + * @return whether verboseComments mode is enabled + */ + public boolean isVerboseComments() { + return verboseComments; + } + + /** + * @param newVerboseComments whether verboseComments mode is enabled + */ + public void setVerboseComments(boolean newVerboseComments) { + this.verboseComments = newVerboseComments; + } +} diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Frank/README.md b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Frank/README.md new file mode 100644 index 0000000000000000000000000000000000000000..db254434a0045cb154422ba01f88c7e47cac42b7 --- /dev/null +++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Frank/README.md @@ -0,0 +1,31 @@ +## Homework 3: Example Simulation Recording OpenDIS Network Streams + +### Assignment + +* Adapt the functionality for [OpenDIS ExampleSimulationProgram](../../../../examples/src/OpenDis7Examples/ExampleSimulationProgram.java), modifying provided code +* Result streams are recorded/saved/replayed using Wireshark or X3D-Edit. + +This assignment presents a Problem Prototyping opportunity. +While some minimal functionality is expected, the general outline of +a networking problem and proposed solution holds great interest. +Think of it as a warmup preparation for your final project. + +This is also a freeplay opportunity. + You have the option to pick one or more of the provided course example programs +and adapt the source to demonstrate a new client-server handshake protocol of interest. + +Be sure to provide a rationale that justifies why the networking choices you made +(TCP/UDP, unicast/multicast, etc.) are the best for the problem you are addressing. + +Refer to [homework2 README](../homework2/README.md) and [assignments README](../../../README.md) +for further details on what specific deliverables are expected in each homework assignment. + +Team efforts are encouraged, though if you choose a team approach be sure to justify why. +This is a good warmup prior to final projects. Have fun with Java networking! + +### Prior Assignment, August 2019 + +In 2019, students worked together on a single project to check wireless multicast connectivity recently deployed on NPS campus. + +See their experimental results in the [NPS Multicast Connectivity Report](../../MV3500Cohort2019JulySeptember/homework3). + diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Leckie/PushTest.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Leckie/PushTest.java deleted file mode 100644 index 569b271a49198f5175d723a0592c7dbe15570f34..0000000000000000000000000000000000000000 --- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Leckie/PushTest.java +++ /dev/null @@ -1,19 +0,0 @@ - -package MV3500Cohort2021JulySeptember.homework3.Leckie; - -/** - * - * @author Jacob Leckie - */ - - -public class PushTest { - - /** - * @param args the command line arguments - */ - public static void main(String[] args) { - // This code does nothing it is test of can I push code. - } - -} diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Leckie/homework3Leckie.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Leckie/homework3Leckie.java new file mode 100644 index 0000000000000000000000000000000000000000..7932050ef8c0f6946f55dfa8d28740a20748ab2e --- /dev/null +++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Leckie/homework3Leckie.java @@ -0,0 +1,379 @@ + +package MV3500Cohort2021JulySeptember.homework3.Leckie; + +/** + * + * @author Jacob Leckie + */ + +/** + * 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 OpenDis7Examples; + +import edu.nps.moves.dis7.enumerations.VariableRecordType; +import edu.nps.moves.dis7.pdus.CommentPdu; +import edu.nps.moves.dis7.pdus.EntityID; +import edu.nps.moves.dis7.pdus.EntityStatePdu; +import edu.nps.moves.dis7.pdus.FirePdu; +import edu.nps.moves.dis7.pdus.Pdu; +import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface; +import edu.nps.moves.dis7.utilities.PduFactory; +import edu.nps.moves.dis7.utilities.stream.PduRecorder; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** The purpose of this program is to provide an easily modifiable example simulation program + * that includes DIS-capable entities doing tasks and reporting them to the network. + * Default settings include PDU recording turned on by default. + */ +public class homework3Leckie +{ + /** + * This runSimulation() method is for you! This block is programmer-modifiable method + * for defining and running a new simulation of interest. + * Support include DIS EntityStatePdu, FirePdu and CommentPdu all available for + * modification and sending in a simulation loop. + * Continuous improvement efforts seek to make this program as easy and straightforward + * as possible for new simulation people to use and adapt. + * All of the other methods are setup, tear down and configuration that you don't have to worry about. + */ + @SuppressWarnings("SleepWhileInLoop") + public void runSimulation () + { + try + { + /** seconds for real-time execution (not simulation time, which may or may not be the same) */ + final double LOOP_DURATION_SECONDS = 1.0; + final int MAX_LOOP_COUNT = 10; // be deliberate out out there! + int loopCount = 0; // initialized at 0 + boolean simulationComplete = false; // sentinel variable as termination condition,, are we done yet? + + // TODO reset clock to zero each time for consistent outputs + + // your model setup: who's who in this zoo? + // create PDU objects and set their values + + EntityID entityID_1 = new EntityID(); + entityID_1.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID; + // TODO use enumerations; is there a unique site triplet for MOVES Institute? + + EntityStatePdu entityStatePdu = pduFactory.makeEntityStatePdu(); + entityStatePdu.setEntityID(entityID_1); + + FirePdu firePdu = pduFactory.makeFirePdu(); + // should we customize this munition? what is it for your simulation? + + // TODO simulation management PDUs for startup + + // loop the simulation while allowed, programmer can set additional conditions to break out and finish + while (loopCount < MAX_LOOP_COUNT) // are we done yet? + { + loopCount++; // good practice: increment loop counter as first action + // ============================================================================================= + // ============================================================================================= + // your own simulation code starts here! + + // are there any other variables to modify at the beginning of your loop? + + // compute a track, update an ESPDU, whatever it is that your model is doing... + + // Where is my entity? Insert changes in position. + entityStatePdu.getEntityLocation().setX(entityStatePdu.getEntityLocation().getX() + 1.0); // 1m per timestep + + // decide whether to fire, and then update the firePdu. Hmmm, you might want a target to shoort at! + + // etc. etc. your code goes here + + // something happens between my simulation entities, la de da de da... + System.out.println ("... My simulation just did something, no really..."); + + + // make your reports: narrative code for CommentPdu here (set all to empty strings to avoid sending) + narrativeMessage1 = "MV3500 ExampleSimulationProgram"; + narrativeMessage2 = "runSimulation() loop " + loopCount; + narrativeMessage3 = ""; // intentionally blank for testing + + // your loop termination condition goes here + if (loopCount > 4) // for example + { + simulationComplete = true; + } + // your own simulation code is finished here! + + + + // ============================================================================================= + // ============================================================================================= + // keep track of timestep: wait duration for elapsed time in this loop + // Thread.sleep needs a (long) parameter for milliseconds, which are clumsy to use sometimes + Thread.sleep((long)(LOOP_DURATION_SECONDS * 1000)); // seconds * (1000 msec/sec) = milliseconds + System.out.println ("... [Pausing for " + LOOP_DURATION_SECONDS + " seconds]"); + + // send the status PDUs for this loop and continue + System.out.println ("sending PDUs for simulation step " + loopCount + ", monitor loopback to confirm sent"); + sendAllPdusForLoopTimestep(entityStatePdu, firePdu, timeStepComment, narrativeMessage1, narrativeMessage2, narrativeMessage3); + System.out.println ("... [PDUs successfully sent for this loop]"); + + // =============================== + // loop now finished, thus terminate if simulation complete, otherwise send latest PDUs and continue + if (simulationComplete || (loopCount > 10000)) // for example; including fail-safe condition is good + { + System.out.println ("... [Termination condition met, simulationComplete=" + simulationComplete + "]"); // ", final loopCount=" + loopCount + + break; + } + } // end of while loop + // all done + narrativeMessage2 = "runSimulation() completed successfully"; + sendCommentPdu(narrativeComment, narrativeMessage1, narrativeMessage2, narrativeMessage3); + System.out.println ("... [final CommentPdu successfully sent for simulation]"); + // TODO simulation management PDUs + } + catch (InterruptedException iex) // handle any exception that your code might choose to provoke! + { + Logger.getLogger(homework3Leckie.class.getName()).log(Level.SEVERE, null, iex); + } + } + /* **************************** infrastructure code, modification is seldom needed ************************* */ + + private boolean verboseComments = true; + String narrativeMessage1 = new String(); + String narrativeMessage2 = new String(); + String narrativeMessage3 = new String(); + + /* VariableRecordType enumerations have potential use with CommentPdu logs */ + VariableRecordType descriptionComment = VariableRecordType.DESCRIPTION; + VariableRecordType narrativeComment = VariableRecordType.COMPLETE_EVENT_REPORT; + VariableRecordType statusComment = VariableRecordType.APPLICATION_STATUS; + VariableRecordType timeStepComment = VariableRecordType.APPLICATION_TIMESTEP; + VariableRecordType otherComment = VariableRecordType.OTHER; + + /** + * Output prefix to identify this class, helps with logging + */ + private final static String TRACE_PREFIX = "[" + homework3Leckie.class.getName() + "] "; + + // class variables + PduFactory pduFactory = new PduFactory(); + DisThreadedNetworkInterface disNetworkInterface; + DisThreadedNetworkInterface.PduListener pduListener; + Pdu receivedPdu; + + static final String networkAddress_DEFAULT = "239.1.2.3"; + static final int networkPort_DEFAULT = 3000; + static String networkAddress = networkAddress_DEFAULT; + static int networkPort = networkPort_DEFAULT; + + /** + * Constructor design goal: additional built-in initialization conveniences can go here + * to keep student efforts focused on the runSimulation() method. + */ + public homework3Leckie() + { + // Constructor is under consideration. Constructor is not currently needed. + } + + /** + * Utility Constructor that allows your example simulation program to override default network address and port + * @param address network address to use + * @param port corresponding network port to use + */ + public homework3Leckie(String address, int port) + { + setNetworkAddress(address); + + setNetworkPort(port); + } + + /** + * @return the networkAddress + */ + public String getNetworkAddress() + { + return networkAddress; + } + + /** + * @param newNetworkAddress the networkAddress to set + */ + public final void setNetworkAddress(String newNetworkAddress) + { + homework3Leckie.networkAddress = newNetworkAddress; + } + + /** + * @return the networkPort + */ + public int getNetworkPort() + { + return networkPort; + } + + /** + * @param newNetworkPort the networkPort to set + */ + public final void setNetworkPort(int newNetworkPort) + { + homework3Leckie.networkPort = newNetworkPort; + } + + /** + * Initialize network interface, choosing best available network interface + */ + public void setUpNetworkInterface() + { + disNetworkInterface = new DisThreadedNetworkInterface(getNetworkAddress(), getNetworkPort()); + + System.out.println("Network confirmation: address=" + disNetworkInterface.getMulticastGroup() + " port=" + disNetworkInterface.getDisPort()); + pduListener = new DisThreadedNetworkInterface.PduListener() + { + /** Callback handler for listener */ + @Override + public void incomingPdu(Pdu newPdu) + { + receivedPdu = newPdu; + } + }; + disNetworkInterface.addListener(pduListener); + } + + /** All done, release network resources */ + public void tearDownNetworkInterface() + { + disNetworkInterface.removeListener(pduListener); + disNetworkInterface.kill(); + disNetworkInterface = null; + } + + /** + * Send a single Protocol Data Unit (PDU) of any type + * @param pdu the pdu to send + */ + private void sendSinglePdu(Pdu pdu) + { + try + { + disNetworkInterface.send(pdu); + Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally + } + catch (InterruptedException ex) + { + System.err.println(this.getClass().getName() + " Error sending PDU: " + ex.getLocalizedMessage()); + System.exit(1); + } + } + + /** + * Send Comment PDU + * @see <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing Information to a Method or a Constructor</a> Arbitrary Number of Arguments + * @param commentType enumeration value describing purpose of the narrative comment + * @param comments String array of narrative comments + */ + public void sendCommentPdu(VariableRecordType commentType, + // vararg... variable-length set of String comments can optionally follow + String... comments) + { + sendAllPdusForLoopTimestep (null, null, commentType, comments); + } + + /** + * Send EntityState, Fire, Comment PDUs that got updated for this loop, reflecting state of current simulation timestep. + * @see <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing Information to a Method or a Constructor</a> Arbitrary Number of Arguments + * @param entityStatePdu the ESPDU to send, if any + * @param firePdu the FirePDU to send, if any + * @param commentType enumeration value describing purpose of the narrative comment + * @param comments String array of narrative comments + */ + public void sendAllPdusForLoopTimestep(EntityStatePdu entityStatePdu, + FirePdu firePdu, + VariableRecordType commentType, + // vararg... variable-length set of String comments can optionally follow + String... comments) + { + if (entityStatePdu != null) + sendSinglePdu(entityStatePdu); + + if (firePdu != null) + sendSinglePdu(firePdu); // bang + + if ((comments != null) && (comments.length > 0)) + { + ArrayList<String> newCommentsList = new ArrayList<>(); + for (int i = 0; i < comments.length; i++) + { + if (!comments[i].isEmpty()) + newCommentsList.add(comments[i]); // OK found something to send + } + if (!newCommentsList.isEmpty()) + { + if (commentType == null) + commentType = otherComment; // fallback value otherComment + // now build the commentPdu from these string inputs, thus constructing a narrative entry + CommentPdu commentPdu = pduFactory.makeCommentPdu(commentType, newCommentsList.toArray(new String[0])); // comments); + sendSinglePdu(commentPdu); + if (isVerboseComments()) + System.out.println("*** [Narrative comment sent: " + commentType.name() + "] " + newCommentsList.toString()); + } + } + } + + /** + * Main method is first executed when a program instance is loaded. + * @see <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html">Java Tutorials: A Closer Look at the "Hello World!" Application</a> + * @param args command-line arguments are an array of optional String parameters that are passed from execution environment during invocation + */ + public static void main(String[] args) + { + System.out.println(TRACE_PREFIX + "started..."); + + homework3Leckie thisProgram = new homework3Leckie(); // creates instance + + // initial execution: can handle args array of initialization arguments here + if (args.length == 2) + { + if ((args[0] != null) && !args[0].isEmpty()) + thisProgram.setNetworkAddress(args[0]); + + if ((args[1] != null) && !args[1].isEmpty()) + thisProgram.setNetworkPort(Integer.parseInt(args[1])); + } + else if (args.length != 0) + { + System.err.println("Usage: " + thisProgram.getClass().getName() + " [address port]"); + System.exit(-1); + } + // OK here we go... + + thisProgram.setUpNetworkInterface(); + + String DEFAULT_OUTPUT_DIRECTORY = "./pduLog"; + String outputDirectory = DEFAULT_OUTPUT_DIRECTORY; + System.out.println("Beginning pdu save to directory " + outputDirectory); + PduRecorder pduRecorder = new PduRecorder(outputDirectory, networkAddress, networkPort); // assumes save + + thisProgram.runSimulation (); // ... your simulation execution code goes in there ... + +// pduRecorder.end(); // ".end()" was throwing an error. + pduRecorder.stop(); + + thisProgram.tearDownNetworkInterface(); // make sure no processes are left lingering + + System.out.println(TRACE_PREFIX + "complete."); // report successful completion + } + + /** + * @return whether verboseComments mode is enabled + */ + public boolean isVerboseComments() { + return verboseComments; + } + + /** + * @param newVerboseComments whether verboseComments mode is enabled + */ + public void setVerboseComments(boolean newVerboseComments) { + this.verboseComments = newVerboseComments; + } +} diff --git a/presentations/01_README_LVC.md b/presentations/01_README_LVC.md index 08387f95ce0b3e9e81014c8bda84dbee1e308c86..fb9dedd504b0bacd8694dfd06533c225446b846a 100644 --- a/presentations/01_README_LVC.md +++ b/presentations/01_README_LVC.md @@ -3,7 +3,9 @@ Interesting aspect of LVC is that definitions can vary widely, continuing to evolve. How someone considers LVC often shows a lot about the individual than the concept. -For NPS students: what do you consider to be great examples of Live, Virtual and Constructive for your professional community? For your modeling and simulation areas of interest? +For NPS students: what do you consider to be great examples of Live, Virtual and Constructive (LVC) +for your professional community? For your modeling and simulation areas of interest? +This interesting question broadens and deepens steadily as capabilities make progress. ## Live @@ -13,7 +15,7 @@ For NPS students: what do you consider to be great examples of Live, Virtual and 3. MEU Amphibious Landing Beach Operations Group 4. Any "live fire" attack, in training 5. Infantry Immersion Trainer (IIT) -6. Cyber training: red vs. blue team link: https://www.csoonline.com/article/2122440/emergency-preparedness-red-team-versus-blue-team-how-to-run-an-effective-simulation.html +6. Cyber training: [red vs. blue team](https://www.csoonline.com/article/2122440/emergency-preparedness-red-team-versus-blue-team-how-to-run-an-effective-simulation.html) 7. (Your Example Here) @@ -24,6 +26,7 @@ For NPS students: what do you consider to be great examples of Live, Virtual and 3. Convoy Simulator 4. Cyber education and training facility in a simulated environment 5. Small Arms fire simulator (i.e AGSHP (germany)) +6. (Your Example Here) ## Constructive @@ -32,5 +35,5 @@ For NPS students: what do you consider to be great examples of Live, Virtual and 2. Combat XXI (Never used constructive simulations during my time in the fleet "Chris C.") 3. Joint Conflict and Tactical Simulation 4. Joint Deployment Logistics Model (JDLM) -3. (Your Example Here) +5. (Your Example Here) diff --git a/presentations/10_TENA_References.md b/presentations/10_TENA_References.md index 13ab18ad40089232a9448862948511ba910aa6a3..474f6d4c69cf7b67ed0b74b0cf678a3676f751b7 100644 --- a/presentations/10_TENA_References.md +++ b/presentations/10_TENA_References.md @@ -2,21 +2,19 @@ # Test and Training Enabling Architecture (TENA) -> "The United States Office of the Secretary of Defense Test Resource Management Center (TRMC) -> has developed a common architecture to support effective integration and reuse of testing, -> training, and simulation capabilities that require real-time collaboration between -> distributed computer systems operating within diverse testing and training environments. -> Through the establishment of the Test and Training Enabling Architecture (TENA), -> the interoperability and reuse of range assets are tremendously improved, -> thereby the reducing development, operation, and maintenance costs of range systems." +> "The OSD’s Test Resource Management Center (TRMC) Central Test and Evaluation +> Investment Program (CTEIP) is developing and validating a common architecture and +> requisite software to integrate testing, training, simulation, and high-performance computing technologies, distributed across many facilities. Through the establishment of a +> common architecture, the Test and Training Enabling Architecture (TENA), reuse and +> interoperability of range assets will be tremendously improved, thus reducing range development, operation, and maintenance costs" -1. [TENA Home](https://www.tena-sda.org/display/TENAintro/Home) and [TENA Introduction Documentation](https://www.tena-sda.org/display/TENAintro/Home) +1. [TENA Home](https://www.tena-sda.org) -2. [IITSEC 2018 TENA Tutorial](../conferences/IITSEC2018/IITSEC2018_TENA_Tutorial_1716wide.pdf) +2. [TENA Overview Fact Sheet](https://www.tena-sda.org/attachments/TENA-OverviewFS-2020-03-16-DistA.pdf) -3. [TENA About](https://www.tena-sda.org/display/TENAintro/About+TENA) +3. [TENA Overview Briefing](https://www.tena-sda.org/attachments/TENA-OverviewBrief-2019-10-15-DistA.pdf) -4. [TENA Introduction Documentation](https://www.tena-sda.org/display/TENAintro/Documentation) +4. [IITSEC 2018 TENA Tutorial](../conferences/IITSEC2018/IITSEC2018_TENA_Tutorial_1716wide.pdf) 5. [TENA Wikipedia](https://en.wikipedia.org/wiki/Test_and_Training_Enabling_Architecture) @@ -26,12 +24,18 @@ > persistent capability for linking distributed facilities, > enabling DoD customers to develop and test warfighting capabilities in a Joint Context." -6. [JMETC DASD DT&E Test Resource Management Center (TMRC)](https://www.acq.osd.mil/dte-trmc/interoperability.html) and [JMETC Home](http://www.jmetc.org) +6. [JMETC DASD DT&E Test Resource Management Center (TMRC)](https://www.trmc.osd.mil) -7. [JMETC Program Overview](https://www.tena-sda.org/download/attachments/67898070/JMETCOverviewBrief-2017-12-19%5BdistA%5D.pdf) (slideset, 2017) +7. [JMETC Home](https://www.trmc.osd.mil/jmetc-home.html) -8. [JMETC Enables Distributed Testing, Overview](https://www.tena-sda.org/download/attachments/67898070/JMETC%20Overview%20171003.pdf) (flyer, 2017) +8. [JMETC Program Overview](https://www.tena-sda.org/attachments/JMETC-OverviewFS-2020-03-16-DistA.pdf) (flyer, 2020) -9. "[Distributed: the Next Step in T&E](https://www.acq.osd.mil/dte-trmc/docs/The%20Next%20Step%20in%20T&E-2014.PDF)," Bernard "Chip" Ferguson and Neyer Torrico, _ITEA Journal of Test and Evaluation_, vol. 35 no. 2, pp. 132-135, June 2014. +9. [TENA JMETC Overview Brief](https://www.trmc.osd.mil/attachments/JMETC-OverviewBrief-2019-06-05-DistA.pdf) (slideset, 2019) -10. "[An Update on the JMETC and the Distributed Testing World](https://www.acq.osd.mil/dte-trmc/docs/JMETC%20and%20the%20Distributed%20Testing%20World.pdf)," Bernard "Chip" Ferguson, _ITEA Journal of Test and Evaluation_, vol. 34 no. 4, pp. 317-320, December 2013. +### Historic + +TODO: link updates, if possible + +* "[Distributed: the Next Step in T&E](https://www.acq.osd.mil/dte-trmc/docs/The%20Next%20Step%20in%20T&E-2014.PDF)," Bernard "Chip" Ferguson and Neyer Torrico, _ITEA Journal of Test and Evaluation_, vol. 35 no. 2, pp. 132-135, June 2014. + +* "[An Update on the JMETC and the Distributed Testing World](https://www.acq.osd.mil/dte-trmc/docs/JMETC%20and%20the%20Distributed%20Testing%20World.pdf)," Bernard "Chip" Ferguson, _ITEA Journal of Test and Evaluation_, vol. 34 no. 4, pp. 317-320, December 2013.