From fc052ec018589a5c9bbc33e7a8f3a557fce38703 Mon Sep 17 00:00:00 2001 From: Andrew Haselton Date: Mon, 3 Aug 2026 19:12:09 -0500 Subject: [PATCH 1/3] Initial Stage 2 ctre code, unfinished --- .gitignore | 2 + examples/stage2/templates/ctre/.gitignore | 187 ++++++++ .../ctre/.wpilib/wpilib_preferences.json | 6 + .../stage2/templates/ctre/WPILib-License.md | 26 + examples/stage2/templates/ctre/build.gradle | 111 +++++ .../ctre/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48966 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + examples/stage2/templates/ctre/gradlew | 248 ++++++++++ examples/stage2/templates/ctre/gradlew.bat | 93 ++++ .../stage2/templates/ctre/settings.gradle | 30 ++ examples/stage2/templates/ctre/simgui-ds.json | 62 +++ .../ctre/src/main/deploy/example.txt | 3 + .../ctre/src/main/java/first/Main.java | 25 + .../main/java/first/robot/FieldConstants.java | 46 ++ .../ctre/src/main/java/first/robot/Poses.java | 47 ++ .../ctre/src/main/java/first/robot/Robot.java | 35 ++ .../main/java/first/robot/mechanisms/Arm.java | 160 +++++++ .../java/first/robot/mechanisms/Claw.java | 61 +++ .../java/first/robot/mechanisms/Drive.java | 357 ++++++++++++++ .../java/first/robot/mechanisms/Elevator.java | 172 +++++++ .../robot/mechanisms/Superstructure.java | 22 + .../main/java/first/robot/opmode/ArmTest.java | 27 ++ .../robot/opmode/BasicScoringTeleop.java | 47 ++ .../java/first/robot/opmode/DriveTest.java | 27 ++ .../java/first/robot/opmode/ElevatorTest.java | 27 ++ .../first/robot/opmode/FullScoringTeleop.java | 49 ++ .../main/java/first/robot/opmode/MyAuto.java | 30 ++ .../java/first/robot/simulation/ArmSim.java | 44 ++ .../java/first/robot/simulation/ClawSim.java | 40 ++ .../first/robot/simulation/ElevatorSim.java | 50 ++ .../templates/ctre/vendordeps/CommandsV3.json | 25 + .../templates/ctre/vendordeps/DogLog.json | 18 + .../vendordeps/Phoenix6-26.50.0-alpha-1.json | 447 ++++++++++++++++++ 33 files changed, 2531 insertions(+) create mode 100644 examples/stage2/templates/ctre/.gitignore create mode 100644 examples/stage2/templates/ctre/.wpilib/wpilib_preferences.json create mode 100644 examples/stage2/templates/ctre/WPILib-License.md create mode 100644 examples/stage2/templates/ctre/build.gradle create mode 100644 examples/stage2/templates/ctre/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/stage2/templates/ctre/gradle/wrapper/gradle-wrapper.properties create mode 100644 examples/stage2/templates/ctre/gradlew create mode 100644 examples/stage2/templates/ctre/gradlew.bat create mode 100644 examples/stage2/templates/ctre/settings.gradle create mode 100644 examples/stage2/templates/ctre/simgui-ds.json create mode 100644 examples/stage2/templates/ctre/src/main/deploy/example.txt create mode 100644 examples/stage2/templates/ctre/src/main/java/first/Main.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/FieldConstants.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/Poses.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/Robot.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Arm.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Claw.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Drive.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Elevator.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Superstructure.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/opmode/ArmTest.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/opmode/BasicScoringTeleop.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/opmode/DriveTest.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/opmode/ElevatorTest.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/opmode/FullScoringTeleop.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/opmode/MyAuto.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ArmSim.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ClawSim.java create mode 100644 examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ElevatorSim.java create mode 100644 examples/stage2/templates/ctre/vendordeps/CommandsV3.json create mode 100644 examples/stage2/templates/ctre/vendordeps/DogLog.json create mode 100644 examples/stage2/templates/ctre/vendordeps/Phoenix6-26.50.0-alpha-1.json diff --git a/.gitignore b/.gitignore index 50e55cb1..094cb9dd 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ bun.lock .DS_Store .vscode +*/.idea/ + # Build files .gradle diff --git a/examples/stage2/templates/ctre/.gitignore b/examples/stage2/templates/ctre/.gitignore new file mode 100644 index 00000000..34cbaac1 --- /dev/null +++ b/examples/stage2/templates/ctre/.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/stage2/templates/ctre/.wpilib/wpilib_preferences.json b/examples/stage2/templates/ctre/.wpilib/wpilib_preferences.json new file mode 100644 index 00000000..19f541ed --- /dev/null +++ b/examples/stage2/templates/ctre/.wpilib/wpilib_preferences.json @@ -0,0 +1,6 @@ +{ + "enableCppIntellisense": false, + "currentLanguage": "java", + "projectYear": "2027_alpha5", + "teamNumber": 9999 +} diff --git a/examples/stage2/templates/ctre/WPILib-License.md b/examples/stage2/templates/ctre/WPILib-License.md new file mode 100644 index 00000000..c2891d4d --- /dev/null +++ b/examples/stage2/templates/ctre/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/stage2/templates/ctre/build.gradle b/examples/stage2/templates/ctre/build.gradle new file mode 100644 index 00000000..55508d09 --- /dev/null +++ b/examples/stage2/templates/ctre/build.gradle @@ -0,0 +1,111 @@ +plugins { + id "java" + id "org.wpilib.GradleRIO" version "2027.0.0-alpha-6" + id "com.gradleup.shadow" version "9.3.0" +} + +java { + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 +} + +def ROBOT_MAIN_CLASS = "first.Main" + +// 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 = true + +// 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' +} diff --git a/examples/stage2/templates/ctre/gradle/wrapper/gradle-wrapper.jar b/examples/stage2/templates/ctre/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..d997cfc60f4cff0e7451d19d49a82fa986695d07 GIT binary patch literal 48966 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

