-
Notifications
You must be signed in to change notification settings - Fork 4
[Tutorial 2.2] Control Structures ExpirationTimer, PauseTimer, MultipleSourceBooleanController, Ticker
See the readme on the main page for the correct documentation.
[Back to the previous tutorial]([Tutorial 2.1] The Logging Framework)
The CCRE provides a number of Control Structures that encapsulate common control functionality. This is done so that programs will not have to re-write these systems themselves, yielding more concise, effective, and maintainable code.
An ExpirationTimer acts sort of like an alarm clock. You can schedule a series of alarms with certain delays, and then start the timer. When each delay passes, the timer will trigger the event associated with the delay. The timer can be fed, which restarts the timer as if it were just started. (This can be used to implement WatchDogs, hence the name feed()
.) The timer can also be stopped, which resets the timer and prevents it from running until it is started again.
For this example, we will connect an ExpirationTimer to some Joystick buttons and make it modify some relay outputs. We'll need the following section of code:
EventInput startTimerButton = Igneous.joystick1.getButtonSource(3);
EventInput stopTimerButton = Igneous.joystick1.getButtonSource(4);
BooleanOutput relay2Forward = Igneous.makeForwardRelay(2); // The forward side of the second Relay.
BooleanOutput relay2Reverse = Igneous.makeReverseRelay(2); // The reverse side. We're using Relay 2 because we connected the compressor to Relay 1.
Creating a new ExpirationTimer:
ExpirationTimer timer = new ExpirationTimer();
Scheduling a message to tell us that we've started the timer:
timer.schedule(50, new EventLogger(LogLevel.INFO, "Timer has been started!"));
schedule()
can take two arguments - the delay to trigger at (in milliseconds) and the EventOutput
to trigger.
Another way of scheduling a message, this time to let us know that it's been running:
EventInput threeSecondsIn = timer.schedule(3000);
EventLogger.log(threeSecondsIn, LogLevel.FINE, "Timer has passed the three-second mark!");
Here, schedule()
takes one argument (the delay) and returns an EventInput
that represents the event of the timer reaching this point.
Now let's turn some relays on and off:
timer.scheduleEnable(1000, relay2Forward);
timer.scheduleDisable(2000, relay2Forward);
This this will turn the forward half of the relay on starting at one second in and turn it off a second later.
Or perhaps a faster way:
timer.scheduleBooleanPeriod(1500, 2500, relay2Reverse, true);
This will turn the reverse half of the relay on (the fourth argument's value) starting at 1.5 seconds and off (the inversion of the fourth argument's value) at 2.5 seconds.
If we needed to do a lot more of this, we could use scheduleToggleSequence
.
Now, let's make this timer go. We could do the following:
startTimerButton.send(new EventOutput() {
public void event() {
timer.start();
}
});
However, this has an issue. It will only work once, and after that it won't again, because the timer's already been started. In fact, it will even throw an exception the second time! That just won't do, will it. Let's start by using startOrFeed()
instead:
startTimerButton.send(new EventOutput() {
public void event() {
timer.startOrFeed();
}
});
This will not throw an exception - if the timer has already started, it will just be fed. This code still isn't optimal, though. Let's make it shorter:
startTimerButton.send(timer.getStartOrFeedEvent());
Or even shorter than that:
timer.startOrFeedWhen(startTimerButton);
We'll also want the same to stop it:
timer.stopWhen(stopTimerButton);
Try this out in the emulator.
Let's imagine for the moment that we want this to be running while a button is pressed, instead of being controlled by some buttons. Instead of our previous startOrFeedWhen()
and stopWhen()
, let's try:
BooleanOutput running = timer.getRunningControl();
This BooleanOutput will allow for controlling the timer's running status. Let's attach it to a button:
BooleanMixing.pumpWhen(Igneous.globalPeriodic, Igneous.joystick1.getButtonChannel(5), running);
This will make the 5th button control the Joystick!
Let's go to something a bit simpler. Sometimes, you want to make something run for a short amount of time, but don't want to deal with the added complexity of an ExpirationTimer.
A PauseTimer is always running or not running, and is an EventOutput and so it can be started by firing its event. If it's already running, the duration is reset. Let's try the following: (Remove the previous code for the ExpirationTimer.)
BooleanOutput relay2Forward = Igneous.makeForwardRelay(2);
PauseTimer timer = new PauseTimer(1000);
Igneous.joystick1.getButtonSource(3).send(timer);
timer.send(relay2Forward);
The new PauseTimer here will run for 1000 milliseconds, as specified in the arguments. The rest of this should be self-explanatory at this point. (Recall that timer is both an EventOutput and a BooleanInput.)
Try this in the emulator. Notice how this code is much shorter and simpler than using an ExpirationTimer, but isn't as powerful.
Sometimes, you need to combine a set of booleans together in a way that isn't easily supported by BooleanMixing
.
What if we want to check if two buttons are pressed and a third boolean is off but by writing to a BooleanOutput instead of reading a BooleanInput?
Let's set up a MultipleSourceBooleanController:
MultipleSourceBooleanController controller = new MultipleSourceBooleanController(MultipleSourceBooleanController.AND);
controller.addInput(Igneous.joystick1.getButtonChannel(4));
controller.addInput(Igneous.joystick1.getButtonChannel(5));
BooleanOutput out = controller.getOutput(false); // Default to false until a value is written.
Now, let's give it an event for when to update:
Igneous.globalPeriodic.send(controller);
This is needed because it needs to know when to recalculate the output.
Let's control a relay now:
controller.send(relay2Forward);
(You could, of course, get()
it instead. Or BooleanMixing.pumpWhen()
it.)
And, of course, do something to provide data to that BooleanOutput:
BooleanMixing.pumpWhen(Igneous.globalPeriodic, Igneous.joystick1.getButtonChannel(6), BooleanMixing.invert(out));
(Here, it would make more sense to use addInput()
instead, but I'm trying to demo the abilities of MultipleSourceBooleanController.)
Try it in the emulator.
Sometimes you want an event to happen once per specific delay. This is often done using a Ticker. (Note, however, that a 10-millisecond timer is included as Igneous.constantPeriodic
.)
Ticker update = new Ticker(5000);
EventLogger.log(update, LogLevel.FINER, "Five-second timer!");
Try it in the emulator. (Remember that the logging mostly comes out in the Eclipse console.)
Next: [The CCRE Collections Framework]([Tutorial 2.3] The CCRE Collections Framework)