robot

  1#!/usr/bin/env python3
  2
  3import typing
  4import inspect
  5import commands2
  6
  7from robotswerve import RobotSwerve
  8from utils.match_monitor import MatchMonitorConnection
  9from utils.datalog_bridge import setup_logging
 10from utils.loop_timing import LoopTimer
 11import wpilib
 12import logging
 13
 14
 15class MyRobot(commands2.TimedCommandRobot):
 16    """
 17    Our default robot class, pass it to wpilib.run
 18    Command v2 robots are encouraged to inherit from TimedCommandRobot, which
 19    has an implementation of (self) -> None:
 20    which runs the scheduler for you
 21    """
 22    # 20 ms default period (50 Hz)
 23    kDefaultPeriod: typing.ClassVar[float] = 20.0
 24    autonomousCommand: typing.Optional[commands2.Command] = None
 25
 26    def __init__(self) -> None:
 27
 28        self.__errorLogged = False
 29        self.__lastError = None
 30        self.__errorCaughtCount = 0
 31
 32        self.__initFrameTiming()
 33
 34        # Bridge Python logging -> wpilog + NT-controlled log level
 35        setup_logging()
 36
 37        # setup our scheduling period. Defaulting to 20 Hz (50 ms)
 38        super().__init__(period=MyRobot.kDefaultPeriod / 1000)
 39        
 40        #Init telem files
 41        self.telemInit()
 42
 43        self.match_monitor = MatchMonitorConnection()
 44
 45        # Instantiate our RobotContainer. This will perform all our button bindings, and put our
 46        # autonomous chooser on the dashboard.
 47        if not hasattr(self, "container"):
 48            # to work around sim creating motors during tests, assign to class and if already created keep using created robot class for tests.
 49            # during robot running, this is only every called once
 50            MyRobot.container = RobotSwerve(lambda: self.isDisabled)
 51
 52    def robotInit(self) -> None:
 53        """
 54        This function is run when the robot is first started up and should be used for any
 55        initialization code.
 56        """
 57        
 58
 59    def telemInit(self) -> None:
 60        """Initialize data logging: NT logging, console, DS, and vendor loggers.
 61
 62        WPILib DataLogManager auto-detects USB and falls back to
 63        /home/lvuser/logs.  Phoenix 6 and REV also auto-detect USB.
 64        Logging is disabled in simulation to avoid junk files.
 65        """
 66        if self.isSimulation():
 67            return
 68
 69        # WPILib data log (.wpilog) — auto-uses USB if present
 70        wpilib.DataLogManager.start()
 71        wpilib.DataLogManager.logNetworkTables(True)
 72        wpilib.DataLogManager.logConsoleOutput(True)
 73        wpilib.DriverStation.startDataLog(wpilib.DataLogManager.getLog())
 74
 75        # Phoenix 6 signal logging (.hoot files)
 76        # Disabled: SignalLogger spams errors about full disks
 77        # from phoenix6 import SignalLogger
 78        # SignalLogger.enable_auto_logging(True)
 79        # SignalLogger.start()
 80
 81        # REV status logging (.revlog files)
 82        from rev import StatusLogger
 83        StatusLogger.start()
 84
 85    def robotPeriodic(self) -> None:
 86        self.__callAndCatch(self.container.robotPeriodic)
 87
 88        wpilib.SmartDashboard.putNumber("Code Crash Count", self.__errorCaughtCount)
 89        self.__frameTimingPeriodic()
 90
 91    def disabledInit(self) -> None:
 92        """This function is called once each time the robot enters Disabled mode."""
 93        self.__timing.reset_all()
 94        self.container.disabledInit()
 95        self.match_monitor.start_upload()
 96
 97    def disabledPeriodic(self) -> None:
 98        """This function is called periodically when disabled"""
 99        self.__timing.start("userCode")
