Skip to content

Guide to Making Our First Robot

Tom Tzook edited this page Nov 25, 2020 · 9 revisions

Welcome! In this guide we will be making our first FlashLib robot. We want be talking about mechanics and electronics, but rather only software. You're free to build an actual robot, but the purpose of this guide is to familiarize ourselves with the structure and components of FlashLib robots.

Environment

We won't be focusing much about the development environment used, since it doesn't matter much. However, we will be showing some images and information about our preferred environment, in order to clarify some stuff that might not be obvious. The environment of our choice is a gradle with a gradle wrapper and using IntelliJ IDEA. The Java version can be of your choosing, as long as it is compatible with 1.8 binaries. For the purposes of this guide we'll be using 1.8, although it should have much (if any) affect and thus won't be apperent.

Setting Up a Robot Project

Setting Up the Build

We won't be explaining how to setup a gradle project, but rather skip ahead.

Initial Project

This should be the look of your project if you're using gradle. At the moment it is empty. Let's start by setting up the build script. Open up the build.gradle file, which should be empty (if not, clear it).

We are going to be using the application plugin which provides an simple setup for running Java applications. Add a plugins DSL statement to use the plugin:

plugins {
    id 'application'
}

We also need to setup the application configuration, which includes the main class. We don't have main class yet, but we would make it in accordance to what we define here:

application {
    mainClassName = 'robot.Main'
}

Next we need to define FlashLib as a dependency of the project. We will start by defining the project repositories which are where dependencies will be retrieved from, and then add the dependency:

repositories {
    mavenCentral()
}

dependencies {
    implementation group: "com.flash3388.flashlib", name: "flashlib.core.robot", version: "$version"
}

Where $version is the FlashLib version you wish to use.

