Generate clean, testable FTC robot projects with proper separation from SDK bloat. Features: - Composite build setup - one shared SDK, multiple clean projects - Subsystem pattern with hardware interfaces for easy testing - JUnit scaffolding - tests run on PC without robot - Minimal project structure (~50KB vs 200MB SDK) - Support for multiple FTC SDK versions Philosophy: Your code should be YOUR code. SDK is just a dependency. Built by Nexus Workshops for FTC teams tired of fighting the standard structure. License: MIT
1548 lines
47 KiB
Bash
Executable File
1548 lines
47 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# FTC Project Generator
|
|
# Copyright (c) 2026 Nexus Workshops LLC
|
|
# Licensed under MIT License
|
|
#
|
|
set -e
|
|
|
|
# FTC Project Generator
|
|
# Creates clean, minimal FTC robot projects with shared SDK
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
FTC_SDK_DIR="${FTC_SDK_DIR:-$HOME/ftc-sdk}"
|
|
DEFAULT_FTC_VERSION="v10.1.1"
|
|
|
|
usage() {
|
|
cat << EOF
|
|
╔════════════════════════════════════════════════════════════════╗
|
|
║ FTC Project Generator - Clean Robot Projects ║
|
|
╚════════════════════════════════════════════════════════════════╝
|
|
|
|
Creates clean, testable FTC robot projects with shared SDK.
|
|
Your code stays separate - SDK is just a build dependency.
|
|
|
|
USAGE:
|
|
$(basename $0) <project-name> [options]
|
|
|
|
OPTIONS:
|
|
-v, --version <tag> FTC SDK git tag (default: $DEFAULT_FTC_VERSION)
|
|
-d, --sdk-dir <path> SDK location (default: $FTC_SDK_DIR)
|
|
-h, --help Show this help
|
|
|
|
EXAMPLES:
|
|
# Create new project with latest SDK
|
|
$(basename $0) my-robot
|
|
|
|
# Create project with specific FTC version
|
|
$(basename $0) competition-bot -v v10.0.0
|
|
|
|
# Use custom SDK location
|
|
$(basename $0) test-bot -d ~/my-ftc-sdk
|
|
|
|
# Create second project (reuses existing SDK!)
|
|
$(basename $0) another-robot
|
|
|
|
WORKFLOW:
|
|
1. Create project: $(basename $0) my-robot
|
|
2. Develop & test: cd my-robot && ./gradlew test --continuous
|
|
3. Deploy to SDK: ./gradlew deployToSDK
|
|
4. Build APK: cd ~/ftc-sdk && ./gradlew build
|
|
5. Install to robot: Use Android Studio or adb
|
|
|
|
WHAT IT DOES:
|
|
✓ Checks/clones FTC SDK (shared across all projects)
|
|
✓ Creates YOUR clean project structure
|
|
✓ Generates test scaffolding with examples
|
|
✓ Sets up composite build to reference SDK
|
|
✓ Ready to code immediately
|
|
|
|
WHY THIS IS BETTER:
|
|
Traditional FTC: This Way:
|
|
• Clone 200MB repo • Your project: 50KB
|
|
• Your code mixed in • SDK separate, shared
|
|
• Hard to test • Tests run on PC instantly
|
|
• One repo per project • One SDK, many projects
|
|
• Forced BSD license • Your license, your code
|
|
|
|
ENVIRONMENT:
|
|
Set defaults to avoid typing them every time:
|
|
|
|
export FTC_SDK_DIR=~/ftc-sdk # SDK location
|
|
export FTC_VERSION=v10.1.1 # Default version
|
|
|
|
PROJECT STRUCTURE:
|
|
my-robot/
|
|
├── src/main/java/robot/
|
|
│ ├── subsystems/ → Your robot logic (tested on PC)
|
|
│ ├── hardware/ → FTC hardware implementations
|
|
│ └── opmodes/ → FTC OpModes for Control Hub
|
|
└── src/test/java/robot/ → Unit tests (run without robot)
|
|
|
|
MULTIPLE PROJECTS:
|
|
One SDK, unlimited projects:
|
|
|
|
$(basename $0) swerve-bot # Creates project 1
|
|
$(basename $0) mecanum-bot # Reuses SDK - just creates project 2
|
|
$(basename $0) test-chassis # Reuses SDK - creates project 3
|
|
|
|
All projects share same SDK but are completely independent!
|
|
|
|
INSTALLATION:
|
|
Add to PATH for global access:
|
|
|
|
# Option 1: Symlink
|
|
sudo ln -s $(pwd)/$(basename $0) /usr/local/bin/
|
|
|
|
# Option 2: Add to PATH
|
|
echo 'export PATH=\$PATH:$(pwd)' >> ~/.bashrc
|
|
|
|
Then use from anywhere:
|
|
cd ~/robotics && $(basename $0) awesome-bot
|
|
|
|
DOCUMENTATION:
|
|
See README.md for detailed information and examples.
|
|
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# Parse arguments
|
|
PROJECT_NAME=""
|
|
FTC_VERSION="$DEFAULT_FTC_VERSION"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-v|--version)
|
|
FTC_VERSION="$2"
|
|
shift 2
|
|
;;
|
|
-d|--sdk-dir)
|
|
FTC_SDK_DIR="$2"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
;;
|
|
*)
|
|
if [ -z "$PROJECT_NAME" ]; then
|
|
PROJECT_NAME="$1"
|
|
else
|
|
echo "Error: Unknown argument: $1"
|
|
exit 1
|
|
fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ -z "$PROJECT_NAME" ]; then
|
|
echo "Error: Project name required"
|
|
echo "Usage: $0 <project-name> [options]"
|
|
echo "Run '$0 --help' for more info"
|
|
exit 1
|
|
fi
|
|
|
|
PROJECT_DIR="$(pwd)/$PROJECT_NAME"
|
|
|
|
echo "============================================"
|
|
echo "FTC Project Generator"
|
|
echo "============================================"
|
|
echo "Project: $PROJECT_NAME"
|
|
echo "SDK Dir: $FTC_SDK_DIR"
|
|
echo "SDK Version: $FTC_VERSION"
|
|
echo "Target: $PROJECT_DIR"
|
|
echo ""
|
|
|
|
# Step 1: Check/setup FTC SDK
|
|
echo ">>> Checking FTC SDK..."
|
|
|
|
if [ -d "$FTC_SDK_DIR" ]; then
|
|
echo "SDK directory exists, checking version..."
|
|
cd "$FTC_SDK_DIR"
|
|
|
|
# Check if it's a git repo
|
|
if [ -d ".git" ]; then
|
|
CURRENT_TAG=$(git describe --tags --exact-match 2>/dev/null || echo "none")
|
|
|
|
if [ "$CURRENT_TAG" = "$FTC_VERSION" ]; then
|
|
echo "✓ SDK already at version $FTC_VERSION"
|
|
else
|
|
echo "Current version: $CURRENT_TAG"
|
|
echo "Fetching and checking out $FTC_VERSION..."
|
|
git fetch --tags
|
|
git checkout "$FTC_VERSION"
|
|
echo "✓ Updated to $FTC_VERSION"
|
|
fi
|
|
else
|
|
echo "Warning: SDK directory exists but is not a git repo"
|
|
echo "Using existing directory anyway..."
|
|
fi
|
|
else
|
|
echo "Cloning FTC SDK $FTC_VERSION..."
|
|
echo "This will take a minute (SDK is ~200MB)..."
|
|
git clone --depth 1 --branch "$FTC_VERSION" \
|
|
https://github.com/FIRST-Tech-Challenge/FtcRobotController.git \
|
|
"$FTC_SDK_DIR" || {
|
|
echo "Error: Failed to clone FTC SDK"
|
|
echo "Check your internet connection or try a different version tag"
|
|
exit 1
|
|
}
|
|
echo "✓ Cloned FTC SDK $FTC_VERSION to $FTC_SDK_DIR"
|
|
fi
|
|
|
|
# Step 2: Create project structure
|
|
echo ""
|
|
echo ">>> Creating project: $PROJECT_NAME"
|
|
|
|
if [ -d "$PROJECT_DIR" ]; then
|
|
echo "Error: Project directory already exists: $PROJECT_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$PROJECT_DIR"
|
|
cd "$PROJECT_DIR"
|
|
|
|
# Create directory structure
|
|
mkdir -p src/main/java/robot/{subsystems,hardware,opmodes}
|
|
mkdir -p src/test/java/robot/{subsystems,hardware}
|
|
|
|
# Step 3: Generate build files
|
|
echo "Generating build configuration..."
|
|
|
|
cat > build.gradle.kts << 'EOF'
|
|
plugins {
|
|
java
|
|
}
|
|
|
|
repositories {
|
|
mavenCentral()
|
|
google()
|
|
}
|
|
|
|
dependencies {
|
|
// Testing (runs on PC without SDK)
|
|
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
|
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
|
testImplementation("org.mockito:mockito-core:5.5.0")
|
|
}
|
|
|
|
java {
|
|
sourceCompatibility = JavaVersion.VERSION_11
|
|
targetCompatibility = JavaVersion.VERSION_11
|
|
}
|
|
|
|
tasks.test {
|
|
useJUnitPlatform()
|
|
testLogging {
|
|
events("passed", "skipped", "failed")
|
|
showStandardStreams = false
|
|
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
|
}
|
|
}
|
|
|
|
// Task to deploy to FTC SDK
|
|
tasks.register<Copy>("deployToSDK") {
|
|
group = "ftc"
|
|
description = "Copy code to FTC SDK TeamCode for deployment"
|
|
|
|
val homeDir = System.getProperty("user.home")
|
|
val sdkDir = providers.gradleProperty("ftcSdkDir")
|
|
.orElse(providers.environmentVariable("FTC_SDK_DIR"))
|
|
.getOrElse("${'$'}homeDir/ftc-sdk")
|
|
|
|
from("src/main/java")
|
|
into("${'$'}sdkDir/TeamCode/src/main/java")
|
|
|
|
doLast {
|
|
println("✓ Code deployed to TeamCode")
|
|
println("")
|
|
println("Next steps:")
|
|
println(" cd ${'$'}sdkDir")
|
|
println(" ./gradlew build")
|
|
}
|
|
}
|
|
EOF
|
|
|
|
cat > settings.gradle.kts << EOF
|
|
rootProject.name = "$PROJECT_NAME"
|
|
EOF
|
|
|
|
cat > gradle.properties << EOF
|
|
# FTC SDK location
|
|
ftcSdkDir=$FTC_SDK_DIR
|
|
|
|
# Gradle daemon
|
|
org.gradle.daemon=true
|
|
org.gradle.parallel=true
|
|
org.gradle.caching=true
|
|
org.gradle.jvmargs=-Xmx2048m
|
|
EOF
|
|
|
|
# Step 4: Generate source files
|
|
echo "Generating source scaffolding..."
|
|
|
|
cat > src/main/java/robot/Pose2d.java << 'EOF'
|
|
package robot;
|
|
|
|
/** Simple 2D pose representation */
|
|
public class Pose2d {
|
|
public final double x, y, heading;
|
|
|
|
public Pose2d(double x, double y, double heading) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.heading = heading;
|
|
}
|
|
|
|
public Pose2d() { this(0, 0, 0); }
|
|
|
|
public double distanceTo(Pose2d other) {
|
|
double dx = x - other.x;
|
|
double dy = y - other.y;
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
}
|
|
}
|
|
EOF
|
|
|
|
cat > src/main/java/robot/subsystems/Drive.java << 'EOF'
|
|
package robot.subsystems;
|
|
|
|
import robot.Pose2d;
|
|
|
|
/**
|
|
* Drive subsystem - your robot logic lives here.
|
|
* Tests run on PC, deploys to robot.
|
|
*/
|
|
public class Drive {
|
|
private final Hardware hardware;
|
|
private boolean fieldRelative = true;
|
|
|
|
public Drive(Hardware hardware) {
|
|
this.hardware = hardware;
|
|
}
|
|
|
|
public void drive(double x, double y, double rotation) {
|
|
if (fieldRelative) {
|
|
double heading = hardware.getHeading();
|
|
double temp = x * Math.cos(-heading) - y * Math.sin(-heading);
|
|
y = x * Math.sin(-heading) + y * Math.cos(-heading);
|
|
x = temp;
|
|
}
|
|
hardware.setPower(x, y, rotation);
|
|
}
|
|
|
|
public Pose2d getPose() {
|
|
return hardware.getPose();
|
|
}
|
|
|
|
public void setFieldRelative(boolean enabled) {
|
|
this.fieldRelative = enabled;
|
|
}
|
|
|
|
/** Hardware interface - implement for real robot */
|
|
public interface Hardware {
|
|
void setPower(double x, double y, double rotation);
|
|
Pose2d getPose();
|
|
double getHeading();
|
|
}
|
|
}
|
|
EOF
|
|
|
|
cat > src/main/java/robot/hardware/MecanumDrive.java << 'EOF'
|
|
package robot.hardware;
|
|
|
|
import robot.Pose2d;
|
|
import robot.subsystems.Drive;
|
|
|
|
// Uncomment when deploying to robot:
|
|
// import com.qualcomm.robotcore.hardware.DcMotor;
|
|
// import com.qualcomm.robotcore.hardware.HardwareMap;
|
|
|
|
/**
|
|
* Real hardware implementation.
|
|
* Stub for testing, uncomment FTC imports for robot.
|
|
*/
|
|
public class MecanumDrive implements Drive.Hardware {
|
|
|
|
// Uncomment for robot:
|
|
// private DcMotor frontLeft, frontRight, backLeft, backRight;
|
|
|
|
private Pose2d pose = new Pose2d();
|
|
|
|
public MecanumDrive(/* HardwareMap hardwareMap */) {
|
|
// frontLeft = hardwareMap.get(DcMotor.class, "frontLeft");
|
|
// frontRight = hardwareMap.get(DcMotor.class, "frontRight");
|
|
// backLeft = hardwareMap.get(DcMotor.class, "backLeft");
|
|
// backRight = hardwareMap.get(DcMotor.class, "backRight");
|
|
}
|
|
|
|
@Override
|
|
public void setPower(double x, double y, double rotation) {
|
|
// Mecanum kinematics
|
|
double frontLeftPower = y + x + rotation;
|
|
double frontRightPower = y - x - rotation;
|
|
double backLeftPower = y - x + rotation;
|
|
double backRightPower = y + x - rotation;
|
|
|
|
// Normalize
|
|
double max = Math.max(1.0, Math.max(
|
|
Math.max(Math.abs(frontLeftPower), Math.abs(frontRightPower)),
|
|
Math.max(Math.abs(backLeftPower), Math.abs(backRightPower))
|
|
));
|
|
|
|
// frontLeft.setPower(frontLeftPower / max);
|
|
// frontRight.setPower(frontRightPower / max);
|
|
// backLeft.setPower(backLeftPower / max);
|
|
// backRight.setPower(backRightPower / max);
|
|
|
|
System.out.printf("Drive: FL=%.2f FR=%.2f BL=%.2f BR=%.2f%n",
|
|
frontLeftPower/max, frontRightPower/max, backLeftPower/max, backRightPower/max);
|
|
}
|
|
|
|
@Override
|
|
public Pose2d getPose() {
|
|
return pose;
|
|
}
|
|
|
|
@Override
|
|
public double getHeading() {
|
|
return pose.heading;
|
|
}
|
|
}
|
|
EOF
|
|
|
|
cat > src/main/java/robot/opmodes/TeleOp.java << 'EOF'
|
|
package robot.opmodes;
|
|
|
|
// Uncomment when deploying to robot:
|
|
// import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
|
// import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
|
|
|
import robot.subsystems.Drive;
|
|
import robot.hardware.MecanumDrive;
|
|
|
|
/**
|
|
* Main TeleOp mode.
|
|
* Uncomment annotations and imports when deploying.
|
|
*/
|
|
// @TeleOp(name="Main TeleOp", group="Competition")
|
|
public class TeleOp /* extends LinearOpMode */ {
|
|
|
|
/*
|
|
@Override
|
|
public void runOpMode() {
|
|
// Initialize hardware
|
|
Drive.Hardware hardware = new MecanumDrive(hardwareMap);
|
|
Drive drive = new Drive(hardware);
|
|
|
|
telemetry.addLine("Ready!");
|
|
telemetry.update();
|
|
|
|
waitForStart();
|
|
|
|
while (opModeIsActive()) {
|
|
// Driver controls
|
|
double x = -gamepad1.left_stick_x;
|
|
double y = -gamepad1.left_stick_y;
|
|
double rotation = -gamepad1.right_stick_x;
|
|
|
|
drive.drive(x, y, rotation);
|
|
|
|
// Telemetry
|
|
telemetry.addData("Pose", drive.getPose());
|
|
telemetry.update();
|
|
|
|
sleep(20);
|
|
}
|
|
}
|
|
*/
|
|
|
|
// Stub for testing compilation
|
|
public static void main(String[] args) {
|
|
System.out.println("TeleOp OpMode - deploy to robot to run");
|
|
}
|
|
}
|
|
EOF
|
|
|
|
cat > src/test/java/robot/subsystems/DriveTest.java << 'EOF'
|
|
package robot.subsystems;
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
import robot.Pose2d;
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
class DriveTest {
|
|
|
|
// Simple inline mock
|
|
static class MockHardware implements Drive.Hardware {
|
|
double lastX, lastY, lastRot;
|
|
Pose2d pose = new Pose2d();
|
|
|
|
@Override
|
|
public void setPower(double x, double y, double rotation) {
|
|
lastX = x; lastY = y; lastRot = rotation;
|
|
}
|
|
|
|
@Override
|
|
public Pose2d getPose() { return pose; }
|
|
|
|
@Override
|
|
public double getHeading() { return pose.heading; }
|
|
}
|
|
|
|
@Test
|
|
void testRobotRelativeDrive() {
|
|
MockHardware mock = new MockHardware();
|
|
Drive drive = new Drive(mock);
|
|
drive.setFieldRelative(false);
|
|
|
|
drive.drive(1.0, 0.5, 0.0);
|
|
|
|
assertEquals(1.0, mock.lastX, 0.01);
|
|
assertEquals(0.5, mock.lastY, 0.01);
|
|
assertEquals(0.0, mock.lastRot, 0.01);
|
|
}
|
|
|
|
@Test
|
|
void testGetPose() {
|
|
MockHardware mock = new MockHardware();
|
|
mock.pose = new Pose2d(1, 2, 0.5);
|
|
Drive drive = new Drive(mock);
|
|
|
|
Pose2d pose = drive.getPose();
|
|
assertEquals(1, pose.x, 0.01);
|
|
assertEquals(2, pose.y, 0.01);
|
|
}
|
|
}
|
|
EOF
|
|
|
|
# Step 5: Create README
|
|
cat > LICENSE << 'EOF'
|
|
MIT License
|
|
|
|
Copyright (c) $(date +%Y) $PROJECT_NAME
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
EOF
|
|
|
|
cat > README.md << EOF
|
|
# $PROJECT_NAME
|
|
|
|
FTC Robot Code - Clean architecture, fully testable on PC.
|
|
|
|
## Quick Start
|
|
|
|
\`\`\`bash
|
|
# Run tests (on PC, no robot needed)
|
|
./gradlew test
|
|
|
|
# Watch tests (auto-rerun on save)
|
|
./gradlew test --continuous
|
|
|
|
# Check for compile errors
|
|
./build.sh
|
|
|
|
# Deploy to robot
|
|
./deploy-to-robot.sh
|
|
\`\`\`
|
|
|
|
## Project Structure
|
|
|
|
\`\`\`
|
|
src/
|
|
├── main/java/robot/
|
|
│ ├── subsystems/ # Your robot logic (tested on PC)
|
|
│ ├── hardware/ # Hardware implementations
|
|
│ └── opmodes/ # FTC OpModes
|
|
└── test/java/robot/ # Unit tests (run without robot)
|
|
\`\`\`
|
|
|
|
## Development Workflow
|
|
|
|
### 1. Develop on PC (Fast Iteration)
|
|
|
|
\`\`\`bash
|
|
./gradlew test --continuous
|
|
\`\`\`
|
|
|
|
Write your robot logic in \`subsystems/\`, test with mocks. No robot required!
|
|
|
|
### 2. Check Build
|
|
|
|
\`\`\`bash
|
|
./build.sh
|
|
\`\`\`
|
|
|
|
Catches compile errors before deployment.
|
|
|
|
### 3. Deploy to Robot
|
|
|
|
\`\`\`bash
|
|
./deploy-to-robot.sh
|
|
\`\`\`
|
|
|
|
Builds and installs to Control Hub (USB or WiFi).
|
|
|
|
## Adding Subsystems
|
|
|
|
Every subsystem follows this pattern:
|
|
|
|
\`\`\`java
|
|
public class MySubsystem {
|
|
private final Hardware hardware;
|
|
|
|
public MySubsystem(Hardware hardware) {
|
|
this.hardware = hardware;
|
|
}
|
|
|
|
// Your robot logic here - tests on PC!
|
|
public void doSomething() {
|
|
hardware.doThing();
|
|
}
|
|
|
|
// Inner interface - implement for robot
|
|
public interface Hardware {
|
|
void doThing();
|
|
}
|
|
}
|
|
\`\`\`
|
|
|
|
Test with inline mock:
|
|
|
|
\`\`\`java
|
|
class MySubsystemTest {
|
|
static class MockHardware implements MySubsystem.Hardware {
|
|
boolean didThing = false;
|
|
public void doThing() { didThing = true; }
|
|
}
|
|
|
|
@Test
|
|
void test() {
|
|
MockHardware mock = new MockHardware();
|
|
MySubsystem sys = new MySubsystem(mock);
|
|
sys.doSomething();
|
|
assertTrue(mock.didThing);
|
|
}
|
|
}
|
|
\`\`\`
|
|
|
|
## Commands
|
|
|
|
| Command | Description |
|
|
|---------|-------------|
|
|
| \`./gradlew test\` | Run all unit tests |
|
|
| \`./gradlew test --continuous\` | Watch mode (auto-rerun on save) |
|
|
| \`./build.sh\` | Build and check for errors |
|
|
| \`./build.sh --clean\` | Clean build |
|
|
| \`./deploy-to-robot.sh\` | Deploy to Control Hub |
|
|
| \`./deploy-to-robot.sh --help\` | Show all deployment options |
|
|
|
|
## Configuration
|
|
|
|
- **FTC SDK Location**: \`$FTC_SDK_DIR\`
|
|
- **FTC SDK Version**: \`$FTC_VERSION\`
|
|
- **Java Version**: 11
|
|
|
|
## Generated By
|
|
|
|
[ftc-new-project](https://github.com/your-org/ftc-project-gen) - Clean FTC robot projects
|
|
|
|
## License
|
|
|
|
MIT - See LICENSE file
|
|
EOF
|
|
|
|
cat > .gitignore << 'EOF'
|
|
# Gradle
|
|
.gradle/
|
|
build/
|
|
gradle-app.setting
|
|
!gradle-wrapper.jar
|
|
|
|
# IntelliJ IDEA
|
|
.idea/
|
|
*.iml
|
|
*.ipr
|
|
*.iws
|
|
out/
|
|
|
|
# VS Code
|
|
.vscode/
|
|
|
|
# Eclipse
|
|
.classpath
|
|
.project
|
|
.settings/
|
|
bin/
|
|
|
|
# macOS
|
|
.DS_Store
|
|
|
|
# Linux
|
|
*~
|
|
|
|
# Windows
|
|
Thumbs.db
|
|
desktop.ini
|
|
|
|
# Logs
|
|
*.log
|
|
|
|
# Test output
|
|
*.class
|
|
hs_err_pid*
|
|
EOF
|
|
|
|
cat > .gitattributes << 'EOF'
|
|
# Auto detect text files and perform LF normalization
|
|
* text=auto
|
|
|
|
# Java sources
|
|
*.java text diff=java
|
|
*.gradle text diff=java
|
|
*.gradle.kts text diff=java
|
|
|
|
# Shell scripts
|
|
*.sh text eol=lf
|
|
|
|
# These files are text and should be normalized (convert crlf => lf)
|
|
*.css text
|
|
*.html text
|
|
*.js text
|
|
*.json text
|
|
*.md text
|
|
*.txt text
|
|
*.xml text
|
|
*.yaml text
|
|
*.yml text
|
|
|
|
# These files are binary and should be left untouched
|
|
*.class binary
|
|
*.jar binary
|
|
*.so binary
|
|
*.dll binary
|
|
*.dylib binary
|
|
EOF
|
|
|
|
cat > .editorconfig << 'EOF'
|
|
# EditorConfig for consistent coding styles
|
|
# Works with: VS Code, Sublime, IntelliJ, etc.
|
|
# https://editorconfig.org
|
|
|
|
root = true
|
|
|
|
[*]
|
|
charset = utf-8
|
|
end_of_line = lf
|
|
insert_final_newline = true
|
|
trim_trailing_whitespace = true
|
|
|
|
[*.java]
|
|
indent_style = space
|
|
indent_size = 4
|
|
|
|
[*.{gradle,gradle.kts}]
|
|
indent_style = space
|
|
indent_size = 4
|
|
|
|
[*.{md,markdown}]
|
|
trim_trailing_whitespace = false
|
|
|
|
[*.{sh,bash}]
|
|
indent_style = space
|
|
indent_size = 2
|
|
EOF
|
|
|
|
# Initialize git repo
|
|
git init -q
|
|
git add .
|
|
git commit -q -m "Initial commit: $PROJECT_NAME
|
|
|
|
Generated by ftc-new-project
|
|
FTC SDK: $FTC_VERSION
|
|
|
|
Project structure:
|
|
- Pure Java core for PC testing
|
|
- FTC hardware implementations
|
|
- Deployment scripts included
|
|
"
|
|
|
|
echo ""
|
|
echo "✓ Git repository initialized"
|
|
|
|
# Step 6: Create Gradle wrapper
|
|
mkdir -p gradle/wrapper
|
|
|
|
cat > gradle/wrapper/gradle-wrapper.properties << 'EOF'
|
|
distributionBase=GRADLE_USER_HOME
|
|
distributionPath=wrapper/dists
|
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
|
zipStoreBase=GRADLE_USER_HOME
|
|
zipStorePath=wrapper/dists
|
|
EOF
|
|
|
|
cat > gradlew << 'GRADLEW'
|
|
#!/bin/sh
|
|
GRADLE_VERSION=8.5
|
|
GRADLE_HOME="$HOME/.gradle/wrapper/dists/gradle-${GRADLE_VERSION}-bin"
|
|
|
|
if [ ! -d "$GRADLE_HOME" ]; then
|
|
echo "Downloading Gradle ${GRADLE_VERSION}..."
|
|
mkdir -p "$GRADLE_HOME"
|
|
wget -q "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" -O /tmp/gradle.zip || {
|
|
echo "Download failed. Install gradle manually from https://gradle.org"
|
|
exit 1
|
|
}
|
|
unzip -q /tmp/gradle.zip -d "$GRADLE_HOME/.."
|
|
rm /tmp/gradle.zip
|
|
fi
|
|
|
|
exec "$GRADLE_HOME/../gradle-${GRADLE_VERSION}/bin/gradle" "$@"
|
|
GRADLEW
|
|
chmod +x gradlew
|
|
|
|
# Step 7: Create build script
|
|
cat > build.sh << 'BUILDSCRIPT'
|
|
#!/bin/bash
|
|
# Build robot code for FTC deployment
|
|
|
|
set -e
|
|
|
|
FTC_SDK_DIR="${FTC_SDK_DIR:-$HOME/ftc-sdk}"
|
|
|
|
show_help() {
|
|
cat << EOF
|
|
╔════════════════════════════════════════════════════════════════╗
|
|
║ Build FTC Robot Code - Help ║
|
|
╚════════════════════════════════════════════════════════════════╝
|
|
|
|
Builds your robot code into an APK without deploying to Control Hub.
|
|
Useful for checking compile errors before deployment.
|
|
|
|
USAGE:
|
|
$(basename $0) [options]
|
|
|
|
OPTIONS:
|
|
-h, --help Show this help
|
|
-c, --clean Clean build (remove old artifacts)
|
|
-v, --verbose Verbose output
|
|
--check-only Only check if code compiles (no APK)
|
|
|
|
WHAT THIS SCRIPT DOES:
|
|
|
|
1. Runs local tests (./gradlew test)
|
|
2. Deploys code to FTC SDK
|
|
3. Builds APK (checks for compile errors)
|
|
4. Shows APK location
|
|
|
|
WHY USE THIS:
|
|
|
|
✓ Check for compile errors before deploying
|
|
✓ Faster than full deployment
|
|
✓ Safe - doesn't touch the robot
|
|
✓ Validates FTC imports are correct
|
|
|
|
EXAMPLES:
|
|
|
|
# Normal build
|
|
./build.sh
|
|
|
|
# Clean build (fresh start)
|
|
./build.sh --clean
|
|
|
|
# Just check compilation, don't build APK
|
|
./build.sh --check-only
|
|
|
|
# Verbose output
|
|
./build.sh --verbose
|
|
|
|
WORKFLOW:
|
|
|
|
During development:
|
|
./gradlew test --continuous # Fast PC testing
|
|
|
|
Before deployment:
|
|
./build.sh # Check compile errors
|
|
|
|
Ready to deploy:
|
|
./deploy-to-robot.sh # Build and deploy to robot
|
|
|
|
SEE ALSO:
|
|
|
|
./gradlew test Run unit tests
|
|
./deploy-to-robot.sh Build and deploy to robot
|
|
./deploy-to-robot.sh --help Full deployment options
|
|
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# Parse arguments
|
|
CLEAN_BUILD=false
|
|
VERBOSE=false
|
|
CHECK_ONLY=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-h|--help)
|
|
show_help
|
|
;;
|
|
-c|--clean)
|
|
CLEAN_BUILD=true
|
|
shift
|
|
;;
|
|
-v|--verbose)
|
|
VERBOSE=true
|
|
shift
|
|
;;
|
|
--check-only)
|
|
CHECK_ONLY=true
|
|
shift
|
|
;;
|
|
*)
|
|
echo "Error: Unknown option: $1"
|
|
echo "Run with --help for usage information"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
GRADLE_ARGS=""
|
|
if [ "$VERBOSE" = true ]; then
|
|
GRADLE_ARGS="--info"
|
|
fi
|
|
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
echo "║ Build FTC Robot Code ║"
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
|
|
# Step 1: Run tests
|
|
echo ">>> Step 1: Running unit tests..."
|
|
./gradlew test $GRADLE_ARGS || {
|
|
echo ""
|
|
echo "✗ Tests failed!"
|
|
echo "Fix test errors before building for robot."
|
|
exit 1
|
|
}
|
|
echo "✓ All tests passed"
|
|
echo ""
|
|
|
|
if [ "$CHECK_ONLY" = true ]; then
|
|
echo ">>> Check complete (--check-only)"
|
|
echo "✓ Code compiles and tests pass"
|
|
exit 0
|
|
fi
|
|
|
|
# Step 2: Deploy to SDK
|
|
echo ">>> Step 2: Deploying code to FTC SDK..."
|
|
./gradlew deployToSDK $GRADLE_ARGS || {
|
|
echo ""
|
|
echo "✗ Failed to deploy code"
|
|
exit 1
|
|
}
|
|
echo ""
|
|
|
|
# Step 3: Build APK
|
|
if [ "$CLEAN_BUILD" = true ]; then
|
|
echo ">>> Step 3: Clean build..."
|
|
cd "$FTC_SDK_DIR"
|
|
./gradlew clean $GRADLE_ARGS
|
|
echo ""
|
|
fi
|
|
|
|
echo ">>> Step 3: Building APK..."
|
|
cd "$FTC_SDK_DIR"
|
|
|
|
# Check for Android SDK before building
|
|
if [ ! -f "local.properties" ] && [ -z "$ANDROID_HOME" ]; then
|
|
echo ""
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
echo "║ Android SDK Not Configured ║"
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
echo "FTC SDK needs Android SDK to build the APK."
|
|
echo ""
|
|
echo "Quick setup:"
|
|
echo ""
|
|
echo "1. Install Android Studio:"
|
|
echo " https://developer.android.com/studio"
|
|
echo ""
|
|
echo "2. After install, Android SDK is usually at:"
|
|
echo " Linux: ~/Android/Sdk"
|
|
echo " macOS: ~/Library/Android/sdk"
|
|
echo " Windows: C:\\Users\\<you>\\AppData\\Local\\Android\\Sdk"
|
|
echo ""
|
|
echo "3. Create local.properties in FTC SDK:"
|
|
echo " echo 'sdk.dir=$HOME/Android/Sdk' > $FTC_SDK_DIR/local.properties"
|
|
echo ""
|
|
echo "Or set environment variable:"
|
|
echo " export ANDROID_HOME=~/Android/Sdk"
|
|
echo " echo 'export ANDROID_HOME=~/Android/Sdk' >> ~/.bashrc"
|
|
echo ""
|
|
echo "After setup, run ./build.sh again"
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
|
|
./gradlew assembleDebug $GRADLE_ARGS || {
|
|
echo ""
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
echo "║ Build Failed - Compile Errors ║"
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
echo "Common issues:"
|
|
echo ""
|
|
echo "1. FTC imports not uncommented"
|
|
echo " → Edit hardware and OpMode files"
|
|
echo " → Uncomment: import com.qualcomm.robotcore..."
|
|
echo ""
|
|
echo "2. Missing FTC SDK"
|
|
echo " → Check: ls $FTC_SDK_DIR"
|
|
echo " → Should see: RobotCore, Hardware, FtcRobotController"
|
|
echo ""
|
|
echo "3. Syntax errors"
|
|
echo " → Check error messages above"
|
|
echo " → Fix in your src/ files"
|
|
echo ""
|
|
echo "Run with --verbose for more details"
|
|
echo ""
|
|
exit 1
|
|
}
|
|
|
|
APK_PATH="$FTC_SDK_DIR/FtcRobotController/build/outputs/apk/debug/FtcRobotController-debug.apk"
|
|
|
|
echo ""
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
echo "║ ✓ Build Successful! ║"
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
echo "APK Location:"
|
|
echo " $APK_PATH"
|
|
echo ""
|
|
echo "APK Size: $(du -h "$APK_PATH" | cut -f1)"
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo ""
|
|
echo " Deploy to robot:"
|
|
echo " ./deploy-to-robot.sh --skip-build"
|
|
echo ""
|
|
echo " Or just run full deployment:"
|
|
echo " ./deploy-to-robot.sh"
|
|
echo ""
|
|
BUILDSCRIPT
|
|
chmod +x build.sh
|
|
|
|
# Step 8: Create deployment script
|
|
cat > deploy-to-robot.sh << 'DEPLOYSCRIPT'
|
|
#!/bin/bash
|
|
# Deploy to FTC Control Hub
|
|
|
|
set -e
|
|
|
|
CONTROL_HUB_IP="${CONTROL_HUB_IP:-192.168.43.1}"
|
|
CONTROL_HUB_PORT="${CONTROL_HUB_PORT:-5555}"
|
|
FTC_SDK_DIR="${FTC_SDK_DIR:-$HOME/ftc-sdk}"
|
|
|
|
show_help() {
|
|
cat << EOF
|
|
╔════════════════════════════════════════════════════════════════╗
|
|
║ Deploy to FTC Control Hub - Help ║
|
|
╚════════════════════════════════════════════════════════════════╝
|
|
|
|
Builds your robot code and deploys it to the Control Hub.
|
|
|
|
USAGE:
|
|
$(basename $0) [options]
|
|
|
|
OPTIONS:
|
|
-h, --help Show this help
|
|
-u, --usb Force USB connection (skip WiFi)
|
|
-w, --wifi Force WiFi connection (skip USB)
|
|
-i, --ip <address> Custom Control Hub IP address
|
|
-p, --port <port> Custom adb port (default: 5555)
|
|
--skip-build Skip building, just install existing APK
|
|
--build-only Build APK but don't install
|
|
|
|
CONNECTION METHODS:
|
|
|
|
1. USB (Recommended - Most Reliable)
|
|
────────────────────────────────────────
|
|
• Plug Control Hub into computer via USB
|
|
• Run: ./deploy-to-robot.sh
|
|
• That's it!
|
|
|
|
Pros: Fast, reliable, no network needed
|
|
Cons: Requires cable
|
|
|
|
2. WiFi Direct (Default FTC Network)
|
|
────────────────────────────────────────
|
|
• Connect computer to 'FIRST-xxxx-RC' network
|
|
• Run: ./deploy-to-robot.sh
|
|
• Uses IP: 192.168.43.1:5555 (FTC standard)
|
|
|
|
Pros: Wireless, convenient
|
|
Cons: Can be slower, network issues
|
|
|
|
3. Custom Network
|
|
────────────────────────────────────────
|
|
• Control Hub connected to your network
|
|
• Find IP: Check router or Control Hub settings
|
|
• Run: ./deploy-to-robot.sh -i 192.168.1.100
|
|
|
|
Pros: Can use your network
|
|
Cons: Need to find IP address
|
|
|
|
EXAMPLES:
|
|
|
|
# Auto-detect (tries USB first, then WiFi)
|
|
./deploy-to-robot.sh
|
|
|
|
# Force USB only
|
|
./deploy-to-robot.sh --usb
|
|
|
|
# Force WiFi only
|
|
./deploy-to-robot.sh --wifi
|
|
|
|
# Custom IP address
|
|
./deploy-to-robot.sh -i 192.168.1.100
|
|
|
|
# Custom IP with environment variable
|
|
CONTROL_HUB_IP=10.0.0.50 ./deploy-to-robot.sh
|
|
|
|
# Build only, no install (for testing build)
|
|
./deploy-to-robot.sh --build-only
|
|
|
|
# Install existing APK (skip build)
|
|
./deploy-to-robot.sh --skip-build
|
|
|
|
TROUBLESHOOTING:
|
|
|
|
"adb: command not found"
|
|
├─> Install Android Platform Tools:
|
|
│ Ubuntu/Debian: sudo apt install android-tools-adb
|
|
│ Arch: sudo pacman -S android-tools
|
|
│ Download: https://developer.android.com/studio/releases/platform-tools
|
|
|
|
"cannot connect to 192.168.43.1:5555"
|
|
├─> Make sure you're connected to FIRST-xxxx-RC WiFi network
|
|
├─> Try USB instead: ./deploy-to-robot.sh --usb
|
|
└─> Check Control Hub is powered on
|
|
|
|
"device unauthorized"
|
|
├─> On Control Hub, allow USB debugging
|
|
└─> Check Control Hub screen for authorization prompt
|
|
|
|
"no devices/emulators found"
|
|
├─> USB: Check cable, try different port
|
|
├─> WiFi: Verify network connection
|
|
└─> Run: adb devices (to see what adb sees)
|
|
|
|
WHAT THIS SCRIPT DOES:
|
|
|
|
1. Deploys your code to ~/ftc-sdk/TeamCode
|
|
2. Builds APK using FTC SDK
|
|
3. Connects to Control Hub (USB or WiFi)
|
|
4. Installs APK to Control Hub
|
|
5. Your OpModes appear on Driver Station
|
|
|
|
AFTER DEPLOYMENT:
|
|
|
|
On Driver Station:
|
|
1. Go to OpModes menu
|
|
2. Select TeleOp → "Main TeleOp"
|
|
3. Press INIT, then START
|
|
4. Your code is now running!
|
|
|
|
ENVIRONMENT VARIABLES:
|
|
|
|
CONTROL_HUB_IP Control Hub IP (default: 192.168.43.1)
|
|
CONTROL_HUB_PORT adb port (default: 5555)
|
|
FTC_SDK_DIR FTC SDK location (default: ~/ftc-sdk)
|
|
|
|
Example:
|
|
CONTROL_HUB_IP=192.168.1.100 FTC_SDK_DIR=~/my-ftc ./deploy-to-robot.sh
|
|
|
|
SEE ALSO:
|
|
|
|
adb devices List connected devices
|
|
adb connect <ip> Connect via WiFi manually
|
|
adb disconnect Disconnect WiFi connection
|
|
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# Parse arguments
|
|
FORCE_USB=false
|
|
FORCE_WIFI=false
|
|
SKIP_BUILD=false
|
|
BUILD_ONLY=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-h|--help)
|
|
show_help
|
|
;;
|
|
-u|--usb)
|
|
FORCE_USB=true
|
|
shift
|
|
;;
|
|
-w|--wifi)
|
|
FORCE_WIFI=true
|
|
shift
|
|
;;
|
|
-i|--ip)
|
|
CONTROL_HUB_IP="$2"
|
|
shift 2
|
|
;;
|
|
-p|--port)
|
|
CONTROL_HUB_PORT="$2"
|
|
shift 2
|
|
;;
|
|
--skip-build)
|
|
SKIP_BUILD=true
|
|
shift
|
|
;;
|
|
--build-only)
|
|
BUILD_ONLY=true
|
|
shift
|
|
;;
|
|
*)
|
|
echo "Error: Unknown option: $1"
|
|
echo "Run with --help for usage information"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
echo "║ Deploy to FTC Control Hub ║"
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
|
|
if [ "$SKIP_BUILD" = false ]; then
|
|
# Step 1: Deploy code to SDK
|
|
echo ">>> Step 1: Deploying code to FTC SDK..."
|
|
./gradlew deployToSDK || {
|
|
echo "Error: Failed to deploy code"
|
|
exit 1
|
|
}
|
|
echo ""
|
|
|
|
# Step 2: Build APK
|
|
echo ">>> Step 2: Building APK..."
|
|
cd "$FTC_SDK_DIR"
|
|
./gradlew assembleDebug || {
|
|
echo "Error: Failed to build APK"
|
|
exit 1
|
|
}
|
|
echo ""
|
|
else
|
|
echo ">>> Skipping build (--skip-build)"
|
|
cd "$FTC_SDK_DIR"
|
|
echo ""
|
|
fi
|
|
|
|
APK_PATH="$FTC_SDK_DIR/FtcRobotController/build/outputs/apk/debug/FtcRobotController-debug.apk"
|
|
|
|
if [ ! -f "$APK_PATH" ]; then
|
|
echo "Error: APK not found at $APK_PATH"
|
|
echo "Try building first: ./deploy-to-robot.sh (without --skip-build)"
|
|
exit 1
|
|
fi
|
|
|
|
echo "✓ APK ready: $(basename $APK_PATH)"
|
|
echo ""
|
|
|
|
if [ "$BUILD_ONLY" = true ]; then
|
|
echo ">>> Build complete (--build-only)"
|
|
echo ""
|
|
echo "APK location: $APK_PATH"
|
|
echo ""
|
|
echo "To install manually:"
|
|
echo " adb install -r $APK_PATH"
|
|
exit 0
|
|
fi
|
|
|
|
# Step 3: Install to robot
|
|
echo ">>> Step 3: Installing to Control Hub..."
|
|
echo ""
|
|
|
|
# Check if adb is available
|
|
if ! command -v adb &> /dev/null; then
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
echo "║ ERROR: adb not found ║"
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
echo "Android Debug Bridge (adb) is required for deployment."
|
|
echo ""
|
|
echo "Install it:"
|
|
echo " Ubuntu/Debian: sudo apt install android-tools-adb"
|
|
echo " Arch Linux: sudo pacman -S android-tools"
|
|
echo " macOS: brew install android-platform-tools"
|
|
echo ""
|
|
echo "Or download Android Platform Tools:"
|
|
echo " https://developer.android.com/studio/releases/platform-tools"
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
|
|
INSTALLED=false
|
|
|
|
# Try USB first (unless forcing WiFi)
|
|
if [ "$FORCE_WIFI" = false ]; then
|
|
echo "Checking for USB connection..."
|
|
USB_DEVICES=$(adb devices | grep -v "List" | grep "device$" | wc -l)
|
|
|
|
if [ "$USB_DEVICES" -gt 0 ]; then
|
|
echo "✓ Control Hub connected via USB"
|
|
echo ""
|
|
adb install -r "$APK_PATH" && INSTALLED=true
|
|
else
|
|
if [ "$FORCE_USB" = true ]; then
|
|
echo "✗ No USB device found (--usb specified)"
|
|
echo ""
|
|
echo "Make sure:"
|
|
echo " • Control Hub is powered on"
|
|
echo " • USB cable is connected"
|
|
echo " • USB debugging is enabled on Control Hub"
|
|
echo ""
|
|
echo "Check with: adb devices"
|
|
exit 1
|
|
else
|
|
echo " No USB device detected"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Try WiFi if USB didn't work (unless forcing USB)
|
|
if [ "$INSTALLED" = false ] && [ "$FORCE_USB" = false ]; then
|
|
echo "Trying WiFi connection to $CONTROL_HUB_IP:$CONTROL_HUB_PORT..."
|
|
echo ""
|
|
|
|
# Try to connect
|
|
adb connect "$CONTROL_HUB_IP:$CONTROL_HUB_PORT" 2>&1 | grep -v "cannot connect" || true
|
|
|
|
# Wait for connection
|
|
sleep 2
|
|
|
|
# Check if connected
|
|
if adb devices | grep -q "$CONTROL_HUB_IP"; then
|
|
echo "✓ Connected via WiFi"
|
|
echo ""
|
|
adb install -r "$APK_PATH" && INSTALLED=true
|
|
fi
|
|
fi
|
|
|
|
if [ "$INSTALLED" = false ]; then
|
|
echo ""
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
echo "║ Could not connect to Control Hub ║"
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
echo "Connection options:"
|
|
echo ""
|
|
echo "1. USB Connection (Recommended)"
|
|
echo " ────────────────────────────────"
|
|
echo " • Plug Control Hub into computer with USB cable"
|
|
echo " • Run: ./deploy-to-robot.sh --usb"
|
|
echo ""
|
|
echo "2. WiFi Direct Connection"
|
|
echo " ────────────────────────────────"
|
|
echo " • Connect to 'FIRST-xxxx-RC' network"
|
|
echo " • Run: ./deploy-to-robot.sh --wifi"
|
|
echo " • Default IP: 192.168.43.1:5555"
|
|
echo ""
|
|
echo "3. Custom Network"
|
|
echo " ────────────────────────────────"
|
|
echo " • Find Control Hub IP on your network"
|
|
echo " • Run: ./deploy-to-robot.sh -i YOUR_IP"
|
|
echo ""
|
|
echo "4. Manual Install"
|
|
echo " ────────────────────────────────"
|
|
echo " • APK built at: $APK_PATH"
|
|
echo " • Use Android Studio to deploy"
|
|
echo " • Or: adb connect <ip>:5555 && adb install -r <apk>"
|
|
echo ""
|
|
echo "Debug with:"
|
|
echo " adb devices # See connected devices"
|
|
echo " adb connect 192.168.43.1 # Connect manually"
|
|
echo ""
|
|
echo "Run with --help for more options"
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
echo "║ ✓ Deployment Complete! ║"
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
echo "On Driver Station:"
|
|
echo " 1. Go to: OpModes menu"
|
|
echo " 2. Select: TeleOp → 'Main TeleOp'"
|
|
echo " 3. Press: INIT, then START"
|
|
echo ""
|
|
echo "Your code is now running on the robot! 🤖"
|
|
echo ""
|
|
DEPLOYSCRIPT
|
|
chmod +x deploy-to-robot.sh
|
|
|
|
echo ""
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
echo "║ ✓ Project Created! ║"
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
echo "Project: $PROJECT_NAME"
|
|
echo "Location: $PROJECT_DIR"
|
|
echo "SDK: $FTC_SDK_DIR ($FTC_VERSION)"
|
|
echo "Git: Initialized with initial commit"
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo " QUICK START"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo "1. Enter project and run tests:"
|
|
echo " cd $PROJECT_NAME"
|
|
echo " ./gradlew test"
|
|
echo ""
|
|
echo "2. Watch tests (auto-rerun on save):"
|
|
echo " ./gradlew test --continuous"
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo " PROJECT STRUCTURE"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo "$PROJECT_NAME/"
|
|
echo "├── src/main/java/robot/"
|
|
echo "│ ├── subsystems/"
|
|
echo "│ │ └── Drive.java ← Your robot logic (EDIT THIS)"
|
|
echo "│ ├── hardware/"
|
|
echo "│ │ └── MecanumDrive.java ← Hardware impl (EDIT THIS)"
|
|
echo "│ └── opmodes/"
|
|
echo "│ └── TeleOp.java ← FTC OpMode (EDIT THIS)"
|
|
echo "│"
|
|
echo "└── src/test/java/robot/"
|
|
echo " └── subsystems/"
|
|
echo " └── DriveTest.java ← Unit tests (ADD MORE)"
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo " DEVELOPMENT WORKFLOW"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo "Day-to-day development (on your PC):"
|
|
echo ""
|
|
echo " 1. Edit code in src/main/java/robot/"
|
|
echo " 2. Edit tests in src/test/java/robot/"
|
|
echo " 3. Run: ./gradlew test --continuous"
|
|
echo " 4. Tests pass → you're good!"
|
|
echo ""
|
|
echo "Example: Add a new subsystem:"
|
|
echo ""
|
|
echo " # Create subsystem with inner Hardware interface"
|
|
echo " src/main/java/robot/subsystems/Intake.java"
|
|
echo ""
|
|
echo " # Create test with inline mock"
|
|
echo " src/test/java/robot/subsystems/IntakeTest.java"
|
|
echo ""
|
|
echo " # Run tests"
|
|
echo " ./gradlew test"
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo " DEPLOYMENT TO ROBOT"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo "When ready to test on actual robot:"
|
|
echo ""
|
|
echo " 1. Uncomment FTC imports in:"
|
|
echo " - src/main/java/robot/hardware/MecanumDrive.java"
|
|
echo " - src/main/java/robot/opmodes/TeleOp.java"
|
|
echo ""
|
|
echo " 2. Run deployment script:"
|
|
echo " ./deploy-to-robot.sh"
|
|
echo ""
|
|
echo " The script will:"
|
|
echo " ✓ Deploy your code to SDK"
|
|
echo " ✓ Build APK"
|
|
echo " ✓ Install to Control Hub (via USB or WiFi)"
|
|
echo ""
|
|
echo " Connection methods:"
|
|
echo " • USB: Just plug in and run"
|
|
echo " • WiFi: Connect to 'FIRST-xxxx-RC' network (IP: 192.168.43.1)"
|
|
echo " • Custom: CONTROL_HUB_IP=192.168.1.x ./deploy-to-robot.sh"
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo " KEY FILES TO EDIT"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo "Start here:"
|
|
echo ""
|
|
echo " src/main/java/robot/subsystems/Drive.java"
|
|
echo " → Your drive logic, tested on PC"
|
|
echo ""
|
|
echo " src/main/java/robot/hardware/MecanumDrive.java"
|
|
echo " → Real motor control (uncomment FTC code when deploying)"
|
|
echo ""
|
|
echo " src/main/java/robot/opmodes/TeleOp.java"
|
|
echo " → Your main OpMode (uncomment FTC code when deploying)"
|
|
echo ""
|
|
echo " src/test/java/robot/subsystems/DriveTest.java"
|
|
echo " → Unit tests - add more as you build!"
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo " USEFUL COMMANDS"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo " Development (on PC):"
|
|
echo " ./gradlew test Run all tests"
|
|
echo " ./gradlew test --continuous Watch mode (auto-rerun)"
|
|
echo ""
|
|
echo " Before deployment:"
|
|
echo " ./build.sh Check for compile errors"
|
|
echo " ./build.sh --clean Clean build"
|
|
echo ""
|
|
echo " Deploy to robot:"
|
|
echo " ./deploy-to-robot.sh Full deployment (build + install)"
|
|
echo " ./deploy-to-robot.sh --help Show all deployment options"
|
|
echo ""
|
|
echo " Other:"
|
|
echo " ./gradlew clean Clean build artifacts"
|
|
echo " ./gradlew tasks List all available tasks"
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo " TIPS"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo " • Keep FTC imports commented during development"
|
|
echo " • Write tests for everything - they run instantly on PC"
|
|
echo " • Use './gradlew test --continuous' for fast iteration"
|
|
echo " • See README.md for detailed documentation"
|
|
echo " • Multiple projects can share the same FTC SDK!"
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo "Ready to start coding? Run:"
|
|
echo ""
|
|
echo " cd $PROJECT_NAME && ./gradlew test --continuous"
|
|
echo ""
|
|
echo "Happy coding! 🤖"
|
|
echo ""
|