Initial commit of magicbot code base

This commit is contained in:
Mike Silva 2026-06-05 12:53:10 -04:00
parent 5986bc8f92
commit 2c7b6eb5c1
93 changed files with 3946 additions and 5703 deletions

1
.github/CODEOWNERS vendored
View File

@ -1 +0,0 @@
@tywilly

View File

@ -1,36 +0,0 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
name: Java CI with Gradle
on:
push:
branches: [ ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Build with Gradle
run: ./gradlew build javadoc

6
.gitmodules vendored
View File

@ -1,6 +0,0 @@
[submodule "lib/Posty"]
path = lib/Posty
url = git@github.com:FairportRobotics/Posty.git
[submodule "lib/RoboLib"]
path = lib/RoboLib
url = git@github.com:FairportRobotics/RoboLib

21
.vscode/launch.json vendored
View File

@ -1,21 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "wpilib",
"name": "WPILib Desktop Debug",
"request": "launch",
"desktop": true,
},
{
"type": "wpilib",
"name": "WPILib roboRIO Debug",
"request": "launch",
"desktop": false,
}
]
}

29
.vscode/settings.json vendored
View File

@ -1,29 +0,0 @@
{
"java.configuration.updateBuildConfiguration": "automatic",
"java.server.launchMode": "Standard",
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"bin/": true,
"**/.classpath": true,
"**/.project": true,
"**/.settings": true,
"**/.factorypath": true,
"**/*~": true
},
"java.test.config": [
{
"name": "WPIlibUnitTests",
"workingDirectory": "${workspaceFolder}/build/jni/release",
"vmargs": [ "-Djava.library.path=${workspaceFolder}/build/jni/release" ],
"env": {
"LD_LIBRARY_PATH": "${workspaceFolder}/build/jni/release" ,
"DYLD_LIBRARY_PATH": "${workspaceFolder}/build/jni/release"
}
},
],
"java.test.defaultConfig": "WPIlibUnitTests"
}

View File

@ -1,6 +0,0 @@
{
"enableCppIntellisense": false,
"currentLanguage": "java",
"projectYear": "2026",
"teamNumber": 578
}

81
LimelightHelpers.py Normal file
View File

@ -0,0 +1,81 @@
"""
LimelightHelpers.py – stub for the community limelightlib-python library.
Replace this file with the real LimelightHelpers.py from:
https://github.com/LimelightVision/limelightlib-python
The following methods must be verified to exist in the real library before
any Limelight-dependent code is run on the robot (see PLAN.txt section 3):
- getBotPoseEstimate_wpiBlue_MegaTag2()
- SetIMUMode()
- getTargetPose_CameraSpace()
- SetRobotOrientation()
- SetFiducialIDFiltersOverride()
- setPipelineIndex()
- getTV()
"""
from __future__ import annotations
from dataclasses import dataclass, field
from wpimath.geometry import Pose2d
@dataclass
class PoseEstimate:
pose: Pose2d = field(default_factory=Pose2d)
timestampSeconds: float = 0.0
tagCount: int = 0
isMegaTag2: bool = False
# ── Pose estimation ────────────────────────────────────────────────────────────
def getBotPoseEstimate_wpiBlue_MegaTag2(limelightName: str) -> PoseEstimate | None:
"""Return None (stub — no camera connected in sim)."""
return None
def getBotPoseEstimate_wpiBlue(limelightName: str) -> PoseEstimate | None:
return None
# ── Robot orientation / IMU ────────────────────────────────────────────────────
def SetRobotOrientation(
limelightName: str,
yaw: float,
yawRate: float,
pitch: float,
pitchRate: float,
roll: float,
rollRate: float,
) -> None:
pass
def SetIMUMode(limelightName: str, mode: int) -> None:
pass
# ── Target information ─────────────────────────────────────────────────────────
def getTV(limelightName: str) -> bool:
"""Return False — no targets visible in stub."""
return False
def getTargetPose_CameraSpace(limelightName: str) -> list[float]:
"""Return empty list — no target in stub."""
return []
# ── Pipeline / filter control ──────────────────────────────────────────────────
def setPipelineIndex(limelightName: str, pipelineIndex: int) -> None:
pass
def SetFiducialIDFiltersOverride(limelightName: str, validIDs: list[int]) -> None:
pass

View File

@ -1,24 +0,0 @@
Copyright (c) 2009-2026 FIRST and other WPILib contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of FIRST, WPILib, nor the names of other WPILib
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY FIRST AND OTHER WPILIB CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY NONINFRINGEMENT AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL FIRST OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

0
autonomous/__init__.py Normal file
View File

38
autonomous/basic_auto.py Normal file
View File

@ -0,0 +1,38 @@
"""
autonomous/basic_auto.py
Fallback MagicBot AutonomousStateMachine used when PathPlanner is unavailable.
Deploys the intake, homes the turret, then stops.
"""
import magicbot
from magicbot.state_machine import state, timed_state
from components.drive import DriveComponent
from components.intake import IntakeComponent
from components.turret import TurretComponent
class BasicAuto(magicbot.AutonomousStateMachine):
"""Simple fallback autonomous: deploy intake → home turret → idle."""
MODE_NAME = "Basic Auto"
DEFAULT = False
# MagicBot injects these
drive: DriveComponent
intake: IntakeComponent
turret: TurretComponent
@timed_state(first=True, duration=2.0, next_state="home_turret")
def deploy_intake(self) -> None:
self.intake.deploy()
@timed_state(duration=3.0, next_state="idle")
def home_turret(self) -> None:
self.intake.stop_deploy()
self.turret.home_turret()
@state
def idle(self) -> None:
self.drive.stop_drive()

View File

@ -0,0 +1,158 @@
"""
autonomous/chassis_align_shoot.py
commands2.Command that replicates AutoShootCommandChassisTurret.java.
Uses a PID controller to camera-centre the robot on the HUB AprilTags and
dead-reckoning as a fallback. Fires the kicker when centred and launcher is
up to speed.
"""
import math
import commands2
from robotpy_apriltag import AprilTagField, AprilTagFieldLayout
from wpilib import DriverStation
from wpimath.controller import PIDController
from wpimath.geometry import Pose3d
import constants
import LimelightHelpers
import utils
from components.drive import DriveComponent
from components.hopper import HopperComponent
from components.turret import TurretComponent
class ChassisAlignShootCommand(commands2.Command):
"""
Aligns the robot chassis and fires the launcher using camera feedback.
Registered with PathPlanner as "ChassisAlignShoot".
"""
_field_tags = AprilTagFieldLayout.loadField(AprilTagField.k2026RebuiltWelded)
def __init__(
self,
drive: DriveComponent,
hopper: HopperComponent,
turret: TurretComponent,
) -> None:
super().__init__()
self._drive = drive
self._hopper = hopper
self._turret = turret
self._tag_filters: list[int] = []
self._closest_tag: Pose3d | None = None
# Camera centering PID (units: degrees)
self._camera_controller = PIDController(0.15, 0.0, 0.0)
self._camera_controller.setTolerance(0.2)
self._camera_controller.setSetpoint(0)
# Dead-reckoning fallback PID (units: radians)
self._dead_reckoning_controller = PIDController(math.pi * 0.1, 0, 0.0)
self._dead_reckoning_controller.setTolerance(0.2)
self._dead_reckoning_controller.enableContinuousInput(-math.pi, math.pi)
self._chassis_rotate_speed = math.pi * 0.1
def initialize(self) -> None:
alliance = DriverStation.getAlliance()
if alliance == DriverStation.Alliance.kBlue:
self._tag_filters = constants.CameraConstants.IDFilters.BLUE_HUB_SHOOTING_IDS
else:
self._tag_filters = constants.CameraConstants.IDFilters.RED_HUB_SHOOTING_IDS
self._turret.set_launcher(3000)
self._turret.set_target_elevation(constants.ShooterConstants.DEFLECTOR_STORED_ANGLE)
self._hopper.spindexer_on()
# Warm-up the camera PID
self._camera_controller.calculate(-100)
# Switch to HUB-tracking pipeline
LimelightHelpers.setPipelineIndex(
constants.CameraConstants.BACK_CAMERA_NAME,
constants.CameraConstants.BACK_HUB_TRACKING_3D_PIPELINE_NUMBER,
)
LimelightHelpers.SetFiducialIDFiltersOverride(
constants.CameraConstants.BACK_CAMERA_NAME, self._tag_filters
)
# Find the closest HUB AprilTag for dead-reckoning
bot_pose = self._drive.get_bot_pose()
tag_poses: list[Pose3d] = [
pose
for tag_id in self._tag_filters
if (pose := self._field_tags.getTagPose(tag_id)) is not None
]
if tag_poses:
# Find nearest tag by distance
def _dist(tag: Pose3d) -> float:
delta = bot_pose.translation().toTranslation2d() - tag.translation().toTranslation2d()
return math.hypot(delta.X(), delta.Y())
self._closest_tag = min(tag_poses, key=_dist)
delta = (
bot_pose.translation().toTranslation2d()
- self._closest_tag.translation().toTranslation2d()
)
self._dead_reckoning_controller.setSetpoint(
math.atan2(delta.Y(), delta.X())
)
def execute(self) -> None:
has_target = LimelightHelpers.getTV(constants.CameraConstants.BACK_CAMERA_NAME)
if not has_target:
# ── Dead-reckoning fallback ──────────────────────────────────
# (Chassis rotation was commented out in Java source; preserved here)
# bot_pose = self._drive.get_bot_pose()
# self._drive.rotate_chassis(
# -self._dead_reckoning_controller.calculate(
# bot_pose.rotation().toRotation2d().radians()
# )
# )
pass
else:
# ── Camera centering ─────────────────────────────────────────
targets_pose = LimelightHelpers.getTargetPose_CameraSpace(
constants.CameraConstants.BACK_CAMERA_NAME
)
if targets_pose and len(targets_pose) >= 3:
distance_z = targets_pose[2]
tx = targets_pose[0]
turret_angle = (
utils.calculate_turret_angle_from_camera_tag_distance(distance_z)
* constants.ShooterConstants.TURRET_GEAR_RATIO
)
self._turret.set_turret_motor_rotation(-turret_angle)
self._drive.rotate_chassis(
-self._camera_controller.calculate(tx)
)
# Fire when centred and launcher is up to speed
if (
self._turret.is_launcher_up_to_speed()
and self._camera_controller.atSetpoint()
):
self._hopper.feed_kicker()
else:
pass # Waiting to fire
def isFinished(self) -> bool:
return False
def end(self, interrupted: bool) -> None:
self._turret.set_launcher(0)
self._hopper.stop_kicker()
self._hopper.spindexer_off()
self._drive.stop_drive()
LimelightHelpers.setPipelineIndex(
constants.CameraConstants.BACK_CAMERA_NAME,
constants.CameraConstants.BACK_LOCALIZATION_PIPELINE_NUMBER,
)

View File

@ -0,0 +1,85 @@
"""
autonomous/turret_shoot.py
commands2.Command that replicates AutoTurretShootCommand.java.
Uses distance-based ballistics (Utils.get_hood_angle/launcher_rpm_for_distance)
to set the turret launcher speed and hood angle, then fires once ready.
"""
import commands2
from wpilib import DriverStation
import constants
import utils
from components.drive import DriveComponent
from components.hopper import HopperComponent
from components.intake import IntakeComponent
from components.turret import TurretComponent
class TurretShootCommand(commands2.Command):
"""
Distance-based auto-shoot command.
Registered with PathPlanner as "TurretShoot".
"""
_WAIT_SECONDS: float = 1.5
def __init__(
self,
hopper: HopperComponent,
turret: TurretComponent,
intake: IntakeComponent,
drive: DriveComponent,
) -> None:
super().__init__()
self._hopper = hopper
self._turret = turret
self._intake = intake
self._drive = drive
self._wait_timer = commands2.WaitCommand(self._WAIT_SECONDS)
self._wait_started = False
def initialize(self) -> None:
self._hopper.spindexer_on()
self._wait_timer = commands2.WaitCommand(self._WAIT_SECONDS)
self._wait_timer.schedule()
self._wait_started = True
def execute(self) -> None:
turret_target = self._turret.get_turret_target_pose()
bot_pose = self._drive.get_bot_pose()
if turret_target is not None:
target_2d = turret_target.toPose2d().translation()
bot_2d = bot_pose.translation().toTranslation2d()
distance = target_2d.distance(bot_2d)
self._turret.set_target_elevation(utils.get_hood_angle_for_distance(distance))
self._turret.set_launcher(utils.get_launcher_rpm_for_distance(distance))
if self._turret.is_launcher_up_to_speed() and self._wait_timer.isFinished():
self._hopper.feed_kicker()
else:
self._hopper.stop_kicker()
def isFinished(self) -> bool:
return False
def end(self, interrupted: bool) -> None:
if self._wait_started:
self._wait_timer.cancel()
self._hopper.spindexer_off()
self._hopper.stop_kicker()
self._turret.set_launcher(0)
self._turret.set_target_elevation(constants.ShooterConstants.DEFLECTOR_STORED_ANGLE)
# Restore turret target to hub for the current alliance
alliance = DriverStation.getAlliance()
if alliance == DriverStation.Alliance.kBlue:
self._turret.set_turret_target_pose(constants.FieldPoses.BLUE_HUB_POSE)
elif alliance == DriverStation.Alliance.kRed:
self._turret.set_turret_target_pose(constants.FieldPoses.RED_HUB_POSE)

View File

