sign in
Start typing to search across all 16 weeks…
home / summer / week 5
Summer · Week 5 of 8

OOP - Classes & Objects

the most important week in this course fr. everything in FRC robot code is a class. don't skip this!!

Why this matters: every subsystem (a distinct part of the robot — intake, drivetrain, shooter — represented as a Java class) on 2974's robot is a class. Drivetrain.java, Shooter.java, Coral.java, Finger.java — all classes. understanding this week means you can read and write real robot code. OOP is confusing at first ngl but once it clicks it clicks fr.

Classes vs Objects

here's the single most important concept in this entire course: a class is a blueprint. an object is one specific thing you built from that blueprint. you write the class once. you can create as many objects from it as you want, and each one is completely independent from the others.

class vs object — the blueprint and the thing

ok let's start with the cookie cutter analogy because it's genuinely the best one. a cookie cutter is just a piece of metal bent into a shape — a star, a gingerbread man, whatever. the cutter itself doesn't taste like anything. it doesn't have sprinkles. it just defines the shape. every cookie you actually cut out IS a cookie — it exists in the real world and it's its own separate thing. you can frost one cookie and leave the others plain. you can eat one and the rest are fine. changing one cookie does nothing to any other cookie.

the cookie cutter = the class. each individual cookie = an object (also called an instance). the class defines the template — what shape the thing has, what data it holds, what it can do. each object you create from that class is its own independent living thing with its own copy of all the data.

second analogy just to make it stick: imagine a blueprint for a house. the blueprint lives in an architect's office. it says "2 bedrooms, 1 bathroom, open kitchen, attached garage." the blueprint itself isn't a house — you can't sleep in it. but from that ONE blueprint, a construction company can build 50 identical houses. each house is separate. what happens in one house doesn't affect any other house. if the family in house #7 paints their walls green, house #12 stays white. blueprint = class. each built house = an object.

the FRC connection — why this is the most important week

on team 2974, every single mechanism on the robot is modeled as a class. DriveSubsystem.java is a class — it's the blueprint. when the robot boots up, it builds ONE object from that blueprint: new DriveSubsystem(). that one object represents your actual robot's drivetrain. same exact deal for Shooter.java, Intake.java, Climber.java — every subsystem file you'll ever write is a class, and each file gets instantiated into exactly one object that lives for the entire match.

this is the moment in this course where things go from "learning Java syntax" to "actually understanding how robot code is structured." classes and objects aren't just an academic concept — they ARE the architecture of every WPILib (WPILib is the Java library all FRC teams use for robot programming — it gives you SubsystemBase, Commands, Triggers, and tons of utilities)-based robot program ever written.

WRT connection: look at the actual Rebuilt repo. every file in the subsystems/ folder is a class. Robot.java creates ONE instance of each subsystem using new. those instances get passed around to commands so everything has access to the actual hardware objects. the class is just the design — new is what builds the real thing.

anatomy of a class — every part labeled

let's look at a complete class with every single element called out. don't just skim past the comments in this one — every line has a specific reason for being there.

java — class definition, every element annotated
// "public class Motor" — this is the CLASS DECLARATION
// "public"  = anyone anywhere in the project can use this class
// "class"   = this keyword tells Java this is a class definition (not an interface, not an enum)
// "Motor"   = the name of the class (always PascalCase — capitalize first letter of every word)
public class Motor {

    // FIELDS (instance variables) — the data each Motor object stores
    // declared inside the class but outside any method
    // every Motor object you create gets its OWN copy of these three values
    // "private" = only code written inside Motor.java can access these directly
    private int     m_id;          // CAN ID — the unique number identifying this motor on the robot's wiring system
    private double  m_speed;       // current speed (-1.0 to 1.0)
    private boolean m_isInverted;  // whether positive speed means backward

    // CONSTRUCTOR — runs automatically when you write "new Motor(...)"
    // three rules: same name as the class (Motor), no return type (NOT void), set initial state
    public Motor(int id, boolean isInverted) {
        m_id         = id;           // save the port number
        m_isInverted = isInverted;   // save whether it needs to be flipped
        m_speed      = 0.0;         // always starts at rest — never spinning on creation
    }

    // METHODS — what this Motor can do, and what info it can give you
    // instance methods belong to a specific object and can access its fields
    public void setSpeed(double speed) {
        // apply inversion: if this motor is inverted, negate the speed
        // this is why the left and right drivetrain motors spin correctly
        // even though they're physically facing opposite directions
        m_speed = m_isInverted ? -speed : speed;
    }

    public double getSpeed() {
        return m_speed;  // give back the current speed so other code can read it
    }
}

creating objects and proving independence

once you have a class, you create objects with the new keyword. here's the critical thing to prove to yourself: once you have two objects, they are completely independent. changing one does NOT change the other. at all. ever.

java — two independent Motor objects
// "new Motor(1, false)" — creates a brand new Motor object in memory
// Java allocates space for it, runs the constructor, returns a reference to it
// leftMotor and rightMotor are TWO COMPLETELY SEPARATE objects
// they happen to be the same "type" (Motor) but they are different things
Motor leftMotor  = new Motor(1, false);  // port 1, not inverted
Motor rightMotor = new Motor(2, true);   // port 2, INVERTED (right side of drive)

// dot notation: call methods ON a specific object
// leftMotor.setSpeed(0.5) means "run setSpeed on the leftMotor object specifically"
leftMotor.setSpeed(0.5);
rightMotor.setSpeed(0.5);

// rightMotor is INVERTED, so 0.5 becomes -0.5 — they spin in opposite directions
// this is exactly how a real tank drive (tank drive = the simple drivetrain style where left stick controls left wheels, right stick controls right wheels) works on an FRC robot!!
System.out.println(leftMotor.getSpeed());  //  0.5
System.out.println(rightMotor.getSpeed()); // -0.5

// NOW: prove independence by stopping leftMotor only
leftMotor.setSpeed(0.0);

// leftMotor is stopped. rightMotor is COMPLETELY UNAFFECTED.
// they are separate objects with separate copies of m_speed
System.out.println(leftMotor.getSpeed());  // 0.0 — stopped
System.out.println(rightMotor.getSpeed()); // -0.5 — still going!!

what does new actually do?

when you write new Motor(1, false), three things happen in exact order and understanding all three will save you from so much confusion later:

  1. allocate memory — Java finds a chunk of RAM big enough to hold all of Motor's fields (m_id, m_speed, m_isInverted) and reserves it for this object
  2. run the constructor — your code inside Motor(int id, boolean isInverted) executes, setting the initial values for that chunk of memory
  3. return a reference — Java gives you back an address (a reference) pointing to where in memory that object now lives. the variable leftMotor holds this address, not the object itself

reference types: a variable like Motor m doesn't HOLD a Motor object inside it — it holds an ADDRESS pointing to where in memory the Motor lives. that's why Motor, String, and all class types are called "reference types." primitives (int, double, boolean) store their actual value. objects store an address. this distinction will matter a lot when you hit null pointer exceptions ngl.

the forgetting-new gotcha (this WILL bite you)

here's the most common mistake when you're first learning objects. you declare a variable of a class type, you plan to use it, but you forget the new keyword. the variable gets created... but it holds null — which means "no object, nothing, empty." then the moment you try to call a method on it, Java blows up with a NullPointerException. it's not a compile error so you won't catch it until the code actually runs.

java — forgetting new: the NullPointerException setup
// BAD — declared the variable but never created the object
// leftMotor is null right now — it doesn't point to anything
Motor leftMotor;
// or even worse — explicitly assigned null:
Motor leftMotor = null;

// now you try to use it...
leftMotor.setSpeed(0.5); // CRASH: NullPointerException at runtime!!
                           // "you can't call a method on nothing"

// ─────────────────────────────────────────
// GOOD — actually create the object with new
Motor leftMotor = new Motor(1, false); // now it points to a real Motor object
leftMotor.setSpeed(0.5);              // works perfectly
Class
The Blueprint
Written once in a .java file. Defines what fields (data) and methods (actions) each object will have. Motor.java is a class.
Object
The Instance
Built from the class at runtime using new. Each object has its OWN independent copy of every field. Also called an "instance."
new
Allocate + Construct
Allocates memory for the object, runs the constructor to initialize it, returns a reference. You MUST use new to create any object.
Dot Notation
objectName.method()
How you call a method on a specific object. leftMotor.setSpeed(0.5) calls setSpeed specifically on leftMotor — not rightMotor.

