diff --git a/.github/workflows/gen_controls.yml b/.github/workflows/gen_controls.yml new file mode 100644 index 00000000..37818ab0 --- /dev/null +++ b/.github/workflows/gen_controls.yml @@ -0,0 +1,42 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Generate Controls PDF + +on: + push: + +permissions: + contents: read + +jobs: + gen_controls: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r scripts/requirements.txt + - name: Generate Controls PDF + run: | + python scripts/gen_controls.py + - name: Upload Controls PDF 1 + uses: actions/upload-artifact@v2 + with: + name: 1_buttons-pdf + path: scripts/1_buttons.pdf + - name: Upload Controls PDF 2 + uses: actions/upload-artifact@v2 + with: + name: 2_buttons-pdf + path: scripts/2_buttons.pdf \ No newline at end of file diff --git a/.gitignore b/.gitignore index c93554d8..df3a0001 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ simgui-ds.json .glass/glass.json .Glass/glass.json +/sim-logs/* +!/sim-logs/.gitkeep + # This gitignore has been specially created by the WPILib team. # If you remove items from this file, intellisense might break. @@ -184,3 +187,5 @@ logs/ # Folder that has CTRE Phoenix Sim device config storage ctre_sim/ +scripts/1_buttons.pdf +scripts/2_buttons.pdf diff --git a/scripts/gen_controls.py b/scripts/gen_controls.py new file mode 100644 index 00000000..b7b62216 --- /dev/null +++ b/scripts/gen_controls.py @@ -0,0 +1,93 @@ +import os +import re +import json +from reportlab.lib.utils import ImageReader +from reportlab.pdfgen import canvas +from PIL import Image + +file_path = os.path.split(os.path.realpath(__file__))[0] + '/' + + +def extract_comments(text): + pattern = r'/\*RHRXBox\s*\n\s*\*Type:\s*(\d+)\s*\n\s*\*Btn:\s*(\d+)\s*\n\s*\*Desc:\s*(.*?)\*/' + comments = re.findall(pattern, text, re.DOTALL) + return [{'type': int(comment[0]), 'btn': int(comment[1].strip()), 'desc': comment[2].strip()} for comment in + comments] + + +def open_file(): + java_file_path = file_path + '../src/main/java/frc/robot/Robot.java' + with open(java_file_path, 'r') as file: + java_code = file.read() + + comments = extract_comments(java_code) + print(json.dumps(comments, indent=4)) + return comments + + +def get_text_location(btn): + height = 753 + if btn == 1: + return 783, height - 300 + if btn == 2: + return 850, height - 230 + if btn == 3: + return 700, height - 200 + if btn == 4: + return 783, height - 150 + if btn == 5: + return 201, 675 + if btn == 6: + return 700, 675 + if btn == 7: + return 400, height - 240 + if btn == 8: + return 550, height - 240 + if btn == 9: + return 24, height - 30 + if btn == 10: + return 900, height - 30 + if btn == 21: + return 320, height - 350 + if btn == 23: + return 320, height - 450 + if btn == 31: + return 181, height - 250 + if btn == 32: + return 625, height - 400 + return 100, 100 + + +def draw_text(c, btn, text): + textobject = c.beginText(*get_text_location(btn)) + textobject.setFont("Helvetica", 30, leading=None) + textobject.setFillColor((225, 0, 0)) + textobject.textLine(text) + c.drawText(textobject) + + +def to_pdf(btns, type_id): + controller_image = Image.open(file_path + "xbox" + str(type_id) + ".png") + + reportlab_pil_img = ImageReader(controller_image) + + # Create a PDF canvas + c = canvas.Canvas(file_path + str(type_id) + "_buttons.pdf", pagesize=(1110, 753)) + c.drawImage(reportlab_pil_img, 0, 0, width=1110, height=753) + + for btn in btns: + draw_text(c, btn['btn'], btn['desc']) + + c.save() + + +def main(): + cmds = open_file() + cmds_driver = [cmd for cmd in cmds if cmd['type'] == 1] + cmds_operator = [cmd for cmd in cmds if cmd['type'] == 2] + to_pdf(cmds_driver, 1) + to_pdf(cmds_operator, 2) + + +if __name__ == "__main__": + main() diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 00000000..127f9d32 --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1,2 @@ +reportlab~=3.6.8 +Pillow~=10.1.0 \ No newline at end of file diff --git a/scripts/xbox1.png b/scripts/xbox1.png new file mode 100644 index 00000000..6c5081a1 Binary files /dev/null and b/scripts/xbox1.png differ diff --git a/scripts/xbox2.png b/scripts/xbox2.png new file mode 100644 index 00000000..d5f65922 Binary files /dev/null and b/scripts/xbox2.png differ diff --git a/sim-logs/.gitkeep b/sim-logs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/frc/robot/Robot.java b/src/main/java/frc/robot/Robot.java index 8f573cf4..f28f07be 100644 --- a/src/main/java/frc/robot/Robot.java +++ b/src/main/java/frc/robot/Robot.java @@ -70,11 +70,15 @@ import frc.robot.util.RedHawkUtil; import frc.robot.util.RumbleManager; import frc.robot.util.SwerveHeadingController; +import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Optional; +import org.littletonrobotics.junction.LogFileUtil; import org.littletonrobotics.junction.LoggedRobot; import org.littletonrobotics.junction.Logger; import org.littletonrobotics.junction.networktables.LoggedDashboardChooser; import org.littletonrobotics.junction.networktables.NT4Publisher; +import org.littletonrobotics.junction.wpilog.WPILOGReader; import org.littletonrobotics.junction.wpilog.WPILOGWriter; import org.littletonrobotics.urcl.URCL; @@ -116,6 +120,18 @@ public void robotInit() { Logger.recordMetadata("BuildDate", GVersion.BUILD_DATE); if (isReal()) { Logger.addDataReceiver(new WPILOGWriter()); + } else { + Logger.addDataReceiver( + new WPILOGWriter( + "sim-logs/" + + new SimpleDateFormat("yyyyMMdd_HHmmss").format(Calendar.getInstance().getTime()) + + ".wpilog")); + if (System.getenv("AKIT_LOG_PATH") != null) { + String logPath = + LogFileUtil + .findReplayLog(); // Pull the replay log from AdvantageScope (or prompt the user) + Logger.setReplaySource(new WPILOGReader(logPath)); // Read replay log + } } Logger.start(); @@ -186,6 +202,11 @@ public void robotInit() { } public void createDriverBindings() { + /*RHRXBox + *Type:1 + *Btn:5 + *Desc:Intake + */ driver .leftBumper() .whileTrue( @@ -212,6 +233,11 @@ public void createDriverBindings() { Cmds.setState(FeederState.OFF), () -> shooter.hasGamePiece()))); + /*RHRXBox + *Type:1 + *Btn:1 + *Desc:Drive towards GP + */ driver .a() .whileTrue( @@ -244,6 +270,11 @@ public void createDriverBindings() { VehicleState.getInstance().resetClosestGP(); }))); + /*RHRXBox + *Type:1 + *Btn:9 + *Desc:Drive towards GP + */ driver .leftTrigger(0.3) .whileTrue( @@ -304,6 +335,11 @@ public void createDriverBindings() { // new WaitCommand(0.05), // Cmds.setState(ShooterPivot.State.INTAKING))); + /*RHRXBox + *Type:1 + *Btn:6 + *Desc:Fender shot + */ driver .rightBumper() .onTrue( @@ -325,6 +361,11 @@ public void createDriverBindings() { new WaitCommand(0.05), ShooterPivot.Commands.setModeAndWait(ShooterPivot.State.INTAKING))); + /*RHRXBox + *Type:1 + *Btn:10 + *Desc:Dynamic shot + */ driver .rightTrigger(0.3) .whileTrue( @@ -356,6 +397,11 @@ public void createDriverBindings() { () -> shooter.hasGamePiece()), ShooterPivot.Commands.setModeAndWait(ShooterPivot.State.INTAKING))); + /*RHRXBox + *Type:1 + *Btn:8 + *Desc:Reset Gyro + */ driver .start() .onTrue( @@ -363,6 +409,11 @@ public void createDriverBindings() { () -> { swerveDrive.resetGyro(Rotation2d.fromDegrees(180)); })); + /*RHRXBox + *Type:1 + *Btn:7 + *Desc:Reset Gyro + */ driver .back() .onTrue( @@ -404,6 +455,11 @@ public void createDriverBindings() { ? 270 : 90)))); + /*RHRXBox + *Type:1 + *Btn:23 + *Desc:Heading controller + */ driver .povDown() .onTrue( @@ -415,6 +471,11 @@ public void createDriverBindings() { ? 180 : 0)))); + /*RHRXBox + *Type:1 + *Btn:3 + *Desc:OTF Amp + */ driver .x() .whileTrue( @@ -428,6 +489,11 @@ public void createDriverBindings() { new WaitUntilCommand(shooterPivot::isAtTargetAngle)))) .onFalse(new InstantCommand(() -> Robot.swerveDrive.setMotionMode(MotionMode.FULL_DRIVE))); + /*RHRXBox + *Type:1 + *Btn:4 + *Desc:Amp + */ driver .y() .onTrue( @@ -449,6 +515,11 @@ public void createDriverBindings() { () -> shooter.hasGamePiece()), Cmds.setState(ShooterPivot.State.INTAKING))); + /*RHRXBox + *Type:1 + *Btn:2 + *Desc:Force Intake + */ driver .b() .onTrue( @@ -459,6 +530,17 @@ public void createDriverBindings() { Cmds.setState(ShooterState.OFF))) .onFalse( Commands.sequence(Cmds.setState(Intake.State.OFF), Cmds.setState(ShooterState.OFF))); + + /*RHRXBox + *Type:1 + *Btn:31 + *Desc:Translation + */ + /*RHRXBox + *Type:1 + *Btn:32 + *Desc:Rotation + */ } public void createOperatorBindings() { @@ -481,7 +563,11 @@ public void createOperatorBindings() { // new WaitCommand(0.05), // ShooterPivot.Commands.setModeAndWait(ShooterPivot.State.INTAKING))); - // Force Fender Shot + /*RHRXBox + *Type:2 + *Btn:1 + *Desc:Force fender shot + */ operator .a() .onTrue( @@ -503,7 +589,11 @@ public void createOperatorBindings() { new WaitCommand(0.05), ShooterPivot.Commands.setModeAndWait(ShooterPivot.State.INTAKING))); - // Lob shot + /*RHRXBox + *Type:2 + *Btn:2 + *Desc:Lob shot + */ operator .b() .whileTrue( @@ -534,7 +624,11 @@ public void createOperatorBindings() { // }) ); - // Elevator up + /*RHRXBox + *Type:2 + *Btn:21 + *Desc:Elevator up + */ operator .povUp() .onTrue( @@ -542,7 +636,11 @@ public void createOperatorBindings() { Cmds.setState(Elevator.State.CHAIN_APPROACH_HEIGHT), Cmds.setState(ShooterPivot.State.PREP_FOR_CLIMB))); - // Elevator down + /*RHRXBox + *Type:2 + *Btn:23 + *Desc:Elevator down + */ operator .povDown() .onTrue( @@ -561,7 +659,11 @@ public void createOperatorBindings() { new WaitUntilCommand(() -> elevator.atTargetHeight()), Cmds.setState(ShooterPivot.State.ON_CHAIN_ANGLE))); - // Elevator manual control + /*RHRXBox + *Type:2 + *Btn:3 + *Desc:Elevator manual control + */ operator .x() .onTrue(Cmds.setState(Elevator.State.MANUAL_CONTROL)) @@ -587,7 +689,11 @@ public void createOperatorBindings() { // new WaitCommand(0.05), // Cmds.setState(ShooterPivot.State.INTAKING))); - // Amp Shot + /*RHRXBox + *Type:2 + *Btn:6 + *Desc:Amp + */ operator .rightBumper() .onTrue( @@ -609,6 +715,11 @@ public void createOperatorBindings() { () -> shooter.hasGamePiece()), Cmds.setState(ShooterPivot.State.INTAKING))); + /*RHRXBox + *Type:2 + *Btn:5 + *Desc:Shoot into amp + */ operator .leftBumper() .whileTrue( @@ -690,7 +801,11 @@ public void createOperatorBindings() { // Cmds.setState(ShooterState.OFF), // () -> shooter.hasGamePiece()))); - // Elevator shot + /*RHRXBox + *Type:2 + *Btn:4 + *Desc:Elevator shot + */ operator .y() .onTrue( @@ -733,7 +848,11 @@ public void createOperatorBindings() { // Cmds.setState(ShooterState.OFF), // () -> shooter.hasGamePiece()))); - // Note in chassis + /*RHRXBox + *Type:2 + *Btn:8 + *Desc:Note in chassis + */ operator .start() .onTrue(Cmds.setState(Intake.State.NOTE_IN_CHASSIS))