commit 81452a86708e76a445427c025a5b46cf94f577a0 Author: Eric Ratliff Date: Tue Jan 13 23:58:43 2026 -0600 Initial release: FTC Project Generator 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 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3971a91 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,34 @@ +# Git +.git +.gitignore +.gitattributes + +# CI/CD +.github + +# Documentation +README.md +LICENSE +*.md + +# Build artifacts +build/ +*/build/ +.gradle/ +*.apk +*.aab + +# IDE +.idea/ +*.iml +.vscode/ + +# OS files +.DS_Store +Thumbs.db + +# Logs +*.log + +# Local config +local.properties \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0fe24dc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,58 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true + +# Java files +[*.java] +indent_style = space +indent_size = 4 +continuation_indent_size = 8 + +# Gradle files +[*.gradle] +indent_style = space +indent_size = 4 + +[*.kts] +indent_style = space +indent_size = 4 + +# XML files +[*.xml] +indent_style = space +indent_size = 4 + +# Properties files +[*.properties] +indent_style = space +indent_size = 4 + +# Shell scripts +[*.sh] +indent_style = space +indent_size = 4 +end_of_line = lf + +# Markdown +[*.md] +trim_trailing_whitespace = false +indent_style = space +indent_size = 2 + +# YAML +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +# JSON +[*.json] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..91de56c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,76 @@ +# 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 + +# These files are text and should be normalized (Convert crlf => lf) +*.css text +*.df text +*.htm text +*.html text +*.js text +*.json text +*.jsp text +*.jspf text +*.jspx text +*.properties text +*.sh text +*.svg text +*.tld text +*.txt text +*.tag text +*.tagx text +*.xml text +*.yml text +*.yaml text + +# These files are binary and should be left untouched +# (binary is a macro for -text -diff) +*.class binary +*.dll binary +*.ear binary +*.jar binary +*.so binary +*.war binary +*.jks binary +*.keystore binary +*.aar binary +*.apk binary +*.ap_ binary +*.aab binary +*.dex binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.mov binary +*.mp4 binary +*.mp3 binary +*.flv binary +*.fla binary +*.swf binary +*.gz binary +*.zip binary +*.7z binary +*.ttf binary +*.eot binary +*.woff binary +*.woff2 binary +*.pyc binary +*.pdf binary +*.ez binary +*.bz2 binary +*.swp binary +*.bin binary + +# Gradle wrapper scripts should always use LF +gradlew text eol=lf +*.sh text eol=lf + +# Windows batch files should use CRLF +*.bat text eol=crlf +*.cmd text eol=crlf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2dd1a6b --- /dev/null +++ b/.gitignore @@ -0,0 +1,114 @@ +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +release/ + +# Gradle files +.gradle/ +build/ +**/build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ +.externalNativeBuild +.cxx + +# IntelliJ +*.iml +.idea/ +misc.xml +deploymentTargetDropDown.xml +render.experimental.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +*.jks +*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +*.lint + +# Android Profiling +*.hprof + +# OS-specific files +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini + +# Linux +*~ + +# IDE-specific +*.swp +*.swo +*~ + +# Node modules (if using any JS tooling) +node_modules/ + +# Backup files +*.bak +*.orig + +# FTC specific +libs/*.aar + +# TeamCode build outputs that shouldn't be committed +TeamCode/build/ +FtcRobotController/build/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..feb888b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Nexus Workshops LLC + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b841614 --- /dev/null +++ b/README.md @@ -0,0 +1,295 @@ +# FTC Project Generator + +Generate clean, testable FTC robot projects. Keep YOUR code separate from the FTC SDK bloat. + +## Philosophy + +**Normal FTC Way:** Clone their repo, modify their code +**This Way:** Your code is clean, FTC SDK is just a dependency + +One shared SDK, multiple clean projects. Test on PC, deploy when ready. + +## Quick Start + +```bash +# Create your first project +./ftc-new-project my-robot + +# Create another project (reuses same SDK!) +./ftc-new-project another-bot + +# Use specific FTC version +./ftc-new-project test-bot --version v10.0.0 +``` + +## What It Does + +1. **Checks/clones FTC SDK** (once, shared across projects) +2. **Creates YOUR clean project** with: + - Minimal structure + - Test scaffolding + - Example subsystem + - Composite build to SDK +3. **Ready to code** - tests run immediately + +## Generated Project Structure + +``` +my-robot/ +├── build.gradle.kts # Your build (references SDK) +├── settings.gradle.kts # Composite build setup +├── gradlew # Gradle wrapper +├── README.md # Project-specific docs +└── src/ + ├── main/java/robot/ + │ ├── Pose2d.java # Utility classes + │ ├── subsystems/ + │ │ └── Drive.java # Example subsystem + │ ├── hardware/ + │ │ └── MecanumDrive.java # Hardware impl stub + │ └── opmodes/ + │ └── TeleOp.java # FTC OpMode + └── test/java/robot/ + └── subsystems/ + └── DriveTest.java # Unit test + +Separate (shared): +~/ftc-sdk/ # FTC SDK (one copy for all projects) +``` + +## Usage + +### Development (Day-to-Day) + +```bash +cd my-robot + +# Run tests +./gradlew test + +# Watch mode (auto-rerun on save) +./gradlew test --continuous + +# Write code, tests pass → you're good! +``` + +### Deployment (When Ready for Robot) + +```bash +# 1. Deploy your code to SDK +./gradlew deployToSDK + +# 2. Build APK +cd ~/ftc-sdk +./gradlew build + +# 3. Install to robot +# Use Android Studio's deploy button, or: +adb install FtcRobotController/build/outputs/apk/debug/FtcRobotController-debug.apk +``` + +## Multiple Projects + +The beauty: **one SDK, many projects** + +```bash +# Create project 1 +./ftc-new-project swerve-bot + +# Create project 2 (reuses SDK!) +./ftc-new-project mecanum-bot + +# Create project 3 (still same SDK!) +./ftc-new-project test-chassis +``` + +Each project: +- Has its own git repo +- Tests independently +- Deploys to shared SDK +- Can have different license +- Is YOUR code, not FTC's + +## Commands Reference + +```bash +# Basic usage +./ftc-new-project + +# Specify FTC version +./ftc-new-project --version v10.1.1 + +# Custom SDK location +./ftc-new-project --sdk-dir ~/my-ftc + +# Get help +./ftc-new-project --help +``` + +## Environment Variables + +```bash +# Set default SDK location +export FTC_SDK_DIR=~/ftc-sdk + +# Now just: +./ftc-new-project my-robot +``` + +## Pattern: Subsystems + +Every generated project follows this pattern: + +```java +// Your subsystem +public class MySubsystem { + private final Hardware hardware; + + public MySubsystem(Hardware hardware) { + this.hardware = hardware; + } + + // Business logic here - tests on PC + public void doSomething() { + hardware.doThing(); + } + + // Inner interface - implement for robot + public interface Hardware { + void doThing(); + } +} + +// Test (inline mock) +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); + } +} + +// Hardware impl (for robot) +public class RealHardware implements MySubsystem.Hardware { + private DcMotor motor; + + public RealHardware(HardwareMap map) { + motor = map.get(DcMotor.class, "motor"); + } + + public void doThing() { + motor.setPower(1.0); + } +} +``` + +## Why This Is Better + +**Traditional FTC:** +- Clone 200MB repo +- Your code mixed with SDK +- Hard to test +- Hard to share +- Stuck with their structure + +**This Way:** +- Your project: ~50KB +- SDK: shared, separate +- Easy to test (runs on PC) +- Easy to share (just your code) +- Your structure, your license + +## FAQ + +**Q: Can I use multiple FTC versions?** +A: Yes! Different projects can reference different SDK dirs. + +**Q: What if I want to update the SDK?** +A: `cd ~/ftc-sdk && git pull && git checkout v10.2.0` + Or specify new version when creating project. + +**Q: Does this work with Android Studio?** +A: For deployment, yes. For development, use whatever you want (Sublime, vim, etc). + +**Q: Can I put my project on GitHub with a different license?** +A: Yes! Your code is separate. Just don't include SDK files. + +**Q: What about Control Hub having multiple programs?** +A: Each project creates OpModes. Deploy multiple projects to SDK, build once, all show up on Control Hub. + +## Installation + +```bash +# Extract the generator +tar xzf ftc-project-generator.tar.gz +cd ftc-project-gen + +# Option 1: Run install script (recommended) +./install.sh # Shows options +sudo ./install.sh # System-wide install + +# Option 2: Manual symlink +sudo ln -s $(pwd)/ftc-new-project /usr/local/bin/ + +# Option 3: Add to PATH +echo "export PATH=\$PATH:$(pwd)" >> ~/.bashrc +source ~/.bashrc + +# Verify installation +ftc-new-project --help +``` + +## The Magic + +When you run tests, your code uses **interfaces** and **mocks** - no FTC SDK needed. + +When you deploy, your code gets copied to SDK's TeamCode and built with the real FTC libraries. + +Your code stays clean. SDK is just a build tool. + +## Example Session + +```bash +# Create project +$ ./ftc-new-project my-bot +>>> Checking FTC SDK... +SDK directory exists, checking version... +✓ SDK already at version v10.1.1 +>>> Creating project: my-bot +✓ Project created: my-bot + +# Develop +$ cd my-bot +$ ./gradlew test +BUILD SUCCESSFUL +2 tests passed + +# Add code, tests pass... + +# Ready to deploy +$ ./gradlew deployToSDK +Code deployed to TeamCode - ready to build APK + +$ cd ~/ftc-sdk +$ ./gradlew build +BUILD SUCCESSFUL + +# Install to robot... +``` + +Clean, simple, modular. As it should be. + +## Credits + +Built by frustrated mentors who think the standard FTC setup is backwards. + +## License + +MIT - do whatever you want. Unlike FTC's forced BSD license nonsense. diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..37ce07f --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,108 @@ +# Troubleshooting + +## Error: "Project with path ':RobotCore' not found" + +**Symptom:** +``` +FAILURE: Build failed with an exception. +* What went wrong: +Project with path ':RobotCore' not found in build ':ftc-sdk'. +``` + +**Cause:** FTC SDK was not cloned or is at wrong location. + +**Solution:** + +1. Check if SDK exists: +```bash +ls ~/ftc-sdk +# Should show: FtcRobotController, RobotCore, Hardware, etc. +``` + +2. If SDK doesn't exist, clone it: +```bash +git clone --depth 1 --branch v10.1.1 \ + https://github.com/FIRST-Tech-Challenge/FtcRobotController.git \ + ~/ftc-sdk +``` + +3. Check your project's `gradle.properties`: +```bash +cat gradle.properties +# Should show: ftcSdkDir=/home/yourusername/ftc-sdk +``` + +4. Update if path is wrong: +```bash +echo "ftcSdkDir=$HOME/ftc-sdk" > gradle.properties +``` + +5. Try again: +```bash +./gradlew test +``` + +## Error: Can't find SDK at specified path + +**Solution:** Set environment variable: +```bash +export FTC_SDK_DIR=~/ftc-sdk +./gradlew test +``` + +Or edit `gradle.properties` in your project. + +## SDK exists but wrong version + +**Solution:** Update SDK to correct version: +```bash +cd ~/ftc-sdk +git fetch --tags +git checkout v10.1.1 # or whatever version you need +``` + +## Fresh Start + +If all else fails: + +```bash +# Remove old SDK +rm -rf ~/ftc-sdk + +# Remove old project +rm -rf my-project + +# Start fresh +ftc-new-project my-project + +# Verify SDK was cloned +ls ~/ftc-sdk + +# Test +cd my-project +./gradlew test +``` + +## Verify Setup + +Run this diagnostic: + +```bash +#!/bin/bash +echo "=== FTC Setup Diagnostic ===" +echo "" +echo "1. SDK Directory:" +ls -la ~/ftc-sdk 2>/dev/null || echo " ✗ SDK not found at ~/ftc-sdk" +echo "" +echo "2. SDK Modules:" +ls ~/ftc-sdk/RobotCore 2>/dev/null && echo " ✓ RobotCore found" || echo " ✗ RobotCore missing" +ls ~/ftc-sdk/Hardware 2>/dev/null && echo " ✓ Hardware found" || echo " ✗ Hardware missing" +echo "" +echo "3. Project gradle.properties:" +cat gradle.properties 2>/dev/null || echo " ✗ No gradle.properties in current dir" +echo "" +echo "4. Environment:" +echo " FTC_SDK_DIR=$FTC_SDK_DIR" +``` + +Save as `check-setup.sh` and run it in your project directory. diff --git a/build.gradle.kts.simple b/build.gradle.kts.simple new file mode 100644 index 0000000..07346b2 --- /dev/null +++ b/build.gradle.kts.simple @@ -0,0 +1,64 @@ +plugins { + java +} + +repositories { + mavenCentral() + google() +} + +dependencies { + // Testing (runs on PC without SDK) - THIS IS WHAT YOU USE FOR DEVELOPMENT + testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testImplementation("org.mockito:mockito-core:5.5.0") + + // FTC SDK - ONLY needed when you uncomment FTC code for deployment + // These will be compile errors until you uncomment, that's OK! + // compileOnly(files("${System.getenv("HOME")}/ftc-sdk/RobotCore/build/libs/RobotCore-release.aar")) + // compileOnly(files("${System.getenv("HOME")}/ftc-sdk/Hardware/build/libs/Hardware-release.aar")) +} + +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 sdkDir = System.getenv("HOME") + "/ftc-sdk" + + from("src/main/java") + into("$sdkDir/TeamCode/src/main/java") + + doLast { + println("✓ Code deployed to $sdkDir/TeamCode") + println("") + println("Next steps:") + println(" cd $sdkDir") + println(" ./gradlew build") + } +} + +tasks.register("ftcBuild") { + group = "ftc" + description = "Deploy code and build FTC APK" + + dependsOn("deployToSDK") + + val sdkDir = System.getenv("HOME") + "/ftc-sdk" + workingDir(file(sdkDir)) + commandLine("./gradlew", "build") +} diff --git a/ftc-new-project b/ftc-new-project new file mode 100755 index 0000000..a7bb150 --- /dev/null +++ b/ftc-new-project @@ -0,0 +1,1547 @@ +#!/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 "" diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..f555457 --- /dev/null +++ b/install.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Install FTC project generator + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +INSTALL_DIR="/usr/local/bin" + +echo "FTC Project Generator - Installation" +echo "" + +# Check if running as root for system install +if [ -w "$INSTALL_DIR" ]; then + echo "Installing to $INSTALL_DIR (system-wide)..." + ln -sf "$SCRIPT_DIR/ftc-new-project" "$INSTALL_DIR/ftc-new-project" + echo "✓ Installed! Use 'ftc-new-project' from anywhere." +else + echo "No write access to $INSTALL_DIR" + echo "" + echo "Choose installation method:" + echo "" + echo "1. System-wide (requires sudo):" + echo " sudo $0" + echo "" + echo "2. User-only (no sudo needed):" + echo " mkdir -p ~/bin" + echo " ln -sf $SCRIPT_DIR/ftc-new-project ~/bin/ftc-new-project" + echo " echo 'export PATH=\$PATH:~/bin' >> ~/.bashrc" + echo " source ~/.bashrc" + echo "" + echo "3. Add this directory to PATH:" + echo " echo 'export PATH=\$PATH:$SCRIPT_DIR' >> ~/.bashrc" + echo " source ~/.bashrc" + exit 1 +fi