Topic 1 — Coding Prompt

Two-Motor Drivetrain
Create and work with multiple independent objects

Write a Motor class with:
• private fields: int m_id, boolean m_inverted, double m_speed
• constructor Motor(int id, boolean inverted) — sets id and inverted, starts speed at 0
void setSpeed(double speed) and double getSpeed()

Then in main:
• create a left motor (id=1, not inverted) and a right motor (id=2, inverted)
• set both speeds to 0.6, then print both
• set ONLY the left motor to 0.0, then print both again

After stopping left, right should still show 0.6. If both change, something's wrong.

Topic 1 — Quick Check


Constructors & Fields

fields are the state of an object — the things it knows about itself. the constructor is the code that runs when the object first comes to life. together they define what an object IS and what it starts as.

what even ARE fields?

think of fields like a hospital patient wristband. the moment you check into a hospital, a nurse puts a bracelet on your wrist with your name, date of birth, blood type, and patient ID. that's YOUR data — not some generic person's data. every patient has their own bracelet with their own values. your wristband says your name, the next patient's wristband says their name. changing one bracelet doesn't touch any other bracelet in the whole hospital.

fields work exactly the same way. when you create a Motor object, Java copies the field template from the class and gives THAT SPECIFIC OBJECT its own little chunk of memory to hold its own values for m_id, m_speed, and m_isInverted. leftMotor's m_speed is physically stored in a different place in memory than rightMotor's m_speed. they just happen to have the same names because they're the same type.

fields are declared at class level — inside the class body, but outside any method. they exist for the entire life of the object. every time a method runs, it can see and modify these fields.

java — fields with WRT naming conventions
public class Motor {

    // FIELDS — declared at class level, outside any method
    // "private" = only Motor's own methods can touch these
    // "m_" prefix = WRT convention: m stands for "member variable"
    //   this makes fields visually distinct from local variables at a glance
    //   when you see m_speed in a method, you immediately know: that's a field
    private int     m_motorID;    // CAN ID — the unique number identifying this motor on the robot's wiring system
    private double  m_speed;      // current commanded speed (-1.0 to 1.0)
    private boolean m_isRunning;  // whether the motor is actively spinning

    // ... constructor and methods go below
}

constructors — the object's birth certificate

a constructor runs automatically, exactly once, the moment you write new ClassName(...). it's your one guaranteed chance to set up the object in a valid state before anything else can touch it. think of it as the birth certificate moment — the instant the object is born, it gets its starting values stamped in.

two absolute rules you need to burn into memory:

  • the constructor name must be exactly the same as the class name — capital letters and all
  • constructors have no return type — not void, not int, not double. nothing. just the name and parentheses. if you add void, Java treats it as a regular method, not a constructor, and it will never run on new
java — valid vs invalid constructors
public class Motor {

    private int     m_motorID;
    private double  m_speed;
    private boolean m_isRunning;

    // VALID CONSTRUCTOR — same name as class, no return type
    // Java calls this automatically when you write new Motor(5)
    public Motor(int motorID) {
        m_motorID  = motorID;  // set initial state here
        m_speed    = 0.0;     // always starts stopped
        m_isRunning = false;  // always starts not running
    }

    // INVALID — has "void" before the name
    // Java does NOT treat this as a constructor!! it's just a regular method
    // calling new Motor(5) would NOT call this — your fields would stay at defaults
    // public void Motor(int motorID) { ... }  <-- do NOT do this

    // INVALID — wrong capitalization (Java is case-sensitive)
    // "motor" != "Motor" — this is also just a regular method
    // public motor(int motorID) { ... }  <-- also wrong
}

the this keyword — "the current object"

inside any instance method or constructor, this is a reference to the object the code is currently running on. if you have Motor leftMotor and Motor rightMotor and you call leftMotor.setSpeed(0.5), then inside setSpeed, this refers to leftMotor. call it on rightMotor instead, and this becomes rightMotor. this is always "the object right now."

