Slide 1

Slide 1 text

DESIGNING 
 TESTABLE 
 ROBOT CODE FRC Team 4931 Edwardsville Technologies 2015 FIRST® Championship Conferences April 22, 2015

Slide 2

Slide 2 text

PRESENTERS • Randall Hauch Coach and Mentor, FRC 4931 Senior Principal Engineer, Red Hat • Zach Anderson Programming Lead, FRC 4931 Edwardsville High School, Class of 2015

Slide 3

Slide 3 text

FRC 4931 • Edwardsville, IL USA • 20 Student members from 4 area high schools • First competed in 2014 Rookie Inspiration Award - 2014 STL Regional FRC Highest Rookie Seed - 2014 STL Regional FRC Gracious Professionalism Award - 2015 STL Regional FRC

Slide 4

Slide 4 text

2015 ROBOT

Slide 5

Slide 5 text

DESIGNING 
 TESTABLE 
 ROBOT CODE

Slide 6

Slide 6 text

Teach Good 
 Software Engineering 
 Principles

Slide 7

Slide 7 text

Version control with
 Git and GitHub

Slide 8

Slide 8 text

GITHUB REPOSITORY

Slide 9

Slide 9 text

PULL REQUEST

Slide 10

Slide 10 text

PULL REQUEST

Slide 11

Slide 11 text

GIT HISTORY

Slide 12

Slide 12 text

Testing robot code

Slide 13

Slide 13 text

Interactive Testing • RobotBuilder generates code - commands and command groups - adds buttons on dashboard to invoke commands • Driver Station test mode - subsystems show up in the dashboard - UI controls to control speed controllers • Great way to debug • But testing all functionality manually is time consuming - Ex: change subsystem logic; retest all commands?

Slide 14

Slide 14 text

Automated testing is better

Slide 15

Slide 15 text

Unit Tests • Verify functionality of small “units” - Individual classes - Small groups of classes • Automated tests are as important as functional code - Verify that your code actually works - Helps ensure your changes didn’t break anything • Run frequently - Multiple times as you change code - All tests must pass before merging

Slide 16

Slide 16 text

UNIT TESTS

Slide 17

Slide 17 text

UNIT TESTS

Slide 18

Slide 18 text

UNIT TESTS

Slide 19

Slide 19 text

Unit Test Recommendations • When adding or changing functionality - Write tests firsts - Then change the code - All tests should pass • When fixing a bug - Add a test that replicates the bug’s effect - Then try to correct the code - All tests should pass

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

Unit testing is easier with 
 code that was 
 designed to be “testable”

Slide 23

Slide 23 text

WPILib library is not testable • Much of it depends on hardware • Few abstractions or interfaces • Difficult to specialize via subclassing (lots of private fields/methods) • Lots of dependencies - One class can bring in many of WPILib’s classes, including those that ultimately require hardware Very difficult to test without real hardware Command RobotState HLUsageReporting Timer Scheduler ButtonScheduler easily unit V

Slide 24

Slide 24 text

Let’s be a bit more specific …

Slide 25

Slide 25 text

Actuators & Sensors

Slide 26

Slide 26 text

Actuators & Sensors Ramp subsystem Guardrail
 subsystem Compressor subsystem Vision subsystem Drive subsystem Wrist subsystem Kicker subsystem Arm subsystem

Slide 27

Slide 27 text

Code for subsystems • Create class for subsystem • Add references to hardware objects - Motor controllers, analog inputs, analog outputs, digital inputs, digital outputs • Add logic - Expose methods for subsystem behavior Drive subsystem

Slide 28

Slide 28 text

Code for subsystems • Tests would create an instance of this subsystem class • That results in creating instances of the hardware classes - Attempts to communicate with the actual hardware - Creates lots of other hardware classes • Fails even before we can test anything Drive subsystem NOT TESTABLE!

Slide 29

Slide 29 text

So what did we do?

Slide 30

Slide 30 text

