Dynamic Autonomous Help

So this year my team decided we wanted to work on something the AZTECHS made, which is a dynamic autonomous. A dynamic autonomous, if you aren’t aware, is when you record an auto in teleop and save it for future use. We couldn’t understand their code, and since there was a lot to work on, I was assigned the task of making our very own. I’ve tried my best to make it portable, so if other teams want to use it they can. The thing is, I’ve hit such a roadblock. In my head, the code I have should be working flawlessly, but it just either doesn’t record correctly, or it doesn’t build or play back correctly. I’ve been trying to figure out the problem for 2 weeks, even porting it to c# for easier debugging using visual studio, and I still can’t find the problem. If I could receive any help on this, that would be very much appreciated. If you have any questions on how things are used, or how it’s supposed to work, please let me know. I am working on documentation, and will post that when the library is finished. I apologize for the amount of code there is; The bulk of it is in TaskGroup and Recorder

Recorder

package team498.robot.dynamic;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import edu.wpi.first.wpilibj.Timer;

/**
 * The Dynamic Autonomous recorder class for the recording, saving, loading, and
 * building of dynamic autonomous modes.<br/>
 * <br/>
 * If you would like to see changes made to this, please contact me at
 * [email protected]
 * 
 * @version 1.0
 * 
 * @author Micah Neitz <br/>
 *         Team 498
 * 
 */
public final class Recorder {

	private static final String DIRECTORY = "/home/lvuser/frc/dynamicauto/";
	private Map<String, Task> tasks;
	private Map<String, JoystickInput> inputs;
	private ArrayList<PassiveTask> passives;
	private ArrayList<JoyState> oldStates;
	private ArrayList<JoyState> newStates;
	private ArrayList<InputLog> logs;
	private Timer timer;

	/**
	 * Creates a new recorder object
	 */
	public Recorder() {
		tasks = new HashMap<String, Task>();
		inputs = new HashMap<String, JoystickInput>();
		passives = new ArrayList<>();
		oldStates = newStates = new ArrayList<>();
		timer = new Timer();
		logs = new ArrayList<>();
	}

	/**
	 * Needs to be run on loop while recording inputs
	 */
	public void Read() {
		// Gets the timestamp
		double time = timer.get();
		//Start the timer
		if (time == 0) {
			timer.start();
		}
		// Keeps old states and grabs the new ones so we can see if they've changed
		oldStates = newStates;
		newStates = _grabValues();
		// If this is the first time we've recorded values, log all the starting values
		if (oldStates.size() == 0) {
			for (JoyState newState : newStates) {

				if (newState.isBool)
					logs.add(new InputLog(newState.name, newState.boolState, time));
				else
					logs.add(new InputLog(newState.name, newState.doubleState, time));

			}
			return;
		}
		if (oldStates.size() != newStates.size()) {
			return;
		}
		for (int i = 0; i < newStates.size(); i++) {
			JoyState newState = newStates.get(i);
			if (newState.isBool) {
				if (newState.boolState != oldStates.get(i).boolState) {
					InputLog input = new InputLog(newState.name, newState.boolState, time);
					logs.add(input);
				}
			} else {
				if (newState.doubleState != oldStates.get(i).doubleState) {
					InputLog input = new InputLog(newState.name, newState.doubleState, time);
					logs.add(input);
				}
			}
		}
	}

	/**
	 * Saves current autonomous
	 * 
	 * @param name
	 *            The name of the autonomous you are saving
	 * @throws IOException
	 *             Throws when you don't have access, or another error out of my
	 *             control occurs
	 */
	public void Save(String name) throws IOException {
		String text = _compileString();
		File directoryFile = new File(DIRECTORY);
		if (!directoryFile.exists())
			directoryFile.mkdir();
		FileWriter fw = new FileWriter(DIRECTORY + name + ".txt");
		BufferedWriter bw = new BufferedWriter(fw);
		bw.write(text);
		bw.close();
	}

