package SimkitOpenDis7Examples; import edu.nps.moves.dis7.enumerations.DisPduType; import edu.nps.moves.dis7.enumerations.VariableRecordType; import edu.nps.moves.dis7.pdus.EntityStatePdu; import edu.nps.moves.dis7.pdus.EulerAngles; import edu.nps.moves.dis7.pdus.Vector3Double; import edu.nps.moves.dis7.utilities.DisChannel; import edu.nps.moves.dis7.utilities.PduFactory; import java.util.SortedSet; import java.util.TreeSet; import simkit.Priority; import simkit.SimEntityBase; /** * Add DIS outputs to TwoCraneBerths simkit simulation * @see SimkitOpenDis7Examples.TwoCraneBerths * @see <a href="run/RunTwoCranesBerthOpenDis7Log.txt" target="_blank">RunTwoCranesBerthOpenDis7Log.txt</a> * @see <a href="TwoCraneBerthsAssignment05.docx" target="_blank">TwoCraneBerthsAssignment05.docx</a> * @see <a href="TwoCraneBerthsAssignment05Solution.docx" target="_blank">TwoCraneBerthsAssignment05Solution.docx</a> * @author abuss@nps.edu * @author brutzman@nps.edu */ public class TwoCraneBerthsOpenDis7 extends SimEntityBase { // Utility constructor method: initial descriptor and verboseness of disNetworkInterface, pduRecorder private final DisChannel disChannel = new DisChannel("TwoCraneBerthsOpenDis7", false, true); PduFactory pduFactory = new PduFactory(); EntityStatePdu espduCrane = (EntityStatePdu) pduFactory.createPdu(DisPduType.ENTITY_STATE); /** * Queue of Ships waiting to go into the berth */ protected SortedSet<Ship> queue; /** * Contains 0, 1, or two Ships being unloaded */ protected SortedSet<Ship> berth; /** * Time in the system for each Ship */ protected double timeInSystem; /** * Delay in the queue for each Ship */ protected double delayInQueue; /** * number of Ships offloaded */ protected int shipCount = 0; /** * Instantiate queue and berth containers */ public TwoCraneBerthsOpenDis7() { this.queue = new TreeSet<>(); this.berth = new TreeSet<>(); } /** * Clear queue and berth containers */ @Override public void reset() { super.reset(); queue.clear(); berth.clear(); timeInSystem = Double.NaN; // Not a Number delayInQueue = Double.NaN; // Not a Number } /** * Only PropertyChangeEvents */ public void doRun() { disChannel.setVerboseComments(true); disChannel.setVerboseDisNetworkInterface(true); firePropertyChange("queue", getQueue()); firePropertyChange("berth", getBerth()); firePropertyChange("timeInSystem", getTimeInSystem()); firePropertyChange("delayInQueue", getDelayInQueue()); } /** * Add the given Ship to queue<br> * If berths is empty, schedule StartUnloadingTwoCranes<br> * If berths has 1 Ship, schedule SwitchToOneCrane * * @param ship Given Ship arriving to harbor */ public void doArrival(Ship ship) { ship.stampTime(); SortedSet<Ship> oldQueue = getQueue(); queue.add(ship); firePropertyChange("queue", oldQueue, getQueue()); if (berth.isEmpty()) { waitDelay("StartUnloadingTwoCranes", 0.0, Priority.HIGH); } if (berth.size() == 1) { waitDelay("SwitchToOneCrane", 0.0); } } /** * Remove the first Ship from queue<br> * DelayInQueue is elapsedTime of that Ship<br> * Add the ship to berth container<br> * Schedule EndUnloadingTwoCranes at half the remaining time */ public void doStartUnloadingTwoCranes() { SortedSet<Ship> oldQueue = getQueue(); Ship ship = queue.first(); queue.remove(ship); firePropertyChange("queue", oldQueue, getQueue()); delayInQueue = ship.getElapsedTime(); firePropertyChange("delayInQueue", getDelayInQueue()); ship.stampTime(); SortedSet<Ship> oldBerth = getBerth(); berth.add(ship); firePropertyChange("berth", oldBerth, getBerth()); // TODO reportCraneContainerUnloadOperationsDIS() waitDelay("EndUnloadingTwoCranes", 0.5 * ship.getRemainingUnloadingTime()); } /** * Remove the (one) Ship from berth<br> * TimeInSystem is the age of the Ship */ public void doEndUnloadingTwoCranes() { SortedSet<Ship> oldBerth = getBerth(); Ship ship = berth.first(); berth.remove(ship); firePropertyChange("berth", oldBerth, getBerth()); timeInSystem = ship.getAge(); firePropertyChange("timeInSystem", getTimeInSystem()); } /** * This event is when a Ship arrives to find only one other Ship being * unloaded.<br> * Credit the ship in the berth with work at a rate of 2 (since 2 cranes * have been unloading it<br> * Interrupt EndUnloadingTwoCranes<br> * Schedule EndUnloadingOneCrane with the Ship already in the berth<br> * Schedule StartUnloadingOneCrane */ public void doSwitchToOneCrane() { Ship ship = berth.first(); ship.work(2.0); ship.stampTime(); interrupt("EndUnloadingTwoCranes"); waitDelay("EndUnloadingOneCrane", ship.getRemainingUnloadingTime(), ship); waitDelay("StartUnloadingOneCrane", 0.0, Priority.HIGH); } /** * Pop the first Ship from the queue<br> * delayInQueue is elapsedTime (from Arrival event)<br> * Add that Ship to berth container<br> * Schedule EndUnloadingOneCrane with that Ship */ public void doStartUnloadingOneCrane() { SortedSet<Ship> oldQueue = getQueue(); Ship ship = queue.first(); queue.remove(ship); firePropertyChange("queue", oldQueue, getQueue()); delayInQueue = ship.getElapsedTime(); firePropertyChange("delayInQueue", getDelayInQueue()); ship.stampTime(); SortedSet<Ship> oldBerth = getBerth(); berth.add(ship); firePropertyChange("berth", oldBerth, getBerth()); // log crane operations for each ship shipCount++; if (shipCount == 1) System.out.println("====================================="); if (shipCount <= 2) { reportCraneContainerUnloadOperationsDIS( ship.getTimeStamp(), // simkit timeStamp 10, // numberOfContainers 90.0 // initial position of Ship ); // TODO indicate berth System.out.println("====================================="); } waitDelay("EndUnloadingOneCrane", ship.getRemainingUnloadingTime(), ship); } /** * Perform crane container unloading operations and send PDUs to report progress * @param simkitTimeStamp simkit timeStamp when crane operations began * @param numberContainers how many container boxes to offload * @param pierDistanceForCraneOffload Y coordinate down pier across from ship's berth, aligned with cargo * @see <a href="https://en.wikipedia.org/wiki/The_Box_(Levinson_book)" target="_blank">The Box: How the Shipping Container Made the World Smaller and the World Economy Bigger is</a> */ public void reportCraneContainerUnloadOperationsDIS( double simkitTimeStamp, // which crane // which berth int numberContainers, double pierDistanceForCraneOffload // lengthOfCrane ) { int disTimeStamp = (int)simkitTimeStamp; // TODO document relationship // Pier coordinate system follows, Right-Hand Rule (RHR) throughout: // +X axis to right with X=0 on centerline of pier (train tracks // +Y axis down pier with Y=0 at head of pier, // +Z axis vertical with Z=0 at level of pier // phi is rotation about +X axis, radians // theta is rotation about +Y axis, radians // psi is rotation about +X axis, radians Vector3Double positionPierHead = new Vector3Double(); // Vector3Double positionPierHead = new Vector3Double(0.0, 0.0, 0.0); // TODO needs utility constructor Vector3Double positionPierOffload = new Vector3Double(); positionPierOffload.setY(pierDistanceForCraneOffload); double craneLinearSpeed = 1.0; // m/sec double craneRotationRate = 10.0; // degrees/second double containerHookupDuration = 60.0; // seconds average double containerDetachDuration = 30.0; // seconds average // not modeling crane elevation angle, only orientation of crane cab and gantry EulerAngles orientationToShip = (new EulerAngles()).setPsi((float) (90.0 * Math.PI/180.0)); // TODO utility methods needed EulerAngles orientationToPier = (new EulerAngles()).setPsi((float) ( 0.0 * Math.PI/180.0)); // TODO utility methods needed // initialize ESPDU // espduCrane.reset(); // TODO intialization utility method // espduCrane.setEntityAppearance(TODO); // highlight active crane? espduCrane.setTimestamp(disTimeStamp); // 1. Crane at head of pier, operator present, boom elevated vertically in stow position espduCrane.setEntityLocation(positionPierHead); espduCrane.setEntityOrientation(orientationToPier); disChannel.sendSinglePdu (disTimeStamp, espduCrane); disChannel.sendCommentPdu(disTimeStamp, VariableRecordType.CARGO, "Ready to process Ship " + shipCount + " with crane at head of pier"); // 2, Ship arrives, tug departs // ship PDU already, TODO can track tugboat here if desired for further fidelity // 3. Crane moves to position of ship: travel to positionOfCrane ay craneLinearSpeed double craneTravelDuration = pierDistanceForCraneOffload / craneLinearSpeed; // units analysis: meters / (meters/second) = seconds espduCrane.setEntityLocation(pierDistanceForCraneOffload, 0.0, 0.0); disChannel.sendSinglePdu(disTimeStamp, espduCrane); disChannel.sendCommentPdu(disTimeStamp, VariableRecordType.CARGO, "Crane moved to position " + pierDistanceForCraneOffload + "m for offload after " + craneTravelDuration + " seconds"); // 4. repeat until done: Crane rotates, lift container, rotates, lower container for (int containerIndex = 1; containerIndex <= numberContainers; containerIndex++) { // 4.a crane rotates to ship double craneRotationDelay = 90.0 / craneRotationRate; // units analysis: degrees / (degrees/second) = seconds espduCrane.setEntityOrientation(orientationToShip); disChannel.sendSinglePdu(disTimeStamp, espduCrane); disChannel.sendCommentPdu(disTimeStamp, VariableRecordType.CARGO, "Crane oriented to ship after " + craneRotationDelay + " seconds" + " with craneRotationRate=" + craneRotationRate + " degrees/second"); // // 4.b announce next step without further delay, then perform container hookup disChannel.sendCommentPdu(disTimeStamp, VariableRecordType.CARGO, "Hooking up Container " + containerIndex + " to crane has started..."); // TODO + whichCrane disTimeStamp += containerHookupDuration; // disChannel.sendSinglePdu(disTimeStamp, espduCrane); // superfluous, crane hasn't moved disChannel.sendCommentPdu(disTimeStamp, VariableRecordType.CARGO, "Hooked up: Container " + containerIndex + " to crane" // TODO + whichCrane + " after " + craneRotationDelay + " seconds"); // 4.c crane rotates to pier disTimeStamp += craneRotationDelay; espduCrane.setEntityOrientation(orientationToPier); disChannel.sendSinglePdu(disTimeStamp, espduCrane); disChannel.sendCommentPdu(disTimeStamp, VariableRecordType.CARGO, "Crane oriented to pier after " + craneRotationDelay + " seconds" + " with craneRotationRate=" + craneRotationRate + " degrees/second"); // 4.d crane unhooks container disTimeStamp += containerDetachDuration; // disChannel.sendSinglePdu(disTimeStamp, espduCrane); // superfluous, crane hasn't moved disChannel.sendCommentPdu(disTimeStamp, VariableRecordType.CARGO, "Crane detached: Container " + containerIndex + " on pier" // TODO + whichCrane + " after " + containerDetachDuration + " seconds"); // send PDUs accordingly double totalDelay = (2.0 * craneRotationDelay + containerHookupDuration + containerDetachDuration); disChannel.sendCommentPdu(disTimeStamp, VariableRecordType.ELAPSED_TIME,"Time duration for crane moving container " + containerIndex + ": " + totalDelay + " seconds"); // Future work: if this container is special, meaning on watchlist, report it } // 4. Crane stows boom in vertical position // TODO future refinement if even-more fidelity is desired // 5. Crane returns to head of pier every time in order to avoid interference with ship mooring/unmooring espduCrane.setEntityLocation(positionPierHead); // head of pier disTimeStamp += craneTravelDuration; // travel time back to head of pier espduCrane.setEntityLocation(positionPierHead); disChannel.sendSinglePdu(disTimeStamp, espduCrane); } /** * Shutdown DIS network interfaces, enabling program termination */ public void shutdownDisChannel() { if (disChannel != null) { disChannel.leave(); disChannel.tearDownNetworkInterface(); } } /** * Remove given Ship from berth<br> * If Ships in queue, schedule StartUnloadingOneCrane<br> * If queue is empty, schedule SwitchToTwoCranes<br> * timeInSystem is age of Ship * * @param ship Given Ship */ public void doEndUnloadingOneCrane(Ship ship) { SortedSet<Ship> oldBerth = getBerth(); berth.remove(ship); firePropertyChange("berth", oldBerth, getBerth()); if (!queue.isEmpty()) { waitDelay("StartUnloadingOneCrane", 0.0, Priority.HIGH); } if (queue.isEmpty() && berth.size() == 1) { waitDelay("SwitchToTwoCranes", 0.0); } timeInSystem = ship.getAge(); firePropertyChange("timeInSystem", getTimeInSystem()); } /** * Credit the work of the remaining Ship in berth at unit rate<br> * Interrupt EndUnloadingOneCrane<br> * Schedule EndUnloadingTwoCranes at double the rate (i.e., half the remaining time) */ public void doSwitchToTwoCranes() { Ship ship = berth.first(); ship.work(1.0); ship.stampTime(); interrupt("EndUnloadingOneCrane", ship); waitDelay("EndUnloadingTwoCranes", 0.5 * ship.getRemainingUnloadingTime()); } /** * Get tree of sorted Ship set queue * @return Shallow copy of queue */ public SortedSet<Ship> getQueue() { return new TreeSet<>(queue); } /** * Get tree of sorted Ship set berths * @return Shallow copy of berth */ public SortedSet<Ship> getBerth() { return new TreeSet<>(berth); } /** * accessor method to get a state variable * @return The timeInSystem */ public double getTimeInSystem() { return timeInSystem; } /** * accessor method to get a state variable * @return The delayInQueue */ public double getDelayInQueue() { return delayInQueue; } }