mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[copybara] Sync with robotpy (#8964)
GitOrigin-RevId: 9dff8f977401e78be0bb6f39cea2328320ab2d95
This commit is contained in:
@@ -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!
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user