100        self.container.disabledPeriodic()
101
102    def autonomousInit(self) -> None:
103        """This autonomous runs the autonomous command selected by your RobotContainer class."""
104        self.match_monitor.stop_upload()
105        self.__timing.reset_all()
106        self.container.autonomousInit()
107
108    def autonomousPeriodic(self) -> None:
109        """This function is called periodically during autonomous"""
110        self.__timing.start("userCode")
111        self.__callAndCatch(self.container.autonomousPeriodic)
112
113    def teleopInit(self) -> None:
114        self.match_monitor.stop_upload()
115        self.__timing.reset_all()
116        self.container.teleopInit()
117
118    def teleopPeriodic(self) -> None:
119        """This function is called periodically during operator control"""
120        self.__timing.start("userCode")
121        self.__callAndCatch(self.container.teleopPeriodic)
122
123    def testInit(self) -> None:
124        self.match_monitor.stop_upload()
125        self.__timing.reset_all()
126        self.container.testInit()
127
128    def testPeriodic(self) -> None:
129        self.__timing.start("userCode")
130        self.container.testPeriodic()
131
132    def getRobot(self) -> RobotSwerve:
133        return self.container
134
135    def __initFrameTiming(self):
136        """Set up loop timing instrumentation.
137
138        Wraps CommandScheduler.run() BEFORE super().__init__() captures it
139        so the scheduler channel measures the real execution cost.
140        """
141        self.__timing = LoopTimer(budget_sec=MyRobot.kDefaultPeriod / 1000)
142        self.__timing.add_channel("userCode")
143        self.__timing.add_channel("scheduler")
144
145        scheduler = commands2.CommandScheduler.getInstance()
146        original_run = scheduler.run
147        timing = self.__timing
148
149        def _timed_run():
150            timing.start("scheduler")
151            original_run()
152            timing.stop("scheduler")
153
154        scheduler.run = _timed_run
155
156    def __frameTimingPeriodic(self):
157        """Stop the userCode channel and publish all timing stats."""
158        self.__timing.stop("userCode")
159        self.__timing.publish()
160
161    def __callAndCatch(self, func: typing.Callable[[], None]) -> None:
162        try:
163            #Invoke the function
164            func()
165
166            #if we returned, it didnt crash so clear the last error if it was set
167            if self.__errorLogged and self.__lastError is not None:
168                logging.info(f"Logged error cleared for: {str(self.__lastError)}")
169                self.__errorLogged = False
170                self.__lastError = None
171        except Exception as e:
172            self.__lastError = e
173            name = inspect.currentframe().f_back.f_code.co_name
174            if self.isSimulation():
175                raise e
176
177            self.__errorCaughtCount = self.__errorCaughtCount + 1
178
179            if not self.__errorLogged:
180                logging.exception(f"(CRASH CATCH) {name} error: ")
181                self.__errorLogged = True
182
183
184if __name__ == "__main__":
185    print("Please run python -m robotpy <args>")
186    exit(1)
class MyRobot(commands2.timedcommandrobot.TimedCommandRobot):
 16class MyRobot(commands2.TimedCommandRobot):
 17    """
 18    Our default robot class, pass it to wpilib.run
 19    Command v2 robots are encouraged to inherit from TimedCommandRobot, which
 20    has an implementation of (self) -> None:
 21    which runs the scheduler for you
 22    """
 23    # 20 ms default period (50 Hz)
 24    kDefaultPeriod: typing.ClassVar[float] = 20.0
 25    autonomousCommand: typing.Optional[commands2.Command] = None
 26
 27    def __init__(self) -> None:
 28
 29        self.__errorLogged = False
 30        self.__lastError = None
 31        self.__errorCaughtCount = 0
 32
 33        self.__initFrameTiming()
 34
 35        # Bridge Python logging -> wpilog + NT-controlled log level
 36        setup_logging()
 37
 38        # setup our scheduling period. Defaulting to 20 Hz (50 ms)
 39        super().__init__(period=MyRobot.kDefaultPeriod / 1000)
 40        
 41        #Init telem files
 42        self.telemInit()
 43
 44        self.match_monitor = MatchMonitorConnection()
 45
 46        # Instantiate our RobotContainer. This will perform all our button bindings, and put our
 47        # autonomous chooser on the dashboard.
 48        if not hasattr(self, "container"):
 49            # to work around sim creating motors during tests, assign to class and if already created keep using created robot class for tests.
 50            # during robot running, this is only every called once
 51            MyRobot.container = RobotSwerve(lambda: self.isDisabled)
 52
 53    def robotInit(self) -> None:
 54        """
 55        This function is run when the robot is first started up and should be used for any
 56        initialization code.
 57        """
 58        
 59
 60    def telemInit(self) -> None:
 61        """Initialize data logging: NT logging, console, DS, and vendor loggers.
 62
 63        WPILib DataLogManager auto-detects USB and falls back to
 64        /home/lvuser/logs.  Phoenix 6 and REV also auto-detect USB.
 65        Logging is disabled in simulation to avoid junk files.
 66        """
 67        if self.isSimulation():
 68            return
 69
 70        # WPILib data log (.wpilog) — auto-uses USB if present
 71        wpilib.DataLogManager.start()
 72        wpilib.DataLogManager.logNetworkTables(True)
 73        wpilib.DataLogManager.logConsoleOutput(True)
 74        wpilib.DriverStation.startDataLog(wpilib.DataLogManager.getLog())
 75
 76        # Phoenix 6 signal logging (.hoot files)
 77        # Disabled: SignalLogger spams errors about full disks
 78        # from phoenix6 import SignalLogger
 79        # SignalLogger.enable_auto_logging(True)
 80        # SignalLogger.start()
 81
 82        # REV status logging (.revlog files)
 83        from rev import StatusLogger
 84        StatusLogger.start()
 85
 86    def robotPeriodic(self) -> None:
 87        self.__callAndCatch(self.container.robotPeriodic)
 88
 89        wpilib.SmartDashboard.putNumber("Code Crash Count", self.__errorCaughtCount)
 90        self.__frameTimingPeriodic()
 91
 92    def disabledInit(self) -> None:
 93        """This function is called once each time the robot enters Disabled mode."""
 94        self.__timing.reset_all()
 95        self.container.disabledInit()
 96        self.match_monitor.start_upload()
 97
 98    def disabledPeriodic(self) -> None:
 99        """This function is called periodically when disabled"""
100        self.__timing.start("userCode")
101        self.container.disabledPeriodic()
102
103    def autonomousInit(self) -> None:
104        """This autonomous runs the autonomous command selected by your RobotContainer class."""
105        self.match_monitor.stop_upload()
106        self.__timing.reset_all()
107        self.container.autonomousInit()
108
109    def autonomousPeriodic(self) -> None:
110        """This function is called periodically during autonomous"""
111        self.__timing.start("userCode")
112        self.__callAndCatch(self.container.autonomousPeriodic)
113
114    def teleopInit(self) -> None:
115        self.match_monitor.stop_upload()
116        self.__timing.reset_all()
117        self.container.teleopInit()
118
119    def teleopPeriodic(self) -> None:
120        """This function is called periodically during operator control"""
121        self.__timing.start("userCode")
122        self.__callAndCatch(self.container.teleopPeriodic)
123
124    def testInit(self) -> None:
125        self.match_monitor.stop_upload()
126        self.__timing.reset_all()
127        self.container.testInit()
128
129    def testPeriodic(self) -> None:
130        self.__timing.start("userCode")
131        self.container.testPeriodic()
132
133    def getRobot(self) -> RobotSwerve:
134        return self.container
135
136    def __initFrameTiming(self):
137        """Set up loop timing instrumentation.
138
139        Wraps CommandScheduler.run() BEFORE super().__init__() captures it
140        so the scheduler channel measures the real execution cost.
141        """
142        self.__timing = LoopTimer(budget_sec=MyRobot.kDefaultPeriod / 1000)
143        self.__timing.add_channel("userCode")
144        self.__timing.add_channel("scheduler")
145
146        scheduler = commands2.CommandScheduler.getInstance()
147        original_run = scheduler.run
148        timing = self.__timing
149
150        def _timed_run():
151            timing.start("scheduler")
152            original_run()
153            timing.stop("scheduler")
154
155        scheduler.run = _timed_run
156
157    def __frameTimingPeriodic(self):
158        """Stop the userCode channel and publish all timing stats."""
159        self.__timing.stop("userCode")
160        self.__timing.publish()
161
162    def __callAndCatch(self, func: typing.Callable[[], None]) -> None:
163        try:
164            #Invoke the function
165            func()
166
167            #if we returned, it didnt crash so clear the last error if it was set
168            if self.__errorLogged and self.__lastError is not None:
169                logging.info(f"Logged error cleared for: {str(self.__lastError)}")
170                self.__errorLogged = False
171                self.__lastError = None
172        except Exception as e:
173            self.__lastError = e
174            name = inspect.currentframe().f_back.f_code.co_name
175            if self.isSimulation():
176                raise e
177
178            self.__errorCaughtCount = self.__errorCaughtCount + 1
179
180            if not self.__errorLogged:
181                logging.exception(f"(CRASH CATCH) {name} error: ")
182                self.__errorLogged = True

Our default robot class, pass it to wpilib.run Command v2 robots are encouraged to inherit from TimedCommandRobot, which has an implementation of (self) -> None: which runs the scheduler for you

MyRobot()
27    def __init__(self) -> None:
28
29        self.__errorLogged = False
30        self.__lastError = None
31        self.__errorCaughtCount = 0
32
33        self.__initFrameTiming()
34
35        # Bridge Python logging -> wpilog + NT-controlled log level
36        setup_logging()
37
38        # setup our scheduling period. Defaulting to 20 Hz (50 ms)
39        super().__init__(period=MyRobot.kDefaultPeriod / 1000)
40        
41        #Init telem files
42        self.telemInit()
43
44        self.match_monitor = MatchMonitorConnection()
45
46        # Instantiate our RobotContainer. This will perform all our button bindings, and put our
47        # autonomous chooser on the dashboard.
48        if not hasattr(self, "container"):
49            # to work around sim creating motors during tests, assign to class and if already created keep using created robot class for tests.
50            # during robot running, this is only every called once
51            MyRobot.container = RobotSwerve(lambda: self.isDisabled)

__init__(self: wpilib._wpilib.TimedRobot, period: wpimath.units.seconds = 0.02) -> None

Constructor for TimedRobot.

Parameters
  • period: The period of the robot loop function.