	/**
	 * Loads an autonomous from memory
	 * 
	 * @param name
	 *            The name of the autonomous you are loading
	 * @throws IOException
	 *             Happens when you have no autonomous to load, or when you typed in
	 *             the name wrong
	 */
	public void Load(String name) throws IOException {
		File directoryFile = new File(DIRECTORY);
		if (!directoryFile.exists())
			throw new IOException("You haven't saved a single autonomous yet!");
		FileReader fr = new FileReader(DIRECTORY + name + ".txt");
		BufferedReader br = new BufferedReader(fr);
		String text = br.readLine();
		br.close();
		if (text == null)
			throw new IOException("That file was empty");
		_parseString(text);
	}

	/**
	 * Clears current autonomous
	 */
	public void Clear() {
		oldStates = newStates = new ArrayList<>();
		timer.stop();
		timer.reset();
	}

	/**
	 * Assigns a task to a button<br/>
	 * <br/>
	 * Does not accept {@link PassiveTask}, will effectively do nothing if you try
	 * to add one this way. Use {@link #AddPassive()} for that
	 * 
	 * @param name
	 *            The name of the {@link JoystickInput}/{@link Task} assignment.
	 *            Could be anything, is just used for identification
	 * @param input
	 *            The {@link JoystickInput} to assign the {@link Task} to, can be an
	 *            axis or a button.
	 * @param task
	 *            The {@link Task} to assign to the {@link JoystickInput}
	 */
	public void Assign(String name, JoystickInput input, Task task) {
		if (task instanceof PassiveTask)
			return;
		tasks.put(name, task);
		inputs.put(name, input);
	}

	/**
	 * Adds a {@link PassiveTask} that will always run in the background of the auto
	 * until completion. <br/>
	 * <br/>
	 * Will accept {@link Task}, although will never change the value that it
	 * defaults to.
	 * 
	 * @param task
	 *            The {@link PassiveTask} to be added
	 */
	public void AddPassive(PassiveTask task) {
		passives.add(task);
	}

	/**
	 * Builds a {@link TaskGroup} out of the current recording.
	 * 
	 * @return The {@link TaskGroup} to be executed
	 */
	public TaskGroup Build() {
		TaskGroup tg = new TaskGroup(tasks, passives, logs);
		return tg;
	}

	/**
	 * Checks if an autonomous exists on file
	 * 
	 * @param name
	 *            The name of the autonomous
	 * @return <b>{@code true}</b> if the autonomous exists
	 */
	public static boolean Check(String name) {
		File file = new File(DIRECTORY + name + ".txt");
		return file.exists();
	}

	// I don't document the private methods, because I don't need to. No one is
	// going to see this besides me, or the people trying to figure out how this
	// works so they can make changes.
	// If you would like to request a change or addition, please contact me at
	// [email protected]

	private ArrayList<JoyState> _grabValues() {
		ArrayList<JoyState> states = new ArrayList<>();
		ArrayList<String> keys = new ArrayList<>(inputs.keySet());
		for (String s : keys) {
			JoyState state = new JoyState();
			if (inputs.get(s).isButton) {
				state.isBool = true;
				state.boolState = inputs.get(s).GetButton();
			} else {
				state.isBool = false;
				state.doubleState = inputs.get(s).GetAxis();
			}
			state.name = s;
			states.add(state);
		}
		return states;
	}

	private String _compileString() {
		StringBuilder builder = new StringBuilder();
		for (InputLog log : logs) {
			builder.append(log.Log());
		}
		return builder.toString().substring(1); // Substring removes first char
	}

	private void _parseString(String text) throws IllegalArgumentException {
		this.logs = new ArrayList<>();
		String] logs = text.split(Pattern.quote(";"));
		for (int i = 0; i < logs.length; i++) {
			String] elements = logs*.split(Pattern.quote("_")); // seperate each element
			// 0 is the name you assigned to the task
			// 1 is the value of the change it is making
			// 2 is whether the input is a button or an axis
			// 3 is the time stamp the change was made
			this.logs.add(new InputLog(elements[0], elements[1], elements[2], elements[3]));
		}
	}
}

