diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..5944445 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(grep:*)", + "WebSearch", + "WebFetch(domain:github.com)" + ] + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..e03fc8f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,37 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/debian +{ + "name": "python:3:10", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "ghcr.io/devcontainers-extra/features/mkdocs:2": {}, + "ghcr.io/devcontainers/features/python:1":{} + }, + "postCreateCommand": [ + "pip3 install --user -r requirements.txt", + "pip3 install --user first-agentic-csa" + "curl -fsSL https://claude.ai/install.sh | bash" + ], + "customizations": { + "vscode": { + "extensions": [ + "main-branch.mkdocs-snippet-lens", + "aikebang.mkdocs-syntax-highlight", + "ytianle.mkdocs-material-linter" + ] + } + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Configure tool-specific properties. + + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..c1cd360 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,107 @@ +# GitHub Copilot / Cursor Instructions for FRC Development + +You are assisting with a FIRST Robotics Competition (FRC) project using WPILib. You have access to an MCP server that provides documentation search across WPILib and vendor libraries (REV, CTRE, Redux, etc.). + +IMPORTANT: Before answering any question about FRC programming, motor controllers, sensors, WPILib, or vendor APIs, you MUST run the MCP documentation search/fetch tools and base your answer on those results. + +## Mandatory Documentation-First Policy + +This repository requires that all FRC-related questions be investigated with the MCP documentation tools before producing a final answer. Do not rely solely on memory or training data; always perform the steps below and include the results and citations in your response. + +Required workflow for FRC questions: + +- **Step 1 — Search (MCP required):** Call `mcp_wpilib_search_frc_docs(query=..., vendors=[...])` (use `vendors=["all"]` if unsure). Capture the top relevant results. +- **Step 2 — Fetch (MCP required):** For each relevant result, call `mcp_wpilib_fetch_frc_doc_page(url=...)` and review the page content for exact API usage, examples, and notes. +- **Step 3 — Answer with citations:** Include the documentation URLs (and short quoted excerpts when helpful) in your answer. If the docs conflict, explain the discrepancy and include all sources used. + +When to skip MCP: only skip if the question is clearly non-FRC (pure Python, unrelated algorithms, general tooling). When in doubt, run the MCP search. + +Examples of required tool usage: +``` +mcp_wpilib_search_frc_docs(query="SparkMax NEO brushless setup", vendors=["rev"]) +mcp_wpilib_fetch_frc_doc_page(url="https://docs.revrobotics.com/...") +``` + +If you cannot call the MCP tools (tool outage or missing permissions), explicitly state that you could not run the required searches and ask whether to proceed with a best-effort answer. Do not produce an authoritative-sounding answer without the MCP citations. + +## Tool Usage Patterns + +**For general questions (MCP):** +``` +mcp_wpilib_search_frc_docs(query="how to configure PID", vendors=["all"]) +``` + +**For vendor-specific questions (MCP):** +``` +mcp_wpilib_search_frc_docs(query="SparkMax current limits", vendors=["rev"]) +mcp_wpilib_search_frc_docs(query="TalonFX motion magic", vendors=["ctre"]) +``` + +**For comparisons (MCP):** +``` +mcp_wpilib_search_frc_docs(query="brushless motor setup", vendors=["rev"], max_results=5) +mcp_wpilib_search_frc_docs(query="brushless motor setup", vendors=["ctre"], max_results=5) +``` + +**After finding relevant pages (MCP):** +``` +mcp_wpilib_fetch_frc_doc_page(url="https://docs.revrobotics.com/...") +``` +**For Markdown or readthedocs:** +https://docs.readthedocs.com/platform/stable/index.html +https://squidfunk.github.io/mkdocs-material/reference/code-blocks/ +https://squidfunk.github.io/mkdocs-material/reference/ + +**For YAGSL:** +https://docs.yagsl.com/ +https://yet-another-software-suite.github.io/YAGSL/javadocs/ +https://github.com/Yet-Another-Software-Suite/YAGSL/tree/main/examples + +## Language and Version Awareness + +- Ask the student what language they're using (Java, Python, or C++) if not obvious from context +- Default to the current season (2025) unless specified otherwise +- Use the `language` and `version` parameters to filter results: +``` +search_frc_docs(query="command based", language="Python", version="2025") +``` + +## Code Style for FRC + +When writing code for FRC projects: + +- Follow WPILib conventions (Command-based or TimedRobot patterns) +- Use vendor-specific APIs correctly (REVLib for SparkMax, Phoenix 6 for TalonFX) +- Include necessary imports +- Add comments explaining the "why" for students learning +- Handle units properly (RPM vs rotations, degrees vs radians) + +## Common Vendor Mappings + +| Hardware | Vendor | Search with | +|----------|--------|-------------| +| SparkMax, SparkFlex, NEO, NEO 550 | REV | `vendors=["rev"]` | +| TalonFX, Falcon 500, Kraken, CANcoder, Pigeon | CTRE | `vendors=["ctre"]` | +| Canandcoder, Canandmag | Redux | `vendors=["redux"]` | +| NavX | WPILib/Studica | `vendors=["wpilib"]` | +| Limelight | WPILib | `vendors=["wpilib"]` | +| PhotonVision | PhotonVision | `vendors=["photonvision"]` | + +## When Docs Don't Have the Answer + +If search results are empty or unhelpful: +1. Try broader search terms +2. Try `vendors=["all"]` to cast a wider net +3. Check `list_frc_doc_sections` to see what's available +4. Be honest with the student that you couldn't find documentation, and offer your best guess with appropriate caveats + +## Example Interaction + +**Student:** "How do I set up a SparkMax for a NEO brushless motor?" + +**You should:** +1. `mcp_wpilib_search_frc_docs(query="SparkMax NEO brushless setup", vendors=["rev"])` +2. Review results, pick most relevant URL +3. `mcp_wpilib_fetch_frc_doc_page(url="...")` to get full content +4. Write code based on current documentation +5. Tell the student: "Based on the REV documentation at [url], here's how to set it up..." \ No newline at end of file diff --git a/.github/workflows/Deploy_site.yml b/.github/workflows/Deploy_site.yml new file mode 100644 index 0000000..b052860 --- /dev/null +++ b/.github/workflows/Deploy_site.yml @@ -0,0 +1,32 @@ +name: Deploy Site +permissions: + contents: write +on: + # pull_request: + # branches: + # - robolancer_updates + push: + branches: + - robolancer_updates +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: pip install -r requirements.txt + - run: mkdocs build + + deploy: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.x + - run: pip install mkdocs + - run: pip install -r requirements.txt + - run: mkdocs gh-deploy --force \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c3e0def..3db0415 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,8 @@ name: Build MkDocs on: - pull_request: + push: branches: - - main + - robolancer_updates jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d244c53..1abbbf6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ name: Publish docs via GitHub Pages on: push: branches: - - main + - robolancer_updates jobs: deploy: runs-on: ubuntu-latest diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 0000000..b40b688 --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,13 @@ +{ + "servers": { + "frc-docs": { + "type": "stdio", + "command": "uvx", + "args": [ + "first_agentic_csa==0.3.12" + ], + "env": {} + } + }, + "inputs": [] +} \ No newline at end of file diff --git a/docs/assets/images/Electronics/Battery.jpg b/docs/assets/images/Electronics/Battery.jpg new file mode 100644 index 0000000..4e3d98d Binary files /dev/null and b/docs/assets/images/Electronics/Battery.jpg differ diff --git a/docs/assets/images/Electronics/Breaker.jpg b/docs/assets/images/Electronics/Breaker.jpg new file mode 100644 index 0000000..bb07ace Binary files /dev/null and b/docs/assets/images/Electronics/Breaker.jpg differ diff --git a/docs/assets/images/Electronics/CANWire.png b/docs/assets/images/Electronics/CANWire.png new file mode 100644 index 0000000..061ae9b Binary files /dev/null and b/docs/assets/images/Electronics/CANWire.png differ diff --git a/docs/assets/images/Electronics/CANcoder.jpg b/docs/assets/images/Electronics/CANcoder.jpg new file mode 100644 index 0000000..ad8f6d6 Binary files /dev/null and b/docs/assets/images/Electronics/CANcoder.jpg differ diff --git a/docs/assets/images/Electronics/CANivore.webp b/docs/assets/images/Electronics/CANivore.webp new file mode 100644 index 0000000..65c2c54 Binary files /dev/null and b/docs/assets/images/Electronics/CANivore.webp differ diff --git a/docs/assets/images/Electronics/Falcon.jpg b/docs/assets/images/Electronics/Falcon.jpg new file mode 100644 index 0000000..f9bb70f Binary files /dev/null and b/docs/assets/images/Electronics/Falcon.jpg differ diff --git a/docs/assets/images/Electronics/Kraken.png b/docs/assets/images/Electronics/Kraken.png new file mode 100644 index 0000000..b1ac958 Binary files /dev/null and b/docs/assets/images/Electronics/Kraken.png differ diff --git a/docs/assets/images/Electronics/LimeLight.png b/docs/assets/images/Electronics/LimeLight.png new file mode 100644 index 0000000..3345299 Binary files /dev/null and b/docs/assets/images/Electronics/LimeLight.png differ diff --git a/docs/assets/images/Electronics/LimitSwitch.jpg b/docs/assets/images/Electronics/LimitSwitch.jpg new file mode 100644 index 0000000..1931fdb Binary files /dev/null and b/docs/assets/images/Electronics/LimitSwitch.jpg differ diff --git a/docs/assets/images/Electronics/PDH.webp b/docs/assets/images/Electronics/PDH.webp new file mode 100644 index 0000000..4511e87 Binary files /dev/null and b/docs/assets/images/Electronics/PDH.webp differ diff --git a/docs/assets/images/Electronics/Pigeon2.webp b/docs/assets/images/Electronics/Pigeon2.webp new file mode 100644 index 0000000..8291995 Binary files /dev/null and b/docs/assets/images/Electronics/Pigeon2.webp differ diff --git a/docs/assets/images/Electronics/Pose.PNG b/docs/assets/images/Electronics/Pose.PNG new file mode 100644 index 0000000..8399410 Binary files /dev/null and b/docs/assets/images/Electronics/Pose.PNG differ diff --git a/docs/assets/images/Electronics/RSL.jpg b/docs/assets/images/Electronics/RSL.jpg new file mode 100644 index 0000000..4ebc5e1 Binary files /dev/null and b/docs/assets/images/Electronics/RSL.jpg differ diff --git a/docs/assets/images/Electronics/Radio.png b/docs/assets/images/Electronics/Radio.png new file mode 100644 index 0000000..07f69e8 Binary files /dev/null and b/docs/assets/images/Electronics/Radio.png differ diff --git a/docs/assets/images/Electronics/RevElectricalSystemDiagram.png b/docs/assets/images/Electronics/RevElectricalSystemDiagram.png new file mode 100644 index 0000000..068554b Binary files /dev/null and b/docs/assets/images/Electronics/RevElectricalSystemDiagram.png differ diff --git a/docs/assets/images/Electronics/RevThroughBore.webp b/docs/assets/images/Electronics/RevThroughBore.webp new file mode 100644 index 0000000..863330a Binary files /dev/null and b/docs/assets/images/Electronics/RevThroughBore.webp differ diff --git a/docs/assets/images/Electronics/RoboRIO.jpg b/docs/assets/images/Electronics/RoboRIO.jpg new file mode 100644 index 0000000..e03fabe Binary files /dev/null and b/docs/assets/images/Electronics/RoboRIO.jpg differ diff --git a/docs/assets/images/Electronics/Solenoid.jpg b/docs/assets/images/Electronics/Solenoid.jpg new file mode 100644 index 0000000..cdd9104 Binary files /dev/null and b/docs/assets/images/Electronics/Solenoid.jpg differ diff --git a/docs/assets/images/Electronics/SparkMax.jpg b/docs/assets/images/Electronics/SparkMax.jpg new file mode 100644 index 0000000..0636991 Binary files /dev/null and b/docs/assets/images/Electronics/SparkMax.jpg differ diff --git a/docs/assets/images/Electronics/VortexFlex.png b/docs/assets/images/Electronics/VortexFlex.png new file mode 100644 index 0000000..fa2eee8 Binary files /dev/null and b/docs/assets/images/Electronics/VortexFlex.png differ diff --git a/docs/assets/images/Electronics/servo.webp b/docs/assets/images/Electronics/servo.webp new file mode 100644 index 0000000..f71de31 Binary files /dev/null and b/docs/assets/images/Electronics/servo.webp differ diff --git a/docs/assets/images/Electronics/x44.webp b/docs/assets/images/Electronics/x44.webp new file mode 100644 index 0000000..39ce5d1 Binary files /dev/null and b/docs/assets/images/Electronics/x44.webp differ diff --git a/docs/assets/images/driving_robot/Quick_fix_import.png b/docs/assets/images/driving_robot/Quick_fix_import.png new file mode 100644 index 0000000..c0296d6 Binary files /dev/null and b/docs/assets/images/driving_robot/Quick_fix_import.png differ diff --git a/docs/assets/images/driving_robot/fix_error_1.PNG b/docs/assets/images/driving_robot/fix_error_1.PNG new file mode 100644 index 0000000..045a7f9 Binary files /dev/null and b/docs/assets/images/driving_robot/fix_error_1.PNG differ diff --git a/docs/assets/images/driving_robot/quick_fix_click.PNG b/docs/assets/images/driving_robot/quick_fix_click.PNG new file mode 100644 index 0000000..9fdc1b5 Binary files /dev/null and b/docs/assets/images/driving_robot/quick_fix_click.PNG differ diff --git a/docs/assets/images/git/GitDemoVideo.mp4 b/docs/assets/images/git/GitDemoVideo.mp4 new file mode 100644 index 0000000..5aca904 Binary files /dev/null and b/docs/assets/images/git/GitDemoVideo.mp4 differ diff --git a/docs/assets/images/git/GitExample.png b/docs/assets/images/git/GitExample.png new file mode 100644 index 0000000..aad8e2a Binary files /dev/null and b/docs/assets/images/git/GitExample.png differ diff --git a/docs/assets/images/git/PRDemoVideo.mkv b/docs/assets/images/git/PRDemoVideo.mkv new file mode 100644 index 0000000..a31a49d Binary files /dev/null and b/docs/assets/images/git/PRDemoVideo.mkv differ diff --git a/docs/assets/images/git/github_GUI_Merge.png b/docs/assets/images/git/github_GUI_Merge.png new file mode 100644 index 0000000..d01668b Binary files /dev/null and b/docs/assets/images/git/github_GUI_Merge.png differ diff --git a/docs/assets/images/git/github_branch_vscode.png b/docs/assets/images/git/github_branch_vscode.png new file mode 100644 index 0000000..50490d9 Binary files /dev/null and b/docs/assets/images/git/github_branch_vscode.png differ diff --git a/docs/assets/images/git/github_pull_requests.png b/docs/assets/images/git/github_pull_requests.png new file mode 100644 index 0000000..d980ad5 Binary files /dev/null and b/docs/assets/images/git/github_pull_requests.png differ diff --git a/docs/assets/images/new_project/Create.png b/docs/assets/images/new_project/Create.png new file mode 100644 index 0000000..afce274 Binary files /dev/null and b/docs/assets/images/new_project/Create.png differ diff --git a/docs/assets/images/vscode_tips/git_graph_vscode.png b/docs/assets/images/vscode_tips/git_graph_vscode.png new file mode 100644 index 0000000..f86c5a7 Binary files /dev/null and b/docs/assets/images/vscode_tips/git_graph_vscode.png differ diff --git a/docs/assets/images/vscode_tips/git_vscode.png b/docs/assets/images/vscode_tips/git_vscode.png new file mode 100644 index 0000000..4d52f6f Binary files /dev/null and b/docs/assets/images/vscode_tips/git_vscode.png differ diff --git a/docs/assets/images/vscode_tips/github_GUI_Merge.png b/docs/assets/images/vscode_tips/github_GUI_Merge.png new file mode 100644 index 0000000..d01668b Binary files /dev/null and b/docs/assets/images/vscode_tips/github_GUI_Merge.png differ diff --git a/docs/assets/images/vscode_tips/github_branch_vscode.png b/docs/assets/images/vscode_tips/github_branch_vscode.png new file mode 100644 index 0000000..50490d9 Binary files /dev/null and b/docs/assets/images/vscode_tips/github_branch_vscode.png differ diff --git a/docs/assets/images/vscode_tips/github_pull_requests.png b/docs/assets/images/vscode_tips/github_pull_requests.png new file mode 100644 index 0000000..d980ad5 Binary files /dev/null and b/docs/assets/images/vscode_tips/github_pull_requests.png differ diff --git a/docs/basics/ElectronicsCrashCourse.md b/docs/basics/ElectronicsCrashCourse.md new file mode 100644 index 0000000..872e699 --- /dev/null +++ b/docs/basics/ElectronicsCrashCourse.md @@ -0,0 +1,316 @@ + +# Electronics Crash Course + +## There are a lot of electrical components on an FRC robot, many of which we will need to interface with in software + +### Electrical system diagram + +Rev electrical system diagram + +This diagram shows many of the electrical components found on a typical FRC robot. +You don't need to memorize this layout, but its handy to have this as a reference. + +### Battery + +Battery + +All of the power our robot uses comes from a single 12-volt car battery. +These can be found on our battery cart in the corner of the lab. +Not all batteries are created equal: Some brands and specific batteries can hold more juice for longer than others. +One way we can see the performance of a battery is the resting voltage (or voltage when no motors or similarly power hungry devices are running) of the battery after it has charged. +This can be checked with the battery beak or on the driver station. +A good battery will have a resting voltage of over 13 volts, and any battery we use will be above 12. +As the battery is used the voltage will go down. +We tend to swap our batteries after they get to around 12.5 volts, although if we are testing something sensitive to voltage (like auto) we might swap them sooner. +After a match a good battery is usually still above 12 volts. +To track batteries so we know which batteries are "good" and which are "bad" we give them names like "Garbanzo" or "W.I.S. (Women In STEM)". + +When the robot is on, the voltage will drop as power is used by processors and lights. +When the robot is enabled, the voltage will drop further as power is actively used by mechanisms. +This is called voltage sag. +This means that we can't pull full power from every motor on the robot at once and have to be careful to design and program around our power budget. +When the voltage of the robot drops too low we can brownout. +This is usually first visible when the LEDs on the robot go out. +When the voltage is low enough, motors will begin to be turned off automatically, which is visible as a stuttering or jerking. +If the voltage is even lower, the robot can restart. +Needless to say, we want to avoid any brownout conditions in a match or when testing, but we often end up seeing them when pushing the robot in testing. + +$V = IR$ is the equation which governs voltage sag, where $V$ is amount the voltage will sag from resting, $I$ is the current draw of the robot, and $R$ is the internal resistance of the battery. + +### Main Breaker + +Main Breaker + +The main breaker is the power switch of the robot. +The red button will turn the robot off, and a small black lever on the side of the breaker will turn it on. +The main breaker is directly connected to the battery and limits the current draw of the robot, or the amount of power it can use at once. +This limit is theoretically 120a, but in practice we can draw much more than that for small but significant periods of time (several seconds). +Whenever you need to be hands on with the robot, make sure the breaker is off. +If you can't connect to the robot, make sure it's on. +If the breaker is on, make sure to wear safety glasses. + +The breaker should be mounted in a visible and accessible location on all robots, although it tends to blend in with its black casing. +We tend to have a 3d-printed guard over the off switch to prevent accidental presses by other robots mid-match. + +### Power Distribution Hub (PDH) + +PDH + +The PDH takes the power from the battery and distributes it to the motors, sensors, and other components on the robot (Almost like its a hub for distributing power!). +We don't have to do anything with the PDH in code, but if a motor or sensor does not have power it could have a bad connection with the PDH. +The PDH will also have a smaller breaker for each motor on it, which limits the power draw from each individual motor to 40 amps. +In practice, we can draw more for a short period of time. +If you are interested in more details about how and why that works, look at the electrical coursework for the team. + +There is a similar product manufactured by CTRE called the Power Distribution Panel, or PDP. +This is essentially an older version of the PDH with fewer motor slots, and is largely legacy hardware. +You might see it in older documentation and robots, however. + +The PDH is also often at one end of our CAN network. What's CAN? Glad you asked . . . + +### The CAN Network + +CAN Wire + +CAN is a type of communication along wires that allow our sensors and motors to communicate along our robot. +In FRC, CAN is transmitted over yellow and green pairs of cables. +Each device on the CAN network has a unique ID that it uses to talk to other devices on the network. +All of our motors and many of our sensors will communicate over CAN. +CAN is also one of the easiest places for electrical problems to become apparent in software because of the thin wires and large reach of the system. +When you start up the robot you may see red error messages in the drive station console reporting CAN errors. +If you see that, something might be broken on the CAN network. + +We have to set the IDs of the devices on the CAN network to make sure they are all uniquely and correctly identified. +It would be very awkward to try to drive the robot and accidentally run the elevator instead of the drivetrain! +To set the IDs of devices on the CAN network we use the [Tuner X](https://pro.docs.ctr-electronics.com/en/stable/docs/tuner/index.html) app built by CTRE or the [Rev Hardware Client](https://docs.revrobotics.com/rev-hardware-client), depending on the motor type being used. +This app also has features to test and configure devices on the CAN network. + +### CANivore + +CANivore + +The CANivore is a device that connects to the [Rio](#roborio-2) (in the next section) over usb. +It allows us to have a second CAN network with increased bandwidth. +This is useful because CAN has a limited amount of information that can travel over it each second, and we can easily reach that limit with the number of motors and sensors we have. +The CANivore gets around some hardware limitations of the Rio to have extra bandwidth on its network compared to the default network. +This also enables some extra latency compensation features on CTRE devices. +In 2024 we exclusively used the CANivore network for motors and sensors. + +### RoboRIO 2 + +RoboRIO + +The RoboRIO (rio) is the brain of the robot. +It is built around a computer running Linux with a large number of Input/Output (IO) ports. +These ports include: + +- Digital IO (DIO) can accept inputs and outputs that are either a 1 or 0, on or off. + When used as an input the signal can rapidly be turned on and off to send numerical data. + This is done using Pulse Width Modulation (PWM), essentially measuring how long a signal is on compared to how long it is off in a given time window to get a numerical value. + For more information on PWM optionally read [this article](https://learn.sparkfun.com/tutorials/pulse-width-modulation/all) +- PWM pins provide additional pins to _output_ PWM signals. + Unlike the DIO ports the PWM ports cannot take inputs. + Many motor controllers support using a PWM signal for control instead of CAN, although it has significantly limited features. +- Analog inputs provide adititional sensor ports, although most sensors use DIO or CAN. +- The large set of pins in the middle of the Rio is the MXP port. + MXP (and the SPI port in the top-right corner) is used for communication over serial interfaces such as I²C protocal. + Unfortunately, there is an issue with I²C that can cause the code to lock up when it is used, so we avoid using the serial ports. + We can get around this issue by using a coprocessor (computer other than the rio, like a raspberry pi) to convert the signal to a different protocol. + Generally we avoid using I²C devices. +- A CAN network originates at the RIO. +- Several USB ports are available on the Rio. + Common uses include external USB sticks to store logs or the [CANivore](#canivore) (see later in this page). + The usb ports can also be used to connect the rio to a computer to deploy code and run the robot, although we usually prefer to use ethernet if we need to run tethered. +- An ethernet port which connects to the radio (in the next section) to communicate to the driver station. + +The Rio also has an SD card as its memory. +When we set up a Rio for the season we need to image it using a tool that comes with the driver station. +The WPILib docs have [instructions](https://docs.wpilib.org/en/stable/docs/zero-to-robot/step-3/roborio2-imaging.html) on how to image the SD card. + +### Note on 2026-7+ Control System +As per [this](https://community.firstinspires.org/introducing-the-future-mobile-robot-controller) blog post, FRC will use a different robot control system called SystemCore beginning with the 2027 season. +This moves away from the RoboRIO and NI and instead will use a controller based on the Raspberry Pi CM5. +More Information to come on Systemcore in 2026. + +### Vivid Hosting Radio + +Radio + +The VH-109 radio is essentially a Wi-Fi router that lives on the robot and connects it to the driver station. +At tournaments we have to take the radio to get reprogrammed for that competition. +This makes it able to connect to the access point (another radio on the field) so that our robot gets enabled and disabled at the right times during matches, however it prevents us from connecting to the robot with a laptop wirelessly $`^1`$. +The radio has four ethernet ports and a pair of terminals for power wires. +One ethernet port connects to the rio and is labeled RIO. +One is usually reserved for tethering to a laptop and is labeled DS. +We can connect certain advanced sensors, like vision systems, to ethernet. +Sometimes we add a network switch to the ethernet network, which is a device that allows us to have more ethernet ports than the radio provides. +Network switches and other ethernet devices are plugged into the AUX1 and AUX2 ports on the radio. + +After each competition we have to reimage the radio to allow it to connect to a laptop wirelessly again. +Refer to the [vivid hosting radio documentation](https://frc-radio.vivid-hosting.net/) for more information. + +We can also connect to the robot's VH-109 by connecting to a second network (which comes from a second radio that's connected to the robot). +This radio could be the VH-113 access point, which is a larger radio that is for the field instead of going on a robot, or a VH-109 configured to act like a VH-113. +This is the setup we'll usually be using at the Loom. + +The radio can either be powered using Power-Over-Ethernet (PoE) or the power terminals. +Be careful with checking for good, consistent radio power if you are having connection issues. + +An older radio known as the OM5P was in use until champs 2024, and you may encounter some on old/offseason robots. +It was much worse to deal with (more fragile and finicky) and we are lucky to be done with it. +It is pictured below. + +Old Radio + +### Motor Controllers + +Spark Max + +Motors are the primary form of movement on the robot, but on their own they are just expensive paperweights. +Motor controllers take signals from our code, often over CAN, and turn them into the voltage sent to the motor. +These controllers plug into slots on the PDH, the CAN network (or much more rarely PWM ports), and the power lines of the motor. + +Many motor controllers have more features than just commanding a voltage to a motor. +For instance they might be able to run PID loops much faster than our code is able to, which drives a motor to a specific position or velocity. +Knowing what motor controller you are using and what features it has is very important when writing robot code. +Pictured above is the Spark Max built by REV Robotics, a common modern motor controller often used with the NEO and NEO 550 motors. +REV also produces a motor called the NEO Vortex which uses the Spark Flex, pictured below. + +NEO Vortex + Spark Flex + + +### The Talon FX + Kraken X60/44 + +Kraken X60 + +**These motors are used by 321 only, but this information is included for reference.** + +The Kraken X60 ("Kraken") motor is the primary motor 321 uses on the robot. +Unlike many other motors, Krakens come with a built in motor controller called the Talon FX. +The Kraken also has a built in encoder, or sensor that tracks the rotation and speed of the motor. +This is a relative encoder, so we tend to pair them with CTRE CANcoders (which are absolute). +More on encoders [below](#encoders). +Documentation for the software to control Krakens can be found [here](https://pro.docs.ctr-electronics.com/en/stable/). +There is also the Kraken X44, which is mostly the same as the X60 but a bit smaller and lighter. +It looks the same in code to us, since it also has a TalonFX controller. + +Kraken X44 + +We also use the Falcon 500 ("Falcon") motor in some places on the robot. +Slightly less powerful, slightly older, and likely out of stock for the forseeable future, Falcons are slowly being phased out of our motor stock. +Because Falcons also have an integrated TalonFX, they behave exactly the same in code as Krakens. +A Falcon is pictured below. + +Falcon + +### Solenoids and Pneumatics + +A solenoid + +Pneumatics are useful for simple, linear, and repeatable motions where motors might not be ideal. +In the past we have used them primarily for extending intakes. +If pneumatic pistons are like motors, solenoids are like motor controllers. +A solenoid takes a control signal and uses it to switch incoming air into one of two output tubes to cause it to either extend or retract. +We usually use double solenoids, which have both powered extension and retraction. +Single solenoids only supply air for extension, and rely on the piston having a spring or something similar to retract them. +Our mechanical team has moved somewhat away from pneumatics over weight and complexity concerns, but they may still appear on future robots. + +### Servos + +A servo + +Servos are similar to motors in that they can produce rotational motion, but are designed for very precise rotation. +Servos are somewhat more common in FTC than FRC, but are good for "single use" mechanisms or something that needs to go to a specific position. +In 2025, we used 2 servos to open the funnel hatch panel in order to climb. + +### Robot Status Light + +RSL + +The Robot Status Light (RSL) is an orange light we are required to have on our robot by competition rules. +When the robot is powered on it will glow solid orange. +When the robot is enabled ie being controlled by joysticks or autonomous code it will flash orange. +This is handled automatically by WPILib. +**When the robot is enabled wear safety glasses and do not go near the robot**. + +We have additional LEDs on the robot that indicate the state of the robot, but they are not standardized year to year and should not be relied upon for safety information. + +## Sensors + +### Encoders + +CANcoder +Rev Through Bore Encoder + +Encoders are devices that measure rotation. +We use them to measure the angles of our swerve modules, the speed of our wheels, the position of our elevator, and much more. +Relative encoders measure relative change in position (e.g. the shaft has rotated 30 degrees since powering on), while absolute encoders measure the exact position of the shaft (e.g. the shaft is 30 degrees from some absolute zero position). +Modern motors have relative encoders built in. + +Absolute encoders are useful when they're measuring something that cannot rotate more than once, because they only return their position in a 0-360 degree range. +This is well suited to mechanisms like arms, that usually can't rotate past 360 degrees without causing problems. +However, motors are almost always geared down such that the motor rotates multiple times per rotation of the mechanism it is attached to, making the absolute data not so absolute. +While this gearing is required to get enough torque from the motor, this is something to keep in mind when prototyping or looking at designs for mechanisms. +If a mechanism has an absolute encoder, it should be rotating 1 or less rotations over the mechanism's range. +If it is infeasible to have an encoder that goes 1 or less rotations for a mechanism, you might want to take a look at the next section, [Limit Switches](#limit-switches) + +Some examples of absolute encoders are the CTRE CANcoder (upper picture). +The CANcoder sends absolute position and velocity data over the CAN network and uses a magnet embedded in the mechanism to keep track of position. +We use these to track the heading of our swerve modules, as well as mechanisms like arms. + +The Rev Through Bore encoder (lower picture) solves the mounting problem by connecting directly to hex shaft (the most common type of shaft in FRC). +However, the Through Bore does not communicate over CAN and requires wiring to the DIO ports. +We used one of these on the hood of our 2022 robot. + +Some teams have seen success getting around the absolute encoder only having one rotation of usefullness by using [Chinese remainder theorem](https://en.wikipedia.org/wiki/Chinese_remainder_theorem) alongside multiple encoders. + +### Limit Switches + +Limit Switch + +A limit switch is a simple sensor that tells us whether a switch is pressed or not. +They are one of the electrically simplest sensors we can use, and are quite useful as a way to reset the encoder on a mechanism ("Zero" the mechanism). +They can also be used to set up safety limits for mechanisms so they don't damage themselves or others. +They are quite fragile, however, so absolute encoders are better to be used where possible. +We used a limit switch on our 2023 robot that was pressed when the elevator was fully retracted. +When it was pressed, we knew that the elevator must be fully retracted so we could reset the encoder to 0 inches of extension. + +However, due to the fragility and additional wiring necessary for limit switches we have moved away from them. +Instead, we use "current zeroing". +This involves slowly powering a mechanism into a hardstop with a known position (like an elevator being all the way down). +When the current draw of the motor spikes, we know that the motor is "stalling", or using power but not moving. +This tells us that we are at the hardstop in the same way that a limit switch would. +Think of it as walking towards a wall blindfolded with your hands outstretched- when you get to a point where you're not getting anywhere and are exerting a lot of force in front of you , you'll know you're at the wall. + +### IMU + +Pigeon 2 Gyro + +An Inertial Measurement Unit (IMU or sometimes Gyro) is a sensor that measures its acceleration and rotation. +We primarily use them to measure the rotation of the robot (heading) so that the robot knows which way it is pointing on the field. +This lets us run field-relative swerve drive. +Pictured above is the Pigeon 2.0 IMU by CTRE, an IMU that connects over the CAN network. + +### Beambreaks/Light Sensors + +A beambreak + +A beambreak has two parts: an emitter, which sends out a beam of infrared light, and a receiver, which is directly across from the emitter and receives that light. +These are useful for when we need to detect if something like a game piece is present or not. +If we mount the two parts on opposite sides of a mechanism, the game piece will block the light from reaching the other side and thus tell us that it's there. + +We might use an IR reflective sensor if we don't want to or can't add the receiver part, but this works in the same way by detecting when something's blocked the beam of light coming from the emitter. + +### Cameras and Vision + +A Limelight Camera + +Cameras and vision systems are an important part of advanced FRC software. +Vision in FRC can be used to detect visual markers called AprilTags on the field, game pieces, and even other robots. +The pictured camera is a Limelight camera, a purchaseable vision solution that connects to the robot over ethernet and we used from 2021-2023. + +However, we've moved towards custom hardware, such as [Arducam cameras](https://www.arducam.com/product/arducam-100fps-global-shutter-usb-camera-board-1mp-720p-ov9281-uvc-webcam-module-with-low-distortion-m12-lens-without-microphones-for-computer-laptop-android-device-and-raspberry-pi/) and [Orange Pi processors](http://www.orangepi.org/). +The cameras plug into the USB ports on the Orange Pi. +The Orange Pi connects to the radio over Ethernet, +It's powered off the PDH with a buck/step down converter (which decreases voltage and increases current, since the Orange Pi only takes 5V and not 12). +If you're having issues, check the PhotonVision docs pages on [networking](https://docs.photonvision.org/en/latest/docs/quick-start/networking.html) and [wiring](https://docs.photonvision.org/en/latest/docs/quick-start/wiring.html). \ No newline at end of file diff --git a/docs/basics/java_basics.md b/docs/basics/java_basics.md index 106effa..5c6370a 100644 --- a/docs/basics/java_basics.md +++ b/docs/basics/java_basics.md @@ -8,14 +8,14 @@ Learning What's What - Objects, variables, and classes (in Java) make up our programs. We define, modify, and use these variables and objects to make our programs run. - Programs use key words to define characteristics of variables or objects. Basic keywords: - - `#!java public` - an object accessible by other classes (files) - - `#!java private` - an object only accessible by its containing class (file). - - `#!java protected` - like private but can be seen by subclasses - - `#!java return` - value to return or give back after method execution (run). - - `#!java void` - a method that returns no value - - `#!java null` - a value that means empty or nothing - -!!! Warning "IMPORTANT NOTE" + - `public` - an object accessible by other classes (files) + - `private` - an object only accessible by its containing class (file). + - `protected` - like private but can be seen by subclasses + - `return` - value to return or give back after method execution (run). + - `void` - a method that returns no value + - `null` - a value that means empty or nothing + +!!! warning "IMPORTANT NOTE" Java is case sensitive, meaning capitalization matters! *** @@ -23,9 +23,11 @@ Learning What's What - Classes are the files that contain our programming - A program can be made up of one class but can also be made up of many classes - - All programs run a main class that can optionally load additional classes either directly or indirectly - - !!! example - main loads class1, class1 loads class2 + - All programs run a main class that can optionally load additional classes either directly or indirectly + + !!! example + main loads class1, class1 loads class2 + - Classes are made up of variables and methods and are often used to separate and organize your code. - Classes can also **call** (use) variables or methods of other classes if those have been set to public. @@ -37,8 +39,9 @@ Learning What's What - They can be called again if the class is programmed to be unloaded (destroyed) and reloaded. - Calls to methods, and assignment of values, within the constructor will run as soon as the class is called (loaded) in the code. - The **new** operator creates an object of a type of class using a constructor - - !!! example - classObject = new className(); + + !!! example + classObject = new className(); *** @@ -82,25 +85,28 @@ Learning What's What - Variables are assigned names and data types on creation - Names can be anything with the exception of pre-existing keywords such as `public` or `int` - Data types define what type of data is being stored in the variables: - - `#!java int` - integers (whole numbers) - - `#!java double` - double precision floating point (fractional/decimal values) - - `#!java boolean` - true or false (true = 1 or false = 0) values. - - `#!java string` - text values contained in parentheses - - !!! Example "Example: `#!java int sum;`" - A variable that can hold whole number values - - !!! Example "Example: `#!java boolean isFull = true;`" - A variable can either hold a true or false value and is being assigned a true value + - `int` - integers (whole numbers) + - `double` - double precision floating point (fractional/decimal values) + - `boolean` - true or false (true = 1 or false = 0) values. + - `string` - text values contained in parentheses + +!!! example "Example: `int sum;`" + A variable that can hold whole number values + +!!! example "Example: `boolean isFull = true;`" + A variable can either hold a true or false value and is being assigned a true value ### Constants Most variables can have their values assigned or reassigned at any point elsewhere in your program. To avoid having a variable change its value during runtime you can make it a **constant** -- In Java you can create constants using the `#!java static final` keywords together in front of the data type of the variable - - The static modifier causes the variable to be available without loading the class where it is defined. +- In Java you can create constants using the `static final` keywords together in front of the data type of the variable + - The static modifier causes the variable to be available without loading the class where it is defined. - The final modifier causes the variable to be unchangeable. - Java constants are normally declared in ALL CAPS. Words in Java constants are normally separated by underscores. - - !!! Example "Example: `#!java public static final double PI_VALUE = 3.14159;`" - A variable that cannot be modified during code run time. + +!!! example "Example: `public static final double PI_VALUE = 3.14159;`" + A variable that cannot be modified during code run time. ### Scope @@ -171,9 +177,10 @@ Most variables can have their values assigned or reassigned at any point elsewhe - There are also many different conventions when programming, this ensures that programs are readable between different people. - A common naming convention: - Programming is often done in CamelCase or lowerCamelCase - - Instead of adding spaces, capitalize the first letter of each word - - !!! example - ThreeMotorDrive, driveForward, setSpeed + - Instead of adding spaces, capitalize the first letter of each word + +!!! example + ThreeMotorDrive, driveForward, setSpeed -!!! info +!!! info There are other naming conventions, but for this tutorial we will use the camel cases diff --git a/docs/basics/wpilib.md b/docs/basics/wpilib.md index 0baae3f..a15408d 100644 --- a/docs/basics/wpilib.md +++ b/docs/basics/wpilib.md @@ -9,9 +9,10 @@ Making FRC Programming Easy - The WPI Robotics library (WPILib) is a set of software classes that interfaces with the hardware and software in your FRC RoboRIO. - There are classes to handle sensors, motor speed controllers, the driver station, and a number of other utility functions. - Documentation is available at -- WPILib adds those sensors and controllers as additional data types (like `#!java int` or `#!java double`) and classes. - - !!!example "Examples" - `Talon`, `Solenoid`, `Encoder`... +- WPILib adds those sensors and controllers as additional data types (like `int` or `double`) and classes. + +??? example "Examples" + `Talon`, `Solenoid`, `Encoder`... *** @@ -40,13 +41,14 @@ Making FRC Programming Easy - Some variables (parts) would be: **leftEye**, **rightEye**, **nose**, **leftEar**, **rightEar**. - Some example methods would be **closeEyes** or **openEyes** since these are things the dog are capable of. - These methods would use both the **leftEye** and **rightEye** and close them. - - ??? example - ```java - //This method closes the dog eyes + + ??? example + ```java + //This method closes the dog eyes public void closeEyes(){ - leftEye.close(); - rightEye.close(); - ``` + leftEye.close(); + rightEye.close(); + ``` - A robot example of a **Drivetrain** subsystem would have **leftMotor**, and **rightMotor** as variables and **setSpeed** as a method telling it how to set the speed of those motor controllers. - Having the **setSpeed** method tells our program that our **Drivetrain** subsystem can set its speed. - ??? example @@ -67,48 +69,56 @@ Making FRC Programming Easy - A **command** is an action a **subsystem(s)** performs. - For example you may want your robot to drive full speed forward so you make a command class called **DriveForward**. - Since a robot uses a **Drivetrain** subsystem to control its motors, this command would call our previously created **setSpeed** method from that subsystem. -- !!! Tip - **Subsystems** define what the robot is made of and what it can do while **commands** actually tell the robot to do those things + +!!! tip + **Subsystems** define what the robot is made of and what it can do while **commands** actually tell the robot to do those things + - Using a dog as an example we can tell the dog to blink by creating a **BlinkEyes** command - The command would call the method, **closeEyes()** then the method **openEyes()** -- ??? example "BlinkEyes Command" - ```java + +??? example "BlinkEyes Command" + ```java //This command will continuously run the two methods in execute protected void execute() { - dog.head.closeEyes(); - dog.head.openEyes(); + dog.head.closeEyes(); + dog.head.openEyes(); } - ``` + ``` + - A robot example of a **DriveForward** command would call (use) the **setSpeed** methods that we created in the **Drivetrain** subsystem - **DriveForward**, when executed, will tell our robot to drive forward using the **Drivetrain** subsystem -- ??? example "DriveForward Command" - ```java + +??? example "DriveForward Command" + ```java //This command tells the robot to drive forward full speed - protected void initialize(){ - robot.drivetrain.setSpeed(1.0); - } - ``` + protected void initialize(){ + robot.drivetrain.setSpeed(1.0); + } + ``` #### Default Command Structure - The template for FRC commands actually come with some pre-defined methods that have special properties for FRC robots, they are: - - `#!java void initialize()` - Methods in here are called just before this Command runs the first time. - - `#!java void execute()` - Methods in here are called repeatedly when this Command is scheduled to run - - `#!java boolean isFinished()` - When this returns true, the Command stops running execute() - - `#!java void end()` - Methods in here are called once after isFinished returns true - - `#!java void interrupted()` - Methods in here are called when another command which requires one or more of the same subsystems is scheduled to run -- !!! Tip - It is good practice to call `end()` in `interrupted()` + - `void initialize()` - Methods in here are called just before this Command runs the first time. + - `void execute()` - Methods in here are called repeatedly when this Command is scheduled to run + - `boolean isFinished()` - When this returns true, the Command stops running execute() + - `void end()` - Methods in here are called once after isFinished returns true + - `void interrupted()` - Methods in here are called when another command which requires one or more of the same subsystems is scheduled to run + +!!! tip + It is good practice to call `end()` in `interrupted()` *** ### Overview of execution -- In FRC programming our main class is **Robot.java** and all other classes (command files and subsystem files) must be loaded from **Robot.java** either directly or indirectly - - !!! Example - **Robot.java** loads **RobotContainer.java**, **RobotContainer.java** loads **DriveForward.java**. +- In FRC programming our main class is **Robot.java** and all other classes (command files and subsystem files) must be loaded from **Robot.java** either directly or indirectly + +!!! example + **Robot.java** loads **RobotContainer.java**, **RobotContainer.java** loads **DriveForward.java**. + - All **subsystem** files must be added to **RobotContainer.java**. - - This loads our **subsystems** into the code and allow its public methods to be useable by other files such as commands later by typing `#!java RobotContainer.nameOfSubsystem.desiredMethod();` + - This loads our **subsystems** into the code and allow its public methods to be useable by other files such as commands later by typing `RobotContainer.nameOfSubsystem.desiredMethod();` *** @@ -124,5 +134,5 @@ See [Default Project Contents](../programming/new_project.md#default-project-con - **Subsystems** define what the robot is made of and what it can do while **commands** actually tell the robot to do those things - All classes must directly or indirectly connect to **Robot.java**. - All **Subsystems** must be added to **RobotContainer.java** -- **RobotMap.java** holds port numbers and IDs accessible throughout the program by typing: `#!java RobotMap.NameOfMotor()` +- **RobotMap.java** holds port numbers and IDs accessible throughout the program by typing: `RobotMap.NameOfMotor()` - **RobotContainer.java** contains our publicly accessible instances of our subsystems. It also connects our commands to physical controllers. diff --git a/docs/code_examples/2026KitBotInline/.gitignore b/docs/code_examples/2026KitBotInline/.gitignore new file mode 100644 index 0000000..34cbaac --- /dev/null +++ b/docs/code_examples/2026KitBotInline/.gitignore @@ -0,0 +1,187 @@ +# This gitignore has been specially created by the WPILib team. +# If you remove items from this file, intellisense might break. + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Gradle ### +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +# # VS Code Specific Java Settings +# DO NOT REMOVE .classpath and .project +.classpath +.project +.settings/ +bin/ + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Fleet +.fleet + +# Simulation GUI and other tools window save file +networktables.json +simgui.json +*-window.json + +# Simulation data log directory +logs/ + +# Folder that has CTRE Phoenix Sim device config storage +ctre_sim/ + +# clangd +/.cache +compile_commands.json + +# Eclipse generated file for annotation processors +.factorypath diff --git a/docs/code_examples/2026KitBotInline/Constants.java b/docs/code_examples/2026KitBotInline/Constants.java new file mode 100644 index 0000000..ed0cc9a --- /dev/null +++ b/docs/code_examples/2026KitBotInline/Constants.java @@ -0,0 +1,63 @@ +// 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. + +package frc.robot; + +/** + * The Constants class provides a convenient place for teams to hold robot-wide + * numerical or boolean constants. This class should not be used for any other + * purpose. All constants should be declared globally (i.e. public static). Do + * not put anything functional in this class. + * + *

+ * It is advised to statically import this class (or one of its inner classes) + * wherever the constants are needed, to reduce verbosity. + */ +public final class Constants { + // --8<-- [start:constants] + public static final class DriveConstants { + // Motor controller IDs for drivetrain motors + public static final int LEFT_LEADER_ID = 1; + public static final int LEFT_FOLLOWER_ID = 2; + public static final int RIGHT_LEADER_ID = 3; + public static final int RIGHT_FOLLOWER_ID = 4; + + // Current limit for drivetrain motors. 60A is a reasonable maximum to reduce + // likelihood of tripping breakers or damaging CIM motors + public static final int DRIVE_MOTOR_CURRENT_LIMIT = 60; + } + //--8<-- [end:constants] + + public static final class FuelConstants { + // Motor controller IDs for Fuel Mechanism motors + public static final int FEEDER_MOTOR_ID = 6; + public static final int INTAKE_LAUNCHER_MOTOR_ID = 5; + + // Current limit and nominal voltage for fuel mechanism motors. + public static final int FEEDER_MOTOR_CURRENT_LIMIT = 60; + public static final int LAUNCHER_MOTOR_CURRENT_LIMIT = 60; + + // Voltage values for various fuel operations. These values may need to be tuned + // based on exact robot construction. + // See the Software Guide for tuning information + public static final double INTAKING_FEEDER_VOLTAGE = -12; + public static final double INTAKING_INTAKE_VOLTAGE = 10; + public static final double LAUNCHING_FEEDER_VOLTAGE = 9; + public static final double LAUNCHING_LAUNCHER_VOLTAGE = 10.6; + public static final double SPIN_UP_FEEDER_VOLTAGE = -6; + public static final double SPIN_UP_SECONDS = 1; + } + + public static final class OperatorConstants { + // Port constants for driver and operator controllers. These should match the + // values in the Joystick tab of the Driver Station software + public static final int DRIVER_CONTROLLER_PORT = 0; + public static final int OPERATOR_CONTROLLER_PORT = 1; + + // This value is multiplied by the joystick value when driving the robot to + // help avoid driving and turning too fast and being difficult to control + public static final double DRIVE_SCALING = .7; + public static final double ROTATION_SCALING = .8; + } +} diff --git a/docs/code_examples/2026KitBotInline/Main.java b/docs/code_examples/2026KitBotInline/Main.java new file mode 100644 index 0000000..8776e5d --- /dev/null +++ b/docs/code_examples/2026KitBotInline/Main.java @@ -0,0 +1,25 @@ +// 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. + +package frc.robot; + +import edu.wpi.first.wpilibj.RobotBase; + +/** + * Do NOT add any static variables to this class, or any initialization at all. Unless you know what + * you are doing, do not modify this file except to change the parameter class to the startRobot + * call. + */ +public final class Main { + private Main() {} + + /** + * Main initialization function. Do not perform any initialization here. + * + *

If you change your main robot class, change the parameter type. + */ + public static void main(String... args) { + RobotBase.startRobot(Robot::new); + } +} diff --git a/docs/code_examples/2026KitBotInline/Robot.java b/docs/code_examples/2026KitBotInline/Robot.java new file mode 100644 index 0000000..9fddd29 --- /dev/null +++ b/docs/code_examples/2026KitBotInline/Robot.java @@ -0,0 +1,130 @@ +// 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. + +package frc.robot; + +import edu.wpi.first.hal.HAL; +import edu.wpi.first.hal.FRCNetComm.tResourceType; +import edu.wpi.first.wpilibj.TimedRobot; +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.CommandScheduler; +import edu.wpi.first.wpilibj2.command.button.CommandGenericHID; + +/** + * The VM is configured to automatically run this class, and to call the + * functions corresponding to + * each mode, as described in the TimedRobot documentation. If you change the + * name of this class or + * the package after creating this project, you must also update the + * build.gradle file in the + * project. + */ +public class Robot extends TimedRobot { + private Command m_autonomousCommand; + + private RobotContainer m_robotContainer; + + /** + * This function is run when the robot is first started up and should be used + * for any + * initialization code. + */ + @Override + public void robotInit() { + // Instantiate our RobotContainer. This will perform all our button bindings, + // and put our + // autonomous chooser on the dashboard. + m_robotContainer = new RobotContainer(); + + // Used to track usage of Kitbot code, please do not remove. + HAL.report(tResourceType.kResourceType_Framework, 10); + } + + /** + * This function is called every 20 ms, no matter the mode. Use this for items + * like diagnostics + * that you want ran during disabled, autonomous, teleoperated and test. + * + *

+ * This runs after the mode specific periodic functions, but before LiveWindow + * and + * SmartDashboard integrated updating. + */ + @Override + public void robotPeriodic() { + // Runs the Scheduler. This is responsible for polling buttons, adding + // newly-scheduled + // commands, running already-scheduled commands, removing finished or + // interrupted commands, + // and running subsystem periodic() methods. This must be called from the + // robot's periodic + // block in order for anything in the Command-based framework to work. + CommandScheduler.getInstance().run(); + } + + /** This function is called once each time the robot enters Disabled mode. */ + @Override + public void disabledInit() { + } + + @Override + public void disabledPeriodic() { + } + + /** + * This autonomous runs the autonomous command selected by your + * {@link RobotContainer} class. + */ + @Override + public void autonomousInit() { + m_autonomousCommand = m_robotContainer.getAutonomousCommand(); + + // schedule the autonomous command (example) + if (m_autonomousCommand != null) { + CommandScheduler.getInstance().schedule(m_autonomousCommand);; + } + } + + /** This function is called periodically during autonomous. */ + @Override + public void autonomousPeriodic() { + } + + @Override + public void teleopInit() { + // This makes sure that the autonomous stops running when + // teleop starts running. If you want the autonomous to + // continue until interrupted by another command, remove + // this line or comment it out. + if (m_autonomousCommand != null) { + m_autonomousCommand.cancel(); + } + } + + /** This function is called periodically during operator control. */ + @Override + public void teleopPeriodic() { + } + + @Override + public void testInit() { + // Cancels all running commands at the start of test mode. + CommandScheduler.getInstance().cancelAll(); + } + + /** This function is called periodically during test mode. */ + @Override + public void testPeriodic() { + } + + /** This function is called once when the robot is first started up. */ + @Override + public void simulationInit() { + } + + /** This function is called periodically whilst in simulation. */ + @Override + public void simulationPeriodic() { + } +} diff --git a/docs/code_examples/2026KitBotInline/RobotContainer.java b/docs/code_examples/2026KitBotInline/RobotContainer.java new file mode 100644 index 0000000..9931f50 --- /dev/null +++ b/docs/code_examples/2026KitBotInline/RobotContainer.java @@ -0,0 +1,104 @@ +// 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. + +package frc.robot; + +import edu.wpi.first.wpilibj.smartdashboard.SendableChooser; +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.button.CommandXboxController; +import edu.wpi.first.wpilibj2.command.button.Trigger; + +import static frc.robot.Constants.OperatorConstants.*; +import static frc.robot.Constants.FuelConstants.*; +import frc.robot.commands.Autos; +import frc.robot.subsystems.CANDriveSubsystem; +import frc.robot.subsystems.CANFuelSubsystem; + +/** + * This class is where the bulk of the robot should be declared. Since + * Command-based is a "declarative" paradigm, very little robot logic should + * actually be handled in the {@link Robot} periodic methods (other than the + * scheduler calls). Instead, the structure of the robot (including subsystems, + * commands, and trigger mappings) should be declared here. + */ +public class RobotContainer { + // The robot's subsystems + private final CANDriveSubsystem driveSubsystem = new CANDriveSubsystem(); + private final CANFuelSubsystem ballSubsystem = new CANFuelSubsystem(); + + // The driver's controller + private final CommandXboxController driverController = new CommandXboxController( + DRIVER_CONTROLLER_PORT); + + // The operator's controller + private final CommandXboxController operatorController = new CommandXboxController( + OPERATOR_CONTROLLER_PORT); + + // The autonomous chooser + private final SendableChooser autoChooser = new SendableChooser<>(); + + /** + * The container for the robot. Contains subsystems, OI devices, and commands. + */ + public RobotContainer() { + configureBindings(); + + // Set the options to show up in the Dashboard for selecting auto modes. If you + // add additional auto modes you can add additional lines here with + // autoChooser.addOption + autoChooser.setDefaultOption("Autonomous", Autos.exampleAuto(driveSubsystem, ballSubsystem)); + } + + /** + * Use this method to define your trigger->command mappings. Triggers can be + * created via the {@link Trigger#Trigger(java.util.function.BooleanSupplier)} + * constructor with an arbitrary predicate, or via the named factories in + * {@link edu.wpi.first.wpilibj2.command.button.CommandGenericHID}'s subclasses + * for {@link CommandXboxController Xbox}/ + * {@link edu.wpi.first.wpilibj2.command.button.CommandPS4Controller PS4} + * controllers or + * {@link edu.wpi.first.wpilibj2.command.button.CommandJoystick Flight + * joysticks}. + */ + private void configureBindings() { + + // While the left bumper on operator controller is held, intake Fuel + operatorController.leftBumper() + .whileTrue(ballSubsystem.runEnd(() -> ballSubsystem.intake(), () -> ballSubsystem.stop())); + // While the right bumper on the operator controller is held, spin up for 1 + // second, then launch fuel. When the button is released, stop. + operatorController.rightBumper() + .whileTrue(ballSubsystem.spinUpCommand().withTimeout(SPIN_UP_SECONDS) + .andThen(ballSubsystem.launchCommand()) + .finallyDo(() -> ballSubsystem.stop())); + // While the A button is held on the operator controller, eject fuel back out + // the intake + operatorController.a() + .whileTrue(ballSubsystem.runEnd(() -> ballSubsystem.eject(), () -> ballSubsystem.stop())); + + // Set the default command for the drive subsystem to the command provided by + // factory with the values provided by the joystick axes on the driver + // controller. The Y axis of the controller is inverted so that pushing the + // stick away from you (a negative value) drives the robot forwards (a positive + // value). The X-axis is also inverted so a positive value (stick to the right) + // results in clockwise rotation (front of the robot turning right). Both axes + // are also scaled down so the rotation is more easily controllable. + // --8<-- [start:drive-config] + driveSubsystem.setDefaultCommand( + driveSubsystem.driveArcade( + () -> -driverController.getLeftY() * DRIVE_SCALING, + () -> -driverController.getRightX() * ROTATION_SCALING)); + // --8<-- [end:drive-config] + } + + /** + * Use this to pass the autonomous command to the main {@link Robot} class. + * + * @return the command to run in autonomous + */ + public Command getAutonomousCommand() { + // An example command will be run in autonomous + return autoChooser.getSelected(); + } +} diff --git a/docs/code_examples/2026KitBotInline/commands/Autos.java b/docs/code_examples/2026KitBotInline/commands/Autos.java new file mode 100644 index 0000000..80f92f4 --- /dev/null +++ b/docs/code_examples/2026KitBotInline/commands/Autos.java @@ -0,0 +1,30 @@ +// 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. + +package frc.robot.commands; + +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.SequentialCommandGroup; +import frc.robot.subsystems.CANFuelSubsystem; +import frc.robot.subsystems.CANDriveSubsystem; + +public final class Autos { + // Example autonomous command which drives forward for 1 second. + public static final Command exampleAuto(CANDriveSubsystem driveSubsystem, CANFuelSubsystem ballSubsystem) { + return new SequentialCommandGroup( + // Drive backwards for .25 seconds. The driveArcadeAuto command factory + // creates a command which does not end which allows us to control + // the timing using the withTimeout decorator + driveSubsystem.driveArcade(() -> 0.5, () -> 0).withTimeout(.25), + // Stop driving. This line uses the regular driveArcade command factory so it + // ends immediately after commanding the motors to stop + driveSubsystem.driveArcade(() -> 0, () -> 0), + // Spin up the launcher for 1 second and then launch balls for 9 seconds, for a + // total of 10 seconds + ballSubsystem.spinUpCommand().withTimeout(1), + ballSubsystem.launchCommand().withTimeout(9), + // Stop running the launcher + ballSubsystem.runOnce(() -> ballSubsystem.stop())); + } +} diff --git a/docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java b/docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java new file mode 100644 index 0000000..3da1857 --- /dev/null +++ b/docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java @@ -0,0 +1,70 @@ +// 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. + +package frc.robot.commands; + +import java.util.function.DoubleSupplier; + +import edu.wpi.first.wpilibj2.command.Command; +import frc.robot.subsystems.CANDriveSubsystem; + +/** + * An example command that uses an arcade drive subsystem. This command demonstrates + * how to create a command that gets input from joystick axes and drives the robot. + */ +public class DriveArcadeCommand extends Command { + // --8<-- [start:class-variables] + private final DoubleSupplier xSpeed; + private final DoubleSupplier zRotation; + private final CANDriveSubsystem driveSubsystem; + // --8<-- [end:class-variables] + + /** + * Creates a new DriveArcadeCommand. + * + * @param xSpeed the input for movement speed as a DoubleSupplier (a function that returns a double) + * @param zRotation the input for rotation speed as a DoubleSupplier + * @param driveSubsystem the subsystem that this command requires + */ + // --8<-- [start:constructor-signature] + public DriveArcadeCommand( + DoubleSupplier xSpeed, DoubleSupplier zRotation, CANDriveSubsystem driveSubsystem) { + // --8<-- [end:constructor-signature] + // --8<-- [start:constructor-body] + this.xSpeed = xSpeed; + this.zRotation = zRotation; + this.driveSubsystem = driveSubsystem; + // Use addRequirements() here to declare subsystem dependencies. + addRequirements(driveSubsystem); + // --8<-- [end:constructor-body] + } + + // Called when the command is initially scheduled. + @Override + public void initialize() {} + + // Called every time the scheduler runs while the command is scheduled. + // --8<-- [start:execute-method] + @Override + public void execute() { + driveSubsystem.arcadeDrive(xSpeed.getAsDouble(), zRotation.getAsDouble()); + } + // --8<-- [end:execute-method] + + // Called once the command ends or is interrupted. + // --8<-- [start:end-method] + @Override + public void end(boolean interrupted) { + driveSubsystem.arcadeDrive(0, 0); + } + // --8<-- [end:end-method] + + // Returns true when the command should end. + // --8<-- [start:is-finished-method] + @Override + public boolean isFinished() { + return false; // This command never finishes on its own; it runs continuously + } + // --8<-- [end:is-finished-method] +} diff --git a/docs/code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java b/docs/code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java new file mode 100644 index 0000000..80534d1 --- /dev/null +++ b/docs/code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java @@ -0,0 +1,104 @@ + +// 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. + +package frc.robot.subsystems; + +import java.util.function.DoubleSupplier; + +import com.revrobotics.spark.SparkBase.PersistMode; +import com.revrobotics.spark.SparkBase.ResetMode; +import com.revrobotics.spark.SparkLowLevel.MotorType; +import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.config.SparkMaxConfig; + +import edu.wpi.first.wpilibj.drive.DifferentialDrive; +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.Commands; +import edu.wpi.first.wpilibj2.command.SubsystemBase; +import static frc.robot.Constants.DriveConstants.*; + +public class CANDriveSubsystem extends SubsystemBase { + // --8<-- [start:motors] + private final SparkMax leftLeader; + private final SparkMax leftFollower; + private final SparkMax rightLeader; + private final SparkMax rightFollower; + + // --8<-- [end:motors] + + // --8<-- [start:differential-drive-variable] + private final DifferentialDrive drive; + // --8<-- [end:differential-drive-variable] + + // --8<-- [start:constructor] + + public CANDriveSubsystem() { + // create brushed motors for drive + leftLeader = new SparkMax(LEFT_LEADER_ID, MotorType.kBrushed); + leftFollower = new SparkMax(LEFT_FOLLOWER_ID, MotorType.kBrushed); + rightLeader = new SparkMax(RIGHT_LEADER_ID, MotorType.kBrushed); + rightFollower = new SparkMax(RIGHT_FOLLOWER_ID, MotorType.kBrushed); + + // set up differential drive class + drive = new DifferentialDrive(leftLeader, rightLeader); + + // Set can timeout. Because this project only sets parameters once on + // construction, the timeout can be long without blocking robot operation. Code + // which sets or gets parameters during operation may need a shorter timeout. + // --8<-- [start:can-timeout] + leftLeader.setCANTimeout(250); + rightLeader.setCANTimeout(250); + leftFollower.setCANTimeout(250); + rightFollower.setCANTimeout(250); + // --8<-- [end:can-timeout] + + // Create the configuration to apply to motors. Voltage compensation + // helps the robot perform more similarly on different + // battery voltages (at the cost of a little bit of top speed on a fully charged + // battery). The current limit helps prevent tripping + // breakers. + // --8<-- [start:voltage-compensation] + SparkMaxConfig config = new SparkMaxConfig(); + config.voltageCompensation(12); + config.smartCurrentLimit(DRIVE_MOTOR_CURRENT_LIMIT); + // --8<-- [end:voltage-compensation] + + // Set configuration to follow each leader and then apply it to corresponding + // follower. Resetting in case a new controller is swapped + // in and persisting in case of a controller reset due to breaker trip + // --8<-- [start:follower-config] + config.follow(leftLeader); + leftFollower.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters); + config.follow(rightLeader); + rightFollower.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters); + // --8<-- [end:follower-config] + + // Remove following, then apply config to right leader + // --8<-- [start:right-leader-config] + config.disableFollowerMode(); + rightLeader.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters); + // --8<-- [end:right-leader-config] + // Set config to inverted and then apply to left leader. Set Left side inverted + // so that postive values drive both sides forward + // --8<-- [start:left-inversion] + config.inverted(true); + leftLeader.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters); + // --8<-- [end:left-inversion] + } + + // --8<-- [end:constructor] + + @Override + public void periodic() { + } + + // Command factory to create command to drive the robot with joystick inputs. + // --8<-- [start:drive-arcade-method] + public Command driveArcade(DoubleSupplier xSpeed, DoubleSupplier zRotation) { + return this.run( + () -> drive.arcadeDrive(xSpeed.getAsDouble(), zRotation.getAsDouble())); + } + // --8<-- [end:drive-arcade-method] +} diff --git a/docs/code_examples/2026KitBotInline/subsystems/CANFuelSubsystem.java b/docs/code_examples/2026KitBotInline/subsystems/CANFuelSubsystem.java new file mode 100644 index 0000000..10d1456 --- /dev/null +++ b/docs/code_examples/2026KitBotInline/subsystems/CANFuelSubsystem.java @@ -0,0 +1,107 @@ +// 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. + +package frc.robot.subsystems; + +import com.revrobotics.spark.SparkBase.PersistMode; +import com.revrobotics.spark.SparkBase.ResetMode; +import com.revrobotics.spark.SparkLowLevel.MotorType; +import com.revrobotics.spark.config.SparkMaxConfig; +import com.revrobotics.spark.SparkMax; + +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.SubsystemBase; +import static frc.robot.Constants.FuelConstants.*; + +public class CANFuelSubsystem extends SubsystemBase { + private final SparkMax feederRoller; + private final SparkMax intakeLauncherRoller; + + /** Creates a new CANBallSubsystem. */ + public CANFuelSubsystem() { + // create brushed motors for each of the motors on the launcher mechanism + intakeLauncherRoller = new SparkMax(INTAKE_LAUNCHER_MOTOR_ID, MotorType.kBrushed); + feederRoller = new SparkMax(FEEDER_MOTOR_ID, MotorType.kBrushed); + + // put default values for various fuel operations onto the dashboard + // all methods in this subsystem pull their values from the dashbaord to allow + // you to tune the values easily, and then replace the values in Constants.java + // with your new values. For more information, see the Software Guide. + SmartDashboard.putNumber("Intaking feeder roller value", INTAKING_FEEDER_VOLTAGE); + SmartDashboard.putNumber("Intaking intake roller value", INTAKING_INTAKE_VOLTAGE); + SmartDashboard.putNumber("Launching feeder roller value", LAUNCHING_FEEDER_VOLTAGE); + SmartDashboard.putNumber("Launching launcher roller value", LAUNCHING_LAUNCHER_VOLTAGE); + SmartDashboard.putNumber("Spin-up feeder roller value", SPIN_UP_FEEDER_VOLTAGE); + + // create the configuration for the feeder roller, set a current limit and apply + // the config to the controller + SparkMaxConfig feederConfig = new SparkMaxConfig(); + feederConfig.smartCurrentLimit(FEEDER_MOTOR_CURRENT_LIMIT); + feederRoller.configure(feederConfig, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters); + + // create the configuration for the launcher roller, set a current limit, set + // the motor to inverted so that positive values are used for both intaking and + // launching, and apply the config to the controller + SparkMaxConfig launcherConfig = new SparkMaxConfig(); + launcherConfig.inverted(true); + launcherConfig.smartCurrentLimit(LAUNCHER_MOTOR_CURRENT_LIMIT); + intakeLauncherRoller.configure(launcherConfig, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters); + } + + // A method to set the rollers to values for intaking + public void intake() { + feederRoller.setVoltage(SmartDashboard.getNumber("Intaking feeder roller value", INTAKING_FEEDER_VOLTAGE)); + intakeLauncherRoller + .setVoltage(SmartDashboard.getNumber("Intaking intake roller value", INTAKING_INTAKE_VOLTAGE)); + } + + // A method to set the rollers to values for ejecting fuel out the intake. Uses + // the same values as intaking, but in the opposite direction. + public void eject() { + feederRoller + .setVoltage(-1 * SmartDashboard.getNumber("Intaking feeder roller value", INTAKING_FEEDER_VOLTAGE)); + intakeLauncherRoller + .setVoltage(-1 * SmartDashboard.getNumber("Intaking launcher roller value", INTAKING_INTAKE_VOLTAGE)); + } + + // A method to set the rollers to values for launching. + public void launch() { + feederRoller.setVoltage(SmartDashboard.getNumber("Launching feeder roller value", LAUNCHING_FEEDER_VOLTAGE)); + intakeLauncherRoller + .setVoltage(SmartDashboard.getNumber("Launching launcher roller value", LAUNCHING_LAUNCHER_VOLTAGE)); + } + + // A method to stop the rollers + public void stop() { + feederRoller.set(0); + intakeLauncherRoller.set(0); + } + + // A method to spin up the launcher roller while spinning the feeder roller to + // push Fuel away from the launcher + public void spinUp() { + feederRoller + .setVoltage(SmartDashboard.getNumber("Spin-up feeder roller value", SPIN_UP_FEEDER_VOLTAGE)); + intakeLauncherRoller + .setVoltage(SmartDashboard.getNumber("Launching launcher roller value", LAUNCHING_LAUNCHER_VOLTAGE)); + } + + // A command factory to turn the spinUp method into a command that requires this + // subsystem + public Command spinUpCommand() { + return this.run(() -> spinUp()); + } + + // A command factory to turn the launch method into a command that requires this + // subsystem + public Command launchCommand() { + return this.run(() -> launch()); + } + + @Override + public void periodic() { + // This method will be called once per scheduler run + } +} diff --git a/docs/code_examples/BasicShooterSubsystem.java b/docs/code_examples/BasicShooterSubsystem.java new file mode 100644 index 0000000..bee064e --- /dev/null +++ b/docs/code_examples/BasicShooterSubsystem.java @@ -0,0 +1,115 @@ +// 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. + +package frc.robot.subsystems; + +import com.revrobotics.spark.SparkBase.PersistMode; +import com.revrobotics.spark.SparkBase.ResetMode; +import com.revrobotics.spark.SparkLowLevel.MotorType; +import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.config.SparkMaxConfig; + +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.SubsystemBase; + +// --8<-- [start:constants_import] +import static frc.robot.subsystems.ShooterConstants.*; +// --8<-- [end:constants_import] + +/** + * A basic shooter subsystem with a flywheel and a feeder. + * + *

The flywheel spins up to shooting speed first. Once it's up to speed, + * the feeder pushes the game piece into the flywheel to launch it. + */ +public class BasicShooterSubsystem extends SubsystemBase { + + // --8<-- [start:motors] + private final SparkMax flywheelMotor; + private final SparkMax feederMotor; + // --8<-- [end:motors] + + // --8<-- [start:constructor] + /** Creates a new BasicShooterSubsystem. */ + public BasicShooterSubsystem() { + // Create brushless NEO motors for the flywheel and feeder + flywheelMotor = new SparkMax(FLYWHEEL_MOTOR_ID, MotorType.kBrushless); + feederMotor = new SparkMax(FEEDER_MOTOR_ID, MotorType.kBrushless); + + // Configure the flywheel motor with a current limit + SparkMaxConfig flywheelConfig = new SparkMaxConfig(); + flywheelConfig.smartCurrentLimit(FLYWHEEL_CURRENT_LIMIT); + flywheelMotor.configure(flywheelConfig, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters); + + // Configure the feeder motor with a current limit and invert the direction + // so that positive voltage feeds game pieces toward the flywheel + SparkMaxConfig feederConfig = new SparkMaxConfig(); + feederConfig.inverted(true); + feederConfig.smartCurrentLimit(FEEDER_CURRENT_LIMIT); + feederMotor.configure(feederConfig, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters); + } + // --8<-- [end:constructor] + + // --8<-- [start:spinup] + /** + * Spins the flywheel up to speed without running the feeder. + * Call this before shoot() to avoid launching a game piece before the + * flywheel has reached full speed. + */ + public void spinUp() { + flywheelMotor.setVoltage(FLYWHEEL_SPINUP_VOLTAGE); + feederMotor.set(0); + } + // --8<-- [end:spinup] + + // --8<-- [start:shoot] + /** + * Runs the flywheel at full shoot speed and engages the feeder to launch + * the game piece. + */ + public void shoot() { + flywheelMotor.setVoltage(FLYWHEEL_SHOOT_VOLTAGE); + feederMotor.setVoltage(FEEDER_SHOOT_VOLTAGE); + } + // --8<-- [end:shoot] + + // --8<-- [start:stop] + /** Stops both the flywheel and feeder motors. */ + public void stop() { + flywheelMotor.set(0); + feederMotor.set(0); + } + // --8<-- [end:stop] + + // --8<-- [start:commands] + /** + * A command that spins the flywheel up to speed. Use this while the robot + * is moving into shooting position, then chain to shootCommand(). + */ + public Command spinUpCommand() { + return this.run(() -> spinUp()); + } + + /** + * A command that runs the full shoot sequence — flywheel at full speed and + * feeder engaged. Run spinUpCommand() first to avoid wasted shots. + */ + public Command shootCommand() { + return this.run(() -> shoot()); + } + + /** + * A command that stops all motors on this subsystem. Bind this as the default + * command so that motors are off when no other command is running. + */ + public Command stopCommand() { + return this.runOnce(() -> stop()); + } + // --8<-- [end:commands] + + @Override + public void periodic() { + // This method will be called once per scheduler run + } +} diff --git a/docs/code_examples/RobotTelemetry.java b/docs/code_examples/RobotTelemetry.java new file mode 100644 index 0000000..852684f --- /dev/null +++ b/docs/code_examples/RobotTelemetry.java @@ -0,0 +1,14 @@ +// Snippet file: Robot.java integration code for the Telemetry subsystem. +// These are not a standalone class — they show the relevant lines to add to Robot.java. + +// --8<-- [start:field-declaration] +public static Telemetry m_telemetry; +// --8<-- [end:field-declaration] + +// --8<-- [start:robot-init] +m_telemetry = new Telemetry(); //This must be initialized after all other robot subsystems +// --8<-- [end:robot-init] + +// --8<-- [start:periodic-update] +Robot.m_telemetry.update(); +// --8<-- [end:periodic-update] diff --git a/docs/code_examples/ShooterConstants.java b/docs/code_examples/ShooterConstants.java new file mode 100644 index 0000000..b12da2c --- /dev/null +++ b/docs/code_examples/ShooterConstants.java @@ -0,0 +1,33 @@ +// 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. + +package frc.robot.subsystems; + +/** + * Constants for the BasicShooterSubsystem. Keeping constants in a dedicated + * file next to the subsystem makes them easy to find and update without + * opening the subsystem code itself. + */ +// --8<-- [start:shooter_constants] +public final class ShooterConstants { + // CAN IDs for the shooter motors. Update these to match your robot's wiring. + public static final int FLYWHEEL_MOTOR_ID = 5; + public static final int FEEDER_MOTOR_ID = 6; + + // Current limits to protect the motors and prevent breakers from tripping. + // Lower the feeder limit since it does not need as much torque as the flywheel. + public static final int FLYWHEEL_CURRENT_LIMIT = 60; + public static final int FEEDER_CURRENT_LIMIT = 40; + + // Voltage values for each operating mode. These may need to be tuned + // based on your robot's specific construction and game piece weight. + public static final double FLYWHEEL_SPINUP_VOLTAGE = 8.0; + public static final double FLYWHEEL_SHOOT_VOLTAGE = 10.0; + public static final double FEEDER_SHOOT_VOLTAGE = 6.0; + + private ShooterConstants() { + // Utility class — do not instantiate + } +} +// --8<-- [end:shooter_constants] diff --git a/docs/code_examples/Telemetry.java b/docs/code_examples/Telemetry.java new file mode 100644 index 0000000..4f575b6 --- /dev/null +++ b/docs/code_examples/Telemetry.java @@ -0,0 +1,48 @@ +package frc.robot.subsystems; + +import edu.wpi.first.wpilibj.command.Subsystem; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; +import frc.robot.Robot; +import frc.robot.commands.*; + +/** + * Add your docs here. + */ +// --8<-- [start:full-class] +public class Telemetry extends Subsystem { + // Put methods for controlling this subsystem + // here. Call these from Commands. + + public Telemetry() { + // --8<-- [start:constructor-drivetrain] + // Drivetrain + SmartDashboard.putData("Reset Drive Encoder", new DriveResetEncoder()); + // --8<-- [end:constructor-drivetrain] + + // --8<-- [start:constructor-shooter] + // Shooter + SmartDashboard.putData("Shooter Up", new ShooterUp()); + SmartDashboard.putData("Shooter Down", new ShooterDown()); + SmartDashboard.putData("Shooter Up Auto", new ShooterUpAuto()); + // --8<-- [end:constructor-shooter] + } + + public void update() { + // --8<-- [start:update-drivetrain] + // Drivetrain + SmartDashboard.putNumber("Drive Encoder Count", Robot.m_drivetrain.getDriveEncoderCount()); + // --8<-- [end:update-drivetrain] + + // --8<-- [start:update-shooter] + // Shooter + SmartDashboard.putBoolean("Shooter Switch", Robot.m_shooter.isShooterSwitchClosed()); + // --8<-- [end:update-shooter] + } + + @Override + public void initDefaultCommand() { + // Set the default command for a subsystem here. + // setDefaultCommand(new MySpecialCommand()); + } +} +// --8<-- [end:full-class] diff --git a/docs/contributing.md b/docs/contributing.md index 16db81a..80fe298 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -39,13 +39,13 @@ Please use the [New Page Template](#new-page-template) [Click here to see tips on creating markdown documents](https://www.markdownguide.org/cheat-sheet/){target=_blank} -!!! Warning +!!! warning Make sure all documentation files end in `.md` -!!! Tip +!!! tip You can add to a certain tab by appending `/tab_name/` to the file name -!!! Tip +!!! tip Visit [Admonitions (call-out) references](https://squidfunk.github.io/mkdocs-material/reference/admonitions/) for a list off call-outs like this one. *** @@ -122,6 +122,6 @@ This section will help you learn to BLANK. - Info - Info 2 -!!! Tip +!!! tip This is a tip. ``` diff --git a/docs/examples/basic_elevator.md b/docs/examples/basic_elevator.md index eb8884a..66cba32 100644 --- a/docs/examples/basic_elevator.md +++ b/docs/examples/basic_elevator.md @@ -39,5 +39,5 @@ This subsystem will contain: - Info - Info 2 -!!! Tip +!!! tip This is a tip. diff --git a/docs/examples/basic_shooter.md b/docs/examples/basic_shooter.md index ded88d8..2b5d85f 100644 --- a/docs/examples/basic_shooter.md +++ b/docs/examples/basic_shooter.md @@ -1,33 +1,192 @@ -# [WIP] Basic Shooting Subsystem - +# Basic Shooter Subsystem -Subtitle - - -![Image Title](imageURL) +A basic, open-loop shooter subsystem with a flywheel and feeder using REV SparkMax motor controllers. ## Overview -This section will help you learn to BLANK. +This guide walks you through building a shooter subsystem from scratch. By the end you will have a working subsystem that can spin up a flywheel, feed a game piece, and stop — all controlled from driver buttons. + +The subsystem has two motors: + +- **Flywheel motor** — a brushless NEO that spins a wheel to launch game pieces +- **Feeder motor** — a brushless NEO that pushes game pieces from a hopper/indexer into the flywheel + + +**See the table of contents for a breakdown of each section.** + +*** + +## What You'll Need + +- Two [REV SparkMax](https://docs.revrobotics.com/brushless/revlib/sparkmax-overview){target=_blank} motor controllers with NEO brushless motors +- [REVLib](https://docs.revrobotics.com/brushless/revlib/overview){target=_blank} vendor dependency installed in your project + +!!! tip + If you haven't installed REVLib yet, go to **WPILib Command Palette → Manage Vendor Libraries → Install new library (online)** and paste in the REVLib JSON URL from the [REV documentation](https://docs.revrobotics.com){target=_blank}. + +*** + +## The Full Example Files + +The complete subsystem code is split across two files in the same folder: + +- [`docs/code_examples/ShooterConstants.java`](../../code_examples/ShooterConstants.java) — all tunable values +- [`docs/code_examples/BasicShooterSubsystem.java`](../../code_examples/BasicShooterSubsystem.java) — the subsystem logic + +Every snippet in this guide is pulled directly from those files. + +*** + +## Constants + +Constants are stored in their own file, `ShooterConstants.java`, placed in the same folder as the subsystem. This keeps all the numbers that need tuning in one easy-to-find place, and prevents `BasicShooterSubsystem.java` from becoming cluttered with magic numbers. + +!!! note + In a real robot project your subsystem files live in `src/main/java/frc/robot/subsystems/`. Put `ShooterConstants.java` in that same folder so it stays with the code it belongs to. + +```java title="ShooterConstants.java" +--8<-- "docs/code_examples/ShooterConstants.java:shooter_constants" +``` + +| Constant | Purpose | +|---|---| +| `FLYWHEEL_MOTOR_ID` | CAN ID of the flywheel SparkMax | +| `FEEDER_MOTOR_ID` | CAN ID of the feeder SparkMax | +| `FLYWHEEL_CURRENT_LIMIT` | Protects the flywheel motor and prevents breaker trips | +| `FEEDER_CURRENT_LIMIT` | The feeder needs less torque, so its limit is lower | +| `FLYWHEEL_SPINUP_VOLTAGE` | Voltage used during spin-up (slightly less than shoot) | +| `FLYWHEEL_SHOOT_VOLTAGE` | Full shooting voltage for the flywheel | +| `FEEDER_SHOOT_VOLTAGE` | Voltage used to push the game piece into the flywheel | + +!!! tip + These voltage values are starting points. You will almost certainly need to tune them on your real robot. Since they all live in `ShooterConstants.java`, you only need to open one file when tuning. + +To use the constants in `BasicShooterSubsystem.java` without prefixing every value with `ShooterConstants.`, add a static import at the top of the subsystem file: + +```java title="BasicShooterSubsystem.java" +--8<-- "docs/code_examples/BasicShooterSubsystem.java:constants_import" +``` + +This lets you write `FLYWHEEL_SHOOT_VOLTAGE` directly in the subsystem instead of `ShooterConstants.FLYWHEEL_SHOOT_VOLTAGE`. + +*** + +## Declaring the Motors + +At the class level, declare `private final` fields for each SparkMax. Marking them `final` prevents them from accidentally being reassigned later. + +```java title="BasicShooterSubsystem.java" +--8<-- "docs/code_examples/BasicShooterSubsystem.java:motors" +``` + +*** + +## The Constructor -**See table of contents for a breakdown of this section.** +The constructor instantiates each SparkMax and applies a configuration to it. Configuration lets you set things like current limits and motor inversion before the robot runs. + +```java title="BasicShooterSubsystem.java" +--8<-- "docs/code_examples/BasicShooterSubsystem.java:constructor" +``` + +!!! note + The feeder motor is inverted so that positive voltage always means "feed toward the flywheel." Without this, you would need to use negative voltages for the feeder, which is confusing to read. + +!!! warning + `ResetMode.kResetSafeParameters` clears any previously saved configuration on the SparkMax before applying the new one. This ensures the motor controller is in a known state each time the robot starts. + +*** + +## Shooter Methods + +### spinUp() + +Spins the flywheel to a lower spin-up voltage while keeping the feeder stopped. Use this to get the flywheel up to speed *before* feeding a game piece. + +```java title="BasicShooterSubsystem.java" +--8<-- "docs/code_examples/BasicShooterSubsystem.java:spinup" +``` + +!!! tip + Spinning up before shooting prevents the flywheel from slowing down the moment a game piece enters it. This gives you a more consistent launch every time. *** -## Section One +### shoot() -- Some info -- Some other into - - Some sub info +Runs the flywheel at full shoot voltage/speed and engages the feeder to push the game piece(s) into the flywheel. -### Section One Subsection +```java title="BasicShooterSubsystem.java" +--8<-- "docs/code_examples/BasicShooterSubsystem.java:shoot" +``` *** -## Section Two +### stop() + +Stops both motors. Call this when the shoot button is released or when a command ends. + +```java title="BasicShooterSubsystem.java" +--8<-- "docs/code_examples/BasicShooterSubsystem.java:stop" +``` + +*** + +## Command Factories + +WPILib's [command-based framework](https://docs.wpilib.org/en/stable/docs/software/commandbased/index.html){target=_blank} lets you convert methods into [`Command`](https://docs.wpilib.org/en/stable/docs/software/commandbased/commands.html){target=_blank} objects that the scheduler can run, interrupt, and chain together. Each factory method below wraps one of the shooter methods in a command. This allows you to easily bind them to buttons and use them in more complex command sequences later on. +Commands can also be written as separate classes, but for simple one-line methods like these, factory methods are a quick and easy way to get commands without extra boilerplate. Separate commmand classes are better when complex logic, multiple steps, or additional requirements are involved. + +```java title="BasicShooterSubsystem.java" +--8<-- "docs/code_examples/BasicShooterSubsystem.java:commands" +``` + +- `this.run(...)` creates a command that calls the lambda **every robot loop** (20 ms) for as long as the command is scheduled. +- `this.runOnce(...)` creates a command that calls the lambda **once** and then immediately finishes. This is appropriate for `stop()` since you only need to set the motors to 0 one time. + +!!! note + Both `run` and `runOnce` automatically require `this` subsystem, so the scheduler will cancel any conflicting command that also requires this subsystem when one of these is scheduled. + +*** + +## Hooking It Up in RobotContainer + +Instantiate the subsystem and bind buttons in `RobotContainer.java`. See the WPILib docs on [binding commands to triggers](https://docs.wpilib.org/en/stable/docs/software/commandbased/binding-commands-to-triggers.html){target=_blank} for more options. + +### Instantiate the Subsystem + +```java title="RobotContainer.java" +private final BasicShooterSubsystem m_shooter = new BasicShooterSubsystem(); +``` + +### Set a Default Command + +Set `stopCommand()` as the default so motors turn off whenever no button is held. + +```java title="RobotContainer.java" +m_shooter.setDefaultCommand(m_shooter.stopCommand()); +``` + +### Bind Buttons + +A typical workflow binds spin-up to one button and the full shoot sequence to another, so the driver can pre-spin the flywheel before feeding balls into the shooter. This ensures the flywheel is up to speed and prevents it from slowing down when a game piece enters. + +```java title="RobotContainer.java" +// Hold the right bumper to spin the flywheel up to speed +new JoystickButton(m_controller, Button.kRightBumper.value) + .whileTrue(m_shooter.spinUpCommand()); + +// Hold the left bumper to spin up AND feed (full shoot) +new JoystickButton(m_controller, Button.kLeftBumper.value) + .whileTrue(m_shooter.shootCommand()); +``` + +!!! tip + `whileTrue` schedules the command when the button is pressed and cancels it (triggering the default command) when the button is released. This means the motors will automatically stop when the driver lets go. + +*** -- Info -- Info 2 +## Next Steps -!!! Tip - This is a tip. +- **Add a sensor** — use a beam break or limit switch to detect when a game piece is loaded, and automate the spin-up/feed sequence. +- **Add velocity control** — use a PID controller and the built-in SparkMax encoder to shoot at a precise RPM instead of open-loop voltage. See the [PID Shooter](pid_shooter.md) guide for details. diff --git a/docs/examples/pid_elevator.md b/docs/examples/pid_elevator.md index 6469618..f927e63 100644 --- a/docs/examples/pid_elevator.md +++ b/docs/examples/pid_elevator.md @@ -29,5 +29,5 @@ This section will help you learn to BLANK. - Info - Info 2 -!!! Tip +!!! tip This is a tip. diff --git a/docs/examples/pid_shooter.md b/docs/examples/pid_shooter.md index ee60e9e..f89ffb3 100644 --- a/docs/examples/pid_shooter.md +++ b/docs/examples/pid_shooter.md @@ -29,5 +29,5 @@ This section will help you learn to BLANK. - Info - Info 2 -!!! Tip +!!! tip This is a tip. diff --git a/docs/index.md b/docs/index.md index c08655f..841fe92 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,11 +2,11 @@ [![FIRST](assets/images/logos/first.png)](https://www.firstinspires.org/robotics/frc/){target=_blank} -The unofficial FIRST Robotics Competition Java Programming Tutorial. +The unofficial FIRST Robotics Competition Java Programming Tutorial for FRC Teams 321 and 427, the Robolancers -!!! Info - Updated for the 2021 Season - Last updated: 9/30/21 +!!! info + Updated for the 2026 Season + Last updated: 1/9/2026 **Disclaimer:** Some screenshots may have different colors, icons, more/less folders/files than you due to themes or personal settings. This is normal and should not impact the tutorial. If you still have any questions please contact us. @@ -18,6 +18,7 @@ The unofficial FIRST Robotics Competition Java Programming Tutorial. | Name | Team | Team Role | | :----------------------------------------------: | :--------------------------------------------: | :-------: | +| Carl Stanton | [321](https://robolancers.com/){target=_blank} | Mentor | [Tayler Uva](https://Tayler.Tech){target=_blank} | [3255](https://SuperNURDs.com/){target=_blank} | Coach | | Isaac Sayasane | [3255](https://SuperNURDs.com/){target=_blank} | Alumni | | Sharon Riggs | [6995](https://frc6995.org){target=_blank} | Mentor | diff --git a/docs/programming/AKitStructureReference.md b/docs/programming/AKitStructureReference.md new file mode 100644 index 0000000..7f09324 --- /dev/null +++ b/docs/programming/AKitStructureReference.md @@ -0,0 +1,328 @@ +# AdvantageKit Code Structure Reference + +This document contains a quick reference for how we structure our code. +It should be used when planning out the structure of a subsystem. + +## Basic Layout + +```mermaid +flowchart TD + subgraph Subsystem + A[Subsystem] + end + Subsystem-->IO + subgraph IO + direction LR + B[IO]-->E[IOInputs] + end + IO-->IOImpl + subgraph IOImpl + C[IO Implementation Real] + D[IO Implementation Sim] + end +``` + +This diagram shows the basic structure of an AKit Subsystem. +It includes 3 layers: + +### Subsystem + +- The "subsystem" layer is a class which `extends SubsystemBase`. +- This class should contain methods that return Commands for this subsystem. +- This class should contain all higher-level control flow within this mechanism. + - For instance, it could contain a `SwerveDriveOdometry` object to track a drivetrain's position. + - It might contain information about the current target of a mechanism, or whether or not the mechanism has homed its position yet. + - *Generally, this information should all be **"processed" information** that we derive from our IOInputs. +- The `Subsystem` file will contain one `IO` instance and one `IOInputs` instance, conventionally called `io` and `inputs` respectively. + +### IO + +- The "IO" layer defines the interface with our hardware, as well as all the values we will log. +- This includes an `interface` called `SubsystemIO` which defines a set of methods to interact with the hardware, such as `setVoltage` or `getPosition`. +However, it doesn't define *how* to do them, because that's specific to each implementation. +The interface just provides a template. +- This class will also include an `updateInputs` method, which takes in an `IOInputs` object and updates it with the latest sensor data from the mechanism. +- The `IOInputs` object is a class that contains various measurements and sensor data about the mechanism, like current draw, encoder position, and voltage output. +- It is marked with `@AutoLog` which means all the inputs and outputs for the subsystem will be automatically recorded in the log, so we can play it back later. + +### IO Implementations + +- These are classes which `implement` the aforementioned `IO` class. +- This means they will specify how to do all of the methods defined in the `IO` class. +- Generally we will have 2 types of `IOImplementation`, `IOSim` and `IOReal`. + - `IOReal` defines its methods to command real hardware to have real outputs. + - `IOSim` often looks similar to `IOReal`, but will have some behaviour added to fake the behaviour of real world physics. + This can include a physics sim which approximates physical behaviour, setting outputs which we can't sim to a default value (like temperature), or other ways of "faking" real world behaviour. +- `IOImplementation`s will contain the actual objects such as `TalonFX`s (for `IOReal`) or `DCMotorSim`s (for `IOSim`). + +## Intake Example + +A cad render of the intake from our 2025 offseason robot + +This example will cover how the code for an intake such as the one above might be set up. + +```mermaid +flowchart TD + subgraph Subsystem + A[IntakeSubsystem] + end + Subsystem-->IO + subgraph IO + direction LR + B[IntakeIO]-->E[IntakeIOInputs] + end + IO-->IOImpl + subgraph IOImpl + C[IntakeIOReal] + D[IntakeIOSim] + end +``` + +Let's start by defining the methods in the `IntakeIO` interface. +There are two motors on this slapdown intake--one that controls the pivot, and one that controls the rollers. +The intake needs to set its position (extended or retracted), so we will need a `setAngle(Rotation2d angle)` method. +We will also need a way to set the rollers' output, so let's add a `setRollerVoltage(double volts)` method. +For convenience, let's add a method `stop()` that calls `setRollerVoltage()` with a voltage of 0. + +We also need to add our `IntakeIOInputs` to the `IntakeIO` file. +This should contain all of the sensor information we need to know about our intake so that we can debug its behaviour with logs. +Then we can add our logged fields for the motor. + +Here is a list of common logged fields for motors: + +- Velocity (Often but not always in rotations per second) + - This is the main field we care about to see if the motor is moving. +- Current draw (Amps) + - This lets us see if the motor is stalling (trying to move but stuck) as well as how much energy the motor is using. +- Temperature (Celsius) + - If a motor gets too hot it will turn itself off to protect its electronics. + This lets us see if we are having issues related to that. + In addition, motors are less efficient as they heat up. +- Voltage (Voltage) + - This lets us see how much we are commanding our motors to move. +- Position (Rotations or inches, often after a gear reduction) + - This lets us track the position of the motor and its corresponding mechanism. + +We can add all of these values into our `IntakeIOInputs` class. + +Finally, add a `updateInputs(IntakeIOInputs inputs)` method that our `IOImplementation`s can call to record these values. +Our `IntakeIO` file should look something like this now: + +```java +// Imports go here + +public interface IntakeIO { + @AutoLog + public class IntakeIOInputs { + + // Pivot motor values + public double pivotVelocityRotationsPerSec; + public double pivotCurrentDrawAmps; + public double pivotTemperatureCelsius; + public double pivotVoltage; + public Rotation2d pivotMotorPosition; + + // Roller motor values + public double rollerVelocityRotationsPerSec; + public double rollerCurrentDrawAmps; + public double rollerTemperatureCelsius; + public double rollerVoltage; + } + + // Methods that IOImplementations will implement + public void setAngle(Rotation2d angle); + + public void setRollerVoltage(double volts); + + // Note the use of "default" + // This means that we don't have to re-implement this method in each IOImplementation, because it will default to + // whatever the specific implementation of setVoltage() is + public default void stop() { + setRollerVoltage(0); + } + + public void updateInputs(IntakeIOInputs inputs); +} +``` + +Next, let's write `IntakeIOReal`. +This will contain all of the hardware we want to interact with on the real robot. + +First, we will need to define the hardware we want to use. +In this case, they will be two `TalonFX`s. + +```java +private final TalonFX pivot = new TalonFX(IntakeSubsystem.PIVOT_MOTOR_ID); +private final TalonFX roller = new TalonFX(IntakeSubsystem.ROLLER_MOTOR_ID); +``` + +Next we will need to implement each of the methods from `IntakeIO`. +Each should `@Override` the template method. +For the sake of brevity, I won't cover that in detail here. *MAKE SUBSYSTEM WALKTHROUGH AND LINK HERE* + +In the end you should have something like: + +```java +public class IntakeIOReal implements IntakeIO { + private final TalonFX pivot = new TalonFX(IntakeSubsystem.PIVOT_MOTOR_ID); + private final TalonFX roller = new TalonFX(IntakeSubsystem.ROLLER_MOTOR_ID); + + @Override + public void updateInputs(IntakeIOInputs inputs) { + // Note that the exact calls here are just examples, and might not work if copy-pasted + + inputs.pivotVelocityRotationsPerSec = pivot.getVelocity(); + inputs.pivotCurrentDrawAmps = pivot.getStatorCurrent(); + inputs.pivotTemperatureCelsius = pivot.getDeviceTemp(); + inputs.pivotVoltage = pivot.getMotorVoltage(); + inputs.pivotMotorPosition = pivot.getMotorPosition(); + + // Roller motor values + inputs.rollerVelocityRotationsPerSec = roller.getVelocity(); + inputs.rollerCurrentDrawAmps = roller.getStatorCurrent(); + inputs.rollerTemperatureCelsius = roller.getDeviceTemp(); + inputs.rollerVoltage = roller.getMotorVoltage(); + } + + @Override + public void setRollerVoltage(double volts) { + roller.setVoltage(volts); + } + + // Note how we don't need to define stop() because it has a default implementation that does what we want + + @Override + public void setAngle(Rotation2d angle) { + pivot.setAngle(angle); + } +} +``` + +We can make a similar class for `IntakeIOSim`, although instead of getting motor outputs directly we would have to use `motor.getSimState()`. +For more information about that, check the [CTRE docs](https://pro.docs.ctr-electronics.com/en/stable/docs/api-reference/simulation/simulation-intro.html). + +Finally, let's write the `IntakeSubsystem` class. +This class will include an instance of `IntakeIO` and an instance of `IntakeIOInputs`. +It will also contain Command factories to allow the rest of our code to interface with it. + +Add the io and io inputs to the class: + +```java +// Snip imports + +public class IntakeSubsystem extends SubsystemBase { + private final IntakeIO io; + private final IntakeIOInputsAutoLogged inputs = new IntakeIOInputsAutoLogged(); + + public IntakeSubsystem(IntakeIO io) { + // Pass in either the sim io or real io + this.io = io; + } +} +``` + +Then we can add a few Command factories to control the subsystem: + +```java +public Command intake(double volts) { + return Commands.run(() -> { + io.setAngle(INTAKE_ANGLE); + io.setRollerVoltage(volts); + }); +} + +public Command stop() { + return Commands.runOnce(() -> { + io.setAngle(RETRACTED_ANGLE); + io.stop(); + }); +} +``` + +Finally, let's add our `periodic()` method to update and log our inputs. + +```java +@Override +public void periodic() { + io.updateInputs(); + // Make sure to import the "littletonRobotics" Logger, not one of the other ones. + Logger.processInputs("Intake", inputs); +} +``` + +Overall, `IntakeSubsystem` should roughly look like: + +```java +// Snip imports + +public class IntakeSubsystem extends SubsystemBase { + IntakeIO io; + IntakeIOInputsAutoLogged inputs; + + public IntakeSubsystem(IntakeIO io) { + // Pass in either the sim io or real io + this.io = io; + inputs = new IntakeIOInputsAutoLogged(); + } + + public Command intake(double volts) { + return Commands.run(() -> { + io.setAngle(INTAKE_ANGLE); + io.setRollerVoltage(volts); + }); + } + + public Command stop() { + return Commands.runOnce(() -> { + io.setAngle(RETRACTED_ANGLE); + io.stop(); + }); + } + + @Override + public void periodic() { + io.updateInputs(); + // Make sure to import the "littletonRobotics" Logger, not one of the other ones. + Logger.processInputs("Intake", inputs); + } +} +``` + +### More complex subsystems + +The intake is a very simple to program subsystem, but more complex ones exist. +A swerve drive might have the following set of classes: + +```mermaid +flowchart TD + subgraph Subsystem + A[SwerveSubsystem] + end + + Subsystem--- 4x -->SwerveModuleIO + subgraph SwerveModuleIO + direction LR + B[SwerveModuleIO]-->E[SwerveModuleIOInputs] + end + SwerveModuleIO-->SwerveModuleIOImpl + subgraph SwerveModuleIOImpl + C[SwerveModuleIOReal] + D[SwerveModuleIOSim] + end + + Subsystem--->GyroIO + subgraph GyroIO + direction LR + X[GyroIO]-->GyroIOInputs + end + GyroIO-->GyroIOImpl + subgraph GyroIOImpl + GyroIOReal + GyroIOSim + end +``` + +This means that we will have one `SwerveSubsystem` class. +Within that class, we will have 4 instances of `SwerveModuleIO` and its corresponding `SwerveModuleIOInputs`, one for each module. +We will also have an instance of `GyroIO` and `GyroIOInputs`. +The `SwerveSubsystem` file handles coordinating and combining data from these `IO`s, and each `IO` handles turning the control signals from the `Subsystem` into motion in the hardware (or sim!). diff --git a/docs/programming/AdvantageKit.md b/docs/programming/AdvantageKit.md new file mode 100644 index 0000000..3984316 --- /dev/null +++ b/docs/programming/AdvantageKit.md @@ -0,0 +1,60 @@ +# AdvantageKit + +> AdvantageKit is a logging framework developed by team 6328 (Mechanical Advantage). + +*** + +## What is Logging? + +Logging is recording some or all of the state of the robot — such as the current values of variables, inputs and outputs, and which Commands are running — so that it can be played back and analyzed later. + +## Why Log? + +Logging helps with debugging by letting you see exactly what was happening to the robot and what it was doing when something broke. This is especially useful at competition, where you have limited time and testing ability to diagnose problems. + +!!! example "Real-world example" + At Houston 2023, team 8033 had an issue in their second quals match where the grabber stopped responding to input. Using the logs from that match, they could see that the sensor readings of the grabber had stopped updating, which pointed to the CAN bus connection to the grabber having broken — a diagnosis that would have been nearly impossible without logs. + +## Why AdvantageKit? + +AdvantageKit logs **every input and output** to and from the robot. This means the full state of the robot can be perfectly recreated from a log file. + +Diagram of AdvantageKit data flow + +It also means that the same inputs can be run through modified code to simulate how the robot *would have* responded — AdvantageKit calls this **replay**. For example, team 6328 used replay to diagnose a vision-tracking issue: they adjusted their tracking algorithm, then confirmed with replay that the change would have produced the correct outputs given the original match inputs. + +AdvantageKit is a mature, actively maintained framework and is closely integrated with **AdvantageScope**, a log and simulation viewer also built by 6328. + +## Drawbacks + +- **Performance overhead** — logging every input and output uses CPU time each loop on the roboRIO. +- **Architecture change** — AdvantageKit requires an IO layer beneath each subsystem, which takes additional effort to write. However, this same IO layer makes simulating subsystems significantly easier, so it is generally a worthwhile trade-off. + +!!! note + 8033-specific usage of AdvantageKit features is covered in more detail in the following lessons. + +*** + +## Resources + +| Resource | Link | +|---|---| +| AdvantageKit docs | [docs.advantagekit.org](https://docs.advantagekit.org/) | +| AdvantageKit repository | [github.com/Mechanical-Advantage/AdvantageKit](https://github.com/Mechanical-Advantage/AdvantageKit) | +| AdvantageScope log viewer | [github.com/Mechanical-Advantage/AdvantageScope](https://github.com/Mechanical-Advantage/AdvantageScope) | +| 6328 logging talk | [6328 logging talk (YouTube)](https://youtu.be/mmNJjKJG8mw) | + +## Examples + +- [6328 2023 robot code](https://github.com/Mechanical-Advantage/RobotCode2023) +- [3476 2023 code](https://github.com/FRC3476/FRC-2023) +- [8033 2025 code](https://github.com/HighlanderRobotics/Reefscape) + +### Exercises + +- Follow this tutorial to add AdvantageKit to your kitbot code. *(coming soon)* + +### Notes + +- See the [AdvantageKit Structure Reference](AKitStructureReference.md) article for more on the IO layer structure +- [Here](https://drive.google.com/drive/folders/1qNMZ7aYOGI31dQNAwt7rhFo97NR7mxtr) are some logs from our 2023-2024 season diff --git a/docs/programming/Elastic.md b/docs/programming/Elastic.md new file mode 100644 index 0000000..8f557fc --- /dev/null +++ b/docs/programming/Elastic.md @@ -0,0 +1,158 @@ +# Using Elastic + + + +## Overview + +In this section we will be going over + +1. Using and organizing the Elastic dashboard +2. Creating the Telemetry subsystem and adding buttons and data to be viewed in Elastic + +*** + +## What is Elastic + +- **Elastic** is a modern FRC robot dashboard that displays robot data during development and competition +- It can display widgets like graphs, camera streams, boolean indicators, and number readouts +- Elastic supports **tabs** to organize widgets into separate pages +- It reads data from **NetworkTables**, which is the same system used by SmartDashboard — no changes to robot code are needed to switch from Shuffleboard or SmartDashboard to Elastic + +!!! tip + Elastic is available as a standalone download from [its GitHub releases page](https://github.com/Gold872/elastic-dashboard/releases){target=_blank}. It can be run independently or configured to launch automatically from the Driver Station. It is also included in the WPILib installation and can be found in the WPILib tools directory. + +## What is Telemetry + +- **Telemetry** is where we add data to be viewed or command buttons on **Elastic** +- For this section of our tutorial we will be adding switch and encoder data to **Elastic** +- The Java code to publish telemetry data uses `SmartDashboard`, which writes to NetworkTables — Elastic reads that data automatically + +## Creating the Telemetry Subsystem + +!!! abstract "" + **1)** Create a new **Subsystem** called **Telemetry** + +!!! abstract "" + **2)** Create a constructor for the **Telemetry** class + + - The constructor is where we will create buttons for Elastic + +!!! abstract "" + **3)** Inside type: + + ```java title="Telemetry.java" + --8<-- "docs/code_examples/Telemetry.java:constructor-drivetrain" + ``` + +!!! abstract "" + **4)** Create a public method called update + + - This method will run periodically in Robot.java to update sensor data in Elastic + +!!! abstract "" + **5)** Inside type: + + ```java title="Telemetry.java" + --8<-- "docs/code_examples/Telemetry.java:update-drivetrain" + ``` + +!!! abstract "" + **6)** Do the same for the **getDriveEncoderDistance** method + +!!! abstract "" + **7)** Try adding the **Shooter** Subsystem commands and sensor methods where they should be + +??? Example + + Your full **Telemetry.java** should look like this + + ```java title="Telemetry.java" + --8<-- "docs/code_examples/Telemetry.java:full-class" + ``` + +## Adding The Telemetry Subsystem to Robot.java + +!!! abstract "" + **1)** When adding **Telemetry** to **Robot.java**, in **robotInit** we must add **Telemetry** after the other subsystems + + - This is because the **Telemetry** subsystem relies on methods that are created in other subsystems before it + - It can be added before or after **OI** since they don't use methods from each other + + !!! note "Why not robotPeriodic?" + `robotPeriodic` runs at the very start of every loop, *before* the command scheduler runs. Putting `update()` there means the displayed values would lag one loop cycle behind the current robot state. By placing `update()` in mode-specific methods (`disabledPeriodic`, `autonomousPeriodic`, `teleopPeriodic`), the dashboard always reflects the most current values after all commands for that loop have executed. + +!!! abstract "" + + **2)** It is **important** that we add the **update** method to **disabledPeriodic, autonomousPeriodic**, and **teleopPeriodic** so that **Elastic** is always being updated with information on our sensors. + +??? Example + + The code you typed before **robotInit** should be this + + ```java title="Robot.java" + --8<-- "docs/code_examples/RobotTelemetry.java:field-declaration" + ``` + + The code you typed in **robotInit** should be this + + ```java title="Robot.java" + --8<-- "docs/code_examples/RobotTelemetry.java:robot-init" + ``` + + The code you typed in **disabledPeriodic, autonomousPeriodic**, and **teleopPeriodic** should be this + + ```java title="Robot.java" + --8<-- "docs/code_examples/RobotTelemetry.java:periodic-update" + ``` + +## Opening Elastic + +After saving and deploying your code: + +**Option A — Launch Elastic manually:** + +1. Open the Elastic application directly. +2. Elastic will automatically connect to the robot's NetworkTables server when the Driver Station connects to the robot. + +**Option B — Configure the Driver Station to open Elastic automatically:** + +1. In the Driver Station, click the **gear icon** on the left side to open settings. +2. Under **Dashboard Type**, select **Elastic** (or browse to the Elastic executable if it does not appear in the list). +3. The Driver Station will launch Elastic automatically when it starts. + +Once Elastic is open and the robot is connected, you should see your NetworkTables keys appear in the widget picker. + +## Using Elastic + +### Adding Widgets + +- **Right-click** on any empty area of a tab to open the widget menu. +- Browse or search the list of available NetworkTables keys (the values your robot is publishing via `SmartDashboard`). +- Click a key to place it as a widget on the tab. + +!!! tip + Each key published with `SmartDashboard.putData`, `putNumber`, or `putBoolean` will appear as a separate widget option. Commands registered with `putData` appear as buttons you can click to schedule them. + +### Tabs + +Tabs let you separate widgets into logical groups — for example one tab for drivetrain data and another for shooter data. + +- Click the **+** button in the tab bar to add a new tab. +- Double-click a tab name to rename it. +- Click a tab to switch to it. + +### Moving and Resizing Widgets + +- Click and drag a widget's **title bar** to reposition it. +- Drag from a widget's **corner or edge** to resize it. +- Right-click a widget for additional options such as renaming or changing its display type. + +### Saving Layouts + +Elastic saves your layout automatically, but you can also explicitly save and load layout files to share with the team. + +- Go to **File → Save Layout** to save the current arrangement to a `.json` file. +- Go to **File → Open Layout** to restore a previously saved layout. + +!!! tip + Save your layout file in your robot project's repository so the whole team can use the same dashboard configuration. diff --git a/docs/programming/autonomous.md b/docs/programming/autonomous.md index bed8a88..50f30ef 100644 --- a/docs/programming/autonomous.md +++ b/docs/programming/autonomous.md @@ -4,7 +4,7 @@ ## Overview -In this section we will be going over +In this section we will be going over: 1. Creating an autonomous command group 2. Using RobotPreferences to quickly change our autonomous values @@ -20,51 +20,62 @@ In this section we will be going over - An autonomous command is a command that is ran during "autonomous mode" under the **autonomousInit** method in **Robot.java** - It could be a single **command** or a **command group** -- It's especially helpful to have if you don't have any cameras to drive the robot during a -"sandstorm" period (2019 game mechanic where the drivers couldn't see during the pre tele-op phase) -- For this tutorial we will create an autonomous **command group** that makes the robot drive forward 5 feet, wait 5 seconds, and then pitch the shooter up during autonomous +- It's especially helpful to have if you don't have any cameras to drive the robot during autonomous (rare, but does happen) +- For this tutorial we will create a simple autonomous **command ** that makes the robot drive forward slightly. ## Creating Commands For Autonomous - Since we can't control our robot during an autonomous command we will want to create commands that allow the robot to move independently of a driver -## Creating the DriveDistance Command +## Creating a basic Autonomous Command -!!! summary "" - **1)** Create a new command called **DriveDistance** +!!! abstract "" + **1)** Create a new command called **AutoCommand** using the `create new class/command` feature in Vscode. + -!!! summary "" +!!! abstract "" **2)** Before the constructor create a **double** called **distance** + ```java + private Double distance; + ``` - We will use this to tell the command to finish when the robot drives the inputted distance + + **3)** Also create a **Timer** called `runtime`. + + ```java + private Time runTime; + ``` + + - This will be used to control how long the robot will move for. -!!! summary "" - **3)** In the **DriveDistance** constructor add a **double** parameter called **inches** +!!! abstract "" + **3)** In the **AutoCommand** constructor add a **DriveSubsystem** parameter called **driveSubsystem** -!!! summary "" +!!! abstract "" **4)** Inside type: ```java distance = inches; ``` -!!! summary "" +!!! abstract "" **5)** In **initialize** add our **resetDriveEncoder** method - We want to reset the encoder before we drive so that it counts the distance from zero -!!! summary "" +!!! abstract "" **6)** In **execute** add our **arcadeDrive** method and change the **moveSpeed** parameter to a **RobotPreference** named **driveDistanceSpeed** and **rotateSpeed** to 0.0 - We only want to drive the robot forward; a **RobotPreference** will help us tune the drive speed -!!! summary "" +!!! abstract "" **7)** In **isFinished** type: ```java return Robot.m_drivetrain.getDriveEncoderDistance() == distance; ``` -!!! summary "" +!!! abstract "" **8)** In **end** stop the **Drivetrain** and call **end** in **interrupted** ??? Example @@ -74,50 +85,42 @@ In this section we will be going over ```java package frc.robot.commands; - import edu.wpi.first.wpilibj.command.Command; - import frc.robot.Robot; + import edu.wpi.first.wpilibj2.command.Command; + import frc.robot.subsystems.Drivetrain; import frc.robot.RobotPreferences; public class DriveDistance extends Command { - private double distance; + private final Drivetrain drivetrain; + private final double distance; - public DriveDistance(double inches) { - // Use requires() here to declare subsystem dependencies - // eg. requires(chassis); - requires(Robot.m_drivetrain); - distance = inches; + public DriveDistance(Drivetrain drivetrain, double inches) { + this.drivetrain = drivetrain; + this.distance = inches; + addRequirements(drivetrain); } - // Called just before this Command runs the first time @Override - protected void initialize() { - Robot.m_drivetrain.resetDriveEncoder(); + public void initialize() { + drivetrain.resetDriveEncoder(); } // Called repeatedly when this Command is scheduled to run @Override - protected void execute() { - Robot.m_drivetrain.arcadeDrive(RobotPreferences.driveDistanceSpeed(), 0.0); + public void execute() { + drivetrain.arcadeDrive(RobotPreferences.driveDistanceSpeed(), 0.0); } // Make this return true when this Command no longer needs to run execute() @Override - protected boolean isFinished() { - return Robot.m_drivetrain.getDriveEncoderDistance() == distance; + public boolean isFinished() { + return drivetrain.getDriveEncoderDistance() >= distance; } // Called once after isFinished returns true @Override - protected void end() { - Robot.m_drivetrain.arcadeDrive(0.0, 0.0); - } - - // Called when another command which requires one or more of the same - // subsystems is scheduled to run - @Override - protected void interrupted() { - end(); + public void end(boolean interrupted) { + drivetrain.arcadeDrive(0.0, 0.0); } } ``` @@ -134,10 +137,10 @@ In this section we will be going over - We will create an **Autonomous command group** with the **DriveDistance** command and the **ShooterPitchUp** command -!!! summary "" +!!! abstract "" **1)** Create a new **Command Group** named **Autonomous** -!!! summary "" +!!! abstract "" **2)** In the constructor type ```java @@ -152,30 +155,30 @@ In this section we will be going over - In order to add timing in between our **commands** in our **command groups** we will need to create a **DoDelay** command - Unlike regular **delays** the **DoDelay** command will not stall our robot, but wait a certain amount of time before running a command -!!! summary "" +!!! abstract "" **1)** Create a new command called **DoDelay** -!!! summary "" +!!! abstract "" **2)** Before the constructor add two private **doubles** called **expireTime** and **timeout** -!!! summary "" +!!! abstract "" **3)** In the constructor add a **double** called **seconds** in the parameter -!!! summary "" +!!! abstract "" **4)** Inside the constructor set **timeout** equal to **seconds** -!!! summary "" +!!! abstract "" **5)** Create a protected **void** method called **startTimer** -!!! summary "" +!!! abstract "" **6)** Inside set **expireTime** equal to **timeSinceInitialized** + **timeout** - This will let the robot know how much time will have passed since the command was initialized when it finishes -!!! summary "" +!!! abstract "" **7)** In **initialized** add our **startTimer** method -!!! summary "" +!!! abstract "" **8)** In **isFinished** return **timeSinceInitialized** is greater or equal to **expireTime** ??? Example @@ -234,7 +237,7 @@ In this section we will be going over ## Adding the DoDelay Command to Autonomous.java -!!! summary "" +!!! abstract "" - Add our **DoDelay** command in between **DriveDistance** and **ShooterPitchUp** with a **RobotPreference** called **autoDelay** ??? Example @@ -277,8 +280,9 @@ In this section we will be going over - In **Robot.java** under **autonomousInit** find **m_autonomousCommand = m_chooser.getSelected();** and change it to - - + !!! note "Why not use the chooser?" + `SendableChooser` allows selecting between multiple autonomous routines from the dashboard at match start, which is useful when you have several autonomous options. For this tutorial we only have one autonomous routine, so using the chooser would add boilerplate (creating options, registering them, fetching the selection) without any benefit. Once you have multiple routines worth choosing from, replacing this line with a `SendableChooser` is a natural next step. + ```java public void autonomousInit() { m_autonomousCommand = new Autonomous(); @@ -287,7 +291,7 @@ In this section we will be going over ## Testing Our Autonomous Command -- Now that we have finished coding our **Autonomous** command deploy code and add our new **RobotPreferences** to the widget on the **ShuffleBoard** +- Now that we have finished coding our **Autonomous** command deploy code and add our new **RobotPreferences** to the widget in **Elastic** - We have three preferences that change our autonomous behavior **driveDistanceSpeed**, **autoDriveDistance** and **autoDelay** - **driveDistanceSpeed** will determine the **direction** and how **fast** the robot drives - **autoDriveDistance** will determine how many **inches** the robot drives **forward** or **backward** diff --git a/docs/programming/deploying.md b/docs/programming/deploying.md index cee592c..6b20bf1 100644 --- a/docs/programming/deploying.md +++ b/docs/programming/deploying.md @@ -24,25 +24,25 @@ To deploy code, first make sure your computer is connected to the robot in **ON ### Software -!!! Note +!!! note Make sure your team number in **wpilib_preferences.json** in the **.wpilib** folder is set to the same team number your roboRIO was programmed for (it should be the number you set when creating the project and you will NOT need to check this every time as it should not change by itself). -!!! summary "" +!!! abstract “” **1)** Select the **W icon** from the tab bar or use the shortcut by holding down **Ctrl+Shift+P** at the same time. (Replace ctrl with cmd on macOS) ![](../assets/images/deploying/w_icon.png) -!!! summary "" +!!! abstract “” **2)** Type and hit enter or select: WPILib: Deploy Robot Code ![](../assets/images/deploying/deploy_command.png) -!!! Tip +!!! tip Alternatively you can do one of the following: - Use **Shift+F5** at any time to deploy. (you may also need to hold fn depending on your computer configuration) - - Right-click on the build.gradle file in the project hierarchy and select "Build Robot Code” - - Open the shortcut menu indicated by the ellipses in the top right corner of the VS Code window and select "Build Robot Code" + - Right-click on the build.gradle file in the project hierarchy and select “Build Robot Code” + - Open the shortcut menu indicated by the ellipses in the top right corner of the VS Code window and select “Build Robot Code” *** @@ -53,4 +53,11 @@ To deploy code, first make sure your computer is connected to the robot in **ON 3. Try moving the joysticks on your controller when enabled. 1. If it doesn’t, check your port numbers for your controller, axes, and motor controllers - \ No newline at end of file +!!! tip "Reading the error log" + If the robot does not behave as expected after enabling, check the **Messages** panel at the bottom of the Driver Station window for error output. Common errors to look for: + + - **Resource allocation errors** — if two motor controllers, sensors, or other devices are configured to use the same port or CAN ID, the roboRIO will log an allocation error stating that there are conflicting IDS. Double-check your `Constants.java` values and make sure no two devices share a port. + !!! note + Duplicate CAN ID's must be fixed with either the robot powered off, or with the SparkMax controller unplugged, as the SparkMax will not allow you to change its CAN ID while it is powered on and connected to the CAN bus. + - **"Robot program exited unexpectedly"** — unlike a regular Java application, the robot program does not stop running when an exception is thrown. Unhandled exceptions will be printed to the log but the program will continue running, sometimes in a broken state. Always check the log if the robot acts strangely. + - **CAN bus errors** — if a SparkMax or other CAN device isn't responding, it will log timeout messages. Make sure the device is powered on and wired correctly. \ No newline at end of file diff --git a/docs/programming/driving_robot.md b/docs/programming/driving_robot.md index 2158e0f..80b9d46 100644 --- a/docs/programming/driving_robot.md +++ b/docs/programming/driving_robot.md @@ -23,524 +23,254 @@ Before we begin we must create the class file for the drivetrain subsystem. See In the Drivetrain class we will tell the subsystem what type of components it will be using. -- A Drivetrain needs motor controllers. In our case we will use 4 Talon SRs (a brand of controller for motors). - - You could use other motor controllers such as Victor SPs or Talon SRXs but we will be using Talon SRs - - If you are using other motor controllers, replace Talon with TalonSRX, Victor, or VictorSP in the code you write depending on the type you use. +- A Drivetrain needs motor controllers. In our case we will use Neo SparkMaxes (a brand of controller for motors made by Rev Robotics). + - You could use other motor controllers such as Victor SPs or Talon SRs but we will be using NEO SparkMaxes + - If you are using other motor controllers, replace SparkMax with Talon, TalonSRX, Victor, or VictorSP in the code you write depending on the type you use. - You can use 2 motors (left and right), but for this tutorial we will use 4. -!!! Tip - Be sure to read [Visual Studio Code Tips](../basics/vscode_tips.md){target=_blank} before getting started! It will make your life a lot easier. +!!! note "More Info" + Be sure to read [Visual Studio Code Tips](../basics/vscode_tips.md){target=_blank} before getting started! It will make your life a lot easier. -### Creating the Talon Variables +### Creating the SparkMax Variables -!!! summary "" - **1)** Create 4 global variables of data type **Talon** and name them: `leftFrontTalon`, `rightFrontTalon`, `leftBackTalon`, `rightBackTalon` - - - To get started type the word Talon followed by the name i.e. `#!java Talon leftFrontTalon;` - - These will eventually hold the object values for Talons and their port numbers. -!!! summary "" - **2)** Next assign their values to `#!java null` ([more info on `null`](../basics/java_basics.md#overview){target=_blank}). - - - We do this to make sure it is empty at this point. - - When we assign these variables a value, we will be getting the motor controller's port numbers out of Constants - - This means we cannot assign them at the global level +**1)** Create 4 global variables of data type **SparkMax** and name them: `leftLeader`, `rightLeader`, `leftFollower`, `rightFollower` -??? Example +- To get started type the word SparkMax followed by the name i.e. `private Final SparkMax leftLeader;` +- These will eventually hold the object values for SparkMaxes, their port numbers, and their motor type (brushed or brushless). - The code you typed should be this: - ```java - Talon leftFrontTalon = null; - Talon leftBackTalon = null; - Talon rightFrontTalon = null; - Talon rightBackTalon = null; - ``` +**2)** These are declared without values right now. - Your full **Drivetrain.java** should look like this: - - ```java - package frc.robot.subsystems; - - import edu.wpi.first.wpilibj.Talon; - import edu.wpi.first.wpilibj.command.Subsystem; - - /** - * Add your docs here. - */ - public class Drivetrain extends Subsystem { - // Put methods for controlling this subsystem - // here. Call these from Commands. - - Talon leftFrontTalon = null; - Talon leftBackTalon = null; - Talon rightFrontTalon = null; - Talon rightBackTalon = null; - - @Override - public void periodic() { - // This method will be called once per scheduler run - } - } - ``` - - -??? fail "If an error occurs (red squiggles)" - 1. Click the word Talon - ![](../assets/images/driving_robot/e1.png) - 2. 💡 Click the light bulb - ![](../assets/images/driving_robot/e2.png) - 3. Select "Import 'Talon' (edu.wpi.first.wpilibj)" - ![](../assets/images/driving_robot/e3.png) - 4. Your error should be gone! - -### Creating and filling the constructor +- We do this to make sure it is empty at this point. +- When we assign these variables a value, we will be getting the motor controller's port numbers out of Constants + - This means we cannot assign them at the global level -!!! summary "" - **1)** Create the constructor for Drivetrain.java ([more info on constructors](../basics/java_basics.md#constructors){target=_blank}) - - - The constructor is where we will assign values to our talon variables. -!!! summary "" - Now that we have created the Talons we must initialize them and tell them what port on the roboRIO they are on. +??? example "Example code" + **SparkMax Motor Member Variables:** - **2)** Initialize (set value of) `leftFrontTalon` to `#!java new Talon(0)`. - - - This initializes a new talon, `leftFrontTalon`, in a new piece of memory and states it is on port 0 of the roboRIO. - - This should be done within the constructor `#!java Drivetrain()` - - This calls the constructor `#!java Talon(int)` in the Talon class. - - The constructor `#!java Talon(int)` takes a variable of type `#!java int`. In this case the `#!java int` (integer) refers to the port number on the roboRIO. - - ??? Info "roboRIO port diagram" - ![](../assets/images/driving_robot/roboRIO_port.png) + ```java title="CANDriveSubsystem.java" + --8<-- "docs/code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java:motors" + ``` -??? Example +!!! note "Using a different motor controller?" + The steps in this section use `SparkMax`, but the pattern is the same for other controllers. Replace `SparkMax` with your controller type (e.g. `TalonFX`, `VictorSP`) and use the corresponding import. Constructor parameters and configuration APIs differ by vendor — consult your vendor's documentation or WPILib's [hardware API guide](https://docs.wpilib.org/en/stable/docs/software/hardware-apis/motors/index.html){target=_blank} for the correct syntax. - The code you typed should be this: +**If an error occurs (red squiggles)** - ```java - public Drivetrain() { - // Talons - leftFrontTalon = new Talon(0); - } - ``` +1. Mouse Over the word SparkMax: The following menu should appear. +![](../assets/images/driving_robot/fix_error_1.PNG) - Your full **Drivetrain.java** should look like this: +2. 💡 Click "quick fix" +![](../assets/images/driving_robot/quick_fix_click.png) - ```java - package frc.robot.subsystems; - - import edu.wpi.first.wpilibj.Talon; - import edu.wpi.first.wpilibj.command.Subsystem; - - /** - * Add your docs here. - */ - public class Drivetrain extends Subsystem { - // Put methods for controlling this subsystem - // here. Call these from Commands. - - Talon leftFrontTalon = null; - Talon leftBackTalon = null; - Talon rightFrontTalon = null; - Talon rightBackTalon = null; - - public Drivetrain() { - // Talons - leftFrontTalon = new Talon(0); - } - - @Override - public void periodic() { - // This method will be called once per scheduler run - } - ``` +3. Select "Import 'SparkMax' (com.revrobotics.spark)" +![](../assets/images/driving_robot/Quick_fix_import.png) -### Using Constants +4. Your error should be gone! - +### Creating and filling the constructor -Since each subsystem has its own components with their own ports, it is easy to lose track of which ports are being used and for what. To counter this you can use a class called **Constants** to hold all these values in a single location. +Now that we have created the SparkMaxes and the Drive Constants we must initialize them and tell them what port on the roboRIO they are on. -!!! summary "" - **1)** To use Constants, instead of putting `0` for the port on the Talon type: - ```java - Constants.DRIVETRAIN_LEFT_FRONT_TALON - ``` - - - Names should follow the pattern SUBSYSTEM_NAME_OF_COMPONENT - - The name is all caps since it is a **constant** ([more info on constants](../basics/java_basics.md#constants){target=_blank}). - -!!! summary "" - **2)** Click on the underlined text - ![](../assets/images/driving_robot/constants/step_1.png) - -!!! summary "" - **3)** Click on the 💡light bulb and select “create constant…” - ![](../assets/images/driving_robot/constants/step_2.png) - -!!! summary "" - **4)** Click on Constants.java tab that just popped up - ![](../assets/images/driving_robot/constants/step_3.png) - -!!! summary "" - **5)** Change the `0` to the correct port for that motor controller on your robot/roboRIO - ![](../assets/images/driving_robot/constants/step_4.png) - - !!! Danger - ***If you set this to the wrong value, you could damage your robot when it tries to move!*** - -!!! summary "" - **6)** Repeat these steps for the remaining Talons. - - !!! Tip - Remember to save both **Drivetrain.java** and **Constants.java** - -??? Example - - The code you type should be this: +**1)** Initialize (set value of) `leftLeader` to `new SparkMax(LEFT_LEADER_ID, MotorType.kBrushless)`. - ```java - leftFrontTalon = new Talon(Constants.DRIVETRAIN_LEFT_FRONT_TALON); - ``` +- This initializes a new SparkMax, `leftLeader`, in a new piece of memory and states it is on the port defined by `LEFT_LEADER_ID`. +- This should be done within the constructor `Drivetrain()` +- This calls the constructor `SparkMax(int, MotorType)` in the SparkMax class. + - The constructor `SparkMax(int, MotorType)` takes a variable of type `int` for the CAN ID and `MotorType` for brushless or brushed. In this case the `int` (integer) refers to the CAN ID on the roboRIO. + + **roboRIO port diagram** - Your full **Drivetrain.java** should look like this: +![](../assets/images/driving_robot/roboRIO_port.png) - ```java - package frc.robot.subsystems; - - import edu.wpi.first.wpilibj.Talon; - import edu.wpi.first.wpilibj.command.Subsystem; - import frc.robot.Constants; - - /** - * Add your docs here. - */ - public class Drivetrain extends Subsystem { - // Put methods for controlling this subsystem - // here. Call these from Commands. - - Talon leftFrontTalon = null; - Talon leftBackTalon = null; - Talon rightFrontTalon = null; - Talon rightBackTalon = null; - - public Drivetrain() { - // Talons - leftFrontTalon = new Talon(Constants.DRIVETRAIN_LEFT_FRONT_TALON); - leftBackTalon = new Talon(Constants.DRIVETRAIN_LEFT_BACK_TALON); - rightFrontTalon = new Talon(Constants.DRIVETRAIN_RIGHT_FRONT_TALON); - rightBackTalon = new Talon(Constants.DRIVETRAIN_RIGHT_BACK_TALON); - } - - @Override - public void periodic() { - // This method will be called once per scheduler run - } - } - ``` - Your full **Constants.java** should look similar to this: +??? example "Constructor Initialization Example" + ```java title="Constructor declaration" + public CANDriveSubSystem () {} + ``` - ```java - package frc.robot; - - public class Constants { - // Talons - public static final int DRIVETRAIN_LEFT_FRONT_TALON = 0; - public static final int DRIVETRAIN_LEFT_BACK_TALON = 1; - public static final int DRIVETRAIN_RIGHT_FRONT_TALON = 2; - public static final int DRIVETRAIN_RIGHT_BACK_TALON = 3; - } - ``` + **Full Constructor: ** - !!! Warning - Remember to use the values for **YOUR** specific robot or you could risk damaging it! + ```java title="Full Constructor" + --8<-- "docs/code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java:constructor" + ``` -### Creating the arcade drive + See [CANDriveSubsystem.java](../code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java) for the complete constructor implementation. -#### What is the Drive Class +### Using Constants -- The FIRST Drive class has many pre-configured methods available to us including DifferentialDrive, and many alterations of MecanumDrive. -- DifferentialDrive contains subsections such as TankDrive and ArcadeDrive. - -- For our tutorial we will be creating an ArcadeDrive -- Arcade drives run by taking a moveSpeed and rotateSpeed. moveSpeed defines the forward and reverse speed and rotateSpeed defines the turning left and right speed. -- To create an arcade drive we will be using our already existing Drivetrain class and adding to it. +!!! note + `Constants.java` fills the same role that older WPILib projects called `RobotMap.java` — a single file mapping every physical port to a named constant. If you see references to `RobotMap` in older FRC code examples, treat them the same as `Constants`. -#### Programing a RobotDrive +Since each subsystem has its own components with their own ports, it is easy to lose track of which ports are being used and for what. To counter this you can use a class called **Constants** to hold all these values in a single location. - +- Names should follow the pattern SUBSYSTEM_NAME_OF_COMPONENT +- The name is all caps since it is a **constant** ([more info on constants](../basics/java_basics.md#constants){target=_blank}). -!!! summary "" - **1)** In the same place we created our talons (outside of the constructor) we will create a **DifferentialDrive** and **SpeedControllerGroups** for our left and right motor controllers. - Outside of the constructor type: - - ```java - SpeedControllerGroup leftMotors = null; - SpeedControllerGroup rightMotors = null; - - DifferentialDrive differentialDrive = null; - ``` - - Since DifferentialDrive only takes 2 parameters we need to create speed controller groups to combine like motor controllers together. - - In this case we will combine the left motors together and the right motors together. +Before we initalize the SparkMax objects we are going to create constants to hold the CAN ID's of the motors. This will happen in constants.java - !!! Warning - You should only group motors that are spinning the same direction physically when positive power is being applied otherwise you could damage your robot. +- Inside the constants class, create a new class called `public static DriveConstants`. +- Inside `DriveConstants` class, create for constants called `LEFT_LEADER_ID`, `LEFT_FOLLOWER_ID`, `RIGHT_LEADER_ID`, and `RIGHT_FOLLOWER_ID`. +- Back in your `DriveTrain` class in `drivetrain.java`, import the `DriveConstants` class as follows: `Import frc.robot.Constants.DriveConstants;`. -!!! summary "" - **2)** Now we must initialize the **SpeedControllerGroups** and **DifferentialDrive** like we did our talons. ... +!!! tip + Make sure to declare constants with `public static final` so they cannot be changed at runtime. - In the constructor type: - - ```java - leftMotors = new SpeedControllerGroup(leftFrontTalon, leftBackTalon); - rightMotors = new SpeedControllerGroup(rightFrontTalon, rightBackTalon); +!!! danger + If you set this to the wrong value, you could damage your robot when it tries to move! +!!! note + To use Constants, instead of putting `0` for the port in the SparkMax type: - differentialDrive = new DifferentialDrive(leftMotors, rightMotors); + ```java title="constants.java" + public static final int LEFT_LEADER_ID = 1; ``` -??? Example +!!! note + Replace the remaining numbers with constants. + +!!! tip + Remember to save both **Drivetrain.java** and **Constants.java** - The code you type outside the constructor should be this: +??? example "DriveConstants Example" + **Drive Constants Definition:** ```java - SpeedControllerGroup leftMotors = null; - SpeedControllerGroup rightMotors = null; - - DifferentialDrive differentialDrive = null; + --8<-- "docs/code_examples/2026KitBotInline/Constants.java:constants" ``` - The code you type inside the constructor should be this: +**Full Constants.java with all Robot Constants:** - ```java - leftMotors = new SpeedControllerGroup(leftFrontTalon, leftBackTalon); - rightMotors = new SpeedControllerGroup(rightFrontTalon, rightBackTalon); +See [Constants.java](../code_examples/2026KitBotInline/Constants.java) for the complete constants file including OperatorConstants and other subsystem constants. - differentialDrive = new DifferentialDrive(leftMotors, rightMotors); - ``` +!!! warning + Remember to use the values for **YOUR** specific robot or you could risk damaging it! - Your full **Drivetrain.java** should look like this: +### Configuring the SparkMaxes - ```java - package frc.robot.subsystems; - - import edu.wpi.first.wpilibj.SpeedControllerGroup; - import edu.wpi.first.wpilibj.Talon; - import edu.wpi.first.wpilibj.command.Subsystem; - import edu.wpi.first.wpilibj.drive.DifferentialDrive; - import frc.robot.Constants; - - /** - * Add your docs here. - */ - public class Drivetrain extends Subsystem { - // Put methods for controlling this subsystem - // here. Call these from Commands. - - Talon leftFrontTalon = null; - Talon leftBackTalon = null; - Talon rightFrontTalon = null; - Talon rightBackTalon = null; - - SpeedControllerGroup leftMotors = null; - SpeedControllerGroup rightMotors = null; - - DifferentialDrive differentialDrive = null; - - public Drivetrain() { - // Talons - leftFrontTalon = new Talon(Constants.DRIVETRAIN_LEFT_FRONT_TALON); - leftBackTalon = new Talon(Constants.DRIVETRAIN_LEFT_BACK_TALON); - rightFrontTalon = new Talon(Constants.DRIVETRAIN_RIGHT_FRONT_TALON); - rightBackTalon = new Talon(Constants.DRIVETRAIN_RIGHT_BACK_TALON); - - leftMotors = new SpeedControllerGroup(leftFrontTalon, leftBackTalon); - rightMotors = new SpeedControllerGroup(rightFrontTalon, rightBackTalon); - - differentialDrive = new DifferentialDrive(leftMotors, rightMotors); - } - - @Override - public void periodic() { - // This method will be called once per scheduler run - } - } - ``` +**Setting CAN Timeout:** -#### Creating the arcadeDrive method +Each SparkMax motor must be configured with a CANTimeout. (How long to wait for a response from the motor controller) -Now it’s time to make an arcadeDrive from our differentialDrive! +```java title="CANDriveSubsystem.java" +--8<-- "docs/code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java:can-timeout" +``` -!!! summary "" - **1)** Let’s create a public void method called “arcadeDrive” with type “double” parameters moveSpeed and rotateSpeed. +**Voltage Compensation and Current Limiting:** - Below the constructor type: +Create the configuration to apply to motors. Voltage compensation helps the robot perform more similarly on different battery voltages (at the cost of a little bit of top speed on a fully charged battery). The current limit helps prevent tripping breakers. - ```java - public void arcadeDrive(double moveSpeed, double rotateSpeed) { +```java title="CANDriveSubsystem.java" +--8<-- "docs/code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java:voltage-compensation" +``` - } - ``` +See [CANDriveSubsystem.java](../code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java) for the full configuration implementation in the constructor. - !!! Tip - By putting something in the parentheses it makes the method require a parameter when it is used. When the method gets used and parameters are passed, they will be store in moveSpeed and rotateSpeed (in that order). See [parameters](../basics/java_basics.md#parameters){target=_blank} for more info. +## Creating the arcade drive -!!! summary "" - **2)** Now lets make our method call the differentialDrive's arcadeDrive method. +### What is the Drive Class - Inside our method type: +- The FIRST Drive class has many pre-configured methods available to us including DifferentialDrive, and many alterations of MecanumDrive. +- DifferentialDrive contains subsections such as TankDrive and ArcadeDrive. + For more information and details on drive bases see the [WPILib documentation](https://docs.wpilib.org/en/stable/docs/software/hardware-apis/motors/wpi-drive-classes.html){target=_blank}. +- For our tutorial we will be creating an ArcadeDrive +- Arcade drives run by taking a moveSpeed and rotateSpeed. moveSpeed defines the forward and reverse speed and rotateSpeed defines the turning left and right speed. +- To create an arcade drive we will be using our already existing Drivetrain class and adding to it. - ```java - differentialDrive.arcadeDrive(moveSpeed, rotateSpeed); - ``` +### Programing a RobotDrive - DifferentialDrive's arcadeDrive method takes parameters moveValue and rotateValue. + - !!! Note - At this point you could instead create a tank drive, however implementation differs slightly. - To do so type `#!java differentialDrive.tankDrive(moveSpeed, rotateSpeed);` instead of `#!java differentialDrive.arcadeDrive(moveSpeed, rotateSpeed);` and change the method name reflect this. +**1)** Create the DifferentialDrive object. - !!! Tip - If you want to limit the max speed you can multiple the speeds by a decimal (i.e. 0.5*moveSpeed will make the motors only move half of their maximum speed) +**Member Variable Declaration:** +```java +--8<-- "docs/code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java:differential-drive-variable" +``` +This defines the drive object that we will use to drive the robot. - You may want to do this for initial testing to make sure everything is going the right direction. +**Constructor Initialization:** +```java +drive = new DifferentialDrive(leftLeader, rightLeader); +``` +This initializes the differential drive object with the left and right leader motors. -??? Example +- Since DifferentialDrive takes 2 parameters we pass the left and right leader motors. +- The follower motors are configured to follow these leaders through the SparkMax configuration. - The code you type should be this: +!!! warning + You should only group motors that are spinning the same direction physically when positive power is being applied otherwise you could damage your robot. - ```java - public void arcadeDrive(double moveSpeed, double rotateSpeed) { - differentialDrive.arcadeDrive(moveSpeed, rotateSpeed); - } - ``` - - Your full **Drivetrain.java** should look like this: +**2)** In order to configure the motors to drive correctly, we need to configure one on each side as the leader and one as the follower. +In the constructor we are going to set the follower motors and link them to the leader motors. To do this we will need to include a couple more classes from the REV Library: +```java +import com.revrobotics.spark.SparkBase.PersistMode; +import com.revrobotics.spark.SparkBase.ResetMode; +``` +Then in the constructor, configure the followers to follow the leaders: - ```java - package frc.robot.subsystems; - - import edu.wpi.first.wpilibj.SpeedControllerGroup; - import edu.wpi.first.wpilibj.Talon; - import edu.wpi.first.wpilibj.command.Subsystem; - import edu.wpi.first.wpilibj.drive.DifferentialDrive; - import frc.robot.Constants; - - /** - * Add your docs here. - */ - public class Drivetrain extends Subsystem { - // Put methods for controlling this subsystem - // here. Call these from Commands. - - Talon leftFrontTalon = null; - Talon leftBackTalon = null; - Talon rightFrontTalon = null; - Talon rightBackTalon = null; - - SpeedControllerGroup leftMotors = null; - SpeedControllerGroup rightMotors = null; - - DifferentialDrive differentialDrive = null; - - public Drivetrain() { - // Talons - leftFrontTalon = new Talon(Constants.DRIVETRAIN_LEFT_FRONT_TALON); - leftBackTalon = new Talon(Constants.DRIVETRAIN_LEFT_BACK_TALON); - rightFrontTalon = new Talon(Constants.DRIVETRAIN_RIGHT_FRONT_TALON); - rightBackTalon = new Talon(Constants.DRIVETRAIN_RIGHT_BACK_TALON); - - leftMotors = new SpeedControllerGroup(leftFrontTalon, leftBackTalon); - rightMotors = new SpeedControllerGroup(rightFrontTalon, rightBackTalon); - - differentialDrive = new DifferentialDrive(leftMotors, rightMotors); - } - - public void arcadeDrive(double moveSpeed, double rotateSpeed) { - differentialDrive.arcadeDrive(moveSpeed, rotateSpeed); - } - - @Override - public void periodic() { - // This method will be called once per scheduler run - } - } - ``` +**Set follower configuration:** +```java +--8<-- "docs/code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java:follower-config" +``` -*** +**Configure right leader:** +```java +--8<-- "docs/code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java:right-leader-config" +``` -## Making our robot controllable +**Invert left leader for correct motor direction:** +```java +--8<-- "docs/code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java:left-inversion" +``` -### Creating the Joystick +??? example "Full Drive Subsystem Example" + See [CANDriveSubsystem.java](../code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java) for the complete implementation with all motor configuration and initialization. -In order to drive our robot, it needs to know what will be controlling it. To do so, we will create a new joystick in RobotContainer.java +### Creating the arcadeDrive method -!!! summary "" - **1)** Open RobotContainer.java +Now it’s time to make an arcadeDrive from our differentialDrive! - **2)** Type:  - ```java - public Joystick driverController = new Joystick(Constants.DRIVER_CONTROLLER); - ``` +!!! abstract + **1)** Let’s create a public void method called “arcadeDrive” with type “double” parameters moveSpeed and rotateSpeed. - + Below the `periodic` method create a new method called `arcadeDrive`. This method will be called from our Drive command to actually move the robot. - - Import any classes if necessary such as: `#!java import edu.wpi.first.wpilibj.Joystick;` - - A variable `driverController` of type Joystick pointing to a joystick on port `DRIVER_CONTROLLER` from **Constants** + ```java + public void arcadeDrive(double moveSpeed, double rotateSpeed) { - **3)** Click the 💡 light bulb to create a new **CONSTANT** and set the value to the port number the joystick uses on the laptop (this can be found in the Driverstation software). + } + ``` - + !!! tip + By putting something in the parentheses it makes the method require a parameter when it is used. When the method gets used and parameters are passed, they will be store in moveSpeed and rotateSpeed (in that order). See [parameters](../basics/java_basics.md#parameters){target=_blank} for more info. -??? Example +!!! abstract + **2)** Now lets make our method call the differentialDrive’s arcadeDrive method. - The code you type should be this: + Inside our method type the call to the differential drive: ```java - public Joystick driverController = new Joystick(Constants.DRIVER_CONTROLLER); + --8<-- “docs/code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java:drive-arcade-method” ``` - Your full **RobotContainer.java** should look like this: + DifferentialDrive’s arcadeDrive method takes parameters moveValue and rotateValue. - ```java - package frc.robot; - - import edu.wpi.first.wpilibj.Joystick; - - /** - * This class is where the bulk of the robot should be declared. Since - * Command-based is a "declarative" paradigm, very little robot logic should - * actually be handled in the {@link Robot} periodic methods (other than the - * scheduler calls). Instead, the structure of the robot (including subsystems, - * commands, and button mappings) should be declared here. - */ - public class RobotContainer { - // The robot's subsystems and commands are defined here... - public Joystick driverController = new Joystick(Constants.DRIVER_CONTROLLER); - } - ``` +!!! note + At this point you could instead create a tank drive, however implementation differs slightly. + To do so type `differentialDrive.tankDrive(moveSpeed, rotateSpeed);` instead of `differentialDrive.arcadeDrive(moveSpeed, rotateSpeed);` and change the method name reflect this. - Your full **Constants.java** should look similar to this: - - ```java - package frc.robot; + !!! tip + If you want to limit the max speed you can multiple the speeds by a decimal (i.e. 0.5*moveSpeed will make the motors only move half of their maximum speed) - public class Constants { - // Talons - public static final int DRIVETRAIN_LEFT_FRONT_TALON = 0; - public static final int DRIVETRAIN_LEFT_BACK_TALON = 1; - public static final int DRIVETRAIN_RIGHT_FRONT_TALON = 2; - public static final int DRIVETRAIN_RIGHT_BACK_TALON = 3; + You may want to do this for initial testing to make sure everything is going the right direction. - // Joysticks - public static final int DRIVER_CONTROLLER = 0; - } - ``` +### Making our robot controllable -### Creating the DriveArcade Command +## Creating the Drivearcade Command - Remember that **methods** tell the robot what it can do but in order to make it do these things we must give it a **command**. See [Command Based Robot](../basics/wpilib.md#command-based-robot){target=_blank} - Now that we have created the method, we need to create a command to call and use that method. @@ -548,218 +278,143 @@ In order to drive our robot, it needs to know what will be controlling it. To do Before we begin we must create the class file for the DriveArcade command. See [Creating a New Command](new_project.md#creating-a-new-command){target=_blank} for info on how to do this and info on what each pre-created method does. -#### In the constructor +### Define variables -!!! summary "" - **1)** In the constructor `#!java DriveArcade()` type: +!!! note + **1)** Create `xspeed` and `zrotation` variables. (to be passed to drive subsystem). These will be declared as `DoubleSuppliers`, which is a function that return a type. This is important for later. + **2)** Create an emtpy `driveSubsystem` instance of `Drivetrain` - ```java - addRequirements(RobotContainer.m_drivetrain); + !!! warning + `DoubleSupplier` and `Drivetrain` will have to be imported as follows: + ```Java + import frc.robot.subsystems.CANDriveSubsystem; + import java.util.function.DoubleSupplier; + ``` + + ```java title=”DriveArcadeCommand.java” + --8<-- “docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java:class-variables” ``` - - - This means, this command will end all other commands currently using drivetrain and will run instead when executed. - - It also means, other commands that require drivetrain will stop this command and run instead when executed. - !!! Warning - If you use the light bulb to import ‘Robot', be sure to import the one with “frc.robot” +### In the constructor -#### In the execute method +!!! note + **1)** Inside the parenthesis of the constructor `driveArcade()` add 3 variables: + ```java title=”DriveArcadeCommand.java” + --8<-- “docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java:constructor-signature” + ``` + These are values that will be passed into the command in `RobotContainer.java` -!!! summary "" - **1)** In the execute method we will create 2 variables of type double called moveSpeed and rotateSpeed. +!!! note + **2)** Inside constructor implementation type: - - We want these variables to be the value of the axis of the controller we are using to drive the robot. So we will set them equal to that by using the joystick getRawAxis method. - - Controllers return an axis value between 1 and -1 to indicate how far the joystick is pushed up or down. Our personal controller returns up as -1 so we want to invert it. - - In Java you can put a negative “ - “in front of a numeric value to invert it (value * -1) - - The joystick’s getRawAxis method will get the position value of the axis as you move it. The method takes parameter “axis number.” (This can be found in the Driverstation software and we will store it in Constants). + ```java title=”DriveArcadeCommand.java” + --8<-- “docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java:constructor-body” + ``` - In the execute() method type: + - The lines starting with `this` set the global variables we defined at the top of our class file to the values being passed into the consturctor. + !!! tip + `this` is how the class instance `object` refers to itself in code. - ```java - double moveSpeed = -RobotContainer.driverController.getRawAxis(Constants.DRIVER_CONTROLLER_MOVE_AXIS); - double rotateSpeed = RobotContainer.driverController.getRawAxis(Constants.DRIVER_CONTROLLER_ROTATE_AXIS); - ``` +!!! note “” + - `addRequirements` means this command will end all other commands currently using drivetrain and will run instead when executed. + - It also means, other commands that require drivetrain will stop this command and run instead when executed. - !!! Tip - Remember to use the light bulb for importing and creating constants if needed! + !!! warning + If you use the light bulb to import ‘Robot’, be sure to import the one with “frc.robot” -!!! summary "" - **2)** Also in the execute method we will we want to call the **arcadeDrive** method we created in **Drivetrain** and give it the variables **moveSpeed** and **rotateSpeed** we created as parameters. +### In the execute method - In the execute() method below rotateSpeed type: +!!! note + **1)** In the execute method we will we want to call the **arcadeDrive** method we created in **Drivetrain** and give it the variables **moveSpeed** `xspeed` and **rotateSpeed** `zrotation` we created as parameters. - ```java - RobotContainer.m_drivetrain.arcadeDrive(moveSpeed, rotateSpeed); + ```java title=”DriveArcadeCommand.java” + --8<-- “docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java:execute-method” ``` -#### In the isFinished method +### In the isFinished method Since we will be using this command to control the robot we want it to run indefinitely. -!!! summary "" - **1)** To do this we are going to continue having isFinished return false, meaning the command will never finish. - - (We don't need to change anything as this is the default) +!!! note + **1)** To do this we are going to continue having isFinished return false, meaning the command will never finish. - !!! Tip + ```java title=”DriveArcadeCommand.java” + --8<-- “docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java:is-finished-method” + ``` + + !!! tip - If we did want a command to finish, we make this return true. - This can be done by replacing false with true to make it finish instantly - Alternatively we can make a condition which can return true - For example `(timePassed > 10)` will return true after 10 seconds but return false anytime before 10 seconds have passed. -#### In the end method - -!!! summary "" - **1)** We will call the arcadeDrive method and give it 0 and 0 as the parameters. +### In the end method - In the end() method type: +!!! note + **1)** We will call the arcadeDrive method and give it 0 and 0 as the parameters. this will stop the robot when the command completes. - ```java - RobotContainer.m_drivetrain.arcadeDrive(0, 0); + ```java title=”DriveArcadeCommand.java” + --8<-- “docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java:end-method” ``` - This make the motors stop running when the command ends by setting the movement speed to zero and rotation speed to zero. -#### Completed Example +### Completed Example -??? Example +See [DriveArcadeCommand.java](../code_examples/2026KitBotInline/commands/DriveArcadeCommand.java) for the complete command class implementation. - Your full **Constants.java** should look similar to this: +### Creating the Joystick - ```java - package frc.robot; - - public class Constants { - // Talons - public static final int DRIVETRAIN_LEFT_FRONT_TALON = 0; - public static final int DRIVETRAIN_LEFT_BACK_TALON = 1; - public static final int DRIVETRAIN_RIGHT_FRONT_TALON = 2; - public static final int DRIVETRAIN_RIGHT_BACK_TALON = 3; - - // Joysticks - public static final int DRIVER_CONTROLLER = 0; - public static final int DRIVER_CONTROLLER_MOVE_AXIS = 1; // Change for your controller - public static final int DRIVER_CONTROLLER_ROTATE_AXIS = 2; // Change for your controller - } - ``` +In order to drive our robot, it needs to know what will be controlling it. To do so, we will use the joystick in `RobotContainer.java`, as `m_drivecontroller`. - Your full **DriveArcade.java** should look like this: +!!! note + **1)** Open Constants.java + Check and make sure the `kDriverControllerPort` constant is present. + **2)** Open RobotContainer.java + - in the imports section, change `ExampleCommand` to `DriveArcade`. + - inside the class, find the line ` private final ExampleSubsystem m_exampleSubsystem = new ExampleSubsystem();` and change `ExampleSubsystem` to `Drivetrain` and `m_exampleSubsystem` to `drivetrain`. - ```java - package frc.robot.commands; - - import edu.wpi.first.wpilibj.command.Command; - import frc.robot.RobotContainer; - import frc.robot.Constants; - - public class DriveArcade extends Command { - public DriveArcade() { - // Use addRequirements() here to declare subsystem dependencies. - addRequirements(RobotContainer.m_drivetrain); - } - - // Called just before this Command runs the first time - @Override - protected void initialize() { - } - - // Called repeatedly when this Command is scheduled to run - @Override - protected void execute() { - double moveSpeed = -RobotContainer.driverController.getRawAxis(Constants.DRIVER_CONTROLLER_MOVE_AXIS); - double rotateSpeed = RobotContainer.driverController.getRawAxis(Constants.DRIVER_CONTROLLER_ROTATE_AXIS); - - RobotContainer.m_drivetrain.arcadeDrive(moveSpeed, rotateSpeed); - } - - // Called once the command ends or is interrupted. - @Override - protected void end(boolean interrupted) { - Robot.m_drivetrain.arcadeDrive(0, 0); - } - - // Make this return true when this Command no longer needs to run execute() - @Override - protected boolean isFinished() { - return false; - } - } - ``` +!!! tip "Finding your joystick port in the Driver Station" + If you are not sure which port your controller is on, open the **Driver Station** application, click the **USB** tab (the icon that looks like a USB plug on the left side), and look at the numbered list of connected devices. The number next to your controller is the port you should use for `kDriverControllerPort` in Constants. Devices can be dragged up or down in the list to change their assigned port number. ### Using setDefaultCommand -- Commands passed to this method will run when the robot is enabled. -- They also run if no other commands using the subsystem are running. - - This is why we write **addRequirements(Robot.m_subsystemName)** in the commands we create, it ends currently running commands using that subsystem to allow a new command is run. +!!! note + **1)** Back in **RobotContainer.java** We will need to remove everything inside the `configureBindings` method. + **2)** in the `configureBindings`we will call the `setDefaultCommand` of `drivetrain` and create a new `DriveArcade` command with parameters. + + !!! tip + - Commands in this method will run when the robot is enabled. + - They also run if no other commands using the subsystem are running. + - This is why we write **addRequirements(Robot.subsystemName)** in the commands we create, it ends currently running commands using that subsystem to allow a new command is run. + - We will the default command for the drive subsystem to an instance of the `DriveArcade` with the values provided by the joystick axes on the driver controller. + - The Y axis of the controller is inverted so that pushing the stick away from you (a negative value) drives the robot forwards (a positive value). + - Similarly for the X axis where we need to flip the value so the joystick matches the WPILib convention of counter-clockwise positive -!!! summary "" - **1)** Back in **RobotContainer.java** in the constructor we will call the `setDefaultCommand` of `m_drivetrain` and pass it the `DriveArcade` command - - In the **RobotContainer.java** constructor type: - - ```java - m_drivetrain.setDefaultCommand(new DriveArcade()); + ```java title="RobotContainer.java" + --8<-- "docs/code_examples/2026KitBotInline/RobotContainer.java:drive-config" ``` + !!! tip + - Notice the `()->` notation above. This notation creates lamdas or anonymous methods. [More about Lambdas](https://www.w3schools.com/java/java_lambda.asp){target=_blank} + - The lambas are required because we set the parameter types of `xpeed` and 'zrotation' in our `DriveArcade` to be `DoubleSuppliers`, which are methods that return doubles. (Which is what the lambdas above return.) + - These are declared as such so that they get and send the updated values from `m_driverController.getLeftY()` and `m_driverController.getRightX()` to the drive motors continuously. - !!! Tip + !!! tip Remember to use the light bulb for importing if needed! + !!! tip + The `New` keyword creates a new instance of a class (object) -??? Example +??? example "Full RobotContainer Example" + See [RobotContainer.java](../code_examples/2026KitBotInline/RobotContainer.java) for the complete RobotContainer implementation. - Your full **RobotContainer.java** should look like this: + The key part for drive configuration is in `configureBindings()`: - ```java - package frc.robot; - - import edu.wpi.first.wpilibj.Joystick; - import frc.robot.commands.*; - import frc.robot.subsystems.*; - import edu.wpi.first.wpilibj2.command.Command; - - /** - * This class is where the bulk of the robot should be declared. Since Command-based is a - * "declarative" paradigm, very little robot logic should actually be handled in the {@link Robot} - * periodic methods (other than the scheduler calls). Instead, the structure of the robot - * (including subsystems, commands, and button mappings) should be declared here. - */ - public class RobotContainer { - // The robot's subsystems and commands are defined here... - public static final Drivetrain m_drivetrain = new Drivetrain(); - private final ExampleSubsystem m_exampleSubsystem = new ExampleSubsystem(); - - private final ExampleCommand m_autoCommand = new ExampleCommand(m_exampleSubsystem); - - public Joystick driverController = new Joystick(Constants.DRIVER_CONTROLLER); - - /** - * The container for the robot. Contains subsystems, OI devices, and commands. - */ - public RobotContainer() { - // Configure the button bindings - configureButtonBindings(); - - // Set default commands on subsystems - m_drivetrain.setDefaultCommand(new DriveArcade()); - } - - /** - * Use this method to define your button->command mappings. Buttons can be created by - * instantiating a {@link GenericHID} or one of its subclasses ({@link - * edu.wpi.first.wpilibj.Joystick} or {@link XboxController}), and then passing it to a - * {@link edu.wpi.first.wpilibj2.command.button.JoystickButton}. - */ - private void configureButtonBindings() { - } - - - /** - * Use this to pass the autonomous command to the main {@link Robot} class. - * - * @return the command to run in autonomous - */ - public Command getAutonomousCommand() { - // An ExampleCommand will run in autonomous - return m_autoCommand; - } - } - ``` + ```java title="RobotContainer.java" + --8<-- "docs/code_examples/2026KitBotInline/RobotContainer.java:drive-config" + ``` + + This sets arcade drive as the default command, using: + + - The negative Y-axis of the left joystick (inverted so pushing away drives forward) + - The negative X-axis of the right joystick (inverted for WPILib counter-clockwise positive convention) + - Both axes scaled for controllability \ No newline at end of file diff --git a/docs/programming/new_project.md b/docs/programming/new_project.md index 0e7bc3a..b6f12d9 100644 --- a/docs/programming/new_project.md +++ b/docs/programming/new_project.md @@ -15,36 +15,36 @@ Before we can start programing a robot, we must create a new project in Visual S ## Creating a New Project -!!! summary "" +!!! abstract "" **1)** Select the **W icon** from the tab bar or use the shortcut by holding down **Ctrl+Shift+P** at the same time. (Replace ctrl with command on macOS) ![](../assets/images/new_project/project/step_1.png) -!!! summary "" +!!! abstract "" **2)** Type and hit enter or select **WPILib: Create a new project** ![](../assets/images/new_project/project/step_2.png) -!!! summary "" +!!! abstract "" **3)** Click **Select a Project Type** and choose **Template** **4)** Click **Select a Language** and choose **Java** **5)** Click **Select a project base** and choose **Command Robot** ![](../assets/images/new_project/project/step_3.png) -!!! summary "" +!!! abstract "" **6)** Click **Select a new project folder** and choose where on your computer you would like to store the program ![](../assets/images/new_project/project/step_4.png) -!!! summary "" +!!! abstract "" **7)** **Enter a project name** in the text field labeled as such **8)** **Enter your team number** in the text field labeled as such **9)** Select **Generate Project** ![](../assets/images/new_project/project/step_5.png) -!!! summary "" +!!! abstract "" **10)** When prompted **“Would you like to open the folder?”**, select **Yes (Current Window)** ![](../assets/images/new_project/project/step_6.png) @@ -59,14 +59,16 @@ Newly created projects have many files within them. We only care about the conte - An example Command - **ExampleSubsystem.java** - An example SubSystem -- **Constants.java** (new in 2020, replaces RobotMap.java) +- **Constants.java** - Used to map physical ports (digital if using the CAN bus) of sensors or devices connected to the robot and assign them a variable name to be used in other parts of the code. - This provides flexibility for changing wiring, makes checking the wiring easier, and significantly reduces the number of magic numbers floating around. - Can also be used to store generic constant values as variables in the code + - This gives a unified place to store and modify values that might be used many places in the code, so they only have to be changes once. + - **Main.java** - Used for advanced programming - Will be ignored/left as is for this tutorial -- **RobotContainer.java** (new in 2020, replaces OI.java) +- **RobotContainer.java** - Used to declare our subsystem - Used to create a connection between commands and Operator Interfaces (OI) such as Joysticks or buttons - **Robot.java** @@ -80,45 +82,47 @@ Newly created projects have many files within them. We only care about the conte ## Creating a New Subsystem -!!! summary "" +!!! abstract "" **1)** Click on the **src** folder to expand it. **2)** Do the same for **java** then **subsystems** ![](../assets/images/new_project/subsystem/step_1.png) -!!! summary "" +!!! abstract "" **3)** Right click on **subsystems** and select **Create a new class/ command.** ![](../assets/images/new_project/subsystem/step_2.png) -!!! summary "" +!!! abstract "" **4)** Select **Subsystem (New)** and type your **DesiredSubsystemName** (i.e. **Drivetrain**) for the name and hit enter on your keyboard. ![](../assets/images/new_project/subsystem/step_3.png) ![](../assets/images/new_project/subsystem/step_4.png) -!!! summary "" +!!! abstract "" **5)** Click on the newly created **DesiredSubsystemName.java** (or **Drivetrain.java** if you named it that) ![](../assets/images/new_project/subsystem/step_5.png) ### Adding the Subsystem to RobotContainer.java - +!!! note "Importing the subsystem" + After typing the new subsystem type (e.g. `Drivetrain`), VSCode will underline it in red because the class hasn't been imported yet. Click the light bulb that appears (or press ++ctrl+period++) and select **Import 'Drivetrain' (frc.robot.subsystems)**. See [Visual Studio Code Tips](../basics/vscode_tips.md){target=_blank} for more on using quick fixes. + !!! warning "Do not forget this step!" When a robot program runs on the roboRIO it only runs the main file Robot.java and anything Robot.java links to such as RobotContainer.java. We have created a new subsystem but we have not yet linked it to Robot.java through RobotContainer.java. !!! danger "***We must do this for EVERY subsystem we create***" -!!! summary "" +!!! abstract "" **1)** In RobotContainer.java we will create a new **public** **global** **constant** variable of type `DesiredSubsystemName` (i.e. `Drivetrain`): - `#!java public static final m_desiredSubsystemName = new DesiredSubsystemName();` - (i.e. `#!java public static final m_drivetrain = new Drivetrain();`) + `public static final m_desiredSubsystemName = new DesiredSubsystemName();` + (i.e. `public static final m_drivetrain = new Drivetrain();`) ![](../assets/images/new_project/subsystem/step_6.png) -Now when we use this subsystem in commands, we must call `#!java RobotContainer.m_desiredSubsystemName.` to get access to it and its methods. (i.e. `#!java RobotContainer.m_drivetrain.someMethod()`) +Now when we use this subsystem in commands, we must call `RobotContainer.m_desiredSubsystemName.` to get access to it and its methods. (i.e. `RobotContainer.m_drivetrain.someMethod()`) ### Default Subsystem Contents @@ -154,24 +158,24 @@ Newly created subsystems are empty with the exception of the periodic. ## Creating a New Command -!!! summary "" +!!! abstract "" **1)** Click on the **src** folder to expand it (if it isn't already). **2)** Do the same for **commands** ![](../assets/images/new_project/command/step_1.png) -!!! summary "" +!!! abstract "" **3)** Right click on **commands** and select **Create a new class/ command.** ![](../assets/images/new_project/command/step_2.png) -!!! summary "" +!!! abstract "" **4)** Select **Command (New)** and type **DesiredCommandName** (i.e. DriveArcade) for the name and hit enter on your keyboard. ![](../assets/images/new_project/command/step_3.png) ![](../assets/images/new_project/command/step_4.png) -!!! summary "" +!!! abstract "" **5)** Click on the newly created **DesiredCommandName.java** (or **DriveArcade.java** if you named it that) ![](../assets/images/new_project/command/step_5.png) diff --git a/docs/programming/pneumatics.md b/docs/programming/pneumatics.md index ca013ac..6246827 100644 --- a/docs/programming/pneumatics.md +++ b/docs/programming/pneumatics.md @@ -39,7 +39,7 @@ See [Creating a New Subsystem](new_project.md#creating-a-new-subsystem){target=_ ### What will be added to the Shooter subsystem -!!! summary "" +!!! abstract "" **1)** - Create a new Shooter subsystem. @@ -50,7 +50,7 @@ See [Creating a New Subsystem](new_project.md#creating-a-new-subsystem){target=_ - Port 1 and Port 2 refer to Forward control and Reverse control ports on the PCM. - Like all ports we use, we will store this in the RobotMap. -!!! summary "" +!!! abstract "" **2)** Create your DoubleSolenoid named pitchSolenoid now using the same technique used to create a talon but replacing Talon with DoubleSolenoid. (For single solenoids just use Solenoid). ??? Example @@ -104,10 +104,10 @@ See [Creating a New Subsystem](new_project.md#creating-a-new-subsystem){target=_ ### Creating Pitch Up/Down Methods -!!! summary "" +!!! abstract "" **1)** Create a public void method called pitchUp. -!!! summary "" +!!! abstract "" **2)** Inside type: ```java @@ -115,10 +115,10 @@ See [Creating a New Subsystem](new_project.md#creating-a-new-subsystem){target=_ ``` - This sets the value of the solenoid to forward (deployed) - !!! Note + !!! note if you wanted multiple solenoids to deploy at the same time also have them do .set(Value.kForward); -!!! summary "" +!!! abstract "" **3)** Do the same for the **pitchDown** method but change **kForward** to **kReverse**. ??? Example @@ -144,18 +144,18 @@ See [Creating a New Subsystem](new_project.md#creating-a-new-subsystem){target=_ - **InstantCommands** work the same as regular commands but hide everything except for initialize(). (InstantCommand extends Command) - Internally, they set isFinished to return always true so execute never runs. -!!! summary "" +!!! abstract "" **1)** Create a new **InstantCommand** called **ShooterUp** - Alternatively: Create a regular **Command** and set **isFinished** to **true** -!!! summary "" +!!! abstract "" **2)** In the constructor adds requires(Robot.m_shooter) -!!! summary "" +!!! abstract "" **3)** In initialize() add our newly created method **pitchUp** method -!!! summary "" +!!! abstract "" **4)** Repeat steps for **ShooterDown** command but change **pitchUp* to **pitchDown** ??? Example @@ -220,8 +220,6 @@ See [Creating a New Subsystem](new_project.md#creating-a-new-subsystem){target=_ } ``` - - ### Mapping Commands to Buttons ### Creating Joystick Buttons @@ -229,13 +227,13 @@ See [Creating a New Subsystem](new_project.md#creating-a-new-subsystem){target=_ - Now that we have created our ShooterUp and ShooterDown commands we need a way to run them. - Lets map them to buttons on our controller! -!!! summary "" +!!! abstract "" **1)** Open OI.java -!!! summary "" +!!! abstract "" **2)** Under our created joystick we will create Button variables and assign them to a button on our joystick -!!! summary "" +!!! abstract "" **3)** Type: ```java @@ -244,7 +242,7 @@ See [Creating a New Subsystem](new_project.md#creating-a-new-subsystem){target=_ - This creates a new Button named D1 (D representing driverController and 1 representing the button number) and sets it as a JoystickButton on the controller ‘driverController’ and button value 1 (this can be found in the Driverstation software). -!!! summary "" +!!! abstract "" **4)** Do this for the rest of the buttons on your controller. ??? Example @@ -282,10 +280,10 @@ See [Creating a New Subsystem](new_project.md#creating-a-new-subsystem){target=_ - Now that we have created the buttons in the code we can map certain commands to them. -!!! summary "" +!!! abstract "" **1)** Create a constructor for OI -!!! summary "" +!!! abstract "" **2)** In the constructor type: ```java @@ -295,7 +293,7 @@ See [Creating a New Subsystem](new_project.md#creating-a-new-subsystem){target=_ - This means **when** the button D1 is **pressed** it runs the ShooterUp command and deploys our pneumatic piston. - There are other types of activations for buttons besides **whenPressed** like: **whenRelease, whileHeld, etc**. -!!! summary "" +!!! abstract "" **3)** Create a whenPressed button for ShooterDown as well ??? Example @@ -309,7 +307,7 @@ See [Creating a New Subsystem](new_project.md#creating-a-new-subsystem){target=_ } ``` -!!! Tip +!!! tip You can change your import at the top of the file from: `import frc.robot.commands.ShooterUp;` to `import frc.robot.commands.*;` diff --git a/docs/programming/robotpreferences.md b/docs/programming/robotpreferences.md index d5e98ee..13169e3 100644 --- a/docs/programming/robotpreferences.md +++ b/docs/programming/robotpreferences.md @@ -6,7 +6,7 @@ In this section we will be going over -1. Creating and using RobotPreferences in shuffleboard +1. Creating and using RobotPreferences in Elastic 2. How to convert encoder counts to inches *** @@ -19,13 +19,13 @@ In this section we will be going over ## Creating RobotPreferences -!!! summary "" +!!! abstract "" **1)** Create a new **empty class** called **RobotPreferences** - This is where we store all of our **RobotPreferences** to access anywhere - If we want to use a **RobotPreference** we call RobotPreferences.preferenceName() -!!! summary "" +!!! abstract "" **2)** Inside the constructor type: ```java @@ -41,8 +41,8 @@ In this section we will be going over return Preferences.getInstance().getVariableType("preferenceName", value); ``` -??? Example - +??? example + Your full **RobotPreferences.java** should look like this ```java @@ -69,10 +69,10 @@ In this section we will be going over - We will use this **RobotPreference** to help us create a method that can keep track of the distance our robot has driven in inches -!!! summary "" +!!! abstract "" **1)** Create a method called **getDriveEncoderDistance** inside of **Drivetrain** -!!! summary "" +!!! abstract "" **2)** Inside type: ```java @@ -81,10 +81,10 @@ In this section we will be going over - This will divide the current encoder count by however many counts there are in a foot then multiply that number by 12 to give us the encoder distance in inches -!!! Note +!!! note You may need to invert this value if your encoder counts backward when the robot is driving forward - -!!! Example + +!!! example The code you typed should be this @@ -94,19 +94,25 @@ In this section we will be going over } ``` -!!! summary "" +!!! abstract "" **3)** Add the method to the **update** method in **Telemetry** ## Using RobotPreferences - +- After deploying the code to your robot, open **Elastic** from the Driver Station. +- In the top menu go to **File → Add Widget** (or look in the left-side widget panel) and find the **Robot Preferences** widget, then drag it onto a tab. +- Alternatively, right-click on an empty area of a tab and select **Add Widget → Robot Preferences**. + +Once the widget is visible: -- After deploying the code to your robot find the **RobotPreferences** widget and add it to your page -- Click the add button and enter the **string** of the **RobotPreference** and its type (doubles and ints are numbers) +- Click the **+** (Add) button inside the widget. +- A dialog will appear. Enter the **key name** (the string you passed to `Preferences.getInstance()`, e.g. `"driveEncoderCountsPerFoot"`) and select the matching **type** (Number for doubles and ints, String for text, Boolean for true/false). +- The preference will appear in the widget with its default value. +- Double-click the value field next to a preference to edit it — changes take effect **immediately** on the running robot without redeploying. - If you double click on the preference value you will notice that you can change its value - If you change a preference value it will update **immediately** -!!! Tip +!!! tip If you want to save your robot preference values that you've changed make sure you hardcode them in **RobotPreferences.java** later or take a picture if you want to use them again later ## Measuring Distance Using Encoders @@ -114,27 +120,27 @@ In this section we will be going over - Right now the encoders tell us distance in terms of encoder counts - We will use our **driveEncoderCountsPerFoot** preference to save how many counts there are when the robot drives 1 foot -!!! summary "" +!!! abstract "" **1)** Move the wheel on your robot with the **Drivetrain** encoder attached 1 foot or drive your robot 1 foot -!!! summary "" +!!! abstract "" **2)** Read how many counts your encoder has in the **Drive Encoder Count** window - If you want to measure again press the **Reset Drive Encoder** command button to reset the **Drivetrain** encoder count -!!! summary "" +!!! abstract "" **3)** Change the value of **driveEncoderCountsPerFoot** in the widget to this number -!!! summary "" +!!! abstract "" **4)** Reset the **Drivetrain** encoder and move the wheel 1 foot or drive the robot 1 foot again -!!! summary "" +!!! abstract "" **5)** Make sure your **Drive Encoder Distance** window reads approximately 12 (this is in inches) - If not repeat these steps again -!!! summary "" +!!! abstract "" **6)** Save your **RobotPreferences** widget with this value -!!! summary "" +!!! abstract "" **7)** Hardcode this value in **RobotPreferences.java** in the **driveEncoderCountsPerFoot** method incase you cannot recover your **RobotPreferences** save diff --git a/docs/programming/shuffleboard.md b/docs/programming/shuffleboard.md index e804eae..9213a75 100644 --- a/docs/programming/shuffleboard.md +++ b/docs/programming/shuffleboard.md @@ -24,37 +24,37 @@ In this section we will be going over ## Creating the Telemetry Subsystem -!!! summary "" +!!! abstract "" **1)** Create a new **Subsystem** called **Telemetry** -!!! summary "" +!!! abstract "" **2)** Create a constructor for the **Telemetry** class - The constructor is where we will create buttons for shuffleboard -!!! summary "" +!!! abstract "" **3)** Inside type: ```java SmartDashboard.putData(“Reset Drive Encoder”, new DriveResetEncoder()); ``` -!!! summary "" +!!! abstract "" **4)** Create a public method called update - This method will run periodically in Robot.java to update sensor data on shuffleboard -!!! summary "" +!!! abstract "" **5)** Inside type: ```java SmartDashboard.putNumber(“Drivetrain Encoder Count”, Robot.m_drivetrain.getDriveEncoderCount()); ``` -!!! summary "" +!!! abstract "" **6)** Do the same for the **getDriveEncoderDistance** method -!!! summary "" +!!! abstract "" **7)** Try adding the **Shooter** Subsystem commands and sensor methods where they should be ??? Example @@ -104,15 +104,16 @@ In this section we will be going over ## Adding The Telemetry Subsystem to Robot.java -!!! summary "" +!!! abstract "" **1)** When adding **Telemetry** to **Robot.java**, in **robotInit** we must add **Telemetry** after the other subsystems - This is because the **Telemetry** subsystem relies on methods that are created in other subsystems before it - It can be added before or after **OI** since they don’t use methods from each other - + !!! note "Why not robotPeriodic?" + `robotPeriodic` runs at the very start of every loop, *before* the command scheduler runs. Putting `update()` there means the displayed values would lag one loop cycle behind the current robot state. By placing `update()` in mode-specific methods (`disabledPeriodic`, `autonomousPeriodic`, `teleopPeriodic`), the dashboard always reflects the most current values after all commands for that loop have executed. -!!! summary "" +!!! abstract "" **2)** It is **important** that we add the **update** method to **disabledPeriodic, autonomousPeriodic**, and **teleopPeriodic** so that the **Shuffleboard** is always being updated with information on our sensors. @@ -146,4 +147,34 @@ In this section we will be going over ## Using Shuffleboard - +Shuffleboard has several features that help you organize and save your dashboard layout. + +### Tabs + +Tabs let you separate widgets into logical groups — for example one tab for drivetrain data and another for shooter data. + +- Click the **+** button in the top-right corner of Shuffleboard to add a new tab. +- Double-click a tab name to rename it. +- You can drag widgets between tabs by selecting them and using cut/paste, or by dragging them to the tab bar. + +### Moving and Resizing Widgets + +- Click and drag a widget to reposition it on the grid. +- Hover over the bottom-right corner of a widget until a resize cursor appears, then drag to resize it. +- Right-click a widget to access options such as changing the widget type (e.g. switch between a number display and a graph). + +### Grouping Widgets + +- You can place related widgets near each other and use a **Layout** container to visually group them. +- Right-click on an empty area of the tab and select **Add Layout** to add a List Layout or Grid Layout container. +- Drag widgets into the layout container to group them. + +### Saving Layouts + +Shuffleboard saves your layout automatically when you close it, but you can also save a named layout file to restore later. + +- Go to **File → Save Layout** to save the current arrangement to a `.json` file. +- Go to **File → Load Layout** to restore a previously saved layout. + +!!! tip + Save your layout to a file in your robot project's repository so that the whole team can use the same dashboard configuration. diff --git a/docs/programming/using_sensors.md b/docs/programming/using_sensors.md index 11c7e44..edc0869 100644 --- a/docs/programming/using_sensors.md +++ b/docs/programming/using_sensors.md @@ -19,7 +19,7 @@ In this section we will be going over ## Programming Switches (i.e. Limit Switches) -!!! summary "" +!!! abstract "" **1)** For this tutorial we are going to add a **switch** to a **shooter subsystem** to automatically change the pitch of the shooter - Inside the shooter subsystem we are going to create a **switch** called **shooterSwitch** @@ -51,12 +51,12 @@ In this section we will be going over ### Creating isShooterSwitchClosed Method -!!! summary "" +!!! abstract "" **1)** Create a **public boolean** method called **isShooterSwitchClosed** - This method will tell us when the shooter switch is pressed -!!! summary "" +!!! abstract "" **2)** Inside type: ```java @@ -64,9 +64,18 @@ In this section we will be going over ``` - Switches have 2 states: open and closed. - - - - Make sure you know which is true or false or you may have to invert the switch by rewiring or using the ! operator + - Make sure you know which is true or false or you may have to invert the switch by rewiring or using the `!` operator + +!!! note "Normally Open vs Normally Closed" + Switches come in two configurations: + + - **Normally Open (NO)** — the circuit is open (disconnected) when the switch is not pressed. `DigitalInput.get()` returns `true` when unpressed. + - **Normally Closed (NC)** — the circuit is closed (connected) when the switch is not pressed. `DigitalInput.get()` returns `false` when unpressed. + + **Recommendation:** Use a Normally Open switch and invert the reading in code: `return !shooterSwitch.get();`. This way, if the switch wiring becomes disconnected, the robot reads the sensor as "not pressed" (`false`) instead of "pressed" (`true`). A disconnected sensor should never accidentally trigger an action. + +!!! tip "Keep inversions in the subsystem" + Always handle sensor inversions inside the subsystem method, not in commands or other classes. This way every caller gets the correct logical value without needing to know about the hardware wiring. For example, use `return !shooterSwitch.get();` here rather than inverting in each command that uses the switch. ??? Example @@ -112,21 +121,21 @@ In this section we will be going over - We will create a **command** that gives an example of how a Shooter switch may be used -!!! summary "" +!!! abstract "" **1)** For this tutorial we will use the switch to create a button that automatically pitches the shooter up after the switch is pressed -!!! summary "" +!!! abstract "" **2)** Create a new **command** called **ShooterUpAuto** -!!! summary "" +!!! abstract "" **3)** In the constructor add requires(Robot.m_Shooter) -!!! summary "" +!!! abstract "" **4)** In isFinished return our **isShooterSwitchClosed** method - we will not put anything in initialize or execute because we don't want anything to happen until the switch is closed -!!! summary "" +!!! abstract "" **5)** In end add our **pitchUp** method - we will not put end in interrupted either because we only want to change the pitch of the shooter if the switch is closed @@ -200,10 +209,10 @@ In this section we will be going over ## Programming Encoders -!!! summary "" +!!! abstract "" **1)** For this tutorial we are going to add a **encoder** to the **Drivetrain** subsystem to keep track of the distance the robot has driven -!!! summary "" +!!! abstract "" **2)** Inside the **Drivetrain** subsystem we are going to create an **encoder** called **driveEncoder** - It will be created as an Encoder @@ -227,25 +236,26 @@ In this section we will be going over ### Creating Drive Encoder Methods -!!! summary "" +!!! abstract "" **1)** Create a public double method called **getDriveEncoderCount** -!!! summary "" +!!! abstract "" **2)** Inside type: ```java return driveEncoder.get(); ``` - - + !!! note + Even though `driveEncoder.get()` returns an `int`, we declare `getDriveEncoderCount()` as returning a `double`. This avoids integer division later — for example, dividing by a `double` preference value like `driveEncoderCountsPerFoot` without a manual cast, so the result is always a precise decimal rather than a truncated whole number. + - Encoders will return counts as an int - Depending which direction the encoder shaft rotates the value will increase or decrease -!!! summary "" +!!! abstract "" **3)** Create a public method called **resetDriveEncoderCount** -!!! summary "" +!!! abstract "" **4)** Inside type: ```java @@ -273,13 +283,13 @@ In this section we will be going over - We need to create a command to use the **resetDriveEncoder** method since it’s a **void** method - We will create a **InstantCommand** since we will only use it to reset the drive encoder -!!! summary "" +!!! abstract "" **1)** Create a new **InstantCommand** called **DriveResetEncoder** -!!! summary "" +!!! abstract "" **2)** In the constructor add requires(Robot.m_drivetrain) -!!! summary "" +!!! abstract "" **3)** In initialize() add our **resetDriveEncoder** method ??? Example diff --git a/docs/programming/yagsl_swerve_tutorial.md b/docs/programming/yagsl_swerve_tutorial.md new file mode 100644 index 0000000..7e46c84 --- /dev/null +++ b/docs/programming/yagsl_swerve_tutorial.md @@ -0,0 +1,671 @@ +# Setting Up a Swerve Drive with YAGSL for FRC + +This tutorial provides a comprehensive, step-by-step guide to setting up a swerve drive using Yet Another Generic Swerve Library (YAGSL) for FIRST Robotics Competition (FRC) projects. YAGSL is designed to simplify swerve drive implementation by providing a generic, well-documented library that works with various motor controllers and sensors, eliminating the need for custom code for each robot configuration. + +## 1. Introduction to YAGSL + +YAGSL (Yet Another Generic Swerve Library) is a swerve drive library developed by current and former BroncBotz mentors for FRC teams. Its primary goal is to make swerve drive programming as straightforward as using a `DifferentialDrive`, while supporting a wide range of hardware combinations. + +### Key Features +- **Generic Design**: Works with mixed hardware (e.g., REV SparkMax with CTRE CANCoder, TalonFX with Pigeon2, etc.) +- **JSON-Based Configuration**: Robot-specific settings are stored in JSON files, allowing the same code to work across different robots +- **Active Maintenance**: Regularly updated and community-supported +- **Comprehensive Documentation**: Extensive guides, examples, and troubleshooting resources + +### Why YAGSL? +Unlike many swerve templates that require extensive modification, YAGSL abstracts hardware differences, so teams can focus on robot logic rather than drive code. It's particularly useful for teams with multiple robots or those using non-standard hardware combinations. + +For more details, see the [YAGSL Overview](https://docs.yagsl.com/overview/what-we-do). + +## 2. Prerequisites and Dependencies + +Before starting, ensure you have the following installed and configured. + +### Software Requirements +- **WPILib**: Latest stable version for your season (2025 recommended) + - Installation guide: [WPILib Setup](https://docs.wpilib.org/en/stable/docs/zero-to-robot/step-2/wpilib-setup.html) +- **REV Hardware Client 2**: For configuring REV devices + - Download: [REV Hardware Client](https://docs.revrobotics.com/rev-hardware-client-2) +- **CTRE Tuner X**: For configuring CTRE devices (Phoenix 6) + - Installation: [Phoenix 6 Installation](https://v6.docs.ctr-electronics.com/en/latest/docs/installation/installation-frc.html) + +### Vendor Dependencies (Vendordeps) +YAGSL requires vendor libraries for all supported hardware, even if not used on your robot. Install these via WPILib's vendor dependency system: + +- **REVLib**: `https://software-metadata.revrobotics.com/REVLib.json` +- **Phoenix 6**: `https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2025-latest.json` +- **ReduxLib**: `https://frcsdk.reduxrobotics.com/ReduxLib.json` +- **PhotonVision** (optional, for vision): `https://maven.photonvision.org/repository/internal/org/photonvision/PhotonLib-json/1.0/PhotonLib-json-1.0.json` +- **YAGSL**: `https://yet-another-software-suite.github.io/YAGSL/yagsl.json` + +Installation steps: [3rd Party Libraries](https://docs.wpilib.org/en/stable/docs/software/vscode-overview/3rd-party-libraries.html#installing-libraries) + +### Hardware Knowledge +You should know your robot's physical characteristics before configuration. See section 3 for details. + +## 3. Hardware Requirements and Getting to Know Your Robot + +A swerve drive consists of: +- **Gyroscope/IMU**: For heading tracking (e.g., Pigeon2, NavX, or built-in IMU) +- **Swerve Modules**: Each containing: + - Drive motor (e.g., NEO, Falcon500, Kraken) + - Angle/steering motor (e.g., NEO 550, TalonFXS) + - Absolute encoder (e.g., CANCoder, Canandmag, Thrifty Encoder) +- **CAN Bus**: For communication (required for most modern FRC hardware) + +### Pre-Configuration Checklist +Before configuring YAGSL, gather these details about your robot: + +- **IMU Type and ID**: What gyroscope are you using and its CAN ID? +- **Module Configuration**: For each swerve module: + - Drive motor type, CAN ID, and gearing + - Angle motor type, CAN ID, and gearing + - Encoder type, CAN ID, and mounting offset + - Physical location relative to robot center (X, Y coordinates in inches) +- **Physical Properties**: + - Wheel diameter + - Drive gear ratio (motor rotations per wheel rotation) + - Angle gear ratio (motor rotations per 360° module rotation) + - Robot track width and wheelbase + - Maximum speed (feet per second) +- **CAN Bus Configuration**: Ensure all devices have unique IDs and proper termination + +For a complete list, see [Getting to Know Your Robot](https://docs.yagsl.com/configuring-yagsl/getting-to-know-your-robot). + +## 4. Configuration Steps (JSON Files, Module Setup) + +YAGSL uses JSON configuration files to define your robot's swerve drive. These files are placed in the `deploy/swerve/` directory of your robot project. + +### Directory Structure +```plaintext +deploy/ +└── swerve/ + ├── controllerproperties.json + ├── modules/ + │ ├── frontleft.json + │ ├── frontright.json + │ ├── backleft.json + │ └── backright.json + ├── physicalproperties.json + ├── pidfproperties.json + └── swervedrive.json +``` + +### Configuration Files Overview + +#### swervedrive.json - Global Drive Configuration + +This file defines the overall swerve drive configuration, including the IMU (gyroscope) settings and references to the individual module configuration files. + +!!! abstract "Key Properties" + - `imu`: Configures the gyroscope/IMU used for heading tracking + - `type`: The type of IMU ("pigeon2", "navx", "adxrs450", etc.) + - `id`: CAN ID of the IMU device + - `canbus`: CAN bus name (usually "rio" for roboRIO bus) + - `invertedIMU`: Whether to invert the IMU reading (used for orientation correction) + - `modules`: Array of module configuration file names + +**Example - Pigeon2 IMU:** +```json +{ + "imu": { + "type": "pigeon2", + "id": 13, + "canbus": "rio" + }, + "invertedIMU": false, + "modules": [ + "frontleft.json", + "frontright.json", + "backleft.json", + "backright.json" + ] +} +``` + +**Example - NavX IMU:** +```json +{ + "imu": { + "type": "navx", + "id": 0, + "canbus": null + }, + "invertedIMU": false, + "modules": [ + "frontleft.json", + "frontright.json", + "backleft.json", + "backright.json" + ] +} +``` + +**Complete swervedrive.json Example:** +```json +{ + "imu": { + "type": "pigeon2", + "id": 13, + "canbus": "rio" + }, + "invertedIMU": false, + "modules": [ + "frontleft.json", + "frontright.json", + "backleft.json", + "backright.json" + ] +} +``` + +#### Module JSON Files - Individual Swerve Module Configuration + +Each swerve module (wheel) has its own configuration file defining the drive motor, angle motor, encoder, and physical location. + +!!! abstract "Key Properties" + - `drive`: Configuration for the drive (translation) motor + - `type`: Motor controller type ("sparkmax", "talonfx", "talonsrx", etc.) + - `id`: CAN ID of the motor controller + - `canbus`: CAN bus name + - `angle`: Configuration for the angle (steering) motor + - `encoder`: Configuration for the absolute encoder + - `type`: Encoder type ("cancoder", "analog", "thrifty", etc.) + - `inverted`: Motor inversion settings + - `drive`: Whether to invert drive motor direction + - `angle`: Whether to invert angle motor direction + - `absoluteEncoderInverted`: Whether to invert encoder reading + - `absoluteEncoderOffset`: Encoder offset in rotations (0.0 to 1.0) + - `location`: Physical location relative to robot center + - `front`: Distance forward from center (inches) + - `left`: Distance left from center (inches, negative for right side) + +**Example - SparkMax with CANCoder:** +```json +{ + "drive": { + "type": "sparkmax", + "id": 2, + "canbus": null + }, + "angle": { + "type": "sparkmax", + "id": 1, + "canbus": null + }, + "encoder": { + "type": "cancoder", + "id": 10, + "canbus": null + }, + "inverted": { + "drive": false, + "angle": false + }, + "absoluteEncoderInverted": false, + "absoluteEncoderOffset": 0.0, + "location": { + "front": 12.0, + "left": -12.0 + } +} +``` + +**Example - TalonFX with CANCoder:** +```json +{ + "drive": { + "type": "talonfx", + "id": 2, + "canbus": null + }, + "angle": { + "type": "talonfx", + "id": 1, + "canbus": null + }, + "encoder": { + "type": "cancoder", + "id": 10, + "canbus": null + }, + "inverted": { + "drive": false, + "angle": false + }, + "absoluteEncoderInverted": false, + "absoluteEncoderOffset": 0.0, + "location": { + "front": 12.0, + "left": -12.0 + } +} +``` + +**Complete frontleft.json Example:** +```json +{ + "drive": { + "type": "sparkmax", + "id": 2, + "canbus": null + }, + "angle": { + "type": "sparkmax", + "id": 1, + "canbus": null + }, + "encoder": { + "type": "cancoder", + "id": 10, + "canbus": null + }, + "inverted": { + "drive": false, + "angle": false + }, + "absoluteEncoderInverted": false, + "absoluteEncoderOffset": 0.0, + "location": { + "front": 12.0, + "left": -12.0 + } +} +``` + +#### physicalproperties.json - Physical Robot Parameters + +This file defines the physical characteristics of your robot and swerve modules that affect calculations. + +!!! abstract "Key Properties" + - `optimalVoltage`: Battery voltage for calculations (usually 12.0V) + - `wheelDiameter`: Diameter of drive wheels in inches + - `driveGearRatio`: Gear ratio from motor to wheel (motor rotations per wheel rotation) + - `angleGearRatio`: Gear ratio from motor to module rotation (motor rotations per 360° module turn) + +**Example - 4-inch wheels with 6.75:1 drive ratio:** +```json +{ + "optimalVoltage": 12.0, + "wheelDiameter": 4.0, + "driveGearRatio": 6.75, + "angleGearRatio": 12.8 +} +``` + +**Example - 3-inch wheels with 8.14:1 drive ratio:** +```json +{ + "optimalVoltage": 12.0, + "wheelDiameter": 3.0, + "driveGearRatio": 8.14, + "angleGearRatio": 12.8 +} +``` + +**Complete physicalproperties.json Example:** +```json +{ + "optimalVoltage": 12.0, + "wheelDiameter": 4.0, + "driveGearRatio": 6.75, + "angleGearRatio": 12.8 +} +``` + +#### pidfproperties.json - Motor Control Tuning + +This file contains PIDF (Proportional, Integral, Derivative, Feedforward) tuning values for both drive and angle motors. + +!!! abstract "Key Properties" + - `drive`: PIDF values for drive motors (translation) + - `p`: Proportional gain + - `i`: Integral gain + - `d`: Derivative gain + - `f`: Feedforward gain + - `iz`: Integral zone (error threshold for integral accumulation) + - `angle`: PIDF values for angle motors (steering) + +**Example - SparkMax tuning values:** +```json +{ + "drive": { + "p": 0.0020645, + "i": 0.0, + "d": 0.0, + "f": 0.0, + "iz": 0.0 + }, + "angle": { + "p": 0.01, + "i": 0.0, + "d": 0.0, + "f": 0.0, + "iz": 0.0 + } +} +``` + +**Example - TalonFX tuning values:** +```json +{ + "drive": { + "p": 1.0, + "i": 0.0, + "d": 0.0, + "f": 0.0, + "iz": 0.0 + }, + "angle": { + "p": 50.0, + "i": 0.0, + "d": 0.32, + "f": 0.0, + "iz": 0.0 + } +} +``` + +**Complete pidfproperties.json Example:** +```json +{ + "drive": { + "p": 0.0020645, + "i": 0.0, + "d": 0.0, + "f": 0.0, + "iz": 0.0 + }, + "angle": { + "p": 0.01, + "i": 0.0, + "d": 0.0, + "f": 0.0, + "iz": 0.0 + } +} +``` + +#### controllerproperties.json - Advanced Control Settings + +This file configures advanced control parameters for heading correction and velocity control (usually left at defaults). + +!!! abstract "Key Properties" + - `heading`: PID values for heading correction + - `p`: Proportional gain for heading control + - `i`: Integral gain + - `d`: Derivative gain + - `velocity`: Velocity control PID values + - `x`: PID for X-axis velocity control + - `y`: PID for Y-axis velocity control + +**Example - Default controller settings:** +```json +{ + "heading": { + "p": 0.4, + "i": 0.0, + "d": 0.0 + }, + "velocity": { + "x": { + "p": 2.0, + "i": 0.0, + "d": 0.0 + }, + "y": { + "p": 2.0, + "i": 0.0, + "d": 0.0 + } + } +} +``` + +**Complete controllerproperties.json Example:** +```json +{ + "heading": { + "p": 0.4, + "i": 0.0, + "d": 0.0 + }, + "velocity": { + "x": { + "p": 2.0, + "i": 0.0, + "d": 0.0 + }, + "y": { + "p": 2.0, + "i": 0.0, + "d": 0.0 + } + } +} +``` + +### Using the Configuration Tool +YAGSL provides an online configuration generator: [YAGSL Config Tool](https://broncbotz3481.github.io/YAGSL-Example/) + +1. Input your robot's physical parameters +2. Select hardware types and IDs +3. Download the generated configuration files +4. Place them in `src/main/deploy/swerve/` + +For manual configuration details, see [Configuration Documentation](https://docs.yagsl.com/configuring-yagsl/configuration). + +## 5. Code Setup and Integration + +### Importing YAGSL +Add YAGSL as a vendor dependency (see section 2), then import in your code: +```java +import swervelib.parser.SwerveParser; +import swervelib.SwerveDrive; +import swervelib.telemetry.SwerveDriveTelemetry; +import swervelib.telemetry.SwerveDriveTelemetry.TelemetryVerbosity; +``` + +### Creating the SwerveDrive Object +In your subsystem constructor: +```java +public class SwerveSubsystem extends SubsystemBase { + private final SwerveDrive swerveDrive; + + public SwerveSubsystem() { + // Configure telemetry verbosity + SwerveDriveTelemetry.verbosity = TelemetryVerbosity.HIGH; + + try { + // Create swerve drive from JSON configuration + File swerveDirectory = new File(Filesystem.getDeployDirectory(), "swerve"); + double maxSpeed = Units.feetToMeters(14.5); // Maximum speed in m/s + swerveDrive = new SwerveParser(swerveDirectory).createSwerveDrive(maxSpeed); + + // Optional: Configure additional settings + swerveDrive.setHeadingCorrection(true); + swerveDrive.setCosineCompensator(true); + + } catch (Exception e) { + throw new RuntimeException("Failed to create swerve drive", e); + } + } +} +``` + +### Telemetry Setup +YAGSL provides extensive telemetry for debugging. Configure verbosity: +```java +SwerveDriveTelemetry.verbosity = TelemetryVerbosity.HIGH; // Options: NONE, LOW, HIGH +``` + +This adds NetworkTables entries under `/SwerveDrive/` for monitoring module states, IMU data, and odometry. + +For more code setup details, see [Code Setup Documentation](https://docs.yagsl.com/configuring-yagsl/code-setup). + +## 6. Basic Driving Code Examples + +### Field-Oriented Drive Command + +!!! tip + Field-oriented drive means the robot moves relative to the field coordinate system, not its own orientation. Forward on the joystick always moves the robot toward the same direction on the field (e.g., toward the opponent's goal), regardless of how the robot is currently rotated. This is the most intuitive and commonly used drive mode for FRC competition robots. + +```java +/** + * Command to drive the robot using translative values and heading as angular velocity. + */ +public Command driveCommand(DoubleSupplier translationX, DoubleSupplier translationY, DoubleSupplier angularRotationX) { + return run(() -> { + // Scale inputs for smoother control + Translation2d scaledInputs = SwerveMath.scaleTranslation( + new Translation2d(translationX.getAsDouble(), translationY.getAsDouble()), + 0.8 + ); + + // Drive field-oriented + swerveDrive.drive( + scaledInputs, + angularRotationX.getAsDouble() * swerveDrive.getMaximumChassisAngularVelocity(), + true, // fieldRelative + false // openLoop + ); + }); +} +``` + +### Robot-Oriented Drive + +!!! tip + Robot-oriented drive means the robot moves relative to its own orientation. Forward on the joystick always moves the robot in the direction it's currently facing. This mode is useful for precise movements or when field orientation isn't important, but can be confusing for drivers during competition. + +```java +public void driveRobotOriented(double xSpeed, double ySpeed, double rot) { + swerveDrive.drive( + new Translation2d(xSpeed, ySpeed).times(swerveDrive.getMaximumChassisVelocity()), + rot * swerveDrive.getMaximumChassisAngularVelocity(), + false, // fieldRelative = false + false // openLoop + ); +} +``` + +### ChassisSpeeds Drive + +!!! tip + ChassisSpeeds drive accepts a WPILib ChassisSpeeds object, which represents the desired velocity of the robot chassis. This is useful when integrating with path planning libraries like PathPlanner or when you have calculated velocities from other sources. It provides the most control over robot motion. + +```java +public void driveFieldOriented(ChassisSpeeds velocity) { + swerveDrive.driveFieldOriented(velocity); +} +``` + +### Joystick Integration + +!!! tip + Joystick integration shows how to connect driver inputs to the drive commands. The default command runs continuously while no other command is active. Note the axis inversions (-driverController.getLeftY()) which account for typical joystick orientations where pushing forward gives negative Y values. + +```java +private final CommandXboxController driverController = new CommandXboxController(0); + +public RobotContainer() { + SwerveSubsystem drivebase = new SwerveSubsystem(); + + // Set default command for field-oriented drive + drivebase.setDefaultCommand( + drivebase.driveCommand( + () -> -driverController.getLeftY(), // Forward/backward (inverted) + () -> -driverController.getLeftX(), // Left/right (inverted) + () -> -driverController.getRightX() // Rotation (inverted) + ) + ); +} +``` + +### Odometry and Pose Reset + +!!! tip + Odometry tracks the robot's position and orientation on the field using wheel encoders and IMU data. Pose reset is useful for correcting odometry drift, often done at the start of autonomous or when vision systems provide accurate position data. + +```java +// Get current pose +public Pose2d getPose() { + return swerveDrive.getPose(); +} + +// Reset odometry +public void resetOdometry(Pose2d pose) { + swerveDrive.resetOdometry(pose); +} +``` + +For more examples, see the [YAGSL Examples Repository](https://github.com/Yet-Another-Software-Suite/YAGSL/tree/main/examples). + +## 7. Tuning and Debugging + +### PIDF Tuning +YAGSL uses PIDF controllers for both drive and angle motors. Start with these values: + +**SparkMax-based systems:** +```json +{ + "drive": {"p": 0.0020645, "i": 0, "d": 0, "f": 0, "iz": 0}, + "angle": {"p": 0.01, "i": 0, "d": 0, "f": 0, "iz": 0} +} +``` + +**TalonFX-based systems:** +```json +{ + "drive": {"p": 1, "i": 0, "d": 0, "f": 0, "iz": 0}, + "angle": {"p": 50, "i": 0, "d": 0.32, "f": 0, "iz": 0} +} +``` + +Tuning process: +1. Set P, I, D, F to 0 +2. Increase P until oscillation occurs +3. Increase D to reduce jitter +4. Fine-tune as needed + +For detailed tuning guides, see [WPILib PID Tuning](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/introduction/tuning-turret.html) and [YAGSL PIDF Tuning](https://docs.yagsl.com/configuring-yagsl/how-to-tune-pidf). + +### The Eight Steps for Inversion +If your swerve drive spins out of control or has incorrect field orientation, use these systematic steps to fix inversion issues: + +1. Set `invertIMU` to `false` in `swervedrive.json` and all drive motor `inverted` to `false` in module JSONs +2. Set `invertIMU` to `true` +3. Invert all drive motors (`"drive": {"inverted": true}`) +4. Set `invertIMU` to `false` +5. Flip module locations (swap front/back or left/right in configuration) +6. Uninvert drive motors (`"drive": {"inverted": false}`) +7. Set `invertIMU` to `true` +8. Invert drive motors again (`"drive": {"inverted": true}`) + +Test after each step. Most robots work after step 1, 3, or 7. + +For complete details, see [When to Invert](https://docs.yagsl.com/configuring-yagsl/when-to-invert) and [The Eight Steps](https://docs.yagsl.com/configuring-yagsl/the-eight-steps). + +### Common Issues +- **Modules not facing correct direction**: Check absolute encoder offsets +- **Robot drifting in odometry**: Verify IMU orientation and module locations +- **Gears grinding**: PID tuning issue, not inversion +- **Inconsistent behavior**: Ensure all modules have same hardware configuration + +## 8. Links to Relevant Documentation + +- **YAGSL Main Documentation**: [docs.yagsl.com](https://docs.yagsl.com/) +- **Configuration Tool**: [YAGSL Config Generator](https://broncbotz3481.github.io/YAGSL-Example/) +- **Examples Repository**: [GitHub Examples](https://github.com/Yet-Another-Software-Suite/YAGSL/tree/main/examples) +- **WPILib Swerve Kinematics**: [Swerve Drive Kinematics](https://docs.wpilib.org/en/stable/docs/software/kinematics-and-odometry/swerve-drive-kinematics.html) +- **CTRE Swerve Overview**: [Phoenix 6 Swerve](https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/swerve-overview.html) +- **REV Swerve Resources**: [REV Swerve Documentation](https://docs.revrobotics.com/brushless/neo/vortex/vortex-shafts) + +## Additional Resources + +- **Complete YAGSL Example Project**: [YAGSL-Example Repository](https://github.com/BroncBotz3481/YAGSL-Example) - A complete, working FRC robot project demonstrating YAGSL implementation +- **YAGSL Community**: Join the [BroncBotz Discord](https://discord.gg/broncbotz) for support +- **Known Configurations**: [YAGSL Configs Repository](https://github.com/BroncBotz3481/YAGSL-Configs) +- **Advanced Features**: Check examples for PathPlanner, PhotonVision, and SysId integration + +This tutorial covers the essentials for getting started with YAGSL. For advanced features like vision integration or custom control algorithms, explore the examples and documentation further. Remember to test thoroughly on a test bench before field use! \ No newline at end of file diff --git a/docs/setup/imaging_roboRIO.md b/docs/setup/imaging_roboRIO.md index 43a7a97..e7cdbb9 100644 --- a/docs/setup/imaging_roboRIO.md +++ b/docs/setup/imaging_roboRIO.md @@ -8,7 +8,7 @@ Flashing the firmware Before we can deploy code to the robot, we must flash a software image on to the roboRIO and possibly update the firmware. This can be accomplished with the roboRIO imaging tool: -!!! Warning "IMPORTANT NOTE" +!!! warning "IMPORTANT NOTE" The [FRC Game Tools (Windows Only)](imaging_roboRIO.md#installing-the-frc-game-tools) need to be installed to access the roboRIO imaging tool. *** diff --git a/docs/setup/install_software.md b/docs/setup/install_software.md index c8e69b8..1f3ba8d 100644 --- a/docs/setup/install_software.md +++ b/docs/setup/install_software.md @@ -12,6 +12,7 @@ Before we can start programing a robot we must install the necessary software fo **See table of contents for a breakdown of this section.** !!! tip + You can install both the **Development Tools** and the **FRC Game Tools** on the same computer or separate computers. However many teams (3255 included) have a development laptop (with both) and a dedicated driverstation laptop (with only the FRC Game Tools) that often stays disconnected from the internet. *** @@ -26,7 +27,7 @@ For **Windows, macOS, or Linux:** [Official FRC installation guide (Windows, macOS, or Linux)](https://docs.wpilib.org/en/stable/docs/getting-started/getting-started-frc-control-system/wpilib-setup.html){target=_blank} -!!! Warning "IMPORTANT NOTE" +!!! warning "IMPORTANT NOTE" These tools only allow you to program and deploy code to an already imaged roboRIO. They do not allow you to drive the robot or image/update the roboRIO. To accomplish those tasks you must install the [FRC Game Tools](#installing-the-frc-game-tools). *** @@ -41,7 +42,7 @@ For **Windows ONLY:** [Official FRC installation guide (Windows only)](https://docs.wpilib.org/en/stable/docs/getting-started/getting-started-frc-control-system/frc-game-tools.html){target=_blank} -!!! Warning "IMPORTANT NOTE" +!!! warning "IMPORTANT NOTE" These tools only allow you to drive the robot and image/update a roboRIO. They do not allow you to program the robot. To accomplish those tasks you must install the [Java Development Tools](#installing-java-development-tools). *** @@ -55,3 +56,7 @@ In order to enable wireless connectivity to the robot outside of FRC events or t For **Windows ONLY:** [Official FRC Radio Configuration Utility and Use guide (Windows only)](https://docs.wpilib.org/en/stable/docs/getting-started/getting-started-frc-control-system/radio-programming.html){target=_blank} + +## customizing WPILIB Vscode + + diff --git a/docs/version_control/BasicGit.md b/docs/version_control/BasicGit.md new file mode 100644 index 0000000..ca97e52 --- /dev/null +++ b/docs/version_control/BasicGit.md @@ -0,0 +1,113 @@ + +# Basic Git + +------------ + +Git is a version control system, or a way for us to collaborate on code and manage having multiple versions of code working in parallel + +While many modern cloud applications, like google docs, have some form of auto-save to the cloud, we don't necessarily want a single auto-updating version of our code. +As we're working, the changes we make can cause code to break very quickly in ways that might not be immediately obvious, which means it's good to be able to take a "snapshot" of the code at a point where it worked. +So, we use a tool called Git to manage different versions of our files and save them to Github, which lets us share the codebase between computers. +Instead of automatically syncing to Github, we save our code in small named chunks called commits. +These named versions makes it easy to revert changes that break previously working behaviour and see when and by who code was written. +Git also lets us have different, parallel versions, called branches, of code. +This means that while one person works on code for the autonomous, another could work on vision, for example, without overriding each other. + +## Resources + +Read one of the following: + +- [WPILib Git intro](https://docs.wpilib.org/en/stable/docs/software/basic-programming/git-getting-started.html) +- [Github Git intro](https://docs.github.com/en/get-started/using-git/about-git) +- [Github Git intro video (start at 0:43)](https://youtu.be/r8jQ9hVA2qs?t=43) + +Install the following: + +- [Windows Git install](https://gitforwindows.org/) +- [Linux/Mac Git install](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +- [Github Desktop](https://desktop.github.com/download/) + +## Basic Git Commands + +To use Git, developers use specific commands to copy, create, change, and combine code. These commands can be executed directly from the command line or by using an application like GitHub Desktop. Here are some common commands for using Git: + +**git init**: initializes a brand new Git repository and begins tracking an existing directory. It adds a hidden subfolder within the existing directory that houses the internal data structure required for version control. + +**git clone** creates a local copy of a project that already exists remotely. The clone includes all the project's files, history, and branches. + +**git add** stages a change. Git tracks changes to a developer's codebase, but it's necessary to stage and take a snapshot of the changes to include them in the project's history. This command performs staging, the first part of that two-step process. Any changes that are staged will become a part of the next snapshot and a part of the project's history. Staging and committing separately gives developers complete control over the history of their project without changing how they code and work. + +**git commit** saves the snapshot to the project history and completes the change-tracking process. In short, a commit functions like taking a photo. Anything that's been staged with git add will become a part of the snapshot with git commit. + +**git status** shows the status of changes as untracked, modified, or staged. + +**git branch** shows the branches being worked on locally. + +**git merge** merges lines of development together. This command is typically used to combine changes made on two distinct branches. For example, a developer would merge when they want to combine changes from a feature branch into the main branch for deployment. + +**git pull** updates the local line of development with updates from its remote counterpart. Developers use this command if a teammate has made commits to a branch on a remote, and they would like to reflect those changes in their local environment. + +**git push** updates the remote repository with any commits made locally to a branch. + +For more information, see the [full reference guide to git commands](https://git-scm.com/docs). + +## Git Log + +>Git log is a tool to view your history in Git. By default Git Log shows all of the commits in the repository, ordered from newest to oldest. in VSCode, the graph panels shows the Git log/History in a visual way. + +![Graph view in VS code](../assets/images/vscode_tips/git_graph_vscode.png) + +## Github Setup + +Github is a website that hosts Git repositories. The Robolancers store all of their codes on Github. + +1. Sign up for a Github Account here: [Sign up for Github](https://github.com/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F&source=header-home) +2. Enable 2 factor Authentitaction (Required to add code): [2fa setup](https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa/configuring-two-factor-authentication) + +## Examples + +![A simple demonstration of committing and pushing some changes in git](../assets/images/git/GitExample.png) + +- Typing `git add .` then `git commit -m "commit name"` then `git push` is the bread and butter of using Git. + This sequence tells Git to collect all of your uncommitted changes, commit them, then push them to Github. + If you'd like to combine these into one command, you can add an alias to your .gitconfig file (ask a lead or mentor for help). +- `git checkout branch-name` switches between branches +- `git checkout -b new-branch` makes a new branch and switches to it. + Note that the first time you push from this branch you will need to enter some extra parameters, but the terminal should prompt you with the correct command when you enter `git push` +- `git status` displays the current branch and what changes you have uncommitted and staged +- `git pull` updates the code on your device with the latest code from Github. + Recommended to do this whenever someone else has been working on the same branch, otherwise you might make conflicting changes +- [A simple demo video of committing some changes](../assets/images/git/GitDemoVideo.mp4) + +## Managing Git in Vscode + +### Git Commands in Vscode GUI + +![Git commands in vscode GUI](../assets/images/vscode_tips/git_vscode.png) + +### Git branches menu + +Accessed by clicking on the branch name at the bottom left +![Github branch menu in Vscode GUI](../assets/images/vscode_tips/github_branch_vscode.png) + +### Merge in another branch in vscode + +![Github Merge menu](../assets/images/vscode_tips/github_GUI_Merge.png) + +## Notes + +- We use GitHub's pull request (PR) feature to manage branches and merges. +Always make sure to merge to main using a PR. +PR's will be explained further in the [Git Workflow docs](GitWorkflow.md). +- Always commit code that at the very least compiles (you can check this by running the "Build robot code" option in WPILib's command bar) +- Commit messages should be short (~10 words), simple, and descriptive. + If it's too long, use multiple lines in the commit message +- Commits should be frequent. Whenever you reach a working state, you should commit before you accidentally break anything again +- Don't commit directly to the `main` branch, as `main` should **always** contain working code. + New code should be developed on separate branches and can only be merged through a pull request after being reviewed and approved. +- **ALWAYS ALWAYS ALWAYS** commit and push before you leave a meeting, especially if you are using a computer that is not yours! + It is never fun to have to commit someone else's code at the start of the day or find out an hour in that you've been working off of someone else's uncommitted (potentially broken!) code. + Uncommitted code also makes it harder to track what is and isn't finished. +- Run a `git status` at the start of a meeting to make sure you committed your code and that you are on the right branch + +### Names diff --git a/docs/version_control/GitWorkflow.md b/docs/version_control/GitWorkflow.md new file mode 100644 index 0000000..1ab5a21 --- /dev/null +++ b/docs/version_control/GitWorkflow.md @@ -0,0 +1,121 @@ +# Git Workflow + +## Branches + +Branches are a deviation or split from the main branch that can be adding or removing a specific feature +For example, I can open a branch to work on a new doc page for this training repo. +Since I am on my own branch, I am not interfering with the main branch's commit history, which is supposed to be kept clean. +A "clean" commit history is made up of large, well named commits to make it easy to quickly skim recent changes. +Because I am on my own branch, another student can also work on their own article without fear of interfering with my work. + +To create a branch run: +`git checkout -b ""` + +Then, this new branch works just like the main branch with `pull`, `add`, `commit`, and `push` commands. +You should see your current branch in blue in the command prompt if you are using git bash. +Otherwise you can use `git status` to check your branch. + +Right now, this branch is only local to your computer. +To upload (push) this branch to the remote repository so others can view it, run `git push`. +The first time you push a branch, git will prompt you to instead run `git push --set-upstream origin `. +Replace `` with the name of your branch. +Now, it should show up on GitHub and be accessible by others. +Use `git push` without the rest to push any further commits. + +## Pull Requests + +Pull requests (PRs) are crucial to write maintainable, clean code. +A pull request is a way to get a review from others on changes before merging them to the main branch. +To ensure that we always have working and tested code on main, pull requests are required to merge code into main on our season repositories. + +To make a pull request, first you need to have changes to merge. +These changes should be on their own, well named branch. +Then go to the pull requests tab on the Github repo and hit `new pull request`. +You should be prompted to compare a branch with main. +Select the branch you want to merge. +You should see a list of changes and a green button that says `Create pull request`. +Click that button, then let a software lead or mentor know that you need review and request them in Github. + +## Issues + +Issues are a way to track features that we want to add and problems we need to fix in the repository. +Issues can be created by going to the issues tab of the repository on Github and clicking `New issue`. +If there is a relevant issue for something you are working on, make sure to keep it up to date! +If you have something that you think should be added or fixed, open an issue! + +## Github Projects + +>The Robolancers programming team uses Github Projects to manage projects and delegate tasks. +You can access the Project for each repository in its own tab at the top, and add tasks organized by the status columns. + +Tasks that are explicitly about a specific PR should be converted to an issue and linked to that PR. +This will automatically resolve the issue when the PR is merged and move the task to the Completed column. +Important milestones (handoff day, our first competition), major features (add preliminary autos, add intake subsystem), and bugfixes/improvements (improve vision filtering, fix multi-piece auto) should all go in the Project. +Leads/Mentors are expected to add these kinds of major deadlines as soon as they're coordinated, and monitor subteam member progress frequently. + +During the commotion of the build season, it is very easy to lose track of what needs to be done, who is supposed to be doing it, and when it needs to be done, and so it doesn't get done. +Hence, **it is expected** that you regularly **enter** events, task assignments, and deadlines so this can accurately reflect what we're working on. +It is also expected that you regularly **check** what has been assigned to you. +If you feel unsure of what you need to do, ask a lead/mentor ASAP! + + +### **🎯 Simplified Git Flow for RoboLancers** + +Git Flow is like having different workbenches in your robotics shop, each with a specific purpose: + +#### 🟣 Your Personal Branch - "Your Workbench” + +- When you want to add a feature or fix a bug, create a branch with YOUR name or the task +- Examples: `mar-liu/fix-intake` or `add-autonomous-mode` +- When ready, you create a **Pull Request** to merge into main + +#### **🔷 Release Branch (Optional - mainly for competitions)** + +- Created right before a big competition +- Used for final testing and bug fixes only +- Like doing final checks before loading the robot on the trailer + +#### **🟠 Hotfix Branch - "Emergency Repairs"** + +- For critical bugs found during competition +- Quick fix that goes straight to main after review +- Like realizing a motor is backwards 5 minutes before a match + +#### Resources + +- [Github creating a PR](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) +- [Github about PRs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests?platform=windows) +- [Github about issues](https://docs.github.com/en/issues/tracking-your-work-with-issues/about-issues) +- [Github linking a PR to an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) + +#### Examples + +- [A video demonstrating the exercise](../assets/images/git/PRDemoVideo.mkv) + + +### Exercises + +- Simple commit + - Clone this repository from Github + - Create a branch from ```main``` with the following name: ```[your_name]_git_practice``` + - Add your name to the list at the bottom of this file + - Commit and push those changes +- Merging changes + - Pull the ```git_practice``` branch into your branch (either with the command line or the GUI) + - Push the merged changes to your branch. + - Create pull request back into ```git_practice``` branch. Add Carl as a reviewer. + +#### Where to create a pull request + +- Select the pull request tab on the repository page on Github +- Select "Create new pull request" +- choose ```git_practice``` as the base branch, and your branch as the compare branch. +- Click "create pull request" +- Select the gear next to assignees on the right side +- Add Carl Stanton ```@snvgglebear``` as a reviewer. +- Select "create pull request" again +![Create a pull request in Github](../assets/images/git/github_pull_requests.png) + + + +### Name diff --git a/file_list.txt b/file_list.txt new file mode 100644 index 0000000..e28853b Binary files /dev/null and b/file_list.txt differ diff --git a/mkdocs.yml b/mkdocs.yml index b4cbfe0..ce08bf9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -3,11 +3,18 @@ nav: - Home: - 'index.md' - 'why_software.md' +- Setup: + - 3rd_party_libs.md +- Version Control: + - 'version_control/BasicGit.md' + - 'version_control/GitWorkflow.md' + - 'version_control/github.md' - Programming Basics: - 'basics/roboRIO.md' - 'basics/sensors.md' - 'basics/wpilib.md' - 'basics/java_basics.md' + - 'basics/ElectronicsCrashCourse.md' - 'basics/vscode_tips.md' # - 'basics/driverstation_tips.md' - FRC Development Environment Setup: @@ -20,58 +27,67 @@ nav: - 'programming/deploying.md' - 'programming/using_sensors.md' - 'programming/pneumatics.md' - - 'programming/shuffleboard.md' + - 'programming/Elastic.md' - 'programming/robotpreferences.md' - 'programming/autonomous.md' - 'programming/pid.md' + - 'programming/AdvantageKit.md' + - 'programming/AKitStructureReference.md' # - 'programming/super_core.md' - Example Subsystems: - 'examples/basic_elevator.md' - 'examples/basic_shooter.md' - 'examples/pid_elevator.md' - 'examples/pid_shooter.md' -- Version Control: - - 'version_control/github.md' + - Improve the Documentation: - 'contributing.md' # Setup site_name: FRC Java Programming repo_name: Tutorial Bot -repo_url: https://github.com/FRCTeam3255/FRC-Java-Tutorial/tree/main -edit_uri: https://github.com/FRCTeam3255/FRC-Java-Tutorial/edit/main/Docs_Source/docs/ -site_author: Tayler Uva +repo_url: https://github.com/RoboLancers/FRC-Java-Tutorial/tree/main +edit_uri: https://github.com/RoboLancers/FRC-Java-Tutorial/edit/main/docs/ +site_author: Tayler Uva, Carl Stanton # Theme theme: - name: 'readthedocs' + name: 'material' favicon: assets/favicon.png - icon: - logo: assets/favicon.png + #icon: + # logo: assets/favicon.png + features: + - content.tabs.link + - content.code.copy use_directory_urls: false # Extensions markdown_extensions: - admonition - - pymdownx.superfences - pymdownx.critic - pymdownx.details + - pymdownx.blocks.tab - pymdownx.betterem - pymdownx.tasklist - - pymdownx.extra + - attr_list - pymdownx.inlinehilite - pymdownx.highlight: - css_class: 'codehilite' + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.snippets: + base_path: ["."] - toc: - permalink: "#" + permalink: "#" # Plugins plugins: - search - - minify: - minify_html: true - minify_js: true - -google_analytics: - - UA-47256977-4 - - auto \ No newline at end of file + # - minify: + # minify_html: true + # minify_js: true diff --git a/requirements.txt b/requirements.txt index 8fe2ba0..b995987 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ # Install requirements: # pip install -r requirements.txt -mkdocs~=1.5 +mkdocs +mkdocs-material pymdown-extensions mkdocs-minify-plugin \ No newline at end of file diff --git a/tutorial-platform-analysis.md b/tutorial-platform-analysis.md new file mode 100644 index 0000000..4d23c3b --- /dev/null +++ b/tutorial-platform-analysis.md @@ -0,0 +1,314 @@ +# Interactive Tutorial Platform Analysis + +**Date:** March 2026 +**Context:** FRC Java Programming Tutorial — evaluating platforms that support custom Markdown content, user progress tracking, and embedded knowledge check questions. + +--- + +## Key Requirements + +1. **Markdown-based content authoring** — existing content is in `.md` format +2. **Knowledge check / quiz questions** — embedded assessments within tutorial pages +3. **User progress tracking** — learners can save and resume their progress +4. **Extensibility** — ability to add custom features over time +5. **Static / self-hostable** — GitHub Pages or similar, no backend required + +--- + +## Important Ecosystem Note (March 2026) + +**[Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) entered maintenance mode in November 2025.** Version 9.7.0 is the last feature release. The maintainer has launched a successor product called **[Zensical](https://github.com/zensical/zensical)** (MIT licensed, MkDocs-YAML compatible) and will provide only critical security/bug fixes for Material through approximately November 2026. [MkDocs](https://www.mkdocs.org/) 1.x core is also unmaintained. A community-driven **MkDocs 2.0 rewrite** is planned but is explicitly incompatible with Material for MkDocs and its plugin ecosystem. + +This is a significant risk factor for long-term platform planning, though it does not affect short-term functionality. + +--- + +## Platform Summaries + +### 1. [MkDocs](https://www.mkdocs.org/) + [Material Theme](https://squidfunk.github.io/mkdocs-material/) *(current stack)* + +Python-based static site generator using YAML configuration. [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) adds a polished responsive theme, admonitions, tabbed content, code annotations, [Mermaid](https://mermaid.js.org/) diagrams, and extensive [PyMdown Extensions](https://facelessuser.github.io/pymdown-extensions/). + +**Markdown Support:** Excellent — [PyMdown Extensions](https://facelessuser.github.io/pymdown-extensions/) provide tasklists, superfences ([Mermaid](https://mermaid.js.org/)), tabbed content, inline highlights, snippets (file inclusion), admonitions, and more. + +**Quiz Support:** +- [`mkdocs-quiz`](https://ewels.github.io/mkdocs-quiz/) (Apache 2.0, v1.6.2, March 2026 — actively maintained): Full-featured quiz plugin using GFM checkbox syntax. Supports single-choice, multiple-choice, and fill-in-the-blank. Provides per-answer feedback, answer explanations, answer shuffling, and confetti on completion. +- A second plugin [`mkdocs_quiz`](https://pypi.org/project/mkdocs_quiz/) (by bdallard) offers a simpler alternative with score display and progress bar. + +**Progress Tracking:** +- `mkdocs-quiz` stores all answers and results in browser **localStorage**, persisting across sessions. Includes a progress sidebar showing answered/correct counts. +- [`mkdocs-material-mark-as-read`](https://github.com/berk-karaal/mkdocs-material-mark-as-read) (MIT): lets users manually mark pages as read, stored in localStorage. +- Material's built-in `navigation.tracking` updates the URL fragment as users scroll. +- No built-in cross-page "X of Y modules completed" dashboard without custom JavaScript. + +**LMS Export:** `mkdocs-quiz` supports **QTI export** (Canvas, Blackboard, Moodle) — unique among all platforms reviewed. + +**Hosting:** Any static host ([GitHub Pages](https://pages.github.com/), [Netlify](https://www.netlify.com/), [Cloudflare Pages](https://pages.cloudflare.com/), [Read the Docs](https://readthedocs.org/), S3). Zero vendor lock-in. + +**Customization:** High — YAML-driven config, custom CSS via `extra_css`, custom JS via `extra_javascript`, HTML overrides via `overrides/` directory. + +**Ecosystem:** Large Python documentation community with hundreds of plugins. However, the maintenance-mode announcement is a significant long-term risk. + +**Verdict:** Strongest out-of-the-box quiz and progress story of all platforms reviewed. `mkdocs-quiz` installs with a single `pip install`, activates with three lines in `mkdocs.yml`, and delivers working quizzes immediately — with no framework migration required. + +--- + +### 2. [Docusaurus](https://docusaurus.io/) + +Meta's React-based static site generator (MIT, 57,000+ GitHub stars). Version 3.9 released late 2025. Production-grade, used by React, Jest, Webpack, and hundreds of major OSS projects. + +**Markdown Support:** Excellent — uses [MDX](https://mdxjs.com/) (Markdown + JSX), allowing any React component to be embedded directly in `.mdx` files. Supports admonitions, live code editors, [Mermaid](https://mermaid.js.org/), math, tabs, and more via remark/rehype plugins. + +**Quiz Support:** +- No official quiz plugin. The Docusaurus team declined to build one, citing MDX's ability to embed custom React components. +- [`@sp-days-framework/docusaurus-plugin-interactive-tasks`](https://www.npmjs.com/package/@sp-days-framework/docusaurus-plugin-interactive-tasks): provides interactive checklist-style tasks with hints, solutions, completion tracking via localStorage, sidebar badges, and cross-tab sync. Not a traditional quiz but a solid approximation. +- Custom React quiz components can be authored and imported in `.mdx` files — state management (localStorage or backend) is handled by the component author. + +**Progress Tracking:** +- No built-in cross-page progress tracking. +- The interactive-tasks plugin provides per-task completion state with sidebar badges. +- Custom solutions using React `useEffect`/`localStorage` or a backend ([Supabase](https://supabase.com/), [Firebase](https://firebase.google.com/)) are straightforward. + +**Hosting:** Any static host ([GitHub Pages](https://pages.github.com/), [Netlify](https://www.netlify.com/), [Vercel](https://vercel.com/), [Cloudflare Pages](https://pages.cloudflare.com/), S3). + +**Customization:** Very high for React developers. Component swizzling overrides any built-in element. The trade-off is a Node.js/React build stack more complex for Python-oriented teams. + +**Ecosystem:** Largest documentation SSG community. 75+ community plugins. Actively developed with no maintenance concerns. + +**Verdict:** High ceiling, low floor. Powerful MDX + React combination enables any quiz or progress UI imaginable, but requires non-trivial development effort compared to a ready-made plugin. Best choice if the project has React expertise or needs quiz UI beyond multiple-choice (e.g., embedded code exercises, interactive diagrams). + +--- + +### 3. [VitePress](https://vitepress.dev/) + +[Vite](https://vitejs.dev/) + [Vue 3](https://vuejs.org/) powered static site generator (MIT) by the Vue.js team. Used by [Vue.js](https://vuejs.org/), [Vite](https://vitejs.dev/), [Vitest](https://vitest.dev/), and [Rollup](https://rollupjs.org/) official documentation. + +**Markdown Support:** Very good — CommonMark + GFM, custom containers (tip/warning/danger/details), math, Mermaid, Vue components used directly in `.md` files (no `.mdx` conversion needed). + +**Quiz Support:** No dedicated quiz plugin exists. Vue Single-File Components (SFCs) can be written and embedded in Markdown, making custom quiz components feasible for Vue developers. + +**Progress Tracking:** No built-in tracking. Custom Vue `` component with localStorage is achievable. + +**Hosting:** Any static host ([GitHub Pages](https://pages.github.com/), [Netlify](https://www.netlify.com/), [Vercel](https://vercel.com/), [Cloudflare Pages](https://pages.cloudflare.com/)). + +**Customization:** High for Vue developers. Theme customization via CSS variables and component overrides. Configuration in TypeScript. + +**Ecosystem:** Mid-sized but growing, concentrated around Vue. Smaller than Docusaurus, larger than Nextra. + +**Verdict:** Similar capability to Docusaurus via Vue components, but no ready-made quiz or progress plugin. Requires Vue expertise. Less suitable for a Python/MkDocs team without a framework migration. + +--- + +### 4. [Astro](https://astro.build/) + [Starlight](https://starlight.astro.build/) + +[Astro](https://astro.build/) is a multi-framework static site builder (MIT) known for its "islands architecture" — zero JS shipped by default, components hydrated on demand. **[Starlight](https://starlight.astro.build/)** is Astro's official documentation theme. Astro 5.x (2025) introduced 5x faster Markdown builds and Live Content Collections. + +**Markdown Support:** Excellent — full [MDX](https://mdxjs.com/) with [Zod](https://zod.dev/) schema validation on frontmatter, [Shiki](https://shiki.style/) syntax highlighting, math, [Mermaid](https://mermaid.js.org/), remark/rehype plugins. Starlight adds its own content model. + +**Quiz Support:** No dedicated Starlight quiz plugin exists. Astro's MDX supports React, Vue, [Svelte](https://svelte.dev/), [Solid](https://www.solidjs.com/), [Preact](https://preactjs.com/), and [Alpine](https://alpinejs.dev/) components in the same file — the broadest multi-framework interoperability of any platform reviewed. + +**Progress Tracking:** No built-in learner progress tracking in Starlight. Astro's islands architecture supports `client:load` interactive components with localStorage access. + +**Hosting:** [Netlify](https://www.netlify.com/), [Vercel](https://vercel.com/), [Cloudflare Pages](https://pages.cloudflare.com/), [GitHub Pages](https://pages.github.com/), AWS, self-hosted. Astro's adapter system also supports SSR (server-side rendering), enabling server-backed progress tracking without a separate API — unique among static SSGs. + +**Customization:** Very high. Starlight allows component overrides, custom CSS, and plugin hooks. + +**Ecosystem:** Large and rapidly growing. Starlight has 40+ community plugins but lacks educational tooling specifically. + +**Verdict:** High ceiling, currently zero floor on quiz/progress plugins for Starlight. The SSR adapter option is the most powerful path to authenticated, server-persisted progress tracking among all platforms reviewed. Strong long-term candidate for a rebuild if custom components are acceptable. + +--- + +### 5. [GitBook](https://www.gitbook.com/) + +Commercial SaaS documentation platform (AI-native, cloud-only as of 2025). The legacy open-source GitBook (v2/v3) and its plugin ecosystem are abandoned. + +**Markdown Support:** Good — web editor with GitHub sync. Lacks fine-grained Markdown extension control. + +**Quiz Support:** Legacy open-source GitBook had two quiz plugins (`plugin-quizzes`, `plugin-quiz`) supporting radio/checkbox questions. **Modern GitBook SaaS has no quiz feature.** 2025 product updates focused on AI features (Lens AI search, auto-localization) — not interactive assessments. + +**Progress Tracking:** None. Analytics integrations (Google Analytics, Slack) track page views, not learner completion. + +**Hosting:** GitBook.com only. No self-hosted option. Custom domains on paid plans. + +**Pricing (2026):** Site plan required to publish. Plus: ~$10/user/month. Pro (AI): substantially higher. + +**Customization:** Low — limited to GitBook's built-in block system. No custom CSS/JS without workarounds. + +**Verdict:** Not recommended. No quiz support in the modern platform, SaaS-only hosting, pricing concerns for a volunteer/FRC team project, and no self-hosted path. + +--- + +### 6. [Docsify](https://docsify.js.org/) + +JavaScript runtime Markdown renderer (MIT). No build step — Markdown is fetched and rendered client-side. Extremely simple setup. + +**Markdown Support:** Good — CommonMark + GFM. Custom markdown-it plugins supported. + +**Quiz Support:** No quiz plugin in the [120+ plugin ecosystem](https://github.com/docsifyjs/awesome-docsify). [`docsify-interactive-checkboxes`](https://github.com/Msabre/docsify-interactive-checkboxes) adds persistent checkbox tasks but not true quiz functionality. + +**Progress Tracking:** [`docsify-progress`](https://github.com/HerbertHe/docsify-progress) shows a reading scroll-position bar (cosmetic, not persisted between visits). No cross-page completion tracking exists. + +**Hosting:** Any static host. Zero build process = simplest possible deployment. GitHub Pages trivially supported. + +**Customization:** Moderate — vanilla JS plugin API. No component system. + +**Key Weakness:** Runtime rendering means search engines see an empty page until JavaScript executes — significant SEO limitations. Performance degrades on large documentation sets. + +**Verdict:** Lowest capability for quiz and progress tracking. The no-build simplicity is appealing but insufficient for a structured tutorial platform requiring assessments. + +--- + +### 7. [Nextra](https://nextra.site/) + +[Next.js](https://nextjs.org/)-based documentation framework (MIT, v4 with App Router support). Used by some major documentation sites. + +**Markdown Support:** Excellent via MDX. Full React component embedding, math, syntax highlighting, custom remark/rehype plugins. + +**Quiz Support:** No dedicated plugin. Custom React quiz components can be authored in MDX files. + +**Progress Tracking:** No built-in tracking. Nextra 4 on App Router can use Next.js API routes for server-side progress, but this requires a backend host (not static GitHub Pages). + +**Hosting:** [Vercel](https://vercel.com/) (first-class), [Netlify](https://www.netlify.com/), [Cloudflare Pages](https://pages.cloudflare.com/) (static export). [GitHub Pages](https://pages.github.com/) requires `output: 'export'` mode, which disables dynamic features. + +**Customization:** High for Next.js/React developers. Less documentation-community tooling than Docusaurus. + +**Verdict:** Similar capability to Docusaurus but with a smaller ecosystem, Vercel-biased hosting, and no plugin directory. Not recommended unless the team is already in the Next.js ecosystem. [Fumadocs](https://fumadocs.vercel.app/) is frequently cited as a better-maintained Next.js documentation alternative in 2026. + +--- + +### 8. [mdBook](https://rust-lang.github.io/mdBook/) + +Rust-based static site generator (Apache 2.0 / MIT) maintained by the Rust language team. Used for official Rust documentation. + +**Markdown Support:** CommonMark + some GFM extensions. Custom preprocessors (Rust or any subprocess). [MathJax](https://www.mathjax.org/) and [Mermaid](https://mermaid.js.org/) via preprocessors. + +**Quiz Support:** +- [`mdbook-quiz`](https://crates.io/crates/mdbook-quiz) (Apache 2.0 + MIT, v0.4.0, 148 GitHub stars): dedicated quiz preprocessor with questions authored in TOML files. Question types: ShortAnswer, MultipleChoice, and Tracing (predict Rust code output — Rust-specific). Used by Will Crichton's Rust learning materials at Brown University. +- [`mdbook-exercises`](https://github.com/rust-lang/mdBook/wiki/Third-party-plugins) (December 2025): interactive exercise blocks with hints, solutions, [Rust Playground](https://play.rust-lang.org/) integration, difficulty levels, time estimates. +- `cache-answers = true` stores user answers in localStorage. + +**Progress Tracking:** `mdbook-quiz` with `cache-answers` persists quiz answers. No cross-book progress dashboard. + +**Hosting:** Any static host. GitHub Pages is first-class with documented CI workflows. + +**Customization:** Moderate — CSS overrides, custom JS, custom HTML. No component system. + +**Ecosystem:** Focused Rust community. Well-suited for Rust content; weaker for general programming education. + +**Verdict:** Excellent quiz infrastructure if you accept the Rust toolchain dependency and Rust-centric question types. `mdbook-quiz` is the most production-proven quiz plugin outside `mkdocs-quiz`. However, the Tracing question type is Rust-specific and migration from MkDocs is substantial. + +--- + +### 9. [LiaScript](https://liascript.github.io/) *(Purpose-Built)* + +Open-source browser-based Markdown interpreter (MIT) extended with interactive learning primitives. Developed at TU Bergakademie Freiberg for Open Educational Resources. No server or build step required. + +**Markdown Support:** Extended CommonMark dialect with LiaScript-specific extensions: text-to-speech, animated presentations, executable code blocks, and native quiz syntax. + +**Quiz Support (Built-In):** Multiple choice, single choice, matrix questions, gap text (fill-in-the-blank), text input, dropdown — all authored directly in Markdown, no plugins needed. Answers stored in localStorage with native progress tracking. + +**Progress Tracking:** Built-in. LiaScript is a Progressive Web App (PWA) and stores course progress locally. Works offline after first load. + +**LMS Export:** SCORM 1.2 and SCORM 2004 export via [`LiaScript-Exporter`](https://github.com/LiaScript/LiaScript-Exporter) CLI. Also exports to IMS, PDF, standalone web project, and Android APK. Most LMS-compatible platform reviewed — supports [Moodle](https://moodle.org/), [ILIAS](https://www.ilias.de/), [OpenOlat](https://www.openolat.com/), [Canvas](https://www.instructure.com/canvas), [Blackboard](https://www.anthology.com/products/teaching-and-learning/blackboard-learn). + +**Hosting:** Content can be served from GitHub, GitLab, Nextcloud, Dropbox, IPFS, and more. The LiaScript interpreter loads from a CDN and processes a Markdown URL — content and renderer are completely decoupled. + +**Customization:** Moderate. The UX is presentation/course-oriented (slides-style navigation), less documentation-site-like. Deep customization requires modifying the interpreter. + +**Ecosystem:** Smaller, concentrated in academic/OER communities. Growing international adoption for low-bandwidth educational deployments. + +**Verdict:** Strongest built-in quiz and progress story of any platform reviewed — without plugins. SCORM export is uniquely valuable for LMS integration with school-based FRC programs. The trade-off is a more course/presentation-oriented UX and a smaller developer community. + +--- + +## Feature Comparison Matrix + +| Feature | MkDocs + Material | Docusaurus | VitePress | Astro/Starlight | GitBook | Docsify | Nextra | mdBook | LiaScript | +|---|---|---|---|---|---|---|---|---|---| +| **Markdown quality** | Excellent | Excellent (MDX) | Very Good | Excellent (MDX) | Good | Good | Excellent (MDX) | Good | Extended MD | +| **Quiz plugin exists** | Yes | Partial (tasks) | No | No | Legacy only | No | No | Yes | Built-in | +| **Quiz question types** | MC, multi, fill-blank | Tasks/checklists | Custom Vue | Custom component | Legacy: radio/CB | Custom JS only | Custom React | SC, SA, Tracing | MC, SC, matrix, gap, text, dropdown | +| **Answer persistence** | localStorage | localStorage (tasks) | Manual | Manual | None | None | Manual | localStorage | localStorage (PWA) | +| **Cross-page progress** | Mark-as-read plugin | Tasks plugin (sidebar) | Manual | Manual | None | None | Manual | Manual | Built-in | +| **LMS export** | QTI (quiz plugin) | None | None | None | None | None | None | None | SCORM 1.2/2004 | +| **Self-hostable** | Yes | Yes | Yes | Yes | No | Yes | Yes (static) | Yes | Yes | +| **Component system** | Jinja2 + vanilla JS | React (MDX) | Vue (inline) | Multi-framework | None | Vanilla JS | React (MDX) | Vanilla JS | None (built-in) | +| **Extensibility** | High (Python plugins) | Very High (React) | High (Vue) | Very High (multi-FW) | Low (SaaS) | Moderate | High (React) | Moderate (Rust) | Low (fixed) | +| **Ecosystem size** | Large (in transition) | Very Large | Medium | Large & growing | Large (locked) | Medium | Small | Small (Rust) | Small (academic) | +| **Maintenance status** | Maintenance mode | Active (Meta) | Active (Vue team) | Very active | Active (SaaS) | Slow | Active | Active (Rust team) | Active | +| **License** | MIT | MIT | MIT | MIT | Proprietary SaaS | MIT | MIT | Apache 2.0/MIT | MIT | +| **Setup complexity** | Low (pip) | Medium (Node/React) | Medium (Node/Vue) | Medium (Node) | None (SaaS) | Very Low | Medium (Node/React) | Medium (Rust) | Very Low (CDN) | + +--- + +## Recommendations + +### Recommendation 1 — Minimal effort, maximum immediate capability + +**Stay on [MkDocs](https://www.mkdocs.org/) + [Material](https://squidfunk.github.io/mkdocs-material/) and add [`mkdocs-quiz`](https://ewels.github.io/mkdocs-quiz/) and [`mkdocs-material-mark-as-read`](https://github.com/berk-karaal/mkdocs-material-mark-as-read).** + +```bash +pip install mkdocs-quiz mkdocs-material-mark-as-read +``` + +Add to `mkdocs.yml`: +```yaml +plugins: + - search + - quiz + - mark-as-read +``` + +This delivers: +- Multiple question types (single choice, multiple choice, fill-in-the-blank) using familiar checkbox Markdown syntax +- localStorage answer persistence with a progress sidebar, out of the box +- QTI export for future LMS integration +- Page-level completion tracking ("mark as read") + +No framework migration. No new toolchain. Existing content unchanged. Working quizzes within an hour of setup. + +The Material maintenance-mode concern is real but not urgent — existing functionality is stable. Monitor **[Zensical](https://github.com/zensical/zensical)** (the MIT-licensed successor by the same maintainer) as the natural long-term migration target with minimal content changes. + +--- + +### Recommendation 2 — Migration path with maximum extensibility + +**Migrate to [Docusaurus](https://docusaurus.io/)** if React expertise exists and the project needs quiz UI beyond multiple-choice (e.g., embedded code exercises, interactive robot simulators, code-grading sandboxes). + +[MDX](https://mdxjs.com/) allows any React component in any page. The [`@sp-days-framework/docusaurus-plugin-interactive-tasks`](https://www.npmjs.com/package/@sp-days-framework/docusaurus-plugin-interactive-tasks) provides structured task completion with sidebar badges immediately. Custom quiz components can be built incrementally. Docusaurus has no maintenance concerns and the largest ecosystem. + +Migration cost: several days — `mkdocs.yml` nav → `docusaurus.config.js`, PyMdown admonition syntax → Docusaurus admonition syntax, custom overrides rebuild. + +--- + +### Recommendation 3 — LMS integration requirement + +**Evaluate [LiaScript](https://liascript.github.io/)** alongside the existing MkDocs reference site if FRC tutorial content needs to integrate with school LMS platforms ([Moodle](https://moodle.org/), [Canvas](https://www.instructure.com/canvas)) for grading or credit. + +LiaScript Markdown files can live in the same GitHub repository. SCORM export is a one-command CLI operation. The course content (Markdown) is shared; only the rendering layer differs between the MkDocs documentation site and the LiaScript course export. + +--- + +### Long-term watch: Zensical + +[Zensical](https://github.com/zensical/zensical) (MIT, by the Material for MkDocs team) is the spiritual successor to Material for MkDocs. It reads existing `mkdocs.yml` files and opens its module/plugin system to third-party developers in early 2026. Once the ecosystem matures — especially if `mkdocs-quiz` authors publish a compatible version — it is the natural migration target from the current stack with minimal content changes. + +--- + +## Sources + +- [mkdocs-quiz (ewels)](https://ewels.github.io/mkdocs-quiz/) — Apache 2.0, v1.6.2, March 2026 +- [mkdocs-material-mark-as-read](https://github.com/berk-karaal/mkdocs-material-mark-as-read) +- [Material for MkDocs maintenance mode announcement](https://github.com/squidfunk/mkdocs-material/issues/8523) +- [Zensical announcement and repo](https://github.com/zensical/zensical) +- [mdbook-quiz](https://github.com/cognitive-engineering-lab/mdbook-quiz) +- [mdbook-exercises](https://github.com/rust-lang/mdBook/wiki/Third-party-plugins) +- [Docusaurus Community Plugin Directory](https://docusaurus.community/plugindirectory/) +- [@sp-days-framework/docusaurus-plugin-interactive-tasks](https://www.npmjs.com/package/@sp-days-framework/docusaurus-plugin-interactive-tasks) +- [LiaScript](https://liascript.github.io/) — MIT, PWA, SCORM export +- [LiaScript-Exporter](https://github.com/LiaScript/LiaScript-Exporter) +- [Docusaurus 3.9 release](https://docusaurus.io/blog) +- [Astro Starlight plugins](https://starlight.astro.build/resources/plugins/) +- [Fumadocs vs Nextra vs Starlight (2026)](https://www.pkgpulse.com/blog/fumadocs-vs-nextra-v4-vs-starlight-documentation-sites-2026) +- [H5P interactive content](https://h5p.org/) — embeddable quiz widgets for any platform