The Common Chicken Runtime Engine v3.4.0
1 Quickstart Guide
1.1 Prerequisites
1.2 Installing the CCRE
1.3 Creating a new project
1.4 Basic code
1.5 Single Joystick, Single Motor
1.6 Running your code on a real robot
2 Introduction to Dataflow programming
2.1 Channels
2.2 Hardware
2.3 Transforming channels
2.4 Cells
3 Review of advanced Java concepts
3.1 Anonymous classes
3.2 Lambdas
4 The software environment
4.1 The Driver Station
4.1.1 Modes
4.1.2 Keyboard shortcuts
4.1.3 Joysticks
4.2 The match sequence
4.3 Safety
4.4 The robo  RIO
4.4.1 The c  RIO
4.5 Downloading code
4.6 Speed controllers
4.6.1 PWM (Pulse-width modulation)
4.6.2 CAN (Control area network)
4.7 Sensors
4.7.1 Digital Inputs
4.7.2 Analog Inputs
4.7.3 Internal Sensors to the robo  RIO
4.7.4 PDP Sensors
4.7.5 RS232 Sensors
4.8 Wi  Fi and Networking
4.8.1 The FMS and port filtering
4.9 Cluck
4.9.1 The Poultry Inspector
4.9.2 Network Tables & Smart Dashboard
5 Detailed guide to the CCRE
5.1 Documentation format
5.2 Flow versus Setup
5.3 Dataflow Channels
5.3.1 Outputs
5.3.2 Inputs
5.3.3 Cancel  Outputs
5.3.4 Channel Cells
5.4 Dataflow Transformations
5.4.1 Event  Outputs
5.4.2 Event  Inputs
5.4.3 Boolean  Outputs
5.4.4 Boolean  Inputs
5.4.5 Float  Outputs
5.4.6 Float  Inputs
5.5 Drive Code Implementations
5.6 Autonomous and Instinct Modules
5.6.1 Autonomous methods
5.6.2 A Dire Warning
5.6.3 Instinct Modules outside of Autonomous
5.6.4 Instinct Multi  Modules
5.7 Hardware Access
5.8 Logging Framework
5.9 Concurrency Best Practices
5.10 State  Machine
5.11 PID controllers
5.12 Time and Timing
5.13 Extended Motors and CAN
5.14 Remote Configuration
5.15 Error Handling
5.16 Control Bindings
5.17 Tunable Values
5.18 Deployment
5.18.1 SSH
5.18.2 Deployment API
5.19 Storage Framework
5.20 Serial I/  O
5.21 Networking Framework
5.22 Phidget control panels
5.23 Special sensors
5.24 The Cluck Pub/  Sub System
5.24.1 Cluck with complexity
5.25 Detailed guide to the Poultry Inspector
5.26 Detailed guide to the Emulator
6 CCRE recipes
7 Versioning policies of the CCRE
8 Javadoc Link
9 Maintainer’s guide
9.1 Hardware access
9.2 CCRE Philosophy
6.4

The Common Chicken Runtime Engine v3.4.0

Cel Skeggs <robotics [at] celskeggs [dot] com>

The CCRE solves the problem of writing elegant and maintainable robot software by using a dataflow model and taking care of the infrastructure for your project so that you can focus on the important parts of your code.

Here’s an example of a robot piloted with Arcade Drive:

Drive.arcade(FRC.joystick1,

             FRC.talon(1, FRC.MOTOR_FORWARD),

             FRC.talon(2, FRC.MOTOR_REVERSE));

Or, something more interesting: an example of a shifting drive train:

BooleanOutput shifter = FRC.makeSolenoid(2);

shifter.setFalseWhen(FRC.startTele);

shifter.setTrueWhen(FRC.joystick1.onPress(3));

shifter.setFalseWhen(FRC.joystick1.onPress(1));

Features:

Here’s what you’ll find in this document:

    1 Quickstart Guide

      1.1 Prerequisites

      1.2 Installing the CCRE

      1.3 Creating a new project

      1.4 Basic code

      1.5 Single Joystick, Single Motor

      1.6 Running your code on a real robot

    2 Introduction to Dataflow programming

      2.1 Channels

      2.2 Hardware

      2.3 Transforming channels

      2.4 Cells

    3 Review of advanced Java concepts

      3.1 Anonymous classes

      3.2 Lambdas

    4 The software environment

      4.1 The Driver Station

        4.1.1 Modes

        4.1.2 Keyboard shortcuts

        4.1.3 Joysticks

      4.2 The match sequence

      4.3 Safety

      4.4 The roboRIO

        4.4.1 The cRIO

      4.5 Downloading code

      4.6 Speed controllers

        4.6.1 PWM (Pulse-width modulation)

        4.6.2 CAN (Control area network)

      4.7 Sensors

        4.7.1 Digital Inputs

        4.7.2 Analog Inputs

        4.7.3 Internal Sensors to the roboRIO

        4.7.4 PDP Sensors

        4.7.5 RS232 Sensors

      4.8 WiFi and Networking

        4.8.1 The FMS and port filtering

      4.9 Cluck

        4.9.1 The Poultry Inspector

        4.9.2 Network Tables & Smart Dashboard

    5 Detailed guide to the CCRE

      5.1 Documentation format

      5.2 Flow versus Setup

      5.3 Dataflow Channels

        5.3.1 Outputs

        5.3.2 Inputs

        5.3.3 CancelOutputs

        5.3.4 Channel Cells

      5.4 Dataflow Transformations

        5.4.1 EventOutputs

        5.4.2 EventInputs

        5.4.3 BooleanOutputs

        5.4.4 BooleanInputs

        5.4.5 FloatOutputs

        5.4.6 FloatInputs

      5.5 Drive Code Implementations

      5.6 Autonomous and Instinct Modules

        5.6.1 Autonomous methods

        5.6.2 A Dire Warning

        5.6.3 Instinct Modules outside of Autonomous

        5.6.4 Instinct MultiModules

      5.7 Hardware Access

      5.8 Logging Framework

      5.9 Concurrency Best Practices

      5.10 StateMachine

      5.11 PID controllers

      5.12 Time and Timing

      5.13 Extended Motors and CAN

      5.14 Remote Configuration

      5.15 Error Handling

      5.16 Control Bindings

      5.17 Tunable Values

      5.18 Deployment

        5.18.1 SSH

        5.18.2 Deployment API

      5.19 Storage Framework

      5.20 Serial I/O

      5.21 Networking Framework

      5.22 Phidget control panels

      5.23 Special sensors

      5.24 The Cluck Pub/Sub System

        5.24.1 Cluck with complexity

      5.25 Detailed guide to the Poultry Inspector

      5.26 Detailed guide to the Emulator

    6 CCRE recipes

    7 Versioning policies of the CCRE

    8 Javadoc Link

    9 Maintainer’s guide

      9.1 Hardware access

      9.2 CCRE Philosophy

1 Quickstart Guide

1.1 Prerequisites

1.2 Installing the CCRE

Short version: import all of the projects from the CCRE repository into Eclipse and Build All.

Long version:

1.3 Creating a new project

Creating a new project is easy.

Simply copy and paste the TemplateRobot project, and give it a new name like MyFirstRobot.

copy

paste

name

Open src/robot/RobotTemplate.java in your new project.

RobotTemplate.java

Replace 0000 with your team number.

Team Number

Now press Project -> Build Project, and once it finishes, you’re done!

After the first time, your project will be rebuilt every time you deploy the code to the robot.

Build Project

1.4 Basic code

We’ll start with something simple. You should see this code in your project:

@Override

public void setupRobot() {

    // Robot setup code goes here.

}

Start by adding

Logger.info("Hello, World!");

to this method.

Now, let’s test this code in the emulator. Open the dropdown next to the External Tools button and select "MyFirstRobot Emulate."

emulate

If the emulator doesn’t appear properly, close it and rebuild the entire CCRE by pressing Project -> Clean... -> Clean All Projects -> OK and then Project -> Build All.

On the emulator window that pops up, you should see it say something like
[INFO] (RobotTemplate.java:20) Hello, World!

Example:

hello world

If so, congratulations! You appear to have installed the CCRE correctly and written a very simple program!

Of course, that’s not particularly interesting, so let’s move on to actual motors and joysticks.

1.5 Single Joystick, Single Motor

Let’s set up a Talon speed controller and control it with the Y axis of a Joystick.

FloatOutput motor = FRC.talon(0, FRC.MOTOR_FORWARD);

FloatInput yAxis = FRC.joystick1.axisY();

yAxis.send(motor);

You will need to import all of the classes referenced by this piece of code. Eclipse has an easy way to do this:

hover error

You can also press Control+1 while your (keyboard) cursor is over the error to pop up the window faster.

fix error

Import the rest of the missing classes and then you can run your program in the emulator!

disabled emulator

X = 1, Y = 2.

Now, let’s try your program. Click on DISABLED to toggle it to ENABLED and then drag the bar under Axis 2 to change the Y axis in your program.

You could try to drag this while the robot is DISABLED, but the motor wouldn’t be able to change.

enabled emulator

1.6 Running your code on a real robot

(If you don’t want to run your code on a real robot yet, skip this section and come back later.)

The kinds of Speed Controllers are:
  • Talons (latest)

  • Jaguars

  • Victors (oldest)

For the current example, the motor port number was chosen arbitrarily: a Talon on PWM port 0. However, your real robot might vary from this. You should figure out which motor you want to run on your robot, and figure out its port number and which kind of speed controller it uses.

It’s possible that your robot uses CAN speed controllers. Try to choose motors that aren’t connected with CAN, as CAN motors are harder to deal with. Don’t worry, we’ll get to CAN motors later.

Once you know which kind of speed controller you’re using (Talon, Victor, Jaguar), you may need to replace talon with victor or jaguar. You may also need to replace the 0 with the correct port number.

Once you have the configuration correct, open the dropdown next to the External Tools button and select "MyFirstRobot Deploy."

deploy

Make sure that the output ends with BUILD SUCCESSFUL, and then you can connect your Driver Station to the robot, connect a Joystick, enable your robot, and move the motor by moving the Joystick!

Congratulations! You now know how to use the basics of the CCRE!

2 Introduction to Dataflow programming

When thinking about how a robot should react to stimuli, often the mental model that you generate is about the flow of data:

image

With traditional methods, you might think to implement that control system like this:

image

However, this technique is really easy to get wrong. That example has the bug where if the red button is pressed for very long - more than 20 milliseconds, which isn’t very long - then the program will blow up the world multiple times!

Yes, that can be fixed, but the code gets more complicated:

This is hard to scale for multiple reasons, including an inability to have inline state. For each button, you need a variable defined in a completely different location in the file (which becomes more significant with larger files) and you have to reference multiple places to figure out what your code does.

image

With the number of things you have to think about in a practical system, this quickly becomes an ineffective strategy: you can do it, but you will probably have a hard time keeping it understandable.

The CCRE solves this by aligning the code more closely with the original model. Recall what we had earlier:

image

The CCRE would express this as:

