EONS TO COME (Organizing Code) - SCHS-Robotics/Software-Wiki GitHub Wiki
8097 = EONS
EONS TO COME = Eight O Nine Seven's Trademarked Original Code Organization Method for Efficiency
EONS TO COME is meant to alleviate code organization problems, the biggest of which is the need for duplicating code. The hope is that its legacy will carry on for eons to come, and yes that is kinda stupid but I still like the acronym.
Challenge: You have a red autonomous program and a blue autonomous program. They do pretty much the same thing, but the robot goes left on the red alliance where on the blue alliance it goes right, and vice versa. However, you DON'T want to make 2 different OpModes with nearly identical code. And what if you want many slightly different variations for your autonomous program?
Solution: Java Inheritance! – But it's more than that. This organization method is meant to be intuitive while also teaching you important things about the Java programming language. It is the WAY TO JAM. That is, it Will Allow You To Obtain Java Abilities and Mastery.
Ok, enough acronyms.
Below is a diagram demonstrating a possible way to organize code for the Velocity Vortex game. You may wonder what "abstract" means, but otherwise it should seem pretty straightforward.
Here's how to implement it in code:
- It is recommended that you use LinearOpMode as the basis for all of your OpModes, rather than the standard OpMode (See OpMode Basics).
BaseOpMode
- Make a class called "BaseOpMode" (better names are welcome) and make it extend LinearOpMode. BUTT WEIGHT! Remember that word "abstract?" BaseOpMode will be an abstract class, meaning it can have anything a regular class can, but is not required to define the
runOpMode()
method. This is good, because BaseOpMode will contain a bunch of widely useful variables and methods, but will not define any specific functionality. The purpose of BaseOpMode is to provide more specialized OpModes such as "Red Autonomous" with generic functionality that doesn't need to be defined every time you make a new OpMode.public abstract class BaseOpMode extends LinearOpMode
- DO NOT copy the template OpMode. Instead, BaseOpMode will have these basic components:
- constants
- variables for hardware devices
- basic movement methods
- hardware initialization method(s)
Example of BaseOpMode:
public abstract class BaseOpMode extends LinearOpMode {
// Constants:
public final static double DEFAULT_SPEED = 0.75;
public final static double ENCODER_TICKS_PER_CM = 53.6;
public final static double ENCODER_TICKS_PER_DEGREE = 27.5;
public final static double SERVO_POS_PER_DEGREE = 1.0 / 151.0;
// Hardware Variables
DcMotor leftMotor;
DcMotor rightMotor;
Servo servo;
ColorSensor colorSensor;
I2cAddr colorI2c = I2cAddr.create8bit(0x3c);
//Servo Position Constants
final double servoInitPos = 0.710;
final double servoEndPos = 0.324;
// Basic Movement Methods
public void goForward(double speed) {
leftMotor.setPower(speed);
rightMotor.setPower(-speed);
}
// Hardware Initialization
public void allInit() {
leftMotor = hardwareMap.dcMotor.get("leftMotor");
rightMotor = hardwareMap.dcMotor.get("rightMotor");
servo = hardwareMap.servo.get("servo");
servo.setPosition(servoInitPos);
colorSensor = hardwareMap.colorSensor.get("colorSensor");
colorSensor.setI2cAddress(colorI2c);
}
}
Extending BaseOpMode
- From now on, whenever you create an OpMode, it will extend BaseOpMode. You may still copy the template OpMode (OpMode Basics), but make sure the class declaration is:
In many cases, the class will actually extend a different class which itself extends BaseOpMode. This is explained below in "Abstract classes and methods".public class SomeClass extends BaseOpMode
- Using methods and variables defined in BaseOpMode is as easy as pi. Everything from BaseOpMode is Inherited in the new class, which means all methods and variables can be used as normal.
Here is an example class, which extends the above example of BaseOpMode:
@Autonomous(name = "Do Stuff", group = "OpMode")
public class SomeClass extends BaseOpMode {
@Override
public void runOpMode() throws InterruptedException {
// Initialization
allInit(); //method in BaseOpMode
waitForStart();
// Go forward for 1 second and move a servo
goForward(1.0); //method in BaseOpMode
sleep(1000);
servo.setPosition(servoEndPos); //using variables in BaseOpMode
// Wait for timer to end or for stop button to be pressed
while (opModeIsActive()) {
idle();
}
}
}
Abstract classes and methods
In the diagram at the top of the page, there are other abstract classes besides BaseOpMode, which provide extra organization. Here's how that works:
- The best example of this an autonomous program with two versions - red and blue - each with the same logic. First, you will have an abstract class SomeAutonomous in order to write this logic under one roof.
public abstract class SomeAutonomous extends BaseOpMode
- Now you have a hierarchy of abstract classes. BaseOpMode is the most broad. SomeAutonomous will be more specific, but still won't have enough information to run as an OpMode itself. The key part of this missing information is directions: game elements on the left in the red alliance will be on the right in the blue alliance.
- Now you're ready for abstract methods. These are methods that can only be in abstract classes, which declare a name and parameters but no functionality. They are simply placeholders. In this case, they will be methods that dictate a direction specific to each alliance:
public abstract class SomeAutonomous extends BaseOpMode { public abstract void turnTowardsAdjacentWall(double speed, double degrees); // Add more abstract methods as needed. }
- Using abstract methods is easy. You can write all of your autonomous logic in one place (in the
runOpMode()
method):public abstract class SomeAutonomous extends BaseOpMode { @Override public void runOpMode() throws InterruptedException { allInit(); //Initialize hardware waitForStart(); //Turn 45 degrees, go forward until range sensor sees a wall, move a servo turnTowardsAdjacentWall(1.0, 45); while(rangeSensor.rawUltrasonic() > 30 && opModeIsActive()) { goForward(1.0); } servo.setPosition(servoEndPos); // Wait for timer to end or for stop button to be pressed while (opModeIsActive()) { idle(); } } public abstract void turnTowardsAdjacentWall(double speed, double degrees); }
- Finally, here's how to easily make the OpModes for the red and blue autonomous programs. They will be regular (not abstract) classes, meaning they must define the body for any abstract methods they inherit. In this case, they each define the functionality for
turnTowardsAdjacentWall()
. It's pretty simple:@Autonomous(name = "Some Red Autonomous", group = "OpMode") public class SomeRedAutonomous extends SomeAutonomous { @Override public void turnTowardsAdjacentWall(double speed, double degrees) { turnLeft(double speed, double degrees);// Left for the red alliance } }
@Autonomous(name = "Some Blue Autonomous", group = "OpMode") public class SomeBlueAutonomous extends SomeAutonomous { @Override public void turnTowardsAdjacentWall(double speed, double degrees) { turnRight(double speed, double degrees);// Right for the blue alliance } }
That's it! Those are all the main parts of EONS TO COME, Eight O Nine Seven's Trademarked Original Code Organization Method for Efficiency. Keep in mind that the examples used here are just examples. Think outside the box and get creative with what you know about inheritance and abstract classes to organize your code intuitively.
Other Useful Stuff
More on Inheritance - Terminology
Some terminology was left out of this to avoid tangents, but this is kind of of important:
- When a class extends another class, the base class is called the superclass and the class that is extending it is called the subclass:
public class Subclass extends Superclass
- When you make a new Java Object of the type SomeClass, the Object is an instance of SomeClass. Whenever you run SomeOpMode, the FTC app is creating an instance of SomeOpMode. In other words, the following code is executed at some point:
new SomeOpMode()
- Any class that is not abstract is known as concrete. You can only create an instance of a concrete class. It impossible to create an instance of an abstract class, so the following code will not compile:
new SomeAbstractClass() // Compilation Error
More on Inheritance - Variables
By now you have some idea about the keyword "Override" preceded by an "@". @Override
does not actually do anything, but signifies that a method is defining a different functionality from one with the same declaration in its superclass (like when you override runOpMode()
).
When you have an instance of a class, no matter the context, and you call a method which that class overrides, the new version of the method will always be used, rather than the version in the superclass. That is to say, the @Override
does what it says it does. The same thing cannot be done with variables. Method and variable inheritance is pretty complex and nuanced, but the gist of it is that you cannot "override" variables like you can with methods.
Why bring this up? Because you might want to override variables, particularly constants, in certain cases, such as an integer for the number of times to move a servo, or simply a string which says the alliance color. The way to do this is simply to use methods with a return value.
Overall, using inheritance and simple methods is a great way to make many slightly different versions for an autonomous program. Below is an example with an abstract SomeAutonomous class and its subclasses, focusing on the red alliance (the blue alliance will be very similar):
public abstract class SomeAutonomous extends BaseOpMode {
@Override
public void runOpMode() throws InterruptedException {
allInit(); //Initialize hardware
telemetry.addData("Running Program", allianceColor());
telemetry.update();
waitForStart();
//Turn 45 degrees, go forward until range sensor sees a wall, move a servo
turnTowardsAdjacentWall(1.0, 45);
while(rangeSensor.rawUltrasonic() > 30 && opModeIsActive()) {
goForward(1.0);
}
for (int i = 0; i < numServoMovements(); i++) {
moveServo();
}
// Wait for timer to end or for stop button to be pressed
while (opModeIsActive()) {
idle();
}
}
public abstract void turnTowardsAdjacentWall(double speed, double degrees);
public abstract String allianceColor();
public int numServoMovements() {
return 1;
}
public void moveServo() {
//Code for moving servo goes here
}
}
@Autonomous(name = "Some Red Autonomous", group = "OpMode")
public class SomeRedAutonomous extends SomeAutonomous {
//This class inherits numServoMovements() from its superclass, so it will return 1 for this OpMode.
@Override
public String allianceColor() {
return "Red";
}
@Override
public void turnTowardsAdjacentWall(double speed, double degrees) {
turnLeft(double speed, double degrees);// Left for the red alliance
}
}
@Autonomous(name = "Some Red Autonomous with extra servo movement", group = "OpMode")
public class SomeRedAutonomousWithExtraServo extends SomeRedAutonomous {
//Notice how this class extends SomeRedAutonomous, so it inherits its definition of turnTowardsAdjacentWall() and allianceColor()
@Override
public void numServoMovements() {
return 3;
}
}
Wow, it's the bottom of the page! Congratulations on making it this far!
As mentioned, remember to think outside the box when putting all of these tools to use.