diff --git a/wash/src/actor/ActorThread.java b/wash/src/actor/ActorThread.java index 18683bb..bb231a2 100644 --- a/wash/src/actor/ActorThread.java +++ b/wash/src/actor/ActorThread.java @@ -25,12 +25,12 @@ public abstract class ActorThread extends Thread { * milliseconds if none available. Returns null if no message is obtained * within 'timeout' milliseconds. */ + @Deprecated protected M receiveWithTimeout(long timeout) throws InterruptedException { return q.poll(timeout, TimeUnit.MILLISECONDS); } /** Wait for a message continuously */ - @Deprecated protected M take() throws InterruptedException { return q.take(); } diff --git a/wash/src/wash/control/ControllerTemp.java b/wash/src/wash/control/ControllerTemp.java index afd0b8b..3caef71 100644 --- a/wash/src/wash/control/ControllerTemp.java +++ b/wash/src/wash/control/ControllerTemp.java @@ -6,67 +6,94 @@ import wash.control.WashingMessage.Order; public final class ControllerTemp extends ActorThread { private WashingIO io; + private Heater heater; private WashingMessage m; - private WashingMessage temp; - private boolean ackSent = true; - private Order heaterState = Order.TEMP_IDLE; public ControllerTemp(WashingIO io) { this.io = io; + heater = new Heater(0); } protected void sendAck() { m.sendAck(this); - ackSent = true; } @Override public void run() { + heater.start(); + while (true) { try { - temp = receiveWithTimeout(10000 / Settings.SPEEDUP); + m = take(); + } catch (Exception e) { + System.exit(1); + } - // If there is a new message, swap - if (temp != null) { - m = temp; - ackSent = false; // We have a new order + switch (m.order()) { + case Order.TEMP_SET_40 -> heater.setTarget(40); + case Order.TEMP_SET_60 -> heater.setTarget(60); + case Order.TEMP_IDLE -> heater.setTarget(0); // TODO: Error + default -> { + continue; } + } + } + } - // If this message is not the same as last time - if (m != null && m.order() != heaterState) { - heaterState = m.order(); - } + /** + * Heater class that extends Thread and controls the temperature + * of the washing machine. + * + * Note that the heater has access to local variables in the + * TemperatureController class. + */ + class Heater extends Thread { + double target; + boolean hovering; - switch (heaterState) { - case Order.TEMP_SET_40 -> { - if (io.getTemperature() <= 39) { - io.heat(true); - } else { - io.heat(false); - if (!ackSent) - sendAck(); - } - } - case Order.TEMP_SET_60 -> { - if (io.getTemperature() <= 59) { - io.heat(true); - } else { - io.heat(false); - if (!ackSent) - sendAck(); - } - } - case Order.TEMP_IDLE -> { + Heater(double target) { + this.target = target; + this.hovering = true; // No acks before actual setpoint is acquired + } + + synchronized public void setTarget(double target) { + this.target = target; + this.hovering = false; + } + + @Override + public void run() { + double current; + try { + while (!isInterrupted()) { + current = io.getTemperature(); + + if (io.getWaterLevel() == 0) { io.heat(false); - if (!ackSent) - sendAck(); - } - default -> { + sleep(60000 / Settings.SPEEDUP); continue; } + + if (current < target) { + io.heat(true); + } + + else if (current >= target) { + io.heat(false); + + if (!hovering) { + sendAck(); + hovering = true; + sleep(60000 / Settings.SPEEDUP); + continue; + } + } + + sleep(60000 / Settings.SPEEDUP); } - } catch (Exception e) { - System.out.println("Exception: " + e); + } catch (InterruptedException e) { + io.heat(false); + Thread.currentThread().interrupt(); } } } diff --git a/wash/src/wash/control/ControllerWater.java b/wash/src/wash/control/ControllerWater.java index 6dcf200..0e7f647 100644 --- a/wash/src/wash/control/ControllerWater.java +++ b/wash/src/wash/control/ControllerWater.java @@ -1,7 +1,5 @@ package wash.control; -import static wash.control.WashingMessage.Order.WATER_IDLE; - import actor.ActorThread; import wash.control.WashingMessage.Order; import wash.io.WashingIO; @@ -10,67 +8,98 @@ public class ControllerWater extends ActorThread { WashingMessage.Order o; WashingMessage m; - WashingMessage temp; WashingIO io; - - Order waterState = WATER_IDLE; - boolean ackSent = true; + WaterPid waterpid; public ControllerWater(WashingIO io) { this.io = io; + this.waterpid = new WaterPid(0, 1); } public void sendAck() { - ackSent = true; m.sendAck(this); } @Override public void run() { + waterpid.start(); while (true) { + // m = poll(60000 / Settings.SPEEDUP).orElse(new WashingMessage(this, Order.NOOP)); try { - temp = receiveWithTimeout(10000 / Settings.SPEEDUP); - - // If there is a new message, swap - if (temp != null) { - m = temp; - ackSent = false; // We have a new order - } - - // If this message is not the same as last time - if (m != null && m.order() != waterState) { - waterState = m.order(); - } - - switch (waterState) { - case Order.WATER_DRAIN -> { - io.drain(true); - io.fill(false); - if (io.getWaterLevel() == 0 && !ackSent) - sendAck(); - } - case Order.WATER_FILL -> { - io.drain(false); - - if (io.getWaterLevel() < 19) - io.fill(true); - else { - io.fill(false); - if (!ackSent) { - sendAck(); - } - } - } - case Order.WATER_IDLE -> { - io.drain(false); - io.fill(false); - } - default -> { - continue; - } - } + m = take(); } catch (Exception e) { - System.out.println("Exception: " + e); + System.exit(1); + } + + switch (m.order()) { + case Order.WATER_DRAIN -> waterpid.setTarget(0); + case Order.WATER_FILL -> waterpid.setTarget(20); + case Order.WATER_IDLE -> waterpid.setIdle(); + default -> { + continue; + } + } + } + } + + /** A pid controller that is not actually a pid controller */ + public final class WaterPid extends Thread { + private double target; + private final double tolerance; // Acceptable range around target + private boolean hover = false; // Indicates if we're close enough to the target + private boolean hasAcknowledged = true; // Tracks if ack has been sent + + public WaterPid(double targetLiter, double tolerance) { + this.target = targetLiter; + this.tolerance = tolerance; + } + + /** Set the target level */ + public synchronized void setTarget(double targetLiter) { + this.target = targetLiter; + this.hover = false; + this.hasAcknowledged = false; // Reset ack when target changes + } + + /** Just coast at current level */ + public synchronized void setIdle() { + this.setTarget(io.getWaterLevel()); + } + + /** Main control loop */ + @Override + public void run() { + while (!isInterrupted()) { + double currentLevel = io.getWaterLevel(); // Get current water level + double error = target - currentLevel; + hover = Math.abs(error) <= tolerance; + + // Open or close drain/fill based on current water level + if (!hover) { + if (error > 0) { + io.fill(true); // Open tap to increase water level + io.drain(false); // Ensure drain is closed + } else { + io.drain(true); // Open drain to decrease water level + io.fill(false); // Ensure tap is closed + } + } else { + // Stop adjustments if hovering near target + io.fill(false); + io.drain(false); + + // Send acknowledgment if target is reached for the first time + if (!hasAcknowledged) { + sendAck(); + hasAcknowledged = true; + } + } + + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } } } } diff --git a/wash/src/wash/control/Settings.java b/wash/src/wash/control/Settings.java index 26433e9..733582c 100644 --- a/wash/src/wash/control/Settings.java +++ b/wash/src/wash/control/Settings.java @@ -3,5 +3,5 @@ package wash.control; public interface Settings { // simulation speed-up factor: 50 means the simulation is 50 times faster than // real time. Modify this as you wish. - int SPEEDUP = 400; + int SPEEDUP = 50; } diff --git a/wash/src/wash/control/Wash.java b/wash/src/wash/control/Wash.java index 5755321..0f0f4bf 100644 --- a/wash/src/wash/control/Wash.java +++ b/wash/src/wash/control/Wash.java @@ -32,7 +32,7 @@ public class Wash { // if the user presses button 0, and a program has been started, stop it switch (n) { - case 0 -> t.interrupt(); + case 0 -> t = new WashingProgramStop(io, temp, water, spin); case 1 -> t = new WashingProgram1(io, temp, water, spin); case 2 -> t = new WashingProgram2(io, temp, water, spin); case 3 -> t = new WashingProgram3(io, temp, water, spin); @@ -42,8 +42,7 @@ public class Wash { } } - if (!t.isInterrupted()) - t.start(); + t.start(); } temp.interrupt(); diff --git a/wash/src/wash/program/WashingProgram1.java b/wash/src/wash/program/WashingProgram1.java index 581ce97..5783210 100644 --- a/wash/src/wash/program/WashingProgram1.java +++ b/wash/src/wash/program/WashingProgram1.java @@ -39,39 +39,19 @@ public final class WashingProgram1 extends ActorThread { // Lock the hatch io.lock(true); - // Let water into the machine water.send(new WashingMessage(this, WATER_FILL)); receive(); - // Heat to 40 C - temp.send(new WashingMessage(this, TEMP_SET_40)); + // Instruct SpinController to rotate barrel slowly, back and forth + // Expect an acknowledgment in response. + spin.send(new WashingMessage(this, SPIN_SLOW)); receive(); - // Keep the temperature for 30 min - Thread.sleep(30 * 60000 / Settings.SPEEDUP); - - // Drain - temp.send(new WashingMessage(this, TEMP_IDLE)); - receive(); - water.send(new WashingMessage(this, WATER_DRAIN)); - receive(); - - // Rinse 5*2 minutes in cold water - for(int a = 0; a < 5; a++) { - water.send(new WashingMessage(this, WATER_FILL)); - receive(); - - Thread.sleep(2 * 60000 / Settings.SPEEDUP); - - water.send(new WashingMessage(this, WATER_DRAIN)); - receive(); - } - - // Centrifuge for five minutes - spin.send(new WashingMessage(this, SPIN_FAST)); - receive(); + // Spin for five simulated minutes (one minute == 60000 milliseconds) Thread.sleep(5 * 60000 / Settings.SPEEDUP); + // Instruct SpinController to stop spin barrel spin. + // Expect an acknowledgment in response. spin.send(new WashingMessage(this, SPIN_OFF)); receive(); diff --git a/wash/src/wash/program/WashingProgram2.java b/wash/src/wash/program/WashingProgram2.java index 860801d..e84e596 100644 --- a/wash/src/wash/program/WashingProgram2.java +++ b/wash/src/wash/program/WashingProgram2.java @@ -40,62 +40,9 @@ public final class WashingProgram2 extends ActorThread { // Lock the hatch io.lock(true); - // Let water into the machine - water.send(new WashingMessage(this, WATER_FILL)); - receive(); - - // Heat to 40 C - temp.send(new WashingMessage(this, TEMP_SET_40)); - receive(); - - // Keep the temperature for 20 min - Thread.sleep(20 * 60000 / Settings.SPEEDUP); - - // Drain - temp.send(new WashingMessage(this, TEMP_IDLE)); - receive(); - water.send(new WashingMessage(this, WATER_DRAIN)); - receive(); - - // Let water into the machine - water.send(new WashingMessage(this, WATER_FILL)); - receive(); - // Heat to 60 C - temp.send(new WashingMessage(this, TEMP_SET_60)); - receive(); - - // Keep the temperature for 20 min - Thread.sleep(30 * 60000 / Settings.SPEEDUP); - - // Kill the heat - temp.send(new WashingMessage(this, TEMP_IDLE)); - receive(); - - // Rinse 5*2 minutes in cold water - for(int a = 0; a < 5; a++) { - water.send(new WashingMessage(this, WATER_FILL)); - receive(); - - Thread.sleep(2 * 60000 / Settings.SPEEDUP); - - water.send(new WashingMessage(this, WATER_DRAIN)); - receive(); - } - - // Centrifuge for five minutes - spin.send(new WashingMessage(this, SPIN_FAST)); - receive(); + // Spin for five simulated minutes (one minute == 60000 milliseconds) Thread.sleep(5 * 60000 / Settings.SPEEDUP); - spin.send(new WashingMessage(this, SPIN_OFF)); - receive(); - - temp.send(new WashingMessage(this, TEMP_IDLE)); - receive(); - - water.send(new WashingMessage(this, WATER_DRAIN)); - receive(); - // Now that the barrel has stopped, it is safe to open the hatch. io.lock(false); System.out.println("WashingProgram2 Finished..."); diff --git a/wash/src/wash/program/WashingProgram3.java b/wash/src/wash/program/WashingProgram3.java index 79ee344..c7b7f26 100644 --- a/wash/src/wash/program/WashingProgram3.java +++ b/wash/src/wash/program/WashingProgram3.java @@ -41,16 +41,17 @@ public final class WashingProgram3 extends ActorThread { io.lock(true); // Switch off heating - temp.send(new WashingMessage(this, TEMP_IDLE)); - receive(); + // temp.send(new WashingMessage(this, TEMP_IDLE)); + // System.out.println(receive()); + // temp.send(new WashingMessage(this, TEMP_SET_40)); + // System.out.println(receive()); - // Spin off just to be sure - spin.send(new WashingMessage(this, SPIN_OFF)); - receive(); + temp.send(new WashingMessage(this, WATER_FILL)); + System.out.println(receive()); + temp.send(new WashingMessage(this, WATER_DRAIN)); + System.out.println(receive()); - // Drain water - water.send(new WashingMessage(this, WATER_DRAIN)); - receive(); + // Thread.sleep(5 * 60000 / Settings.SPEEDUP); // Unlock hatch io.lock(false); diff --git a/wash/src/wash/program/WashingProgramStop.java b/wash/src/wash/program/WashingProgramStop.java new file mode 100644 index 0000000..410d596 --- /dev/null +++ b/wash/src/wash/program/WashingProgramStop.java @@ -0,0 +1,80 @@ +package wash.program; + +import actor.ActorThread; +import wash.control.WashingMessage; +import wash.io.WashingIO; + +import static wash.control.WashingMessage.Order.*; + +/** + * Program 3 for washing machine. This also serves as an example of how washing + * programs can be structured. + * + * This short program stops all regulation of temperature and water levels, + * stops the barrel from spinning, and drains the machine of water. + * + * It can be used after an emergency stop (program 0) or a power failure. + */ +public final class WashingProgramStop extends ActorThread { + + private WashingIO io; + private ActorThread temp; + private ActorThread water; + private ActorThread spin; + + public WashingProgramStop(WashingIO io, + ActorThread temp, + ActorThread water, + ActorThread spin) + { + this.io = io; + this.temp = temp; + this.water = water; + this.spin = spin; + } + + @Override + public void run() { + try { + System.out.println("WashingProgramStop Starting..."); + + // Switch off heating + temp.send(new WashingMessage(this, TEMP_IDLE)); + + // Wait for temperature controller to acknowledge + WashingMessage ack1 = receive(); + System.out.println("got " + ack1); + + // Drain barrel, which may take some time. To ensure the barrel + // is drained before we continue, an acknowledgment is required. + water.send(new WashingMessage(this, WATER_DRAIN)); + WashingMessage ack2 = receive(); // wait for acknowledgment + System.out.println("got " + ack2); + + // Now that the barrel is drained, we can turn off water regulation. + water.send(new WashingMessage(this, WATER_IDLE)); + WashingMessage ack3 = receive(); // wait for acknowledgment + System.out.println("got " + ack3); + + // Switch off spin. We expect an acknowledgment, to ensure + // the hatch isn't opened while the barrel is spinning. + spin.send(new WashingMessage(this, SPIN_OFF)); + WashingMessage ack4 = receive(); // wait for acknowledgment + System.out.println("got " + ack4); + + // Unlock hatch + io.lock(false); + + System.out.println("WashingProgramStop Finished..."); + } catch (InterruptedException e) { + System.out.println("WashingProgramStop Interrupted..."); + // If we end up here, it means the program was interrupt()'ed: + // set all controllers to idle + + temp.send(new WashingMessage(this, TEMP_IDLE)); + water.send(new WashingMessage(this, WATER_IDLE)); + spin.send(new WashingMessage(this, SPIN_OFF)); + System.out.println("washing stop program terminated"); + } + } +}