diff --git a/.gitignore b/.gitignore index 16253cd..565bfac 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ bun.lock .gradle */build/* +# IntelliJ +.idea \ No newline at end of file diff --git a/examples/stage1/snippets/.gitignore b/examples/stage1/snippets/.gitignore new file mode 100644 index 0000000..34cbaac --- /dev/null +++ b/examples/stage1/snippets/.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/examples/stage1/snippets/.wpilib/wpilib_preferences.json b/examples/stage1/snippets/.wpilib/wpilib_preferences.json new file mode 100644 index 0000000..19f541e --- /dev/null +++ b/examples/stage1/snippets/.wpilib/wpilib_preferences.json @@ -0,0 +1,6 @@ +{ + "enableCppIntellisense": false, + "currentLanguage": "java", + "projectYear": "2027_alpha5", + "teamNumber": 9999 +} diff --git a/examples/stage1/snippets/WPILib-License.md b/examples/stage1/snippets/WPILib-License.md new file mode 100644 index 0000000..c2891d4 --- /dev/null +++ b/examples/stage1/snippets/WPILib-License.md @@ -0,0 +1,26 @@ +Copyright (c) 2009-2026 FIRST and other WPILib contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +- Neither the name of FIRST, WPILib, nor the names of other WPILib + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY FIRST AND OTHER WPILIB CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY NONINFRINGEMENT AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL FIRST OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/examples/stage1/snippets/build.gradle b/examples/stage1/snippets/build.gradle new file mode 100644 index 0000000..f729df7 --- /dev/null +++ b/examples/stage1/snippets/build.gradle @@ -0,0 +1,147 @@ +plugins { + id "java" + id "org.wpilib.GradleRIO" version "2027.0.0-alpha-6" + id "com.gradleup.shadow" version "9.3.0" + id "com.diffplug.spotless" version "8.0.0" +} + +java { + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 +} + +def ROBOT_MAIN_CLASS = "" + +// Define my targets (SystemCore) and artifacts (deployable files) +// This is added by GradleRIO's backing project DeployUtils. +deploy { + targets { + systemcore(getTargetTypeClass('SystemCore')) { + // Team number is loaded either from the .wpilib/wpilib_preferences.json + // or from command line. If not found an exception will be thrown. + // You can use getTeamOrDefault(team) instead of getTeamNumber if you + // want to store a team number in this file. + team = project.wpilib.getTeamNumber() + // Use the default systemcore host name. This must be called after setting team + // as happens on the line above + useDefaultSystemcoreHostName() + debug = project.wpilib.getDebugOrDefault(false) + + artifacts { + // First part is artifact name, 2nd is artifact type + // getTargetTypeClass is a shortcut to get the class type using a string + + wpilibJava(getArtifactTypeClass('WPILibJavaArtifact')) { + } + + // Static files artifact + wpilibStaticFileDeploy(getArtifactTypeClass('FileTreeArtifact')) { + files = project.fileTree('src/main/deploy') + directory = '/home/systemcore/deploy' + deleteOldFiles = false // Change to true to delete files on systemcore that no + // longer exist in deploy directory of this project + } + } + } + } +} + +def deployArtifact = deploy.targets.systemcore.artifacts.wpilibJava + +// Set to true to use debug for all targets including JNI, which will drastically impact +// performance. +wpi.java.debugJni = false + +// Set this to true to enable desktop support. +def includeDesktopSupport = false + +// Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries. +// Also defines JUnit 5. +dependencies { + annotationProcessor wpi.java.deps.wpilibAnnotations() + implementation wpi.java.deps.wpilib() + implementation wpi.java.vendor.java() + + systemcoreDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.systemcore) + systemcoreDebug wpi.java.vendor.jniDebug(wpi.platforms.systemcore) + + systemcoreRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.systemcore) + systemcoreRelease wpi.java.vendor.jniRelease(wpi.platforms.systemcore) + + nativeDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.desktop) + nativeDebug wpi.java.vendor.jniDebug(wpi.platforms.desktop) + simulationDebug wpi.sim.enableDebug() + + nativeRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.desktop) + nativeRelease wpi.java.vendor.jniRelease(wpi.platforms.desktop) + simulationRelease wpi.sim.enableRelease() + + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +test { + useJUnitPlatform() + systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true' +} + +// Simulation configuration (e.g. environment variables). +wpi.sim.addGui().defaultEnabled = true +wpi.sim.addDriverstation() + +// Setting up my Jar File. In this case, adding all libraries into the main jar ('fat/shaded jar') +// in order to make them all available at runtime and merging service files to make JSON work. +// Also adding the manifest so WPILib knows where to look for our Robot Class. +shadowJar { + mergeServiceFiles() + from('src') { into 'backup/src' } + from('vendordeps') { into 'backup/vendordeps' } + from('build.gradle') { into 'backup' } + manifest org.wpilib.gradlerio.GradleRIOPlugin.javaManifest(ROBOT_MAIN_CLASS) + duplicatesStrategy = DuplicatesStrategy.INCLUDE +} + +// Configure jar and deploy tasks +deployArtifact.jarTask = shadowJar +wpi.java.configureExecutableTasks(shadowJar) +wpi.java.configureTestTasks(test) + +// Configure string concat to always inline compile +tasks.withType(JavaCompile) { + options.compilerArgs.add '-XDstringConcat=inline' +} + +project.compileJava.dependsOn(spotlessApply) +spotless { + java { + target fileTree(".") { + include "**/*.java" + exclude "**/build/**", "**/build-*/**" + } + toggleOffOn() + palantirJavaFormat() + leadingSpacesToTabs(4) + leadingTabsToSpaces(2) + trimTrailingWhitespace() + endWithNewline() + } + groovyGradle { + target fileTree(".") { + include "**/*.gradle" + exclude "**/build/**", "**/build-*/**" + } + greclipse() + leadingTabsToSpaces(4) + trimTrailingWhitespace() + endWithNewline() + } + format "misc", { + target fileTree(".") { + include "**/*.md", "**/.gitignore" + exclude "**/build/**", "**/build-*/**" + } + trimTrailingWhitespace() + leadingTabsToSpaces(2) + endWithNewline() + } +} diff --git a/examples/stage1/snippets/gradle/wrapper/gradle-wrapper.jar b/examples/stage1/snippets/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..d997cfc Binary files /dev/null and b/examples/stage1/snippets/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/stage1/snippets/gradle/wrapper/gradle-wrapper.properties b/examples/stage1/snippets/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..42bb5ac --- /dev/null +++ b/examples/stage1/snippets/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=permwrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=permwrapper/dists diff --git a/examples/stage1/snippets/gradlew b/examples/stage1/snippets/gradlew new file mode 100755 index 0000000..739907d --- /dev/null +++ b/examples/stage1/snippets/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/stage1/snippets/gradlew.bat b/examples/stage1/snippets/gradlew.bat new file mode 100644 index 0000000..c4bdd3a --- /dev/null +++ b/examples/stage1/snippets/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/stage1/snippets/settings.gradle b/examples/stage1/snippets/settings.gradle new file mode 100644 index 0000000..e2626a8 --- /dev/null +++ b/examples/stage1/snippets/settings.gradle @@ -0,0 +1,30 @@ +import org.gradle.internal.os.OperatingSystem + +pluginManagement { + repositories { + String wpilibYear = '2027_alpha5' + File wpilibHome + if (OperatingSystem.current().isWindows()) { + String publicFolder = System.getenv('PUBLIC') + if (publicFolder == null) { + publicFolder = "C:\\Users\\Public" + } + def homeRoot = new File(publicFolder, "wpilib") + wpilibHome = new File(homeRoot, wpilibYear) + } else { + def userFolder = System.getProperty("user.home") + def homeRoot = new File(userFolder, "wpilib") + wpilibHome = new File(homeRoot, wpilibYear) + } + def wpilibHomeMaven = new File(wpilibHome, 'maven') + maven { + name = 'wpilibHome' + url = wpilibHomeMaven + } + mavenLocal() + gradlePluginPortal() + } +} + +Properties props = System.getProperties(); +props.setProperty("org.gradle.internal.native.headers.unresolved.dependencies.ignore", "true"); diff --git a/examples/stage1/snippets/src/main/java/org/wpilib/command3/Mechanism.java b/examples/stage1/snippets/src/main/java/org/wpilib/command3/Mechanism.java new file mode 100644 index 0000000..e2de2b8 --- /dev/null +++ b/examples/stage1/snippets/src/main/java/org/wpilib/command3/Mechanism.java @@ -0,0 +1,127 @@ +// 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 org.wpilib.command3; + +import java.util.List; +import java.util.function.Consumer; +import org.wpilib.annotation.NoDiscard; +import org.wpilib.units.measure.Time; + +/** + * A "shim" for the `Mechanism` interface. In WPILib 2027 alpha 6, Mechanisms were still classes; + * however, alpha 7 changes them to interfaces. In an effort to not rewrite any docs, this shim will + * exist for now until alpha 7 releases. + */ +public interface Mechanism { + /** + * Returns the scheduler under which this subsystem and its default commands are registered. The + * scheduler is also used to fetch running commands for the subsystem. + * + * @return The registered scheduler. + */ + default Scheduler getRegisteredScheduler() { + return Scheduler.getDefault(); + } + + /** + * Gets the name of this mechanism. This will default to the name of this mechanism's class. + * + * @return The name of the mechanism. + */ + @NoDiscard + default String getName() { + return getClass().getSimpleName(); + } + + /** + * Sets the default command to run on the mechanism when no other command is scheduled. The + * default command's priority is effectively the minimum allowable priority for any command + * requiring a mechanism. For this reason, it's recommended that a default command have a priority + * less than {@link Command#DEFAULT_PRIORITY} so it doesn't prevent low-priority commands from + * running. + * + *

The default command is initially an idle command that only owns the mechanism without doing + * anything. This command has the lowest possible priority to allow any other command to run. + * + * @param defaultCommand the new default command + */ + default void setDefaultCommand(Command defaultCommand) { + getRegisteredScheduler().setDefaultCommand(this, defaultCommand); + } + + /** + * Gets the default command that was set by the latest call to {@link + * #setDefaultCommand(Command)}. + * + * @return The currently configured default command + */ + default Command getDefaultCommand() { + return getRegisteredScheduler().getDefaultCommandFor(this); + } + + /** + * Starts building a command that requires this mechanism. + * + * @param commandBody The main function body of the command. + * @return The command builder, for further configuration. + */ + default NeedsNameBuilderStage run(Consumer commandBody) { + return new StagedCommandBuilder().requiring(this).executing(commandBody); + } + + /** + * Starts building a command that requires this mechanism. The given function will be called + * repeatedly in an infinite loop. Useful for building commands that don't need state or multiple + * stages of logic. + * + * @param loopBody The body of the infinite loop. + * @return The command builder, for further configuration. + */ + default NeedsNameBuilderStage runRepeatedly(Runnable loopBody) { + return run(coroutine -> { + while (true) { + loopBody.run(); + coroutine.yield(); + } + }); + } + + /** + * Returns a command that idles this mechanism until another command claims it. The idle command + * has {@link Command#LOWEST_PRIORITY the lowest priority} and can be interrupted by any other + * command. + * + *

The {@link #getDefaultCommand() default command} for every mechanism is an idle command + * unless a different default command has been configured. + * + * @return A new idle command. + */ + default Command idle() { + return run(Coroutine::park).withPriority(Command.LOWEST_PRIORITY).named(getName() + "[IDLE]"); + } + + /** + * Returns a command that idles this mechanism for the given duration of time. + * + * @param duration How long the mechanism should idle for. + * @return A new idle command. + */ + default Command idleFor(Time duration) { + return idle().withTimeout(duration); + } + + /** + * Gets all running commands that require this mechanism. Commands are returned in the order in + * which they were scheduled. The returned list is read-only. Every command in the list will have + * been scheduled by the previous entry in the list or by intermediate commands that do not + * require the mechanism. + * + * @return The currently running commands that require the mechanism. + */ + @NoDiscard + default List getRunningCommands() { + return getRegisteredScheduler().getRunningCommandsFor(this); + } +} diff --git a/examples/stage1/snippets/src/main/java/org/wpilib/command3/button/CommandXboxController.java b/examples/stage1/snippets/src/main/java/org/wpilib/command3/button/CommandXboxController.java new file mode 100644 index 0000000..b07e7ca --- /dev/null +++ b/examples/stage1/snippets/src/main/java/org/wpilib/command3/button/CommandXboxController.java @@ -0,0 +1,23 @@ +package org.wpilib.command3.button; + +import org.wpilib.command3.Trigger; + +/** + * A shim for the `CommandXboxController` class, which isn't yet present in wpilib beta 6 (but is in + * beta 7). + */ +public class CommandXboxController { + public CommandXboxController(int port) {} + + public double getLeftY() { + return 0; + } + + public Trigger a() { + return new Trigger(() -> false); + } + + public Trigger leftBumper() { + return new Trigger(() -> false); + } +} diff --git a/examples/stage1/snippets/src/main/java/org/wpilib/driverstation/XboxController.java b/examples/stage1/snippets/src/main/java/org/wpilib/driverstation/XboxController.java new file mode 100644 index 0000000..41ef170 --- /dev/null +++ b/examples/stage1/snippets/src/main/java/org/wpilib/driverstation/XboxController.java @@ -0,0 +1,13 @@ +package org.wpilib.driverstation; + +/** + * A shim for the `CommandXboxController` class, which isn't yet present in wpilib beta 6 (but is in + * beta 7). + */ +public class XboxController { + public XboxController(int id) {} + + public boolean getAButton() { + return false; + } +} diff --git a/examples/stage1/snippets/src/main/java/sources/CommandBasedKitbot.java b/examples/stage1/snippets/src/main/java/sources/CommandBasedKitbot.java new file mode 100644 index 0000000..67abc56 --- /dev/null +++ b/examples/stage1/snippets/src/main/java/sources/CommandBasedKitbot.java @@ -0,0 +1,43 @@ +package sources; + +import org.wpilib.command3.Command; +import org.wpilib.command3.Scheduler; +import org.wpilib.command3.button.CommandXboxController; +import org.wpilib.framework.OpModeRobot; +import org.wpilib.opmode.PeriodicOpMode; + +class CommandBasedKitbot { + void schedulerExample() { + // [robotDef] + class Robot extends OpModeRobot { + @Override + public void robotPeriodic() { + Scheduler.getDefault().run(); + } + } + // [/robotDef] + } + + class MyTeleop extends PeriodicOpMode { + private final CommandXboxController xbox = new CommandXboxController(0); + + // [triggerBindingDef] + public MyTeleop(Robot robot) { + xbox.leftBumper().whileTrue(robot.intake.intake()).whileTrue(robot.feeder.intake()); + + // now fill in the rest... + } + // [/triggerBindingDef] + } + + class ExampleMechanism { + Command intake() { + return null; + } + } + + class Robot { + ExampleMechanism intake = new ExampleMechanism(); + ExampleMechanism feeder = new ExampleMechanism(); + } +} diff --git a/examples/stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java b/examples/stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java new file mode 100644 index 0000000..2726831 --- /dev/null +++ b/examples/stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java @@ -0,0 +1,75 @@ +package sources; + +import static org.wpilib.units.Units.Seconds; + +import java.util.function.DoubleSupplier; +import org.wpilib.command3.Command; +import org.wpilib.command3.Mechanism; +import org.wpilib.command3.Scheduler; +import org.wpilib.drive.DifferentialDrive; +import org.wpilib.hardware.imu.OnboardIMU; +import org.wpilib.opmode.PeriodicOpMode; + +class CommandBasedKitbotPt2 { + class ExampleOpMode extends PeriodicOpMode { + private final Command myAutoCommand = null; + + // [startMethod] + @Override + public void start() { + Scheduler.getDefault().schedule(myAutoCommand); + } + // [/startMethod] + + public ExampleOpMode(Robot robot) { + DoubleSupplier forwardThrottle = null, rotationThrottle = null; + + var driveCmd = + // [driveCommand] + robot.drivetrain.arcadeDrive(forwardThrottle, rotationThrottle); + // [/driveCommand] + + var driveCmdWithTimeout = + // [4SecDriveCommand] + robot.drivetrain + .arcadeDrive(forwardThrottle, rotationThrottle) + .withTimeout(Seconds.of(4)); + // [/4SecDriveCommand] + } + } + + class Robot { + ExampleDriveMechanism drivetrain = new ExampleDriveMechanism(); + } + + class ExampleDriveMechanism implements Mechanism { + private final OnboardIMU imu = new OnboardIMU(OnboardIMU.MountOrientation.FLAT); + private final DifferentialDrive differentialDrive = new DifferentialDrive(throttle -> {}, throttle -> {}); + + Command arcadeDrive(DoubleSupplier forwardThrottle, DoubleSupplier rotationThrottle) { + return run(coroutine -> { + // [arcadeDriveBody] + while (true) { + differentialDrive.arcadeDrive( + forwardThrottle.getAsDouble(), rotationThrottle.getAsDouble()); + coroutine.yield(); + } + // [/arcadeDriveBody] + }) + .named("Drive"); + } + + Command rotateInPlaceHint(double angleDegrees) { + return run(coroutine -> { + // [rotateInPlaceBody] + double targetAngle = imu.getRotation2d().getDegrees() + angleDegrees; + while (true) { + // What to add here? + coroutine.yield(); + } + // [/rotateInPlaceBody] + }) + .named("RotateInPlace"); + } + } +} diff --git a/examples/stage1/snippets/src/main/java/sources/CommandBody.java b/examples/stage1/snippets/src/main/java/sources/CommandBody.java new file mode 100644 index 0000000..ae507c8 --- /dev/null +++ b/examples/stage1/snippets/src/main/java/sources/CommandBody.java @@ -0,0 +1,47 @@ +package sources; + +import org.wpilib.command3.Command; +import org.wpilib.drive.DifferentialDrive; + +class CommandBody { + void main() { + // [triangleLoop] + int number = 1; + while (number < 4) { + System.out.println("-".repeat(number)); + number = number + 1; + } + while (number > 0) { + System.out.println("-".repeat(number)); + number = number - 1; + } + // [/triangleLoop] + + var exampleCommand = Command.noRequirements(coroutine -> { + var motor = new ExampleMotor(); + var differentialDrive = new DifferentialDrive(throttle -> {}, throttle -> {}); + + // [rotate90CommandBody] + double targetDirection = getCurrentYaw() + 90; + while (getCurrentYaw() < targetDirection) { + differentialDrive.arcadeDrive(0, 0.5); + coroutine.yield(); + } + differentialDrive.arcadeDrive(0, 0); + // [/rotate90CommandBody] + + // [fullThrottleCmdBody] + System.out.println("Full Speed Baby!"); + while (true) { + motor.setThrottle(1.0); + coroutine.yield(); // Confused? We'll explain this in the section below + } + // [/fullThrottleCmdBody] + }) + .named("Example!"); + } + + private double getCurrentYaw() { + return 0; + } +} diff --git a/examples/stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java b/examples/stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java new file mode 100644 index 0000000..5cd7fa8 --- /dev/null +++ b/examples/stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java @@ -0,0 +1,111 @@ +package sources; + +import org.wpilib.command3.Command; +import org.wpilib.command3.Mechanism; + +class CommandsAndMechsPt1 implements Mechanism { + private final ExampleMotor motor = new ExampleMotor(); + + void partialDefinitions() { + // [mechanismDef] + class Intake implements Mechanism { + // Store any motors specific to the mechanism as private members. + // This can include TalonFX, SparkMax and/or SparkFlex instances. + private final ExampleMotor motor = new ExampleMotor(); + } + // [/mechanismDef] + + // [mechanismInRobotDef] + class Robot { + private final Intake intake = new Intake(); + } + // [/mechanismInRobotDef] + } + + void commandDefinitions() { + var exampleCommand = + // [commandDef] + run(coroutine -> { + System.out.println("Full Speed Baby!"); + while (true) { + motor.setThrottle(1.0); + coroutine.yield(); + } + }) + .named("Set to Full Throttle"); + // [/commandDef] + + var exampleCommandWithPriority = + // [commandWithPriorityDef] + run(coroutine -> { + System.out.println("Full Speed Baby!"); + while (true) { + motor.setThrottle(1.0); + coroutine.yield(); + } + }) + .withPriority(1) // recall that the default priority is 0. + .named("Set to Full Throttle"); + // [/commandWithPriorityDef] + } + + // [fullThrottleIntake] + class Intake implements Mechanism { + private final ExampleMotor motor = new ExampleMotor(); + + public Command fullThrottle() { + return run(coroutine -> { + System.out.println("Full Speed Baby!"); + while (true) { + motor.setThrottle(1.0); + coroutine.yield(); + } + }) + .named("Set to Full Throttle"); + } + } + // [/fullThrottleIntake] + + // [runAtThrottleCommand] + // This command can now set the intake at any throttle level: + public Command runAtThrottle(double throttle) { + return run(coroutine -> { + while (true) { + motor.setThrottle(throttle); + coroutine.yield(); + } + }) + .named("Set Throttle to " + throttle); + } + // [/runAtThrottleCommand] + + // [commandAwait] + public Command printHiThenFullThrottle() { + return run(coroutine -> { + // "Hi" is printed before the runAtThrottle Command is run + System.out.println("Hi!"); + coroutine.await(runAtThrottle(1.0)); + }) + .named("Set to Full Throttle & Print Hi"); + } + // [/commandAwait] + + // [commandSequence] + public Command deployPivot() { + return null; // placeholder for actual command + } + + public Command runIntakeRollers() { + return null; // placeholder for actual command + } + + public Command commandSequence() { + return run(coroutine -> { + System.out.println("Hi!"); // step 1 + coroutine.await(deployPivot()); // step 2 + coroutine.await(runIntakeRollers()); // step 3 + }) + .named("Command Sequence"); + } + // [/commandSequence] +} diff --git a/examples/stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java b/examples/stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java new file mode 100644 index 0000000..0f1d0b2 --- /dev/null +++ b/examples/stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java @@ -0,0 +1,80 @@ +package sources; + +import static org.wpilib.units.Units.Seconds; + +import org.wpilib.command3.Command; +import org.wpilib.command3.Mechanism; + +class CommandsAndMechsPt2 { + // [defaultCommand] + class Intake implements Mechanism { + // Placeholder for TalonFX, SparkMax or SparkFlex + private final ExampleMotor motor = new ExampleMotor(); + + public Intake() { + setDefaultCommand(idle()); + } + + public Command idle() { + return run(coroutine -> { + while (true) { + motor.setThrottle(0); + coroutine.yield(); + } + }) + .named("Idle"); + } + } + // [/defaultCommand] + + // [noRequirementsCommand] + class Robot { + public Command justPrintHi() { + return Command.noRequirements(coroutine -> { + System.out.println("Hello World!"); + }) + .named("Hello World!"); + } + } + // [/noRequirementsCommand] + + class CommandUsages implements Mechanism { + // [delayCommand] + public Command wait5SecsThenPrintHi() { + return run(coroutine -> { + coroutine.wait(Seconds.of(5)); + System.out.println("This will show up 5 seconds later."); + }) + .named("Wait 5 Secs, Then Print Hi"); + } + // [/delayCommand] + + // [timeoutCommand] + public Command fullThrottleForFiveSeconds() { + return fullThrottle().withTimeout(Seconds.of(5)); + } + + public Command fullThrottle() { + return null; // placeholder for actual command + } + // [/timeoutCommand] + + // [parallelCommands] + public Command parallelCommands() { + return run(coroutine -> { + System.out.println("Hi!"); + coroutine.awaitAll(deployIntake(), runIntakeRollers()); + }) + .named("Parallel Commands"); + } + + private Command deployIntake() { + return null; // placeholder for actual command + } + + private Command runIntakeRollers() { + return null; // placeholder for actual command + } + // [/parallelCommands] + } +} diff --git a/examples/stage1/snippets/src/main/java/sources/ExampleMotor.java b/examples/stage1/snippets/src/main/java/sources/ExampleMotor.java new file mode 100644 index 0000000..72e6e6f --- /dev/null +++ b/examples/stage1/snippets/src/main/java/sources/ExampleMotor.java @@ -0,0 +1,10 @@ +package sources; + +/** This class acts as a stand-in for a TalonFX, SparkMax, etc. in snippets. */ +public class ExampleMotor { + void setThrottle(double throttle) {} + + double speed() { + return 0; + } +} diff --git a/examples/stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java b/examples/stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java new file mode 100644 index 0000000..dc44a6c --- /dev/null +++ b/examples/stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java @@ -0,0 +1,96 @@ +package sources; + +import java.util.function.DoubleSupplier; +import org.wpilib.command3.Command; +import org.wpilib.command3.Mechanism; +import org.wpilib.command3.Trigger; +import org.wpilib.command3.button.CommandXboxController; +import org.wpilib.driverstation.RobotState; + +class SuppliersInCommandBased { + void main() { + var motor = new ExampleMotor(); + var xbox = new CommandXboxController(0); + + // [doubleSupplierExample] + DoubleSupplier supplier = () -> xbox.getLeftY(); + double controllerOutput = supplier.getAsDouble(); + + // [/doubleSupplierExample] + + // [triggerBooleanSupplier] + new Trigger(() -> motor.speed() > 60); + + // [/triggerBooleanSupplier] + } + + void correctUsages() { + class Intake implements Mechanism { + private final ExampleMotor motor = new ExampleMotor(); + + // [runAtThrottleSupplier] + public Command runAtThrottle(DoubleSupplier throttleSupplier) { + return run(coroutine -> { + while (true) { + double throttle = throttleSupplier.getAsDouble(); + motor.setThrottle(throttle); + coroutine.yield(); + } + }) + .named("Run Intake"); + } + // [/runAtThrottleSupplier] + + // [untilModifier] + // From the Intake class mentioned earlier + public Command fullThrottleUntilRobotDisabled() { + return fullThrottle().until(() -> RobotState.isDisabled()).named("Full Throttle Until Disable"); + } + + private Command fullThrottle() { + return null; // placeholder for actual command + } + // [/untilModifier] + } + + // [correctCommand] + class Robot { + public Robot() { + var xbox = new CommandXboxController(0); + var intake = new Intake(); + intake.setDefaultCommand(intake.runAtThrottle(() -> xbox.getLeftY())); + } + } + // [/correctCommand] + } + + void incorrectUsages() { + // [runAtThrottleDouble] + class Intake implements Mechanism { + // Placeholder for TalonFX, SparkMax or SparkFlex + private final ExampleMotor motor = new ExampleMotor(); + + public Command runAtThrottle(double throttle) { + return run(coroutine -> { + while (true) { + motor.setThrottle(throttle); + coroutine.yield(); + } + }) + .named("Run Intake"); + } + } + // [/runAtThrottleDouble] + + // [incorrectCommand] + class Robot { + public Robot() { + var xbox = new CommandXboxController(0); + var intake = new Intake(); + double controllerOutput = xbox.getLeftY(); + intake.setDefaultCommand(intake.runAtThrottle(controllerOutput)); + } + } + // [/incorrectCommand] + } +} diff --git a/examples/stage1/snippets/src/main/java/sources/Triggers.java b/examples/stage1/snippets/src/main/java/sources/Triggers.java new file mode 100644 index 0000000..e8f5e71 --- /dev/null +++ b/examples/stage1/snippets/src/main/java/sources/Triggers.java @@ -0,0 +1,113 @@ +package sources; + +import org.wpilib.command3.Command; +import org.wpilib.command3.Scheduler; +import org.wpilib.command3.Trigger; +import org.wpilib.command3.button.CommandXboxController; +import org.wpilib.driverstation.RobotState; +import org.wpilib.driverstation.XboxController; +import org.wpilib.framework.TimedRobot; +import org.wpilib.opmode.Autonomous; +import org.wpilib.opmode.PeriodicOpMode; + +class Triggers { + void main() { + var motor = new ExampleMotor(); + var intake = new ExampleMechanism(); + var shooter = new ExampleMechanism(); + + // [motorTooFastTrigger] + Trigger motorTooFastTrigger = new Trigger(() -> motor.speed() > 60); + boolean isMotorTooFast = motorTooFastTrigger.getAsBoolean(); + // [/motorTooFastTrigger] + + // [multiBindingsVerbose] + Trigger teleopEnabledTrigger = new Trigger(() -> RobotState.isTeleopEnabled()); + teleopEnabledTrigger.whileTrue(intake.runAtThrottle(0.5)); + teleopEnabledTrigger.whileTrue(shooter.runAtThrottle(0.5)); + // [/multiBindingsVerbose] + + // [multiBindings] + new Trigger(() -> RobotState.isTeleopEnabled()) + .whileTrue(intake.runAtThrottle(0.5)) + .whileTrue(shooter.runAtThrottle(0.5)); + // [/multiBindings] + + // [xboxGetAButton] + XboxController xbox = new XboxController(0); + boolean aButtonHeld = xbox.getAButton(); + // [/xboxGetAButton] + + // [triggerAButton] + XboxController xbox2 = new XboxController(0); + Trigger aButton = new Trigger(() -> xbox2.getAButton()); + aButton.whileTrue(someCommand()); + // [/triggerAButton] + + // [commandXboxController] + CommandXboxController xbox3 = new CommandXboxController(0); + Trigger aButton2 = xbox3.a(); + aButton2.whileTrue(someCommand()); + // shorthand: + xbox3.a().whileTrue(someCommand()); + // [/commandXboxController] + + // [scheduleAndPrint] + Scheduler.getDefault().schedule(myAutonomousCommand()); + System.out.println("Hello!"); + // [/scheduleAndPrint] + } + + void scheduleMethodExamples() { + // [timedRobotExample] + class Robot extends TimedRobot { + @Override + public void autonomousInit() { + Scheduler.getDefault().schedule(myAutonomousCommand()); + } + } + // [/timedRobotExample] + + // [opModeExample] + @Autonomous + class MyAutoOpMode extends PeriodicOpMode { + @Override + public void start() { + Scheduler.getDefault().schedule(myAutonomousCommand()); + } + } + // [/opModeExample] + } + + // [teleopEnabledRobot] + public class Robot { + private final ExampleMechanism intake = new ExampleMechanism(); + + private static boolean teleopEnabled() { + return RobotState.isTeleopEnabled(); // in case you didn't know! + } + + public Robot() { + Trigger teleopEnabledTrigger = new Trigger(() -> teleopEnabled()); + teleopEnabledTrigger.onTrue(intake.runAtThrottle(0.5)); + + // A shorthand for the above: + new Trigger(() -> teleopEnabled()).onTrue(intake.runAtThrottle(0.5)); + } + } + // [/teleopEnabledRobot] + + Command myAutonomousCommand() { + return null; + } + + Command someCommand() { + return null; + } + + class ExampleMechanism { + Command runAtThrottle(double throttle) { + return null; + } + } +} diff --git a/examples/stage1/snippets/vendordeps/CommandsV3.json b/examples/stage1/snippets/vendordeps/CommandsV3.json new file mode 100644 index 0000000..f406bde --- /dev/null +++ b/examples/stage1/snippets/vendordeps/CommandsV3.json @@ -0,0 +1,25 @@ +{ + "fileName": "CommandsV3.json", + "name": "Commands v3", + "version": "1.0.0", + "uuid": "4decdc05-a056-46cf-9561-39449bbb0130", + "wpilibYear": "2027_alpha5", + "mavenUrls": [], + "jsonUrl": "", + "conflictsWith": [ + { + "uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266", + "errorMessage": "Users can not have both Commands v2 and Commands v3 vendordeps in their robot program.", + "offlineFileName": "CommandsV2.json" + } + ], + "javaDependencies": [ + { + "groupId": "org.wpilib", + "artifactId": "commands3-java", + "version": "wpilib" + } + ], + "jniDependencies": [], + "cppDependencies": [] +} diff --git a/public/stage1/commands/2910-robot.png b/public/stage1/commands/2910-robot.png new file mode 100644 index 0000000..921907c Binary files /dev/null and b/public/stage1/commands/2910-robot.png differ diff --git a/public/stage1/commands/human-flow-diagram.png b/public/stage1/commands/human-flow-diagram.png new file mode 100644 index 0000000..9990a9e Binary files /dev/null and b/public/stage1/commands/human-flow-diagram.png differ diff --git a/public/stage1/commands/robot-flow-diagram.png b/public/stage1/commands/robot-flow-diagram.png new file mode 100644 index 0000000..07e1456 Binary files /dev/null and b/public/stage1/commands/robot-flow-diagram.png differ diff --git a/src/config/sidebarConfig.ts b/src/config/sidebarConfig.ts index db34be6..4683acf 100644 --- a/src/config/sidebarConfig.ts +++ b/src/config/sidebarConfig.ts @@ -82,6 +82,45 @@ export const sidebarSections: Record = { }, ], + '/stage-1b-commands': [ + { + label: 'Stage 1B: Commands', + items: [ + { + label: 'The Concepts', + slug: 'stage-1b-commands/command-based-overview', + }, + { + label: 'The Body of a Command', + slug: 'stage-1b-commands/the-command-body', + }, + { + label: 'Commands & Mechanisms, Pt. 1', + slug: 'stage-1b-commands/commands-and-mechanisms', + }, + { + label: 'Triggers and Scheduling', + slug: 'stage-1b-commands/triggers', + }, + { + label: 'Commands & Mechanisms, Pt. 2', + slug: 'stage-1b-commands/commands-and-mechanisms-pt2', + }, + { + label: 'Exercise - Kitbot Rewrite, Pt. 1', + slug: 'stage-1b-commands/command-based-kitbot', + }, + { + label: 'Suppliers in Command-Based', + slug: 'stage-1b-commands/suppliers-in-command-based', + }, + { + label: 'Exercise - Kitbot Rewrite, Pt. 2', + slug: 'stage-1b-commands/command-based-kitbot-pt2', + }, + ], + }, + ], // Intro To Java section '/intro-to-java': [ { diff --git a/src/content/docs/getting-started/required-tools.mdx b/src/content/docs/getting-started/required-tools.mdx index 76f71ad..93ba4d2 100644 --- a/src/content/docs/getting-started/required-tools.mdx +++ b/src/content/docs/getting-started/required-tools.mdx @@ -36,4 +36,4 @@ It’s commonly used in FRC because it's free and easily accessible, in addition For many of the lessons on this website, it will be necessary to have a GitHub account. One can be can be created [here](https://github.com/signup). -It's also recommended to sign up for the [GitHub student developer pack](https://education.github.com/pack), this will give you access to a variety of resources. +It's also recommended to sign up for the [GitHub student developer pack](https://education.github.com/pack), this will give you access to a variety of resources/ diff --git a/src/content/docs/stage-1b-commands/command-based-kitbot-pt2.mdx b/src/content/docs/stage-1b-commands/command-based-kitbot-pt2.mdx new file mode 100644 index 0000000..d4f2ba2 --- /dev/null +++ b/src/content/docs/stage-1b-commands/command-based-kitbot-pt2.mdx @@ -0,0 +1,127 @@ +--- +title: Exercise - Kitbot Rewrite, Pt. 2 +description: Rewriting the kitbot code in stage 1A to command-based +prev: stage-1b-commands/suppliers-in-command-based +next: false +--- + +import Aside from '../../../components/Aside.astro'; + +### What You'll Accomplish + +In this second part of the kitbot rewrite, you will: + +1. Create a `Drivetrain` mechanism class. +2. Define an `arcadeDrive` command that dynamically reads controller inputs using `DoubleSupplier`s. +3. Bind the default drivetrain command in your teleop OpMode to enable driving. +4. Define some basic autonomous modes. + +### Task 1: The Drivetrain Mechanism + +In `Drivetrain.java`, implement the following: + +1. An `idle()` command that commands `0.0` speed and `0.0` rotation, and is set as the default command in the constructor. +2. An `arcadeDrive(DoubleSupplier forwardThrottle, DoubleSupplier rotationThrottle)` command that continuously updates the drive motor speeds using the values from the suppliers. + +{/* prettier-ignore */} +

+ Hint + Since the joystick values change constantly, you must fetch the speed and + rotation from the suppliers inside the command's loop. You should have the + following inside of your command's body: + ```java stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java#arcadeDriveBody + + ``` + +
+ +### Task 2: Drivetrain Setup + +1. Create a `Drivetrain` instance in `Robot.java` named `drivetrain`. + It should be a public instead of private. +2. Then, open `MyTeleop.java` (your teleop OpMode). + In the constructor, set the drivetrain's default command to `arcadeDrive`. + Pass lambda expressions that read from your controller's joystick axes. + +
+ Hint 1 + You can create an arcade drive command like so: + ```java stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java#driveCommand + + ``` + Can you figure out the expressions to replace `forwardThrottle` and `rotationThrottle` with? +
+
+ Hint 2 + The expression for the `forwardThrottle` argument is `() -> -xbox.getLeftY()`, since we + want to recompute the value of `-xbox.getLeftY()` while the `arcadeDrive` command is running.

+ Can you figure out the expression for the `rotationThrottle` argument, considering + that the goal is to recompute the value of `xbox.getRightX()`? +
+ + + +### Task 3: Drive Straight Autonomous + +Go to the `DriveStraightAutoMode.java` file. +In the `start()` method, schedule +an `arcadeDrive` command with a timeout of 4 seconds. + +This `arcadeDrive` command should have a constant forward throttle of `0.5` +and a constant rotational throttle of `0`. + +
+ Hint 1 + To schedule a command without a trigger, use `Scheduler.getDefault().schedule(Command)`: + ```java stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java#startMethod + + ``` + Can you figure out what `myAutoCommand` should be? +
+
+ Hint 2 + A `DoubleSupplier` with syntax `() -> 0.5` will always return + 0.5 when its `getAsDouble()` method is called.

+ Use this information to complete the following `Command` by + replacing `forwardThrottle` and `rotationalThrottle` with `DoubleSupplier`s: + ```java stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java#4SecDriveCommand + + ``` + +
+ +### Task 4: Rotate in Place Command + +Create a new `Command` in `Drivetrain.java`, called `rotateInPlace`, +with the parameters `double angleDegrees` and `DoubleSupplier rotationThrottle`. +It should execute these actions, in order: + +1. Define a local variable, named `targetAngle`, whose value is the sum of `angleDegrees` and the robot's current yaw. + To get the robot's current yaw, call `imu.getRotation2d().getDegrees()`. +2. Call `differentialDrive.arcadeDrive()` in a while loop, + which should continue while the robot's current yaw is less than the `targetAngle`. + +
+ Hint + ```java stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java#rotateInPlaceBody + + ``` + +
+ +### Task 5: Rotate in Place Autonomous + +Finally, use the `rotateInPlace` command to implement a turn-in-place autonomous within `RotationAutoMode.java`. +It should rotate the robot 90 degrees from its starting position, at a throttle of 0.2. + +
+ Hint + Use the `Scheduler` to schedule a new `rotateInPlace` command, just like how + task 3 accomplishes this. +
diff --git a/src/content/docs/stage-1b-commands/command-based-kitbot.mdx b/src/content/docs/stage-1b-commands/command-based-kitbot.mdx new file mode 100644 index 0000000..163c483 --- /dev/null +++ b/src/content/docs/stage-1b-commands/command-based-kitbot.mdx @@ -0,0 +1,121 @@ +--- +title: Exercise - Kitbot Rewrite, Pt. 1 +description: Rewriting the kitbot code in stage 1A to command-based +prev: stage-1b-commands/commands-and-mechanisms-pt2 +next: stage-1b-commands/suppliers-in-command-based +--- + +import Aside from '../../../components/Aside.astro'; + +### What You'll Accomplish + +You will rewrite the code for a kitbot's intake launcher and feeder +mechanisms. +In part 2 of this exercise, you'll rewrite the drivetrain code +in command-based, and add some basic auto modes. + + + +### Task 1: Setup + +Add the `Scheduler.getDefault().run()` statement in the `Robot` class. +This allows for commands to function. + +```java stage1/snippets/src/main/java/sources/CommandBasedKitbot.java#robotDef + +``` + +### Task 2: The Feeder Mechanism + +Add the following to the feeder mechanism in the `Feeder.java` file: + +1. A private `TalonFX` motor controller object, named `motor`, + with ID 5 and a systemcore CANBus of ID 0. +2. A `feed()` command that sets the throttle of the motor to 0.75 forever. +3. An `intake()` command that sets the throttle of the motor to -1 forever. +4. An `outtake()` command that sets the throttle of the motor to 1 forever. +5. An `idle()` command that sets the throttle of the motor to 0 forever. + Make this the default command. + +The `feed()`, `intake()`, `outtake()` and `idle()` commands should be returned from methods. + +After that, you should see a property named `sim` that is currently marked `null`. +Replace `null` with `new SingleFlywheelSim(motor, "Feeder")`. + +
+ Item 1, Hint 1 + You can create a `CANBus` object via `CANBus.systemcore(id)`. +
+{/* prettier-ignore */} +
+ Item 1, Hint 2 + Remember that `TalonFX` is a class, and that creating objects within another + class is done like so:
+ {/* rli:ignore */} + ```java + private TypeOfClass myMotor = new TypeOfClass(); + ``` + First, figure out what to replace `TypeOfClass` with. Next, + add the appropriate constructor parameters by hovering over + the red squiggly line. +
+
+ Items 2-4, Hint 1 + Re-read the "Defining Commands" and "Combining Commands and Mechanisms" + subsections of the "Commands and Mechanisms, Pt. 1" supersection (it should + be visible from the left sidebar). +
+
+ Item 5, Hint 1 + To set a mechanism's default command, call `setDefaultCommand(Command)` + inside of the constructor. +
+ +### Task 3: The IntakeLauncher Mechanism + +Add the following to the intake launcher mechanism in the `IntakeLauncher.java` file: + +1. A private `TalonFX` motor controller object, with ID 4 and a systemcore CANBus of ID 0 +2. A `shoot()` command that waits 2 seconds, then sets the throttle of the motor to 0.9 forever. +3. A `intake()` command that sets throttle to 0.8 forever. +4. A `outtake()` command that sets throttle to -0.8 forever. +5. A `idle()` command that sets throttle to 0 forever. + Make this the default command. + +
+ Hint + Call `coroutine.wait(Seconds.of(2))` to wait 2 seconds within a `Command`. +
+ +### Task 4: Finish the `Robot` class + +Create new instances of the `IntakeLauncher` and `Feeder` classes. +They should be +public properties of the `Robot` class, so that they are accessible from opmodes. +Name them `intakeLauncher` and `feeder`, respectively. + +### Task 5: Finish the teleop OpMode + +In the constructor of the teleop opmode (it should be in `MyTeleop.java`), +create a private `CommandXboxController` instance named `xbox`. + +In your opmode constructor, define the following behavior: + +1. While the `xbox.leftBumper()` trigger is active, + run the `robot.intake.intake()` and `robot.feeder.intake()` commands. +2. While the `xbox.rightBumper()` trigger is active, + run the `robot.intake.shoot()` and `robot.feeder.feed()` commands. +3. While the `xbox.a()` trigger is active, + run the `robot.intake.outtake()` and `robot.feeder.outtake()` commands. + +
+ Hint + ```java stage1/snippets/src/main/java/sources/CommandBasedKitbot.java#triggerBindingDef + + ``` + Can you think of why `whileTrue()` is used instead of `onTrue()`? + +
diff --git a/src/content/docs/stage-1b-commands/command-based-overview.mdx b/src/content/docs/stage-1b-commands/command-based-overview.mdx new file mode 100644 index 0000000..316e88e --- /dev/null +++ b/src/content/docs/stage-1b-commands/command-based-overview.mdx @@ -0,0 +1,85 @@ +--- +title: The Concepts of Command-Based Programming +description: The what, how, and why of commands +prev: false +next: stage-1b-commands/the-command-body +--- + +import Aside from '../../../components/Aside.astro'; +import ContentImage from '../../../components/ContentImage.astro'; + +### What is Command-Based Programming? + +Up until now, most of the code you've written has been akin to a +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 an alarm clock rings, you turn it off and tumble out of bed. +When your belly rumbles, you walk to the fridge and get a snack. +When the clock strikes 8 AM, +you open the front door to leave for school. + + + +Notice how none of these actions had a predefined order; rather, they were a reaction to +to certain kinds of stimuli. + +Command-based programming models robot behavior in a similar way. + +1. It defines behaviors/actions(tumbling out of bed, getting a snack, etc.) as `Command`s. +2. It defines the stimuli that trigger these behaviors (a ringing alarm clock, a rumbling belly, etc.) as `Trigger`s. + +For instance, a command-based robot might have the following structure: + + + +Here, everything on the left is a `Trigger`, while everything on the right is a `Command`. + + + +### Mechanisms and Priority + +However, the structure of using `Command`s to represent behaviors misses an important component. + +Think back to the human example: you’re walking to the fridge, but the clock hits 8 AM before you get there. +You can't do both at once, since both actions require your arms and legs. +So, you stop and leave for school. + +Next, the robot. +If the X and Y buttons are held, the shooter and intake motors should both run. +But if the X and A buttons are held, the robot code must choose between running the shooter +at a fast speed or a slow speed. + +Command-Based models this relationship with 2 concepts: Mechansisms and Priority. + +##### Mechanisms + +A Mechanism is a robot component whose state can be updated through commands. +`Command`s declare +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. + + + +##### Priority + +When 2 commands share one or more requirements, priority determines which command +should be cancelled and which command should continue. + +In the human example, the "leave for school" `Command` would have a higher priority +than the "get a snack" `Command`. + +Priority is represented as an `int`. +Commands default to a priority of 0, and a +larger number represents a higher priority. diff --git a/src/content/docs/stage-1b-commands/commands-and-mechanisms-pt2.mdx b/src/content/docs/stage-1b-commands/commands-and-mechanisms-pt2.mdx new file mode 100644 index 0000000..d9c95b3 --- /dev/null +++ b/src/content/docs/stage-1b-commands/commands-and-mechanisms-pt2.mdx @@ -0,0 +1,71 @@ +--- +title: Commands and Mechanisms, Part 2 +description: Intermediate-level uses of commands and mechanisms +prev: stage-1b-commands/triggers +next: stage-1b-commands/command-based-kitbot +--- + +import ContentImage from '../../../components/ContentImage.astro'; + +### Default Commands + +Most mechanisms define a "default command" that runs when +no other commands requiring it are active. +The default command +of an intake or a shooter, for instance, would be setting the throttle +of all motors to 0, forever. + + + +To set a mechanism's default command, simply call `setDefaultCommand(Command)`, which is +an instance method of all mechanisms: + +```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java#defaultCommand + +``` + +### Adding Delays to Commands + +Use `coroutine.wait()` to wait for a predefined amount of time within +the body of a command: + +```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java#delayCommand + +``` + +You can also give an existing command a timeout with the `withTimeout()` modifier: + +```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java#timeoutCommand + +``` + +### More commands info + +The following information isn't strictly needed for +the exercises in stage 1b; however, they will be very +useful for future exercises and coding a real robot. + +#### Commands without requirements + +Sometimes, you need to create a command that doesn't require any mechanisms. +To do so, you can replace `run(...)` with `Command.noRequirements(...)`. + +```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java#noRequirementsCommand + +``` + +#### Running Commands in parallel + +`coroutine.awaitAll(a, b, c)` will run the commands a, b, and c in parallel, +waiting for all 3 to finish before proceeding. + +```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java#parallelCommands + +``` + +On the other hand, `coroutine.awaitAny(a, b, c)` runs a, b, and c in parallel +until any of a, b, or c finishes. +Then, it will cancel the remaining commands. diff --git a/src/content/docs/stage-1b-commands/commands-and-mechanisms.mdx b/src/content/docs/stage-1b-commands/commands-and-mechanisms.mdx new file mode 100644 index 0000000..de8f005 --- /dev/null +++ b/src/content/docs/stage-1b-commands/commands-and-mechanisms.mdx @@ -0,0 +1,135 @@ +--- +title: Commands & Mechanisms, Part 1 +description: The basics of defining mechanisms and writing commands +prev: stage-1b-commands/the-command-body +next: stage-1b-commands/why-mechanisms +--- + +import Aside from '../../../components/Aside.astro'; + +### Defining Mechanisms + +Mechanisms are simply classes that have the `implements Mechanism` keyword: + +```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#mechanismDef + +``` + +In general, you should create one instance of each mechanism, and that instance should be stored +in your robot class: + +```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#mechanismInRobotDef + +``` + + + +### Defining Commands + +Most commands are defined inside of the file containing the class of a mechanism +(like Intake.java or Shooter.java). +The code below defines a new `Command`: + +```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#commandDef + +``` + +Notice that: + +- Every line of code between `coroutine -> {` and `}` will be run + when the command runs, like a method body. +- This command will print "Full Speed Baby!" once before commanding + the motor to spin at max speed. +- The `named("Set to Full Throttle")` call is required to create a `Command`. + This allows coders to distinguish this command from others when debugging their code. + +To change the priority of the command, simply add a `withPriority(int)` call before +you call `named()`: + +```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#commandWithPriorityDef + +``` + + + +### Combining Commands and Mechanisms + +Here's an example of a command defined inside of a mechanism class: + +```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#fullThrottleIntake + +``` + +Commands created with `run()` will automatically require the mechanism that they are defined inside. +Here, the full throttle command requires the `Intake`. + +Also, notice that the fullThrottle method creates and returns a command. +All mechanism-specific commands should be defined this way, for 2 reasons: + +1. It allows for commands to define parameters, just like a method. +2. It gives the `Robot` class easy access to these commands. + +```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#runAtThrottleCommand + +``` + +However, this approach requires caution. +Observe the snippet below: + +{/* rli:ignore */} + +```java +public Command printHiThenFullThrottle() { + return run(coroutine -> { + System.out.println("Hi!"); + runAtThrottle(1.0); // Error: Commands must be used! Did you mean to fork it or bind it to a trigger? + }) + .named("Set to Full Throttle"); +} +``` + +Calling the `fullThrottle()` +method, as shown, doesn't spin the motor, but instead creates a `Command` object that must be run separately. +The java +compiler will catch this and throw a compile-time error. + + + + + +### Command Sequences + +If you want to run the `fullThrottle` command inside of another command, you must +call `coroutine.await(Command)`, like so: + +```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#commandAwait + +``` + +That means you can run multiple commands in sequence: + +```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#commandSequence + +``` diff --git a/src/content/docs/stage-1b-commands/suppliers-in-command-based.mdx b/src/content/docs/stage-1b-commands/suppliers-in-command-based.mdx new file mode 100644 index 0000000..a4a88b1 --- /dev/null +++ b/src/content/docs/stage-1b-commands/suppliers-in-command-based.mdx @@ -0,0 +1,107 @@ +--- +title: Suppliers in Command-Based +description: The basics of suppliers and their use in command-based programming +prev: stage-1b-commands/command-based-kitbot +next: stage-1b-commands/command-based-kitbot-pt2 +--- + +import Aside from '../../../components/Aside.astro'; + +### A Small Riddle + +Let's say that we define an `Intake` mechanism: + +```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#runAtThrottleDouble + +``` + +And the following code in Robot.java: + +```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#incorrectCommand + +``` + + + +When you run the code above, what happens? + +1. The intake does a little dance before rejoicing your coding skills. +2. The intake runs at a speed dependent on how far up or down the joystick + on the left is pushed. +3. The intake does nothing. + +
+ Solution + #3: The intake does nothing. +
+ +### But Why? + +Recall that the constructor of the `Robot` class is called when the +robot code is first loaded, not when the robot is enabled. +At this point, +your hands aren't even on the controller - so, the value of `controllerOutput` would be 0. + +When the robot is enabled, the value of `getLeftY()` will change; however, the value of the `throttle` +parameter passed into the `runAtThrottle` command cannot, because it's value has already been determined +before the command has even began. + +### Fixing the problem + +In java, we use a `DoubleSupplier` to represent a value of type `double` that is constantly changing. +We call `getAsDouble()` to fetch its current value. + +```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#runAtThrottleSupplier + +``` + +`DoubleSupplier` instances are created with the syntax of `() -> expression`, with the caveat that +the expression must return a `double`. +Calling `getAsDouble()` will evaluate that expression. + +```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#doubleSupplierExample + +``` + + + +Here is how we would define and use a `DoubleSupplier` in our previous example: + +```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#correctCommand + +``` + +### `BooleanSupplier`s and the `until()` Command Modifier + +You might recognize the `() ->` syntax used to define `DoubleSupplier`s. +As it turns out, +we define `Trigger`s with that syntax: + +```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#triggerBooleanSupplier + +``` + +Notice, however, that the expression to the left of the `() ->` statement is +a `boolean` instead of a `double`. +As it turns out, the constructor for a `Trigger` +takes a `BooleanSupplier`. + + + +`BooleanSupplier`s are used in one other place: adding stop conditions to commands. +You can use the `until(BooleanSupplier)` method to add an arbitrary stop condition +to a command: + +```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#untilModifier + +``` diff --git a/src/content/docs/stage-1b-commands/the-command-body.mdx b/src/content/docs/stage-1b-commands/the-command-body.mdx new file mode 100644 index 0000000..b716a79 --- /dev/null +++ b/src/content/docs/stage-1b-commands/the-command-body.mdx @@ -0,0 +1,62 @@ +--- +title: The Body of a Command +description: How the behavior of commands are written +prev: stage-1b-commands/command-based-overview +next: stage-1b-commands/commands-and-mechanisms +--- + +import Aside from '../../../components/Aside.astro'; + +### Something Looks Familiar Here... + +If you've ever coded in Scratch, with blocks, or taken an intro to programming class, +you've probably written software as a sequence of statements. +Like so: + +```java stage1/snippets/src/main/java/sources/CommandBody.java#triangleLoop + +``` + +(In case you were curious - this prints a triangle to the console.) + +From the previous section, you might remember that command-based programming, +as a whole, cannot be represented with just a sequence of statements. +However, individual commands (or robot actions) can. + +For instance, this command body runs a motor at full speed forever: + +```java stage1/snippets/src/main/java/sources/CommandBody.java#fullThrottleCmdBody + +``` + +And this command body would rotate your robot by roughly 90 degrees, then stop: + +```java stage1/snippets/src/main/java/sources/CommandBody.java#rotate90CommandBody + +``` + +Fundamentally, the commands (or robot actions) themselves can be represented with familiar +programming structures: while loops, variables, method calls, and more. + + + +### The Meaning of `coroutine.yield();` + +`coroutine.yield()` is a special statement that must be called inside of while loops. +It allows for commands to run in parallel, while ensuring that background tasks +(like motor safety checks) are run. +An anology would be the +sips of water you take while you finish your homework. + +The `coroutine` object contains many other useful methods that can only be called +inside of commands. +Some of these will be mentioned in the following sections. + + diff --git a/src/content/docs/stage-1b-commands/triggers.mdx b/src/content/docs/stage-1b-commands/triggers.mdx new file mode 100644 index 0000000..f622c40 --- /dev/null +++ b/src/content/docs/stage-1b-commands/triggers.mdx @@ -0,0 +1,107 @@ +--- +title: Triggers and Scheduling +description: How commands are scheduled, with and without triggers +prev: stage-1b-commands/commands-and-mechanisms +next: stage-1b-commands/commands-and-mechanisms-pt-2 +--- + +import Aside from '../../../components/Aside.astro'; + +### A quick recap + +Remember that `Trigger`s are the stimuli that trigger robot behavior, which we call `Command`s. +We say that `Trigger`s "schedule" `Command`s. + +### How triggers are defined + +sources/Triggers, at their core, are methods that return a boolean - true for active, and false for inactive. +You can fetch +a trigger's active status with the `getAsBoolean()` method: + +```java stage1/snippets/src/main/java/sources/Triggers.java#motorTooFastTrigger + +``` + +There are 2 ways triggers can schedule commands: + +1. `trigger.onTrue(Command)`: Schedules a command when a trigger switches from inactive to active. + In the following example, the `runAtThrottle` command will run once teleop mode is selected and enabled. + +```java stage1/snippets/src/main/java/sources/Triggers.java#teleopEnabledRobot + +``` + +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. + + + +### Controller-based sources/Triggers + +A common stimulus for robot behavior is the buttons on a controller. +For instance, shooting balls +while the x button on the operator controller is held. +From section 1A, you might recall that fetching +the state of a button can be done like so: + +```java stage1/snippets/src/main/java/sources/Triggers.java#xboxGetAButton + +``` + +Where the `getAButton()` method returns `true` if the button is being held, and `false` otherwise. +So, it's +possible to create a trigger mapped to the a button like so: + +```java stage1/snippets/src/main/java/sources/Triggers.java#triggerAButton + +``` + +However, this is a common enough usecase that the commands framework provides a special class, +called `CommandXboxController`, that makes interfacing with controller buttons easier for command-based +programming. + +```java stage1/snippets/src/main/java/sources/Triggers.java#commandXboxController + +``` + +### Scheduling commands manually + +You can also decide to run a command immediately, bypassing triggers entirely, like so: + +```java stage1/snippets/src/main/java/sources/Triggers.java#scheduleAndPrint + +``` + +Think of calling `schedule()` as ordering a robot to start washing the dishes +without waiting for it to finish. +In this case, the `println("Hello!")` statement +will run before the autonomous command completes. + + + +Example Usage with `TimedRobot`: + +```java stage1/snippets/src/main/java/sources/Triggers.java#timedRobotExample + +``` + +Example `OpMode` usage: + +```java stage1/snippets/src/main/java/sources/Triggers.java#opModeExample + +```