2 Commits

Author SHA1 Message Date
Eric Ratliff
5f68bde615 Fixing so many tests 2026-02-02 23:08:22 -06:00
Eric Ratliff
38f1e0f3ed Fixed warnings and patched some files, working on testing 2026-02-02 22:45:53 -06:00
29 changed files with 1128 additions and 1817 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "weevil" name = "weevil"
version = "1.1.0-rc1" version = "1.1.0-beta.2"
edition = "2021" edition = "2021"
authors = ["Eric Ratliff <eric@nxlearn.net>"] authors = ["Eric Ratliff <eric@nxlearn.net>"]
description = "FTC robotics project generator - bores into complexity, emerges with clean code" description = "FTC robotics project generator - bores into complexity, emerges with clean code"

716
README.md
View File

@@ -28,100 +28,20 @@ This approach works against standard software engineering practices and creates
- ✅ Are actually testable and maintainable - ✅ Are actually testable and maintainable
- ✅ Work seamlessly with Android Studio - ✅ Work seamlessly with Android Studio
- ✅ Support proxy/air-gapped environments - ✅ Support proxy/air-gapped environments
-**Start from professional templates with working code** ⭐ NEW in v1.1.0!
Students focus on building robots, not navigating SDK internals. Students focus on building robots, not navigating SDK internals.
--- ---
## ⭐ What's New in v1.1.0
### Professional Templates - The Game Changer!
**Start with working, tested code instead of empty files!**
```bash
# Create with our professional testing showcase
weevil new my-robot --template testing
cd my-robot
./gradlew test # 45 tests pass in < 2 seconds ✓
```
**You get:**
- ✅ 3 complete, working subsystems
- ✅ Full hardware abstraction layer
- ✅ 45 passing tests demonstrating best practices
- ✅ Professional documentation (6 files)
- ✅ Real patterns used in competition
**Why this matters:** Most FTC teams start with empty projects and learn by trial-and-error on hardware. Now you can learn from professional code, run tests instantly on your PC, and modify working examples for your robot.
This is the kind of code students would write if they had years of experience. Now they can START with it.
---
## Features ## Features
### 🎯 Professional Templates (v1.1.0)
```bash
# List available templates
weevil new --list-templates
# Create with basic template (minimal)
weevil new my-robot
# Create with testing template (professional showcase)
weevil new my-robot --template testing
```
**Available Templates:**
| Template | Description | Files | Tests | Perfect For |
|----------|-------------|-------|-------|-------------|
| `basic` | Minimal starting point | ~10 | 1 | Starting from scratch |
| `testing` | Professional showcase | ~30 | 45 | Learning best practices |
**Testing Template Includes:**
**Subsystems** (3 complete implementations):
- `MotorCycler` - State machine for motor cycling with timing
- `WallApproach` - Sensor-based wall approach with deceleration
- `TurnController` - Gyro-based turning with angle wraparound
**Hardware Layer** (interfaces + implementations + mocks):
- Motor controllers with FTC wrappers
- Distance sensors with test mocks
- Gyro sensors with simulation
- Clean abstraction enabling unit testing
**Tests** (45 tests, < 2 second runtime):
- Unit tests for each subsystem
- Integration tests for system behaviors
- Mock-based testing (no hardware required!)
**Documentation** (professional quality):
- `DESIGN_AND_TEST_PLAN.md` - Complete architecture
- `TESTING_GUIDE.md` - How to write tests
- `TESTING_SHOWCASE.md` - What's included
- `SOLUTION.md` - Problem-solving patterns
- `ARCHITECTURE.md` - Design decisions
- `QUICKSTART.md` - Get started in 5 minutes
### 🎯 Clean Project Structure ### 🎯 Clean Project Structure
``` ```
my-robot/ my-robot/
├── src/ ├── src/
│ ├── main/java/robot/ # Your robot code lives here │ ├── main/java/robot/ # Your robot code lives here
│ │ ├── hardware/ # Hardware interfaces (in testing template)
│ │ ├── subsystems/ # Robot subsystems (in testing template)
│ │ └── opmodes/ # OpModes
│ └── test/java/robot/ # Unit tests (run on PC!) │ └── test/java/robot/ # Unit tests (run on PC!)
│ ├── hardware/ # Hardware mocks (in testing template) ├── .idea/ # Android Studio integration (auto-generated)
│ └── subsystems/ # Subsystem tests (in testing template)
├── docs/ # Documentation (in testing template)
├── .idea/ # Android Studio integration
├── build.sh / build.bat # One command to build ├── build.sh / build.bat # One command to build
├── deploy.sh / deploy.bat # One command to deploy ├── deploy.sh / deploy.bat # One command to deploy
└── .weevil.toml # Project configuration └── .weevil.toml # Project configuration
@@ -133,8 +53,7 @@ my-robot/
weevil setup weevil setup
# Create a new robot project # Create a new robot project
weevil new awesome-robot # Basic template weevil new awesome-robot
weevil new awesome-robot --template testing # Testing showcase
# Test your code (no robot required!) # Test your code (no robot required!)
cd awesome-robot cd awesome-robot
@@ -183,14 +102,13 @@ weevil setup # Uses proxy automatically
### 💻 Android Studio Integration (v1.1.0) ### 💻 Android Studio Integration (v1.1.0)
Projects work seamlessly with Android Studio: Projects work seamlessly with Android Studio:
- **One-click deployment** - Run configurations appear automatically - **One-click deployment** - Run configurations appear automatically in the Run dropdown
- **Clean file tree** - Internal directories hidden, only your code visible - **Clean file tree** - Internal directories hidden, only your code visible
- **No configuration needed** - Just open the project and hit Run - **No configuration needed** - Just open the project and hit Run
See [Android Studio Setup](#android-studio-setup) for details. See [Android Studio Setup](#android-studio-setup) for details.
### ✨ Smart Features ### ✨ Smart Features
- **Professional templates** - Start with tested, working code NEW!
- **Per-project SDK configuration** - Different projects can use different SDK versions - **Per-project SDK configuration** - Different projects can use different SDK versions
- **Automatic Gradle wrapper** - No manual setup required - **Automatic Gradle wrapper** - No manual setup required
- **Cross-platform** - Works on Linux, macOS, and Windows - **Cross-platform** - Works on Linux, macOS, and Windows
@@ -229,7 +147,14 @@ export PATH="$PATH:$(pwd)/target/release"
### 1. Set Up Your Environment ### 1. Set Up Your Environment
```bash ```bash
# Check what's installed
weevil doctor
# Install everything automatically
weevil setup weevil setup
# Or install to custom location
weevil setup --ftc-sdk ~/my-sdks/ftc --android-sdk ~/my-sdks/android
``` ```
Weevil will: Weevil will:
@@ -240,180 +165,115 @@ Weevil will:
### 2. Create Your First Project ### 2. Create Your First Project
**Recommended: Start with the testing template**
```bash
weevil new my-robot --template testing
cd my-robot
```
**Or start minimal:**
```bash ```bash
weevil new my-robot weevil new my-robot
cd my-robot cd my-robot
``` ```
### 3. Run Tests (Testing Template) Weevil generates:
- Clean project structure
- Android Studio run configurations
- Example test files
- Build and deploy scripts
- Git repository with `.gitignore`
### 3. Write Some Code
Create `src/main/java/robot/MyOpMode.java`:
```java
package robot;
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
@TeleOp(name="My OpMode")
public class MyOpMode extends LinearOpMode {
@Override
public void runOpMode() {
telemetry.addData("Status", "Initialized");
telemetry.update();
waitForStart();
while (opModeIsActive()) {
telemetry.addData("Status", "Running");
telemetry.update();
}
}
}
```
### 4. Test Locally (No Robot!)
```bash ```bash
./gradlew test ./gradlew test
``` ```
Output: Write unit tests in `src/test/java/robot/` that run on your PC. No need to deploy to a robot for every code change!
```
BasicTest > testBasic() PASSED
MotorCyclerTest > testInitialState() PASSED
MotorCyclerTest > testCycleFromOnToOff() PASSED
... 42 more tests ...
BUILD SUCCESSFUL in 1s ### 5. Deploy to Robot
45 tests passed
```
**All tests run on your PC - no robot required!**
### 4. Explore the Code (Testing Template)
```bash
# Read the overview
cat QUICKSTART.md
# Study a subsystem
cat src/main/java/robot/subsystems/WallApproach.java
# See how it's tested
cat src/test/java/robot/subsystems/WallApproachTest.java
# Check the architecture
cat DESIGN_AND_TEST_PLAN.md
```
### 5. Modify for Your Robot
```bash
# The testing template gives you working patterns to modify
# Option 1: Modify existing subsystems
vim src/main/java/robot/subsystems/WallApproach.java
# Option 2: Copy and adapt
cp src/main/java/robot/subsystems/WallApproach.java \
src/main/java/robot/subsystems/MyApproach.java
# Run tests to verify
./gradlew test
```
### 6. Deploy to Robot
```bash ```bash
# Build APK
./build.sh ./build.sh
./deploy.sh --wifi
# Deploy via USB
./deploy.sh --usb
# Deploy via WiFi
./deploy.sh --wifi -i 192.168.49.1
# Auto-detect (tries USB, falls back to WiFi)
./deploy.sh
``` ```
--- ---
## Template System
### Listing Templates
```bash
weevil new --list-templates
```
Output:
```
Available templates:
basic (default)
Minimal FTC project structure
Perfect for: Teams starting from scratch
Files: ~10 | Code: ~50 lines
testing
Professional testing showcase with examples
Perfect for: Learning best practices
Files: ~30 | Code: ~2,500 lines | Tests: 45
Includes:
• 3 complete subsystems (MotorCycler, WallApproach, TurnController)
• Hardware abstraction layer with mocks
• 45 passing tests (< 2 seconds)
• Comprehensive documentation
Usage:
weevil new <project-name> # Uses basic template
weevil new <project-name> --template testing # Uses testing template
```
### Basic Template
**Use when:** Starting from scratch, want minimal boilerplate
**What you get:**
- Clean directory structure
- Placeholder OpMode
- Basic test file
- Build/deploy scripts
- Documentation
**Files:** ~10
**Code:** ~50 lines
**Tests:** 1
### Testing Template
**Use when:** Want to learn professional patterns, need working examples
**What you get:**
| Category | What's Included |
|----------|-----------------|
| **Subsystems** | 3 complete implementations demonstrating real patterns |
| **Hardware** | 6 interfaces + FTC wrappers + test mocks |
| **Tests** | 45 comprehensive tests (unit + integration) |
| **Docs** | 6 professional documentation files |
| **Patterns** | State machines, hardware abstraction, testing strategies |
**Files:** ~30
**Code:** ~2,500 lines
**Tests:** 45 (< 2 second runtime)
**Perfect for:**
- Learning how professional FTC code is structured
- Understanding test-driven development
- Seeing working examples before writing your own
- Teaching your team best practices
- Workshops and training sessions
---
## Android Studio Setup ## Android Studio Setup
### Opening a Weevil Project ### Opening a Weevil Project
1. Launch Android Studio 1. Launch Android Studio
2. Choose **Open** (not "New Project") 2. Choose **Open** (not "New Project")
3. Navigate to your project directory 3. Navigate to your project directory (e.g., `my-robot`)
4. Click OK 4. Click OK
You'll see: Android Studio will index the project. After a few seconds, you'll see:
- Clean file tree (only your code visible) - **Clean file tree** - Only `src/`, scripts, and essential files visible
- Run configurations in dropdown - **Run configurations** - Dropdown next to the green play button shows:
- One-click deployment - **Build** - Builds APK without deploying
- **Deploy (auto)** - Auto-detects USB or WiFi
- **Deploy (USB)** - Forces USB connection
- **Deploy (WiFi)** - Forces WiFi connection
- **Test** - Runs unit tests
### First-Time: Install Shell Script Plugin ### First-Time Setup: Shell Script Plugin
1. **File → Settings** (Ctrl+Alt+S) **Important:** Android Studio requires the Shell Script plugin to run Weevil's deployment scripts.
2. **Plugins → Marketplace**
3. Search **"Shell Script"** 1. Go to **File → Settings** (or **Ctrl+Alt+S**)
4. Install plugin (by JetBrains) 2. Navigate to **Plugins**
5. Restart Android Studio 3. Click the **Marketplace** tab
4. Search for **"Shell Script"**
5. Install the plugin (by JetBrains)
6. Restart Android Studio
After restart, the run configurations will work.
### Running from Android Studio ### Running from Android Studio
1. Select configuration (Test, Build, Deploy) 1. Select a configuration from the dropdown (e.g., "Deploy (auto)")
2. Click green play button (▶) 2. Click the green play button (▶) or press **Shift+F10**
3. Watch output in Run panel 3. Watch the output in the Run panel at the bottom
**That's it!** Deploy to robot without leaving IDE. **That's it!** Students can now build and deploy without leaving the IDE.
### Platform Notes
- **Linux/macOS:** Uses the Unix run configurations (`.sh` scripts)
- **Windows:** Uses the Windows run configurations (`.bat` scripts)
- Android Studio automatically hides the configurations for the other platform
--- ---
@@ -421,68 +281,128 @@ You'll see:
### Proxy Configuration ### Proxy Configuration
#### Corporate Environments
```bash ```bash
# Corporate proxy # Set proxy for all Weevil operations
weevil --proxy http://proxy.company.com:8080 setup weevil --proxy http://proxy.company.com:8080 setup
weevil --proxy http://proxy.company.com:8080 new robot-project
# Environment variable (auto-detected) # Or use environment variables (auto-detected)
export HTTPS_PROXY=http://proxy:8080 export HTTPS_PROXY=http://proxy:8080
weevil setup export HTTP_PROXY=http://proxy:8080
weevil setup # Automatically uses proxy
```
# Bypass proxy #### Air-Gapped / Offline Installation
If you're on an isolated network without internet:
1. **Download SDKs manually on a connected machine:**
- FTC SDK: `git clone https://github.com/FIRST-Tech-Challenge/FtcRobotController.git`
- Android SDK: Download from https://developer.android.com/studio
- Gradle: Download distribution from https://gradle.org/releases/
2. **Transfer to isolated machine via USB drive**
3. **Install using local paths:**
```bash
weevil setup --ftc-sdk /path/to/FtcRobotController --android-sdk /path/to/android-sdk
```
#### Bypass Proxy
```bash
# Force direct connection (ignore proxy environment variables)
weevil --no-proxy setup weevil --no-proxy setup
``` ```
### Multiple SDK Versions ### Multiple SDK Versions
Working with multiple SDK versions? No problem:
```bash ```bash
# Create with specific SDK # Create project with specific SDK
weevil new experimental-bot --ftc-sdk /path/to/sdk-v11.0 weevil new experimental-bot --ftc-sdk /path/to/sdk-v11.0
# Switch SDKs later # Later, switch SDKs
weevil config experimental-bot --set-sdk /path/to/sdk-v11.1 weevil config experimental-bot --set-sdk /path/to/sdk-v11.1
# Rebuild with new SDK
weevil upgrade experimental-bot
cd experimental-bot
./build.sh
``` ```
### Upgrading Projects ### Upgrading Projects
When Weevil releases new features:
```bash ```bash
weevil upgrade my-robot weevil upgrade my-robot
``` ```
Updates build scripts, Gradle config, and IDE integration. This updates:
- Build scripts
- Deployment scripts
- Gradle configuration
- Android Studio run configurations
- Project templates
**Your code in `src/` is never touched.** **Your code in `src/` is never touched.**
### System Maintenance ### System Maintenance
```bash ```bash
weevil doctor # Check system health # Check what's installed
weevil uninstall --dry-run # Preview uninstall weevil doctor
weevil uninstall --only 1 # Remove specific component
# See what can be uninstalled
weevil uninstall --dry-run
# Remove specific components
weevil uninstall --only 1 # Removes FTC SDK only
# Full uninstall (removes everything Weevil installed)
weevil uninstall
``` ```
### Cross-Platform Development
All scripts work on Windows, Linux, and macOS:
**Linux/Mac:**
```bash
./build.sh
./deploy.sh --wifi
```
**Windows:**
```cmd
build.bat
deploy.bat
```
**Android Studio:** Works identically on all platforms
--- ---
## Project Configuration ## Project Configuration
`.weevil.toml`: Each project has a `.weevil.toml` file:
```toml ```toml
[project]
project_name = "my-robot" project_name = "my-robot"
created = "2026-02-02T10:30:00Z"
weevil_version = "1.1.0" weevil_version = "1.1.0"
template = "testing"
ftc_sdk_path = "/home/user/.weevil/ftc-sdk" ftc_sdk_path = "/home/user/.weevil/ftc-sdk"
ftc_sdk_version = "v10.1.1"
[ftc] android_sdk_path = "/home/user/.weevil/android-sdk"
sdk_version = "v10.1.1"
[build]
gradle_version = "8.5"
``` ```
Manage with: You can edit this manually or use:
```bash ```bash
weevil config my-robot # View weevil config my-robot # View config
weevil config my-robot --set-sdk /new/sdk # Change SDK weevil config my-robot --set-sdk /new/sdk # Change SDK
``` ```
@@ -493,44 +413,60 @@ weevil config my-robot --set-sdk /new/sdk # Change SDK
### Recommended Git Workflow ### Recommended Git Workflow
```bash ```bash
weevil new competition-bot --template testing # Create project
weevil new competition-bot
cd competition-bot cd competition-bot
# Already a git repo! # Project is already a git repo!
git remote add origin https://nxgit.dev/team/robot.git git remote add origin https://nxgit.dev/team/robot.git
git push -u origin main git push -u origin main
# Development cycle # Make changes
./gradlew test # Test locally # ... edit code ...
git commit -am "Add feature"
git push
./deploy.sh # Deploy to robot
```
### Learning from the Testing Template
```bash
# Create learning project
weevil new learning --template testing
cd learning
# Study the architecture
cat DESIGN_AND_TEST_PLAN.md
# Run tests and see patterns
./gradlew test ./gradlew test
git commit -am "Add autonomous mode"
git push
# Read a subsystem # Deploy to robot
cat src/main/java/robot/subsystems/MotorCycler.java ./deploy.sh
# Read its tests
cat src/test/java/robot/subsystems/MotorCyclerTest.java
# Copy patterns for your robot
cp src/main/java/robot/subsystems/MotorCycler.java \
src/main/java/robot/subsystems/MySystem.java
``` ```
### Testing Strategy
1. **Unit Tests** - Test business logic on your PC
```bash
./gradlew test
# Or from Android Studio: select "Test" and click Run
```
2. **Integration Tests** - Test on actual hardware
```bash
./build.sh
./deploy.sh --usb
# Run via Driver Station
```
### Team Collaboration
**Project Structure is Portable:**
```bash
# Team member clones repo
git clone https://nxgit.dev/team/robot.git
cd robot
# Check SDK location
weevil config .
# Set SDK to local path (if different from .weevil.toml)
weevil config . --set-sdk ~/ftc-sdk
# Build and deploy
./build.sh
./deploy.sh
```
**Android Studio users:** Just open the project. The `.idea/` folder contains all run configurations.
--- ---
## Command Reference ## Command Reference
@@ -539,47 +475,48 @@ cp src/main/java/robot/subsystems/MotorCycler.java \
| Command | Description | | Command | Description |
|---------|-------------| |---------|-------------|
| `weevil doctor` | Check system health | | `weevil doctor` | Check system health and dependencies |
| `weevil setup` | Install FTC SDK, Android SDK | | `weevil setup` | Install FTC SDK, Android SDK, and dependencies |
| `weevil setup --ftc-sdk <path>` | Install to custom location | | `weevil setup --ftc-sdk <path>` | Install to custom FTC SDK location |
| `weevil uninstall` | Remove all components | | `weevil uninstall` | Remove all Weevil-managed components |
| `weevil uninstall --dry-run` | Preview uninstall | | `weevil uninstall --dry-run` | Show what would be removed |
| `weevil uninstall --only <N>` | Remove specific component | | `weevil uninstall --only <N>` | Remove specific component by index |
### Project Commands ### Project Commands
| Command | Description | | Command | Description |
|---------|-------------| |---------|-------------|
| `weevil new <name>` | Create project (basic template) | | `weevil new <name>` | Create new FTC project |
| `weevil new <name> --template <t>` | Create with template | | `weevil new <name> --ftc-sdk <path>` | Create with specific SDK |
| `weevil new --list-templates` | Show available templates |
| `weevil upgrade <path>` | Update project infrastructure | | `weevil upgrade <path>` | Update project infrastructure |
| `weevil config <path>` | View configuration | | `weevil config <path>` | View project configuration |
| `weevil config <path> --set-sdk <sdk>` | Change FTC SDK | | `weevil config <path> --set-sdk <sdk>` | Change FTC SDK path |
### SDK Commands ### SDK Commands
| Command | Description | | Command | Description |
|---------|-------------| |---------|-------------|
| `weevil sdk status` | Show SDK status | | `weevil sdk status` | Show SDK locations and status |
| `weevil sdk install` | Download and install SDKs | | `weevil sdk install` | Download and install SDKs |
| `weevil sdk update` | Update to latest SDKs | | `weevil sdk update` | Update SDKs to latest versions |
### Global Flags ### Global Flags
| Flag | Description | | Flag | Description |
|------|-------------| |------|-------------|
| `--proxy <url>` | Use HTTP proxy | | `--proxy <url>` | Use HTTP proxy for all network operations |
| `--no-proxy` | Bypass proxy | | `--no-proxy` | Bypass proxy (ignore HTTPS_PROXY env vars) |
### Deployment Options ### Deployment Options
**`deploy.sh` / `deploy.bat` flags:**
| Flag | Description | | Flag | Description |
|------|-------------| |------|-------------|
| `--usb` | Force USB | | `--usb` | Force USB deployment |
| `--wifi` | Force WiFi | | `--wifi` | Force WiFi deployment |
| `-i <ip>` | Custom IP | | `-i <ip>` | Custom Control Hub IP |
| `--timeout <sec>` | WiFi timeout | | `--timeout <sec>` | WiFi connection timeout |
--- ---
@@ -588,91 +525,166 @@ cp src/main/java/robot/subsystems/MotorCycler.java \
### How It Works ### How It Works
1. **Project Generation** 1. **Project Generation**
- Creates standalone Java project - Creates standalone Java project structure
- Optionally overlays template (basic/testing) - Generates Gradle build files that reference FTC SDK
- Generates build files referencing FTC SDK
- Sets up deployment scripts - Sets up deployment scripts
- Creates Android Studio integration - Creates Android Studio run configurations
2. **Build Process** 2. **Build Process**
- Copies code to FTC SDK's TeamCode - Runs `deployToSDK` Gradle task
- Builds APK using SDK - Copies your code to FTC SDK's `TeamCode` directory
- Leaves project directory clean - Builds APK using SDK's Android configuration
- Leaves your project directory clean
3. **Deployment** 3. **Deployment**
- Finds APK in SDK - Finds built APK in SDK
- Connects to Control Hub (USB/WiFi) - Connects to Control Hub (USB or WiFi)
- Installs using `adb` - Installs APK using `adb`
4. **Proxy Support**
- reqwest HTTP client respects `--proxy` flag and HTTPS_PROXY env vars
- git2/libgit2 gets temporary proxy env vars during clone/fetch
- Gradle wrapper reads HTTPS_PROXY natively
### Why This Approach? ### Why This Approach?
**Separation of Concerns:** **Separation of Concerns:**
- Your code: `my-robot/src/` - Your code: `my-robot/src/`
- Build infrastructure: `*.gradle.kts` - Build infrastructure: `my-robot/*.gradle.kts`
- FTC SDK: System installation - FTC SDK: System-level installation
- Templates: Starting points - IDE integration: Auto-generated, auto-upgraded
**Benefits:** **Benefits:**
- Test without SDK complications - Test code without SDK complications
- Multiple projects per SDK - Multiple projects per SDK installation
- SDK updates don't break projects - SDK updates don't break your projects
- Proper version control - Proper version control (no massive SDK in repo)
- Industry-standard structure - Industry-standard project structure
- Learn from professional examples - Students use familiar tools (Android Studio)
--- ---
## Testing ## Testing
Weevil includes comprehensive tests:
```bash ```bash
cargo test # All tests # Run all tests
cargo test --test integration # Integration tests cargo test
cargo test --test template_tests # Template tests
# Run specific test suites
cargo test --test integration
cargo test --test project_lifecycle
cargo test --test proxy_integration
cargo test config_tests
``` ```
**Coverage:** **Test Coverage:**
- Project creation - ✅ Project creation and structure
- Template extraction - ✅ Configuration persistence
- Configuration - ✅ SDK detection and validation
- SDK detection - ✅ Build script generation
- Build scripts - ✅ Upgrade workflow
- Proxy support - ✅ CLI commands
- 62 tests passing - ✅ Proxy configuration and network operations
- ✅ Environment setup and health checks
--- ---
## Troubleshooting ## Troubleshooting
### "FTC SDK not found" ### "FTC SDK not found"
```bash ```bash
# Check system health
weevil doctor weevil doctor
# Install SDK
weevil setup weevil setup
# Or specify custom location
weevil new my-robot --ftc-sdk /custom/path/to/sdk
``` ```
### "adb: command not found" ### "adb: command not found"
Install Android platform-tools:
**Linux:**
```bash ```bash
weevil setup # Installs Android SDK with adb # Weevil can install it for you
weevil setup
# Or install manually
sudo apt install android-tools-adb
``` ```
### "Build failed" **macOS:**
```bash ```bash
brew install android-platform-tools
```
**Windows:**
Download Android SDK Platform Tools from Google or run `weevil setup`.
### "Build failed"
```bash
# Clean and rebuild
cd my-robot
./gradlew clean ./gradlew clean
./build.sh ./build.sh
# Check SDK path
weevil config .
# Verify system health
weevil doctor weevil doctor
``` ```
### "Deploy failed - No devices" ### "Deploy failed - No devices"
**USB:** `./deploy.sh --usb`
**WiFi:** `./deploy.sh -i 192.168.43.1`
### "Unknown run configuration type ShellScript" **USB:**
Install Shell Script plugin in Android Studio (see [Android Studio Setup](#android-studio-setup)) 1. Connect robot via USB
2. Run `adb devices` to verify connection
3. Try `./deploy.sh --usb`
**WiFi:**
1. Connect to robot's WiFi network
2. Find Control Hub IP (usually 192.168.43.1 or 192.168.49.1)
3. Try `./deploy.sh -i <ip>`
### Android Studio: "Unknown run configuration type ShellScript"
The Shell Script plugin is not installed. See [Android Studio Setup](#android-studio-setup) for installation instructions.
### Proxy Issues
```bash
# Test proxy connectivity
weevil --proxy http://proxy:8080 sdk status
# Bypass proxy if it's causing issues
weevil --no-proxy setup
# Check environment variables
echo $HTTPS_PROXY
echo $HTTP_PROXY
```
--- ---
## Contributing ## Contributing
Contributions welcome! Contributions welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Write tests for new features
4. Ensure `cargo test` passes with zero warnings
5. Submit a pull request
### Development Setup
```bash ```bash
git clone https://www.nxgit.dev/nexus-workshops/weevil.git git clone https://www.nxgit.dev/nexus-workshops/weevil.git
@@ -681,7 +693,7 @@ cargo build
cargo test cargo test
# Run locally # Run locally
cargo run -- new test-project --template testing cargo run -- new test-project
``` ```
--- ---
@@ -690,23 +702,22 @@ cargo run -- new test-project --template testing
**Why "Weevil"?** **Why "Weevil"?**
Like the boll weevil boring through cotton bolls to reach valuable fibers, this tool bores through SDK complexity to help students reach what matters: building robots and learning to code. Like the boll weevil that bores through complex cotton bolls to reach the valuable fibers inside, this tool bores through the complexity of the FTC SDK structure to help students reach what matters: building robots and learning to code.
**Design Principles:** **Design Principles:**
1. **Students first** - Minimize cognitive load 1. **Students first** - Minimize cognitive load for learners
2. **Industry practices** - Teach real software engineering 2. **Industry practices** - Teach real software engineering
3. **Testability** - Enable TDD workflows 3. **Testability** - Enable TDD and proper testing workflows
4. **Simplicity** - One command, one purpose 4. **Simplicity** - One command should do one obvious thing
5. **Transparency** - Students understand what's happening 5. **Transparency** - Students should understand what's happening
6. **Tool compatibility** - Work with familiar tools 6. **Tool compatibility** - Work with tools students already know
7. **Learn from examples** - Provide professional code to study
--- ---
## License ## License
MIT License - See [LICENSE](LICENSE) MIT License - See [LICENSE](LICENSE) file for details.
--- ---
@@ -714,7 +725,7 @@ MIT License - See [LICENSE](LICENSE)
Created by Eric Ratliff for [Nexus Workshops LLC](https://nexusworkshops.com) Created by Eric Ratliff for [Nexus Workshops LLC](https://nexusworkshops.com)
Built with frustration at unnecessarily complex frameworks, and hope that students can focus on robotics instead of build systems. Built with frustration at unnecessarily complex robotics frameworks, and hope that students can focus on robotics instead of build systems.
**For FIRST Tech Challenge teams everywhere** - may your builds be fast and your deployments successful. 🤖 **For FIRST Tech Challenge teams everywhere** - may your builds be fast and your deployments successful. 🤖
@@ -725,25 +736,24 @@ Built with frustration at unnecessarily complex frameworks, and hope that studen
**Current Version:** 1.1.0 **Current Version:** 1.1.0
**What Works:** **What Works:**
- Project generation with templates - ✅ Project generation
- Professional testing showcase template
- ✅ Cross-platform build/deploy - ✅ Cross-platform build/deploy
- ✅ SDK management and auto-install - ✅ SDK management and auto-install
- ✅ Configuration management - ✅ Configuration management
- ✅ Project upgrades - ✅ Project upgrades
- ✅ Local unit testing - ✅ Local unit testing
- System diagnostics - ✅ System diagnostics (`weevil doctor`)
- ✅ Selective uninstall - ✅ Selective uninstall
- Proxy support - Proxy support for corporate/air-gapped environments
- Android Studio integration - Android Studio integration with one-click deployment
**Roadmap:** **Roadmap:**
- 📋 `weevil add` - Package management system (v1.2.0) - 📋 Package management for FTC libraries
- 📋 Community package repository - 📋 Template system for common robot configurations
- 📋 Additional templates (mecanum, vision)
- 📋 VS Code integration - 📋 VS Code integration
- 📋 Team collaboration features - 📋 Team collaboration features
- 📋 Multi-robot support - 📋 Automated testing on robot hardware
- 📋 Multi-robot support (manage multiple Control Hubs)
--- ---

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
# Weevil Template & Package System - Specification # Weevil Template & Package System - Complete Specification
**Version:** 1.1 **Version:** 1.0
**Date:** February 2, 2026 **Date:** February 2, 2026
**Status:** Template system ✅ COMPLETE | Package system 📋 Planned for v1.2.0 **Status:** Ready for implementation
--- ---
@@ -10,185 +10,168 @@
This document specifies two complementary features for Weevil: This document specifies two complementary features for Weevil:
1. **Template System (v1.1.0)** - ✅ **IMPLEMENTED** - Project scaffolding with professional code templates 1. **`weevil create` (v1.1.0)** - Project scaffolding with templates
2. **`weevil add` Package System (v1.2.0)** - 📋 **PLANNED** - Component package management 2. **`weevil add` (v1.2.0)** - Package management system
Together, these enable teams to start with professional code and extend projects with community-shared components. Together, these enable teams to start with professional code and extend projects with community-shared components.
--- ---
## Part 1: Template System ✅ IMPLEMENTED in v1.1.0 ## Part 1: `weevil create` Command
### Status: COMPLETE ### Overview
The template system has been fully implemented and shipped in v1.1.0. **Purpose:** Generate complete FTC robot projects from templates
### Implementation Summary **Version:** v1.1.0-beta.3
**Priority:** HIGH
### Command Syntax
**Command Syntax:**
```bash ```bash
weevil new <project-name> [--template <name>] [--list-templates] weevil create <project-name> [OPTIONS]
OPTIONS:
--template <name> Template to use (default: "basic")
--path <directory> Where to create project (default: current dir)
--force Overwrite existing directory
--no-init Don't initialize git repository
--dry-run Show what would be created
``` ```
**Delivered Templates:** ### Templates (v1.1.0)
1. **`basic`** (default) - Minimal FTC project #### Template 1: `basic` (Default)
- 8 files, ~50 lines of code
- Clean starting point
- Example OpMode placeholder
2. **`testing`** - Professional showcase **Purpose:** Minimal starting point
- 28 files, ~2,500 lines of code
- 45 comprehensive tests (< 2 sec runtime)
- 3 complete subsystems
- Hardware abstraction layer
- Full documentation
**Key Features Delivered:** **Structure:**
- Template extraction and overlay system ```
- Variable substitution (`{{PROJECT_NAME}}`, etc.) my-robot/
- Template validation with helpful errors ├── src/
- `--list-templates` command │ ├── main/java/robot/
- Templates embedded in binary (no external files) │ │ └── opmodes/
- Complete test coverage (62 tests passing) │ │ └── BasicOpMode.java
│ └── test/java/robot/
│ └── .gitkeep
├── build.gradle
├── settings.gradle
├── .weevil.toml
├── .gitignore
├── README.md
└── build.bat / build.sh
```
### Template Variable Substitution **Files:** ~10
**Code:** ~50 lines
Implemented variables: #### Template 2: `testing` (Showcase)
| Variable | Example Value | **Purpose:** Professional testing demonstration
|----------|---------------|
| `{{PROJECT_NAME}}` | `my-robot` |
| `{{PACKAGE_NAME}}` | `myrobot` |
| `{{CREATION_DATE}}` | `2026-02-02T10:30:00Z` |
| `{{WEEVIL_VERSION}}` | `1.1.0` |
| `{{TEMPLATE_NAME}}` | `testing` |
### Testing Template Contents **Includes:**
- 3 subsystems (MotorCycler, WallApproach, TurnController)
- 6 hardware interfaces + FTC implementations
- 6 test mocks
- 45 passing tests
- Complete documentation (6 files)
**Subsystems** (3): **Files:** ~30
- `MotorCycler.java` - State machine for motor cycling **Code:** ~2,500 lines
- `WallApproach.java` - Sensor-based navigation **Tests:** 45 (< 2 sec runtime)
- `TurnController.java` - Gyro-based turning
**Hardware Layer** (12 files): **Documentation:**
- 3 interfaces (MotorController, DistanceSensor, GyroSensor) - README.md
- 3 FTC implementations - DESIGN_AND_TEST_PLAN.md
- 3 mock implementations - TESTING_GUIDE.md
- 3 additional interfaces - TESTING_SHOWCASE.md
- SOLUTION.md
**Tests** (45 tests): - ARCHITECTURE.md
- Unit tests for each subsystem
- Integration tests
- All passing in < 2 seconds
**Documentation** (6 files):
- DESIGN_AND_TEST_PLAN.md (29 KB)
- TESTING_GUIDE.md (13 KB)
- TESTING_SHOWCASE.md (9 KB)
- ARCHITECTURE.md (6 KB)
- SOLUTION.md (3 KB)
- QUICKSTART.md (5 KB)
### Usage Examples ### Usage Examples
```bash ```bash
# Create with default template # Create minimal project
weevil new my-robot weevil create my-robot
# Create with testing template # Create with testing template
weevil new my-robot --template testing weevil create my-robot --template testing
# List available templates # Create in specific location
weevil new --list-templates weevil create my-robot --template testing --path ~/ftc-projects
# Output from list: # Preview without creating
# Available templates: weevil create my-robot --template testing --dry-run
#
# basic (default)
# Minimal FTC project structure
# Perfect for: Teams starting from scratch
# Files: ~10 | Code: ~50 lines
#
# testing
# Professional testing showcase with examples
# Perfect for: Learning best practices
# Files: ~30 | Code: ~2,500 lines | Tests: 45
# Includes:
# • 3 complete subsystems
# • Hardware abstraction layer with mocks
# • 45 passing tests (< 2 seconds)
# • Comprehensive documentation
``` ```
### Implementation Architecture ### Variable Substitution
**Storage:** Templates embedded in binary using `include_dir!` macro Templates support variables:
**Directory Structure:** | Variable | Description | Example |
``` |----------|-------------|---------|
weevil/ | `{{PROJECT_NAME}}` | Project directory name | `my-robot` |
├── templates/ | `{{PACKAGE_NAME}}` | Java package name | `myrobot` |
│ ├── basic/ | `{{CREATION_DATE}}` | ISO 8601 timestamp | `2026-02-02T10:30:00Z` |
│ │ ├── .gitignore | `{{WEEVIL_VERSION}}` | Weevil version | `1.1.0` |
│ │ ├── README.md.template | `{{TEMPLATE_NAME}}` | Template used | `testing` |
│ │ ├── settings.gradle
│ │ └── src/... (.gitkeep files) **Example:**
│ └── testing/ ```java
│ ├── .gitignore // File: src/main/java/robot/subsystems/Example.java
│ ├── README.md.template // Generated by Weevil {{WEEVIL_VERSION}} on {{CREATION_DATE}}
│ ├── DESIGN_AND_TEST_PLAN.md package robot.{{PACKAGE_NAME}};
│ ├── ... (6 doc files)
│ └── src/
│ ├── main/java/robot/
│ │ ├── hardware/... (6 files)
│ │ ├── subsystems/... (3 files)
│ │ └── opmodes/...
│ └── test/java/robot/
│ ├── hardware/... (3 files)
│ └── subsystems/... (4 files)
``` ```
**Key Implementation Details:** Becomes:
- Templates complement ProjectBuilder (don't replace it) ```java
- ProjectBuilder creates infrastructure (.weevil.toml, build.gradle.kts, etc.) // Generated by Weevil 1.1.0 on 2026-02-02T10:30:00Z
- Templates overlay content (source code, docs) package robot.myrobot;
- Files ending in `.template` get variable substitution ```
- Regular files copied as-is
### Success Metrics (Achieved) ### Success Output
- 62 tests passing (zero warnings) ```
- Testing template has 45 passing tests ✓ Created FTC project 'my-robot' using template 'testing'
- Clean separation: ProjectBuilder vs Templates
- Cross-platform compatibility (Windows, Linux, macOS)
- No template fragmentation (templates don't include build files)
- Professional code quality in testing template
- Comprehensive documentation
### Lessons Learned Next steps:
cd my-robot
weevil test # Run 45 tests (< 2 seconds)
weevil build # Build APK
weevil deploy # Deploy to robot
1. **Don't fight ProjectBuilder** - Templates should complement, not replace infrastructure Documentation:
2. **Embed in binary** - No external file dependencies README.md - Project overview
3. **Variable substitution** - Essential for project-specific values DESIGN_AND_TEST_PLAN.md - System architecture
4. **Test thoroughly** - Template extraction, variable substitution, file handling all need tests TESTING_GUIDE.md - Testing patterns
5. **Documentation matters** - The testing template's value is in its docs as much as code ```
### Implementation Notes
**Storage Location:**
```
~/.weevil/templates/
├── basic/
│ ├── template.toml
│ └── files/
└── testing/
├── template.toml
└── files/
```
**Effort Estimate:** 2-3 days
**Complexity:** MEDIUM
--- ---
## Part 2: `weevil add` Command - Package Management System ## Part 2: `weevil add` Command
### Status: PLANNED for v1.2.0
The package management system will allow teams to add pre-built components to existing projects.
### Overview ### Overview
**Purpose:** Add components to existing Weevil projects **Purpose:** Add components to existing projects
**Version:** v1.2.0 **Version:** v1.2.0
**Priority:** HIGH **Priority:** MEDIUM-HIGH
**Estimated Effort:** 2-3 weeks
### Command Syntax ### Command Syntax
@@ -240,11 +223,11 @@ team1234/sensors/custom-lidar/core
```bash ```bash
# Add complete package # Add complete package
weevil add nexus/hardware/dc-motor/complete weevil add nexus/hardware/dc-motor
# Add specific variant # Add specific variant
weevil add nexus/hardware/dc-motor/core weevil add nexus/hardware/dc-motor/core
weevil add nexus/hardware/dc-motor/mock --dev weevil add nexus/hardware/dc-motor/mock
# Add subsystem (auto-installs dependencies) # Add subsystem (auto-installs dependencies)
weevil add nexus/subsystems/wall-approach/complete weevil add nexus/subsystems/wall-approach/complete
@@ -264,12 +247,6 @@ weevil add nexus/subsystems/turn-controller/core --interactive
Packages declare dependencies in manifest: Packages declare dependencies in manifest:
```toml ```toml
[package]
name = "wall-approach"
scope = "nexus"
category = "subsystems"
version = "1.0.0"
[dependencies] [dependencies]
"nexus/hardware/distance/core" = "^2.0.0" "nexus/hardware/distance/core" = "^2.0.0"
"nexus/hardware/dc-motor/core" = "^1.0.0" "nexus/hardware/dc-motor/core" = "^1.0.0"
@@ -280,9 +257,7 @@ version = "1.0.0"
**Automatic Resolution:** **Automatic Resolution:**
```bash ```bash
$ weevil add nexus/subsystems/wall-approach/complete weevil add nexus/subsystems/wall-approach/complete
Resolving dependencies...
Installing: Installing:
1. nexus/hardware/distance/core (v2.1.0) - dependency 1. nexus/hardware/distance/core (v2.1.0) - dependency
@@ -312,7 +287,7 @@ Skipped:
#### Force Mode #### Force Mode
```bash ```bash
$ weevil add nexus/hardware/dc-motor/core --force weevil add nexus/hardware/dc-motor/core --force
⚠ Warning: --force will overwrite 2 files ⚠ Warning: --force will overwrite 2 files
Continue? [y/N]: y Continue? [y/N]: y
@@ -323,7 +298,7 @@ Continue? [y/N]: y
#### Interactive Mode #### Interactive Mode
```bash ```bash
$ weevil add nexus/hardware/dc-motor/core --interactive weevil add nexus/hardware/dc-motor/core --interactive
Conflict: MotorController.java Conflict: MotorController.java
@@ -440,7 +415,7 @@ dependencies = ["core", "mock", "example"]
### Package Repository ### Package Repository
**Location:** https://packages.nxgit.dev (to be implemented) **Location:** https://packages.nxgit.dev
**Structure:** **Structure:**
``` ```
@@ -458,9 +433,21 @@ packages.nxgit.dev/
└── team1234/... └── team1234/...
``` ```
### Implementation Notes
**Effort Estimate:** 2-3 weeks
**Complexity:** HIGH
**Key Components:**
1. Package registry (hosted)
2. Dependency resolver
3. Conflict detector
4. File installer
5. Supporting commands (remove, search, list)
--- ---
## Supporting Commands (v1.2.0) ## Supporting Commands
### `weevil remove` ### `weevil remove`
@@ -520,45 +507,72 @@ weevil update <package> # Update specific
--- ---
## Strategic Benefits
### For Teams
- **Faster start** - Working code immediately
- **Best practices** - Learn from examples
- **Code reuse** - Don't reinvent wheels
- **Community** - Share solutions
### For Nexus Workshops
- **Authority** - Set FTC code standards
- **Network effects** - More packages = more value
- **Revenue** - Workshops teach patterns
- **Differentiation** - Unique offering
### For FTC Community
- **Quality** - Raised bar across teams
- **Collaboration** - Build on each other
- **Education** - Professional patterns
- **Innovation** - Focus on unique solutions
---
## Success Metrics
### v1.1.0 (create)
- [ ] 50+ teams use testing template in first month
- [ ] Positive feedback on quality
- [ ] Used in Nexus Workshops curriculum
- [ ] Documentation complete
### v1.2.0 (add)
- [ ] 20+ quality packages at launch
- [ ] 100+ downloads first month
- [ ] 5+ community packages
- [ ] Active ecosystem
---
## Implementation Roadmap ## Implementation Roadmap
### Phase 1: v1.1.0 ✅ COMPLETE ### Phase 1: v1.1.0 (2-3 days)
**Template System:** **`weevil create` command:**
- [x] Template storage system (embedded in binary) - [ ] Template storage system
- [x] Variable substitution engine - [ ] Variable substitution
- [x] Basic template (minimal project) - [ ] Basic template
- [x] Testing template (professional showcase) - [ ] Testing template
- [x] `--list-templates` command - [ ] Git initialization
- [x] Template validation - [ ] Success/error messages
- [x] Success/error messages - [ ] Documentation
- [x] Documentation (README, DESIGN_AND_TEST_PLAN, etc.) - [ ] Tests
- [x] Comprehensive tests (62 tests passing)
- [x] Cross-platform support
**Delivered:** ### Phase 2: v1.2.0 (2-3 weeks)
- Template system fully functional
- Two high-quality templates
- Professional documentation
- 100% test coverage
- Zero warnings in `cargo test`
### Phase 2: v1.2.0 📋 PLANNED (2-3 weeks) **`weevil add` command:**
- [ ] Package registry setup
**`weevil add` Package System:** - [ ] Package manifest format
- [ ] Package registry infrastructure - [ ] Dependency resolver
- [ ] Package manifest format (`package.toml`) - [ ] Conflict detection
- [ ] Dependency resolver (semver) - [ ] File installation
- [ ] Conflict detection and resolution - [ ] Remove command
- [ ] File installation system - [ ] Search command
- [ ] `weevil remove` command - [ ] List command
- [ ] `weevil search` command
- [ ] `weevil list` command
- [ ] `weevil info` command
- [ ] `weevil update` command
- [ ] 10+ launch packages - [ ] 10+ launch packages
- [ ] Documentation - [ ] Documentation
- [ ] Comprehensive tests - [ ] Tests
--- ---
@@ -587,48 +601,7 @@ weevil update <package> # Update specific
--- ---
## Strategic Benefits ## Package Quality Standards
### For Teams
- **Faster start** - Working code from day one (via templates)
- **Code reuse** - Don't reinvent wheels (via packages)
- **Best practices** - Learn from examples
- **Community** - Share solutions
### For Nexus Workshops
- **Authority** - Set FTC code standards
- **Network effects** - More packages = more value
- **Revenue** - Workshops teach patterns
- **Differentiation** - Unique offering
### For FTC Community
- **Quality** - Raised bar across teams
- **Collaboration** - Build on each other
- **Education** - Professional patterns
- **Innovation** - Focus on unique solutions
---
## Success Metrics
### v1.1.0 (Templates) ✅ ACHIEVED
- [x] Template system implemented
- [x] Testing template includes 45 passing tests
- [x] Professional documentation delivered
- [x] 62 tests passing, zero warnings
- [x] Cross-platform support
- [ ] 50+ teams use testing template (tracking in progress)
- [ ] Used in Nexus Workshops curriculum (planned)
### v1.2.0 (Packages) 📋 PLANNED
- [ ] 20+ quality packages at launch
- [ ] 100+ downloads first month
- [ ] 5+ community packages
- [ ] Active ecosystem
---
## Package Quality Standards (v1.2.0)
**Required (All Packages):** **Required (All Packages):**
- Valid package.toml - Valid package.toml
@@ -651,21 +624,6 @@ weevil update <package> # Update specific
--- ---
## Open Questions (v1.2.0)
1. **Versioning:** How handle breaking changes? (Semver with pre-release tags)
2. **Testing:** Require tests in packages? (Recommended, not required)
3. **Licensing:** Enforce compliance? (Check but don't block)
4. **Moderation:** Who approves packages? (Automated checks + manual review for Nexus Verified)
5. **Private packages:** Support team-private? (v1.3.0 feature)
6. **Namespaces:** Use team numbers? (Optional, teams can use team1234 as scope)
7. **Binary support:** Allow compiled code? (No, source only)
8. **Update notifications:** Alert on updates? (Yes, via `weevil list --upgradable`)
9. **Code signing:** Security/trust model? (GPG signatures for Nexus Verified, optional for community)
10. **Monorepo:** Where store packages? (Separate repo: weevil-packages)
---
## Future Enhancements ## Future Enhancements
### v1.3.0+ ### v1.3.0+
@@ -674,14 +632,27 @@ weevil update <package> # Update specific
- `weevil publish` command - `weevil publish` command
- Package mirrors - Package mirrors
- Offline mode - Offline mode
- Additional templates (mecanum, vision, etc.)
### v2.0.0+ ### v2.0.0+
- Binary packages - Binary packages
- Pre-built libraries - Pre-built libraries
- Cloud builds - Cloud builds
- Team collaboration features - Team collaboration features
- VS Code integration
---
## Open Questions
1. **Versioning:** How handle breaking changes?
2. **Testing:** Require tests in packages?
3. **Licensing:** Enforce compliance?
4. **Moderation:** Who approves packages?
5. **Private packages:** Support team-private?
6. **Namespaces:** Use team numbers?
7. **Binary support:** Allow compiled code?
8. **Update notifications:** Alert on updates?
9. **Code signing:** Security/trust model?
10. **Monorepo:** Where store templates/packages?
--- ---
@@ -695,33 +666,102 @@ weevil update <package> # Update specific
--- ---
## Appendix: Comparison Matrix ## Appendix: Complete Examples
| Feature | Templates (v1.1.0) | Packages (v1.2.0) | ### Example 1: Creating Project
|---------|-------------------|-------------------|
| **Purpose** | Start projects | Extend projects | ```bash
| **When** | Project creation | After creation | $ weevil create my-robot --template testing
| **Size** | Large (complete projects) | Small (components) |
| **Conflicts** | None (new project) | Possible (merging) | Creating FTC project 'my-robot'...
| **Dependencies** | None | Yes (dependency tree) |
| **Variants** | 2 templates | Many per package | ✓ Created directory structure
| **Customization** | Fork template | Use as-is or modify | ✓ Generated source files (17 files)
| **Updates** | Manual (copy pattern) | `weevil update` | ✓ Generated test files (4 files)
| **Status** | Shipped | 📋 Planned | ✓ Created build configuration
✓ Generated documentation (6 files)
✓ Initialized Git repository
Project created successfully!
Next steps:
cd my-robot
weevil test # 45 tests pass in < 2 sec
weevil build # Build APK
weevil deploy --auto # Deploy to robot
Documentation:
README.md - Overview
DESIGN_AND_TEST_PLAN.md - Architecture
TESTING_GUIDE.md - How to test
```
### Example 2: Adding Package
```bash
$ cd my-robot
$ weevil add nexus/subsystems/mecanum-drive/complete
Resolving dependencies...
Package nexus/subsystems/mecanum-drive/complete requires:
- nexus/hardware/dc-motor/core (v1.2.0)
- nexus/hardware/imu/core (v2.0.0)
Installing 3 packages:
1. nexus/hardware/dc-motor/core (v1.2.0)
2. nexus/hardware/imu/core (v2.0.0)
3. nexus/subsystems/mecanum-drive/complete (v2.1.0)
✓ Added 12 source files
✓ Added 8 test files
✓ Updated build.gradle
Package installed successfully!
Next steps:
See docs/subsystems/MECANUM_DRIVE.md for usage
Run weevil test to verify integration
```
### Example 3: Handling Conflicts
```bash
$ weevil add nexus/hardware/dc-motor/core
⚠ File conflict detected:
src/main/java/robot/hardware/MotorController.java (exists)
Options:
1. Skip conflicting files (safe, default)
2. Overwrite conflicting files (--force)
3. Interactive resolution (--interactive)
4. Abort
Choice [1]: 1
Added:
✓ src/main/java/robot/hardware/FtcMotorController.java
✓ docs/hardware/DC_MOTOR.md
Skipped:
⊗ src/main/java/robot/hardware/MotorController.java
Package partially installed (1 file skipped)
Note: Package may not work correctly if required files skipped
Consider using --force or --interactive for complete installation
```
--- ---
*End of Specification* *End of Specification*
**Status:** **Status:** Ready for Implementation
- Part 1 (Templates): COMPLETE in v1.1.0
- 📋 Part 2 (Packages): PLANNED for v1.2.0
**Next Steps:** **Next Steps:**
1. Ship v1.1.0 with template system 1. Implement `weevil create` for v1.1.0-beta.3
2. Gather feedback on testing template 2. Use testing showcase as `testing` template
3. Begin v1.2.0 package system development 3. Plan v1.2.0 package system
4. Create initial package set
**Contact:** eric@intrepidfusion.com **Contact:** eric@intrepidfusion.com
**Organization:** Nexus Workshops LLC **Organization:** Nexus Workshops LLC

View File

@@ -513,7 +513,7 @@ class BasicTest {
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" /> <option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" /> <option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" /> <option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="C:\Windows\System32\cmd.exe" /> <option name="INTERPRETER_PATH" value="cmd.exe" />
<option name="INTERPRETER_OPTIONS" value="/c" /> <option name="INTERPRETER_OPTIONS" value="/c" />
<option name="EXECUTE_IN_TERMINAL" value="true" /> <option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" /> <option name="EXECUTE_SCRIPT_FILE" value="true" />
@@ -561,7 +561,7 @@ class BasicTest {
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" /> <option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" /> <option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" /> <option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="C:\Windows\System32\cmd.exe" /> <option name="INTERPRETER_PATH" value="cmd.exe" />
<option name="INTERPRETER_OPTIONS" value="/c" /> <option name="INTERPRETER_OPTIONS" value="/c" />
<option name="EXECUTE_IN_TERMINAL" value="true" /> <option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" /> <option name="EXECUTE_SCRIPT_FILE" value="true" />
@@ -609,7 +609,7 @@ class BasicTest {
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" /> <option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" /> <option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" /> <option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="C:\Windows\System32\cmd.exe" /> <option name="INTERPRETER_PATH" value="cmd.exe" />
<option name="INTERPRETER_OPTIONS" value="/c" /> <option name="INTERPRETER_OPTIONS" value="/c" />
<option name="EXECUTE_IN_TERMINAL" value="true" /> <option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" /> <option name="EXECUTE_SCRIPT_FILE" value="true" />
@@ -657,7 +657,7 @@ class BasicTest {
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" /> <option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" /> <option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" /> <option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="C:\Windows\System32\cmd.exe" /> <option name="INTERPRETER_PATH" value="cmd.exe" />
<option name="INTERPRETER_OPTIONS" value="/c" /> <option name="INTERPRETER_OPTIONS" value="/c" />
<option name="EXECUTE_IN_TERMINAL" value="true" /> <option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" /> <option name="EXECUTE_SCRIPT_FILE" value="true" />
@@ -705,7 +705,7 @@ class BasicTest {
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" /> <option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" /> <option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" /> <option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="C:\Windows\System32\cmd.exe" /> <option name="INTERPRETER_PATH" value="cmd.exe" />
<option name="INTERPRETER_OPTIONS" value="/c" /> <option name="INTERPRETER_OPTIONS" value="/c" />
<option name="EXECUTE_IN_TERMINAL" value="true" /> <option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" /> <option name="EXECUTE_SCRIPT_FILE" value="true" />

View File

@@ -8,7 +8,6 @@ use colored::*;
// Embed template directories at compile time // Embed template directories at compile time
static BASIC_TEMPLATE: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates/basic"); static BASIC_TEMPLATE: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates/basic");
static TESTING_TEMPLATE: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates/testing"); static TESTING_TEMPLATE: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates/testing");
static LOCALIZATION_TEMPLATE: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates/localization");
pub struct TemplateManager { pub struct TemplateManager {
#[allow(dead_code)] #[allow(dead_code)]
@@ -40,14 +39,13 @@ impl TemplateManager {
} }
pub fn template_exists(&self, name: &str) -> bool { pub fn template_exists(&self, name: &str) -> bool {
matches!(name, "basic" | "testing" | "localization") matches!(name, "basic" | "testing")
} }
pub fn list_templates(&self) -> Vec<String> { pub fn list_templates(&self) -> Vec<String> {
vec![ vec![
" basic - Minimal FTC project (default)".to_string(), " basic - Minimal FTC project (default)".to_string(),
" testing - Testing showcase with examples".to_string(), " testing - Testing showcase with examples".to_string(),
" localization - Grid-based positioning with sensor fusion".to_string(),
] ]
} }
@@ -70,14 +68,6 @@ impl TemplateManager {
test_count: 45, test_count: 45,
is_default: false, is_default: false,
}, },
TemplateInfo {
name: "localization".to_string(),
description: "Grid-based robot localization with sensor fusion".to_string(),
file_count: 21,
line_count: 1500,
test_count: 3,
is_default: false,
},
]) ])
} }
@@ -100,14 +90,6 @@ impl TemplateManager {
test_count: 45, test_count: 45,
is_default: false, is_default: false,
}, },
"localization" => TemplateInfo {
name: "localization".to_string(),
description: "Grid-based robot localization system with multi-sensor fusion and fault tolerance.".to_string(),
file_count: 21,
line_count: 1500,
test_count: 3,
is_default: false,
},
_ => bail!("Unknown template: {}", name), _ => bail!("Unknown template: {}", name),
}; };
@@ -127,16 +109,6 @@ impl TemplateManager {
println!(); println!();
} }
if info.name == "localization" {
println!("{}", "Features:".bright_white().bold());
println!(" • 12x12 field grid system (12-inch cells)");
println!(" • Multi-sensor fusion (encoders + IMU + vision)");
println!(" • Fault-tolerant positioning (graceful degradation)");
println!(" • Kalman-filter-style sensor fusion");
println!(" • Professional robotics patterns");
println!();
}
println!("{}", "Files included:".bright_white().bold()); println!("{}", "Files included:".bright_white().bold());
println!(" {} files", info.file_count); println!(" {} files", info.file_count);
println!(" ~{} lines of code", info.line_count); println!(" ~{} lines of code", info.line_count);
@@ -160,7 +132,6 @@ impl TemplateManager {
let template_dir = match template_name { let template_dir = match template_name {
"basic" => &BASIC_TEMPLATE, "basic" => &BASIC_TEMPLATE,
"testing" => &TESTING_TEMPLATE, "testing" => &TESTING_TEMPLATE,
"localization" => &LOCALIZATION_TEMPLATE,
_ => bail!("Unknown template: {}", template_name), _ => bail!("Unknown template: {}", template_name),
}; };
@@ -262,7 +233,6 @@ mod tests {
let mgr = TemplateManager::new().unwrap(); let mgr = TemplateManager::new().unwrap();
assert!(mgr.template_exists("basic")); assert!(mgr.template_exists("basic"));
assert!(mgr.template_exists("testing")); assert!(mgr.template_exists("testing"));
assert!(mgr.template_exists("localization"));
assert!(!mgr.template_exists("nonexistent")); assert!(!mgr.template_exists("nonexistent"));
} }
@@ -270,19 +240,6 @@ mod tests {
fn test_list_templates() { fn test_list_templates() {
let mgr = TemplateManager::new().unwrap(); let mgr = TemplateManager::new().unwrap();
let templates = mgr.list_templates(); let templates = mgr.list_templates();
assert_eq!(templates.len(), 3); assert_eq!(templates.len(), 2);
assert!(templates[0].contains("basic"));
assert!(templates[1].contains("testing"));
assert!(templates[2].contains("localization"));
}
#[test]
fn test_template_info_all() {
let mgr = TemplateManager::new().unwrap();
let infos = mgr.get_template_info_all().unwrap();
assert_eq!(infos.len(), 3);
assert_eq!(infos[0].name, "basic");
assert_eq!(infos[1].name, "testing");
assert_eq!(infos[2].name, "localization");
} }
} }

View File

@@ -1,7 +0,0 @@
build/
.gradle/
*.iml
.idea/
local.properties
*.apk
.DS_Store

View File

@@ -1,53 +0,0 @@
# {{PROJECT_NAME}} - Localization Template
Grid-based robot localization with sensor fusion and fault tolerance.
**Created:** {{CREATION_DATE}}
**Weevil:** {{WEEVIL_VERSION}}
**Template:** localization
## What's Included
- **Grid System** - 12x12 field grid (12" cells)
- **Sensor Fusion** - Combine encoders, IMU, vision
- **Fault Tolerance** - Graceful sensor failure handling
- **3 Tests** - All passing
## Quick Start
```bash
./gradlew test # Run tests
./build.sh # Build
./deploy.sh # Deploy
```
## Architecture
Field divided into 144 cells (12x12 grid):
- Cell (0,0) = Red backstage
- Cell (11,11) = Blue backstage
- Cell (6,6) = Center
Sensor fusion priority:
1. Vision (AprilTags) - ±2" accuracy
2. IMU + Odometry - ±4" accuracy
3. Odometry only - ±12" accuracy
## Files
**Localization:**
- GridCell.java - Cell representation
- Pose2D.java - Position + heading
- FieldGrid.java - Coordinate system
- RobotLocalizer.java - Sensor fusion engine
**Sensors:**
- OdometryTracker.java - Dead reckoning
- ImuLocalizer.java - Heading tracking
- VisionLocalizer.java - AprilTag positioning
**Docs:**
- LOCALIZATION_GUIDE.md - How it works
- GRID_SYSTEM.md - Field coordinates
See docs/ for full documentation.

View File

@@ -1,41 +0,0 @@
# Field Grid System
## Grid Layout
12x12 cells, each 12" x 12":
```
0 1 2 3 4 5 6 7 8 9 10 11
11 . . . . . . . . . . . B
10 . . . . . . . . . . . .
9 . . . . . . . . . . . .
8 . . . . . . . . . . . .
7 . . . . . . . . . . . .
6 . . . . . X . . . . . .
5 . . . . . . . . . . . .
4 . . . . . . . . . . . .
3 . . . . . . . . . . . .
2 . . . . . . . . . . . .
1 . . . . . . . . . . . .
0 R . . . . . . . . . . .
R = Red backstage (0,0)
B = Blue backstage (11,11)
X = Center (6,6)
```
## Usage
```java
GridCell cell = new GridCell(5, 7);
double dist = cell.distanceTo(FieldGrid.CENTER);
double angle = cell.angleTo(FieldGrid.BLUE_BACKSTAGE);
```
## Common Locations
```java
FieldGrid.RED_BACKSTAGE // (0, 0)
FieldGrid.BLUE_BACKSTAGE // (11, 11)
FieldGrid.CENTER // (6, 6)
```

View File

@@ -1,72 +0,0 @@
# Robot Localization Guide
## What is Localization?
Answering: "Where is my robot on the field?"
## The Grid System
12ft x 12ft field → 12x12 grid of 12" cells
```
Cell (0,0) = Red backstage
Cell (11,11) = Blue backstage
Cell (6,6) = Center
```
## Sensor Fusion
Combine three sensors:
1. **Odometry (Encoders)** - Track wheel rotation
- Accuracy: ±1" per foot (cumulative drift)
- Always available
2. **IMU (Gyroscope)** - Measure heading
- Accuracy: ±2° (non-cumulative)
- Corrects heading drift
3. **Vision (AprilTags)** - Detect position markers
- Accuracy: ±2" (when visible)
- Ground truth - resets drift
## Fusion Strategy
```
if vision available:
position = vision (most accurate)
correct odometry
elif IMU available:
position = odometry
heading = IMU
else:
position = odometry only (dead reckoning)
```
## Fault Tolerance
| Sensors | Accuracy | Confidence |
|---------|----------|------------|
| All 3 | ±2" | 100% |
| Odometry + IMU | ±4" | 70% |
| Odometry only | ±12" | 40% |
System keeps working when sensors fail!
## Usage
```java
RobotLocalizer localizer = new RobotLocalizer(odometry, imu, vision);
localizer.setInitialPose(new Pose2D(12, 12, 0));
while (opModeIsActive()) {
localizer.update();
GridCell cell = localizer.getCurrentCell();
double confidence = localizer.getConfidence();
// Make decisions based on position
}
```
See README.md for full examples.

View File

@@ -1,17 +0,0 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
mavenCentral()
google()
}
}
rootProject.name = "{{PROJECT_NAME}}"

View File

@@ -1,8 +0,0 @@
package robot.hardware;
public interface Encoder {
int getTicks();
int getTicksPerRevolution();
boolean isConnected();
void reset();
}

View File

@@ -1,8 +0,0 @@
package robot.hardware;
public interface GyroSensor {
double getHeading();
boolean isConnected();
void calibrate();
boolean isCalibrated();
}

View File

@@ -1,8 +0,0 @@
package robot.hardware;
import robot.localization.Pose2D;
public interface VisionCamera {
Pose2D detectPose();
boolean isConnected();
}

View File

@@ -1,31 +0,0 @@
package robot.localization;
public class FieldGrid {
public static final int FIELD_SIZE = 144;
public static final int CELL_SIZE = 12;
public static final int GRID_SIZE = 12;
public static final GridCell RED_BACKSTAGE = new GridCell(0, 0);
public static final GridCell BLUE_BACKSTAGE = new GridCell(11, 11);
public static final GridCell CENTER = new GridCell(6, 6);
public static GridCell poseToCell(Pose2D pose) {
double cx = Math.max(0, Math.min(FIELD_SIZE - 0.001, pose.x));
double cy = Math.max(0, Math.min(FIELD_SIZE - 0.001, pose.y));
return new GridCell((int)(cx / CELL_SIZE), (int)(cy / CELL_SIZE));
}
public static Pose2D cellToPose(GridCell cell) {
return new Pose2D((cell.x + 0.5) * CELL_SIZE, (cell.y + 0.5) * CELL_SIZE, 0);
}
public static boolean isWithinField(Pose2D pose) {
return pose.x >= 0 && pose.x <= FIELD_SIZE && pose.y >= 0 && pose.y <= FIELD_SIZE;
}
public static Pose2D clampToField(Pose2D pose) {
double x = Math.max(0, Math.min(FIELD_SIZE, pose.x));
double y = Math.max(0, Math.min(FIELD_SIZE, pose.y));
return new Pose2D(x, y, pose.heading);
}
}

View File

@@ -1,50 +0,0 @@
package robot.localization;
public class GridCell {
public final int x, y;
public GridCell(int x, int y) {
if (x < 0 || x > 11 || y < 0 || y > 11) {
throw new IllegalArgumentException("Cell out of bounds: (" + x + "," + y + ")");
}
this.x = x;
this.y = y;
}
public double distanceTo(GridCell other) {
int dx = other.x - this.x;
int dy = other.y - this.y;
return Math.sqrt(dx * dx + dy * dy) * FieldGrid.CELL_SIZE;
}
public double angleTo(GridCell other) {
return Math.toDegrees(Math.atan2(other.y - this.y, other.x - this.x));
}
public Pose2D getCenterPose() {
return new Pose2D((x + 0.5) * FieldGrid.CELL_SIZE, (y + 0.5) * FieldGrid.CELL_SIZE, 0);
}
public boolean isAdjacentTo(GridCell other) {
int dx = Math.abs(other.x - this.x);
int dy = Math.abs(other.y - this.y);
return dx <= 1 && dy <= 1 && (dx + dy) > 0;
}
public int manhattanDistanceTo(GridCell other) {
return Math.abs(other.x - this.x) + Math.abs(other.y - this.y);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof GridCell)) return false;
GridCell o = (GridCell) obj;
return this.x == o.x && this.y == o.y;
}
@Override
public int hashCode() { return x * 31 + y; }
@Override
public String toString() { return "Cell(" + x + "," + y + ")"; }
}

View File

@@ -1,29 +0,0 @@
package robot.localization;
import robot.hardware.GyroSensor;
public class ImuLocalizer {
private final GyroSensor gyro;
private double headingOffset;
public ImuLocalizer(GyroSensor gyro) {
this.gyro = gyro;
this.headingOffset = 0;
}
public void calibrate(double initialHeading) {
if (gyro.isConnected()) {
gyro.calibrate();
this.headingOffset = initialHeading - gyro.getHeading();
}
}
public Double getHeading() {
if (!gyro.isConnected() || !gyro.isCalibrated()) return null;
return Pose2D.normalizeAngle(gyro.getHeading() + headingOffset);
}
public boolean isWorking() {
return gyro.isConnected() && gyro.isCalibrated();
}
}

View File

@@ -1,67 +0,0 @@
package robot.localization;
import robot.hardware.Encoder;
public class OdometryTracker {
private final Encoder leftEncoder, rightEncoder;
private final double wheelDiameter, trackWidth;
private Pose2D currentPose;
private int lastLeftTicks, lastRightTicks;
public OdometryTracker(Encoder left, Encoder right, double wheelDia, double trackW) {
this.leftEncoder = left;
this.rightEncoder = right;
this.wheelDiameter = wheelDia;
this.trackWidth = trackW;
this.currentPose = new Pose2D(0, 0, 0);
this.lastLeftTicks = left.getTicks();
this.lastRightTicks = right.getTicks();
}
public OdometryTracker(Encoder left, Encoder right) {
this(left, right, 4.0, 16.0);
}
public void setPose(Pose2D pose) {
this.currentPose = pose;
this.lastLeftTicks = leftEncoder.getTicks();
this.lastRightTicks = rightEncoder.getTicks();
}
public Pose2D getPose() {
int leftTicks = leftEncoder.getTicks();
int rightTicks = rightEncoder.getTicks();
int deltaLeft = leftTicks - lastLeftTicks;
int deltaRight = rightTicks - lastRightTicks;
double ticksPerInch = leftEncoder.getTicksPerRevolution() / (Math.PI * wheelDiameter);
double leftDist = deltaLeft / ticksPerInch;
double rightDist = deltaRight / ticksPerInch;
lastLeftTicks = leftTicks;
lastRightTicks = rightTicks;
double distanceMoved = (leftDist + rightDist) / 2.0;
double angleChanged = (rightDist - leftDist) / trackWidth;
double midHeading = currentPose.heading + Math.toDegrees(angleChanged / 2);
double deltaX = distanceMoved * Math.cos(Math.toRadians(midHeading));
double deltaY = distanceMoved * Math.sin(Math.toRadians(midHeading));
currentPose = new Pose2D(
currentPose.x + deltaX,
currentPose.y + deltaY,
currentPose.heading + Math.toDegrees(angleChanged)
);
return currentPose;
}
public void correctPose(Pose2D pose) { this.currentPose = pose; }
public void correctHeading(double heading) {
this.currentPose = new Pose2D(currentPose.x, currentPose.y, heading);
}
public boolean isWorking() {
return leftEncoder.isConnected() && rightEncoder.isConnected();
}
}

View File

@@ -1,53 +0,0 @@
package robot.localization;
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 = normalizeAngle(heading);
}
public static double normalizeAngle(double degrees) {
double angle = degrees % 360;
if (angle > 180) angle -= 360;
else if (angle < -180) angle += 360;
return angle;
}
public double distanceTo(Pose2D other) {
double dx = other.x - this.x;
double dy = other.y - this.y;
return Math.sqrt(dx * dx + dy * dy);
}
public double angleTo(Pose2D other) {
return Math.toDegrees(Math.atan2(other.y - this.y, other.x - this.x));
}
public double headingDifferenceTo(Pose2D other) {
return normalizeAngle(angleTo(other) - this.heading);
}
public Pose2D translate(double dx, double dy) {
return new Pose2D(this.x + dx, this.y + dy, this.heading);
}
public Pose2D rotate(double degrees) {
return new Pose2D(this.x, this.y, this.heading + degrees);
}
public boolean isWithinField() {
return x >= 0 && x <= FieldGrid.FIELD_SIZE && y >= 0 && y <= FieldGrid.FIELD_SIZE;
}
public GridCell toGridCell() {
return FieldGrid.poseToCell(this);
}
@Override
public String toString() {
return String.format("Pose(%.1f\", %.1f\", %.1f°)", x, y, heading);
}
}

View File

@@ -1,78 +0,0 @@
package robot.localization;
public class RobotLocalizer {
private final OdometryTracker odometry;
private final ImuLocalizer imuLocalizer;
private final VisionLocalizer visionLocalizer;
private Pose2D currentPose;
private long lastUpdateTime;
public RobotLocalizer(OdometryTracker odometry, ImuLocalizer imu, VisionLocalizer vision) {
this.odometry = odometry;
this.imuLocalizer = imu;
this.visionLocalizer = vision;
this.currentPose = new Pose2D(0, 0, 0);
this.lastUpdateTime = System.currentTimeMillis();
}
public void setInitialPose(Pose2D pose) {
this.currentPose = pose;
this.odometry.setPose(pose);
this.lastUpdateTime = System.currentTimeMillis();
}
public void update() {
Pose2D odometryPose = odometry.getPose();
Double imuHeading = imuLocalizer.getHeading();
Pose2D visionPose = visionLocalizer.getPose();
if (visionPose != null) {
currentPose = visionPose;
odometry.correctPose(visionPose);
} else if (imuHeading != null) {
currentPose = new Pose2D(odometryPose.x, odometryPose.y, imuHeading);
odometry.correctHeading(imuHeading);
} else {
currentPose = odometryPose;
}
currentPose = FieldGrid.clampToField(currentPose);
}
public Pose2D getCurrentPose() { return currentPose; }
public GridCell getCurrentCell() { return FieldGrid.poseToCell(currentPose); }
public SensorHealth getSensorHealth() {
return new SensorHealth(
odometry.isWorking(),
imuLocalizer.isWorking(),
visionLocalizer.isWorking()
);
}
public double getConfidence() {
SensorHealth h = getSensorHealth();
if (h.visionWorking) return 1.0;
if (h.imuWorking) return 0.7;
if (h.odometryWorking) return 0.4;
return 0.0;
}
public static class SensorHealth {
public final boolean odometryWorking, imuWorking, visionWorking;
public SensorHealth(boolean o, boolean i, boolean v) {
odometryWorking = o; imuWorking = i; visionWorking = v;
}
public int getSensorCount() {
return (odometryWorking ? 1 : 0) + (imuWorking ? 1 : 0) + (visionWorking ? 1 : 0);
}
public String getStatus() {
int c = getSensorCount();
if (c == 3) return "Excellent";
if (c == 2) return "Good";
if (c == 1) return "Degraded";
return "Critical";
}
}
}

View File

@@ -1,35 +0,0 @@
package robot.localization;
import robot.hardware.VisionCamera;
public class VisionLocalizer {
private final VisionCamera camera;
private Pose2D lastVisionPose;
private long lastUpdateTime;
public VisionLocalizer(VisionCamera camera) {
this.camera = camera;
this.lastVisionPose = null;
this.lastUpdateTime = 0;
}
public Pose2D getPose() {
if (!camera.isConnected()) return null;
Pose2D detected = camera.detectPose();
if (detected != null) {
lastVisionPose = detected;
lastUpdateTime = System.currentTimeMillis();
}
return lastVisionPose;
}
public long getTimeSinceLastUpdate() {
if (lastUpdateTime == 0) return Long.MAX_VALUE;
return System.currentTimeMillis() - lastUpdateTime;
}
public boolean isWorking() {
return camera.isConnected() && getTimeSinceLastUpdate() < 10000;
}
}

View File

@@ -1,15 +0,0 @@
package robot.hardware;
public class MockEncoder implements Encoder {
private int ticks = 0;
private boolean connected = true;
public int getTicks() { return ticks; }
public int getTicksPerRevolution() { return 1000; }
public boolean isConnected() { return connected; }
public void reset() { ticks = 0; }
public void setTicks(int t) { ticks = t; }
public void addTicks(int delta) { ticks += delta; }
public void setConnected(boolean c) { connected = c; }
}

View File

@@ -1,16 +0,0 @@
package robot.hardware;
public class MockGyroSensor implements GyroSensor {
private double heading = 0;
private boolean connected = true;
private boolean calibrated = true;
public double getHeading() { return heading; }
public boolean isConnected() { return connected; }
public void calibrate() { calibrated = true; }
public boolean isCalibrated() { return calibrated; }
public void setHeading(double h) { heading = h; }
public void setConnected(boolean c) { connected = c; }
public void setCalibrated(boolean c) { calibrated = c; }
}

View File

@@ -1,14 +0,0 @@
package robot.hardware;
import robot.localization.Pose2D;
public class MockVisionCamera implements VisionCamera {
private Pose2D pose = null;
private boolean connected = true;
public Pose2D detectPose() { return pose; }
public boolean isConnected() { return connected; }
public void setPose(Pose2D p) { pose = p; }
public void setConnected(boolean c) { connected = c; }
}

View File

@@ -1,35 +0,0 @@
package robot.localization;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class GridCellTest {
@Test
void testCellCreation() {
GridCell cell = new GridCell(5, 7);
assertEquals(5, cell.x);
assertEquals(7, cell.y);
}
@Test
void testInvalidCell() {
assertThrows(IllegalArgumentException.class, () -> new GridCell(-1, 5));
assertThrows(IllegalArgumentException.class, () -> new GridCell(5, 12));
}
@Test
void testDistance() {
GridCell a = new GridCell(0, 0);
GridCell b = new GridCell(3, 4);
assertEquals(60.0, a.distanceTo(b), 0.001);
}
@Test
void testAngle() {
GridCell origin = new GridCell(0, 0);
GridCell right = new GridCell(1, 0);
GridCell up = new GridCell(0, 1);
assertEquals(0.0, origin.angleTo(right), 0.001);
assertEquals(90.0, origin.angleTo(up), 0.001);
}
}

View File

@@ -1,30 +0,0 @@
package robot.localization;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class Pose2DTest {
@Test
void testCreation() {
Pose2D pose = new Pose2D(24.0, 36.0, 45.0);
assertEquals(24.0, pose.x, 0.001);
assertEquals(36.0, pose.y, 0.001);
assertEquals(45.0, pose.heading, 0.001);
}
@Test
void testNormalization() {
Pose2D p1 = new Pose2D(0, 0, 370);
assertEquals(10.0, p1.heading, 0.001);
Pose2D p2 = new Pose2D(0, 0, -190);
assertEquals(170.0, p2.heading, 0.001);
}
@Test
void testDistance() {
Pose2D a = new Pose2D(0, 0, 0);
Pose2D b = new Pose2D(3, 4, 0);
assertEquals(5.0, a.distanceTo(b), 0.001);
}
}

View File

@@ -1,57 +0,0 @@
package robot.localization;
import org.junit.jupiter.api.Test;
import robot.hardware.*;
import static org.junit.jupiter.api.Assertions.*;
class SensorFusionTest {
@Test
void testVisionCorrection() {
MockEncoder left = new MockEncoder();
MockEncoder right = new MockEncoder();
MockGyroSensor gyro = new MockGyroSensor();
MockVisionCamera camera = new MockVisionCamera();
OdometryTracker odometry = new OdometryTracker(left, right);
ImuLocalizer imu = new ImuLocalizer(gyro);
VisionLocalizer vision = new VisionLocalizer(camera);
RobotLocalizer localizer = new RobotLocalizer(odometry, imu, vision);
localizer.setInitialPose(new Pose2D(0, 0, 0));
camera.setPose(new Pose2D(12, 12, 0));
localizer.update();
Pose2D pose = localizer.getCurrentPose();
assertEquals(12.0, pose.x, 0.1);
assertEquals(12.0, pose.y, 0.1);
}
@Test
void testGracefulDegradation() {
MockEncoder left = new MockEncoder();
MockEncoder right = new MockEncoder();
MockGyroSensor gyro = new MockGyroSensor();
MockVisionCamera camera = new MockVisionCamera();
// Set a pose so vision is actually "working" (not just connected)
camera.setPose(new Pose2D(12, 12, 0));
OdometryTracker odometry = new OdometryTracker(left, right);
ImuLocalizer imu = new ImuLocalizer(gyro);
VisionLocalizer vision = new VisionLocalizer(camera);
RobotLocalizer localizer = new RobotLocalizer(odometry, imu, vision);
localizer.update(); // Need to update to actually use vision
assertEquals(1.0, localizer.getConfidence(), 0.01);
camera.setConnected(false);
localizer.update();
assertEquals(0.7, localizer.getConfidence(), 0.01);
gyro.setConnected(false);
localizer.update();
assertEquals(0.4, localizer.getConfidence(), 0.01);
}
}

View File

@@ -1,3 +1,3 @@
// Intentionally hardcoded. When you bump the version in Cargo.toml, // Intentionally hardcoded. When you bump the version in Cargo.toml,
// tests will fail here until you update this to match. // tests will fail here until you update this to match.
pub const EXPECTED_VERSION: &str = "1.1.0-rc1"; pub const EXPECTED_VERSION: &str = "1.1.0-beta.2";

View File

@@ -10,7 +10,7 @@ use weevil::templates::{TemplateManager, TemplateContext};
fn test_context(project_name: &str) -> TemplateContext { fn test_context(project_name: &str) -> TemplateContext {
TemplateContext { TemplateContext {
project_name: project_name.to_string(), project_name: project_name.to_string(),
package_name: project_name.to_lowercase().replace("-", "").replace("_", ""), package_name: project_name.to_lowercase().replace("-", ""),
creation_date: "2026-02-02T12:00:00Z".to_string(), creation_date: "2026-02-02T12:00:00Z".to_string(),
weevil_version: "1.1.0-test".to_string(), weevil_version: "1.1.0-test".to_string(),
template_name: "basic".to_string(), template_name: "basic".to_string(),
@@ -37,7 +37,7 @@ fn test_list_templates() {
let mgr = TemplateManager::new().unwrap(); let mgr = TemplateManager::new().unwrap();
let templates = mgr.list_templates(); let templates = mgr.list_templates();
assert_eq!(templates.len(), 3, "Should have exactly 3 templates"); assert_eq!(templates.len(), 2, "Should have exactly 2 templates");
assert!(templates.iter().any(|t| t.contains("basic")), "Should list basic template"); assert!(templates.iter().any(|t| t.contains("basic")), "Should list basic template");
assert!(templates.iter().any(|t| t.contains("testing")), "Should list testing template"); assert!(templates.iter().any(|t| t.contains("testing")), "Should list testing template");
} }
@@ -54,13 +54,13 @@ fn test_basic_template_extraction() -> Result<()> {
assert!(file_count > 0, "Should extract at least one file from basic template"); assert!(file_count > 0, "Should extract at least one file from basic template");
// Verify key files exist (basic template has minimal files) // Verify key files exist
assert!(project_dir.join(".gitignore").exists(), ".gitignore should exist"); assert!(project_dir.join(".gitignore").exists(), ".gitignore should exist");
assert!(project_dir.join("README.md").exists(), "README.md should exist (processed from .template)"); assert!(project_dir.join("README.md").exists(), "README.md should exist (processed from .template)");
assert!(project_dir.join(".weevil.toml").exists(), ".weevil.toml should exist (processed from .template)");
assert!(project_dir.join("build.gradle").exists(), "build.gradle should exist (processed from .template)");
assert!(project_dir.join("settings.gradle").exists(), "settings.gradle should exist"); assert!(project_dir.join("settings.gradle").exists(), "settings.gradle should exist");
// Note: .weevil.toml and build.gradle are created by ProjectBuilder, not template
// Verify OpMode exists // Verify OpMode exists
let opmode_path = project_dir.join("src/main/java/robot/opmodes/BasicOpMode.java"); let opmode_path = project_dir.join("src/main/java/robot/opmodes/BasicOpMode.java");
assert!(opmode_path.exists(), "BasicOpMode.java should exist"); assert!(opmode_path.exists(), "BasicOpMode.java should exist");
@@ -129,12 +129,12 @@ fn test_template_variable_substitution() -> Result<()> {
assert!(!readme_content.contains("{{PROJECT_NAME}}"), "README should not contain template variable"); assert!(!readme_content.contains("{{PROJECT_NAME}}"), "README should not contain template variable");
assert!(!readme_content.contains("{{WEEVIL_VERSION}}"), "README should not contain template variable"); assert!(!readme_content.contains("{{WEEVIL_VERSION}}"), "README should not contain template variable");
// Check BasicOpMode.java for variable substitution // Check .weevil.toml for variable substitution
let opmode_path = project_dir.join("src/main/java/robot/opmodes/BasicOpMode.java"); let weevil_toml = project_dir.join(".weevil.toml");
let opmode_content = fs::read_to_string(opmode_path)?; let toml_content = fs::read_to_string(weevil_toml)?;
assert!(opmode_content.contains("my-test-robot"), "BasicOpMode should contain project name"); assert!(toml_content.contains("my-test-robot"), ".weevil.toml should contain project name");
assert!(!opmode_content.contains("{{PROJECT_NAME}}"), "BasicOpMode should not contain template variable"); assert!(!toml_content.contains("{{PROJECT_NAME}}"), ".weevil.toml should not contain template variable");
Ok(()) Ok(())
} }
@@ -154,15 +154,11 @@ fn test_invalid_template_extraction() {
#[test] #[test]
fn test_package_name_sanitization() { fn test_package_name_sanitization() {
// Test that the helper creates correct package names
let context1 = test_context("my-robot"); let context1 = test_context("my-robot");
assert_eq!(context1.package_name, "myrobot", "Hyphens should be removed"); assert_eq!(context1.package_name, "myrobot", "Hyphens should be removed");
let context2 = test_context("team_1234_bot"); let context2 = test_context("team_1234_bot");
assert_eq!(context2.package_name, "team1234bot", "Underscores should be removed"); assert_eq!(context2.package_name, "team1234bot", "Underscores should be removed");
let context3 = test_context("My-Cool_Bot");
assert_eq!(context3.package_name, "mycoolbot", "Mixed case and separators should be handled");
} }
/// Integration test: Create a project with testing template and run gradle tests /// Integration test: Create a project with testing template and run gradle tests