[copybara] Sync with robotpy (#8964)

GitOrigin-RevId: 9dff8f977401e78be0bb6f39cea2328320ab2d95
This commit is contained in:
PJ Reiniger
2026-06-08 22:22:48 -04:00
committed by GitHub
parent 0213ecf382
commit 111130d8bb
20 changed files with 1026 additions and 265 deletions

View File

@@ -1,174 +0,0 @@
Guidelines for porting WPILib examples to Python
================================================
To ensure that our examples are helpful and accurate for those learning how to
use RobotPy, we have a set of guidelines for adding new examples to the project.
These guidelines are not strictly enforced, but we do ask that you follow them
when submitting pull requests with new examples. This will make the review
process easier for everyone involved.
In general, our examples are based on the Java examples from allwpilib, as Java
is often easier to translate to Python. However, not all of our existing
examples adhere to all of these guidelines. If you see an opportunity to improve
an existing example, feel free to make the necessary changes.
Shorter thoughts
----------------
Testing:
* New examples must run! You *must* test your code on either a robot or in
simulation. If there's something broken in RobotPy, file an issue to get it
fixed
* You can find instructions on how to test a vision file [here](https://robotpy.readthedocs.io/en/stable/vision/other.html#vision-other-runcustom)!
* Format your code with black
General:
* We always try to stay as close to the original examples as possible
* `Main.java` is never needed
* Don't ever check in files for your IDE (.vscode, .idea, etc)
* Copy over the copyright statement from the original file
Naming:
* Filenames should always be all lowercase
* Function names are camelCase
* Class names start with a capital letter
* Class method names are camelCase
* Class member variables such as `m_name` should be `self.name` in Python
* Protected/private methods/members can optionally be prefixed with `_`
Misc conversion thoughts
* Comparisons to null such as `foo == null` become `foo is None`
* Single-line lambdas can be converted to python lambda statements. Anything
longer needs to be a separate function somewhere
* Never modify `sys.path` directly!
Longer thoughts
---------------
Never initialize anything other than constants at class/global scope. Here are
a few examples:
```python
# OK: just a constant
MY_CONSTANT = 42
# BAD: at global scope
motor = wpilib.Talon(1)
class MyRobot:
# BAD: at class scope
motor = wpilib.Talon(1)
def __init__(self):
# OK: variable assigned to `self`
self.motor = wpilib.Talon(1)
```
---
Import order doesn't really matter, but we prefer the following convention:
```python
# Import things from the python standard library first
import os
import typing
# Import things from robotpy in a second group
import wpilib
import commands2
# Import things from the other files in the example last
import constants
import subsystems.drivetrain
```
---
The `pass` statement is only required for empty functions:
```python
# OK
def empty_function():
pass
def has_docstring():
"""Some docstring"""
pass # NOT NEEDED
class C:
def __init__(self):
super().__init__()
pass # NOT NEEDED
```
Include all the comments
------------------------
**IMPORTANT**: Include all the comments from the existing examples. These
comments provide helpful explanations and context for the code.
Converting Java comments to Python docstrings can be tedious and error prone. We
have a tool at https://github.com/robotpy/devtools/blob/main/sphinxify_server.py
that launches an HTML page that you can just paste doxygen or javadoc comments
into and it will convert it to a mostly usable docstring.
```python
# 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.
"""
Some docstring describing what this file does
"""
class SomeClass:
"""
This describes what this class does
"""
def __init__(self):
"""
This describes what the constructor does
"""
def myFunction(self, a: int) -> int:
"""
This function is great.
:param a: Input parameter a
"""
```
Command-based robot specific things
-----------------------------------
We use `commands2.TimedCommandRobot` instead of TimedRobot. It provides a
`robotPeriodic` method for you, so it doesn't need to be included from
the java code unless robotPeriodic function does something other than
run the command scheduler.
Java examples will often have a `Constants.java` file with a bunch of constants
in it. RobotPy examples will put those constants in a `constants.py` as globals.
To group constants sometimes it makes sense to put each group in its own class,
but a single `Constants` class should be avoided.
Final thoughts
--------------
Before translating WPILib Java code to RobotPy's WPILib, first take some time
and read through the existing RobotPy code to get a feel for the style of the
code. Try to keep it Pythonic and yet true to the original spirit of the code.
Style *does* matter, as students will be reading through this code and it will
potentially influence their decisions in the future.
Remember, all contributions are welcome, no matter how big or small!

View File

@@ -1,79 +0,0 @@
#!/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 pathlib import Path
def check_file_content(file_path):
with open(file_path, "r") as file:
lines = file.readlines()
if file.name.endswith("robot.py"):
expected_lines = [
"#!/usr/bin/env python3\n",
"#\n",
"# Copyright (c) FIRST and other WPILib contributors.\n",
"# Open Source Software; you can modify and/or share it under the terms of\n",
"# the WPILib BSD license file in the root directory of this project.\n",
"#\n",
"\n",
]
else:
expected_lines = [
"#\n",
"# Copyright (c) FIRST and other WPILib contributors.\n",
"# Open Source Software; you can modify and/or share it under the terms of\n",
"# the WPILib BSD license file in the root directory of this project.\n",
"#\n",
"\n",
]
if lines[: len(expected_lines)] != expected_lines:
print(
"\n".join(
[
f"{file_path}",
"ERROR: File must start with the following lines",
"------------------------------",
"".join(expected_lines[:-1]),
"------------------------------",
"Found:",
"".join(lines[: len(expected_lines)]),
"------------------------------",
]
)
)
return False
return True
def main():
current_directory = Path(__file__).parent
python_files = [
x
for x in current_directory.glob("./**/*.py")
if not x.parent == current_directory and x.stat().st_size != 0
]
non_compliant_files = [
file for file in python_files if not check_file_content(file)
]
non_compliant_files.sort()
if non_compliant_files:
print("Non-compliant files:")
for file in non_compliant_files:
print(f"- {file}")
exit(1) # Exit with an error code
else:
print("All files are compliant.")
if __name__ == "__main__":
main()

View File

@@ -18,7 +18,7 @@ class MyRobot(wpilib.TimedRobot):
self.leftDrive = wpilib.PWMSparkMax(0)
self.rightDrive = wpilib.PWMSparkMax(1)
self.robotDrive = wpilib.DifferentialDrive(self.leftDrive, self.rightDrive)
self.controller = wpilib.NiDsXboxController(0)
self.controller = wpilib.Gamepad(0)
self.timer = wpilib.Timer()
# We need to invert one side of the drivetrain so that positive voltages

View File

@@ -18,7 +18,7 @@ class MyRobot(wpilib.TimedRobot):
# Reuse buffer
# Default to a length of 60
self.ledData = [wpilib.AddressableLED.LEDData() for _ in range(60)]
self.ledData = wpilib.AddressableLEDBuffer(60)
self.led.setLength(len(self.ledData))
# Set the data