the most common place you'll write this is in constructors when a parameter has the same name as a field. without this, Java can't tell which one you mean:

java — the naming conflict and how this solves it
public class Motor {

    private int m_motorID;

    // what if the parameter name exactly matches the field name?
    // Java has to pick ONE — it picks the local one (the parameter)
    // so "motorID = motorID" assigns the parameter to itself, doing nothing
    // m_motorID stays 0. the bug is SILENT — no error, wrong behavior forever
    public Motor(int motorID) {
        motorID = motorID; // BUG: assigns parameter to itself, field never set!!
    }
}
java — fix: use this.fieldName to be explicit
public class Motor {

    private int     m_motorID;
    private double  m_speed;
    private boolean m_isInverted;

    public Motor(int motorID, boolean isInverted) {
        // "this.m_motorID" = the field on THIS specific object (what we want to set)
        // "motorID"        = the parameter that was passed in (the new value)
        // left side is the destination, right side is the source — just like any assignment
        this.m_motorID   = motorID;    // field = parameter: correct!!
        this.m_isInverted = isInverted;
        this.m_speed      = 0.0;      // no conflict here but using this for clarity
    }
}

WRT tip: using the m_ prefix on fields mostly avoids the naming conflict problem entirely — your field is m_motorID and your parameter is motorID, so they never clash. but you will see this in tons of Java code in the wild (especially without the m_ convention), so make sure you know exactly what it means when you encounter it.

the motorID = motorID gotcha (this WILL bite you)

let me tell you about a bug that has ruined many first Java projects. you write a class, you write a constructor, you test it — everything seems to work. but your object's fields are always 0, always false, always empty. you're setting them in the constructor, you swear you are. then you stare at this for 30 minutes and finally see it: motorID = motorID. assigning a parameter to itself. the field never got set. never a warning. never an error. just silent wrong behavior.

java — the self-assignment silent bug in full context
// BUGGY VERSION — field is never actually set
public class Motor {
    private int motorID;  // no m_ prefix (this sets up the name clash)

    public Motor(int motorID) {
        motorID = motorID;  // assigns the parameter to itself. field stays 0.
    }

    public int getID() { return motorID; }
}

Motor m = new Motor(5);
System.out.println(m.getID()); // prints: 0 ... NOT 5. silent bug!!

// ─────────────────────────────────────────
// FIXED VERSION — use m_ prefix to prevent the name clash
public class Motor {
    private int m_motorID;  // m_ prefix = field, different name from parameter

    public Motor(int motorID) {
        m_motorID = motorID;  // unambiguous: left is field, right is parameter
    }

    public int getID() { return m_motorID; }
}

Motor m = new Motor(5);
System.out.println(m.getID()); // prints: 5 — correct!!

common gotcha: Java does NOT warn you about motorID = motorID unless your IDE has a specific lint rule for it. it's valid Java. it just does nothing useful. the fix: always use m_ on instance variables on WRT. that way the field and the parameter can never have the same name.

Fields
State of an object
Declared at class level. Each object gets its own independent copy. Use m_ prefix on WRT. Usually private.
Constructor
Object's birth certificate
Same name as class. No return type — ever. Runs once on new. Sets initial state of the object.
this
The current object
this inside a method is the object the method was called on. Used to clarify field vs parameter when names clash.
m_ prefix
WRT naming rule
All instance variables on 2974 use m_: m_speed, m_isRunning, m_motorID. Visually distinct from local vars instantly.

Topic 2 — Coding Prompt

Build a Sensor Class
Practice fields, constructors, and the m_ naming convention

Write a class called LimitSwitch with:
• private fields: int m_port, boolean m_isPressed, String m_name
• constructor LimitSwitch(int port, String name) — sets port and name, starts isPressed as false
void press() — sets m_isPressed to true
void release() — sets m_isPressed to false
boolean isPressed() — returns the current state

In main, create two switches. Press switch 1. Check that switch 2 is still not pressed — they should be independent.

Topic 2 — Quick Check


Access Modifiers

access modifiers are keywords that control who can see and use a field or method. they're how you protect your object's data from being incorrectly modified by other parts of the code. this is the "control" part of being in control of your own data.

what even ARE access modifiers?

imagine a filing cabinet with three different drawers. the bottom drawer is locked with a key only you have — nobody else can open it. the middle drawer is unlocked but in your private office — only your family (people in the same building) can get to it. the top drawer sits in the lobby and literally anyone who walks in can open it.

that's the three access modifiers you'll use. private is the bottom drawer — locked, only this class can open it. protected is the middle drawer — this class and its subclasses (more on that in week 6). public is the top drawer in the lobby — anyone anywhere in the project can touch it directly.

ModifierWho can access itWhen to use it
publicAnyone, anywhere in the projectMethods you want other classes to call (getters, setters, actions)
privateOnly inside the class it's declared inInstance variables — almost always. Use this as your default.
protectedThis class and its subclassesWhen designing for inheritance (week 6 territory)

why private fields? the DangerousMotor story

imagine you're writing a motor wrapper class and you make m_speed public. "it's fine," you think, "I'll just be careful." three weeks later, someone on the team is writing drive code at 11pm during build season and they type driveMotor.m_speed = 9999.0 instead of driveMotor.setSpeed(0.99). compiles fine. deploys fine. robot arm goes full power to 9999 in a direction it shouldn't and now you're explaining to your parents why you need to buy a new motor. this is not a hypothetical. this is why we use private.

java — DangerousMotor vs SafeMotor
// BAD — public field, anyone can bypass your logic and write garbage values
public class DangerousMotor {
    public double speed; // no protection at all!!
}

// anywhere in the project, someone can do this:
DangerousMotor m = new DangerousMotor();
m.speed = 9999.0; // compiles fine. no error. your motor is cooked.
m.speed = -500.0; // also fine. also bad.
m.speed = Double.NaN; // Java lets you. hardware does not appreciate this.

// ─────────────────────────────────────────────────────────────
// GOOD — private field with a setter that validates input
public class SafeMotor {
    private double m_speed; // can only be changed through setSpeed()

    public void setSpeed(double speed) {
        // Math.max(-1.0, ...) ensures speed is never below -1.0
        // Math.min(1.0, ...)  ensures speed is never above  1.0
        // this is called "clamping" — it's in every motor wrapper ever written
        m_speed = Math.max(-1.0, Math.min(1.0, speed));
    }

    public double getSpeed() { return m_speed; }
}

SafeMotor m = new SafeMotor();
m.setSpeed(9999.0); // setSpeed clamps it to 1.0. motor is fine.
m.setSpeed(-500.0); // clamped to -1.0. safe.
// m.m_speed = 9999.0; // COMPILE ERROR: m_speed has private access in SafeMotor

encapsulation — the big picture

the pattern of private fields plus public methods is called encapsulation. it's one of the core principles of object-oriented programming. the idea: your object owns its data. other code can interact with it, but only through the doors you provide (the public methods). you control what goes in and what comes out. nobody can reach in and corrupt your state directly.

on WRT, the rule is absolute: all instance variables are private. no exceptions. if another class needs to read the value, you write a getter. if it needs to change the value, you write a setter with validation. this isn't bureaucracy — it's what makes real robot code survive a full season without random crashes caused by someone setting a field to an invalid value on a tired Tuesday night during build season.

WRT rule: ALL instance variables are private. period. always. no exceptions. if another class needs the value, write a public getter. if it needs to change the value, write a public setter with validation. this is non-negotiable on 2974.

public methods — the intended interface

while fields should be private, most of your methods should be public. methods are HOW other code interacts with your object — they're the public interface. you want to make it easy for commands and other subsystems to call your methods, so keep the methods public. keep the data private.

java — typical access pattern for a subsystem
public class Intake {

    // fields: PRIVATE — other classes cannot directly touch these
    private double  m_speed;
    private boolean m_isRunning;

    public Intake() {
        m_speed    = 0.0;
        m_isRunning = false;
    }

    // methods: PUBLIC — these are the "doors" other classes use to interact
    public void run(double speed) {
        m_speed    = Math.max(0.0, Math.min(1.0, speed)); // intake only goes forward
        m_isRunning = true;
    }

    public void stop() {
        m_speed    = 0.0;
        m_isRunning = false;
    }

    public boolean isRunning() { return m_isRunning; }
    public double  getSpeed()   { return m_speed; }
}
public
Open to everyone
Any class anywhere can call this. Use for methods you want other code to invoke. Rarely use for fields.
private
This class only
Only code inside this exact class can access it. Always use for instance variables. Prevents uncontrolled changes.
protected
Class + subclasses
This class and any class that extends it (week 6). Rarely needed at your level but you'll see it in WPILib code.
Encapsulation
private data + public methods
The core OOP pattern. Private fields, public getters/setters that validate. Protects state from corruption.

Topic 3 — Coding Prompt

Safe Shooter Speed
Apply encapsulation to protect a critical field

Write a Shooter class with a private double m_flywheelSpeed field.

Write a setter setFlywheelSpeed(double speed) that protects the field — the stored value should never go below 0.0 or above 1.0. If someone passes 2.5, store 1.0. If they pass -0.5, store 0.0.

Also write:
getFlywheelSpeed() — returns the stored speed
boolean isReadyToShoot() — returns true if speed is above 0.8

Test: set speed to 2.5, then call getFlywheelSpeed(). It should return 1.0, not 2.5.

Topic 3 — Quick Check


Methods & Encapsulation

you learned how to write methods in week 4. now they're inside a class, which makes them instance methods — methods that belong to a specific object and can directly access that object's private fields. this is where everything comes together.

instance methods — behavior that belongs to an object

in week 4, some methods had the static keyword — Math.abs(-5), Math.max(a, b). you called those on the CLASS directly, no object needed. static methods are just utility functions that belong to the class as a whole, not to any particular object. they can't access fields because there's no "this object" when you call them.

instance methods are completely different. they belong to a specific object. when you call leftMotor.setSpeed(0.5), the setSpeed method runs in the context of leftMotor. it can see m_speed, m_id, m_isInverted — all of leftMotor's private fields directly. that's the whole point. the method knows which object it belongs to, so it can read and modify that object's state.

java — static vs instance method side by side
public class Motor {

    private double m_speed;

    // INSTANCE METHOD — belongs to each individual Motor object
    // can access m_speed, m_id, m_isInverted — anything on "this"
    // called as: someMotor.setSpeed(0.5)
    public void setSpeed(double speed) {
        m_speed = speed; // this.m_speed = speed (this is implied)
    }

    // STATIC METHOD — belongs to the Motor CLASS, not any object
    // no access to m_speed or "this" — there's no object context here
    // called as: Motor.clamp(1.5)  — no object needed
    // useful for pure utility functions that don't need object state
    public static double clamp(double value) {
        return Math.max(-1.0, Math.min(1.0, value)); // pure math, no fields needed
    }
}

// calling them:
Motor m = new Motor(1, false);
m.setSpeed(0.5);            // instance method — need an object
double safe = Motor.clamp(5.0); // static method — called on the class directly

the getter/setter pattern — using it correctly

the full getter/setter pattern shows up in literally every subsystem you will ever write on this team. private field. public getter that returns it. public setter that validates before storing. here's the naming convention: getters are getX(), but for booleans, use isX() instead (reads more naturally: if (motor.isRunning()) vs if (motor.getIsRunning())). setters are always setX(value).

java — complete getter/setter pattern with all naming conventions
public class Motor {

    private int     m_motorID;
    private double  m_speed;
    private boolean m_isRunning;

    public Motor(int motorID) {
        m_motorID  = motorID;
        m_speed    = 0.0;
        m_isRunning = false;
    }

    // GETTERS — return the private value, no parameters, just return
    // naming: getX() for most types, isX() for booleans (reads naturally)
    public double  getSpeed()   { return m_speed; }
    public int     getMotorID() { return m_motorID; }
    public boolean isRunning()  { return m_isRunning; } // isX() for booleans!!

    // SETTERS — validate input, then store it
    // naming: setX(value) where X matches the field name
    public void setSpeed(double speed) {
        // clamp to [-1.0, 1.0] so nothing can set an unsafe value
        m_speed = Math.max(-1.0, Math.min(1.0, speed));
    }

