164 lines
6.7 KiB
Python
164 lines
6.7 KiB
Python
"""
|
||
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)
|