TaskGroup

package team498.robot.dynamic;

import java.util.ArrayList;
import java.util.Map;

import edu.wpi.first.wpilibj.Timer;

/**
 * Behaves similar to the way CommandGroup does for Commands, except it's for
 * tasks and you don't make it yourself, it's built by Recorder
 * 
 * @version 1.0
 * 
 * @author Micah Neitz<br/>
 *         Team 498
 *
 */
public final class TaskGroup {
	private Map<String, Task> tasks;
	private ArrayList<PassiveTask> passives;
	private ArrayList<InputLog> logs;
	private ArrayList<String> keys;
	private int index = 0;
	private Timer timer;

	/**
	 * Used by Recorder to build the task group, it doesn't make sense to be used
	 * outside of its usage in Recorder
	 * 
	 * @param tasks
	 *            The list of tasks sorted by their names
	 * @param passives
	 *            The list of passive tasks
	 * @param logs
	 *            The list of log recordings to use
	 */
	public TaskGroup(Map<String, Task> tasks, ArrayList<PassiveTask> passives, ArrayList<InputLog> logs) {
		this.tasks = tasks;
		this.passives = passives;
		this.logs = logs;
		this.keys = new ArrayList<>(this.tasks.keySet());
		timer = new Timer();
	}

	/**
	 * Should be put in autoPeriodic, or ran on loop during autonomous
	 */
	public void Execute() {

		// Checks if there is no logs. If so, don't continue
		if (logs.size() == 0) {
			return;
		}

		// Checks if you have been going longer than the autonomous period. If so, don't
		// continue
		if (timer.get() > 15)
			return;

		// Checks if the timer has been started, and start it if it hasn't
		if (timer.get() == 0) {
			timer.start();
		}

		// If the timer has passed your timestamp, change the value in your command
		// accordingly
		while (timer.get() > logs.get(index).GetTime()) {
			InputLog log = logs.get(index);
			if (tasks.get(log.GetName()).IsButton()) {
				tasks.get(log.GetName()).Change(log.GetBoolean());
			} else {
				tasks.get(log.GetName()).Change(log.GetDouble());
				System.out.println(log.GetDouble());
			}
			++index;
		}

		// Execute all the active tasks
		for (String s : keys) {
			tasks.get(s).Execute();
		}

		// Execute all the passive tasks
		for (int i = 0; i < passives.size(); ++i) {
			passives.get(i).Execute();
		}
	}
}

PassiveTask

package team498.robot.dynamic;

/**
 * A task that doesn't have a value to be changed directly.<br/>
 * Inherit from this to add somehting as a passive in Recorder
 * 
 * @version 1.0
 * @author Micah Neitz<br/>
 *         Team 498
 *
 */
public abstract class PassiveTask extends Task {

	/**
	 * Isn't used in a passive task, so it's overriden and finalized so you don't
	 * have to worry about it
	 */
	@Override
	public final void Change(boolean value) {
	}

	/**
	 * Isn't used in a passive task, so it's overriden and finalized so you don't
	 * have to worry about it
	 */
	@Override
	public final void Change(double value) {
	}

	/**
	 * Isn't used in a passive task, so it's overriden and finalized so you don't
	 * have to worry about it
	 */
	@Override
	public final boolean IsButton() {
		return false;
	}
}

JoystickInput


package team498.robot.dynamic;

import edu.wpi.first.wpilibj.Joystick;

/**
 * Represents a joystick input, can be an axis or a button, your choice.<br/>
 * Made in a similar fashion to JoystickButton, but with axis support.
 * 
 * @version 1.0
 * 
 * @author Micah Neitz <br/>
 *         Team 498
 */
public final class JoystickInput {

	Joystick stick;
	int port;
	boolean isButton;