@ -1,141 +0,0 @@
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2026.2.1"
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
def ROBOT_MAIN_CLASS = "frc.robot.Main"
// Define my targets (RoboRIO) and artifacts (deployable files)
// This is added by GradleRIO's backing project DeployUtils.
deploy {
targets {
roborio(getTargetTypeClass('RoboRIO')) {
// Team number is loaded either from the .wpilib/wpilib_preferences.json
// or from command line. If not found an exception will be thrown.
// You can use getTeamOrDefault(team) instead of getTeamNumber if you
// want to store a team number in this file.
team = project.frc.getTeamNumber()
debug = project.frc.getDebugOrDefault(false)
artifacts {
// First part is artifact name, 2nd is artifact type
// getTargetTypeClass is a shortcut to get the class type using a string
frcJava(getArtifactTypeClass('FRCJavaArtifact')) {
}
// Static files artifact
frcStaticFileDeploy(getArtifactTypeClass('FileTreeArtifact')) {
files = project.fileTree('src/main/deploy')
directory = '/home/lvuser/deploy'
deleteOldFiles = false // Change to true to delete files on roboRIO that no
// longer exist in deploy directory of this project
}
}
}
}
}
def deployArtifact = deploy.targets.roborio.artifacts.frcJava
// Set to true to use debug for all targets including JNI, which will drastically impact
// performance.
wpi.java.debugJni = false
// Set this to true to enable desktop support.
def includeDesktopSupport = true
// Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries.
// Also defines JUnit 5.
dependencies {
annotationProcessor wpi.java.deps.wpilibAnnotations()
implementation wpi.java.deps.wpilib()
implementation wpi.java.vendor.java()
roborioDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.roborio)
roborioDebug wpi.java.vendor.jniDebug(wpi.platforms.roborio)
roborioRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.roborio)
roborioRelease wpi.java.vendor.jniRelease(wpi.platforms.roborio)
nativeDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.desktop)
nativeDebug wpi.java.vendor.jniDebug(wpi.platforms.desktop)
simulationDebug wpi.sim.enableDebug()
nativeRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.desktop)
nativeRelease wpi.java.vendor.jniRelease(wpi.platforms.desktop)
simulationRelease wpi.sim.enableRelease()
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
test {
useJUnitPlatform()
systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true'
}
// Simulation configuration (e.g. environment variables).
wpi.sim.addGui().defaultEnabled = true
wpi.sim.addDriverstation()
// Setting up my Jar File. In this case, adding all libraries into the main jar ('fat jar')
// in order to make them all available at runtime. Also adding the manifest so WPILib
// knows where to look for our Robot Class.
jar {
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
from('src') { into 'backup/src' }
from('vendordeps') { into 'backup/vendordeps' }
from('build.gradle') { into 'backup' }
manifest edu.wpi.first.gradlerio.GradleRIOPlugin.javaManifest(ROBOT_MAIN_CLASS)
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
// Configure jar and deploy tasks
deployArtifact.jarTask = jar
wpi.java.configureExecutableTasks(jar)
wpi.java.configureTestTasks(test)
def saveCommitTask = tasks.register("saveCommit") {
println "Current Version: " + getCheckedOutGitCommitHash();
def deployFolder = "$projectDir/src/main/deploy/"
def commitFile = new FileWriter(deployFolder + "versionfile.txt");
doLast {
commitFile.write(getCheckedOutGitCommitHash() + " - " + new Date().toString());
commitFile.close();
println "versionfile.txt written";
}
}
def getCheckedOutGitCommitHash() {
def gitFolder = "$projectDir/.git/"
def takeFromHash = 12
/*
* '.git/HEAD' contains either
* in case of detached head: the currently checked out commit hash
* otherwise: a reference to a file containing the current commit hash
*/
def head = new File(gitFolder + "HEAD").text.split(":") // .git/HEAD
def isCommit = head.length == 1 // e5a7c79edabbf7dd39888442df081b1c9d8e88fd
// def isRef = head.length > 1 // ref: refs/heads/master
if(isCommit) return head[0].trim().take(takeFromHash) // e5a7c79edabb
def refHead = new File(gitFolder + head[1].trim()) // .git/refs/heads/master
refHead.text.trim().take takeFromHash
}
// Configure string concat to always inline compile
tasks.withType(JavaCompile) {
options.compilerArgs.add '-XDstringConcat=inline'
dependsOn saveCommitTask
}
apply from: 'libs.gradle'

0
components/__init__.py Normal file
View File

359
components/controller.py Normal file
View File

@ -0,0 +1,359 @@
"""
components/controller.py – MagicBot-compatible Xbox controller wrapper.
XboxController wraps wpilib.XboxController to provide:
- Deadband-corrected joystick axes
- Edge-detection helpers (was_pressed / pressed) for every button
- Integration with MagicBot's component injection system (``port`` attribute)
"""
import wpilib
class XboxController:
"""
MagicBot component that wraps a wpilib.XboxController.
MagicBot injects the ``port`` attribute before ``setup()`` is called,
so declare it at class level — its value is overridden per-robot in
``MyRobot`` via the ``driver_controller_port`` prefixed variable.
Joystick values are deadband-corrected and cached each loop in
``execute()`` so all code within a single loop sees consistent axis values.
Button helpers follow two patterns:
- ``*_pressed()`` – True while the button is held down.
- ``*_was_pressed()`` – True on the loop after the button was released
(one-shot, resets immediately after reading).
"""
#: Whether to apply deadband correction to raw joystick values.
correct_for_deadband: bool = True
#: Deadband radius; inputs within ±deadband map to 0.
deadband: float = 0.1
#: USB port number; injected by MagicBot from ``driver_controller_port``.
port: int
#: Cached left-stick X axis value (deadband-corrected).
left_x: float = 0.0
#: Cached left-stick Y axis value (deadband-corrected).
left_y: float = 0.0
#: Cached right-stick X axis value (deadband-corrected).
right_x: float = 0.0
#: Cached right-stick Y axis value (deadband-corrected).
right_y: float = 0.0
def setup(self) -> None:
"""
Setup the Xbox controller.
This method is called once when the robot is initialized.
"""
self.this_controller = wpilib.XboxController(self.port)
controller_buttons = [
"A",
"B",
"X",
"Y",
"DPad_Up",
"DPad_Down",
"DPad_Left",
"DPad_Right",
"Left_Bumper",
"Right_Bumper",
"Left_Trigger",
"Right_Trigger",
"Start",
"Back",
]
self.button_is_pressed = {button: False for button in controller_buttons}
self.button_was_pressed = {button: False for button in controller_buttons}
def _button_pressed(self, button_name: str, pressed: bool) -> bool:
"""
Generic method to check if a button is pressed.
:param button_name: The name of the button to check.
:param pressed: The current state of the button (True if pressed, False otherwise).
"""
if self.button_is_pressed[button_name] and not pressed:
self.button_was_pressed[button_name] = True
self.button_is_pressed[button_name] = pressed
return pressed
def _button_was_pressed(self, button_name: str) -> bool:
"""
Generic method to check if a button was pressed since the last check.
:param button_name: The name of the button to check.
:return: True if the button was pressed since the last check, False otherwise.
"""
if self.button_was_pressed[button_name]:
self.button_was_pressed[button_name] = False
return True
return False
def _corrected_joystick_value(self, raw_value: float) -> float:
"""
Apply deadband correction to a joystick value.
:param raw_value: The raw joystick value.
:return: The corrected joystick value.
"""
if not self.correct_for_deadband:
return raw_value
if abs(raw_value) < self.deadband:
return 0.0
return (raw_value / abs(raw_value)) * (
(abs(raw_value) - self.deadband) / (1 - self.deadband)
)
def execute(self) -> None:
"""
Capture the current state of all buttons to track presses.
This method should be called at the end of each control loop iteration.
"""
self.a_button_pressed()
self.b_button_pressed()
self.x_button_pressed()
self.y_button_pressed()
self.dpad_up_pressed()
self.dpad_down_pressed()
self.dpad_left_pressed()
self.dpad_right_pressed()
self.left_bumper_pressed()
self.right_bumper_pressed()
self.left_trigger_pressed()
self.right_trigger_pressed()
self.start_button_pressed()
self.back_button_pressed()
self.left_y = self._corrected_joystick_value(self.this_controller.getLeftY())
self.left_x = self._corrected_joystick_value(self.this_controller.getLeftX())
self.right_y = self._corrected_joystick_value(self.this_controller.getRightY())
self.right_x = self._corrected_joystick_value(self.this_controller.getRightX())
def a_button_pressed(self) -> bool:
"""
Check if the A button is pressed.
:return: True if the A button is pressed, False otherwise.
"""
return self._button_pressed("A", self.this_controller.getAButton())
def a_button_was_pressed(self) -> bool:
"""
Check if the A button was pressed since the last check.
:return: True if the A button was pressed since the last check, False otherwise.
"""
return self._button_was_pressed("A")
def b_button_pressed(self) -> bool:
"""
Check if the B button is pressed.
:return: True if the B button is pressed, False otherwise.
"""
return self._button_pressed("B", self.this_controller.getBButton())
def b_button_was_pressed(self) -> bool:
"""
Check if the B button was pressed since the last check.
:return: True if the B button was pressed since the last check, False otherwise.
"""
return self._button_was_pressed("B")
def x_button_pressed(self) -> bool:
"""
Check if the X button is pressed.
:return: True if the X button is pressed, False otherwise.
"""
return self._button_pressed("X", self.this_controller.getXButton())
def x_button_was_pressed(self) -> bool:
"""
Check if the X button was pressed since the last check.
:return: True if the X button was pressed since the last check, False otherwise.
"""
return self._button_was_pressed("X")
def y_button_pressed(self) -> bool:
"""
Check if the Y button is pressed.
:return: True if the Y button is pressed, False otherwise.
"""
return self._button_pressed("Y", self.this_controller.getYButton())
def y_button_was_pressed(self) -> bool:
"""
Check if the Y button was pressed since the last check.
:return: True if the Y button was pressed since the last check, False otherwise.
"""
return self._button_was_pressed("Y")
def dpad_up_pressed(self) -> bool:
"""
Check if the D-pad is pressed up.
:return: True if the D-pad is pressed up, False otherwise.
"""
return self._button_pressed("DPad_Up", self.this_controller.getPOV() == 0)
def dpad_up_was_pressed(self) -> bool:
"""
Check if the D-pad was pressed up since the last check.
:return: True if the the D-pad was pressed up since the last check, False otherwise.
"""
return self._button_was_pressed("DPad_Up")
def dpad_down_pressed(self) -> bool:
"""
Check if the D-pad is pressed down.
:return: True if the D-pad is pressed down, False otherwise.
"""
return self._button_pressed("DPad_Down", self.this_controller.getPOV() == 180)
def dpad_down_was_pressed(self) -> bool:
"""
Check if the D-pad was pressed down since the last check.
:return: True if the the D-pad was pressed down since the last check, False otherwise.
"""
return self._button_was_pressed("DPad_Down")
def dpad_left_pressed(self) -> bool:
"""
Check if the D-pad is pressed left.
:return: True if the D-pad is pressed left, False otherwise.
"""
return self._button_pressed("DPad_Left", self.this_controller.getPOV() == 270)
def dpad_left_was_pressed(self) -> bool:
"""
Check if the D-pad was pressed left since the last check.
:return: True if the the D-pad was pressed left since the last check, False otherwise.
"""
return self._button_was_pressed("DPad_Left")
def dpad_right_pressed(self) -> bool:
"""
Check if the D-pad is pressed right.
:return: True if the D-pad is pressed right, False otherwise.
"""
return self._button_pressed("DPad_Right", self.this_controller.getPOV() == 90)
def dpad_right_was_pressed(self) -> bool:
"""
Check if the D-pad was pressed right since the last check.
:return: True if the the D-pad was pressed right since the last check, False otherwise.
"""
return self._button_was_pressed("DPad_Right")
def left_bumper_pressed(self) -> bool:
"""
Check if the left bumper is pressed.
:return: True if the left bumper is pressed, False otherwise.
"""
return self._button_pressed("Left_Bumper", self.this_controller.getLeftBumper())
def left_bumper_was_pressed(self) -> bool:
"""
Check if the left bumper was pressed since the last check.
:return: True if the left bumper was pressed since the last check, False otherwise.
"""
return self._button_was_pressed("Left_Bumper")
def right_bumper_pressed(self) -> bool:
"""
Check if the right bumper is pressed.
:return: True if the right bumper is pressed, False otherwise.
"""
return self._button_pressed(
"Right_Bumper", self.this_controller.getRightBumper()
)
def right_bumper_was_pressed(self) -> bool:
"""
Check if the right bumper was pressed since the last check.
:return: True if the right bumper was pressed since the last check, False otherwise.
"""
return self._button_was_pressed("Right_Bumper")
def left_trigger_pressed(self) -> float:
"""
Get the value of the left trigger.
:return: The value of the left trigger, ranging from 0.0 to 1.0.
"""
self._button_pressed(
"Left_Trigger", self.this_controller.getLeftTriggerAxis() >= 0.5
)
return self.this_controller.getLeftTriggerAxis()
def left_trigger_was_pressed(self) -> bool:
"""
Check if the left trigger was pressed since the last check.
:return: True if the left trigger was pressed since the last check, False otherwise.
"""
return self._button_was_pressed("Left_Trigger")
def right_trigger_pressed(self) -> float:
"""
Get the value of the right trigger.
:return: The value of the right trigger, ranging from 0.0 to 1.0.
"""
self._button_pressed(
"Right_Trigger", self.this_controller.getRightTriggerAxis() >= 0.5
)
return self.this_controller.getRightTriggerAxis()
def right_trigger_was_pressed(self) -> bool:
"""
Check if the right trigger was pressed since the last check.
:return: True if the right trigger was pressed since the last check, False otherwise.
"""
return self._button_was_pressed("Right_Trigger")
def start_button_pressed(self) -> bool:
"""
Check if the start button is pressed.
:return: True if the start button is pressed, False otherwise.
"""
return self._button_pressed("Start", self.this_controller.getStartButton())
def start_button_was_pressed(self) -> bool:
"""
Check if the start button was pressed since the last check.
:return: True if the start button was pressed since the last check, False otherwise.
"""
return self._button_was_pressed("Start")
def back_button_pressed(self) -> bool:
"""
Check if the back button is pressed.
:return: True if the back button is pressed, False otherwise.
"""
return self._button_pressed("Back", self.this_controller.getBackButton())
def back_button_was_pressed(self) -> bool:
"""
Check if the back button was pressed since the last check.
:return: True if the back button was pressed since the last check, False otherwise.
"""
return self._button_was_pressed("Back")

316
components/drive.py Normal file
View File

@ -0,0 +1,316 @@
"""
components/drive.py – DriveSubsystem ported to MagicBot component.
Owns four SwerveModule instances. All swerve math uses standard wpimath.
Integrates with PathPlannerLib and Limelight vision.
"""
import math
import os
import magicbot
from pathplannerlib.auto import AutoBuilder
from pathplannerlib.config import PIDConstants, RobotConfig
from pathplannerlib.controller import PPHolonomicDriveController
from pathplannerlib.logging import PathPlannerLogging
from phoenix6.hardware import Pigeon2
from wpilib import DriverStation, RobotState, SendableChooser, SmartDashboard, getDeployDirectory
from wpimath.estimator import SwerveDrive4PoseEstimator
from wpimath.geometry import Pose2d, Pose3d
from wpimath.kinematics import (
ChassisSpeeds,
SwerveDrive4Kinematics,
)
import constants
import LimelightHelpers
from components.swerve_module import SwerveModule
class DriveComponent:
"""
MagicBot component that drives four swerve modules and fuses vision
measurements via a SwerveDrive4PoseEstimator.
"""
def setup(self) -> None:
# ── Swerve modules ─────────────────────────────────────────────────
self._front_left = SwerveModule(
name="Front Left",
drive_id=constants.SwerveDriveIDs.FRONT_LEFT_DRIVE_ID,
steer_id=constants.SwerveDriveIDs.FRONT_LEFT_STEER_ID,
encoder_id=constants.SwerveDriveIDs.FRONT_LEFT_ENCODER_ID,
steer_offset_rotations=constants.SwerveDriveOffsets.FRONT_LEFT_MOTOR_OFFSET,
drive_inverted=False,
canbus="Drive",
)
self._front_right = SwerveModule(
name="Front Right",
drive_id=constants.SwerveDriveIDs.FRONT_RIGHT_DRIVE_ID,
steer_id=constants.SwerveDriveIDs.FRONT_RIGHT_STEER_ID,
encoder_id=constants.SwerveDriveIDs.FRONT_RIGHT_ENCODER_ID,
steer_offset_rotations=constants.SwerveDriveOffsets.FRONT_RIGHT_MOTOR_OFFSET,
drive_inverted=True, # Front Right is inverted
canbus="Drive",
)
self._back_left = SwerveModule(
name="Back Left",
drive_id=constants.SwerveDriveIDs.BACK_LEFT_DRIVE_ID,
steer_id=constants.SwerveDriveIDs.BACK_LEFT_STEER_ID,
encoder_id=constants.SwerveDriveIDs.BACK_LEFT_ENCODER_ID,
steer_offset_rotations=constants.SwerveDriveOffsets.BACK_LEFT_MOTOR_OFFSET,
drive_inverted=False,
canbus="Drive",
)
self._back_right = SwerveModule(
name="Back Right",
drive_id=constants.SwerveDriveIDs.BACK_RIGHT_DRIVE_ID,
steer_id=constants.SwerveDriveIDs.BACK_RIGHT_STEER_ID,
encoder_id=constants.SwerveDriveIDs.BACK_RIGHT_ENCODER_ID,
steer_offset_rotations=constants.SwerveDriveOffsets.BACK_RIGHT_MOTOR_OFFSET,
drive_inverted=True, # Back Right is inverted
canbus="Drive",
)
self._modules = [
self._front_left,
self._front_right,
self._back_left,
self._back_right,
]
# ── Kinematics ─────────────────────────────────────────────────────
self._kinematics = SwerveDrive4Kinematics(
constants.SwerveDriveModuleLocations.FRONT_LEFT,
constants.SwerveDriveModuleLocations.FRONT_RIGHT,
constants.SwerveDriveModuleLocations.BACK_LEFT,
constants.SwerveDriveModuleLocations.BACK_RIGHT,
)
# ── Gyro ───────────────────────────────────────────────────────────
self._pigeon = Pigeon2(constants.ExtraIDEntities.PIGEON_ID, "Drive")
# ── Pose estimator ─────────────────────────────────────────────────
self._pose_estimator = SwerveDrive4PoseEstimator(
self._kinematics,
self._pigeon.getRotation2d(),
self._module_positions(),
Pose2d(),
)
# 3 std-dev values: (x_std, y_std, heading_std)
self._pose_estimator.setVisionMeasurementStdDevs((0.7, 0.7, 0.1))
# ── Desired chassis speeds (reset each loop) ────────────────────────
self._desired_speeds = ChassisSpeeds()
# ── PathPlanner target pose (for @feedback) ────────────────────────
self._pp_target_pose = Pose2d()
# ── PathPlanner / AutoBuilder ──────────────────────────────────────
# _DriveSubsystemAdapter is imported from robot.py at runtime to
# avoid circular imports; it is passed in via inject_drive_adapter().
self._drive_adapter = None
self._auto_chooser: SendableChooser | None = None
def inject_drive_adapter(self, adapter, robot_config: RobotConfig) -> None:
"""
Called by robot.py after all objects are constructed.
Finalises AutoBuilder configuration and builds the auto chooser.
"""
AutoBuilder.configure(
self.get_robot_pose_2d, # pose supplier
self.set_pose, # pose resetter
self.get_robot_relative_speeds, # speeds supplier
lambda speeds, ff: self.set_chassis_speeds(speeds), # consumer (no negation)
PPHolonomicDriveController(
PIDConstants(5, 0, 0),
PIDConstants(math.pi, 0, 0),
),
robot_config,
self._is_red_alliance, # flip-path predicate
adapter, # dummy Subsystem
)
# Build auto chooser manually so a bad path file skips that auto instead
# of crashing the entire chooser (PathPlanner throws ZeroDivisionError on
# paths with consecutive rotation targets at the same point).
autos_dir = os.path.join(getDeployDirectory(), "pathplanner", "autos")
self._auto_chooser = SendableChooser()
self._auto_chooser.setDefaultOption("Do Nothing", None)
if os.path.isdir(autos_dir):
for filename in os.listdir(autos_dir):
auto_name = filename.removesuffix(".auto")
try:
self._auto_chooser.addOption(auto_name, AutoBuilder.buildAuto(auto_name))
except Exception as build_err:
SmartDashboard.putString(f"Auto/{auto_name} error", str(build_err))
SmartDashboard.putData("Auto Chooser", self._auto_chooser)
PathPlannerLogging.setLogTargetPoseCallback(
lambda pose: setattr(self, "_pp_target_pose", pose)
)
# ── Public drive API ───────────────────────────────────────────────────────
def set_chassis_speeds_field_relative(
self, vx: float, vy: float, omega: float
) -> None:
"""
Convert joystick inputs (with quadratic scaling) to robot-relative
ChassisSpeeds and store them to be applied in execute().
"""
scaled_vx = vx * abs(vx) * constants.RobotChassisLimits.MAX_ROBOT_LINEAR_VELOCITY
scaled_vy = vy * abs(vy) * constants.RobotChassisLimits.MAX_ROBOT_LINEAR_VELOCITY
scaled_omega = omega * abs(omega) * constants.RobotChassisLimits.MAX_ROBOT_ANGULAR_VELOCITY
self._desired_speeds = ChassisSpeeds.fromFieldRelativeSpeeds(
scaled_vx,
scaled_vy,
scaled_omega,
self._pigeon.getRotation2d(),
)
def set_chassis_speeds(self, speeds: ChassisSpeeds) -> None:
"""Direct robot-relative speed input (used by PathPlanner consumer)."""
self._desired_speeds = speeds
def stop_drive(self) -> None:
self._desired_speeds = ChassisSpeeds()
def rotate_chassis(self, rot_speed: float) -> None:
self._desired_speeds = ChassisSpeeds.fromRobotRelativeSpeeds(
0, 0, rot_speed, self._pigeon.getRotation2d()
)
def get_bot_pose(self) -> Pose3d:
return Pose3d(self._pose_estimator.getEstimatedPosition())
def get_robot_pose_2d(self) -> Pose2d:
return self._pose_estimator.getEstimatedPosition()
def get_robot_relative_speeds(self) -> ChassisSpeeds:
states = self._module_states()
return self._kinematics.toChassisSpeeds(states)
def set_pose(self, pose: Pose2d) -> None:
self._pose_estimator.resetPosition(
self._pigeon.getRotation2d(),
self._module_positions(),
pose,
)
def set_brake_mode(self, brake: bool) -> None:
from phoenix6.configs import MotorOutputConfigs
from phoenix6.signals import NeutralModeValue
mode = NeutralModeValue.BRAKE if brake else NeutralModeValue.COAST
for module in self._modules:
cfg = MotorOutputConfigs().with_neutral_mode(mode)
module._drive_motor.configurator.apply(cfg)
module._steer_motor.configurator.apply(cfg)
def get_auto_command(self):
if self._auto_chooser is not None:
return self._auto_chooser.getSelected()
return None
# ── @feedback properties ───────────────────────────────────────────────────
@magicbot.feedback
def robot_x(self) -> float:
return self._pose_estimator.getEstimatedPosition().X()
@magicbot.feedback
def robot_y(self) -> float:
return self._pose_estimator.getEstimatedPosition().Y()
@magicbot.feedback
def robot_heading_degrees(self) -> float:
return self._pose_estimator.getEstimatedPosition().rotation().degrees()
@magicbot.feedback
def pathplanner_target_x(self) -> float:
return self._pp_target_pose.X()
@magicbot.feedback
def pathplanner_target_y(self) -> float:
return self._pp_target_pose.Y()
# ── MagicBot lifecycle ─────────────────────────────────────────────────────
def execute(self) -> None:
# Apply desired speeds through kinematics
module_states = self._kinematics.toSwerveModuleStates(self._desired_speeds)
# Desaturate wheel speeds to enforce linear velocity limit
SwerveDrive4Kinematics.desaturateWheelSpeeds(
module_states,
constants.RobotChassisLimits.MAX_ROBOT_LINEAR_VELOCITY,
)
# Send states to modules
for module, state in zip(self._modules, module_states):
module.set_desired_state(state)
# Reset desired speeds so the robot stops if nothing sets them this loop
self._desired_speeds = ChassisSpeeds()
# Update pose estimator with gyro + module positions
self._pose_estimator.update(
self._pigeon.getRotation2d(),
self._module_positions(),
)
heading_deg = self.get_robot_pose_2d().rotation().degrees()
# Update Limelight orientation so MegaTag2 knows where the robot is facing
LimelightHelpers.SetRobotOrientation(
constants.CameraConstants.FRONT_CAMERA_NAME, heading_deg, 0, 0, 0, 0, 0
)
LimelightHelpers.SetRobotOrientation(
constants.CameraConstants.BACK_CAMERA_NAME, heading_deg, 0, 0, 0, 0, 0
)
# IMU mode: 1 when disabled (external), 4 when enabled (fused)
imu_mode = 1 if RobotState.isDisabled() else 4
LimelightHelpers.SetIMUMode(constants.CameraConstants.FRONT_CAMERA_NAME, imu_mode)
LimelightHelpers.SetIMUMode(constants.CameraConstants.BACK_CAMERA_NAME, imu_mode)
# MegaTag2 vision fusion
front_estimate = LimelightHelpers.getBotPoseEstimate_wpiBlue_MegaTag2(
constants.CameraConstants.FRONT_CAMERA_NAME
)
back_estimate = LimelightHelpers.getBotPoseEstimate_wpiBlue_MegaTag2(
constants.CameraConstants.BACK_CAMERA_NAME
)
if front_estimate is not None and front_estimate.tagCount >= 2:
self._pose_estimator.addVisionMeasurement(
front_estimate.pose,
front_estimate.timestampSeconds,
)
if back_estimate is not None and back_estimate.tagCount >= 2:
self._pose_estimator.addVisionMeasurement(
back_estimate.pose,
back_estimate.timestampSeconds,
)
# Publish per-module telemetry
for module in self._modules:
module.publish_telemetry()
# ── Helpers ────────────────────────────────────────────────────────────────
def _module_positions(self):
fl, fr, bl, br = self._modules
return (fl.get_position(), fr.get_position(), bl.get_position(), br.get_position())
def _module_states(self):
fl, fr, bl, br = self._modules
return (fl.get_state(), fr.get_state(), bl.get_state(), br.get_state())
@staticmethod
def _is_red_alliance() -> bool:
alliance = DriverStation.getAlliance()
if alliance:
return alliance == DriverStation.Alliance.kRed
return False

82
components/hopper.py Normal file
View File

@ -0,0 +1,82 @@
"""
components/hopper.py – HopperSubsystem ported to MagicBot component.
Hardware:
- TalonFX spindexer motor
- TalonFX kicker motor
"""
import magicbot
from phoenix6.configs import MotorOutputConfigs, Slot0Configs, TalonFXConfiguration
from phoenix6.hardware import TalonFX
import constants
class HopperComponent:
"""MagicBot component that controls the hopper (spindexer + kicker)."""
def setup(self) -> None:
"""
MagicBot lifecycle hook — instantiate and configure all hopper hardware.
Called once by MagicBot after all component objects are created but
before the robot enters any enabled mode.
"""
# ── Spindexer ──────────────────────────────────────────────────────
self._spindexer_motor = TalonFX(constants.HopperConstants.SPINDEXER_MOTOR_ID)
spindexer_cfg = TalonFXConfiguration()
spindexer_cfg.motor_output = MotorOutputConfigs().with_inverted(
constants.HopperConstants.SPINDEXER_MOTOR_DIRECTION
)
spindexer_cfg.slot0 = Slot0Configs().with_k_p(0).with_k_i(0).with_k_d(0).with_k_s(0)
self._spindexer_motor.configurator.apply(spindexer_cfg)
# ── Kicker ─────────────────────────────────────────────────────────
self._kicker_motor = TalonFX(constants.HopperConstants.KICKER_MOTOR_ID)
kicker_cfg = TalonFXConfiguration()
kicker_cfg.motor_output = MotorOutputConfigs().with_inverted(
constants.HopperConstants.KICKER_MOTOR_DIRECTION
)
kicker_cfg.slot0 = Slot0Configs().with_k_p(0).with_k_i(0).with_k_d(0).with_k_s(0)
self._kicker_motor.configurator.apply(kicker_cfg)
self._kicker_velocity = self._kicker_motor.get_velocity()
# ── Public API ─────────────────────────────────────────────────────────────
def feed_kicker(self) -> None:
"""Drive the kicker motor forward at 75 % output to push a note into the launcher."""
self._kicker_motor.set(0.75)
def reverse_kicker(self) -> None:
"""Drive the kicker motor in reverse at 50 % output to un-jam a stuck note."""
self._kicker_motor.set(-0.5)
def stop_kicker(self) -> None:
"""Stop the kicker motor."""
self._kicker_motor.stop_motor()
def spindexer_on(self) -> None:
"""Drive the spindexer forward at 75 % output to advance notes toward the kicker."""
self._spindexer_motor.set(0.75)
def spindexer_reverse(self) -> None:
"""Drive the spindexer in reverse at 75 % output to clear a jam."""
self._spindexer_motor.set(-0.75)
def spindexer_off(self) -> None:
"""Stop the spindexer motor."""
self._spindexer_motor.stop_motor()
# ── Telemetry ──────────────────────────────────────────────────────────────
@magicbot.feedback
def kicker_speed_rps(self) -> float:
return self._kicker_velocity.refresh().value_as_double
# ── MagicBot lifecycle ─────────────────────────────────────────────────────
def execute(self) -> None:
# No periodic logic required; all control is driven by external callers.
pass

84
components/intake.py Normal file
View File

@ -0,0 +1,84 @@
"""
components/intake.py – IntakeSubsystem ported to MagicBot component.
Hardware:
- TalonFX intake motor (Phoenix 6)
- WPI_TalonSRX deploy motor (Phoenix 5) with forward/reverse limit switches
"""
import wpilib
from phoenix5 import LimitSwitchNormal, LimitSwitchSource, WPI_TalonSRX
from phoenix6.configs import MotorOutputConfigs, TalonFXConfiguration
from phoenix6.hardware import TalonFX
import constants
class IntakeComponent:
"""MagicBot component that controls the intake and deploy mechanism."""
def setup(self) -> None:
# ── Intake motor (Phoenix 6 TalonFX) ───────────────────────────────
self._intake_motor = TalonFX(constants.IntakeConstants.INTAKE_MOTOR_ID)
intake_cfg = TalonFXConfiguration()
intake_cfg.motor_output = MotorOutputConfigs().with_inverted(
constants.IntakeConstants.INTAKE_MOTOR_DIRECTION
)
self._intake_motor.configurator.apply(intake_cfg)
# ── Deploy motor (Phoenix 5 WPI_TalonSRX) ──────────────────────────
self._deploy_motor = WPI_TalonSRX(constants.IntakeConstants.DEPLOY_MOTOR_ID)
self._deploy_motor.configForwardLimitSwitchSource(
LimitSwitchSource.FeedbackConnector, LimitSwitchNormal.NormallyOpen
)
self._deploy_motor.configReverseLimitSwitchSource(
LimitSwitchSource.FeedbackConnector, LimitSwitchNormal.NormallyOpen
)
self._deploy_motor.setInverted(constants.IntakeConstants.DEPLOY_MOTOR_INVERTED)
# Controller reference — injected by robot.py after setup
self._controller: wpilib.XboxController | None = None
# ── Public API ─────────────────────────────────────────────────────────────
def set_controller(self, controller: wpilib.XboxController) -> None:
"""Inject the driver Xbox controller for rumble feedback."""
self._controller = controller
def deploy(self) -> None:
"""Run the deploy motor forward briefly (call once; timing handled externally)."""
self._deploy_motor.set(1.0)
def stop_deploy(self) -> None:
self._deploy_motor.stopMotor()
def reset_deploy(self) -> None:
"""Retract the intake deploy mechanism."""
self._deploy_motor.set(-1.0)
def set_speed(self, speed: float) -> None:
self._intake_motor.set(speed)
def stop_intake(self) -> None:
self._intake_motor.stopMotor()
if self._controller is not None:
self._controller.setRumble(wpilib.XboxController.RumbleType.kBothRumble, 0.0)
def intake(self) -> None:
"""Toggle intake on/off with controller rumble feedback."""
if self._intake_motor.get() == 0:
self.set_speed(-0.5)
if self._controller is not None:
self._controller.setRumble(wpilib.XboxController.RumbleType.kBothRumble, 0.5)
else:
self._intake_motor.stopMotor()
if self._controller is not None:
self._controller.setRumble(wpilib.XboxController.RumbleType.kBothRumble, 0.0)
def reverse_intake(self) -> None:
self.set_speed(0.5)
# ── MagicBot lifecycle ─────────────────────────────────────────────────────
def execute(self) -> None:
pass

155
components/swerve_module.py Normal file
View File

@ -0,0 +1,155 @@
"""
components/swerve_module.py – Plain Python class (NOT a MagicBot component).
Four instances are owned by DriveComponent. Encapsulates all per-module hardware:
- phoenix6 TalonFX drive motor
- phoenix6 TalonFX steer motor
- phoenix6 CANcoder
"""
import math
import ntcore
from phoenix6.configs import (
CANcoderConfiguration,
FeedbackConfigs,
MagnetSensorConfigs,
MotorOutputConfigs,
Slot0Configs,
TalonFXConfiguration,
)
from phoenix6.controls import PositionVoltage, VelocityVoltage
from phoenix6.hardware import CANcoder, TalonFX
from phoenix6.signals import (
FeedbackSensorSourceValue,
InvertedValue,
SensorDirectionValue,
)
from wpimath.geometry import Rotation2d
from wpimath.kinematics import SwerveModulePosition, SwerveModuleState
# Physical constants shared by all modules
GEAR_RATIO: float = 8.14
WHEEL_DIAMETER_METERS: float = 0.1016 # 4 inches
WHEEL_CIRCUMFERENCE_METERS: float = WHEEL_DIAMETER_METERS * 3.14159265358979
class SwerveModule:
"""
Plain Python class (not a MagicBot component).
Encapsulates a single swerve module: drive motor, steer motor, CANcoder.
"""
def __init__(
self,
name: str,
drive_id: int,
steer_id: int,
encoder_id: int,
steer_offset_rotations: float,
drive_inverted: bool,
canbus: str = "Drive",
) -> None:
self._name = name
# ── CANcoder ───────────────────────────────────────────────────────
self._encoder = CANcoder(encoder_id, canbus)
encoder_cfg = CANcoderConfiguration()
encoder_cfg.magnet_sensor = (
MagnetSensorConfigs()
.with_magnet_offset(steer_offset_rotations)
.with_sensor_direction(SensorDirectionValue.COUNTER_CLOCKWISE_POSITIVE)
)
self._encoder.configurator.apply(encoder_cfg)
# ── Drive motor (TalonFX) ──────────────────────────────────────────
self._drive_motor = TalonFX(drive_id, canbus)
drive_cfg = TalonFXConfiguration()
drive_cfg.slot0 = (
Slot0Configs()
.with_k_p(0.1)
.with_k_i(0.0)
.with_k_d(0.01)
.with_k_v(0.2)
)
drive_cfg.motor_output = MotorOutputConfigs().with_inverted(
InvertedValue.CLOCKWISE_POSITIVE if drive_inverted else InvertedValue.COUNTER_CLOCKWISE_POSITIVE
)
self._drive_motor.configurator.apply(drive_cfg)
self._drive_velocity_signal = self._drive_motor.get_velocity()
self._drive_position_signal = self._drive_motor.get_position()
# ── Steer motor (TalonFX) ──────────────────────────────────────────
self._steer_motor = TalonFX(steer_id, canbus)
steer_cfg = TalonFXConfiguration()
steer_cfg.slot0 = (
Slot0Configs()
.with_k_p(20)
.with_k_i(0)
.with_k_d(0.0)
.with_k_v(0)
)
# Use the CANcoder as the absolute feedback source for the steer motor
steer_cfg.feedback = (
FeedbackConfigs()
.with_feedback_remote_sensor_id(encoder_id)
.with_feedback_sensor_source(FeedbackSensorSourceValue.REMOTE_CANCODER)
)
self._steer_motor.configurator.apply(steer_cfg)
self._steer_position_signal = self._steer_motor.get_position()
# Controls
self._drive_control = VelocityVoltage(0)
self._steer_control = PositionVoltage(0)
# Desired state (for telemetry)
self._desired_state = SwerveModuleState(0, Rotation2d())
# ── NetworkTables telemetry entries ────────────────────────────────
inst = ntcore.NetworkTableInstance.getDefault()
tbl = inst.getTable(f"SwerveModules/{name}")
self._nt_desired_speed = tbl.getDoubleTopic("desired_speed_mps").publish()
self._nt_desired_angle = tbl.getDoubleTopic("desired_angle_deg").publish()
self._nt_actual_speed = tbl.getDoubleTopic("actual_speed_mps").publish()
self._nt_actual_angle = tbl.getDoubleTopic("actual_angle_deg").publish()
# ── Public API ─────────────────────────────────────────────────────────────
def set_desired_state(self, state: SwerveModuleState) -> None:
"""Optimize the state and apply drive velocity + steer position controls."""
state.optimize(self._get_steer_angle()) # mutates in-place; returns None in WPILib 2026
self._desired_state = state
# Drive: convert m/s → rotations/s
drive_rps = (state.speed / WHEEL_CIRCUMFERENCE_METERS) * GEAR_RATIO
self._drive_motor.set_control(self._drive_control.with_velocity(drive_rps))
# Steer: position in rotations (CANcoder feedback is in rotations)
steer_rotations = state.angle.radians() / (2 * math.pi)
self._steer_motor.set_control(self._steer_control.with_position(steer_rotations))
def get_state(self) -> SwerveModuleState:
"""Return current speed (m/s) and angle from motor signals."""
velocity_rps = self._drive_velocity_signal.refresh().value_as_double
speed_mps = (velocity_rps / GEAR_RATIO) * WHEEL_CIRCUMFERENCE_METERS
return SwerveModuleState(speed_mps, self._get_steer_angle())
def get_position(self) -> SwerveModulePosition:
"""Return current distance (m) and angle for odometry update."""
position_rotations = self._drive_position_signal.refresh().value_as_double
distance_meters = (position_rotations / GEAR_RATIO) * WHEEL_CIRCUMFERENCE_METERS
return SwerveModulePosition(distance_meters, self._get_steer_angle())
def publish_telemetry(self) -> None:
"""Write module telemetry to NetworkTables. Called each loop by DriveComponent."""
actual = self.get_state()
self._nt_desired_speed.set(self._desired_state.speed)
self._nt_desired_angle.set(self._desired_state.angle.degrees())
self._nt_actual_speed.set(actual.speed)
self._nt_actual_angle.set(actual.angle.degrees())
# ── Helpers ────────────────────────────────────────────────────────────────
def _get_steer_angle(self) -> Rotation2d:
rotations = self._steer_position_signal.refresh().value_as_double
return Rotation2d.fromRotations(rotations)

233
components/turret.py Normal file
View File

@ -0,0 +1,233 @@
"""
components/turret.py – TurretSubsystem ported to MagicBot component.
Hardware:
- TalonFX turret motor (Phoenix 6)
- SparkMax launcher motor (REV, brushless)
- DigitalInput limit switch
- Servo hood
"""
import enum
import math
import magicbot
import wpilib
from phoenix6.configs import (
CurrentLimitsConfigs,
FeedbackConfigs,
MotorOutputConfigs,
Slot0Configs,
SoftwareLimitSwitchConfigs,
TalonFXConfiguration,
)
from phoenix6.controls import PositionVoltage
from phoenix6.hardware import TalonFX
from phoenix6.signals import InvertedValue
from rev import (
ClosedLoopSlot,
FeedbackSensor,
PersistMode,
ResetMode,
SparkBase,
SparkLowLevel,
SparkMax,
SparkMaxConfig,
)
from wpimath.geometry import Pose3d, Rotation2d
import constants
class TurretState(enum.Enum):
INIT = enum.auto()
HOMING = enum.auto()
READY = enum.auto()
class TurretComponent:
"""MagicBot component that controls the turret, launcher, and hood."""
def setup(self) -> None:
# ── State ──────────────────────────────────────────────────────────
self._turret_state = TurretState.INIT
self._turret_offset_rotations: float = 0.0
self._forward_limit: float = 0.0
self._reverse_limit: float = 0.0
self._launcher_setpoint_rpm: float = 0.0
# ── Limit switch ───────────────────────────────────────────────────
self._turret_limit_switch = wpilib.DigitalInput(
constants.ShooterConstants.TURRET_LIMIT_CHANNEL
)
# ── Turret motor (TalonFX) ─────────────────────────────────────────
self._turret_motor = TalonFX(constants.ShooterConstants.TURRET_MOTOR_ID)
turret_cfg = TalonFXConfiguration()
turret_cfg.slot0 = (
Slot0Configs()
.with_k_p(6)
.with_k_i(0)
.with_k_d(0.3)
.with_k_s(0)
)
turret_cfg.motor_output = MotorOutputConfigs().with_inverted(
InvertedValue.CLOCKWISE_POSITIVE
)
turret_cfg.current_limits = CurrentLimitsConfigs().with_stator_current_limit(50)
turret_cfg.software_limit_switch = (
SoftwareLimitSwitchConfigs()
.with_forward_soft_limit_enable(False)
.with_reverse_soft_limit_enable(False)
)
turret_cfg.feedback = FeedbackConfigs().with_feedback_rotor_offset(0)
self._turret_motor.configurator.apply(turret_cfg)
self._turret_position_signal = self._turret_motor.get_position()
self._turret_error_signal = self._turret_motor.get_closed_loop_error()
self._turret_control = PositionVoltage(0)
# ── Launcher motor (SparkMax / REV NEO) ────────────────────────────
self._launcher_motor = SparkMax(
constants.ShooterConstants.LAUNCHER_MOTOR_ID,
SparkLowLevel.MotorType.kBrushless,
)
launcher_cfg = SparkMaxConfig()
# Apply equivalent of REV_NEO preset manually
launcher_cfg.inverted(constants.ShooterConstants.LAUNCHER_MOTOR_INVERTED)
launcher_cfg.voltageCompensation(10)
launcher_cfg.closedLoop.P(0.0002).I(0.000001).D(0.0005)
launcher_cfg.closedLoop.feedForward.kS(0.2).kV(0.0).kA(0.0)
launcher_cfg.closedLoop.setFeedbackSensor(FeedbackSensor.kPrimaryEncoder)
launcher_cfg.closedLoop.positionWrappingEnabled(False)
self._launcher_motor.configure(
launcher_cfg,
ResetMode.kResetSafeParameters,
PersistMode.kPersistParameters,
)
self._launcher_controller = self._launcher_motor.getClosedLoopController()
# ── Hood servo ─────────────────────────────────────────────────────
self._hood = wpilib.Servo(constants.ShooterConstants.HOOD_SERVO_CHANNEL)
self._turret_target_pose: Pose3d = Pose3d()
# ── Public API ─────────────────────────────────────────────────────────────
def set_launcher(self, speed_rpm: float) -> None:
self._launcher_setpoint_rpm = speed_rpm
if speed_rpm == 0:
self._launcher_motor.stopMotor()
return
self._launcher_controller.setSetpoint(
speed_rpm, SparkBase.ControlType.kVelocity, ClosedLoopSlot.kSlot0
)
def is_launcher_up_to_speed(self) -> bool:
# Currently always returns True (same as Java source)
return True
def set_target_elevation(self, elev: float) -> None:
pos = abs((elev - 300) / 300)
self._hood.set(pos)
def home_turret(self) -> None:
if self._turret_state != TurretState.INIT:
return
self._turret_motor.set(constants.ShooterConstants.HOMING_SPEED)
self._turret_state = TurretState.HOMING
def is_turret_ready(self) -> bool:
return self._turret_state == TurretState.READY
def get_turret_angle_robot_relative(self) -> float:
return self._get_turret_motor_position() / constants.ShooterConstants.TURRET_GEAR_RATIO
def set_turret_target_pose(self, target_pose: Pose3d) -> None:
self._turret_target_pose = target_pose
def get_turret_target_pose(self) -> Pose3d:
return self._turret_target_pose
def set_turret_field_relative(self, robot_pose, pos: float) -> None:
"""Set turret angle field-relative, accounting for robot heading."""
if self._turret_state != TurretState.READY:
return
corrected_pos = -pos # Flip angle to match CCW positive
robot_rotations = robot_pose.rotation().toRotation2d().radians() / (2 * math.pi)
turret_angle = (
Rotation2d.fromRotations(corrected_pos)
+ Rotation2d.fromRotations(robot_rotations)
- Rotation2d.fromRotations(0.5)
)
self.set_turret_robot_relative(turret_angle.radians() / (2 * math.pi))
def set_turret_robot_relative(self, pos: float) -> None:
if self._turret_state != TurretState.READY:
return
corrected_pos = -pos # Flip angle to match CCW positive
self.set_turret_motor_rotation(
corrected_pos * constants.ShooterConstants.TURRET_GEAR_RATIO
)
def set_turret_motor_rotation(self, pos: float) -> None:
pos_with_offset = pos + self._turret_offset_rotations
self._turret_motor.set_control(self._turret_control.with_position(pos_with_offset))
def is_turret_at_target(self) -> bool:
return abs(self._turret_error_signal.refresh().value_as_double) <= 0.1
# ── Feedback properties ────────────────────────────────────────────────────
@magicbot.feedback
def turret_state(self) -> str:
return self._turret_state.name
@magicbot.feedback
def turret_angle_robot_relative(self) -> float:
return self.get_turret_angle_robot_relative()
@magicbot.feedback
def turret_position_error(self) -> float:
return self._turret_error_signal.refresh().value_as_double
@magicbot.feedback
def launcher_speed_rpm(self) -> float:
return self._launcher_motor.getEncoder().getVelocity()
@magicbot.feedback
def turret_limit_switch(self) -> bool:
return self._turret_limit_switch.get()
# ── MagicBot lifecycle ─────────────────────────────────────────────────────
def execute(self) -> None:
# Homing state machine
if self._turret_state == TurretState.HOMING:
if self._turret_limit_switch.get(): # Switch tripped → homed
self._turret_motor.stopMotor()
raw_pos = self._turret_position_signal.refresh().value_as_double
if wpilib.RobotBase.isReal():
self._turret_offset_rotations = raw_pos - 1.323
else:
self._turret_offset_rotations = 0.0
self._turret_state = TurretState.READY
self._forward_limit = 1.5 + self._turret_offset_rotations
self._reverse_limit = -1.5 + self._turret_offset_rotations
# Enable software limits now that homing is complete
limit_cfg = (
SoftwareLimitSwitchConfigs()
.with_forward_soft_limit_enable(True)
.with_forward_soft_limit_threshold(self._forward_limit)
.with_reverse_soft_limit_enable(True)
.with_reverse_soft_limit_threshold(self._reverse_limit)
)
self._turret_motor.configurator.apply(limit_cfg)
# ── Helpers ────────────────────────────────────────────────────────────────
def _get_turret_motor_position(self) -> float:
return self._turret_position_signal.refresh().value_as_double - self._turret_offset_rotations

202
constants.py Normal file
View File

@ -0,0 +1,202 @@
"""
constants.py – robot-wide constants for Castor 2026 (Python/MagicBot port).
All Java "public static final" fields become Python class-level attributes.
"""
import enum
import math
from phoenix6.signals import InvertedValue
from robotpy_apriltag import AprilTagField, AprilTagFieldLayout
from wpimath.geometry import (
Pose3d,
Rotation3d,
Transform3d,
Translation2d,
Translation3d,
)
class OperatorConstants:
CONTROLLER_PORT: int = 0
class CameraConstants:
class IDFilters:
RED_HUB_SHOOTING_IDS: list[int] = [8, 9, 10, 11]
BLUE_HUB_SHOOTING_IDS: list[int] = [27, 26, 25, 24]
BACK_CAMERA_NAME: str = "limelight-back"
BACK_LOCALIZATION_PIPELINE_NUMBER: int = 0
BACK_HUB_TRACKING_2D_PIPELINE_NUMBER: int = 1
BACK_HUB_TRACKING_3D_PIPELINE_NUMBER: int = 2
FRONT_CAMERA_NAME: str = "limelight-front"
FRONT_LOCALIZATION_PIPELINE_NUMBER: int = 0
BLUE_ZONE_COOR: list[float] = [0, 3.5]
RED_ZONE_COOR: list[float] = [13, 16]
NEUTRAL_ZONE_COOR: list[float] = [5.5, 11]
NEUTRAL1_ZONE_COOR: list[float] = [5.5, 11, 0, 3]
NEUTRAL2_ZONE_COOR: list[float] = [5.5, 11, 5, 8]
BLUE1_ZONE_COOR: list[float] = [0, 3.5, 0, 3]
BLUE2_ZONE_COOR: list[float] = [0, 3.5, 5, 8]
RED1_ZONE_COOR: list[float] = [13, 16, 0, 3]
RED2_ZONE_COOR: list[float] = [13, 16, 5, 8]
# Swerve encoder offsets as of 3/2/26
class SwerveDriveOffsets:
FRONT_LEFT_MOTOR_OFFSET: float = -0.336426
FRONT_RIGHT_MOTOR_OFFSET: float = 0.108887
BACK_LEFT_MOTOR_OFFSET: float = -0.492432
BACK_RIGHT_MOTOR_OFFSET: float = 0.470215
# CAN ID numbering convention:
# 1x = Front Left, 2x = Front Right, 3x = Back Left, 4x = Back Right
# Single-digit IDs are non-motor devices (e.g. Pigeon2)
class SwerveDriveIDs:
FRONT_LEFT_STEER_ID: int = 11
FRONT_LEFT_DRIVE_ID: int = 12
FRONT_LEFT_ENCODER_ID: int = 13
FRONT_RIGHT_STEER_ID: int = 21
FRONT_RIGHT_DRIVE_ID: int = 22
FRONT_RIGHT_ENCODER_ID: int = 23
BACK_LEFT_STEER_ID: int = 31
BACK_LEFT_DRIVE_ID: int = 32
BACK_LEFT_ENCODER_ID: int = 33
BACK_RIGHT_STEER_ID: int = 41
BACK_RIGHT_DRIVE_ID: int = 42
BACK_RIGHT_ENCODER_ID: int = 43
class RobotChassisLimits:
# Max linear velocity (m/s)
MAX_ROBOT_LINEAR_VELOCITY: float = 3.0
# Max linear acceleration (m/s²)
MAX_ROBOT_LINEAR_VELOCITY_ACCEL: float = 2.0
# Max angular velocity (rad/s)
MAX_ROBOT_ANGULAR_VELOCITY: float = math.pi
# Max angular acceleration (rad/s²)
MAX_ROBOT_ANGULAR_VELOCITY_ACCEL: float = math.pi
class ExtraIDEntities:
PIGEON_ID: int = 1
class IntakeConstants:
DEPLOY_MOTOR_ID: int = 17
DEPLOY_MOTOR_INVERTED: bool = True # TODO: Test this
INTAKE_MOTOR_ID: int = 2
INTAKE_MOTOR_DIRECTION: InvertedValue = InvertedValue.CLOCKWISE_POSITIVE # TODO: Test this
class ShooterConstants:
LAUNCHER_MOTOR_ID: int = 3
LAUNCHER_MOTOR_INVERTED: bool = False
TURRET_MOTOR_ID: int = 4
TURRET_MOTOR_DIRECTION: InvertedValue = InvertedValue.CLOCKWISE_POSITIVE
TURRET_LIMIT_CHANNEL: int = 9
# Plain floats (degrees) replacing edu.wpi.first.units.Degrees.of(x)
LIMIT_AZIMUTH_POS: float = 165.0 # degrees
LIMIT_AZIMUTH_NEG: float = 5.0 # degrees
HOMING_SPEED: float = 0.05
TURRET_GEAR_RATIO: float = 6.0
HOOD_SERVO_CHANNEL: int = 0
HOOD_SERVO_INVERTED: bool = False # TODO: Test this
HOOD_ENCODER_ID: int = 4
TARGET_ELEVATION_DIF: float = 1.0 # degrees
LIMIT_ELEVATION_POS: float = 90.0 # degrees
LIMIT_ELEVATION_NEG: float = 0.0 # degrees
DEFLECTOR_STORED_ANGLE: float = 0.0
DEFLECTOR_SET_ANGLE1: float = 100.0
DEFLECTOR_SET_ANGLE2: float = 200.0
DEFLECTOR_SET_ANGLE3: float = 300.0
DEFLECTOR_SERVO_RATIO: float = 1.0
class HopperConstants:
KICKER_MOTOR_ID: int = 9
KICKER_MOTOR_DIRECTION: InvertedValue = InvertedValue.CLOCKWISE_POSITIVE
SPINDEXER_MOTOR_ID: int = 8
SPINDEXER_MOTOR_DIRECTION: InvertedValue = InvertedValue.COUNTER_CLOCKWISE_POSITIVE
# ── Swerve module Translation2d locations ────────────────────────────────────
# Robot is 22" × 22"; modules are at the corners.
# Half of 22" = 11" = 0.2794 m from robot centre to each module.
# WPILib convention: +X is forward, +Y is to the left.
class SwerveDriveModuleLocations:
_HALF: float = 0.2794 # metres (11 inches)
FRONT_LEFT: Translation2d = Translation2d( _HALF, _HALF)
FRONT_RIGHT: Translation2d = Translation2d( _HALF, -_HALF)
BACK_LEFT: Translation2d = Translation2d(-_HALF, _HALF)
BACK_RIGHT: Translation2d = Translation2d(-_HALF, -_HALF)
# ── April-tag field layout (loaded once at import time) ───────────────────────
_field_tags = AprilTagFieldLayout.loadField(AprilTagField.k2026RebuiltWelded)
class FieldPoses:
BLUE_HUB_POSE: Pose3d = _field_tags.getTagPose(26).transformBy(
Transform3d(Translation3d(-0.5207, 0, 0), Rotation3d())
)
RED_HUB_POSE: Pose3d = _field_tags.getTagPose(10).transformBy(
Transform3d(Translation3d(-0.5207, 0, 0), Rotation3d())
)
# Passing targets — left/right from driver's perspective;
# origin at blue side of field; rotations are zero (don't matter).
BLUE_LEFT_ALLIANCE_ZONE_PASS_TARGET_POSE: Pose3d = Pose3d(1.99, 6.35, 0, Rotation3d())
BLUE_RIGHT_ALLIANCE_ZONE_PASS_TARGET_POSE: Pose3d = Pose3d(1.99, 1.72, 0, Rotation3d())
BLUE_LEFT_NEUTRAL_ZONE_PASS_TARGET_POSE: Pose3d = Pose3d(8.27, 6.35, 0, Rotation3d())
BLUE_RIGHT_NEUTRAL_ZONE_PASS_TARGET_POSE: Pose3d = Pose3d(8.27, 1.72, 0, Rotation3d())
RED_LEFT_ALLIANCE_ZONE_PASS_TARGET_POSE: Pose3d = Pose3d(14.55, 1.72, 0, Rotation3d())
RED_RIGHT_ALLIANCE_ZONE_PASS_TARGET_POSE: Pose3d = Pose3d(14.55, 6.35, 0, Rotation3d())
RED_LEFT_NEUTRAL_ZONE_PASS_TARGET_POSE: Pose3d = Pose3d(8.27, 1.72, 0, Rotation3d())
RED_RIGHT_NEUTRAL_ZONE_PASS_TARGET_POSE: Pose3d = Pose3d(8.27, 6.35, 0, Rotation3d())
# ── Shooting-region boundary lines ───────────────────────────────────────────
class ShootingRegionDimensions:
BLUE_ALLIANCE_ZONE_REGION_X_MAX: float = 3.5
RED_ALLIANCE_ZONE_REGION_X_MIN: float = 13.0
NEUTRAL_ZONE_REGION_X_MIN: float = 5.5
NEUTRAL_ZONE_REGION_X_MAX: float = 11.0
BLUE_RIGHT_REGION_Y_MAX: float = 3.0
RED_LEFT_REGION_Y_MAX: float = 3.0
BLUE_LEFT_REGION_Y_MIN: float = 5.0
RED_RIGHT_REGION_Y_MIN: float = 5.0
# ── Shooting regions (Java enum → Python enum.Enum) ───────────────────────────
class ShootingRegion(enum.Enum):
NON_SHOOTING_REGION = enum.auto()
BLUE_OWN_ALLIANCE_ZONE = enum.auto()
BLUE_LEFT_NEUTRAL_ZONE = enum.auto()
BLUE_RIGHT_NEUTRAL_ZONE = enum.auto()
BLUE_LEFT_OPPONENT_ALLIANCE_ZONE = enum.auto()
BLUE_RIGHT_OPPONENT_ALLIANCE_ZONE = enum.auto()
RED_OWN_ALLIANCE_ZONE = enum.auto()
RED_LEFT_NEUTRAL_ZONE = enum.auto()
RED_RIGHT_NEUTRAL_ZONE = enum.auto()
RED_LEFT_OPPONENT_ALLIANCE_ZONE = enum.auto()
RED_RIGHT_OPPONENT_ALLIANCE_ZONE = enum.auto()

37
genie.py Normal file
View File

@ -0,0 +1,37 @@
"""
genie.py – Thin MagicRobot subclass that integrates commands2.CommandScheduler.
GenieRobot bridges MagicBot's component-injection lifecycle with the
commands2/PathPlannerLib command-based pattern by running the
CommandScheduler once per robot loop iteration.
"""
import commands2
import magicbot
class GenieRobot(magicbot.MagicRobot):
"""
MagicRobot subclass that runs the commands2 CommandScheduler each loop.
Inherit from this instead of MagicRobot whenever PathPlannerLib commands
or other commands2 commands need to execute.
"""
def robotPeriodic(self) -> None:
"""Run the CommandScheduler every loop, then delegate to MagicRobot."""
commands2.CommandScheduler.getInstance().run()
return super().robotPeriodic()
def scheduleCommand(self, command: commands2.Command) -> None:
"""
Schedule a commands2 Command to run via the CommandScheduler.
Used to kick off PathPlanner autos and named commands.
:param command: The command to schedule.
"""
commands2.CommandScheduler.getInstance().schedule(command)
def disabledPeriodic(self) -> None:
"""Called periodically while the robot is disabled. No-op by default."""
pass

View File

@ -1,4 +0,0 @@
frcYear = 2026
wpiVersion = 2026.2.1
ctreVersion = 26.1.0

Binary file not shown.

View File

@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=permwrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=permwrapper/dists

252
gradlew vendored
View File

@ -1,252 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
gradlew.bat vendored
View File

@ -1,94 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

@ -1 +0,0 @@
Subproject commit f3e38dc7ec323ba5cca8293a6df8de677d067d4d

@ -1 +0,0 @@
Subproject commit c0478b01a526712b8308ac329017857177a3c876

View File

@ -1,11 +0,0 @@
build{
dependsOn ':lib:Posty:lib:build'
dependsOn ':lib:RoboLib:lib:build'
}
dependencies{
// Team 578 libraries
implementation project(':lib:Posty:lib')
implementation project(':lib:RoboLib:lib')
}

36
pyproject.toml Normal file
View File

@ -0,0 +1,36 @@
#
# Use this configuration file to control what RobotPy packages are installed
# on your RoboRIO
#
[tool.robotpy]
# Version of robotpy this project depends on
robotpy_version = "2026.2.2"
# Which optional robotpy components should be installed?
# -> equivalent to `pip install robotpy[extra1, ...]
components = [
#"all",
"apriltag",
"commands2", # Needed for PathPlanner + GenieRobot pattern
# "cscore",
# "romi",
# "sim",
# "xrp",
]
# Other pip packages to install, such as vendor packages (each element
# is equivalent to a line in requirements.txt)
requires = [
"robotpy-rev", # SparkMax (launcher motor)
"robotpy-ctre", # WPI_TalonSRX (deploy motor, Phoenix 5), TalonFX (drive, turret, hopper), CANcoder, Pigeon2
"robotpy-pathplannerlib", # Path following
]
[tool.ruff]
line-length = 120
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "W", "I"] # pycodestyle, pyflakes, isort

