robotswerve

Container to hold the main robot code.

Controller Map

Driver Controller

Operator Controller

  1"""
  2Container to hold the main robot code.
  3
  4## Controller Map
  5
  6![Driver Controller](./assets/2026bot_controller_map_page1.png)
  7
  8![Operator Controller](./assets/2026bot_controller_map_page2.png)
  9"""
 10
 11# Native imports
 12import json
 13import os
 14from pathlib import Path
 15from typing import Callable
 16
 17# Internal imports
 18from config import HoodConfig, OperatorRobotConfig
 19import constants.swerve_constants as consts
 20from data.telemetry import Telemetry
 21import commands
 22import subsystem
 23from subsystem.health_and_status import HealthAndStatus
 24from subsystem.nfc_battery_tracker import NfcBatteryTracker
 25from subsystem.robot_state import RobotState
 26from utils.input import InputFactory
 27from utils.odometry_logic_2026 import determineShooterTargets2026
 28
 29# Third-party imports
 30import commands2
 31import wpilib
 32from pathplannerlib.auto import AutoBuilder
 33from wpimath.geometry import Rotation2d
 34
 35class RobotSwerve:
 36    def __init__(self, is_disabled: Callable[[], bool]) -> None:
 37        # networktables setup
 38        self.field = wpilib.Field2d()
 39        wpilib.SmartDashboard.putData("Field", self.field)
 40
 41        # In simulation, pre-seed CANcoder supply voltage so the absolute
 42        # encoders report valid data when swerve modules call
 43        # baseline_relative_encoders() during construction.
 44        if wpilib.RobotBase.isSimulation():
 45            import phoenix6
 46            for base_id in OperatorRobotConfig.swerve_module_channels:
 47                encoder_id = base_id + 2
 48                encoder = phoenix6.hardware.CANcoder(encoder_id)
 49                encoder.sim_state.set_supply_voltage(12.0)
 50
 51        # Subsystem instantiation
 52        self.drivetrain = subsystem.drivetrain.swerve_drivetrain.SwerveDrivetrain()
 53        self.health_and_status = HealthAndStatus()
 54        self.nfc_battery_tracker = NfcBatteryTracker()
 55        from subsystem.localization.localization import Localization
 56        self.localization = Localization(self.drivetrain, field=self.field)
 57        self._vision_cycle_counter = 0
 58
 59        # Mechanism subsystems — toggle each to True/False individually
 60        _enable_shooter = False
 61        _enable_feed = False
 62        _enable_hood = False
 63        _enable_hopper = False
 64        _enable_intake_position = False
 65        _enable_intake_roller = True
 66
 67        self.shooter = subsystem.mechanisms.shooter.Shooter() if _enable_shooter else None
 68        self.feed = subsystem.mechanisms.shooter.Feed() if _enable_feed else None
 69        self.hood = subsystem.mechanisms.shooter.createHood(consts.HoodConstants, HoodConfig) if _enable_hood else None
 70        self.hopper = subsystem.Hopper() if _enable_hopper else None
 71        self.intake_position = subsystem.IntakePosition() if _enable_intake_position else None
 72        self.intake_roller = subsystem.IntakeRoller() if _enable_intake_roller else None
 73        self.robot_state = RobotState()
 74        # Alliance instantiation
 75        self.updateAlliance()
 76
 77        # Initialize timer
 78        self.timer = wpilib.Timer()
 79        self.timer.start()
 80
 81        # HID setup — config-driven via InputFactory
 82        wpilib.DriverStation.silenceJoystickConnectionWarning(True)
 83        self.factory = InputFactory(config_path="data/inputs/2026bot.yaml")
 84
 85        # Speed toggle state
 86        self._drive_scale_slow = 0.25
 87        self._drive_scale_fast = 1
 88        self._drive_is_slow = False
 89
 90        # TODO: Move input retrieval and binding into commands/{subsystem}_controls.py
 91        # files as part of the subsystem registry refactor. Each subsystem's controls
 92        # module should own its own factory.get*() calls and command wiring.
 93        self._configure_controls()
 94
 95        # Autonomous setup
 96        self.auto_command = None
 97        self.auto_chooser = AutoBuilder.buildAutoChooser()
 98        wpilib.SmartDashboard.putData("Select auto routine", self.auto_chooser)
 99
