Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .vale.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,26 @@ StylesPath = .styles
MinAlertLevel = warning
Packages = Google, write-good

# Frcsoftware.Spelling (below) is our spellcheck rule; it replaces Vale's
# built-in Vale.Spelling so we control the dictionary in use. The 'Vale'
# style itself must stay in BasedOnStyles since it's what makes the Vocab
# below (and Vale.Terms/Vale.Avoid) take effect.
#
# To allow a word through spellcheck, or to enforce that a term always
# appears with a specific casing (e.g. "WPILib", not "wpilib"), add it to
# vale-accept-words.txt or src/data/glossary.ts (see setup-vale.ts).
Vocab = Frcsoftware

[*.mdx]
BasedOnStyles = Google, write-good
BasedOnStyles = Vale, Google, write-good, Frcsoftware

# "Disabled" and "enabled" are the names of the robot's states in FRC (they're
# what the Driver Station's Enable/Disable buttons put the robot into), so
# Google.WordListCase's "use 'turn off' instead" advice doesn't apply to them.
TokenIgnores = (?i)\b(?:disabled|enabled)\b

Vale.Spelling = NO
Frcsoftware.Spelling = YES

# Google style - selectively enable useful rules
Google.GenderBias = error
Expand Down
125 changes: 93 additions & 32 deletions scripts/setup-vale.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import { createWriteStream, writeFileSync, existsSync, mkdirSync } from 'fs';
import { resolve, dirname } from 'path';
import {
createWriteStream,
writeFileSync,
readFileSync,
copyFileSync,
existsSync,
mkdirSync,
} from 'fs';
import { resolve } from 'path';
import { fileURLToPath } from 'url';
import { glossaryTerms } from '../src/data/glossary';
import { pipeline } from 'stream/promises';

// Update glossary terms
const ROOT = fileURLToPath(new URL('..', import.meta.url));
const OUTPUT = resolve(ROOT, '.styles/config/ignore/glossary.txt');
const OUTPUT_DIR = dirname(OUTPUT);

if (!existsSync(OUTPUT_DIR)) {
mkdirSync(OUTPUT_DIR, { recursive: true });
}

const terms = [...new Set(glossaryTerms.map(({ term }) => term))].sort((a, b) =>
a.toLowerCase().localeCompare(b.toLowerCase()),
);
const DICT_DIR = resolve(ROOT, '.styles/config/dictionaries');
const DIC_PATH = resolve(DICT_DIR, 'en_US.dic');
const AFF_PATH = resolve(DICT_DIR, 'en_US.aff');

const content = terms.join('\n') + '\n';
writeFileSync(OUTPUT, content);
const ACCEPT_WORDS_PATH = resolve(ROOT, 'vale-accept-words.txt');
const SPELLING_RULE_PATH = resolve(ROOT, 'scripts/vale-spelling-rule.yml');

console.log(`Wrote ${terms.length} glossary terms to ${OUTPUT}.`);
const VOCAB_DIR = resolve(ROOT, '.styles/config/vocabularies/Frcsoftware');
const STYLE_DIR = resolve(ROOT, '.styles/Frcsoftware');