This should be the end product of your build.gradle (we've changed the order a bit):

plugins {
    id 'application'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation group: "com.flash3388.flashlib", name: "flashlib.core.robot", version: "$version"
}

application {
    mainClassName = 'robot.Main'
}

Setting up

Robot Package

We'll create the package which will contain the robot code. Let's call it robot.

Create the robot package by right clicking the java folder -> New -> Package and name it robot: create package 1 create package 2 create package 3

Creating Main Class

Now we add the main class for the project. We will make it under robot package in a class named Main (so robot.Main).

Create the main class by right clicking the robot package -> New -> Java Class and name it Main: create main class 1 create main class 2 create main class 3

This will result in an empty class. Thanks to how we configured gradle (setting mainClassName) this class will be our main class. We now need to implement the main method which will start running the robot.

package robot;

public class Main {
    public static void main(String[] args) {

    }
}

Let's leave it for now, we'll come back to this class and finish it.

Robot Control

The RobotControl of a robot is responsible for providing different control components, which are necessary in order to operate the robot.

Unfortunately because we don't have a physical robot, it is difficult to fully implement RobotControl, since the components can be platform dependent. Because of this, we will be using stub components for parts which depend on the platform used; they can be easily substituted later.

Create a new class called MyRobotControl that implements the RobotControl interface:

public class MyRobotControl implements RobotControl {

    @Override
    public Supplier<? extends RobotMode> getModeSupplier() {
        return null;
    }

    @Override
    public IoInterface getIoInterface() {
        return null;
    }

    @Override
    public HidInterface getHidInterface() {
        return null;
    }

    @Override
    public Scheduler getScheduler() {
        return null;
    }

    @Override
    public Clock getClock() {
        return null;
    }

    @Override
    public Logger getLogger() {
        return null;
    }

    @Override
    public void registerCloseables(Collection<? extends AutoCloseable> closeables) {

    }
}

There are a bunch of methods we need to implement. Let's create an instance variable for all the components:

public class MyRobotControl implements RobotControl {

    private final Supplier<? extends RobotMode> mRobotModeSupplier;
    private final IoInterface mIoInterface;
    private final HidInterface mHidInterface;
    private final Scheduler mScheduler;
    private final Clock mClock;
    private final Logger mLogger;
    private final ResourceHolder mResourceHolder;

...

The components are:

  • mRobotModeSupplier: supplies the operation modes of the robot.
  • mIoInterface: allows interfacing with the hardware of the local platform, in order to control electronic components.
  • mHidInterface: allows interfacing with controllers and other human interface devices, providing human control.
  • mScheduler: the execution component for action-based robots.
  • Clock: provides timestamp information. Used by other components.
  • Logger: the robot log.
  • ResourceHolder: an helper class for holding resources used by the robot.

We now need to provide values for our variables. We'll do this via the constructor. The basic constructor should receive Logger and ResourceHolder and create the rest. But this can be modified if needed:

    public MyRobotControl(Logger logger, ResourceHolder resourceHolder) {
        mLogger = logger;
        mResourceHolder = resourceHolder;
        
        mClock = RobotFactory.newDefaultClock();
        mRobotModeSupplier = new StaticRobotModeSupplier(RobotMode.DISABLED);
        mIoInterface = new IoInterface.Stub();
        mHidInterface = new HidInterface.Stub();
        mScheduler = RobotFactory.newDefaultScheduler(mClock, mLogger);
    }

The Clock and Scheduler implementations are easily provided from FlashLib and are fully functional.

The RobotModeSupplier implementation used here is one that uses only a single mode: RobotMode.DISABLED. Normally we would want something that can be modified remotely, but there is no built-in implementation which provides that.

The IoInterface is platform-dependent since it interacts of hardware. Because of that, it's a stub.

The HidInterface should normally be controlled remotely, but there is no such built-in implementation. Thus, we use a stub here.

With the components created, we can implement the methods now:

public class MyRobotControl implements RobotControl {

    private final Supplier<? extends RobotMode> mRobotModeSupplier;
    private final IoInterface mIoInterface;
    private final HidInterface mHidInterface;
    private final Scheduler mScheduler;
    private final Clock mClock;
    private final Logger mLogger;
    private final ResourceHolder mResourceHolder;

    public MyRobotControl(Logger logger, ResourceHolder resourceHolder) {
        mLogger = logger;
        mResourceHolder = resourceHolder;

        mClock = RobotFactory.newDefaultClock();
        mRobotModeSupplier = new StaticRobotModeSupplier(RobotMode.DISABLED);
        mIoInterface = new IoInterface.Stub();
        mHidInterface = new HidInterface.Stub();
        mScheduler = RobotFactory.newDefaultScheduler(mClock, mLogger);
    }

    @Override
    public Supplier<? extends RobotMode> getModeSupplier() {
        return mRobotModeSupplier;
    }

    @Override
    public IoInterface getIoInterface() {
        return mIoInterface;
    }

    @Override
    public HidInterface getHidInterface() {
        return mHidInterface;
    }

    @Override
    public Scheduler getScheduler() {
        return mScheduler;
    }

    @Override
    public Clock getClock() {
        return mClock;
    }

    @Override
    public Logger getLogger() {
        return mLogger;
    }

    @Override
    public void registerCloseables(Collection<? extends AutoCloseable> closeables) {
        mResourceHolder.add(closeables);
    }
}

It's not a complex class, but a crucial one. Changing the implementation of components is simple, as all that is required is to change the instance created in the constructor to the wanted one.

Setting Up the Robot Class

Not to be confused with the Main class, the Robot Class (or Robot Main Class) is the class responsible for defining and running the robot. It is based on a Robot Base, which defines a running context for the robot.

We will start by creating a class MyRobot under robot package:

package robot;

public class MyRobot {

}

We are going to use the LoopingRobotBase which provides an iterative flow based on the robot mode. To use this base we will implement the IterativeRobot interface - public class MyRobot implements IterativeRobot and add a single constructor, which receives RobotControl (and possibly saves it in an instance variable, if wanted):

public class MyRobot implements IterativeRobot {

    private final RobotControl mRobotControl;

    public MyRobot(RobotControl robotControl) {
        mRobotControl = robotControl;
    }

    @Override
    public void disabledInit() {

    }

    @Override
    public void disabledPeriodic() {

    }

    @Override
    public void modeInit(RobotMode mode) {

    }

    @Override
    public void modePeriodic(RobotMode mode) {

    }

    @Override
    public void robotPeriodic() {

    }

    @Override
    public void robotStop() {

    }
}

Running Robot from Main

With all our classes set-up, we can go back to main and implement the code that will run the robot. We will start by creating the Logger for the robot:

Logger logger = new LoggerBuilder("robot")
       .build();

You can add calls to the LoggerBuilder methods in order to configure the logger, as described here.

With that, we call RobotMain.start which will run our robot program. It receives information about our robot base and robot control in the form of RobotCreator interface, which will come from the classes we created. We can do this simply with a lambda:

public class Main {

    public static void main(String[] args) {
        Logger logger = new LoggerBuilder("robot")
                .build();

        RobotMain.start((_logger, resourceHolder) -> {
            RobotControl robotControl = new MyRobotControl(logger, resourceHolder);
            RobotBase robotBase = new LoopingRobotBase(MyRobot::new);

            return new RobotImplementation(robotControl, robotBase);
        }, logger);
    }
}

And with that our main is complete.

Making The Robot

With our project all setup, we can now actually run the code, but we won't do that just yet.

Robot Subsystems

The next step in our robot are the subsystems. The robot subsystems are different mechanical or electronic components of the robot which have a specific job and are independent from others. Each subsystem can be comprised of a dozen different components which are all working together to achieve one task (or several).

FlashLib promotes a concept of System-Oriented Code, which is described further here, and you should read it before continuing.

With the understanding on that paradigm in mind, we can start making our subsystems. Since we don't have an actual physical robot available, we'll make one up.

Drive System

Drive

Let's say that the drive system of our robot is as shown in the image. Our job is to implement the subsystem code for it. The system shown is a tank-like drive, which utilizes 4 motors - 2 for each side. It is considered tank-like because each side is operated separately and thus is moved separately.

We'll start by creating a new package under robot called subsystems, under which we'll create our subsystem classes. Like before, we'll right click the robot package -> New -> Package and add .subsystems so that the text box shows robot.subsystems.

Now we need to create the class for the subsystem. We'll right click the new subsystems package -> New -> Class and enter a name - DriveSystem.

package robot.subsystems;

public class DriveSystem {

}

For a class to be a subsystem it must extend Subsystem, which is what we'll do:

public class DriveSystem extends Subsystem {

}

Next we need to add properties (or instance variables) to the class. According to the System-Oriented design, those will be electronic components that are used to operate the system. In our system we have 4 electric motors, 2 per side connected to a gearbox. Each motor is controlled by some speed controller, so that brings us to 4 speed controllers - 2 for the right side, 2 for the left side:

public class DriveSystem extends Subsystem {

    private final SpeedController mRight1;
    private final SpeedController mRight2;
    private final SpeedController mLeft1;
    private final SpeedController mLeft2;
}

Since the speed controllers for each side will operate together (mRight1 + mRight2, mLeft1 + mLeft2) we can combine each 2 under a single SpeedController variable and create with SpeedControllerGroup (we'll how later):

public class DriveSystem extends Subsystem {

    private final SpeedController mRight;
    private final SpeedController mLeft;
}

We should receive our properties directly from the constructor, leaving the creation of the components up to the robot class:

public class DriveSystem extends Subsystem {

    private final SpeedController mRight;
    private final SpeedController mLeft;

    public DriveSystem(SpeedController right, SpeedController left) {
        mRight = right;
        mLeft = left;
    }
}

With our properties defined an initialized, let's move onward to the methods. In System-Oriented design, the methods represent simple operations that the components are capable of, like set speed, stop, etc. This is exactly what we're going to do. To help us choose the methods to export, in addition to providing some extra functionality; we can use system interfaces from FlashLib. These interfaces declare methods which should be provided by subsystems of different types. In our case, the system is a tank-like drive, which is represented by TankDrive:

public class DriveSystem extends Subsystem implements TankDrive {

    private final SpeedController mRight;
    private final SpeedController mLeft;

    public DriveSystem(SpeedController right, SpeedController left) {
        mRight = right;
        mLeft = left;
    }

    @Override
    public void tankDrive(double right, double left) {

    }

    @Override
    public void arcadeDrive(double moveValue, double rotateValue) {

    }

    @Override
    public void stop() {

    }
}

We received 3 methods to implement from the interface:

  • tankDrive defines a tank-like control, where each side is directed separately. The values receives are precent VBus values for the motors.
  • arcadeDrive is a more complicated control for tank-like drives: it emulates an arcade joystick game, where a single joystick, with its Y and X values, is used to move the drive.
  • stop is a simple directive to stop the motion of the system, i.e. the motor.

Implementing tankDrive and stop is quite simple. However, arcadeDrive is more challenging, so we'll leave it for later.

    @Override
    public void tankDrive(double right, double left) {
        // since right and left parameters are percent vbus, just like what SpeedController.set expects, we can just pass it directly.
        mRight.set(right);
        mLeft.set(left);
    }

    @Override
    public void stop() {
        mRight.stop();
        mLeft.stop();
    }

To implement arcadeDrive we'll use a helper class called DriveAlgorithms which implements several algorithms for drive system control, arcadeDrive among them. We'll care an instance of it at the class level and use it in arcadeDrive:

public class DriveSystem extends Subsystem implements TankDrive {

    private final SpeedController mRight;
    private final SpeedController mLeft;
    private final DriveAlgorithms mAlgorithms;

    public DriveSystem(SpeedController right, SpeedController left) {
        mRight = right;
        mLeft = left;
        mAlgorithms = new DriveAlgorithms(); // no need to receive it from robot
    }
    @Override
    public void arcadeDrive(double moveValue, double rotateValue) {
        TankDriveSpeed speed = mAlgorithms.arcadeDrive(moveValue, rotateValue);
        // TankDrive interface provides a tankDrive(TankDriveSpeed) method with a default implementation that calls tankDrive(double, double). Thanks to that, we can simply pass speed to this method. 
        // This is one among several methods provided with default implementation by TankDrive.
        tankDrive(speed);
    }

The final product we get:

public class DriveSystem extends Subsystem implements TankDrive {

    private final SpeedController mRight;
    private final SpeedController mLeft;
    private final DriveAlgorithms mAlgorithms;

    public DriveSystem(SpeedController right, SpeedController left) {
        mRight = right;
        mLeft = left;
        mAlgorithms = new DriveAlgorithms();
    }

    @Override
    public void tankDrive(double right, double left) {
        mRight.set(right);
        mLeft.set(left);
    }

    @Override
    public void arcadeDrive(double moveValue, double rotateValue) {
        TankDriveSpeed speed = mAlgorithms.arcadeDrive(moveValue, rotateValue);
        tankDrive(speed);
    }

    @Override
    public void stop() {
        mRight.stop();
        mLeft.stop();
    }
}

Now that the class is finished, we can use it in the robot class. Subsystem classes should only have one instance, which will be used throughout the code. They should be initialized in the robot init phase. Assuming we are using a LoopingRobotBase and implementing IterativeRobot:

public class Robot implements IterativeRobot {

    private final DriveSystem mDriveSystem;

    public Robot(RobotControl robotControl) {
        mDriveSystem = new DriveSystem(rightMotors, leftMotors);
    }
}

rightMotors and leftMotors are the speed controllers for the system. FlashLib recommends the use of a RobotMap to store connection information. So we'll create a RobotMap class under robot package:

package robot;

public class RobotMap {

}

We'll add constants for each of our motors, as each has a connection. The data type is IoChannel from flashlib.core.io. The creation of those channels depends on the implementation we use, which is stub is our case, thus we will use stub channels:

public class RobotMap {
    public static final IoChannel DRIVE_RIGHT1 = new IoChannel.Stub();
    public static final IoChannel DRIVE_RIGHT2 = new IoChannel.Stub();
    public static final IoChannel DRIVE_LEFT1 = new IoChannel.Stub();
    public static final IoChannel DRIVE_LEFT2 = new IoChannel.Stub();
}

We'll use those to create our speed controllers. This depends on the specific speed controller device used for each motor, so for this example we'll use PwmTalonSrx, which implements a PWM-connected TalonSRX. Creating one looks something like this:

RobotControl robotControl = ...
Pwm pwm = robotControl.newPwm(RobotMap.DRIVE_RIGHT1);
SpeedController speedController = new PwmTalonSrx(pwm);
    public Robot(RobotControl robotControl) {
        SpeedController right1 = new PwmTalonSrx(robotControl.newPwm(RobotMap.DRIVE_RIGHT1));
        SpeedController right2 = new PwmTalonSrx(robotControl.newPwm(RobotMap.DRIVE_RIGHT2));
        SpeedController rightMotors = new SpeedControllerGroup(right1, right2);

        SpeedController left1 = new PwmTalonSrx(robotControl.newPwm(RobotMap.DRIVE_LEFT1));
        SpeedController left2 = new PwmTalonSrx(robotControl.newPwm(RobotMap.DRIVE_LEFT2));
        SpeedController leftMotors = new SpeedControllerGroup(left1, left2);

        mDriveSystem = new DriveSystem(rightMotors, leftMotors);
    }

TODO: CONTINUE WHEN I GET THE CHANGE.