164
robot.py Normal file
View File

@ -0,0 +1,164 @@
"""
robot.py – Main robot file for Castor 2026 (Python/MagicBot port).
MyRobot extends GenieRobot (which extends MagicRobot and also runs the
CommandScheduler each loop so PathPlannerLib commands work seamlessly).
"""
import commands2
import wpilib
from pathplannerlib.auto import NamedCommands
from pathplannerlib.config import RobotConfig
import constants
from autonomous.chassis_align_shoot import ChassisAlignShootCommand
from autonomous.turret_shoot import TurretShootCommand
from components.controller import XboxController
from components.drive import DriveComponent
from components.hopper import HopperComponent
from components.intake import IntakeComponent
from components.turret import TurretComponent
from genie import GenieRobot
class _DriveSubsystemAdapter(commands2.Subsystem):
"""
Satisfies AutoBuilder.configure()'s subsystem requirement parameter.
DriveComponent is not a commands2.Subsystem, so this adapter bridges the gap.
"""
pass
class MyRobot(GenieRobot):
# ── MagicBot component declarations (framework auto-injects instances) ──
driver_controller: XboxController
# Prefixed variable: MagicBot strips "driver_controller_" and injects
# `port` into the XboxController component before its setup() runs.
driver_controller_port: int = constants.OperatorConstants.CONTROLLER_PORT
drive: DriveComponent
hopper: HopperComponent
intake: IntakeComponent
turret: TurretComponent
def createObjects(self) -> None:
"""
Called by MagicBot before any component setup() methods.
Class-level prefixed variables (e.g. driver_controller_port) are
injected into components automatically after this method returns.
"""
# Tracks the active A/X turret-shoot command so it can be cancelled
self._shoot_cmd: TurretShootCommand | None = None
def _post_setup(self) -> None:
"""
Called after all component setup() methods have run (from robotInit).
Wire button bindings and PathPlanner named commands here because
component objects are fully constructed at this point.
"""
# Pass the underlying wpilib controller to intake for rumble feedback
self.intake.set_controller(self.driver_controller.this_controller)
# ── PathPlanner named commands ─────────────────────────────────────
NamedCommands.registerCommand(
"ChassisAlignShoot",
ChassisAlignShootCommand(self.drive, self.hopper, self.turret),
)
NamedCommands.registerCommand(
"TurretShoot",
TurretShootCommand(self.hopper, self.turret, self.intake, self.drive),
)
NamedCommands.registerCommand(
"RunIntake",
commands2.FunctionalCommand(
onInit=self.intake.intake,
onExecute=lambda: None,
onEnd=lambda interrupted: self.intake.stop_intake(),
isFinished=lambda: False,
),
)
# Aliases used in PathPlanner auto files
NamedCommands.registerCommand(
"Shoot preload",
TurretShootCommand(self.hopper, self.turret, self.intake, self.drive),
)
NamedCommands.registerCommand(
"Shoot Headed R-Tr",
ChassisAlignShootCommand(self.drive, self.hopper, self.turret),
)
NamedCommands.registerCommand(
"Shoot headed L-Tr",
ChassisAlignShootCommand(self.drive, self.hopper, self.turret),
)
# ── AutoBuilder / auto chooser ─────────────────────────────────────
try:
robot_config = RobotConfig.fromGUISettings()
drive_adapter = _DriveSubsystemAdapter()
self.drive.inject_drive_adapter(drive_adapter, robot_config)
except Exception as e:
print(f"[robot.py] AutoBuilder configuration failed: {e}")
def teleopPeriodic(self) -> None:
# ── Drive ──────────────────────────────────────────────────────────
self.drive.set_chassis_speeds_field_relative(
-self.driver_controller.left_y,
-self.driver_controller.left_x,
-self.driver_controller.right_x,
)
# ── One-shot actions (fire when button is released) ────────────────
if self.driver_controller.left_bumper_was_pressed():
self.turret.home_turret()
if self.driver_controller.right_bumper_was_pressed():
self.intake.intake()
if self.driver_controller.y_button_was_pressed():
self.scheduleCommand(
commands2.SequentialCommandGroup(
commands2.InstantCommand(self.intake.deploy),
commands2.WaitCommand(2.0),
commands2.InstantCommand(self.intake.stop_deploy),
)
)
# ── Held actions ───────────────────────────────────────────────────
if self.driver_controller.start_button_pressed():
self.intake.reset_deploy()
if self.driver_controller.b_button_pressed():
self.intake.reverse_intake()
self.hopper.reverse_kicker()
# A or X held → schedule turret-shoot command; cancel when released
shooting = (
self.driver_controller.a_button_pressed()
or self.driver_controller.x_button_pressed()
)
if shooting:
if self._shoot_cmd is None or self._shoot_cmd.isFinished():
self._shoot_cmd = TurretShootCommand(
self.hopper, self.turret, self.intake, self.drive
)
self.scheduleCommand(self._shoot_cmd)
elif self._shoot_cmd is not None and not self._shoot_cmd.isFinished():
self._shoot_cmd.cancel()
self._shoot_cmd = None
def autonomousInit(self) -> None:
auto_cmd = self.drive.get_auto_command()
if auto_cmd is not None:
self.scheduleCommand(auto_cmd)
def disabledInit(self) -> None:
self.drive.set_brake_mode(True)
def robotInit(self) -> None:
super().robotInit()
self.drive.set_brake_mode(False)
self._post_setup()
if __name__ == "__main__":
wpilib.run(MyRobot)