100        # Telemetry setup
101        wpilib.SmartDashboard.putNumber("Drivetrain speed", self._drive_scale_fast)
102        self._enable_telemetry = wpilib.SmartDashboard.getBoolean("enableTelemetry", True)
103        if self._enable_telemetry:
104            self.telemetry = Telemetry(
105                driveTrain=self.drivetrain,
106            )
107
108        wpilib.SmartDashboard.putString("Robot Version", self.getDeployInfo("git-hash"))
109        wpilib.SmartDashboard.putString("Git Branch", self.getDeployInfo("git-branch"))
110
111
112
113    def robotPeriodic(self):
114        self._vision_cycle_counter += 1
115        if self._vision_cycle_counter >= OperatorRobotConfig.vision_update_rate_divisor:
116            self._vision_cycle_counter = 0
117            self.localization.update()
118
119        if self._enable_telemetry and self.telemetry:
120            self.telemetry.runDefaultDataCollections()
121
122        self.field.setRobotPose(self.drivetrain.current_pose())
123
124    def disabledInit(self):
125        self.updateAlliance()
126        self.drivetrain.set_motor_stop_modes(to_drive=True, to_break=True, all_motor_override=True, burn_flash=False)
127        self.drivetrain.stop_driving()
128        self.nfc_battery_tracker.onDisabledInit()
129
130        if self.shooter:
131            self.shooter.setRPM(0)
132            self.shooter.resetOffset()
133        if self.feed:
134            self.feed.stop()
135        if self.hopper:
136            self.hopper.stop()
137        if self.intake_roller:
138            self.intake_roller.stop()
139        if self.intake_position:
140            self.intake_position.disable()
141
142    def disabledPeriodic(self):
143        pass
144
145
146    def autonomousInit(self):
147        self.updateAlliance()
148        self.auto_command = self.auto_chooser.getSelected()
149        if self.auto_command:
150            self.auto_command.schedule()
151            if self.auto_command.getName().lower() == "instantcommand":
152                self.drivetrain.reset_pose_estimator(self.drivetrain.get_default_starting_pose())
153        else:
154            self.drivetrain.reset_pose_estimator(self.drivetrain.get_default_starting_pose())
155
156    def autonomousPeriodic(self):
157        pass
158
159    def teleopInit(self):
160        self.updateAlliance()
161        if self.auto_command:
162            self.auto_command.cancel()
163            if self.auto_command.getName().lower() == "instantcommand":
164                self.drivetrain.reset_pose_estimator(self.drivetrain.get_default_starting_pose())
165        else:
166            self.drivetrain.reset_pose_estimator(self.drivetrain.get_default_starting_pose())
167
168        # DefaultDrive: raw square input (diagonals get full per-axis output)
169        # DefaultDriveCircular: remaps circular stick input so diagonals
170        #   reach full speed (use when stick hardware caps diagonal ~70%)
171        self.drivetrain.setDefaultCommand(
172            commands.default_swerve_drive.DefaultDrive(  # TODO: switch back to DefaultDriveCircular
173                self.drivetrain,
174                self.translate_x,
175                self.translate_y,
176                self.rotate,
177                lambda: not self.robot_relative_btn()
178            )
179        )
180
181        if self.hood:
182            self.hood.setDefaultCommand(self.hood.autoAngleCommand())
183
184    def teleopPeriodic(self):
185        pass
186
187    def testInit(self):
188        self.updateAlliance()
189        commands2.CommandScheduler.getInstance().cancelAll()
190        # See teleopInit for DefaultDrive vs DefaultDriveCircular notes
191        self.drivetrain.setDefaultCommand(
192            commands.default_swerve_drive.DefaultDrive(  # TODO: switch back to DefaultDriveCircular
193                self.drivetrain,
194                self.translate_x,
195                self.translate_y,
196                self.rotate,
197                lambda: not self.robot_relative_btn()
198            )
199        )
200        commands2.cmd.run(lambda: self.drivetrain.drive(2, 0, 0, False), self.drivetrain).withTimeout(5).schedule()
201
202        if self.hood:
203            self.hood.setDefaultCommand(
204                self.hood.manualTestCommand(self.hood_angle_input))
205
206    def testPeriodic(self):
207        pass
208
209    def _configure_controls(self) -> None:
210        """Retrieve managed inputs from the factory and wire command bindings.
211
212        TODO: Move into commands/{subsystem}_controls.py files as part of the
213        subsystem registry refactor. Each subsystem's controls module would
214        call register_controls(subsystem, container) and own its own
215        factory.get*() calls and command wiring.
216        """
217        # Managed drive inputs
218        self.translate_x = self.factory.getAnalog("drivetrain.translate_x")
219        self.translate_y = self.factory.getAnalog("drivetrain.translate_y")
220        self.rotate = self.factory.getAnalog("drivetrain.rotate")
221        self.robot_relative_btn = self.factory.getRawButton("drivetrain.robot_relative")
222
223        # Cancel-all: emergency stop all commands
224        self.factory.getButton("drivetrain.cancel_all").bind(
225            commands2.cmd.runOnce(
226                lambda: commands2.CommandScheduler.getInstance().cancelAll()
227            )
228        )
229
230        # Speed toggle: switch between slow and fast drive scale
231        self.factory.getButton("drivetrain.speed_toggle").bind(
232            commands2.cmd.runOnce(self._toggle_drive_scale)
233        )
234
235        # Auto-rotate: align drivetrain to target while held
236        self.factory.getButton("drivetrain.auto_align").bind(
237            commands.auto.pid_to_angle.PIDAlignToTarget(
238                self.drivetrain,
239                lambda: determineShooterTargets2026(self.drivetrain.current_pose, self.alliance),
240                self.translate_x,
241                self.translate_y,
242                lambda: not self.robot_relative_btn(),
243                alignment_angle=Rotation2d.fromDegrees(180)
244            )
245        )
246
247        # Mechanism controls — only registered when subsystems are enabled
248        if self.shooter:
249            self.factory.getButton("shooter.increment_RPM").bind(
250                commands2.cmd.runOnce(lambda: self.shooter.modifyOffset(consts.ShooterConstants.offsetDelta), self.shooter)
251            )
252            self.factory.getButton("shooter.decrement_RPM").bind(
253                commands2.cmd.runOnce(lambda: self.shooter.modifyOffset(-consts.ShooterConstants.offsetDelta), self.shooter)
254            )
255            self.factory.getButton("shooter.reset_RPM_offset").bind(
256                commands2.cmd.runOnce(self.shooter.resetOffset, self.shooter)
257            )
258
259        if self.hood:
260            self.hood_angle_input = self.factory.getAnalog("hood.angle")
261
262        if self.hopper:
263            self.factory.getButton("hopper.toggle_hopper").bind(
264                commands.ball_transport.run_hopper_while_active(self.hopper)
265            )
266
267        if self.intake_position:
268            self.factory.getButton("intake.toggle_deploy").bind(
269                commands.intake_commands.toggle_intake_deploy(self.intake_position)
270            )
271
272        if self.intake_roller:
273            self.factory.getButton("ball_transport.hold_roller").bind(
274                commands.ball_transport.run_roller_while_held(self.intake_roller)
275            )
276
277        if self.shooter and self.hood:
278            self.factory.getButton("shooter.spinup_toggle").bind(
279                commands.shooter_commands.toggle_spinup(self.shooter, self.hood)
280            )
281
282        if self.hopper and self.feed:
283            self.factory.getButton("ball_transport.run_hopper_feed").bind(
284                commands.ball_transport.run_hopper_and_feed(self.hopper, self.feed)
285            )
286
287        #Turret stop
288        # Map all drive axes' scale to a shared SmartDashboard entry.
289        # Dashboard changes and Y-button toggles both write to this path;
290        # the factory auto-syncs the value into all three analogs each cycle.
291        _SPEED_NT = "/SmartDashboard/Drivetrain speed"
292        for analog in (self.translate_x, self.translate_y, self.rotate):
293            analog.mapParamToNtPath(_SPEED_NT, "scale")
294
295    def _toggle_drive_scale(self) -> None:
296        """Toggle between slow and fast drive scale presets.
297
298        Writes the new scale to SmartDashboard; the factory auto-syncs
299        it into all three drive analogs via mapParamToNtPath each cycle.
300        """
301        self._drive_is_slow = not self._drive_is_slow
302        scale = self._drive_scale_slow if self._drive_is_slow else self._drive_scale_fast
303        wpilib.SmartDashboard.putNumber("Drivetrain speed", scale)
304
305    def getDeployInfo(self, key: str) -> str:
306        """Gets the Git SHA of the deployed robot by parsing ~/deploy.json and returning the git-hash from the JSON key OR if deploy.json is unavailable will return "unknown"
307            example deploy.json: '{"deploy-host": "DESKTOP-80HA89O", "deploy-user": "ehsra", "deploy-date": "2023-03-02T17:54:14", "code-path": "blah", "git-hash": "3f4e89f138d9d78093bd4869e0cac9b61becd2b9", "git-desc": "3f4e89f-dirty", "git-branch": "fix-recal-nbeasley"}
308
309        Args:
310            key (str): The desired json key to get. Popular onces are git-hash, deploy-host, deploy-user
311
312        Returns:
313            str: Returns the value of the desired deploy key
314        """
315        json_object = None
316        home = str(Path.home()) + os.path.sep
317        release_file = home + 'py' + os.path.sep + "deploy.json"
318        try:
319            # Read from ~/deploy.json
320            with open(release_file, "r") as openfile:
321                json_object = json.load(openfile)
322                if key in json_object:
323                    return json_object[key]
324                else:
325                    return f"Key: {key} Not Found in JSON"
326        except OSError:
327            return "unknown"
328        except json.JSONDecodeError:
329            return "bad json in deploy file check for unescaped "
330
331    def updateAlliance(self) -> None:
332        """
333        Update the alliance the robot is on
334        """
335        self.alliance = wpilib.DriverStation.getAlliance()
336        self.drivetrain.update_alliance_flag(self.alliance)
class RobotSwerve:
 36class RobotSwerve:
 37    def __init__(self, is_disabled: Callable[[], bool]) -> None:
 38        # networktables setup
 39        self.field = wpilib.Field2d()
 40        wpilib.SmartDashboard.putData("Field", self.field)
 41
 42        # In simulation, pre-seed CANcoder supply voltage so the absolute
 43        # encoders report valid data when swerve modules call
 44        # baseline_relative_encoders() during construction.
 45        if wpilib.RobotBase.isSimulation():
 46            import phoenix6
 47            for base_id in OperatorRobotConfig.swerve_module_channels:
 48                encoder_id = base_id + 2
 49                encoder = phoenix6.hardware.CANcoder(encoder_id)
 50                encoder.sim_state.set_supply_voltage(12.0)
 51
 52        # Subsystem instantiation
 53        self.drivetrain = subsystem.drivetrain.swerve_drivetrain.SwerveDrivetrain()
 54        self.health_and_status = HealthAndStatus()
 55        self.nfc_battery_tracker = NfcBatteryTracker()
 56        from subsystem.localization.localization import Localization
 57        self.localization = Localization(self.drivetrain, field=self.field)
 58        self._vision_cycle_counter = 0
 59
 60        # Mechanism subsystems — toggle each to True/False individually
 61        _enable_shooter = False
 62        _enable_feed = False
 63        _enable_hood = False
 64        _enable_hopper = False
 65        _enable_intake_position = False
 66        _enable_intake_roller = True
 67
 68        self.shooter = subsystem.mechanisms.shooter.Shooter() if _enable_shooter else None
 69        self.feed = subsystem.mechanisms.shooter.Feed() if _enable_feed else None
 70        self.hood = subsystem.mechanisms.shooter.createHood(consts.HoodConstants, HoodConfig) if _enable_hood else None
 71        self.hopper = subsystem.Hopper() if _enable_hopper else None
 72        self.intake_position = subsystem.IntakePosition() if _enable_intake_position else None
 73        self.intake_roller = subsystem.IntakeRoller() if _enable_intake_roller else None
 74        self.robot_state = RobotState()
 75        # Alliance instantiation
 76        self.updateAlliance()
 77
 78        # Initialize timer
 79        self.timer = wpilib.Timer()
 80        self.timer.start()
 81
 82        # HID setup — config-driven via InputFactory
 83        wpilib.DriverStation.silenceJoystickConnectionWarning(True)
 84        self.factory = InputFactory(config_path="data/inputs/2026bot.yaml")
 85
 86        # Speed toggle state
 87        self._drive_scale_slow = 0.25
 88        self._drive_scale_fast = 1
 89        self._drive_is_slow = False
 90
 91        # TODO: Move input retrieval and binding into commands/{subsystem}_controls.py
 92        # files as part of the subsystem registry refactor. Each subsystem's controls
 93        # module should own its own factory.get*() calls and command wiring.
 94        self._configure_controls()
 95
 96        # Autonomous setup
 97        self.auto_command = None
 98        self.auto_chooser = AutoBuilder.buildAutoChooser()
 99        wpilib.SmartDashboard.putData("Select auto routine", self.auto_chooser)
