magicbot module
- class magicbot.magicrobot.MagicRobot[source]
Bases:
RobotBase
Robots that use the MagicBot framework should use this as their base robot class. If you use this as your base, you must implement the following methods:
MagicRobot uses the
AutonomousModeSelector
to allow you to define multiple autonomous modes and to select one of them via the SmartDashboard/Shuffleboard.MagicRobot will set the following NetworkTables variables automatically:
/robot/mode
: one of ‘disabled’, ‘auto’, ‘teleop’, or ‘test’/robot/is_simulation
: True/False/robot/is_ds_attached
: True/False
Constructor for a generic robot program.
User code can be placed in the constructor that runs before the Autonomous or Operator Control period starts. The constructor will run to completion before Autonomous is entered.
This must be used to ensure that the communications code starts. In the future it would be nice to put this code into it’s own task that loads on boot so ensure that it runs.
- autonomousInit()[source]
Initialization code for autonomous mode may go here.
Users may override this method for initialization code which will be called each time the robot enters autonomous mode, regardless of the selected autonomous mode.
This can be useful for code that must be run at the beginning of a match. :rtype:
None
Note
This method is called after every component’s
on_enable
method, but before the selected autonomous mode’son_enable
method.
- consumeExceptions(forceReport=False)[source]
This returns a context manager which will consume any uncaught exceptions that might otherwise crash the robot.
Example usage:
def teleopPeriodic(self): with self.consumeExceptions(): if self.joystick.getTrigger(): self.shooter.shoot() with self.consumeExceptions(): if self.joystick.getRawButton(2): self.ball_intake.run() # and so on...
- Parameters:
forceReport (
bool
) – Always report the exception to the DS. Don’t set this to True
See also
onException()
for more details
- control_loop_wait_time = 0.02
Amount of time each loop takes (default is 20ms)
- createObjects()[source]
You should override this and initialize all of your wpilib objects here (and not in your components, for example). This serves two purposes: :rtype:
None
It puts all of your motor/sensor initialization in the same place, so that if you need to change a port/pin number it makes it really easy to find it. Additionally, if you want to create a simplified robot program to test a specific thing, it makes it really easy to copy/paste it elsewhere
It allows you to use the magic injection mechanism to share variables between components
Note
Do not access your magic components in this function, as their instances have not been created yet. Do not create them either.
- disabledInit()[source]
Initialization code for disabled mode may go here.
Users may override this method for initialization code which will be called each time the robot enters disabled mode. :rtype:
None
Note
The
on_disable
functions of all components are called before this function is called.
- disabledPeriodic()[source]
Periodic code for disabled mode should go here.
Users should override this method for code which will be called periodically at a regular rate while the robot is in disabled mode.
This code executes before the
execute
functions of all components are called.
- error_report_interval = 0.5
Error report interval: when an FMS is attached, how often should uncaught exceptions be reported?
- logger = <Logger robot (WARNING)>
A Python logging object that you can use to send messages to the log. It is recommended to use this instead of print statements.
- onException(forceReport=False)[source]
This function must only be called when an unexpected exception has occurred that would otherwise crash the robot code. Use this inside your
operatorActions()
function.If the FMS is attached (eg, during a real competition match), this function will return without raising an error. However, it will try to report one-off errors to the Driver Station so that it will be recorded in the Driver Station Log Viewer. Repeated errors may not get logged.
Example usage:
def teleopPeriodic(self): try: if self.joystick.getTrigger(): self.shooter.shoot() except: self.onException() try: if self.joystick.getRawButton(2): self.ball_intake.run() except: self.onException() # and so on...
- Parameters:
forceReport (
bool
) – Always report the exception to the DS. Don’t set this to True- Return type:
None
- robotPeriodic()[source]
Periodic code for all modes should go here.
Users must override this method to utilize it but it is not required.
This function gets called last in each mode. You may use it for any code you need to run during all modes of the robot (e.g NetworkTables updates)
The default implementation will update SmartDashboard, LiveWindow and Shuffleboard.
- Return type:
None
- startCompetition()[source]
This runs the mode-switching loop. :rtype:
None
Warning
Internal API, don’t override
- teleopInit()[source]
Initialization code for teleop control code may go here.
Users may override this method for initialization code which will be called each time the robot enters teleop mode. :rtype:
None
Note
The
on_enable
functions of all components are called before this function is called.
- teleopPeriodic()[source]
Periodic code for teleop mode should go here.
Users should override this method for code which will be called periodically at a regular rate while the robot is in teleop mode.
This code executes before the
execute
functions of all components are called.Note
If you want this function to be called in autonomous mode, set
use_teleop_in_autonomous
to True in your robot class.
- testInit()[source]
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 disabled mode.
- Return type:
None
- use_teleop_in_autonomous = False
If True, teleopPeriodic will be called in autonomous mode
Component
- class magicbot.magiccomponent.MagicComponent[source]
Bases:
object
To automagically retrieve variables defined in your base robot object, you can add the following:
class MyComponent: # other variables 'imported' automatically from MagicRobot elevator_motor: Talon other_component: MyOtherComponent ... def execute(self): # This will be automatically set to the Talon # instance created in robot.py self.elevator_motor.set(self.value)
What this says is “find the variable in the robot class called ‘elevator_motor’, which is a Talon”. If the name and type match, then the variable will automatically be injected into your component when it is created.
Note
You don’t need to inherit from
MagicComponent
, it is only provided for documentation’s sake-
logger:
Logger
- on_enable()[source]
Called when the robot enters autonomous or teleoperated mode. This function should initialize your component to a “safe” state so that unexpected things don’t happen when enabling the robot. :rtype:
None
Note
You’ll note that there isn’t a separate initialization function for autonomous and teleoperated modes. This is intentional, as they should be the same.
- setup()[source]
This function is called after
createObjects
has been called in the main robot class, and after all components have been createdThe setup function is optional and components do not have to define one.
setup()
functions are called in order of component definition in the main robot class. :rtype:None
Note
For technical reasons, variables imported from MagicRobot are not initialized when your component’s constructor is called. However, they will be initialized by the time this function is called.
-
logger:
Tunable
- class magicbot.magic_tunable.StructSerializable(*args, **kwargs)[source]
Bases:
Protocol
Any type that is a wpiutil.wpistruct.
-
WPIStruct:
ClassVar
-
WPIStruct:
- magicbot.magic_tunable.collect_feedbacks(component, cname, prefix='components')[source]
Finds all methods decorated with
feedback()
on an object and returns a list of 2-tuples (method, NetworkTables entry setter).Note
This isn’t useful for normal use.
- magicbot.magic_tunable.feedback(f=None, *, key=None)[source]
This decorator allows you to create NetworkTables values that are automatically updated with the return value of a method.
key
is an optional parameter, and if it is not supplied, the key will default to the method name with a leadingget_
removed. If the method does not start withget_
, the key will be the full name of the method.The key of the NetworkTables value will vary based on what kind of object the decorated method belongs to: :rtype:
Callable
A component:
/components/COMPONENTNAME/VARNAME
Your main robot class:
/robot/VARNAME
The NetworkTables value will be auto-updated in all modes.
Warning
The function should only act as a getter, and must not take any arguments (other than self).
Example:
from magicbot import feedback class MyComponent: navx: ... @feedback def get_angle(self) -> float: return self.navx.getYaw() class MyRobot(magicbot.MagicRobot): my_component: MyComponent ...
In this example, the NetworkTable key is stored at
/components/my_component/angle
.See also
LiveWindow
may suit your needs, especially if you wish to monitor WPILib objects.Added in version 2018.1.0.
Changed in version 2024.1.0: WPILib Struct serializable types are supported when the return type is type hinted. An
int
return type hint now creates an integer topic.
- magicbot.magic_tunable.setup_tunables(component, cname, prefix='components')[source]
Connects the tunables on an object to NetworkTables.
- Parameters:
component – Component object
cname (
str
) – Name of componentprefix (
Optional
[str
]) – Prefix to use, or no prefix if None
- Return type:
None
Note
This is not needed in normal use, only useful for testing
- class magicbot.magic_tunable.tunable(default, *, writeDefault=True, subtable=None, doc=None)[source]
Bases:
Generic
[V
]This allows you to define simple properties that allow you to easily communicate with other programs via NetworkTables.
The following example will define a NetworkTable variable at
/components/my_component/foo
:class MyRobot(magicbot.MagicRobot): my_component: MyComponent ... from magicbot import tunable class MyComponent: # define the tunable property foo = tunable(True) def execute(self): # set the variable self.foo = True # get the variable foo = self.foo
The key of the NetworkTables variable will vary based on what kind of object the decorated method belongs to:
A component:
/components/COMPONENTNAME/VARNAME
An autonomous mode:
/autonomous/MODENAME/VARNAME
Your main robot class:
/robot/VARNAME
Note
When executing unit tests on objects that create tunables, you will want to use setup_tunables to set the object up. In normal usage, MagicRobot does this for you, so you don’t have to do anything special.
Changed in version 2024.1.0: Added support for WPILib Struct serializable types. Integer defaults now create integer topics instead of double topics.
Resettable
- magicbot.magic_reset.collect_resets(cls)[source]
Get all the
will_reset_to
variables and their values from a class. :rtype:dict
[str
,Any
]Note
This isn’t useful for normal use.
- class magicbot.magic_reset.will_reset_to(default)[source]
Bases:
Generic
[V
]This marker indicates that this variable on a component will be reset to a default value at the very end of each control loop.
Example usage:
class Component: foo = will_reset_to(False) def control_fn(self): self.foo = True def execute(self): if self.foo: # ... # after all components are executed, foo is reset # back to the default value (False)
Note
This will only work for MagicRobot components
Warning
This will not work on classes that set
__slots__
.- default
State machines
- class magicbot.state_machine.AutonomousStateMachine[source]
Bases:
StateMachine
This is a specialized version of the StateMachine that is designed to be used as an autonomous mode. There are a few key differences:
The
engage()
function is always called, so the state machine will always run to completion unless done() is calledVERBOSE_LOGGING is set to True, so a log message will be printed out upon each state transition
- VERBOSE_LOGGING = True
- done()[source]
Call this function to end execution of the state machine.
This function will always be called when a state machine ends. Even if the engage function is called repeatedly, done() will be called. :rtype:
None
Note
If you wish to do something each time execution ceases, override this function (but be sure to call
super().done()
!)
- class magicbot.state_machine.StateMachine[source]
Bases:
object
The StateMachine class is used to implement magicbot components that allow one to easily define a finite state machine (FSM) that can be executed via the magicbot framework.
You create a component class that inherits from
StateMachine
. Each state is represented as a single function, and you indicate that a function is a particular state by decorating it with one of the following decorators:As the state machine executes, the decorated function representing the current state will be called. Decorated state functions can receive the following parameters (all of which are optional):
tm
- The number of seconds since autonomous has startedstate_tm
- The number of seconds since this state has been active (note: it may not start at zero!)initial_call
- Set to True when the state is initially called, False otherwise. If the state is switched to multiple times, this will be set to True at the start of each state.
To be consistent with the magicbot philosophy, in order for the state machine to execute its states you must call the
engage()
function upon each execution of the main robot control loop. If you do not call this function, then execution of the FSM will cease.Note
If you wish for the FSM to continue executing state functions regardless whether
engage()
is called, you must set themust_finish
parameter in your state decorator to be True.When execution ceases (because
engage()
was not called), thedone()
function will be called and the FSM will be reset to the starting state. The state functions will not be called again unlessengage
is called.As a magicbot component, StateMachine contains an
execute
function that will be called on each control loop. All state execution occurs from within that function call. If you call other components from a StateMachine, you should ensure that your StateMachine is declared before the other components in your Robot class.Warning
As StateMachine already contains an execute function, there is no need to define your own
execute
function for a state machine component – if you overrideexecute
, then the state machine may not work correctly. Instead, use the@default_state
decorator.Here’s a very simple example of how you might implement a shooter automation component that moves a ball into a shooter when the shooter is ready:
class ShooterAutomation(magicbot.StateMachine): # Some other component shooter: Shooter ball_pusher: BallPusher def fire(self): """This is called from the main loop.""" self.engage() @state(first=True) def begin_firing(self): """ This function will only be called IFF fire is called and the FSM isn't currently in the 'firing' state. If fire was not called, this function will not execute. """ self.shooter.enable() if self.shooter.ready(): self.next_state('firing') @timed_state(duration=1.0, must_finish=True) def firing(self): """ Because must_finish=True, once the FSM has reached this state, this state will continue executing even if engage isn't called. """ self.shooter.enable() self.ball_pusher.push() # # Note that there is no execute function defined as part of # this component # ... class MyRobot(magicbot.MagicRobot): shooter_automation: ShooterAutomation shooter: Shooter ball_pusher: BallPusher def teleopPeriodic(self): if self.joystick.getTrigger(): self.shooter_automation.fire()
This object has a lot of really useful NetworkTables integration as well:
tunables are created in /components/NAME/state - state durations can be tuned here - The ‘current state’ is output as it happens - Descriptions and names of the states are here (for dashboard use)
Warning
This object is not intended to be threadsafe and should not be accessed from multiple threads
- VERBOSE_LOGGING = False
- current_state
NT variable that indicates which state will be executed next (though, does not guarantee that it will be executed). Will return an empty string if the state machine is not currently engaged.
- done()[source]
Call this function to end execution of the state machine.
This function will always be called when a state machine ends. Even if the engage function is called repeatedly, done() will be called. :rtype:
None
Note
If you wish to do something each time execution ceases, override this function (but be sure to call
super().done()
!)
- engage(initial_state=None, force=False)[source]
This signals that you want the state machine to execute its states.
- Parameters:
initial_state (
Union
[str
,_State
,None
]) – If specified and execution is not currently occurring, start in this state instead of in the ‘first’ stateforce (
bool
) – If True, will transition even if the state machine is currently active.
- Return type:
None
- execute()[source]
magicbot component API: This is called on each iteration of the control loop. Most of the time, you will not want to override this function. If you find you want to, you may want to use the @default_state mechanism instead.
- Return type:
None
- property is_executing: bool
- Returns:
True if the state machine is executing states
-
logger:
Logger
A Python logging object automatically injected by magicbot. It can be used to send messages to the log, instead of using print statements.
- next_state(state)[source]
Call this function to transition to the next state
- Parameters:
state (
Union
[str
,_State
]) – Name of the state to transition to- Return type:
None
Note
This should only be called from one of the state functions
- next_state_now(state)[source]
Call this function to transition to the next state, and call the next state function immediately. Prefer to use
next_state()
instead.- Parameters:
state (
Union
[str
,_State
]) – Name of the state to transition to- Return type:
None
Note
This should only be called from one of the state functions
- on_disable()[source]
magicbot component API: called when autonomous/teleop is disabled
- Return type:
None
- magicbot.state_machine.default_state(f)[source]
If this decorator is applied to a method in an object that inherits from
StateMachine
, it indicates that the method is a default state; that is, if no other states are executing, this state will execute. If the state machine is always executing, the default state will never execute.There can only be a single default state in a StateMachine object.
The decorated function can have the following arguments in any order: :rtype:
_State
tm
- The number of seconds since the state machine has startedstate_tm
- The number of seconds since this state has been active (note: it may not start at zero!)initial_call
- Set to True when the state is initially called, False otherwise. If the state is switched to multiple times, this will be set to True at the start of each state execution.
- magicbot.state_machine.state(f=None, *, first=False, must_finish=False)[source]
If this decorator is applied to a function in an object that inherits from
StateMachine
, it indicates that the function is a state. The state will continue to be executed until thenext_state
function is executed.The decorated function can have the following arguments in any order:
tm
- The number of seconds since the state machine has startedstate_tm
- The number of seconds since this state has been active (note: it may not start at zero!)initial_call
- Set to True when the state is initially called, False otherwise. If the state is switched to multiple times, this will be set to True at the start of each state execution.
- Parameters:
first (
bool
) – If True, this state will be ran firstmust_finish (
bool
) – If True, then this state will continue executing even ifengage()
is not called. However, ifdone()
is called, execution will stop regardless of whether this is set.
- Return type:
Union
[Callable
[[Callable
[...
,None
]],_State
],_State
]
- magicbot.state_machine.timed_state(*, duration, next_state=None, first=False, must_finish=False)[source]
If this decorator is applied to a function in an object that inherits from
StateMachine
, it indicates that the function is a state that will run for a set amount of time unless interrupted.It is guaranteed that a timed_state will execute at least once, even if it expires prior to being executed.
The decorated function can have the following arguments in any order:
tm
- The number of seconds since the state machine has startedstate_tm
- The number of seconds since this state has been active (note: it may not start at zero!)initial_call
- Set to True when the state is initially called, False otherwise. If the state is switched to multiple times, this will be set to True at the start of each state execution.
- Parameters:
duration (
float
) – The length of time to run the state before progressing to the next statenext_state (
Union
[str
,_State
,None
]) – The name of the next state. If not specified, then this will be the last state executed if time expiresfirst (
bool
) – If True, this state will be ran firstmust_finish (
bool
) – If True, then this state will continue executing even ifengage()
is not called. However, ifdone()
is called, execution will stop regardless of whether this is set.
- Return type:
Callable
[[Callable
[...
,None
]],_State
]