diff --git a/lib/src/main/java/com/team2813/lib2813/robot/PeriodicRegistry.java b/lib/src/main/java/com/team2813/lib2813/robot/PeriodicRegistry.java
new file mode 100644
index 00000000..1dad2b8b
--- /dev/null
+++ b/lib/src/main/java/com/team2813/lib2813/robot/PeriodicRegistry.java
@@ -0,0 +1,42 @@
+/*
+Copyright 2026 Prospect Robotics SWENext Club
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package com.team2813.lib2813.robot;
+
+import java.util.function.Consumer;
+
+/**
+ * Service for registering callbacks that need to run periodically.
+ *
+ *
>Subsystems can extend {@link com.team2813.lib2813.subsystems.ModularSubsystem} to get access
+ * to an instance of {@link PeriodicRegistry}. This will ensure that time spent in the periodic
+ * methods are associated with the subsystem.
+ */
+public interface PeriodicRegistry {
+
+ /**
+ * Add a callback to run for each iteration of the event loop.
+ *
+ * @param callback The callback to run.
+ */
+ void addPeriodic(Consumer callback);
+
+ /**
+ * Add a callback to run in simulation mode for each iteration of the event loop.
+ *
+ * @param callback The callback to run.
+ */
+ void addSimulationPeriodic(Consumer callback);
+}
diff --git a/lib/src/main/java/com/team2813/lib2813/robot/RobotState.java b/lib/src/main/java/com/team2813/lib2813/robot/RobotState.java
new file mode 100644
index 00000000..72bac1e9
--- /dev/null
+++ b/lib/src/main/java/com/team2813/lib2813/robot/RobotState.java
@@ -0,0 +1,58 @@
+/*
+Copyright 2026 Prospect Robotics SWENext Club
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package com.team2813.lib2813.robot;
+
+/** Provides APIs for getting the current state of the robot. */
+public interface RobotState {
+
+ /**
+ * Determines if the Robot is currently enabled.
+ *
+ * @return {@code True} if the Robot is currently enabled by the Driver Station.
+ */
+ boolean isEnabled();
+
+ /**
+ * Determines if the Robot is currently disabled.
+ *
+ * @return {@code True} if the Robot is currently disabled by the Driver Station.
+ */
+ default boolean isDisabled() {
+ return !isEnabled();
+ }
+
+ /**
+ * Determines if the robot is currently in Autonomous mode as determined by the Driver Station.
+ *
+ * @return {@code True} if the robot is currently operating autonomously.
+ */
+ boolean isAutonomous();
+
+ /**
+ * Determine sif the robot is currently in Test mode as determined by the Driver Station.
+ *
+ * @return {@code True} if the robot is currently operating in Test mode.
+ */
+ boolean isTest();
+
+ /**
+ * Determine if the robot is currently in Operator Control mode as determined by the Driver
+ * Station.
+ *
+ * @return {@code True} if the robot is currently operating in Operator Control mode.
+ */
+ boolean isTeleop();
+}
diff --git a/lib/src/main/java/com/team2813/lib2813/robot/SimplePeriodicRegistry.java b/lib/src/main/java/com/team2813/lib2813/robot/SimplePeriodicRegistry.java
new file mode 100644
index 00000000..7e4f0b7c
--- /dev/null
+++ b/lib/src/main/java/com/team2813/lib2813/robot/SimplePeriodicRegistry.java
@@ -0,0 +1,87 @@
+/*
+Copyright 2026 Prospect Robotics SWENext Club
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package com.team2813.lib2813.robot;
+
+import edu.wpi.first.wpilibj.DriverStation;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * {@link PeriodicRegistry} implementation that gets the state from {@link DriverStation}.
+ *
+ * Most code should not directly use this class. Subsystems can extend {@link
+ * com.team2813.lib2813.subsystems.ModularSubsystem} to get access to an instance of {@link
+ * PeriodicRegistry}.
+ *
+ *
Alternatively, the main robot code can construct this class, as long as it calls {@link
+ * #callPeriodicFunctions()} in {@code robotPeriodic()} and {@link
+ * #callSimulationPeriodicFunctions()} in {@code simulationPeriodic()}.
+ */
+public final class SimplePeriodicRegistry implements PeriodicRegistry {
+ private final List> periodicFunctions = new ArrayList<>();
+ private final List> simulationPeriodicFunctions = new ArrayList<>();
+
+ @Override
+ public void addPeriodic(Consumer callback) {
+ periodicFunctions.add(callback);
+ }
+
+ @Override
+ public void addSimulationPeriodic(Consumer callback) {
+ simulationPeriodicFunctions.add(callback);
+ }
+
+ /** Calls all the callbacks added via {@link #addPeriodic(Consumer)}. */
+ public void callPeriodicFunctions() {
+ RobotState robotState = SimpleRobotState.getInstance();
+ periodicFunctions.forEach(fun -> fun.accept(robotState));
+ }
+
+ /** Calls all the callbacks added via {@link #addSimulationPeriodic(Consumer)}. */
+ public void callSimulationPeriodicFunctions() {
+ RobotState robotState = SimpleRobotState.getInstance();
+ simulationPeriodicFunctions.forEach(fun -> fun.accept(robotState));
+ }
+
+ private static class SimpleRobotState implements RobotState {
+ private static final SimpleRobotState INSTANCE = new SimpleRobotState();
+
+ static RobotState getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return DriverStation.isEnabled();
+ }
+
+ @Override
+ public boolean isAutonomous() {
+ return DriverStation.isAutonomous();
+ }
+
+ @Override
+ public boolean isTest() {
+ return DriverStation.isTest();
+ }
+
+ @Override
+ public boolean isTeleop() {
+ return DriverStation.isTeleop();
+ }
+ }
+}
diff --git a/lib/src/main/java/com/team2813/lib2813/subsystems/ModularSubsystem.java b/lib/src/main/java/com/team2813/lib2813/subsystems/ModularSubsystem.java
new file mode 100644
index 00000000..f012f2c9
--- /dev/null
+++ b/lib/src/main/java/com/team2813/lib2813/subsystems/ModularSubsystem.java
@@ -0,0 +1,71 @@
+/*
+Copyright 2026 Prospect Robotics SWENext Club
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package com.team2813.lib2813.subsystems;
+
+import com.team2813.lib2813.robot.PeriodicRegistry;
+import com.team2813.lib2813.robot.SimplePeriodicRegistry;
+import edu.wpi.first.wpilibj2.command.CommandScheduler;
+import edu.wpi.first.wpilibj2.command.SubsystemBase;
+
+/**
+ * A base for subsystems that has APIs to support modular code.
+ *
+ * Provides access to {@link PeriodicRegistry}, which can be passed to reusable components.
+ */
+public abstract class ModularSubsystem extends SubsystemBase {
+ private final SimplePeriodicRegistry periodicRegistry = new SimplePeriodicRegistry();
+
+ /** Constructor. Telemetry/log name defaults to the classname. */
+ protected ModularSubsystem() {
+ super();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param name Name of the subsystem for telemetry and logging.
+ */
+ protected ModularSubsystem(String name) {
+ super(name);
+ }
+
+ /**
+ * Gets a periodic registry for registering functions to periodically be called by this subsystem.
+ */
+ protected final PeriodicRegistry getPeriodicRegistry() {
+ return periodicRegistry;
+ }
+
+ /**
+ * Called periodically by the {@link CommandScheduler}.
+ *
+ *
This is intentionally marked as final; subclasses should use {@link #getPeriodicRegistry()}.
+ */
+ @Override
+ public final void periodic() {
+ periodicRegistry.callPeriodicFunctions();
+ }
+
+ /**
+ * Called periodically by the {@link CommandScheduler} when in simulation mode.
+ *
+ *
This is intentionally marked as final; subclasses should use {@link #getPeriodicRegistry()}.
+ */
+ @Override
+ public final void simulationPeriodic() {
+ periodicRegistry.callSimulationPeriodicFunctions();
+ }
+}