100
101        # Telemetry setup
102        wpilib.SmartDashboard.putNumber("Drivetrain speed", self._drive_scale_fast)
103        self._enable_telemetry = wpilib.SmartDashboard.getBoolean("enableTelemetry", True)
104        if self._enable_telemetry:
105            self.telemetry = Telemetry(
106                driveTrain=self.drivetrain,
107            )
108
109        wpilib.SmartDashboard.putString("Robot Version", self.getDeployInfo("git-hash"))
110        wpilib.SmartDashboard.putString("Git Branch", self.getDeployInfo("git-branch"))
111
112
113
114    def robotPeriodic(self):
115        self._vision_cycle_counter += 1
116        if self._vision_cycle_counter >= OperatorRobotConfig.vision_update_rate_divisor:
117            self._vision_cycle_counter = 0
118            self.localization.update()
119
120        if self._enable_telemetry and self.telemetry:
121            self.telemetry.runDefaultDataCollections()
122
123        self.field.setRobotPose(self.drivetrain.current_pose())
124
125    def disabledInit(self):
126        self.updateAlliance()
127        self.drivetrain.set_motor_stop_modes(to_drive=True, to_break=True, all_motor_override=True, burn_flash=False)
128        self.drivetrain.stop_driving()
129        self.nfc_battery_tracker.onDisabledInit()
130
131        if self.shooter:
132            self.shooter.setRPM(0)
133            self.shooter.resetOffset()
134        if self.feed:
135            self.feed.stop()
136        if self.hopper:
137            self.hopper.stop()
138        if self.intake_roller:
139            self.intake_roller.stop()
140        if self.intake_position:
141            self.intake_position.disable()
142
143    def disabledPeriodic(self):
144        pass
145
146
147    def autonomousInit(self):
148        self.updateAlliance()
149        self.auto_command = self.auto_chooser.getSelected()
150        if self.auto_command:
151            self.auto_command.schedule()
152            if self.auto_command.getName().lower() == "instantcommand":
153                self.drivetrain.reset_pose_estimator(self.drivetrain.get_default_starting_pose())
154        else:
155            self.drivetrain.reset_pose_estimator(self.drivetrain.get_default_starting_pose())
156
157    def autonomousPeriodic(self):
158        pass
159
160    def teleopInit(self):
161        self.updateAlliance()
162        if self.auto_command:
163            self.auto_command.cancel()
164            if self.auto_command.getName().lower() == "instantcommand":
165                self.drivetrain.reset_pose_estimator(self.drivetrain.get_default_starting_pose())
166        else:
167            self.drivetrain.reset_pose_estimator(self.drivetrain.get_default_starting_pose())
168
169        # DefaultDrive: raw square input (diagonals get full per-axis output)
170        # DefaultDriveCircular: remaps circular stick input so diagonals
171        #   reach full speed (use when stick hardware caps diagonal ~70%)
172        self.drivetrain.setDefaultCommand(
173            commands.default_swerve_drive.DefaultDrive(  # TODO: switch back to DefaultDriveCircular
174                self.drivetrain,
175                self.translate_x,
176                self.translate_y,
177                self.rotate,
178                lambda: not self.robot_relative_btn()
179            )
180        )
181
182        if self.hood:
183            self.hood.setDefaultCommand(self.hood.autoAngleCommand())
184
185    def teleopPeriodic(self):
186        pass
187
188    def testInit(self):
189        self.updateAlliance()
190        commands2.CommandScheduler.getInstance().cancelAll()
191        # See teleopInit for DefaultDrive vs DefaultDriveCircular notes
192        self.drivetrain.setDefaultCommand(
193            commands.default_swerve_drive.DefaultDrive(  # TODO: switch back to DefaultDriveCircular
194                self.drivetrain,
195                self.translate_x,
196                self.translate_y,
197                self.rotate,
198                lambda: not self.robot_relative_btn()
199            )
200        )
201        commands2.cmd.run(lambda: self.drivetrain.drive(2, 0, 0, False), self.drivetrain).withTimeout(5).schedule()
202
203        if self.hood:
204            self.hood.setDefaultCommand(
205                self.hood.manualTestCommand(self.hood_angle_input))
206
207    def testPeriodic(self):
208        pass
209
210    def _configure_controls(self) -> None:
211        """Retrieve managed inputs from the factory and wire command bindings.
212
213        TODO: Move into commands/{subsystem}_controls.py files as part of the
214        subsystem registry refactor. Each subsystem's controls module would
215        call register_controls(subsystem, container) and own its own
216        factory.get*() calls and command wiring.
217        """
218        # Managed drive inputs
219        self.translate_x = self.factory.getAnalog("drivetrain.translate_x")
220        self.translate_y = self.factory.getAnalog("drivetrain.translate_y")
221        self.rotate = self.factory.getAnalog("drivetrain.rotate")
222        self.robot_relative_btn = self.factory.getRawButton("drivetrain.robot_relative")
223
224        # Cancel-all: emergency stop all commands
225        self.factory.getButton("drivetrain.cancel_all").bind(
226            commands2.cmd.runOnce(
227                lambda: commands2.CommandScheduler.getInstance().cancelAll()
228            )
229        )
230
231        # Speed toggle: switch between slow and fast drive scale
232        self.factory.getButton("drivetrain.speed_toggle").bind(
233            commands2.cmd.runOnce(self._toggle_drive_scale)
234        )
235
236        # Auto-rotate: align drivetrain to target while held
237        self.factory.getButton("drivetrain.auto_align").bind(
238            commands.auto.pid_to_angle.PIDAlignToTarget(
239                self.drivetrain,
240                lambda: determineShooterTargets2026(self.drivetrain.current_pose, self.alliance),
241                self.translate_x,
242                self.translate_y,
243                lambda: not self.robot_relative_btn(),
244                alignment_angle=Rotation2d.fromDegrees(180)
245            )
246        )
247
248        # Mechanism controls — only registered when subsystems are enabled
249        if self.shooter:
250            self.factory.getButton("shooter.increment_RPM").bind(
251                commands2.cmd.runOnce(lambda: self.shooter.modifyOffset(consts.ShooterConstants.offsetDelta), self.shooter)
252            )
253            self.factory.getButton("shooter.decrement_RPM").bind(
254                commands2.cmd.runOnce(lambda: self.shooter.modifyOffset(-consts.ShooterConstants.offsetDelta), self.shooter)
255            )
256            self.factory.getButton("shooter.reset_RPM_offset").bind(
257                commands2.cmd.runOnce(self.shooter.resetOffset, self.shooter)
258            )
259
260        if self.hood:
261            self.hood_angle_input = self.factory.getAnalog("hood.angle")
262
263        if self.hopper:
264            self.factory.getButton("hopper.toggle_hopper").bind(
265                commands.ball_transport.run_hopper_while_active(self.hopper)
266            )
267
268        if self.intake_position:
269            self.factory.getButton("intake.toggle_deploy").bind(
270                commands.intake_commands.toggle_intake_deploy(self.intake_position)
271            )
272
273        if self.intake_roller:
274            self.factory.getButton("ball_transport.hold_roller").bind(
275                commands.ball_transport.run_roller_while_held(self.intake_roller)
276            )
277
278        if self.shooter and self.hood:
279            self.factory.getButton("shooter.spinup_toggle").bind(
280                commands.shooter_commands.toggle_spinup(self.shooter, self.hood)
281            )
282
283        if self.hopper and self.feed:
284            self.factory.getButton("ball_transport.run_hopper_feed").bind(
285                commands.ball_transport.run_hopper_and_feed(self.hopper, self.feed)
286            )
287
288        #Turret stop
289        # Map all drive axes' scale to a shared SmartDashboard entry.
290        # Dashboard changes and Y-button toggles both write to this path;
291        # the factory auto-syncs the value into all three analogs each cycle.
292        _SPEED_NT = "/SmartDashboard/Drivetrain speed"
293        for analog in (self.translate_x, self.translate_y, self.rotate):
294            analog.mapParamToNtPath(_SPEED_NT, "scale")
295
296    def _toggle_drive_scale(self) -> None:
297        """Toggle between slow and fast drive scale presets.
298
299        Writes the new scale to SmartDashboard; the factory auto-syncs
300        it into all three drive analogs via mapParamToNtPath each cycle.
301        """
302        self._drive_is_slow = not self._drive_is_slow
303        scale = self._drive_scale_slow if self._drive_is_slow else self._drive_scale_fast
304        wpilib.SmartDashboard.putNumber("Drivetrain speed", scale)
305
306    def getDeployInfo(self, key: str) -> str:
307        """Gets the Git SHA of the deployed robot by parsing ~/deploy.json and returning the git-hash from the JSON key OR if deploy.json is unavailable will return "unknown"
308            example deploy.json: '{"deploy-host": "DESKTOP-80HA89O", "deploy-user": "ehsra", "deploy-date": "2023-03-02T17:54:14", "code-path": "blah", "git-hash": "3f4e89f138d9d78093bd4869e0cac9b61becd2b9", "git-desc": "3f4e89f-dirty", "git-branch": "fix-recal-nbeasley"}
309
310        Args:
311            key (str): The desired json key to get. Popular onces are git-hash, deploy-host, deploy-user
312
313        Returns:
314            str: Returns the value of the desired deploy key
315        """
316        json_object = None
317        home = str(Path.home()) + os.path.sep
318        release_file = home + 'py' + os.path.sep + "deploy.json"
319        try:
320            # Read from ~/deploy.json
321            with open(release_file, "r") as openfile:
322                json_object = json.load(openfile)
323                if key in json_object:
324                    return json_object[key]
325                else:
326                    return f"Key: {key} Not Found in JSON"
327        except OSError:
328            return "unknown"
329        except json.JSONDecodeError:
330            return "bad json in deploy file check for unescaped "
331
332    def updateAlliance(self) -> None:
333        """
334        Update the alliance the robot is on
335        """
336        self.alliance = wpilib.DriverStation.getAlliance()
337        self.drivetrain.update_alliance_flag(self.alliance)
RobotSwerve(is_disabled: Callable[[], bool])
 37    def __init__(self, is_disabled: Callable[[], bool]) -> None:
 38        # networktables setup
 39        self.field = wpilib.Field2d()
 40        wpilib.SmartDashboard.putData("Field", self.field)
 41
 42        # In simulation, pre-seed CANcoder supply voltage so the absolute
 43        # encoders report valid data when swerve modules call
 44        # baseline_relative_encoders() during construction.
 45        if wpilib.RobotBase.isSimulation():
 46            import phoenix6
 47            for base_id in OperatorRobotConfig.swerve_module_channels:
 48                encoder_id = base_id + 2
 49                encoder = phoenix6.hardware.CANcoder(encoder_id)
 50                encoder.sim_state.set_supply_voltage(12.0)
 51
 52        # Subsystem instantiation
 53        self.drivetrain = subsystem.drivetrain.swerve_drivetrain.SwerveDrivetrain()
 54        self.health_and_status = HealthAndStatus()
 55        self.nfc_battery_tracker = NfcBatteryTracker()
 56        from subsystem.localization.localization import Localization
 57        self.localization = Localization(self.drivetrain, field=self.field)
 58        self._vision_cycle_counter = 0
 59
 60        # Mechanism subsystems — toggle each to True/False individually
 61        _enable_shooter = False
 62        _enable_feed = False
 63        _enable_hood = False
 64        _enable_hopper = False
 65        _enable_intake_position = False
 66        _enable_intake_roller = True
 67
 68        self.shooter = subsystem.mechanisms.shooter.Shooter() if _enable_shooter else None
 69        self.feed = subsystem.mechanisms.shooter.Feed() if _enable_feed else None
 70        self.hood = subsystem.mechanisms.shooter.createHood(consts.HoodConstants, HoodConfig) if _enable_hood else None
 71        self.hopper = subsystem.Hopper() if _enable_hopper else None
 72        self.intake_position = subsystem.IntakePosition() if _enable_intake_position else None
 73        self.intake_roller = subsystem.IntakeRoller() if _enable_intake_roller else None
 74        self.robot_state = RobotState()
 75        # Alliance instantiation
 76        self.updateAlliance()
 77
 78        # Initialize timer
 79        self.timer = wpilib.Timer()
 80        self.timer.start()
 81
 82        # HID setup — config-driven via InputFactory
 83        wpilib.DriverStation.silenceJoystickConnectionWarning(True)
 84        self.factory = InputFactory(config_path="data/inputs/2026bot.yaml")
 85
 86        # Speed toggle state
 87        self._drive_scale_slow = 0.25
 88        self._drive_scale_fast = 1
 89        self._drive_is_slow = False
 90
 91        # TODO: Move input retrieval and binding into commands/{subsystem}_controls.py
 92        # files as part of the subsystem registry refactor. Each subsystem's controls
 93        # module should own its own factory.get*() calls and command wiring.
 94        self._configure_controls()
 95
 96        # Autonomous setup
 97        self.auto_command = None
 98        self.auto_chooser = AutoBuilder.buildAutoChooser()
 99        wpilib.SmartDashboard.putData("Select auto routine", self.auto_chooser)