    // ACTION METHODS — verbs that do something meaningful using the object's state
    public void start(double speed) {
        setSpeed(speed);      // call our own setter — reuse its validation!!
        m_isRunning = true;
    }

    public void stop() {
        m_speed    = 0.0;
        m_isRunning = false;
    }

    /** returns a human-readable status line for logging */
    public String getStatusString() {
        return String.format("Motor %d: %.2f | %s",
            m_motorID, m_speed, m_isRunning ? "RUNNING" : "STOPPED");
    }
}

a real-looking FRC subsystem

ok here's the payoff. everything from this whole week — classes, fields, constructors, access modifiers, getters, setters, action methods — assembled into something that looks like actual robot code. this is basically what you'd write in a real match season if someone said "write the shooter subsystem."

java — ShooterSubsystem (simplified but genuinely real-looking)
/**
 * Models the shooter mechanism.
 * Manages flywheel speed and tracks whether we're at target velocity.
 * On WRT, every subsystem class looks like this.
 */
public class ShooterSubsystem {

    // all fields: private, m_ prefixed — state owned by THIS object
    private int     m_topMotorID;
    private int     m_bottomMotorID;
    private double  m_targetSpeed;   // what speed we're ASKING for (0.0 to 1.0)
    private boolean m_isAtSpeed;     // whether flywheel has actually reached that speed

    // constructor: takes motor CAN IDs, sets safe starting state
    public ShooterSubsystem(int topID, int bottomID) {
        m_topMotorID    = topID;
        m_bottomMotorID = bottomID;
        m_targetSpeed   = 0.0;   // starts off — never spin on boot without being asked
        m_isAtSpeed     = false; // definitely not at speed when we just turned on
    }

    /** Sets the desired flywheel speed, clamped to [0.0, 1.0]. Shooters don't go backward. */
    public void setTargetSpeed(double speed) {
        m_targetSpeed = Math.max(0.0, Math.min(1.0, speed));
    }

    /** Spins up the shooter to full power. Short for setTargetSpeed(1.0). */
    public void spinUp() {
        setTargetSpeed(1.0); // reuse the setter so we still get validation
    }

    /** Stops the flywheel and clears at-speed flag. */
    public void stop() {
        m_targetSpeed = 0.0;
        m_isAtSpeed   = false; // if we're stopping, we're definitely not at speed anymore
    }

    /** @return current target speed (0.0 to 1.0) */
    public double  getTargetSpeed() { return m_targetSpeed; }

    /** @return true if the flywheel has reached target velocity */
    public boolean isAtSpeed()      { return m_isAtSpeed; }

    /** @return formatted status string for Shuffleboard */
    public String getStatusString() {
        return String.format("Shooter: %.2f | %s",
            m_targetSpeed, m_isAtSpeed ? "AT SPEED" : "SPINNING UP");
    }
}

// usage in Robot.java — this is basically how it works in the real codebase
ShooterSubsystem m_shooter = new ShooterSubsystem(7, 8); // motors on CAN 7 and 8

m_shooter.spinUp();
System.out.println(m_shooter.getTargetSpeed()); // 1.0
System.out.println(m_shooter.isAtSpeed());       // false (in reality: updated by sensors)
System.out.println(m_shooter.getStatusString()); // "Shooter: 1.00 | SPINNING UP"

WRT method conventions: getters are getX() (or isX() for booleans), setters are setX(value), and action methods are descriptive verbs: spinUp(), stop(), deploy(), retract(). keep the names obvious. someone reading your code at 2am during build season should not have to think about what a method does.

calling your own methods from inside the class

one thing that's easy to miss: methods inside a class can call OTHER methods inside the same class. you saw it above — spinUp() calls setTargetSpeed(1.0). this is huge because it means you write the validation logic ONCE in the setter, and every other method that needs to set speed just calls the setter. you don't copy-paste the clamping math five places. one place, one set of logic, everything stays consistent.

java — calling your own methods: write logic once, reuse everywhere
public class Intake {

    private double  m_speed;
    private boolean m_isRunning;

    public Intake() { m_speed = 0.0; m_isRunning = false; }