`Lem|V$A>eR0uN8Y&sdjtu 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

1bvi{DS7E@q1{$_8->K{_OWv2 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 literal 0 HcmV?d00001 diff --git a/examples/stage2/templates/ctre/gradle/wrapper/gradle-wrapper.properties b/examples/stage2/templates/ctre/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..42bb5ac9 --- /dev/null +++ b/examples/stage2/templates/ctre/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/stage2/templates/ctre/gradlew b/examples/stage2/templates/ctre/gradlew new file mode 100644 index 00000000..739907df --- /dev/null +++ b/examples/stage2/templates/ctre/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/stage2/templates/ctre/gradlew.bat b/examples/stage2/templates/ctre/gradlew.bat new file mode 100644 index 00000000..c4bdd3ab --- /dev/null +++ b/examples/stage2/templates/ctre/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/stage2/templates/ctre/settings.gradle b/examples/stage2/templates/ctre/settings.gradle new file mode 100644 index 00000000..e2626a81 --- /dev/null +++ b/examples/stage2/templates/ctre/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/stage2/templates/ctre/simgui-ds.json b/examples/stage2/templates/ctre/simgui-ds.json new file mode 100644 index 00000000..9da8aa99 --- /dev/null +++ b/examples/stage2/templates/ctre/simgui-ds.json @@ -0,0 +1,62 @@ +{ + "keyboardJoysticks": [{ + "axisConfig": [{ + "decKey": 546, + "incKey": 549 + }, { + "decKey": 568, + "incKey": 564 + }, { + "decKey": 550, + "decayRate": 0, + "incKey": 563, + "keyRate": 0.01 + }], + "axisCount": 3, + "buttonCount": 4, + "buttonKeys": [571, 569, 548, 567], + "povConfig": [{ + "keyDown": 614, + "keyDownLeft": 613, + "keyDownRight": 615, + "keyLeft": 616, + "keyRight": 618, + "keyUp": 620, + "keyUpLeft": 619, + "keyUpRight": 621 + }], + "povCount": 1 + }, { + "axisConfig": [{ + "decKey": 555, + "incKey": 557 + }, { + "decKey": 554, + "incKey": 556 + }], + "axisCount": 2, + "buttonCount": 4, + "buttonKeys": [558, 597, 599, 600], + "povCount": 0 + }, { + "axisConfig": [{ + "decKey": 513, + "incKey": 514 + }, { + "decKey": 515, + "incKey": 516 + }], + "axisCount": 2, + "buttonCount": 6, + "buttonKeys": [521, 519, 517, 522, 520, 518], + "povCount": 0 + }, { + "axisCount": 0, + "buttonCount": 0, + "povCount": 0 + }], + "robotJoysticks": [{ + "guid": "78696e70757401000000000000000000", + "useGamepad": true + }] + } diff --git a/examples/stage2/templates/ctre/src/main/deploy/example.txt b/examples/stage2/templates/ctre/src/main/deploy/example.txt new file mode 100644 index 00000000..c9bc6a2f --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/deploy/example.txt @@ -0,0 +1,3 @@ +Files placed in this directory will be deployed to the Systemcore into the +'deploy' directory in the home folder. Use the 'Filesystem.getDeployDirectory' wpilib function +to get a proper path relative to the deploy directory. \ No newline at end of file diff --git a/examples/stage2/templates/ctre/src/main/java/first/Main.java b/examples/stage2/templates/ctre/src/main/java/first/Main.java new file mode 100644 index 00000000..7d287e7a --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/Main.java @@ -0,0 +1,25 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package first; + +import org.wpilib.framework.RobotBase; + +/** + * Do NOT add any static variables to this class, or any initialization at all. Unless you know what + * you are doing, do not modify this file except to change the parameter class to the startRobot + * call. + */ +public final class Main { + private Main() {} + + /** + * Main initialization function. Do not perform any initialization here. + * + *

If you change your main robot class, change the parameter type. + */ + public static void main(String... args) { + RobotBase.startRobot(first.robot.Robot.class); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/FieldConstants.java b/examples/stage2/templates/ctre/src/main/java/first/robot/FieldConstants.java new file mode 100644 index 00000000..40e1d187 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/FieldConstants.java @@ -0,0 +1,46 @@ +package first.robot; + +import org.wpilib.math.geometry.Pose2d; +import org.wpilib.math.geometry.Rotation2d; +import org.wpilib.math.geometry.Translation2d; +import org.wpilib.vision.apriltag.AprilTagFieldLayout; +import org.wpilib.vision.apriltag.AprilTagFields; + +public class FieldConstants { + + public static final double fieldWidth = AprilTagFieldLayout.loadField(AprilTagFields.k2025ReefscapeWelded).getFieldWidth(); + public static final double fieldLength = AprilTagFieldLayout.loadField(AprilTagFields.k2025ReefscapeWelded).getFieldLength(); + + public static final Translation2d BLUE_REEF_CENTER = new Translation2d(4.263, 0); + public static final Translation2d RED_REEF_CENTER = mirrorX(BLUE_REEF_CENTER); + + + + + + + + public static Translation2d mirrorX(Translation2d translation) { + return new Translation2d(-translation.getX(), translation.getY()); + } + + public static Pose2d mirrorX(Pose2d pose) { + return new Pose2d(mirrorX(pose.getTranslation()), Rotation2d.kCCW_Pi_2.minus(pose.getRotation())); + } + + public static Translation2d mirrorY(Translation2d translation) { + return new Translation2d(translation.getX(), -translation.getY()); + } + + public static Pose2d mirrorY(Pose2d pose) { + return new Pose2d(mirrorY(pose.getTranslation()), pose.getRotation().unaryMinus()); + } + + public static Translation2d rotateAboutCenter(Translation2d translation) { + return mirrorX(mirrorY(translation)); + } + + public static Pose2d rotateAboutCenter(Pose2d pose) { + return mirrorX(mirrorY(pose)); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/Poses.java b/examples/stage2/templates/ctre/src/main/java/first/robot/Poses.java new file mode 100644 index 00000000..4bc98f9d --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/Poses.java @@ -0,0 +1,47 @@ +package first.robot; + +import org.wpilib.math.geometry.Pose2d; +import org.wpilib.math.geometry.Rotation2d; +import org.wpilib.math.geometry.Transform2d; +import org.wpilib.math.geometry.Translation2d; + +import java.util.List; + +public class Poses { + + public static final Pose2d BLUE_REEF_A = new Pose2d(FieldConstants.BLUE_REEF_CENTER.plus(new Translation2d(1.4, -0.164309)), Rotation2d.kZero); + public static final Pose2d BLUE_REEF_B = FieldConstants.mirrorY(BLUE_REEF_A); + public static final Pose2d BLUE_REEF_C = BLUE_REEF_A.rotateAround(FieldConstants.BLUE_REEF_CENTER, Rotation2d.fromDegrees(60)); + public static final Pose2d BLUE_REEF_D = BLUE_REEF_B.rotateAround(FieldConstants.BLUE_REEF_CENTER, Rotation2d.fromDegrees(60)); + public static final Pose2d BLUE_REEF_E = BLUE_REEF_A.rotateAround(FieldConstants.BLUE_REEF_CENTER, Rotation2d.fromDegrees(120)); + public static final Pose2d BLUE_REEF_F = BLUE_REEF_B.rotateAround(FieldConstants.BLUE_REEF_CENTER, Rotation2d.fromDegrees(120)); + public static final Pose2d BLUE_REEF_G = BLUE_REEF_A.rotateAround(FieldConstants.BLUE_REEF_CENTER, Rotation2d.fromDegrees(180)); + public static final Pose2d BLUE_REEF_H = BLUE_REEF_B.rotateAround(FieldConstants.BLUE_REEF_CENTER, Rotation2d.fromDegrees(180)); + public static final Pose2d BLUE_REEF_I = BLUE_REEF_A.rotateAround(FieldConstants.BLUE_REEF_CENTER, Rotation2d.fromDegrees(240)); + public static final Pose2d BLUE_REEF_J = BLUE_REEF_B.rotateAround(FieldConstants.BLUE_REEF_CENTER, Rotation2d.fromDegrees(240)); + public static final Pose2d BLUE_REEF_K = BLUE_REEF_A.rotateAround(FieldConstants.BLUE_REEF_CENTER, Rotation2d.fromDegrees(300)); + public static final Pose2d BLUE_REEF_L = BLUE_REEF_B.rotateAround(FieldConstants.BLUE_REEF_CENTER, Rotation2d.fromDegrees(300)); + + public static final List BLUE_REEF_POSES = List.of(BLUE_REEF_A, BLUE_REEF_B, BLUE_REEF_C, BLUE_REEF_D, BLUE_REEF_E, BLUE_REEF_F, BLUE_REEF_G, BLUE_REEF_H, BLUE_REEF_I, BLUE_REEF_J, BLUE_REEF_K, BLUE_REEF_L); + public static final List BLUE_REEF_LEFT_POSES = List.of(BLUE_REEF_A, BLUE_REEF_C, BLUE_REEF_E, BLUE_REEF_G, BLUE_REEF_I, BLUE_REEF_K); + public static final List BLUE_REEF_RIGHT_POSES = List.of(BLUE_REEF_B, BLUE_REEF_D, BLUE_REEF_F, BLUE_REEF_H, BLUE_REEF_J, BLUE_REEF_L); + + public static final Pose2d RED_REEF_A = FieldConstants.rotateAboutCenter(BLUE_REEF_A); + public static final Pose2d RED_REEF_B = FieldConstants.rotateAboutCenter(BLUE_REEF_B); + public static final Pose2d RED_REEF_C = FieldConstants.rotateAboutCenter(BLUE_REEF_C); + public static final Pose2d RED_REEF_D = FieldConstants.rotateAboutCenter(BLUE_REEF_D); + public static final Pose2d RED_REEF_E = FieldConstants.rotateAboutCenter(BLUE_REEF_E); + public static final Pose2d RED_REEF_F = FieldConstants.rotateAboutCenter(BLUE_REEF_F); + public static final Pose2d RED_REEF_G = FieldConstants.rotateAboutCenter(BLUE_REEF_G); + public static final Pose2d RED_REEF_H = FieldConstants.rotateAboutCenter(BLUE_REEF_H); + public static final Pose2d RED_REEF_I = FieldConstants.rotateAboutCenter(BLUE_REEF_I); + public static final Pose2d RED_REEF_J = FieldConstants.rotateAboutCenter(BLUE_REEF_J); + public static final Pose2d RED_REEF_K = FieldConstants.rotateAboutCenter(BLUE_REEF_K); + public static final Pose2d RED_REEF_L = FieldConstants.rotateAboutCenter(BLUE_REEF_L); + + public static final List RED_REEF_POSES = List.of(RED_REEF_A, RED_REEF_B, RED_REEF_C, RED_REEF_D, RED_REEF_E, RED_REEF_F, RED_REEF_G, RED_REEF_H, RED_REEF_I, RED_REEF_J, RED_REEF_K, RED_REEF_L); + public static final List RED_REEF_LEFT_POSES = List.of(RED_REEF_A, RED_REEF_C, RED_REEF_E, RED_REEF_G, RED_REEF_I, RED_REEF_K); + public static final List RED_REEF_RIGHT_POSES = List.of(RED_REEF_B, RED_REEF_D, RED_REEF_F, RED_REEF_H, RED_REEF_J, RED_REEF_L); + + public static final Transform2d REEF_PREALIGN_TRANSFORM = new Transform2d(0.3, 0, Rotation2d.kZero); +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/Robot.java b/examples/stage2/templates/ctre/src/main/java/first/robot/Robot.java new file mode 100644 index 00000000..6552e961 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/Robot.java @@ -0,0 +1,35 @@ +// 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 first.robot; + +import dev.doglog.DogLog; +import dev.doglog.DogLogOptions; +import first.robot.mechanisms.*; +import org.wpilib.command3.Scheduler; +import org.wpilib.framework.OpModeRobot; + +public class Robot extends OpModeRobot { + + public final Drive drive; + public final Elevator elevator; + public final Arm arm; + public final Superstructure superstructure; + public final Claw claw; + + public Robot() { + DogLog.setOptions(new DogLogOptions().withCaptureDs(true)); + + drive = new Drive(); + elevator = new Elevator(); + arm = new Arm(); + superstructure = new Superstructure(elevator, arm); + claw = new Claw(); + } + + @Override + public void robotPeriodic() { + Scheduler.getDefault().run(); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Arm.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Arm.java new file mode 100644 index 00000000..23ad994c --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Arm.java @@ -0,0 +1,160 @@ +package first.robot.mechanisms; + +import com.ctre.phoenix6.BaseStatusSignal; +import com.ctre.phoenix6.CANBus; +import com.ctre.phoenix6.StatusSignal; +import com.ctre.phoenix6.configs.CurrentLimitsConfigs; +import com.ctre.phoenix6.configs.TalonFXConfiguration; +import com.ctre.phoenix6.controls.Follower; +import com.ctre.phoenix6.controls.PositionVoltage; +import com.ctre.phoenix6.controls.VoltageOut; +import com.ctre.phoenix6.hardware.TalonFX; +import com.ctre.phoenix6.signals.GravityTypeValue; +import com.ctre.phoenix6.signals.InvertedValue; +import com.ctre.phoenix6.signals.MotorAlignmentValue; +import com.ctre.phoenix6.signals.NeutralModeValue; +import dev.doglog.DogLog; +import first.robot.simulation.ArmSim; +import org.wpilib.command3.Command; +import org.wpilib.command3.Mechanism; +import org.wpilib.command3.Scheduler; +import org.wpilib.math.geometry.Rotation2d; +import org.wpilib.units.measure.Angle; +import org.wpilib.units.measure.AngularVelocity; +import org.wpilib.units.measure.Current; +import org.wpilib.units.measure.Voltage; + +public class Arm extends Mechanism { + + private static class Constants { + static final int MOTOR_ID = 22; + static final CANBus BUS = CANBus.systemcore(1); + + static final InvertedValue MOTOR_INVERSION = InvertedValue.Clockwise_Positive; + + static final double GEAR_RATIO = (56.0 / 16.0) * (84.0 / 10.0); // Two stage, 56:16, 84:10 reductions + + static final double kS = 0; + static final double kG = 0.777; + static final double kV = 3.464; + static final double kA = 0.192; + static final double kP = 60; + static final double kI = 0; + static final double kD = 7; + + static final double POSITION_TOLERANCE = 3.0/360.0; + } + + private TalonFX motor; + private ArmSim sim; + + private VoltageOut voltageRequest; + private PositionVoltage positionRequest; + + private StatusSignal appliedVoltageSignal; + private StatusSignal positionSignal; + private StatusSignal velocitySignal; + private StatusSignal statorCurrentSignal; + private StatusSignal supplyCurrentSignal; + + private BaseStatusSignal[] signals; + + private double setpoint = 0; + + public Arm() { + // Construct and configure motors + motor = new TalonFX(Constants.MOTOR_ID, Constants.BUS); + + TalonFXConfiguration leaderConfiguration = new TalonFXConfiguration(); + leaderConfiguration.MotorOutput.withNeutralMode(NeutralModeValue.Brake) + .withInverted(Constants.MOTOR_INVERSION); + leaderConfiguration.Feedback.withSensorToMechanismRatio(Constants.GEAR_RATIO); + leaderConfiguration.Slot0.withKS(Constants.kS) + .withKG(Constants.kG) + .withKV(Constants.kV) + .withKA(Constants.kA) + .withKP(Constants.kP) + .withKI(Constants.kI) + .withKD(Constants.kD) + .withGravityType(GravityTypeValue.Arm_Cosine); + + CurrentLimitsConfigs currentLimitsConfigs = new CurrentLimitsConfigs(); + currentLimitsConfigs.withStatorCurrentLimit(60) + .withStatorCurrentLimitEnable(true) + .withSupplyCurrentLimit(60) + .withSupplyCurrentLimitEnable(true); + + leaderConfiguration.withCurrentLimits(currentLimitsConfigs); + + motor.getConfigurator().apply(leaderConfiguration); + + // Set up periodic method to run every code loop + Scheduler.getDefault().addPeriodic(this::periodic); + + // Set up simulation + sim = new ArmSim(motor); + + // Set up requests + voltageRequest = new VoltageOut(0); + positionRequest = new PositionVoltage(0); + + // Set up status signals + appliedVoltageSignal = motor.getMotorVoltage(); + positionSignal = motor.getPosition(); + velocitySignal = motor.getVelocity(); + statorCurrentSignal = motor.getStatorCurrent(); + supplyCurrentSignal = motor.getSupplyCurrent(); + + signals = new BaseStatusSignal[] {appliedVoltageSignal, positionSignal, velocitySignal, + statorCurrentSignal, supplyCurrentSignal}; + } + + private void periodic() { + // Update simulation + sim.periodic(); + + BaseStatusSignal.refreshAll(signals); + + DogLog.log("Arm/Applied Voltage", getAppliedVoltage()); + DogLog.log("Arm/Position", getPosition()); + DogLog.log("Arm/Velocity", getVelocity()); + DogLog.log("Arm/At Setpoint", isAtSetpoint()); + DogLog.log("Arm/Active Commands", getRunningCommands().toString()); + } + + public Command setVoltage(double voltage) { + return run(coro -> { + motor.setControl(voltageRequest.withOutput(voltage)); + }).named("Set Voltage " + voltage + "V"); + } + + public Command setPosition(double position) { + return run(coro -> { + setpoint = position; + motor.setControl(positionRequest.withPosition(position)); + DogLog.log("Arm/Setpoint", position); + + coro.await(Command.waitUntil(() -> isAtPosition(position)).named("Is at position " + position)); + }).named("Set Position " + position + "rot"); + } + + public double getPosition() { + return positionSignal.getValueAsDouble(); + } + + public double getVelocity() { + return velocitySignal.getValueAsDouble(); + } + + public double getAppliedVoltage() { + return appliedVoltageSignal.getValueAsDouble(); + } + + public boolean isAtPosition(double position) { + return Math.abs(getPosition() - position) < Constants.POSITION_TOLERANCE; + } + + public boolean isAtSetpoint() { + return isAtPosition(setpoint); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Claw.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Claw.java new file mode 100644 index 00000000..192cbf75 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Claw.java @@ -0,0 +1,61 @@ +package first.robot.mechanisms; + +import com.ctre.phoenix6.BaseStatusSignal; +import com.ctre.phoenix6.CANBus; +import com.ctre.phoenix6.StatusSignal; +import com.ctre.phoenix6.controls.VoltageOut; +import com.ctre.phoenix6.hardware.TalonFX; +import dev.doglog.DogLog; +import first.robot.simulation.ClawSim; +import org.wpilib.command3.Command; +import org.wpilib.command3.Mechanism; +import org.wpilib.command3.Scheduler; +import org.wpilib.units.measure.Current; +import org.wpilib.units.measure.Voltage; + +public class Claw extends Mechanism { + private final TalonFX motor; + + private final ClawSim sim; + + private VoltageOut voltageRequest; + + private StatusSignal appliedVoltageSignal; + private StatusSignal statorCurrentSignal; + private StatusSignal supplyCurrentSignal; + + private BaseStatusSignal[] signals; + + public Claw() { + motor = new TalonFX(23, CANBus.systemcore(2)); + + sim = new ClawSim(motor); + + voltageRequest = new VoltageOut(0); + + appliedVoltageSignal = motor.getMotorVoltage(); + statorCurrentSignal = motor.getStatorCurrent(); + supplyCurrentSignal = motor.getSupplyCurrent(); + + signals = new BaseStatusSignal[] {appliedVoltageSignal, statorCurrentSignal, supplyCurrentSignal}; + + Scheduler.getDefault().addPeriodic(this::periodic); + } + + public void periodic() { + sim.periodic(); + + BaseStatusSignal.refreshAll(signals); + + DogLog.log("Claw/Applied Voltage", appliedVoltageSignal.getValueAsDouble()); + DogLog.log("Claw/Stator Current", statorCurrentSignal.getValueAsDouble()); + DogLog.log("Claw/Supply Current", supplyCurrentSignal.getValueAsDouble()); + DogLog.log("Claw/Active Commands", getRunningCommands().toString()); + } + + public Command setVoltage(double voltage) { + return run((coro) -> { + motor.setControl(voltageRequest.withOutput(voltage)); + }).named("Set Voltage: " + voltage + "V"); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Drive.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Drive.java new file mode 100644 index 00000000..cce65275 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Drive.java @@ -0,0 +1,357 @@ +package first.robot.mechanisms; + +import com.ctre.phoenix6.configs.CANcoderConfiguration; +import com.ctre.phoenix6.configs.ParentConfiguration; +import com.ctre.phoenix6.configs.Slot0Configs; +import com.ctre.phoenix6.configs.TalonFXConfiguration; +import com.ctre.phoenix6.hardware.CANcoder; +import com.ctre.phoenix6.hardware.TalonFX; +import com.ctre.phoenix6.signals.StaticFeedforwardSignValue; +import com.ctre.phoenix6.swerve.*; +import dev.doglog.DogLog; +import org.wpilib.command3.*; +import org.wpilib.command3.button.CommandNiDsXboxController; +import org.wpilib.math.controller.PIDController; +import org.wpilib.math.controller.ProfiledPIDController; +import org.wpilib.math.geometry.Pose2d; +import org.wpilib.math.geometry.Rotation2d; +import org.wpilib.math.geometry.Translation2d; +import org.wpilib.math.kinematics.ChassisVelocities; +import org.wpilib.math.kinematics.SwerveModulePosition; +import org.wpilib.math.trajectory.TrapezoidProfile; +import org.wpilib.math.util.MathUtil; +import org.wpilib.math.util.Units; + +import java.util.Set; +import java.util.function.Supplier; + +import static org.wpilib.units.Units.*; + +public class Drive extends Mechanism { + + private final SwerveDrivetrain swerve; + + private final SwerveDrivetrainConstants constants; + private final SwerveModuleConstantsFactory moduleConstantsFactory; + + private final double DRIVE_WIDTH = Units.inchesToMeters(22.729228); + private final double DRIVE_LENGTH = Units.inchesToMeters(22.729228); + + private static final double MAX_VELOCITY = Units.feetToMeters(14.9); + private static final double MAX_ACCELERATION = Units.feetToMeters(8); + + public Drive() { + constants = new SwerveDrivetrainConstants().withCANBusName("can_s0").withPigeon2Id(4); + + moduleConstantsFactory = new SwerveModuleConstantsFactory<>() + .withDriveMotorGearRatio(7.03) + .withSteerMotorGearRatio(26.09090909090909) + .withCouplingGearRatio(3.857142857142857) + .withWheelRadius(Units.inchesToMeters(2)) +// .withSteerMotorGains(new Slot0Configs() +// .withKP(0.05).withKD(0) +// .withKS(0.2).withKV(0.72972) +// .withStaticFeedforwardSign(StaticFeedforwardSignValue.UseClosedLoopSign)) + .withSteerMotorGains(new Slot0Configs() + .withKP(100).withKI(0).withKD(0.5) + .withKS(0.1).withKV(2.49).withKA(0) + .withStaticFeedforwardSign(StaticFeedforwardSignValue.UseClosedLoopSign)) + .withDriveMotorGains(new Slot0Configs() + .withKP(8).withKD(0) + .withKS(0.2)) +// .withDriveMotorGains(new Slot0Configs() +// .withKP(0.3).withKI(0).withKD(0) +// .withKS(0.2).withKV(0.13)) + .withSteerMotorClosedLoopOutput(SwerveModuleConstants.ClosedLoopOutputType.Voltage) + .withDriveMotorClosedLoopOutput(SwerveModuleConstants.ClosedLoopOutputType.Voltage) + .withSlipCurrent(Amps.of(45)) + .withSpeedAt12Volts(MAX_VELOCITY) + .withDriveMotorType(SwerveModuleConstants.DriveMotorArrangement.TalonFX_Integrated) + .withSteerMotorType(SwerveModuleConstants.SteerMotorArrangement.TalonFX_Integrated) + .withFeedbackSource(SwerveModuleConstants.SteerFeedbackType.RemoteCANcoder) + .withDriveMotorInitialConfigs(new TalonFXConfiguration()) + .withSteerMotorInitialConfigs(new TalonFXConfiguration()) + .withEncoderInitialConfigs(new CANcoderConfiguration()) + .withSteerInertia(KilogramSquareMeters.of(0.01)) + .withDriveInertia(KilogramSquareMeters.of(0.03)) + .withSteerFrictionVoltage(Volts.of(0.1)) + .withDriveFrictionVoltage(Volts.of(0.2)); + + var moduleConstants = new SwerveModuleConstants[] { + moduleConstantsFactory.createModuleConstants(10, 11, 10, 0, DRIVE_LENGTH/2, DRIVE_WIDTH/2, false, false, false), + moduleConstantsFactory.createModuleConstants(12, 13, 12, 0, DRIVE_LENGTH/2, -DRIVE_WIDTH/2, false, false, false), + moduleConstantsFactory.createModuleConstants(14, 15, 14, 0, -DRIVE_LENGTH/2, DRIVE_WIDTH/2, false, false, false), + moduleConstantsFactory.createModuleConstants(16, 17, 16, 0, -DRIVE_LENGTH/2, -DRIVE_WIDTH/2, false, false, false) + }; + + swerve = new SwerveDrivetrain<>(TalonFX::new, TalonFX::new, CANcoder::new, constants, moduleConstants); + + Scheduler.getDefault().addPeriodic(this::periodic); + } + + public void periodic() { + // Update sim 5x per loop for greater PID realism + swerve.updateSimState(1.0/50.0/5.0, 12); + swerve.updateSimState(1.0/50.0/5.0, 12); + swerve.updateSimState(1.0/50.0/5.0, 12); + swerve.updateSimState(1.0/50.0/5.0, 12); + swerve.updateSimState(1.0/50.0/5.0, 12); + + DogLog.log("Drive/Pose", getPose()); + + var modules = swerve.getModules(); + SwerveModulePosition[] modulePositions = new SwerveModulePosition[4]; + for (int i = 0; i < modules.length; i++) { + modulePositions[i] = modules[i].getPosition(true); + DogLog.log("Drive/ModuleVel/"+i, modules[i].getDriveMotor().getVelocity().getValueAsDouble()); + } + + DogLog.log("Drive/ModulesReal", swerve.getState().ModuleVelocities); + DogLog.log("Drive/ModulesTargets", swerve.getState().ModuleTargets); + DogLog.log("Drive/Velocity", swerve.getState().Velocity); + DogLog.log("Drive/Active Commands", getRunningCommands().toString()); + } + + public Pose2d getPose() { + return swerve.getState().Pose; + } + + public Rotation2d getHeading() { + return getPose().getRotation(); + } + + + /** + * @return Field-relative robot velocity + */ + public ChassisVelocities getVelocity() { + return swerve.getState().Velocity.toFieldRelative(getHeading()); + } + + public Command getDriveCommand(CommandNiDsXboxController controller) { + return runRepeatedly(() -> { + double x = -MathUtil.applyDeadband(controller.getLeftY(), 0.1) * MAX_VELOCITY; + double y = -MathUtil.applyDeadband(controller.getLeftX(), 0.1) * MAX_VELOCITY; + double rotation = -MathUtil.applyDeadband(controller.getRightX(), 0.10) * 10; + + var request = new SwerveRequest.FieldCentric(); + request.withForwardPerspective(SwerveRequest.ForwardPerspectiveValue.OperatorPerspective); + request.withVelocityX(x); + request.withVelocityY(y); + request.withRotationalRate(rotation) + .withDeadband(0.1*4.54); + + swerve.setControl(request); + + DogLog.log("Drive/DesiredVelocity", new ChassisVelocities(x, y, rotation).toRobotRelative(getHeading())); + }).named("Teleop Drive"); + } + + public static class AutoAlignCommand implements Command { + + private final Drive drive; + + private final SwerveRequest.ApplyFieldVelocity swerveRequest = new SwerveRequest.ApplyFieldVelocity();; + + private Pose2d targetPose = new Pose2d(); + private Supplier poseSupplier = () -> {return new Pose2d();}; + private boolean usePoseSupplier = false; + + private double accelerationLimit = MAX_ACCELERATION; + private double velocityLimit = MAX_VELOCITY; + private double positionTolerance = Units.inchesToMeters(2); + private double angleTolerance = Units.degreesToRadians(3); + private boolean runContinuously = false; + private boolean ignoreRotation = false; + private double targetVelocity = 0; + + private double angularVelocityLimit = 5; + private double angularAccelerationLimit = 45; + + private ProfiledPIDController headingController; + + public AutoAlignCommand(Drive drive, Pose2d target) { + this.drive = drive; + this.targetPose = target; + this.usePoseSupplier = false; + + headingController = new ProfiledPIDController(6, 0, 0.2, new TrapezoidProfile.Constraints(angularVelocityLimit, angularAccelerationLimit)); + headingController.enableContinuousInput(-Math.PI, Math.PI); + } + + public AutoAlignCommand(Drive drive, Supplier poseSupplier) { + this.drive = drive; + this.poseSupplier = poseSupplier; + this.usePoseSupplier = true; + + headingController = new ProfiledPIDController(6, 0, 0.2, new TrapezoidProfile.Constraints(angularVelocityLimit, angularAccelerationLimit)); + headingController.enableContinuousInput(-Math.PI, Math.PI); + } + + @Override + public void run(Coroutine coroutine) { + if (usePoseSupplier) { + targetPose = poseSupplier.get(); + } + + headingController.reset(drive.getHeading().getRadians(), drive.getVelocity().omega); + + while (runContinuously || !atPosition()) { + var currentPose = drive.getPose(); + + if (usePoseSupplier) { + targetPose = poseSupplier.get(); + } + + DogLog.log("Drive/AutoAlign/At Target", atPosition()); + DogLog.log("Drive/AutoAlign/Position in Tolerance", targetPose.getTranslation().getDistance(drive.getPose().getTranslation()) <= positionTolerance); + DogLog.log("Drive/AutoAlign/Angle in Tolerance", (ignoreRotation || Math.abs(targetPose.getRotation().minus(drive.getPose().getRotation()).getRadians()) <= angleTolerance)); + + DogLog.log("Drive/AutoAlign/Target Pose", targetPose); + + Translation2d vectorToTarget = targetPose.getTranslation().minus(currentPose.getTranslation()); + double distanceToEnd = vectorToTarget.getNorm(); + + DogLog.log("Drive/AutoAlign/Distance", distanceToEnd); + +// ChassisVelocities currentVelocity = drive.getVelocity(); +// Translation2d currentVelocityAsTranslation = new Translation2d(currentVelocity.vx, currentVelocity.vy); +// +// double velocityTowardsTarget = vectorToTarget.div(distanceToEnd).dot(currentVelocityAsTranslation); +// +// double maxDistanceForCurrentSpeed = (Math.pow(targetVelocity,2) - Math.pow(velocityTowardsTarget,2)) / (2 * -accelerationLimit); + + double velocityForDistance = Math.pow(targetVelocity,2) - 2*-accelerationLimit*distanceToEnd; + + DogLog.log("Drive/AutoAlign/Velocity for Distance", velocityForDistance); + + double velocityToTarget = Math.min(velocityForDistance * 0.85, velocityLimit); + + DogLog.log("Drive/AutoAlign/Targeted Velocity", velocityToTarget); + + Translation2d velocities = vectorToTarget.div(distanceToEnd).times(velocityToTarget); + + + + double omega = 0; + + if(!ignoreRotation) { + omega = headingController.calculate(currentPose.getRotation().getRadians(), targetPose.getRotation().getRadians()); + } + + ChassisVelocities chassisVelocities = new ChassisVelocities(velocities.getX(), velocities.getY(), omega); + + DogLog.log("Drive/DesiredVelocity", chassisVelocities); + + swerveRequest.withVelocity(chassisVelocities); + drive.swerve.setControl(swerveRequest); + + coroutine.yield(); + } + + // If command ends naturally, have the robot stop + swerveRequest.withVelocity(new ChassisVelocities()); + drive.swerve.setControl(swerveRequest); + } + + @Override + public String name() { + return "AutoAlign"; + } + + @Override + public Set requirements() { + return Set.of(drive); + } + + /** + * Sets a static target pose for the command + * @param pose The pose to target + * @return this command + */ + public AutoAlignCommand withTargetPose(Pose2d pose) { + this.targetPose = pose; + this.usePoseSupplier = false; + return this; + } + + /** + * Sets a pose supplier for the command to use, which will be evaluated continuously + * @param supplier The supplier to use for the pose target + * @return this command + */ + public AutoAlignCommand withTargetPoseSupplier(Supplier supplier) { + this.poseSupplier = supplier; + this.usePoseSupplier = true; + return this; + } + + /** + * Sets the maximum allowed acceleration for the robot to use + * @param accelerationLimit The maximum allowed acceleration + * @return this command + */ + public AutoAlignCommand withAccelerationLimit(double accelerationLimit) { + this.accelerationLimit = accelerationLimit; + return this; + } + + /** + * Sets the maximum allowed velocity for the robot to use + * @param velocityLimit The maximum allowed velocity + * @return this command + */ + public AutoAlignCommand withVelocityLimit(double velocityLimit) { + this.velocityLimit = velocityLimit; + return this; + } + + /** + * Sets the tolerance for the final position of the robot. Used to determine whether + * @param tolerance + * @return + */ + public AutoAlignCommand withPositionTolerance(double tolerance) { + this.positionTolerance = tolerance; + return this; + } + + public AutoAlignCommand withAngleTolerance(double tolerance) { + this.angleTolerance = tolerance; + return this; + } + + public AutoAlignCommand withRunningContinuously(boolean runContinuously) { + this.runContinuously = runContinuously; + return this; + } + + public AutoAlignCommand withIgnoringRotation(boolean ignoringRotation) { + this.ignoreRotation = ignoringRotation; + return this; + } + + public AutoAlignCommand withTargetVelocity(double targetVelocity) { + this.targetVelocity = targetVelocity; + return this; + } + + public AutoAlignCommand withAngularAccelerationLimit(double angularAccelerationLimit) { + this.angularAccelerationLimit = angularAccelerationLimit; + return this; + } + + public AutoAlignCommand withAngularVelocityLimit(double angularVelocityLimit) { + this.angularVelocityLimit = angularVelocityLimit; + return this; + } + + public boolean atPosition() { + return targetPose.getTranslation().getDistance(drive.getPose().getTranslation()) <= positionTolerance && (ignoreRotation || Math.abs(targetPose.getRotation().minus(drive.getPose().getRotation()).getRadians()) <= angleTolerance); + } + + public Command waitUntilAtPosition() { + return Command.waitUntil(this::atPosition).named("Wait Until At Position"); + } + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Elevator.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Elevator.java new file mode 100644 index 00000000..e5f80cea --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Elevator.java @@ -0,0 +1,172 @@ +package first.robot.mechanisms; + +import com.ctre.phoenix6.BaseStatusSignal; +import com.ctre.phoenix6.CANBus; +import com.ctre.phoenix6.StatusSignal; +import com.ctre.phoenix6.configs.CurrentLimitsConfigs; +import com.ctre.phoenix6.configs.TalonFXConfiguration; +import com.ctre.phoenix6.controls.Follower; +import com.ctre.phoenix6.controls.PositionVoltage; +import com.ctre.phoenix6.controls.VoltageOut; +import com.ctre.phoenix6.hardware.TalonFX; +import com.ctre.phoenix6.signals.GravityTypeValue; +import com.ctre.phoenix6.signals.InvertedValue; +import com.ctre.phoenix6.signals.MotorAlignmentValue; +import com.ctre.phoenix6.signals.NeutralModeValue; +import dev.doglog.DogLog; +import first.robot.simulation.ElevatorSim; +import org.wpilib.command3.Command; +import org.wpilib.command3.Mechanism; +import org.wpilib.command3.Scheduler; +import org.wpilib.math.util.Units; +import org.wpilib.units.measure.*; + +public class Elevator extends Mechanism { + + private static class Constants { + static final int LEADER_ID = 20; + static final int FOLLOWER_ID = 21; + static final CANBus BUS = CANBus.systemcore(1); + + static final InvertedValue LEADER_INVERSION = InvertedValue.Clockwise_Positive; + static final MotorAlignmentValue FOLLOWER_ALIGNMENT = MotorAlignmentValue.Opposed; + + static final double GEAR_RATIO = (44.0 / 14.0) * (42.0 / 22.0); // Two stage, 14:44, 22:44 reductions + static final double PULLEY_CIRCUMFERENCE = 24 * 0.005; // Convert 24t pulley to circumference, in m + + static final double kS = 0; + static final double kG = 0.26; + static final double kV = 5.94; + static final double kA = 0.03; + static final double kP = 18.3; + static final double kI = 0; + static final double kD = 0.7;; + + static final double POSITION_TOLERANCE = Units.inchesToMeters(0.5); + } + + private final TalonFX leader; + private final TalonFX follower; + + private final ElevatorSim sim; + + private final VoltageOut voltageRequest; + private final PositionVoltage positionRequest; + + private final StatusSignal appliedVoltageSignal; + private final StatusSignal positionSignal; + private final StatusSignal velocitySignal; + private final StatusSignal leaderStatorCurrentSignal; + private final StatusSignal followerStatorCurrentSignal; + private final StatusSignal leaderSupplyCurrentSignal; + private final StatusSignal followerSupplyCurrentSignal; + + private final BaseStatusSignal[] signals; + + private double setpoint = 0; + + public Elevator() { + // Construct and configure motors + leader = new TalonFX(Constants.LEADER_ID, Constants.BUS); + follower = new TalonFX(Constants.FOLLOWER_ID, Constants.BUS); + + TalonFXConfiguration leaderConfiguration = new TalonFXConfiguration(); + leaderConfiguration.MotorOutput.withNeutralMode(NeutralModeValue.Brake) + .withInverted(Constants.LEADER_INVERSION); + leaderConfiguration.Feedback.withSensorToMechanismRatio(Constants.GEAR_RATIO); + leaderConfiguration.Slot0.withKS(Constants.kS) + .withKG(Constants.kG) + .withKV(Constants.kV) + .withKA(Constants.kA) + .withKP(Constants.kP) + .withKI(Constants.kI) + .withKD(Constants.kD) + .withGravityType(GravityTypeValue.Elevator_Static); + + CurrentLimitsConfigs currentLimitsConfigs = new CurrentLimitsConfigs(); + currentLimitsConfigs.withStatorCurrentLimit(60) + .withStatorCurrentLimitEnable(true) + .withSupplyCurrentLimit(60) + .withSupplyCurrentLimitEnable(true); + + leaderConfiguration.withCurrentLimits(currentLimitsConfigs); + + leader.getConfigurator().apply(leaderConfiguration); + + follower.getConfigurator().apply(currentLimitsConfigs); + + follower.setControl(new Follower(leader.getDeviceID(), Constants.FOLLOWER_ALIGNMENT)); + + // Set up periodic method to run every code loop + Scheduler.getDefault().addPeriodic(this::periodic); + + // Set up simulation + sim = new ElevatorSim(leader, follower); + + // Set up requests + voltageRequest = new VoltageOut(0); + positionRequest = new PositionVoltage(0); + + // Set up status signals + appliedVoltageSignal = leader.getMotorVoltage(); + positionSignal = leader.getPosition(); + velocitySignal = leader.getVelocity(); + leaderStatorCurrentSignal = leader.getStatorCurrent(); + followerStatorCurrentSignal = follower.getStatorCurrent(); + leaderSupplyCurrentSignal = leader.getSupplyCurrent(); + followerSupplyCurrentSignal = follower.getSupplyCurrent(); + + signals = new BaseStatusSignal[] {appliedVoltageSignal, positionSignal, velocitySignal, + leaderStatorCurrentSignal, followerStatorCurrentSignal, leaderSupplyCurrentSignal, + followerSupplyCurrentSignal}; + } + + private void periodic() { + // Update simulation + sim.periodic(); + + BaseStatusSignal.refreshAll(signals); + + DogLog.log("Elevator/Applied Voltage", getAppliedVoltage()); + DogLog.log("Elevator/Position", getPosition()); + DogLog.log("Elevator/Velocity", getVelocity()); + DogLog.log("Elevator/At Setpoint", isAtSetpoint()); + DogLog.log("Elevator/Active Commands", getRunningCommands().toString()); + } + + public Command setVoltage(double voltage) { + return run(coro -> { + leader.setControl(voltageRequest.withOutput(voltage)); + }).named("Set Voltage " + voltage + "V"); + } + + public Command setPosition(double position) { + return run(coro -> { + setpoint = position; + leader.setControl(positionRequest.withPosition(position / Constants.PULLEY_CIRCUMFERENCE)); + DogLog.log("Elevator/Setpoint", position); + + coro.await(Command.waitUntil(() -> isAtPosition(position)).named("Is At Position " + position)); + }).named("Set Position " + position + "m"); + } + + public double getPosition() { + return positionSignal.getValueAsDouble() * Constants.PULLEY_CIRCUMFERENCE; + } + + public double getVelocity() { + return velocitySignal.getValueAsDouble() * Constants.PULLEY_CIRCUMFERENCE; + } + + public double getAppliedVoltage() { + return appliedVoltageSignal.getValueAsDouble(); + } + + public boolean isAtPosition(double position) { + return Math.abs(getPosition() - position) < Constants.POSITION_TOLERANCE; + } + + public boolean isAtSetpoint() { + return isAtPosition(setpoint); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Superstructure.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Superstructure.java new file mode 100644 index 00000000..8e123b55 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Superstructure.java @@ -0,0 +1,22 @@ +package first.robot.mechanisms; + +import org.wpilib.command3.Command; +import org.wpilib.command3.Mechanism; + +public class Superstructure extends Mechanism { + + private final Elevator elevator; + private final Arm arm; + + public Superstructure(Elevator elevator, Arm arm) { + this.elevator = elevator; + this.arm = arm; + } + + public Command setPosition(double elevatorPosition, double armPosition) { + return run((coro) -> { + coro.awaitAll(elevator.setPosition(elevatorPosition), + arm.setPosition(armPosition)); + }).named("Set Position"); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/ArmTest.java b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/ArmTest.java new file mode 100644 index 00000000..ddcd1603 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/ArmTest.java @@ -0,0 +1,27 @@ +// 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 first.robot.opmode; + +import first.robot.Robot; +import org.wpilib.command3.button.CommandGamepad; +import org.wpilib.opmode.PeriodicOpMode; +import org.wpilib.opmode.Teleop; +import org.wpilib.opmode.Utility; + +@Utility +public class ArmTest extends PeriodicOpMode { + private final Robot robot; + + private final CommandGamepad controller; + + public ArmTest(Robot robot) { + this.robot = robot; + + controller = new CommandGamepad(0); + + controller.button(0).onTrue(robot.arm.setPosition(1)); + controller.button(1).onTrue(robot.arm.setPosition(0)); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/BasicScoringTeleop.java b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/BasicScoringTeleop.java new file mode 100644 index 00000000..aa2cb245 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/BasicScoringTeleop.java @@ -0,0 +1,47 @@ +// 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 first.robot.opmode; + +import first.robot.Poses; +import first.robot.Robot; +import first.robot.mechanisms.Drive; +import first.robot.mechanisms.Drive.AutoAlignCommand; +import org.wpilib.command3.Command; +import org.wpilib.command3.button.CommandGamepad; +import org.wpilib.command3.button.CommandNiDsXboxController; +import org.wpilib.math.util.Units; +import org.wpilib.opmode.PeriodicOpMode; +import org.wpilib.opmode.Teleop; +import org.wpilib.opmode.Utility; +import static org.wpilib.units.Units.*; +import org.wpilib.units.measure.Time; + +@Teleop +public class BasicScoringTeleop extends PeriodicOpMode { + private final Robot robot; + + private final CommandNiDsXboxController controller; + + public BasicScoringTeleop(Robot robot) { + this.robot = robot; + + controller = new CommandNiDsXboxController(0); + + robot.drive.setDefaultCommand(robot.drive.getDriveCommand(controller)); + + controller.button(0).whileTrue(Command.noRequirements((coro) -> { + coro.awaitAll(robot.superstructure.setPosition(1, 1), + new AutoAlignCommand(robot.drive, Poses.BLUE_REEF_A.transformBy(Poses.REEF_PREALIGN_TRANSFORM)).withPositionTolerance(Units.inchesToMeters(6))); + + AutoAlignCommand finalAlign = new AutoAlignCommand(robot.drive, Poses.BLUE_REEF_A).withRunningContinuously(true); + coro.fork(finalAlign); + coro.await(finalAlign.waitUntilAtPosition()); + + coro.fork(robot.superstructure.setPosition(0.5, 0.5)); + coro.wait(Seconds.of(0.25)); + coro.await(robot.claw.setVoltage(-6)); + }).named("AutoAlign and Score")); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/DriveTest.java b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/DriveTest.java new file mode 100644 index 00000000..47d8cc80 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/DriveTest.java @@ -0,0 +1,27 @@ +// 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 first.robot.opmode; + +import first.robot.Robot; +import org.wpilib.command3.button.CommandGamepad; +import org.wpilib.command3.button.CommandNiDsXboxController; +import org.wpilib.opmode.PeriodicOpMode; +import org.wpilib.opmode.Teleop; +import org.wpilib.opmode.Utility; + +@Utility +public class DriveTest extends PeriodicOpMode { + private final Robot robot; + + private final CommandNiDsXboxController controller; + + public DriveTest(Robot robot) { + this.robot = robot; + + controller = new CommandNiDsXboxController(0); + + robot.drive.setDefaultCommand(robot.drive.getDriveCommand(controller)); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/ElevatorTest.java b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/ElevatorTest.java new file mode 100644 index 00000000..b91ec5c3 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/ElevatorTest.java @@ -0,0 +1,27 @@ +// 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 first.robot.opmode; + +import org.wpilib.command3.button.CommandGamepad; +import org.wpilib.opmode.PeriodicOpMode; +import org.wpilib.opmode.Teleop; +import first.robot.Robot; +import org.wpilib.opmode.Utility; + +@Utility +public class ElevatorTest extends PeriodicOpMode { + private final Robot robot; + + private final CommandGamepad controller; + + public ElevatorTest(Robot robot) { + this.robot = robot; + + controller = new CommandGamepad(0); + + controller.button(0).onTrue(robot.elevator.setPosition(1)); + controller.button(1).onTrue(robot.elevator.setPosition(0)); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/FullScoringTeleop.java b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/FullScoringTeleop.java new file mode 100644 index 00000000..23793e96 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/FullScoringTeleop.java @@ -0,0 +1,49 @@ +package first.robot.opmode; + +import first.robot.Poses; +import first.robot.Robot; +import first.robot.mechanisms.Drive; +import org.wpilib.command3.Command; +import org.wpilib.command3.NeedsNameBuilderStage; +import org.wpilib.command3.button.CommandNiDsXboxController; +import org.wpilib.math.geometry.Pose2d; +import org.wpilib.math.util.Units; +import org.wpilib.opmode.PeriodicOpMode; +import org.wpilib.opmode.Teleop; + +import java.util.List; + +import static org.wpilib.units.Units.Seconds; + +@Teleop +public class FullScoringTeleop extends PeriodicOpMode { + private final Robot robot; + + private final CommandNiDsXboxController controller; + + public FullScoringTeleop(Robot robot) { + this.robot = robot; + + controller = new CommandNiDsXboxController(0); + + robot.drive.setDefaultCommand(robot.drive.getDriveCommand(controller)); + + controller.button(4).whileTrue(createScoreCommand(Poses.BLUE_REEF_LEFT_POSES).named("Autoalign and score (left)")); + controller.button(5).whileTrue(createScoreCommand(Poses.BLUE_REEF_RIGHT_POSES).named("Autoalign and score (right)")); + } + + private NeedsNameBuilderStage createScoreCommand(List poses) { + return Command.noRequirements((coro) -> { + coro.awaitAll(robot.superstructure.setPosition(1, 1), + new Drive.AutoAlignCommand(robot.drive, () -> robot.drive.getPose().nearest(poses).transformBy(Poses.REEF_PREALIGN_TRANSFORM)).withPositionTolerance(Units.inchesToMeters(6))); + + Drive.AutoAlignCommand finalAlign = new Drive.AutoAlignCommand(robot.drive, () -> robot.drive.getPose().nearest(poses)).withRunningContinuously(true); + coro.fork(finalAlign); + coro.await(finalAlign.waitUntilAtPosition()); + + coro.fork(robot.superstructure.setPosition(0.5, 0.5)); + coro.wait(Seconds.of(0.25)); + coro.await(robot.claw.setVoltage(-6)); + }); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/MyAuto.java b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/MyAuto.java new file mode 100644 index 00000000..244cd9f0 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/MyAuto.java @@ -0,0 +1,30 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package first.robot.opmode; + +import org.wpilib.opmode.Autonomous; +import org.wpilib.opmode.PeriodicOpMode; +import first.robot.Robot; + +@Autonomous(name = "My Auto", group = "Group 1") +public class MyAuto extends PeriodicOpMode { + private final Robot robot; + + /** The Robot instance is passed into the opmode via the constructor. */ + public MyAuto(Robot robot) { + this.robot = robot; + } + + /* + * This method runs periodically, using the same period as the Robot instance. + * + * Additional periodic methods may be configured with addPeriodic(), + * which can have periods that differ from the main Robot instance. + */ + @Override + public void periodic() { + // Put custom auto code here + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ArmSim.java b/examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ArmSim.java new file mode 100644 index 00000000..5e81b260 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ArmSim.java @@ -0,0 +1,44 @@ +package first.robot.simulation; + +import com.ctre.phoenix6.hardware.TalonFX; +import com.ctre.phoenix6.sim.ChassisReference; +import com.ctre.phoenix6.sim.TalonFXSimState; +import org.wpilib.math.system.DCMotor; +import org.wpilib.math.util.Units; +import org.wpilib.simulation.SingleJointedArmSim; + +public class ArmSim { + + private final TalonFX motor; + + private final TalonFXSimState motorSim; + + private final SingleJointedArmSim sim; + + private final double GEAR_RATIO = (56.0 / 16.0) * (84.0 / 10.0); // Two stage, 56:16, 84:10 reductions + private final double J = SingleJointedArmSim.estimateMOI(Units.inchesToMeters(15.2), 3.6); + private final double ARM_LENGTH = Units.inchesToMeters(15.2); + private final double MIN_ANGLE = 0; + private final double MAX_ANGLE = 1.5 * Math.PI * 2; + + public ArmSim(TalonFX motor) { + this.motor = motor; + + motorSim = motor.getSimState(); + + motorSim.Orientation = ChassisReference.Clockwise_Positive; + + sim = new SingleJointedArmSim(DCMotor.getKrakenX60(1), GEAR_RATIO, J, ARM_LENGTH, MIN_ANGLE, MAX_ANGLE, true, -Math.PI/2); + } + + public void periodic() { + sim.setInputVoltage(motorSim.getMotorVoltage()); + sim.update(0.02); + + double motorPosition = sim.getAngle() / (Math.PI * 2) * GEAR_RATIO; + motorSim.setRawRotorPosition(motorPosition); + + double motorVelocity = sim.getVelocity() / (Math.PI * 2) * GEAR_RATIO; + motorSim.setRotorVelocity(motorVelocity); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ClawSim.java b/examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ClawSim.java new file mode 100644 index 00000000..09844073 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ClawSim.java @@ -0,0 +1,40 @@ +package first.robot.simulation; + +import com.ctre.phoenix6.hardware.TalonFX; +import com.ctre.phoenix6.sim.ChassisReference; +import com.ctre.phoenix6.sim.TalonFXSimState; +import org.wpilib.math.system.DCMotor; +import org.wpilib.math.system.Models; +import org.wpilib.math.util.Units; +import org.wpilib.simulation.DCMotorSim; +import org.wpilib.simulation.FlywheelSim; +import org.wpilib.simulation.SingleJointedArmSim; + +public class ClawSim { + + private final TalonFX motor; + + private final TalonFXSimState motorSim; + + private final FlywheelSim sim; + + private final double GEAR_RATIO = (18.0 / 12.0) * (54.0 / 18.0) * (22.0 / 18.0) * (22.0 / 18.0); + private final double J = 0.00001; + + public ClawSim(TalonFX motor) { + this.motor = motor; + + motorSim = motor.getSimState(); + + motorSim.Orientation = ChassisReference.Clockwise_Positive; + + sim = new FlywheelSim(Models.flywheelFromPhysicalConstants(DCMotor.getKrakenX44(1), J, GEAR_RATIO), DCMotor.getKrakenX44(1)); + } + + public void periodic() { + sim.setInputVoltage(motorSim.getMotorVoltage()); + sim.update(0.02); + + motorSim.setRotorVelocity(Units.radiansToRotations(sim.getAngularVelocity())); + } +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ElevatorSim.java b/examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ElevatorSim.java new file mode 100644 index 00000000..00e3fc05 --- /dev/null +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ElevatorSim.java @@ -0,0 +1,50 @@ +package first.robot.simulation; + +import com.ctre.phoenix6.hardware.TalonFX; +import com.ctre.phoenix6.sim.ChassisReference; +import com.ctre.phoenix6.sim.TalonFXSimState; +import org.wpilib.math.system.DCMotor; + +public class ElevatorSim { + + private TalonFX leader; + private TalonFX follower; + + private TalonFXSimState leaderSim; + private TalonFXSimState followerSim; + + private org.wpilib.simulation.ElevatorSim sim; + + private final double GEAR_RATIO = (44.0 / 14.0) * (42.0 / 22.0); // Two stage, 14:44, 22:44 reductions + private final double PULLEY_CIRCUMFERENCE = 24 * 0.005; // Convert 24t pulley to circumference, in m + private final double PULLEY_RADIUS = PULLEY_CIRCUMFERENCE / (2 * Math.PI); + private final double CARRIAGE_MASS = 11.24; // kg + private final double MAX_HEIGHT = 1.347; // m + + public ElevatorSim(TalonFX leader, TalonFX follower) { + this.leader = leader; + this.follower = follower; + + leaderSim = leader.getSimState(); + followerSim = follower.getSimState(); + + leaderSim.Orientation = ChassisReference.Clockwise_Positive; + + + sim = new org.wpilib.simulation.ElevatorSim(DCMotor.getKrakenX60(2), GEAR_RATIO, CARRIAGE_MASS, + PULLEY_RADIUS, 0, MAX_HEIGHT, true, 0); + } + + public void periodic() { + sim.setInputVoltage(leaderSim.getMotorVoltage()); + sim.update(0.02); + + double motorPosition = sim.getPosition() / PULLEY_CIRCUMFERENCE * GEAR_RATIO; + leaderSim.setRawRotorPosition(motorPosition); + followerSim.setRawRotorPosition(motorPosition); + + double motorVelocity = sim.getVelocity() / PULLEY_CIRCUMFERENCE * GEAR_RATIO; + leaderSim.setRotorVelocity(motorVelocity); + followerSim.setRotorVelocity(motorVelocity); + } +} diff --git a/examples/stage2/templates/ctre/vendordeps/CommandsV3.json b/examples/stage2/templates/ctre/vendordeps/CommandsV3.json new file mode 100644 index 00000000..f406bde7 --- /dev/null +++ b/examples/stage2/templates/ctre/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/examples/stage2/templates/ctre/vendordeps/DogLog.json b/examples/stage2/templates/ctre/vendordeps/DogLog.json new file mode 100644 index 00000000..2834d26c --- /dev/null +++ b/examples/stage2/templates/ctre/vendordeps/DogLog.json @@ -0,0 +1,18 @@ +{ + "javaDependencies": [ + { + "groupId": "com.github.jonahsnider", + "artifactId": "doglog", + "version": "2027.1.0" + } + ], + "fileName": "DogLog.json", + "wpilibYear": "2027_alpha5", + "jsonUrl": "https://doglog.dev/vendordep.json", + "name": "DogLog", + "jniDependencies": [], + "mavenUrls": ["https://jitpack.io"], + "cppDependencies": [], + "version": "2027.1.0", + "uuid": "65592ce1-2251-4a31-8e4b-2df20dacebe4" +} diff --git a/examples/stage2/templates/ctre/vendordeps/Phoenix6-26.50.0-alpha-1.json b/examples/stage2/templates/ctre/vendordeps/Phoenix6-26.50.0-alpha-1.json new file mode 100644 index 00000000..589b309c --- /dev/null +++ b/examples/stage2/templates/ctre/vendordeps/Phoenix6-26.50.0-alpha-1.json @@ -0,0 +1,447 @@ +{ + "fileName": "Phoenix6-26.50.0-alpha-1.json", + "name": "CTRE-Phoenix (v6)", + "version": "26.50.0-alpha-1", + "wpilibYear": "2027_alpha5", + "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", + "mavenUrls": ["https://maven.ctr-electronics.com/release/"], + "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2027-latest.json", + "conflictsWith": [ + { + "uuid": "e7900d8d-826f-4dca-a1ff-182f658e98af", + "errorMessage": "Users cannot have both the replay and regular Phoenix 6 vendordeps in their robot program.", + "offlineFileName": "Phoenix6-replay-frc2027-latest.json" + } + ], + "javaDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "wpiapi-java", + "version": "26.50.0-alpha-1" + } + ], + "jniDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "api-cpp", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "linuxsystemcore" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix6", + "artifactId": "tools", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "linuxsystemcore" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "api-cpp-sim", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "tools-sim", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonSRX", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simVictorSPX", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simPigeonIMU", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFX", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFXS", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANcoder", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProPigeon2", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANrange", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANdi", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANdle", + "version": "26.50.0-alpha-1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + } + ], + "cppDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "wpiapi-cpp", + "version": "26.50.0-alpha-1", + "libName": "CTRE_Phoenix6_WPI", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "linuxsystemcore" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix6", + "artifactId": "tools", + "version": "26.50.0-alpha-1", + "libName": "CTRE_PhoenixTools", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "linuxsystemcore" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "wpiapi-cpp-sim", + "version": "26.50.0-alpha-1", + "libName": "CTRE_Phoenix6_WPISim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "tools-sim", + "version": "26.50.0-alpha-1", + "libName": "CTRE_PhoenixTools_Sim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonSRX", + "version": "26.50.0-alpha-1", + "libName": "CTRE_SimTalonSRX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simVictorSPX", + "version": "26.50.0-alpha-1", + "libName": "CTRE_SimVictorSPX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simPigeonIMU", + "version": "26.50.0-alpha-1", + "libName": "CTRE_SimPigeonIMU", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFX", + "version": "26.50.0-alpha-1", + "libName": "CTRE_SimProTalonFX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFXS", + "version": "26.50.0-alpha-1", + "libName": "CTRE_SimProTalonFXS", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANcoder", + "version": "26.50.0-alpha-1", + "libName": "CTRE_SimProCANcoder", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProPigeon2", + "version": "26.50.0-alpha-1", + "libName": "CTRE_SimProPigeon2", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANrange", + "version": "26.50.0-alpha-1", + "libName": "CTRE_SimProCANrange", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANdi", + "version": "26.50.0-alpha-1", + "libName": "CTRE_SimProCANdi", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANdle", + "version": "26.50.0-alpha-1", + "libName": "CTRE_SimProCANdle", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + } + ] +} From 45cbe134ae5093a2a5609d0e6366ddab44c048d5 Mon Sep 17 00:00:00 2001 From: Andrew Haselton Date: Mon, 3 Aug 2026 20:45:46 -0500 Subject: [PATCH 2/3] Tweaks + Javadoc --- .../main/java/first/robot/mechanisms/Arm.java | 29 +++++++++++++++++-- .../java/first/robot/mechanisms/Claw.java | 4 +++ .../java/first/robot/mechanisms/Drive.java | 15 ++++++++-- .../java/first/robot/mechanisms/Elevator.java | 26 ++++++++++++++++- .../robot/opmode/BasicScoringTeleop.java | 7 +++-- .../first/robot/opmode/FullScoringTeleop.java | 6 ++-- 6 files changed, 76 insertions(+), 11 deletions(-) diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Arm.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Arm.java index 23ad994c..6e75a9a7 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Arm.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Arm.java @@ -19,6 +19,7 @@ import org.wpilib.command3.Mechanism; import org.wpilib.command3.Scheduler; import org.wpilib.math.geometry.Rotation2d; +import org.wpilib.math.util.Units; import org.wpilib.units.measure.Angle; import org.wpilib.units.measure.AngularVelocity; import org.wpilib.units.measure.Current; @@ -42,7 +43,7 @@ private static class Constants { static final double kI = 0; static final double kD = 7; - static final double POSITION_TOLERANCE = 3.0/360.0; + static final double POSITION_TOLERANCE = Units.degreesToRotations(3); } private TalonFX motor; @@ -122,38 +123,62 @@ private void periodic() { DogLog.log("Arm/Active Commands", getRunningCommands().toString()); } + /** + * @param voltage the voltage to apply to the motor + * @return a command + */ public Command setVoltage(double voltage) { return run(coro -> { motor.setControl(voltageRequest.withOutput(voltage)); }).named("Set Voltage " + voltage + "V"); } + /** + * @param position the position for the arm to target, in rotations + * @return a command + */ public Command setPosition(double position) { return run(coro -> { setpoint = position; motor.setControl(positionRequest.withPosition(position)); DogLog.log("Arm/Setpoint", position); - coro.await(Command.waitUntil(() -> isAtPosition(position)).named("Is at position " + position)); + coro.waitUntil(() -> isAtPosition(position)); }).named("Set Position " + position + "rot"); } + /** + * @return the current position of the arm, in rotations + */ public double getPosition() { return positionSignal.getValueAsDouble(); } + /** + * @return the current velocity, in rotations per second + */ public double getVelocity() { return velocitySignal.getValueAsDouble(); } + /** + * @return the current voltage being applied to the arm motor + */ public double getAppliedVoltage() { return appliedVoltageSignal.getValueAsDouble(); } + /** + * @param position the position to compare against, in rotations + * @return whether the arm is at that positoin + */ public boolean isAtPosition(double position) { return Math.abs(getPosition() - position) < Constants.POSITION_TOLERANCE; } + /** + * @return whether the arm is at its current setpoint + */ public boolean isAtSetpoint() { return isAtPosition(setpoint); } diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Claw.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Claw.java index 192cbf75..e246f05a 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Claw.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Claw.java @@ -53,6 +53,10 @@ public void periodic() { DogLog.log("Claw/Active Commands", getRunningCommands().toString()); } + /** + * @param voltage the voltage to apply to the motors + * @return a command + */ public Command setVoltage(double voltage) { return run((coro) -> { motor.setControl(voltageRequest.withOutput(voltage)); diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Drive.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Drive.java index cce65275..7160eada 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Drive.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Drive.java @@ -112,10 +112,16 @@ public void periodic() { DogLog.log("Drive/Active Commands", getRunningCommands().toString()); } + /** + * @return the current pose of the drivetrain + */ public Pose2d getPose() { return swerve.getState().Pose; } + /** + * @return the current heading of the drivetrain + */ public Rotation2d getHeading() { return getPose().getRotation(); } @@ -128,6 +134,11 @@ public ChassisVelocities getVelocity() { return swerve.getState().Velocity.toFieldRelative(getHeading()); } + /** + * Creates a teleop drive command + * @param controller the controller to use for inputs, the left stick controls translation, while the right stick controls rotation + * @return a command + */ public Command getDriveCommand(CommandNiDsXboxController controller) { return runRepeatedly(() -> { double x = -MathUtil.applyDeadband(controller.getLeftY(), 0.1) * MAX_VELOCITY; @@ -138,8 +149,8 @@ public Command getDriveCommand(CommandNiDsXboxController controller) { request.withForwardPerspective(SwerveRequest.ForwardPerspectiveValue.OperatorPerspective); request.withVelocityX(x); request.withVelocityY(y); - request.withRotationalRate(rotation) - .withDeadband(0.1*4.54); + request.withRotationalRate(rotation); + request.withDeadband(0.1*4.54); swerve.setControl(request); diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Elevator.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Elevator.java index e5f80cea..90ae8adb 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Elevator.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Elevator.java @@ -134,38 +134,62 @@ private void periodic() { DogLog.log("Elevator/Active Commands", getRunningCommands().toString()); } + /** + * @param voltage the voltage to apply to the motors + * @return a command + */ public Command setVoltage(double voltage) { return run(coro -> { leader.setControl(voltageRequest.withOutput(voltage)); }).named("Set Voltage " + voltage + "V"); } + /** + * @param position the position for the elevator to target, in meters + * @return a command + */ public Command setPosition(double position) { return run(coro -> { setpoint = position; leader.setControl(positionRequest.withPosition(position / Constants.PULLEY_CIRCUMFERENCE)); DogLog.log("Elevator/Setpoint", position); - coro.await(Command.waitUntil(() -> isAtPosition(position)).named("Is At Position " + position)); + coro.waitUntil(() -> isAtPosition(position)); }).named("Set Position " + position + "m"); } + /** + * @return the position of the elevator, in meters + */ public double getPosition() { return positionSignal.getValueAsDouble() * Constants.PULLEY_CIRCUMFERENCE; } + /** + * @return the velocity of the elevator, in meters per second + */ public double getVelocity() { return velocitySignal.getValueAsDouble() * Constants.PULLEY_CIRCUMFERENCE; } + /** + * @return the current voltage being applied to the arm motor + */ public double getAppliedVoltage() { return appliedVoltageSignal.getValueAsDouble(); } + /** + * @param position the position to compare against, in meters + * @return whether the elevator is at that position + */ public boolean isAtPosition(double position) { return Math.abs(getPosition() - position) < Constants.POSITION_TOLERANCE; } + /** + * @return whether the elevator is at its current setpoint + */ public boolean isAtSetpoint() { return isAtPosition(setpoint); } diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/BasicScoringTeleop.java b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/BasicScoringTeleop.java index aa2cb245..9761f735 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/BasicScoringTeleop.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/BasicScoringTeleop.java @@ -32,12 +32,13 @@ public BasicScoringTeleop(Robot robot) { robot.drive.setDefaultCommand(robot.drive.getDriveCommand(controller)); controller.button(0).whileTrue(Command.noRequirements((coro) -> { - coro.awaitAll(robot.superstructure.setPosition(1, 1), - new AutoAlignCommand(robot.drive, Poses.BLUE_REEF_A.transformBy(Poses.REEF_PREALIGN_TRANSFORM)).withPositionTolerance(Units.inchesToMeters(6))); + coro.fork(new AutoAlignCommand(robot.drive, Poses.BLUE_REEF_A.transformBy(Poses.REEF_PREALIGN_TRANSFORM)).withRunningContinuously(true)); + coro.await(robot.superstructure.setPosition(1, 1)); + AutoAlignCommand finalAlign = new AutoAlignCommand(robot.drive, Poses.BLUE_REEF_A).withRunningContinuously(true); coro.fork(finalAlign); - coro.await(finalAlign.waitUntilAtPosition()); + coro.waitUntil(finalAlign::atPosition); coro.fork(robot.superstructure.setPosition(0.5, 0.5)); coro.wait(Seconds.of(0.25)); diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/FullScoringTeleop.java b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/FullScoringTeleop.java index 23793e96..a281c02a 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/FullScoringTeleop.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/FullScoringTeleop.java @@ -34,12 +34,12 @@ public FullScoringTeleop(Robot robot) { private NeedsNameBuilderStage createScoreCommand(List poses) { return Command.noRequirements((coro) -> { - coro.awaitAll(robot.superstructure.setPosition(1, 1), - new Drive.AutoAlignCommand(robot.drive, () -> robot.drive.getPose().nearest(poses).transformBy(Poses.REEF_PREALIGN_TRANSFORM)).withPositionTolerance(Units.inchesToMeters(6))); + coro.fork(new Drive.AutoAlignCommand(robot.drive, () -> robot.drive.getPose().nearest(poses).transformBy(Poses.REEF_PREALIGN_TRANSFORM)).withRunningContinuously(true)); + coro.await(robot.superstructure.setPosition(1, 1)); Drive.AutoAlignCommand finalAlign = new Drive.AutoAlignCommand(robot.drive, () -> robot.drive.getPose().nearest(poses)).withRunningContinuously(true); coro.fork(finalAlign); - coro.await(finalAlign.waitUntilAtPosition()); + coro.waitUntil(finalAlign::atPosition); coro.fork(robot.superstructure.setPosition(0.5, 0.5)); coro.wait(Seconds.of(0.25)); From 2072d69dda7332e4d2405d2205cfaa7c73499020 Mon Sep 17 00:00:00 2001 From: Andrew Haselton Date: Thu, 3 Sep 2026 23:45:22 -0500 Subject: [PATCH 3/3] Update to alpha 7 --- .../ctre/.wpilib/wpilib_preferences.json | 4 +- .../stage2/templates/ctre/WPILib-License.md | 20 ++- examples/stage2/templates/ctre/build.gradle | 30 ++--- .../stage2/templates/ctre/settings.gradle | 14 ++- examples/stage2/templates/ctre/simgui-ds.json | 119 +++++++++--------- .../ctre/src/main/java/first/Main.java | 3 +- .../main/java/first/robot/FieldConstants.java | 9 +- .../ctre/src/main/java/first/robot/Poses.java | 4 +- .../ctre/src/main/java/first/robot/Robot.java | 4 - .../main/java/first/robot/mechanisms/Arm.java | 28 ++--- .../java/first/robot/mechanisms/Claw.java | 12 +- .../java/first/robot/mechanisms/Drive.java | 37 +++--- .../java/first/robot/mechanisms/Elevator.java | 16 +-- .../robot/mechanisms/Superstructure.java | 2 +- .../robot/opmode/BasicScoringTeleop.java | 7 +- .../java/first/robot/opmode/DriveTest.java | 5 +- .../first/robot/opmode/FullScoringTeleop.java | 9 +- .../java/first/robot/simulation/ClawSim.java | 2 +- .../templates/ctre/vendordeps/CommandsV3.json | 46 +++---- .../templates/ctre/vendordeps/DogLog.json | 18 --- .../vendordeps/Phoenix6-26.50.0-alpha-1.json | 8 +- 21 files changed, 190 insertions(+), 207 deletions(-) delete mode 100644 examples/stage2/templates/ctre/vendordeps/DogLog.json diff --git a/examples/stage2/templates/ctre/.wpilib/wpilib_preferences.json b/examples/stage2/templates/ctre/.wpilib/wpilib_preferences.json index 19f541ed..14c708c9 100644 --- a/examples/stage2/templates/ctre/.wpilib/wpilib_preferences.json +++ b/examples/stage2/templates/ctre/.wpilib/wpilib_preferences.json @@ -1,6 +1,6 @@ { "enableCppIntellisense": false, "currentLanguage": "java", - "projectYear": "2027_alpha5", + "projectYear": "2027_alpha7", "teamNumber": 9999 -} +} \ No newline at end of file diff --git a/examples/stage2/templates/ctre/WPILib-License.md b/examples/stage2/templates/ctre/WPILib-License.md index c2891d4d..eb3061b0 100644 --- a/examples/stage2/templates/ctre/WPILib-License.md +++ b/examples/stage2/templates/ctre/WPILib-License.md @@ -3,21 +3,19 @@ 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. + * 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 +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 diff --git a/examples/stage2/templates/ctre/build.gradle b/examples/stage2/templates/ctre/build.gradle index 55508d09..dcaa3c0d 100644 --- a/examples/stage2/templates/ctre/build.gradle +++ b/examples/stage2/templates/ctre/build.gradle @@ -1,7 +1,7 @@ plugins { id "java" - id "org.wpilib.GradleRIO" version "2027.0.0-alpha-6" - id "com.gradleup.shadow" version "9.3.0" + id "application" + id "org.wpilib.GradleRIO" version "2027.0.0-alpha-7" } java { @@ -31,6 +31,8 @@ deploy { // getTargetTypeClass is a shortcut to get the class type using a string wpilibJava(getArtifactTypeClass('WPILibJavaArtifact')) { + // Set to true to use debug including JNI, which will drastically impact performance. + debugJni = false } // Static files artifact @@ -47,9 +49,8 @@ deploy { 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 to true to use debug for simulation including JNI +wpi.java.runSimWithDebugJni = false // Set this to true to enable desktop support. def includeDesktopSupport = true @@ -88,23 +89,18 @@ test { 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() +application.mainClass = ROBOT_MAIN_CLASS + +deployArtifact.configureApplication(application) +wpi.java.configureApplication(application) +wpi.java.configureTestTasks(test) + +jar { 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' diff --git a/examples/stage2/templates/ctre/settings.gradle b/examples/stage2/templates/ctre/settings.gradle index e2626a81..a17bda05 100644 --- a/examples/stage2/templates/ctre/settings.gradle +++ b/examples/stage2/templates/ctre/settings.gradle @@ -2,18 +2,26 @@ import org.gradle.internal.os.OperatingSystem pluginManagement { repositories { - String wpilibYear = '2027_alpha5' + String wpilibYear = '2027_alpha7' File wpilibHome - if (OperatingSystem.current().isWindows()) { + def os = OperatingSystem.current() + if (os.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 if (os.isLinux()) { + String xdgDataHome = System.getenv('XDG_DATA_HOME') + if (xdgDataHome == null || xdgDataHome.trim().isEmpty() || !new File(xdgDataHome).isAbsolute()) { + xdgDataHome = new File(System.getProperty("user.home"), ".local/share").getPath() + } + def homeRoot = new File(xdgDataHome, "wpilib") + wpilibHome = new File(homeRoot, wpilibYear) } else { def userFolder = System.getProperty("user.home") - def homeRoot = new File(userFolder, "wpilib") + def homeRoot = new File(userFolder, ".wpilib") wpilibHome = new File(homeRoot, wpilibYear) } def wpilibHomeMaven = new File(wpilibHome, 'maven') diff --git a/examples/stage2/templates/ctre/simgui-ds.json b/examples/stage2/templates/ctre/simgui-ds.json index 9da8aa99..00a0a6d9 100644 --- a/examples/stage2/templates/ctre/simgui-ds.json +++ b/examples/stage2/templates/ctre/simgui-ds.json @@ -1,62 +1,59 @@ { - "keyboardJoysticks": [{ - "axisConfig": [{ - "decKey": 546, - "incKey": 549 - }, { - "decKey": 568, - "incKey": 564 - }, { - "decKey": 550, - "decayRate": 0, - "incKey": 563, - "keyRate": 0.01 - }], - "axisCount": 3, - "buttonCount": 4, - "buttonKeys": [571, 569, 548, 567], - "povConfig": [{ - "keyDown": 614, - "keyDownLeft": 613, - "keyDownRight": 615, - "keyLeft": 616, - "keyRight": 618, - "keyUp": 620, - "keyUpLeft": 619, - "keyUpRight": 621 - }], - "povCount": 1 - }, { - "axisConfig": [{ - "decKey": 555, - "incKey": 557 - }, { - "decKey": 554, - "incKey": 556 - }], - "axisCount": 2, - "buttonCount": 4, - "buttonKeys": [558, 597, 599, 600], - "povCount": 0 - }, { - "axisConfig": [{ - "decKey": 513, - "incKey": 514 - }, { - "decKey": 515, - "incKey": 516 - }], - "axisCount": 2, - "buttonCount": 6, - "buttonKeys": [521, 519, 517, 522, 520, 518], - "povCount": 0 - }, { - "axisCount": 0, - "buttonCount": 0, - "povCount": 0 - }], - "robotJoysticks": [{ - "guid": "78696e70757401000000000000000000", - "useGamepad": true - }] - } + "keyboardJoysticks": [{ + "axisConfig": [{ + "decKey": 546, + "incKey": 549 + }, { + "decKey": 568, + "incKey": 564 + }, { + "decKey": 550, + "decayRate": 0, + "incKey": 563, + "keyRate": 0.01 + }], + "axisCount": 3, + "buttonCount": 4, + "buttonKeys": [571, 569, 548, 567], + "povConfig": [{ + "keyDown": 614, + "keyDownLeft": 613, + "keyDownRight": 615, + "keyLeft": 616, + "keyRight": 618, + "keyUp": 620, + "keyUpLeft": 619, + "keyUpRight": 621 + }], + "povCount": 1 + }, { + "axisConfig": [{ + "decKey": 555, + "incKey": 557 + }, { + "decKey": 554, + "incKey": 556 + }], + "axisCount": 2, + "buttonCount": 4, + "buttonKeys": [558, 597, 599, 600], + "povCount": 0 + }, { + "axisConfig": [{ + "decKey": 513, + "incKey": 514 + }, { + "decKey": 515, + "incKey": 516 + }], + "axisCount": 2, + "buttonCount": 6, + "buttonKeys": [521, 519, 517, 522, 520, 518], + "povCount": 0 + }, { + "axisCount": 0, + "buttonCount": 0, + "povCount": 0 + }], + "robotJoysticks": [{"guid": "Keyboard0"}] +} diff --git a/examples/stage2/templates/ctre/src/main/java/first/Main.java b/examples/stage2/templates/ctre/src/main/java/first/Main.java index 7d287e7a..4b6da4de 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/Main.java +++ b/examples/stage2/templates/ctre/src/main/java/first/Main.java @@ -4,6 +4,7 @@ package first; +import first.robot.Robot; import org.wpilib.framework.RobotBase; /** @@ -20,6 +21,6 @@ private Main() {} *

If you change your main robot class, change the parameter type. */ public static void main(String... args) { - RobotBase.startRobot(first.robot.Robot.class); + RobotBase.startRobot(Robot::new); } } diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/FieldConstants.java b/examples/stage2/templates/ctre/src/main/java/first/robot/FieldConstants.java index 40e1d187..c29017d3 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/FieldConstants.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/FieldConstants.java @@ -1,15 +1,14 @@ package first.robot; +import org.wpilib.fields.Fields; import org.wpilib.math.geometry.Pose2d; import org.wpilib.math.geometry.Rotation2d; import org.wpilib.math.geometry.Translation2d; -import org.wpilib.vision.apriltag.AprilTagFieldLayout; -import org.wpilib.vision.apriltag.AprilTagFields; public class FieldConstants { - public static final double fieldWidth = AprilTagFieldLayout.loadField(AprilTagFields.k2025ReefscapeWelded).getFieldWidth(); - public static final double fieldLength = AprilTagFieldLayout.loadField(AprilTagFields.k2025ReefscapeWelded).getFieldLength(); + public static final double fieldWidth = Fields.FRC_2025_REEFSCAPE_WELDED.width; + public static final double fieldLength = Fields.FRC_2025_REEFSCAPE_WELDED.length; public static final Translation2d BLUE_REEF_CENTER = new Translation2d(4.263, 0); public static final Translation2d RED_REEF_CENTER = mirrorX(BLUE_REEF_CENTER); @@ -25,7 +24,7 @@ public static Translation2d mirrorX(Translation2d translation) { } public static Pose2d mirrorX(Pose2d pose) { - return new Pose2d(mirrorX(pose.getTranslation()), Rotation2d.kCCW_Pi_2.minus(pose.getRotation())); + return new Pose2d(mirrorX(pose.getTranslation()), Rotation2d.CCW_PI_2.minus(pose.getRotation())); } public static Translation2d mirrorY(Translation2d translation) { diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/Poses.java b/examples/stage2/templates/ctre/src/main/java/first/robot/Poses.java index 4bc98f9d..67ab1acb 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/Poses.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/Poses.java @@ -9,7 +9,7 @@ public class Poses { - public static final Pose2d BLUE_REEF_A = new Pose2d(FieldConstants.BLUE_REEF_CENTER.plus(new Translation2d(1.4, -0.164309)), Rotation2d.kZero); + public static final Pose2d BLUE_REEF_A = new Pose2d(FieldConstants.BLUE_REEF_CENTER.plus(new Translation2d(1.4, -0.164309)), Rotation2d.ZERO); public static final Pose2d BLUE_REEF_B = FieldConstants.mirrorY(BLUE_REEF_A); public static final Pose2d BLUE_REEF_C = BLUE_REEF_A.rotateAround(FieldConstants.BLUE_REEF_CENTER, Rotation2d.fromDegrees(60)); public static final Pose2d BLUE_REEF_D = BLUE_REEF_B.rotateAround(FieldConstants.BLUE_REEF_CENTER, Rotation2d.fromDegrees(60)); @@ -43,5 +43,5 @@ public class Poses { public static final List RED_REEF_LEFT_POSES = List.of(RED_REEF_A, RED_REEF_C, RED_REEF_E, RED_REEF_G, RED_REEF_I, RED_REEF_K); public static final List RED_REEF_RIGHT_POSES = List.of(RED_REEF_B, RED_REEF_D, RED_REEF_F, RED_REEF_H, RED_REEF_J, RED_REEF_L); - public static final Transform2d REEF_PREALIGN_TRANSFORM = new Transform2d(0.3, 0, Rotation2d.kZero); + public static final Transform2d REEF_PREALIGN_TRANSFORM = new Transform2d(0.3, 0, Rotation2d.ZERO); } diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/Robot.java b/examples/stage2/templates/ctre/src/main/java/first/robot/Robot.java index 6552e961..013415af 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/Robot.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/Robot.java @@ -4,8 +4,6 @@ package first.robot; -import dev.doglog.DogLog; -import dev.doglog.DogLogOptions; import first.robot.mechanisms.*; import org.wpilib.command3.Scheduler; import org.wpilib.framework.OpModeRobot; @@ -19,8 +17,6 @@ public class Robot extends OpModeRobot { public final Claw claw; public Robot() { - DogLog.setOptions(new DogLogOptions().withCaptureDs(true)); - drive = new Drive(); elevator = new Elevator(); arm = new Arm(); diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Arm.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Arm.java index 6e75a9a7..36037f51 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Arm.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Arm.java @@ -13,19 +13,19 @@ import com.ctre.phoenix6.signals.InvertedValue; import com.ctre.phoenix6.signals.MotorAlignmentValue; import com.ctre.phoenix6.signals.NeutralModeValue; -import dev.doglog.DogLog; import first.robot.simulation.ArmSim; import org.wpilib.command3.Command; import org.wpilib.command3.Mechanism; import org.wpilib.command3.Scheduler; import org.wpilib.math.geometry.Rotation2d; import org.wpilib.math.util.Units; +import org.wpilib.telemetry.Telemetry; import org.wpilib.units.measure.Angle; import org.wpilib.units.measure.AngularVelocity; import org.wpilib.units.measure.Current; import org.wpilib.units.measure.Voltage; -public class Arm extends Mechanism { +public class Arm implements Mechanism { private static class Constants { static final int MOTOR_ID = 22; @@ -66,11 +66,11 @@ public Arm() { // Construct and configure motors motor = new TalonFX(Constants.MOTOR_ID, Constants.BUS); - TalonFXConfiguration leaderConfiguration = new TalonFXConfiguration(); - leaderConfiguration.MotorOutput.withNeutralMode(NeutralModeValue.Brake) + TalonFXConfiguration configuration = new TalonFXConfiguration(); + configuration.MotorOutput.withNeutralMode(NeutralModeValue.Brake) .withInverted(Constants.MOTOR_INVERSION); - leaderConfiguration.Feedback.withSensorToMechanismRatio(Constants.GEAR_RATIO); - leaderConfiguration.Slot0.withKS(Constants.kS) + configuration.Feedback.withSensorToMechanismRatio(Constants.GEAR_RATIO); + configuration.Slot0.withKS(Constants.kS) .withKG(Constants.kG) .withKV(Constants.kV) .withKA(Constants.kA) @@ -85,9 +85,9 @@ public Arm() { .withSupplyCurrentLimit(60) .withSupplyCurrentLimitEnable(true); - leaderConfiguration.withCurrentLimits(currentLimitsConfigs); + configuration.withCurrentLimits(currentLimitsConfigs); - motor.getConfigurator().apply(leaderConfiguration); + motor.getConfigurator().apply(configuration); // Set up periodic method to run every code loop Scheduler.getDefault().addPeriodic(this::periodic); @@ -116,11 +116,11 @@ private void periodic() { BaseStatusSignal.refreshAll(signals); - DogLog.log("Arm/Applied Voltage", getAppliedVoltage()); - DogLog.log("Arm/Position", getPosition()); - DogLog.log("Arm/Velocity", getVelocity()); - DogLog.log("Arm/At Setpoint", isAtSetpoint()); - DogLog.log("Arm/Active Commands", getRunningCommands().toString()); + Telemetry.log("Arm/Applied Voltage", getAppliedVoltage()); + Telemetry.log("Arm/Position", getPosition()); + Telemetry.log("Arm/Velocity", getVelocity()); + Telemetry.log("Arm/At Setpoint", isAtSetpoint()); + Telemetry.log("Arm/Active Commands", getRunningCommands().toString()); } /** @@ -141,7 +141,7 @@ public Command setPosition(double position) { return run(coro -> { setpoint = position; motor.setControl(positionRequest.withPosition(position)); - DogLog.log("Arm/Setpoint", position); + Telemetry.log("Arm/Setpoint", position); coro.waitUntil(() -> isAtPosition(position)); }).named("Set Position " + position + "rot"); diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Claw.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Claw.java index e246f05a..55e7927a 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Claw.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Claw.java @@ -5,15 +5,15 @@ import com.ctre.phoenix6.StatusSignal; import com.ctre.phoenix6.controls.VoltageOut; import com.ctre.phoenix6.hardware.TalonFX; -import dev.doglog.DogLog; import first.robot.simulation.ClawSim; import org.wpilib.command3.Command; import org.wpilib.command3.Mechanism; import org.wpilib.command3.Scheduler; +import org.wpilib.telemetry.Telemetry; import org.wpilib.units.measure.Current; import org.wpilib.units.measure.Voltage; -public class Claw extends Mechanism { +public class Claw implements Mechanism { private final TalonFX motor; private final ClawSim sim; @@ -47,10 +47,10 @@ public void periodic() { BaseStatusSignal.refreshAll(signals); - DogLog.log("Claw/Applied Voltage", appliedVoltageSignal.getValueAsDouble()); - DogLog.log("Claw/Stator Current", statorCurrentSignal.getValueAsDouble()); - DogLog.log("Claw/Supply Current", supplyCurrentSignal.getValueAsDouble()); - DogLog.log("Claw/Active Commands", getRunningCommands().toString()); + Telemetry.log("Claw/Applied Voltage", appliedVoltageSignal.getValueAsDouble()); + Telemetry.log("Claw/Stator Current", statorCurrentSignal.getValueAsDouble()); + Telemetry.log("Claw/Supply Current", supplyCurrentSignal.getValueAsDouble()); + Telemetry.log("Claw/Active Commands", getRunningCommands().toString()); } /** diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Drive.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Drive.java index 7160eada..41588ff2 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Drive.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Drive.java @@ -8,9 +8,9 @@ import com.ctre.phoenix6.hardware.TalonFX; import com.ctre.phoenix6.signals.StaticFeedforwardSignValue; import com.ctre.phoenix6.swerve.*; -import dev.doglog.DogLog; import org.wpilib.command3.*; import org.wpilib.command3.button.CommandNiDsXboxController; +import org.wpilib.command3.button.CommandXboxController; import org.wpilib.math.controller.PIDController; import org.wpilib.math.controller.ProfiledPIDController; import org.wpilib.math.geometry.Pose2d; @@ -21,13 +21,14 @@ import org.wpilib.math.trajectory.TrapezoidProfile; import org.wpilib.math.util.MathUtil; import org.wpilib.math.util.Units; +import org.wpilib.telemetry.Telemetry; import java.util.Set; import java.util.function.Supplier; import static org.wpilib.units.Units.*; -public class Drive extends Mechanism { +public class Drive implements Mechanism { private final SwerveDrivetrain swerve; @@ -97,19 +98,19 @@ public void periodic() { swerve.updateSimState(1.0/50.0/5.0, 12); swerve.updateSimState(1.0/50.0/5.0, 12); - DogLog.log("Drive/Pose", getPose()); + Telemetry.log("Drive/Pose", getPose()); var modules = swerve.getModules(); SwerveModulePosition[] modulePositions = new SwerveModulePosition[4]; for (int i = 0; i < modules.length; i++) { modulePositions[i] = modules[i].getPosition(true); - DogLog.log("Drive/ModuleVel/"+i, modules[i].getDriveMotor().getVelocity().getValueAsDouble()); + Telemetry.log("Drive/ModuleVel/"+i, modules[i].getDriveMotor().getVelocity().getValueAsDouble()); } - DogLog.log("Drive/ModulesReal", swerve.getState().ModuleVelocities); - DogLog.log("Drive/ModulesTargets", swerve.getState().ModuleTargets); - DogLog.log("Drive/Velocity", swerve.getState().Velocity); - DogLog.log("Drive/Active Commands", getRunningCommands().toString()); + Telemetry.log("Drive/ModulesReal", swerve.getState().ModuleVelocities); + Telemetry.log("Drive/ModulesTargets", swerve.getState().ModuleTargets); + Telemetry.log("Drive/Velocity", swerve.getState().Velocity); + Telemetry.log("Drive/Active Commands", getRunningCommands().toString()); } /** @@ -139,7 +140,7 @@ public ChassisVelocities getVelocity() { * @param controller the controller to use for inputs, the left stick controls translation, while the right stick controls rotation * @return a command */ - public Command getDriveCommand(CommandNiDsXboxController controller) { + public Command getDriveCommand(CommandXboxController controller) { return runRepeatedly(() -> { double x = -MathUtil.applyDeadband(controller.getLeftY(), 0.1) * MAX_VELOCITY; double y = -MathUtil.applyDeadband(controller.getLeftX(), 0.1) * MAX_VELOCITY; @@ -154,7 +155,7 @@ public Command getDriveCommand(CommandNiDsXboxController controller) { swerve.setControl(request); - DogLog.log("Drive/DesiredVelocity", new ChassisVelocities(x, y, rotation).toRobotRelative(getHeading())); + Telemetry.log("Drive/DesiredVelocity", new ChassisVelocities(x, y, rotation).toRobotRelative(getHeading())); }).named("Teleop Drive"); } @@ -214,16 +215,16 @@ public void run(Coroutine coroutine) { targetPose = poseSupplier.get(); } - DogLog.log("Drive/AutoAlign/At Target", atPosition()); - DogLog.log("Drive/AutoAlign/Position in Tolerance", targetPose.getTranslation().getDistance(drive.getPose().getTranslation()) <= positionTolerance); - DogLog.log("Drive/AutoAlign/Angle in Tolerance", (ignoreRotation || Math.abs(targetPose.getRotation().minus(drive.getPose().getRotation()).getRadians()) <= angleTolerance)); + Telemetry.log("Drive/AutoAlign/At Target", atPosition()); + Telemetry.log("Drive/AutoAlign/Position in Tolerance", targetPose.getTranslation().getDistance(drive.getPose().getTranslation()) <= positionTolerance); + Telemetry.log("Drive/AutoAlign/Angle in Tolerance", (ignoreRotation || Math.abs(targetPose.getRotation().minus(drive.getPose().getRotation()).getRadians()) <= angleTolerance)); - DogLog.log("Drive/AutoAlign/Target Pose", targetPose); + Telemetry.log("Drive/AutoAlign/Target Pose", targetPose); Translation2d vectorToTarget = targetPose.getTranslation().minus(currentPose.getTranslation()); double distanceToEnd = vectorToTarget.getNorm(); - DogLog.log("Drive/AutoAlign/Distance", distanceToEnd); + Telemetry.log("Drive/AutoAlign/Distance", distanceToEnd); // ChassisVelocities currentVelocity = drive.getVelocity(); // Translation2d currentVelocityAsTranslation = new Translation2d(currentVelocity.vx, currentVelocity.vy); @@ -234,11 +235,11 @@ public void run(Coroutine coroutine) { double velocityForDistance = Math.pow(targetVelocity,2) - 2*-accelerationLimit*distanceToEnd; - DogLog.log("Drive/AutoAlign/Velocity for Distance", velocityForDistance); + Telemetry.log("Drive/AutoAlign/Velocity for Distance", velocityForDistance); double velocityToTarget = Math.min(velocityForDistance * 0.85, velocityLimit); - DogLog.log("Drive/AutoAlign/Targeted Velocity", velocityToTarget); + Telemetry.log("Drive/AutoAlign/Targeted Velocity", velocityToTarget); Translation2d velocities = vectorToTarget.div(distanceToEnd).times(velocityToTarget); @@ -252,7 +253,7 @@ public void run(Coroutine coroutine) { ChassisVelocities chassisVelocities = new ChassisVelocities(velocities.getX(), velocities.getY(), omega); - DogLog.log("Drive/DesiredVelocity", chassisVelocities); + Telemetry.log("Drive/DesiredVelocity", chassisVelocities); swerveRequest.withVelocity(chassisVelocities); drive.swerve.setControl(swerveRequest); diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Elevator.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Elevator.java index 90ae8adb..5e626b1c 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Elevator.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Elevator.java @@ -13,15 +13,15 @@ import com.ctre.phoenix6.signals.InvertedValue; import com.ctre.phoenix6.signals.MotorAlignmentValue; import com.ctre.phoenix6.signals.NeutralModeValue; -import dev.doglog.DogLog; import first.robot.simulation.ElevatorSim; import org.wpilib.command3.Command; import org.wpilib.command3.Mechanism; import org.wpilib.command3.Scheduler; import org.wpilib.math.util.Units; +import org.wpilib.telemetry.Telemetry; import org.wpilib.units.measure.*; -public class Elevator extends Mechanism { +public class Elevator implements Mechanism { private static class Constants { static final int LEADER_ID = 20; @@ -127,11 +127,11 @@ private void periodic() { BaseStatusSignal.refreshAll(signals); - DogLog.log("Elevator/Applied Voltage", getAppliedVoltage()); - DogLog.log("Elevator/Position", getPosition()); - DogLog.log("Elevator/Velocity", getVelocity()); - DogLog.log("Elevator/At Setpoint", isAtSetpoint()); - DogLog.log("Elevator/Active Commands", getRunningCommands().toString()); + Telemetry.log("Elevator/Applied Voltage", getAppliedVoltage()); + Telemetry.log("Elevator/Position", getPosition()); + Telemetry.log("Elevator/Velocity", getVelocity()); + Telemetry.log("Elevator/At Setpoint", isAtSetpoint()); + Telemetry.log("Elevator/Active Commands", getRunningCommands().toString()); } /** @@ -152,7 +152,7 @@ public Command setPosition(double position) { return run(coro -> { setpoint = position; leader.setControl(positionRequest.withPosition(position / Constants.PULLEY_CIRCUMFERENCE)); - DogLog.log("Elevator/Setpoint", position); + Telemetry.log("Elevator/Setpoint", position); coro.waitUntil(() -> isAtPosition(position)); }).named("Set Position " + position + "m"); diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Superstructure.java b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Superstructure.java index 8e123b55..bd313cef 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Superstructure.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/mechanisms/Superstructure.java @@ -3,7 +3,7 @@ import org.wpilib.command3.Command; import org.wpilib.command3.Mechanism; -public class Superstructure extends Mechanism { +public class Superstructure implements Mechanism { private final Elevator elevator; private final Arm arm; diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/BasicScoringTeleop.java b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/BasicScoringTeleop.java index 9761f735..fd44d8e5 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/BasicScoringTeleop.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/BasicScoringTeleop.java @@ -11,6 +11,7 @@ import org.wpilib.command3.Command; import org.wpilib.command3.button.CommandGamepad; import org.wpilib.command3.button.CommandNiDsXboxController; +import org.wpilib.command3.button.CommandXboxController; import org.wpilib.math.util.Units; import org.wpilib.opmode.PeriodicOpMode; import org.wpilib.opmode.Teleop; @@ -22,16 +23,16 @@ public class BasicScoringTeleop extends PeriodicOpMode { private final Robot robot; - private final CommandNiDsXboxController controller; + private final CommandXboxController controller; public BasicScoringTeleop(Robot robot) { this.robot = robot; - controller = new CommandNiDsXboxController(0); + controller = new CommandXboxController(0); robot.drive.setDefaultCommand(robot.drive.getDriveCommand(controller)); - controller.button(0).whileTrue(Command.noRequirements((coro) -> { + controller.a().whileTrue(Command.noRequirements((coro) -> { coro.fork(new AutoAlignCommand(robot.drive, Poses.BLUE_REEF_A.transformBy(Poses.REEF_PREALIGN_TRANSFORM)).withRunningContinuously(true)); coro.await(robot.superstructure.setPosition(1, 1)); diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/DriveTest.java b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/DriveTest.java index 47d8cc80..0b18f3fd 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/DriveTest.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/DriveTest.java @@ -7,6 +7,7 @@ import first.robot.Robot; import org.wpilib.command3.button.CommandGamepad; import org.wpilib.command3.button.CommandNiDsXboxController; +import org.wpilib.command3.button.CommandXboxController; import org.wpilib.opmode.PeriodicOpMode; import org.wpilib.opmode.Teleop; import org.wpilib.opmode.Utility; @@ -15,12 +16,12 @@ public class DriveTest extends PeriodicOpMode { private final Robot robot; - private final CommandNiDsXboxController controller; + private final CommandXboxController controller; public DriveTest(Robot robot) { this.robot = robot; - controller = new CommandNiDsXboxController(0); + controller = new CommandXboxController(0); robot.drive.setDefaultCommand(robot.drive.getDriveCommand(controller)); } diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/FullScoringTeleop.java b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/FullScoringTeleop.java index a281c02a..07d08649 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/FullScoringTeleop.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/opmode/FullScoringTeleop.java @@ -6,6 +6,7 @@ import org.wpilib.command3.Command; import org.wpilib.command3.NeedsNameBuilderStage; import org.wpilib.command3.button.CommandNiDsXboxController; +import org.wpilib.command3.button.CommandXboxController; import org.wpilib.math.geometry.Pose2d; import org.wpilib.math.util.Units; import org.wpilib.opmode.PeriodicOpMode; @@ -19,17 +20,17 @@ public class FullScoringTeleop extends PeriodicOpMode { private final Robot robot; - private final CommandNiDsXboxController controller; + private final CommandXboxController controller; public FullScoringTeleop(Robot robot) { this.robot = robot; - controller = new CommandNiDsXboxController(0); + controller = new CommandXboxController(0); robot.drive.setDefaultCommand(robot.drive.getDriveCommand(controller)); - controller.button(4).whileTrue(createScoreCommand(Poses.BLUE_REEF_LEFT_POSES).named("Autoalign and score (left)")); - controller.button(5).whileTrue(createScoreCommand(Poses.BLUE_REEF_RIGHT_POSES).named("Autoalign and score (right)")); + controller.leftBumper().whileTrue(createScoreCommand(Poses.BLUE_REEF_LEFT_POSES).named("Autoalign and score (left)")); + controller.rightBumper().whileTrue(createScoreCommand(Poses.BLUE_REEF_RIGHT_POSES).named("Autoalign and score (right)")); } private NeedsNameBuilderStage createScoreCommand(List poses) { diff --git a/examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ClawSim.java b/examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ClawSim.java index 09844073..0ed8d1c3 100644 --- a/examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ClawSim.java +++ b/examples/stage2/templates/ctre/src/main/java/first/robot/simulation/ClawSim.java @@ -35,6 +35,6 @@ public void periodic() { sim.setInputVoltage(motorSim.getMotorVoltage()); sim.update(0.02); - motorSim.setRotorVelocity(Units.radiansToRotations(sim.getAngularVelocity())); + motorSim.setRotorVelocity(Units.radiansToRotations(sim.getAngularVelocity()) * GEAR_RATIO); } } diff --git a/examples/stage2/templates/ctre/vendordeps/CommandsV3.json b/examples/stage2/templates/ctre/vendordeps/CommandsV3.json index f406bde7..7ce12fd7 100644 --- a/examples/stage2/templates/ctre/vendordeps/CommandsV3.json +++ b/examples/stage2/templates/ctre/vendordeps/CommandsV3.json @@ -1,25 +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": [] + "fileName": "CommandsV3.json", + "name": "Commands v3", + "version": "1.0.0", + "uuid": "4decdc05-a056-46cf-9561-39449bbb0130", + "wpilibYear": "2027_alpha7", + "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": "commandsv3-java", + "version": "wpilib" + } + ], + "jniDependencies": [], + "cppDependencies": [] } diff --git a/examples/stage2/templates/ctre/vendordeps/DogLog.json b/examples/stage2/templates/ctre/vendordeps/DogLog.json deleted file mode 100644 index 2834d26c..00000000 --- a/examples/stage2/templates/ctre/vendordeps/DogLog.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "javaDependencies": [ - { - "groupId": "com.github.jonahsnider", - "artifactId": "doglog", - "version": "2027.1.0" - } - ], - "fileName": "DogLog.json", - "wpilibYear": "2027_alpha5", - "jsonUrl": "https://doglog.dev/vendordep.json", - "name": "DogLog", - "jniDependencies": [], - "mavenUrls": ["https://jitpack.io"], - "cppDependencies": [], - "version": "2027.1.0", - "uuid": "65592ce1-2251-4a31-8e4b-2df20dacebe4" -} diff --git a/examples/stage2/templates/ctre/vendordeps/Phoenix6-26.50.0-alpha-1.json b/examples/stage2/templates/ctre/vendordeps/Phoenix6-26.50.0-alpha-1.json index 589b309c..5b7f1dc9 100644 --- a/examples/stage2/templates/ctre/vendordeps/Phoenix6-26.50.0-alpha-1.json +++ b/examples/stage2/templates/ctre/vendordeps/Phoenix6-26.50.0-alpha-1.json @@ -2,9 +2,11 @@ "fileName": "Phoenix6-26.50.0-alpha-1.json", "name": "CTRE-Phoenix (v6)", "version": "26.50.0-alpha-1", - "wpilibYear": "2027_alpha5", + "wpilibYear": "2027_alpha7", "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", - "mavenUrls": ["https://maven.ctr-electronics.com/release/"], + "mavenUrls": [ + "https://maven.ctr-electronics.com/release/" + ], "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2027-latest.json", "conflictsWith": [ { @@ -444,4 +446,4 @@ "simMode": "swsim" } ] -} +} \ No newline at end of file