View File

@ -1,33 +0,0 @@
import org.gradle.internal.os.OperatingSystem
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
String frcYear = '2026'
File frcHome
if (OperatingSystem.current().isWindows()) {
String publicFolder = System.getenv('PUBLIC')
if (publicFolder == null) {
publicFolder = "C:\\Users\\Public"
}
def homeRoot = new File(publicFolder, "wpilib")
frcHome = new File(homeRoot, frcYear)
} else {
def userFolder = System.getProperty("user.home")
def homeRoot = new File(userFolder, "wpilib")
frcHome = new File(homeRoot, frcYear)
}
def frcHomeMaven = new File(frcHome, 'maven')
maven {
name = 'frcHome'
url = frcHomeMaven
}
}
}
Properties props = System.getProperties();
props.setProperty("org.gradle.internal.native.headers.unresolved.dependencies.ignore", "true");
include ':lib:Posty:lib'
include ':lib:RoboLib:lib'

View File

@ -1,105 +0,0 @@
{
"Keyboard 0 Settings": {
"window": {
"visible": true
}
},
"keyboardJoysticks": [
{
"axisConfig": [
{
"decKey": 65,
"incKey": 68
},
{
"decKey": 83,
"incKey": 87
},
{
"decayRate": 0.009999999776482582,
"keyRate": 0.009999999776482582
},
{},
{
"decKey": 81,
"incKey": 69
}
],
"axisCount": 5,
"buttonCount": 4,
"buttonKeys": [
90,
88,
67,
86
],
"povConfig": [
{
"key0": 328,
"key135": 323,
"key180": 322,
"key225": 321,
"key270": 324,
"key315": 327,
"key45": 329,
"key90": 326
}
],
"povCount": 1
},
{
"axisConfig": [
{
"decKey": 74,
"incKey": 76
},
{
"decKey": 73,
"incKey": 75
}
],
"axisCount": 2,
"buttonCount": 4,
"buttonKeys": [
77,
44,
46,
47
],
"povCount": 0
},
{
"axisConfig": [
{
"decKey": 263,
"incKey": 262
},
{
"decKey": 265,
"incKey": 264
}
],
"axisCount": 2,
"buttonCount": 6,
"buttonKeys": [
260,
268,
266,
261,
269,
267
],
"povCount": 0
},
{
"axisCount": 0,
"buttonCount": 0,
"povCount": 0
}
],
"robotJoysticks": [
{
"guid": "Keyboard0"
}
]
}

View File

@ -1,220 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package frc.robot;
import com.ctre.phoenix6.signals.InvertedValue;
import edu.wpi.first.apriltag.AprilTagFieldLayout;
import edu.wpi.first.apriltag.AprilTagFields;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.geometry.Transform2d;
import edu.wpi.first.math.geometry.Transform3d;
import edu.wpi.first.units.measure.Angle;
/**
* The Constants class provides a convenient place for teams to hold robot-wide
* numerical or boolean constants. This class should not be used for any other
* purpose. All constants should be declared globally (i.e. public static). Do
* not put anything functional in this class.
*
* <p>
* It is advised to statically import this class (or one of its inner classes)
* wherever the constants are needed, to reduce verbosity.
*/
public final class Constants {
public static class OperatorConstants {
public static final int kDriverControllerPort = 0;
}
public static class CameraConstanst {
public static class IDFilters {
public static final int[] RED_HUB_SHOOTING_IDS = new int[]{8, 9, 10, 11};
public static final int[] BLUE_HUB_SHOOTING_IDS = new int[]{27, 26, 25, 24};
}
public static final String BACK_CAMERA_NAME = "limelight-back";
public static final int BACK_LOCALIZATION_PIPELINE_NUMBER = 0;
public static final int BACK_HUB_TRACKING_2D_PIPELINE_NUMBER = 1;
public static final int BACK_HUB_TRACKING_3D_PIPELINE_NUMBER = 2;
public static final String FRONT_CAMERA_NAME = "limelight-front";
public static final int FRONT_LOCALIZATION_PIPELINE_NUMBER = 0;
public static final double[] BLUE_ZONE_COOR = new double[]{0, 3.5};
public static final double[] RED_ZONE_COOR = new double[]{13, 16};
public static final double[] NEUTRAL_ZONE_COOR = new double[]{5.5, 11};
public static final double[] NEUTRAL1_ZONE_COOR = new double[]{5.5, 11, 0, 3};
public static final double[] NEUTRAL2_ZONE_COOR = new double[]{5.5, 11, 5, 8};
public static final double[] BLUE1_ZONE_COOR = new double[]{0, 3.5, 0, 3};
public static final double[] BLUE2_ZONE_COOR = new double[]{0, 3.5, 5, 8};
public static final double[] RED1_ZONE_COOR = new double[]{13, 16, 0, 3};
public static final double[] RED2_ZONE_COOR = new double[]{13, 16, 5, 8};
}
public static class AdvantageKitConstants {
public enum RobotType {
SIM,
REAL,
REPLAY
}
public static final RobotType CURRENT_MODE = RobotType.REAL;
}
// Swerve offsets as of 3/2/26
public static class SwerveDriveOffsets {
public static final float FRONT_LEFT_MOTOR_OFFSET = -0.336426f;
public static final float FRONT_RIGHT_MOTOR_OFFSET = 0.108887f;
public static final float BACK_LEFT_MOTOR_OFFSET = -0.492432f;
public static final float BACK_RIGHT_MOTOR_OFFSET = 0.470215f;
}
/*
* Numbers starting with 1 (E.g. 11, 12, 13) are in the Front Left of the robot
* Numbers starting with 2 (E.g. 21, 22, 23) are in the Front Right of the robot
* Numbers starting with 3 (E.g. 31, 32, 33) are in the Back Left of the robot
* Numbers starting with 4 (E.g. 41, 42, 43) are in the Back Right of the robot
* Laters numbers will be added for more subsystems
* Single diget numbers (E.g. 1, 2, 3) are extra, non-motor, pieces that require IDs, such as the Pigeon
*/
public static class SwerveDriveIDs {
public static final int FRONT_LEFT_STEER_ID = 11;
public static final int FRONT_LEFT_DRIVE_ID = 12;
public static final int FRONT_LEFT_ENCODER_ID = 13;
public static final int FRONT_RIGHT_STEER_ID = 21;
public static final int FRONT_RIGHT_DRIVE_ID = 22;
public static final int FRONT_RIGHT_ENCODER_ID = 23;
public static final int BACK_LEFT_STEER_ID = 31;
public static final int BACK_LEFT_DRIVE_ID = 32;
public static final int BACK_LEFT_ENCODER_ID = 33;
public static final int BACK_RIGHT_STEER_ID = 41;
public static final int BACK_RIGHT_DRIVE_ID = 42;
public static final int BACK_RIGHT_ENCODER_ID = 43;
}
public static class RobotChassisLimits {
// Max velocity in meters per second
public static final double MAX_ROBOT_LINEAR_VELOCITY = 3;
// In meters per second squared
public static final double MAX_ROBOT_LINEAR_VELOCITY_ACCEL = 2;
// In radians per second
public static final double MAX_ROBOT_ANGULAR_VELOCITY = Math.PI;
// In radians per second squared
public static final double MAX_ROBOT_ANGULAR_VELOCITY_ACCEL = Math.PI;
}
//Bird
public static class ExtraIDEntities {
public static final int PIGEON_ID = 1;
}
public static class IntakeConstants {
public static final int DEPLOY_MOTOR_ID = 17;
public static final boolean DEPLOY_MOTOR_INVERTED = true; // TODO: Test this
public static final int INTAKE_MOTOR_ID = 2;
public static final InvertedValue INTAKE_MOTOR_DIRECTION = InvertedValue.Clockwise_Positive; // TODO: Test this
}
public static class ShooterConstants {
public static final int LAUNCHER_MOTOR_ID = 3;
public static final boolean LAUNCHER_MOTOR_INVERTED = false;
public static final int TURRET_MOTOR_ID = 4;
public static final InvertedValue TURRET_MOTOR_DIRECTION = InvertedValue.CounterClockwise_Positive;
public static final int TURRET_LIMIT_CHANNEL = 9;
public static final Angle LIMIT_AXIMUTH_POS = edu.wpi.first.units.Units.Degrees.of(165);
public static final Angle LIMIT_AXIMUTH_NEG = edu.wpi.first.units.Units.Degrees.of(5);
public static final double HOMING_SPEED = .05;
public static final double TURRET_GEAR_RATIO = 6;
public static final int HOOD_SERVO_CHANNEL = 0;
public static final boolean HOOD_SERVO_INVERTED = false; // TODO: Test this
public static final int HOOD_ENCODER_ID = 4;
public static final Angle TARGET_ELEVATION_DIF = edu.wpi.first.units.Units.Degrees.of(1);
public static final Angle LIMIT_ELEVATION_POS = edu.wpi.first.units.Units.Degrees.of(90);
public static final Angle LIMIT_ELEVATION_NEG = edu.wpi.first.units.Units.Degrees.of(0);
public static final double DEFLECTOR_STORED_ANGLE = 0;
public static final double DEFLECTOR_SET_ANGLE1 = 100;
public static final double DEFLECTOR_SET_ANGLE2 = 200;
public static final double DEFLECTOR_SET_ANGLE3 = 300;
public static final double DEFLECTOR_SERVO_RATIO = 1.0;
}
public static class HopperConstants {
public static final int KICKER_MOTOR_ID = 9;
public static final InvertedValue KICKER_MOTOR_DIRECTION = InvertedValue.Clockwise_Positive;
public static final int SPINDEXER_MOTOR_ID = 8;
public static final InvertedValue SPINDEXER_MOTOR_DIRECTION = InvertedValue.CounterClockwise_Positive;
}
public static class FieldPoses {
private static AprilTagFieldLayout fieldTags = AprilTagFieldLayout.loadField(AprilTagFields.k2026RebuiltWelded);
public static Pose3d BLUE_HUB_POSE = fieldTags.getTagPose(26).get().transformBy(new Transform3d(new Transform2d(-0.5207, 0, Rotation2d.kZero)));
public static Pose3d RED_HUB_POSE = fieldTags.getTagPose(10).get().transformBy(new Transform3d(new Transform2d(-0.5207, 0, Rotation2d.kZero)));
// These are the field targets for passing fuel to the Alliance Zone or to the Neutral Zone from the opponent's side.
// Left/Right are from driver's perspective. Coordinates assume origin at blue side of field.
// Rotation are zero because they don't matter.
public static Pose3d BLUE_LEFT_ALLIANCE_ZONE_PASS_TARGET_POSE = new Pose3d(1.99, 6.35, 0, new Rotation3d());
public static Pose3d BLUE_RIGHT_ALLIANCE_ZONE_PASS_TARGET_POSE = new Pose3d(1.99, 1.72, 0, new Rotation3d());
public static Pose3d BLUE_LEFT_NEUTRAL_ZONE_PASS_TARGET_POSE = new Pose3d(8.27, 6.35, 0, new Rotation3d());
public static Pose3d BLUE_RIGHT_NEUTRAL_ZONE_PASS_TARGET_POSE = new Pose3d(8.27, 1.72, 0, new Rotation3d());
public static Pose3d RED_LEFT_ALLIANCE_ZONE_PASS_TARGET_POSE = new Pose3d(14.55, 1.72, 0, new Rotation3d());
public static Pose3d RED_RIGHT_ALLIANCE_ZONE_PASS_TARGET_POSE = new Pose3d(14.55, 6.35, 0, new Rotation3d());
public static Pose3d RED_LEFT_NEUTRAL_ZONE_PASS_TARGET_POSE = new Pose3d(8.27, 1.72, 0, new Rotation3d());
public static Pose3d RED_RIGHT_NEUTRAL_ZONE_PASS_TARGET_POSE = new Pose3d(8.27, 6.35, 0, new Rotation3d());
}
// These represent the dividing lines for the regions in our shooting strategy.
public static class ShootingRegionDimensions {
public static double BLUE_ALLIANCE_ZONE_REGION_X_MAX = 3.5;
public static double RED_ALLIANCE_ZONE_REGION_X_MIN = 13.0;
public static double NEUTRAL_ZONE_REGION_X_MIN = 5.5;
public static double NEUTRAL_ZONE_REGION_X_MAX = 11.0;
public static double BLUE_RIGHT_REGION_Y_MAX = 3.0;
public static double RED_LEFT_REGION_Y_MAX = 3.0; // same meaning as the previous one, but from red perspective
public static double BLUE_LEFT_REGION_Y_MIN = 5.0;
public static double RED_RIGHT_REGION_Y_MIN = 5.0; // same meaning as the previous one, but from red perspective
}
// Regions that define the AutoTurret shooting behaviors.
// Color ID in the name reflects robot's alliance since region definitions vary by alliance.
// Left/Right are from driver's perspective.
public enum ShootingRegion {
NON_SHOOTING_REGION, // Any part of the field that's not in one of the regions
BLUE_OWN_ALLIANCE_ZONE,
BLUE_LEFT_NEUTRAL_ZONE,
BLUE_RIGHT_NEUTRAL_ZONE,
BLUE_LEFT_OPPONENT_ALLIANCE_ZONE,
BLUE_RIGHT_OPPONENT_ALLIANCE_ZONE,
RED_OWN_ALLIANCE_ZONE,
RED_LEFT_NEUTRAL_ZONE,
RED_RIGHT_NEUTRAL_ZONE,
RED_LEFT_OPPONENT_ALLIANCE_ZONE,
RED_RIGHT_OPPONENT_ALLIANCE_ZONE
}
}

View File

@ -1,25 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package frc.robot;
import edu.wpi.first.wpilibj.RobotBase;
/**
* Do NOT add any static variables to this class, or any initialization at all. Unless you know what
* you are doing, do not modify this file except to change the parameter class to the startRobot
* call.
*/
public final class Main {
private Main() {}
/**
* Main initialization function. Do not perform any initialization here.
*
* <p>If you change your main robot class, change the parameter type.
*/
public static void main(String... args) {
RobotBase.startRobot(Robot::new);
}
}

View File