	/**
	 * Makes the input, uses the {@link Joystick} and port to decide where to get
	 * values from. It doesn't know whether you want an axis or a button, so it
	 * needs you to specify
	 * 
	 * @param stick
	 *            The {@link Joystick} to gran the values from
	 * @param port
	 *            The port to read from the {@link Joystick}
	 * @param isButton
	 *            Determines if the input is an axis or a button
	 */
	public JoystickInput(Joystick stick, int port, boolean isButton) {
		this.stick = stick;
		this.port = port;
		this.isButton = isButton;
	}

	/**
	 * Returns the axis of the joystick, or {@code 0.0} if the joystick is meant to
	 * be a button
	 * 
	 * @return The axis value
	 */
	public double GetAxis() {
		return isButton ? 0.0 : stick.getRawAxis(port);
	}

	/**
	 * Returns the value of the button, or {@code false} if it's meant to be an axis
	 * 
	 * @return The button value
	 */
	public boolean GetButton() {
		return isButton ? stick.getRawButton(port) : false;
	}
}

Joystate


package team498.robot.dynamic;

/**
 * Represents the state of a joystick input. Under the hood, should never be
 * used
 * 
 * @version 1.0
 * 
 * @author Micah Neitz<br/>
 *         Team 498
 *
 */
final class JoyState {
	public String name;
	public boolean boolState;
	public double doubleState;
	public boolean isBool;
}

ITask


package team498.robot.dynamic;

/**
 * This is the task interface, and should never be used. Under the hood class
 * 
 * @version 1.0
 * 
 * @author Micah Neitz <br/>
 *         Team 498
 */
interface ITask {
	abstract void Init();

	abstract void Run();

	abstract void Execute();

	abstract void Change(boolean value);

	abstract void Change(double value);

	abstract boolean IsButton();
}

InputLog


package team498.robot.dynamic;

import java.math.RoundingMode;
import java.text.DecimalFormat;

/**
 * Logs input to prepare for string compile. Under the hood class, should never
 * be used
 * 
 * @version 1.0
 * 
 * @author Micah Neitz<br/>
 *         Team 498
 *
 */
final class InputLog {

	private String _name;
	private double _doubleValue;
	private double _time;
	private boolean _boolValue;
	private boolean _isButton;

	public String Log() {
		DecimalFormat df = new DecimalFormat("0.0####");
		df.setRoundingMode(RoundingMode.HALF_UP);
		return String.format(";%s_%s_%s_%s", _name, _isButton ? _boolValue ? "1" : "0" : df.format(_doubleValue),
				_isButton ? "1" : "0", _time);
	}

	public String ToString() {
		return Log();
	}

	public double GetDouble() {
		return _doubleValue;
	}

	public boolean GetBoolean() {
		return _boolValue;
	}

	public double GetTime() {
		return _time;
	}

	public boolean IsButton() {
		return _isButton;
	}

	public String GetName() {
		return _name;
	}

	public InputLog(String name, double value, double time) {
		this._name = name;
		this._doubleValue = value;
		this._time = time;
		this._boolValue = false;
		this._isButton = false;
	}

	public InputLog(String name, String value, String isButton, String time) throws IllegalArgumentException {
		boolean valid = true;
		try {
			Double.parseDouble(value);
			Double.parseDouble(time);
		} catch (NumberFormatException e) {
			valid = false;
		}
		if (!valid)
			throw new IllegalArgumentException("Value or Time string was invalid!");
		this._name = name;
		this._boolValue = value == "1";
		this._isButton = isButton == "1";
		this._doubleValue = Double.parseDouble(value);
		this._time = Double.parseDouble(time);
	}

	public InputLog(String name, boolean value, double time) {
		this._name = name;
		this._time = time;
		this._boolValue = value;
		this._doubleValue = 0.0;
		this._isButton = true;
	}
}


So I’ve noticed that I’ve posted the code and asked for help, but haven’t given how the code works or any context on how to use it and what’s going wrong.