This is slightly simplified, but not by much.

image

Or, in practice:

red_button.onPress().and(key_input).send(blow_up_world);

And that’s the reason that we built the CCRE.

2.1 Channels

The CCRE is built around the concept of "channels." A channel has an implementation that handles one side of the channel, and any number of users that talk to the implementation over the channel. Neither side has to know much about the details of the other side.

There are six kinds of channels, along two axes.

Event

Boolean

Float

Input

EventInput

BooleanInput

FloatInput

Output

EventOutput

BooleanOutput

FloatOutput

With an Output, the users of the channel can send messages over the channel to the implementation behind it.

With an Input, the users of the channel can request the present state of the channel from the implementation (if any), and ask the implementation to tell them when the value represented by the input changes.

2.2 Hardware

The CCRE includes an API that provides access to an FRC robot’s hardware via channels.

For example:

BooleanOutput led = FRC.makeDigitalOutput(7);

FloatOutput test_motor = FRC.talon(3, FRC.MOTOR_FORWARD, 0.2f);

EventInput start_match = FRC.startTele;

BooleanInput button = FRC.joystick1.button(3);

FloatInput axis = FRC.joystick6.axis(3);

See Hardware Access below for more info on robot hardware.

2.3 Transforming channels

It turns out that, often, you want to do similar things with your channels. So, correspondingly, all channels have a variety of built-in methods to help you on your journey!

The simplest example is probably send, which works for all three varieties of channels:

// this also works if you replace Boolean with Event or Float in both places.

BooleanInput x = /* ... */;

BooleanOutput y = /* ... */;

x.send(y);

This connects the input to the output, so that y is updated with the current value of x both immediately and whenever x changes. In the case of events, send causes the EventOutput to be fired whenever the EventInput is produced.

Another simple example is onPress:

BooleanInput bumper = FRC.makeDigitalInput(3);

bumper.onPress().send(stop_motors);

This creates an EventInput that fires when the BooleanInput becomes true.

Also useful are setWhen (along with setFalseWhen and setTrueWhen, and getSetEvent):

driving_forward.setTrueWhen(FRC.joystick1.onPress(2));

stop_motors = driving_forward.getSetEvent(false);

See Dataflow Transformations for more info.

2.4 Cells

Unlike in normal Java, you don’t use variables to store state in CCRE dataflow code. Instead, you use Cells, which are similar, but also act as Inputs and Outputs.

For example:

BooleanCell cell = new BooleanCell();

// ...

cell.get();

// ...

cell.set(true);

// ...

cell.get();

// ...

cell.set(false);

// ...

cell.get();

You can use this with dataflow:

BooleanCell cell = new BooleanCell();

some_boolean_input.send(cell);

cell.onPress().send(do_something);

Cells exist for Events, Booleans, and Floats. For Events, rather than store a value, they simply propagate events:

EventCell cell = new EventCell();

cell.send(do_something);

cell.send(do_something_else);

x_happened.send(cell);

y_happened.send(cell);

This example would cause do_something and do_something_else to be fired whenever x_happened is produced or y_happened is produced.

Sometimes, you want to connect together methods that aren’t easy to connect together. For example:

send_some_data_to_an_output(???);

do_something_based_on_an_input(???);

How would you connect these? There’s no implementation to provide either end. Luckily, we can use Cells!

// this also works for events and floats with EventCell and FloatCell.

BooleanCell intermediate_channel = new BooleanCell();

send_some_data_to_an_output(intermediate_channel);

do_something_based_on_an_input(intermediate_channel);

You can also use them to connect far-away sections of your code.

BooleanCell some_shared_value = new BooleanCell();

// ... somewhere ...

some_input.send(some_shared_value);

// ... somewhere else ...

some_shared_value.send(some_output);

3 Review of advanced Java concepts

There are a number of features of Java which are heavily used by the CCRE that you might not be familiar with. Below is a quick catalogue of them.

3.1 Anonymous classes

Sometimes you might want to implement an Output that does something unique, so you can’t use anything built-in. You could put this somewhere else:

public class CatPettingEventOutput implements EventOutput {

  private final Cat cat_to_pet;

  public CatPettingEventOutput(Cat cat_to_pet) {

    this.cat_to_pet = cat_to_pet;

  }

  @Override

  public void event() {

    // pet a cat

  }

}

and later:

EventOutput pet_fluffy = new CatPettingEventOutput(fluffy);

But then the class definition is far away from the actual use, and your code gets clogged up with all of your classes. Clearly, there has to be an easier way:

EventOutput pet_fluffy = new EventOutput() {

  public void event() {

    // pet fluffy

  }

};

Useful!

3.2 Lambdas

Let’s go one step further. In Java 8, there’s a new feature called Lambdas which can replace some simple anonymous classes with even shorter code.

Instead of these:

EventOutput pet_fluffy = new EventOutput() {

  public void event() {

    // pet fluffy

  }

};

BooleanOutput fluffy_cage_light = new BooleanOutput() {

  public void set(boolean light_on) {

    // turn on or off the light in fluffy's cage

  }

};

FloatOutput fluffy_heater_temp = new FloatOutput() {

  public void set(float temperature) {

    // change the thermostat on fluffy's cage

  }

};

You can now simply say:

EventOutput pet_fluffy = () -> {

  // pet fluffy

};

BooleanOutput fluffy_cage_light = (light_on) -> {

  // turn on or off the light in fluffy's cage

};

FloatOutput fluffy_heater_temp = (temperature) -> {

  // change the thermostat on fluffy's cage

};

Also, if you only have a single statement in the lambda, you can omit the {}:

EventOutput pet_fluffy = () -> {

  do_pet_fluffy();

};

can become:

EventOutput pet_fluffy = () -> do_pet_fluffy();

Even nicer!

4 The software environment

There are a number of components that you will be working with.

    4.1 The Driver Station

      4.1.1 Modes

      4.1.2 Keyboard shortcuts

      4.1.3 Joysticks

    4.2 The match sequence

    4.3 Safety

    4.4 The roboRIO

      4.4.1 The cRIO

    4.5 Downloading code

    4.6 Speed controllers

      4.6.1 PWM (Pulse-width modulation)

      4.6.2 CAN (Control area network)

    4.7 Sensors

      4.7.1 Digital Inputs

      4.7.2 Analog Inputs

      4.7.3 Internal Sensors to the roboRIO

      4.7.4 PDP Sensors

      4.7.5 RS232 Sensors

    4.8 WiFi and Networking

      4.8.1 The FMS and port filtering

    4.9 Cluck

      4.9.1 The Poultry Inspector

      4.9.2 Network Tables & Smart Dashboard

4.1 The Driver Station

Unfortunately, the DS software only runs on Microsoft Windows.

The Driver Station is two different things: a piece of software, and the Windows laptop that runs that software.

The Driver Station software connects over the robot’s wireless network to the roboRIO (the brain of the robot) and tells it:

It also talks to the Field Management System (FMS) if you’re playing in a real competition.

The software looks like this:

The laptop might looks something like this: (depending on your team)

I’m only showing the left half of the DS - the right half is relatively unimportant.

Let’s go over the tabs available on the DS:

4.1.1 Modes

There are many different conceptualizations of what a "mode" is. The core three are Autonomous Mode, Teleoperated Mode, and Test Mode.

From the robot’s perspective, there’s also disabled mode. (You can also think of it as Enabled versus Disabled and the three fundamental modes.)

When in disabled mode, nothing on the robot should move. Safe!

From the driver station’s perspective, there is also Practice mode, which is useful in theory but not much in practice. (heh.) This mode simply sequences through the other modes in the standard order.

There’s also the emergency stop mode, which is entered by pressing the spacebar (in practice) or the physical e-stop button (on the real field.) Once the robot enters emergency stop mode, it is disabled until the robot is physically turned off and on again.

Note that the spacebar will cause an emergency stop regardless of whether or not the driver station is the current window. If you edit code on your driver station, make sure to disable the robot first. You don’t want to emergency stop the robot whenever you type a space into your program.

4.1.2 Keyboard shortcuts

There are a few important keyboard shortcuts:

Always keep your hand near the disable key when enabling the robot.

4.1.3 Joysticks

The driver station can have up to six Joysticks attached to it. Each Joystick is an individual physical device. Examples of Joysticks:

This is a Joystick:

This is ALSO a Joystick:

This? Another Joystick. Not two Joysticks - just one.

Each Joystick has some number of axes - each axis measures a value from -1.0 to 1.0. It also has some number of buttons - each button can be either pressed or released.

Standard axes are the position of a Joystick on the forward-backward (Y) axis and the left-right (X) axis.

For example, a trigger on a Joystick is a button, and an altitude control wheel (as on the base of the first Joystick) is an axis.

The X axis on a Joystick is usually axis #1, and the Y axis is usually axis #2.

If you work with something with multiple XY sticks, this may vary.

On an xbox controller, for example, #1 & #2 are the axes on left stick, and for the right stick, the X axis is #5 and the Y axis is #6. The trigger axes are #3 (left) and #4 (right.)

4.2 The match sequence

Here’s how a match goes in normal FRC competitions:

There is no way for a team to disable a robot during this time except to emergency-stop the robot. The robot can be forcibly disabled by the referees if it displays unsafe behaviour.

Autonomous mode requires some different programming techniques to write well. See Autonomous and Instinct Modules below.

4.3 Safety

There are some important guidelines that you need to follow, even if you’re working on software:

Remember that following safety procedures are the difference between getting work done and being in the hospital.

4.4 The roboRIO

image sourced from http://khengineering.github.io/RoboRio/faq/roborio/

The roboRIO is FRC’s next-generation robot controller, from National Instruments. It supplants the previous cRIO, and runs Linux with PREEMPT_RT patches on an ARM processor.

It contains a set of ports for interfacing with PWM-controlled devices, CAN bus devices, I2C devices, SPI devices, RS232 (serial) devices, miscellaneous digital I/O devices, relays, analog inputs, USB devices, and networked devices over Ethernet.

You aren’t allowed to control anything on your robot (except for nonfunctional components like LEDs) with any other controller than the roboRIO, so teams have to use it as their main controller.

See below in this document for some of the devices that attach to the roboRIO.

4.4.1 The cRIO

The cRIO was the previous platform used as a robot controller. It ran VxWorks instead of Linux, and the processor was PowerPC instead of ARM. This made it extremely hard to get any software for it. The only JVM we could use was the Squawk JVM, which only supported Java 1.3. (We’re on Java 8 now.) The CCRE pioneered using retrotranslation technology to allow us to use some Java 5 features on the cRIO, but even with those the system was much harder to use than the modern roboRIO.

4.5 Downloading code

To download code, as we said before, you can easily deploy code to the robot:

To see how this works behind the scenes, see Deployment.

4.6 Speed controllers

A speed controller stands between the Power Distribution Panel (PDP) and individual motors, and varies the speed of the motor based on a signal from the roboRIO. There are two primary ways to control a motor: via PWM or via CAN.

4.6.1 PWM (Pulse-width modulation)

