[robotpy] Mirror most other subprojects (#8208)

GitOrigin-RevId: ac60fd3cf4a24023184376687da28373d14b781a

This mirrors the robotpy files for the following projects:
- apriltag
- datalog
- hal
- ntcore
- romiVendordep
- wpilibc
- wpimath
- xrpVendordep

This excludes cscore and the halsim wrappers for at this time.

NOTE: This does not hook these projects up to the build system, just simply mirrors the files. The building will take place in a follow up PR to make it easier to review the changes necessary to build.
This commit is contained in:
PJ Reiniger
2025-10-24 01:28:04 -04:00
committed by GitHub
parent 8992dcdc99
commit 44b9cc1398
545 changed files with 27293 additions and 38 deletions

View File

@@ -0,0 +1,31 @@
import logging
import pytest
import ntcore
import wpilib
from wpilib.simulation._simulation import _resetWpilibSimulationData
@pytest.fixture
def cfg_logging(caplog):
caplog.set_level(logging.INFO)
@pytest.fixture(scope="function")
def wpilib_state():
try:
yield None
finally:
_resetWpilibSimulationData()
@pytest.fixture(scope="function")
def nt(cfg_logging, wpilib_state):
instance = ntcore.NetworkTableInstance.getDefault()
instance.startLocal()
try:
yield instance
finally:
instance.stopLocal()
instance._reset()

View File

@@ -0,0 +1,223 @@
import typing as T
import pytest
from ntcore import NetworkTableInstance
from wpilib import Alert, SmartDashboard
from wpilib.simulation import pauseTiming, resumeTiming, stepTiming
AlertType = Alert.AlertType
@pytest.fixture(scope="function")
def group_name(nt, request):
group_name = f"AlertTest_{request.node.name}"
yield group_name
SmartDashboard.updateValues()
assert len(get_active_alerts(nt, group_name, AlertType.kError)) == 0
assert len(get_active_alerts(nt, group_name, AlertType.kWarning)) == 0
assert len(get_active_alerts(nt, group_name, AlertType.kInfo)) == 0
def get_subscriber_for_type(
nt: NetworkTableInstance, group_name: str, alert_type: AlertType
):
subtable_name = {
AlertType.kError: "errors",
AlertType.kWarning: "warnings",
AlertType.kInfo: "infos",
}.get(alert_type, "unknown")
topic = f"/SmartDashboard/{group_name}/{subtable_name}"
return nt.getStringArrayTopic(topic).subscribe([])
def get_active_alerts(
nt: NetworkTableInstance, group_name: str, alert_type: AlertType
) -> T.List[str]:
SmartDashboard.updateValues()
with get_subscriber_for_type(nt, group_name, alert_type) as sub:
return sub.get()
def is_alert_active(
nt: NetworkTableInstance, group_name: str, text: str, alert_type: AlertType
):
active_alerts = get_active_alerts(nt, group_name, alert_type)
return text in active_alerts
def assert_state(
nt: NetworkTableInstance,
group_name: str,
alert_type: AlertType,
expected_state: T.List[str],
):
assert expected_state == get_active_alerts(nt, group_name, alert_type)
def test_set_unset_single(nt, group_name):
with Alert(group_name, "one", AlertType.kError) as one:
assert not is_alert_active(nt, group_name, "one", AlertType.kError)
assert not is_alert_active(nt, group_name, "two", AlertType.kInfo)
one.set(True)
assert is_alert_active(nt, group_name, "one", AlertType.kError)
one.set(True)
assert is_alert_active(nt, group_name, "one", AlertType.kError)
one.set(False)
assert not is_alert_active(nt, group_name, "one", AlertType.kError)
def test_set_unset_multiple(nt, group_name):
with (
Alert(group_name, "one", AlertType.kError) as one,
Alert(group_name, "two", AlertType.kInfo) as two,
):
assert not is_alert_active(nt, group_name, "one", AlertType.kError)
assert not is_alert_active(nt, group_name, "two", AlertType.kInfo)
one.set(True)
assert is_alert_active(nt, group_name, "one", AlertType.kError)
assert not is_alert_active(nt, group_name, "two", AlertType.kInfo)
one.set(True)
two.set(True)
assert is_alert_active(nt, group_name, "one", AlertType.kError)
assert is_alert_active(nt, group_name, "two", AlertType.kInfo)
one.set(False)
assert not is_alert_active(nt, group_name, "one", AlertType.kError)
assert is_alert_active(nt, group_name, "two", AlertType.kInfo)
def test_set_is_idempotent(nt, group_name):
group_name = group_name
with (
Alert(group_name, "A", AlertType.kInfo) as a,
Alert(group_name, "B", AlertType.kInfo) as b,
Alert(group_name, "C", AlertType.kInfo) as c,
):
a.set(True)
b.set(True)
c.set(True)
start_state = get_active_alerts(nt, group_name, AlertType.kInfo)
assert set(start_state) == {"A", "B", "C"}
b.set(True)
assert_state(nt, group_name, AlertType.kInfo, start_state)
a.set(True)
assert_state(nt, group_name, AlertType.kInfo, start_state)
def test_close_unsets_alert(nt, group_name):
group_name = group_name
with Alert(group_name, "alert", AlertType.kWarning) as alert:
alert.set(True)
assert is_alert_active(nt, group_name, "alert", AlertType.kWarning)
assert not is_alert_active(nt, group_name, "alert", AlertType.kWarning)
def test_set_text_while_unset(nt, group_name):
group_name = group_name
with Alert(group_name, "BEFORE", AlertType.kInfo) as alert:
assert alert.getText() == "BEFORE"
alert.set(True)
assert is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
alert.set(False)
assert not is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
alert.setText("AFTER")
assert alert.getText() == "AFTER"
alert.set(True)
assert not is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
assert is_alert_active(nt, group_name, "AFTER", AlertType.kInfo)
def test_set_text_while_set(nt, group_name):
with Alert(group_name, "BEFORE", AlertType.kInfo) as alert:
assert alert.getText() == "BEFORE"
alert.set(True)
assert is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
alert.setText("AFTER")
assert alert.getText() == "AFTER"
assert not is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
assert is_alert_active(nt, group_name, "AFTER", AlertType.kInfo)
def test_set_text_does_not_affect_sort(nt, group_name):
pauseTiming()
try:
with (
Alert(group_name, "A", AlertType.kInfo) as a,
Alert(group_name, "B", AlertType.kInfo) as b,
Alert(group_name, "C", AlertType.kInfo) as c,
):
a.set(True)
stepTiming(1)
b.set(True)
stepTiming(1)
c.set(True)
expected_state = get_active_alerts(nt, group_name, AlertType.kInfo)
expected_state[expected_state.index("B")] = "AFTER"
b.setText("AFTER")
assert_state(nt, group_name, AlertType.kInfo, expected_state)
finally:
resumeTiming()
def test_sort_order(nt, group_name):
pauseTiming()
try:
with (
Alert(group_name, "A", AlertType.kInfo) as a,
Alert(group_name, "B", AlertType.kInfo) as b,
Alert(group_name, "C", AlertType.kInfo) as c,
):
a.set(True)
assert_state(nt, group_name, AlertType.kInfo, ["A"])
stepTiming(1)
b.set(True)
assert_state(nt, group_name, AlertType.kInfo, ["B", "A"])
stepTiming(1)
c.set(True)
assert_state(nt, group_name, AlertType.kInfo, ["C", "B", "A"])
stepTiming(1)
c.set(False)
assert_state(nt, group_name, AlertType.kInfo, ["B", "A"])
stepTiming(1)
c.set(True)
assert_state(nt, group_name, AlertType.kInfo, ["C", "B", "A"])
stepTiming(1)
a.set(False)
assert_state(nt, group_name, AlertType.kInfo, ["C", "B"])
stepTiming(1)
b.set(False)
assert_state(nt, group_name, AlertType.kInfo, ["C"])
stepTiming(1)
b.set(True)
assert_state(nt, group_name, AlertType.kInfo, ["B", "C"])
stepTiming(1)
a.set(True)
assert_state(nt, group_name, AlertType.kInfo, ["A", "B", "C"])
finally:
resumeTiming()

View File

@@ -0,0 +1,13 @@
import pathlib
import wpilib
import pytest
import sys
@pytest.mark.skipif(sys.platform == "darwin", reason="DataLogManager crashes on exit")
def test_get_log(tmp_path: pathlib.Path):
log_dir = tmp_path / "wpilogs"
log_dir.mkdir()
wpilib.DataLogManager.start(str(log_dir))
log = wpilib.DataLogManager.getLog()
assert log is not None

View File

@@ -0,0 +1,144 @@
import math
from wpilib import Joystick
from wpilib.simulation import JoystickSim
def test_getX() -> None:
joy = Joystick(1)
joysim = JoystickSim(joy)
joysim.setX(0.25)
joysim.notifyNewData()
assert math.isclose(joy.getX(), 0.25)
joysim.setX(0)
joysim.notifyNewData()
assert math.isclose(joy.getX(), 0.0)
def test_getY() -> None:
joy = Joystick(1)
joysim = JoystickSim(joy)
joysim.setY(0.25)
joysim.notifyNewData()
assert math.isclose(joy.getY(), 0.25)
joysim.setY(0)
joysim.notifyNewData()
assert math.isclose(joy.getY(), 0.0)
def test_getZ() -> None:
joy = Joystick(1)
joysim = JoystickSim(joy)
joysim.setZ(0.25)
joysim.notifyNewData()
assert math.isclose(joy.getZ(), 0.25)
joysim.setZ(0)
joysim.notifyNewData()
assert math.isclose(joy.getZ(), 0.0)
def test_getTwist() -> None:
joy = Joystick(1)
joysim = JoystickSim(joy)
joysim.setTwist(0.25)
joysim.notifyNewData()
assert math.isclose(joy.getTwist(), 0.25)
joysim.setTwist(0)
joysim.notifyNewData()
assert math.isclose(joy.getTwist(), 0.0)
def test_getThrottle() -> None:
joy = Joystick(1)
joysim = JoystickSim(joy)
joysim.setThrottle(0.25)
joysim.notifyNewData()
assert math.isclose(joy.getThrottle(), 0.25)
joysim.setThrottle(0)
joysim.notifyNewData()
assert math.isclose(joy.getThrottle(), 0.0)
def test_getTrigger() -> None:
joy = Joystick(1)
joysim = JoystickSim(joy)
joysim.setTrigger(True)
joysim.notifyNewData()
assert joy.getTrigger()
joysim.setTrigger(False)
joysim.notifyNewData()
assert not joy.getTrigger()
def test_getTop() -> None:
joy = Joystick(1)
joysim = JoystickSim(joy)
joysim.setTop(True)
joysim.notifyNewData()
assert joy.getTop()
joysim.setTop(False)
joysim.notifyNewData()
assert not joy.getTop()
def test_getMagnitude() -> None:
joy = Joystick(1)
joysim = JoystickSim(joy)
# X Only
joysim.setX(0.5)
joysim.setY(0.0)
joysim.notifyNewData()
assert math.isclose(joy.getMagnitude(), 0.5)
# Y Only
joysim.setX(0.0)
joysim.setY(-0.5)
joysim.notifyNewData()
assert math.isclose(joy.getMagnitude(), 0.5)
# Both
joysim.setX(0.5)
joysim.setY(-0.5)
joysim.notifyNewData()
assert math.isclose(joy.getMagnitude(), 0.70710678118)
def test_getDirection() -> None:
joy = Joystick(1)
joysim = JoystickSim(joy)
# X Only
joysim.setX(0.5)
joysim.setY(0.0)
joysim.notifyNewData()
assert math.isclose(joy.getDirectionDegrees(), 90)
assert math.isclose(joy.getDirectionRadians(), math.radians(90))
# Y Only
joysim.setX(0.0)
joysim.setY(-0.5)
joysim.notifyNewData()
assert math.isclose(joy.getDirectionDegrees(), 0)
assert math.isclose(joy.getDirectionRadians(), math.radians(0))
# Both
joysim.setX(0.5)
joysim.setY(-0.5)
joysim.notifyNewData()
assert math.isclose(joy.getDirectionDegrees(), 45)
assert math.isclose(joy.getDirectionRadians(), math.radians(45))

View File

@@ -0,0 +1,11 @@
from wpilib import Mechanism2d
def test_create_mechanism():
m = Mechanism2d(100, 100)
r1 = m.getRoot("r1", 10, 10)
l1 = r1.appendLigament("l1", 4, 3)
l2 = l1.appendLigament("l2", 4, 3)
assert l2 is not None
# TODO... check that they do something?

View File

@@ -0,0 +1,62 @@
# 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.
import threading
import pytest
from wpilib import Notifier
from wpilib.simulation import pauseTiming, restartTiming, resumeTiming, stepTiming
# These tests fail because of race conditions in simulation
if False:
class AtomicInteger:
def __init__(self) -> None:
self.lock = threading.Lock()
self.val = 0
def get(self) -> int:
with self.lock:
return self.val
def getAndIncrement(self) -> int:
with self.lock:
val = self.val
self.val += 1
return val
@pytest.fixture
def counter():
return AtomicInteger()
@pytest.fixture
def notifier(counter):
pauseTiming()
restartTiming()
n = Notifier(counter.getAndIncrement)
yield n
del n
resumeTiming()
@pytest.mark.xfail(strict=False)
def test_testStartPeriodicAndStop(counter: AtomicInteger, notifier: Notifier):
notifier.startPeriodic(1.0)
stepTiming(10)
notifier.stop()
assert counter.get() == 10
stepTiming(3)
assert counter.get() == 10
@pytest.mark.xfail(strict=False)
def test_testStartSingle(counter, notifier):
notifier.startSingle(1.0)
stepTiming(10)
assert counter.get() == 1

View File

@@ -0,0 +1,55 @@
import pytest
from ntcore import NetworkTableInstance
from wpilib import SendableChooser, SmartDashboard
@pytest.fixture
def chooser() -> SendableChooser:
chooser = SendableChooser()
for i in range(1, 4):
chooser.addOption(str(i), i)
return chooser
@pytest.mark.parametrize("value", [0, 1, 2, 3])
def test_returns_selected(
nt: NetworkTableInstance, chooser: SendableChooser, value: int
):
chooser.setDefaultOption("0", 0)
with nt.getStringTopic(
"/SmartDashboard/ReturnsSelectedChooser/selected"
).publish() as pub:
SmartDashboard.putData("ReturnsSelectedChooser", chooser)
SmartDashboard.updateValues()
print("set", value)
pub.set(str(value))
SmartDashboard.updateValues()
print("get", chooser.getSelected())
assert value == chooser.getSelected()
def test_default_is_returned_on_no_select(chooser: SendableChooser):
chooser.setDefaultOption("4", 4)
assert 4 == chooser.getSelected()
def test_default_constructable_is_returned_on_no_select_and_no_default(
chooser: SendableChooser,
):
assert chooser.getSelected() is None
def test_change_listener(nt: NetworkTableInstance, chooser: SendableChooser):
current_val = [0]
def on_change(val):
current_val[0] = val
chooser.onChange(on_change)
SmartDashboard.putData("ChangeListenerChooser", chooser)
SmartDashboard.updateValues()
SmartDashboard.putString("ChangeListenerChooser/selected", "3")
SmartDashboard.updateValues()
assert 3 == current_val[0]

View File

@@ -0,0 +1,43 @@
import pytest
import re
import weakref
import wpilib
def test_sendable_chooser():
chooser = wpilib.SendableChooser()
assert chooser.getSelected() is None
chooser.setDefaultOption("option", True)
assert chooser.getSelected() is True
def test_smart_dashboard_putdata():
t = wpilib.Talon(4)
ref = weakref.ref(t)
wpilib.SmartDashboard.putData("talon", t)
del t
assert bool(ref) is True
assert wpilib.SmartDashboard.getData("talon") is ref()
def test_motorcontrollergroup():
t1 = wpilib.Talon(7)
t2 = wpilib.Talon(8)
g = wpilib.MotorControllerGroup(t1, t2)
g.set(1)
assert t1.get() == pytest.approx(1)
assert t2.get() == pytest.approx(1)
g.set(-1)
assert t1.get() == pytest.approx(-1)
assert t2.get() == pytest.approx(-1)
def test_motorcontrollergroup_error():
with pytest.raises(
TypeError, match=re.escape("Argument 1 must be a MotorController (got '1')")
):
wpilib.MotorControllerGroup(1)

View File

@@ -0,0 +1,5 @@
import wpilib.drive
def test_wpilib_drive():
pass

View File

@@ -0,0 +1,5 @@
import wpilib.interfaces
def test_wpilib_interfaces():
pass

View File

@@ -0,0 +1,5 @@
import wpilib.simulation
def test_wpilib_simulation():
pass