So the way it’s designed, what it should do is something similar to Command-Based robots. You can inherit from Task and make a task of your own, to do a task like driving or using an arm. You can then assign these tasks to inputs, either a button or an axis, specified in JoystickInput. You use Read on a loop to record a list of button states, and you can use Save and Load to save this list. The way you would use it: You make a Recorder object, you use read to record 15 seconds of inputs, you would save this to file using whatever name you please, and you could load it back and build it into a TaskGroup. You use the execute() function of the taskgroup on loop during autonomous to run the recorded auto. The issue is, it seems to either have issues making the logs or reading them, not sure, but it won’t use an axis correctly. I havent had a chance to test if it reads buttons correctly.

Have you looked on the roboRio for the recording file? If the recording file looks good, then the issue is playback. If the recording file is not good, then the issue is with recording.

1 Like

Becuase of the way the recorder is built, it isn’t required to save and load. From what I’ve seen, the saving and loading appears to work. Even if you record an auto and teleop and then immediately play the recorder object in auto, it doesn’t function. It uses the axis for fractions of a second, several times in a row, instead of for 1 second once, things like that.

I guess I missed the point of all this, then. I thought the idea was to drive the robot, record the state as you went along, write that record to a file, then retrieve the record from the file and play it back at a later time. If it doesn’t save it somewhere, what are you trying to accomplish?

Sorry, I’m not the best at explaining things. It’s intention is to save files and the load them later, but for testing purposes this isn’t required. If you would like an example on how both of these situations would work (using Command-Based Robot, because that’s what I’m familiar with) then that could be supplied.

Where this all started:

What I was trying to get you to do is narrow down the area the problem exists in. If you can cut the potential area where the problem is in half, then you’ve got half as much code to search through. If you don’t know whether the recording is working or the play back is working, then… the obvious thing to try is to narrow that down. So, even if you don’t need to for what you’re doing… write the file. Then you can open the file on the Rio in a text editor and see if it recorded what it was supposed to. If it did, then you have a playback issue. If it did not, then you at least have a recording issue.

Hey, I’m a rookie programmer on the aztechs team this year. As far as I can tell what we did was create a command that ran Sequentially other commands. If you want to use our code as a reference you can download it off of our github here: https://github.com/Aztechs157/FRC-2017

Hope this helps!

Thank you for trying to help, I hadn’t realized there was more than one AZTECHS team lol. I am referring to team 6479 and their Dynamic Autonomous Concept

I’m working on a similar system that records all controller inputs to a file and replays them during autonomous. I didn’t have enough time to record the various autonomous modes, so I’ll probably end up recording them during our practice time if our sensor-based autonomous doesn’t work.

I’ll release our code on GitHub as soon as I’m done with it, if you want to use it as an example.

There are, in fact, 6+ Aztech(s) according to TBA.

Thank you, I’ll certainly look into that. I plan on making it a library so other teams can use it with ease, which is probably where a lot of my issues are coming from. Either way, I can’t test it right now due to the lack of a bot to test on :slight_smile: Thank you for wanting to help, I will definitely check that out

That is intense, lol. It’s interesting to see something like that, I wonder if any of them have considered a name change. I would assume that would make it more difficult to be found by people looking to sponsor. I don’t know, just some thought candy. :slight_smile:

Hi guys: We’re in the midst of doing the same thing (effectively as I type this) and I found code from both RobotCasserole1736 (logging) and BeachBot330 (logs and playback) to be EXTREMELY helpful. The RobotCasserole1736 github include a folder for a browser-based CSV file viewer (very slick!), and the BeachBot330 code uses the millisecond timer method in Java to synch up the readout of the data file with the clock on the roboRIO, so you don’t blow past 15 secs of auto command data in a few seconds of looping. Recommended. I’ll go do a shout-out once we have our code cleaned up for distro on GitHub shortly …

Sweet! I appreciate all the support and feedback I’ve been getting. I haven’t had the chance to work on my own code for about a month now due to restrictions in my team’s schedule and resources being more needed elsewhere, but this is remarkable! It’s almost like we have our own team, trying to piece together the puzzle. As soon as I get the chance to work on my own code again, I’ll post on here and try to get a Github repo up that way you guys will have something better to look at that isn’t my mess of a first post.

Don’t forget 2994 - ASTECHZ :smiley: