FTCDashboard Comprehensive Guide
What is FTCDashboard?
FTCDashboard is a powerful web-based dashboard that provides real-time robot monitoring and control capabilities for FTC teams. Unlike basic telemetry that only shows text output, FTCDashboard offers a rich graphical interface with live data plotting, field visualization, configuration management, remote control features, and camera streaming.
Key Benefits of FTCDashboard
- Real-time data visualization with live graphs and charts
- Field view for robot position tracking and path visualization
- Runtime configuration without code recompilation
- Remote OpMode control and custom buttons
- Camera streaming integration
- Multi-device support for team collaboration
Installation and Setup
FTCDashboard is installed as a Gradle dependency in your Android Studio project. The library automatically sets up a web server on your robot that can be accessed from any device on the same network.
Step 1: Add Gradle Dependency
dependencies {
// Other dependencies...
implementation 'com.acmerobotics.dashboard:dashboard:0.4.17'
}
// Make sure you have the correct repositories
repositories {
google()
mavenCentral()
maven { url = "https://maven.brott.dev/" }
}Step 2: Required Imports
import com.acmerobotics.dashboard.FtcDashboard;
import com.acmerobotics.dashboard.config.Config;
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
import com.acmerobotics.dashboard.telemetry.TelemetryPacket;
import com.acmerobotics.roadrunner.geometry.Pose2d;
import org.firstinspires.ftc.robotcore.external.hardware.camera.Webcam;Step 3: Basic OpMode Setup
@Config
public class ComprehensiveDashboardOpMode extends OpMode {
private FtcDashboard dashboard;
private MultipleTelemetry telemetry;
private TelemetryPacket packet;
// Hardware objects
private DcMotor leftMotor, rightMotor;
private Servo armServo;
private Webcam webcam;
// Robot position tracking
private double x = 0, y = 0, heading = 0;
private List<Pose2d> actualPath = new ArrayList<>();
// Configuration variables
public static double DRIVE_SPEED = 0.5;
public static double TURN_SPEED = 0.3;
public static int TARGET_POSITION = 1000;
public static boolean USE_ENCODERS = true;
public static double PID_KP = 0.1;
public static double PID_KI = 0.0;
public static double PID_KD = 0.0;
// Camera configuration
public static int CAMERA_WIDTH = 640;
public static int CAMERA_HEIGHT = 480;
public static int CAMERA_FPS = 30;
// Status tracking
private boolean cameraActive = false;
private int frameCount = 0;
private boolean isExecutingAction = false;
private String currentAction = "None";
@Override
public void init() {
initializeDashboard();
initializeHardware();
initializeCamera();
telemetry.addData("Status", "Dashboard initialized");
telemetry.update();
}
private void initializeDashboard() {
dashboard = FtcDashboard.getInstance();
telemetry = new MultipleTelemetry(this.telemetry, dashboard.getTelemetry());
packet = new TelemetryPacket();
}
private void initializeHardware() {
leftMotor = hardwareMap.get(DcMotor.class, "left_motor");
rightMotor = hardwareMap.get(DcMotor.class, "right_motor");
armServo = hardwareMap.get(Servo.class, "arm_servo");
if (USE_ENCODERS) {
leftMotor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
rightMotor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
}
}
private void initializeCamera() {
try {
webcam = hardwareMap.get(Webcam.class, "webcam");
webcam.setViewportRenderer(Webcam.ViewportRenderer.BUFFERED);
webcam.setResolution(CAMERA_WIDTH, CAMERA_HEIGHT);
webcam.setFps(CAMERA_FPS);
cameraActive = webcam.isOpen();
} catch (Exception e) {
telemetry.addData("Camera Error", "Failed to initialize: " + e.getMessage());
cameraActive = false;
}
}Telemetry and Plotting
FTCDashboard transforms basic telemetry into a powerful visualization tool. While traditional telemetry only displays text data, FTCDashboard provides real-time plotting capabilities that make it much easier to understand robot behavior, tune PID controllers, and debug autonomous programs.
Real-time Data Plotting
@Override
public void loop() {
updateRobotPosition();
updateCameraStatus();
// Basic telemetry (shows in both Driver Station and Dashboard)
telemetry.addData("Loop Time", "%.2f seconds", getRuntime());
telemetry.addData("Battery Voltage", "%.2f V", getBatteryVoltage());
// Dashboard-specific telemetry with plotting
packet.put("Loop Time", getRuntime());
packet.put("Battery Voltage", getBatteryVoltage());
packet.put("Left Motor Power", leftMotor.getPower());
packet.put("Right Motor Power", rightMotor.getPower());
packet.put("Left Encoder Position", leftMotor.getCurrentPosition());
packet.put("Right Encoder Position", rightMotor.getCurrentPosition());
packet.put("Robot X Position", x);
packet.put("Robot Y Position", y);
packet.put("Robot Heading", heading);
// Create scatter plots for analysis
packet.put("Time vs Left Power", getRuntime(), leftMotor.getPower());
packet.put("Time vs Right Power", getRuntime(), rightMotor.getPower());
packet.put("Power vs Position", leftMotor.getPower(), leftMotor.getCurrentPosition());
// Color code the plots
packet.put("Left Motor Power:color", "red");
packet.put("Right Motor Power:color", "blue");
packet.put("Robot X Position:color", "green");
packet.put("Robot Y Position:color", "orange");
// Send telemetry to both destinations
telemetry.update();
dashboard.sendTelemetryPacket(packet);
sleep(50); // 20 Hz update rate
}
private void updateRobotPosition() {
// Read current encoder positions
double leftPos = leftMotor.getCurrentPosition();
double rightPos = rightMotor.getCurrentPosition();
// Calculate average distance traveled (simplified odometry)
double distance = (leftPos + rightPos) / 2.0 * 0.001; // 0.001 meters per tick
// Calculate x, y position based on current heading
x = distance * Math.cos(heading);
y = distance * Math.sin(heading);
// Update heading based on difference between left and right encoders
double headingChange = (rightPos - leftPos) * 0.0001;
heading += headingChange;
// Record position for path tracking
if (actualPath.size() > 100) {
actualPath.remove(0);
}
actualPath.add(new Pose2d(x, y, heading));
}
private void updateCameraStatus() {
frameCount++;
if (webcam != null) {
cameraActive = webcam.isOpen();
}
packet.put("Camera Active", cameraActive);
packet.put("Frame Count", frameCount);
}
private double getBatteryVoltage() {
return hardwareMap.voltageSensor.get("Expansion Hub 2").getVoltage();
}Configuration Variables
FTCDashboard's configuration system allows you to change robot parameters without recompiling your code. This is incredibly useful for tuning PID values, adjusting autonomous parameters, and testing different configurations during development and competition.
Configuration Groups
// Drive configuration group
public static class DriveConfig {
@Value(min = 0.0, max = 1.0)
public static double MAX_SPEED = 0.8;
@Value(min = 0.0, max = 1.0)
public static double TURN_SPEED = 0.6;
public static boolean REVERSE_LEFT = false;
public static boolean REVERSE_RIGHT = false;
}
// PID configuration group
public static class PIDConfig {
@Value(min = 0.0, max = 2.0)
public static double KP = 0.1;
@Value(min = 0.0, max = 1.0)
public static double KI = 0.0;
@Value(min = 0.0, max = 1.0)
public static double KD = 0.0;
@Value(min = 0, max = 2000)
public static int TOLERANCE = 50;
}
// Camera configuration group
public static class CameraConfig {
@Value(min = 320, max = 1280)
public static int WIDTH = 640;
@Value(min = 240, max = 720)
public static int HEIGHT = 480;
@Value(min = 10, max = 60)
public static int FPS = 30;
public static boolean ENABLE_STREAMING = true;
}Field View Visualization
FTCDashboard's field view provides a powerful way to visualize your robot's position and movement on the game field. This feature is essential for autonomous programming, path planning, and debugging robot navigation.
Field View Setup
// Field dimensions (adjust for your specific game)
private static final double FIELD_WIDTH = 12.0; // 12 feet in meters
private static final double FIELD_LENGTH = 12.0; // 12 feet in meters
private void setupFieldView() {
// Set field dimensions for proper coordinate system
packet.fieldOverlay().setFieldDimensions(FIELD_WIDTH, FIELD_LENGTH);
// Draw field boundaries
packet.fieldOverlay()
.setStroke("black")
.setStrokeWidth(0.05)
.strokeRect(0, 0, FIELD_WIDTH, FIELD_LENGTH);
// Draw coordinate system reference
packet.fieldOverlay()
.setStroke("red")
.setStrokeWidth(0.02)
.strokeLine(0, 0, 1, 0); // X-axis
packet.fieldOverlay()
.setStroke("green")
.setStrokeWidth(0.02)
.strokeLine(0, 0, 0, 1); // Y-axis
}
private void drawRobotOnField() {
// Draw robot as a blue circle
packet.fieldOverlay()
.setFill("blue")
.fillCircle(x, y, 0.1);
// Draw robot heading as a yellow line
packet.fieldOverlay()
.setStroke("yellow")
.setStrokeWidth(0.03)
.strokeLine(x, y,
x + 0.2 * Math.cos(heading),
y + 0.2 * Math.sin(heading));
}
private void drawActualPath() {
// Draw actual path in red
packet.fieldOverlay()
.setStroke("red")
.setStrokeWidth(0.02);
for (int i = 0; i < actualPath.size() - 1; i++) {
Pose2d current = actualPath.get(i);
Pose2d next = actualPath.get(i + 1);
packet.fieldOverlay().strokeLine(current.getX(), current.getY(),
next.getX(), next.getY());
}
}
private void drawPlannedPath() {
// Define planned path waypoints
double[][] waypoints = {
{0, 0, 0}, // Start at origin
{1, 0, 0}, // Move 1 meter right
{1, 1, Math.PI/2}, // Move 1 meter up, turn 90 degrees
{0, 1, Math.PI}, // Move 1 meter left, turn 180 degrees
{0, 0, -Math.PI/2} // Return to start, turn -90 degrees
};
// Draw planned path in blue
packet.fieldOverlay()
.setStroke("blue")
.setStrokeWidth(0.03);
for (int i = 0; i < waypoints.length - 1; i++) {
double[] current = waypoints[i];
double[] next = waypoints[i + 1];
packet.fieldOverlay().strokeLine(current[0], current[1],
next[0], next[1]);
}
// Draw waypoint markers
for (double[] waypoint : waypoints) {
packet.fieldOverlay()
.setFill("blue")
.fillCircle(waypoint[0], waypoint[1], 0.05);
}
}
private void drawGameElements() {
// Draw cones as orange circles
packet.fieldOverlay()
.setFill("orange")
.fillCircle(2, 2, 0.1);
packet.fieldOverlay()
.setFill("orange")
.fillCircle(8, 3, 0.1);
// Draw cubes as purple rectangles
packet.fieldOverlay()
.setFill("purple")
.fillRect(1, 1, 0.2, 0.2);
packet.fieldOverlay()
.setFill("purple")
.fillRect(9, 6, 0.2, 0.2);
// Draw scoring zones as semi-transparent green rectangles
packet.fieldOverlay()
.setFill("green")
.setFillAlpha(0.2)
.fillRect(10, 10, 2, 2);
}OpMode Controls
FTCDashboard provides powerful remote control capabilities that allow you to start, stop, and control OpModes from the web interface. This is particularly useful for autonomous testing, debugging, and competition scenarios.
Custom Button Actions
// Custom action methods
private void checkCustomActions() {
// These actions would be triggered by custom buttons in the dashboard
// For now, we'll use gamepad buttons as examples
if (gamepad1.a && !isExecutingAction) {
executeForwardAction();
} else if (gamepad1.b && !isExecutingAction) {
executeTurnAction();
} else if (gamepad1.x && !isExecutingAction) {
executeArmUpAction();
} else if (gamepad1.y && !isExecutingAction) {
executeArmDownAction();
} else if (gamepad1.back) {
stopAllActions();
}
}
private void executeForwardAction() {
isExecutingAction = true;
currentAction = "Moving Forward";
int targetPosition = (int) TARGET_POSITION;
leftMotor.setTargetPosition(targetPosition);
rightMotor.setTargetPosition(targetPosition);
leftMotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
rightMotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
leftMotor.setPower(DRIVE_SPEED);
rightMotor.setPower(DRIVE_SPEED);
}
private void executeTurnAction() {
isExecutingAction = true;
currentAction = "Turning";
// Calculate encoder ticks for turn
double wheelCircumference = 2 * Math.PI * 2; // 2 inch radius
double robotCircumference = 2 * Math.PI * 12; // 12 inch robot width
int targetTicks = (int) ((90.0 / 360.0) * robotCircumference / wheelCircumference * 1440);
leftMotor.setTargetPosition(targetTicks);
rightMotor.setTargetPosition(-targetTicks);
leftMotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
rightMotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
leftMotor.setPower(TURN_SPEED);
rightMotor.setPower(TURN_SPEED);
}
private void executeArmUpAction() {
isExecutingAction = true;
currentAction = "Arm Up";
armServo.setPosition(1.0);
// Simulate action completion after a delay
new Thread(() -> {
try {
Thread.sleep(1000);
stopAllActions();
} catch (InterruptedException e) {
// Handle interruption
}
}).start();
}
private void executeArmDownAction() {
isExecutingAction = true;
currentAction = "Arm Down";
armServo.setPosition(0.0);
// Simulate action completion after a delay
new Thread(() -> {
try {
Thread.sleep(1000);
stopAllActions();
} catch (InterruptedException e) {
// Handle interruption
}
}).start();
}
private void stopAllActions() {
isExecutingAction = false;
currentAction = "None";
leftMotor.setPower(0);
rightMotor.setPower(0);
leftMotor.setMode(DcMotor.RunMode.RUN_USING_ENCODER);
rightMotor.setMode(DcMotor.RunMode.RUN_USING_ENCODER);
}Camera Streaming
FTCDashboard provides powerful camera streaming capabilities that allow you to view live video feeds from your robot's cameras directly in the web interface. This is essential for vision-based autonomous programming and real-time robot monitoring.
Camera Integration
private void updateCameraConfiguration() {
// Update camera settings if they've changed
if (webcam != null && webcam.isOpen()) {
try {
webcam.setResolution(CameraConfig.WIDTH, CameraConfig.HEIGHT);
webcam.setFps(CameraConfig.FPS);
} catch (Exception e) {
telemetry.addData("Camera Config Error", e.getMessage());
}
}
}
private void sendCameraData() {
packet.put("Camera Active", cameraActive);
packet.put("Frame Count", frameCount);
packet.put("Camera Width", CameraConfig.WIDTH);
packet.put("Camera Height", CameraConfig.HEIGHT);
packet.put("Camera FPS", CameraConfig.FPS);
packet.put("Enable Streaming", CameraConfig.ENABLE_STREAMING);
// Simulate color detection (in practice, you'd analyze actual camera frames)
if (CameraConfig.ENABLE_STREAMING) {
double redIntensity = Math.random() * 255;
double greenIntensity = Math.random() * 255;
double blueIntensity = Math.random() * 255;
packet.put("Red Intensity", redIntensity);
packet.put("Green Intensity", greenIntensity);
packet.put("Blue Intensity", blueIntensity);
packet.put("Red Detected", redIntensity > 150);
packet.put("Green Detected", greenIntensity > 150);
packet.put("Blue Detected", blueIntensity > 150);
}
}
@Override
public void stop() {
// Close camera properly
if (webcam != null && webcam.isOpen()) {
webcam.close();
cameraActive = false;
}
// Stop all motors for safety
leftMotor.setPower(0);
rightMotor.setPower(0);
// Send final status to dashboard
packet.put("Status", "Stopped");
packet.put("Final Frame Count", frameCount);
dashboard.sendTelemetryPacket(packet);
telemetry.addData("Status", "OpMode stopped");
telemetry.update();
}Complete Integration Example
Here's how to integrate all dashboard features into a single comprehensive OpMode:
Complete Dashboard Integration
@Override
public void loop() {
// 1. Update robot position and camera status
updateRobotPosition();
updateCameraStatus();
updateCameraConfiguration();
// 2. Check for custom actions
checkCustomActions();
// 3. Monitor action completion
if (isExecutingAction) {
if (leftMotor.getMode() == DcMotor.RunMode.RUN_TO_POSITION) {
if (!leftMotor.isBusy() && !rightMotor.isBusy()) {
stopAllActions();
}
}
}
// 4. Normal robot control when not executing custom action
if (!isExecutingAction) {
normalControl();
}
// 5. Update field view
setupFieldView();
drawRobotOnField();
drawActualPath();
drawPlannedPath();
drawGameElements();
// 6. Send all data to dashboard
sendAllTelemetry();
// 7. Update telemetry
updateTelemetry();
sleep(50); // 20 Hz update rate
}
private void normalControl() {
double leftPower = gamepad1.left_stick_y * DriveConfig.MAX_SPEED;
double rightPower = gamepad1.right_stick_y * DriveConfig.MAX_SPEED;
// Handle turning
if (Math.abs(gamepad1.right_stick_x) > 0.1) {
leftPower = DriveConfig.TURN_SPEED * gamepad1.right_stick_x;
rightPower = -DriveConfig.TURN_SPEED * gamepad1.right_stick_x;
}
leftMotor.setPower(leftPower);
rightMotor.setPower(rightPower);
}
private void sendAllTelemetry() {
// Basic telemetry
packet.put("Loop Time", getRuntime());
packet.put("Battery Voltage", getBatteryVoltage());
packet.put("Status", isExecutingAction ? "Executing Action" : "Ready");
packet.put("Current Action", currentAction);
// Motor data
packet.put("Left Motor Power", leftMotor.getPower());
packet.put("Right Motor Power", rightMotor.getPower());
packet.put("Left Encoder", leftMotor.getCurrentPosition());
packet.put("Right Encoder", rightMotor.getCurrentPosition());
// Robot position
packet.put("Robot X", x);
packet.put("Robot Y", y);
packet.put("Robot Heading", heading);
// Configuration values
packet.put("Drive Speed", DriveConfig.MAX_SPEED);
packet.put("Turn Speed", DriveConfig.TURN_SPEED);
packet.put("PID Kp", PIDConfig.KP);
packet.put("PID Ki", PIDConfig.KI);
packet.put("PID Kd", PIDConfig.KD);
// Camera data
sendCameraData();
// Send to dashboard
dashboard.sendTelemetryPacket(packet);
}
private void updateTelemetry() {
telemetry.addData("Status", isExecutingAction ? "Executing Action" : "Ready");
telemetry.addData("Current Action", currentAction);
telemetry.addData("Left Power", "%.2f", leftMotor.getPower());
telemetry.addData("Right Power", "%.2f", rightMotor.getPower());
telemetry.addData("Position", "(%.2f, %.2f)", x, y);
telemetry.addData("Heading", "%.2f rad", heading);
telemetry.addData("Camera Active", cameraActive);
telemetry.addData("Frame Count", frameCount);
telemetry.update();
}Practice Exercise: Comprehensive Dashboard Implementation
Create a complete OpMode that demonstrates all FTCDashboard features working together.
- Set up FTCDashboard with all required imports and initialization
- Implement real-time data plotting with multiple data series
- Create configuration variables for different robot parameters
- Add field view visualization with robot tracking and path planning
- Implement custom button actions for robot control
- Add camera streaming with basic image processing
- Test all features and verify they work together properly
// Your comprehensive OpMode should include:
// 1. Dashboard initialization and hardware setup
// 2. Real-time plotting of motor data, position, and sensor values
// 3. Configuration variables for PID, drive, and camera settings
// 4. Field view with robot position, planned path, and actual path
// 5. Custom buttons for autonomous actions and testing
// 6. Camera streaming with basic color detection
// 7. Integration of all features in a single loop() methodDashboard Best Practices
- Use appropriate update frequencies (10-20 Hz) to avoid overwhelming the interface
- Organize configuration variables into logical groups
- Use different colors and line styles for clear data visualization
- Implement proper error handling for all dashboard features
- Test dashboard functionality thoroughly before competition
- Document custom button functions and configuration parameters
- Monitor performance and adjust settings as needed
Troubleshooting Dashboard Issues
- Dashboard not accessible: Check network connection and firewall settings
- No data appearing: Ensure MultipleTelemetry is used and telemetry.update() is called
- Configuration not updating: Verify variables are static and public with @Config annotation
- Field view not showing: Check fieldOverlay() methods and coordinate system
- Camera not streaming: Verify camera hardware and configuration settings
- Performance issues: Reduce update frequency or simplify data being sent
- Custom buttons not working: Check button implementation and event handling
Further Reading Resources
For more information about FTCDashboard and advanced features: