Team Style Guide
This guide documents TEAM1771's architecture patterns and coding conventions, based on our competition-proven code from Reefscape-2025.
Core Philosophy
Command-based architecture with state machines
- Subsystems manage hardware and internal state
- Commands request state changes, don't directly control hardware
- Triggers in RobotContainer coordinate high-level behavior
- State machines provide predictable, debuggable control flow
Subsystem Architecture
Key Principles
- Minimal Periodic Logic: Use
Periodic()only for telemetry updates - State-Based Control: Implement behavior as state machines with
processState() - Command Interface: Expose
setDesiredStateCmd()for state changes - Default Command: Use
runSubsystemCmd()as the default command to drive the state machine - Snapshot Pattern: Provide
getState()for thread-safe state access
Subsystem Template
// ExampleSubsystem.h
#pragma once
#include <frc2/command/SubsystemBase.h>
#include <frc2/command/CommandPtr.h>
#include <functional>
class ExampleSubsystem : public frc2::SubsystemBase {
public:
// State enumeration
enum class STATE {
OFF,
ON,
IDLE
};
// Snapshot struct for thread-safe state access
struct StateSnapshot {
STATE currentState;
double someValue;
};
ExampleSubsystem();
// State access
StateSnapshot getState() const;
// Command factories
frc2::CommandPtr setDesiredStateCmd(STATE state);
frc2::CommandPtr runSubsystemCmd();
// Telemetry registration
void registerTelemetry(std::function<void()> callback);
// Periodic (telemetry only)
void Periodic() override;
private:
STATE m_currentState = STATE::OFF;
STATE m_desiredState = STATE::OFF;
double m_counter = 0.0;
std::vector<std::function<void()>> m_telemetryCallbacks;
// State machine implementation
void processState();
// State handlers
void handleOffState();
void handleOnState();
void handleIdleState();
};
Subsystem Implementation
// ExampleSubsystem.cpp
#include "subsystems/ExampleSubsystem.h"
#include <frc2/command/Commands.h>
#include <frc/smartdashboard/SmartDashboard.h>
ExampleSubsystem::ExampleSubsystem() {
SetName("ExampleSubsystem");
}
ExampleSubsystem::StateSnapshot ExampleSubsystem::getState() const {
return {
.currentState = m_currentState,
.someValue = m_counter
};
}
frc2::CommandPtr ExampleSubsystem::setDesiredStateCmd(STATE state) {
return frc2::cmd::RunOnce([this, state] {
m_desiredState = state;
}, {this});
}
frc2::CommandPtr ExampleSubsystem::runSubsystemCmd() {
return frc2::cmd::Run([this] {
processState();
}, {this});
}
void ExampleSubsystem::registerTelemetry(std::function<void()> callback) {
m_telemetryCallbacks.push_back(callback);
}
void ExampleSubsystem::Periodic() {
// Only call telemetry callbacks
for (auto& callback : m_telemetryCallbacks) {
callback();
}
}
void ExampleSubsystem::processState() {
// Transition logic
if (m_currentState != m_desiredState) {
m_currentState = m_desiredState;
}
// State-based behavior
switch (m_currentState) {
case STATE::OFF:
handleOffState();
break;
case STATE::ON:
handleOnState();
break;
case STATE::IDLE:
handleIdleState();
break;
}
}
void ExampleSubsystem::handleOffState() {
// Reset counters, stop motors, etc.
m_counter = 0.0;
}
void ExampleSubsystem::handleOnState() {
// Perform active behavior
m_counter += 0.02; // Assuming 50Hz loop
}
void ExampleSubsystem::handleIdleState() {
// Maintain position, coast, etc.
}
RobotContainer Patterns
Trigger-Based Bindings
Use triggers to gate behavior based on conditions:
// RobotContainer.cpp
void RobotContainer::ConfigureBindings() {
// Hold-to-run pattern
m_driverController.R2()
.OnTrue(m_exampleSubsystem.setDesiredStateCmd(ExampleSubsystem::STATE::ON))
.OnFalse(m_exampleSubsystem.setDesiredStateCmd(ExampleSubsystem::STATE::OFF));
// Compound trigger
m_driverController.L2().And(frc2::Trigger([this] {
return m_exampleSubsystem.getState().currentState != ExampleSubsystem::STATE::OFF;
}))
.OnTrue(m_exampleSubsystem.setDesiredStateCmd(ExampleSubsystem::STATE::IDLE));
// Emergency stop - sets safe states
m_driverController.Cross()
.OnTrue(m_exampleSubsystem.setDesiredStateCmd(ExampleSubsystem::STATE::OFF));
}
Default Commands
Set default commands that run continuously:
RobotContainer::RobotContainer() {
// Set default command to drive the state machine
m_exampleSubsystem.SetDefaultCommand(m_exampleSubsystem.runSubsystemCmd());
}
Command Patterns
setDesiredStateCmd
Commands that request state changes:
- Use
frc2::cmd::RunOnce() - Set desired state, don't directly control hardware
- Return immediately
frc2::CommandPtr ExampleSubsystem::setDesiredStateCmd(STATE state) {
return frc2::cmd::RunOnce([this, state] {
m_desiredState = state;
}, {this});
}
runSubsystemCmd
The default command that processes the state machine:
- Use
frc2::cmd::Run() - Calls
processState()every loop - Never ends (runs until interrupted)
frc2::CommandPtr ExampleSubsystem::runSubsystemCmd() {
return frc2::cmd::Run([this] {
processState();
}, {this});
}
Telemetry Best Practices
Registration Pattern
Register telemetry callbacks in RobotContainer:
// RobotContainer.cpp
RobotContainer::RobotContainer() {
ConfigureTelemetry();
}
void RobotContainer::ConfigureTelemetry() {
m_exampleSubsystem.registerTelemetry([this] {
auto state = m_exampleSubsystem.getState();
frc::SmartDashboard::PutString("ExampleSubsystem/State",
StateToString(state.currentState));
frc::SmartDashboard::PutNumber("ExampleSubsystem/Counter",
state.someValue);
});
}
Elastic-Compatible Values
Publish values that work well in Elastic dashboard:
- State strings: Enum to string conversions
- Numeric values: Counters, positions, velocities
- Boolean flags: Status indicators
- Hierarchical keys: Use "/" separators (e.g., "Subsystem/Property")
Emergency Stop Pattern
Never call .stop() directly on subsystems or hardware!
Instead, set subsystems to safe states:
// ❌ BAD - Directly calling stop
m_driverController.Cross().OnTrue(frc2::cmd::RunOnce([this] {
m_subsystem1.Stop();
m_subsystem2.Stop();
}));
// ✅ GOOD - Setting safe states via state machine
m_driverController.Cross().OnTrue(
m_subsystem1.setDesiredStateCmd(Subsystem1::STATE::OFF)
.AndThen(m_subsystem2.setDesiredStateCmd(Subsystem2::STATE::EMPTY))
);
This allows:
- State machines to clean up properly
- Telemetry to reflect actual state
- Predictable behavior for restart
Code Style
Naming Conventions
- Classes: PascalCase (e.g.,
ExampleSubsystem) - Methods: camelCase (e.g.,
setDesiredStateCmd) - Private members: m_ prefix (e.g.,
m_currentState) - Constants: UPPER_SNAKE_CASE (e.g.,
MAX_SPEED) - Enums: UPPER_SNAKE_CASE values (e.g.,
STATE::OFF)
File Organization
src/
├── main/
│ ├── cpp/
│ │ ├── Robot.cpp
│ │ ├── RobotContainer.cpp
│ │ └── subsystems/
│ │ └── ExampleSubsystem.cpp
│ └── include/
│ ├── Robot.h
│ ├── RobotContainer.h
│ └── subsystems/
│ └── ExampleSubsystem.h
Header Guards
Use #pragma once instead of traditional include guards.
Testing in Simulation
- Build first: Always build before simulating
- Check telemetry: Verify SmartDashboard values update
- Test states: Exercise all state transitions
- Verify triggers: Confirm button bindings work correctly
- Emergency stop: Test that Cross button sets safe states
Key Takeaways
✅ DO:
- Use state machines for subsystem control
- Expose
setDesiredStateCmd()for state changes - Keep
Periodic()for telemetry only - Use triggers for complex conditions
- Set default commands with
runSubsystemCmd()
❌ DON'T:
- Put control logic in
Periodic() - Call
.stop()directly on hardware - Control motors directly from commands
- Use blocking calls in commands
- Bypass the state machine
Examples
All projects in this bootcamp follow these patterns. Study:
projects/00-setup-check/- Basic state machine example- Future lessons will build on these fundamentals
References
- Reefscape-2025 Repository - Full competition code
- WPILib Command-Based
- C++ State Machines