With PWM, you can drive Talon SRs, Talon SRXes, Victors, and Jaguars.

A Victor:

A Jaguar (also controllable over CAN):

A Talon:

A Talon SRX (also controllable over CAN):

For a discussion of how PWM works, see the Wikipedia article. The important attributes for us are:

See Hardware Access for information on how to communicate with PWM speed controllers.

4.6.2 CAN (Control area network)

With CAN, you can drive Talon SRXes and CAN Jaguars.

A Jaguar (also controllable over PWM):

A Talon SRX (also controllable over PWM):

For a discussion of how CAN works, see the Wikipedia article. The relevant attributes:

See Hardware Access for information on how to communicate with CAN speed controllers.

4.7 Sensors

Sensors can be classified by how they connect to the roboRIO:

4.7.1 Digital Inputs

Digital Inputs are in the GPIO (general purpose IO) section of the roboRIO. These report either HIGH (true) or LOW (false) at any specific time. For obvious reasons, these correspond to BooleanInputs.

Touch sensors, light sensors, magnetic switches, and pressure switches are examples of simple digital inputs. In one state, they are pressed/activated, and in another state, they are released/deactivated. IMPORTANT: False is not necessary deactivated and true is not necessarily activated! Many (perhaps the majority) of sensors are true by default and only false when activated. Make sure to check your specific sensors to see how they function!

Encoders are bidirectional rotation sensors: every certain fraction of a rotation, they send a directioned tick back to the roboRIO (this is very simplified, of course), which tells the roboRIO that the encoder has spun more. The FPGA totals these ticks and provides you with a sum, which you can access from your code. These are provided as FloatInputs even though they are discrete. Unlike most sensors, these require two digital input ports to function. There are specific APIs for working with encoders.

Gear tooth sensors are a simplified form of encoders that don’t tell you direction - only that motion is occurring. They only require a single digital input port and are easier to install, so they are sometimes used instead of encoders when you only care about measuring speed or distance without direction.

4.7.2 Analog Inputs

Analog inputs are in a dedicated section of the roboRIO. They provide a value in volts from the connected sensor. It is likely that you will need to scale this reading to something more useful to you.

Often, analog sensors are linear: they have a point at which they are zero and a point at which they are one. (The normalize method is very useful for handling these.)

A pressure sensor is a good example that measures the pressure of a robot’s pneumatic system. A certain base voltage is produced for a pressure of 1 atm by the pressure sensor, and a certain amount more is produced for each psi above that.

Gyros (gyroscopes) provide an analog value based on the rotation speed, which is integrated in the FPGA. There are specific APIs for working with gyros.

Other analog sensors include accelerometers and current sensors.

4.7.3 Internal Sensors to the roboRIO

The roboRIO also contains a set of internal accelerometers. It also contains sensors to measure voltages and currents on various internal rails, so you can check the health of the battery. These are FloatInputs.

4.7.4 PDP Sensors

The PDP, or Power Distribution Panel, provides data over the CAN bus to the roboRIO, which tells it the current battery voltage and the present current consumption on each port of the PDP. Useful!

4.7.5 RS232 Sensors

Sensors can also be connected over some of the other data buses. (These are: RS232, SPI, I2C, etc.) These usually require custom drivers to be written to use them.

Currently, the CCRE contains a driver for the UM7LT, which is a heading sensor from CH Robotics that tells us the current orientation of the robot. Note, however, that it requires a lot of calibration to use.

4.8 WiFi and Networking

Here’s a brief overview of how the networking works on a robot when not on a competition field:

image

Each element in this chain is a distinct device with an assigned IPv4 (internet protocol version 4) address, which is made up of four numbers from 0 to 255 (inclusive.)

Computers, in general, can only communicate via knowing each others’ IP address - in the case of the internet, usually IP addresses are acquired via the Domain Name System (DNS). DNS lets your computer ask, for example, for an IP address for team1540.org, and might get back 65.39.205.61 or something else depending on changes to the IP address of the server.

On your local network, unlike on the internet, IP addresses are local. Since most IP addresses refer to specific machines on the wide internet, certain ranges are reserved for private IP addresses:

10.0.0.0 - 10.255.255.255 - 10.0.0.0/8
172.16.0.0 - 172.31.255.255 - 172.16.0.0/12
192.168.0.0 - 192.168.255.255 - 192.168.0.0/16

For example, the IP address 192.168.1.1 is a valid local IP address. In the FRC control system, IP addresses are assigned based on team number, in the form 10.TE.AM.0/24 (which means, for example, 10.15.40.0 through 10.15.40.255 for team 1540. For shorter team numbers, it might be 10.2.54.XX or 10.0.11.XX.)

Within that range, certain addresses are reserved: (for this example, we use team 1540 addresses.)

10.15.40.0
    Reserved, as it ends in 0. Don't use it.
10.15.40.1
    The robot's wireless access point.
    This is usually a D-Link DAP-1522 Rev B wireless radio.
10.15.40.2
    Previously reserved for the cRIO.
    It can be used by the roboRIO if you wish.
10.15.40.3 - 10.15.40.19
    Unused. Allocate static addresses from these.
10.15.40.20 - 10.15.40.199
    Assigned by the DHCP server on the roboRIO. (See below.)
10.15.40.200 - 10.15.40.254
    Unused. Allocate static addresses from these.
10.15.40.255
    Reserved, as it ends in 255. Don't use it.

There are two primary ways for a computer to get an IP address: a statically-allocated address or a DHCP-allocated address.

DHCP (Dynamic Host Configuration Protocol) lets your computer, after it has joined a network, ask others on the network "Please give me an address!" and a DHCP server on the network will tell it the address. Pretty much every normal wireless or wired network that you would plug your computer into uses DHCP.

A static address is set manually on the computer.

DHCP has advantages in terms of it being easier to configure on each computer... but it means that IP addresses can change over time, requires a running DHCP server, and other issues. A static address, by contrast, must be manually configured as to not conflict with any other address, but doesn’t require as much infrastructure.

With the old 2014 cRIO control system, everything used static addresses. Today, with the 2015 roboRIO control system, everything but the wireless AP uses DHCP (as provided BY the wireless AP.) To solve the issue of the DHCP addresses changing, the roboRIO uses mDNS, which is a version of DNS that allows computers on a local network to find each other based on local names. The roboRIO is usually roboRIO-NNNN-FRC.local. (For example, roboRIO-1540-FRC.local.) In 2015, this was roboRIO-NNNN.local, but that was changed for 2016.

4.8.1 The FMS and port filtering

In a competition, instead of laptops connecting directly to wireless APs, the wireless radios are reprogrammed to act as wireless bridges, and then when they are on the field, the field generates access points for the robots in the current match, and the bridges connect to those APs. The laptops then connect over a wired network through the FMS to the robots.

This is mostly transparent, but has two major effects:

A listing of available ports, up to date as of the 2015 challenge:

The CCRE implementation for FRC hosts Cluck servers on 1540, 1735, 5800, and 5805 by default, and 5800 is used as the default port to connect to.

(See the next section for info on Cluck.)

4.9 Cluck

The CCRE includes a publish-subscribe networking system, designed to seamlessly integrate with the rest of the CCRE channel system.

The basic idea is that a group of computers connect to each other over Cluck transports, and can exchange messages over this. Specifically, one side can publish channels and another side can then subscribe to those channels.

For example, in the previous example of CCRE usage, you could put the blowing-up-the-world part onto its own computer/robot:

image

It’s actually about this easy to connect things with actual code. See The Cluck Pub/Sub System.

4.9.1 The Poultry Inspector

In usual use, you don’t need to connect multiple robots or other pieces of code together with Cluck - often, the main use is to publish a bunch of channels and inspect them via the Poultry Inspector.

Don’t worry - it’s not as complicated as the image may make it look. That’s just with a lot of channels displayed.

See Detailed guide to the Poultry Inspector below for more details on how to use it.

4.9.2 Network Tables & Smart Dashboard

WPILib, the "official" framework for writing robot code, has a similar (but, in our minds, insufficiently powerful) system called NetworkTables, and an equivalent to the Poultry Inspector called SmartDashboard.

We don’t use these, due to feeling that the engineering behind them is not sufficiently robust for our purposes.

5 Detailed guide to the CCRE

This section is designed to provide a detailed guide on how to use just about everything in the CCRE!

It’s organized into the following sections:

    5.1 Documentation format

    5.2 Flow versus Setup

    5.3 Dataflow Channels

      5.3.1 Outputs

      5.3.2 Inputs

      5.3.3 CancelOutputs

      5.3.4 Channel Cells

    5.4 Dataflow Transformations

      5.4.1 EventOutputs

      5.4.2 EventInputs

      5.4.3 BooleanOutputs

      5.4.4 BooleanInputs

      5.4.5 FloatOutputs

      5.4.6 FloatInputs

    5.5 Drive Code Implementations

    5.6 Autonomous and Instinct Modules

      5.6.1 Autonomous methods

      5.6.2 A Dire Warning

      5.6.3 Instinct Modules outside of Autonomous

      5.6.4 Instinct MultiModules

    5.7 Hardware Access

    5.8 Logging Framework

    5.9 Concurrency Best Practices

    5.10 StateMachine

    5.11 PID controllers

    5.12 Time and Timing

    5.13 Extended Motors and CAN

    5.14 Remote Configuration

    5.15 Error Handling

    5.16 Control Bindings

    5.17 Tunable Values

    5.18 Deployment

      5.18.1 SSH

      5.18.2 Deployment API

    5.19 Storage Framework

    5.20 Serial I/O

    5.21 Networking Framework

    5.22 Phidget control panels

    5.23 Special sensors

    5.24 The Cluck Pub/Sub System

      5.24.1 Cluck with complexity

    5.25 Detailed guide to the Poultry Inspector

    5.26 Detailed guide to the Emulator

5.1 Documentation format

The following formats are used when describing constructors, methods, or fields:

setup

new BooleanCell(boolean initial_value)

This constructor could be used, for example, as:

BooleanCell cell = new BooleanCell(false);

setup

EventInput BooleanInput.onPress()

This method could be used, for example, as:

BooleanInput button = /* ... some code goes here ... */;

EventInput press = button.onPress();

setup

static double Math.sin(double a)

This method could be used, for example, as:

float x = 10.0f;

double y = Math.sin(x);

setup

static EventOutput.ignored : EventOutput

This field could be used, for example, as:

EventOutput output = EventOutput.ignored;

output.event();

5.2 Flow versus Setup

There are two conditions in which CCRE code can run: flow mode and setup mode. When the robot first starts, it is running in setup mode while your code sets up its functionality. Once this finishes, the robot goes into flow mode and executes the control that has been set up.

Every method in the CCRE is tagged as either flow or setup. For example, the documentation might say:

flow

void BooleanOutput.set(boolean value)

for flow mode or perhaps

setup

EventInput BooleanInput.onPress()

Flow mode versus setup mode is more about where you call a method from rather than exactly when it occurs, but in general you can understand the modes based on whether or not the robot is done setting up yet.

