-
Notifications
You must be signed in to change notification settings - Fork 0
Guide to Making Our First Robot
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.
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 versioned we will use is 1.8, which is the minimal supported version by FlashLib.
We won't be explaining how to setup a gradle project, but rather skip ahead.
This should be the look of your gradle 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 were 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'
}
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
:
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
:
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 this for now, we'll come back to this class and finish it.
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 this, 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
]((https://github.com/Flash3388/FlashLib/wiki/Robot-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 needed is to change the instance created in the constructor to the wanted one.
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 saves it in an instance variable:
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() {
}
}
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.