/*
* Copyright 2014-2015 Cel Skeggs
*
* This file is part of the CCRE, the Common Chicken Runtime Engine.
*
* The CCRE is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the CCRE. If not, see <http://www.gnu.org/licenses/>.
*/
package ccre.instinct;
import java.util.ArrayList;
import ccre.channel.BooleanInput;
import ccre.channel.EventOutput;
import ccre.cluck.CluckPublisher;
import ccre.log.Logger;
import ccre.rconf.RConf;
import ccre.rconf.RConf.Entry;
import ccre.rconf.RConfable;
import ccre.tuning.TuningContext;
import ccre.verifier.FlowPhase;
/**
* An easy way to have multiple autonomous modes. Simply register a series of
* InstinctModeModules with this.
*
* @author skeggsc
*/
public final class InstinctMultiModule extends InstinctModule {
/**
* The list of modes registered with this InstinctMultiModule.
*/
private final ArrayList<InstinctModeModule> modes = new ArrayList<InstinctModeModule>();
/**
* The actively-selected mode, or null.
*/
private InstinctModeModule mode;
/**
* The TuningContext used both for the storage segment and cluck node to
* store and edit the current mode.
*/
private final TuningContext context;
/**
* Create a new InstinctMultiModule with a BooleanInput controlling when
* this module should run.
*
* @param shouldBeRunning The input to control the running of this module.
* @param context the TuningContext to use in this MultiModule.
*/
public InstinctMultiModule(BooleanInput shouldBeRunning, TuningContext context) {
super(shouldBeRunning);
this.context = context;
}
/**
* Create a new InstinctModule that needs to be registered before it will be
* useful.
*
* @param context the TuningContext to use in this MultiModule.
*
* @see ccre.frc.FRC#registerAutonomous(InstinctModule)
*/
public InstinctMultiModule(TuningContext context) {
super();
this.context = context;
}
/**
* Set the active mode to the given mode.
*
* @param mode the new active mode.
* @throws IllegalArgumentException if the specified mode does not exist in
* this MultiModule.
*/
@FlowPhase
public void setActiveMode(InstinctModeModule mode) throws IllegalArgumentException {
if (!modes.contains(mode)) {
throw new IllegalArgumentException("The specified mode does not exist: " + mode.getModeName() + " in " + modes);
}
this.mode = mode;
this.context.getSegment().setStringForKey("autonomous-mode", mode.getModeName());
}
/**
* Find the active mode.
*
* @return the active mode.
*/
public InstinctModeModule getActiveMode() {
return mode;
}
/**
* Publish controls over Cluck to manipulate the current mode. These are a
* set of buttons that view or change them mode.
*
* By default, only "Autonomous Mode Check" will be published, which will
* log the current autonomous mode. This lets the driver know the current
* mode.
*
* If showIndividualModes is true, then each mode registered so far will get
* its own "Autonomous Mode: <mode>" button that will switch to that
* mode.
*
* If showCycleChooser is true, then "Autonomous Mode Next" and
* "Autonomous Mode Previous" will be published, which will cycle through
* the autonomous modes.
*
* @param showIndividualModes if the individual modes should have controls.
* @param showCycleChooser if a cyclical selector should have controls.
*/
public void publishDefaultControls(boolean showIndividualModes, boolean showCycleChooser) {
CluckPublisher.publish(context.getNode(), "Autonomous Mode Check", new EventOutput() {
@Override
public void event() {
Logger.info("Current autonomous mode: " + mode.getModeName());
}
});
if (showIndividualModes) {
for (final InstinctModeModule curmode : modes) {
CluckPublisher.publish(context.getNode(), "Autonomous Mode: " + curmode.getModeName(), new EventOutput() {
@Override
public void event() {
setActiveMode(curmode);
Logger.info("New autonomous mode: " + mode.getModeName());
}
});
}
}
if (showCycleChooser) {
CluckPublisher.publish(context.getNode(), "Autonomous Mode Next", new EventOutput() {
@Override
public void event() {
boolean wasLast = (mode == modes.get(modes.size() - 1));
for (InstinctModeModule m : modes) {
if (wasLast) {
setActiveMode(m);
Logger.info("New autonomous mode: " + mode.getModeName());
return;
} else {
wasLast = (m == mode);
}
}
// Couldn't find the mode. (We would have returned earlier.)
Logger.warning("Mode not found while iterating: " + mode.getModeName());
setActiveMode(modes.get(0));// Just use the first mode.
Logger.info("New autonomous mode: " + mode.getModeName());
}
});
CluckPublisher.publish(context.getNode(), "Autonomous Mode Previous", new EventOutput() {
@Override
public void event() {
InstinctModeModule last = modes.get(modes.size() - 1);
for (InstinctModeModule m : modes) {
if (m == mode) {
setActiveMode(last);
Logger.info("New autonomous mode: " + mode.getModeName());
return;
}
last = m;
}
// Couldn't find the mode. (We would have returned earlier.)
Logger.warning("Mode not found while iterating: " + mode.getModeName());
setActiveMode(modes.get(0));// Just use the first mode.
Logger.info("New autonomous mode: " + mode.getModeName());
}
});
}
}
/**
* Publish an RConfComponent that shows the current autonomous mode and
* allows to change it.
*/
public void publishRConfControls() {
CluckPublisher.publishRConf(context.getNode(), "Autonomous Mode Selector", new RConfable() {
@Override
public Entry[] queryRConf() throws InterruptedException {
Entry[] outs = new Entry[2 + modes.size()];
outs[0] = RConf.title("Select Autonomous Mode");
int i = 1;
for (InstinctModeModule m : modes) {
outs[i++] = (m == mode) ? RConf.string(m.getModeName()) : RConf.button(m.getModeName());
}
outs[i] = RConf.autoRefresh(5000);
return outs;
}
@Override
public boolean signalRConf(int field, byte[] data) throws InterruptedException {
if (field >= 1 && field <= modes.size()) {
setActiveMode(modes.get(field - 1));
return true;
}
return false;
}
});
}
/**
* Load the default mode setting and any settings in any of the modes.
*
* @param defaultMode the mode to default to if there is no valid saved
* mode.
*/
public void loadSettings(InstinctModeModule defaultMode) {
if (defaultMode == null) {
throw new NullPointerException("A default mode must be specified!");
}
String modeName = this.context.getSegment().getStringForKey("autonomous-mode");
if (modeName == null) {
this.mode = defaultMode;
} else {
this.mode = null;
for (InstinctModeModule foundMode : modes) {
if (foundMode.getModeName().equals(modeName)) {
this.mode = foundMode;
}
}
if (this.mode == null) {
Logger.warning("Invalid loaded mode name: " + modeName);
this.mode = defaultMode;
}
}
for (InstinctModeModule curmode : modes) {
curmode.loadSettings(context);
}
}
/**
* Add the specified mode to this MultiModule.
*
* @param newMode the mode to add.
* @return the added mode.
* @throws IllegalArgumentException if the mode's name is already used.
*/
public InstinctModeModule addMode(InstinctModeModule newMode) throws IllegalArgumentException {
for (InstinctModeModule oldMode : modes) {
if (oldMode.getModeName().equals(newMode.getModeName())) {
throw new IllegalArgumentException("Duplicate mode name: " + newMode.getModeName());
}
}
newMode.setParent(this);
modes.add(newMode);
return newMode;
}
/**
* Add a "null mode" to this MultiModule.
*
* The null mode will have the given name and will do nothing but log a
* message when it runs. This could be "No mode selected" or something.
*
* @param name the null mode's name.
* @param message the null mode's message.
* @return the newly created mode.
*/
public InstinctModeModule addNullMode(String name, final String message) {
if (message == null) {
throw new NullPointerException("The null mode's message cannot be null!");
}
return this.addMode(new InstinctModeModule(name) {
@Override
protected void autonomousMain() throws AutonomousModeOverException, InterruptedException {
Logger.info(message);
}
@Override
public void loadSettings(TuningContext ignoredContext) {
// Don't load anything.
}
});
}
@Override
protected final void autonomousMain() throws Throwable {
if (mode == null) {
Logger.severe("No autonomous mode found! Did you remember to call InstinctMultiModule.loadSettings()?");
} else {
Logger.info("Running autonomous mode: " + mode.getModeName());
mode.autonomousMain();
}
}
}