@ -1,188 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package frc.robot;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
import org.fairportrobotics.frc.posty.PostyManager;
import org.littletonrobotics.junction.LoggedRobot;
import org.littletonrobotics.junction.Logger;
import org.littletonrobotics.junction.networktables.NT4Publisher;
import org.littletonrobotics.junction.wpilog.WPILOGWriter;
import com.ctre.phoenix6.swerve.SwerveModule;
import edu.wpi.first.wpilibj.Filesystem;
import edu.wpi.first.wpilibj.PowerDistribution;
import edu.wpi.first.wpilibj.PowerDistribution.ModuleType;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
/**
* The VM is configured to automatically run this class, and to call the
* functions corresponding to
* each mode, as described in the TimedRobot documentation. If you change the
* name of this class or
* the package after creating this project, you must also update the
* build.gradle file in the
* project.
*/
public class Robot extends LoggedRobot {
private Command m_autonomousCommand;
private RobotContainer m_robotContainer;
public static String getVersion() {
try {
Scanner scan = new Scanner(new File(Filesystem.getDeployDirectory().getAbsolutePath() + "/versionfile.txt"));
String ver = scan.nextLine();
scan.close();
return ver;
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return "";
}
/**
* This function is run when the robot is first started up and should be used
* for any
* initialization code.
*/
@Override
public void robotInit() {
String swVersion = getVersion();
Logger.recordMetadata("ProjectName", "MyProject"); // Set a metadata value
Logger.recordMetadata("Software Version", swVersion);
if (isReal()) {
Logger.addDataReceiver(new WPILOGWriter()); // Log to a USB stick ("/U/logs")
Logger.addDataReceiver(new NT4Publisher()); // Publish data to NetworkTables
new PowerDistribution(1, ModuleType.kRev); // Enables power distribution logging
} else {
setUseTiming(false); // Run as fast as possible
// String logPath = LogFileUtil.findReplayLog(); // Pull the replay log from AdvantageScope (or prompt the user)
// Logger.setReplaySource(new WPILOGReader(logPath)); // Read replay log
// Logger.addDataReceiver(new WPILOGWriter(LogFileUtil.addPathSuffix(logPath, "_sim"))); // Save outputs to a new log
Logger.addDataReceiver(new NT4Publisher()); // Publish data to NetworkTables
}
// Logger.disableDeterministicTimestamps() // See "Deterministic Timestamps" in
// the "Understanding Data Flow" page
Logger.start(); // Start logging! No more data receivers, replay sources, or metadata values may
// be added.
// Print out software version
Logger.recordOutput("Software Version", swVersion);
System.out.println("Robot Software Version: " + swVersion);
// Instantiate our RobotContainer. This will perform all our button bindings,
// and put our
// autonomous chooser on the dashboard.
m_robotContainer = new RobotContainer();
// Collect Posty tests
PostyManager.getInstance().collectTests();
// Run all POST tests after robot has been initialized
PostyManager.getInstance().runAllPOSTs();
m_robotContainer.driveSubsystem.driveSystem.setBrakeMode(false);
}
/**
* This function is called every 20 ms, no matter the mode. Use this for items
* like diagnostics
* that you want ran during disabled, autonomous, teleoperated and test.
*
* <p>
* This runs after the mode specific periodic functions, but before LiveWindow
* and
* SmartDashboard integrated updating.
*/
@Override
public void robotPeriodic() {
// Runs the Scheduler. This is responsible for polling buttons, adding
// newly-scheduled
// commands, running already-scheduled commands, removing finished or
// interrupted commands,
// and running subsystem periodic() methods. This must be called from the
// robot's periodic
// block in order for anything in the Command-based framework to work.
CommandScheduler.getInstance().run();
}
/** This function is called once each time the robot enters Disabled mode. */
@Override
public void disabledInit() {
m_robotContainer.driveSubsystem.driveSystem.setBrakeMode(true);
}
@Override
public void disabledPeriodic() {
}
/**
* This autonomous runs the autonomous command selected by your
* {@link RobotContainer} class.
*/
@Override
public void autonomousInit() {
m_autonomousCommand = m_robotContainer.getAutonomousCommand();
// schedule the autonomous command (example)
if (m_autonomousCommand != null) {
CommandScheduler.getInstance().schedule(m_autonomousCommand);
}
}
/** This function is called periodically during autonomous. */
@Override
public void autonomousPeriodic() {
}
@Override
public void teleopInit() {
// This makes sure that the autonomous stops running when
// teleop starts running. If you want the autonomous to
// continue until interrupted by another command, remove
// this line or comment it out.
if (m_autonomousCommand != null) {
m_autonomousCommand.cancel();
}
}
/** This function is called periodically during operator control. */
@Override
public void teleopPeriodic() {
}
@Override
public void testInit() {
// Cancels all running commands at the start of test mode.
CommandScheduler.getInstance().cancelAll();
}
/** This function is called periodically during test mode. */
@Override
public void testPeriodic() {
}
/** This function is called once when the robot is first started up. */
@Override
public void simulationInit() {
}
/** This function is called periodically whilst in simulation. */
@Override
public void simulationPeriodic() {
}
}

View File

@ -1,103 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package frc.robot;
import com.pathplanner.lib.auto.NamedCommands;
import edu.wpi.first.wpilibj.GenericHID.RumbleType;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.Commands;
import edu.wpi.first.wpilibj2.command.button.CommandXboxController;
import edu.wpi.first.wpilibj2.command.button.Trigger;
import frc.robot.Constants.OperatorConstants;
import frc.robot.commands.AutoShootCommandChassisTurret;
import frc.robot.commands.AutoTurretCommand;
import frc.robot.commands.AutoTurretShootCommand;
import frc.robot.commands.Reverse;
import frc.robot.commands.SetDeflectorCommand;
import frc.robot.subsystems.DriveSubsystem;
import frc.robot.subsystems.HopperSubsystem;
import frc.robot.subsystems.IntakeSubsystem;
import frc.robot.subsystems.TurretSubsystem;
/**
* This class is where the bulk of the robot should be declared. Since
* Command-based is a "declarative" paradigm, very little robot logic should
* actually be handled in the {@link Robot} periodic methods (other than the
* scheduler calls). Instead, the structure of the robot (including subsystems,
* commands, and trigger mappings) should be declared here.
*/
public class RobotContainer {
// The robot's subsystems and commands are defined here...
// Replace with CommandPS4Controller or CommandJoystick if needed
private final CommandXboxController m_driverController
= new CommandXboxController(OperatorConstants.kDriverControllerPort);
// private final CommandPS4Controller m_driverController = new CommandPS4Controller(OperatorConstants.kDriverControllerPort);
public final DriveSubsystem driveSubsystem = new DriveSubsystem(m_driverController);
public final IntakeSubsystem intakeSubsystem = new IntakeSubsystem();
public final TurretSubsystem turretSubsystem = new TurretSubsystem();
public final HopperSubsystem hopperSubsystem = new HopperSubsystem();
/**
* The container for the robot. Contains subsystems, OI devices, and
* commands.
*/
public RobotContainer() {
// turretSubsystem.setDefaultCommand(new AutoTurretCommand(turretSubsystem, driveSubsystem));
// turretSubsystem.homeTurret();
// Configure the trigger bindings
configureBindings();
NamedCommands.registerCommand("ChassisAlignShoot", new AutoShootCommandChassisTurret(driveSubsystem, hopperSubsystem, turretSubsystem));
NamedCommands.registerCommand("RunIntake", intakeSubsystem.intake(m_driverController.getHID()));
NamedCommands.registerCommand("TurretShoot", new AutoTurretShootCommand(hopperSubsystem, turretSubsystem, intakeSubsystem, driveSubsystem));
turretSubsystem.setDefaultCommand(new AutoTurretCommand(turretSubsystem, driveSubsystem));
}
/**
* Use this method to define your trigger->command mappings. Triggers can be
* created via the
* {@link Trigger#Trigger(java.util.function.BooleanSupplier)} constructor
* with an arbitrary predicate, or via the named factories in {@link
* edu.wpi.first.wpilibj2.command.button.CommandGenericHID}'s subclasses for {@link
* CommandXboxController Xbox}/{@link edu.wpi.first.wpilibj2.command.button.CommandPS4Controller
* PS4} controllers or {@link edu.wpi.first.wpilibj2.command.button.CommandJoystick Flight
* joysticks}.
*
*/
private void configureBindings() {
// m_driverController.povDown().onTrue(new SetDeflectorCommand(turretSubsystem, Constants.ShooterConstants.DEFLECTOR_STORED_ANGLE));
// m_driverController.povLeft().onTrue(new SetDeflectorCommand(turretSubsystem, Constants.ShooterConstants.DEFLECTOR_SET_ANGLE1));
// m_driverController.povRight().onTrue(new SetDeflectorCommand(turretSubsystem, Constants.ShooterConstants.DEFLECTOR_SET_ANGLE3));
// m_driverController.povUp().onTrue(new SetDeflectorCommand(turretSubsystem, Constants.ShooterConstants.DEFLECTOR_SET_ANGLE2));
//m_driverController.back().whileTrue(new AutoShootCommandChassisTurretPathPlanner(driveSubsystem, hopperSubsystem, turretSubsystem));
m_driverController.leftBumper().onTrue(Commands.runOnce(() -> turretSubsystem.homeTurret(), turretSubsystem));
m_driverController.rightBumper().onTrue(Commands.parallel(intakeSubsystem.intake(m_driverController.getHID())));
m_driverController.start().whileTrue(intakeSubsystem.resetDeploy());
m_driverController.a().whileTrue((new AutoTurretShootCommand(hopperSubsystem, turretSubsystem, intakeSubsystem, driveSubsystem)));
m_driverController.x().whileTrue((new AutoTurretShootCommand(hopperSubsystem, turretSubsystem, intakeSubsystem, driveSubsystem)));
m_driverController.b().whileTrue(new Reverse(intakeSubsystem, hopperSubsystem, m_driverController.getHID()));
m_driverController.y().onTrue(intakeSubsystem.deploy());
}
/**
* Use this to pass the autonomous command to the main {@link Robot} class.
*
* @return the command to run in autonomous
*/
public Command getAutonomousCommand() {
// An example command will be run in autonomous
return Commands.parallel(intakeSubsystem.deploy(), Commands.run(() -> {
turretSubsystem.homeTurret();
}, turretSubsystem), driveSubsystem.getAutoCommand());
}
}

View File

@ -1,173 +0,0 @@
package frc.robot;
import edu.wpi.first.wpilibj.DriverStation.Alliance;
import frc.robot.Constants;
import frc.robot.Constants.ShootingRegion;
import frc.robot.Constants.ShootingRegionDimensions;
import org.littletonrobotics.junction.Logger;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.units.measure.Angle;
public class Utils {
/**
* Calculates the needed angle of the turret for a distance from the HUB.
* @param distanceToTag
* @return number of rotations of the turret, robot relative
*/
public static double calculateTurretAngleFromCameraTagDistance(double distanceToTag){
// Calculate turret Angle
double cameraDistanceToTarget = distanceToTag + .6;
double cameraToShootDistance = 0.26035;
// a^2 + b^2 = c^2
double shooterDistanceToTarget = Math
.sqrt(Math.pow(cameraDistanceToTarget, 2) + Math.pow(cameraToShootDistance, 2));
double angleAtShooter = Math.acos(cameraToShootDistance / shooterDistanceToTarget);
double robotTurretAngle = (Math.PI / 2) - angleAtShooter;
double robotTurretRotations = (robotTurretAngle / Math.PI);
return robotTurretRotations;
}
/**
* Determines the region for the robot based on its location and alliance.
* Assumes the robot is a 27" square. Thresholds based on observations from simulator.
* @param robotPose: the robot's pose
* @param alliance: the red/blue alliance of the robot, as a sanity check
* @return the robot's shooting region
*/
public static ShootingRegion findRobotShootingRegion(Pose3d robotPose, Alliance alliance){
ShootingRegion region = Constants.ShootingRegion.NON_SHOOTING_REGION; // the default return value if robot isn't in a valid region
double robotX = robotPose.getX();
double robotY = robotPose.getY();
// Receommended: Add a sanity check for invalid robot poses.
if (alliance == Alliance.Blue) {
if (robotX < Constants.ShootingRegionDimensions.BLUE_ALLIANCE_ZONE_REGION_X_MAX) {
region = Constants.ShootingRegion.BLUE_OWN_ALLIANCE_ZONE;
}
else if (robotX > Constants.ShootingRegionDimensions.NEUTRAL_ZONE_REGION_X_MIN &&
robotX < Constants.ShootingRegionDimensions.NEUTRAL_ZONE_REGION_X_MAX &&
robotY > Constants.ShootingRegionDimensions.BLUE_LEFT_REGION_Y_MIN) {
region = Constants.ShootingRegion.BLUE_LEFT_NEUTRAL_ZONE;
}
else if (robotX > Constants.ShootingRegionDimensions.NEUTRAL_ZONE_REGION_X_MIN &&
robotX < Constants.ShootingRegionDimensions.NEUTRAL_ZONE_REGION_X_MAX &&
robotY < Constants.ShootingRegionDimensions.BLUE_RIGHT_REGION_Y_MAX) {
region = Constants.ShootingRegion.BLUE_RIGHT_NEUTRAL_ZONE;
}
else if (robotX > Constants.ShootingRegionDimensions.RED_ALLIANCE_ZONE_REGION_X_MIN &&
robotY > Constants.ShootingRegionDimensions.BLUE_LEFT_REGION_Y_MIN) {
region = Constants.ShootingRegion.BLUE_LEFT_OPPONENT_ALLIANCE_ZONE;
}
else if (robotX > Constants.ShootingRegionDimensions.RED_ALLIANCE_ZONE_REGION_X_MIN &&
robotY < Constants.ShootingRegionDimensions.BLUE_RIGHT_REGION_Y_MAX) {
region = Constants.ShootingRegion.BLUE_RIGHT_OPPONENT_ALLIANCE_ZONE;
}
}
else if (alliance == Alliance.Red) {
if (robotX > Constants.ShootingRegionDimensions.RED_ALLIANCE_ZONE_REGION_X_MIN) {
region = Constants.ShootingRegion.RED_OWN_ALLIANCE_ZONE;
}
else if (robotX > Constants.ShootingRegionDimensions.NEUTRAL_ZONE_REGION_X_MIN &&
robotX < Constants.ShootingRegionDimensions.NEUTRAL_ZONE_REGION_X_MAX &&
robotY < Constants.ShootingRegionDimensions.RED_LEFT_REGION_Y_MAX) {
region = Constants.ShootingRegion.RED_LEFT_NEUTRAL_ZONE;
}
else if (robotX > Constants.ShootingRegionDimensions.NEUTRAL_ZONE_REGION_X_MIN &&
robotX < Constants.ShootingRegionDimensions.NEUTRAL_ZONE_REGION_X_MAX &&
robotY > Constants.ShootingRegionDimensions.RED_RIGHT_REGION_Y_MIN) {
region = Constants.ShootingRegion.RED_RIGHT_NEUTRAL_ZONE;
}
else if (robotX < Constants.ShootingRegionDimensions.BLUE_ALLIANCE_ZONE_REGION_X_MAX &&
robotY < Constants.ShootingRegionDimensions.RED_LEFT_REGION_Y_MAX) {
region = Constants.ShootingRegion.RED_LEFT_OPPONENT_ALLIANCE_ZONE;
}
else if (robotX > Constants.ShootingRegionDimensions.RED_ALLIANCE_ZONE_REGION_X_MIN &&
robotY > Constants.ShootingRegionDimensions.RED_RIGHT_REGION_Y_MIN) {
region = Constants.ShootingRegion.RED_RIGHT_OPPONENT_ALLIANCE_ZONE;
}
}
Logger.recordOutput("Utils-ShootingRegion", region);
return region;
}
public static double getHoodAngleForDistance(double distanceInMeters){
if(distanceInMeters >= 4){
return 200;
}
return 0;
}
public static double getLauncherRPMForDistance(double distanceInMeters){
return Math.min((667.557*distanceInMeters) + 1500.699, 5000);
}
/**
* Determines the appropriate shooting target for the robot based on its alliance
* and its current region on the field. There are 10 possible targets:
* Scoring hub, 2x passing from Neutral Zone, 2x passing from opponent's Alliance Zone,
* and these exist for both red and blue alliances.
* Right/left are from driver's perspective.
* @param region: the identifier of the robot's current field region
* @param alliance: the red/blue alliance of the robot, as a sanity check
* @return the shooting target for a robot with the provided properties. Returns all-zero
* Pose3d object if the region/alliance combination is invalid.
*/
public static Pose3d findShootingTarget(ShootingRegion region, Alliance alliance){
Pose3d shootingTarget = new Pose3d();
if (alliance == Alliance.Blue) {
switch (region) {
case BLUE_OWN_ALLIANCE_ZONE:
shootingTarget = Constants.FieldPoses.BLUE_HUB_POSE;
break;
case BLUE_LEFT_NEUTRAL_ZONE:
shootingTarget = Constants.FieldPoses.BLUE_LEFT_ALLIANCE_ZONE_PASS_TARGET_POSE;
break;
case BLUE_RIGHT_NEUTRAL_ZONE:
shootingTarget = Constants.FieldPoses.BLUE_RIGHT_ALLIANCE_ZONE_PASS_TARGET_POSE;
break;
case BLUE_LEFT_OPPONENT_ALLIANCE_ZONE:
shootingTarget = Constants.FieldPoses.BLUE_LEFT_NEUTRAL_ZONE_PASS_TARGET_POSE;
break;
case BLUE_RIGHT_OPPONENT_ALLIANCE_ZONE:
shootingTarget = Constants.FieldPoses.BLUE_RIGHT_NEUTRAL_ZONE_PASS_TARGET_POSE;
break;
default:
break;
}
}
else if (alliance == Alliance.Red) {
switch (region) {
case RED_OWN_ALLIANCE_ZONE:
shootingTarget = Constants.FieldPoses.RED_HUB_POSE;
break;
case RED_LEFT_NEUTRAL_ZONE:
shootingTarget = Constants.FieldPoses.RED_LEFT_ALLIANCE_ZONE_PASS_TARGET_POSE;
break;
case RED_RIGHT_NEUTRAL_ZONE:
shootingTarget = Constants.FieldPoses.RED_RIGHT_ALLIANCE_ZONE_PASS_TARGET_POSE;
break;
case RED_LEFT_OPPONENT_ALLIANCE_ZONE:
shootingTarget = Constants.FieldPoses.RED_LEFT_NEUTRAL_ZONE_PASS_TARGET_POSE;
break;
case RED_RIGHT_OPPONENT_ALLIANCE_ZONE:
shootingTarget = Constants.FieldPoses.RED_RIGHT_NEUTRAL_ZONE_PASS_TARGET_POSE;
break;
default:
break;
}
}
return shootingTarget;
}
}

View File

@ -1,154 +0,0 @@
package frc.robot.commands;
import java.util.Arrays;
import java.util.List;
import org.fairportrobotics.frc.robolib.vision.limelight.LimelightHelpers;
import org.littletonrobotics.junction.Logger;
import edu.wpi.first.apriltag.AprilTagFieldLayout;
import edu.wpi.first.apriltag.AprilTagFields;
import edu.wpi.first.math.controller.PIDController;
import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.geometry.Transform2d;
import edu.wpi.first.math.geometry.Transform3d;
import edu.wpi.first.math.geometry.Translation2d;
import edu.wpi.first.math.geometry.Translation3d;
import edu.wpi.first.units.Units;
import edu.wpi.first.units.measure.Angle;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.DriverStation.Alliance;
import edu.wpi.first.wpilibj2.command.Command;
import frc.robot.Constants;
import frc.robot.Utils;
import frc.robot.subsystems.DriveSubsystem;
import frc.robot.subsystems.HopperSubsystem;
import frc.robot.subsystems.TurretSubsystem;
public class AutoShootCommandChassisTurret extends Command{
// For Chassis Control
private DriveSubsystem driveSubsystem;
private HopperSubsystem hopperSubsystem;
private TurretSubsystem turretSubsystem;
private PIDController cameraAutoCenterController;
private PIDController deadreckoningAutoCenterController;
private int[] tagFilters;
private AprilTagFieldLayout fieldTags = AprilTagFieldLayout.loadField(AprilTagFields.k2026RebuiltWelded);
private Pose3d closestTag;
private final double chassisRotateSpeed = Math.PI * 0.1;
public AutoShootCommandChassisTurret(DriveSubsystem driveSubsystem, HopperSubsystem hopperSubsystem, TurretSubsystem turretSubsystem)
{
this.driveSubsystem = driveSubsystem;
this.hopperSubsystem = hopperSubsystem;
this.turretSubsystem = turretSubsystem;
this.addRequirements(driveSubsystem, hopperSubsystem, turretSubsystem);
cameraAutoCenterController = new PIDController(0.15, 0.0, 0.0);
cameraAutoCenterController.setTolerance(0.2); // Units degrees
cameraAutoCenterController.setSetpoint(0);
deadreckoningAutoCenterController = new PIDController(Math.PI * 0.1, 0, 0.0);
deadreckoningAutoCenterController.setTolerance(0.2); // Units is rotations
deadreckoningAutoCenterController.enableContinuousInput(-Math.PI, Math.PI);
}
@Override
public void initialize() {
DriverStation.getAlliance().ifPresent((aliance) -> {
if (aliance == Alliance.Blue) {
tagFilters = Constants.CameraConstanst.IDFilters.BLUE_HUB_SHOOTING_IDS;
} else {
tagFilters = Constants.CameraConstanst.IDFilters.RED_HUB_SHOOTING_IDS;
}
});
turretSubsystem.setLauncher(3000);
turretSubsystem.setTargetElevation(Constants.ShooterConstants.DEFLECTOR_STORED_ANGLE);
hopperSubsystem.spindexerOn();
cameraAutoCenterController.calculate(-100);
// Change to HUB tracking pipeline
LimelightHelpers.setPipelineIndex(Constants.CameraConstanst.BACK_CAMERA_NAME, Constants.CameraConstanst.BACK_HUB_TRACKING_3D_PIPELINE_NUMBER);
LimelightHelpers.SetFiducialIDFiltersOverride(Constants.CameraConstanst.BACK_CAMERA_NAME, tagFilters);
Pose3d botPose = driveSubsystem.getBotPose();
// Find closest target to us
List<Pose3d> tagPoses = Arrays.stream(tagFilters).mapToObj(tagId -> fieldTags.getTagPose(tagId).get()).toList();
closestTag = botPose.nearest(tagPoses);
Logger.recordOutput("AutoAlign-ClosestAprilTagPose", closestTag);
Translation2d delta = botPose.getTranslation().toTranslation2d().minus(closestTag.getTranslation().toTranslation2d());
Logger.recordOutput("AutoAlign-ClosestAprilTagTransform", delta);
deadreckoningAutoCenterController.setSetpoint(delta.getAngle().plus(Rotation2d.kZero).getRadians());
}
@Override
public void execute() {
// Need to compute shoot speed and angle
Logger.recordOutput("AutoAlign-CenteringError", cameraAutoCenterController.getError());
// No april tag in camera view. Rotate towards our theoretical closest tag
if(!LimelightHelpers.getTV(Constants.CameraConstanst.BACK_CAMERA_NAME)){
Logger.recordOutput("AutoAlignState-Tracking", "Deadreckoning");
Pose3d botPose = driveSubsystem.getBotPose();
//driveSubsystem.rotateChassis(-deadreckoningAutoCenterController.calculate(botPose.getRotation().toRotation2d().getRadians()));
}
else // We have an april tag, center it to the camera frame
{
Logger.recordOutput("AutoAlignState-Tracking", "Camera");
// [tx, ty, tz, pitch, yaw, roll]
double[] targetsPose = LimelightHelpers.getTargetPose_CameraSpace(Constants.CameraConstanst.BACK_CAMERA_NAME);
if(targetsPose.length >= 1){
Logger.recordOutput("AutoAlign-Target Distance", targetsPose[2]);
// Calculate turret Angle
double turretAngle = Utils.calculateTurretAngleFromCameraTagDistance(targetsPose[2]) * Constants.ShooterConstants.TURRET_GEAR_RATIO;
turretSubsystem.setTurretMotorRotation(-turretAngle);
driveSubsystem.rotateChassis(-cameraAutoCenterController.calculate(targetsPose[0])); // This may need to be negated
}
}
// If robot is centered on HUB and shooter is up to speed. Run the kicker
if(turretSubsystem.isLauncherUpToSpeed() && cameraAutoCenterController.atSetpoint()){
Logger.recordOutput("AutoAlignState-Shoot", "FIRE!");
hopperSubsystem.feedKicker();
}else{
Logger.recordOutput("AutoAlignState-Shoot", "Waiting");
}
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void end(boolean interrupted) {
turretSubsystem.setLauncher(0);
hopperSubsystem.stopKicker();
hopperSubsystem.spindexerOff();
driveSubsystem.stopDrive();
LimelightHelpers.setPipelineIndex(Constants.CameraConstanst.BACK_CAMERA_NAME, Constants.CameraConstanst.BACK_LOCALIZATION_PIPELINE_NUMBER);
}
}

View File

@ -1,130 +0,0 @@
package frc.robot.commands;
import java.util.Arrays;
import java.util.List;
import org.fairportrobotics.frc.robolib.vision.limelight.LimelightHelpers;
import org.littletonrobotics.junction.Logger;
import com.pathplanner.lib.auto.AutoBuilder;
import com.pathplanner.lib.path.PathConstraints;
import edu.wpi.first.apriltag.AprilTagFieldLayout;
import edu.wpi.first.apriltag.AprilTagFields;
import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Translation2d;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.DriverStation.Alliance;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
import frc.robot.Constants;
import frc.robot.subsystems.DriveSubsystem;
import frc.robot.subsystems.HopperSubsystem;
import frc.robot.subsystems.TurretSubsystem;
public class AutoShootCommandChassisTurretPathPlanner extends Command {
// For Chassis Control
private DriveSubsystem driveSubsystem;
private HopperSubsystem hopperSubsystem;
private TurretSubsystem turretSubsystem;
private int[] tagFilters;
private AprilTagFieldLayout fieldTags = AprilTagFieldLayout.loadField(AprilTagFields.k2026RebuiltWelded);
private Pose3d closestTag;
private Pose2d targetPPPose;
private Command pathFollowCommand;
public AutoShootCommandChassisTurretPathPlanner(DriveSubsystem driveSubsystem, HopperSubsystem hopperSubsystem,
TurretSubsystem turretSubsystem) {
this.driveSubsystem = driveSubsystem;
this.hopperSubsystem = hopperSubsystem;
this.turretSubsystem = turretSubsystem;
this.addRequirements(driveSubsystem, hopperSubsystem, turretSubsystem);
}
@Override
public void initialize() {
DriverStation.getAlliance().ifPresent((aliance) -> {
Pose3d targetHub;
if (aliance == Alliance.Blue) {
tagFilters = Constants.CameraConstanst.IDFilters.BLUE_HUB_SHOOTING_IDS;
targetHub = Constants.FieldPoses.BLUE_HUB_POSE;
} else {
tagFilters = Constants.CameraConstanst.IDFilters.RED_HUB_SHOOTING_IDS;
targetHub = Constants.FieldPoses.RED_HUB_POSE;
}
Pose3d botPose = driveSubsystem.getBotPose();
// Find closest target to us
List<Pose3d> tagPoses = Arrays.stream(tagFilters).mapToObj(tagId -> fieldTags.getTagPose(tagId).get())
.toList();
closestTag = botPose.nearest(tagPoses);
Logger.recordOutput("AutoAlignPP-ClosestAprilTagPose", closestTag);
Translation2d delta = botPose.getTranslation().toTranslation2d()
.minus(closestTag.getTranslation().toTranslation2d());
Logger.recordOutput("AutoAlignPP-ClosestAprilTagTransform", delta);
// Add 180 because we want the back of the robot to face the HUB
// deadreckoningAutoCenterController.setSetpoint(delta.getAngle().plus(Rotation2d.k180deg).getRadians());
// Translate back 1 meter from HUB
Translation2d newTrans = targetHub.getTranslation().toTranslation2d()
.plus(new Translation2d(1.5, delta.getAngle()));
targetPPPose = new Pose2d(newTrans, delta.getAngle());
Logger.recordOutput("AutoAlignPP-PPTargetPose", targetPPPose);
pathFollowCommand = AutoBuilder.pathfindToPose(targetPPPose,
new PathConstraints(Constants.RobotChassisLimits.MAX_ROBOT_LINEAR_VELOCITY,
Constants.RobotChassisLimits.MAX_ROBOT_LINEAR_VELOCITY_ACCEL,
Constants.RobotChassisLimits.MAX_ROBOT_ANGULAR_VELOCITY,
Constants.RobotChassisLimits.MAX_ROBOT_LINEAR_VELOCITY_ACCEL));
CommandScheduler.getInstance().schedule(pathFollowCommand);
});
turretSubsystem.setLauncher(5500);
hopperSubsystem.spindexerOn();
}
@Override
public void execute() {
// If robot is centered on HUB and shooter is up to speed. Run the kicker
if (turretSubsystem.isLauncherUpToSpeed() && pathFollowCommand.isFinished()) {
Logger.recordOutput("AutoAlignState-Shoot", "FIRE!");
hopperSubsystem.feedKicker();
} else {
Logger.recordOutput("AutoAlignState-Shoot", "Waiting");
}
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void end(boolean interrupted) {
CommandScheduler.getInstance().cancel(pathFollowCommand);
turretSubsystem.setLauncher(0);
hopperSubsystem.stopKicker();
hopperSubsystem.spindexerOff();
driveSubsystem.stopDrive();
LimelightHelpers.setPipelineIndex(Constants.CameraConstanst.BACK_CAMERA_NAME,
Constants.CameraConstanst.BACK_LOCALIZATION_PIPELINE_NUMBER);
}
}

View File

@ -1,96 +0,0 @@
package frc.robot.commands;
import org.littletonrobotics.junction.Logger;
import edu.wpi.first.apriltag.AprilTagFieldLayout;
import edu.wpi.first.apriltag.AprilTagFields;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.geometry.Transform2d;
import edu.wpi.first.math.geometry.Transform3d;
import edu.wpi.first.math.geometry.Translation2d;
import edu.wpi.first.math.geometry.Translation3d;
import edu.wpi.first.units.Units;
import edu.wpi.first.units.measure.Angle;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.DriverStation.Alliance;
import edu.wpi.first.wpilibj2.command.Command;
import frc.robot.Constants;
import frc.robot.Constants.ShootingRegion;
import frc.robot.Utils;
import frc.robot.subsystems.DriveSubsystem;
import frc.robot.subsystems.TurretSubsystem;
public class AutoTurretCommand extends Command {
private final Translation2d CENTER_TURRET_TO_ROBOT = new Translation2d(-0.1335024, -0.1824736);
private TurretSubsystem turretSubsystem;
private DriveSubsystem driveSubsystem;
private Alliance alliance;
public AutoTurretCommand(TurretSubsystem turretSubsystem, DriveSubsystem driveSubsystem) {
this.turretSubsystem = turretSubsystem;
this.driveSubsystem = driveSubsystem;
addRequirements(turretSubsystem); // Not adding drive as we don't wan't the drive command to be de-scheduled
}
@Override
public void initialize() {
// Perform auto-home
turretSubsystem.homeTurret();
DriverStation.getAlliance().ifPresent((aliance) -> {
if (aliance == Alliance.Blue) {
turretSubsystem.setTurretTargetPose(Constants.FieldPoses.BLUE_HUB_POSE);
aliance = Alliance.Blue;
} else {
turretSubsystem.setTurretTargetPose(Constants.FieldPoses.RED_HUB_POSE);
aliance = Alliance.Red;
}
});
}
@Override
public void execute() {
ShootingRegion shootingRegion = Utils.findRobotShootingRegion(driveSubsystem.getBotPose(), DriverStation.getAlliance().get());
if(shootingRegion != ShootingRegion.NON_SHOOTING_REGION){
Pose3d targetPose = Utils.findShootingTarget(shootingRegion, DriverStation.getAlliance().get());
turretSubsystem.setTurretTargetPose(targetPose);
}
Logger.recordOutput("AutoTurret-ScoringTarget", turretSubsystem.getTurretTargetPose());
Pose3d botPose = driveSubsystem.getBotPose();
Pose3d turretPose = botPose.plus(new Transform3d(new Translation3d(CENTER_TURRET_TO_ROBOT),
new Rotation3d(Rotation2d.fromRotations(turretSubsystem.getTurretAngleRobotRelative() + 0.5))));
Logger.recordOutput("AutoTurret-TurretPose", turretPose);
Translation2d targetTranslation = turretSubsystem.getTurretTargetPose().getTranslation().toTranslation2d().minus(turretPose.getTranslation().toTranslation2d());
Logger.recordOutput("AutoTurret-Translation", targetTranslation);
if(turretSubsystem.isTurretReady()){
turretSubsystem.setTurretFieldRelative(botPose, targetTranslation.getAngle().getRotations());
// turretSubsystem.setTurretRobotRelative(0.25);
// turretSubsystem.setTurretMotorRotation(0);
}
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void end(boolean interrupted) {
}
}

View File

@ -1,90 +0,0 @@
package frc.robot.commands;
import org.littletonrobotics.junction.Logger;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.units.Units;
import edu.wpi.first.units.measure.Angle;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.DriverStation.Alliance;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
import edu.wpi.first.wpilibj2.command.Commands;
import frc.robot.Constants;
import frc.robot.Utils;
import frc.robot.subsystems.DriveSubsystem;
import frc.robot.subsystems.HopperSubsystem;
import frc.robot.subsystems.IntakeSubsystem;
import frc.robot.subsystems.TurretSubsystem;
public class AutoTurretShootCommand extends Command{
private HopperSubsystem hopperSubsystem;
private TurretSubsystem turretSubsystem;
private IntakeSubsystem intakeSubsystem;
private DriveSubsystem driveSubsystem;
private Command waitCommand = Commands.waitSeconds(1.5);
private Alliance alliance;
public AutoTurretShootCommand(HopperSubsystem hopperSubsystem, TurretSubsystem turretSubsystem, IntakeSubsystem intakeSubsystem, DriveSubsystem driveSubsystem){
this.hopperSubsystem = hopperSubsystem;
this.turretSubsystem = turretSubsystem;
this.intakeSubsystem = intakeSubsystem;
this.driveSubsystem = driveSubsystem;
addRequirements(hopperSubsystem, intakeSubsystem);
}
@Override
public void initialize() {
hopperSubsystem.spindexerOn();
CommandScheduler.getInstance().schedule(waitCommand);
alliance = DriverStation.getAlliance().get();
Logger.recordOutput("AutoTurretShoot-State", "SHOOTING");
}
@Override
public void execute() {
double distance = turretSubsystem.getTurretTargetPose().toPose2d().getTranslation().getDistance(driveSubsystem.getBotPose().getTranslation().toTranslation2d());
Logger.recordOutput("AutoTurretShoot-DistanceToTarget(Meters)", distance);
turretSubsystem.setTargetElevation(Utils.getHoodAngleForDistance(distance));
turretSubsystem.setLauncher(Utils.getLauncherRPMForDistance(distance));
if(turretSubsystem.isLauncherUpToSpeed() && waitCommand.isFinished()){
hopperSubsystem.feedKicker();
}else{
hopperSubsystem.stopKicker();
}
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void end(boolean interrupted) {
waitCommand.cancel();
hopperSubsystem.spindexerOff();
hopperSubsystem.stopKicker();
turretSubsystem.setLauncher(0);
turretSubsystem.setTargetElevation(Constants.ShooterConstants.DEFLECTOR_STORED_ANGLE);
Logger.recordOutput("AutoTurretShoot-State", "NOT SHOOTING");
DriverStation.getAlliance().ifPresent((aliance) -> {
if (aliance == Alliance.Blue) {
turretSubsystem.setTurretTargetPose(Constants.FieldPoses.BLUE_HUB_POSE);
} else {
turretSubsystem.setTurretTargetPose(Constants.FieldPoses.RED_HUB_POSE);
}
});
// turretSubsystem.setTurretMotorRotation(-0.34); // Return to 0 after MIAMI VALLEY
}
}

View File

@ -1,12 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package frc.robot.commands;
public final class Autos {
private Autos() {
throw new UnsupportedOperationException("This is a utility class!");
}
}

View File

@ -1,43 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package frc.robot.commands;
import frc.robot.subsystems.ExampleSubsystem;
import edu.wpi.first.wpilibj2.command.Command;
/** An example command that uses an example subsystem. */
public class ExampleCommand extends Command {
@SuppressWarnings({"PMD.UnusedPrivateField", "PMD.SingularField"})
private final ExampleSubsystem m_subsystem;
/**
* Creates a new ExampleCommand.
*
* @param subsystem The subsystem used by this command.
*/
public ExampleCommand(ExampleSubsystem subsystem) {
m_subsystem = subsystem;
// Use addRequirements() here to declare subsystem dependencies.
addRequirements(subsystem);
}
// Called when the command is initially scheduled.
@Override
public void initialize() {}
// Called every time the scheduler runs while the command is scheduled.
@Override
public void execute() {}
// Called once the command ends or is interrupted.
@Override
public void end(boolean interrupted) {}
// Returns true when the command should end.
@Override
public boolean isFinished() {
return false;
}
}

View File

@ -1,57 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package frc.robot.commands;
import edu.wpi.first.wpilibj.XboxController;
import edu.wpi.first.wpilibj2.command.Command;
import frc.robot.subsystems.HopperSubsystem;
import frc.robot.subsystems.IntakeSubsystem;
/**
* An example command that uses an example subsystem.
*/
public class Reverse extends Command {
private IntakeSubsystem intakeSubsystem;
private HopperSubsystem hopperSubsystem;
private XboxController m_XboxController;
/**
* Creates a new ExampleCommand.
*
* @param subsystem The subsystem used by this command.
*/
public Reverse(IntakeSubsystem intakeSubsystem, HopperSubsystem hopperSubsystem, XboxController m_XboxController) {
this.intakeSubsystem = intakeSubsystem;
this.hopperSubsystem = hopperSubsystem;
this.m_XboxController = m_XboxController;
}
// Called when the command is initially scheduled.
@Override
public void initialize() {
}
// Called every time the scheduler runs while the command is scheduled.
@Override
public void execute() {
intakeSubsystem.reverseIntake();
hopperSubsystem.spindexerReverse();
hopperSubsystem.reverseKicker();
}
// Called once the command ends or is interrupted.
@Override
public void end(boolean interrupted) {
hopperSubsystem.stopKicker();
hopperSubsystem.spindexerOff();
intakeSubsystem.stopIntake(m_XboxController);
}
// Returns true when the command should end.
@Override
public boolean isFinished() {
return false;
}
}

View File

@ -1,31 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package frc.robot.commands;
import frc.robot.subsystems.TurretSubsystem;
import edu.wpi.first.wpilibj2.command.Command;
/** An example command that uses an example subsystem. **/
public class SetDeflectorCommand extends Command {
private final TurretSubsystem m_subsystem;
private double deployAngle;
public SetDeflectorCommand(TurretSubsystem subsystem, double deployAngle) {
m_subsystem = subsystem;
this.deployAngle = deployAngle;
}
// Called when the command is initially scheduled.
@Override
public void initialize() {
m_subsystem.setTargetElevation(deployAngle);
}
// Returns true when the command should end.
@Override
public boolean isFinished() {
return true;
}
}

View File

@ -1,86 +0,0 @@
package frc.robot.commands;
import org.fairportrobotics.frc.robolib.vision.limelight.LimelightHelpers;
import org.littletonrobotics.junction.Logger;
import edu.wpi.first.apriltag.AprilTagFieldLayout;
import edu.wpi.first.apriltag.AprilTagFields;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.DriverStation.Alliance;
import edu.wpi.first.wpilibj2.command.Command;
import frc.robot.Constants;
import frc.robot.subsystems.DriveSubsystem;
public class ZoneCheck extends Command {
private Zone zone;
private DriveSubsystem driveSubsystem;
public ZoneCheck(DriveSubsystem driveSubsystem)
{
this.driveSubsystem = driveSubsystem;
}
public enum Zone {
Blue,
Red,
Neutral,
INIT;
}
@Override
public void initialize() {
zone = Zone.INIT;
}
@Override
public void execute() {
getZone();
Logger.recordOutput("FieldZone", zone);
DriverStation.getAlliance().ifPresent((aliance) -> {
if (aliance == Alliance.Blue) {
switch (zone) {
case Blue:
System.out.println("BLUE");
break;
case Neutral:
System.out.println("NEUTRAL");
break;
case Red:
System.out.println("RED");
break;
default:
System.out.println("****");
}
} else {
switch (zone) {
case Blue:
System.out.println("BLUE");
break;
case Neutral:
System.out.println("NEUTRAL");
break;
case Red:
System.out.println("RED");
break;
default:
System.out.println("****");
}
}
});
}
@Override
public boolean isFinished() {
return false;
}
private void getZone() {
if (driveSubsystem.getBotPose().getX() >= Constants.CameraConstanst.RED_ZONE_COOR[0]) {
zone = Zone.Red;
} else if (driveSubsystem.getBotPose().getX() >= Constants.CameraConstanst.NEUTRAL_ZONE_COOR[0]) {
zone = Zone.Neutral;
} else {
zone = Zone.Blue;
}
}
}

View File

@ -1,280 +0,0 @@
package frc.robot.subsystems;
import static org.fairportrobotics.frc.posty.assertions.Assertions.assertThat;
import org.fairportrobotics.frc.posty.TestableSubsystem;
import org.fairportrobotics.frc.posty.test.PostTest;
import org.fairportrobotics.frc.robolib.drivesystems.swerve.SwerveBuilder;
import org.fairportrobotics.frc.robolib.drivesystems.swerve.SwerveDriveSystem;
import org.fairportrobotics.frc.robolib.drivesystems.swerve.SwerveModule;
import org.fairportrobotics.frc.robolib.vision.limelight.LimelightHelpers;
import org.littletonrobotics.junction.Logger;
import com.pathplanner.lib.auto.AutoBuilder;
import com.pathplanner.lib.config.PIDConstants;
import com.pathplanner.lib.config.RobotConfig;
import com.pathplanner.lib.controllers.PPHolonomicDriveController;
import com.pathplanner.lib.util.PathPlannerLogging;
import edu.wpi.first.math.VecBuilder;
import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Translation2d;
import edu.wpi.first.math.kinematics.ChassisSpeeds;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.RobotState;
import edu.wpi.first.wpilibj.smartdashboard.SendableChooser;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.Commands;
import edu.wpi.first.wpilibj2.command.button.CommandXboxController;
import frc.robot.Constants;
public class DriveSubsystem extends TestableSubsystem {
private SendableChooser<Command> autoChooser;
private SwerveBuilder swerveBuilder = new SwerveBuilder();
public SwerveDriveSystem driveSystem;
private double driveP = 0.1;
private double driveI = 0.0;
private double driveD = 0.01;
private double driveKV = 0.2;
private double steerP = 20;
private double steerI = 0;
private double steerD = 0.0;
private double steerKV = 0;
public DriveSubsystem(CommandXboxController xboxController) {
driveSystem = swerveBuilder
.withCanbusName("Drive")
.withPigeonId(Constants.ExtraIDEntities.PIGEON_ID)
.withMaxLinearVelocity(Constants.RobotChassisLimits.MAX_ROBOT_LINEAR_VELOCITY)
.withMaxAngularVelocity(Constants.RobotChassisLimits.MAX_ROBOT_ANGULAR_VELOCITY)
.withSwerveModule(
swerveBuilder.new SwerveModuleBuilder()
.withDriveMotorId(Constants.SwerveDriveIDs.FRONT_LEFT_DRIVE_ID)
.withDriveKP(driveP)
.withDriveKI(driveI)
.withDriveKD(driveD)
.withDriveKV(driveKV)
.withSteerMotorId(Constants.SwerveDriveIDs.FRONT_LEFT_STEER_ID)
.withSteerKP(steerP)
.withSteerKI(steerI)
.withSteerKD(steerD)
.withSteerKV(steerKV)
// If the offsets don't work, contact Tyler W or Dom G
.withSteerOffset(Constants.SwerveDriveOffsets.FRONT_LEFT_MOTOR_OFFSET)
.withSteerEncoderId(Constants.SwerveDriveIDs.FRONT_LEFT_ENCODER_ID)
.withModuleLocation(new Translation2d(1, 1))
.withModuleName("Front Left")
.withGearRatio(8.14)
.withWheelDiameter(0.1016)
.build())
.withSwerveModule(
swerveBuilder.new SwerveModuleBuilder()
.withDriveMotorId(Constants.SwerveDriveIDs.FRONT_RIGHT_DRIVE_ID)
.withDriveKP(driveP)
.withDriveKI(driveI)
.withDriveKD(driveD)
.withDriveKV(driveKV)
.withDriveInverted()
.withSteerMotorId(Constants.SwerveDriveIDs.FRONT_RIGHT_STEER_ID)
.withSteerKP(steerP)
.withSteerKI(steerI)
.withSteerKD(steerD)
.withSteerKV(steerKV)
.withSteerOffset(Constants.SwerveDriveOffsets.FRONT_RIGHT_MOTOR_OFFSET)
.withSteerEncoderId(Constants.SwerveDriveIDs.FRONT_RIGHT_ENCODER_ID)
.withModuleLocation(new Translation2d(1, -1))
.withModuleName("Front Right")
.withGearRatio(8.14)
.withWheelDiameter(0.1016)
.build())
.withSwerveModule(
swerveBuilder.new SwerveModuleBuilder()
.withDriveMotorId(Constants.SwerveDriveIDs.BACK_LEFT_DRIVE_ID)
.withDriveKP(driveP)
.withDriveKI(driveI)
.withDriveKD(driveD)
.withDriveKV(driveKV)
.withSteerMotorId(Constants.SwerveDriveIDs.BACK_LEFT_STEER_ID)
.withSteerKP(steerP)
.withSteerKI(steerI)
.withSteerKD(steerD)
.withSteerKV(steerKV)
.withSteerOffset(Constants.SwerveDriveOffsets.BACK_LEFT_MOTOR_OFFSET)
.withSteerEncoderId(Constants.SwerveDriveIDs.BACK_LEFT_ENCODER_ID)
.withModuleLocation(new Translation2d(-1, 1))
.withModuleName("Back Left")
.withGearRatio(8.14)
.withWheelDiameter(0.1016)
.build())
.withSwerveModule(
swerveBuilder.new SwerveModuleBuilder()
.withDriveMotorId(Constants.SwerveDriveIDs.BACK_RIGHT_DRIVE_ID)
.withDriveKP(driveP)
.withDriveKI(driveI)
.withDriveKD(driveD)
.withDriveKV(driveKV)
.withDriveInverted()
.withSteerMotorId(Constants.SwerveDriveIDs.BACK_RIGHT_STEER_ID)
.withSteerKP(steerP)
.withSteerKI(steerI)
.withSteerKD(steerD)
.withSteerKV(steerKV)
.withSteerOffset(Constants.SwerveDriveOffsets.BACK_RIGHT_MOTOR_OFFSET)
.withSteerEncoderId(Constants.SwerveDriveIDs.BACK_RIGHT_ENCODER_ID)
.withModuleLocation(new Translation2d(-1, -1))
.withModuleName("Back Right")
.withGearRatio(8.14)
.withWheelDiameter(0.1016)
.build())
.build();
// Set vision measurement confidence values
driveSystem.getPoseEstimator().setVisionMeasurementStdDevs(VecBuilder.fill(0.7, 0.7, 0.7, 0.1));
this.setDefaultCommand(Commands.run(new Runnable() {
@Override
public void run() {
driveSystem.setChassisSpeedsFromJoystickFieldRelative(
-(xboxController.getLeftY() * Math.abs(xboxController.getLeftY())),
-(xboxController.getLeftX() * Math.abs(xboxController.getLeftX())),
-(xboxController.getRawAxis(4) * Math.abs(xboxController.getRawAxis(4))));
Logger.recordOutput("RequestedSwerveState", driveSystem.getRequestedModuleStates());
Logger.recordOutput("ActualSwerveState", driveSystem.getActualModuleStates());
}
}, this));
RobotConfig config;
try {
config = RobotConfig.fromGUISettings();
AutoBuilder.configure(
driveSystem::getRobotPose2d,
driveSystem::setPose2d,
driveSystem::getRobotRelativeSpeeds,
(speeds, feedforwards) -> {driveSystem.setChassisSpeed(ChassisSpeeds.fromRobotRelativeSpeeds(-speeds.vxMetersPerSecond, -speeds.vyMetersPerSecond, -speeds.omegaRadiansPerSecond, driveSystem.getRobotHeading()), new Translation2d());},
new PPHolonomicDriveController(
new PIDConstants(5, 0, 0),
new PIDConstants(Math.PI, 0, 0)),
config,
() -> {
var alliance = DriverStation.getAlliance();
if (alliance.isPresent()) {
return alliance.get() == DriverStation.Alliance.Red;
}
return false;
},
this);
autoChooser = AutoBuilder.buildAutoChooser();
autoChooser.setDefaultOption("Do Nothing", Commands.none());
SmartDashboard.putData("Auto Chooser", autoChooser);
// PathPlannerLogging.setLogActivePathCallback((List<Pose2d> pose) -> {});
PathPlannerLogging.setLogTargetPoseCallback((Pose2d targetPose) -> { Logger.recordOutput("PathPlanner-TargetPose", targetPose);});
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void periodic() {
super.periodic();
driveSystem.periodic();
Logger.recordOutput("RobotPose", driveSystem.getRobotPose3d());
LimelightHelpers.SetRobotOrientation(Constants.CameraConstanst.FRONT_CAMERA_NAME, driveSystem.getRobotPose2d().getRotation().getDegrees(), 0, 0, 0, 0, 0);
LimelightHelpers.SetRobotOrientation(Constants.CameraConstanst.BACK_CAMERA_NAME, driveSystem.getRobotPose2d().getRotation().getDegrees(), 0, 0, 0, 0, 0);
// MegaTag 2 impl
if(RobotState.isDisabled()){
LimelightHelpers.SetIMUMode(Constants.CameraConstanst.FRONT_CAMERA_NAME, 1);
LimelightHelpers.SetIMUMode(Constants.CameraConstanst.BACK_CAMERA_NAME, 1);
}else{
LimelightHelpers.SetIMUMode(Constants.CameraConstanst.FRONT_CAMERA_NAME, 4);
LimelightHelpers.SetIMUMode(Constants.CameraConstanst.BACK_CAMERA_NAME, 4);
}
LimelightHelpers.PoseEstimate frontTag = LimelightHelpers.getBotPoseEstimate_wpiBlue_MegaTag2(Constants.CameraConstanst.FRONT_CAMERA_NAME);
LimelightHelpers.PoseEstimate backTag = LimelightHelpers.getBotPoseEstimate_wpiBlue_MegaTag2(Constants.CameraConstanst.BACK_CAMERA_NAME);
if(frontTag.tagCount >= 2){
driveSystem.getPoseEstimator().addVisionMeasurement(new Pose3d(frontTag.pose), frontTag.timestampSeconds);
Logger.recordOutput("FrontPose-Estimate", frontTag.pose);
Logger.recordOutput("FrontPose-IsMegaTag2", frontTag.isMegaTag2);
}
if(backTag.tagCount >= 2){
driveSystem.getPoseEstimator().addVisionMeasurement(new Pose3d(backTag.pose), backTag.timestampSeconds);
Logger.recordOutput("BackPose-Estimate", backTag.pose);
Logger.recordOutput("BackPose-IsMegaTag2", frontTag.isMegaTag2);
}
// MegaTag 1 Impl
// LimelightHelpers.PoseEstimate frontCameraPose = LimelightHelpers.getBotPoseEstimate_wpiBlue(Constants.CameraConstanst.FRONT_CAMERA_NAME);
// if (frontCameraPose.tagCount >= 2) {
// driveSystem.getPoseEstimator().addVisionMeasurement(new Pose3d(frontCameraPose.pose), frontCameraPose.timestampSeconds);
// Logger.recordOutput("FrontPoseEstimate", frontCameraPose.pose);
// }
// LimelightHelpers.PoseEstimate backCameraPose = LimelightHelpers.getBotPoseEstimate_wpiBlue(Constants.CameraConstanst.BACK_CAMERA_NAME);
// if (backCameraPose.tagCount >= 2) {
// driveSystem.getPoseEstimator().addVisionMeasurement(new Pose3d(backCameraPose.pose), backCameraPose.timestampSeconds);
// Logger.recordOutput("BackPoseEstimate", backCameraPose.pose);
// }
}
@Override
public void simulationPeriodic() {
super.simulationPeriodic();
driveSystem.simulationPeriodic();
}
public Command getAutoCommand() {
return autoChooser.getSelected();
}
public void stopDrive() {
driveSystem.setChassisSpeed(ChassisSpeeds.fromRobotRelativeSpeeds(0, 0, 0, driveSystem.getGyro().getRotation2d()), Translation2d.kZero);
}
public void rotateChassis(double rotSpeed) {
driveSystem.setChassisSpeed(ChassisSpeeds.fromRobotRelativeSpeeds(0, 0, rotSpeed, driveSystem.getGyro().getRotation2d()), Translation2d.kZero);
}
public Pose3d getBotPose() {
return driveSystem.getRobotPose3d();
}
@PostTest()
public void DriveSubsystem_CanDevicesConnected() {
SwerveModule[] modules = driveSystem.getModules();
for (int i = 0; i < modules.length; i++) {
assertThat(modules[i].getDriveMotor().isConnected())
.as(modules[i].getModuleName() + " drive motor is not connected").isTrue();
assertThat(modules[i].getSteerMotor().isConnected())
.as(modules[i].getModuleName() + " steer motor is not connected").isTrue();
assertThat(modules[i].getSteerEncoder().isConnected())
.as(modules[i].getModuleName() + " CANCoder is not connected").isTrue();
}
assertThat(driveSystem.getGyro().isConnected()).as("Pigeon is not connected").isTrue();
}
}

View File

@ -1,60 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package frc.robot.subsystems;
import org.fairportrobotics.frc.posty.TestableSubsystem;
import org.fairportrobotics.frc.posty.test.PostTest;
import static org.fairportrobotics.frc.posty.assertions.Assertions.*;
import edu.wpi.first.wpilibj2.command.Command;
public class ExampleSubsystem extends TestableSubsystem {
/** Creates a new ExampleSubsystem. */
public ExampleSubsystem() {}
/**
* Example command factory method.
*
* @return a command
*/
public Command exampleMethodCommand() {
// Inline construction of command goes here.
// Subsystem::RunOnce implicitly requires `this` subsystem.
return runOnce(
() -> {
/* one-time action goes here */
});
}
/**
* An example method querying a boolean state of the subsystem (for example, a digital sensor).
*
* @return value of some boolean subsystem state, such as a digital sensor.
*/
public boolean exampleCondition() {
// Query some boolean state, such as a digital sensor.
return false;
}
@Override
public void periodic() {
// This method will be called once per scheduler run
}
@Override
public void simulationPeriodic() {
// This method will be called once per scheduler run during simulation
}
@PostTest(name = "A friendly name", enabled = true)
public void myFailingPostTest(){
assertThat(true).isFalse();
}
@PostTest(enabled = true)
public void myPassingPostTest(){
assertThat("Hello World").endWith("World");
}
}

View File

@ -1,114 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package frc.robot.subsystems;
import org.fairportrobotics.frc.posty.TestableSubsystem;
import static org.fairportrobotics.frc.posty.assertions.Assertions.assertThat;
import org.fairportrobotics.frc.posty.test.PostTest;
import org.littletonrobotics.junction.Logger;
import com.ctre.phoenix6.StatusSignal;
import com.ctre.phoenix6.configs.MotorOutputConfigs;
import com.ctre.phoenix6.configs.Slot0Configs;
import com.ctre.phoenix6.configs.TalonFXConfiguration;
import com.ctre.phoenix6.controls.VelocityVoltage;
import com.ctre.phoenix6.hardware.TalonFX;
import edu.wpi.first.units.Units;
import edu.wpi.first.units.measure.AngularVelocity;
import edu.wpi.first.wpilibj.GenericHID.RumbleType;
import edu.wpi.first.wpilibj.XboxController;
import edu.wpi.first.wpilibj2.command.Command;
import frc.robot.Constants;
public class HopperSubsystem extends TestableSubsystem {
private TalonFX spindexerMotor;
private TalonFX kickerMotor;
private VelocityVoltage kickerControl = new VelocityVoltage(0);
private StatusSignal<AngularVelocity> kickerVelocity;
/**
* Creates a new HopperSubsystem.
*/
public HopperSubsystem() {
spindexerMotor = new TalonFX(Constants.HopperConstants.SPINDEXER_MOTOR_ID);
spindexerMotor.getConfigurator().apply(
new TalonFXConfiguration()
.withMotorOutput(
new MotorOutputConfigs()
.withInverted(Constants.HopperConstants.SPINDEXER_MOTOR_DIRECTION))
.withSlot0(
new Slot0Configs()
.withKP(0)
.withKI(0)
.withKD(0)
.withKS(0)));
kickerMotor = new TalonFX(Constants.HopperConstants.KICKER_MOTOR_ID);
kickerMotor.getConfigurator().apply(
new TalonFXConfiguration()
.withMotorOutput(
new MotorOutputConfigs()
.withInverted(Constants.HopperConstants.KICKER_MOTOR_DIRECTION)
)
.withSlot0(new Slot0Configs()
.withKP(0)
.withKI(0)
.withKD(0)
.withKS(0)
));
kickerVelocity = kickerMotor.getVelocity();
}
public void feedKicker() {
//kickerMotor.setControl(kickerControl.withVelocity(1000));
kickerMotor.set(0.75);
}
public void reverseKicker() {
//kickerMotor.setControl(kickerControl.withVelocity(-500));
kickerMotor.set(-0.5);
}
public void stopKicker() {
kickerMotor.stopMotor();
}
public void spindexerOn() {
spindexerMotor.set(0.75);
}
public void spindexerReverse() {
spindexerMotor.set(-0.75);
}
public void spindexerOff() {
spindexerMotor.stopMotor();
}
public Command spinny() {
return this.runOnce(() -> {
if (spindexerMotor.get() == 0) {
spindexerOn();
} else {
spindexerOff();
}
});
}
@Override
public void periodic() {
Logger.recordOutput("HopperSubsystem-KickerSpeed(Rotations/sec)", kickerVelocity.refresh().getValue().in(Units.RotationsPerSecond));
}
@PostTest(enabled = true)
public void hopperSystem_CANDevicesConnected() {
assertThat(kickerMotor.isConnected()).as("Kicker motor not connected!").isTrue();
assertThat(spindexerMotor.isConnected()).as("Spindexer motor not connected!").isTrue();
}
}

View File

@ -1,93 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package frc.robot.subsystems;
import org.fairportrobotics.frc.posty.TestableSubsystem;
import static org.fairportrobotics.frc.posty.assertions.Assertions.assertThat;
import org.fairportrobotics.frc.posty.test.PostTest;
import static org.fairportrobotics.frc.robolib.motors.Utils.SetMotorDirection;
import com.ctre.phoenix.motorcontrol.LimitSwitchNormal;
import com.ctre.phoenix.motorcontrol.LimitSwitchSource;
import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX;
import com.ctre.phoenix6.hardware.TalonFX;
import edu.wpi.first.wpilibj.XboxController;
import edu.wpi.first.wpilibj.GenericHID.RumbleType;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.Commands;
import frc.robot.Constants;
public class IntakeSubsystem extends TestableSubsystem {
/**
* Creates a new ExampleSubsystem.
*/
private TalonFX intakeMotor;
private WPI_TalonSRX deployMotor;
public IntakeSubsystem() {
intakeMotor = new TalonFX(Constants.IntakeConstants.INTAKE_MOTOR_ID);
SetMotorDirection(intakeMotor, Constants.IntakeConstants.INTAKE_MOTOR_DIRECTION);
deployMotor = new WPI_TalonSRX(Constants.IntakeConstants.DEPLOY_MOTOR_ID);
deployMotor.configForwardLimitSwitchSource(LimitSwitchSource.FeedbackConnector, LimitSwitchNormal.NormallyOpen);
deployMotor.configReverseLimitSwitchSource(LimitSwitchSource.FeedbackConnector, LimitSwitchNormal.NormallyOpen);
deployMotor.setInverted(Constants.IntakeConstants.DEPLOY_MOTOR_INVERTED);
}
public Command deploy() {
return Commands.sequence(
Commands.deadline(
Commands.waitSeconds(2),
this.runOnce(
() -> {
deployMotor.set(1);
} // Bump this speed up if intake doesn't deploy
)
),
this.runOnce(() -> deployMotor.stopMotor())
);
}
public Command resetDeploy() {
return this.runEnd(() -> deployMotor.set(-1), () -> deployMotor.stopMotor());
}
public void setSpeed(double speed) {
intakeMotor.set(speed);
}
public Command stopIntake(XboxController m_XboxController) {
return this.runOnce(() -> {
intakeMotor.stopMotor();
m_XboxController.setRumble(RumbleType.kBothRumble, .0);
});
}
public Command intake(XboxController m_XboxController) {
return this.runOnce(() -> {
if (intakeMotor.get() == 0) {
setSpeed(-.5);
m_XboxController.setRumble(RumbleType.kBothRumble, .5);
} else {
intakeMotor.stopMotor();
m_XboxController.setRumble(RumbleType.kBothRumble, .0);
}
});
}
public Command reverseIntake() {
return this.runEnd(() -> setSpeed(.5), () -> {
intakeMotor.stopMotor();
});
}
@PostTest(enabled = true)
public void IntakeSubsystem_CANDevicesConnected() {
assertThat(intakeMotor.isConnected()).as("Intake motor not connected!").isTrue();
assertThat(deployMotor.isAlive()).as("Deploy controller not connected!").isTrue();
}
}

View File

@ -1,318 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package frc.robot.subsystems;
import org.fairportrobotics.frc.posty.TestableSubsystem;
import org.fairportrobotics.frc.posty.test.PostTest;
import org.littletonrobotics.junction.Logger;
import org.littletonrobotics.junction.ConsoleSource.RoboRIO;
import static org.fairportrobotics.frc.posty.assertions.Assertions.*;
import com.ctre.phoenix6.controls.PositionVoltage;
import com.ctre.phoenix6.StatusSignal;
import com.ctre.phoenix6.configs.CurrentLimitsConfigs;
import com.ctre.phoenix6.configs.FeedbackConfigs;
import com.ctre.phoenix6.configs.MotorOutputConfigs;
import com.ctre.phoenix6.configs.Slot0Configs;
import com.ctre.phoenix6.configs.SoftwareLimitSwitchConfigs;
import com.ctre.phoenix6.configs.TalonFXConfiguration;
import com.ctre.phoenix6.hardware.TalonFX;
import com.ctre.phoenix6.signals.ForwardLimitValue;
import com.ctre.phoenix6.signals.InvertedValue;
import com.ctre.phoenix6.signals.ReverseLimitValue;
import com.ctre.phoenix6.sim.TalonFXSimState;
import com.revrobotics.PersistMode;
import com.revrobotics.ResetMode;
import com.revrobotics.spark.ClosedLoopSlot;
import com.revrobotics.spark.FeedbackSensor;
import com.revrobotics.spark.SparkClosedLoopController;
import com.revrobotics.spark.SparkMax;
import com.revrobotics.spark.SparkBase.ControlType;
import com.revrobotics.spark.SparkLowLevel.MotorType;
import com.revrobotics.spark.config.SparkMaxConfig;
import frc.robot.Constants;
import frc.robot.Robot;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.units.AngleUnit;
import edu.wpi.first.units.Units;
import edu.wpi.first.units.measure.Angle;
import edu.wpi.first.wpilibj.DigitalInput;
import edu.wpi.first.wpilibj.Servo;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.Commands;
public class TurretSubsystem extends TestableSubsystem {
public enum TurretState {
INIT,
HOMING,
READY;
}
private final boolean LOGGING = true;
private TurretState turretState;
private DigitalInput turretLimitSwitch;
private TalonFX turretMotor;
private PositionVoltage turretControlMode = new PositionVoltage(0);
private TalonFXSimState turretSim;
private StatusSignal<Angle> turretMotorPosition;
private StatusSignal<ForwardLimitValue> turretMotorForwardLimit;
private StatusSignal<ReverseLimitValue> turretMotorReverseLimit;
private StatusSignal<Double> turretPositionError;
private double forwardLimit = 0;
private double reverseLimit = 0;
private SparkMax launcherMotor;
private SparkClosedLoopController launcherControler;
private double turretOffsetInRotations = 0;
private Servo hood;
private Pose3d turretTarget;
public TurretSubsystem() {
// Turret configuration
turretLimitSwitch = new DigitalInput(Constants.ShooterConstants.TURRET_LIMIT_CHANNEL);
turretMotor = new TalonFX(Constants.ShooterConstants.TURRET_MOTOR_ID);
turretMotor.getConfigurator().apply(
new TalonFXConfiguration()
.withSlot0(
new Slot0Configs()
.withKP(6)
.withKI(0)
.withKD(0.3)
.withKS(0)
)
.withMotorOutput(
new MotorOutputConfigs()
.withInverted(InvertedValue.Clockwise_Positive)
)
.withCurrentLimits(
new CurrentLimitsConfigs()
.withStatorCurrentLimit(50)
)
.withSoftwareLimitSwitch(new SoftwareLimitSwitchConfigs().withForwardSoftLimitEnable(false).withReverseSoftLimitEnable(false))
.withFeedback(
new FeedbackConfigs()
.withFeedbackRotorOffset(0)
)
);
turretMotorPosition = turretMotor.getPosition();
turretMotorForwardLimit = turretMotor.getForwardLimit();
turretMotorReverseLimit = turretMotor.getReverseLimit();
turretPositionError = turretMotor.getClosedLoopError();
turretSim = turretMotor.getSimState();
// Launcher configuration
launcherMotor = new SparkMax(Constants.ShooterConstants.LAUNCHER_MOTOR_ID, MotorType.kBrushless);
SparkMaxConfig config = new SparkMaxConfig().apply((SparkMaxConfig) SparkMaxConfig.Presets.REV_NEO);
config.inverted(Constants.ShooterConstants.LAUNCHER_MOTOR_INVERTED);
config.voltageCompensation(10);
config.closedLoop.p(0.0002).i(0.000001).d(0.0005); // I = 0.0000001
config.closedLoop.feedForward.kS(0.2).kV(0.000).kA(0.000);
// config.closedLoop.allowedClosedLoopError(10, ClosedLoopSlot.kSlot0);
config.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder);
config.closedLoop.positionWrappingEnabled(false);
// config.closedLoopRampRate(0.5);
// config.smartCurrentLimit(5, 50);
launcherMotor.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);
launcherControler = launcherMotor.getClosedLoopController();
// Hood configuration
hood = new Servo(Constants.ShooterConstants.HOOD_SERVO_CHANNEL);
turretState = TurretState.INIT;
}
public void setLauncher(double speed) {
if(speed == 0)
{
launcherMotor.stopMotor();
return;
}
launcherControler.setSetpoint(speed, ControlType.kVelocity, ClosedLoopSlot.kSlot0);
}
public boolean isLauncherUpToSpeed() {
// Valid if velcity is within 10% of setpoint.
// May need to lower this
return true;
// return Math.abs(launcherControler.getSetpoint() - launcherMotor.getEncoder().getVelocity()) >= launcherControler.getSetpoint() * 0.03;
}
public void setTargetElevation(double elev)
{
double pos = Math.abs((elev - 300)/300);
Logger.recordOutput("ShooterHood-Angle", pos);
hood.set(pos);
}
public Command revSpeedCommand()
{
return this.runOnce(() -> setLauncher(-.5));
}
public Command setShooter(int speedRPM){
return Commands.runEnd(() -> {
setLauncher(speedRPM);
}, () -> {
launcherMotor.stopMotor();
}, this);
}
public void homeTurret()
{
if(turretState != TurretState.INIT){
return;
}
turretMotor.set(Constants.ShooterConstants.HOMING_SPEED);
turretState = TurretState.HOMING;
}
public boolean isTurretReady(){
return turretState == TurretState.READY;
}
public void turretRotateDir(double speed)
{
turretMotor.set(speed);
}
public double getTurretAngleRobotRelative()
{
return getTurretMotorPosition() / Constants.ShooterConstants.TURRET_GEAR_RATIO;
}
public void setTurretTargetPose(Pose3d targetPose){
turretTarget = targetPose;
}
public Pose3d getTurretTargetPose(){
return turretTarget;
}
private double getTurretMotorPosition(){
return turretMotorPosition.refresh().getValueAsDouble() - turretOffsetInRotations;
}
@Override
public void periodic() {
if (turretState == TurretState.HOMING) {
if (turretLimitSwitch.get()) { // Turret has tripped the switch
turretMotor.stopMotor();
turretOffsetInRotations = (turretMotorPosition.refresh().getValueAsDouble() - 1.323);
if (!Robot.isReal()) {
turretOffsetInRotations = 0; // Flip turret around
}
turretState = TurretState.READY;
forwardLimit = (1.5 + turretOffsetInRotations);
reverseLimit = (-1.5 + turretOffsetInRotations);
Logger.recordOutput("TurretSubsystem-TurretForwardLimit", forwardLimit);
Logger.recordOutput("TurretSubsystem-TurretReverseLimit", reverseLimit);
turretMotor.getConfigurator().apply(
new SoftwareLimitSwitchConfigs()
.withForwardSoftLimitEnable(true)
.withForwardSoftLimitThreshold(forwardLimit)
.withReverseSoftLimitEnable(true)
.withReverseSoftLimitThreshold(reverseLimit));
// setTurretRobotRelative(0);
// setTurretMotorRotation(-0.34); // Return to 0 after MIAMI VALLEY
}
}
if (LOGGING) {
Logger.recordOutput("TurretSubsystem-TurretLimitSwitch", turretLimitSwitch.get());
Logger.recordOutput("TurretSubsystem-Launcher Speed (RPM)", launcherMotor.getEncoder().getVelocity());
Logger.recordOutput("TurretSubsystem-TurretState", turretState);
Logger.recordOutput("TurretSubsystem-TurretPositionRobotRelative", getTurretAngleRobotRelative());
Logger.recordOutput("TurretSubsystem-TurretMotorPositionWithOffset", getTurretMotorPosition());
Logger.recordOutput("TurretSubsystem-TurretMotorPositionRaw",
turretMotorPosition.refresh().getValue().in(Units.Rotations));
Logger.recordOutput("TurretSubsystem-TurretRequestedPosRaw",
turretControlMode.getPositionMeasure().in(Units.Rotations));
Logger.recordOutput("TurretSubsystem-TurretRequestedPos", getRequestedPosTurretRelative());
Logger.recordOutput("TurretSubsystem-TurretOffset", turretOffsetInRotations);
Logger.recordOutput("TurretSubsystem-TurretForwardLimitHit", turretMotorForwardLimit.refresh().getValue());
Logger.recordOutput("TurretSubsystem-TurretReverseLimitHit", turretMotorReverseLimit.refresh().getValue());
Logger.recordOutput("TurretSubsystem-TurretPositionError", turretPositionError.refresh().getValueAsDouble());
Logger.recordOutput("TurretSubsystem-LauncherSetpoint(RPM)", launcherControler.getSetpoint());
}
}
public void setTurretFieldRelative(Pose3d robotPose, double pos)
{
if(turretState != TurretState.READY)
{
// Not ready to set angle
return;
}
double correctedPos = -pos; // Flip angle to match CCW positive. Add 0.5 to flip around
double robotRotations = robotPose.getRotation().toRotation2d().getRotations();
Rotation2d turretAngle = Rotation2d.fromRotations(correctedPos).plus(Rotation2d.fromRotations(robotRotations)).minus(Rotation2d.fromRotations(0.5));
Logger.recordOutput("TurretSubsystem-TurretFieldTarget", turretAngle.getRotations());
setTurretRobotRelative(((turretAngle.getRotations())));
}
public void setTurretRobotRelative(double pos)
{
if(turretState != TurretState.READY)
{
// Not ready to set angle
return;
}
double correctedPos = -pos; // Flip angle to match CCW positive
setTurretMotorRotation(correctedPos * Constants.ShooterConstants.TURRET_GEAR_RATIO);
}
public void setTurretMotorRotation(double pos){
double posWithOffset = pos + turretOffsetInRotations;
Logger.recordOutput("Blah", posWithOffset);
if(Robot.isSimulation()){
if(posWithOffset >= forwardLimit){
turretSim.setForwardLimit(true);
} else {
turretSim.setForwardLimit(false);
}
if(posWithOffset <= reverseLimit){
turretSim.setReverseLimit(true);
} else {
turretSim.setReverseLimit(false);
}
}
turretMotor.setControl(turretControlMode.withPosition(posWithOffset));
turretSim.setRawRotorPosition(-posWithOffset);
}
public boolean isTurretAtTarget(){
return Math.abs(turretPositionError.refresh().getValueAsDouble()) <= 0.1;
}
private double getRequestedPosTurretRelative() {
return (turretControlMode.getPositionMeasure().in(Units.Rotations) - turretOffsetInRotations) / Constants.ShooterConstants.TURRET_GEAR_RATIO;
}
@PostTest()
public void TurretSubsystem_CANDevicesConnected(){
assertThat(launcherMotor.getFaults().can).as("Launcher motor not connected!").isFalse();
assertThat(turretMotor.isConnected()).as("Turret motor not connected!").isTrue();
}
}

167
utils.py Normal file
View File

@ -0,0 +1,167 @@
"""
utils.py – pure math utilities for Castor 2026 (Python/MagicBot port).
Direct translation of Utils.java. No framework dependencies.
"""
import math
from wpilib import DriverStation
from wpimath.geometry import Pose3d
from constants import FieldPoses, ShootingRegion, ShootingRegionDimensions
def calculate_turret_angle_from_camera_tag_distance(distance_to_tag: float) -> float:
"""
Calculates the turret rotation (in rotations, robot-relative) needed
to aim at the HUB given the camera's measured distance to the tag.
:param distance_to_tag: camera distance to the AprilTag (metres)
:return: turret angle in rotations (robot-relative)
"""
camera_distance_to_target = distance_to_tag + 0.6
camera_to_shoot_distance = 0.26035
# Pythagorean theorem
shooter_distance_to_target = math.sqrt(
camera_distance_to_target**2 + camera_to_shoot_distance**2
)
angle_at_shooter = math.acos(camera_to_shoot_distance / shooter_distance_to_target)
robot_turret_angle = (math.pi / 2) - angle_at_shooter
robot_turret_rotations = robot_turret_angle / math.pi
return robot_turret_rotations
def find_robot_shooting_region(
robot_pose: Pose3d,
alliance: DriverStation.Alliance,
) -> ShootingRegion:
"""
Determines the shooting region for the robot based on its pose and alliance.
:param robot_pose: current robot pose
:param alliance: robot's alliance (Blue or Red)
:return: the matching ShootingRegion enum value
"""
region = ShootingRegion.NON_SHOOTING_REGION
robot_x = robot_pose.X()
robot_y = robot_pose.Y()
# Local aliases to keep condition lines under 120 chars
D = ShootingRegionDimensions
in_neutral_x = D.NEUTRAL_ZONE_REGION_X_MIN < robot_x < D.NEUTRAL_ZONE_REGION_X_MAX
if alliance == DriverStation.Alliance.kBlue:
if robot_x < ShootingRegionDimensions.BLUE_ALLIANCE_ZONE_REGION_X_MAX:
region = ShootingRegion.BLUE_OWN_ALLIANCE_ZONE
elif (
in_neutral_x
and robot_y > ShootingRegionDimensions.BLUE_LEFT_REGION_Y_MIN
):
region = ShootingRegion.BLUE_LEFT_NEUTRAL_ZONE
elif (
in_neutral_x
and robot_y < ShootingRegionDimensions.BLUE_RIGHT_REGION_Y_MAX
):
region = ShootingRegion.BLUE_RIGHT_NEUTRAL_ZONE
elif (
robot_x > ShootingRegionDimensions.RED_ALLIANCE_ZONE_REGION_X_MIN
and robot_y > ShootingRegionDimensions.BLUE_LEFT_REGION_Y_MIN
):
region = ShootingRegion.BLUE_LEFT_OPPONENT_ALLIANCE_ZONE
elif (
robot_x > ShootingRegionDimensions.RED_ALLIANCE_ZONE_REGION_X_MIN
and robot_y < ShootingRegionDimensions.BLUE_RIGHT_REGION_Y_MAX
):
region = ShootingRegion.BLUE_RIGHT_OPPONENT_ALLIANCE_ZONE
elif alliance == DriverStation.Alliance.kRed:
if robot_x > ShootingRegionDimensions.RED_ALLIANCE_ZONE_REGION_X_MIN:
region = ShootingRegion.RED_OWN_ALLIANCE_ZONE
elif (
in_neutral_x
and robot_y < ShootingRegionDimensions.RED_LEFT_REGION_Y_MAX
):
region = ShootingRegion.RED_LEFT_NEUTRAL_ZONE
elif (
in_neutral_x
and robot_y > ShootingRegionDimensions.RED_RIGHT_REGION_Y_MIN
):
region = ShootingRegion.RED_RIGHT_NEUTRAL_ZONE
elif (
robot_x < ShootingRegionDimensions.BLUE_ALLIANCE_ZONE_REGION_X_MAX
and robot_y < ShootingRegionDimensions.RED_LEFT_REGION_Y_MAX
):
region = ShootingRegion.RED_LEFT_OPPONENT_ALLIANCE_ZONE
elif (
robot_x > ShootingRegionDimensions.RED_ALLIANCE_ZONE_REGION_X_MIN
and robot_y > ShootingRegionDimensions.RED_RIGHT_REGION_Y_MIN
):
region = ShootingRegion.RED_RIGHT_OPPONENT_ALLIANCE_ZONE
return region
def get_hood_angle_for_distance(distance_meters: float) -> float:
"""
Returns the hood angle for a given distance.
:param distance_meters: distance to target in metres
:return: hood servo angle
"""
if distance_meters >= 4:
return 200.0
return 0.0
def get_launcher_rpm_for_distance(distance_meters: float) -> float:
"""
Returns the launcher RPM for a given distance, capped at 5000 RPM.
:param distance_meters: distance to target in metres
:return: launcher RPM
"""
return min((667.557 * distance_meters) + 1500.699, 5000.0)
def find_shooting_target(
region: ShootingRegion,
alliance: DriverStation.Alliance,
) -> Pose3d:
"""
Determines the appropriate shooting target for the robot's current region
and alliance.
:param region: the robot's current ShootingRegion
:param alliance: the robot's alliance (Blue or Red)
:return: target Pose3d, or all-zero Pose3d if region/alliance is invalid
"""
target = Pose3d()
if alliance == DriverStation.Alliance.kBlue:
if region == ShootingRegion.BLUE_OWN_ALLIANCE_ZONE:
target = FieldPoses.BLUE_HUB_POSE
elif region == ShootingRegion.BLUE_LEFT_NEUTRAL_ZONE:
target = FieldPoses.BLUE_LEFT_ALLIANCE_ZONE_PASS_TARGET_POSE
elif region == ShootingRegion.BLUE_RIGHT_NEUTRAL_ZONE:
target = FieldPoses.BLUE_RIGHT_ALLIANCE_ZONE_PASS_TARGET_POSE
elif region == ShootingRegion.BLUE_LEFT_OPPONENT_ALLIANCE_ZONE:
target = FieldPoses.BLUE_LEFT_NEUTRAL_ZONE_PASS_TARGET_POSE
elif region == ShootingRegion.BLUE_RIGHT_OPPONENT_ALLIANCE_ZONE:
target = FieldPoses.BLUE_RIGHT_NEUTRAL_ZONE_PASS_TARGET_POSE
elif alliance == DriverStation.Alliance.kRed:
if region == ShootingRegion.RED_OWN_ALLIANCE_ZONE:
target = FieldPoses.RED_HUB_POSE
elif region == ShootingRegion.RED_LEFT_NEUTRAL_ZONE:
target = FieldPoses.RED_LEFT_ALLIANCE_ZONE_PASS_TARGET_POSE
elif region == ShootingRegion.RED_RIGHT_NEUTRAL_ZONE:
target = FieldPoses.RED_RIGHT_ALLIANCE_ZONE_PASS_TARGET_POSE
elif region == ShootingRegion.RED_LEFT_OPPONENT_ALLIANCE_ZONE:
target = FieldPoses.RED_LEFT_NEUTRAL_ZONE_PASS_TARGET_POSE
elif region == ShootingRegion.RED_RIGHT_OPPONENT_ALLIANCE_ZONE:
target = FieldPoses.RED_RIGHT_NEUTRAL_ZONE_PASS_TARGET_POSE
return target

View File

@ -1,35 +0,0 @@
{
"fileName": "AdvantageKit.json",
"name": "AdvantageKit",
"version": "26.0.2",
"uuid": "d820cc26-74e3-11ec-90d6-0242ac120003",
"frcYear": "2026",
"mavenUrls": [
"https://frcmaven.wpi.edu/artifactory/littletonrobotics-mvn-release/"
],
"jsonUrl": "https://github.com/Mechanical-Advantage/AdvantageKit/releases/latest/download/AdvantageKit.json",
"javaDependencies": [
{
"groupId": "org.littletonrobotics.akit",
"artifactId": "akit-java",
"version": "26.0.2"
}
],
"jniDependencies": [
{
"groupId": "org.littletonrobotics.akit",
"artifactId": "akit-wpilibio",
"version": "26.0.2",
"skipInvalidPlatforms": false,
"isJar": false,
"validPlatforms": [
"linuxathena",
"linuxx86-64",
"linuxarm64",
"osxuniversal",
"windowsx86-64"
]
}
],
"cppDependencies": []
}

View File

@ -1,44 +0,0 @@
{
"fileName": "ChoreoLib2026.json",
"name": "ChoreoLib",
"version": "2026.0.2",
"uuid": "b5e23f0a-dac9-4ad2-8dd6-02767c520aca",
"frcYear": "2026",
"mavenUrls": [
"https://frcmaven.wpi.edu/artifactory/sleipnirgroup-mvn-release/",
"https://repo1.maven.org/maven2"
],
"jsonUrl": "https://choreo.autos/lib/ChoreoLib2026.json",
"javaDependencies": [
{
"groupId": "choreo",
"artifactId": "ChoreoLib-java",
"version": "2026.0.2"
},
{
"groupId": "com.google.code.gson",
"artifactId": "gson",
"version": "2.11.0"
}
],
"jniDependencies": [],
"cppDependencies": [
{
"groupId": "choreo",
"artifactId": "ChoreoLib-cpp",
"version": "2026.0.2",
"libName": "ChoreoLib",
"headerClassifier": "headers",
"sharedLibrary": false,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"osxuniversal",
"linuxathena",
"linuxarm32",
"linuxarm64"
]
}
]
}

View File

@ -1,38 +0,0 @@
{
"fileName": "PathplannerLib-2026.1.2.json",
"name": "PathplannerLib",
"version": "2026.1.2",
"uuid": "1b42324f-17c6-4875-8e77-1c312bc8c786",
"frcYear": "2026",
"mavenUrls": [
"https://3015rangerrobotics.github.io/pathplannerlib/repo"
],
"jsonUrl": "https://3015rangerrobotics.github.io/pathplannerlib/PathplannerLib.json",
"javaDependencies": [
{
"groupId": "com.pathplanner.lib",
"artifactId": "PathplannerLib-java",
"version": "2026.1.2"
}
],
"jniDependencies": [],
"cppDependencies": [
{
"groupId": "com.pathplanner.lib",
"artifactId": "PathplannerLib-cpp",
"version": "2026.1.2",
"libName": "PathplannerLib",
"headerClassifier": "headers",
"sharedLibrary": false,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"osxuniversal",
"linuxathena",
"linuxarm32",
"linuxarm64"
]
}
]
}

View File

@ -1,171 +0,0 @@
{
"fileName": "Phoenix5-5.36.0.json",
"name": "CTRE-Phoenix (v5)",
"version": "5.36.0",
"frcYear": "2026",
"uuid": "ab676553-b602-441f-a38d-f1296eff6537",
"mavenUrls": [
"https://maven.ctr-electronics.com/release/"
],
"jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix/Phoenix5-frc2026-latest.json",
"requires": [
{
"uuid": "e995de00-2c64-4df5-8831-c1441420ff19",
"errorMessage": "Phoenix 5 requires low-level libraries from Phoenix 6. Please add the Phoenix 6 vendordep before adding Phoenix 5.",
"offlineFileName": "Phoenix6-frc2026-latest.json",
"onlineUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2026-latest.json"
}
],
"conflictsWith": [
{
"uuid": "e7900d8d-826f-4dca-a1ff-182f658e98af",
"errorMessage": "Users must use the Phoenix 5 replay vendordep when using the Phoenix 6 replay vendordep.",
"offlineFileName": "Phoenix6-replay-frc2026-latest.json"
},
{
"uuid": "fbc886a4-2cec-40c0-9835-71086a8cc3df",
"errorMessage": "Users cannot have both the replay and regular Phoenix 5 vendordeps in their robot program.",
"offlineFileName": "Phoenix5-replay-frc2026-latest.json"
}
],
"javaDependencies": [
{
"groupId": "com.ctre.phoenix",
"artifactId": "api-java",
"version": "5.36.0"
},
{
"groupId": "com.ctre.phoenix",
"artifactId": "wpiapi-java",
"version": "5.36.0"
}
],
"jniDependencies": [
{
"groupId": "com.ctre.phoenix",
"artifactId": "cci",
"version": "5.36.0",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"linuxathena"
],
"simMode": "hwsim"
},
{
"groupId": "com.ctre.phoenix.sim",
"artifactId": "cci-sim",
"version": "5.36.0",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
}
],
"cppDependencies": [
{
"groupId": "com.ctre.phoenix",
"artifactId": "wpiapi-cpp",
"version": "5.36.0",
"libName": "CTRE_Phoenix_WPI",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"linuxathena"
],
"simMode": "hwsim"
},
{
"groupId": "com.ctre.phoenix",
"artifactId": "api-cpp",
"version": "5.36.0",
"libName": "CTRE_Phoenix",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"linuxathena"
],
"simMode": "hwsim"
},
{
"groupId": "com.ctre.phoenix",
"artifactId": "cci",
"version": "5.36.0",
"libName": "CTRE_PhoenixCCI",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"linuxathena"
],
"simMode": "hwsim"
},
{
"groupId": "com.ctre.phoenix.sim",
"artifactId": "wpiapi-cpp-sim",
"version": "5.36.0",
"libName": "CTRE_Phoenix_WPISim",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix.sim",
"artifactId": "api-cpp-sim",
"version": "5.36.0",
"libName": "CTRE_PhoenixSim",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix.sim",
"artifactId": "cci-sim",
"version": "5.36.0",
"libName": "CTRE_PhoenixCCISim",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
}
]
}