    // setter: all validation logic lives HERE and only here
    public void setSpeed(double speed) {
        m_speed = Math.max(0.0, Math.min(1.0, speed));
    }

    // action methods just call setSpeed() — they don't duplicate the clamping
    public void runSlow()  { setSpeed(0.4); m_isRunning = true; }
    public void runFast()  { setSpeed(0.9); m_isRunning = true; }
    public void eject()    { setSpeed(0.6); m_isRunning = true; } // same direction for now

    public void stop() {
        m_speed    = 0.0;
        m_isRunning = false;
    }

    // now if clamping logic needs to change (e.g., to 0.0-0.8 for safety)
    // you ONLY change it in setSpeed() and everywhere else just works
}
Instance Method
Belongs to an object
Can access all the object's private fields via this. Called on an object: motor.setSpeed(0.5).
Getter
getX() / isX()
Returns the value of a private field. No parameters, just return m_field;. Use isX() for boolean fields.
Setter
setX(value)
Accepts a new value, validates it (clamp, null check, range check), then stores it. Validation lives here and ONLY here.
Action Method
Descriptive verbs
spinUp(), stop(), deploy() — methods that DO something meaningful. Usually call setters internally.

Topic 4 — Coding Prompt

Complete Subsystem from Scratch
Write a full class with all patterns applied

Write a Climber class with three private fields:
double m_extendSpeed, boolean m_isExtended, boolean m_isClimbing

Constructor: set all three to safe defaults — 0.0, false, false.

Methods:
extend(double speed) — clamp speed to 0–1, set m_extendSpeed, set m_isExtended = true
retract() — set speed to 0, m_isExtended = false
startClimb() — ONLY sets m_isClimbing = true if m_isExtended is already true, otherwise do nothing
stopClimb() — sets m_isClimbing = false
• getters for all three fields

Test: call extend(0.8), then startClimb(). Is m_isClimbing true? Now call retract() and try startClimb() again — it should NOT work.

Topic 4 — Quick Check


Live Class Builder

Type field declarations and watch the class generate

Fill in the Blanks

// Declare a private double field (WRT style)
double m_motorSpeed;
// Constructor for class Intake taking int motorID
public (int motorID) { }
// Create a new Motor with id=3, inverted=false
Motor m =
// Inside a method, refer to this object's own field
.m_speed = speed;

Knowledge Check


Coding Challenge

Build an Intake Class
Write a real FRC subsystem class from scratch

Write an Intake class with:
• private fields: int m_motorID, double m_speed, boolean m_isRunning
• constructor Intake(int motorID) — sets motorID, starts speed at 0, isRunning at false
start(double speed) — sets m_speed and m_isRunning = true
stop() — sets m_speed to 0, m_isRunning = false
isRunning() and getSpeed() getters

In main, create two Intake objects. Start one, leave the other stopped. Print both — only one should show isRunning = true.


Project Task — Week 5
DriveSubsystem.java

Create DriveSubsystem.java in your minibot-project folder — the heart of your MiniBot project. This models how a real FRC drivetrain subsystem is structured on team 2974. You're writing plain Java here (no WPILib needed) — just simulate the data and behavior.

  • Private fields: int[] m_motorIDs (4 motors: FL, FR, BL, BR), double m_currentSpeed, boolean m_isInverted
  • Constructor takes no parameters — initialize m_motorIDs from your Constants.DriveK values (use the IDs you defined in week 1)
  • public void setSpeed(double speed) — clamp speed to [-1.0, 1.0] before storing
  • public double getSpeed() — return current speed
  • public void stop() — sets speed to 0
  • public boolean isMoving() — returns true if |speed| > 0.01
  • public String getStatusString() — returns a formatted string like "Drive: 0.75 m/s | MOVING"
  • Full Javadoc on the class and every public method
no WPILib needed — your file will compile and run with just the Java extension in VS Code. the goal is the structure and logic, not real hardware control. wiring it to actual motors happens in the offseason phase.

Weekly Test

covers everything from week 5. the big OOP week. score goes to the leads — try without looking back!!

week 5 test
classes, objects, constructors, encapsulation · 8 questions!!