kDefaultPeriod: ClassVar[float] = 20.0
autonomousCommand: commands2.command.Command | None = None
match_monitor
def robotInit(self) -> None:
53    def robotInit(self) -> None:
54        """
55        This function is run when the robot is first started up and should be used for any
56        initialization code.
57        """

This function is run when the robot is first started up and should be used for any initialization code.

def telemInit(self) -> None:
60    def telemInit(self) -> None:
61        """Initialize data logging: NT logging, console, DS, and vendor loggers.
62
63        WPILib DataLogManager auto-detects USB and falls back to
64        /home/lvuser/logs.  Phoenix 6 and REV also auto-detect USB.
65        Logging is disabled in simulation to avoid junk files.
66        """
67        if self.isSimulation():
68            return
69
70        # WPILib data log (.wpilog) — auto-uses USB if present
71        wpilib.DataLogManager.start()
72        wpilib.DataLogManager.logNetworkTables(True)
73        wpilib.DataLogManager.logConsoleOutput(True)
74        wpilib.DriverStation.startDataLog(wpilib.DataLogManager.getLog())
75
76        # Phoenix 6 signal logging (.hoot files)
77        # Disabled: SignalLogger spams errors about full disks
78        # from phoenix6 import SignalLogger
79        # SignalLogger.enable_auto_logging(True)
80        # SignalLogger.start()
81
82        # REV status logging (.revlog files)
83        from rev import StatusLogger
84        StatusLogger.start()

Initialize data logging: NT logging, console, DS, and vendor loggers.

WPILib DataLogManager auto-detects USB and falls back to /home/lvuser/logs. Phoenix 6 and REV also auto-detect USB. Logging is disabled in simulation to avoid junk files.

def robotPeriodic(self) -> None:
86    def robotPeriodic(self) -> None:
87        self.__callAndCatch(self.container.robotPeriodic)
88
89        wpilib.SmartDashboard.putNumber("Code Crash Count", self.__errorCaughtCount)
90        self.__frameTimingPeriodic()

robotPeriodic(self: wpilib._wpilib.IterativeRobotBase) -> None

Periodic code for all modes should go here.

This function is called each time a new packet is received from the driver station.

def disabledInit(self) -> None:
92    def disabledInit(self) -> None:
93        """This function is called once each time the robot enters Disabled mode."""
94        self.__timing.reset_all()
95        self.container.disabledInit()
96        self.match_monitor.start_upload()

This function is called once each time the robot enters Disabled mode.

def disabledPeriodic(self) -> None:
 98    def disabledPeriodic(self) -> None:
 99        """This function is called periodically when disabled"""
100        self.__timing.start("userCode")
101        self.container.disabledPeriodic()

This function is called periodically when disabled

def autonomousInit(self) -> None:
103    def autonomousInit(self) -> None:
104        """This autonomous runs the autonomous command selected by your RobotContainer class."""
105        self.match_monitor.stop_upload()
106        self.__timing.reset_all()
107        self.container.autonomousInit()

This autonomous runs the autonomous command selected by your RobotContainer class.

def autonomousPeriodic(self) -> None:
109    def autonomousPeriodic(self) -> None:
110        """This function is called periodically during autonomous"""
111        self.__timing.start("userCode")
112        self.__callAndCatch(self.container.autonomousPeriodic)

This function is called periodically during autonomous

def teleopInit(self) -> None:
114    def teleopInit(self) -> None:
115        self.match_monitor.stop_upload()
116        self.__timing.reset_all()
117        self.container.teleopInit()

teleopInit(self: wpilib._wpilib.IterativeRobotBase) -> None

Initialization code for teleop mode should go here.

Users should override this method for initialization code which will be called each time the robot enters teleop mode.

def teleopPeriodic(self) -> None:
119    def teleopPeriodic(self) -> None:
120        """This function is called periodically during operator control"""
121        self.__timing.start("userCode")
122        self.__callAndCatch(self.container.teleopPeriodic)

This function is called periodically during operator control

def testInit(self) -> None:
124    def testInit(self) -> None:
125        self.match_monitor.stop_upload()
126        self.__timing.reset_all()
127        self.container.testInit()

testInit(self: wpilib._wpilib.IterativeRobotBase) -> None

Initialization code for test mode should go here.

Users should override this method for initialization code which will be called each time the robot enters test mode.

def testPeriodic(self) -> None:
129    def testPeriodic(self) -> None:
130        self.__timing.start("userCode")
131        self.container.testPeriodic()

testPeriodic(self: wpilib._wpilib.IterativeRobotBase) -> None

Periodic code for test mode should go here.

Users should override this method for code which will be called each time a new packet is received from the driver station and the robot is in test mode.

def getRobot(self) -> robotswerve.RobotSwerve:
133    def getRobot(self) -> RobotSwerve:
134        return self.container