View File

@ -1,449 +0,0 @@
{
"fileName": "Phoenix6-26.1.2.json",
"name": "CTRE-Phoenix (v6)",
"version": "26.1.2",
"frcYear": "2026",
"uuid": "e995de00-2c64-4df5-8831-c1441420ff19",
"mavenUrls": [
"https://maven.ctr-electronics.com/release/"
],
"jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2026-latest.json",
"conflictsWith": [
{
"uuid": "e7900d8d-826f-4dca-a1ff-182f658e98af",
"errorMessage": "Users can not have both the replay and regular Phoenix 6 vendordeps in their robot program.",
"offlineFileName": "Phoenix6-replay-frc2026-latest.json"
}
],
"javaDependencies": [
{
"groupId": "com.ctre.phoenix6",
"artifactId": "wpiapi-java",
"version": "26.1.2"
}
],
"jniDependencies": [
{
"groupId": "com.ctre.phoenix6",
"artifactId": "api-cpp",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"linuxathena"
],
"simMode": "hwsim"
},
{
"groupId": "com.ctre.phoenix6",
"artifactId": "tools",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"linuxathena"
],
"simMode": "hwsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "api-cpp-sim",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "tools-sim",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simTalonSRX",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simVictorSPX",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simPigeonIMU",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProTalonFX",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProTalonFXS",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProCANcoder",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProPigeon2",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProCANrange",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProCANdi",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProCANdle",
"version": "26.1.2",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
}
],
"cppDependencies": [
{
"groupId": "com.ctre.phoenix6",
"artifactId": "wpiapi-cpp",
"version": "26.1.2",
"libName": "CTRE_Phoenix6_WPI",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"linuxathena"
],
"simMode": "hwsim"
},
{
"groupId": "com.ctre.phoenix6",
"artifactId": "tools",
"version": "26.1.2",
"libName": "CTRE_PhoenixTools",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"linuxathena"
],
"simMode": "hwsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "wpiapi-cpp-sim",
"version": "26.1.2",
"libName": "CTRE_Phoenix6_WPISim",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "tools-sim",
"version": "26.1.2",
"libName": "CTRE_PhoenixTools_Sim",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simTalonSRX",
"version": "26.1.2",
"libName": "CTRE_SimTalonSRX",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simVictorSPX",
"version": "26.1.2",
"libName": "CTRE_SimVictorSPX",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simPigeonIMU",
"version": "26.1.2",
"libName": "CTRE_SimPigeonIMU",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProTalonFX",
"version": "26.1.2",
"libName": "CTRE_SimProTalonFX",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProTalonFXS",
"version": "26.1.2",
"libName": "CTRE_SimProTalonFXS",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProCANcoder",
"version": "26.1.2",
"libName": "CTRE_SimProCANcoder",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProPigeon2",
"version": "26.1.2",
"libName": "CTRE_SimProPigeon2",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProCANrange",
"version": "26.1.2",
"libName": "CTRE_SimProCANrange",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProCANdi",
"version": "26.1.2",
"libName": "CTRE_SimProCANdi",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
},
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProCANdle",
"version": "26.1.2",
"libName": "CTRE_SimProCANdle",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxx86-64",
"linuxarm64",
"osxuniversal"
],
"simMode": "swsim"
}
]
}

