Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
The composition of hardware components and their basic helper functions
An FTCLib Subsystem is like a part of your robot that controls specific tasks, such as driving, lifting an arm, or shooting a game element. Each subsystem has its own job and knows how to control the motors, sensors, or other components related to that job. By breaking the robot into subsystems, your code becomes easier to manage and update because each part is responsible for its own actions. Think of it like different departments in a company—each one has a clear purpose, making the whole robot work smoothly together.
The advantage of using subsystems in FTCLib is that it allows for smooth control of multiple parts of your robot at once. Organizing your code this way allows you to run several subsystems without everything happening in one big block. This means one thread can manage multiple subsystems simultaneously without commands blocking each other. For example, the robot can move and operate its arm, and if a new command interrupts a current action—like stopping the drive to shoot a game element—it can smoothly switch tasks without delays or confusion.
Hardware and Setup:
The Arm
subsystem in our example demonstrates essential aspects of subsystem creation:
Hardware References: Declares member variables like motor
, wristServo
, etc., linking them to hardware names from a centralized place for these "magic strings," such as HardwareNames
.
Motor Configuration: Sets up the motor
for precise position control using MotorEx
, defining parameters like PID coefficients and tolerances.
Servo Initialization: Retrieves servo objects from the hardwareMap
and sets their initial positions.
Helpful Methods:
Subsystems often include convenience methods to control hardware directly. The Arm
class provides examples:
travelMode()
: Sets the arm for driving by closing the claw, adjusting the wrist, and storing the new wrist position.
wristUp()
/wristDown()
: Increment/decrement the wrist servo position within set limits.
openClaw()
/closeClaw()
: Manipulate the claw servo based on an isOpen
boolean flag.
toggleOpen()
/toggleRoll()
: Implement button-pressable actions using conditional logic.
State Management:
Subsystems can maintain internal state using variables like isOpen
for the claw. The toString()
method provides a human-readable snapshot of the subsystem's current state (servo positions in this case).
Where do state variables go?
There are state variables like wristAng
and isOpen
in this subsystem. Does every subsystem get unique state variables? Should we consolidate them into the MyRobot
file? These are organizational guidelines that are flexible and will need to be discussed so all programmers know how things ought to be organized.
Beyond Commands:
Remember, not every action needs a dedicated Command. Servos like the wrist can be adjusted directly using methods like wristUp()
. They're instantaneous and aren't a threat to block off other commands. So here's how we can bind these to a button (more details in Running Your Code).
Next Steps:
While this explanation covers the basics of subsystems, exploring the provided Robot
OpMode will reveal how these subsystems are integrated and controlled within your robot's overall behavior. Remember, effectively utilizing subsystems is vital in writing well-structured and maintainable robot code in FTCLib.
Our team uses Java to program our robots. Many of the coding skills can be learned from our AP Computer Science course.
Conceptual overview of the "what" and "why" of Command pattern
The Command pattern is a fundamental design principle FTCLib uses, and understanding it will significantly enhance your coding experience. This page delves into the "what" and "why" behind this pattern, equipping you to write efficient and maintainable robot code.
The Command pattern boasts a rich history dating back to the late 1970s and early 1980s. The concept emerged within the Smalltalk and Xerox PARC communities, documented in publications like Anneliese Schürr's 1992 paper "Separation of Concerns and the Command Pattern." Initially focused on graphical user interfaces (GUIs), the pattern's flexibility was adopted in various software development domains.
Its popularity soared with the rise of design patterns in the 1990s, solidified by Erich Gamma et al.'s influential book "Design Patterns: Elements of Reusable Object-Oriented Software." This widespread recognition cemented the Command pattern as a valuable tool for promoting loose coupling, reusability, and maintainability in object-oriented programming.
The Command pattern originates from software design principles and offers a structured way to manage robot actions. Instead of writing long, blocking code sections, you create independent classes called Commands. Each Command encapsulates your robot's specific task, like driving forward, turning, or activating a mechanism.
Traditional robot programming often involves lengthy code blocks that execute sequentially, potentially hindering responsiveness and multitasking. If your robot is stuck inside of a loop that gradually moves a motor, it's not doing other things. It's blocked until you move on.
Non-blocking commands eliminate the need to do one thing at a time by having repeatedly called execute
functions that run until their isFinished
condition is reached. This allows the robot to perform multiple actions simultaneously. This means smoother operation and the ability to react to real-time events effectively.
Commands are designed for reusability. You can create a generic RotateCommand
and adapt it for different distances or speeds across various parts of your code. This reduces code duplication and simplifies maintenance.
FTCLib encourages modularity with separate Command files. This translates to clear ownership and promotes a collaborative development environment. Team members can work on their specific Commands without worrying about conflicts in common code sections. Version control systems like Git become even more valuable as team members push their Command commits independently.
FTCLib does much more than implement the Command pattern. We've got ourselves an extensive toolbox to elevate our robot programming:
Finer Motor Control:
Motor Wrappers: Simplify motor control with pre-built classes handling direction, speed, and encoder feedback.
PID Control Helpers: Fine-tune motor movements with built-in PID controllers for precise control.
Navigation Expertise:
Odometry Support: Implement robust navigation using odometry wheels. FTCLib calculates robot pose from encoder readings, simplifying movement and autonomous routines.
Path Following: Define and execute pre-defined paths for complex maneuvers and automated navigation.
Visionary Solutions:
EasyOpenCV Integration: Seamlessly incorporate computer vision tasks. Detect objects, track targets, and analyze the environment using EasyOpenCV's intuitive tools.
Camera Support: Access and manage multiple cameras on your robot, expanding your visual perception capabilities.
Sensor Fusion Power:
Sensor Wrappers: Interact easily with various sensors like gyros, accelerometers, and ultrasonic sensors. Access their data effortlessly for informed decision-making.
Data Fusion: Combine data from multiple sensors to better understand your robot's environment, enabling intelligent responses.
Additional Gems:
Telemetry: Log and display robot data on the Driver Station for debugging, monitoring, and optimization.
Logging: Record robot behavior for analysis and performance improvements.
Scheduling: Manage task execution precisely with the built-in scheduler, ensuring coordinated robot actions.
The files that drive FTCLib
Before looking under the hood of our code, programmers should study the Command pattern's goals of organized, non-blocking code. We've done that ✅. You don't need to go past this point. We hardly ever need to touch these files. Programmers can contribute to a team by knowing how Command
and Subsystem
work together and how to run their code. Proceeding on means you want the next level. You're comfortable and open-minded about how this all works.
The secret to understanding how we turned FTC's code into a Command pattern is how the following two files connect. They sit in our utils folder, working their magic--we may never need to touch them. Let's start with Robot.java
.
This file acts as an invisible layer, providing essential functionalities:
Shared State: The isDisabled
static variable is a central flag accessible throughout your code, indicating robot state (enabled/disabled). The disable()
and enable()
methods allow external control over the robot's state during transitions between autonomous and teleop periods.
Command Control Center: Methods like reset()
, run()
, schedule()
, and register()
grant access to the CommandScheduler
, the mastermind behind command execution and resource management. Think of it as the conductor of your robot's actions.
isDisabled
: Expanding State VariablesRemember, the way the isDisabled
variable controls state is not how we need to track our robot's current status. It doesn't need a static variable; we'll have easy access to the instance of our robot. We're not restricted to booleans. We'll use custom state variables of all sorts--here's an example that uses enums to represent various robot conditions:
Here's the most straightforward connection point between Command and FTC. This file is our translator.
CommandOpMode
runs LinearOpMode
's critical method runOpMode()
method. That's it 🤯. Sure, we can also post telemetry
updates, but the real essential bit is just that stupid run()
call. That keeps our CommandScheduler working while satisfying the requirements for FTC gameplay.
The reset()
method within both Robot
and CommandOpMode
plays a crucial role in managing Commands during various FTC gameplay scenarios. Here are some practical examples of when you might utilize it:
1. Autonomous Period Transitions:
Switching Strategies: During autonomous, imagine your robot first performs a pre-determined path using a pre-scheduled set of Commands. If external sensors detect an unexpected obstacle, you can call reset()
in Robot
or CommandOpMode
to clear all running Commands and initiate a new set of Commands for obstacle avoidance or adaptation.
Recovering from Errors: If a sensor reading is deemed unreliable or a Command encounters an error, resetting can clear the current action and allow you to initiate a new Command for recovery or safe shutdown.
2. Teleop Period:
Button-Triggered Resets: Consider a button press on your gamepad that triggers reset()
in CommandOpMode
. This could be used to:
Cancel an ongoing movement Command if the driver wants to stop abruptly.
Reset arm or claw positions to known starting points for precise manipulation.
Restart a specific Command sequence for a repeated action.
3. Testing and Debugging:
Isolating Command Behavior: During testing, you might use reset()
after each test run to isolate specific Commands and verify their functionality without interference from previous actions.
Reproducing Issues: If your robot exhibits unexpected behavior, strategically placing reset()
calls can help reproduce the issue for easier debugging and troubleshooting.
A function-by-function discussion of a Command's essential components
Commands are the heart of FTCLib, promoting clean and organized robot code. Before we get too technical, let's dissect a basic Command example, exploring its critical sections.
When crafting a Command, the constructor serves as its architect, defining essential elements before the Command's execution:
1. Establishing Requirements:
Timeout: Specifies the maximum duration for the Command's execution, ensuring it doesn't run indefinitely. This is a default feature but can be eliminated for commands that run by default (like the DriveCommand
)
Subsystem Dependencies: Identifies which robot subsystems (e.g., drive, arm, elevator) are crucial for the Command's operation. This declaration is accomplished through the addRequirements(subsystem)
call. That subsystem will cancel any other commands when this one is run.
Handy References: Creates shortcuts to frequently used robot components, streamlining code and enhancing readability. Connection to our robot object allows us to check on its state variables.
2. Pre-Execution Setup:
The constructor executes during robot setup, preparing the Command before its actual launch. This separation from the initialize()
method, which runs immediately before execution, allows for careful configuration and resource management.
The initialize()
method acts as the Command's launchpad, performing critical setup tasks right before execution:
1. Setting the Stage:
Timer Activation: Initializes the timeout timer using timer.start()
to enforce the Command's duration.
Target Calculation:
RoadRunner: Constructs an Action object using actionBuilder
for complex trajectories.
2. Key Considerations:
Once and Done: initialize()
executes only once before the Command's execution loop begins.
Preparation Focus: Its primary responsibility lies in configuring initial values, targets, and action plans, setting the stage for successful execution.
Essential Reminder:
Although initialize()
lays the groundwork, the execute()
method, covered next, drives the Command's actions and interacts with the robot's physical systems. This non-blocking and concurrent nature can be a new concept for high school programmers.
The execute()
method is the Command's heart and soul, translating plans into action.
1. The Execution Loop:
Imagine execute()
as a conductor, continuously calling the shots throughout the Command's lifespan. It doesn't have a set duration; it will keep getting called until its isFinished()
method signals completion. However, it's crucial to avoid blocking calls like while
loops or sleep()
that would halt the entire robot program.
2. Concurrent Commands: Sharing the Stage Gracefully:
Remember our analogy of multiple Commands as athletes? They might perform simultaneously, especially if they don't require the same resources. execute()
needs to consider this possibility, ensuring smooth cooperation and avoiding conflicts.
3. Key Principles:
Non-Blocking: Steer clear of blocking calls that would freeze the robot program. Instead, favor short, focused actions within execute()
and rely on isFinished()
for termination.
Concurrent-Friendly: Design your execute()
logic to function correctly even when other Commands are running concurrently, as long as they don't interfere with the required resources.
4. Understanding the Challenge:
This non-blocking and concurrent nature can be a new concept for high school programmers. Here's an analogy to help:
Imagine juggling. Each Command is a ball you keep in the air. You can't hold onto any ball for too long (blocking call), or you'll drop the others (other Commands needing resources). But by quickly tossing and catching each ball (short actions), you can keep them all going simultaneously (concurrently).
Remember:
execute()
is the action stage, but design it wisely to avoid blocking and ensure smooth cooperation with other concurrent Commands. It's like the insides of the while loops we're avoiding because they're blocking (and the condition of this avoided while loops is the isFinished
)
Embrace non-blocking techniques and keep your execute()
methods focused and efficient for optimal robot performance.
The final act of our Command play unfolds in these two methods:
Think of isFinished()
as the stage manager, waiting for the right moment to signal the end of the Command's performance. Similar to the condition in a while
loop, it continuously evaluates whether the Command has achieved its goal. If isFinished()
returns true
, the curtain falls, and the Command gracefully exits.
Key Considerations:
Exit Criteria: Define clear conditions for completion within isFinished()
. Common criteria include:
Time elapsing (using the timer started in initialize()
)
Sensor readings reaching desired values
External signals (e.g., button press)
Concurrent Awareness: Remember other Commands might be waiting in the wings. Ensure your isFinished()
logic doesn't hold onto resources unnecessarily, preventing other Commands from running.
The end()
method serves as the post-performance cleanup crew, ensuring everything is left tidy after the Command finishes, whether naturally or interrupted by another Command.
Key Considerations:
Power Down: Stop motors, reset servos, and release any acquired resources.
State Management: If using an ENUM to track subsystem state, ensure it reflects the actual final state, especially if interrupted. Update the ENUM variable accordingly in end()
.
Cleanup Thoroughness: Leave the robot and subsystems ready for the next Command without lingering effects.
Where our commands meet our subsystems
From the little Android driver station, here's where we select our autonomous or teleop opModes. Our autonomous entry point can have some options to indicate where the robot is starting on the field and any decisions about scoring that need to be made.
The teleop kick-off is much simpler:
These two opModes go to the same place but get there using different constructors. The robot knows whether to run autonomous or teleop based on which constructor is being called.
This is where Commands get lined up for autonomous mode or bound to controller inputs in teleop. MyRobot
is a soulless filename; typically, we'll rename this file each year after our robot.
One for teleop, requiring only the opMode
object.
Another for autonomous, taking additional parameters so we know our starting position and what script to run (isRed
, isLeft
, goForBoard
)
Once our drive train drive
is registered, we give it a default command, DriveCommand
defaultCommand
is a special command assigned to a subsystem that continuously runs unless interrupted by another higher-priority command.
The DriveCommand
will constantly watch joystick inputs as well as apply dead zones and field-orientated drive
If you press a button that triggers another command for the drive system (e.g., rotating), that command will interrupt the DriveCommand
temporarily until it finishes.
Once the other command finishes, the DriveCommand
automatically resumes as the defaultCommand
, listening to joystick inputs again.
The MyRobot
code demonstrates different ways to connect gamepad buttons with robot actions using FTCLib. Here's a breakdown of the standard methods and how they translate to gameplay scenarios:
1. whenPressed
:
Definition: Executes a command once when the button is initially pressed.
Example: Pressing the A button on Gamepad 1 toggles the driving mode.
FTC Gameplay:
Use this for actions that only need to happen once per button press, like activating a boost ability or triggering a quick arm movement.
2. whenReleased
:
Definition: Executes a command once when the button is released.
Example: Releasing the B button on Gamepad 2 closes the arm claw.
FTC Gameplay:
This is useful for actions that occur when you stop pressing the button, like stopping arm movement or ending a special ability.
3. whileHeld
:
Definition: Continuously executes a command while the button is held down.
Example: Holding the d-pad on Gamepad 2 raises or lowers the arm's wrist.
4. toggleWhenActive
:
Definition: Starts/stops a command each time the button is pressed, regardless of previous state.
Example: Not used in the provided code, but it could be used for toggling a light or other on/off functionality.
FTC Gameplay:
This is helpful for quickly activating and deactivating abilities without keeping the button held down.
Here's an example of how to link a button with a command using whileHeld
:
This code creates a button listener for the up d-pad on Gamepad 2. When held down, it continuously executes a command that raises the arm's wrist.
initAuto
In MyRobot
's initAuto
function, the autonomous script takes shape. But how do you structure your commands? Let's delve into the options:
1. Sequential Commands:
Commands execute one after another, completing before the next starts.
Think of it as a step-by-step recipe: scan for the prop, turn towards it, open the claw, move closer, etc.
Benefits:
Easy to understand and troubleshoot.
Ensures each action finishes cleanly before proceeding.
Drawbacks:
Might be slower if some actions could happen simultaneously.
Less flexibility for complex maneuvers involving interactions or timing dependencies.
2. Concurrent Commands:
Multiple commands run simultaneously, sharing resources and potentially interacting dynamically.
Imagine multitasking: turning while moving closer or raising the wrist while opening the claw.
Benefits:
Can be more efficient if actions don't rely on each other's completion.
Offers greater flexibility for intricate robot behaviors.
Drawbacks:
Requires careful planning to avoid conflicts and ensure smooth coordination.
Debugging can be more challenging.
Writing the Script:
initAuto
provides flexibility:
Within initAuto
: You can build your entire autonomous sequence directly in our robot file using a new SequentialCommandGroup()
with nested commands. This approach keeps everything centralized and concise.
Separate File: Files are kept shorter if we keep the autonomous procedure in its own SequentialCommandGroup file. This means a programmer tasked with this job could push changes to our GitHub repo with less concern for conflicts. 🤝
FTCLib: Calculates the encoder target position for precise motor control. See examples here:
Autonomous mode is easier and more effective with the RoadRunner toolkit. It's not easy to understand all at once, but it makes a lot of sense if we look a little bit at a time.
I believe RoadRunner was developed by Team #8367, Acme Robotics, around 2018 to address the need for more precise and efficient motion planning in FTC competitions. Before RoadRunner, many teams relied on less advanced methods for robot trajectory control, limiting the precision of their autonomous routines. By 2019 and 2020, RoadRunner gained widespread adoption. It has since become a go-to tool for FTC teams seeking smooth, accurate robot movement during autonomous periods.
RoadRunner is a motion planning library designed for FTC robots, allowing teams to create precise, smooth trajectories for autonomous movement. It uses kinematic models, such as mecanum and tank drive, to account for a robot’s unique drivetrain and behavior. RoadRunner operates by defining Pose2d (position and orientation) coordinates and allows teams to create paths with splines (curved paths) and waypoints (specific positions the robot should pass through). The library incorporates feedforward control and supports tuning for optimal motion via parameters like kV, kA, and track width, providing fine-tuned trajectory generation for complex autonomous tasks.
VOCABULARY
Pose2d: Defines the robot’s position (x, y) and orientation (heading) on the field.
Spline: A smooth, curved path that the robot follows during its trajectory.
Waypoint: Specific points that the robot must pass through on its path.
Feedforward Control: A method of controlling robot movement based on predicted motion, helping smooth trajectories.
kV: A constant used to model the robot’s velocity.
kA: A constant used to model the robot’s acceleration.
Track Width: The distance between the left and right wheels of a robot, important for accurately modeling turns.
Mecanum Drive: A type of drivetrain that allows for omnidirectional movement using angled wheels.
Tank Drive: A drivetrain setup where each side of the robot operates independently, allowing for tight turns.
Odometry: Use of data from motion sensors to estimate change in position over time. It is used in robotics by some legged or wheeled robots to estimate their position relative to a starting location. This method is sensitive to errors due to the integration of velocity measurements over time to give position estimates
To integrate FTCLib with RoadRunner, we start by modifying the provided Mecanum Drive class. First, we remove its status as final
, allowing us to extend it. We then make this class extend FTCLib’s Subsystem class, renaming it to RoadRunner for clarity. We inject our parameters using the Params object but keep the rest of the logic minimal in this class. Instead, we place our custom functionality in the Mecanum Drive class, where we define any additional logic or custom functions tailored to our specific robot. This separation allows RoadRunner to handle trajectory generation while our custom drive logic remains organized in Mecanum Drive.
Let's get our robot to recognize certain objects and their relative position.
OpenCV began as an Intel research project in 1999, aiming to accelerate real-time computer vision tasks. Its 2002 open-source release fostered a large developer community, leading to its growth into a powerful and versatile library for various computer vision applications across fields like robotics, self-driving cars, and medical imaging.
OpenCV's success as a powerful yet general-purpose library sparked the development of more specialized solutions. EasyOpenCV (EOCV) emerged within the FTC (FIRST Tech Challenge) robotics community. It leverages OpenCV's core functionalities but simplifies them and adds robot-specific features for easier integration with FTC robots. This allows FTC teams to focus on their robot's vision-based tasks without needing in-depth computer vision expertise.
Tensor: A multi-dimensional array used to represent data in machine learning models. Tensors are the fundamental data structures in deep learning frameworks like TensorFlow and PyTorch.
Inference: The process of running a trained machine learning model on new input data to obtain predictions or outputs.
Allocate Tensors: The process of allocating memory for tensors in the model's input and output layers. This is typically done before running the model's inference on input data.
Output Tensor: The tensor(s) produced by the machine learning model after running inference on the input data. The output tensor(s) contain the model's predictions or outputs, such as object bounding boxes, class probabilities, or segmentation masks.
Non-Maximum Suppression (NMS): A post-processing technique used in object detection to eliminate redundant overlapping bounding boxes for the same object. NMS helps to ensure that only the most confident and accurate bounding box is retained for each object.
This is rough AI-generated code that has served our class well as a springboard conversation.