for setup mode. Note the text that says "flow" or "setup".

Except in very specific cases, your code should never call a setup method from flow mode, or a flow method from setup mode. The main exception is that sometimes you want to preinitialize the value of something, and so may call a flow method from setup mode in such cases.

5.3 Dataflow Channels

As touched on before, a channel represents the ability to communicate in some fashion with an implementation. Channels can either be inputs or outputs, and carry values that are either booleans, floats, or events.

5.3.1 Outputs

Outputs are, at a fundamental level, something with a state that can be changed, but not necessarily queried.

flow

void BooleanOutput.set(boolean value)

flow

void FloatOutput.set(float value)

flow

void EventOutput.event()

You can call these methods to change the current state of the output. All control of outputs will at some level reduce to these methods.

The "flow block" annotation here specifies that the code in the block starting on that line is in flow mode.

setup

EventOutput eo = () -> {

flow block

    // do something

};

setup

BooleanOutput bo = (value) -> {

flow block

    // do something with value, a boolean

};

setup

FloatOutput eo = (value) -> {

flow block

    // do something with value, a float

};

You can implement an output with the above code. You can replace // do something with the flow mode code to execute when the output is controlled.

Please note that, most of the time, you shouldn’t need to implement your own channels! Usually there is already an implementation for you. See Dataflow Transformations.

5.3.2 Inputs

flow

boolean BooleanInput.get()

flow

float FloatInput.get()

For many inputs, you can access the current value at any time with get(). This calculates the current value at the time that you call get().

setup

event_input.send(() -> {

flow block

    // do something

});

setup

boolean_input.send((value) -> {

flow block

    // do something with value, the new boolean value

});

setup

float_input.send((value) -> {

flow block

    // do something with value, the new float value

});

More importantly, you can listen for changes on an input. This works for all inputs. You may notice that these contain a part suspiciously similar to defining outputs: this is because you subscribe to a value by telling an input to send all values to an output, which you can implement yourself.

setup

CancelOutput EventInput.send(EventOutput target)

setup

CancelOutput BooleanInput.send(BooleanOutput target)

setup

CancelOutput FloatInput.send(FloatOutput target)

The send family of methods are all used to connect an input to an output with the same type of data. So, whenever the value of the input changes (for a value-based input, like BooleanInput or FloatInput) or the input is produced (for EventInput), then target will received that value or event.

See below for CancelOutput.

setup

EventInput new_input = new DerivedEventInput(inputs) {

    protected boolean shouldProduce() {

flow block

        return /* true if an event should be produced right now */;

    }

};

setup

BooleanInput new_input = new DerivedBooleanInput(inputs) {

    protected boolean apply() {

flow block

        return /* value */;

    }

};

setup

FloatInput new_input = new DerivedFloatInput(inputs) {

    protected float apply() {

flow block

        return /* value */;

    }

};

You can easily implement an input with the above code. You can replace /* value */ with the flow mode code to calculate the value of the input.

apply() will be called once when the input is created, and then exactly once for each time that one of inputs is updated. The value provided by apply will be returned by get.

Since the value only updates when one of inputs updates, make sure that you don’t access anything not included in that list. You can figure out most of what you need in that list by looking at the inputs you call get() on.

Please note that, most of the time, you shouldn’t need to implement your own channels! Usually there is already an implementation for you. See Dataflow Transformations.

setup

CancelOutput EventInput.onUpdate(EventOutput target)

setup

CancelOutput BooleanInput.onUpdate(EventOutput target)

setup

CancelOutput FloatInput.onUpdate(EventOutput target)

For EventInput, onUpdate and send are essentially identical.

The onUpdate family of methods are similar to the send family of methods, but instead of sending a value in the case of a BooleanInput or a FloatInput, they just notify target when the value changes.

See below for CancelOutput.

Here, "setup block" means that the code in this block runs in setup mode.

setup

EventInput input = new EventInput() {

    public CancelOutput onUpdate(EventOutput target) {

setup block

        // set up to tell target when this input changes

        return /* a CancelOutput that cancels the connection */;

    }

};

setup

BooleanInput input = new BooleanInput() {

    public CancelOutput onUpdate(EventOutput target) {

setup block

        // set up to tell target when this input changes

        return /* a CancelOutput that cancels the connection */;

    }

    public boolean get() {

flow block

        return /* the current value */;

    }

};

setup

FloatInput input = new FloatInput() {

    public CancelOutput onUpdate(EventOutput target) {

setup block

        // set up to tell target when this input changes

        return /* a CancelOutput that cancels the connection */;

    }

    public float get() {

flow block

        return /* the current value */;

    }

};

If you want more control over your inputs, you can implement them directly. You implement onUpdate to specify when the input changes, and implement get to specify the value.

WARNING: You probably don’t want to use this! It’s hard to implement, and you might make a mistake. new DerivedEventInput and friends, as detailed above are better for almost all implementations.

5.3.3 CancelOutputs

The send and onUpdate methods also return an CancelOutput. This allows you to cancel a send after it is sent. For example:

CancelOutput cancellator = an_input.send(an_output);

// ... much later ...

cancellator.cancel();

setup

void CancelOutput.cancel()

You call the cancel method to cancel whatever the CancelOutput represents. Note that although this class is similar to EventOutput, it is used in the setup mode, rather than flow mode.

setup

CancelOutput cancellator = () -> {

setup block

    // ... cancel whatever it is ...

};

You may need to implement a CancelOutput yourself, which can be done as shown here. Note, of course, that the body is a setup block, not a flow block.

setup

CancelOutput CancelOutput.combine(CancelOutput other)

This combines the CancelOutput you call it on with another CancelOutput, so that the resulting CancelOutput cancels both of these CancelOutputs.

5.3.4 Channel Cells

A Cell is the dataflow equivalent of a variable. In the case of a BooleanCell or a FloatCell, it simply holds a value that can be used as a BooleanOutput or FloatOutput (to modify the value) or as a BooleanInput or FloatInput (to read the value.) As a EventCell, it simply propagates events through itself, and can be used as either an EventInput (to receive events) or EventOutput (to send events.)

The general idea of a channel that is both an Input and an Output is called an IO, for example BooleanIO or EventIO. All Cells are IOs.

setup

EventOutput EventIO.asInput()

setup

BooleanOutput BooleanIO.asInput()

setup

FloatOutput FloatIO.asInput()

setup

EventOutput EventIO.asOutput()

setup

BooleanOutput BooleanIO.asOutput()

setup

FloatOutput FloatIO.asOutput()

The asInput() and asOutput() methods allow you to use an IO as just an Input or just an Output. Calling x.asInput() will simply return x, but in a variable of a different type, so this is equivalent to casting.

BooleanIO x = new BooleanCell();

x.asInput() == x == (BooleanInput) x;

However, sometimes you need to force which side you use the Cell or IO as, which is why this functionality is provided. While you can cast, usage of asInput() and asOutput() is checked at compile time, so any misuses will be found before you try to run your code. With casting, a mistake might not be found until you run code on your robot.

setup

static EventIO EventIO.compose(EventInput input, EventOutput output)

setup

static BooleanIO BooleanIO.compose(BooleanInput input, BooleanOutput 

    output)

setup

static FloatIO FloatIO.compose(FloatInput input, FloatOutput output)

Sometimes, you want to combine an input and output for the same thing into a single IO. You can simply use compose(input, output) to do this.

flow

void BooleanIO.toggle()

You can use toggle() to toggle the value of a BooleanIO.

BooleanIO something = /* ... */;

// ...

something.toggle();

// is equivalent to

something.set(!something.get());

setup

EventOutput BooleanIO.eventToggle()

You can get an event that lets you toggle the value of a BooleanIO, as if you had called toggle.

EventInput something_happened = /* ... */;

BooleanIO something_controllable = /* ... */;

something_happened.send(something_controllable.eventToggle();

This would toggle something_controllable whenever something_happened.

setup

void BooleanIO.toggleWhen(EventInput when)

This tells the BooleanIO to toggle itself whenever when fires.

EventInput something_happened = /* ... */;

BooleanIO something_controllable = /* ... */;

something_controllable.toggleWhen(something_happened);

setup

new EventCell()

This lets you create a new anonymous EventIO that simply passes events through itself.

EventIO passthrough_event = new EventCell();

// ...

passthrough_event.send(some_output);

// ...

some_input.send(passthrough_event);

setup

new EventCell(EventOutput... outputs)

This lets you create a new anonymous EventIO that simply passes events through itself.

This sends any events that occur to all of the outputs in the constructor arguments, in addition to any other places.

EventIO passthrough_event = new EventCell(a, b, c);

// is equivalent to

EventIO passthrough_event = new EventCell();

passthrough_event.send(a);

passthrough_event.send(b);

passthrough_event.send(c);

setup

new BooleanCell()

This lets you create a new anonymous BooleanIO that simply stores a value. The initial value is false.

BooleanCell some_value = new BooleanIO();

// ...

some_value.send(some_output);

// ...

some_input.send(some_value);

setup

new BooleanCell(boolean default)

This lets you create a new anonymous BooleanIO that simply stores a value. The initial value is default.

BooleanCell some_value = new BooleanIO(true);

// ...

some_value.send(some_output);

// ...

some_input.send(some_value);

setup

new BooleanCell(boolean default)

This lets you create a new anonymous BooleanIO that simply stores a value. The initial value is default.

BooleanCell some_value = new BooleanCell(true);

// ...

some_value.send(some_output);

// ...

some_input.send(some_value);

setup

new BooleanCell(BooleanOutput... targets);

This lets you create a new anonymous BooleanIO that simply stores a value.

This sends the value to all of the outputs in the constructor arguments, in addition to any other places.

BooleanIO passthrough_value = new BooleanCell(a, b, c);

// is equivalent to

BooleanIO passthrough_value = new BooleanCell();

passthrough_value.send(a);

passthrough_value.send(b);

passthrough_value.send(c);

setup

new FloatCell()

This lets you create a new anonymous FloatIO that simply stores a value. The initial value is 0.

FloatCell some_value = new FloatIO();

// ...

some_value.send(some_output);

// ...

some_input.send(some_value);

setup

new FloatCell(float default)

This lets you create a new anonymous FloatIO that simply stores a value. The initial value is default.

FloatCell some_value = new FloatIO(3.2f);

// ...

some_value.send(some_output);

// ...

some_input.send(some_value);

setup

new FloatCell(float default)

This lets you create a new anonymous FloatIO that simply stores a value. The initial value is default.

FloatCell some_value = new FloatCell(0);

// ...

some_value.send(some_output);

// ...

some_input.send(some_value);

setup

new FloatCell(FloatOutput... targets);

This lets you create a new anonymous FloatIO that simply stores a value.

This sends the value to all of the outputs in the constructor arguments, in addition to any other places.

FloatIO passthrough_value = new FloatCell(a, b, c);

// is equivalent to

FloatIO passthrough_value = new FloatCell();

passthrough_value.send(a);

passthrough_value.send(b);

passthrough_value.send(c);

5.4 Dataflow Transformations

In practice, most ways that you might want to transform a channel are ways that someone else has also wanted to. For example, one might convert a BooleanInput to an EventInput for when the BooleanInput becomes true. This is very useful for checking button or sensor presses.

Since these transformations appear repeatedly, the CCRE provides easy ways to perform these transformations.

However, don’t let the term "transformation" confuse you: when you call output.negate(), for example, nothing changes about output. Rather a new channel is returned by output.negate():

FloatOutput negated_output = output.negate();

By "transformation", we usually mean a relationship between an existing channel and a newly-created channel, but sometimes we include things that aren’t technically transformations but are similarly useful and related to channels.

This section will explain various ways to transform a channel.

5.4.1 EventOutputs

setup

EventOutput EventOutput.combine(EventOutput other)

This combines two EventOutputs into a single EventOutput, so that firing the result would fire both of the outputs.

EventOutput merged = a.combine(b);

// then this would be equivalent:

merged.event();

// to this:

a.event();

b.event();

setup

static EventOutput EventOutput.combine(EventOutput event...)

This combines a set of EventOutputs into a single EventOutput, so that firing the result would fire all of the outputs.

EventOutput merged = EventOutput.combine(a, b);

// then this would be equivalent:

merged.event();

// to this:

a.event();

b.event();

setup

EventOutput EventOutput.filter(BooleanInput allow)

setup

EventOutput EventOutput.filterNot(BooleanInput deny)

filter(allow) filters an EventOutput so that events are only propagated further when allow.get() == true.

filter(deny) is similar, but events are only propagated when deny.get() == false.

EventOutput maybe_blow_up_world = blow_up_world.filter(has_authorization);

// then this would be equivalent:

maybe_blow_up_world.event();

// to this:

if (has_authorization.get()) {

    blow_up_world.event();

}

setup

EventOutput EventOutput.debounce(long minimumMilliseconds)

Debouncing limits how often an event can occur, such that if an event occurs a short time after another event, it will be ignored. The debouncing time is given by minimumMilliseconds, which is the minimum number of milliseconds after one event before another event can pass through.

This is used to handle buttons, switches, or sensors that tend to "bounce" and trigger multiple times when you press them once.

The timeout is relative to the last event that was passed through; events that are ignored do not delay the timeout. This way, if an event occurs repeatedly at a period smaller than minimumMilliseconds, there will be about one event every minimumMilliseconds.

setup

CancelOutput EventOutput.on(EventInput when)

This is the same as send, but in the opposite direction.

EventInput a;

EventOutput b;

// these are equivalent:

a.send(b);

b.on(a);

setup

static EventOutput.ignored : EventOutput

This EventOutput ignores all events sent to it.

EventOutput.ignored.event();

// is equivalent to doing absolutely nothing.

setup

EventIO EventOutput.cell()

cell converts a EventOutput into a EventIO.

This is equivalent to wrapping a new EventCell around this EventOutput, but can be more efficient in some cases, and is sometimes more concise.

EventCell ec = new EventCell();

EventIO ec2 = ec.cell();

// then ec2 == ec

EventOutput eo = /* some normal output */;

EventIO ec3 = eo.cell();

// then ec3 is equivalent to new EventCell(eo)

5.4.2 EventInputs

setup

EventInput EventInput.or(EventInput other)

This is an EventInput that is fired whenever either of the EventInputs are fired.

EventInput c = a.or(b);

// this is equivalent:

c.send(output);

// to:

a.send(output);

b.send(output);

setup

EventInput EventInput.and(BooleanInput allow)

setup

EventInput EventInput.andNot(BooleanInput deny)

This is an EventInput limited to only firing when allow.get() == true or deny.get() == false, depending on which version you use.

So, each time that the original EventInput fires, allow or deny is polled, and if the value is the expected value, the result EventInput fires.

EventInput c = a.and(b);

// this is equivalent:

c.send(output);

// to:

a.send(output.filter(b));

setup

EventInput EventInput.debounced(long minimumMilliseconds)

Debouncing limits how often an event can occur, such that if an event occurs a short time after another event, it will be ignored. The debouncing time is given by minimumMilliseconds, which is the minimum number of milliseconds after one event before another event can pass through.

This is used to handle buttons, switches, or sensors that tend to "bounce" and trigger multiple times when you press them once.

The timeout is relative to the last event that was passed through; events that are ignored do not delay the timeout. This way, if an event occurs repeatedly at a period smaller than minimumMilliseconds, there will be about one event every minimumMilliseconds.

setup

static EventInput.never : EventInput

This is an EventInput that never occurs.

EventInput.never.send(output);

// is equivalent to doing absolutely nothing!

5.4.3 BooleanOutputs

setup

BooleanOutput BooleanOutput.invert()

This is an inverted version of the original BooleanOutput.

BooleanOutput inv = a.invert();

// so then this is equivalent

inv.set(false);

// to this:

a.set(true);

setup

BooleanOutput BooleanOutput.combine(BooleanOutput other)

This combines two BooleanOutputs into a single BooleanOutput, so that setting the result to a value would set both of the original outputs to that value.

BooleanOutput merged = a.combine(b);

// then this would be equivalent:

merged.set(true);

// to this:

a.set(true);

b.set(true);

setup

static BooleanOutput BooleanOutput.combine(BooleanOutput output...)

This combines a set of BooleanOutputs into a single BooleanOutput, so that setting the result to a value would set all of the original outputs to that value.

BooleanOutput merged = BooleanOutput.combine(a, b);

// then this would be equivalent:

merged.set(true);

// to this:

a.set(true);

b.set(true);

setup

BooleanOutput BooleanOutput.limitUpdatesTo(EventInput update)

This limits a BooleanOutput such that changes in its value propagate when, and only when, update is fired.

EventCell b = new EventCell();

BooleanOutput c = a.limitUpdatesTo(b);

// ...

c.set(true);

b.event();

setup

EventOutput BooleanOutput.eventSet(boolean value)

setup

EventOutput BooleanOutput.eventSet(BooleanInput value)

This provides an EventOutput that sets this BooleanOutput to some value, either a constant value or a value fetched in the moment from a BooleanInput.

EventOutput o = a.eventSet(true);

// and then:

o.event();

// is equivalent to:

a.set(true);

With a BooleanInput parameter:

EventOutput o = a.eventSet(input);

// and then:

o.event();

// is equivalent to:

a.set(input.get());

setup

void BooleanOutput.setWhen(boolean value, EventInput when)

setup

void BooleanOutput.setWhen(BooleanInput value, EventInput when)

setup

void BooleanOutput.setTrueWhen(EventInput when)

setup

void BooleanOutput.setFalseWhen(EventInput when)

When when fires, this BooleanOutput will be set to value or value.get(), depending on type.

In the case of setTrueWhen(when) or setFalseWhen(when), the boolean given in the name takes the place of value.

EventCell event = new EventCell();

output.setWhen(true, event);

// and then:

event.event();

// is equivalent to:

output.set(true);

With a BooleanInput parameter:

EventCell event = new EventCell();

output.setWhen(input, event);

// and then:

event.event();

// is equivalent to:

output.set(input.get());

With a constant name:

EventCell event = new EventCell();

output.setFalseWhen(event);

// and then:

event.event();

// is equivalent to:

output.set(false);

setup

static BooleanOutput BooleanOutput.polarize(EventOutput toFalse, 

    EventOutput toTrue)

This combines two EventOutputs into a BooleanOutput. When the value sent to the BooleanOutput changes to true, toTrue will be fired, and when it changes to false, toFalse will be fired.

BooleanOutput b = BooleanOutput.polarize(forFalse, forTrue);

// and then:

b.set(true);

// would be equivalent to:

forTrue.event();

// IF b was last set to false.

BooleanOutput b = BooleanOutput.polarize(forFalse, forTrue);

// ...

b.set(true);

// and then a subsequent

b.set(true);

// would be equivalent to doing nothing.

setup

BooleanOutput BooleanOutput.filter(BooleanInput allow)

setup

BooleanOutput BooleanOutput.filterNot(BooleanInput deny)

filter filters a BooleanOutput so that it only changes when allow.get() == true.

filterNot filters a BooleanOutput so that it only changes when deny.get() == false.

As long as allow.get() == true, each change to the returned BooleanOutput will modify the original BooleanOutput.

When allow changes to false, the current value is preserved.

As long as allow.get() == false, each change to the returned BooleanOutput will be remembered, but not propagated.

When allow changes to true, the last remembered value will be propagated.

BooleanOutput filtered = original.filter(allow);

// ...

allow.set(true);

filtered.set(true);

filtered.set(false);

filtered.set(true);

allow.set(false);

filtered.set(false);

filtered.set(true);

filtered.set(false);

allow.set(true);

original.filterNot(deny) is equivalent to original.filter(deny.not()).

setup

static BooleanOutput.ignored : BooleanOutput

This is a BooleanOutput that ignores all values sent to it.

BooleanOutput.ignored.set(false);

// is equivalent to absolutely nothing!

setup

BooleanIO BooleanOutput.cell(boolean default_value)

cell converts a BooleanOutput into a BooleanIO and sets its initial value.

This is equivalent to wrapping a new BooleanCell around this BooleanOutput, but can be more efficient in some cases, and is sometimes more concise.

BooleanCell bc = new BooleanCell();

BooleanIO bc2 = c.cell(true);

// then bc2 == bc and bc is set to true

BooleanOutput bo = /* some normal output */;

BooleanIO bc3 = bo.cell(false);

// then bc3 is equivalent to new BooleanCell(bo)

5.4.4 BooleanInputs

setup

static BooleanInput.alwaysTrue : BooleanInput

A BooleanInput that is always true and never changes.

setup

static BooleanInput.alwaysFalse : BooleanInput

A BooleanInput that is always false and never changes.

setup

static BooleanInput BooleanInput.always(boolean value)

A BooleanInput that is always value and never changes.

setup

BooleanInput BooleanInput.not()

Provides a BooleanInput that is always the inverse of the original BooleanInput.

This means that input.not().get() == !input.get().

setup

BooleanInput BooleanInput.and(BooleanInput b)

Provides a BooleanInput that is true if and only if both BooleanInputs are true.

This means that a.and(b).get() == (a.get() && b.get()).

setup

BooleanInput BooleanInput.andNot(BooleanInput b)

a.andNot(b) is equivalent to a.and(b.not().

setup

BooleanInput BooleanInput.xor(BooleanInput b)

Provides a BooleanInput that is true when one of the BooleanInputs is true and the other is false.

This means that a.and(b).get() == (a.get() ^ b.get()).

setup

BooleanInput BooleanInput.or(BooleanInput b)

Provides a BooleanInput that is true if either of the BooleanInputs are true.

This means that a.and(b).get() == (a.get() || b.get()).

setup

BooleanInput BooleanInput.orNot(BooleanInput b)

a.orNot(b) is equivalent to a.or(b.not()).

setup

EventInput BooleanInput.onPress()

setup

EventInput BooleanInput.onRelease()

setup

EventInput BooleanInput.onChange()

onPress provides a EventInput that is produced whenever this BooleanInput changes from false to true.

onRelease is the same, but for when true changes to false.

onChange is similar, but happens whenever any change occurs.

BooleanCell input = new BooleanCell(false);

EventInput press = input.onPress();

EventInput release = input.onRelease();

EventInput change = input.onChange();

// ...

input.set(true);

input.set(false);

input.set(false);

input.set(true);

input.set(true);

input.set(false);

input.set(true);

setup

void BooleanInput.onPress(EventOutput event)

setup

void BooleanInput.onRelease(EventOutput event)

setup

void BooleanInput.onChange(EventOutput event)

onPress fires event whenever this BooleanInput changes from false to true.

onRelease is the same, but for when true changes to false.

onChange is similar, but happens whenever any change occurs.

BooleanCell input = new BooleanCell(false);

input.onPress(press);

input.onRelease(release);

input.onChange(change);

// ...

input.set(true);

input.set(false);

input.set(false);

input.set(true);

input.set(true);

input.set(false);

input.set(true);

setup

FloatInput BooleanInput.toFloat(float off, float on)

setup

FloatInput BooleanInput.toFloat(float off, FloatInput on)

setup

FloatInput BooleanInput.toFloat(FloatInput off, float on)

setup

FloatInput BooleanInput.toFloat(FloatInput off, FloatInput on)

toFloat provides a FloatInput with a value selected from one of two other floats or FloatInputs.

BooleanCell input = new BooleanCell(false);

FloatInput converted = input.toFloat(0.0f, 0.75f);

// ...

// converted is 0.0

input.set(true);

// converted is 0.75

input.set(false);

// converted is 0.0

setup

BooleanInput BooleanInput.select(boolean off, boolean on)

setup

BooleanInput BooleanInput.select(boolean off, BooleanInput on)

setup

BooleanInput BooleanInput.select(BooleanInput off, boolean on)

setup

BooleanInput BooleanInput.select(BooleanInput off, BooleanInput on)

select provides a BooleanInput with a value selected from one of two other booleans or BooleanInputs.

BooleanCell input = new BooleanCell(false);

BooleanInput converted = input.select(a, b);

// ...

// converted is a.get()

input.set(true);

// converted is b.get()

input.set(false);

// converted is a.get()

setup

BooleanInput BooleanInput.filterUpdates(BooleanInput allow)

setup

BooleanInput BooleanInput.filterUpdatesNot(BooleanInput deny)

filterUpdates provides a BooleanInput that follows the value of the original BooleanInput while allow.get() == true, and holds the last value while allow.get() == false.

a.filterUpdatesNot(b) is equivalent to a.filterUpdates(b.not()).

BooleanCell unlocked = new BooleanCell();

BooleanInput lockable = original.filterUpdates(unlocked);

// ...

unlocked.set(true);

original.set(true);

original.set(false);

original.set(true);

original.set(false);

original.set(true);

unlocked.set(false);

original.set(false);

original.set(true);

original.set(false);

unlocked.set(true);

original.set(true);

original.set(false);

unlocked.set(false);

original.set(true);

5.4.5 FloatOutputs

setup

FloatOutput FloatOutput.negate()

This is a negated version of the original FloatOutput.

FloatOutput inv = a.negate();

// so then this is equivalent

inv.set(1.0f);

// to this:

a.set(-1.0f);

setup

FloatOutput FloatOutput.negateIf(BooleanInput negate)

This is either equivalent to the original FloatOutput or a negated version of the original FloatOutput.

FloatOutput inv = a.negateIf(neg);

// so then this is equivalent

inv.set(1.0f);

// to this:

a.set(-1.0f);

// if neg.get() == true or

a.set(1.0f);

// if neg.get() == false

setup

FloatOutput FloatOutput.combine(FloatOutput other)

This combines two FloatOutputs into a single FloatOutput, so that setting the result to a value would set both of the original outputs to that value.

FloatOutput merged = a.combine(b);

// then this would be equivalent:

merged.set(0.3f);

// to this:

a.set(0.3f);

b.set(0.3f);

setup

static FloatOutput FloatOutput.combine(FloatOutput output...)

This combines a set of FloatOutputs into a single FloatOutput, so that setting the result to a value would set all of the original outputs to that value.

FloatOutput merged = FloatOutput.combine(a, b);

// then this would be equivalent:

merged.set(0.3f);

// to this:

a.set(0.3f);

b.set(0.3f);

setup

EventOutput FloatOutput.eventSet(float value)

setup

EventOutput FloatOutput.eventSet(FloatInput value)

This provides an EventOutput that sets this FloatOutput to some value, either a constant value or a value fetched in the moment from a FloatInput.

EventOutput o = a.eventSet(0.7f);

// and then:

o.event();

// is equivalent to:

a.set(0.7f);

With a FloatInput parameter:

EventOutput o = a.eventSet(input);

// and then:

o.event();

// is equivalent to:

a.set(input.get());

setup

void FloatOutput.setWhen(float value, EventInput when)

setup

void FloatOutput.setWhen(FloatInput value, EventInput when)

When when fires, this FloatOutput will be set to value or value.get(), depending on type.

EventCell event = new EventCell();

output.setWhen(0.3f, event);

// and then:

event.event();

// is equivalent to:

output.set(0.3f);

With a FloatInput parameter:

EventCell event = new EventCell();

output.setWhen(input, event);

// and then:

event.event();

// is equivalent to:

output.set(input.get());

setup

FloatOutput FloatOutput.outputDeadzone(float deadzone)

Provides a FloatOutput whose values go through a deadzone filter before reaching the original FloatOutput.

Usually, you want to apply deadzones on your inputs rather than outputs. See FloatInput.deadzone for the equivalent FloatInput version of this method.

The idea of a deadzone is that it chops out values close to zero.

Usually, a centered Joystick gives values of something like 0.047, not 0.000. You often want to interpret this as zero, because - for example - you usually might not want motors to move in this case.

By using a deadzone of 0.1, any values from around -0.09999999 to around 0.09999999 are converted to exactly zero.

setup

FloatOutput FloatOutput.addRamping(float limit, EventInput updateWhen)

Adds a ramping layer on top of a FloatOutput.

The idea of ramping is that immediately changing from, for example, 0 feet per second to 16 feet per second can be very stressing on a robot’s drivetrain - or other similar components. You usually want to "ramp up" to the desired speed, for example at four feet per second per second.

You configure ramping with a maximum delta and an event to update ramping. The idea is that whenever updateWhen is fired, the actual output is moved closer to the most recent target value. The maximum amount by which it can change is limit.

A recommended ramping setup, in absence of actual testing, is addRamping(0.1f, FRC.constantPeriodic). Since FRC.constantPeriodic is a ten-millisecond loop, this means that it takes 100 milliseconds to go from stopped to full speed. You may need to tweak this lower or higher based on actual testing.

setup

FloatOutput FloatOutput.viaDerivative()

This provides a FloatOutput such that the derivatives of the data sent to it are sent to the original FloatOutput.

You probably want to use FloatInput.derivative instead. It usually makes more sense.

For those of you who don’t know calculus, this essentially converts a position to a speed. So you could send the total count of encoder ticks through viaDerivative and you would get the encoder speed.

FloatCell speed = new FloatCell();

encoder.send(speed.viaDerivative());

WARNING: There is a bug in the implementation that is very hard to solve. Usually, a sensor position "wiggles" slightly due to measurement noise. However, if this doesn’t happen, the derivative doesn’t know when to update the speed, and the speed will perpetually be the last measured speed. It’s hard to find a way to fix this, but we’re working on it. There is a workaround:

FloatCell speed = new FloatCell();

speed.viaDerivative().setWhen(encoder, FRC.sensorPeriodic);

setup

FloatOutput FloatOutput.filter(FloatInput allow)

setup

FloatOutput FloatOutput.filterNot(FloatInput deny)

filter filters a FloatOutput so that it only changes when allow.get() == true.

filterNot filters a FloatOutput so that it only changes when deny.get() == false.

As long as allow.get() == true, each change to the returned FloatOutput will modify the original FloatOutput.

When allow changes to false, the current value is preserved.

As long as allow.get() == false, each change to the returned FloatOutput will be remembered, but not propagated.

When allow changes to true, the last remembered value will be propagated.

FloatOutput filtered = original.filter(allow);

// ...

allow.set(true);

filtered.set(0.3f);

filtered.set(0.0f);

filtered.set(0.6f);

allow.set(false);

filtered.set(0.3f);

filtered.set(0.0f);

filtered.set(0.5f);

allow.set(true);

original.filterNot(deny) is equivalent to original.filter(deny.not()).

setup

BooleanOutput FloatOutput.fromBoolean(float off, float on)

setup

BooleanOutput FloatOutput.fromBoolean(float off, FloatInput on)

setup

BooleanOutput FloatOutput.fromBoolean(FloatInput off, float on)

setup

BooleanOutput FloatOutput.fromBoolean(FloatInput off, FloatInput on)

fromBoolean provides a BooleanOutput that controls a FloatOutput with a value selected from one of two floats or FloatInputs.

FloatCell output = new FloatCell(0.3f);

BooleanOutput converted = output.fromBoolean(0.0f, 0.75f);

// ...

// converted is 0.3

converted.set(true);

// converted is 0.75

converted.set(false);

// converted is 0.0

converted.set(true);

// converted is 0.75

converted.set(false);

// converted is 0.0

setup

static FloatOutput.ignored : FloatOutput

This is a FloatOutput that ignores all values sent to it.

FloatOutput.ignored.set(1.0f);

// is equivalent to absolutely nothing!

setup

FloatIO FloatOutput.cell(float default_value)

cell converts a FloatOutput into a FloatIO and sets its initial value.

This is equivalent to wrapping a new FloatCell around this FloatOutput, but can be more efficient in some cases, and is sometimes more concise.

FloatCell fc = new FloatCell();

FloatIO fc2 = fc.cell(0.0f);

// then fc2 == fc and fc is set to 0.0f

FloatOutput fo = /* some normal output */;

FloatIO fc3 = fo.cell(0.0f);

// then fc3 is equivalent to new FloatCell(fo)

5.4.6 FloatInputs

setup

static FloatInput FloatInput.always(float value)

Provides a FloatInput that is always equal to value and never changes.

FloatInput i = FloatInput.always(17.0f);

// ...

i.get();

setup

static FloatInput.zero : FloatInput

A FloatInput that is always equal to zero. Equivalent to FloatInput.always(0).

FloatInput i = FloatInput.zero;

// ...

i.get();

setup

FloatInput FloatInput.plus(FloatInput other)

setup

FloatInput FloatInput.plus(float other)

setup

FloatInput FloatInput.minus(FloatInput other)

setup

FloatInput FloatInput.minus(float other)

setup

FloatInput FloatInput.minusRev(FloatInput other)

setup

FloatInput FloatInput.minusRev(float other)

setup

FloatInput FloatInput.multipliedBy(FloatInput other)

setup

FloatInput FloatInput.multipliedBy(float other)

setup

FloatInput FloatInput.dividedBy(FloatInput other)

setup

FloatInput FloatInput.dividedBy(float other)

setup

FloatInput FloatInput.dividedByRev(FloatInput other)

setup

FloatInput FloatInput.dividedByRev(float other)

These arithmetic methods allow you to perform arithmetic on the values of FloatInputs.

For example, a.plus(b) has the value of a.get() + b.get() or a.get() + b depending on which version you use.

The Rev methods reverse the order of the operands. Since addition and multiplication are commutative, plusRev and multipliedByRev are unnecessary and do not exist.

a.plus(b)

a.minus(b)

a.minusRev(b)

a.multipliedBy(b)

a.dividedBy(b)

a.dividedByRev(b)

An example:

FloatInput real_drive_speed =

    original_drive_speed.multipliedBy(is_kid_mode.toFloat(0.5f, 1.0f));

// and then:

real_drive_speed.get()

// is either

original_drive_speed.get() * 1.0f

// or

original_drive_speed.get() * 0.5f

setup

BooleanInput FloatInput.atLeast(float minimum)

setup

BooleanInput FloatInput.atLeast(FloatInput minimum)

setup

BooleanInput FloatInput.atMost(float maximum)

setup

BooleanInput FloatInput.atMost(FloatInput maximum)

setup

BooleanInput FloatInput.outsideRange(float minimum, float maximum)

setup

BooleanInput FloatInput.outsideRange(float minimum, FloatInput maximum)

setup

BooleanInput FloatInput.outsideRange(FloatInput minimum, float maximum)

setup

BooleanInput FloatInput.outsideRange(FloatInput minimum, FloatInput 

    maximum)

setup

BooleanInput FloatInput.inRange(float minimum, float maximum)

setup

BooleanInput FloatInput.inRange(float minimum, FloatInput maximum)

setup

BooleanInput FloatInput.inRange(FloatInput minimum, float maximum)

setup

BooleanInput FloatInput.inRange(FloatInput minimum, FloatInput maximum)

These comparison methods allow you to compare a channel against fixed or channel-based values.

a.atLeast(b) has the value of a.get() >= b. a.atMost(b) has the value of a.get() <= b. a.outsideRange(b,c) has the value of a.get() < b || a.get() > c. a.inRange(b,c) has the value of b <= a.get() && a.get() <= c.

BooleanInput low_on_pressure = pressure.atMost(40);

// and then

low_on_pressure.get()

// is equivalent to:

pressure.get() <= 40

setup

FloatInput FloatInput.negated()

Provides a FloatInput that is the negated version of this FloatInput.

FloatInput negated = original.negated();

// and then:

negated.get()

// is equivalent to

-original.get()

setup

FloatInput FloatInput.negatedIf(BooleanInput negate)

Provides a FloatInput that is either equivalent to this FloatInput or the negated version of this FloatInput, based on the negate argument.

FloatInput negated = original.negatedIf(neg);

// and then:

negated.get()

// is equivalent to

-original.get()

// if neg.get() == true or

original.get()

// if neg.get() == false

setup

FloatInput FloatInput.absolute()

Provides a FloatInput that is the absolute value version of this FloatInput.

FloatInput abs = original.absolute();

// and then:

abs.get()

// is equivalent to

Math.abs(original.get())

setup

EventInput FloatInput.onChange()

Provides an EventInput that fires whenever this FloatInput changes by any amount.

FloatCell cell = new FloatCell(0);

EventInput change = cell.onChange();

// ...

cell.set(3.2f);

cell.set(3.2f);

cell.set(0.0f);

setup

EventInput FloatInput.onChangeBy(float magnitude)

Provides an EventInput that fires whenever this FloatInput changes by at least magnitude from the last time that it changed.

FloatCell cell = new FloatCell(0);

EventInput change = cell.onChangeBy(1.0f);

// ...

cell.set(1.1f);

cell.set(30.0f);

cell.set(29.9f);

cell.set(29.1f);

cell.set(29.0f);

cell.set(29.9f);

cell.set(28.1f);

cell.set(27.9f);

setup

FloatInput FloatInput.deadzone(float deadzone)

Provides a FloatInput with the value of the original FloatInput, but with a deadzone applied.

The idea of a deadzone is that it chops out values close to zero.

Usually, a centered Joystick gives values of something like 0.047, not 0.000. You often want to interpret this as zero, because - for example - you usually might not want motors to move in this case.

By using a deadzone of 0.1, any values from around -0.09999999 to around 0.09999999 are converted to exactly zero.

FloatInput deadzoned = original.deadzone(0.1f);

// ...

original.set(1.0f);

original.set(0.1f);

original.set(0.0999f);

original.set(0.05f);

original.set(0.0f);

original.set(-0.05f);

original.set(-0.0999f);

original.set(-0.1f);

original.set(-1.0f);

setup

FloatInput FloatInput.normalize(float zeroV, float oneV)

setup

FloatInput FloatInput.normalize(float zeroV, FloatInput oneV)

setup

FloatInput FloatInput.normalize(FloatInput zeroV, float oneV)

setup

FloatInput FloatInput.normalize(FloatInput zeroV, FloatInput oneV)

Linearly maps from two configurable values to the range of zero to one. zeroV becomes 0.0f, and oneV becomes 1. (zeroV + oneV) / 2 would become 0.5f. This linear map extends through all of the real numbers: it is converted into simply an addition and a multiplication.

You can specify two constant values, two values based on channels, or a combination of the two.

If you had a pressure sensor that outputted 1.1 Volts for an empty tank, and 4.6 Volts for a full tank, you can map these to the range 0 to 1, and use them as a fraction.

You could say:

FloatInput sensor = /* ... */;

FloatInput pressureFraction = sensor.normalize(1.1f, 4.6f);

// pressureFraction would be 0 when the sensor's voltage is 1.1 volts,

// and it would be 1 when the sensor's voltage is 4.6 volts.

// if the sensor reported 4.8 volts, the fraction would be about 1.06.

setup

FloatInput FloatInput.withRamping(float limit, EventInput updateWhen)

Adds a ramping layer on top of a FloatInput.

The idea of ramping is that immediately changing from, for example, 0 feet per second to 16 feet per second can be very stressing on a robot’s drivetrain - or other similar components. You usually want to "ramp up" to the desired speed, for example at four feet per second per second.

You configure ramping with a maximum delta and an event to update ramping. The idea is that whenever updateWhen is fired, the actual output is moved closer to the most recent target value. The maximum amount by which it can change is limit.

A recommended ramping setup, in absence of actual testing, is withRamping(0.1f, FRC.constantPeriodic). Since FRC.constantPeriodic is a ten-millisecond loop, this means that it takes 100 milliseconds to go from stopped to full speed. You may need to tweak this lower or higher based on actual testing.

setup

EventOutput FloatInput.createRampingEvent(float limit, FloatOutput 

    target)

Provides an event that performs incremental ramping based on this FloatInput and controlling target as an output.

The idea of ramping is that immediately changing from, for example, 0 feet per second to 16 feet per second can be very stressing on a robot’s drivetrain - or other similar components. You usually want to "ramp up" to the desired speed, for example at four feet per second per second.

You configure ramping with a maximum delta and an event to update ramping. The idea is that whenever the returned EventOutput is fired, the actual output is moved closer to the most recent target value. The maximum amount by which it can change is limit.

It’s usually easier to use FloatInput.withRamping or FloatOutput.addRamping, which are easier to use.

setup

FloatInput FloatInput.filterUpdates(BooleanInput allow)

setup

FloatInput FloatInput.filterUpdatesNot(BooleanInput deny)

filterUpdates provides a FloatInput that follows the value of the original FloatInput while allow.get() == true, and holds the last value while allow.get() == false.

a.filterUpdatesNot(b) is equivalent to a.filterUpdates(b.not()).

BooleanCell unlocked = new BooleanCell();

FloatInput lockable = original.filterUpdates(unlocked);

// ...

unlocked.set(true);

original.set(5.0f);

original.set(2.0f);

original.set(-1.2f);

unlocked.set(false);

original.set(-0.5f);

original.set(6.1f);

original.set(3.1f);

unlocked.set(true);

original.set(0.3f);

original.set(0.8f);

unlocked.set(false);

original.set(1.0f);

5.5 Drive Code Implementations

Drive Code is the part of robot code that controls the drive base in response to the state of the driver’s Joystick.

There are three main types of drive code:

setup

void Drive.tank(FloatInput leftIn, FloatInput rightIn, FloatOutput 

    leftOut, FloatOutput rightOut)

This sets up tank drive code for the given axes and motors.

In Tank Drive, the driver has two Joysticks or axes, which can be moved forward and back to control the sides of the robot independently: the left axis controls the left side of the robot, and the right axis controls the right side of the robot.

FloatInput left_axis = FRC.joystick1.axisY();

FloatInput right_axis = FRC.joystick2.axisY();

FloatOutput left_motor = FRC.talon(0, FRC.MOTOR_FORWARD);

FloatOutput right_motor = FRC.talon(1, FRC.MOTOR_REVERSE);

// and then the key:

Drive.tank(left_axis, right_axis, left_motor, right_motor);

This method is very simple, and is equivalent to:

left_axis.send(left_motor);

right_axis.send(right_motor);

setup

void Drive.extendedTank(FloatInput leftIn, FloatInput rightIn, 

    FloatInput forward, FloatOutput leftOut, FloatOutput rightOut)

This is similar to tankDrive, but takes an additional forward parameter that is added to the two axes, so that it can be used for exact movement forward and backward.

It is equivalent to:

left_axis.plus(forward).send(left_motor);

right_axis.plus(forward).send(right_motor);

setup

void Drive.arcade(FloatInput sideways, FloatInput forward, FloatOutput 

    leftOut, FloatOutput rightOut)

setup

void Drive.arcade(Joystick joystick, FloatOutput leftOut, FloatOutput 

    rightOut)

This sets up two axes to control the robot with Arcade Drive.

Arcade Drive is also known as single-joystick drive: it allows the robot to be controlled by being pushed in any direction. Forward for forward, backward for backward, left to turn left, and right to turn right. This is done based on one axis for left-right, and one axis for forward-backward. For some, it is the most intuitive control scheme.

FloatInput sideways_axis = FRC.joystick1.axisX();

FloatInput forward_axis = FRC.joystick1.axisY();

FloatOutput left_motor = FRC.talon(0, FRC.MOTOR_FORWARD);

FloatOutput right_motor = FRC.talon(1, FRC.MOTOR_REVERSE);

Drive.arcade(sideways_axis, forward_axis, left_motor, right_motor);

The second form of this method is the same as the first, but it uses the X and Y axis from a single Joystick.

Drive.arcade(FRC.joystick1, left_motor, right_motor);

This piece of drive code is fairly simple, and is equivalent to:

forward.plus(sideways).send(leftOut);

forward.minus(sideways).send(rightOut);

setup

void Drive.mecanum(FloatInput forward, FloatInput strafe, FloatInput 

    rotate, FloatOutput leftFrontMotor, FloatOutput leftBackMotor, 
    FloatOutput rightFrontMotor, FloatOutput rightBackMotor)

This sets up Mecanum Drive on four motors with channels to control forward-backward, left-right (strafe), and left-right (rotate.)

Mecanum drive works with very special wheels (mecanum wheels) to allow the robot to maneuver itself in any direction - not just forward, backward, and rotating. The code to do so is complicated, but it’s all included in this method.

The forward and strafe channels control motion in the XY plane, and the rotate channel controls rotation.

FloatInput strafe_axis = FRC.joystick1.axisX();

FloatInput forward_axis = FRC.joystick1.axisY();

FloatInput rotate_axis = FRC.joystick2.axisX();

// ... motors ...

Drive.mecanum(forward_axis, strafe_axis, rotate_axis,

    left_front_motor, left_back_motor, right_front_motor, right_back_motor);

5.6 Autonomous and Instinct Modules

In autonomous mode, your code tends to need behavior somewhat different from teleoperated code. While control of actuators usually still needs the same dataflow setup, the sequencing required for autonomous mode is not easy to implement with dataflow.

Therefore, the CCRE provides an imperative autonomous mode system, using units of imperative code called InstinctModules.

An "imperative block" is the same as a flow block, but can also use methods marked as "imperative" methods, which are methods that may pause execution.

setup

FRC.registerAutonomous(new InstinctModule() {

    @Override

    protected void autonomousMain() throws Throwable {

imperative block

        // Autonomous code goes here.

    }

});

This is the basic structure of your autonomous code, for when you only need a single sequence. Your imperative code goes in the internal block. The important difference about code here is that it is allowed to "block", or wait for an event to occur. Normal flow mode code cannot wait at all. To allow this to work, InstinctModule bodies run in separate threads.

When autonomous mode ends, your code will get aborted by an AutonomousModeOverException or an InterruptedException. Do not attempt to handle these exceptions.

An example of an autonomous mode:

FRC.registerAutonomous(new InstinctModule() {

    @Override

    protected void autonomousMain() throws Throwable {

        leftMotor.set(0.5f);

        rightMotor.set(0.5f);

        waitForTime(5000);

        leftMotor.set(0.0f);

        rightMotor.set(0.0f);

    }

});

This example autonomous mode runs drive motors forward at half speed for five seconds.

5.6.1 Autonomous methods

There are a variety of methods available that let your code wait for various conditions. While these methods are waiting, the rest of your code (that is outside of this InstinctModule) will continue normally.

These methods can only be used from within an InstinctModule.

imperative

void waitForTime(long milliseconds)

imperative

void waitForTime(FloatInput seconds)

This method waits for milliseconds milliseconds to elapse before continuing. Note that most waits, especially short waits, will be overapproximated: they will in practice wait slightly longer than you expect.

The second form, which takes a FloatInput, is approximately equivalent to waitForTime(seconds.get() * 1000). It exists entirely for convenience when using variable delays. Note that any changes to the value of the FloatInput that occur while this method is waiting will be ignored.

piston.set(true);

waitForTime(1000);

piston.set(false);

This example autonomous mode will extend a piston for one second, and then retract it.

imperative

void waitUntil(BooleanInput waitFor)

imperative

boolean waitUntil(long timeout, BooleanInput waitFor)

imperative

void waitUntilNot(BooleanInput waitFor)

imperative

boolean waitUntilNot(long timeout, BooleanInput waitFor)

waitUntil waits until BooleanInput’s value is true. It is not guaranteed to resume if the value changes to true and very quick changes back to false.

The second form of waitUntil is similar, but will also stop waiting once timeout seconds have ellapsed. It will return true if the condition became true, and false if it timed out instead.

waitUntilNot is the inverse: it waits until a BooleanInput’s value is false.

drive_motors.set(0.3f);

waitUntil(bumper_sensor);

drive_motors.set(0.0f);

Given that bumper_sensor has been defined outside of the InstinctModule as a touch sensor on the robot, this example autonomous mode will drive forward until it hits something.

imperative

void waitForEvent(EventInput source)

This method waits for a specific event to fire. If the event fires before this method is called, it will be ignored.

drive_motors.set(0.3f);

waitForEvent(stop_button);

drive_motors.set(0.0f);

imperative

int waitUntilOneOf(BooleanInput waitFor...)

imperative

int waitUntilOneOf(long timeout, BooleanInput waitFor...)

This method waits until one of a list of BooleanInputs becomes true. It also takes an optional timeout after which it will return regardless of the values of any of the BooleanInputs.

It returns the index of the BooleanInput that became true, starting at zero, or -1 on a timeout.

switch (waitUntilOneOf(5000, left_bumper, right_bumper)) {

case -1:

    // ...

    break;

case 0:

    // ...

    break;

case 1:

    // ...

    break;

}

imperative

void waitUntilAtLeast(FloatInput waitFor, float minimum)

imperative

void waitUntilAtMost(FloatInput waitFor, float maximum)

These methods wait for a FloatInput to satisfy a particular comparison: either at least or at most some value.

waitUntilAtLeast(air_pressure, 70.0f);

5.6.2 A Dire Warning

Note, very specifically, that you cannot use setup mode methods inside an InstinctModule! This means that the following is NOT ALLOWED:

FRC.registerAutonomous(new InstinctModule() {

    @Override

    protected void autonomousMain() throws Throwable {

        // ...

        // THIS IS NOT ALLOWED. DO NOT EVER DO THIS.

        waitUntil(some_condition.not());

        // ...

    }

});

You will have major issues if you attempt to do this. Instead, try this:

BooleanInput not_some_condition = some_condition.not();

FRC.registerAutonomous(new InstinctModule() {

    @Override

    protected void autonomousMain() throws Throwable {

        // ...

        // This is allowed.

        waitUntil(not_some_condition);

        // ...

    }

});

In this specific case, the following would be recommended instead:

FRC.registerAutonomous(new InstinctModule() {

    @Override

    protected void autonomousMain() throws Throwable {

        // ...

        // This is recommended.

        waitUntilNot(some_condition);

        // ...

    }

});

5.6.3 Instinct Modules outside of Autonomous

You can also use InstinctModules in other contexts besides autonomous modes, because they can be useful for semi-automatic control of parts of a robot.

BooleanCell run_this_instinct_module = new BooleanCell(false);

setup

new InstinctModule(run_this_instinct_module) {

    @Override

    protected void autonomousMain() throws Throwable {

imperative block

        // Automatic code goes here.

    }

});

You can pass any BooleanInput you want to the constructor, and the module will run while that BooleanInput is true.

setup

void InstinctModule.setShouldBeRunning(BooleanInput when)

Equivalently to the previous example, you can construct an InstinctModule without any parameters and then call setShouldBeRunning later with the same value.

This means that FRC.registerAutonomous(module) is actually just a call to module.setShouldBeRunning!

setup

BooleanIO InstinctModule.controlIO()

Besides setShouldBeRunning, you can also use controlIO, which provides you with a BooleanIO representing whether the module should be running.

Note that you cannot provide multiple sources of control to an InstinctModule, across all of the three ways.

InstinctModule mod = new InstinctModule() { /* ... */ };

BooleanIO run = mod.controlIO;

run.set(true);

run.toggle();

run.toggle();

run.set(true);

run.set(false);

5.6.4 Instinct MultiModules

In progress.

5.7 Hardware Access

In progress.

5.8 Logging Framework

In progress.

5.9 Concurrency Best Practices

In progress.

5.10 StateMachine

In progress.

5.11 PID controllers

In progress.

5.12 Time and Timing

In progress. (Including Time, scheduling, Ticker, ExpirationTimer, PauseTimer.)

5.13 Extended Motors and CAN

In progress.

5.14 Remote Configuration

In progress.

5.15 Error Handling

In progress.

5.16 Control Bindings

In progress.

5.17 Tunable Values

In progress.

5.18 Deployment

How does this work behind the scenes?

First, this goes through the DeploymentEngine, which dispatches to the deploy() DepTask in the default Deployment class:

@DepTask

public static void deploy() throws Exception {

    Artifact result = DepRoboRIO.buildProject(robotMain);

    // code slightly abbreviated for clarity.

    DepRoboRIO.RIOShell rshell = DepRoboRIO.discoverAndVerify(robot.RobotTemplate.TEAM_NUMBER);

    rshell.archiveLogsTo(DepProject.root());

    rshell.downloadAndStart(result);

}

To find the roboRIO, it checks roboRIO-NNNN-FRC.local (where NNNN is your team number), 172.22.11.2, 10.XX.YY.2, and roboRIO-NNNN.local (where XXYY is your team number.)

This builds your robot code, discovers the roboRIO based on your team number, grabs any old logfiles from the robot, deploys the new robot code to the robot, and restarts the running code.

To be able to talk to your robot to change the code, your laptop needs to be on the same network as the robot, and you need to be able to connect to the robot. (Try running ping roboRIO-NNNN-FRC.local to see if it can be reached, if you’re having any problems.)

5.18.1 SSH

Sometimes, the robot breaks and you need to figure out what’s going on. I’m not going to go into all of the details of this but I’ll overview how you start.

You can connect to the roboRIO over SSH (aka Secure SHell) - you simply point your SSH client at roboRIO-NNNN-FRC.local (where NNNN is your team number) and enter the correct username and password.

On Linux or Mac OS X, you can do this from the command line with pre-installed tools:

On Windows, you can use PuTTY.

$ ssh admin@roboRIO-1540-FRC.local
Password:
$ do stuff on the robot

If you are familiar with nix-like systems, it may surprise you that the superuser account (uid 0) is named ’admin’, not ’root’.

The default password is the blank password, and the username is either ’admin’ or ’lvuser’.

Once you’re on the robot, you want to look in /home/lvuser, where you should find the user program and some related files, including the most recent log files.

5.18.2 Deployment API

In progress.

5.19 Storage Framework

In progress.

5.20 Serial I/O

In progress.

5.21 Networking Framework

In progress.

5.22 Phidget control panels

In progress.

5.23 Special sensors

(UM7LT.) In progress.

5.24 The Cluck Pub/Sub System

In progress.

5.24.1 Cluck with complexity

In progress.

5.25 Detailed guide to the Poultry Inspector

In progress.

5.26 Detailed guide to the Emulator

In progress.

6 CCRE recipes

In progress.

7 Versioning policies of the CCRE

In progress.

8 Javadoc Link

You can browse the Javadoc!

9 Maintainer’s guide

In progress.

9.1 Hardware access

The CCRE provides interfaces to the underlying hardware via WPILib’s JNI layer, which attaches to the WPILib Hardware Abstraction Layer (in C++), which attaches to the NI ChipObject proprietary library, which attaches to the NiFPGA interface to the FPGA (field-programmable gate array) device that manages the communication between the higher-level code and the I/O ports.

In other words, here’s how hardware access works (CCRE at the left - other systems also shown):

image

9.2 CCRE Philosophy

In progress.