Typical hardware components

Slide 31

Slide 31 text

Hardware abstractions Accelerometer getXacceleration():double getYacceleration():double getZacceleration():double Motor setSpeed( speed: double ):void getDirection():Direction stop():void AngleSensor getAngle():double reset():void CurrentSensor getCurrent():double DriveTrain drive( leftSpeed:double, rightSpeed:double):void DistanceSensor getDistance():double Switch isTriggered():boolean SpeedSensor getSpeed():double Relay isOn():boolean isOff():boolean getState():State Solenoid extend():void retract():void getDirection():Direction isExtending():boolean isRetracting():boolean Gyroscope getRate():double getHeading():double reset():void and more!

Slide 32

Slide 32 text

Traditionally: use WPILib classes directly but this is not testable DigitalInput edu.wpi.first.wpilibj SubsystemX

Slide 33

Slide 33 text

Instead subsystems use the abstractions Switch isTriggered():boolean SubsystemX

Slide 34

Slide 34 text

Then use a hardware implementation 
 on the robot HardwareSwitch DigitalInput edu.wpi.first.wpilibj Switch isTriggered():boolean SubsystemX

Slide 35

Slide 35 text

And a mock implementation for testing isTriggered():boolean setTriggered():void setNotTriggered():void Switch isTriggered():boolean MockSwitch SubsystemX SubsystemXTest

Slide 36

Slide 36 text

Similar patterns for the
 other abstractions

Slide 37

Slide 37 text

These abstractions are
 simple and 
 easy to customize

Slide 38

Slide 38 text

Use anonymous classes

Slide 39

Slide 39 text

Compose one motor from 2 motors

Slide 40

Slide 40 text

Or even from 3 motors

Slide 41

Slide 41 text

Java 8 lambdas make this even easier • Interfaces with one abstract method can be labeled as @FunctionalInterface • Lets Java 8 treat a lambda (or closure) as an implementation of the interface • Saves lots of boilerplate code

Slide 42

Slide 42 text

For example … Switch could be implemented with an anonymous class: Switch isTriggered():boolean

Slide 43

Slide 43 text

But Switch is a @FunctionalInterface so it can be implemented with a lambda expression: Switch isTriggered():boolean

Slide 44

Slide 44 text

But Switch is a @FunctionalInterface Switch isTriggered():boolean so it can be implemented with a lambda expression:

Slide 45

Slide 45 text

So is DriveTrain so it can also be implemented with a lambda expression: DriveTrain drive( leftSpeed:double, rightSpeed:double):void

Slide 46

Slide 46 text

These simple abstractions 
 make the code very readable

Slide 47

Slide 47 text

Let’s see how subsystems 
 use these abstractions

Slide 48

Slide 48 text

A sample subsystem This class contains our non-trivial logic 
 and drive algorithms.
 
 We want to test this! driveTrain shifter 1 1 DriveTrain Relay DriveSystem arcade(driveSpeed:double, turnSpeed:double) tank(leftSpeed:double, rightSpeed:double) cheesy(throttle:double, wheel:double, isQuickTurn:boolean) highGear() lowGear() stop()

Slide 49

Slide 49 text

MockDriveTrain MockRelay Easily unit tested These mocks record whether the DriveSystem used driveTrain and shifter correctly. The tests create mock implementations that can be programmatically set and checked. DriveSystem arcade(driveSpeed:double, turnSpeed:double) tank(leftSpeed:double, rightSpeed:double) cheesy(throttle:double, wheel:double, isQuickTurn:boolean) highGear() lowGear() stop() driveTrain shifter 1 1 DriveTrain Relay DriveSystemTest

Slide 50

Slide 50 text

Code is also very testable

Slide 51

Slide 51 text

composed DriveTrain HardwareRelay Motor Relay edu.wpi.first.wpilibj SpeedController edu.wpi.first.wpilibj 4 1 HardwareMotor 1 Also easily used on the robot (This may seem complicated, 
 but each of these classes 
 is remarkably 
 simple and easy to use) driveTrain shifter 1 1 DriveTrain Relay DriveSystem arcade(driveSpeed:double, turnSpeed:double) tank(leftSpeed:double, rightSpeed:double) cheesy(throttle:double, wheel:double, isQuickTurn:boolean) highGear() lowGear() stop() We use implementations that delegate to the WPILib hardware classes.

Slide 52

Slide 52 text

Code is very readable

Slide 53

Slide 53 text

Code is very readable (and simple)

Slide 54

Slide 54 text

Benefits of hardware abstractions • Each interface represent one thing - very simple - no implementation details • Easy to use • Easy to read • Easy to test

Slide 55

Slide 55 text

Unit tests only get us so far • Can’t easily unit test the whole robot - we do need real hardware for a lot of it • Can’t test control under realistic situations - real sensors are noisy - sensors reflect changes in output - feedback loops are difficult to model We need to still test on the real robot, but …

Slide 56

Slide 56 text

We want to record 
 everything that happens 
 when a robot runs

Slide 57

Slide 57 text

Our data recorder • Captures - continuous sensors (voltage, current, distance, etc.) - discrete sensors (buttons, switches, etc.) - control output (speed, solenoid actuation, etc.) - discrete activities (command states, etc.) • Sends data - to a file for later post-processing off-robot, or - over NetworkTables for real-time processing off-robot

Slide 58

Slide 58 text

WPILib command framework • How to record change in state? • Tried specializing standard Scheduler - too many private methods • Could add data logging in all of our commands - we have lots of commands - would pollute each command with boilerplate - prone to error • Design new command framework - not ideal, but we could make commands more testable

Slide 59

Slide 59 text

New command framework • All the complex logic is hidden in the scheduler - runs on a fixed interval (e.g., every 5ms) - uses a single thread - records all changes in command state as commands are run • Commands and command groups are very simple - lightweight and easy to implement - easier to use - easier to unit test with the real or mock subsystems

Slide 60

Slide 60 text

Post-processing data logs 
 with Tableau

Slide 61

Slide 61 text

Tableau • Desktop data analytics application - drag & drop analysis and graphing - every FRC team given 5 licenses • With it we can - graph all data channels vs time - compare, filter, and correlate different channels • Easy to visualize what happened on the robot

Slide 62

Slide 62 text

Throttle Position Forward Throttle Position Reverse Throttle Position Neutral Current (Amps) levels behaving as expected

Slide 63

Slide 63 text

Battery Level (Volts) Current (Amps)

Slide 64

Slide 64 text

Robot Ramp Test Determine when robot is on the ramp G-Force Y-Axis G-Force Filter Set to -0.9 to 0.9 Gs Grey Band Indicates Filter A trigger was depressed when the robot was on the ramp. This was done to validate the blue bars above When G-Force exceeds +-0.9, value equals one.

Slide 65

Slide 65 text

Summary • You can unit test lots of robot code • Made possible with new hardware abstraction layer - uses real WPILib hardware classes on robot - easy to unit test without hardware • New command framework - easier to use and test • Data recorder captures all inputs and outputs - easy to see and figure out what happened - helps testing with robot

Slide 66

Slide 66 text

Future plans • Create standalone open source library - easy to use on robot - easy to use in unit tests - sits on top of WPILib • We are considering a new open source project - hope to collaborate with other teams - stay tuned! • Hopefully also influence future WPILib versions - abstractions, commands, data logging - improved Ant build files to support unit testing and 
 3rd party libraries

Slide 67

Slide 67 text

Thank you! GOLD-LEVEL SPONSORS

Slide 68

Slide 68 text

Thank you! SILVER SPONSORS BRONZE SPONSORS

Slide 69

Slide 69 text

Resources • Website: http://evilletech.com • FRC 4931 code for 2015 season - https://github.com/frc-4931/2015-Robot • Follow us on Twitter @frc4931