// If not already present, download dictionary
async function downloadFile(url: string, path: string) {
const response = await fetch(url);
if (!response.ok)
Expand All @@ -38,20 +38,81 @@ async function downloadFile(url: string, path: string) {
await pipeline(response.body, fileStream);
}

const dictsToDownload = [
{
path: resolve(ROOT, '.styles/config/dictionaries/en_US.dic'),
url: 'https://raw.githubusercontent.com/LibreOffice/dictionaries/refs/tags/libreoffice-26.2.5.1/en/en_US.dic',
},
{
path: resolve(ROOT, '.styles/config/dictionaries/en_US.aff'),
url: 'https://raw.githubusercontent.com/LibreOffice/dictionaries/refs/tags/libreoffice-26.2.5.1/en/en_US.aff',
},
];

dictsToDownload.forEach((dict) => {
if (!existsSync(dict.path)) {
mkdirSync(dirname(dict.path), { recursive: true });
downloadFile(dict.url, dict.path);
// Vale's spelling check needs a Hunspell dictionary; we pin a specific
// LibreOffice release rather than relying on whatever Vale bundles.
async function ensureDictionaries() {
if (existsSync(DIC_PATH) && existsSync(AFF_PATH)) return;

mkdirSync(DICT_DIR, { recursive: true });
const version = 'libreoffice-26.2.5.1';
await Promise.all([
downloadFile(
`https://raw.githubusercontent.com/LibreOffice/dictionaries/refs/tags/${version}/en/en_US.dic`,
DIC_PATH,
),
downloadFile(
`https://raw.githubusercontent.com/LibreOffice/dictionaries/refs/tags/${version}/en/en_US.aff`,
AFF_PATH,
),
]);
}

// Hunspell .dic files are "<word count>\n<word>/<affix flags>\n...".
function loadDictionaryWords(): Set<string> {
const [, ...lines] = readFileSync(DIC_PATH, 'utf-8').split('\n');
const words = new Set<string>();
for (const line of lines) {
const word = (line.split('/')[0] ?? '').trim();
if (word) words.add(word.toLowerCase());
}
});
return words;
}

function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

// Builds Vale's Vocab accept list (`.styles/config/vocabularies/Frcsoftware/accept.txt`)
// from two committed sources:
// - vale-accept-words.txt: plain jargon words, always accepted regardless of casing.
// - src/data/glossary.ts: terms with tooltip definitions. A term marked
// `caseSensitive: true` is enforced with its exact casing (e.g. "WPILib"),
// UNLESS its lowercase form is itself a real English word (e.g. "CAN"),
// in which case enforcing casing would flag ordinary prose ("can you...")
// as an error, so it falls back to case-insensitive acceptance.
function buildAcceptEntries(dictionaryWords: Set<string>): string[] {
const entries = new Set<string>();

for (const { term, caseSensitive } of glossaryTerms) {
const enforceCase =
caseSensitive && !dictionaryWords.has(term.toLowerCase());
entries.add(
enforceCase ? escapeRegExp(term) : `(?i)${escapeRegExp(term)}`,
);
}

const customWords = readFileSync(ACCEPT_WORDS_PATH, 'utf-8')
.split('\n')
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#'));
for (const word of customWords) {
entries.add(`(?i)${escapeRegExp(word)}`);
}

return [...entries].sort((a, b) =>
a.toLowerCase().localeCompare(b.toLowerCase()),
);
}

await ensureDictionaries();

const acceptEntries = buildAcceptEntries(loadDictionaryWords());
mkdirSync(VOCAB_DIR, { recursive: true });
writeFileSync(
resolve(VOCAB_DIR, 'accept.txt'),
acceptEntries.join('\n') + '\n',
);
console.log(`Wrote ${acceptEntries.length} accepted terms to Vale vocabulary.`);

mkdirSync(STYLE_DIR, { recursive: true });
copyFileSync(SPELLING_RULE_PATH, resolve(STYLE_DIR, 'Spelling.yml'));
5 changes: 5 additions & 0 deletions scripts/vale-spelling-rule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
extends: spelling
message: "Did you really mean '%s'?"
level: warning
dictionaries:
- en_US
2 changes: 1 addition & 1 deletion src/content/docs/best-practices/git-usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ For advanced users, the command line offers more capabilities.

The `main` branch is where the working, tested version of the code lives during the build season.
When multiple programmers are working on different changes, creating separate development branches can help prevent merge conflicts.
For example, creating a seprate branch for vision code ensures that the code on the drivetrain branch isn't affected.
For example, creating a separate branch for vision code ensures that the code on the drivetrain branch isn't affected.
Making branches for each competition helps isolate fixes and ensures that code is still reviewed before merging to `main`.

To maintain a branch, you must stay up to date with `main`.
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/contribution/methodsOfContributing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ You can get a local hosted version of the website to have a live preview of the

1. Open the repository in VS Code (doesn't matter which branch)
2. Toggle the bottom panel on if there isn't one with the shortcut `Ctrl + J`
3. Click the dropdown next to the + on the top righthand side of the bottom panel and click "Terminal"
3. Click the dropdown next to the + on the top right-hand side of the bottom panel and click "Terminal"
4. Run the command `pnpm install` to install all needed packages (FIRST TIME)
5. Run the command `pnpm dev` to start the development server
6. If everything went smoothly it should say its serving on something like `http://localhost:4321`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ You can find the download link [here](https://github.com/wpilibsuite/allwpilib/r
tools and not 2026 WPILib tools.
</Aside>

Once you have downloaded the 2027 WPILib tools, you can follow the instructions on how to set up the WPILib tools here [here](https://docs.wpilib.org/en/stable/docs/zero-to-robot/step-2/wpilib-setup.html).
Once you have downloaded the 2027 WPILib tools, you can follow the instructions on how to set up the WPILib tools [here](https://docs.wpilib.org/en/stable/docs/zero-to-robot/step-2/wpilib-setup.html).

The WPILib tool package also includes different programs that are useful for data logging, simulation, dashboards, and more.
We will use some of these tools in later stages.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ VS Code's layout is categorized into a few key regions:
The editor is star of the show and it is where you can view and edit files.
- Next, the **Panel** is at the bottom of the screen.
It has four main views, which are the Terminal, Problems, Output, and Debug Console.
- Finally, the **Command Palette** is accesed through `Ctrl+Shift+P` / `Cmd+Shift+P` and it allows for you to search and run any command in VS Code.
- Finally, the **Command Palette** is accessed through `Ctrl+Shift+P` / `Cmd+Shift+P` and it allows for you to search and run any command in VS Code.

<ContentFigure
width="564"
Expand Down Expand Up @@ -82,7 +82,7 @@ A dot on a file's tab indicates **unsaved changes**; it turns into an "X" (close
## WPILib VS Code

For FRC, you use a separate version of VS Code developed by WPILib which is part of the WPILib installer.
If you already have VS Code installed on your computer, the WPILib VS Code installs seperately with the WPILib extension already installed and some settings changed.
If you already have VS Code installed on your computer, the WPILib VS Code installs separately with the WPILib extension already installed and some settings changed.

### WPILib Commands

Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/learning-course/stage0/operators.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ codeRegionSources:
default: stage0/snippets/src/Operators.java
---

In Java, operators are used to change or compare the values of variables.
Java uses operators to change or compare the values of variables.
There four different types of operators are:

- Arithmetic Operators
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Robot code needs to be run in a loop so that it can continually make new command
To accomplish this Periodic Methods are provided in the `Robot` class and `OpMode` classes.
Periodic methods get called every 20ms by default, causing any code placed inside of them to be run 50 times per second.
OpModeRobot has additional periodic methods that only run during specific robot states.
For example, the `teleopPeriodic()` function will only be called when teleop mode is selected on the driverstation.
For example, the `teleopPeriodic()` function will only be called when teleop mode is selected on the Driver Station.

At the moment `DrivetrainSim` will not actually do anything because it is not being told to update periodically.
To fix this, `DrivetrainSim`'s `periodic()` function should be called inside of the `Robot` class' `simulationPeriodic()` function.
Expand Down Expand Up @@ -110,8 +110,8 @@ After adding the simulation code your `Robot.java` file should now look like thi
When simulating code there are two main windows to control and visualize what the code is doing.

The first important window is the Sim GUI.
The Sim GUI is automatically opened when simulating code and acts as both a driverstation and shows information about simulated devices such as position and velocity.
More information about the Sim GUI can be found in [WPILIb's documentation](https://docs.wpilib.org/en/stable/docs/software/wpilib-tools/robot-simulation/simulation-gui.html).
The Sim GUI is automatically opened when simulating code and acts as both a Driver Station and shows information about simulated devices such as position and velocity.
More information about the Sim GUI can be found in [WPILib's documentation](https://docs.wpilib.org/en/stable/docs/software/wpilib-tools/robot-simulation/simulation-gui.html).

The other important window is a program called AdvantageScope.
AdvantageScope is bundled with WPILib and is used to visualize data sent by the robot.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ This will intake the fuel.
</Tabs>

When the controller's A button is pressed the IntakeLauncher motor should have a throttle of -0.8 while the Feeder motor should have a throttle of 1.0.
This will outake the fuel.
This will outtake the fuel.

<Tabs syncKey={REV_CTRE_CHOOSER_KEY}>
<TabItem label="CTRE">
Expand Down Expand Up @@ -249,7 +249,7 @@ This will stop the motors.

The process for testing the new motors in teleop is very similar to the process used to test the drivetrain.
Holding the "E" key will cause the robot to intake, "Q" will cause the robot to launch.
and "R" will cause the robot to outake.
and "R" will cause the robot to outtake.
Instead of looking at the 2D Field tab in Advantage Scope, use the Line Graph tab to view data from the motors.
The voltage applied from the motor controllers to their motors can be viewed on the left axis while the right axis shows the motors' velocities.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ When creating a motor controller object, the physical motor controller's CAN ID
CAN Bus refers to which of the 5 Systemcore CAN ports, or which CANivore, the device is plugged into.
CAN ID is an integer that each CAN device is configured to have.
All devices on a given CAN Bus must have a unique ID.
Using the combination of CAN Bus and CAN ID SystemCore can give commands to the correct motor controller.
Using the combination of CAN Bus and CAN ID Systemcore can give commands to the correct motor controller.

For this exercise the motor controllers will have the IDs:

Expand Down Expand Up @@ -121,7 +121,7 @@ the configurations that need to be added and their proper values.
For this section only the motor controller's invert setting will be configured.
This setting controls what direction a motor spins when the motor controller is given a command with a positive sign.
Since there are two motors on each side of the drivetrain, its important to ensure that the each of the motors on a side
move in sync with eachother.
move in sync with each other.
This can be accomplished by telling one of the motor controllers to follow the other.
This is why one motor is named Leader and the other is Follower.
The code tells the Follower to listen to the commands given to the Leader.
Expand Down Expand Up @@ -225,7 +225,7 @@ Remember that some of the configurations may be different.

While there are several ways to control a tank drive, this stage will be using arcade drive.
Arcade drive uses the y-axis of a joystick to control how fast the robot drives forward or backward while the x-axis controls how fast the robot rotates clockwise or counter clockwise.
WPIlib provides a class to convert joystick inputs into commands for the motors to follow called `DifferentialDrive`.
WPILib provides a class to convert joystick inputs into commands for the motors to follow called `DifferentialDrive`.

An instance of `DifferentialDrive` should be created under where the motor controllers were declared.

Expand Down Expand Up @@ -316,10 +316,10 @@ By now your `Robot` class in `Robot.java` should look like this and VS code shou

# OpModes

`OpMode`s are a class that registers itself with the driverstation providing a name and robot mode (autonomous, teleop, or utility).
This allows the robot to run different code based on what is selected on the driverstation.
`OpMode`s are a class that registers itself with the Driver Station providing a name and robot mode (autonomous, teleop, or utility).
This allows the robot to run different code based on what is selected on the Driver Station.
This stage will use classes that extend `PeriodicOpMode.`
By extending `PeriodicOpMode` these classes gain a few useful functions that are only called when the OpMode is selected on the driverstation.
By extending `PeriodicOpMode` these classes gain a few useful functions that are only called when the OpMode is selected on the Driver Station.

- `start()` is called once when the robot transitions from disabled to enabled.
- `periodic()` is called repeatedly when the robot is enabled.
Expand All @@ -332,7 +332,7 @@ Two blank `PeriodicOpMode`s, `MyTeleop.java` and `MyAuto.java` are provided unde
To control the robot with joysticks a Teleop OpMode needs to be created that periodically gives the `DifferentialDrive` instance new values from the controller.
First a instance of `NiDsXboxController` needs to be created.
This class has functions that provide the state of different buttons on the controller.
Multiple controllers can be used at once so the driverstation gives each a slot.
Multiple controllers can be used at once so the Driver Station gives each a slot.
The index provided in the constructor tells the `NiDsXboxController`which slot to listen too.

<Tabs syncKey={REV_CTRE_CHOOSER_KEY}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ Additionally both game pieces and your robot are in a known position allowing fo
You also need to coordinate your autonomous routine with your alliance partners so it is important to have multiple routines and the ability to adjust them.

An autonomous routine is simply an OpMode that gets run during the autonomous portion of the match.
When writing an OpMode that will be run during the autonomous mode, there is an `@Autonomous` annotation that is added above the class definition.
This annotation tells the driverstation to place the OpMode under the autonomous selector.
When writing an OpMode that will be run during the autonomous mode, there is an `@Autonomous` annotation that is added directly before the class definition.
This annotation tells the Driver Station to place the OpMode under the autonomous selector.
This OpMode will use a `Timer` to base its behavior off of the time elapsed from the beginning of the autonomous period.
Time-based autos trade off simplicity for reliability, since slight variations in starting configuration could cause your robot to take more or less time to perform different actions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ set of instructions (execute this task, then this, and finally this).
However, robots (and humans!) don't just execute a set of tasks and shut down.

Imagine your daily routine.
When your alarm clock rings, you turn it off, then tumble out of bed.
When an alarm clock rings, you turn it off, then tumble out of bed.
When your belly rumbles, you walk to the fridge, then get a snack.
When the clock strikes 8 AM, you open the front door to leave for school.
When the clock strikes 8 AM,
you open the front door to leave for school.

<ContentImage src="/learning-course/stage1/stage1b/human-flow-diagram.webp" />

Expand Down Expand Up @@ -64,7 +65,7 @@ the mechanisms they require - thus, mechanisms are also called "requirements".

An intake is a mechanism because your code controls its speed.
On the other hand,
an apriltag camera wouldn't be because your code only reads data from it, but doesn't update its state.
an AprilTag camera wouldn't be because your code only reads data from it, but doesn't update its state.

<Aside type="note">
In the human case, your arms and legs would be mechanisms, but your ears
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Triggers can schedule commands in two ways:

2. `trigger.whileTrue(Command)`: Identical to onTrue, but cancels the running command when the trigger becomes inactive again.
If `teleopEnabledTrigger.onTrue(...)` was changed to `teleopEnabledTrigger.whileTrue(...)`, disabling teleop mode
on the driver station will cancel the `runAtThrottle` command mid-run.
on the Driver Station will cancel the `runAtThrottle` command mid-run.

<Aside type="note">
You can bind multiple commands to the same trigger!
Expand Down
Loading