Skip to main content

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​

  1. Minimal Periodic Logic: Use Periodic() only for telemetry updates
  2. State-Based Control: Implement behavior as state machines with processState()
  3. Command Interface: Expose setDesiredStateCmd() for state changes
  4. Default Command: Use runSubsystemCmd() as the default command to drive the state machine
  5. 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​

  1. Build first: Always build before simulating
  2. Check telemetry: Verify SmartDashboard values update
  3. Test states: Exercise all state transitions
  4. Verify triggers: Confirm button bindings work correctly
  5. 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​