100
101        # Telemetry setup
102        wpilib.SmartDashboard.putNumber("Drivetrain speed", self._drive_scale_fast)
103        self._enable_telemetry = wpilib.SmartDashboard.getBoolean("enableTelemetry", True)
104        if self._enable_telemetry:
105            self.telemetry = Telemetry(
106                driveTrain=self.drivetrain,
107            )
108
109        wpilib.SmartDashboard.putString("Robot Version", self.getDeployInfo("git-hash"))
110        wpilib.SmartDashboard.putString("Git Branch", self.getDeployInfo("git-branch"))
field
drivetrain
health_and_status
nfc_battery_tracker
localization
shooter
feed
hood
hopper
intake_position
intake_roller
robot_state
timer
factory
auto_command
auto_chooser
def robotPeriodic(self):
114    def robotPeriodic(self):
115        self._vision_cycle_counter += 1
116        if self._vision_cycle_counter >= OperatorRobotConfig.vision_update_rate_divisor:
117            self._vision_cycle_counter = 0
118            self.localization.update()
119
120        if self._enable_telemetry and self.telemetry:
121            self.telemetry.runDefaultDataCollections()
122
123        self.field.setRobotPose(self.drivetrain.current_pose())
def disabledInit(self):
125    def disabledInit(self):
126        self.updateAlliance()
127        self.drivetrain.set_motor_stop_modes(to_drive=True, to_break=True, all_motor_override=True, burn_flash=False)
128        self.drivetrain.stop_driving()
129        self.nfc_battery_tracker.onDisabledInit()
130
131        if self.shooter:
132            self.shooter.setRPM(0)
133            self.shooter.resetOffset()
134        if self.feed:
135            self.feed.stop()
136        if self.hopper:
137            self.hopper.stop()
138        if self.intake_roller:
139            self.intake_roller.stop()
140        if self.intake_position:
141            self.intake_position.disable()
def disabledPeriodic(self):
143    def disabledPeriodic(self):
144        pass
def autonomousInit(self):
147    def autonomousInit(self):
148        self.updateAlliance()
149        self.auto_command = self.auto_chooser.getSelected()
150        if self.auto_command:
151            self.auto_command.schedule()
152            if self.auto_command.getName().lower() == "instantcommand":
153                self.drivetrain.reset_pose_estimator(self.drivetrain.get_default_starting_pose())
154        else:
155            self.drivetrain.reset_pose_estimator(self.drivetrain.get_default_starting_pose())
def autonomousPeriodic(self):
157    def autonomousPeriodic(self):
158        pass
def teleopInit(self):
160    def teleopInit(self):
161        self.updateAlliance()
162        if self.auto_command:
163            self.auto_command.cancel()
164            if self.auto_command.getName().lower() == "instantcommand":
165                self.drivetrain.reset_pose_estimator(self.drivetrain.get_default_starting_pose())
166        else:
167            self.drivetrain.reset_pose_estimator(self.drivetrain.get_default_starting_pose())
168
169        # DefaultDrive: raw square input (diagonals get full per-axis output)
170        # DefaultDriveCircular: remaps circular stick input so diagonals
171        #   reach full speed (use when stick hardware caps diagonal ~70%)
172        self.drivetrain.setDefaultCommand(
173            commands.default_swerve_drive.DefaultDrive(  # TODO: switch back to DefaultDriveCircular
174                self.drivetrain,
175                self.translate_x,
176                self.translate_y,
177                self.rotate,
178                lambda: not self.robot_relative_btn()
179            )
180        )
181
182        if self.hood:
183            self.hood.setDefaultCommand(self.hood.autoAngleCommand())
def teleopPeriodic(self):
185    def teleopPeriodic(self):
186        pass
def testInit(self):
188    def testInit(self):
189        self.updateAlliance()
190        commands2.CommandScheduler.getInstance().cancelAll()
191        # See teleopInit for DefaultDrive vs DefaultDriveCircular notes
192        self.drivetrain.setDefaultCommand(
193            commands.default_swerve_drive.DefaultDrive(  # TODO: switch back to DefaultDriveCircular
194                self.drivetrain,
195                self.translate_x,
196                self.translate_y,
197                self.rotate,
198                lambda: not self.robot_relative_btn()
199            )
200        )
201        commands2.cmd.run(lambda: self.drivetrain.drive(2, 0, 0, False), self.drivetrain).withTimeout(5).schedule()
202
203        if self.hood:
204            self.hood.setDefaultCommand(
205                self.hood.manualTestCommand(self.hood_angle_input))
def testPeriodic(self):
207    def testPeriodic(self):
208        pass
def getDeployInfo(self, key: str) -> str:
306    def getDeployInfo(self, key: str) -> str:
307        """Gets the Git SHA of the deployed robot by parsing ~/deploy.json and returning the git-hash from the JSON key OR if deploy.json is unavailable will return "unknown"
308            example deploy.json: '{"deploy-host": "DESKTOP-80HA89O", "deploy-user": "ehsra", "deploy-date": "2023-03-02T17:54:14", "code-path": "blah", "git-hash": "3f4e89f138d9d78093bd4869e0cac9b61becd2b9", "git-desc": "3f4e89f-dirty", "git-branch": "fix-recal-nbeasley"}
309
310        Args:
311            key (str): The desired json key to get. Popular onces are git-hash, deploy-host, deploy-user
312
313        Returns:
314            str: Returns the value of the desired deploy key
315        """
316        json_object = None
317        home = str(Path.home()) + os.path.sep
318        release_file = home + 'py' + os.path.sep + "deploy.json"
319        try:
320            # Read from ~/deploy.json
321            with open(release_file, "r") as openfile:
322                json_object = json.load(openfile)
323                if key in json_object:
324                    return json_object[key]
325                else:
326                    return f"Key: {key} Not Found in JSON"
327        except OSError:
328            return "unknown"
329        except json.JSONDecodeError:
330            return "bad json in deploy file check for unescaped "

Gets the Git SHA of the deployed robot by parsing ~/deploy.json and returning the git-hash from the JSON key OR if deploy.json is unavailable will return "unknown" example deploy.json: '{"deploy-host": "DESKTOP-80HA89O", "deploy-user": "ehsra", "deploy-date": "2023-03-02T17:54:14", "code-path": "blah", "git-hash": "3f4e89f138d9d78093bd4869e0cac9b61becd2b9", "git-desc": "3f4e89f-dirty", "git-branch": "fix-recal-nbeasley"}

Args: key (str): The desired json key to get. Popular onces are git-hash, deploy-host, deploy-user

Returns: str: Returns the value of the desired deploy key

def updateAlliance(self) -> None:
332    def updateAlliance(self) -> None:
333        """
334        Update the alliance the robot is on
335        """
336        self.alliance = wpilib.DriverStation.getAlliance()
337        self.drivetrain.update_alliance_flag(self.alliance)

Update the alliance the robot is on