#!/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) [options] OPTIONS: -v, --version FTC SDK git tag (default: $DEFAULT_FTC_VERSION) -d, --sdk-dir 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 [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("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\\\\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
Custom Control Hub IP address -p, --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 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 :5555 && adb install -r " 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 ""