Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions examples/robot/ExpansionHubSample/defaultautomode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#
# Copyright (c) FIRST and other WPILib contributors.
# Open Source Software; you can modify and/or share it under the terms of
# the WPILib BSD license file in the root directory of this project.
#

from wpilib import PeriodicOpMode, Timer
from wpilib.opmoderobot import autonomous


@autonomous
class DefaultAutoMode(PeriodicOpMode):
def __init__(self, robot):
super().__init__()
self.robot = robot
self.timer = Timer()

def start(self):
self.timer.reset()
self.timer.start()

def periodic(self):
if self.timer.get() < 2.0:
self.robot.motor0.setThrottle(0.5)
self.robot.motor1.setThrottle(0.5)
elif self.timer.get() < 4.0:
self.robot.motor0.setThrottle(0.9)
self.robot.motor1.setThrottle(0.9)
else:
self.robot.motor0.setThrottle(0.0)
self.robot.motor1.setThrottle(0.0)
24 changes: 24 additions & 0 deletions examples/robot/ExpansionHubSample/defaulttelemode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#
# Copyright (c) FIRST and other WPILib contributors.
# Open Source Software; you can modify and/or share it under the terms of
# the WPILib BSD license file in the root directory of this project.
#

from wpilib import Gamepad, PeriodicOpMode
from wpilib.opmoderobot import teleop


@teleop
class DefaultTeleMode(PeriodicOpMode):
Comment thread
virtuald marked this conversation as resolved.
def __init__(self, robot):
super().__init__()
self.robot = robot
self.gamepad = Gamepad(0)

def periodic(self):
self.robot.motor0.setThrottle(-self.gamepad.getLeftY())
self.robot.motor1.setThrottle(-self.gamepad.getRightY())
self.robot.motor2.setThrottle(-self.gamepad.getLeftX())
self.robot.motor3.setThrottle(-self.gamepad.getRightX())
self.robot.servo0.setPosition(self.gamepad.getLeftTriggerAxis())
self.robot.servo1.setPosition(self.gamepad.getRightTriggerAxis())
20 changes: 20 additions & 0 deletions examples/robot/ExpansionHubSample/robot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env python3
#
# Copyright (c) FIRST and other WPILib contributors.
# Open Source Software; you can modify and/or share it under the terms of
# the WPILib BSD license file in the root directory of this project.
#

from wpilib import ExpansionHubMotor, ExpansionHubServo
from wpilib.opmoderobot import OpModeRobot


class Robot(OpModeRobot):
def __init__(self):
super().__init__()
self.motor0 = ExpansionHubMotor(0, 0)
self.motor1 = ExpansionHubMotor(0, 1)
self.motor2 = ExpansionHubMotor(0, 2)
self.motor3 = ExpansionHubMotor(0, 3)
self.servo0 = ExpansionHubServo(0, 0)
self.servo1 = ExpansionHubServo(0, 1)
1 change: 1 addition & 0 deletions examples/robot/examples.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ base = [
"ElevatorTrapezoidProfile",
"Encoder",
"EventLoop",
"ExpansionHubSample",
"FlywheelBangBangController",
"GettingStarted",
"Gyro",
Expand Down
237 changes: 235 additions & 2 deletions subprojects/robotpy-wpilib/tests/test_opmode_robot.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import importlib
import pytest
import threading
from wpilib import simulation as wsim
from wpimath.units import seconds
from wpilib.opmoderobot import OpModeRobot
from wpilib import OpMode, RobotState
from wpilib.opmoderobot import OpModeRobot, autonomous, teleop, utility
from wpilib import OpMode, RobotState, autonomous as top_level_autonomous
from hal._wpiHal import RobotMode
from wpiutil import Color

Expand Down Expand Up @@ -62,6 +63,45 @@ def sim_timing_setup():
RobotState.clearOpModes()


def test_opmode_decorators_attach_metadata():
@autonomous(group="Drive", description="Auto desc")
class AutoMode(OpMode):
pass

metadata = AutoMode.__wpilib_opmode_metadata__
assert metadata.mode == RobotMode.AUTONOMOUS
assert metadata.name == "AutoMode"
assert metadata.group == "Drive"
assert metadata.description == "Auto desc"


def test_opmode_decorator_rejects_multiple_modes():
with pytest.raises(ValueError, match="multiple opmode decorators"):

@teleop
@autonomous
class BadMode(OpMode):
pass


def test_opmode_decorator_preserves_explicit_metadata():
@utility(
name="Arm Test",
group="Mechanisms",
description="tests arm",
textColor=Color.WHITE,
backgroundColor=Color.BLACK,
)
class UtilityMode(OpMode):
pass

metadata = UtilityMode.__wpilib_opmode_metadata__
assert metadata.name == "Arm Test"
assert metadata.textColor == Color.WHITE
assert metadata.backgroundColor == Color.BLACK
assert top_level_autonomous is autonomous


def test_add_op_mode():
class MyMockRobot(MockRobot):
def __init__(self):
Expand Down Expand Up @@ -187,3 +227,196 @@ def test_robot_periodic(periodic_robot_test_fixture):
# Additional time steps should continue calling RobotPeriodic
wsim.stepTiming(kPeriod)
assert robot.periodic_count == 2


def test_opmode_robot_auto_discovers_decorated_modules(tmp_path, monkeypatch):
pkg = tmp_path / "samplebot"
pkg.mkdir()
(pkg / "__init__.py").write_text("")
(pkg / "robot.py").write_text("""\
from wpilib.opmoderobot import OpModeRobot
class Robot(OpModeRobot):
def __init__(self):
super().__init__()
""")
(pkg / "default_auto_mode.py").write_text("""\
from wpilib import PeriodicOpMode
from wpilib.opmoderobot import autonomous
@autonomous
class DefaultAutoMode(PeriodicOpMode):
pass
""")
(pkg / "default_tele_mode.py").write_text("""\
from wpilib import PeriodicOpMode
from wpilib.opmoderobot import teleop
@teleop
class DefaultTeleMode(PeriodicOpMode):
pass
""")

monkeypatch.syspath_prepend(str(tmp_path))
module = importlib.import_module("samplebot.robot")
robot = module.Robot()

options = wsim.DriverStationSim.getOpModeOptions()
assert {opt.name for opt in options} == {"DefaultAutoMode", "DefaultTeleMode"}


def test_opmode_robot_skips_non_candidate_files(tmp_path, monkeypatch):
pkg = tmp_path / "safeimportbot"
pkg.mkdir()
(pkg / "__init__.py").write_text("")
(pkg / "robot.py").write_text("""\
from wpilib.opmoderobot import OpModeRobot
class Robot(OpModeRobot):
def __init__(self):
super().__init__()
""")
(pkg / "default_auto_mode.py").write_text("""\
from wpilib import PeriodicOpMode
from wpilib.opmoderobot import autonomous
@autonomous
class DefaultAutoMode(PeriodicOpMode):
pass
""")
(pkg / "helper.py").write_text("raise RuntimeError('should not import')\n")

monkeypatch.syspath_prepend(str(tmp_path))
module = importlib.import_module("safeimportbot.robot")
module.Robot()

options = wsim.DriverStationSim.getOpModeOptions()
assert {opt.name for opt in options} == {"DefaultAutoMode"}


def test_opmode_robot_fails_on_syntax_error_in_scan_tree(tmp_path, monkeypatch):
pkg = tmp_path / "brokenbot"
pkg.mkdir()
(pkg / "__init__.py").write_text("")
(pkg / "robot.py").write_text("""\
from wpilib.opmoderobot import OpModeRobot
class Robot(OpModeRobot):
def __init__(self):
super().__init__()
""")
(pkg / "broken.py").write_text("def nope(:\n")

monkeypatch.syspath_prepend(str(tmp_path))
module = importlib.import_module("brokenbot.robot")

with pytest.raises(RuntimeError, match="broken.py"):
module.Robot()


def test_opmode_robot_fails_on_candidate_import_error(tmp_path, monkeypatch):
pkg = tmp_path / "importbrokenbot"
pkg.mkdir()
(pkg / "__init__.py").write_text("")
(pkg / "robot.py").write_text("""\
from wpilib.opmoderobot import OpModeRobot
class Robot(OpModeRobot):
def __init__(self):
super().__init__()
""")
(pkg / "bad_auto.py").write_text("""\
from wpilib import PeriodicOpMode
from wpilib.opmoderobot import autonomous
raise RuntimeError('boom')
@autonomous
class BadAuto(PeriodicOpMode):
pass
""")

monkeypatch.syspath_prepend(str(tmp_path))
module = importlib.import_module("importbrokenbot.robot")

with pytest.raises(RuntimeError, match="bad_auto.py"):
module.Robot()


def test_opmode_robot_rejects_duplicate_names_within_mode(tmp_path, monkeypatch):
pkg = tmp_path / "duplicatebot"
pkg.mkdir()
(pkg / "__init__.py").write_text("")
(pkg / "robot.py").write_text("""\
from wpilib.opmoderobot import OpModeRobot
class Robot(OpModeRobot):
def __init__(self):
super().__init__()
""")
(pkg / "drive_modes.py").write_text("""\
from wpilib import PeriodicOpMode
from wpilib.opmoderobot import teleop
@teleop(name='Drive')
class DriveModeA(PeriodicOpMode):
pass
@teleop(name='Drive')
class DriveModeB(PeriodicOpMode):
pass
""")

monkeypatch.syspath_prepend(str(tmp_path))
module = importlib.import_module("duplicatebot.robot")

with pytest.raises(ValueError, match="duplicate"):
module.Robot()


def test_opmode_robot_rejects_decorated_non_opmode_class(tmp_path, monkeypatch):
pkg = tmp_path / "typecheckbot"
pkg.mkdir()
(pkg / "__init__.py").write_text("")
(pkg / "robot.py").write_text("""\
from wpilib.opmoderobot import OpModeRobot
class Robot(OpModeRobot):
def __init__(self):
super().__init__()
""")
(pkg / "not_an_opmode.py").write_text("""\
from wpilib.opmoderobot import autonomous
@autonomous
class NotAnOpMode:
pass
""")

monkeypatch.syspath_prepend(str(tmp_path))
module = importlib.import_module("typecheckbot.robot")

with pytest.raises(TypeError, match="OpMode"):
module.Robot()


def test_expansion_hub_style_project_discovers_split_opmodes(tmp_path, monkeypatch):
pkg = tmp_path / "expansionhubsample"
pkg.mkdir()
(pkg / "__init__.py").write_text("")
(pkg / "robot.py").write_text("""\
from wpilib.opmoderobot import OpModeRobot
class Robot(OpModeRobot):
motor0 = None
def __init__(self):
super().__init__()
""")
(pkg / "defaultautomode.py").write_text("""\
from wpilib import PeriodicOpMode
from wpilib.opmoderobot import autonomous
@autonomous
class DefaultAutoMode(PeriodicOpMode):
def __init__(self, robot):
self.robot = robot
""")
(pkg / "defaulttelemode.py").write_text("""\
from wpilib import PeriodicOpMode
from wpilib.opmoderobot import teleop
@teleop
class DefaultTeleMode(PeriodicOpMode):
def __init__(self, robot):
self.robot = robot
""")

monkeypatch.syspath_prepend(str(tmp_path))
module = importlib.import_module("expansionhubsample.robot")
module.Robot()

options = wsim.DriverStationSim.getOpModeOptions()
assert {opt.name for opt in options} == {"DefaultAutoMode", "DefaultTeleMode"}
4 changes: 2 additions & 2 deletions subprojects/robotpy-wpilib/wpilib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,9 @@

del _init__wpilib

from .opmoderobot import OpModeRobot
from .opmoderobot import OpModeRobot, autonomous, teleop, utility

__all__ += ["OpModeRobot"]
__all__ += ["OpModeRobot", "autonomous", "teleop", "utility"]

from .cameraserver import CameraServer
from .deployinfo import getDeployData
Expand Down
Loading
Loading