This is a brief overview of our codebase. Since the codebase is constantly changing, expect that some or many parts of these docs will be out-of-date. The most up-to-date reference on our codebase is our code itself.
To see when any documentation page was last updated, click “Edit on GitHub” in the top right, then see the timestamp on the last commit.
Gameplay is where our strategy is produced. It takes in the current state and assigns all robots a task based on some coordination. Our current system has a play selector that chooses a play based on world state. After that, a play assigns tactics (multi-robot behavior), each tactic assigns a role (complex single-robot behavior) based on their progress, and the role select an atomic skill (kick, move, etc.) to perform. The gameplay loop is set to tick at 60Hz. In general, these layers each use a state machine to transition between behaviors.
Below is the tick process. Each time the timer ends, the callback method is entered:
timer_period = 1 / 60 # seconds
self.timer = self.create_timer(timer_period, self.gameplay_tick)
motion_control.cpp uses a PID controller to correct robot position error
that may come from deviations in hardware.
The generic PID controller implementation is not in software, but it is in the
Correction for error in robot velocity is done by robot firmware (the code that runs on each robot’s microcontroller) in the separate firmware repository using a Kalman Filter. It uses IMU data and motor feedback, in conjunction with previous state, with an understanding of the various noises that could contribute to error to calculate a duty cycle to send to each motor. At a high level, a duty cycle is the ratio between high and low signals. We need this ratio since our motors are controlled entirely electrically. Again, more detail can be found in our firmware repository.
Motion planning is about generating a path from a robot’s current position and orientation (pose) to a goal supplied by gameplay. Starting at the lowest level, we use RRT, or Rapidly exploring Random Trees, for path generation. When the straight line path to a goal fails, we utilize the position path generated by RRT (if there are no obstacles in that straight line why bother with RRT). We have our own RRT implementation in a separate repository which we use as a submodule. This way others can easily use our RRT implementation if they want.
But we can’t send a path to a robot and expect it to figure it out. We need
to give our firmware a single velocity command for that instant of planning.
finds that velocity trajectory which we can then return as the current
trajectory as the robot taking constraints such as max acceleration into
At the highest level of planning, we have several different planners which the
planning_node selects based on the skill that gameplay requested. They
have different behaviors and based on the their names which give you insight
on their purpose. They are all located
Although this is not an extensive explanation of everything planning does, it
is a good start, and looking
planning folder will give you more information about this topic.
main.cpp & Processor
main.cpp is located in
soccer/src/soccer/. It is the entry point of soccer
and starts the processor and the ui. It also takes in configuration values
that may have been provided by the launch file (such as initial team color)
and shares them with the rest of the program.
Processor is defined in
starts multiple temp nodes. It then updates the context in an infinite
Context is used to update the ui. It is good that most other nodes
are not dependent on this loop.
/radio directory. We have two different radio implementations
that inherit from a base class: a sim radio for virtual
matches or testing locally, and a network radio for when we run on real
Sim radio sends commands to our robots using the
league designated ports for yellow and blue. We can take advantage of this
to simulate matches against ourselves (see the sim2play launch file for
example) or play other teams virtually without hardware. The simulation packets
are standardized by the league and are located in the
(the ones with the ssl prefix).
The network radio expects each robot to know the ip address of where to send to (the field computer or comp laptop). When soccer eventually receives a robot status message from a robot, it adds the robot id to an ip map. Now our software knows how to reach that robot id! If that robot doesn’t send any robot status in a while, network radio will disconnect that robot and that robot id will disappear from the UI (when running with real robots).
The referee node captures the match state and distributes it to the rest of the system. We have two referee nodes: external referee, which takes commands from the SSL Game Controller, and internal referee, which takes commands from our internal UI, so we don’t always have to launch the game controller. The specific ports and message formats for referee messages are designated by the league.
We use Qt for UI development. Code for it is located in
qt directory contains the view as .ui files and images that are
placed on the view.
You could directly edit these .ui files based on their
proper syntax to change our view, but I wouldn’t recommend it.
Instead install Qt designer and open .ui files through it to save yourself a
lot of time.
After the view, all the C++ files act as the view-model for each view. Some of them are quite messy and do not follow our naming conventions; however, if you follow the common method patterns, you should be able to add new functionality to any part of our ui.
There are two parts to vision: a receiver and filter. First, receiver gets
frames from the simulator or camera. The ports and protocol used for vision data
is standardized for all teams in the Small Size League. It updates our
geometry data if the received packet has geometry information (see
rj_protos/protos/ssl_vision_geometry.proto for what that packet contains),
then sends the raw image to the filter.
Vision Filter uses a kalman filter to
estimate the current world state (information like ball and robot position) and
publishes that as a built world
state message for the rest of codebase to use.