View File

@ -1,133 +0,0 @@
{
"fileName": "REVLib.json",
"name": "REVLib",
"version": "2026.0.5",
"frcYear": "2026",
"uuid": "3f48eb8c-50fe-43a6-9cb7-44c86353c4cb",
"mavenUrls": [
"https://maven.revrobotics.com/"
],
"jsonUrl": "https://software-metadata.revrobotics.com/REVLib-2026.json",
"javaDependencies": [
{
"groupId": "com.revrobotics.frc",
"artifactId": "REVLib-java",
"version": "2026.0.5"
}
],
"jniDependencies": [
{
"groupId": "com.revrobotics.frc",
"artifactId": "REVLib-driver",
"version": "2026.0.5",
"skipInvalidPlatforms": true,
"isJar": false,
"validPlatforms": [
"windowsx86-64",
"linuxarm64",
"linuxx86-64",
"linuxathena",
"linuxarm32",
"osxuniversal"
]
},
{
"groupId": "com.revrobotics.frc",
"artifactId": "RevLibBackendDriver",
"version": "2026.0.5",
"skipInvalidPlatforms": true,
"isJar": false,
"validPlatforms": [
"windowsx86-64",
"linuxarm64",
"linuxx86-64",
"linuxathena",
"linuxarm32",
"osxuniversal"
]
},
{
"groupId": "com.revrobotics.frc",
"artifactId": "RevLibWpiBackendDriver",
"version": "2026.0.5",
"skipInvalidPlatforms": true,
"isJar": false,
"validPlatforms": [
"windowsx86-64",
"linuxarm64",
"linuxx86-64",
"linuxathena",
"linuxarm32",
"osxuniversal"
]
}
],
"cppDependencies": [
{
"groupId": "com.revrobotics.frc",
"artifactId": "REVLib-cpp",
"version": "2026.0.5",
"libName": "REVLib",
"headerClassifier": "headers",
"sharedLibrary": false,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxarm64",
"linuxx86-64",
"linuxathena",
"linuxarm32",
"osxuniversal"
]
},
{
"groupId": "com.revrobotics.frc",
"artifactId": "REVLib-driver",
"version": "2026.0.5",
"libName": "REVLibDriver",
"headerClassifier": "headers",
"sharedLibrary": false,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxarm64",
"linuxx86-64",
"linuxathena",
"linuxarm32",
"osxuniversal"
]
},
{
"groupId": "com.revrobotics.frc",
"artifactId": "RevLibBackendDriver",
"version": "2026.0.5",
"libName": "BackendDriver",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxarm64",
"linuxx86-64",
"linuxathena",
"linuxarm32",
"osxuniversal"
]
},
{
"groupId": "com.revrobotics.frc",
"artifactId": "RevLibWpiBackendDriver",
"version": "2026.0.5",
"libName": "REVLibWpi",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"windowsx86-64",
"linuxarm64",
"linuxx86-64",
"linuxathena",
"linuxarm32",
"osxuniversal"
]
}
]
}

View File

@ -1,39 +0,0 @@
{
"fileName": "WPILibNewCommands.json",
"name": "WPILib-New-Commands",
"version": "1.0.0",
"uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266",
"frcYear": "2026",
"mavenUrls": [],
"jsonUrl": "",
"javaDependencies": [
{
"groupId": "edu.wpi.first.wpilibNewCommands",
"artifactId": "wpilibNewCommands-java",
"version": "wpilib"
}
],
"jniDependencies": [],
"cppDependencies": [
{
"groupId": "edu.wpi.first.wpilibNewCommands",
"artifactId": "wpilibNewCommands-cpp",
"version": "wpilib",
"libName": "wpilibNewCommands",
"headerClassifier": "headers",
"sourcesClassifier": "sources",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"linuxsystemcore",
"linuxathena",
"linuxarm32",
"linuxarm64",
"windowsx86-64",
"windowsx86",
"linuxx86-64",
"osxuniversal"
]
}
]
}