diff --git a/assignment03/SYSC3303_A03_W26_Starting_code/README.txt b/assignment03/SYSC3303_A03_W26_Starting_code/README.txt new file mode 100644 index 0000000..623cd79 --- /dev/null +++ b/assignment03/SYSC3303_A03_W26_Starting_code/README.txt @@ -0,0 +1,166 @@ +================================================================================ +PELICAN CROSSING STATE MACHINE - README +================================================================================ +PROJECT OVERVIEW +================================================================================ +Project: Pelican Pedestrian Crossing Controller - Finite State Machine +Language: Java 8+ +Design Pattern: Enum + Switch-Based State Machine +Implementation: PelicanCrossing.java +Test Harness: PelicanCrossingPublicTest.java + +This implementation models a traffic light controller that safely manages the +intersection between vehicle traffic and pedestrian crossing using a finite +state machine with event-driven behaviour. + +SOURCE FILES +================================================================================ +Primary Implementation: PelicanCrossing.java - Complete FSM implementation +Required for Testing: PelicanCrossingPublicTest.java - Provided test harness + +DESIGN APPROACH +================================================================================ +This solution uses the Enum + Switch-Based approach (allowed alternative to +State Pattern). This design was chosen because: + 1. Fixed State Set: The crossing has exactly eight states that never change + 2. Simple Logic: Each state behaviour is straightforward (update timers/signals) + 3. Performance: Direct switch dispatch is fast and lightweight + 4. Clarity: Easy to understand the complete flow in one place + 5. Embedded Fit: Suitable for embedded systems with resource constraints +The architecture: + • Single State variable (the provided State enum) + • Constructor initializes automatically + • dispatch(Event) routes via switch on current state + • Private handler method per state processes events + • Each handler directly updates state and signals + +COMPILATION & EXECUTION +================================================================================ +Compile: + $ javac PelicanCrossing.java + Expected: No errors or warnings +Run Manual Test: + PelicanCrossing fsm = new PelicanCrossing(); + System.out.println(fsm.getState()); // OPERATIONAL_CARS_GREEN_NO_PED + System.out.println(fsm.getCarSignal()); // GREEN + System.out.println(fsm.getPedSignal()); // DONT_WALK_ON +Run Provided Test Harness: + $ javac -cp junit-jupiter-api-5.x.x.jar \ + PelicanCrossing.java PelicanCrossingPublicTest.java + $ java -cp junit-platform-console-standalone-1.x.x.jar \ + org.junit.platform.console.ConsoleLauncher --scan-classpath + Expected Results: + init_entersOperationalCarsGreenNoPed PASSED + pedsWaiting_beforeMinGreen_remembersWaitingButStaysInCarsGreen PASSED + pedWaiting_duringMinGreen_thenAutoYellow_whenMinGreenEnds PASSED + walkTimesOut_thenFlash PASSED + offlineMode_offFromAnyOperationalState_setsSafeAndFlashes PASSED + +IMPLEMENTATION DETAILS +================================================================================ +State Variable: Single State enum (provided in starter code) + • Tracks current leaf state + • Updated directly by dispatch handlers + • Returned by getState() for test harness +Signal Variables: CarSignal and PedSignal enums + • Updated as state enters or transitions + • Maintained by dispatch handlers + • Accessible via getCarSignal() and getPedSignal() +Private Timers: + • greenTimer: Countdown for minimum green phase + • yellowTimer: Countdown for yellow phase + • walkTimer: Countdown for pedestrian walk phase + • flashCounter: Counter for pedestrian flashing +Private Flags: + • pedsWaiting: Boolean flag for pedestrian button pressed +Constructor: Calls dispatch(Event.INIT) to initialize FSM +Dispatch: Routes to handler method based on current state +Handlers: 8 private methods (one per state), each processing events for that state + +STATE MACHINE BEHAVIOR +================================================================================ +HIGH-LEVEL FLOW: + Operational Mode (Normal Operation): + ├─ Vehicles Enabled Phase + │ ├─ OPERATIONAL_CARS_GREEN_NO_PED: Green for cars, no pedestrians waiting + │ ├─ OPERATIONAL_CARS_GREEN_PED_WAIT: Green for cars, pedestrians waiting + │ └─ OPERATIONAL_CARS_GREEN_INT: Interruptible green (waiting for ped) + ├─ Yellow Transition + │ └─ OPERATIONAL_CARS_YELLOW: 2-tick yellow transition + └─ Pedestrians Enabled Phase + ├─ OPERATIONAL_PEDS_WALK: 3-tick walk phase + └─ OPERATIONAL_PEDS_FLASH: 6-tick flashing "Don't Walk" + Offline Mode (Safety/Maintenance): + ├─ OFFLINE_FLASH_ON: Flashing amber + flashing don't walk + └─ OFFLINE_FLASH_OFF: Amber off + pedestrian signal off + +TIMING BEHAVIOR +================================================================================ +Green Phase (Minimum 3 ticks): + • Vehicles receive GREEN signal + • Pedestrians see DONT_WALK_ON + • If pedestrian arrives before timeout, transition to yellow when min-green ends + • If no pedestrian, enter interruptible green (stay green indefinitely) +Yellow Phase (2 ticks): + • Vehicles receive YELLOW signal + • Pedestrians see DONT_WALK_ON +Walk Phase (3 ticks): + • Pedestrians receive WALK signal + • Vehicles receive RED signal +Pedestrian Flashing (6 ticks): + • Pedestrian signal alternates between DONT_WALK_ON and DONT_WALK_OFF + • Vehicles receive RED signal + • After flashing completes, cycle returns to vehicle green +Offline Phase: + • Car signal alternates between FLASHING_AMBER_ON and FLASHING_AMBER_OFF + • Pedestrian signal alternates between DONT_WALK_ON and DONT_WALK_OFF + • Each Q_TIMEOUT event toggles the flashing state + +SAFETY GUARANTEES +================================================================================ +Property 1: Vehicles Always Safe + ├─ Cars are RED outside vehicle-enabled phases + ├─ Cars cannot be GREEN during pedestrian phases + └─ Enforced by state machine structure +Property 2: Pedestrians Always Safe + ├─ Pedestrians show DONT_WALK outside pedestrian-enabled phases + ├─ Pedestrians cannot be WALK during vehicle green + └─ Enforced by state machine structure + +EVENTS +================================================================================ +INIT: + • Initialization event + • Triggers first entry to OPERATIONAL_CARS_GREEN_NO_PED + • Called automatically in constructor +PEDS_WAITING: + • Pedestrian button pressed + • Marks pedestrians as waiting + • If during min-green, triggers yellow when minimum ends + • If during interruptible green, immediately triggers yellow +Q_TIMEOUT: + • Simulated clock tick (one unit of time) + • Decrements internal timers + • Triggers state transitions when timers expire + • No real-time delays or threads used +OFF: + • Enter offline mode from any operational state + • Sets safe signals (flashing amber and flashing don't walk) + • Enters OFFLINE_FLASH_ON state +ON: + • Exit offline mode from any offline state + • Returns to OPERATIONAL_CARS_GREEN_NO_PED + • Reinitialize timers and pedestrian flag + +SIGNAL OUTPUTS +================================================================================ +Car Signal (5 possible values): + • RED: Vehicles must stop + • GREEN: Vehicles may proceed + • YELLOW: Vehicles prepare to stop + • FLASHING_AMBER_ON: Offline mode (amber on) + • FLASHING_AMBER_OFF: Offline mode (amber off) +Pedestrian Signal (3 possible values): + • WALK: Pedestrians may cross + • DONT_WALK_ON: Steady don't walk + • DONT_WALK_OFF: Flashing don't walk (off phase) \ No newline at end of file diff --git a/assignment03/SYSC3303_A03_W26_Starting_code/SYSC3303_A03_W26_Starting_code/PelicanCrossing.java b/assignment03/SYSC3303_A03_W26_Starting_code/SYSC3303_A03_W26_Starting_code/PelicanCrossing.java new file mode 100644 index 0000000..095f2d1 --- /dev/null +++ b/assignment03/SYSC3303_A03_W26_Starting_code/SYSC3303_A03_W26_Starting_code/PelicanCrossing.java @@ -0,0 +1,474 @@ +package SYSC3303_A03_W26_Starting_code.SYSC3303_A03_W26_Starting_code; +/** + * ============================================================ + * Pelican Crossing State Machine - State Pattern Solution + * ============================================================ + *
+ * Pelican Crossing Controller + *
+ * STUDENT STARTER CODE + *
+ * You MUST NOT: + * - Rename this class + * - Rename enums or enum values + * - Change method signatures + * - Remove required fields + *
+ * You MAY: + * - Add private helper methods + * - Add private classes (State Pattern) + * - Add private variables + *
+ * The TA JUnit harness depends on the exact names below. + * + * @author Dr. Rami Sabouni, + * Systems and Computer Engineering, + * Carleton University + * @version 1.0, February 8, 2026 + *
+ * IMPLEMENTATION: Object-Oriented State Pattern with Inner Classes + *
+ * This implementation uses the State Pattern with private inner classes to encapsulate state-specific behaviour. + * Each state class handles its own event dispatching and transitions, resulting in clean, maintainable code. + * This version uses ONLY the State enum variable provided in the starter code. No redundant state objects. + * + * @author Lavji, Fareen XXXXXX543 + * @version 1.2, February 28, 2026 + */ +public class PelicanCrossing { + + /* ========================================================= + * ENUMS — DO NOT MODIFY NAMES OR VALUES + * ========================================================= */ + + /** Events injected into the state machine */ + public enum Event { + INIT, + PEDS_WAITING, + Q_TIMEOUT, + OFF, + ON + } + + /** Leaf states used for grading and testing */ + public enum State { + OPERATIONAL_CARS_GREEN_NO_PED, + OPERATIONAL_CARS_GREEN_PED_WAIT, + OPERATIONAL_CARS_GREEN_INT, + OPERATIONAL_CARS_YELLOW, + OPERATIONAL_PEDS_WALK, + OPERATIONAL_PEDS_FLASH, + + OFFLINE_FLASH_ON, + OFFLINE_FLASH_OFF + } + + /** Output signal for cars */ + public enum CarSignal { + RED, + GREEN, + YELLOW, + FLASHING_AMBER_ON, + FLASHING_AMBER_OFF + } + + /** Output signal for pedestrians */ + public enum PedSignal { + DONT_WALK_ON, + DONT_WALK_OFF, + WALK + } + + /* ========================================================= + * TIMING CONSTANTS (ticks) + * DO NOT RENAME — values may be changed if justified + * ========================================================= */ + + public static final int GREEN_TOUT = 3; // minimum green duration + public static final int YELLOW_TOUT = 2; // yellow duration + public static final int WALK_TOUT = 3; // walk duration + public static final int PED_FLASH_N = 6; // number of flashing ticks + + /* ========================================================= + * REQUIRED INTERNAL STATE + * ========================================================= */ + + /** Current leaf state (used by TA tests) */ + private State state; + + /** Output signals (used by TA tests) */ + private CarSignal carSignal; + private PedSignal pedSignal; + + // Private helper timers and flags + private int greenTimer; // Countdown timer for green phase + private int yellowTimer; // Countdown timer for the yellow phase + private int walkTimer; // Countdown timer for walk phase + private int flashCounter; // Counter for pedestrian flashing + private boolean pedsWaiting; // Flag: are pedestrians waiting? + + /* ========================================================= + * CONSTRUCTOR + * ========================================================= */ + + public PelicanCrossing() { + state = State.OPERATIONAL_CARS_GREEN_NO_PED; // safety + // Initialize to initial state + dispatch(Event.INIT); + } + + /* ========================================================= + * REQUIRED PUBLIC API — DO NOT CHANGE SIGNATURES + * ========================================================= */ + + /** + * Inject an event into the state machine. + */ + public void dispatch(Event e) { + // Route by current state to the appropriate handler + if (state == null) { + System.out.println("ERROR: Null state transition"); + return; + } + + switch (state) { + case OPERATIONAL_CARS_GREEN_NO_PED: + dispatchOperationalCarsGreenNoPed(e); + break; + case OPERATIONAL_CARS_GREEN_PED_WAIT: + dispatchOperationalCarsGreenPedWait(e); + break; + case OPERATIONAL_CARS_GREEN_INT: + dispatchOperationalCarsGreenInt(e); + break; + case OPERATIONAL_CARS_YELLOW: + dispatchOperationalCarsYellow(e); + break; + case OPERATIONAL_PEDS_WALK: + dispatchOperationalPedsWalk(e); + break; + case OPERATIONAL_PEDS_FLASH: + dispatchOperationalPedsFlash(e); + break; + case OFFLINE_FLASH_ON: + dispatchOfflineFlashOn(e); + break; + case OFFLINE_FLASH_OFF: + dispatchOfflineFlashOff(e); + break; + default: + break; + } + } + + /** + * Convenience method: advance the clock by n ticks. + * Each tick corresponds to one Q_TIMEOUT event. + */ + public void tick(int n) { + for (int i = 0; i < n; i++) { + dispatch(Event.Q_TIMEOUT); + } + } + + /** + * Return the current leaf state. + * Used directly by the TA JUnit harness. + */ + public State getState() { return state; } + + /** + * Return the current car signal. + */ + public CarSignal getCarSignal() { return carSignal; } + + /** + * Return the current pedestrian signal. + */ + public PedSignal getPedSignal() { return pedSignal; } + + /* ========================================================= + * PRIVATE DISPATCH HANDLERS (One per state) + * ========================================================= */ + /** + * Handle events in the OPERATIONAL_CARS_GREEN_NO_PED state. + * Initial state: vehicles have green light, no pedestrians waiting. + */ + private void dispatchOperationalCarsGreenNoPed(Event e) { + switch (e) { + case INIT: + state = State.OPERATIONAL_CARS_GREEN_NO_PED; + carSignal = CarSignal.GREEN; + pedSignal = PedSignal.DONT_WALK_ON; + greenTimer = GREEN_TOUT; + pedsWaiting = false; + break; + + case PEDS_WAITING: + // Pedestrian button pressed during green + state = State.OPERATIONAL_CARS_GREEN_PED_WAIT; + pedsWaiting = true; + // Signals stay the same (still GREEN, DONT_WALK) + break; + + case Q_TIMEOUT: + greenTimer--; + if (greenTimer <= 0) { + // Min green reached, transition to interruptible green + state = State.OPERATIONAL_CARS_GREEN_INT; + // Signal stays GREEN, but now interruptible by pedestrians + } + break; + + case OFF: + // Enter offline mode from any state + state = State.OFFLINE_FLASH_ON; + carSignal = CarSignal.FLASHING_AMBER_ON; + pedSignal = PedSignal.DONT_WALK_ON; + break; + + case ON: + // Already operational, ignore + break; + + default: + break; + } + } + + /** + * Handle events in the OPERATIONAL_CARS_GREEN_PED_WAIT state. + * Vehicles have green, but pedestrians are waiting. + */ + private void dispatchOperationalCarsGreenPedWait(Event e) { + switch (e) { + case Q_TIMEOUT: + greenTimer--; + if (greenTimer <= 0) { + // Min green ended and pedestrians waiting: transition to yellow + state = State.OPERATIONAL_CARS_YELLOW; + carSignal = CarSignal.YELLOW; + yellowTimer = YELLOW_TOUT; + } + break; + + case OFF: + // Enter offline mode + state = State.OFFLINE_FLASH_ON; + carSignal = CarSignal.FLASHING_AMBER_ON; + pedSignal = PedSignal.DONT_WALK_ON; + break; + + case ON: + // Already operational, ignore + break; + + default: + break; + } + } + + /** + * Handle events in the OPERATIONAL_CARS_GREEN_INT state. + * Interruptible green: vehicles green; can be interrupted by pedestrians. + */ + private void dispatchOperationalCarsGreenInt(Event e) { + switch (e) { + case PEDS_WAITING: + // Pedestrian arrived during interruptible green: go to yellow + state = State.OPERATIONAL_CARS_YELLOW; + carSignal = CarSignal.YELLOW; + yellowTimer = YELLOW_TOUT; + break; + + case Q_TIMEOUT: + // Stay in interruptible green (wait for pedestrian) + // Signal stays GREEN + break; + + case OFF: + // Enter offline mode + state = State.OFFLINE_FLASH_ON; + carSignal = CarSignal.FLASHING_AMBER_ON; + pedSignal = PedSignal.DONT_WALK_ON; + break; + + case ON: + // Already operational, ignore + break; + + default: + break; + } + } + + /** + * Handle events in the OPERATIONAL_CARS_YELLOW state. + * Yellow transition between car and pedestrian phases. + */ + private void dispatchOperationalCarsYellow(Event e) { + switch (e) { + case Q_TIMEOUT: + yellowTimer--; + if (yellowTimer <= 0) { + // Yellow timeout: transition to pedestrian walk phase + state = State.OPERATIONAL_PEDS_WALK; + carSignal = CarSignal.RED; + pedSignal = PedSignal.WALK; + walkTimer = WALK_TOUT; + pedsWaiting = false; // Reset flag + } + break; + + case OFF: + // Enter offline mode + state = State.OFFLINE_FLASH_ON; + carSignal = CarSignal.FLASHING_AMBER_ON; + pedSignal = PedSignal.DONT_WALK_ON; + break; + + case ON: + // Already operational, ignore + break; + + default: + break; + } + } + + /** + * Handle events in the OPERATIONAL_PEDS_WALK state. + * Pedestrians have WALK signal, vehicles are RED. + */ + private void dispatchOperationalPedsWalk(Event e) { + switch (e) { + case Q_TIMEOUT: + walkTimer--; + if (walkTimer <= 0) { + // Walk phase timeout: transition to pedestrian flashing + state = State.OPERATIONAL_PEDS_FLASH; + pedSignal = PedSignal.DONT_WALK_ON; + flashCounter = PED_FLASH_N; + // Car signal stays RED + } + break; + + case OFF: + // Enter offline mode + state = State.OFFLINE_FLASH_ON; + carSignal = CarSignal.FLASHING_AMBER_ON; + pedSignal = PedSignal.DONT_WALK_ON; + break; + + case ON: + // Already operational, ignore + break; + + default: + break; + } + } + + /** + * Handle events in the OPERATIONAL_PEDS_FLASH state. + * Pedestrians see flashing "Don't Walk", then cycle back to vehicles. + */ + private void dispatchOperationalPedsFlash(Event e) { + switch (e) { + case Q_TIMEOUT: + flashCounter--; + if (flashCounter <= 0) { + // Flash phase complete: back to vehicle green phase + state = State.OPERATIONAL_CARS_GREEN_NO_PED; + carSignal = CarSignal.GREEN; + pedSignal = PedSignal.DONT_WALK_ON; + greenTimer = GREEN_TOUT; + pedsWaiting = false; + } else { + // Toggle pedestrian signal: even/odd toggle between ON/OFF + if (flashCounter % 2 == 0) { + pedSignal = PedSignal.DONT_WALK_ON; + } else { + pedSignal = PedSignal.DONT_WALK_OFF; + } + } + break; + + case OFF: + // Enter offline mode + state = State.OFFLINE_FLASH_ON; + carSignal = CarSignal.FLASHING_AMBER_ON; + pedSignal = PedSignal.DONT_WALK_ON; + break; + + case ON: + // Already operational, ignore + break; + + default: + break; + } + } + + /** + * Handle events in the OFFLINE_FLASH_ON state. + * System offline: flashing amber lights, safe pedestrian signal. + */ + private void dispatchOfflineFlashOn(Event e) { + switch (e) { + case Q_TIMEOUT: + // Toggle to flash OFF on the next tick + state = State.OFFLINE_FLASH_OFF; + carSignal = CarSignal.FLASHING_AMBER_OFF; + pedSignal = PedSignal.DONT_WALK_OFF; + break; + + case ON: + // Return to operational mode + state = State.OPERATIONAL_CARS_GREEN_NO_PED; + carSignal = CarSignal.GREEN; + pedSignal = PedSignal.DONT_WALK_ON; + greenTimer = GREEN_TOUT; + pedsWaiting = false; + break; + + case OFF: + // Already offline, ignore + break; + + default: + break; + } + } + + /** + * Handle events in the OFFLINE_FLASH_OFF state. + * System offline: flashing amber lights (off phase), safe pedestrian signal. + */ + private void dispatchOfflineFlashOff(Event e) { + switch (e) { + case Q_TIMEOUT: + // Toggle back to flash ON the next tick + state = State.OFFLINE_FLASH_ON; + carSignal = CarSignal.FLASHING_AMBER_ON; + pedSignal = PedSignal.DONT_WALK_ON; + break; + + case ON: + // Return to operational mode + state = State.OPERATIONAL_CARS_GREEN_NO_PED; + carSignal = CarSignal.GREEN; + pedSignal = PedSignal.DONT_WALK_ON; + greenTimer = GREEN_TOUT; + pedsWaiting = false; + break; + + case OFF: + // Already offline, ignore + break; + + default: + break; + } + } +} \ No newline at end of file diff --git a/assignment03/SYSC3303_A03_W26_Starting_code/SYSC3303_A03_W26_Starting_code/PelicanCrossingPublicTest.java b/assignment03/SYSC3303_A03_W26_Starting_code/SYSC3303_A03_W26_Starting_code/PelicanCrossingPublicTest.java new file mode 100644 index 0000000..e829d40 --- /dev/null +++ b/assignment03/SYSC3303_A03_W26_Starting_code/SYSC3303_A03_W26_Starting_code/PelicanCrossingPublicTest.java @@ -0,0 +1,113 @@ +package SYSC3303_A03_W26_Starting_code.SYSC3303_A03_W26_Starting_code; +/** + * ============================================================ + * Pelican Crossing State Machine - Test Harness - Students + * ============================================================ + + * @author Dr. Rami Sabouni, + * Systems and Computer Engineering, + * Carleton University + * @version 1.0, February 8, 2026 + */ + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class PelicanCrossingPublicTest { + + @Test + void init_entersOperationalCarsGreenNoPed() { + PelicanCrossing fsm = new PelicanCrossing(); + assertEquals(PelicanCrossing.State.OPERATIONAL_CARS_GREEN_NO_PED, fsm.getState()); + assertEquals(PelicanCrossing.CarSignal.GREEN, fsm.getCarSignal()); + assertEquals(PelicanCrossing.PedSignal.DONT_WALK_ON, fsm.getPedSignal()); + } + + @Test + void pedsWaiting_beforeMinGreen_remembersWaitingButStaysInCarsGreen() { + PelicanCrossing fsm = new PelicanCrossing(); + + fsm.dispatch(PelicanCrossing.Event.PEDS_WAITING); + + assertEquals(PelicanCrossing.State.OPERATIONAL_CARS_GREEN_PED_WAIT, fsm.getState()); + assertEquals(PelicanCrossing.CarSignal.GREEN, fsm.getCarSignal()); + assertEquals(PelicanCrossing.PedSignal.DONT_WALK_ON, fsm.getPedSignal()); + } + + + @Test + void pedWaiting_duringMinGreen_thenAutoYellow_whenMinGreenEnds() { + PelicanCrossing fsm = new PelicanCrossing(); + fsm.dispatch(PelicanCrossing.Event.PEDS_WAITING); + assertEquals(PelicanCrossing.State.OPERATIONAL_CARS_GREEN_PED_WAIT, fsm.getState()); + + // Finish remaining green ticks + fsm.tick(PelicanCrossing.GREEN_TOUT); + + // Student solution goes straight to yellow when green min ends in a ped-wait path + assertEquals(PelicanCrossing.State.OPERATIONAL_CARS_YELLOW, fsm.getState()); + assertEquals(PelicanCrossing.CarSignal.YELLOW, fsm.getCarSignal()); + } + + @Test + void walkTimesOut_thenFlash() { + PelicanCrossing fsm = new PelicanCrossing(); + fsm.tick(PelicanCrossing.GREEN_TOUT); + fsm.dispatch(PelicanCrossing.Event.PEDS_WAITING); + fsm.tick(PelicanCrossing.YELLOW_TOUT); + + fsm.tick(PelicanCrossing.WALK_TOUT); + + assertEquals(PelicanCrossing.State.OPERATIONAL_PEDS_FLASH, fsm.getState()); + assertEquals(PelicanCrossing.CarSignal.RED, fsm.getCarSignal()); + assertTrue( + fsm.getPedSignal() == PelicanCrossing.PedSignal.DONT_WALK_ON || + fsm.getPedSignal() == PelicanCrossing.PedSignal.DONT_WALK_OFF + ); + } + + @Test + void offlineMode_offFromAnyOperationalState_setsSafeAndFlashes() { + PelicanCrossing fsm = new PelicanCrossing(); + + // Move to another operational state + fsm.dispatch(PelicanCrossing.Event.PEDS_WAITING); + assertTrue(fsm.getState().name().startsWith("OPERATIONAL_")); + + // OFF should work from here + fsm.dispatch(PelicanCrossing.Event.OFF); + + assertTrue(fsm.getState().name().startsWith("OFFLINE_")); + // Safe outputs in offline are flashing amber + dont-walk flashing + assertTrue( + fsm.getCarSignal() == PelicanCrossing.CarSignal.FLASHING_AMBER_ON || + fsm.getCarSignal() == PelicanCrossing.CarSignal.FLASHING_AMBER_OFF + ); + assertTrue( + fsm.getPedSignal() == PelicanCrossing.PedSignal.DONT_WALK_ON || + fsm.getPedSignal() == PelicanCrossing.PedSignal.DONT_WALK_OFF + ); + + PelicanCrossing.State s1 = fsm.getState(); + fsm.dispatch(PelicanCrossing.Event.Q_TIMEOUT); + PelicanCrossing.State s2 = fsm.getState(); + assertNotEquals(s1, s2, "Offline should toggle between flash states on each tick"); + } + + @Test + void offlineMode_onEvent_returnsToOperationalCarsGreenNoPed() { + PelicanCrossing fsm = new PelicanCrossing(); + + // Put FSM into offline mode + fsm.dispatch(PelicanCrossing.Event.OFF); + assertTrue(fsm.getState().name().startsWith("OFFLINE_")); + + // Dispatch ON event + fsm.dispatch(PelicanCrossing.Event.ON); + + // Assert FSM returns to OPERATIONAL_CARS_GREEN_NO_PED + assertEquals(PelicanCrossing.State.OPERATIONAL_CARS_GREEN_NO_PED, fsm.getState()); + assertEquals(PelicanCrossing.CarSignal.GREEN, fsm.getCarSignal()); + assertEquals(PelicanCrossing.PedSignal.DONT_WALK_ON, fsm.getPedSignal()); + } +} \ No newline at end of file diff --git a/assignment04/Agent.java b/assignment04/Agent.java new file mode 100644 index 0000000..654e7fb --- /dev/null +++ b/assignment04/Agent.java @@ -0,0 +1,50 @@ +/** + * This class is for the Agent in this drone assembly system. + * The Agent selects two random components and places them on the common table. + * The Agent will repeat this procedure until 20 drones are assembled in total + * + * @author Dr. Rami Sabouni, + * Systems and Computer Engineering, + * Carleton University + * @version 1.0, January 07th, 2025 + * @version 2.0, January 10th, 2026 + */ + +public class Agent implements Runnable { + + private AssemblyTable assemblyTable; //The common table between Agent and Technicians + + /** + * Constructor for Agent + * + * @param t The common table between Agent and Technicians + */ + public Agent(AssemblyTable t){ + this.assemblyTable = t; + } + + /** + * Method used when Agent thread is ran + */ + public void run(){ + Components components1, components2; + System.out.println("[" + Thread.currentThread().getName() + "] Waiting to place first components on the table..."); + while (this.assemblyTable.getDronesAssembled() < 20){ //Will loop until 20 drones have been assembled + //Randomly selects two different components + components1 = Components.getRandomComponent(); + components2 = Components.getRandomComponent(); + while (components1 == components2){ //If components are the same, select and new second component + components2 = Components.getRandomComponent(); + } + this.assemblyTable.addComponents(components1, components2); //Places the two selected components on the table + // Sleep for between 0 and 5 seconds before calculating n! + try { + Thread.sleep((int)(Math.random() * 5000)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + //All drones have been assembled + System.out.println("[" + Thread.currentThread().getName() + "] 20 drones assembled, ending..."); + } +} \ No newline at end of file diff --git a/assignment04/AssemblyTable.java b/assignment04/AssemblyTable.java new file mode 100644 index 0000000..dd513f4 --- /dev/null +++ b/assignment04/AssemblyTable.java @@ -0,0 +1,149 @@ +/** + * This class is for the assembly table in this Autonomous Drone Assembly Line. + * The table serves as a common place where components are placed by the agent and taken by the technician. + * The table accepts components from the Agent and notifies all technicians that they are available. + * The table determines when each technician is allowed to take the components, based on what their missing components are. + * The table lets the right Technician assemble a drone, then notifies the Agent that the table is empty. + * The table will allow components to be placed and taken until 20 drones are assembled. + * + * + * @author Dr. Rami Sabouni, + * Systems and Computer Engineering, + * Carleton University + * @version 1.0, January 07th, 2025 + * @version 2.0, January 10th, 2026 + */ +public class AssemblyTable { + private final int SIZE = 2; //Capacity of table + private Components[] components = new Components[SIZE]; //List of components on the table + private boolean tableFull = false; //True if both components have been placed on the table + private int dronesMade = 0; //Running total of drones assembled + + /** + * Method used to allow an Agent to place components on the table when table is empty + * @param components1 First component to be placed by Agent + * @param components2 Second component to be placed by Agent + */ + public synchronized void addComponents(Components components1, Components components2) { + while (tableFull) { //Makes agent wait until table is empty to place components + if (this.dronesMade == 20){ //Will exit if no more drones are required to be assembled + return; + } + try { + wait(); //Tells agent to wait until notified + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + if (this.dronesMade == 20){ //Will exit if no more drones are required to be assembled + return; + } + + //Components are placed on table + components[0] = components1; + components[1] = components2; + + // Random delay to simulate real scenario + try { + Thread.sleep((int)(Math.random() * 1000)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + tableFull = true; //Table is now full + System.out.println("[" + Thread.currentThread().getName() + "] " + components1.toString() + " and " + components2.toString() + " placed on the table."); + notifyAll(); //Notify all Technicians that table is full + } + + /** + * Method used by Technicians to obtain components on table and assemble a drone + * + * @param components The component the Technician has an infinite supply of (Used to determine if Technician is eligible to take the components on the table) + */ + public synchronized void getComponents(Components components) + { + while (!tableFull || componentsContains(components)) { //Makes Technician wait until the table is full and until the two required components from the Agent is available + if (this.dronesMade == 20){ //If 20 drones have been assembled, do not assemble another + return; + } + try { + wait(); //Make the Technician wait until notified that new components are available + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + System.out.println("[" + Thread.currentThread().getName() + "] Drone assembled."); + System.out.println("[" + Thread.currentThread().getName() + "] Waiting for remaining components..."); + this.dronesMade++; //Increase running total of drones assembled + System.out.println("[Counter] Drones assembled: " + this.dronesMade); + System.out.println("--------------------------------------------------------------"); + //Clear components and set table to empty + this.components[0] = null; + this.components[1] = null; + tableFull = false; + + // Random delay to simulate real scenario + try { + Thread.sleep((int)(Math.random() * 1000)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + notifyAll(); //Notify Technicians and Agent that components have changed + } + + /** + * Method used to check if the component given is one of the two components on the table + * + * @param components Component from Technician (used to check if Technician can accept the components on the table) + * @return True if component is on the table, false otherwise + */ + private boolean componentsContains (Components components){ + //If there are no components on the table, or one of the components on the table is the same as the component given from the Technician, return True; false otherwise + return (this.components[0] == null || this.components[1] == null || (this.components[0] == components || this.components[1] == components)); + } + + /** + * Getter method for getDronesMade. + * + * @return dronesMade + */ + public synchronized int getDronesAssembled(){ + return this.dronesMade; + } + + /** + * Method used to create new Technician threads; keeps consistency of naming conventions between threads + * + * @param t Common table between Technicians and Agent + * @param i Component that Technician will have an infinite supply of + * @return Created Technician thread + */ + private static Thread makeNewTechnician(AssemblyTable t, Components i){ + return new Thread(new Technician(t, i), "Technician-" + i.toString()); + } + + /** + * Method used to run the program. The program creates all threads and starts them + * + * @param args + */ + public static void main (String[] args){ + + Thread TechnicianFrame, TechnicianPropulsion, TechnicianControl, agent; //Threads for each Technician and the Agent + AssemblyTable assemblyTable; //Table + + assemblyTable = new AssemblyTable(); //Common Table for all Technicians and Agent + agent = new Thread(new Agent(assemblyTable), "Agent"); //Agent thread created + TechnicianFrame = makeNewTechnician(assemblyTable, Components.Frame); //Frame Technician created + TechnicianPropulsion = makeNewTechnician(assemblyTable, Components.PropulsionUnit); //Propulsion Technician created + TechnicianControl = makeNewTechnician(assemblyTable, Components.ControlFirmware); //Control Firmware Technician created + + //Start all Technician and Agent threads + TechnicianFrame.start(); + TechnicianPropulsion.start(); + TechnicianControl.start(); + agent.start(); + } +} \ No newline at end of file diff --git a/assignment04/Components.java b/assignment04/Components.java new file mode 100644 index 0000000..f3f40b2 --- /dev/null +++ b/assignment04/Components.java @@ -0,0 +1,27 @@ +/** + * Components enums + * + * @author Dr. Rami Sabouni, + * Systems and Computer Engineering, + * Carleton University + * @version 1.0, January 07th, 2025 + * @version 2.0, January 10th, 2026 + */ + +import java.util.Random; + +public enum Components { + Frame, + PropulsionUnit, + ControlFirmware; + + private static final Random random = new Random(); + + /** + * Pick a random value of the Components enum. + * @return a random Component. + */ + public static Components getRandomComponent() { + return values()[random.nextInt(values().length)]; + } +} \ No newline at end of file diff --git a/assignment04/Technician.java b/assignment04/Technician.java new file mode 100644 index 0000000..0ad332d --- /dev/null +++ b/assignment04/Technician.java @@ -0,0 +1,47 @@ +/** + * This class is for the Technicians in this drone making system. + * The Technician has an infinite supply of one of the three components. + * The Technician will wait at the table until the other two components are placed, and will then make a drone and assemble it. + * The Technician will repeat this procedure until 20 drones are assembled in total between all Technicians + * + * @author Dr. Rami Sabouni, + * Systems and Computer Engineering, + * Carleton University + * @version 1.0, January 07th, 2025 + * @version 2.0, January 10th, 2026 + */ + +public class Technician implements Runnable { + private AssemblyTable assemblyTable; //The common table between Agent and Technicians + private Components components; //The only component each instance of Technician has an infinite supply of (this component is different between all three Technicians) + + /** + * Constructor for Technician + * + * @param t //The common table between Agent and Technicians + * @param i //The component this Technician has an infinite supply of + */ + public Technician(AssemblyTable t, Components i){ + this.assemblyTable = t; + this.components = i; + } + + /** + * Method used for each Technician thread when ran + */ + public void run(){ + System.out.println("[" + Thread.currentThread().getName() + "] Waiting for remaining components..."); + while (this.assemblyTable.getDronesAssembled() < 20){ //Will loop until 20 drones have been assembled + this.assemblyTable.getComponents(this.components); //Attempts to obtain the missing components for the Technician (if obtained, drone is assembled) + // Sleep for up to 5 seconds before performing work + try { + Thread.sleep((int)(Math.random() * 5000)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + //All drones have been assembled + System.out.println("[" + Thread.currentThread().getName() + "] 20 drones assembled, ending..."); + } +} \ No newline at end of file