Skip to content
Snippets Groups Projects
TwoCraneBerthsOpenDis7.java 15.69 KiB
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;
    }
}